From efa526142ff99af7475d4e49934656e8fe2af59b Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Thu, 2 Apr 2026 22:16:15 -0700 Subject: [PATCH] feat: add create template for support agent --- cmd/project/create_template.go | 96 ++++++------------------------- cmd/project/create_test.go | 89 ++++++++++++---------------- docs/reference/experiments.md | 1 - internal/experiment/experiment.go | 4 -- 4 files changed, 54 insertions(+), 136 deletions(-) diff --git a/cmd/project/create_template.go b/cmd/project/create_template.go index 44ec13b1..c3c0c067 100644 --- a/cmd/project/create_template.go +++ b/cmd/project/create_template.go @@ -21,7 +21,6 @@ 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" @@ -32,53 +31,9 @@ import ( ) // 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")), - 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", - }, - { - 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" - } - +func getSelectionOptions(categoryID string) []promptObject { templatePromptObjects := map[string]([]promptObject){ - "slack-cli#getting-started": []promptObject{ + "slack-cli#getting-started": { { Title: fmt.Sprintf("Bolt for JavaScript %s", style.Secondary("Node.js")), Repository: "slack-samples/bolt-js-starter-template", @@ -88,6 +43,16 @@ func getSelectionOptions(clients *shared.ClientFactory, categoryID string) []pro Repository: "slack-samples/bolt-python-starter-template", }, }, + "slack-cli#ai-apps": { + { + Title: fmt.Sprintf("Support Agent %s", style.Secondary("Resolve IT support cases")), + 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")), @@ -102,18 +67,7 @@ func getSelectionOptions(clients *shared.ClientFactory, categoryID string) []pro Repository: "slack-samples/deno-starter-template", }, }, - "slack-cli#ai-apps": { - { - 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 templatePromptObjects[categoryID] } @@ -243,14 +197,10 @@ func promptTemplateSelection(cmd *cobra.Command, clients *shared.ClientFactory, // 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:" - } + if categoryID == "slack-cli#ai-apps" { + prompt = "Select a template:" } - options := getSelectionOptions(clients, categoryID) + options := getSelectionOptions(categoryID) titles := make([]string, len(options)) for i, m := range options { titles[i] = m.Title @@ -348,28 +298,18 @@ func listTemplates(ctx context.Context, clients *shared.ClientFactory, categoryS } var categories []categoryInfo - if categoryShortcut == "agent" && clients.Config.WithExperimentOn(experiment.Templates) { + if categoryShortcut == "agent" { 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) { + } else { 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"}, } - } else { - categories = []categoryInfo{ - {id: "slack-cli#getting-started", name: "Getting started"}, - {id: "slack-cli#ai-apps", name: "AI Agent apps"}, - {id: "slack-cli#automation-apps", name: "Automation apps"}, - } } for _, category := range categories { @@ -383,7 +323,7 @@ func listTemplates(ctx context.Context, clients *shared.ClientFactory, categoryS secondary = append(secondary, repo) } } else { - for _, tmpl := range getSelectionOptions(clients, category.id) { + for _, tmpl := range getSelectionOptions(category.id) { secondary = append(secondary, tmpl.Repository) } } diff --git a/cmd/project/create_test.go b/cmd/project/create_test.go index dd209ef7..ce6380c7 100644 --- a/cmd/project/create_test.go +++ b/cmd/project/create_test.go @@ -120,7 +120,15 @@ func TestCreateCommand(t *testing.T) { CmdArgs: []string{"agent"}, Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { cm.IO.On("IsTTY").Return(true) - // Should skip category prompt and go directly to language selection + // Should skip category prompt and go directly to template selection + cm.IO.On("SelectPrompt", mock.Anything, "Select a template:", mock.Anything, mock.Anything). + Return( + iostreams.SelectPromptResponse{ + Prompt: true, + Index: 1, // Select Custom Agent + }, + nil, + ) cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything). Return( iostreams.SelectPromptResponse{ @@ -151,7 +159,15 @@ func TestCreateCommand(t *testing.T) { "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 + // Should skip category prompt and go directly to template selection + cm.IO.On("SelectPrompt", mock.Anything, "Select a template:", mock.Anything, mock.Anything). + Return( + iostreams.SelectPromptResponse{ + Prompt: true, + Index: 1, // Select Custom Agent + }, + nil, + ) cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything). Return( iostreams.SelectPromptResponse{ @@ -178,12 +194,9 @@ func TestCreateCommand(t *testing.T) { cm.IO.AssertNotCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything) }, }, - "creates a pydantic ai agent app with templates experiment": { + "creates a pydantic ai agent app": { CmdArgs: []string{"my-pydantic-app"}, Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { - cm.AddDefaultMocks() - cm.Config.ExperimentsFlag = append(cm.Config.ExperimentsFlag, "templates") - cm.Config.LoadExperiments(ctx, cm.IO.PrintDebug) cm.IO.On("SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything). Return(iostreams.SelectPromptResponse{Prompt: true, Index: 1}, nil) cm.IO.On("SelectPrompt", mock.Anything, "Select a template:", mock.Anything, mock.Anything). @@ -283,6 +296,14 @@ func TestCreateCommand(t *testing.T) { 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 template:", mock.Anything, mock.Anything). + Return( + iostreams.SelectPromptResponse{ + Prompt: true, + Index: 1, // Select Custom Agent + }, + nil, + ) cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything). Return( iostreams.SelectPromptResponse{ @@ -346,6 +367,14 @@ func TestCreateCommand(t *testing.T) { 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 template:", mock.Anything, mock.Anything). + Return( + iostreams.SelectPromptResponse{ + Prompt: true, + Index: 1, // Select Custom Agent + }, + nil, + ) cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything). Return( iostreams.SelectPromptResponse{ @@ -567,49 +596,6 @@ func TestCreateCommand(t *testing.T) { createClientMock = new(CreateClientMock) CreateFunc = createClientMock.Create }, - ExpectedOutputs: []string{ - "Getting started", - "AI Agent apps", - "Automation apps", - "slack-samples/bolt-js-starter-template", - "slack-samples/bolt-python-starter-template", - "slack-samples/bolt-js-assistant-template", - "slack-samples/bolt-python-assistant-template", - "slack-samples/bolt-js-custom-function-template", - "slack-samples/bolt-python-custom-function-template", - "slack-samples/deno-starter-template", - }, - ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { - createClientMock.AssertNotCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything) - }, - }, - "lists agent templates with agent --list flag": { - CmdArgs: []string{"agent", "--list"}, - Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { - createClientMock = new(CreateClientMock) - CreateFunc = createClientMock.Create - }, - ExpectedOutputs: []string{ - "AI Agent apps", - "slack-samples/bolt-js-assistant-template", - "slack-samples/bolt-python-assistant-template", - }, - ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { - createClientMock.AssertNotCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything) - output := cm.GetCombinedOutput() - assert.NotContains(t, output, "Getting started") - assert.NotContains(t, output, "Automation apps") - }, - }, - "lists all templates with --list flag and templates experiment": { - CmdArgs: []string{"--list"}, - Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { - cm.AddDefaultMocks() - cm.Config.ExperimentsFlag = append(cm.Config.ExperimentsFlag, "templates") - cm.Config.LoadExperiments(ctx, cm.IO.PrintDebug) - createClientMock = new(CreateClientMock) - CreateFunc = createClientMock.Create - }, ExpectedOutputs: []string{ "Getting started", "slack-samples/bolt-js-starter-template", @@ -630,12 +616,9 @@ func TestCreateCommand(t *testing.T) { createClientMock.AssertNotCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything) }, }, - "lists agent templates with agent --list flag and templates experiment": { + "lists agent templates with agent --list flag": { CmdArgs: []string{"agent", "--list"}, Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { - cm.AddDefaultMocks() - cm.Config.ExperimentsFlag = append(cm.Config.ExperimentsFlag, "templates") - cm.Config.LoadExperiments(ctx, cm.IO.PrintDebug) createClientMock = new(CreateClientMock) CreateFunc = createClientMock.Create }, diff --git a/docs/reference/experiments.md b/docs/reference/experiments.md index 754822c9..e2c57ca5 100644 --- a/docs/reference/experiments.md +++ b/docs/reference/experiments.md @@ -8,7 +8,6 @@ The following is a list of currently available experiments. We'll remove experim - `lipgloss`: shows pretty styles. - `sandboxes`: enables users who have joined the Slack Developer Program to manage their sandboxes ([PR#379](https://github.com/slackapi/slack-cli/pull/379)). -- `templates`: brings more agent templates to the `create` command. ## Experiments changelog diff --git a/internal/experiment/experiment.go b/internal/experiment/experiment.go index 375ba188..7d51b654 100644 --- a/internal/experiment/experiment.go +++ b/internal/experiment/experiment.go @@ -38,9 +38,6 @@ const ( // Sandboxes experiment lets users who have joined the Slack Developer Program use the CLI to manage their sandboxes. Sandboxes Experiment = "sandboxes" - - // Templates experiment brings more agent templates to the create command. - Templates Experiment = "templates" ) // AllExperiments is a list of all available experiments that can be enabled @@ -49,7 +46,6 @@ var AllExperiments = []Experiment{ Lipgloss, Placeholder, Sandboxes, - Templates, } // EnabledExperiments is a list of experiments that are permanently enabled