Vee

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:

text
https://vee.navbytes.io/llms-full.txt

Paste 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:

text
https://vee.navbytes.io/guide/plugin-authoring.html   ← the page you read
https://vee.navbytes.io/guide/plugin-authoring.md     ← the source a model reads

For 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:

text
https://vee.navbytes.io/schemas/widget-card.schema.json
https://vee.navbytes.io/schemas/json-output.schema.json

A 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:

sh
vee lint ./cpu.30s.sh

It 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:

sh
vee lint --text ./menu.txt

Then see what Vee would actually build from it:

sh
vee dev --text ./menu.txt

See 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 words silently keeps only two. It needs tooltip="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 lint is 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.sh runs once on demand; cpu.30s.sh runs every thirty seconds. The plugin also needs chmod +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