Skip to content

Commit a523c56

Browse files
committed
fix: separate inputs for framework and adapter options
1 parent b503ded commit a523c56

2 files changed

Lines changed: 180 additions & 32 deletions

File tree

cmd/project/create_template.go

Lines changed: 121 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -71,38 +71,96 @@ func getSelectionOptions(categoryID string) []promptObject {
7171
return templatePromptObjects[categoryID]
7272
}
7373

74-
// getFrameworkOptions returns the framework choices for a given template.
74+
// getFrameworkOptions returns the framework choices for a given AI app template.
7575
func getFrameworkOptions(template string) []promptObject {
7676
frameworkPromptObjects := map[string][]promptObject{
7777
"slack-cli#ai-apps/support-agent": {
7878
{
79-
Title: fmt.Sprintf("Claude Agent SDK %s", style.Secondary("Bolt for Python")),
79+
Title: fmt.Sprintf("Bolt for JavaScript %s", style.Secondary("Node.js")),
80+
Repository: "slack-cli#ai-apps/support-agent/bolt-js",
81+
},
82+
{
83+
Title: fmt.Sprintf("Bolt for Python %s", style.Secondary("Python")),
84+
Repository: "slack-cli#ai-apps/support-agent/bolt-python",
85+
},
86+
},
87+
"slack-cli#ai-apps/starter-agent": {
88+
{
89+
Title: fmt.Sprintf("Bolt for JavaScript %s", style.Secondary("Node.js")),
90+
Repository: "slack-cli#ai-apps/starter-agent/bolt-js",
91+
},
92+
{
93+
Title: fmt.Sprintf("Bolt for Python %s", style.Secondary("Python")),
94+
Repository: "slack-cli#ai-apps/starter-agent/bolt-python",
95+
},
96+
},
97+
}
98+
return frameworkPromptObjects[template]
99+
}
100+
101+
// getAdapterOptions returns the AI adapter choices for a given template and framework.
102+
func getAdapterOptions(framework string) []promptObject {
103+
adapterPromptObjects := map[string][]promptObject{
104+
"slack-cli#ai-apps/support-agent/bolt-js": {
105+
{
106+
Title: "Claude Agent SDK",
107+
Repository: "slack-samples/bolt-js-support-agent",
108+
Subdir: "claude-agent-sdk",
109+
},
110+
{
111+
Title: "OpenAI Agents SDK",
112+
Repository: "slack-samples/bolt-js-support-agent",
113+
Subdir: "openai-agents-sdk",
114+
},
115+
},
116+
"slack-cli#ai-apps/support-agent/bolt-python": {
117+
{
118+
Title: "Claude Agent SDK",
80119
Repository: "slack-samples/bolt-python-support-agent",
81120
Subdir: "claude-agent-sdk",
82121
},
83122
{
84-
Title: fmt.Sprintf("OpenAI Agents SDK %s", style.Secondary("Bolt for Python")),
123+
Title: "OpenAI Agents SDK",
85124
Repository: "slack-samples/bolt-python-support-agent",
86125
Subdir: "openai-agents-sdk",
87126
},
88127
{
89-
Title: fmt.Sprintf("Pydantic AI %s", style.Secondary("Bolt for Python")),
128+
Title: "Pydantic AI",
90129
Repository: "slack-samples/bolt-python-support-agent",
91130
Subdir: "pydantic-ai",
92131
},
93132
},
94-
"slack-cli#ai-apps/starter-agent": {
133+
"slack-cli#ai-apps/starter-agent/bolt-js": {
95134
{
96-
Title: fmt.Sprintf("Bolt for JavaScript %s", style.Secondary("Node.js")),
135+
Title: "Claude Agent SDK",
97136
Repository: "slack-samples/bolt-js-starter-agent",
137+
Subdir: "claude-agent-sdk",
98138
},
99139
{
100-
Title: fmt.Sprintf("Bolt for Python %s", style.Secondary("Python")),
140+
Title: "OpenAI Agents SDK",
141+
Repository: "slack-samples/bolt-js-starter-agent",
142+
Subdir: "openai-agents-sdk",
143+
},
144+
},
145+
"slack-cli#ai-apps/starter-agent/bolt-python": {
146+
{
147+
Title: "Claude Agent SDK",
148+
Repository: "slack-samples/bolt-python-starter-agent",
149+
Subdir: "claude-agent-sdk",
150+
},
151+
{
152+
Title: "OpenAI Agents SDK",
153+
Repository: "slack-samples/bolt-python-starter-agent",
154+
Subdir: "openai-agents-sdk",
155+
},
156+
{
157+
Title: "Pydantic AI",
101158
Repository: "slack-samples/bolt-python-starter-agent",
159+
Subdir: "pydantic-ai",
102160
},
103161
},
104162
}
105-
return frameworkPromptObjects[template]
163+
return adapterPromptObjects[framework]
106164
}
107165

108166
// getSelectionOptionsForCategory returns the top-level category options for
@@ -223,31 +281,63 @@ func promptTemplateSelection(cmd *cobra.Command, clients *shared.ClientFactory,
223281
}
224282
template := options[selection.Index].Repository
225283

226-
// Prompt for the example framework
227-
examples := getFrameworkOptions(template)
228-
choices := make([]string, len(examples))
229-
for i, opt := range examples {
230-
choices[i] = opt.Title
284+
// Prompt for the framework
285+
frameworks := getFrameworkOptions(template)
286+
frameworkChoices := make([]string, len(frameworks))
287+
for i, opt := range frameworks {
288+
frameworkChoices[i] = opt.Title
231289
}
232-
choice, err := clients.IO.SelectPrompt(ctx, "Select a framework:", choices, iostreams.SelectPromptConfig{
290+
frameworkSelection, err := clients.IO.SelectPrompt(ctx, "Select a framework:", frameworkChoices, iostreams.SelectPromptConfig{
233291
Description: func(value string, index int) string {
234-
return examples[index].Description
292+
return frameworks[index].Description
235293
},
236294
Required: true,
237295
Template: getSelectionTemplate(clients),
238296
})
239297
if err != nil {
240298
return create.Template{}, err
241-
} else if choice.Flag {
299+
} else if frameworkSelection.Flag {
242300
return create.Template{}, slackerror.New(slackerror.ErrPrompt)
243301
}
244-
example := examples[choice.Index]
245-
resolved, err := create.ResolveTemplateURL(example.Repository)
302+
framework := frameworks[frameworkSelection.Index]
303+
304+
// Check if there are adapter options for this framework
305+
adapters := getAdapterOptions(framework.Repository)
306+
if len(adapters) > 0 {
307+
adapterChoices := make([]string, len(adapters))
308+
for i, opt := range adapters {
309+
adapterChoices[i] = opt.Title
310+
}
311+
adapterSelection, err := clients.IO.SelectPrompt(ctx, "Select an adapter:", adapterChoices, iostreams.SelectPromptConfig{
312+
Description: func(value string, index int) string {
313+
return adapters[index].Description
314+
},
315+
Required: true,
316+
Template: getSelectionTemplate(clients),
317+
})
318+
if err != nil {
319+
return create.Template{}, err
320+
} else if adapterSelection.Flag {
321+
return create.Template{}, slackerror.New(slackerror.ErrPrompt)
322+
}
323+
adapter := adapters[adapterSelection.Index]
324+
resolved, err := create.ResolveTemplateURL(adapter.Repository)
325+
if err != nil {
326+
return create.Template{}, err
327+
}
328+
if adapter.Subdir != "" {
329+
resolved.SetSubdir(adapter.Subdir)
330+
}
331+
return resolved, nil
332+
}
333+
334+
// No adapter options - resolve the framework directly
335+
resolved, err := create.ResolveTemplateURL(framework.Repository)
246336
if err != nil {
247337
return create.Template{}, err
248338
}
249-
if example.Subdir != "" {
250-
resolved.SetSubdir(example.Subdir)
339+
if framework.Subdir != "" {
340+
resolved.SetSubdir(framework.Subdir)
251341
}
252342
return resolved, nil
253343
}
@@ -315,12 +405,18 @@ func listTemplates(ctx context.Context, clients *shared.ClientFactory, categoryS
315405
for _, category := range categories {
316406
var secondary []string
317407
if frameworks := getFrameworkOptions(category.id); len(frameworks) > 0 {
318-
for _, tmpl := range frameworks {
319-
repo := tmpl.Repository
320-
if tmpl.Subdir != "" {
321-
repo = fmt.Sprintf("%s --subdir %s", repo, tmpl.Subdir)
408+
for _, fw := range frameworks {
409+
if adapters := getAdapterOptions(fw.Repository); len(adapters) > 0 {
410+
for _, adapter := range adapters {
411+
repo := adapter.Repository
412+
if adapter.Subdir != "" {
413+
repo = fmt.Sprintf("%s --subdir %s", repo, adapter.Subdir)
414+
}
415+
secondary = append(secondary, repo)
416+
}
417+
} else {
418+
secondary = append(secondary, fw.Repository)
322419
}
323-
secondary = append(secondary, repo)
324420
}
325421
} else {
326422
for _, tmpl := range getSelectionOptions(category.id) {

cmd/project/create_test.go

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,15 @@ func TestCreateCommand(t *testing.T) {
133133
Return(
134134
iostreams.SelectPromptResponse{
135135
Prompt: true,
136-
Index: 0, // Select Node.js template
136+
Index: 0, // Select Node.js
137+
},
138+
nil,
139+
)
140+
cm.IO.On("SelectPrompt", mock.Anything, "Select an adapter:", mock.Anything, mock.Anything).
141+
Return(
142+
iostreams.SelectPromptResponse{
143+
Prompt: true,
144+
Index: 0, // Select Claude Agent SDK
137145
},
138146
nil,
139147
)
@@ -146,9 +154,11 @@ func TestCreateCommand(t *testing.T) {
146154
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
147155
template, err := create.ResolveTemplateURL("slack-samples/bolt-js-starter-agent")
148156
require.NoError(t, err)
157+
template.SetSubdir("claude-agent-sdk")
149158
expected := create.CreateArgs{
150159
AppName: "my-agent",
151160
Template: template,
161+
Subdir: "claude-agent-sdk",
152162
}
153163
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, expected)
154164
// Verify that category prompt was NOT called
@@ -172,7 +182,15 @@ func TestCreateCommand(t *testing.T) {
172182
Return(
173183
iostreams.SelectPromptResponse{
174184
Prompt: true,
175-
Index: 1, // Select Python template
185+
Index: 1, // Select Python
186+
},
187+
nil,
188+
)
189+
cm.IO.On("SelectPrompt", mock.Anything, "Select an adapter:", mock.Anything, mock.Anything).
190+
Return(
191+
iostreams.SelectPromptResponse{
192+
Prompt: true,
193+
Index: 0, // Select Claude Agent SDK
176194
},
177195
nil,
178196
)
@@ -183,9 +201,11 @@ func TestCreateCommand(t *testing.T) {
183201
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
184202
template, err := create.ResolveTemplateURL("slack-samples/bolt-python-starter-agent")
185203
require.NoError(t, err)
204+
template.SetSubdir("claude-agent-sdk")
186205
expected := create.CreateArgs{
187206
AppName: "my-agent-app",
188207
Template: template,
208+
Subdir: "claude-agent-sdk",
189209
}
190210
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, expected)
191211
// Verify that category prompt was NOT called
@@ -202,7 +222,9 @@ func TestCreateCommand(t *testing.T) {
202222
cm.IO.On("SelectPrompt", mock.Anything, "Select a template:", mock.Anything, mock.Anything).
203223
Return(iostreams.SelectPromptResponse{Prompt: true, Index: 0}, nil)
204224
cm.IO.On("SelectPrompt", mock.Anything, "Select a framework:", mock.Anything, mock.Anything).
205-
Return(iostreams.SelectPromptResponse{Prompt: true, Index: 2}, nil)
225+
Return(iostreams.SelectPromptResponse{Prompt: true, Index: 1}, nil) // Select Bolt for Python
226+
cm.IO.On("SelectPrompt", mock.Anything, "Select an adapter:", mock.Anything, mock.Anything).
227+
Return(iostreams.SelectPromptResponse{Prompt: true, Index: 2}, nil) // Select Pydantic AI
206228
createClientMock = new(CreateClientMock)
207229
createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything).Return("", nil)
208230
CreateFunc = createClientMock.Create
@@ -312,16 +334,26 @@ func TestCreateCommand(t *testing.T) {
312334
},
313335
nil,
314336
)
337+
cm.IO.On("SelectPrompt", mock.Anything, "Select an adapter:", mock.Anything, mock.Anything).
338+
Return(
339+
iostreams.SelectPromptResponse{
340+
Prompt: true,
341+
Index: 0, // Select Claude Agent SDK
342+
},
343+
nil,
344+
)
315345
createClientMock = new(CreateClientMock)
316346
createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything).Return("", nil)
317347
CreateFunc = createClientMock.Create
318348
},
319349
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
320350
template, err := create.ResolveTemplateURL("slack-samples/bolt-js-starter-agent")
321351
require.NoError(t, err)
352+
template.SetSubdir("claude-agent-sdk")
322353
expected := create.CreateArgs{
323354
AppName: "my-custom-name", // --name flag overrides
324355
Template: template,
356+
Subdir: "claude-agent-sdk",
325357
}
326358
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, expected)
327359
// Verify that category prompt was NOT called (shortcut was triggered)
@@ -383,16 +415,26 @@ func TestCreateCommand(t *testing.T) {
383415
},
384416
nil,
385417
)
418+
cm.IO.On("SelectPrompt", mock.Anything, "Select an adapter:", mock.Anything, mock.Anything).
419+
Return(
420+
iostreams.SelectPromptResponse{
421+
Prompt: true,
422+
Index: 0, // Select Claude Agent SDK
423+
},
424+
nil,
425+
)
386426
createClientMock = new(CreateClientMock)
387427
createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything).Return("", nil)
388428
CreateFunc = createClientMock.Create
389429
},
390430
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
391431
template, err := create.ResolveTemplateURL("slack-samples/bolt-js-starter-agent")
392432
require.NoError(t, err)
433+
template.SetSubdir("claude-agent-sdk")
393434
expected := create.CreateArgs{
394435
AppName: "my-name", // --name flag overrides "my-project" positional arg
395436
Template: template,
437+
Subdir: "claude-agent-sdk",
396438
}
397439
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, expected)
398440
// Verify that category prompt was NOT called (agent shortcut was triggered)
@@ -601,12 +643,17 @@ func TestCreateCommand(t *testing.T) {
601643
"slack-samples/bolt-js-starter-template",
602644
"slack-samples/bolt-python-starter-template",
603645
"Support agent",
646+
"slack-samples/bolt-js-support-agent --subdir claude-agent-sdk",
647+
"slack-samples/bolt-js-support-agent --subdir openai-agents-sdk",
604648
"slack-samples/bolt-python-support-agent --subdir claude-agent-sdk",
605649
"slack-samples/bolt-python-support-agent --subdir openai-agents-sdk",
606650
"slack-samples/bolt-python-support-agent --subdir pydantic-ai",
607651
"Starter agent",
608-
"slack-samples/bolt-js-starter-agent",
609-
"slack-samples/bolt-python-starter-agent",
652+
"slack-samples/bolt-js-starter-agent --subdir claude-agent-sdk",
653+
"slack-samples/bolt-js-starter-agent --subdir openai-agents-sdk",
654+
"slack-samples/bolt-python-starter-agent --subdir claude-agent-sdk",
655+
"slack-samples/bolt-python-starter-agent --subdir openai-agents-sdk",
656+
"slack-samples/bolt-python-starter-agent --subdir pydantic-ai",
610657
"Automation apps",
611658
"slack-samples/bolt-js-custom-function-template",
612659
"slack-samples/bolt-python-custom-function-template",
@@ -624,12 +671,17 @@ func TestCreateCommand(t *testing.T) {
624671
},
625672
ExpectedOutputs: []string{
626673
"Support agent",
674+
"slack-samples/bolt-js-support-agent --subdir claude-agent-sdk",
675+
"slack-samples/bolt-js-support-agent --subdir openai-agents-sdk",
627676
"slack-samples/bolt-python-support-agent --subdir claude-agent-sdk",
628677
"slack-samples/bolt-python-support-agent --subdir openai-agents-sdk",
629678
"slack-samples/bolt-python-support-agent --subdir pydantic-ai",
630679
"Starter agent",
631-
"slack-samples/bolt-js-starter-agent",
632-
"slack-samples/bolt-python-starter-agent",
680+
"slack-samples/bolt-js-starter-agent --subdir claude-agent-sdk",
681+
"slack-samples/bolt-js-starter-agent --subdir openai-agents-sdk",
682+
"slack-samples/bolt-python-starter-agent --subdir claude-agent-sdk",
683+
"slack-samples/bolt-python-starter-agent --subdir openai-agents-sdk",
684+
"slack-samples/bolt-python-starter-agent --subdir pydantic-ai",
633685
},
634686
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
635687
createClientMock.AssertNotCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything)

0 commit comments

Comments
 (0)