CLI

Command-line tools for Pluma: plan a screen, scaffold page and block recipes into your app as your own code, run codemods across Pluma breaking changes, and file gap reports where Pluma falls short.


@customerio/pluma-cli

Command-line tools for Pluma, Customer.io's design system. It ships codemods — automated migrations that move a consuming app across Pluma breaking changes — grouped by the @customerio/pluma-components version each change lands in, and recipes: page and block examples you copy into your app as your own code, with plan to pick one for the screen you describe. It also files gap reports each time Pluma falls short of what a developer or agent needed, to wherever the consuming project chooses.

Usage

No install needed — run it with pnx:

# See every codemod, grouped by the pluma-components version it targets
pnx @customerio/pluma-cli@latest upgrade --list

# Run one (always dry-run first to preview the diff)
pnx @customerio/pluma-cli@latest upgrade --codemod button-variant-rename react --dry ./src
pnx @customerio/pluma-cli@latest upgrade --codemod button-variant-rename react ./src

Or add it as a dev dependency and call the pluma-cli binary:

pnpm add -D @customerio/pluma-cli
pnpm exec pluma-cli upgrade --list

pluma-cli --help lists the commands, and pluma-cli <command> --help shows one command's usage:

pluma-cli --help
pluma-cli — command-line tools for Pluma.

Usage:
  pluma-cli <command> [options]

Commands:
  plan            Turn a description of a screen into a starter recipe, the
                  blocks and components that fit, and the rules to follow.
  scaffold        List recipes, show one, or copy one into your app as your
                  own code.
  upgrade         List and run codemods that migrate your app across Pluma
                  breaking changes.
  report-gap      Report that Pluma fell short of what you needed, to the
                  handlers in pluma.config.mjs.

Options:
  -h, --help      Show this help.
  -v, --version   Show the pluma-cli version.

Run `pluma-cli <command> --help` for a command's usage.

Commands

table
CommandWhat it does
planTurn a description of a screen into a starter recipe, the blocks and components that fit, and the rules to follow.
scaffoldList recipes, show one, or copy one into your app as your own code.
upgradeList and run codemods that migrate your app across Pluma breaking changes.
report-gapReport that Pluma fell short of what you needed, to the handlers in pluma.config.mjs.

pluma.config.mjs

The project-wide config file for Pluma tools, read from the working directory of pluma-cli and of the MCP server. It is an ES module whose default export is an object:

// pluma.config.mjs
import { jsonlFile } from '@customerio/pluma-cli/gap-report';

/** @type {import('@customerio/pluma-cli/gap-report').PlumaConfig} */
export default {
	version: 1,
	gapReport: [jsonlFile({ path: '.pluma/team-gaps.jsonl' })],
};
table
KeyDescription
versionThe shape the file is written against. Optional; only 1 exists.
gapReportOne handler or a list of them. Gap reports go to each, in order. Without it, reports go to the fallback file.

Keys a release doesn't know are ignored. A file that fails to load, or whose version or handlers are invalid, stops report-gap with status 2 rather than sending reports to the fallback.

@customerio/pluma-cli/gap-report exports the handlers gapReport takes. The report-gap page lists the four built-in handlers, the handler interface for writing your own, and the report every handler receives.

The .pluma/ directory

A run keeps its files in .pluma/ at the root of the repository it runs in — the nearest directory up from the working directory that holds a .git, or the working directory itself outside a repository:

table
FileWhat it is
.pluma/codemod-ledger.jsonThe run ledger, for the codemods that keep one.
.pluma/codemod-report.jsonThe findings report of the last run that had any.
.pluma/gap-reports.jsonlGap reports when no handler is configured, one per line.

Commit the directory with the migrated files. A developer with work in flight when the migration lands can then merge it in and run the codemods again over their own changes, and the ledger keeps that run off everything the first one migrated. It is the same directory however deep in the repository a run starts, so every run in a monorepo shares one ledger. --ledger <path> and --report <path> put either file somewhere else.

Before 5.0.0 the defaults were pluma-codemod-ledger.json and pluma-codemod-report.json in the working directory. A run that finds a ledger there or at the repository root moves it into .pluma/ and says so — commit the move. If .pluma/ already holds a ledger (a monorepo whose apps each ran the codemods from their own directory, say), the old rows are merged into it. --dry reads an old ledger where it is and moves nothing. An old report can be deleted; every run writes a fresh one.

JSON output

Every command that takes --json prints exactly one JSON value on stdout and nothing else there, so its output can be piped to jq. Messages and errors go to stderr. The exit status is 0 when the command did what it was asked, 1 when a codemod run or a gap report delivery failed, and 2 for invalid input.

Each command page describes its JSON shape: plan, scaffold, upgrade and report-gap.

Development

pnpm --filter @customerio/pluma-cli run dev -- upgrade --list   # run from source via tsx
pnpm --filter @customerio/pluma-cli run build                   # bundle the CLI + copy transforms and recipes to dist/
pnpm --filter @customerio/pluma-cli run test                    # vitest (CLI layer) + node --test (transforms)
pnpm --filter @customerio/pluma-cli run typecheck
pnpm --filter @customerio/pluma-cli run docs:samples            # regenerate the sample output in docs/

The build bundles the codemod transforms and the recipes from packages/components/src/recipes into dist/. Run from source, plan and scaffold read the components package directly.