Skip to content

Commit 6523d82

Browse files
authored
Merge pull request #72 from salesforcecli/sh/more-refactoring
W-17669620 - feat:more refactoring
2 parents 07de93c + 46e5272 commit 6523d82

File tree

7 files changed

+84
-24
lines changed

7 files changed

+84
-24
lines changed

command-snapshot.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
},
2020
{
2121
"alias": [],
22-
"command": "agent:generate:spec",
22+
"command": "agent:generate:agent-spec",
2323
"flagAliases": [],
24-
"flagChars": ["o", "t"],
24+
"flagChars": ["o", "p"],
2525
"flags": [
2626
"agent-user",
2727
"api-version",

messages/agent.create.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ Name (label) of the new agent.
2828

2929
API name of the new agent; if not specified, the API name is derived from the agent name (label); the API name must not exist in the org.
3030

31+
# flags.agent-api-name.prompt
32+
33+
API name of the new agent (default = %s)
34+
3135
# flags.planner-id.summary
3236

3337
An existing GenAiPlanner ID to associate with the agent.
File renamed without changes.
File renamed without changes.

src/commands/agent/create.ts

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
77
import { resolve } from 'node:path';
8-
import { readFileSync, writeFileSync } from 'node:fs';
8+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
99
import YAML from 'yaml';
1010
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
1111
import { Lifecycle, Messages } from '@salesforce/core';
1212
import { MultiStageOutput } from '@oclif/multi-stage-output';
13+
import { input as inquirerInput } from '@inquirer/prompts';
1314
import { colorize } from '@oclif/core/ux';
1415
import {
1516
Agent,
@@ -20,7 +21,8 @@ import {
2021
generateAgentApiName,
2122
} from '@salesforce/agents';
2223
import { FlaggablePrompt, makeFlags, promptForFlag, validateAgentType } from '../../flags.js';
23-
import { AgentSpecFileContents } from './generate/spec.js';
24+
import { theme } from '../../inquirer-theme.js';
25+
import { AgentSpecFileContents } from './generate/agent-spec.js';
2426

2527
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
2628
const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.create');
@@ -43,6 +45,33 @@ const FLAGGABLE_PROMPTS = {
4345
validate: (d: string): boolean | string => d.length > 0 || 'Agent Name cannot be empty',
4446
required: true,
4547
},
48+
'agent-api-name': {
49+
message: messages.getMessage('flags.agent-api-name.summary'),
50+
validate: (d: string): boolean | string => {
51+
if (d.length === 0) {
52+
return true;
53+
}
54+
if (d.length > 80) {
55+
return 'API name cannot be over 80 characters.';
56+
}
57+
const regex = /^[A-Za-z][A-Za-z0-9_]*[A-Za-z0-9]+$/;
58+
if (!regex.test(d)) {
59+
return 'Invalid API name.';
60+
}
61+
return true;
62+
},
63+
},
64+
spec: {
65+
message: messages.getMessage('flags.spec.summary'),
66+
validate: (d: string): boolean | string => {
67+
const specPath = resolve(d);
68+
if (!existsSync(specPath)) {
69+
return 'Please enter an existing agent spec (yaml) file';
70+
}
71+
return true;
72+
},
73+
required: true,
74+
},
4675
} satisfies Record<string, FlaggablePrompt>;
4776

4877
export default class AgentCreate extends SfCommand<AgentCreateResult> {
@@ -56,18 +85,9 @@ export default class AgentCreate extends SfCommand<AgentCreateResult> {
5685
'target-org': Flags.requiredOrg(),
5786
'api-version': Flags.orgApiVersion(),
5887
...makeFlags(FLAGGABLE_PROMPTS),
59-
spec: Flags.file({
60-
// char: 'f',
61-
summary: messages.getMessage('flags.spec.summary'),
62-
exists: true,
63-
required: true,
64-
}),
6588
preview: Flags.boolean({
6689
summary: messages.getMessage('flags.preview.summary'),
6790
}),
68-
'agent-api-name': Flags.string({
69-
summary: messages.getMessage('flags.agent-api-name.summary'),
70-
}),
7191
// This would be used as more of an agent update than create.
7292
// Could possibly move to an `agent update` command.
7393
'planner-id': Flags.string({
@@ -81,17 +101,36 @@ export default class AgentCreate extends SfCommand<AgentCreateResult> {
81101
const { flags } = await this.parse(AgentCreate);
82102

83103
// throw error if --json is used and not all required flags are provided
84-
if (this.jsonEnabled() && !flags['agent-name']) {
85-
throw messages.createError('error.missingRequiredFlags', ['agent-name']);
104+
if (this.jsonEnabled()) {
105+
if (!flags['agent-name']) {
106+
throw messages.createError('error.missingRequiredFlags', ['agent-name']);
107+
}
108+
if (!flags.spec) {
109+
throw messages.createError('error.missingRequiredFlags', ['spec']);
110+
}
86111
}
87112

113+
// If we don't have an agent spec yet, prompt.
114+
const specPath = flags.spec ?? (await promptForFlag(FLAGGABLE_PROMPTS['spec']));
115+
88116
// Read the agent spec and validate
89-
const inputSpec = YAML.parse(readFileSync(resolve(flags.spec), 'utf8')) as AgentSpecFileContents;
117+
const inputSpec = YAML.parse(readFileSync(resolve(specPath), 'utf8')) as AgentSpecFileContents;
90118
validateSpec(inputSpec);
91119

92120
// If we don't have an agent name yet, prompt.
93121
const agentName = flags['agent-name'] ?? (await promptForFlag(FLAGGABLE_PROMPTS['agent-name']));
94-
const agentApiName = flags['agent-api-name'] ?? generateAgentApiName(agentName);
122+
let agentApiName = flags['agent-api-name'];
123+
if (!agentApiName) {
124+
agentApiName = generateAgentApiName(agentName);
125+
const promptedValue = await inquirerInput({
126+
message: messages.getMessage('flags.agent-api-name.prompt', [agentApiName]),
127+
validate: FLAGGABLE_PROMPTS['agent-api-name'].validate,
128+
theme,
129+
});
130+
if (promptedValue?.length) {
131+
agentApiName = promptedValue;
132+
}
133+
}
95134

96135
let title: string;
97136
const stages = [MSO_STAGES.parse];

src/commands/agent/generate/spec.ts renamed to src/commands/agent/generate/agent-spec.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Agent, AgentJobSpecCreateConfigV2, AgentJobSpecV2 } from '@salesforce/a
1313
import { FlaggablePrompt, makeFlags, promptForFlag, validateAgentType, validateMaxTopics } from '../../../flags.js';
1414

1515
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
16-
const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.generate.spec');
16+
const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.generate.agent-spec');
1717

1818
// The JSON response returned by the command.
1919
export type AgentCreateSpecResult = {
@@ -34,7 +34,6 @@ export const FLAGGABLE_PROMPTS = {
3434
type: {
3535
message: messages.getMessage('flags.type.summary'),
3636
validate: (d: string): boolean | string => d.length > 0 || 'Type cannot be empty',
37-
char: 't',
3837
options: ['customer', 'internal'],
3938
required: true,
4039
},
@@ -122,7 +121,7 @@ export default class AgentCreateSpec extends SfCommand<AgentCreateSpecResult> {
122121
}),
123122
'output-file': Flags.file({
124123
summary: messages.getMessage('flags.output-file.summary'),
125-
default: join('config', 'agentSpec.yaml'),
124+
default: join('specs', 'agentSpec.yaml'),
126125
}),
127126
'full-interview': Flags.boolean({
128127
summary: messages.getMessage('flags.full-interview.summary'),
@@ -136,6 +135,7 @@ export default class AgentCreateSpec extends SfCommand<AgentCreateSpecResult> {
136135
}),
137136
'no-prompt': Flags.boolean({
138137
summary: messages.getMessage('flags.no-prompt.summary'),
138+
char: 'p',
139139
}),
140140
};
141141

@@ -275,8 +275,25 @@ const buildSpecFile = (
275275
propertyOrder.map((prop) => {
276276
// @ts-expect-error need better typing of the array.
277277
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
278-
const val = specResponse[prop] ?? extraProps[prop];
278+
let val = specResponse[prop] ?? extraProps[prop];
279279
if (val != null || (typeof val === 'string' && val.length > 0)) {
280+
if (prop === 'topics') {
281+
// Ensure topics are [{name, description}]
282+
val = (val as string[]).map((t) =>
283+
Object.keys(t)
284+
.sort()
285+
.reverse()
286+
.reduce(
287+
(acc, key) => ({
288+
...acc,
289+
// @ts-expect-error need better typing of the array.
290+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
291+
[key]: t[key],
292+
}),
293+
{}
294+
)
295+
);
296+
}
280297
// @ts-expect-error need better typing of the array.
281298
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
282299
specFileContents[prop] = val;

test/commands/agent/generate/spec.nut.ts renamed to test/commands/agent/generate/agent-spec.nut.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { join, resolve } from 'node:path';
88
import { statSync } from 'node:fs';
99
import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit';
1010
import { expect } from 'chai';
11-
import { AgentCreateSpecResult } from '../../../../src/commands/agent/generate/spec.js';
11+
import { AgentCreateSpecResult } from '../../../../src/commands/agent/generate/agent-spec.js';
1212

1313
describe('agent generate spec NUTs', () => {
1414
let session: TestSession;
@@ -32,13 +32,13 @@ describe('agent generate spec NUTs', () => {
3232
const companyName = 'Test Company Name';
3333
const companyDescription = 'Test Company Description';
3434
const companyWebsite = 'https://test-company-website.org';
35-
const command = `agent generate spec ${targetOrg} --type ${type} --role "${role}" --company-name "${companyName}" --company-description "${companyDescription}" --company-website ${companyWebsite} --json`;
35+
const command = `agent generate agent-spec ${targetOrg} --type ${type} --role "${role}" --company-name "${companyName}" --company-description "${companyDescription}" --company-website ${companyWebsite} --json`;
3636
const output = execCmd<AgentCreateSpecResult>(command, {
3737
ensureExitCode: 0,
3838
env: { ...process.env, SF_MOCK_DIR: mockDir },
3939
}).jsonOutput;
4040

41-
const expectedFilePath = resolve(session.project.dir, 'config', 'agentSpec.yaml');
41+
const expectedFilePath = resolve(session.project.dir, 'specs', 'agentSpec.yaml');
4242
expect(output?.result.isSuccess).to.be.true;
4343
expect(output?.result.specPath).to.equal(expectedFilePath);
4444
expect(output?.result.agentType).to.equal(type);

0 commit comments

Comments
 (0)