This document provides guidelines for AI agents (like Claude, Copilot, Cursor, etc.) working on this CLI codebase. These rules are derived from RFC 000282: CLI Design Patterns and Best Practices.
Important: These are best practices to create a cohesive, predictable user experience. If a particular scenario doesn't align with these conventions, prioritize what's best for the customer and feature requirements.
- Lowercase and Hyphenated: All topic and command names should be lowercase and use hyphens (
-) as separators for multi-word names - Plural Nouns for Topics: The topic immediately preceding a command should be a plural noun. Topics that serve as logical organizers for whole sections of commands can be a descriptive noun
- Action Verbs for Commands: Command names should begin with an action verb describing the operation being performed. Terms should be predictable, consistent, and memorable
Common verbs for command operations:
- create - for creating a new resource
- add - for adding a resource to a collection/list
- destroy - for destroying a resource entirely
- remove - for removing a resource from a collection/list
- update - for modifying an existing resource
The index of a command serves as the command for listing resources.
- If the command requires parameters, the ARGUMENT to the command should serve as the primary input (i.e., the name or id of the associated resource)
- There should never be more than one optional ARGUMENT
- In general, flags are preferred to args
- Commands that will only ever have one input, an arg can be appropriate
- Limit the number of required flags and arguments
- Make use of default values where possible
- Consider a prompt-based flow for a command that has many required inputs
- Flags should have descriptive words as default with a shorthand if appropriate
- If the name of an argument requires multiple words, separate them with underscores
- Multiple key-value input values with unknown key labels should be passed in using the "multiple: true" oclif option, which provides for allowing a flag to be submitted multiple times (e.g.:
--option key1=value1 --option key2=value2)
- Terms used to describe features and Heroku primitives should align with how they're described in Dev Center articles (particularly as described in the published Platform API reference)
- There should be description text for each command, flag, and argument
- There should be at least one example for each command. If there are multiple important flows, multiple examples are appropriate
- Examples should use the longform flag (e.g.,
--appinstead of-a) - Examples should refer to user apps as
example-app, unless more descriptive language is needed (e.g.example-source-app) - Description should include clear, descriptive text that humans can understand and LLM agents can interpret
- Keep all descriptions clear and concise. Fragments are preferable to sentences. Start with lowercase and if it's a fragment, don't end with a period
- If settings are configurable, note that either in description or output text
- Add a Dev Center link if it's important for user comprehension
- In the event any part of the execution of a command fails, the command should return a non-zero exit code, which can be accomplished by:
- Allowing an uncaught exception to go uncaught (oclif will return the error message to the user)
- Use oclif's explicit exit function (ux.exit, or this.exit)
- Commands that succeed return 0
- All commands produce some output upon completion
- Network requests should utilize the custom fetch-based API Client described in the heroku-fetch RFC once that is ready
- Consistent translation of data for our common tooling and components
- Ensures a consistent debugging experience
- Use schema defined types where possible, such as found in typescript-api-schema
- Default to passing through Platform API error messages to the user
- Thin data validation in the CLI
- Do not replicate complicated validation logic that exists in the API
- Make use of basic validation where it makes sense
- Limit number of blocking API calls
- Make use of concurrent API calls wherever possible
UX components come from two primary sources:
-
oclif/core - Provides basic components:
ux.stdout- Standard outputux.stderr- Standard errorux.action.startandux.action.stop- Action spinners
-
heroku-cli-util (hux) - Provides more involved components:
hux.table- Tabular data display- Themeable color system
- Additional UX utilities
- Repository: https://github.com/heroku/heroku-cli-util
Here is a list of commonly used ux components and when they are typically used:
-
ux.action.start and ux.action.stop
- Whenever data is changed (e.g., a POST, UPDATE, DELETE, etc.)
- The request is expected to take longer than normal time (greater than, say, 600ms, as in when generating a report)
-
hux.confirm
- Destructive and irreversible actions
- Important changes to a resource
-
hux.table
- The display of multiple instances of a resource (i.e., tabular data)
-
hux.styledObject and hux.styledJson
- The display of a singular instance of a resource
-
ux.error
- When an error condition occurs and we want to inform the user that the command failed with exit code 1
-
System notifications (heroku-cli/notifications)
- When some part of command execution could take a long time (e.g., greater than 20 seconds) to complete
-
Progress bar (cli-progress)
- When some action taking place has known progress values to display to the user (as in when downloading something)
The CLI uses a themeable color system provided by heroku-cli-util. Colors should follow the organization and definitions described in:
When contributing code to this CLI:
- ✅ Use lowercase-hyphenated names for commands and topics
- ✅ Start command names with action verbs (create, add, destroy, remove, update)
- ✅ Prefer flags over arguments; limit required parameters
- ✅ Write clear, concise descriptions (fragments preferred, lowercase, no ending period)
- ✅ Include examples using longform flags and
example-appas the app name - ✅ Use appropriate ux components (ux.action for mutations, ux.table for lists, etc.)
- ✅ Return non-zero exit codes on failures, zero on success
- ✅ Keep validation logic thin; defer to the API
- ✅ Use concurrent API calls to minimize blocking time
- ✅ Pass through Platform API error messages to users