@soleri/cli 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/create.js +15 -5
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/doctor.js +7 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/extend.d.ts +2 -0
- package/dist/commands/extend.js +167 -0
- package/dist/commands/extend.js.map +1 -0
- package/dist/commands/list.js +52 -17
- package/dist/commands/list.js.map +1 -1
- package/dist/main.js +6 -1
- package/dist/main.js.map +1 -1
- package/dist/prompts/archetypes.d.ts +21 -0
- package/dist/prompts/archetypes.js +153 -0
- package/dist/prompts/archetypes.js.map +1 -0
- package/dist/prompts/create-wizard.d.ts +1 -1
- package/dist/prompts/create-wizard.js +299 -76
- package/dist/prompts/create-wizard.js.map +1 -1
- package/dist/prompts/playbook.d.ts +63 -0
- package/dist/prompts/playbook.js +154 -0
- package/dist/prompts/playbook.js.map +1 -0
- package/package.json +1 -1
- package/src/commands/create.ts +17 -6
- package/src/commands/doctor.ts +9 -2
- package/src/commands/extend.ts +197 -0
- package/src/commands/list.ts +60 -21
- package/src/main.ts +7 -1
- package/src/prompts/archetypes.ts +212 -0
- package/src/prompts/create-wizard.ts +345 -69
- package/src/prompts/playbook.ts +301 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-built agent archetypes that pre-fill the wizard.
|
|
3
|
+
* Each archetype provides sensible defaults for role, description,
|
|
4
|
+
* domains, principles, skills, and greeting — so the user can
|
|
5
|
+
* scaffold a full agent with minimal typing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface Archetype {
|
|
9
|
+
value: string;
|
|
10
|
+
label: string;
|
|
11
|
+
hint: string;
|
|
12
|
+
defaults: {
|
|
13
|
+
role: string;
|
|
14
|
+
description: string;
|
|
15
|
+
domains: string[];
|
|
16
|
+
principles: string[];
|
|
17
|
+
skills: string[];
|
|
18
|
+
tone: 'precise' | 'mentor' | 'pragmatic';
|
|
19
|
+
greetingTemplate: (name: string) => string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const ARCHETYPES: Archetype[] = [
|
|
24
|
+
{
|
|
25
|
+
value: 'code-reviewer',
|
|
26
|
+
label: 'Code Reviewer',
|
|
27
|
+
hint: 'Catches bugs, enforces patterns, reviews PRs before merge',
|
|
28
|
+
defaults: {
|
|
29
|
+
role: 'Catches bugs, enforces code patterns, and reviews pull requests before merge',
|
|
30
|
+
description:
|
|
31
|
+
'This agent reviews code for quality issues, anti-patterns, naming conventions, test coverage gaps, and architectural violations. It provides actionable feedback with concrete fix suggestions.',
|
|
32
|
+
domains: ['code-review', 'architecture'],
|
|
33
|
+
principles: [
|
|
34
|
+
'Actionable feedback only',
|
|
35
|
+
'Respect existing patterns',
|
|
36
|
+
'Simplicity over cleverness',
|
|
37
|
+
],
|
|
38
|
+
skills: [
|
|
39
|
+
'writing-plans',
|
|
40
|
+
'executing-plans',
|
|
41
|
+
'code-patrol',
|
|
42
|
+
'fix-and-learn',
|
|
43
|
+
'second-opinion',
|
|
44
|
+
],
|
|
45
|
+
tone: 'pragmatic',
|
|
46
|
+
greetingTemplate: (name) =>
|
|
47
|
+
`Hello! I'm ${name}. Drop a PR link or paste code — I'll review it for bugs, patterns, and quality.`,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
value: 'security-auditor',
|
|
52
|
+
label: 'Security Auditor',
|
|
53
|
+
hint: 'OWASP Top 10, dependency scanning, secrets detection',
|
|
54
|
+
defaults: {
|
|
55
|
+
role: 'Identifies vulnerabilities and enforces secure coding practices',
|
|
56
|
+
description:
|
|
57
|
+
'This agent scans code for security issues including OWASP Top 10, dependency vulnerabilities, secrets exposure, injection risks, and insecure configurations. It provides remediation guidance with severity ratings.',
|
|
58
|
+
domains: ['security', 'code-review'],
|
|
59
|
+
principles: [
|
|
60
|
+
'Security first',
|
|
61
|
+
'Fail closed, not open',
|
|
62
|
+
'Zero trust by default',
|
|
63
|
+
'Least privilege always',
|
|
64
|
+
],
|
|
65
|
+
skills: [
|
|
66
|
+
'writing-plans',
|
|
67
|
+
'executing-plans',
|
|
68
|
+
'code-patrol',
|
|
69
|
+
'fix-and-learn',
|
|
70
|
+
'vault-navigator',
|
|
71
|
+
],
|
|
72
|
+
tone: 'precise',
|
|
73
|
+
greetingTemplate: (name) =>
|
|
74
|
+
`Hello! I'm ${name}. I help identify vulnerabilities and enforce secure coding practices across your codebase.`,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
value: 'api-architect',
|
|
79
|
+
label: 'API Architect',
|
|
80
|
+
hint: 'REST/GraphQL design, contract validation, versioning',
|
|
81
|
+
defaults: {
|
|
82
|
+
role: 'Designs and validates APIs for consistency, usability, and correctness',
|
|
83
|
+
description:
|
|
84
|
+
'This agent reviews API designs for RESTful conventions, GraphQL best practices, versioning strategy, error handling, pagination patterns, and contract consistency. It catches breaking changes before they ship.',
|
|
85
|
+
domains: ['api-design', 'architecture'],
|
|
86
|
+
principles: [
|
|
87
|
+
'Convention over configuration',
|
|
88
|
+
'Design for the consumer, not the implementer',
|
|
89
|
+
'Respect existing patterns',
|
|
90
|
+
'Every migration must be reversible',
|
|
91
|
+
],
|
|
92
|
+
skills: [
|
|
93
|
+
'writing-plans',
|
|
94
|
+
'executing-plans',
|
|
95
|
+
'vault-navigator',
|
|
96
|
+
'vault-capture',
|
|
97
|
+
'second-opinion',
|
|
98
|
+
],
|
|
99
|
+
tone: 'mentor',
|
|
100
|
+
greetingTemplate: (name) =>
|
|
101
|
+
`Hello! I'm ${name}. Share your API design or schema — I'll review it for consistency, usability, and best practices.`,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
value: 'test-engineer',
|
|
106
|
+
label: 'Test Engineer',
|
|
107
|
+
hint: 'Test generation, coverage analysis, TDD workflow',
|
|
108
|
+
defaults: {
|
|
109
|
+
role: 'Generates tests, analyzes coverage, and enforces test-driven development',
|
|
110
|
+
description:
|
|
111
|
+
'This agent helps write comprehensive test suites, identifies coverage gaps, suggests edge cases, and guides TDD workflows. It supports unit, integration, and end-to-end testing strategies.',
|
|
112
|
+
domains: ['testing', 'code-review'],
|
|
113
|
+
principles: [
|
|
114
|
+
'Test everything that can break',
|
|
115
|
+
'Simplicity over cleverness',
|
|
116
|
+
'Actionable feedback only',
|
|
117
|
+
'Respect existing patterns',
|
|
118
|
+
],
|
|
119
|
+
skills: [
|
|
120
|
+
'writing-plans',
|
|
121
|
+
'executing-plans',
|
|
122
|
+
'test-driven-development',
|
|
123
|
+
'fix-and-learn',
|
|
124
|
+
'code-patrol',
|
|
125
|
+
],
|
|
126
|
+
tone: 'pragmatic',
|
|
127
|
+
greetingTemplate: (name) =>
|
|
128
|
+
`Hello! I'm ${name}. Point me at code that needs tests — I'll generate comprehensive suites and identify coverage gaps.`,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
value: 'devops-pilot',
|
|
133
|
+
label: 'DevOps Pilot',
|
|
134
|
+
hint: 'CI/CD pipelines, infrastructure, deployment automation',
|
|
135
|
+
defaults: {
|
|
136
|
+
role: 'Manages CI/CD pipelines, infrastructure, and deployment automation',
|
|
137
|
+
description:
|
|
138
|
+
'This agent helps design and maintain CI/CD pipelines, Docker configurations, infrastructure as code, monitoring setup, and deployment strategies. It follows reliability engineering best practices.',
|
|
139
|
+
domains: ['devops', 'architecture'],
|
|
140
|
+
principles: [
|
|
141
|
+
'Automate everything repeatable',
|
|
142
|
+
'Graceful degradation over hard failures',
|
|
143
|
+
'Observability built in from day one',
|
|
144
|
+
'Convention over configuration',
|
|
145
|
+
],
|
|
146
|
+
skills: [
|
|
147
|
+
'writing-plans',
|
|
148
|
+
'executing-plans',
|
|
149
|
+
'vault-navigator',
|
|
150
|
+
'fix-and-learn',
|
|
151
|
+
'knowledge-harvest',
|
|
152
|
+
],
|
|
153
|
+
tone: 'pragmatic',
|
|
154
|
+
greetingTemplate: (name) =>
|
|
155
|
+
`Hello! I'm ${name}. I help with CI/CD, infrastructure, and deployment — describe your setup or issue.`,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
value: 'database-architect',
|
|
160
|
+
label: 'Database Architect',
|
|
161
|
+
hint: 'Schema design, migrations, query optimization',
|
|
162
|
+
defaults: {
|
|
163
|
+
role: 'Designs database schemas, manages migrations, and optimizes queries',
|
|
164
|
+
description:
|
|
165
|
+
'This agent reviews database designs for normalization, indexing strategy, migration safety, query performance, and data integrity. It supports SQL and NoSQL patterns.',
|
|
166
|
+
domains: ['database', 'performance'],
|
|
167
|
+
principles: [
|
|
168
|
+
'Every migration must be reversible',
|
|
169
|
+
'Convention over configuration',
|
|
170
|
+
'Test everything that can break',
|
|
171
|
+
'Simplicity over cleverness',
|
|
172
|
+
],
|
|
173
|
+
skills: [
|
|
174
|
+
'writing-plans',
|
|
175
|
+
'executing-plans',
|
|
176
|
+
'vault-navigator',
|
|
177
|
+
'vault-capture',
|
|
178
|
+
'knowledge-harvest',
|
|
179
|
+
],
|
|
180
|
+
tone: 'precise',
|
|
181
|
+
greetingTemplate: (name) =>
|
|
182
|
+
`Hello! I'm ${name}. Share your schema, migration, or query — I'll review it for correctness and performance.`,
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
value: 'full-stack',
|
|
187
|
+
label: 'Full-Stack Assistant',
|
|
188
|
+
hint: 'General-purpose dev helper across the entire stack',
|
|
189
|
+
defaults: {
|
|
190
|
+
role: 'A general-purpose development assistant across the full stack',
|
|
191
|
+
description:
|
|
192
|
+
'This agent helps with frontend, backend, database, testing, and deployment tasks. It provides balanced guidance across the entire stack without deep specialization in any single area.',
|
|
193
|
+
domains: ['code-review', 'testing', 'architecture'],
|
|
194
|
+
principles: [
|
|
195
|
+
'Simplicity over cleverness',
|
|
196
|
+
'Test everything that can break',
|
|
197
|
+
'Respect existing patterns',
|
|
198
|
+
],
|
|
199
|
+
skills: [
|
|
200
|
+
'writing-plans',
|
|
201
|
+
'executing-plans',
|
|
202
|
+
'test-driven-development',
|
|
203
|
+
'code-patrol',
|
|
204
|
+
'fix-and-learn',
|
|
205
|
+
'vault-navigator',
|
|
206
|
+
],
|
|
207
|
+
tone: 'mentor',
|
|
208
|
+
greetingTemplate: (name) =>
|
|
209
|
+
`Hello! I'm ${name}. I help across the full stack — frontend, backend, testing, deployment. What are you working on?`,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
];
|
|
@@ -1,31 +1,73 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Interactive create wizard using @clack/prompts.
|
|
3
|
+
*
|
|
4
|
+
* Guided flow with archetypes, multiselects, and playbook-assisted
|
|
5
|
+
* custom fields. Happy path: 1 typed field (name), everything else
|
|
6
|
+
* is Enter / arrow keys / space bar.
|
|
3
7
|
*/
|
|
4
8
|
import * as p from '@clack/prompts';
|
|
5
9
|
import type { AgentConfig } from '@soleri/forge/lib';
|
|
10
|
+
import { ARCHETYPES, type Archetype } from './archetypes.js';
|
|
11
|
+
import {
|
|
12
|
+
DOMAIN_OPTIONS,
|
|
13
|
+
CUSTOM_DOMAIN_GUIDANCE,
|
|
14
|
+
PRINCIPLE_CATEGORIES,
|
|
15
|
+
CUSTOM_PRINCIPLE_GUIDANCE,
|
|
16
|
+
SKILL_CATEGORIES,
|
|
17
|
+
CORE_SKILLS,
|
|
18
|
+
ALL_OPTIONAL_SKILLS,
|
|
19
|
+
TONE_OPTIONS,
|
|
20
|
+
CUSTOM_ROLE_GUIDANCE,
|
|
21
|
+
CUSTOM_DESCRIPTION_GUIDANCE,
|
|
22
|
+
CUSTOM_GREETING_GUIDANCE,
|
|
23
|
+
} from './playbook.js';
|
|
24
|
+
|
|
25
|
+
/** Slugify a display name into a kebab-case ID. */
|
|
26
|
+
function slugify(name: string): string {
|
|
27
|
+
return name
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
30
|
+
.replace(/^-|-$/g, '');
|
|
31
|
+
}
|
|
6
32
|
|
|
7
33
|
/**
|
|
8
34
|
* Run the interactive create wizard and return an AgentConfig.
|
|
9
|
-
* Returns null if the user cancels.
|
|
35
|
+
* Returns null if the user cancels at any point.
|
|
10
36
|
*/
|
|
11
37
|
export async function runCreateWizard(initialName?: string): Promise<AgentConfig | null> {
|
|
12
38
|
p.intro('Create a new Soleri agent');
|
|
13
39
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
((
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
40
|
+
// ─── Step 1: Archetype ────────────────────────────────────
|
|
41
|
+
const archetypeChoices = [
|
|
42
|
+
...ARCHETYPES.map((a) => ({
|
|
43
|
+
value: a.value,
|
|
44
|
+
label: a.label,
|
|
45
|
+
hint: a.hint,
|
|
46
|
+
})),
|
|
47
|
+
{
|
|
48
|
+
value: '_custom',
|
|
49
|
+
label: '\u2726 Create Custom',
|
|
50
|
+
hint: "I'll guide you through defining your own agent type",
|
|
51
|
+
},
|
|
52
|
+
];
|
|
23
53
|
|
|
24
|
-
|
|
54
|
+
const archetypeValue = await p.select({
|
|
55
|
+
message: 'What kind of agent are you building?',
|
|
56
|
+
options: archetypeChoices,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (p.isCancel(archetypeValue)) return null;
|
|
60
|
+
|
|
61
|
+
const archetype: Archetype | undefined = ARCHETYPES.find((a) => a.value === archetypeValue);
|
|
62
|
+
const isCustom = archetypeValue === '_custom';
|
|
63
|
+
|
|
64
|
+
// ─── Step 2: Display name ─────────────────────────────────
|
|
65
|
+
const nameDefault = archetype ? archetype.label : undefined;
|
|
25
66
|
|
|
26
67
|
const name = (await p.text({
|
|
27
68
|
message: 'Display name',
|
|
28
|
-
placeholder: 'My Agent',
|
|
69
|
+
placeholder: nameDefault ?? 'My Agent',
|
|
70
|
+
initialValue: initialName ?? nameDefault,
|
|
29
71
|
validate: (v) => {
|
|
30
72
|
if (!v || v.length > 50) return 'Required (max 50 chars)';
|
|
31
73
|
},
|
|
@@ -33,81 +75,313 @@ export async function runCreateWizard(initialName?: string): Promise<AgentConfig
|
|
|
33
75
|
|
|
34
76
|
if (p.isCancel(name)) return null;
|
|
35
77
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
78
|
+
// ─── Step 3: Agent ID (auto-derived, confirm or edit) ─────
|
|
79
|
+
const autoId = slugify(name);
|
|
80
|
+
|
|
81
|
+
const id = (await p.text({
|
|
82
|
+
message: 'Agent ID (auto-generated, press Enter to accept)',
|
|
83
|
+
placeholder: autoId,
|
|
84
|
+
initialValue: autoId,
|
|
85
|
+
validate: (v = '') => {
|
|
86
|
+
if (!/^[a-z][a-z0-9-]*$/.test(v)) return 'Must be kebab-case (e.g., "my-agent")';
|
|
41
87
|
},
|
|
42
88
|
})) as string;
|
|
43
89
|
|
|
44
|
-
if (p.isCancel(
|
|
90
|
+
if (p.isCancel(id)) return null;
|
|
45
91
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
placeholder: 'This agent helps developers with...',
|
|
49
|
-
validate: (v) => {
|
|
50
|
-
if (!v || v.length < 10 || v.length > 500) return 'Required (10-500 chars)';
|
|
51
|
-
},
|
|
52
|
-
})) as string;
|
|
92
|
+
// ─── Step 4: Role ─────────────────────────────────────────
|
|
93
|
+
let role: string;
|
|
53
94
|
|
|
54
|
-
if (
|
|
95
|
+
if (isCustom) {
|
|
96
|
+
p.note(
|
|
97
|
+
[
|
|
98
|
+
CUSTOM_ROLE_GUIDANCE.instruction,
|
|
99
|
+
'',
|
|
100
|
+
'Examples:',
|
|
101
|
+
...CUSTOM_ROLE_GUIDANCE.examples.map((e) => ` "${e}"`),
|
|
102
|
+
].join('\n'),
|
|
103
|
+
'\u2726 Custom Agent Playbook',
|
|
104
|
+
);
|
|
55
105
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
.filter(Boolean);
|
|
64
|
-
if (parts.length === 0) return 'At least one domain required';
|
|
65
|
-
for (const d of parts) {
|
|
66
|
-
if (!/^[a-z][a-z0-9-]*$/.test(d)) return `Invalid domain "${d}" — must be kebab-case`;
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
})) as string;
|
|
106
|
+
const customRole = (await p.text({
|
|
107
|
+
message: 'What does your agent do? (one sentence)',
|
|
108
|
+
placeholder: 'Validates GraphQL schemas against federation rules',
|
|
109
|
+
validate: (v) => {
|
|
110
|
+
if (!v || v.length > 100) return 'Required (max 100 chars)';
|
|
111
|
+
},
|
|
112
|
+
})) as string;
|
|
70
113
|
|
|
71
|
-
|
|
114
|
+
if (p.isCancel(customRole)) return null;
|
|
115
|
+
role = customRole;
|
|
116
|
+
} else {
|
|
117
|
+
const prefilledRole = archetype!.defaults.role;
|
|
118
|
+
const editedRole = (await p.text({
|
|
119
|
+
message: 'Role (pre-filled, press Enter to accept)',
|
|
120
|
+
initialValue: prefilledRole,
|
|
121
|
+
validate: (v) => {
|
|
122
|
+
if (!v || v.length > 100) return 'Required (max 100 chars)';
|
|
123
|
+
},
|
|
124
|
+
})) as string;
|
|
72
125
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
.filter(Boolean);
|
|
126
|
+
if (p.isCancel(editedRole)) return null;
|
|
127
|
+
role = editedRole;
|
|
128
|
+
}
|
|
77
129
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
130
|
+
// ─── Step 5: Description ──────────────────────────────────
|
|
131
|
+
let description: string;
|
|
132
|
+
|
|
133
|
+
if (isCustom) {
|
|
134
|
+
p.note(
|
|
135
|
+
[
|
|
136
|
+
CUSTOM_DESCRIPTION_GUIDANCE.instruction,
|
|
137
|
+
'',
|
|
138
|
+
'Example:',
|
|
139
|
+
...CUSTOM_DESCRIPTION_GUIDANCE.examples.map((e) => ` "${e}"`),
|
|
140
|
+
].join('\n'),
|
|
141
|
+
'\u2726 Description',
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const customDesc = (await p.text({
|
|
145
|
+
message: 'Describe your agent in detail',
|
|
146
|
+
placeholder: 'This agent helps developers with...',
|
|
147
|
+
validate: (v) => {
|
|
148
|
+
if (!v || v.length < 10 || v.length > 500) return 'Required (10-500 chars)';
|
|
149
|
+
},
|
|
150
|
+
})) as string;
|
|
151
|
+
|
|
152
|
+
if (p.isCancel(customDesc)) return null;
|
|
153
|
+
description = customDesc;
|
|
154
|
+
} else {
|
|
155
|
+
const prefilledDesc = archetype!.defaults.description;
|
|
156
|
+
const editedDesc = (await p.text({
|
|
157
|
+
message: 'Description (pre-filled, press Enter to accept)',
|
|
158
|
+
initialValue: prefilledDesc,
|
|
159
|
+
validate: (v) => {
|
|
160
|
+
if (!v || v.length < 10 || v.length > 500) return 'Required (10-500 chars)';
|
|
161
|
+
},
|
|
162
|
+
})) as string;
|
|
163
|
+
|
|
164
|
+
if (p.isCancel(editedDesc)) return null;
|
|
165
|
+
description = editedDesc;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ─── Step 6: Domains (multiselect) ────────────────────────
|
|
169
|
+
const preselectedDomains = new Set(archetype?.defaults.domains ?? []);
|
|
170
|
+
|
|
171
|
+
const domainChoices = [
|
|
172
|
+
...DOMAIN_OPTIONS.map((d) => ({
|
|
173
|
+
value: d.value,
|
|
174
|
+
label: d.label,
|
|
175
|
+
hint: d.hint,
|
|
176
|
+
})),
|
|
177
|
+
{
|
|
178
|
+
value: '_custom',
|
|
179
|
+
label: '\u2726 Add custom domain...',
|
|
180
|
+
hint: 'Define your own domain with playbook guidance',
|
|
88
181
|
},
|
|
89
|
-
|
|
182
|
+
];
|
|
90
183
|
|
|
91
|
-
|
|
184
|
+
// Pre-select archetype domains via initialValues
|
|
185
|
+
const domainSelection = await p.multiselect({
|
|
186
|
+
message: 'Select domains (areas of expertise)',
|
|
187
|
+
options: domainChoices,
|
|
188
|
+
initialValues: [...preselectedDomains],
|
|
189
|
+
required: true,
|
|
190
|
+
});
|
|
92
191
|
|
|
93
|
-
|
|
94
|
-
.split('\n')
|
|
95
|
-
.map((s) => s.trim())
|
|
96
|
-
.filter(Boolean);
|
|
192
|
+
if (p.isCancel(domainSelection)) return null;
|
|
97
193
|
|
|
98
|
-
const
|
|
194
|
+
const domains = (domainSelection as string[]).filter((d) => d !== '_custom');
|
|
195
|
+
const wantsCustomDomain = (domainSelection as string[]).includes('_custom');
|
|
196
|
+
|
|
197
|
+
if (wantsCustomDomain) {
|
|
198
|
+
p.note(
|
|
199
|
+
[
|
|
200
|
+
CUSTOM_DOMAIN_GUIDANCE.instruction,
|
|
201
|
+
'',
|
|
202
|
+
'Examples:',
|
|
203
|
+
...CUSTOM_DOMAIN_GUIDANCE.examples.map((e) => ` ${e}`),
|
|
204
|
+
'',
|
|
205
|
+
'Avoid:',
|
|
206
|
+
...CUSTOM_DOMAIN_GUIDANCE.antiExamples.map((e) => ` \u2717 ${e}`),
|
|
207
|
+
].join('\n'),
|
|
208
|
+
'\u2726 Custom Domain',
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const customDomain = (await p.text({
|
|
212
|
+
message: 'Custom domain name (kebab-case)',
|
|
213
|
+
placeholder: 'graphql-federation',
|
|
214
|
+
validate: (v = '') => {
|
|
215
|
+
if (!/^[a-z][a-z0-9-]*$/.test(v)) return 'Must be kebab-case';
|
|
216
|
+
if (domains.includes(v)) return 'Already selected';
|
|
217
|
+
},
|
|
218
|
+
})) as string;
|
|
219
|
+
|
|
220
|
+
if (!p.isCancel(customDomain)) {
|
|
221
|
+
domains.push(customDomain);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (domains.length === 0) {
|
|
226
|
+
p.log.error('At least one domain is required');
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ─── Step 7: Principles (multiselect) ─────────────────────
|
|
231
|
+
const preselectedPrinciples = new Set(archetype?.defaults.principles ?? []);
|
|
232
|
+
|
|
233
|
+
// Flatten categories into a single options list with group labels
|
|
234
|
+
const principleChoices = PRINCIPLE_CATEGORIES.flatMap((cat) => cat.options.map((o) => ({
|
|
235
|
+
value: o.value,
|
|
236
|
+
label: o.label,
|
|
237
|
+
hint: cat.label,
|
|
238
|
+
})));
|
|
239
|
+
|
|
240
|
+
principleChoices.push({
|
|
241
|
+
value: '_custom',
|
|
242
|
+
label: '\u2726 Add custom principle...',
|
|
243
|
+
hint: 'Write your own guiding principle',
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const principleSelection = await p.multiselect({
|
|
247
|
+
message: 'Select guiding principles',
|
|
248
|
+
options: principleChoices,
|
|
249
|
+
initialValues: [...preselectedPrinciples],
|
|
250
|
+
required: true,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
if (p.isCancel(principleSelection)) return null;
|
|
254
|
+
|
|
255
|
+
const principles = (principleSelection as string[]).filter((p) => p !== '_custom');
|
|
256
|
+
const wantsCustomPrinciple = (principleSelection as string[]).includes('_custom');
|
|
257
|
+
|
|
258
|
+
if (wantsCustomPrinciple) {
|
|
259
|
+
p.note(
|
|
260
|
+
[
|
|
261
|
+
CUSTOM_PRINCIPLE_GUIDANCE.instruction,
|
|
262
|
+
'',
|
|
263
|
+
'Good principles are specific and actionable:',
|
|
264
|
+
...CUSTOM_PRINCIPLE_GUIDANCE.examples.map((e) => ` \u2713 "${e}"`),
|
|
265
|
+
'',
|
|
266
|
+
'Avoid vague principles:',
|
|
267
|
+
...CUSTOM_PRINCIPLE_GUIDANCE.antiExamples.map((e) => ` \u2717 ${e}`),
|
|
268
|
+
].join('\n'),
|
|
269
|
+
'\u2726 Custom Principle',
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const customPrinciple = (await p.text({
|
|
273
|
+
message: 'Your custom principle',
|
|
274
|
+
placeholder: 'Every public API must have a deprecation path',
|
|
275
|
+
validate: (v) => {
|
|
276
|
+
if (!v) return 'Required';
|
|
277
|
+
if (v.length > 100) return 'Max 100 chars';
|
|
278
|
+
},
|
|
279
|
+
})) as string;
|
|
280
|
+
|
|
281
|
+
if (!p.isCancel(customPrinciple)) {
|
|
282
|
+
principles.push(customPrinciple);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (principles.length === 0) {
|
|
287
|
+
p.log.error('At least one principle is required');
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ─── Step 8: Communication tone ───────────────────────────
|
|
292
|
+
const defaultTone = archetype?.defaults.tone ?? 'pragmatic';
|
|
293
|
+
|
|
294
|
+
const tone = await p.select({
|
|
295
|
+
message: 'Communication tone',
|
|
296
|
+
options: TONE_OPTIONS.map((t) => ({
|
|
297
|
+
value: t.value,
|
|
298
|
+
label: t.label,
|
|
299
|
+
hint: t.hint,
|
|
300
|
+
})),
|
|
301
|
+
initialValue: defaultTone,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
if (p.isCancel(tone)) return null;
|
|
305
|
+
|
|
306
|
+
// ─── Step 9: Skills (multiselect) ─────────────────────────
|
|
307
|
+
const preselectedSkills = new Set(archetype?.defaults.skills ?? []);
|
|
308
|
+
|
|
309
|
+
p.note(`Always included: ${CORE_SKILLS.join(', ')}`, 'Core Skills');
|
|
310
|
+
|
|
311
|
+
const skillChoices = SKILL_CATEGORIES.flatMap((cat) =>
|
|
312
|
+
cat.options.map((o) => ({
|
|
313
|
+
value: o.value,
|
|
314
|
+
label: o.label,
|
|
315
|
+
hint: `${o.hint} (${cat.label})`,
|
|
316
|
+
})),
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
const skillSelection = await p.multiselect({
|
|
320
|
+
message: 'Select additional skills',
|
|
321
|
+
options: skillChoices,
|
|
322
|
+
initialValues: [...preselectedSkills].filter((s) => ALL_OPTIONAL_SKILLS.includes(s)),
|
|
323
|
+
required: false,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
if (p.isCancel(skillSelection)) return null;
|
|
327
|
+
|
|
328
|
+
const selectedSkills = [...CORE_SKILLS, ...(skillSelection as string[])];
|
|
329
|
+
|
|
330
|
+
// ─── Step 10: Greeting (auto or custom) ───────────────────
|
|
331
|
+
const autoGreeting = archetype
|
|
332
|
+
? archetype.defaults.greetingTemplate(name)
|
|
333
|
+
: `Hello! I'm ${name}. I ${role[0].toLowerCase()}${role.slice(1)}.`;
|
|
334
|
+
|
|
335
|
+
const greetingChoice = await p.select({
|
|
99
336
|
message: 'Greeting message',
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
337
|
+
options: [
|
|
338
|
+
{
|
|
339
|
+
value: 'auto',
|
|
340
|
+
label: `Auto \u2014 "${autoGreeting.length > 70 ? autoGreeting.slice(0, 67) + '...' : autoGreeting}"`,
|
|
341
|
+
hint: 'Generated from name + role',
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
value: 'custom',
|
|
345
|
+
label: '\u2726 Custom \u2014 Write your own greeting',
|
|
346
|
+
hint: 'Opens playbook-guided text field',
|
|
347
|
+
},
|
|
348
|
+
],
|
|
349
|
+
initialValue: 'auto',
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
if (p.isCancel(greetingChoice)) return null;
|
|
353
|
+
|
|
354
|
+
let greeting: string;
|
|
355
|
+
|
|
356
|
+
if (greetingChoice === 'custom') {
|
|
357
|
+
p.note(
|
|
358
|
+
[
|
|
359
|
+
CUSTOM_GREETING_GUIDANCE.instruction,
|
|
360
|
+
'',
|
|
361
|
+
'Examples:',
|
|
362
|
+
...CUSTOM_GREETING_GUIDANCE.examples.map((e) => ` "${e}"`),
|
|
363
|
+
].join('\n'),
|
|
364
|
+
'\u2726 Custom Greeting',
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
const customGreeting = (await p.text({
|
|
368
|
+
message: 'Your greeting',
|
|
369
|
+
placeholder: `Hello! I'm ${name}...`,
|
|
370
|
+
validate: (v) => {
|
|
371
|
+
if (!v || v.length < 10 || v.length > 300) return 'Required (10-300 chars)';
|
|
372
|
+
},
|
|
373
|
+
})) as string;
|
|
105
374
|
|
|
106
|
-
|
|
375
|
+
if (p.isCancel(customGreeting)) return null;
|
|
376
|
+
greeting = customGreeting;
|
|
377
|
+
} else {
|
|
378
|
+
greeting = autoGreeting;
|
|
379
|
+
}
|
|
107
380
|
|
|
381
|
+
// ─── Step 11: Output directory ────────────────────────────
|
|
108
382
|
const outputDir = (await p.text({
|
|
109
383
|
message: 'Output directory',
|
|
110
|
-
|
|
384
|
+
initialValue: process.cwd(),
|
|
111
385
|
placeholder: process.cwd(),
|
|
112
386
|
validate: (v) => {
|
|
113
387
|
if (!v) return 'Required';
|
|
@@ -123,7 +397,9 @@ export async function runCreateWizard(initialName?: string): Promise<AgentConfig
|
|
|
123
397
|
description,
|
|
124
398
|
domains,
|
|
125
399
|
principles,
|
|
400
|
+
tone: tone as 'precise' | 'mentor' | 'pragmatic',
|
|
126
401
|
greeting,
|
|
127
402
|
outputDir,
|
|
403
|
+
skills: selectedSkills,
|
|
128
404
|
};
|
|
129
405
|
}
|