plan

plan turns a plain-words description of a screen into a starting point: the page recipe to start from (or none, when no page fits), the block recipes and components that fit, the scaffold command that copies the starter into your app, and the rules every recipe follows. It works offline, and the same words always give the same answer.

When to use it

Run plan before you build a new screen, so you start from a recipe and the components that fit instead of from a blank file. It is a first step: once you know which recipe you want, scaffold --show and scaffold take over. For the props of a component it lists, use the Pluma MCP server (get_component) or the docs at pluma.customer.io.

Usage

pluma-cli plan "<idea>" [--json]

Describe the screen in plain words. Components the starter recipe already uses are marked with *. The plan ends with the next step, next: pluma-cli scaffold <id> <dest-dir> --framework <react|ember>, and the scaffold --show <id> command to look at the recipe first. Run it without an idea to see the workflow: plan, then scaffold --show, then scaffold, then look up each component's props with the Pluma MCP server (get_component, or plan_ui for this same plan) or the docs.

Flags

table
FlagDescription
--jsonPrint the plan as JSON (see JSON output).
-h, --helpShow help.

The idea can be one quoted argument or several words; plan joins them.

Examples

Plan a list page

pluma-cli plan "a list of campaigns the user can filter and search"
Idea: a list of campaigns the user can filter and search
Read as: list, campaigns, filter, search

Starter recipe:
  list-with-filters (page): List with filters
    Use this page for the index of a collection people browse and narrow down, such as a workspace's campaigns, with the action that creates a new one in the header.
    Matched: list (name), campaigns (keyword), filter (name), search (keyword)
    Components: badge, button, data-table, formatted-date-time, page

Supporting blocks:
  panel-with-description-list: Panel with description list
    Use this block to summarize one object, such as a campaign, segment or person, as a short list of labeled facts on its detail page.
    Matched: list (name), campaigns (description)
  empty-state-cta: Empty state with call to action
    Use this block when a list or panel has nothing to show yet and one action would fill it, such as creating the first campaign.
    Matched: list (description), campaigns (description)

Supporting components (* already in the starter):
    description-list: Organize and explain a group of terms and their descriptions.
    filters: The Filters component is a versatile component that can be used to filter data based on user-defined criteria.
    list: Lists group related items together in sequential or non-sequential order.
    search: Search provides the ability to narrow down data using a specialized TextField.
  * data-table: DataTable enables users to act upon complex datasets with a variety of features including filtering and sorting.
    condition-editor: The ConditionEditor components lay out editable condition rows — a card per condition holding the controls that edit it, joined to the rows around it by their operator.
    condition-viewer: The ConditionViewer components render a read-only summary of a set of rules — one boxed sentence per rule, nested under the operator that combines them.
    table: Tables display a set of data in an organized tabular format.

next: pluma-cli scaffold list-with-filters <dest-dir> --framework <react|ember>
  To look at it first: pluma-cli scaffold --show list-with-filters
  Then look up props with the Pluma MCP server (get_component) or the docs.

Rules:
  - Both frameworks exist.
  - The Pluma lint rules pass.
  - Pluma components come from the package.
  - No raw `div` or `span`.
  - No `style` attribute anywhere, and no `className` or `class` on a Pluma component.
  - No color, spacing or radius literals.
  - The root element matches the type.
  - `recipe.json` is valid.
  - `components` lists exactly what the source imports.

Get the plan as JSON

