The owlat plugins CLI
Scaffold, add, remove, and regenerate bundled plugin composition with owlat-plugins.
@owlat/plugin-cli (binary owlat-plugins) is the lifecycle tool for the bundled plugin set. It operates on the checked-in plugins.config.ts composition point and delegates every composition decision to @owlat/plugin-codegen — it never re-implements composition.
Run it from anywhere inside the Owlat workspace. From a source checkout:
bun run plugins:prepare # build the plugin-kit contracts once
bun packages/plugin-cli/src/index.ts --help
owlat plugins — manage bundled Owlat plugins
Usage: owlat plugins <command> [options]
Commands:
create <plugin-id> [--name <package>] [--dir <path>] [--template <name>]
[--dry-run]
Scaffold a new plugin package (never installs or executes code).
--template minimal (default) emits an empty manifest; --template
send-provider emits a complete send-transport bundle: send module,
feedback webhook, sending-domain identity, capabilities and test stubs.
add <package> [--dry-run]
Add a bundled plugin package to plugins.config.ts and preview its
capability diff. --dry-run previews without writing.
remove <package> [--dry-run]
Remove a bundled plugin package from plugins.config.ts.
codegen [--check] [--boundaries-only]
Regenerate (or check) the bundled composition via @owlat/plugin-codegen.
dev
Regenerate the composition and re-run on every plugins.config.ts change.
Run from anywhere inside the Owlat workspace.
Safety properties
These hold for every command, and they are what make the CLI safe to run against a production checkout:
- It never evaluates
plugins.config.tsas code. The config is parsed as static data. - It never imports an arbitrary path. The only module loading it performs is delegated to the codegen's verified loader, which imports exclusively the lockfile-pinned, provenance-checked manifest entry of each bundled package — never a contribution or component module.
- Validation precedes mutation. The proposed package set is fully loaded and validated before any file is written; a missing, mis-pinned, or invalid package fails with an actionable error and an unchanged config.
- Edits are deterministic and idempotent. Re-running produces byte-identical output; an already-listed
add(or an absentremove) is a reported no-op that writes nothing. - Failures roll back.
createremoves every file and directory it created if any write fails; a failed config write leaves the config unchanged.
create
owlat-plugins create hello-owlat [--name @acme/owlat-hello] [--dir plugins/hello] [--dry-run]
Scaffolds a plugin package: package.json, tsconfig.json, vitest.config.ts, README.md, src/manifest.ts, src/index.ts, and a manifest test. Content is a pure function of the id, package name, and the directory's position in the workspace — no timestamps, no randomness.
Defaults: --name @owlat/plugin-<id>, --dir examples/plugins/<id>. Re-running on an unchanged scaffold is a no-op; a file that exists with different content aborts the run rather than clobbering it.
add / remove
owlat-plugins add @vendor/owlat-plugin-example --dry-run
owlat-plugins add @vendor/owlat-plugin-example
owlat-plugins remove @vendor/owlat-plugin-example
Both print a capability diff before writing: the plugins added or removed, and the capabilities the composition gains or loses as a union across the bundled set. Read this diff — for a bundled plugin it is the complete list of host operations the package can ever request.
--dry-run prints the diff plus the exact proposed plugins.config.ts and writes nothing.
If the current set cannot be loaded — for example the package you are removing is itself broken — the proposed set is still validated and the mutation still proceeds; the diff reports the before-state as unavailable instead of silently showing misleading arithmetic.
add only edits the composition list. The package must already be a direct root dependencies / optionalDependencies entry installed from the registry, or the verified loader rejects it: "Bundled plugin <name> must be installed as a root dependency or optionalDependency".
codegen
owlat-plugins codegen # regenerate the composition files
owlat-plugins codegen --check # fail if output is missing or stale (CI)
owlat-plugins codegen --boundaries-only # only enforce the import boundary
Regenerates the checked-in Convex and Nuxt composition modules, the Convex component installer, and every contribution catalog/registry pair. --boundaries-only enforces the rule that core source may not import a configured plugin package outside those generated files — including through Node/Bun loaders or repository aliases — without importing plugin code at all.
Equivalent workspace scripts, which are what CI and the build graph run:
bun run plugins:codegen # regenerate
bun run plugins:check # --check
bun run lint:plugin-imports # --boundaries-only
bun run plugins:check is part of ci:lint, ci:verify, and the turbo build and deploy tasks, so a stale or invalid composition fails the build closed.
dev
owlat-plugins dev
Regenerates the composition, then re-runs it on every change to plugins.config.ts. Useful while iterating on the bundled set; it is not a substitute for committing the generated output.
Commit discipline
Generated composition files are checked in. Always commit the config edit and the regenerated output together — a generated artifact belongs in the same commit as the input that produced it, and plugins:check will fail CI otherwise.