| name | cli-command-development |
| description | Creating new CLI commands and topics for the B2C CLI using oclif. Use when adding a new command, creating a topic, adding flags or arguments, implementing table output, or extending BaseCommand/OAuthCommand/InstanceCommand. |
| metadata | {"internal":true} |
CLI Command Development
This skill covers creating new CLI commands and topics for the B2C CLI.
Command Organization
Commands live in packages/b2c-cli/src/commands/. The directory structure maps directly to command names:
commands/
├── code/
│ ├── deploy.ts → b2c code deploy
│ ├── activate.ts → b2c code activate
│ └── list.ts → b2c code list
├── sandbox/
│ ├── create.ts → b2c sandbox create
│ └── list.ts → b2c sandbox list
└── mrt/
└── env/
└── var/
└── set.ts → b2c mrt env var set
Command Class Hierarchy
Choose the appropriate base class based on what your command needs:
BaseCommand (logging, JSON output, error handling)
└─ OAuthCommand (OAuth authentication)
├─ InstanceCommand (B2C instance: hostname, code version)
│ ├─ CartridgeCommand (cartridge path + filters)
│ ├─ JobCommand (job execution helpers)
│ └─ WebDavCommand (WebDAV root directory)
├─ MrtCommand (Managed Runtime API)
└─ OdsCommand (On-Demand Sandbox API)
Import from @salesforce/b2c-tooling-sdk/cli:
import { InstanceCommand, CartridgeCommand, OdsCommand } from '@salesforce/b2c-tooling-sdk/cli';
Standard Command Template
import {Args, Flags} from '@oclif/core';
import {InstanceCommand} from '@salesforce/b2c-tooling-sdk/cli';
import {getApiErrorMessage} from '@salesforce/b2c-tooling-sdk';
import {t} from '../../i18n/index.js';
interface MyCommandResponse {
success: boolean;
data: SomeType[];
}
export default class MyCommand extends InstanceCommand<typeof MyCommand> {
static description = t('commands.topic.mycommand.description', 'Human-readable description');
static enableJsonFlag = true;
static examples = [
'<%= config.bin %> <%= command.id %> arg1',
'<%= config.bin %> <%= command.id %> --flag value',
'<%= config.bin %> <%= command.id %> --json',
];
static args = {
name: Args.string({
description: 'Description of the argument',
: ,
}),
};
flags = {
: .({
: ,
: ,
: ,
}),
: .({
: ,
: ,
}),
};
(): <> {
.();
{name} = .;
{myFlag, myBool} = .;
.((, , {name}));
{data, error, response} = ...();
(error) {
.((, , {
: (error, response),
}));
}
: = {
: ,
data,
};
(.()) {
result;
}
.();
result;
}
}
Adding a New Topic
When creating a new command topic, add it to packages/b2c-cli/package.json in the oclif section:
{
"oclif": {
"topics": {
"newtopic": {
"description": "Commands for new functionality"
},
"newtopic:subtopic": {
"description": "Subtopic commands"
}
}
}
}
Flag Patterns
Common Flag Types
static flags = {
server: Flags.string({
char: 's',
description: 'Server hostname',
env: 'SFCC_SERVER',
}),
timeout: Flags.integer({
description: 'Timeout in seconds',
default: 60,
}),
wait: Flags.boolean({
description: 'Wait for completion',
default: true,
allowNo: true,
}),
channels: Flags.string({
description: 'Site channels (comma-separated)',
multiple: true,
multipleNonGreedy: true,
delimiter: ',',
}),
format: Flags.string({
description: 'Output format',
options: ['json', , ],
: ,
}),
: .({
: ,
: [],
}),
};
Table Output
Use createTable for consistent tabular output:
import {createTable, TableRenderer, type ColumnDef} from '@salesforce/b2c-tooling-sdk/cli';
type MyData = {id: string; name: string; status: string};
const COLUMNS: Record<string, ColumnDef<MyData>> = {
id: {
header: 'ID',
get: (item) => item.id,
},
name: {
header: 'Name',
get: (item) => item.name,
},
status: {
header: 'Status',
get: (item) => item.status,
extended: true,
},
};
const DEFAULT_COLUMNS = ['id', 'name'];
const tableRenderer = new TableRenderer(COLUMNS);
tableRenderer.render(data, );
columns = ..
? tableRenderer.(...())
: ;
tableRenderer.(data, columns);
Internationalization
All user-facing strings use the t() function:
import {t} from '../../i18n/index.js';
this.log(t('commands.topic.cmd.message', 'Default message'));
this.log(t('commands.topic.cmd.working', 'Processing {{count}} items...', {count: 5}));
this.error(t('commands.topic.cmd.error', 'Failed: {{message}}', {message: err.message}));
Keys follow the pattern: commands.<topic>.<command>.<key>
Validation Methods
Base classes provide validation helpers:
this.requireOAuthCredentials();
this.hasOAuthCredentials();
this.requireServer();
this.requireCodeVersion();
this.requireWebDavCredentials();
this.requireMrtCredentials();
Accessing Clients
InstanceCommand provides lazy-loaded clients:
const result = await this.instance.ocapi.GET('/code_versions');
await this.instance.webdav.put('path/to/file', buffer);
const sandboxes = await this.odsClient.GET('/sandboxes');
const projects = await this.mrtClient.GET('/api/projects/');
Error Handling
this.error('Something went wrong');
this.error('Config file not found', {
suggestions: ['Run b2c auth login first', 'Check your dw.json file'],
});
this.warn('Deprecated flag used');
import {getApiErrorMessage} from '@salesforce/b2c-tooling-sdk';
const {data, error, response} = await this.instance.ocapi.GET('/sites', {...});
if (error) {
this.error(t('commands.topic.cmd.apiError', 'API error: {{message}}', {
message: getApiErrorMessage(error, response),
}));
}
Important: Always destructure response alongside error when making API calls. The getApiErrorMessage utility extracts clean messages from ODS, OCAPI, and SCAPI error patterns, and falls back to HTTP status (e.g., "HTTP 521 Web Server Is Down") for non-JSON responses like HTML error pages.
See API Client Development for supported error patterns.
Troubleshooting
Command not found after creating file: Ensure the file is in the correct packages/b2c-cli/src/commands/ subdirectory matching the intended command path. Run pnpm --filter @salesforce/b2c-cli run build to regenerate the oclif manifest. For new topics, add the topic to package.json under oclif.topics.
Flag parsing errors: Check that flag names use kebab-case in the static flags definition. The char shorthand must be a single character. If using dependsOn, the referenced flag must exist in the same command's flags.
Missing i18n keys: The t() function falls back to the default string (second argument), so missing keys won't crash at runtime. However, keep key paths consistent with the commands.<topic>.<command>.<key> pattern for future localization.
"requireX" methods not available: Verify the command extends the correct base class. requireServer() is on InstanceCommand, requireOAuthCredentials() is on OAuthCommand, requireMrtCredentials() is on MrtCommand. Check the class hierarchy if a method is missing.
Creating a Command Checklist
- Create file at
packages/b2c-cli/src/commands/<topic>/<command>.ts
- Choose appropriate base class
- Define
static description, examples, args, flags
- Set
static enableJsonFlag = true for JSON output support
- Implement
run() method with proper return type
- Add topic to
package.json if new
- Add i18n keys for all user-facing strings
- Update skill in
skills/b2c-cli/skills/b2c-<topic>/SKILL.md if exists
- Update CLI reference docs in
docs/cli/<topic>.md
- Build and test:
pnpm run build && pnpm --filter @salesforce/b2c-cli run test