pluma-cli plan "a settings page for workspace preferences" --json
{
  "version": 1,
  "idea": "a settings page for workspace preferences",
  "terms": [
    "settings",
    "page",
    "workspace",
    "preferences"
  ],
  "starter": {
    "id": "settings-form",
    "type": "page",
    "title": "Settings form",
    "description": "Use this page when people edit the configuration of a whole workspace, account or integration and save it in one go, such as a workspace's name, timezone and default sender. It names the screen and its scope in the header and keeps the fields in a narrow column, with Cancel and Save changes at the end of the form. Add a panel per group of related settings as the page grows.",
    "components": [
      "button",
      "checkbox",
      "form",
      "form-layout",
      "page",
      "panel",
      "select",
      "text-field"
    ],
    "score": 20,
    "matches": [
      {
        "term": "settings",
        "tier": "name"
      },
      {
        "term": "page",
        "tier": "keyword"
      },
      {
        "term": "workspace",
        "tier": "keyword"
      },
      {
        "term": "preferences",
        "tier": "keyword"
      }
    ]
  },
  "blocks": [
    {
      "id": "form-section",
      "type": "block",
      "title": "Form section",
      "description": "Use this block for one titled group of related fields, such as sender details, inside a longer form or settings page. Stack several of them to break a long form into sections people can scan, each with a short description of what its fields control. Mark the fields people can leave empty as optional and add help text where a field needs it.",
      "components": [
        "form-layout",
        "panel",
        "text-field"
      ],
      "score": 7,
      "matches": [
        {
          "term": "settings",
          "tier": "keyword"
        },
        {
          "term": "page",
          "tier": "description"
        },
        {
          "term": "preferences",
          "tier": "keyword",
          "synonym": "settings"
        }
      ]
    },
    {
      "id": "panel-with-description-list",
      "type": "block",
      "title": "Panel with description list",
      "description": "Use this block to summarize one object, such as a campaign, segment or person, as a short list of labeled facts on its detail page. Each row pairs a label with its value, so people can scan status, configuration and dates at a glance. Put the action that changes these facts, such as editing them, in the panel header.",
      "components": [
        "badge",
        "button",
        "description-list",
        "formatted-date-time",
        "panel"
      ],
      "score": 7,
      "matches": [
        {
          "term": "settings",
          "tier": "keyword"
        },
        {
          "term": "page",
          "tier": "description"
        },
        {
          "term": "preferences",
          "tier": "keyword",
          "synonym": "settings"
        }
      ]
    }
  ],
  "components": [
    {
      "id": "page",
      "title": "Page",
      "description": "Page is the outermost layout for a route, setting where the title and page-level actions sit and how wide the content column is.",
      "inStarter": true,
      "score": 9.5,
      "matches": [
        {
          "term": "settings",
          "tier": "description"
        },
        {
          "term": "page",
          "tier": "name"
        },
        {
          "term": "preferences",
          "tier": "description",
          "synonym": "settings"
        }
      ]
    },
    {
      "id": "page-loader",
      "title": "PageLoader",
      "description": "PageLoader communicates that a process is currently running on a page-level context.",
      "inStarter": false,
      "score": 8,
      "matches": [
        {
          "term": "page",
          "tier": "name"
        }
      ]
    }
  ],
  "scaffold": "pluma-cli scaffold settings-form <dest-dir> --framework <react|ember>",
  "rules": [
    "Both frameworks exist.",
    "The Pluma lint rules pass.",
    "Pluma components come from the package.",
    "No raw `div` or `span`.",
    "No `style` attribute anywhere, and no `className` or `class` on a Pluma component.",
    "No color, spacing or radius literals.",
    "The root element matches the type.",
    "`recipe.json` is valid.",
    "`components` lists exactly what the source imports."
  ]
}

An idea no recipe fits

When no page recipe matches strongly, the plan says so and suggests starting from a Page and the closest blocks. When nothing matches at all, it lists no blocks or components and prints no next: step.

pluma-cli plan "a three-dimensional globe of office locations"
Idea: a three-dimensional globe of office locations
Read as: three, dimensional, globe, office, locations

Starter recipe: none matches strongly. Start from a Page and the closest blocks below.

Supporting blocks: none match.

Supporting components: none match.

Rules:
  - Both frameworks exist.
  - The Pluma lint rules pass.
  - Pluma components come from the package.
  - No raw `div` or `span`.
  - No `style` attribute anywhere, and no `className` or `class` on a Pluma component.
  - No color, spacing or radius literals.
  - The root element matches the type.
  - `recipe.json` is valid.
  - `components` lists exactly what the source imports.

JSON output

--json prints one object:

table
KeyValue
versionThe shape of the plan. Currently 1.
ideaThe idea as you passed it.
termsThe words of the idea plan matched on, in order, with filler words dropped.
starterThe page recipe to start from, or null when none matches strongly enough.
blocksBlock recipes that fit, best first, up to three.
componentsComponents that fit, best first.
scaffoldThe scaffold command for the starter, or for the top block when there is no starter; null when neither is.
rulesThe rules every recipe follows, as sentences.

starter and each entry in blocks is a recipe:

table
KeyValue
idThe recipe id scaffold takes.
typepage or block.
titleThe recipe's title.
descriptionWhat the recipe is for and when to use it.
componentsThe ids of the Pluma components it uses.
scoreHow strongly it matched; higher is better.
matchesThe words that matched it (see below).

Each entry in components has id, title, description, score and matches, and inStarter: true when the starter recipe already uses the component.

Each entry in matches has term (the word from the idea), tier (where it matched: name, keyword, typo or description), and synonym when the word matched through a synonym.

MCP twin

The plan_ui tool of the Pluma MCP server returns the same plan for the same idea, for agents.

table
CLIplan_ui parameter
"<idea>"idea
--jsonNone: the tool always returns the plan as JSON.

Troubleshooting

Unknown option

Unknown option: --verbose

plan takes no flag but --json and -h/--help, and reads any other argument that starts with - as a mistake. It prints the message and the help, and exits with status 2. Remove the flag, or put the whole idea in quotes if a word of it starts with -.

The help prints instead of a plan

plan with no idea, or with only --json, prints the help and the workflow and exits with status 0. Pass the idea in quotes: pluma-cli plan "a settings page for workspace preferences".

No starter recipe matches

Starter recipe: none matches strongly. Start from a Page and the closest blocks below.

No page recipe matched the idea strongly enough to start from. Build the page from a Page and the blocks the plan lists, if any. Rewording the idea with the words a recipe uses (list, settings, detail, wizard, dashboard and so on) can help; pluma-cli scaffold --list shows every recipe and its description.