Vee documentation
Writing plugins with an LLM
A Vee plugin is a small, self-contained script with a strict output format and a fast way to check it. That shape suits an LLM well — but only if you give it the format and a way to verify what it wrote. This page is how.
Give it the format, not a search result
The whole plugin documentation is published as one file:
https://vee.navbytes.io/llms-full.txtPaste it, or point a tool that can fetch URLs at it. It is about 140 KB of Markdown — one comfortable request, and it contains every page of this guide.
If you want less, llms.txt is an index of
every page with a one-line description, and each guide page is available as
Markdown by swapping the extension:
https://vee.navbytes.io/guide/plugin-authoring.html ← the page you read
https://vee.navbytes.io/guide/plugin-authoring.md ← the source a model readsFor most plugin work, plugin-authoring.md alone is enough context. Add widgets.md if the plugin renders a widget, and json-output.md if it emits JSON.
Give it the schemas, not a description of them
If the plugin prints structured output, hand over the schema rather than prose. It encodes every field, enum, and clamp, and CI proves it matches what Vee actually accepts:
https://vee.navbytes.io/schemas/widget-card.schema.json
https://vee.navbytes.io/schemas/json-output.schema.jsonA model that has the schema will not invent a template that does not exist or a
progress value outside 0…1. One that is working from a paragraph might.
Close the loop with vee lint
This is the part that matters. A generated plugin is plausible; vee lint
makes it correct:
vee lint ./cpu.30s.shIt exits non-zero on anything it flags, so it works as the check in an agent loop: generate, lint, feed the findings back, repeat until clean. For a plugin that is not executable yet, or when you want to iterate on the output shape before writing the script, lint the protocol text directly:
vee lint --text ./menu.txtThen see what Vee would actually build from it:
vee dev --text ./menu.txtSee Debugging and testing plugins for both commands in full, and Troubleshooting for what each diagnostic means.
The mistakes to watch for
vee lint exists because these are the mistakes people make when hand-writing
the format. They are the same ones a model makes, and most of them produce a
plugin that runs while rendering something subtly wrong — which is why reading
the output is not enough on its own.
- An unescaped
|in display text. The first|on a line separates text from parameters, so a literal pipe truncates the item. It must be\|. The SDKs escape this for you; hand-written output does not. - Unquoted parameter values containing spaces.
tooltip=two wordssilently keeps onlytwo. It needstooltip="two words". - Confusing the first
---with the rest. The first one splits the menu-bar title from the dropdown; every later one is a divider. - Assuming indentation nests. Submenus nest with leading
--, not whitespace, and each extra--is one more level. - Inventing parameters. Vee preserves unknown parameters rather than
erroring, so a plausible-but-nonexistent one renders nothing and says nothing.
vee lintis what tells you. The compatibility matrix is the list of what is real, and which tool it came from. - Forgetting the filename carries the interval.
cpu.shruns once on demand;cpu.30s.shruns every thirty seconds. The plugin also needschmod +x. - Expecting state between runs. Every refresh is a fresh process. Anything
that must persist goes in
SWIFTBAR_PLUGIN_CACHE_PATH, not a variable.
Be specific about the surface
"Write a Vee plugin" underspecifies three things worth stating outright: which language (any executable works — shell, Python, and TypeScript are the common ones), which surface (menu bar, widget, or both), and what the plugin should do when its dependency, token, or network is missing. That last one matters more than it sounds: a plugin that degrades to a useful "not configured" row beats one that prints a stack trace into your menu bar.
If the plugin touches the network, a secret, the filesystem, or another binary,
ask for the <vee.*> trust declarations too — and check them
against what the code actually does. They are advisory, and a declaration that
does not match its behavior is worse than none.
See also
- Plugin authoring reference — the format itself.
- Debugging and testing plugins —
vee lint,vee dev, and the rest of the loop. - Plugin SDKs — typed builders that make whole classes of the mistakes above impossible.
- Trust model — declaring what a plugin touches.