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.ts as 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 absent remove) is a reported no-op that writes nothing.
  • Failures roll back. create removes 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.

A package must already be installed

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.