diff --git a/cmd/project/create.go b/cmd/project/create.go index 24d4d78a..9a489120 100644 --- a/cmd/project/create.go +++ b/cmd/project/create.go @@ -31,6 +31,7 @@ import ( // Flags var createTemplateURLFlag string var createGitBranchFlag string +var createAppNameFlag string // Handle to client's create function used for testing // TODO - Find best practice, such as using an Interface and Struct to create a client @@ -53,13 +54,17 @@ const viewMoreSamples = "slack-cli#view-more-samples" func NewCreateCommand(clients *shared.ClientFactory) *cobra.Command { cmd := &cobra.Command{ SuggestFor: []string{"new"}, - Use: "create [name] [flags]", + Use: "create [name | agent ] [flags]", Short: "Create a new Slack project", - Long: `Create a new Slack project on your local machine from an optional template`, + Long: `Create a new Slack project on your local machine from an optional template. + +The 'agent' argument is a shortcut to create an AI Agent app. If you want to +name your app 'agent' (not create an AI Agent), use the --name flag instead.`, 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"}, + {Command: "create --name my-project", Meaning: "Create a project named 'my-project'"}, }), Args: cobra.MaximumNArgs(2), RunE: func(cmd *cobra.Command, args []string) error { @@ -71,6 +76,7 @@ func NewCreateCommand(clients *shared.ClientFactory) *cobra.Command { // Add flags cmd.Flags().StringVarP(&createTemplateURLFlag, "template", "t", "", "template URL for your app") cmd.Flags().StringVarP(&createGitBranchFlag, "branch", "b", "", "name of git branch to checkout") + cmd.Flags().StringVarP(&createAppNameFlag, "name", "n", "", "name for your app (overrides the name argument)") return cmd } @@ -85,6 +91,7 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args [] appNameArg := "" categoryShortcut := "" templateFlagProvided := cmd.Flags().Changed("template") + nameFlagProvided := cmd.Flags().Changed("name") if len(args) > 0 { switch args[0] { @@ -108,6 +115,12 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args [] } } + // --name flag overrides any positional app name argument + // This allows users to name their app "agent" without triggering the AI Agent shortcut + if nameFlagProvided { + appNameArg = createAppNameFlag + } + // Collect the template URL or select a starting template template, err := promptTemplateSelection(cmd, clients, categoryShortcut) if err != nil { diff --git a/cmd/project/create_test.go b/cmd/project/create_test.go index 69e40408..7007f988 100644 --- a/cmd/project/create_test.go +++ b/cmd/project/create_test.go @@ -195,6 +195,131 @@ func TestCreateCommand(t *testing.T) { createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected) }, }, + "creates an app named agent using name flag without triggering shortcut": { + CmdArgs: []string{"--name", "agent"}, + Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { + // Should prompt for category since agent shortcut is NOT triggered + cm.IO.On("SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything). + Return( + iostreams.SelectPromptResponse{ + Prompt: true, + Index: 0, // Select starter app + }, + nil, + ) + cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything). + Return( + iostreams.SelectPromptResponse{ + Prompt: true, + Index: 0, // Select Node.js + }, + 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) + // Verify that category prompt WAS called (shortcut was not triggered) + cm.IO.AssertCalled(t, "SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything) + }, + }, + "creates an agent app with name flag overriding positional arg": { + CmdArgs: []string{"agent", "--name", "my-custom-name"}, + Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { + // Should skip category prompt due to agent shortcut + cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything). + Return( + iostreams.SelectPromptResponse{ + Prompt: true, + Index: 0, // Select Node.js + }, + 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{ + AppName: "my-custom-name", // --name flag overrides + Template: template, + } + createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected) + // Verify that category prompt was NOT called (shortcut was triggered) + cm.IO.AssertNotCalled(t, "SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything) + }, + }, + "name flag overrides positional app name argument": { + CmdArgs: []string{"my-project", "--name", "my-name"}, + 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{ + Prompt: true, + Index: 0, // Select starter app + }, + nil, + ) + cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything). + Return( + iostreams.SelectPromptResponse{ + Prompt: true, + Index: 0, // Select Node.js + }, + 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: "my-name", // --name flag overrides "my-project" positional arg + Template: template, + } + createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected) + }, + }, + "name flag overrides positional app name argument with agent shortcut": { + CmdArgs: []string{"agent", "my-project", "--name", "my-name"}, + Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { + // Should skip category prompt due to agent shortcut + cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything). + Return( + iostreams.SelectPromptResponse{ + Prompt: true, + Index: 0, // Select Node.js + }, + 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{ + AppName: "my-name", // --name flag overrides "my-project" positional arg + Template: template, + } + createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected) + // Verify that category prompt was NOT called (agent shortcut was triggered) + cm.IO.AssertNotCalled(t, "SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything) + }, + }, }, func(cf *shared.ClientFactory) *cobra.Command { return NewCreateCommand(cf) })