@@ -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+ }, {
126+ sandboxFlag ,
127+ & cli.BoolFlag {
128+ Name : "no-sandbox" ,
129+ Usage : "If set, will not create a sandbox for the project. " ,
130+ Value : false ,
131+ },
132+ }},
133+ }},
134+ Flags : []cli.Flag {},
135+ ArgsUsage : "[AGENT-NAME]" ,
136+ DisableSliceFlagSeparator : true ,
137+ },
100138 {
101139 Name : "create" ,
102140 Usage : "Create a new LiveKit Cloud Agent" ,
@@ -330,6 +368,91 @@ func createAgentClient(ctx context.Context, cmd *cli.Command) (context.Context,
330368 return ctx , nil
331369}
332370
371+ func initAgent (ctx context.Context , cmd * cli.Command ) error {
372+ // TODO: (@rektdeckard) move compatibility flag into template index,
373+ // then show template picker containing only compatible templates
374+ if ! (cmd .IsSet ("lang" ) || cmd .IsSet ("template" ) || cmd .IsSet ("template-url" )) {
375+ var lang string
376+ // Prompt for language
377+ if err := huh .NewSelect [string ]().
378+ Title ("Select the language for your agent project" ).
379+ Options (
380+ huh .NewOption ("Python" , "python" ),
381+ huh .NewOption ("Node.js" , "node" ),
382+ ).
383+ Value (& lang ).
384+ WithTheme (util .Theme ).
385+ Run (); err != nil {
386+ return err
387+ }
388+
389+ switch lang {
390+ case "node" :
391+ templateURL = "https://github.com/livekit-examples/agent-starter-node"
392+ case "python" :
393+ templateURL = "https://github.com/livekit-examples/agent-starter-python"
394+ default :
395+ return fmt .Errorf ("unsupported language: %s" , lang )
396+ }
397+ }
398+
399+ logger .Debugw ("Initializing agent project" , "working-dir" , workingDir )
400+
401+ // Create sandbox
402+ if ! cmd .Bool ("no-sandbox" ) || sandboxID == "" {
403+ if err := util .Await ("Creating sandbox app..." , ctx , func (ctx context.Context ) error {
404+ token , err := requireToken (ctx , cmd )
405+ if err != nil {
406+ return err
407+ }
408+
409+ appName = cmd .Args ().First ()
410+ if appName == "" {
411+ appName = project .Name
412+ }
413+ // We set agent name in env for use in template tasks
414+ os .Setenv ("LIVEKIT_AGENT_NAME" , appName )
415+
416+ // TODO: (@rektdeckard) figure out why AccessKeyProvider does not immediately
417+ // have access to newly-created API keys, then remove this sleep
418+ time .Sleep (4 * time .Second )
419+ sandboxID , err = bootstrap .CreateSandbox (
420+ ctx ,
421+ appName ,
422+ // NOTE: we may want to support embed sandbox in the future
423+ "https://github.com/livekit-examples/agent-starter-react" ,
424+ token ,
425+ serverURL ,
426+ )
427+ return err
428+ }); err != nil {
429+ return fmt .Errorf ("failed to create sandbox: %w" , err )
430+ } else {
431+ fmt .Println ("Creating sandbox app..." )
432+ fmt .Printf ("Created sandbox app [%s]\n " , util .Accented (sandboxID ))
433+ }
434+
435+ }
436+
437+ // Run template bootstrap
438+ shouldDeploy := cmd .Bool ("deploy" )
439+ if shouldDeploy {
440+ cmd .Set ("install" , "true" )
441+ }
442+ if err := setupTemplate (ctx , cmd ); err != nil {
443+ return err
444+ }
445+ // Deploy if requested
446+ if shouldDeploy {
447+ fmt .Println ("Deploying agent..." )
448+ if err := createAgent (ctx , cmd ); err != nil {
449+ return fmt .Errorf ("failed to deploy agent: %w" , err )
450+ }
451+ }
452+
453+ return nil
454+ }
455+
333456func createAgent (ctx context.Context , cmd * cli.Command ) error {
334457 subdomainMatches := subdomainPattern .FindStringSubmatch (project .URL )
335458 if len (subdomainMatches ) < 2 {
@@ -424,11 +547,9 @@ func createAgent(ctx context.Context, cmd *cli.Command) error {
424547 resp , err := agentsClient .CreateAgent (ctx , req )
425548 if err != nil {
426549 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- }
550+ return fmt .Errorf ("unable to create agent: %s" , twerr .Msg ())
430551 }
431- return err
552+ return fmt . Errorf ( "unable to create agent: %w" , err )
432553 }
433554
434555 lkConfig .Agent .ID = resp .AgentId
@@ -512,11 +633,9 @@ func createAgentConfig(ctx context.Context, cmd *cli.Command) error {
512633 })
513634 if err != nil {
514635 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- }
636+ return fmt .Errorf ("unable to list agents: %s" , twerr .Msg ())
518637 }
519- return err
638+ return fmt . Errorf ( "unable to list agents: %w" , err )
520639 }
521640 if len (response .Agents ) == 0 {
522641 return fmt .Errorf ("agent not found" )
@@ -581,11 +700,9 @@ func deployAgent(ctx context.Context, cmd *cli.Command) error {
581700 resp , err := agentsClient .DeployAgent (ctx , req )
582701 if err != nil {
583702 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- }
703+ return fmt .Errorf ("unable to deploy agent: %s" , twerr .Msg ())
587704 }
588- return err
705+ return fmt . Errorf ( "unable to deploy agent: %w" , err )
589706 }
590707
591708 if ! resp .Success {
@@ -619,11 +736,9 @@ func getAgentStatus(ctx context.Context, cmd *cli.Command) error {
619736 })
620737 if err != nil {
621738 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- }
739+ return fmt .Errorf ("unable to list agents: %s" , twerr .Msg ())
625740 }
626- return err
741+ return fmt . Errorf ( "unable to list agents: %w" , err )
627742 }
628743
629744 if len (res .Agents ) == 0 {
@@ -721,11 +836,9 @@ func updateAgent(ctx context.Context, cmd *cli.Command) error {
721836 })
722837 if err != nil {
723838 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- }
839+ return fmt .Errorf ("unable to update agent: %s" , twerr .Msg ())
727840 }
728- return err
841+ return fmt . Errorf ( "unable to update agent: %w" , err )
729842 }
730843
731844 if resp .Success {
@@ -755,11 +868,9 @@ func rollbackAgent(ctx context.Context, cmd *cli.Command) error {
755868
756869 if err != nil {
757870 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- }
871+ return fmt .Errorf ("unable to rollback agent: %s" , twerr .Msg ())
761872 }
762- return err
873+ return fmt . Errorf ( "unable to rollback agent: %w" , err )
763874 }
764875
765876 if ! resp .Success {
@@ -818,11 +929,9 @@ func deleteAgent(ctx context.Context, cmd *cli.Command) error {
818929
819930 if err != nil {
820931 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- }
932+ return fmt .Errorf ("unable to delete agent: %s" , twerr .Msg ())
824933 }
825- return err
934+ return fmt . Errorf ( "unable to delete agent: %w" , err )
826935 }
827936
828937 if ! res .Success {
@@ -846,11 +955,9 @@ func listAgentVersions(ctx context.Context, cmd *cli.Command) error {
846955 versions , err := agentsClient .ListAgentVersions (ctx , req )
847956 if err != nil {
848957 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- }
958+ return fmt .Errorf ("unable to list agent versions: %s" , twerr .Msg ())
852959 }
853- return err
960+ return fmt . Errorf ( "unable to list agent versions: %w" , err )
854961 }
855962
856963 table := util .CreateTable ().
@@ -885,23 +992,19 @@ func listAgents(ctx context.Context, cmd *cli.Command) error {
885992 })
886993 if err != nil {
887994 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- }
995+ return fmt .Errorf ("unable to list agents: %s" , twerr .Msg ())
891996 }
892- return err
997+ return fmt . Errorf ( "unable to list agents: %w" , err )
893998 }
894999 items = append (items , res .Agents ... )
8951000 }
8961001 } else {
8971002 agents , err := agentsClient .ListAgents (ctx , & lkproto.ListAgentsRequest {})
8981003 if err != nil {
8991004 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- }
1005+ return fmt .Errorf ("unable to list agents: %s" , twerr .Msg ())
9031006 }
904- return err
1007+ return fmt . Errorf ( "unable to list agents: %w" , err )
9051008 }
9061009 items = agents .Agents
9071010 }
@@ -950,11 +1053,9 @@ func listAgentSecrets(ctx context.Context, cmd *cli.Command) error {
9501053 secrets , err := agentsClient .ListAgentSecrets (ctx , req )
9511054 if err != nil {
9521055 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- }
1056+ return fmt .Errorf ("unable to list agent secrets: %s" , twerr .Msg ())
9561057 }
957- return err
1058+ return fmt . Errorf ( "unable to list agent secrets: %w" , err )
9581059 }
9591060
9601061 table := util .CreateTable ().
@@ -1011,11 +1112,9 @@ func updateAgentSecrets(ctx context.Context, cmd *cli.Command) error {
10111112 resp , err := agentsClient .UpdateAgentSecrets (ctx , req )
10121113 if err != nil {
10131114 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- }
1115+ return fmt .Errorf ("unable to update agent secrets: %s" , twerr .Msg ())
10171116 }
1018- return err
1117+ return fmt . Errorf ( "unable to update agent secrets: %w" , err )
10191118 }
10201119
10211120 if resp .Success {
@@ -1067,11 +1166,9 @@ func selectAgent(ctx context.Context, _ *cli.Command, excludeEmptyVersion bool)
10671166 })
10681167 if err != nil {
10691168 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- }
1169+ return "" , fmt .Errorf ("unable to list agents: %s" , twerr .Msg ())
10731170 }
1074- return "" , err
1171+ return "" , fmt . Errorf ( "unable to list agents: %w" , err )
10751172 }
10761173
10771174 if len (agents .Agents ) == 0 {
@@ -1213,11 +1310,9 @@ func getClientSettings(ctx context.Context, silent bool) (map[string]string, err
12131310
12141311 if err != nil {
12151312 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- }
1313+ return nil , fmt .Errorf ("unable to get client settings: %s" , twerr .Msg ())
12191314 }
1220- return nil , err
1315+ return nil , fmt . Errorf ( "unable to get client settings: %w" , err )
12211316 }
12221317
12231318 if clientSettingsResponse == nil {
0 commit comments