Imported from vanillagreencom/vgs (
.agents/skills/vgs-plugin/SKILL.md). Install upstream withnpx skills add vanillagreencom/vgs --skill vgs-plugin. Copyright stays with the author (MIT).
Project Instructions
Problems with a kendex-owned skill go through kendex report; check ownership in the file first.
VGS delivery policy overrides the shared workflow's mandatory review, CI-wait, branch-size and commit-hook gates. Work targets main. Reviews and PRs are optional; push directly or merge a PR immediately with gh, without a review loop, queue or CI wait. Do not arm git guards. Run scripts/validate once on the final relevant diff; use --changed for a fix round, and reuse passing results while those inputs are unchanged. Do not rerun a full battery at commit, push or submission. Use --full only for an explicit full sweep or an unmapped dependency. Keep the live-session safety rules.
vgs-plugin
Write a plugin for the v2 shell. The contract is docs/architecture/plugins.md; this skill holds the steps, the scaffold command and the templates.
.agents/skills/vgs-plugin/scripts/vgs-plugin new acme.weather --kinds bar-widget,service
.agents/skills/vgs-plugin/scripts/vgs-plugin check shell/plugins/acme.weather
Rules
-
Consumer text follows
docs/architecture/copy.md. -
One directory, one
manifest.jsonat its root, one QML entry point per kind. Copy the templates. The field table isdocs/architecture/plugin-manifest.md§ Manifest; an unknown key is refused. -
Declare surfaces, never dependencies. Every external command the plugin runs goes in the manifest's
requirements, with its package per manager,optionalwhen the plugin works without it, and a one-linepurpose; a requirement names a command, never another plugin:docs/architecture/requirements.md. The core shows its notice when the plugin is installed or enabled without a command it needs; a button that asks the user to install one of the plugin's own commands callsshell.requirements.offer, never a package manager. -
Imports and names: the allowed table in
references/api.md§ Allowed imports, and nothing else.scripts/check-plugin-boundary.pyrefuses the rest. -
Every entry point declares
property var shell: nullor inherits it fromBarWidget. Capabilities come fromshell.<name>after naming them incapabilities; a widget never reads them frombar. -
Settings default in the manifest's
settingsand arrive asshell.settingsfor every kind; a bar widget also reads them withsetting(name, fallback). A change reaches the running instance as a newshell; hold no copy. -
The manifest is the plugin's settings page: name an
iconfrom the shipped Lucide set, and give every setting a user would reasonably change, and the plugin reads, aschemaentry, withmin,max,stepanduniton a number,presetson a format-like value,allowCustomonly when custom values are useful, and agroupfor its section; a string withoutpresetsoroptionsFromis refused. A constant earns an entry only when a user has a reason to change it. A key ishyprland.binds, never a schema entry; the Settings page's Keys row captures a pressed combo for it withShortcutField. The Settings window draws the page from these alone:docs/architecture/plugin-manifest.md§ Manifest, D032, D077. -
A bar widget extends
BarWidgetfromqs.Uiand sizes itself withimplicitWidthandimplicitHeight. The core assignsbar,moduleNameandsettingsafter creation; the widget sets none of them. -
A bar declares
leftSection,centerSectionandrightSection. The core mounts every plugin widget; a widget the bar draws itself registers throughshell.builtins. -
A surface the user works in, moves or tiles, such as a settings or tools window, is kind
window, a Hyprland window; a section inside a System-style holder is kindpaneand starts fromtemplates/Pane.qml; a flyout from a widget is an anchoredpanelormenu, which closes on a click outside it:docs/architecture/surfaces.md, D044. -
Compose the components of
qs.Ui(references/api.md§ Components inqs.Ui) before drawing anything by hand. Every colour, size, font, radius, opacity and duration reads a token throughThemeinqs.Commons.scripts/check-design-tokens.pyrefuses aThemepath that names no token and reports each literal it finds. -
Keyboard support is first class: follow
docs/architecture/keyboard.mdfor each pointer action and focusable control. -
Before choosing a default key, check common Hyprland bindings and avoid Super with H/J/K/L, W/A/S/D or a digit.
-
A list whose rows each take one selection declares one
ListCursorfromqs.Uiand names it in each row'scursor; keys and hover write the list's one selection, and no row draws its own hover or highlight fill:docs/architecture/motion.md. -
A plugin whose design must look the same under every theme owns its look instead: the manifest's
appearancenames a table of its own, every value islook.<path>fromTheme.appearance(TOKENS, LIGHT), and the theme reaches it through its mode, accent and motion scale alone.docs/architecture/appearance.mdis the contract; it reads no otherThememember. -
One owner per timer, watcher, poller and subprocess, inside the entry point's tree. A
Processgets its stdout parser before it starts. -
A value the plugin's widgets, flyout or Settings page show is plugin status: declare it in the manifest's
status, name capabilitystatus, write it from the one instance that owns its source, the service, withshell.status.set, and read it fromshell.status.values. One writer per key; a credential's value never enters status, only its presence.docs/architecture/status.md, D037. -
A plugin that needs a Hyprland key, a blur rule or an input option declares it as data in the manifest's
hyprlandkey and never writes Hyprland configuration: the core renders it into the one Hyprland layer,docs/architecture/hyprland.md. An input option maps a schema setting throughhyprland.optionsand reads Hyprland back through capabilityhyprland, never its ownhyprctl:docs/architecture/hyprland-options.md. A plugin reads the outputs through capabilitymonitorsand sets no monitor rule, throughhyprctl keyword,evalor a file: the user's own Hyprland config sets every output,docs/architecture/hyprland-monitors.md. -
No manual commands (D061): a setup step is automatic or one click. Give the status entry that shows it an
action, the plugin's own TUI, its own requirement commands or a step of the core's root setup table it declares insystemSteps(docs/architecture/tui-system.md), and publish when it applies; a credential is apresenceListitem naming its account of the manifest'ssecrets, which the Settings page connects through a masked field. A command a user could run shows only as the entry'scommand, behind Show command, never in a README step, a hint or a notice:docs/architecture/status-actions.md§ Actions and secrets.vgs-plugin checkrunsscripts/check-user-commands.py. -
Consumer features need no developer setup (D075). A feature that needs an app, API key or token the user must create is an owner-only extra: a setting off by default with no
schemaentry, named in the manifest'sextraswith the status entries and optional requirements only it uses, which the core hides while it is off, and documented under "Extras (not supported)" in the README:docs/architecture/plugin-manifest.md§ Manifest. -
A command the user must see or answer runs in a floating TUI: a script under the plugin's
tui/, declared as data in the manifest'stuikey and opened withshell.tui.run, never a command string:docs/architecture/tui-capability.md§ The capability. -
No cache keyed by data other applications supply without a ceiling.
-
Every Quickshell type, property and signal comes from the 0.3.1 reference on Context7:
ctx7 docs /websites/quickshell_v0_3_1 <query>. -
A first-party plugin's README shows a screenshot made by
scripts/readme-shots.sh, with its row indocs/images/plugins/shots.tsv:docs/architecture/readme-images.md. -
Land with
scripts/validate manifests,scripts/validate boundaryandscripts/validate qmlclean, the last with the readback rowsworkflows/new-plugin.mdstep 9 names.
Workflows
| Workflow | Trigger |
|---|---|
workflows/new-plugin.md |
Creating a plugin from nothing |
workflows/review-plugin.md |
Reviewing or changing an existing plugin |
References
references/api.md: what a plugin receives and may call, per kind.templates/:manifest.json.tmpl,BarWidget.qml,Service.qml,Panel.qml,Window.qml,Pane.qml,Bar.qml,Background.qml,tui.sh.
