Skip to content

Commit d92ba87

Browse files
authored
claude skills and agents trimming; link to skills for cusor compat (#26)
1 parent 228feb3 commit d92ba87

5 files changed

Lines changed: 1668 additions & 117 deletions

File tree

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
---
2+
name: cli-command-development
3+
description: Creating new CLI commands and topics for the B2C CLI using oclif
4+
---
5+
6+
# CLI Command Development
7+
8+
This skill covers creating new CLI commands and topics for the B2C CLI.
9+
10+
## Command Organization
11+
12+
Commands live in `packages/b2c-cli/src/commands/`. The directory structure maps directly to command names:
13+
14+
```
15+
commands/
16+
├── code/
17+
│ ├── deploy.ts → b2c code deploy
18+
│ ├── activate.ts → b2c code activate
19+
│ └── list.ts → b2c code list
20+
├── ods/
21+
│ ├── create.ts → b2c ods create
22+
│ └── list.ts → b2c ods list
23+
└── mrt/
24+
└── env/
25+
└── var/
26+
└── set.ts → b2c mrt env var set
27+
```
28+
29+
## Command Class Hierarchy
30+
31+
Choose the appropriate base class based on what your command needs:
32+
33+
```
34+
BaseCommand (logging, JSON output, error handling)
35+
└─ OAuthCommand (OAuth authentication)
36+
├─ InstanceCommand (B2C instance: hostname, code version)
37+
│ ├─ CartridgeCommand (cartridge path + filters)
38+
│ ├─ JobCommand (job execution helpers)
39+
│ └─ WebDavCommand (WebDAV root directory)
40+
├─ MrtCommand (Managed Runtime API)
41+
└─ OdsCommand (On-Demand Sandbox API)
42+
```
43+
44+
Import from `@salesforce/b2c-tooling-sdk/cli`:
45+
46+
```typescript
47+
import { InstanceCommand, CartridgeCommand, OdsCommand } from '@salesforce/b2c-tooling-sdk/cli';
48+
```
49+
50+
## Standard Command Template
51+
52+
```typescript
53+
/*
54+
* Copyright (c) 2025, Salesforce, Inc.
55+
* SPDX-License-Identifier: Apache-2
56+
* For full license text, see the license.txt file in the repo root
57+
*/
58+
import {Args, Flags} from '@oclif/core';
59+
import {InstanceCommand} from '@salesforce/b2c-tooling-sdk/cli';
60+
import {t} from '../../i18n/index.js';
61+
62+
interface MyCommandResponse {
63+
success: boolean;
64+
data: SomeType[];
65+
}
66+
67+
export default class MyCommand extends InstanceCommand<typeof MyCommand> {
68+
static description = t('commands.topic.mycommand.description', 'Human-readable description');
69+
70+
static enableJsonFlag = true;
71+
72+
static examples = [
73+
'<%= config.bin %> <%= command.id %> arg1',
74+
'<%= config.bin %> <%= command.id %> --flag value',
75+
'<%= config.bin %> <%= command.id %> --json',
76+
];
77+
78+
static args = {
79+
name: Args.string({
80+
description: 'Description of the argument',
81+
required: true,
82+
}),
83+
};
84+
85+
static flags = {
86+
myFlag: Flags.string({
87+
char: 'm',
88+
description: 'Flag description',
89+
default: 'defaultValue',
90+
}),
91+
myBool: Flags.boolean({
92+
description: 'Boolean flag',
93+
default: false,
94+
}),
95+
};
96+
97+
async run(): Promise<MyCommandResponse> {
98+
// Validation - call appropriate require* methods
99+
this.requireServer();
100+
101+
// Access parsed args and flags
102+
const {name} = this.args;
103+
const {myFlag, myBool} = this.flags;
104+
105+
this.log(t('commands.topic.mycommand.working', 'Working on {{name}}...', {name}));
106+
107+
// Implementation
108+
const result = await this.instance.ocapi.GET('/some/endpoint');
109+
110+
if (!result.data) {
111+
this.error(t('commands.topic.mycommand.error', 'Failed: {{message}}', {
112+
message: result.response?.statusText || 'Unknown error',
113+
}));
114+
}
115+
116+
const response: MyCommandResponse = {
117+
success: true,
118+
data: result.data,
119+
};
120+
121+
// JSON mode returns the object directly (oclif handles serialization)
122+
if (this.jsonEnabled()) {
123+
return response;
124+
}
125+
126+
// Human-readable output
127+
this.log('Success!');
128+
return response;
129+
}
130+
}
131+
```
132+
133+
## Adding a New Topic
134+
135+
When creating a new command topic, add it to `packages/b2c-cli/package.json` in the oclif section:
136+
137+
```json
138+
{
139+
"oclif": {
140+
"topics": {
141+
"newtopic": {
142+
"description": "Commands for new functionality"
143+
},
144+
"newtopic:subtopic": {
145+
"description": "Subtopic commands"
146+
}
147+
}
148+
}
149+
}
150+
```
151+
152+
## Flag Patterns
153+
154+
### Common Flag Types
155+
156+
```typescript
157+
static flags = {
158+
// String with short alias and env var fallback
159+
server: Flags.string({
160+
char: 's',
161+
description: 'Server hostname',
162+
env: 'SFCC_SERVER',
163+
}),
164+
165+
// Integer with default
166+
timeout: Flags.integer({
167+
description: 'Timeout in seconds',
168+
default: 60,
169+
}),
170+
171+
// Boolean with --no-* variant
172+
wait: Flags.boolean({
173+
description: 'Wait for completion',
174+
default: true,
175+
allowNo: true, // enables --no-wait
176+
}),
177+
178+
// Comma-separated multiple values
179+
channels: Flags.string({
180+
description: 'Site channels (comma-separated)',
181+
multiple: true,
182+
multipleNonGreedy: true,
183+
delimiter: ',',
184+
}),
185+
186+
// Enum-like options
187+
format: Flags.string({
188+
description: 'Output format',
189+
options: ['json', 'csv', 'table'],
190+
default: 'table',
191+
}),
192+
193+
// Conditional flag
194+
secret: Flags.string({
195+
description: 'Client secret (required for private clients)',
196+
dependsOn: ['client-id'],
197+
}),
198+
};
199+
```
200+
201+
## Table Output
202+
203+
Use `createTable` for consistent tabular output:
204+
205+
```typescript
206+
import {createTable, TableRenderer, type ColumnDef} from '@salesforce/b2c-tooling-sdk/cli';
207+
208+
type MyData = {id: string; name: string; status: string};
209+
210+
const COLUMNS: Record<string, ColumnDef<MyData>> = {
211+
id: {
212+
header: 'ID',
213+
get: (item) => item.id,
214+
},
215+
name: {
216+
header: 'Name',
217+
get: (item) => item.name,
218+
},
219+
status: {
220+
header: 'Status',
221+
get: (item) => item.status,
222+
extended: true, // Only shown with --extended
223+
},
224+
};
225+
226+
const DEFAULT_COLUMNS = ['id', 'name'];
227+
const tableRenderer = new TableRenderer(COLUMNS);
228+
229+
// In run():
230+
tableRenderer.render(data, DEFAULT_COLUMNS);
231+
232+
// With --columns flag support:
233+
const columns = this.flags.columns
234+
? tableRenderer.validateColumnKeys(this.flags.columns.split(','))
235+
: DEFAULT_COLUMNS;
236+
tableRenderer.render(data, columns);
237+
```
238+
239+
## Internationalization
240+
241+
All user-facing strings use the `t()` function:
242+
243+
```typescript
244+
import {t} from '../../i18n/index.js';
245+
246+
// Basic usage
247+
this.log(t('commands.topic.cmd.message', 'Default message'));
248+
249+
// With interpolation
250+
this.log(t('commands.topic.cmd.working', 'Processing {{count}} items...', {count: 5}));
251+
252+
// For errors
253+
this.error(t('commands.topic.cmd.error', 'Failed: {{message}}', {message: err.message}));
254+
```
255+
256+
Keys follow the pattern: `commands.<topic>.<command>.<key>`
257+
258+
## Validation Methods
259+
260+
Base classes provide validation helpers:
261+
262+
```typescript
263+
// From OAuthCommand
264+
this.requireOAuthCredentials(); // Ensures clientId + clientSecret
265+
this.hasOAuthCredentials(); // Returns boolean
266+
267+
// From InstanceCommand
268+
this.requireServer(); // Ensures hostname is set
269+
this.requireCodeVersion(); // Ensures code version is set
270+
this.requireWebDavCredentials(); // Ensures WebDAV auth (Basic or OAuth)
271+
272+
// From MrtCommand
273+
this.requireMrtCredentials(); // Ensures MRT API credentials
274+
```
275+
276+
## Accessing Clients
277+
278+
InstanceCommand provides lazy-loaded clients:
279+
280+
```typescript
281+
// OCAPI client (OAuth authenticated)
282+
const result = await this.instance.ocapi.GET('/code_versions');
283+
284+
// WebDAV client (Basic or OAuth authenticated)
285+
await this.instance.webdav.put('path/to/file', buffer);
286+
287+
// ODS client (from OdsCommand)
288+
const sandboxes = await this.odsClient.GET('/sandboxes');
289+
290+
// MRT client (from MrtCommand)
291+
const projects = await this.mrtClient.GET('/api/projects/');
292+
```
293+
294+
## Error Handling
295+
296+
```typescript
297+
// Simple error (exits with code 1)
298+
this.error('Something went wrong');
299+
300+
// Error with suggestions
301+
this.error('Config file not found', {
302+
suggestions: ['Run b2c auth login first', 'Check your dw.json file'],
303+
});
304+
305+
// Warning (continues execution)
306+
this.warn('Deprecated flag used');
307+
308+
// Structured API errors
309+
if (result.error) {
310+
this.error(t('commands.topic.cmd.apiError', 'API error: {{message}}', {
311+
message: formatApiError(result.error),
312+
}));
313+
}
314+
```
315+
316+
## Creating a Command Checklist
317+
318+
1. Create file at `packages/b2c-cli/src/commands/<topic>/<command>.ts`
319+
2. Choose appropriate base class
320+
3. Define `static description`, `examples`, `args`, `flags`
321+
4. Set `static enableJsonFlag = true` for JSON output support
322+
5. Implement `run()` method with proper return type
323+
6. Add topic to `package.json` if new
324+
7. Add i18n keys for all user-facing strings
325+
8. Update skill in `plugins/b2c-cli/skills/b2c-<topic>/SKILL.md` if exists
326+
9. Update CLI reference docs in `docs/cli/<topic>.md`
327+
10. Build and test: `pnpm run build && pnpm --filter @salesforce/b2c-cli run test`

0 commit comments

Comments
 (0)