omakit 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -45
- package/blocks/history.json +68 -0
- package/blocks/run/NOTICE +12 -0
- package/blocks/run/Run.qml +242 -0
- package/blocks/run/run-supervisor.py +522 -0
- package/blocks/store/NOTICE +12 -0
- package/blocks/store/Store.qml +157 -0
- package/blocks/store/store-helper.py +431 -0
- package/package.json +12 -5
- package/skills/omarchy-plugin-audit/SKILL.md +11 -5
- package/skills/omarchy-plugin-build/SKILL.md +164 -0
- package/skills/omarchy-plugin-check/SKILL.md +6 -3
- package/skills/omarchy-plugin-submit/SKILL.md +4 -1
- package/skills/omarchy-plugin-validation-watch/SKILL.md +5 -2
- package/skills/omarchy-plugin-weigh/SKILL.md +13 -2
- package/tests/fixtures/weigh/clean/Widget.qml +19 -0
- package/tests/fixtures/weigh/clean/manifest.json +9 -0
- package/tests/fixtures/weigh/clean/tests/harness.qml +7 -0
- package/tests/fixtures/weigh/idle-panel/Panel.qml +65 -0
- package/tests/fixtures/weigh/idle-panel/manifest.json +9 -0
- package/tests/fixtures/weigh/poller/Service.qml +50 -0
- package/tests/fixtures/weigh/poller/manifest.json +9 -0
- package/tests/fixtures/weigh/timer-180ms/Widget.qml +25 -0
- package/tests/fixtures/weigh/timer-180ms/manifest.json +9 -0
- package/tests/lab/run/harness/scenarios/controls.sh +6 -0
- package/tests/lab/run/harness/scenarios/envprobe.sh +10 -0
- package/tests/lab/run/harness/scenarios/forge.sh +11 -0
- package/tests/lab/run/harness/scenarios/holder.sh +5 -0
- package/tests/lab/run/harness/scenarios/orphan.sh +6 -0
- package/tests/lab/run/harness/scenarios/stall.sh +5 -0
- package/tests/lab/run/harness/scenarios/stubborn.sh +5 -0
- package/tests/lab/run/harness/scenarios/tree.sh +7 -0
- package/tests/lab/run/harness/shell.qml +84 -0
- package/tests/lab/run/report.py +217 -0
- package/tests/lab/run/suite.sh +106 -0
- package/tests/lab/store/harness/shell.qml +73 -0
- package/tests/lab/store/report.py +133 -0
- package/tests/lab/store/suite.sh +109 -0
- package/tests/parity/corpus.mjs +8 -3
- package/tests/parity/run.mjs +4 -4
- package/tools/audit/audit.mjs +17 -6
- package/tools/audit/git.mjs +3 -3
- package/tools/audit/report.mjs +31 -5
- package/tools/blocks/add.mjs +138 -0
- package/tools/blocks/commit.json +5 -0
- package/tools/blocks/record-commit.mjs +77 -0
- package/tools/blocks/registry.mjs +191 -0
- package/tools/blocks/stamp.mjs +61 -0
- package/tools/inspect/contract.mjs +36 -5
- package/tools/inspect/helpers.mjs +217 -0
- package/tools/inspect/inspect.mjs +68 -3
- package/tools/inspect/patterns.mjs +18 -3
- package/tools/inspect/processes.mjs +38 -5
- package/tools/inspect/report.mjs +18 -2
- package/tools/inspect/writes.mjs +22 -4
- package/tools/lab/guest.mjs +155 -0
- package/tools/lab/harness.sh +119 -0
- package/tools/lab/host.mjs +177 -0
- package/tools/lab/inspect.mjs +240 -0
- package/tools/lab/omarchy.gpg +13 -0
- package/tools/lab/patches/omarchy-iso-test.patch +351 -0
- package/tools/lab/paths.mjs +173 -0
- package/tools/lab/pin.json +42 -0
- package/tools/lab/pin.mjs +64 -0
- package/tools/lab/prune.mjs +68 -0
- package/tools/lab/qemu.mjs +153 -0
- package/tools/lab/qmp-cli.mjs +21 -0
- package/tools/lab/report.mjs +183 -0
- package/tools/lab/run.mjs +344 -0
- package/tools/lab/setup.mjs +430 -0
- package/tools/lab/suites/run.sh +35 -0
- package/tools/lab/suites/store.sh +41 -0
- package/tools/lab/suites/weigh.sh +196 -0
- package/tools/lab/suites.mjs +142 -0
- package/tools/lab/verify.mjs +134 -0
- package/tools/marketplace/README.md +38 -1
- package/tools/marketplace/banner.mjs +23 -2
- package/tools/marketplace/cli.mjs +449 -146
- package/tools/marketplace/completion-check.mjs +27 -1
- package/tools/marketplace/completion.mjs +32 -4
- package/tools/marketplace/doctor.mjs +47 -9
- package/tools/marketplace/github.mjs +52 -6
- package/tools/marketplace/local-transport.mjs +1 -1
- package/tools/marketplace/options.mjs +16 -5
- package/tools/marketplace/outcome.mjs +244 -0
- package/tools/marketplace/pin.mjs +178 -33
- package/tools/marketplace/setup.mjs +16 -15
- package/tools/marketplace/tree.mjs +1 -1
- package/tools/marketplace/upgrade.mjs +5 -5
- package/tools/marketplace/usage.mjs +116 -72
- package/tools/subject/resolve.mjs +19 -6
- package/tools/weigh/audit.mjs +47 -16
- package/tools/weigh/config.mjs +105 -24
- package/tools/weigh/list.mjs +10 -1
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: omarchy-plugin-build
|
|
3
|
+
description: Build the process and state plumbing of an Omarchy Quattro plugin with omakit's Run and Store blocks instead of a bare QML Process or a FileView write. Use whenever plugin code starts a program (a Process block, execDetached, a helper script, a poll) or keeps a file of its own (state, a cache, remembered choices), when inspect shows a process-lifecycle, unbounded-buffering, environment-trust or file-and-state-boundary row, or when a review comment names a deadline, an output cap, PATH, the environment, an orphaned process, a symlink, /tmp, an atomic write or a permission. Adds tested, versioned files the plugin owns; posts nothing.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Build with the Run and Store blocks
|
|
7
|
+
|
|
8
|
+
This is the build job: copy the measured process and private-state plumbing
|
|
9
|
+
into the plugin before checking, tracking or proving anything around it.
|
|
10
|
+
|
|
11
|
+
## Start every process through Run
|
|
12
|
+
|
|
13
|
+
Whenever plugin code starts a program, it goes through the Run block, never
|
|
14
|
+
through a bare `Process {`, `Quickshell.execDetached` or a shell string.
|
|
15
|
+
Add the block once, from the plugin's directory:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
omakit add run <path-to-the-plugin-repo>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
That writes `omakit/Run.qml`, `omakit/run-supervisor.py` and
|
|
22
|
+
`omakit/NOTICE` into the plugin's tree, each with a header naming the
|
|
23
|
+
block, its version, the MIT licence, the omakit commit and the body's
|
|
24
|
+
sha256, and nothing else. Commit them with the plugin: they are the
|
|
25
|
+
plugin's files now. Then, in the QML that starts the program:
|
|
26
|
+
|
|
27
|
+
```qml
|
|
28
|
+
import "omakit"
|
|
29
|
+
|
|
30
|
+
Run {
|
|
31
|
+
id: catalog
|
|
32
|
+
command: [Quickshell.env("HOME") + "/.config/omarchy/plugins/<id>/catalog.sh", "--refresh"]
|
|
33
|
+
deadlineMs: 30000
|
|
34
|
+
onFinished: result => {
|
|
35
|
+
if (result.state === "ok") model.parse(result.stdout)
|
|
36
|
+
else status.text = result.state + (result.reason ? ": " + result.reason : "")
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
and `catalog.start()` where the program should run. The rules Run holds,
|
|
42
|
+
and the count of review comments in one week that asked for each, are in
|
|
43
|
+
`docs/BLOCKS.md`. The ones that shape the call:
|
|
44
|
+
|
|
45
|
+
- `command[0]` is an absolute path. Omarchy's own commands are under
|
|
46
|
+
`Quickshell.env("HOME") + "/.local/share/omarchy/bin/"`; the plugin's
|
|
47
|
+
own helpers under its plugin directory; system tools under `/usr/bin/`.
|
|
48
|
+
A bare name is `spawn-failed` with the reason.
|
|
49
|
+
- `command` is argv. `["/usr/bin/bash", "-c", "..."]` is refused; write
|
|
50
|
+
the helper as a file and pass its path. `allowShellString: true` exists
|
|
51
|
+
for the one case that cannot be a file, on its own line, where a reviewer
|
|
52
|
+
sees it.
|
|
53
|
+
- The program sees `PATH=/usr/bin`, `HOME`, `LANG`, `XDG_RUNTIME_DIR` and
|
|
54
|
+
what `environment: ({ ... })` adds. Nothing else. A helper that needs a
|
|
55
|
+
variable gets it there, by name.
|
|
56
|
+
- `deadlineMs` is when the run ends whatever the program does; pick it for
|
|
57
|
+
the slow case and let `timeout` be the state that tells the user.
|
|
58
|
+
- `result.stdout` and `result.stderr` are bounded plain text with control
|
|
59
|
+
characters removed; bind them to a `Text` with `textFormat:
|
|
60
|
+
Text.PlainText`, never to rich text.
|
|
61
|
+
- `result.state` is one of `ok`, `exit`, `timeout`, `overflow`,
|
|
62
|
+
`cancelled`, `spawn-failed`, `supervisor-lost`, `python-missing`. Handle
|
|
63
|
+
`ok` and show the state word for the rest; every failure state carries
|
|
64
|
+
`reason` or `stderr` for the details.
|
|
65
|
+
- A `start()` on a live run cancels it and follows; destroying the
|
|
66
|
+
component ends the group. Do not add a `Timer` that kills, a
|
|
67
|
+
`Component.onDestruction` that signals, or a `StdioCollector`: the block
|
|
68
|
+
does those, and inspect knows it.
|
|
69
|
+
|
|
70
|
+
## Never edit the block's files
|
|
71
|
+
|
|
72
|
+
`omakit inspect` recognises an unmodified block by the sha256 in its header
|
|
73
|
+
and lists it as one row that raises nothing; a modified copy is reported
|
|
74
|
+
as modified and read like any other file, and `omakit add run --update`
|
|
75
|
+
refuses to overwrite it. If the block lacks something, the fix belongs in
|
|
76
|
+
omakit; open an issue there, keep the copy unmodified, and put what the
|
|
77
|
+
plugin needs on the plugin's side of the API.
|
|
78
|
+
|
|
79
|
+
Update the copy when omakit ships a new block version:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
omakit add run <path-to-the-plugin-repo> --update
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Helpers that also run outside Run
|
|
86
|
+
|
|
87
|
+
A helper Run starts inherits the closed environment, and so does every
|
|
88
|
+
program the helper starts by bare name: `jq`, `find`, `curl` inside it
|
|
89
|
+
resolve in `/usr/bin` and nowhere else, which is what the review asks for
|
|
90
|
+
at that boundary. Nothing in the helper has to change for that.
|
|
91
|
+
|
|
92
|
+
A helper that also runs where Run did not start it (a hook Omarchy runs
|
|
93
|
+
from `hooks/`, a test, a terminal) starts with these three lines, its
|
|
94
|
+
own, on the plugin's side:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
PATH=/usr/bin
|
|
98
|
+
unset BASH_ENV ENV
|
|
99
|
+
set -euo pipefail
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Check the result
|
|
103
|
+
|
|
104
|
+
After adding the block and moving each site to it, run the check loop of
|
|
105
|
+
`skills/omarchy-plugin-check/SKILL.md`. `omakit inspect` should show one
|
|
106
|
+
`blocks` line, `run 0.2.1, 2 files, unmodified: no row of its own`, each
|
|
107
|
+
`Run {` site as a process with its deadline observed through the block,
|
|
108
|
+
and no process-lifecycle or unbounded-buffering row at those sites. A row
|
|
109
|
+
that remains names a site that still uses `Process` directly.
|
|
110
|
+
|
|
111
|
+
## Keep state through Store
|
|
112
|
+
|
|
113
|
+
Whenever plugin code keeps a file of its own (remembered choices, a
|
|
114
|
+
cache it parses, anything under `~/.local/state` or `~/.cache`), it goes
|
|
115
|
+
through the Store block, never through a `FileView` write, a shell
|
|
116
|
+
redirect or a helper's own `open()`:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
omakit add store <path-to-the-plugin-repo>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
That writes `omakit/Store.qml` and `omakit/store-helper.py`, and the
|
|
123
|
+
run block if it is not there yet, since Store starts its helper through
|
|
124
|
+
Run. Then, in the QML that keeps the state:
|
|
125
|
+
|
|
126
|
+
```qml
|
|
127
|
+
import "omakit"
|
|
128
|
+
|
|
129
|
+
Store {
|
|
130
|
+
id: memory
|
|
131
|
+
pluginId: "<the plugin's id>"
|
|
132
|
+
name: "memory.json"
|
|
133
|
+
schema: ({ type: "object", required: ["version"], properties: { version: { type: "integer", minimum: 1 } }, additionalProperties: false })
|
|
134
|
+
onFinished: result => {
|
|
135
|
+
if (result.op === "read" && result.state === "ok") apply(result.value)
|
|
136
|
+
else if (result.state !== "missing") status.text = result.state + ": " + result.reason
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`memory.read()`, `memory.write(value)` and `memory.remove()` queue and run
|
|
142
|
+
one at a time; each answers once through `finished` with `op`, `state`
|
|
143
|
+
and, for a read, `value`. The rules Store holds and the review comments
|
|
144
|
+
that asked for each are in `docs/BLOCKS.md`; the ones that shape the call:
|
|
145
|
+
|
|
146
|
+
- `pluginId` names the private directory, `$XDG_STATE_HOME/<pluginId>`
|
|
147
|
+
(or the cache base with `kind: "cache"`), created 0700; `name` is one
|
|
148
|
+
file in it. Nothing else of the plugin's touches that directory.
|
|
149
|
+
- Always give a `schema`. A read that departs from it is `invalid` with
|
|
150
|
+
the path that departs, and the plugin shows the state word instead of
|
|
151
|
+
using the value; a write is checked the same way before it starts.
|
|
152
|
+
- A write is one value of at most 64 KiB. A cache a helper downloads is
|
|
153
|
+
the helper's own transaction (`store-helper.py` is the same code,
|
|
154
|
+
importable); Store keeps the plugin's state.
|
|
155
|
+
- `result.state` is one of `ok`, `missing`, `invalid`, `refused`,
|
|
156
|
+
`overflow`, `failed`, `helper-failed`. Treat `missing` as a first run
|
|
157
|
+
and every other non-`ok` state as "show the reason, keep the defaults".
|
|
158
|
+
- Do not stat, test or read the file by path first; the block never does,
|
|
159
|
+
and a check before the open is what the review calls check-then-use.
|
|
160
|
+
|
|
161
|
+
`omakit inspect` lists an unmodified store block as one row and each
|
|
162
|
+
`Store {}` site as a write under a directory the plugin controls at mode
|
|
163
|
+
0600; a `FileView` write or a shell redirect that remains names a site
|
|
164
|
+
that still keeps state on its own.
|
|
@@ -3,7 +3,10 @@ name: omarchy-plugin-check
|
|
|
3
3
|
description: Check an Omarchy Quattro plugin while building or changing it, before committing or pushing. Use whenever you create, edit, refactor or test an Omarchy plugin, when asked whether a plugin is marketplace-ready, or before any push to its default branch. Runs the marketplace's own security baseline and submission checks locally, read-only, reports what the marketplace would refuse, and lists what the tree does (processes, hosts, writes, timers) as observations beside the review classes a human reviewer raises most.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Check a plugin while building it
|
|
7
|
+
|
|
8
|
+
This is the check job: inspect the tree, run the marketplace's own baseline,
|
|
9
|
+
and run every submission check locally before a person sees the result.
|
|
7
10
|
|
|
8
11
|
## Run this the way you run a test suite
|
|
9
12
|
|
|
@@ -111,8 +114,8 @@ fetches a sparse read-only checkout of one marketplace commit under
|
|
|
111
114
|
|
|
112
115
|
`submit --json` ends with `outcome`:
|
|
113
116
|
|
|
114
|
-
- `ready`, exit 0: every blocking check passed
|
|
115
|
-
|
|
117
|
+
- `ready`, exit 0: every blocking check passed and the title and body were
|
|
118
|
+
produced. This is not review approval or a safety claim.
|
|
116
119
|
- `refused`, exit 1: a blocking check failed. `blocking` lists the root
|
|
117
120
|
causes; each check carries `detail`, `paths`, `remedy` and `why`.
|
|
118
121
|
- `listed`, exit 0: the plugin is already listed by this repository.
|
|
@@ -3,7 +3,10 @@ name: omarchy-plugin-submit
|
|
|
3
3
|
description: Submit an Omarchy Quattro plugin to the plugin marketplace on its owner's behalf. Use when asked to submit, list or publish a plugin to the Omarchy marketplace, or to check whether a plugin is ready to submit. Runs every pre-submission check, produces the exact issue title and body, and posts nothing.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Check before submitting
|
|
7
|
+
|
|
8
|
+
Submission is the last part of the check job. The command produces the exact
|
|
9
|
+
title and body after the local checks, and the owner decides what happens next.
|
|
7
10
|
|
|
8
11
|
## The one thing to get right
|
|
9
12
|
|
|
@@ -3,7 +3,10 @@ name: omarchy-plugin-validation-watch
|
|
|
3
3
|
description: Diagnose an Omarchy marketplace plugin submission that has gone quiet or is stuck waiting. Use when a submission issue has had no progress, when a reviewer asked for a fresh validation, or when fixes were pushed but nothing happened. Checks whether the validated commit has fallen behind the repository and names the one action that re-runs validation.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Track a submission that has gone quiet
|
|
7
|
+
|
|
8
|
+
This is one half of the track job: compare the commit the marketplace validated
|
|
9
|
+
with the repository's current commit, then name the next action without posting.
|
|
7
10
|
|
|
8
11
|
## The mechanism, first
|
|
9
12
|
|
|
@@ -23,7 +26,7 @@ comparisons were stale (62.8%), with 64 of 583 issues unknown. The older
|
|
|
23
26
|
2026-09-12 sample found 68/93 readable issues stale (73.1%); that rate was
|
|
24
27
|
not a measurement of all 464 issues in the queue.
|
|
25
28
|
|
|
26
|
-
##
|
|
29
|
+
## Run the check
|
|
27
30
|
|
|
28
31
|
```bash
|
|
29
32
|
omakit watch https://github.com/omacom/omarchy-plugin-marketplace/issues/<number>
|
|
@@ -3,7 +3,11 @@ name: omarchy-plugin-weigh
|
|
|
3
3
|
description: Weigh an Omarchy Quattro plugin on the shell, in CPU and child processes, by restarting the shell without it and with it. Use before a submission, after a change that adds a timer, a process or a file watcher, or when asked how heavy a plugin is. Restarts the person's shell, so it must never run without their explicit agreement.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Measure what a plugin weighs on the shell
|
|
7
|
+
|
|
8
|
+
Weigh supplies supporting evidence beside the four jobs. Prove remains the
|
|
9
|
+
disposable guest through `omakit lab prove`; weigh is the consented desktop A/B
|
|
10
|
+
measurement of CPU and child processes.
|
|
7
11
|
|
|
8
12
|
## The one thing to get right
|
|
9
13
|
|
|
@@ -34,7 +38,14 @@ one sentence naming what is missing, is how it refuses an Omarchy it cannot
|
|
|
34
38
|
weigh on: no `omarchy-shell` (an install older than the Quattro shell), no
|
|
35
39
|
`omarchy-restart-shell`, no readable version, a shell that does not answer
|
|
36
40
|
`ping`, or an IPC target without the four methods it relies on. Report that
|
|
37
|
-
sentence to the person as it is; do not work around it.
|
|
41
|
+
sentence to the person as it is; do not work around it. A
|
|
42
|
+
`shell.json.omakit-backup-<stamp>` already beside `shell.json` is refused
|
|
43
|
+
the same way (`backup-present`): an earlier measurement did not finish its
|
|
44
|
+
restore. Show the person the sentence and the restore it names; never copy,
|
|
45
|
+
move or remove that backup yourself, and never run the measurement in a
|
|
46
|
+
way that can be killed without notice (a tool timeout, a closing terminal):
|
|
47
|
+
a `SIGKILL` is the one exit that leaves `shell.json` on the measurement's
|
|
48
|
+
configuration.
|
|
38
49
|
|
|
39
50
|
## When to run it
|
|
40
51
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
|
|
3
|
+
// Nothing here wakes up. The word "Timer" in this comment must not count,
|
|
4
|
+
// and neither must the string below.
|
|
5
|
+
Item {
|
|
6
|
+
id: root
|
|
7
|
+
property var bar: null
|
|
8
|
+
property string moduleName: ""
|
|
9
|
+
property var settings: ({})
|
|
10
|
+
property string label: "Timer { interval: 1 }"
|
|
11
|
+
|
|
12
|
+
implicitWidth: text.implicitWidth
|
|
13
|
+
implicitHeight: text.implicitHeight
|
|
14
|
+
|
|
15
|
+
Text {
|
|
16
|
+
id: text
|
|
17
|
+
text: "clean"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import Quickshell
|
|
3
|
+
import Quickshell.Io
|
|
4
|
+
import Quickshell.Hyprland
|
|
5
|
+
|
|
6
|
+
Item {
|
|
7
|
+
id: root
|
|
8
|
+
property var shell: null
|
|
9
|
+
property bool opened: false
|
|
10
|
+
property int brightness: 0
|
|
11
|
+
property string state: ""
|
|
12
|
+
|
|
13
|
+
function open(payloadJson) { opened = true; refresh() }
|
|
14
|
+
function close() { opened = false; retry.stop() }
|
|
15
|
+
function refresh() { stateProc.running = true }
|
|
16
|
+
function setBrightness(value) { brightness = value; brightnessDebounce.restart() }
|
|
17
|
+
|
|
18
|
+
// Runs only while the panel is open: conditional, zero declared wakeups.
|
|
19
|
+
Timer {
|
|
20
|
+
interval: 5000
|
|
21
|
+
running: root.opened
|
|
22
|
+
repeat: true
|
|
23
|
+
onTriggered: root.refresh()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// One-shot debounce: never a wakeup source on its own.
|
|
27
|
+
Timer {
|
|
28
|
+
id: brightnessDebounce
|
|
29
|
+
interval: 180
|
|
30
|
+
repeat: false
|
|
31
|
+
onTriggered: root.setBrightness(root.brightness)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Declared off, started from code in two places.
|
|
35
|
+
Timer {
|
|
36
|
+
id: retry
|
|
37
|
+
interval: 1000
|
|
38
|
+
repeat: true
|
|
39
|
+
onTriggered: root.refresh()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Process {
|
|
43
|
+
id: stateProc
|
|
44
|
+
command: ["omarchy-monitor-state"]
|
|
45
|
+
stdout: StdioCollector { waitForEnd: true; onStreamFinished: root.state = String(text) }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
FileView {
|
|
49
|
+
id: toggles
|
|
50
|
+
path: Quickshell.env("HOME") + "/.local/state/omarchy/toggles"
|
|
51
|
+
watchChanges: true
|
|
52
|
+
onFileChanged: { reload(); retry.start() }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
Connections {
|
|
56
|
+
target: Hyprland
|
|
57
|
+
function onRawEvent(event) { retry.running = true }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Quickshell's clock ticks at its precision, once a minute here.
|
|
61
|
+
SystemClock {
|
|
62
|
+
id: clock
|
|
63
|
+
precision: SystemClock.Minutes
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "fixture.idle-panel",
|
|
4
|
+
"name": "Fixture: idle when closed",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"description": "A panel whose 5 s timer runs only while open, plus a debounce, a code-started timer and a once-a-minute SystemClock.",
|
|
7
|
+
"kinds": ["panel"],
|
|
8
|
+
"entryPoints": { "panel": "Panel.qml" }
|
|
9
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import Quickshell
|
|
3
|
+
import Quickshell.Io
|
|
4
|
+
|
|
5
|
+
QtObject {
|
|
6
|
+
id: root
|
|
7
|
+
property string loadavg: ""
|
|
8
|
+
property string helperPath: Quickshell.env("HOME") + "/.config/omarchy/plugins/fixture.poller/bin/helper"
|
|
9
|
+
|
|
10
|
+
// A poller: the timer restarts the process every 5 seconds (60 * 1000 / 12).
|
|
11
|
+
property Timer poll: Timer {
|
|
12
|
+
interval: 60 * 1000 / 12
|
|
13
|
+
repeat: true
|
|
14
|
+
running: true
|
|
15
|
+
triggeredOnStart: true
|
|
16
|
+
onTriggered: reader.running = true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
property Process reader: Process {
|
|
20
|
+
id: reader
|
|
21
|
+
command: ["cat", "/proc/loadavg"]
|
|
22
|
+
stdout: StdioCollector {
|
|
23
|
+
waitForEnd: true
|
|
24
|
+
onStreamFinished: root.loadavg = String(text).trim()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Long-lived: a watcher that stays up for the life of the service.
|
|
29
|
+
property Process watcher: Process {
|
|
30
|
+
id: watcher
|
|
31
|
+
command: [
|
|
32
|
+
"inotifywait", "-m", "-q",
|
|
33
|
+
"-e", "close_write",
|
|
34
|
+
"/tmp"
|
|
35
|
+
]
|
|
36
|
+
running: true
|
|
37
|
+
stdout: SplitParser { onRead: function(line) { root.loadavg = line } }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Dynamic: the executable is built from the plugin's own directory.
|
|
41
|
+
property Process helper: Process { id: helper; command: [root.helperPath, "status"]; running: true }
|
|
42
|
+
|
|
43
|
+
function refresh() {
|
|
44
|
+
helper.command = [root.helperPath, "refresh", "--now"]
|
|
45
|
+
helper.running = true
|
|
46
|
+
Quickshell.execDetached(["notify-send", "fixture", "refreshed"])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
Component.onCompleted: Quickshell.execDetached([root.helperPath, "hello"])
|
|
50
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "fixture.poller",
|
|
4
|
+
"name": "Fixture: poller",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"description": "A service that runs a command every 5 s, one long-lived watcher process, and one dynamic command.",
|
|
7
|
+
"kinds": ["service"],
|
|
8
|
+
"entryPoints": { "service": "Service.qml" }
|
|
9
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
|
|
3
|
+
Item {
|
|
4
|
+
id: root
|
|
5
|
+
property var bar: null
|
|
6
|
+
property string moduleName: ""
|
|
7
|
+
property var settings: ({})
|
|
8
|
+
property int ticks: 0
|
|
9
|
+
|
|
10
|
+
implicitWidth: text.implicitWidth
|
|
11
|
+
implicitHeight: text.implicitHeight
|
|
12
|
+
|
|
13
|
+
Text {
|
|
14
|
+
id: text
|
|
15
|
+
text: String(root.ticks)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
Timer {
|
|
19
|
+
id: tick
|
|
20
|
+
interval: 180
|
|
21
|
+
repeat: true
|
|
22
|
+
running: true
|
|
23
|
+
onTriggered: root.ticks++
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "fixture.timer-180ms",
|
|
4
|
+
"name": "Fixture: 180 ms timer",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"description": "A bar widget with one repeating 180 ms timer, always running.",
|
|
7
|
+
"kinds": ["bar-widget"],
|
|
8
|
+
"entryPoints": { "barWidget": "Widget.qml" }
|
|
9
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/bash
|
|
2
|
+
# Reports what a helper sees. env, sort, sed and wc are called by bare name
|
|
3
|
+
# on purpose: with shadow copies first in the session's PATH, a hit means the
|
|
4
|
+
# closed environment did not reach this line.
|
|
5
|
+
echo "PATH=$PATH"
|
|
6
|
+
echo "BASH_ENV=${BASH_ENV-unset}"
|
|
7
|
+
echo "PYTHONPATH=${PYTHONPATH-unset}"
|
|
8
|
+
echo "envcount=$(env | wc -l)"
|
|
9
|
+
env | sort | sed 's/^/env: /'
|
|
10
|
+
exit 0
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/bash
|
|
2
|
+
# A hostile program: same uid as its supervisor, so it can open the
|
|
3
|
+
# supervisor's stdout through /proc and write into the protocol pipe. It
|
|
4
|
+
# forges a leader line naming a group that is not its own and a result
|
|
5
|
+
# that says ok, then kills the supervisor and stays alive.
|
|
6
|
+
exec 3>"/proc/$PPID/fd/1"
|
|
7
|
+
printf '{"ev":"leader","pid":4194303,"pgid":4194303,"atMs":1}\n' >&3
|
|
8
|
+
printf '{"ev":"result","state":"ok","exitCode":0,"termSignal":null,"ms":1,"pgid":4194303,"survivors":0,"outBytes":6,"outLines":1,"errBytes":0,"errLines":0,"stdout":"forged\\u001b[31m\\n","stderr":"","signals":[]}\n' >&3
|
|
9
|
+
exec 3>&-
|
|
10
|
+
kill -9 "$PPID"
|
|
11
|
+
sleep 300
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// The lab harness: a throwaway Quickshell configuration that loads the Run
|
|
2
|
+
// block from ./omakit (copied there by suite.sh from blocks/run/) and runs
|
|
3
|
+
// one scenario named by RUNLAB_SCENARIO, logging JSON events with a RUNLAB
|
|
4
|
+
// prefix for the driver to read. Never loaded into omarchy-shell.
|
|
5
|
+
import QtQuick
|
|
6
|
+
import Quickshell
|
|
7
|
+
import "omakit"
|
|
8
|
+
|
|
9
|
+
ShellRoot {
|
|
10
|
+
id: root
|
|
11
|
+
property string scenario: Quickshell.env("RUNLAB_SCENARIO")
|
|
12
|
+
property string base: Quickshell.env("RUNLAB_BASE")
|
|
13
|
+
property int keep: Number(Quickshell.env("RUNLAB_KEEP") || 65536)
|
|
14
|
+
property var table: ({
|
|
15
|
+
"producer": { command: ["/usr/bin/head", "-c", "1073741824", "/dev/zero"], deadlineMs: 60000, graceMs: 1000, maxBytes: 1048576 },
|
|
16
|
+
"producer-stream": { command: ["/usr/bin/head", "-c", "1073741824", "/dev/zero"], deadlineMs: 120000, graceMs: 1000, maxBytes: 2147483647 },
|
|
17
|
+
"holder": { command: ["/usr/bin/bash", base + "/scenarios/holder.sh"], deadlineMs: 10000, graceMs: 1000, maxBytes: 65536 },
|
|
18
|
+
"stubborn": { command: ["/usr/bin/bash", base + "/scenarios/stubborn.sh"], deadlineMs: 2000, graceMs: 1000, maxBytes: 65536 },
|
|
19
|
+
"hostile": { command: ["/usr/bin/bash", base + "/scenarios/envprobe.sh"], deadlineMs: 5000, graceMs: 1000, maxBytes: 65536 },
|
|
20
|
+
"destroy": { command: ["/usr/bin/bash", base + "/scenarios/tree.sh"], deadlineMs: 10000, graceMs: 1000, maxBytes: 65536, destroyAfterMs: 500 },
|
|
21
|
+
"cancel": { command: ["/usr/bin/bash", base + "/scenarios/tree.sh"], deadlineMs: 10000, graceMs: 1000, maxBytes: 65536, cancelAfterMs: 500 },
|
|
22
|
+
"supersede": { command: ["/usr/bin/bash", base + "/scenarios/tree.sh"], deadlineMs: 10000, graceMs: 1000, maxBytes: 65536, supersedeAfterMs: 500 },
|
|
23
|
+
"shell-string": { command: ["/usr/bin/bash", "-c", "echo never"], deadlineMs: 5000, graceMs: 1000, maxBytes: 65536 },
|
|
24
|
+
"relative": { command: ["bash", base + "/scenarios/envprobe.sh"], deadlineMs: 5000, graceMs: 1000, maxBytes: 65536 },
|
|
25
|
+
"missing": { command: ["/usr/bin/no-such-program-runlab"], deadlineMs: 5000, graceMs: 1000, maxBytes: 65536 },
|
|
26
|
+
"controls": { command: ["/usr/bin/bash", base + "/scenarios/controls.sh"], deadlineMs: 5000, graceMs: 1000, maxBytes: 65536 },
|
|
27
|
+
"concurrency": { command: ["/usr/bin/head", "-c", "1048576", "/dev/zero"], deadlineMs: 30000, graceMs: 1000, maxBytes: 2097152, count: 10 },
|
|
28
|
+
"forge": { command: ["/usr/bin/bash", base + "/scenarios/forge.sh"], deadlineMs: 5000, graceMs: 1000, maxBytes: 65536 },
|
|
29
|
+
"orphan": { command: ["/usr/bin/bash", base + "/scenarios/orphan.sh"], deadlineMs: 5000, graceMs: 1000, maxBytes: 65536 },
|
|
30
|
+
"stalled-supersede": { command: ["/usr/bin/bash", base + "/scenarios/stall.sh"], deadlineMs: 2000, graceMs: 500, maxBytes: 65536, supersedeAfterMs: 500 },
|
|
31
|
+
"destroy-early": { command: ["/usr/bin/bash", base + "/scenarios/tree.sh"], deadlineMs: 10000, graceMs: 1000, maxBytes: 65536, destroyBusyMs: 300 },
|
|
32
|
+
"shell-string-option": { command: ["/usr/bin/bash", "-o", "pipefail", "-c", "echo never"], deadlineMs: 5000, graceMs: 1000, maxBytes: 65536 },
|
|
33
|
+
"shell-string-wrapper": { command: ["/usr/bin/env", "bash", "-c", "echo never"], deadlineMs: 5000, graceMs: 1000, maxBytes: 65536 }
|
|
34
|
+
})
|
|
35
|
+
property var runs: []
|
|
36
|
+
property int finishedCount: 0
|
|
37
|
+
|
|
38
|
+
function log(o) { console.log("RUNLAB " + JSON.stringify(o)) }
|
|
39
|
+
|
|
40
|
+
property Component runComponent: Component { Run {} }
|
|
41
|
+
|
|
42
|
+
function create(spec, index) {
|
|
43
|
+
const object = root.runComponent.createObject(root, { command: spec.command, deadlineMs: spec.deadlineMs, graceMs: spec.graceMs, maxBytes: spec.maxBytes, keepBytes: root.keep })
|
|
44
|
+
object.finished.connect(result => { result.ev = "result"; result.index = index; root.finishedCount += 1; root.log(result); if (root.finishedCount === (spec.count || 1)) root.log({ ev: "all-finished", t: Date.now() }) })
|
|
45
|
+
return object
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
property Timer starter: Timer {
|
|
49
|
+
interval: 3000; running: true
|
|
50
|
+
onTriggered: {
|
|
51
|
+
const spec = root.table[root.scenario]
|
|
52
|
+
const count = spec.count || 1
|
|
53
|
+
const objects = []
|
|
54
|
+
for (let index = 0; index < count; index += 1) objects.push(root.create(spec, index))
|
|
55
|
+
root.runs = objects
|
|
56
|
+
root.log({ ev: "start", scenario: root.scenario, count: count, t: Date.now() })
|
|
57
|
+
for (const object of objects) object.start()
|
|
58
|
+
if (spec.destroyBusyMs) {
|
|
59
|
+
// The event loop is held after start(), so the supervisor has
|
|
60
|
+
// forked the program but this side has not read its leader line
|
|
61
|
+
// when the object is destroyed on the next turn.
|
|
62
|
+
const until = Date.now() + spec.destroyBusyMs
|
|
63
|
+
while (Date.now() < until) {}
|
|
64
|
+
root.log({ ev: "destroy", pgid: objects[0]._pgid, t: Date.now() })
|
|
65
|
+
objects[0].destroy(); root.runs = []
|
|
66
|
+
}
|
|
67
|
+
if (spec.destroyAfterMs) { root.later.mode = "destroy"; root.later.interval = spec.destroyAfterMs; root.later.start() }
|
|
68
|
+
if (spec.cancelAfterMs) { root.later.mode = "cancel"; root.later.interval = spec.cancelAfterMs; root.later.start() }
|
|
69
|
+
if (spec.supersedeAfterMs) { root.later.mode = "supersede"; root.later.interval = spec.supersedeAfterMs; root.later.start() }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
property Timer later: Timer {
|
|
73
|
+
property string mode: ""
|
|
74
|
+
onTriggered: {
|
|
75
|
+
const object = root.runs[0]
|
|
76
|
+
root.log({ ev: mode, pgid: object._pgid, t: Date.now() })
|
|
77
|
+
if (mode === "destroy") { object.destroy(); root.runs = [] }
|
|
78
|
+
else if (mode === "cancel") object.cancel()
|
|
79
|
+
else if (mode === "supersede") { object.command = ["/usr/bin/bash", root.base + "/scenarios/envprobe.sh"]; object.start() }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
Component.onCompleted: log({ ev: "ready", pid: Quickshell.processId, scenario: scenario })
|
|
84
|
+
}
|