-
Notifications
You must be signed in to change notification settings - Fork 31
feat(experiment): prompt create shows more example templates and agents #402
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
42aeefd
716cffb
cd63d76
d63f851
0d884c9
2195bb8
40aee42
84029fd
40cf61c
e962dfc
200ec43
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ import ( | |
| "time" | ||
|
|
||
| "github.com/slackapi/slack-cli/internal/api" | ||
| "github.com/slackapi/slack-cli/internal/experiment" | ||
| "github.com/slackapi/slack-cli/internal/iostreams" | ||
| "github.com/slackapi/slack-cli/internal/pkg/create" | ||
| "github.com/slackapi/slack-cli/internal/shared" | ||
|
|
@@ -30,12 +31,52 @@ import ( | |
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| // getSelectionOptions returns the app template options for a given category. | ||
| func getSelectionOptions(clients *shared.ClientFactory, categoryID string) []promptObject { | ||
| if clients.Config.WithExperimentOn(experiment.Templates) { | ||
| templatePromptObjects := map[string]([]promptObject){ | ||
| "slack-cli#getting-started": { | ||
| { | ||
| Title: fmt.Sprintf("Bolt for JavaScript %s", style.Secondary("Node.js")), | ||
| Repository: "slack-samples/bolt-js-starter-template", | ||
| }, | ||
| { | ||
| Title: fmt.Sprintf("Bolt for Python %s", style.Secondary("Python")), | ||
| Repository: "slack-samples/bolt-python-starter-template", | ||
| }, | ||
| }, | ||
| "slack-cli#ai-apps": { | ||
| { | ||
| Title: fmt.Sprintf("Support Agent %s", style.Secondary("Resolve IT support cases")), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mwbrooks Amazing eye! ποΈβπ¨οΈ β¨ I'm a fan of promoting these selections too. I found it to read natural in testing. Were you thinking this should be a stable or experimental change? I realize that adjacent changes to |
||
| Repository: "slack-cli#ai-apps/support-agent", | ||
| }, | ||
| { | ||
| Title: fmt.Sprintf("Custom Agent %s", style.Secondary("Start from scratch")), | ||
| Repository: "slack-cli#ai-apps/custom-agent", | ||
| }, | ||
| }, | ||
| "slack-cli#automation-apps": { | ||
| { | ||
| Title: fmt.Sprintf("Bolt for JavaScript %s", style.Secondary("Node.js")), | ||
| Repository: "slack-samples/bolt-js-custom-function-template", | ||
| }, | ||
| { | ||
| Title: fmt.Sprintf("Bolt for Python %s", style.Secondary("Python")), | ||
| Repository: "slack-samples/bolt-python-custom-function-template", | ||
| }, | ||
zimeg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| Title: fmt.Sprintf("Deno Slack SDK %s", style.Secondary("Deno")), | ||
| Repository: "slack-samples/deno-starter-template", | ||
| }, | ||
| }, | ||
| } | ||
| return templatePromptObjects[categoryID] | ||
| } | ||
|
|
||
| if strings.TrimSpace(categoryID) == "" { | ||
| categoryID = "slack-cli#getting-started" | ||
| } | ||
|
|
||
| // App categories and templates | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. πͺ note: This comment is removed in favor of |
||
| templatePromptObjects := map[string]([]promptObject){ | ||
| "slack-cli#getting-started": []promptObject{ | ||
| { | ||
|
|
@@ -76,6 +117,42 @@ func getSelectionOptions(clients *shared.ClientFactory, categoryID string) []pro | |
| return templatePromptObjects[categoryID] | ||
| } | ||
|
|
||
| // getFrameworkOptions returns the framework choices for a given template. | ||
| func getFrameworkOptions(template string) []promptObject { | ||
| frameworkPromptObjects := map[string][]promptObject{ | ||
| "slack-cli#ai-apps/support-agent": { | ||
| { | ||
| Title: fmt.Sprintf("Claude Agent SDK %s", style.Secondary("Bolt for Python")), | ||
| Repository: "slack-samples/bolt-python-support-agent", | ||
| Subdir: "claude-agent-sdk", | ||
| }, | ||
| { | ||
| Title: fmt.Sprintf("OpenAI Agents SDK %s", style.Secondary("Bolt for Python")), | ||
| Repository: "slack-samples/bolt-python-support-agent", | ||
| Subdir: "openai-agents-sdk", | ||
| }, | ||
| { | ||
| Title: fmt.Sprintf("Pydantic AI %s", style.Secondary("Bolt for Python")), | ||
| Repository: "slack-samples/bolt-python-support-agent", | ||
| Subdir: "pydantic-ai", | ||
| }, | ||
| }, | ||
| "slack-cli#ai-apps/custom-agent": { | ||
| { | ||
| Title: fmt.Sprintf("Bolt for JavaScript %s", style.Secondary("Node.js")), | ||
| Repository: "slack-samples/bolt-js-assistant-template", | ||
| }, | ||
| { | ||
| Title: fmt.Sprintf("Bolt for Python %s", style.Secondary("Python")), | ||
| Repository: "slack-samples/bolt-python-assistant-template", | ||
| }, | ||
| }, | ||
| } | ||
| return frameworkPromptObjects[template] | ||
| } | ||
|
|
||
| // getSelectionOptionsForCategory returns the top-level category options for | ||
| // the create command template selection. | ||
| func getSelectionOptionsForCategory(clients *shared.ClientFactory) []promptObject { | ||
| return []promptObject{ | ||
| { | ||
|
|
@@ -101,11 +178,16 @@ func getSelectionOptionsForCategory(clients *shared.ClientFactory) []promptObjec | |
| func promptTemplateSelection(cmd *cobra.Command, clients *shared.ClientFactory, categoryShortcut string) (create.Template, error) { | ||
| ctx := cmd.Context() | ||
| var categoryID string | ||
| var selectedTemplate string | ||
|
|
||
| // Check if a category shortcut was provided | ||
| if categoryShortcut == "agent" { | ||
| categoryID = "slack-cli#ai-apps" | ||
| if categoryShortcut != "" { | ||
| switch categoryShortcut { | ||
| case "agent": | ||
| categoryID = "slack-cli#ai-apps" | ||
| default: | ||
| return create.Template{}, slackerror.New(slackerror.ErrInvalidArgs). | ||
| WithMessage("The %s category was not found", categoryShortcut) | ||
|
Comment on lines
+188
to
+189
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
| } else { | ||
| // Prompt for the category | ||
| promptForCategory := "Select an app:" | ||
|
|
@@ -128,73 +210,96 @@ func promptTemplateSelection(cmd *cobra.Command, clients *shared.ClientFactory, | |
| if err != nil { | ||
| return create.Template{}, slackerror.ToSlackError(err) | ||
| } else if selection.Flag { | ||
| selectedTemplate = selection.Option | ||
| template, err := create.ResolveTemplateURL(selection.Option) | ||
| if err != nil { | ||
| return create.Template{}, err | ||
| } | ||
| confirm, err := confirmExternalTemplateSelection(cmd, clients, template) | ||
| if err != nil { | ||
| return create.Template{}, slackerror.ToSlackError(err) | ||
| } else if !confirm { | ||
| return create.Template{}, slackerror.New(slackerror.ErrUntrustedSource) | ||
| } | ||
| return template, nil | ||
|
Comment on lines
+213
to
+223
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π₯¦ note: We now return as soon as possible if the |
||
| } else if selection.Prompt { | ||
| categoryID = optionsForCategory[selection.Index].Repository | ||
| } | ||
|
|
||
| // Set template to view more samples, so the sample prompt is triggered | ||
| if categoryID == viewMoreSamples { | ||
| selectedTemplate = viewMoreSamples | ||
| sampler := api.NewHTTPClient(api.HTTPClientOptions{ | ||
| TotalTimeOut: 60 * time.Second, | ||
| }) | ||
| samples, err := create.GetSampleRepos(sampler) | ||
| if err != nil { | ||
| return create.Template{}, err | ||
| } | ||
| selectedSample, err := promptSampleSelection(ctx, clients, samples) | ||
| if err != nil { | ||
| return create.Template{}, err | ||
| } | ||
| return create.ResolveTemplateURL(selectedSample) | ||
|
Comment on lines
+229
to
+240
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π½οΈ note: An earlier return with
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes a lot more sense now that there are fewer changes and I can connect the dots π |
||
| } | ||
| } | ||
|
|
||
| // Prompt for the template | ||
| if selectedTemplate == "" { | ||
| prompt := "Select a language:" | ||
| options := getSelectionOptions(clients, categoryID) | ||
| titles := make([]string, len(options)) | ||
| for i, m := range options { | ||
| titles[i] = m.Title | ||
| // Prompt for the example template | ||
| prompt := "Select a language:" | ||
| if clients.Config.WithExperimentOn(experiment.Templates) { | ||
| if categoryID == "slack-cli#ai-apps" { | ||
| prompt = "Select a template:" | ||
| } else { | ||
| prompt = "Select a language:" | ||
| } | ||
| template := getSelectionTemplate(clients) | ||
|
|
||
| // Print a trace with info about the template title options provided by CLI | ||
| clients.IO.PrintTrace(ctx, slacktrace.CreateTemplateOptions, strings.Join(titles, ", ")) | ||
| } | ||
| options := getSelectionOptions(clients, categoryID) | ||
| titles := make([]string, len(options)) | ||
| for i, m := range options { | ||
| titles[i] = m.Title | ||
| } | ||
| clients.IO.PrintTrace(ctx, slacktrace.CreateTemplateOptions, strings.Join(titles, ", ")) | ||
|
|
||
| // Prompt to choose a template | ||
| selection, err := clients.IO.SelectPrompt(ctx, prompt, titles, iostreams.SelectPromptConfig{ | ||
| Flag: clients.Config.Flags.Lookup("template"), | ||
| Required: true, | ||
| Template: template, | ||
| }) | ||
| if err != nil { | ||
| return create.Template{}, slackerror.ToSlackError(err) | ||
| } else if selection.Flag { | ||
| selectedTemplate = selection.Option | ||
| } else if selection.Prompt { | ||
| selectedTemplate = options[selection.Index].Repository | ||
| } | ||
| selection, err := clients.IO.SelectPrompt(ctx, prompt, titles, iostreams.SelectPromptConfig{ | ||
| Description: func(value string, index int) string { | ||
| return options[index].Description | ||
| }, | ||
| Required: true, | ||
| Template: getSelectionTemplate(clients), | ||
| }) | ||
| if err != nil { | ||
| return create.Template{}, err | ||
| } else if selection.Flag { | ||
| return create.Template{}, slackerror.New(slackerror.ErrPrompt) | ||
| } else if selection.Prompt && !strings.HasPrefix(options[selection.Index].Repository, "slack-cli#") { | ||
| return create.ResolveTemplateURL(options[selection.Index].Repository) | ||
| } | ||
| template := options[selection.Index].Repository | ||
|
|
||
| // Ensure user is okay to proceed if template source is from a non-trusted source | ||
| switch selectedTemplate { | ||
| case viewMoreSamples: | ||
| sampler := api.NewHTTPClient(api.HTTPClientOptions{ | ||
| TotalTimeOut: 60 * time.Second, | ||
| }) | ||
| samples, err := create.GetSampleRepos(sampler) | ||
| if err != nil { | ||
| return create.Template{}, err | ||
| } | ||
| selectedSample, err := promptSampleSelection(ctx, clients, samples) | ||
| if err != nil { | ||
| return create.Template{}, err | ||
| } | ||
| return create.ResolveTemplateURL(selectedSample) | ||
| default: | ||
| template, err := create.ResolveTemplateURL(selectedTemplate) | ||
| if err != nil { | ||
| return create.Template{}, err | ||
| } | ||
| confirm, err := confirmExternalTemplateSelection(cmd, clients, template) | ||
| if err != nil { | ||
| return create.Template{}, slackerror.ToSlackError(err) | ||
| } else if !confirm { | ||
| return create.Template{}, slackerror.New(slackerror.ErrUntrustedSource) | ||
| } | ||
| return template, nil | ||
| // Prompt for the example framework | ||
| examples := getFrameworkOptions(template) | ||
| choices := make([]string, len(examples)) | ||
| for i, opt := range examples { | ||
| choices[i] = opt.Title | ||
| } | ||
| choice, err := clients.IO.SelectPrompt(ctx, "Select a language:", choices, iostreams.SelectPromptConfig{ | ||
| Description: func(value string, index int) string { | ||
| return examples[index].Description | ||
| }, | ||
| Required: true, | ||
| Template: getSelectionTemplate(clients), | ||
| }) | ||
| if err != nil { | ||
| return create.Template{}, err | ||
| } else if choice.Flag { | ||
| return create.Template{}, slackerror.New(slackerror.ErrPrompt) | ||
| } | ||
| example := examples[choice.Index] | ||
| resolved, err := create.ResolveTemplateURL(example.Repository) | ||
| if err != nil { | ||
| return create.Template{}, err | ||
| } | ||
| if example.Subdir != "" { | ||
| resolved.SetSubdir(example.Subdir) | ||
| } | ||
| return resolved, nil | ||
| } | ||
|
|
||
| // confirmExternalTemplateSelection prompts the user to confirm that they want to create an app from | ||
|
|
@@ -243,10 +348,22 @@ func listTemplates(ctx context.Context, clients *shared.ClientFactory, categoryS | |
| } | ||
|
|
||
| var categories []categoryInfo | ||
| if categoryShortcut == "agent" { | ||
| if categoryShortcut == "agent" && clients.Config.WithExperimentOn(experiment.Templates) { | ||
| categories = []categoryInfo{ | ||
| {id: "slack-cli#ai-apps/support-agent", name: "Support agent"}, | ||
| {id: "slack-cli#ai-apps/custom-agent", name: "Custom agent"}, | ||
| } | ||
| } else if categoryShortcut == "agent" { | ||
| categories = []categoryInfo{ | ||
| {id: "slack-cli#ai-apps", name: "AI Agent apps"}, | ||
| } | ||
| } else if clients.Config.WithExperimentOn(experiment.Templates) { | ||
| categories = []categoryInfo{ | ||
| {id: "slack-cli#getting-started", name: "Getting started"}, | ||
| {id: "slack-cli#ai-apps/support-agent", name: "Support agent"}, | ||
| {id: "slack-cli#ai-apps/custom-agent", name: "Custom agent"}, | ||
| {id: "slack-cli#automation-apps", name: "Automation apps"}, | ||
| } | ||
|
Comment on lines
+361
to
+366
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. praise: I like the |
||
| } else { | ||
| categories = []categoryInfo{ | ||
| {id: "slack-cli#getting-started", name: "Getting started"}, | ||
|
|
@@ -256,10 +373,19 @@ func listTemplates(ctx context.Context, clients *shared.ClientFactory, categoryS | |
| } | ||
|
|
||
| for _, category := range categories { | ||
| templates := getSelectionOptions(clients, category.id) | ||
| secondary := make([]string, len(templates)) | ||
| for i, tmpl := range templates { | ||
| secondary[i] = tmpl.Repository | ||
| var secondary []string | ||
| if frameworks := getFrameworkOptions(category.id); len(frameworks) > 0 { | ||
| for _, tmpl := range frameworks { | ||
| repo := tmpl.Repository | ||
| if tmpl.Subdir != "" { | ||
| repo = fmt.Sprintf("%s --subdir %s", repo, tmpl.Subdir) | ||
| } | ||
| secondary = append(secondary, repo) | ||
| } | ||
| } else { | ||
| for _, tmpl := range getSelectionOptions(clients, category.id) { | ||
| secondary = append(secondary, tmpl.Repository) | ||
| } | ||
| } | ||
| clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{ | ||
| Emoji: "house_buildings", | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
πͺ¬ thought: The changes of this PR make me think of the
createselections now as:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thought: Yea, it feels like we're expanding on the creation journey. When I think about it, I can imagine: