The emdash-plugin CLI

On this page

@emdash-cms/plugin-cli is the authoring toolchain: scaffold, build, watch, validate, bundle, publish, plus identity and discovery. The binary is emdash-plugin.

The CLI uses an Atmosphere account as the publisher identity for package profiles and releases.

Commands

The CLI provides the following commands:

emdash-plugin init [name]                    Scaffold a new sandboxed plugin
emdash-plugin build                          Build dist/ (plugin.mjs, manifest.json, index.mjs)
emdash-plugin dev                            Watch sources and rebuild on change
emdash-plugin bundle                         Pack dist/ + assets into a registry tarball
emdash-plugin validate [path]                Validate emdash-plugin.jsonc against the schema
emdash-plugin publish                        Build, upload, and publish a release
emdash-plugin profile setup                  Prepare the signed package profile for delegated releases
emdash-plugin release setup                  Create the delegated-release GitHub Actions workflow
emdash-plugin login <handle-or-did>          Sign in with your Atmosphere account
emdash-plugin logout [--did <did>]           Revoke the active session
emdash-plugin whoami                         Show stored sessions
emdash-plugin switch <did>                   Switch the active publisher session
emdash-plugin search <query>                 Free-text registry search
emdash-plugin info <handle-or-did> <slug>    Show package details

The 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"
	}
}

init

Create a new plugin with init:

npx @emdash-cms/plugin-cli init my-plugin

This 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

build reads emdash-plugin.jsonc, src/plugin.ts, and an optional sibling package.json, and emits the following files:

ArtifactWhat 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.jsonThe 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.

dev

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

emdash-plugin validate          # ./emdash-plugin.jsonc
emdash-plugin validate path/    # a specific directory

Offline 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

bundle is a thin packaging step on top of build:

  1. Runs build to produce dist/.
  2. Validates the bundle: no Node-builtin imports, no oversized files, capability sanity.
  3. Collects optional assets — README, icon, screenshots.
  4. Tarballs. Inside the tarball, plugin.mjs is packed as backend.js (the filename the registry expects). The output is dist/<slug>-<version>.tar.gz.

--validate-only skips tarball creation but still produces the dist/ artifacts — “validate” implies “build first”.

publish

publish builds and validates the plugin, uploads the package and listing images to your PDS, then writes the release record.

emdash-plugin login alice.example.com
emdash-plugin publish

publish 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; --no-manifest opts out entirely.

Pass --url <https-url> to use an externally hosted package bundle. The CLI downloads and validates the URL before publishing. Add --local <path> to verify that a local tarball matches the downloaded bytes.

Full walkthrough: Bundling and publishing.

profile setup

profile setup prepares the publisher-owned package profile for automated releases. It creates a missing profile from emdash-plugin.jsonc, or adds delegated-release settings to an existing valid profile without replacing its package metadata.

Run the interactive setup from the plugin directory:

emdash-plugin profile setup
FlagDefaultDescription
--dir <path>Current directoryPlugin source directory.
--repository <url>Manifest repoCanonical public GitHub repository URL. Interactive setup asks when neither value is present.
--confirmation <mode>escalation-onlyUse escalation-only for permission increases or always for every release.
--yes, -yfalseAccept the default policy without prompting. Required when a non-interactive run would change the profile.

The command uses the active CLI login to write the profile. It refuses to replace a different signed repository. Run emdash-plugin switch <did> when the active account does not match the manifest publisher.

release setup

release setup runs package-profile setup, then creates .github/workflows/emdash-release.yml for the release service.

emdash-plugin release setup

It accepts the profile setup flags plus the following workflow options:

FlagDefaultDescription
--service-url <origin>https://releases.emdashcms.comHTTPS origin used by the generated Action.
--action-ref <ref>mainEmDash repository ref containing the release Action.
--forcefalseReplace an existing generated workflow. Without it, setup leaves the existing file unchanged.

The command never pushes the generated workflow. Follow Automated plugin releases to authorise the release service, connect the workflow, and publish the first release.

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.