The emdash-plugin CLI
@emdash-cms/plugin-cli is the authoring toolchain: scaffold, build, watch, validate, bundle, publish, plus identity and discovery. The binary is emdash-plugin.
Commands
Section titled “Commands”The CLI provides the following commands:
emdash-plugin init [name] Scaffold a new sandboxed pluginemdash-plugin build Build dist/ (plugin.mjs, manifest.json, index.mjs)emdash-plugin dev Watch sources and rebuild on changeemdash-plugin bundle Pack dist/ + assets into a registry tarballemdash-plugin validate [path] Validate emdash-plugin.jsonc against the schemaemdash-plugin publish --url <url> Publish a release pointing at a hosted tarballemdash-plugin login <handle-or-did> Sign in with your Atmosphere accountemdash-plugin logout [--did <did>] Revoke the active sessionemdash-plugin whoami Show stored sessionsemdash-plugin switch <did> Switch the active publisher sessionemdash-plugin search <query> Free-text registry searchemdash-plugin info <handle-or-did> <slug> Show package detailsThe non-interactive output commands (whoami, validate, search, info, login, publish) accept --json for machine-readable output. Discovery commands (search, info) accept --registry-url <url> (or EMDASH_REGISTRY_URL).
The following example shows the two scripts most plugins add to package.json:
{ "scripts": { "build": "emdash-plugin build", "dev": "emdash-plugin dev" }}Create a new plugin with init:
npx @emdash-cms/plugin-cli init my-pluginThis scaffolds a self-contained plugin: emdash-plugin.jsonc, src/plugin.ts (one example route in the satisfies SandboxedPlugin shape), package.json, tsconfig.json, a test, a README, and .gitignore. A slug is the only required input. A scaffold created from just a slug is a valid starting point: the manifest carries TODO: comments for the few fields to fill in — publisher, author, and security contact — before the plugin will load or publish.
build reads emdash-plugin.jsonc, src/plugin.ts, and an optional sibling package.json, and emits the following files:
| Artifact | What it is |
|---|---|
dist/plugin.mjs (+ dist/plugin.d.mts) | The hooks and routes. Loaded in-process (plugins: []) and by the sandbox loader (sandboxed: []). |
dist/manifest.json | The plugin’s manifest, including the hooks and routes read from src/plugin.ts. bundle includes this file as-is; npm consumers read it without parsing the JSONC source. |
dist/index.mjs (+ dist/index.d.mts) | The descriptor module a site imports in astro.config.mjs. Emitted only when a sibling package.json exists; registry-only plugins skip it, since nothing imports it. |
dist/ is build output. Do not commit it. The scaffold’s .gitignore excludes it, and installs rebuild it.
Watches src/**, emdash-plugin.jsonc, and package.json, debouncing rebuilds at 150 ms. Rebuilds are serialised. On a failed rebuild it leaves the last good dist/ in place, so a site importing the plugin via a workspace/file link keeps working until the next successful build. Ctrl-C drains cleanly.
Develop against a real site by running pnpm dev here and pnpm add file:../path/to/this in the site, then importing the plugin’s default export into emdash({ sandboxed: [...] }).
validate
Section titled “validate”emdash-plugin validate # ./emdash-plugin.jsoncemdash-plugin validate path/ # a specific directoryOffline schema check with tsc-style file:line:column diagnostics, including the manifest’s cross-field rules. No network. Good as a pre-commit or CI gate. See the manifest reference.
bundle
Section titled “bundle”bundle is a thin packaging step on top of build:
- Runs
buildto producedist/. - Validates the bundle: no Node-builtin imports, no oversized files, capability sanity.
- Collects optional assets — README, icon, screenshots.
- Tarballs. Inside the tarball,
plugin.mjsis packed asbackend.js(the filename the registry expects). The output isdist/<slug>-<version>.tar.gz.
--validate-only skips tarball creation but still produces the dist/ artifacts — “validate” implies “build first”.
publish
Section titled “publish”The CLI does not host artifacts; you do, anywhere public.
emdash-plugin login # if not already logged inemdash-plugin bundle # produces dist/<slug>-<version>.tar.gz# upload that tarball to a public URL, then:emdash-plugin publish --url https://your-host/my-plugin-1.0.0.tar.gzpublish reads the manifest for profile fields and enforces publisher pinning. On first publish, pass --license and a security contact (or keep them in the manifest). Explicit flags override manifest values, which is useful in CI; --no-manifest opts out entirely.
Full walkthrough: Bundling and publishing.
Programmatic API
Section titled “Programmatic API”import { buildPlugin, bundlePlugin } from "@emdash-cms/plugin-cli";
await buildPlugin({ dir: "./my-plugin" });const result = await bundlePlugin({ dir: "./my-plugin" });For discovery and credential helpers, import from @emdash-cms/registry-client.