Imported from olafkfreund/nixarchy-distrobox (
AGENTS.md). Install upstream withnpx skills add olafkfreund/nixarchy-distrobox. Copyright stays with the author.
AGENTS.md
Instructions for any AI agent working in this repository: Claude Code, Codex, Copilot,
Gemini or others. CLAUDE.md and .github/copilot-instructions.md point here. This
file is the single source. When anything disagrees with it, this file wins.
What this repository is
nixarchy.distrobox is an Omarchy shell plugin written in
Quickshell QML. It manages distrobox containers through two
surfaces:
- a bar widget, whose popup sits under the glyph (
Panel.qml); - a full-screen keyboard menu (
Menu.qml).
It lists boxes and can:
- create a box from a form that covers every
distrobox createflag; - enter, start, stop, restart, upgrade or delete a box.
Create and upgrade stream their output into the panel.
flake.nix packages the plugin for NixOS and nixarchy. The user guide is
docs/usage.md. The design is in intent/, spec/ and plan/.
Layout
| Path | Owns |
|---|---|
Model.js |
All logic: parsing engine output, rows, validation, and every command's argv. Pure .pragma library with no QML, tested under Node. |
qmldir |
Declares DistroboxState a singleton, so the bar and the menu share one instance. |
DistroboxState.qml |
Data, polling, the operation lock, the stream log, and every Process. |
DistroboxView.qml |
Interaction: modes (list / form / log), cursor, filter, confirmations, keys. Shared by both surfaces. |
BoxList.qml, CreateForm.qml, LogView.qml, ShortcutSheet.qml |
Drawing pieces used by the view. |
Panel.qml |
The bar widget host: glyph, KeyboardPanel popup, and IPC target nixarchy.distrobox.bar. |
Menu.qml |
The full-screen menu host (manifest kind menu). It scales the view 1.45×. |
manifest.json |
Plugin id nixarchy.distrobox, kinds menu + bar-widget, keepLoaded: true, settings schema. |
flake.nix |
The package (an explicit files list, copied as real files) and checks.<system>.default. |
share/omarchy-menu.jsonc |
The Omarchy menu row users paste in. |
tests/ |
Node tests for Model.js (tests/run.js). |
intent/, spec/, plan/ |
Design artifacts for each task. See Workflow. |
Commands
node tests/run.js # Model tests
nix flake check # tests + manifest, entry points, no symlinks, no pacman/yay, no hex colours
nix flake check --all-systems --no-build # aarch64 evaluates
nix build # the plugin folder, exactly as nixarchy links it
omarchy plugin validate "$(readlink -f result)"
To see the repo the way omarchy plugin add would, validate a fresh clone rather than
the working tree. The result link that nix build leaves behind is a symlink, so
validating . fails once you have built:
d=$(mktemp -d) && git clone -q . "$d/p" && rm -rf "$d/p/.git" && omarchy plugin validate "$d/p"
Verifying live (on a nixarchy desktop)
- Install a copy (a symlinked checkout does not reload on
rescanPlugins):
Then enable it once:rm -rf ~/.config/omarchy/plugins/nixarchy.distrobox cp -rL result ~/.config/omarchy/plugins/nixarchy.distrobox chmod -R u+w ~/.config/omarchy/plugins/nixarchy.distroboxomarchy plugin enable nixarchy.distrobox. - Restart the shell with
omarchy-restart-shell, then wait untilomarchy-shell shell pinganswers. - Check the log for errors. Get the instance from
qs list --all, then runqs log -i <instance>. - Open each surface:
- the menu:
omarchy-shell shell toggle nixarchy.distrobox '{}', or'{"create":true}'to open straight into the form; - the popup:
omarchy shell nixarchy.distrobox.bar open.
- the menu:
- Confirm what is up with
hyprctl layers -j. The menu's namespace isnixarchy-distrobox-menu. - Test boxes: name them
t1,t2and so on, and remove them withdistrobox rmwhen done.
Retaking the captures
Real captures only, and never of anything but the plugin, demo-* boxes and
the wallpaper:
- Stage. Run
docs/capture.sh --setup. It refuses if anydemo-*box or home already exists, and it savesshell.jsonandomarchy-menu.jsonc. Note the current workspace on each monitor and the do-not-disturb state (omarchy-shell notifications isDnd), then turn do-not-disturb on (setDnd true). - Clear the screen. Put every monitor you capture on an empty workspace
(
hyprctl dispatch 'hl.dsp.focus({ workspace = "31" })'). Park the pointer (hyprctl dispatch 'hl.dsp.cursor.move({ x = …, y = … })'), then reopen the surface. Moving the pointer by dispatch does not clear a hover tooltip. - Drive the surfaces.
- Open them with IPC, and send keys with
wtype, only while a plugin layer is up. Checkhyprctl layers -jforomarchy-keyboard-panelornixarchy-distrobox-menubefore every key:wtypetypes into whatever has focus. - The owner must not be using the desktop. If a workspace changes under you, stop.
- Take stills with
docs/capture.sh --shot NAME X,Y WxH. - Record with
wl-screenrec -g …. It refuses a region that crosses an output edge by even one pixel.
- Open them with IPC, and send keys with
- Encode. WebM (VP9,
-crf 40) and MP4 (H.264,-crf 28). Look at every still, and at a frame sheet of every video (ffmpeg -vf fps=1,scale=…,tile=…), before committing. - Tear down. Run
docs/capture.sh --teardown, restore do-not-disturb and the workspaces, and diff the box list and both config files against a snapshot taken before step 1.docs/img/must stay under 8 MB, and CI enforces it: it ships inside everyomarchy plugin addclone.
Rules
Each rule records a real failure or a hard constraint:
- No symlinks anywhere in the repository.
omarchy plugin addclones this repo as the plugin folder, andomarchy-plugin-validaterefuses any symlink inside it. That is whyCLAUDE.mdimportsAGENTS.mdinstead of linking to it. - No hardcoded colours. Use
Color.*,Style.*andBorder.*tokens, so themes switch cleanly.nix flake checkfails on"#rrggbb". - No
pacmanoryay, not even in comments. nixarchy fails the rebuild on them. - A new runtime file goes in the
fileslist inflake.nix, or it is not in the package. - Run external commands by name from
PATH. Never wrap or bundle them. A missing command fails silently inside a QMLProcess, so document it as a requirement. - Argv arrays only, never
sh -c. Every command is built inModel.jsas an array and returnsnullon invalid input. - Every form field is allowlisted for its host quoting context.
distrobox-createrunseval ${cmd}on the host. Some fields land unquoted, some inside"…"and some inside'…'. So one argv element per field does not protect the host.Model.validateFormowns the rules. Any new field needs its own rule and a hostile-input test row. - Every distrobox argv starts with
env DBX_CONTAINER_MANAGER=<engine>. The engine is captured when the operation starts. Without it, a docker user's stop or delete can hit a podman box with the same name. - One mutation at a time, via the singleton. Start, stop, restart, remove, create and upgrade are refused while another mutation runs, from either surface. Listing never locks.
- Start is
distrobox enter -T -- true, not<engine> start. A box thatpsreports as running may not have finished its first-run setup yet. - Lists read through a QObject
varproperty are Qt sequence wrappers, not JS arrays. Checklength, notArray.isArray(seeModel.settingsFor). - The surfaces are keep-loaded.
open()resets the view and then focuses whatever belongs to the final mode, viaQt.callLater. It never touches the stream or the log. - Nothing polls while every surface is closed. The bar's slow poll for the glyph is the only exception.
- Logic goes in
Model.js, with a Node test. Keep QML to drawing and wiring. - A user-visible change updates
docs/usage.mdand the README in the same PR.
Workflow
Any task that is tracked as an issue, or that touches more than one file, goes
through three artifacts named with the slug YYYY-MM-DD-<issue>-<slug>. Typos,
lock bumps and one-line config changes are exempt.
intent/<slug>.md(why), committed asstatus: draft. Stop for the owner's review.- After approval,
spec/<slug>.md(what). Stop. - After approval,
plan/<slug>.md(how, self-contained). Stop. - Implement only once the plan is
status: approved.
- Never approve an artifact yourself.
- Record each approval as its own commit, for example
docs(plan): approve <slug> (#N). - Make one commit per plan step, and cite the step.
- If the work deviates from the plan, update
plan/in the same commit as the code. - The PR links all three artifacts and closes the issue. Review compares the diff
to
plan/.
Branches are named feat|fix|docs/<issue>-<slug>. Commit subjects use Conventional
Commits (feat:, fix:, docs:, build:, ci:, refactor:), each with the
issue number.
Known follow-ups
--rm-homeon delete, and--rootboxes.- Wire p620 and razer to this flake.