Skip to content

Commit 75fcdfc

Browse files
authored
feat(lk): support non-interactive terminals and default accept flag (#776)
* feat(lk): support non-interactive terminals and default accept flag * chore(agents): fix no-sandbox check
1 parent 6168f05 commit 75fcdfc

9 files changed

Lines changed: 222 additions & 107 deletions

File tree

cmd/lk/agent.go

Lines changed: 96 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ var (
209209
secretsFileFlag,
210210
secretsMountFlag,
211211
silentFlag,
212+
regionFlag,
212213
ignoreEmptySecretsFlag,
213214
skipSDKCheckFlag,
214215
},
@@ -401,34 +402,38 @@ func initAgent(ctx context.Context, cmd *cli.Command) error {
401402
// TODO: (@rektdeckard) move compatibility flag into template index,
402403
// then show template picker containing only compatible templates
403404
if !(cmd.IsSet("lang") || cmd.IsSet("template") || cmd.IsSet("template-url")) {
404-
var lang string
405-
// Prompt for language
406-
if err := huh.NewSelect[string]().
407-
Title("Select the language for your agent project").
408-
Options(
409-
huh.NewOption("Python", "python"),
410-
huh.NewOption("Node.js", "node"),
411-
).
412-
Value(&lang).
413-
WithTheme(util.Theme).
414-
Run(); err != nil {
415-
return err
416-
}
417-
418-
switch lang {
419-
case "node":
420-
templateURL = "https://github.com/livekit-examples/agent-starter-node"
421-
case "python":
405+
if SkipPrompts(cmd) {
422406
templateURL = "https://github.com/livekit-examples/agent-starter-python"
423-
default:
424-
return fmt.Errorf("unsupported language: %s", lang)
407+
} else {
408+
var lang string
409+
// Prompt for language
410+
if err := huh.NewSelect[string]().
411+
Title("Select the language for your agent project").
412+
Options(
413+
huh.NewOption("Python", "python"),
414+
huh.NewOption("Node.js", "node"),
415+
).
416+
Value(&lang).
417+
WithTheme(util.Theme).
418+
Run(); err != nil {
419+
return err
420+
}
421+
422+
switch lang {
423+
case "node":
424+
templateURL = "https://github.com/livekit-examples/agent-starter-node"
425+
case "python":
426+
templateURL = "https://github.com/livekit-examples/agent-starter-python"
427+
default:
428+
return fmt.Errorf("unsupported language: %s", lang)
429+
}
425430
}
426431
}
427432

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

430-
// Create sandbox
431-
if !cmd.Bool("no-sandbox") || sandboxID == "" {
435+
// Create sandbox only when not disabled by flag and we don't already have one
436+
if !cmd.Bool("no-sandbox") && sandboxID == "" {
432437
if err := util.Await("Creating sandbox app...", ctx, func(ctx context.Context) error {
433438
token, err := requireToken(ctx, cmd)
434439
if err != nil {
@@ -495,14 +500,16 @@ func createAgent(ctx context.Context, cmd *cli.Command) error {
495500
// set via a command line flag, because intent is clear.
496501
if !cmd.IsSet("project") {
497502
useProject := true
498-
if err := huh.NewForm(huh.NewGroup(huh.NewConfirm().
499-
Title(fmt.Sprintf("Use project [%s] (%s) to create agent deployment?", project.Name, project.URL)).
500-
Value(&useProject).
501-
Negative("Select another").
502-
Inline(false).
503-
WithTheme(util.Theme))).
504-
Run(); err != nil {
505-
return err
503+
if !SkipPrompts(cmd) {
504+
if err := huh.NewForm(huh.NewGroup(huh.NewConfirm().
505+
Title(fmt.Sprintf("Use project [%s] (%s) to create agent deployment?", project.Name, project.URL)).
506+
Value(&useProject).
507+
Negative("Select another").
508+
Inline(false).
509+
WithTheme(util.Theme))).
510+
Run(); err != nil {
511+
return err
512+
}
506513
}
507514
if !useProject {
508515
if _, err := selectProject(ctx, cmd); err != nil {
@@ -580,7 +587,9 @@ func createAgent(ctx context.Context, cmd *cli.Command) error {
580587
}
581588
slices.Sort(regionOptions)
582589

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

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

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

642651
func createAgentConfig(ctx context.Context, cmd *cli.Command) error {
643652
if _, err := os.Stat(tomlFilename); err == nil {
644-
var overwrite bool
645-
if err := huh.NewForm(
646-
huh.NewGroup(
647-
huh.NewConfirm().
648-
Title(
649-
fmt.Sprintf("Config file [%s] file already exists. Overwrite?", tomlFilename),
650-
).
651-
Value(&overwrite).
652-
WithTheme(util.Theme),
653-
),
654-
).
655-
Run(); err != nil {
656-
return err
653+
overwrite := false
654+
if SkipPrompts(cmd) {
655+
overwrite = true
656+
} else {
657+
var overwriteVal bool
658+
if err := huh.NewForm(
659+
huh.NewGroup(
660+
huh.NewConfirm().
661+
Title(
662+
fmt.Sprintf("Config file [%s] file already exists. Overwrite?", tomlFilename),
663+
).
664+
Value(&overwriteVal).
665+
WithTheme(util.Theme),
666+
),
667+
).
668+
Run(); err != nil {
669+
return err
670+
}
671+
overwrite = overwriteVal
657672
}
658673
if !overwrite {
659674
return fmt.Errorf("config file [%s] already exists", util.Accented(tomlFilename))
@@ -709,24 +724,26 @@ func createAgentConfig(ctx context.Context, cmd *cli.Command) error {
709724
}
710725

711726
func deployAgent(ctx context.Context, cmd *cli.Command) error {
712-
var req *lkproto.DeployAgentRequest
727+
// If no agent exists yet (no --id and no config with agent), do first-time create (which deploys).
728+
if cmd.String("id") == "" {
729+
configExists, err := requireConfig(workingDir, tomlFilename)
730+
if err != nil && configExists {
731+
return err
732+
}
733+
if !configExists || lkConfig == nil || !lkConfig.HasAgent() {
734+
return createAgent(ctx, cmd)
735+
}
736+
}
713737

714738
agentId, err := getAgentID(ctx, cmd, workingDir, tomlFilename, false)
715739
if err != nil {
716740
return err
717741
}
718742

719-
req = &lkproto.DeployAgentRequest{
720-
AgentId: agentId,
721-
}
722-
723743
secrets, err := requireSecrets(ctx, cmd, false, true)
724744
if err != nil {
725745
return err
726746
}
727-
if len(secrets) > 0 {
728-
req.Secrets = secrets
729-
}
730747

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

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

11391156
var confirmOverwrite bool
11401157
if cmd.Bool("overwrite") {
1141-
if err := huh.NewForm(
1142-
huh.NewGroup(
1143-
huh.NewConfirm().
1144-
Title(fmt.Sprintf("This will remove all existing secrets. Are you sure you want to proceed [%s]?", agentID)).
1145-
Value(&confirmOverwrite).
1146-
Inline(false).
1147-
WithTheme(util.Theme),
1148-
),
1149-
).Run(); err != nil {
1150-
return err
1158+
confirmOverwrite = SkipPrompts(cmd)
1159+
if !SkipPrompts(cmd) {
1160+
if err := huh.NewForm(
1161+
huh.NewGroup(
1162+
huh.NewConfirm().
1163+
Title(fmt.Sprintf("This will remove all existing secrets. Are you sure you want to proceed [%s]?", agentID)).
1164+
Value(&confirmOverwrite).
1165+
Inline(false).
1166+
WithTheme(util.Theme),
1167+
),
1168+
).Run(); err != nil {
1169+
return err
1170+
}
11511171
}
11521172
if !confirmOverwrite {
11531173
return nil
@@ -1207,7 +1227,7 @@ func getAgentID(ctx context.Context, cmd *cli.Command, agentDir string, tomlFile
12071227
return agentID, nil
12081228
}
12091229

1210-
func selectAgent(ctx context.Context, _ *cli.Command, excludeEmptyVersion bool) (string, error) {
1230+
func selectAgent(ctx context.Context, cmd *cli.Command, excludeEmptyVersion bool) (string, error) {
12111231
var agents *lkproto.ListAgentsResponse
12121232

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

1258+
if SkipPrompts(cmd) {
1259+
if len(agentNames) != 1 {
1260+
return "", fmt.Errorf("non-interactive mode: set --id when multiple agents exist")
1261+
}
1262+
return agentNames[0].Value, nil
1263+
}
1264+
12381265
var selectedAgent string
12391266
if err := huh.NewSelect[string]().
12401267
Title("Select an agent").
@@ -1289,7 +1316,7 @@ func requireSecrets(_ context.Context, cmd *cli.Command, required, lazy bool) ([
12891316

12901317
shouldReadFromDisk := cmd.IsSet("secrets-file") || !lazy || (required && len(secrets) == 0)
12911318
if shouldReadFromDisk {
1292-
file, env, err := agentfs.DetectEnvFile(cmd.String("secrets-file"))
1319+
file, env, err := agentfs.DetectEnvFile(cmd.String("secrets-file"), SkipPrompts(cmd))
12931320
if err != nil {
12941321
return nil, err
12951322
}
@@ -1366,15 +1393,15 @@ func requireDockerfile(ctx context.Context, cmd *cli.Command, workingDir string,
13661393
"Creating Dockerfile...",
13671394
ctx,
13681395
func(ctx context.Context) error {
1369-
return agentfs.CreateDockerfile(workingDir, projectType, settingsMap)
1396+
return agentfs.CreateDockerfile(workingDir, projectType, settingsMap, SkipPrompts(cmd))
13701397
},
13711398
)
13721399
if err != nil {
13731400
return err
13741401
}
13751402
fmt.Println("Created [" + util.Accented("Dockerfile") + "]")
13761403
} else {
1377-
if err := agentfs.CreateDockerfile(workingDir, projectType, settingsMap); err != nil {
1404+
if err := agentfs.CreateDockerfile(workingDir, projectType, settingsMap, SkipPrompts(cmd)); err != nil {
13781405
return err
13791406
}
13801407
}
@@ -1490,7 +1517,7 @@ func generateAgentDockerfile(ctx context.Context, cmd *cli.Command) error {
14901517
}
14911518

14921519
// Generate contents without writing
1493-
dockerfileContent, dockerignoreContent, err := agentfs.GenerateDockerArtifacts(workingDir, projectType, settingsMap)
1520+
dockerfileContent, dockerignoreContent, err := agentfs.GenerateDockerArtifacts(workingDir, projectType, settingsMap, SkipPrompts(cmd))
14941521
if err != nil {
14951522
return err
14961523
}

0 commit comments

Comments
 (0)