cli-command-development
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
/*
* Copyright (c) 2025, Salesforce, Inc.
* SPDX-License-Identifier: Apache-2
* For full license text, see the license.txt file in the repo root
*/
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',
required: true,
}),
};
static flags = {
myFlag: Flags.string({
char: 'm',
description: 'Flag description',
default: 'defaultValue',
}),
myBool: Flags.boolean({
description: 'Boolean flag',
default: false,
}),
};
async run(): Promise<MyCommandResponse> {
// Validation - call appropriate require* methods
this.requireServer();
// Access parsed args and flags
const {name} = this.args;
const {myFlag, myBool} = this.flags;
this.log(t('commands.topic.mycommand.working', 'Working on {{name}}...', {name}));
// Implementation
const {data, error, response} = await this.instance.ocapi.GET('/some/endpoint');
if (error) {
this.error(t('commands.topic.mycommand.error', 'Failed: {{message}}', {
message: getApiErrorMessage(error, response),
}));
}
const result: MyCommandResponse = {
success: true,
data,
};
// JSON mode returns the object directly (oclif handles serialization)
if (this.jsonEnabled()) {
return result;
}
// Human-readable output
this.log('Success!');
return 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 = {
// String with short alias and env var fallback
server: Flags.string({
char: 's',
description: 'Server hostname',
env: 'SFCC_SERVER',
}),
// Integer with default
timeout: Flags.integer({
description: 'Timeout in seconds',
default: 60,
}),
// Boolean with --no-* variant
wait: Flags.boolean({
description: 'Wait for completion',
default: true,
allowNo: true, // enables --no-wait
}),
// Comma-separated multiple values
channels: Flags.string({
description: 'Site channels (comma-separated)',
multiple: true,
multipleNonGreedy: true,
delimiter: ',',
}),
// Enum-like options
format: Flags.string({
description: 'Output format',
options: ['json', 'csv', 'table'],
default: 'table',
}),
// Conditional flag
secret: Flags.string({
description: 'Client secret (required for private clients)',
dependsOn: ['client-id'],
}),
};
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, // Only shown with --extended
},
};
const DEFAULT_COLUMNS = ['id', 'name'];
const tableRenderer = new TableRenderer(COLUMNS);
// In run():
tableRenderer.render(data, DEFAULT_COLUMNS);
// With --columns flag support:
const columns = this.flags.columns
? tableRenderer.validateColumnKeys(this.flags.columns.split(','))
: DEFAULT_COLUMNS;
tableRenderer.render(data, columns);
Internationalization
All user-facing strings use the t() function:
import {t} from '../../i18n/index.js';
// Basic usage
this.log(t('commands.topic.cmd.message', 'Default message'));
// With interpolation
this.log(t('commands.topic.cmd.working', 'Processing {{count}} items...', {count: 5}));
// For errors
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:
// From OAuthCommand
this.requireOAuthCredentials(); // Ensures clientId + clientSecret
this.hasOAuthCredentials(); // Returns boolean
// From InstanceCommand
this.requireServer(); // Ensures hostname is set
this.requireCodeVersion(); // Ensures code version is set
this.requireWebDavCredentials(); // Ensures WebDAV auth (Basic or OAuth)
// From MrtCommand
this.requireMrtCredentials(); // Ensures MRT API credentials
Accessing Clients
InstanceCommand provides lazy-loaded clients:
// OCAPI client (OAuth authenticated)
const result = await this.instance.ocapi.GET('/code_versions');
// WebDAV client (Basic or OAuth authenticated)
await this.instance.webdav.put('path/to/file', buffer);
// ODS client (from OdsCommand)
const sandboxes = await this.odsClient.GET('/sandboxes');
// MRT client (from MrtCommand)
const projects = await this.mrtClient.GET('/api/projects/');
Error Handling
// Simple error (exits with code 1)
this.error('Something went wrong');
// Error with suggestions
this.error('Config file not found', {
suggestions: ['Run b2c auth login first', 'Check your dw.json file'],
});
// Warning (continues execution)
this.warn('Deprecated flag used');
// API errors - use getApiErrorMessage for clean messages
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 = truefor JSON output support - Implement
run()method with proper return type - Add topic to
package.jsonif new - Add i18n keys for all user-facing strings
- Update skill in
skills/b2c-cli/skills/b2c-<topic>/SKILL.mdif exists - Update CLI reference docs in
docs/cli/<topic>.md - Build and test:
pnpm run build && pnpm --filter @salesforce/b2c-cli run test
More from salesforcecommercecloud/b2c-developer-tooling
b2c-webdav
List, upload, download, and manage files on B2C Commerce instances via WebDAV. Use this skill whenever the user needs to upload files to IMPEX directories, download exports from an instance, list remote files, create or delete directories, or zip/unzip files on the server. Also use when managing file transfers to sandboxes or browsing instance file systems -- even if they just say 'upload a file to the instance' or 'check what's in the IMPEX folder'.
103b2c-config
Inspect and debug CLI configuration, instance connections, and authentication. Use this skill whenever the user needs to check which dw.json or credentials are active, manage multiple instance profiles, retrieve OAuth tokens for scripting, troubleshoot authentication failures or connection errors, or integrate with VS Code or other editors. Also use when environment variables override config or the wrong sandbox is being targeted -- even if they just say 'why is it connecting to the wrong instance' or 'get me an access token'.
90b2c-controllers
Create storefront controllers using SFRA or classic patterns with server.get/post, middleware chains, and res.render/json. Use this skill whenever the user needs to build a page route, handle form submissions, create AJAX endpoints, extend or override existing controllers, or add middleware to a request pipeline. Also use when debugging route registration or response rendering -- even if they just say 'new page endpoint' or 'handle a POST request'.
86b2c-scapi-schemas
Browse and retrieve SCAPI OpenAPI schema specifications. Use this skill whenever the user needs to list available SCAPI APIs, inspect endpoint paths or request/response shapes, explore data models for products or orders, check which fields an API returns, or understand SCAPI versioning. Also use when looking up API details before building an integration -- even if they just say 'what fields does the product API return' or 'show me the SCAPI endpoints'.
84b2c-job
Run and monitor jobs on B2C Commerce instances using the b2c CLI, including site archive import/export and search indexing. Use this skill whenever the user needs to trigger a job, import a site archive, export site data, rebuild search indexes, check job status, or troubleshoot failed job executions — even if they just say "import this folder" or "rebuild the search index".
83b2c-isml
Build ISML templates with isprint, isset, isloop, isdecorate, isinclude tags, and ${...} expression syntax. Use this skill whenever the user needs to write or debug storefront templates, create decorator layouts with isreplace, build reusable template modules, control HTML encoding in output, or use ISML expression language for dynamic content. Also use when fixing template rendering issues -- even if they just say 'loop through products in the template' or 'my HTML is getting escaped'.
82