@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.
@@ -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
- const id =
15
- initialName ??
16
- ((await p.text({
17
- message: 'Agent ID (kebab-case)',
18
- placeholder: 'my-agent',
19
- validate: (v = '') => {
20
- if (!/^[a-z][a-z0-9-]*$/.test(v)) return 'Must be kebab-case (e.g., "my-agent")';
21
- },
22
- })) as string);
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
- if (p.isCancel(id)) return null;
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
- const role = (await p.text({
37
- message: 'Role (one line)',
38
- placeholder: 'A helpful AI assistant for...',
39
- validate: (v) => {
40
- if (!v || v.length > 100) return 'Required (max 100 chars)';
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(role)) return null;
90
+ if (p.isCancel(id)) return null;
45
91
 
46
- const description = (await p.text({
47
- message: 'Description',
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 (p.isCancel(description)) return null;
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
- const domainsRaw = (await p.text({
57
- message: 'Domains (comma-separated, kebab-case)',
58
- placeholder: 'api-design, security, testing',
59
- validate: (v = '') => {
60
- const parts = v
61
- .split(',')
62
- .map((s) => s.trim())
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
- if (p.isCancel(domainsRaw)) return null;
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
- const domains = domainsRaw
74
- .split(',')
75
- .map((s) => s.trim())
76
- .filter(Boolean);
126
+ if (p.isCancel(editedRole)) return null;
127
+ role = editedRole;
128
+ }
77
129
 
78
- const principlesRaw = (await p.text({
79
- message: 'Principles (one per line)',
80
- placeholder: 'Security first\nSimplicity over cleverness\nTest everything',
81
- validate: (v = '') => {
82
- const lines = v
83
- .split('\n')
84
- .map((s) => s.trim())
85
- .filter(Boolean);
86
- if (lines.length === 0) return 'At least one principle required';
87
- if (lines.length > 10) return 'Max 10 principles';
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
- })) as string;
182
+ ];
90
183
 
91
- if (p.isCancel(principlesRaw)) return null;
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
- const principles = principlesRaw
94
- .split('\n')
95
- .map((s) => s.trim())
96
- .filter(Boolean);
192
+ if (p.isCancel(domainSelection)) return null;
97
193
 
98
- const greeting = (await p.text({
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
- placeholder: `Hello! I'm ${name}, your AI assistant for...`,
101
- validate: (v) => {
102
- if (!v || v.length < 10 || v.length > 300) return 'Required (10-300 chars)';
103
- },
104
- })) as string;
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
- if (p.isCancel(greeting)) return null;
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
- defaultValue: process.cwd(),
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
  }