Skip to content

Commit 86c17d2

Browse files
committed
feat: simplify agent creation wizard
1 parent cf5bf28 commit 86c17d2

15 files changed

Lines changed: 361 additions & 79 deletions

apps/cli/src/commands/agent/agents/ui/AgentsListView.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -199,16 +199,15 @@ export function AgentsListView(props: {
199199
<Box marginY={1}>{renderCreateNew()}</Box>
200200
) : null}
201201
<Text dimColor>
202-
No agents found. Create specialized subagents that the CLI can
203-
delegate to.
202+
No agents found. Press Enter on Create new agent to start.
204203
</Text>
205204
<Text dimColor>
206-
Each subagent has its own context window, custom system prompt, and
207-
specific tools.
205+
Choose Quick draft for the shortest path, or Customize draft when
206+
you need tools, model, or color control.
208207
</Text>
209208
<Text dimColor>
210-
Try creating: Code Reviewer, Code Simplifier, Security Reviewer,
211-
Tech Lead, or UX Reviewer.
209+
Useful starters: code-reviewer, test-writer, security-reviewer,
210+
tech-lead, ux-reviewer.
212211
</Text>
213212
{props.source !== 'built-in' &&
214213
props.agents.some(a => a.source === 'built-in') ? (
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { describe, expect, test } from 'bun:test'
2+
import { getWizardStepSubtitle } from './Wizard'
3+
4+
describe('getWizardStepSubtitle', () => {
5+
test('uses an open-ended label before the setup path is selected', () => {
6+
expect(
7+
getWizardStepSubtitle(
8+
{ stepIndex: 0, totalSteps: 10, wizardData: {} },
9+
'Choose location',
10+
),
11+
).toBe('Step 1 - Choose location')
12+
})
13+
14+
test('shows the short quick path after generation', () => {
15+
expect(
16+
getWizardStepSubtitle(
17+
{
18+
stepIndex: 9,
19+
totalSteps: 10,
20+
wizardData: { method: 'quickGenerate' },
21+
},
22+
'Review and save',
23+
),
24+
).toBe('Step 4/4 - Review and save')
25+
})
26+
27+
test('shows the customization path when settings are exposed', () => {
28+
expect(
29+
getWizardStepSubtitle(
30+
{
31+
stepIndex: 6,
32+
totalSteps: 10,
33+
wizardData: { method: 'customGenerate' },
34+
},
35+
'Select tools',
36+
),
37+
).toBe('Step 4/7 - Select tools')
38+
})
39+
40+
test('shows the full manual path for manual setup', () => {
41+
expect(
42+
getWizardStepSubtitle(
43+
{
44+
stepIndex: 3,
45+
totalSteps: 10,
46+
wizardData: { method: 'manual' },
47+
},
48+
'Name the agent',
49+
),
50+
).toBe('Step 3/9 - Name the agent')
51+
})
52+
})

apps/cli/src/commands/agent/agents/ui/wizard/Wizard.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useCallback, useMemo, useState } from 'react'
22
import { Instructions, Panel } from '../components'
3-
import type { WizardData } from './types'
3+
import type { WizardData, WizardMethod } from './types'
44

55
export type WizardContextValue = {
66
stepIndex: number
@@ -14,6 +14,28 @@ export type WizardContextValue = {
1414
done: () => void
1515
}
1616

17+
const WIZARD_PATHS: Record<WizardMethod, number[]> = {
18+
quickGenerate: [0, 1, 2, 9],
19+
customGenerate: [0, 1, 2, 6, 7, 8, 9],
20+
manual: [0, 1, 3, 4, 5, 6, 7, 8, 9],
21+
}
22+
23+
export function getWizardStepSubtitle(
24+
ctx: Pick<WizardContextValue, 'stepIndex' | 'totalSteps' | 'wizardData'>,
25+
subtitle: string,
26+
): string {
27+
const method = ctx.wizardData.method
28+
if (!method) return `Step ${ctx.stepIndex + 1} - ${subtitle}`
29+
30+
const path = WIZARD_PATHS[method]
31+
const pathIndex = path.indexOf(ctx.stepIndex)
32+
if (pathIndex === -1) {
33+
return `Step ${ctx.stepIndex + 1}/${ctx.totalSteps} - ${subtitle}`
34+
}
35+
36+
return `Step ${pathIndex + 1}/${path.length} - ${subtitle}`
37+
}
38+
1739
export function Wizard(props: {
1840
steps: Array<(ctx: WizardContextValue) => React.ReactNode>
1941
initialData?: WizardData

apps/cli/src/commands/agent/agents/ui/wizard/steps/StepAgentType.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import TextInput from '#ui-ink/components/TextInput'
44
import { useKeypress } from '#ui-ink/hooks/useKeypress'
55
import { validateAgentType } from '../../../generation'
66
import { themeColor } from '../../colors'
7-
import { WizardPanel, type WizardContextValue } from '../Wizard'
7+
import {
8+
getWizardStepSubtitle,
9+
WizardPanel,
10+
type WizardContextValue,
11+
} from '../Wizard'
812

913
export function StepAgentType({ ctx }: { ctx: WizardContextValue }) {
1014
const [value, setValue] = useState(ctx.wizardData.agentType ?? '')
@@ -33,7 +37,7 @@ export function StepAgentType({ ctx }: { ctx: WizardContextValue }) {
3337

3438
return (
3539
<WizardPanel
36-
subtitle="Agent type (identifier)"
40+
subtitle={getWizardStepSubtitle(ctx, 'Name the agent')}
3741
footerText="Press Enter to continue - Esc to go back"
3842
>
3943
<Box flexDirection="column" marginTop={1} gap={1}>
@@ -46,7 +50,10 @@ export function StepAgentType({ ctx }: { ctx: WizardContextValue }) {
4650
cursorOffset={cursorOffset}
4751
onChangeCursorOffset={setCursorOffset}
4852
/>
49-
<Text dimColor>e.g., code-reviewer, tech-lead, etc</Text>
53+
<Text dimColor>
54+
Use a short lowercase name, usually 2-4 words: code-reviewer,
55+
test-writer, tech-lead.
56+
</Text>
5057
{error ? <Text color={themeColor('error')}>{error}</Text> : null}
5158
</Box>
5259
</WizardPanel>

apps/cli/src/commands/agent/agents/ui/wizard/steps/StepChooseColor.tsx

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import React from 'react'
2-
import { Box } from 'ink'
2+
import { Box, Text } from 'ink'
33
import { ColorPicker } from '../../ColorPicker'
4-
import type { AgentColor } from '../../types'
5-
import { DEFAULT_AGENT_MODEL } from '../../types'
4+
import {
5+
COLOR_OPTIONS,
6+
DEFAULT_AGENT_MODEL,
7+
type AgentColor,
8+
} from '../../types'
69
import type { WizardFinalAgent } from '../types'
710
import { useKeypress } from '#ui-ink/hooks/useKeypress'
8-
import { WizardPanel, type WizardContextValue } from '../Wizard'
11+
import {
12+
getWizardStepSubtitle,
13+
WizardPanel,
14+
type WizardContextValue,
15+
} from '../Wizard'
916

1017
export function StepChooseColor({ ctx }: { ctx: WizardContextValue }) {
1118
useKeypress((_input, key) => {
@@ -16,6 +23,12 @@ export function StepChooseColor({ ctx }: { ctx: WizardContextValue }) {
1623
})
1724

1825
const agentType = ctx.wizardData.agentType ?? 'agent'
26+
const currentColor = COLOR_OPTIONS.includes(
27+
ctx.wizardData.selectedColor as AgentColor,
28+
)
29+
? (ctx.wizardData.selectedColor as AgentColor)
30+
: 'automatic'
31+
1932
const onConfirm = (color: AgentColor) => {
2033
const selectedColor = color === 'automatic' ? undefined : color
2134
const finalAgent: WizardFinalAgent = {
@@ -37,13 +50,17 @@ export function StepChooseColor({ ctx }: { ctx: WizardContextValue }) {
3750

3851
return (
3952
<WizardPanel
40-
subtitle="Choose background color"
53+
subtitle={getWizardStepSubtitle(ctx, 'Choose color')}
4154
footerText="Press Up/Down to navigate - Enter to select - Esc to go back"
4255
>
43-
<Box marginTop={1}>
56+
<Box flexDirection="column" marginTop={1} gap={1}>
57+
<Text dimColor>
58+
Automatic is fine. Pick a color only to make busy agent lists easier
59+
to scan.
60+
</Text>
4461
<ColorPicker
4562
agentName={agentType}
46-
currentColor="automatic"
63+
currentColor={currentColor}
4764
onConfirm={onConfirm}
4865
/>
4966
</Box>

apps/cli/src/commands/agent/agents/ui/wizard/steps/StepChooseLocation.tsx

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import React from 'react'
2-
import { Box } from 'ink'
2+
import { Box, Text } from 'ink'
33
import { Select } from '#ui-ink/components/CustomSelect/select'
44
import { useKeypress } from '#ui-ink/hooks/useKeypress'
5-
import { WizardPanel, type WizardContextValue } from '../Wizard'
5+
import {
6+
getWizardStepSubtitle,
7+
WizardPanel,
8+
type WizardContextValue,
9+
} from '../Wizard'
610

711
export function StepChooseLocation({ ctx }: { ctx: WizardContextValue }) {
812
useKeypress((_input, key) => {
@@ -14,15 +18,24 @@ export function StepChooseLocation({ ctx }: { ctx: WizardContextValue }) {
1418

1519
return (
1620
<WizardPanel
17-
subtitle="Choose location"
21+
subtitle={getWizardStepSubtitle(ctx, 'Choose location')}
1822
footerText="Press Up/Down to navigate - Enter to select - Esc to cancel"
1923
>
20-
<Box marginTop={1}>
24+
<Box flexDirection="column" marginTop={1} gap={1}>
25+
<Text dimColor>
26+
Project is best for this repo. Personal is best for reusable agents.
27+
</Text>
2128
<Select
2229
key="location-select"
2330
options={[
24-
{ label: 'Project (.kode/agents/)', value: 'projectSettings' },
25-
{ label: 'Personal (~/.kode/agents/)', value: 'userSettings' },
31+
{
32+
label: 'Project (recommended) - saved in .kode/agents/',
33+
value: 'projectSettings',
34+
},
35+
{
36+
label: 'Personal - saved in ~/.kode/agents/',
37+
value: 'userSettings',
38+
},
2639
]}
2740
onChange={value => {
2841
const location =

apps/cli/src/commands/agent/agents/ui/wizard/steps/StepChooseMethod.tsx

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import React from 'react'
2-
import { Box } from 'ink'
2+
import { Box, Text } from 'ink'
33
import { Select } from '#ui-ink/components/CustomSelect/select'
44
import { useKeypress } from '#ui-ink/hooks/useKeypress'
5-
import { WizardPanel, type WizardContextValue } from '../Wizard'
5+
import {
6+
getWizardStepSubtitle,
7+
WizardPanel,
8+
type WizardContextValue,
9+
} from '../Wizard'
610
import type { WizardMethod } from '../types'
711

812
export function StepChooseMethod({ ctx }: { ctx: WizardContextValue }) {
@@ -15,27 +19,42 @@ export function StepChooseMethod({ ctx }: { ctx: WizardContextValue }) {
1519

1620
return (
1721
<WizardPanel
18-
subtitle="Creation method"
22+
subtitle={getWizardStepSubtitle(ctx, 'Choose setup path')}
1923
footerText="Press Up/Down to navigate - Enter to select - Esc to go back"
2024
>
21-
<Box marginTop={1}>
25+
<Box flexDirection="column" marginTop={1} gap={1}>
26+
<Text dimColor>
27+
Quick draft asks one question and uses recommended defaults. Advanced
28+
paths keep every control available.
29+
</Text>
2230
<Select
2331
key="method-select"
2432
options={[
2533
{
26-
label: 'Generate with the current model (recommended)',
27-
value: 'generate',
34+
label: 'Quick draft (recommended) - describe, review, save',
35+
value: 'quickGenerate',
36+
},
37+
{
38+
label: 'Customize draft - AI writes it, you tune settings',
39+
value: 'customGenerate',
40+
},
41+
{
42+
label: 'Manual setup - write every field yourself',
43+
value: 'manual',
2844
},
29-
{ label: 'Manual configuration', value: 'manual' },
3045
]}
3146
onChange={value => {
32-
const method: WizardMethod =
33-
value === 'manual' ? 'manual' : 'generate'
47+
const method: WizardMethod = (() => {
48+
if (value === 'manual') return 'manual'
49+
if (value === 'customGenerate') return 'customGenerate'
50+
return 'quickGenerate'
51+
})()
3452
ctx.updateWizardData({
3553
method,
36-
wasGenerated: method === 'generate',
54+
wasGenerated: false,
55+
finalAgent: undefined,
3756
})
38-
if (method === 'generate') ctx.goNext()
57+
if (method !== 'manual') ctx.goNext()
3958
else ctx.goToStep(3)
4059
}}
4160
/>
Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,52 @@
11
import { describe, expect, test } from 'bun:test'
2-
import { __getConfirmFooterTextForTests } from './StepConfirm'
2+
import {
3+
__getConfirmFooterTextForTests,
4+
__getToolSummaryForTests,
5+
__splitValidationWarningsForTests,
6+
} from './StepConfirm'
37

48
describe('StepConfirm footer text', () => {
59
test('shows save actions while idle', () => {
610
expect(__getConfirmFooterTextForTests(false)).toBe(
7-
'Press s/Enter to save - e to edit in your editor - Esc to cancel',
11+
'Enter/s to save - e to save and edit - Esc to go back',
812
)
913
})
1014

1115
test('shows saving state while save is in progress', () => {
1216
expect(__getConfirmFooterTextForTests(true)).toBe('Saving agent...')
1317
})
1418
})
19+
20+
describe('StepConfirm tool summary', () => {
21+
test('describes the recommended all-tools default', () => {
22+
expect(__getToolSummaryForTests(undefined)).toBe(
23+
'All tools (recommended default)',
24+
)
25+
})
26+
27+
test('describes a no-tools specialist', () => {
28+
expect(__getToolSummaryForTests([])).toBe('No tools (strict and limited)')
29+
})
30+
31+
test('formats multiple selected tools', () => {
32+
expect(__getToolSummaryForTests(['Read', 'Bash', 'Edit'])).toBe(
33+
'Read, Bash, and Edit',
34+
)
35+
})
36+
})
37+
38+
describe('StepConfirm validation message grouping', () => {
39+
test('shows tool access advisories as notes', () => {
40+
expect(
41+
__splitValidationWarningsForTests([
42+
'Agent has access to all tools',
43+
'Unrecognized tools: UnknownTool',
44+
]),
45+
).toEqual({
46+
notes: [
47+
'All tools are enabled. Limit tools only for stricter specialists.',
48+
],
49+
warnings: ['Unrecognized tools: UnknownTool'],
50+
})
51+
})
52+
})

0 commit comments

Comments
 (0)