- Luau 100%
| jira | ||
| niri-tabctl | ||
| zj | ||
| zon-mode | ||
| .gitignore | ||
| catalog.toml | ||
| README.md | ||
noctalia-plugins
Personal Noctalia plugins, laid out as a Noctalia plugin source
repo: one directory per plugin, plus a catalog.toml at the root indexing them all.
Noctalia v5 plugins are Luau scripts driven by the shell's C++ host. They
are not the QML plugins of the v3/v4 (Quickshell) era — those live in the archived
legacy-v4-plugins repo and do not load
under v5.
Plugins
| Plugin | Id | What it does |
|---|---|---|
niri-tabctl |
tomster/niri-tabctl |
/tab — fuzzy-search every open browser tab and jump to it, switching workspace and focusing the window. Needs tabctl; see its README |
zj |
tomster/zj |
/zj — jump back into a project's zellij session from anywhere: the projects zj has opened, most recent first, running, exited or gone. Enter switches the project's companion terminal to it. See its README |
zon-mode |
tomster/zon-mode |
Bar indicator for zon-mode (dotfiles): a green Z when this machine is in ZON mode, grey when not. Refreshed by zon-mode's hooks (noctalia msg plugin tomster/zon-mode:indicator all refresh), re-checked every minute |
Installing
Two sources are declared in ~/.config/noctalia/settings.toml (managed by chezmoi), so a
fresh machine gets both from chezmoi apply:
# Baseline. Noctalia clones and materializes this itself into
# ~/.local/state/noctalia/plugins/, so the plugins work with no local checkout.
[[plugins.source]]
name = "tomster"
kind = "git"
location = "https://git.tomster.org/tomster/noctalia-plugins"
enabled = true
# Dev override, listed last so it wins where it exists.
[[plugins.source]]
name = "tomster-dev"
kind = "path"
location = "~/Development/workdirs/config/noctalia-plugins"
enabled = true
[plugins]
enabled = ["tomster/niri-tabctl"]
HTTPS rather than the SSH remote on purpose: Noctalia runs git non-interactively at startup, so an SSH URL would need an agent-loaded key before the shell starts and would otherwise fail silently.
Note that declaring [[plugins.source]] at all replaces Noctalia's built-in source list
rather than appending to it — official and community must be restated verbatim alongside
these, or they vanish together with every plugin installed from them.
The equivalent imperative form:
noctalia msg plugins source add tomster git https://git.tomster.org/tomster/noctalia-plugins
noctalia msg plugins source add tomster-dev path ~/Development/workdirs/config/noctalia-plugins
noctalia msg plugins enable tomster/niri-tabctl
The plugin id (tomster/niri-tabctl) is canonical, and a plugin directory name must equal the
part of the id after the /.
Precedence
Only one copy of a given author/plugin ever loads. Roots are scanned lowest-to-highest, so
the last one wins:
~/.local/share/noctalia/plugins/<dir>/— the local drop-in, beats everything- later-declared sources, over earlier ones — hence
tomster-devaftertomster - the built-in
officialandcommunitysources
So on a machine with this repo checked out there (a worktree in the config workdir), edits are live; on one without,
Noctalia silently skips the missing path root and serves the git copy. Nothing warns, nothing
breaks — a missing path source is an early return in PluginRegistry::scanDir.
You can see which copy won in the log:
plugins] plugin 'tomster/niri-tabctl' at /home/tomster/Development/workdirs/config/noctalia-plugins/niri-tabctl
overrides earlier copy at .../state/noctalia/plugins/materialized/tomster/niri-tabctl
Where not to edit
Neither state directory is a usable working copy:
~/.local/state/noctalia/plugins/sources/<name>/repois a blobless clone with no working tree (--filter=blob:none --no-checkout) — there are no files to edit.~/.local/state/noctalia/plugins/materialized/<name>/<plugin>/has no.gitat all. It is agit checkout --work-treeexport that Noctalia atomically replaces on every update, so edits there are destroyed without warning.
Always work in this checkout, commit and push from here.
Adding the git source to a running shell
Git sources are materialized once at startup — PluginManager::refresh() guards the call
with if (!m_applied) so a config reload never re-touches the network. A newly declared git
source therefore does nothing until the next shell restart. To pick it up immediately:
noctalia msg plugins enable tomster/niri-tabctl # resolving the source clones + exports it
Developing
# static check: undeclared getConfig keys, missing entry files, unread settings
noctalia plugins lint niri-tabctl
# open the launcher straight into a mode
noctalia msg panel-open launcher "/tab "
# watch the shell log
noctalia msg log-level-set debug
journalctl --user -f -o cat _PID=$(pgrep -x noctalia)
Reload rules, which are not symmetrical:
| Changed | To pick it up |
|---|---|
*.luau |
nothing — hot-reloads on save, with a "Reloaded script" toast |
plugin.toml |
noctalia msg config-reload |
translations/*.json |
touch the entry .luau — only Luau files are watched, so a config-reload is not enough and every changed key silently renders verbatim |
The translations one is a trap: the symptom is plugin translation key '…' not found in the
log while the file on disk plainly contains the key.
Reference
- Plugin docs:
docs/user/plugins/development/in a noctalia checkout —manifest.mdx,entries.mdx,runtime-api.mdx,declarative-ui.mdx,workflow.mdx - Ground truth for the
noctalia.*API:src/scripting/luau_host.cpp(kNoctaliaBaseLib) - Type stubs:
noctalia.d.luauin official-plugins - ~200 worked examples: community-plugins
Publishing
To offer a plugin to everyone, PR it into
community-plugins. That needs a
README.md, a thumbnail.webp and translations/en.json alongside the manifest; CI
validates them and regenerates that repo's catalog on merge. Directory names there are
first-come.