From f2a985b7358d6ffcca85049eb871a7575b0791ed Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Tue, 3 Feb 2026 16:37:22 -0500 Subject: [PATCH 1/2] feat: agent argument shortcut to slack create command --- cmd/project/create.go | 32 +++++++++++-- cmd/project/create_template.go | 65 +++++++++++++------------ cmd/project/create_test.go | 88 ++++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 35 deletions(-) diff --git a/cmd/project/create.go b/cmd/project/create.go index 2eb578b1..2305e11e 100644 --- a/cmd/project/create.go +++ b/cmd/project/create.go @@ -58,9 +58,10 @@ func NewCreateCommand(clients *shared.ClientFactory) *cobra.Command { Long: `Create a new Slack project on your local machine from an optional template`, Example: style.ExampleCommandsf([]style.ExampleCommand{ {Command: "create my-project", Meaning: "Create a new project from a template"}, + {Command: "create agent my-agent-app", Meaning: "Create a new AI agent app"}, {Command: "create my-project -t slack-samples/deno-hello-world", Meaning: "Start a new project from a specific template"}, }), - Args: cobra.MaximumNArgs(1), + Args: cobra.MaximumNArgs(2), RunE: func(cmd *cobra.Command, args []string) error { clients.Config.SetFlags(cmd) return runCreateCommand(clients, cmd, args) @@ -80,14 +81,35 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args [] // Set up event logger log := newCreateLogger(clients, cmd) - // Get optional app name passed as an arg + // Get optional app name passed as an arg and check for category shortcuts appNameArg := "" - if len(args) > 0 && args[0] != "samples" && args[0] != "create" { - appNameArg = args[0] + categoryShortcut := "" + templateFlagProvided := cmd.Flags().Changed("template") + + if len(args) > 0 { + switch args[0] { + case "samples", "create": + // These are special commands, not app names + case "agent": + // Only treat as shortcut if --template flag is not provided + if !templateFlagProvided { + // Shortcut to AI apps category + categoryShortcut = "agent" + // Check if a second argument was provided as the app name + if len(args) > 1 { + appNameArg = args[1] + } + } else { + // When --template is provided, "agent" is the app name + appNameArg = args[0] + } + default: + appNameArg = args[0] + } } // Collect the template URL or select a starting template - template, err := promptTemplateSelection(cmd, clients) + template, err := promptTemplateSelection(cmd, clients, categoryShortcut) if err != nil { return err } diff --git a/cmd/project/create_template.go b/cmd/project/create_template.go index 105cfdbe..359842a8 100644 --- a/cmd/project/create_template.go +++ b/cmd/project/create_template.go @@ -97,43 +97,48 @@ func getSelectionOptionsForCategory(clients *shared.ClientFactory) []promptObjec } // promptTemplateSelection prompts the user to select a project template -func promptTemplateSelection(cmd *cobra.Command, clients *shared.ClientFactory) (create.Template, error) { +func promptTemplateSelection(cmd *cobra.Command, clients *shared.ClientFactory, categoryShortcut string) (create.Template, error) { ctx := cmd.Context() var categoryID string var selectedTemplate string - // Prompt for the category - promptForCategory := "Select an app:" - optionsForCategory := getSelectionOptionsForCategory(clients) - titlesForCategory := make([]string, len(optionsForCategory)) - for i, m := range optionsForCategory { - titlesForCategory[i] = m.Title - } - templateForCategory := getSelectionTemplate(clients) + // Check if a category shortcut was provided + if categoryShortcut == "agent" { + categoryID = "slack-cli#ai-apps" + } else { + // Prompt for the category + promptForCategory := "Select an app:" + optionsForCategory := getSelectionOptionsForCategory(clients) + titlesForCategory := make([]string, len(optionsForCategory)) + for i, m := range optionsForCategory { + titlesForCategory[i] = m.Title + } + templateForCategory := getSelectionTemplate(clients) - // Print a trace with info about the category title options provided by CLI - clients.IO.PrintTrace(ctx, slacktrace.CreateCategoryOptions, strings.Join(titlesForCategory, ", ")) + // Print a trace with info about the category title options provided by CLI + clients.IO.PrintTrace(ctx, slacktrace.CreateCategoryOptions, strings.Join(titlesForCategory, ", ")) - // Prompt to choose a category - selection, err := clients.IO.SelectPrompt(ctx, promptForCategory, titlesForCategory, iostreams.SelectPromptConfig{ - Description: func(value string, index int) string { - return optionsForCategory[index].Description - }, - Flag: clients.Config.Flags.Lookup("template"), - Required: true, - Template: templateForCategory, - }) - if err != nil { - return create.Template{}, slackerror.ToSlackError(err) - } else if selection.Flag { - selectedTemplate = selection.Option - } else if selection.Prompt { - categoryID = optionsForCategory[selection.Index].Repository - } + // Prompt to choose a category + selection, err := clients.IO.SelectPrompt(ctx, promptForCategory, titlesForCategory, iostreams.SelectPromptConfig{ + Description: func(value string, index int) string { + return optionsForCategory[index].Description + }, + Flag: clients.Config.Flags.Lookup("template"), + Required: true, + Template: templateForCategory, + }) + if err != nil { + return create.Template{}, slackerror.ToSlackError(err) + } else if selection.Flag { + selectedTemplate = selection.Option + } 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 + // Set template to view more samples, so the sample prompt is triggered + if categoryID == viewMoreSamples { + selectedTemplate = viewMoreSamples + } } // Prompt for the template diff --git a/cmd/project/create_test.go b/cmd/project/create_test.go index d5137c53..202fd863 100644 --- a/cmd/project/create_test.go +++ b/cmd/project/create_test.go @@ -107,6 +107,94 @@ func TestCreateCommand(t *testing.T) { createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected) }, }, + "creates an agent app using agent argument shortcut": { + CmdArgs: []string{"agent"}, + Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { + // Should skip category prompt and go directly to language selection + cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything). + Return( + iostreams.SelectPromptResponse{ + Prompt: true, + Index: 0, // Select Node.js template + }, + nil, + ) + createClientMock = new(CreateClientMock) + createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", nil) + CreateFunc = createClientMock.Create + }, + ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { + template, err := create.ResolveTemplateURL("slack-samples/bolt-js-assistant-template") + require.NoError(t, err) + expected := create.CreateArgs{ + Template: template, + } + createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected) + // Verify that category prompt was NOT called + cm.IO.AssertNotCalled(t, "SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything) + }, + }, + "creates an agent app with app name using agent argument": { + CmdArgs: []string{"agent", "my-agent-app"}, + Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { + // Should skip category prompt and go directly to language selection + cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything). + Return( + iostreams.SelectPromptResponse{ + Prompt: true, + Index: 1, // Select Python template + }, + nil, + ) + createClientMock = new(CreateClientMock) + createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", nil) + CreateFunc = createClientMock.Create + }, + ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { + template, err := create.ResolveTemplateURL("slack-samples/bolt-python-assistant-template") + require.NoError(t, err) + expected := create.CreateArgs{ + AppName: "my-agent-app", + Template: template, + } + createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected) + // Verify that category prompt was NOT called + cm.IO.AssertNotCalled(t, "SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything) + }, + }, + "creates an app named agent when template flag is provided": { + CmdArgs: []string{"agent", "--template", "slack-samples/bolt-js-starter-template"}, + Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { + cm.IO.On("SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything). + Return( + iostreams.SelectPromptResponse{ + Flag: true, + Option: "slack-samples/bolt-js-starter-template", + }, + nil, + ) + cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything). + Return( + iostreams.SelectPromptResponse{ + Flag: true, + Option: "slack-samples/bolt-js-starter-template", + }, + nil, + ) + createClientMock = new(CreateClientMock) + createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", nil) + CreateFunc = createClientMock.Create + }, + ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { + template, err := create.ResolveTemplateURL("slack-samples/bolt-js-starter-template") + require.NoError(t, err) + expected := create.CreateArgs{ + AppName: "agent", + Template: template, + } + createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected) + }, + }, }, func(cf *shared.ClientFactory) *cobra.Command { return NewCreateCommand(cf) }) From 7c7952ff3c09a7709d2ffbabd5570d2c6898aad5 Mon Sep 17 00:00:00 2001 From: Maria Alejandra <104795114+srtaalej@users.noreply.github.com> Date: Wed, 4 Feb 2026 10:48:48 -0500 Subject: [PATCH 2/2] Update cmd/project/create.go Co-authored-by: Michael Brooks --- cmd/project/create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/project/create.go b/cmd/project/create.go index 2305e11e..48d0763b 100644 --- a/cmd/project/create.go +++ b/cmd/project/create.go @@ -58,7 +58,7 @@ func NewCreateCommand(clients *shared.ClientFactory) *cobra.Command { Long: `Create a new Slack project on your local machine from an optional template`, Example: style.ExampleCommandsf([]style.ExampleCommand{ {Command: "create my-project", Meaning: "Create a new project from a template"}, - {Command: "create agent my-agent-app", Meaning: "Create a new AI agent app"}, + {Command: "create agent my-agent-app", Meaning: "Create a new AI Agent app"}, {Command: "create my-project -t slack-samples/deno-hello-world", Meaning: "Start a new project from a specific template"}, }), Args: cobra.MaximumNArgs(2),