plugins
Read this before changing the status bar, notifications, shell plugins, widgets, or idle/lock behavior.
Claude Code
$ npx -y skills add omacom/omarchy --agent claude-code
Cursor
Cursor also loads subagents from .claude/agents, so copy this file there to use it in Cursor.
Agent definition
plugins.mdOmarchy Shell: Bar, Plugins, and Idle
Read this before changing the status bar, notifications, shell plugins, widgets, or idle/lock behavior.
The bar, notification daemon, settings panel, and assorted overlays all run inside a single long-running Quickshell process (`omarchy-shell`).
~/.config/omarchy/shell.json # User overrides: bar, plugins, idle
~/.config/omarchy/plugins/<plugin-id>/ # User-owned shell plugins
$OMARCHY_PATH/config/omarchy/shell.json # Canonical defaults
The shell hot-reloads `shell.json` on save — no restart needed for layout changes. `idle.screensaver` and `idle.lock` are seconds since user idle began.
**Commands:** `omarchy restart shell`, `omarchy refresh shell`
Plugin UI Safety
Plugins run inside Omarchy's long-lived, unsandboxed shell. Treat text from network responses, files, IPC, settings, environment variables, and command output as untrusted.
- QML `Text` defaults to `Text.AutoText`. Do not bind untrusted data to an AutoText sink: markup-shaped input such as `<img src="https://example.invalid/tracker">` can be interpreted as rich text and load a resource.
- For display text owned by the plugin, set `textFormat: Text.PlainText`. Prefer a local `PlainText` component when the plugin has multiple text elements.
Text {
textFormat: Text.PlainText
text: modelData.name
}- When passing dynamic text to an Omarchy-owned component whose internal `Text` format the plugin cannot set, neutralize markup in the plugin first with a helper of its own, and add a regression test; Omarchy ships no such helper. Never edit the packaged component to add a plain-text option: that belongs in a pull request to Omarchy.
- Bound and validate downloaded data before displaying or saving it: limit response and collection sizes, string lengths, identifiers, and timestamps.
- Keep network access explicit. Use fixed HTTPS sources, timeouts, response-size limits, and validation. For periodic background refreshes, disclose the behavior and let users enable or disable it. Never execute downloaded content or interpolate untrusted values into shell commands.
- Add regression coverage for markup-shaped input and every shared tooltip or notification sink that receives dynamic text.
Plugin Motion
Motion in a plugin should feel like the rest of the shell. Most of it comes free from Omarchy's kit in `qs.Ui`: a `KeyboardPanel` grows out of its bar icon and fades, morphs into a neighbouring panel in the same bar section, and grows to fit content that arrives late; a `PopupCard` grows and fades; a `Button` gives a little when pressed. Build on those rather than animating your own open and close.
For anything the plugin animates itself, `import qs.Commons` and:
- Give every duration as `Style.duration(ms)`, never a bare number. It returns 0 when the user has turned animations off with `omarchy toggle animations`.
- Ease movement and entrances with `Style.easeOut` (`easing.type: Easing.Bezier; easing.bezierCurve: Style.easeOut`) or `Easing.OutCubic`. Never an ease-in curve, no bounce, and linear only for constant motion such as a spinner.
- Keep it under 300ms, with exits shorter than entrances: about 180ms in, 120ms out.
- Never scale from 0; start at 0.95–0.97 with opacity 0.
- Don't animate what a single keystroke opens or dismisses many times a day; it should just appear or go.
- Animate only for a reason: showing where something came from or went, confirming an input, or keeping a change from jumping.
import QtQuick
import qs.Commons
Item {
property bool shown: false
opacity: shown ? 1 : 0
scale: shown ? 1 : 0.97
Behavior on opacity { NumberAnimation { duration: Style.duration(180); easing.type: Easing.OutCubic } }
Behavior on scale {
NumberAnimation { duration: Style.duration(180); easing.type: Easing.Bezier; easing.bezierCurve: Style.easeOut }
}
}Translations
Omarchy runs in 50 languages, and a plugin should follow the one picked under Setup > Language the way the built-in plugins do: live, without a shell restart. Ship the plugin's strings as flat JSON catalogs in an `i18n/` directory beside its QML, one file per Omarchy language code (`en.json`, `da.json`, `pt-BR.json`; `$OMARCHY_PATH/default/i18n/locales.json` lists all 50), and read them through `Commons.Translations`:
import qs.Commons as Commons
Item {
Commons.Translations { id: strings; directory: Qt.resolvedUrl("i18n") }
Text {
textFormat: Text.PlainText
text: strings.tn("panel.items", count).arg(Commons.I18n.formatNumber(count))
}
}{ "panel.items": { "one": "%1 item", "other": "%1 items" } }- A string missing from the current language falls back to its base language (`pt-BR` to `pt`), then to `en.json`, then to the key, so a plugin can ship English alone and add languages one file at a time.
- Keep each string whole with `%1` placeholders filled by `.arg()`; never glue translated fragments into a sentence, since other languages reorder words.
- A string that counts something is a plural entry, an object keyed by CLDR category, read with `strings.tn(key, count)`. English needs `one` and `other`; each translation supplies the categories its language uses (Russian `one`, `few`, `many`, `other`; Arabic six; Japanese only `other`; the rules are in `$OMARCHY_PATH/shell/Commons/Plural.js`).
- Write displayed numbers with `Commons.I18n.formatNumber(value, decimals)` so they take the language's decimal separator and digits (`13,1` in Danish).
- Arabic, Hebrew, Persian, and Urdu mirror the layout. A plugin built on `KeyboardPanel` or `PopupCard` is mirrored for free; anything it positions by explicit `x` should follow `LayoutMirroring.enabled`, glyphs that mean back or forward should swap with `Commons.I18n.rightToLeft`, and Left/Right keys should go through `Util.readingKey(event)`.
- Keep calls to `strings.t(...)` inside bindings so a language switch re-renders them. Text built once when something happens keeps th
Read more
Omarchy Shell: Bar, Plugins, and Idle
Read this before changing the status bar, notifications, shell plugins, widgets, or idle/lock behavior.
The bar, notification daemon, settings panel, and assorted overlays all run inside a single long-running Quickshell process (`omarchy-shell`).
~/.config/omarchy/shell.json # User overrides: bar, plugins, idle ~/.config/omarchy/plugins/<plugin-id>/ # User-owned shell plugins $OMARCHY_PATH/config/omarchy/shell.json # Canonical defaults
The shell hot-reloads `shell.json` on save — no restart needed for layout changes. `idle.screensaver` and `idle.lock` are seconds since user idle began.
**Commands:** `omarchy restart shell`, `omarchy refresh shell`
Plugin UI Safety
Plugins run inside Omarchy's long-lived, unsandboxed shell. Treat text from network responses, files, IPC, settings, environment variables, and command output as untrusted.
- QML `Text` defaults to `Text.AutoText`. Do not bind untrusted data to an AutoText sink: markup-shaped input such as `<img src="https://example.invalid/tracker">` can be interpreted as rich text and load a resource.
- For display text owned by the plugin, set `textFormat: Text.PlainText`. Prefer a local `PlainText` component when the plugin has multiple text elements.
Text {
textFormat: Text.PlainText
text: modelData.name
}- When passing dynamic text to an Omarchy-owned component whose internal `Text` format the plugin cannot set, neutralize markup in the plugin first with a helper of its own, and add a regression test; Omarchy ships no such helper. Never edit the packaged component to add a plain-text option: that belongs in a pull request to Omarchy.
- Bound and validate downloaded data before displaying or saving it: limit response and collection sizes, string lengths, identifiers, and timestamps.
- Keep network access explicit. Use fixed HTTPS sources, timeouts, response-size limits, and validation. For periodic background refreshes, disclose the behavior and let users enable or disable it. Never execute downloaded content or interpolate untrusted values into shell commands.
- Add regression coverage for markup-shaped input and every shared tooltip or notification sink that receives dynamic text.
Plugin Motion
Motion in a plugin should feel like the rest of the shell. Most of it comes free from Omarchy's kit in `qs.Ui`: a `KeyboardPanel` grows out of its bar icon and fades, morphs into a neighbouring panel in the same bar section, and grows to fit content that arrives late; a `PopupCard` grows and fades; a `Button` gives a little when pressed. Build on those rather than animating your own open and close.
For anything the plugin animates itself, `import qs.Commons` and:
- Give every duration as `Style.duration(ms)`, never a bare number. It returns 0 when the user has turned animations off with `omarchy toggle animations`.
- Ease movement and entrances with `Style.easeOut` (`easing.type: Easing.Bezier; easing.bezierCurve: Style.easeOut`) or `Easing.OutCubic`. Never an ease-in curve, no bounce, and linear only for constant motion such as a spinner.
- Keep it under 300ms, with exits shorter than entrances: about 180ms in, 120ms out.
- Never scale from 0; start at 0.95–0.97 with opacity 0.
- Don't animate what a single keystroke opens or dismisses many times a day; it should just appear or go.
- Animate only for a reason: showing where something came from or went, confirming an input, or keeping a change from jumping.
import QtQuick
import qs.Commons
Item {
property bool shown: false
opacity: shown ? 1 : 0
scale: shown ? 1 : 0.97
Behavior on opacity { NumberAnimation { duration: Style.duration(180); easing.type: Easing.OutCubic } }
Behavior on scale {
NumberAnimation { duration: Style.duration(180); easing.type: Easing.Bezier; easing.bezierCurve: Style.easeOut }
}
}Translations
Omarchy runs in 50 languages, and a plugin should follow the one picked under Setup > Language the way the built-in plugins do: live, without a shell restart. Ship the plugin's strings as flat JSON catalogs in an `i18n/` directory beside its QML, one file per Omarchy language code (`en.json`, `da.json`, `pt-BR.json`; `$OMARCHY_PATH/default/i18n/locales.json` lists all 50), and read them through `Commons.Translations`:
import qs.Commons as Commons
Item {
Commons.Translations { id: strings; directory: Qt.resolvedUrl("i18n") }
Text {
textFormat: Text.PlainText
text: strings.tn("panel.items", count).arg(Commons.I18n.formatNumber(count))
}
}{ "panel.items": { "one": "%1 item", "other": "%1 items" } }- A string missing from the current language falls back to its base language (`pt-BR` to `pt`), then to `en.json`, then to the key, so a plugin can ship English alone and add languages one file at a time.
- Keep each string whole with `%1` placeholders filled by `.arg()`; never glue translated fragments into a sentence, since other languages reorder words.
- A string that counts something is a plural entry, an object keyed by CLDR category, read with `strings.tn(key, count)`. English needs `one` and `other`; each translation supplies the categories its language uses (Russian `one`, `few`, `many`, `other`; Arabic six; Japanese only `other`; the rules are in `$OMARCHY_PATH/shell/Commons/Plural.js`).
- Write displayed numbers with `Commons.I18n.formatNumber(value, decimals)` so they take the language's decimal separator and digits (`13,1` in Danish).
- Arabic, Hebrew, Persian, and Urdu mirror the layout. A plugin built on `KeyboardPanel` or `PopupCard` is mirrored for free; anything it positions by explicit `x` should follow `LayoutMirroring.enabled`, glyphs that mean back or forward should swap with `Commons.I18n.rightToLeft`, and Left/Right keys should go through `Util.readingKey(event)`.
- Keep calls to `strings.t(...)` inside bindings so a language switch re-renders them. Text built once when something happens keeps th
Omarchy is a beautiful, fun & agentic Linux distribution by DHH. Read more at omarchy.org.
Other agents on omarchy.
More agents from other plugins.
architect
AgentMUST BE USED when starting new projects or planning major changes. This agent specializes exclusively in system architecture…
api-reference
Agent**⚠️ DEVELOPER REFERENCE ONLY** — This file documents the raw GraphQL API schema for development and maintenance purposes (adding…
architect
AgentProduce design + contracts (OpenAPI/AsyncAPI/JSON Schema) + ADRs for a greenfield feature. Wraps /plan. Use when the spec exists…
Questions about plugins.
- Which coding agents can use plugins?
- plugins installs in Claude Code and Cursor: each loads subagents from .claude/agents, so copy the file there. Flowy's auto-invocation runs in Claude Code only.

