Plugins
Extend the nub CLI with your own subcommands — an unknown verb resolves to an executable named after it and runs, the same convention git and cargo use for their plugins.
Run nub <verb> with a verb nub doesn't recognize and it resolves an executable named nub-<verb>, then runs it with the rest of the arguments forwarded untouched. This is the same convention behind git-foo and cargo-foo.
nub changeset publish # runs nub-changeset publish
nub release --tag beta # runs nub-release --tag betaA plugin is just a package that installs a nub-<verb> bin — there is no plugin manifest, no registration step, and no config field to declare it. Install it like any other dependency and the verb works.
nub add -D nub-changeset # ships a nub-changeset bin
nub changeset # now resolves and runs itResolution
The prefixed name is resolved in two places, in order:
node_modules/.bin— the project-local bin, walking up from the current directory (the same chainnub runandnubxuse).PATH— a globally-installed plugin.
Only the prefixed nub-<verb> name is ever probed — never the bare verb. A mistyped command like nub bnuild resolves nothing and prints the usual not-a-command error; it can't fall through to a random bnuild binary on your PATH.
Built-ins always win
A plugin can never shadow a built-in verb. Native commands and package-manager verbs are matched before any plugin lookup, so nub run, nub install, nub add, and the rest always dispatch to nub itself, even if a nub-run bin exists in node_modules/.bin. The fallthrough fires only for a verb nub has no built-in for.
Script names take the same priority. A verb that names a script in the local package.json, or one of the conventional names dev, build, test, start, and lint, resolves to the nub run <verb> hint before the plugin lookup — so a nub-build or nub-test plugin is never reached through its bare verb. Name a plugin for something that isn't a script (nub-changeset, nub-release), or invoke a script-named one through nub run or by its bin directly.
Writing a plugin
A plugin is a runnable executable named nub-<verb> — a compiled JavaScript bin or a native binary, the same as any other node_modules/.bin entry or a git/cargo subcommand. There's no plugin-specific build target: author it in TypeScript and publish the compiled output, exactly as you'd ship any other bin. Installed bins aren't transpiled — the byte-parity boundary that keeps node_modules identical to plain Node — so the entry that actually runs has to be JavaScript or native.
A JavaScript plugin runs under nub's runtime augmentation, exactly as nub run runs a script — the version-gated globals are present. A native executable just runs. The plugin's exit code is what nub returns.
#!/usr/bin/env node
// nub-changeset — authored in TypeScript, published as compiled JS
const target = process.argv[2] ?? "staging";
console.log(`releasing ${target}`);To run a plugin with augmentation disabled, pass --node before the verb — nub --node changeset, the same position a file run uses. Anything after the verb is forwarded to the plugin untouched, so a trailing nub changeset --node reaches the plugin as an argument and does not disable augmentation. See the runtime overview for the contract.
Related
- Package runner —
nubx, for running a tool by name without installing it as a subcommand. - Running scripts —
nub run, which shares the bin-resolution chain. - Runtime — TypeScript-first execution and the augmentation a JS/TS plugin inherits.
Package-manager shims
An opt-in for muscle memory. Install the shims and a bare pnpm, npm, or yarn command routes through Nub to the package manager your project pins, with no extra Node process in front.
FAQ
The short, indexed answers to common questions about Nub — what it is, how it works, and what it deliberately is not.