Plugin commands
A plugin declared in .vx/workspace.ts can add verbs:
import { definePlugin, type VxPlugin } from '@vzn/vx'
export function mcp(): VxPlugin { return definePlugin(import.meta, { commands: { mcp: { description: 'serve the run history to an AI agent over stdio', async run(argv, ctx) { // ctx.workspaceRoot, ctx.cacheDir, ctx.concurrency, ctx.cache, ctx.warn(...) return 0 // the process exit code }, }, }, })}The plugin’s name is its package name, which definePlugin reads from
import.meta; a plain object is refused when the workspace loads.
ctx.cache reads the plugin’s own entries as setup writes them (the
local store, then each remote the workspace’s cache plugins supply,
under the run’s scope), read-only, opened on first use.
(@vzn/vx-history ships exactly this: vx history, what it
learned per task and the reservation its admit hook packs.) The dispatcher tries core’s verbs
first and consults plugins only for a word core does not know, loading
the workspace config from the cwd to find them (outside a workspace the
verb is simply unknown). A plugin verb that names a core verb, or one
plugins of two packages both declare, is refused when the workspace
loads — such a verb could never run, or would hide the other plugin’s.
Plugins of one package are one owner (two of its plugins may carry the
same verb): the first declared runs. A plugin verb’s
return value is the exit code, an integer 0–255 (anything else — nothing,
a fraction, 256, which the OS would keep as 0 — fails naming the plugin
and the verb), and a thrown UserError prints as
cleanly as core’s own. Anything else thrown fails in one line, no stack:
plugin '<p>' failed in command '<verb>': <msg>. vx help lists every plugin verb under “Plugin
commands”, with the plugin’s name.
An unknown verb is answered with what this workspace knows: the “did you
mean” set is core’s verbs AND the ones its plugins declare (the lookup
that just failed already loaded them, so it costs nothing), and when
nothing is close enough to guess, a second line says where a verb can
come from — the verbs declared here, or that .vx/workspace.ts is what
would declare one, or that there is no workspace here at all. Core still
names no package: it lists what the workspace itself declares.