From 382cdef4dd62cf298242b59051155de4706580c7 Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Wed, 5 Mar 2025 09:18:14 -0600 Subject: [PATCH] New Project Flow: ask for agent --- cmd/agent.go | 60 +++++++++++++++----- cmd/cloud.go | 16 +++--- cmd/project.go | 44 +++++++------- internal/errsystem/console.go | 4 +- internal/templates/data/bunjs/rules.yaml | 5 +- internal/templates/data/nodejs/rules.yaml | 5 +- internal/templates/data/python-uv/rules.yaml | 7 +-- internal/templates/steps.go | 2 +- internal/templates/template.go | 22 +++---- 9 files changed, 94 insertions(+), 71 deletions(-) diff --git a/cmd/agent.go b/cmd/agent.go index 9bbaaab7..eed82fcb 100644 --- a/cmd/agent.go +++ b/cmd/agent.go @@ -84,10 +84,38 @@ var agentDeleteCmd = &cobra.Command{ }, } +func getAgentInfoFlow(logger logger.Logger, remoteAgents []agent.Agent, name string, description string) (string, string) { + if name == "" { + var prompt, help string + if len(remoteAgents) > 0 { + prompt = "What should we name the agent?" + help = "The name of the agent must be unique within the project" + } else { + prompt = "What should we name the initial agent?" + help = "The name can be changed at any time and helps identify the agent" + } + name = tui.InputWithValidation(logger, prompt, help, 255, func(name string) error { + for _, agent := range remoteAgents { + if strings.EqualFold(agent.Name, name) { + return fmt.Errorf("agent %s already exists with this name", name) + } + } + return nil + }) + } + + if description == "" { + description = tui.Input(logger, "How should we describe what the "+name+" agent does?", "The description of the agent is optional but helpful for understanding the role of the agent") + } + + return name, description +} + var agentCreateCmd = &cobra.Command{ - Use: "create", + Use: "create [name] [description]", Short: "Create a new agent", Aliases: []string{"new"}, + Args: cobra.MaximumNArgs(2), Run: func(cmd *cobra.Command, args []string) { logger := env.NewLogger(cmd) theproject := ensureProject(cmd) @@ -101,16 +129,18 @@ var agentCreateCmd = &cobra.Command{ initScreenWithLogo() - name := tui.InputWithValidation(logger, "What should we name the agent?", "The name of the agent must be unique within the project", 255, func(name string) error { - for _, agent := range remoteAgents { - if strings.EqualFold(agent.Name, name) { - return fmt.Errorf("agent %s already exists with this name", name) - } - } - return nil - }) + var name string + var description string + + if len(args) > 0 { + name = args[0] + } - description := tui.Input(logger, "How should we describe what the "+name+" agent does?", "The description of the agent is optional but helpful for understanding the role of the agent") + if len(args) > 1 { + description = args[1] + } + + name, description = getAgentInfoFlow(logger, remoteAgents, name, description) action := func() { agentID, err := agent.CreateAgent(logger, apiUrl, apikey, theproject.Project.ProjectId, name, description) @@ -124,10 +154,12 @@ var agentCreateCmd = &cobra.Command{ } if err := rules.NewAgent(templates.TemplateContext{ - Logger: logger, - Name: name, - Description: description, - ProjectDir: theproject.Dir, + Logger: logger, + AgentName: name, + Name: name, + Description: description, + AgentDescription: description, + ProjectDir: theproject.Dir, }); err != nil { errsystem.New(errsystem.ErrApiRequest, err, errsystem.WithAttributes(map[string]any{"name": name})).ShowErrorAndExit() } diff --git a/cmd/cloud.go b/cmd/cloud.go index f6d1c523..0ddb839e 100644 --- a/cmd/cloud.go +++ b/cmd/cloud.go @@ -71,18 +71,12 @@ type projectContext struct { Token string } -func ensureProject(cmd *cobra.Command) projectContext { - logger := env.NewLogger(cmd) - dir := resolveProjectDir(cmd) - apiUrl, appUrl := getURLs(logger) - token, _ := ensureLoggedIn() - +func loadProject(logger logger.Logger, dir string, apiUrl string, appUrl string, token string) projectContext { theproject := project.NewProject() if err := theproject.Load(dir); err != nil { errsystem.New(errsystem.ErrInvalidConfiguration, err, errsystem.WithContextMessage("Error loading project from disk")).ShowErrorAndExit() } - return projectContext{ Logger: logger, Project: theproject, @@ -93,6 +87,14 @@ func ensureProject(cmd *cobra.Command) projectContext { } } +func ensureProject(cmd *cobra.Command) projectContext { + logger := env.NewLogger(cmd) + dir := resolveProjectDir(cmd) + apiUrl, appUrl := getURLs(logger) + token, _ := ensureLoggedIn() + return loadProject(logger, dir, apiUrl, appUrl, token) +} + var cloudDeployCmd = &cobra.Command{ Use: "deploy", Short: "Deploy project to the cloud", diff --git a/cmd/project.go b/cmd/project.go index 2560f0a3..438e6413 100644 --- a/cmd/project.go +++ b/cmd/project.go @@ -19,7 +19,6 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/spf13/cobra" - "github.com/spf13/viper" ) var projectCmd = &cobra.Command{ @@ -231,17 +230,13 @@ func showProjectSelector(items []list.Item) *templates.Template { } var projectNewCmd = &cobra.Command{ - Use: "create [name]", + Use: "create [name] [description] [agent-name] [agent-description]", Short: "Create a new project", Aliases: []string{"new"}, - Args: cobra.MaximumNArgs(1), + Args: cobra.MaximumNArgs(4), Run: func(cmd *cobra.Command, args []string) { logger := env.NewLogger(cmd) - apikey := viper.GetString("auth.api_key") - if apikey == "" { - logger.Fatal("You are not logged in. Please run `agentuity login` to login.") - } - + apikey, _ := ensureLoggedIn() apiUrl, appUrl := getURLs(logger) initScreenWithLogo() @@ -252,7 +247,7 @@ var projectNewCmd = &cobra.Command{ orgId := promptForOrganization(logger, apiUrl, apikey) - var name string + var name, description, agentName, agentDescription string if len(args) > 0 { name = args[0] @@ -279,7 +274,9 @@ var projectNewCmd = &cobra.Command{ }) } - description := tui.Input(logger, "How should we describe what the "+name+" project does?", "The description of the project is optional but helpful") + if description == "" { + description = tui.Input(logger, "How should we describe what the "+name+" project does?", "The description of the project is optional but helpful") + } projectDir := filepath.Join(cwd, util.SafeFilename(name)) dir, _ := cmd.Flags().GetString("dir") @@ -358,15 +355,21 @@ var projectNewCmd = &cobra.Command{ } } + if agentName == "" { + agentName, agentDescription = getAgentInfoFlow(logger, nil, agentName, agentDescription) + } + var projectData *project.ProjectData tui.ShowSpinner("creating project ...", func() { rules, err := provider.NewProject(templates.TemplateContext{ - Context: context.Background(), - Logger: logger, - Name: name, - Description: description, - ProjectDir: projectDir, + Context: context.Background(), + Logger: logger, + Name: name, + Description: description, + ProjectDir: projectDir, + AgentName: agentName, + AgentDescription: agentDescription, }) if err != nil { errsystem.New(errsystem.ErrCreateProject, err, errsystem.WithContextMessage("Failed to create project")).ShowErrorAndExit() @@ -380,8 +383,8 @@ var projectNewCmd = &cobra.Command{ Name: name, Description: description, Provider: rules, - AgentName: rules.NewProjectSteps.InitialAgent.Name, - AgentDescription: rules.NewProjectSteps.InitialAgent.Description, + AgentName: agentName, + AgentDescription: agentDescription, }) }) @@ -450,10 +453,7 @@ var projectDeleteCmd = &cobra.Command{ Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { logger := env.NewLogger(cmd) - apikey := viper.GetString("auth.api_key") - if apikey == "" { - logger.Fatal("You are not logged in. Please run `agentuity login` to login.") - } + apikey, _ := ensureLoggedIn() apiUrl, _ := getURLs(logger) var projects []project.ProjectListData action := func() { @@ -516,5 +516,5 @@ func init() { projectCmd.AddCommand(projectDeleteCmd) projectNewCmd.Flags().StringP("dir", "d", "", "The directory to create the project in") - projectNewCmd.Flags().StringP("provider", "p", "", "The provider to use for the project") + projectNewCmd.Flags().StringP("provider", "p", "", "The provider template to use for the project") } diff --git a/internal/errsystem/console.go b/internal/errsystem/console.go index b8f4a35a..657b02ce 100644 --- a/internal/errsystem/console.go +++ b/internal/errsystem/console.go @@ -57,7 +57,9 @@ func (e *errSystem) writeCrashReportFile(stackTrace string) string { report.Attributes = e.attributes report.CLIVersion = Version report.StackTrace = stackTrace - json.NewEncoder(tmp).Encode(report) + enc := json.NewEncoder(tmp) + enc.SetIndent(" ", " ") + enc.Encode(report) return tmp.Name() } diff --git a/internal/templates/data/bunjs/rules.yaml b/internal/templates/data/bunjs/rules.yaml index 82de923d..849a8aa8 100644 --- a/internal/templates/data/bunjs/rules.yaml +++ b/internal/templates/data/bunjs/rules.yaml @@ -44,9 +44,6 @@ new_agent: filename: "src/agents/{{ .Name | safe_filename }}/index.ts" from: "common/js/agent.ts" new_project: - initial: - name: "MyFirstAgent" - description: "This is my first agent which uses the Vercel AI SDK to generate a text response" steps: - command: bun args: @@ -92,7 +89,7 @@ new_project: filename: "index.ts" from: "common/js/boot.ts" - action: create_file - filename: "src/agents/MyFirstAgent/index.ts" + filename: "src/agents/{{ .AgentName | safe_filename }}/index.ts" from: "common/js/agent.ts" - action: copy_dir from: "common/js/cursor" diff --git a/internal/templates/data/nodejs/rules.yaml b/internal/templates/data/nodejs/rules.yaml index 48f85fb6..695f3531 100644 --- a/internal/templates/data/nodejs/rules.yaml +++ b/internal/templates/data/nodejs/rules.yaml @@ -37,9 +37,6 @@ new_agent: filename: "src/agents/{{ .Name | safe_filename }}/index.ts" from: "common/js/agent.ts" new_project: - initial: - name: "MyFirstAgent" - description: "This is my first agent which uses the Vercel AI SDK to generate a text response" steps: - command: npm args: @@ -84,7 +81,7 @@ new_project: filename: ".gitignore" from: "data/nodejs/gitignore" - action: create_file - filename: "src/agents/MyFirstAgent/index.ts" + filename: "src/agents/{{ .AgentName | safe_filename }}/index.ts" from: "common/js/agent.ts" - action: copy_dir from: "common/js/cursor" diff --git a/internal/templates/data/python-uv/rules.yaml b/internal/templates/data/python-uv/rules.yaml index a35effcf..f0df07ca 100644 --- a/internal/templates/data/python-uv/rules.yaml +++ b/internal/templates/data/python-uv/rules.yaml @@ -31,9 +31,6 @@ new_agent: filename: "agents/{{ .Name | safe_filename }}/agent.py" from: "common/py/agent.py" new_project: - initial: - name: "myfirstagent" - description: "This is my first agent which uses the LiteLLM to generate a text response" steps: - command: uv args: @@ -44,7 +41,7 @@ new_project: args: - init - --name - - "{{ .Name }}" + - "{{ .Name | safe_filename }}" - --no-package - --python - ">=3.10" @@ -72,5 +69,5 @@ new_project: filename: ".gitignore" from: "data/common/py/gitignore" - action: create_file - filename: "agents/myfirstagent/agent.py" + filename: "agents/{{ .AgentName | safe_filename }}/agent.py" from: "common/py/agent.py" diff --git a/internal/templates/steps.go b/internal/templates/steps.go index 29326955..1ec83dbd 100644 --- a/internal/templates/steps.go +++ b/internal/templates/steps.go @@ -374,7 +374,7 @@ func resolveStep(ctx TemplateContext, step any) (Step, bool) { } var name string if val, ok := kv["name"].(string); ok { - name = ctx.Interpolate(val).(string) + name = util.SafeFilename(ctx.Interpolate(val).(string)) } var version string if val, ok := kv["version"].(string); ok { diff --git a/internal/templates/template.go b/internal/templates/template.go index 673e9eb3..3e501299 100644 --- a/internal/templates/template.go +++ b/internal/templates/template.go @@ -12,12 +12,14 @@ import ( ) type TemplateContext struct { - Context context.Context - Logger logger.Logger - Name string - Description string - ProjectDir string - Template *Template + Context context.Context + Logger logger.Logger + Name string + Description string + AgentName string + AgentDescription string + ProjectDir string + Template *Template } func funcTemplates(t *template.Template) *template.Template { @@ -50,8 +52,7 @@ func (t *TemplateContext) Interpolate(val any) any { } type NewProjectSteps struct { - InitialAgent InitialAgent `yaml:"initial"` - Steps []any `yaml:"steps"` + Steps []any `yaml:"steps"` } type NewAgentSteps struct { @@ -86,11 +87,6 @@ type Deployment struct { Args []string `yaml:"args"` } -type InitialAgent struct { - Name string `yaml:"name"` - Description string `yaml:"description"` -} - type TemplateRules struct { Identifier string `yaml:"identifier"` Runtime string `yaml:"runtime"`