@@ -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,89 @@ 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+ if ! (cmd .IsSet ("lang" ) || cmd .IsSet ("template" ) || cmd .IsSet ("template-url" )) {
373+ var lang string
374+ // Prompt for language
375+ if err := huh .NewSelect [string ]().
376+ Title ("Select the language for your agent project" ).
377+ Options (
378+ huh .NewOption ("Python" , "python" ),
379+ huh .NewOption ("Node.js" , "node" ),
380+ ).
381+ Value (& lang ).
382+ WithTheme (util .Theme ).
383+ Run (); err != nil {
384+ return err
385+ }
386+
387+ switch lang {
388+ case "node" :
389+ return fmt .Errorf ("this language is not yet supported" )
390+ case "python" :
391+ templateURL = "https://github.com/livekit-examples/agent-starter-python-gateway"
392+ default :
393+ return fmt .Errorf ("unsupported language: %s" , lang )
394+ }
395+ }
396+
397+ logger .Debugw ("Initializing agent project" , "working-dir" , workingDir )
398+
399+ // Create sandbox
400+ if ! cmd .Bool ("no-sandbox" ) || sandboxID == "" {
401+ if err := util .Await ("Creating sandbox app..." , ctx , func (ctx context.Context ) error {
402+ token , err := requireToken (ctx , cmd )
403+ if err != nil {
404+ return err
405+ }
406+
407+ appName = cmd .Args ().First ()
408+ if appName == "" {
409+ appName = project .Name
410+ }
411+ // We set agent name in env for use in template tasks
412+ os .Setenv ("LIVEKIT_AGENT_NAME" , appName )
413+
414+ // TODO: (@rektdeckard) figure out why AccessKeyProvider does not immediately
415+ // have access to newly-created API keys, then remove this sleep
416+ time .Sleep (4 * time .Second )
417+ sandboxID , err = bootstrap .CreateSandbox (
418+ ctx ,
419+ appName ,
420+ // NOTE: we may want to support embed sandbox in the future
421+ "https://github.com/livekit-examples/agent-starter-react" ,
422+ token ,
423+ serverURL ,
424+ )
425+ return err
426+ }); err != nil {
427+ return fmt .Errorf ("failed to create sandbox: %w" , err )
428+ } else {
429+ fmt .Println ("Creating sandbox app..." )
430+ fmt .Printf ("Created sandbox app [%s]\n " , util .Accented (sandboxID ))
431+ }
432+
433+ }
434+
435+ // Run template bootstrap
436+ shouldDeploy := cmd .Bool ("deploy" )
437+ if shouldDeploy {
438+ cmd .Set ("install" , "true" )
439+ }
440+ if err := setupTemplate (ctx , cmd ); err != nil {
441+ return err
442+ }
443+ // Deploy if requested
444+ if shouldDeploy {
445+ fmt .Println ("Deploying agent..." )
446+ if err := createAgent (ctx , cmd ); err != nil {
447+ return fmt .Errorf ("failed to deploy agent: %w" , err )
448+ }
449+ }
450+
451+ return nil
452+ }
453+
333454func createAgent (ctx context.Context , cmd * cli.Command ) error {
334455 subdomainMatches := subdomainPattern .FindStringSubmatch (project .URL )
335456 if len (subdomainMatches ) < 2 {
@@ -424,11 +545,9 @@ func createAgent(ctx context.Context, cmd *cli.Command) error {
424545 resp , err := agentsClient .CreateAgent (ctx , req )
425546 if err != nil {
426547 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- }
548+ return fmt .Errorf ("unable to create agent: %s" , twerr .Msg ())
430549 }
431- return err
550+ return fmt . Errorf ( "unable to create agent: %w" , err )
432551 }
433552
434553 lkConfig .Agent .ID = resp .AgentId
@@ -512,11 +631,9 @@ func createAgentConfig(ctx context.Context, cmd *cli.Command) error {
512631 })
513632 if err != nil {
514633 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- }
634+ return fmt .Errorf ("unable to list agents: %s" , twerr .Msg ())
518635 }
519- return err
636+ return fmt . Errorf ( "unable to list agents: %w" , err )
520637 }
521638 if len (response .Agents ) == 0 {
522639 return fmt .Errorf ("agent not found" )
@@ -581,11 +698,9 @@ func deployAgent(ctx context.Context, cmd *cli.Command) error {
581698 resp , err := agentsClient .DeployAgent (ctx , req )
582699 if err != nil {
583700 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- }
701+ return fmt .Errorf ("unable to deploy agent: %s" , twerr .Msg ())
587702 }
588- return err
703+ return fmt . Errorf ( "unable to deploy agent: %w" , err )
589704 }
590705
591706 if ! resp .Success {
@@ -619,11 +734,9 @@ func getAgentStatus(ctx context.Context, cmd *cli.Command) error {
619734 })
620735 if err != nil {
621736 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- }
737+ return fmt .Errorf ("unable to list agents: %s" , twerr .Msg ())
625738 }
626- return err
739+ return fmt . Errorf ( "unable to list agents: %w" , err )
627740 }
628741
629742 if len (res .Agents ) == 0 {
@@ -721,11 +834,9 @@ func updateAgent(ctx context.Context, cmd *cli.Command) error {
721834 })
722835 if err != nil {
723836 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- }
837+ return fmt .Errorf ("unable to update agent: %s" , twerr .Msg ())
727838 }
728- return err
839+ return fmt . Errorf ( "unable to update agent: %w" , err )
729840 }
730841
731842 if resp .Success {
@@ -755,11 +866,9 @@ func rollbackAgent(ctx context.Context, cmd *cli.Command) error {
755866
756867 if err != nil {
757868 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- }
869+ return fmt .Errorf ("unable to rollback agent: %s" , twerr .Msg ())
761870 }
762- return err
871+ return fmt . Errorf ( "unable to rollback agent: %w" , err )
763872 }
764873
765874 if ! resp .Success {
@@ -818,11 +927,9 @@ func deleteAgent(ctx context.Context, cmd *cli.Command) error {
818927
819928 if err != nil {
820929 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- }
930+ return fmt .Errorf ("unable to delete agent: %s" , twerr .Msg ())
824931 }
825- return err
932+ return fmt . Errorf ( "unable to delete agent: %w" , err )
826933 }
827934
828935 if ! res .Success {
@@ -846,11 +953,9 @@ func listAgentVersions(ctx context.Context, cmd *cli.Command) error {
846953 versions , err := agentsClient .ListAgentVersions (ctx , req )
847954 if err != nil {
848955 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- }
956+ return fmt .Errorf ("unable to list agent versions: %s" , twerr .Msg ())
852957 }
853- return err
958+ return fmt . Errorf ( "unable to list agent versions: %w" , err )
854959 }
855960
856961 table := util .CreateTable ().
@@ -885,23 +990,19 @@ func listAgents(ctx context.Context, cmd *cli.Command) error {
885990 })
886991 if err != nil {
887992 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- }
993+ return fmt .Errorf ("unable to list agents: %s" , twerr .Msg ())
891994 }
892- return err
995+ return fmt . Errorf ( "unable to list agents: %w" , err )
893996 }
894997 items = append (items , res .Agents ... )
895998 }
896999 } else {
8971000 agents , err := agentsClient .ListAgents (ctx , & lkproto.ListAgentsRequest {})
8981001 if err != nil {
8991002 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- }
1003+ return fmt .Errorf ("unable to list agents: %s" , twerr .Msg ())
9031004 }
904- return err
1005+ return fmt . Errorf ( "unable to list agents: %w" , err )
9051006 }
9061007 items = agents .Agents
9071008 }
@@ -950,11 +1051,9 @@ func listAgentSecrets(ctx context.Context, cmd *cli.Command) error {
9501051 secrets , err := agentsClient .ListAgentSecrets (ctx , req )
9511052 if err != nil {
9521053 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- }
1054+ return fmt .Errorf ("unable to list agent secrets: %s" , twerr .Msg ())
9561055 }
957- return err
1056+ return fmt . Errorf ( "unable to list agent secrets: %w" , err )
9581057 }
9591058
9601059 table := util .CreateTable ().
@@ -1011,11 +1110,9 @@ func updateAgentSecrets(ctx context.Context, cmd *cli.Command) error {
10111110 resp , err := agentsClient .UpdateAgentSecrets (ctx , req )
10121111 if err != nil {
10131112 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- }
1113+ return fmt .Errorf ("unable to update agent secrets: %s" , twerr .Msg ())
10171114 }
1018- return err
1115+ return fmt . Errorf ( "unable to update agent secrets: %w" , err )
10191116 }
10201117
10211118 if resp .Success {
@@ -1067,11 +1164,9 @@ func selectAgent(ctx context.Context, _ *cli.Command, excludeEmptyVersion bool)
10671164 })
10681165 if err != nil {
10691166 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- }
1167+ return "" , fmt .Errorf ("unable to list agents: %s" , twerr .Msg ())
10731168 }
1074- return "" , err
1169+ return "" , fmt . Errorf ( "unable to list agents: %w" , err )
10751170 }
10761171
10771172 if len (agents .Agents ) == 0 {
@@ -1213,11 +1308,9 @@ func getClientSettings(ctx context.Context, silent bool) (map[string]string, err
12131308
12141309 if err != nil {
12151310 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- }
1311+ return nil , fmt .Errorf ("unable to get client settings: %s" , twerr .Msg ())
12191312 }
1220- return nil , err
1313+ return nil , fmt . Errorf ( "unable to get client settings: %w" , err )
12211314 }
12221315
12231316 if clientSettingsResponse == nil {
0 commit comments