@@ -32,17 +32,14 @@ import (
3232
3333 livekitcli "github.com/livekit/livekit-cli/v2"
3434 "github.com/livekit/livekit-cli/v2/pkg/agentfs"
35+ "github.com/livekit/livekit-cli/v2/pkg/bootstrap"
3536 "github.com/livekit/livekit-cli/v2/pkg/config"
3637 "github.com/livekit/livekit-cli/v2/pkg/util"
3738 lkproto "github.com/livekit/protocol/livekit"
3839 "github.com/livekit/protocol/logger"
3940 lksdk "github.com/livekit/server-sdk-go/v2"
4041)
4142
42- const (
43- cloudAgentsBetaSignupURL = "https://forms.gle/GkGNNTiMt2qyfnu78"
44- )
45-
4643var (
4744 idFlag = func (required bool ) * cli.StringFlag {
4845 return & cli.StringFlag {
9794 Aliases : []string {"a" },
9895 Usage : "Manage LiveKit Cloud Agents" ,
9996 Commands : []* cli.Command {
97+ {
98+ Name : "init" ,
99+ Usage : "Initialize a new LiveKit Cloud agent project" ,
100+ Before : createAgentClient ,
101+ Action : initAgent ,
102+ MutuallyExclusiveFlags : []cli.MutuallyExclusiveFlags {{
103+ Flags : [][]cli.Flag {{
104+ & cli.StringFlag {
105+ Name : "lang" ,
106+ Usage : "`LANGUAGE` of the project, one of \" node\" , \" python\" " ,
107+ Action : func (ctx context.Context , cmd * cli.Command , l string ) error {
108+ if l == "" {
109+ return nil
110+ }
111+ if ! slices .Contains ([]string {"node" , "python" }, l ) {
112+ return fmt .Errorf ("unsupported language: %s" , l )
113+ }
114+ return nil
115+ },
116+ Hidden : true ,
117+ },
118+ & cli.BoolFlag {
119+ Name : "deploy" ,
120+ Usage : "If set, automatically deploys the agent to LiveKit Cloud after initialization." ,
121+ Value : false ,
122+ },
123+ templateFlag ,
124+ templateURLFlag ,
125+ sandboxFlag ,
126+ }},
127+ }},
128+ Flags : []cli.Flag {},
129+ ArgsUsage : "[AGENT-NAME]" ,
130+ DisableSliceFlagSeparator : true ,
131+ },
100132 {
101133 Name : "create" ,
102134 Usage : "Create a new LiveKit Cloud Agent" ,
@@ -330,6 +362,82 @@ func createAgentClient(ctx context.Context, cmd *cli.Command) (context.Context,
330362 return ctx , nil
331363}
332364
365+ func initAgent (ctx context.Context , cmd * cli.Command ) error {
366+ if ! (cmd .IsSet ("lang" ) || cmd .IsSet ("template" ) || cmd .IsSet ("template-url" )) {
367+ var lang string
368+ // Prompt for language
369+ if err := huh .NewSelect [string ]().
370+ Title ("Select the language for your agent project" ).
371+ Options (
372+ huh .NewOption ("Python" , "python" ),
373+ huh .NewOption ("Node.js" , "node" ),
374+ ).
375+ Value (& lang ).
376+ WithTheme (util .Theme ).
377+ Run (); err != nil {
378+ return err
379+ }
380+
381+ switch lang {
382+ case "node" :
383+ return fmt .Errorf ("this language is not yet supported" )
384+ case "python" :
385+ templateURL = "https://github.com/livekit-examples/agent-starter-python-gateway"
386+ break
387+ default :
388+ return fmt .Errorf ("unsupported language: %s" , lang )
389+ }
390+ }
391+
392+ logger .Debugw ("Initializing agent project" , "working-dir" , workingDir )
393+
394+ // Create sandbox if one is not provided
395+ if sandboxID == "" {
396+ fmt .Println ("Creating Sandbox app..." )
397+ token , err := requireToken (ctx , cmd )
398+ if err != nil {
399+ return err
400+ }
401+
402+ appName = cmd .Args ().First ()
403+ if appName == "" {
404+ appName = project .Name
405+ }
406+ // We set agent name in env for use in template tasks
407+ os .Setenv ("LIVEKIT_AGENT_NAME" , appName )
408+
409+ // NOTE: for now, we assume that agent-starter-react is the desired template.
410+ sandboxID , err = bootstrap .CreateSandbox (
411+ ctx ,
412+ appName ,
413+ "https://github.com/livekit-examples/agent-starter-react" ,
414+ token ,
415+ serverURL ,
416+ )
417+ if err != nil {
418+ return fmt .Errorf ("failed to create sandbox: %w" , err )
419+ }
420+ }
421+
422+ // Run template bootstrap
423+ shouldDeploy := cmd .Bool ("deploy" )
424+ if shouldDeploy {
425+ cmd .Set ("install" , "true" )
426+ }
427+ if err := setupTemplate (ctx , cmd ); err != nil {
428+ return err
429+ }
430+ // Deploy if requested
431+ if shouldDeploy {
432+ fmt .Println ("Deploying agent..." )
433+ if err := createAgent (ctx , cmd ); err != nil {
434+ return fmt .Errorf ("failed to deploy agent: %w" , err )
435+ }
436+ }
437+
438+ return nil
439+ }
440+
333441func createAgent (ctx context.Context , cmd * cli.Command ) error {
334442 subdomainMatches := subdomainPattern .FindStringSubmatch (project .URL )
335443 if len (subdomainMatches ) < 2 {
@@ -424,11 +532,9 @@ func createAgent(ctx context.Context, cmd *cli.Command) error {
424532 resp , err := agentsClient .CreateAgent (ctx , req )
425533 if err != nil {
426534 if twerr , ok := err .(twirp.Error ); ok {
427- if twerr .Code () == twirp .PermissionDenied {
428- return fmt .Errorf ("agent hosting is disabled for this project -- join the beta program here [%s]" , cloudAgentsBetaSignupURL )
429- }
535+ return fmt .Errorf ("unable to create agent: %s" , twerr .Msg ())
430536 }
431- return err
537+ return fmt . Errorf ( "unable to create agent: %w" , err )
432538 }
433539
434540 lkConfig .Agent .ID = resp .AgentId
@@ -512,11 +618,9 @@ func createAgentConfig(ctx context.Context, cmd *cli.Command) error {
512618 })
513619 if err != nil {
514620 if twerr , ok := err .(twirp.Error ); ok {
515- if twerr .Code () == twirp .PermissionDenied {
516- return fmt .Errorf ("agent hosting is disabled for this project -- join the beta program here [%s]" , cloudAgentsBetaSignupURL )
517- }
621+ return fmt .Errorf ("unable to list agents: %s" , twerr .Msg ())
518622 }
519- return err
623+ return fmt . Errorf ( "unable to list agents: %w" , err )
520624 }
521625 if len (response .Agents ) == 0 {
522626 return fmt .Errorf ("agent not found" )
@@ -581,11 +685,9 @@ func deployAgent(ctx context.Context, cmd *cli.Command) error {
581685 resp , err := agentsClient .DeployAgent (ctx , req )
582686 if err != nil {
583687 if twerr , ok := err .(twirp.Error ); ok {
584- if twerr .Code () == twirp .PermissionDenied {
585- return fmt .Errorf ("agent hosting is disabled for this project -- join the beta program here [%s]" , cloudAgentsBetaSignupURL )
586- }
688+ return fmt .Errorf ("unable to deploy agent: %s" , twerr .Msg ())
587689 }
588- return err
690+ return fmt . Errorf ( "unable to deploy agent: %w" , err )
589691 }
590692
591693 if ! resp .Success {
@@ -619,11 +721,9 @@ func getAgentStatus(ctx context.Context, cmd *cli.Command) error {
619721 })
620722 if err != nil {
621723 if twerr , ok := err .(twirp.Error ); ok {
622- if twerr .Code () == twirp .PermissionDenied {
623- return fmt .Errorf ("agent hosting is disabled for this project -- join the beta program here [%s]" , cloudAgentsBetaSignupURL )
624- }
724+ return fmt .Errorf ("unable to list agents: %s" , twerr .Msg ())
625725 }
626- return err
726+ return fmt . Errorf ( "unable to list agents: %w" , err )
627727 }
628728
629729 if len (res .Agents ) == 0 {
@@ -721,11 +821,9 @@ func updateAgent(ctx context.Context, cmd *cli.Command) error {
721821 })
722822 if err != nil {
723823 if twerr , ok := err .(twirp.Error ); ok {
724- if twerr .Code () == twirp .PermissionDenied {
725- return fmt .Errorf ("agent hosting is disabled for this project -- join the beta program here [%s]" , cloudAgentsBetaSignupURL )
726- }
824+ return fmt .Errorf ("unable to update agent: %s" , twerr .Msg ())
727825 }
728- return err
826+ return fmt . Errorf ( "unable to update agent: %w" , err )
729827 }
730828
731829 if resp .Success {
@@ -755,11 +853,9 @@ func rollbackAgent(ctx context.Context, cmd *cli.Command) error {
755853
756854 if err != nil {
757855 if twerr , ok := err .(twirp.Error ); ok {
758- if twerr .Code () == twirp .PermissionDenied {
759- return fmt .Errorf ("agent hosting is disabled for this project -- join the beta program here [%s]" , cloudAgentsBetaSignupURL )
760- }
856+ return fmt .Errorf ("unable to rollback agent: %s" , twerr .Msg ())
761857 }
762- return err
858+ return fmt . Errorf ( "unable to rollback agent: %w" , err )
763859 }
764860
765861 if ! resp .Success {
@@ -818,11 +914,9 @@ func deleteAgent(ctx context.Context, cmd *cli.Command) error {
818914
819915 if err != nil {
820916 if twerr , ok := err .(twirp.Error ); ok {
821- if twerr .Code () == twirp .PermissionDenied {
822- return fmt .Errorf ("agent hosting is disabled for this project -- join the beta program here [%s]" , cloudAgentsBetaSignupURL )
823- }
917+ return fmt .Errorf ("unable to delete agent: %s" , twerr .Msg ())
824918 }
825- return err
919+ return fmt . Errorf ( "unable to delete agent: %w" , err )
826920 }
827921
828922 if ! res .Success {
@@ -846,11 +940,9 @@ func listAgentVersions(ctx context.Context, cmd *cli.Command) error {
846940 versions , err := agentsClient .ListAgentVersions (ctx , req )
847941 if err != nil {
848942 if twerr , ok := err .(twirp.Error ); ok {
849- if twerr .Code () == twirp .PermissionDenied {
850- return fmt .Errorf ("agent hosting is disabled for this project -- join the beta program here [%s]" , cloudAgentsBetaSignupURL )
851- }
943+ return fmt .Errorf ("unable to list agent versions: %s" , twerr .Msg ())
852944 }
853- return err
945+ return fmt . Errorf ( "unable to list agent versions: %w" , err )
854946 }
855947
856948 table := util .CreateTable ().
@@ -885,23 +977,19 @@ func listAgents(ctx context.Context, cmd *cli.Command) error {
885977 })
886978 if err != nil {
887979 if twerr , ok := err .(twirp.Error ); ok {
888- if twerr .Code () == twirp .PermissionDenied {
889- return fmt .Errorf ("agent hosting is disabled for this project -- join the beta program here [%s]" , cloudAgentsBetaSignupURL )
890- }
980+ return fmt .Errorf ("unable to list agents: %s" , twerr .Msg ())
891981 }
892- return err
982+ return fmt . Errorf ( "unable to list agents: %w" , err )
893983 }
894984 items = append (items , res .Agents ... )
895985 }
896986 } else {
897987 agents , err := agentsClient .ListAgents (ctx , & lkproto.ListAgentsRequest {})
898988 if err != nil {
899989 if twerr , ok := err .(twirp.Error ); ok {
900- if twerr .Code () == twirp .PermissionDenied {
901- return fmt .Errorf ("agent hosting is disabled for this project -- join the beta program here [%s]" , cloudAgentsBetaSignupURL )
902- }
990+ return fmt .Errorf ("unable to list agents: %s" , twerr .Msg ())
903991 }
904- return err
992+ return fmt . Errorf ( "unable to list agents: %w" , err )
905993 }
906994 items = agents .Agents
907995 }
@@ -950,11 +1038,9 @@ func listAgentSecrets(ctx context.Context, cmd *cli.Command) error {
9501038 secrets , err := agentsClient .ListAgentSecrets (ctx , req )
9511039 if err != nil {
9521040 if twerr , ok := err .(twirp.Error ); ok {
953- if twerr .Code () == twirp .PermissionDenied {
954- return fmt .Errorf ("agent hosting is disabled for this project -- join the beta program here [%s]" , cloudAgentsBetaSignupURL )
955- }
1041+ return fmt .Errorf ("unable to list agent secrets: %s" , twerr .Msg ())
9561042 }
957- return err
1043+ return fmt . Errorf ( "unable to list agent secrets: %w" , err )
9581044 }
9591045
9601046 table := util .CreateTable ().
@@ -1011,11 +1097,9 @@ func updateAgentSecrets(ctx context.Context, cmd *cli.Command) error {
10111097 resp , err := agentsClient .UpdateAgentSecrets (ctx , req )
10121098 if err != nil {
10131099 if twerr , ok := err .(twirp.Error ); ok {
1014- if twerr .Code () == twirp .PermissionDenied {
1015- return fmt .Errorf ("agent hosting is disabled for this project -- join the beta program here [%s]" , cloudAgentsBetaSignupURL )
1016- }
1100+ return fmt .Errorf ("unable to update agent secrets: %s" , twerr .Msg ())
10171101 }
1018- return err
1102+ return fmt . Errorf ( "unable to update agent secrets: %w" , err )
10191103 }
10201104
10211105 if resp .Success {
@@ -1067,11 +1151,9 @@ func selectAgent(ctx context.Context, _ *cli.Command, excludeEmptyVersion bool)
10671151 })
10681152 if err != nil {
10691153 if twerr , ok := err .(twirp.Error ); ok {
1070- if twerr .Code () == twirp .PermissionDenied {
1071- return "" , fmt .Errorf ("agent hosting is disabled for this project -- join the beta program here [%s]" , cloudAgentsBetaSignupURL )
1072- }
1154+ return "" , fmt .Errorf ("unable to list agents: %s" , twerr .Msg ())
10731155 }
1074- return "" , err
1156+ return "" , fmt . Errorf ( "unable to list agents: %w" , err )
10751157 }
10761158
10771159 if len (agents .Agents ) == 0 {
@@ -1213,11 +1295,9 @@ func getClientSettings(ctx context.Context, silent bool) (map[string]string, err
12131295
12141296 if err != nil {
12151297 if twerr , ok := err .(twirp.Error ); ok {
1216- if twerr .Code () == twirp .PermissionDenied {
1217- return nil , fmt .Errorf ("agent hosting is disabled for this project -- join the beta program here [%s]" , cloudAgentsBetaSignupURL )
1218- }
1298+ return nil , fmt .Errorf ("unable to get client settings: %s" , twerr .Msg ())
12191299 }
1220- return nil , err
1300+ return nil , fmt . Errorf ( "unable to get client settings: %w" , err )
12211301 }
12221302
12231303 if clientSettingsResponse == nil {
0 commit comments