Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 96 additions & 69 deletions cmd/lk/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ var (
secretsFileFlag,
secretsMountFlag,
silentFlag,
regionFlag,
ignoreEmptySecretsFlag,
skipSDKCheckFlag,
},
Expand Down Expand Up @@ -401,34 +402,38 @@ func initAgent(ctx context.Context, cmd *cli.Command) error {
// TODO: (@rektdeckard) move compatibility flag into template index,
// then show template picker containing only compatible templates
if !(cmd.IsSet("lang") || cmd.IsSet("template") || cmd.IsSet("template-url")) {
var lang string
// Prompt for language
if err := huh.NewSelect[string]().
Title("Select the language for your agent project").
Options(
huh.NewOption("Python", "python"),
huh.NewOption("Node.js", "node"),
).
Value(&lang).
WithTheme(util.Theme).
Run(); err != nil {
return err
}

switch lang {
case "node":
templateURL = "https://github.com/livekit-examples/agent-starter-node"
case "python":
if SkipPrompts(cmd) {
templateURL = "https://github.com/livekit-examples/agent-starter-python"
default:
return fmt.Errorf("unsupported language: %s", lang)
} else {
var lang string
// Prompt for language
if err := huh.NewSelect[string]().
Title("Select the language for your agent project").
Options(
huh.NewOption("Python", "python"),
huh.NewOption("Node.js", "node"),
).
Value(&lang).
WithTheme(util.Theme).
Run(); err != nil {
return err
}

switch lang {
case "node":
templateURL = "https://github.com/livekit-examples/agent-starter-node"
case "python":
templateURL = "https://github.com/livekit-examples/agent-starter-python"
default:
return fmt.Errorf("unsupported language: %s", lang)
}
}
}

logger.Debugw("Initializing agent project", "working-dir", workingDir)

// Create sandbox
if !cmd.Bool("no-sandbox") || sandboxID == "" {
// Create sandbox only when not disabled by flag and we don't already have one
if !cmd.Bool("no-sandbox") && sandboxID == "" {
if err := util.Await("Creating sandbox app...", ctx, func(ctx context.Context) error {
token, err := requireToken(ctx, cmd)
if err != nil {
Expand Down Expand Up @@ -495,14 +500,16 @@ func createAgent(ctx context.Context, cmd *cli.Command) error {
// set via a command line flag, because intent is clear.
if !cmd.IsSet("project") {
useProject := true
if err := huh.NewForm(huh.NewGroup(huh.NewConfirm().
Title(fmt.Sprintf("Use project [%s] (%s) to create agent deployment?", project.Name, project.URL)).
Value(&useProject).
Negative("Select another").
Inline(false).
WithTheme(util.Theme))).
Run(); err != nil {
return err
if !SkipPrompts(cmd) {
if err := huh.NewForm(huh.NewGroup(huh.NewConfirm().
Title(fmt.Sprintf("Use project [%s] (%s) to create agent deployment?", project.Name, project.URL)).
Value(&useProject).
Negative("Select another").
Inline(false).
WithTheme(util.Theme))).
Run(); err != nil {
return err
}
}
if !useProject {
if _, err := selectProject(ctx, cmd); err != nil {
Expand Down Expand Up @@ -580,7 +587,9 @@ func createAgent(ctx context.Context, cmd *cli.Command) error {
}
slices.Sort(regionOptions)

if err := huh.NewSelect[string]().
if SkipPrompts(cmd) {
return fmt.Errorf("non-interactive mode: --region flag must be specified, available regions: %v", regionOptions)
} else if err := huh.NewSelect[string]().
Title("Select region for agent deployment").
Options(huh.NewOptions(regionOptions...)...).
Value(&region).
Expand Down Expand Up @@ -619,7 +628,7 @@ func createAgent(ctx context.Context, cmd *cli.Command) error {

fmt.Println("Build completed - You can view build logs later with `lk agent logs --log-type=build`")

if !silent {
if !silent && !SkipPrompts(cmd) {
var viewLogs bool = true
if err := huh.NewForm(
huh.NewGroup(
Expand All @@ -641,19 +650,25 @@ func createAgent(ctx context.Context, cmd *cli.Command) error {

func createAgentConfig(ctx context.Context, cmd *cli.Command) error {
if _, err := os.Stat(tomlFilename); err == nil {
var overwrite bool
if err := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title(
fmt.Sprintf("Config file [%s] file already exists. Overwrite?", tomlFilename),
).
Value(&overwrite).
WithTheme(util.Theme),
),
).
Run(); err != nil {
return err
overwrite := false
if SkipPrompts(cmd) {
overwrite = true
} else {
var overwriteVal bool
if err := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title(
fmt.Sprintf("Config file [%s] file already exists. Overwrite?", tomlFilename),
).
Value(&overwriteVal).
WithTheme(util.Theme),
),
).
Run(); err != nil {
return err
}
overwrite = overwriteVal
}
if !overwrite {
return fmt.Errorf("config file [%s] already exists", util.Accented(tomlFilename))
Expand Down Expand Up @@ -709,24 +724,26 @@ func createAgentConfig(ctx context.Context, cmd *cli.Command) error {
}

func deployAgent(ctx context.Context, cmd *cli.Command) error {
var req *lkproto.DeployAgentRequest
// If no agent exists yet (no --id and no config with agent), do first-time create (which deploys).
if cmd.String("id") == "" {
configExists, err := requireConfig(workingDir, tomlFilename)
if err != nil && configExists {
return err
}
if !configExists || lkConfig == nil || !lkConfig.HasAgent() {
return createAgent(ctx, cmd)
}
}

agentId, err := getAgentID(ctx, cmd, workingDir, tomlFilename, false)
if err != nil {
return err
}

req = &lkproto.DeployAgentRequest{
AgentId: agentId,
}

secrets, err := requireSecrets(ctx, cmd, false, true)
if err != nil {
return err
}
if len(secrets) > 0 {
req.Secrets = secrets
}

projectType, err := agentfs.DetectProjectType(os.DirFS(workingDir))
if err != nil {
Expand Down Expand Up @@ -945,7 +962,7 @@ func deleteAgent(ctx context.Context, cmd *cli.Command) error {
return err
}

if !silent {
if !silent && !SkipPrompts(cmd) {
var confirmDelete bool
if err := huh.NewForm(
huh.NewGroup(
Expand Down Expand Up @@ -1138,16 +1155,19 @@ func updateAgentSecrets(ctx context.Context, cmd *cli.Command) error {

var confirmOverwrite bool
if cmd.Bool("overwrite") {
if err := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title(fmt.Sprintf("This will remove all existing secrets. Are you sure you want to proceed [%s]?", agentID)).
Value(&confirmOverwrite).
Inline(false).
WithTheme(util.Theme),
),
).Run(); err != nil {
return err
confirmOverwrite = SkipPrompts(cmd)
if !SkipPrompts(cmd) {
if err := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title(fmt.Sprintf("This will remove all existing secrets. Are you sure you want to proceed [%s]?", agentID)).
Value(&confirmOverwrite).
Inline(false).
WithTheme(util.Theme),
),
).Run(); err != nil {
return err
}
}
if !confirmOverwrite {
return nil
Expand Down Expand Up @@ -1207,7 +1227,7 @@ func getAgentID(ctx context.Context, cmd *cli.Command, agentDir string, tomlFile
return agentID, nil
}

func selectAgent(ctx context.Context, _ *cli.Command, excludeEmptyVersion bool) (string, error) {
func selectAgent(ctx context.Context, cmd *cli.Command, excludeEmptyVersion bool) (string, error) {
var agents *lkproto.ListAgentsResponse

err := util.Await("No agent ID provided, selecting from available agents...", ctx, func(ctx context.Context) error {
Expand Down Expand Up @@ -1235,6 +1255,13 @@ func selectAgent(ctx context.Context, _ *cli.Command, excludeEmptyVersion bool)
agentNames = append(agentNames, huh.Option[string]{Key: name, Value: agent.AgentId})
}

if SkipPrompts(cmd) {
if len(agentNames) != 1 {
return "", fmt.Errorf("non-interactive mode: set --id when multiple agents exist")
}
return agentNames[0].Value, nil
}

var selectedAgent string
if err := huh.NewSelect[string]().
Title("Select an agent").
Expand Down Expand Up @@ -1289,7 +1316,7 @@ func requireSecrets(_ context.Context, cmd *cli.Command, required, lazy bool) ([

shouldReadFromDisk := cmd.IsSet("secrets-file") || !lazy || (required && len(secrets) == 0)
if shouldReadFromDisk {
file, env, err := agentfs.DetectEnvFile(cmd.String("secrets-file"))
file, env, err := agentfs.DetectEnvFile(cmd.String("secrets-file"), SkipPrompts(cmd))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1366,15 +1393,15 @@ func requireDockerfile(ctx context.Context, cmd *cli.Command, workingDir string,
"Creating Dockerfile...",
ctx,
func(ctx context.Context) error {
return agentfs.CreateDockerfile(workingDir, projectType, settingsMap)
return agentfs.CreateDockerfile(workingDir, projectType, settingsMap, SkipPrompts(cmd))
},
)
if err != nil {
return err
}
fmt.Println("Created [" + util.Accented("Dockerfile") + "]")
} else {
if err := agentfs.CreateDockerfile(workingDir, projectType, settingsMap); err != nil {
if err := agentfs.CreateDockerfile(workingDir, projectType, settingsMap, SkipPrompts(cmd)); err != nil {
return err
}
}
Expand Down Expand Up @@ -1490,7 +1517,7 @@ func generateAgentDockerfile(ctx context.Context, cmd *cli.Command) error {
}

// Generate contents without writing
dockerfileContent, dockerignoreContent, err := agentfs.GenerateDockerArtifacts(workingDir, projectType, settingsMap)
dockerfileContent, dockerignoreContent, err := agentfs.GenerateDockerArtifacts(workingDir, projectType, settingsMap, SkipPrompts(cmd))
if err != nil {
return err
}
Expand Down
Loading
Loading