Your Omarchy plugin works on your machine. Prove its contract in CI.
Omarchy Plugin CI is a secretless GitHub Action and local CLI that checks an Omarchy Quattro plugin's manifest, entry points, structural QML contract, review-worthy capabilities, and isolated configuration lifecycle.
OMARCHY PLUGIN CI
PASS Manifest schema
PASS Plugin ID namespace
PASS Entry points
PASS QML root contract
PASS Summon lifecycle
WARN External network access: api.example.com
PASS Isolated enable and disable
PASS Source remained unchanged
Contract level: 3/3
It does not execute plugin code during lifecycle simulation, does not request a GitHub token, and does not claim that a static report proves a plugin is secure.
Community project. Not affiliated with Basecamp or the Omarchy project.
Add .github/workflows/omarchy-plugin-ci.yml to an Omarchy plugin repository:
name: Omarchy Plugin CI
on:
push:
pull_request:
permissions:
contents: read
jobs:
plugin-contract:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
- uses: duclucky/omarchy-plugin-ci@v0.1.0-beta.1Push the workflow. The Action writes a complete report into the GitHub Job
Summary and stores machine-readable reports in .omarchy-plugin-ci/.
The beta tag is intentionally explicit. For a stricter supply-chain policy, pin the Action to the full commit SHA shown by the release and let Dependabot or Renovate propose updates.
An Omarchy third-party plugin is a Git repository with a manifest.json at its
root. Plugin code runs unsandboxed inside the long-lived omarchy-shell
process, with the same access as the user's account. The official manual asks
users to review repositories before enabling them.
Plugin authors need a repeatable answer to smaller, deterministic questions:
- Does the manifest match the current Quattro contract?
- Does every declared kind have a safe entry point?
- Can a path or symlink escape the plugin directory?
- Does a summonable component expose the expected lifecycle?
- What network, process, credential, persistence, or privilege patterns should a human review?
- Does enable and disable produce a clean
shell.jsontransition? - Does the validation process leave the source untouched?
This project turns those questions into versioned evidence. It is a decision aid for maintainers and reviewers, not a security certification.
The report assigns the highest completed contract level:
| Level | Evidence | What it proves |
|---|---|---|
| 0 | Contract failed | The manifest did not reach the official structural baseline |
| 1 | Manifest contract | Required fields, ID rules, kinds, entry-point paths, and bar placement are valid |
| 2 | Structural QML | Entry points avoid ShellRoot; summonable components expose or inherit open and close |
| 3 | Isolated lifecycle | Staging, enable, disable, remove, JSON persistence, and source immutability passed in a temporary HOME |
Capability warnings do not lower the contract level. They describe behavior a reviewer should inspect. Use strict mode when project policy requires every warning to be resolved or explicitly suppressed in a future policy file.
Level 3 is not a real desktop session. It does not prove:
- visual correctness in Quickshell or Hyprland;
- correct behavior on every monitor, GPU, driver, or scale factor;
- correct DBus, PipeWire, network, Bluetooth, or hardware integration;
- compatibility with every past or future Omarchy release;
- absence of malicious or vulnerable behavior;
- that
qmllintcan resolve Omarchy-specific imports on a generic runner.
A real Omarchy session remains the correct place for rendering, interaction, IPC, hardware, and end-to-end user testing.
Hard failures include:
- missing or invalid
manifest.json; - unsupported
schemaVersion; - missing
id,name,version,kinds, orentryPoints; - empty
kinds; - unsupported plugin kind;
- use of the reserved
omarchy.*namespace by a third-party plugin; - ID containing
/or..; - absolute, empty, escaping, or missing entry-point paths;
- a declared kind without its matching entry point;
- invalid
barWidget.defaultSection.
The supported kind-to-entry-point mapping is:
| Manifest kind | Entry-point key |
|---|---|
bar-widget |
barWidget |
bar |
bar |
panel |
panel |
overlay |
overlay |
menu |
menu |
service |
service |
Best-practice warnings include a non-namespaced ID, non-SemVer version, or
missing author and description. These are useful for distribution but are
not represented as official hard requirements by the captured registry
contract.
The built-in structural checks:
- reject
pragma Singletonon an entry point; - reject
ShellRootas an entry-point root; - identify the root component;
- require
open(payloadJson)andclose()for panel, overlay, and menu entry points, unless they inherit the officialPanelcomponent lifecycle.
If qmllint is available, every QML file is also passed to it. A non-zero
qmllint result is a warning because generic CI runners may not have the
Omarchy and Quickshell import paths. Structural checks still run when
qmllint is missing or disabled.
The bounded scanner reviews runtime files for high-signal patterns:
| Category | Examples |
|---|---|
network |
External URLs, curl, wget |
process |
Quickshell Process, Node child processes, Python subprocesses |
credentials |
SSH/GPG paths and credential-like environment names |
privilege |
sudo, pkexec, doas |
persistence |
systemd enable, autostart, crontab |
remote-code |
Download piped into a shell |
destructive |
Recursive or forceful delete commands |
Each match includes the file, line, and a bounded evidence excerpt. Findings are warnings because pattern matching can produce false positives and cannot establish intent.
The report deliberately avoids a fake trust score. A clean scan means only that these specific patterns were not detected.
The lifecycle check creates a temporary HOME and:
- Copies the plugin into
.config/omarchy/plugins/<id>without executing it. - Reads the staged manifest and verifies the ID.
- Applies the documented Quattro
shell.jsonreference for its kind. - Persists and parses the JSON.
- Removes every reference during disable.
- Removes only the staged plugin directory.
- Confirms the source tree digest did not change.
- Deletes the temporary sandbox.
This is explicitly a simulation of the documented configuration contract. It does not invoke a live shell IPC endpoint and does not run install hooks or plugin code.
| Input | Default | Description |
|---|---|---|
plugin-path |
. |
Repository root or plugin subdirectory |
report-dir |
.omarchy-plugin-ci |
Destination for report.json and report.md |
strict |
false |
Turn any warning into a failing verdict |
qmllint |
auto |
Run qmllint when available; use false to skip |
Example for a plugin in a monorepo:
- uses: duclucky/omarchy-plugin-ci@v0.1.0-beta.1
with:
plugin-path: plugins/weather
report-dir: artifacts/weather-contract
strict: "true"| Output | Example | Description |
|---|---|---|
verdict |
pass |
Selected policy result |
contract-level |
3 |
Highest completed evidence level |
warnings |
2 |
Warning count |
failures |
0 |
Hard contract failure count |
report-json |
/.../report.json |
Absolute JSON report path |
report-markdown |
/.../report.md |
Absolute Markdown report path |
Consume an output in another step:
- id: omarchy
uses: duclucky/omarchy-plugin-ci@v0.1.0-beta.1
- if: always()
run: echo "Contract level ${{ steps.omarchy.outputs.contract-level }}/3"Requirements:
- Node.js 20 or newer
- Git, only for cloning the project
- Optional:
qmllint
Run from a tagged checkout:
git clone --depth 1 --branch v0.1.0-beta.1 \
https://github.com/duclucky/omarchy-plugin-ci.git
cd omarchy-plugin-ci
node src/cli.mjs --plugin /path/to/pluginOr install the CLI from the checkout:
npm install --global .
omarchy-plugin-ci --plugin /path/to/pluginUseful options:
# JSON to stdout and files
omarchy-plugin-ci --plugin . --json
# Fail when any warning exists
omarchy-plugin-ci --plugin . --strict
# Disable optional qmllint execution
omarchy-plugin-ci --plugin . --no-qmllint
# Compact output for scripts
omarchy-plugin-ci --plugin . --quietreport.json is intended for automation and future catalog ingestion:
{
"plugin": {
"id": "example.focus-timer",
"version": "1.0.0",
"kinds": ["bar-widget"]
},
"contract": {
"id": "omarchy-quattro-plugin-v1",
"level": 3
},
"verdict": "pass",
"counts": {
"pass": 14,
"warn": 0,
"fail": 0,
"skip": 1
},
"capabilities": [],
"findings": []
}report.md contains the same evidence in a reviewable table. In GitHub Actions
it is appended automatically to $GITHUB_STEP_SUMMARY.
Reports use only the plugin directory name, relative file paths, bounded line evidence, manifest metadata, and check results. The tool does not intentionally include the caller's absolute plugin path in the report.
| Code | Meaning |
|---|---|
0 |
Checks passed under the selected warning policy |
1 |
Hard failure, or one or more warnings in strict mode |
2 |
Invalid argument, missing plugin directory, or internal execution error |
Recommended workflow permissions:
permissions:
contents: readThe Action:
- does not require
GITHUB_TOKEN; - does not request write permissions;
- does not read repository secrets;
- does not upload telemetry;
- does not call an external API;
- does not execute plugin code in the lifecycle simulation;
- does not use
sudo; - writes reports only to the chosen report directory;
- uses an operating-system temporary directory for the lifecycle sandbox.
The runner may still have network access. Use your organization's normal runner isolation and Action pinning policy.
The bundled omarchy-quattro-plugin-v1 snapshot was captured on 2026-08-24
from:
See contracts/quattro-v1.json for the
machine-readable mapping and docs/CONTRACT.md for the
project's contract update policy.
Omarchy changes quickly. A passing report is evidence against this pinned contract snapshot, not a promise about an unknown future branch.
This is a SKIP, not a failure. The built-in structural checks still run. Add
the Qt package that provides qmllint to the runner if your project wants the
additional lint pass, or set qmllint: "false" to make the choice explicit.
Omarchy plugins import Quickshell and Omarchy modules that a generic Ubuntu runner may not know. The tool records the first bounded diagnostic lines as a warning. It does not misrepresent unresolved imports as a runtime failure.
The beta scanner intentionally prefers visible false positives over an opaque trust score. Keep executable fixtures outside the plugin path when possible. Open a rule issue with the minimal case if a pattern should be narrowed.
Level 3 validates a filesystem and configuration lifecycle, not rendering. Reproduce the issue in a real Omarchy session and attach the generated report so contract failures and runtime failures remain separate.
Declare every kind and matching entry point. A plugin containing
bar-widget is represented by its bar layout entry during lifecycle
simulation, matching the shell's enabled-state model. A full bar replaces
bar.id. Other plugins use the top-level plugins[] array.
npm ci --ignore-scripts
npm run check
npm test
npm run audit:fixturesThe test suite covers:
- a valid bar widget reaching level 3;
- a summonable panel with lifecycle methods;
- reserved IDs and path traversal failures;
- capability reporting;
- strict warning policy;
- GitHub Action output files;
- CLI help, version, and exit-code behavior.
GitHub Actions repeats the suite on Ubuntu and in an Arch Linux container, dogfoods the local Action, and tests the clean npm package archive.
New hard failures must be deterministic and tied to official Omarchy source or documentation. Capability heuristics remain warnings by default. Every rule needs a minimal fixture and test.
Read CONTRIBUTING.md, then open a focused pull request. Report vulnerabilities through private vulnerability reporting.
- Contract diff bot for new Omarchy tags and branches
- Policy file for acknowledged capabilities and scoped exceptions
- Headless Quickshell experiment with an explicit runtime evidence level
- SARIF output for code scanning integrations
- Reusable starter repository for new Omarchy plugins
- Optional public compatibility index sourced only from signed CI reports
These are roadmap items, not features claimed by the beta.