Plugins can use inline configuration or separate packages.
To create a package, see Create a plugin package.
Packages support named commands, validated inputs, JSON reports, and managed port-forwards.
Enter :plugin-cancel to stop the active plugin run.
Popup and report runs immediately show a floating activity panel, even for silent
adapters. It displays a spinner, elapsed time, and live stderr diagnostics; stdout
remains the final text or JSON report, not a progress protocol. Esc hides the
panel without cancelling. Ctrl+Alt+T toggles the panel without restarting or
cancelling the job, including while typing in the palette. It also restores
retained diagnostics after completion; Enter then opens the report.
:plugin-activity reopens the current run, or its completed report.
Rebind or disable the toggle with [keys.global].plugin_activity. Ctrl+C cancels the run and its process group while the panel
is focused; outside the panel it still quits sofka. Use arrows or hjkl,
PgUp/PgDn, and Home/End to scroll; G resumes following. : hides the
panel and opens the palette.
Completion opens the normal report if the panel is visible. When hidden, it only notifies; your current focus and typed command stay intact. The latest run is retained in memory until navigation cancels/clears it or another run replaces it. Failures (including timeout, capture limits, and invalid reports) include the sanitized diagnostic tail. A cancelled panel keeps its diagnostics until cleared. Background and interactive terminal modes do not open activity panels.
The compact activity panel is capped at 100 columns and 18 rows, shrinking for small terminals. The tail keeps at most 64 KiB and 256 lines across the run, clipping each long line at 2048 bytes rather than wrapping progress bars. Older lines are discarded even while scrolling is paused. Bulk diagnostics include the namespace for namespaced targets and use separate stream identities even when names match. They may interleave; final results retain marked order and eight-job concurrency. UTF-8 is decoded across chunks, invalid bytes are replaced, terminal escapes and controls are stripped. A bare carriage return replaces the current transient line; CRLF commits a normal newline, including across read boundaries. Repeated progress redraws therefore replace the bar instead of filling the log history. This is display sanitization, not secret redaction: adapters must not print credentials. Existing 1 MiB per-pipe capture limits still cancel excessive output; activity does not permit unlimited stderr. No manifest changes are needed.
The official catalog contains reviewed plugin packages. Review means a maintainer reviewed that published version. Plugins still run with your permissions and inherited environment; the catalog does not provide a process sandbox or guarantee that an adapter or external tool has no defects.
Catalog commands do not open the TUI, read cluster credentials, connect to Kubernetes, or execute adapters:
sofka plugin search [QUERY]
sofka plugin describe ID[@VERSION]
sofka plugin install ID[@VERSION] [ID[@VERSION] ...]
sofka plugin update [ID ...]
sofka plugin list
sofka plugin remove ID [ID ...]install ID and update select the highest compatible, active, stable package
version. An explicit ID@VERSION installs exactly that version and supports
updates and rollbacks. Reinstalling the same intact version succeeds without
changing files. An update run reports every plugin it cannot update — one the
catalog no longer serves, one whose installed version the catalog has dropped,
one with local modifications — updates the rest, and fails at the end. Sofka never updates plugins during startup, search, or
reload.
Search, describe, install, and update fetch the complete index.json once per
command and cache its validated commit snapshot. Add --offline to use that
snapshot; offline installation also needs the matching cached archive. Sofka
prints the cache age because withdrawal information may be stale. Network
failure does not silently fall back to cached metadata.
Managed packages are installed under
$XDG_CONFIG_HOME/sofka/plugins/<id>, or ~/.config/sofka/plugins/<id>. Each
contains a .sofka-install.json record with its versions and file hashes. A
removal moves the package into sofka/.plugin-trash before deleting it, and
the next plugin command empties that directory. Sofka creates that directory
and marks it as its own, and empties only a directory carrying that mark; an
existing .plugin-trash holding anything else is refused, not adopted. Sofka
deletes nothing else in the config directory, whatever it is named.
list works offline and labels manual packages and local modifications. A
symlinked package directory is always manual: sofka runs the package behind the
link and never takes ownership of it. Update and removal refuse modified
packages, symlinks, and unmanaged directories; resolve those paths manually.
There is no destructive force option.
The installer verifies BLAKE3 before extraction, rejects links and unsafe
paths, validates the existing plugin.toml format, and activates a complete
staged directory. It also reconciles the staged plugin.toml against the
catalog entry the package was selected from and refuses a package whose
version or command settings disagree, so what describe
reports is what the session runs. It reports missing external tools and their installation
instructions, but does not install them. Metadata and package downloads have
separate budgets: a download that makes no progress for 30 seconds is dropped,
while a large package is given up to 15 minutes to arrive. After an
installation, update, or removal changed anything, enter :reload in an
existing session.
Add --json to search, describe, and list for stable machine-readable output.
Search returns an array with id, display_name, description, tags,
latest_version, compatible, installed, installed_version, and
withdrawal_reason. Describe returns id, display_name, description,
tags, publisher, repository, version, status, withdrawal_reason,
license, readme, sofka, platforms, requirements, confirmation,
installed, installed_version, and installed_withdrawal_reason.
For a schema 2 package, commands lists each command's name, optional palette
and key, args, scopes, command, target, output, mutating, confirm,
dangerous, and network_load. For an old release, the execution fields remain
at the top level. confirmation is true if any command has confirm,
dangerous, or network_load set to true. List returns an array with id,
version, path, managed, modified, and withdrawal_reason.
A package can contain several commands. Each command has separate scopes, inputs, and safety settings. Installation, update, and removal apply to all commands in the package. Catalog schema 2 can contain both old release records and new releases with command arrays. Sofka still accepts catalog schema 1 and old installed packages. Older Sofka clients cannot read catalog schema 2.
Home Manager users can continue to place immutable package sources in the same
directory. Sofka reports those as manual packages and does not take ownership
of them. Since plugin is now a CLI command, use sofka --resource plugin to
open a Kubernetes resource whose name is exactly plugin.
sofka ships one plugin: :sanitize, which deletes the pods a namespace has
finished with. It needs nothing installed - see Sanitize pods.
An inline entry or a user package of the same name replaces it.
For core plugins maintained and shipped by the sofka project, Rust is the preferred language. External plugins can use any programming language that follows the plugin protocol. See Language choice.
[[plugins]] assigns an external command to a key or palette command. key is a chord: a single
character ("g"), a modifier combination ("ctrl-g", "alt-x", "shift-b"), or
a function or named key ("f5", "ctrl-f2"). A built-in key wins over a plugin
on the same chord. You can change or disable that built-in binding
to release the key. :config reports bindings hidden by built-in actions.
For the minus key, use "-" alone or "ctrl--", "alt--", or "ctrl-alt--"
with modifiers. The final two hyphens are the separator and the minus key.
"shift--" is not supported. Bind the character that your keyboard produces
instead. For example, use "_" if Shift+minus produces an underscore, or
"ctrl-_" to add Ctrl.
[[plugins]]
key = "shift-y"
name = "yaml-summary"
command = "kubectl"
args = ["get", "$RESOURCE", "$NAME", "-n", "$NAMESPACE", "-o", "yaml"]
scopes = ["pods", "deployments"] # omit for all resources
mutating = false # read-only: still runs under --readonly
output = "popup" # captured into a scrollable view (see below)
[[plugins]]
key = "ctrl-x"
name = "restart-rollout"
command = "kubectl"
args = ["rollout", "restart", "$RESOURCE/$NAME", "-n", "$NAMESPACE"]
scopes = ["deployments"]
dangerous = true # confirm (showing the exact command) first- Placeholders are substituted as whole arguments, never spliced into a shell
string:
$NAME,$NAMESPACE/$NS,$CONTEXT,$CLUSTER,$RESOURCE(plural),$GROUP,$VERSION,$KIND,$FILTER. outputselectsterminal,popup,background, orreport.terminalis the default. It suspends the TUI for an interactive command. On Unix, Ctrl-C interrupts the command. Sofka resumes when the command exits.popupshows captured text.backgroundshows a completion message.reportshows a JSON report. Captured modes usetimeout("30s"by default) and enforce output limits.paletteassigns a command name, such aspalette = "scan"for:scan. Thekeyfield is optional whenpaletteis present.target = "context"runs once without a selected row. The default,selection, uses selected or marked rows.requireslists required executables.installsupplies instructions when an executable is absent.inputsdefines validatedname=valuearguments. See Inputs.network_load = trueidentifies a load test. It requires confirmation and blocks the plugin in read-only mode.mutating(defaulttrue): read-only mode blocks a mutating plugin. Set it tofalseto allow a known read-only one.confirm/dangerous: prompt before running, showing the exact executable and arguments.dangerousalso shows ⚠.shell = true: opt intosh -c. Placeholders still arrive as positional parameters ($1,$2, …), never interpolated into the script.- Bulk: with rows marked (
space), apopuporbackgroundplugin runs over every marked row and reports partial failures. An interactiveterminalplugin can't run over a set and refuses a marked run.
Guardrails match plugin actions with plugin:<palette>, or plugin:<name> when no palette command exists.
On an invalid value (a bad chord, an unknown output, a malformed timeout)
sofka disables just that plugin or falls back to the default and shows a warning
in :config. Plugins appear in ? help with their chord and scope.
[[bookmarks]] are saved navigation commands. One keystroke jumps to a resource
and can switch context or namespace and apply a filter, sort, and view. The key
chord is optional - bookmarks are always in the command palette (★, ranked
above resources).
[[bookmarks]]
key = "shift-1" # optional
name = "Prod API failures"
resource = "pods"
context = "prod-eu" # optional: switched first
namespace = "checkout" # optional; all/* = all namespaces
filter = "status!=Running -l app=api" # optional, same syntax as `/`
sort = "RESTARTS:desc" # optional: COLUMN[:asc|:desc]
view = "xray" # optional: xray | pulseWithout an active workspace, Tab / Shift-Tab cycle pods → services →
deployments → statefulsets → daemonsets → secrets → configmaps → ingresses →
PVCs in the current namespace, including all namespaces. The cycle wraps and
skips kinds absent from API discovery. From a resource outside this set, Tab
starts at pods and Shift-Tab starts at PVCs (or the next available kind in
that direction). Switching resources clears filters and drill-down scope, as
with :resource; [ / ] still navigate view history.
[[workspaces]] group several views into a named set for one task - checkout
ops, a cluster upgrade, cert renewal. Open one with a chord or the palette (▦).
sofka switches the optional context once and shows the first view. Tab /
Shift-Tab cycle the other views. You stay in the workspace.
[[workspaces]]
key = "ctrl-w"
name = "Checkout ops"
context = "prod-eu" # optional: switched once on open
[[workspaces.views]]
name = "API pods"
resource = "pods"
namespace = "checkout"
filter = "-l app=api"
sort = "RESTARTS:desc"
[[workspaces.views]]
name = "Ingress"
resource = "ingresses"
namespace = "checkout"Port-forwards started with f/F run in the background and are managed with
:pf. [[forwards]] adds named entries that appear in :pf even while stopped
(⏎ starts one), with optional autostart on connect and on matching context
switches.
[[forwards]]
name = "argocd"
target = "svc/argocd-server" # kubectl syntax: pod/…, svc/…, deploy/…
namespace = "argocd"
ports = "8080:443" # LOCAL:REMOTE
autostart = true # start when sofka connects (default false)
contexts = ["home"] # optional: only these contextssofka stops every forward on quit instead of orphaning it.