From 0e8cae377c944a8f5c240462f50158b03627ca83 Mon Sep 17 00:00:00 2001 From: YS Liu Date: Thu, 26 Feb 2026 14:44:43 +0800 Subject: [PATCH] Add config command with model list and prompt subcommands Made-with: Cursor --- cmd/picoclaw/internal/configcmd/command.go | 38 +++++ .../internal/configcmd/model_list_add.go | 129 ++++++++++++++++ .../internal/configcmd/model_list_get.go | 99 +++++++++++++ .../internal/configcmd/model_list_keys.go | 50 +++++++ .../internal/configcmd/model_list_list.go | 78 ++++++++++ .../internal/configcmd/model_list_remove.go | 72 +++++++++ .../internal/configcmd/model_list_set.go | 100 +++++++++++++ .../internal/configcmd/model_list_update.go | 138 ++++++++++++++++++ cmd/picoclaw/internal/configcmd/prompt.go | 30 ++++ cmd/picoclaw/main.go | 2 + 10 files changed, 736 insertions(+) create mode 100644 cmd/picoclaw/internal/configcmd/command.go create mode 100644 cmd/picoclaw/internal/configcmd/model_list_add.go create mode 100644 cmd/picoclaw/internal/configcmd/model_list_get.go create mode 100644 cmd/picoclaw/internal/configcmd/model_list_keys.go create mode 100644 cmd/picoclaw/internal/configcmd/model_list_list.go create mode 100644 cmd/picoclaw/internal/configcmd/model_list_remove.go create mode 100644 cmd/picoclaw/internal/configcmd/model_list_set.go create mode 100644 cmd/picoclaw/internal/configcmd/model_list_update.go create mode 100644 cmd/picoclaw/internal/configcmd/prompt.go diff --git a/cmd/picoclaw/internal/configcmd/command.go b/cmd/picoclaw/internal/configcmd/command.go new file mode 100644 index 000000000..c2663ea4e --- /dev/null +++ b/cmd/picoclaw/internal/configcmd/command.go @@ -0,0 +1,38 @@ +package configcmd + +import ( + "github.com/spf13/cobra" +) + +func NewConfigCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "config", + Short: "Manage configuration (model_list)", + RunE: func(cmd *cobra.Command, _ []string) error { + return cmd.Help() + }, + } + + cmd.AddCommand(newModelListCommand()) + return cmd +} + +func newModelListCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "model_list", + Short: "Manage model_list (list, get, set, add, remove, update)", + RunE: func(cmd *cobra.Command, _ []string) error { + return cmd.Help() + }, + } + + cmd.AddCommand( + newModelListListCommand(), + newModelListGetCommand(), + newModelListSetCommand(), + newModelListAddCommand(), + newModelListRemoveCommand(), + newModelListUpdateCommand(), + ) + return cmd +} diff --git a/cmd/picoclaw/internal/configcmd/model_list_add.go b/cmd/picoclaw/internal/configcmd/model_list_add.go new file mode 100644 index 000000000..33627eca2 --- /dev/null +++ b/cmd/picoclaw/internal/configcmd/model_list_add.go @@ -0,0 +1,129 @@ +package configcmd + +import ( + "fmt" + "os" + "strings" + + "github.com/spf13/cobra" + + "github.com/sipeed/picoclaw/cmd/picoclaw/internal" + "github.com/sipeed/picoclaw/pkg/config" +) + +func newModelListAddCommand() *cobra.Command { + var ( + modelName string + model string + apiBase string + apiKey string + proxy string + authMethod string + maxTokensFld string + tokenURL string + clientID string + clientSecret string + ) + + cmd := &cobra.Command{ + Use: "add [model_name]", + Short: "Add a model to model_list", + Args: cobra.MaximumNArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + if len(args) > 0 && modelName == "" { + modelName = args[0] + } + return runModelListAdd(modelName, model, apiBase, apiKey, proxy, authMethod, maxTokensFld, tokenURL, clientID, clientSecret) + }, + } + + cmd.Flags().StringVar(&modelName, "model-name", "", "User-facing model name (e.g. qwen-turbo)") + cmd.Flags().StringVar(&model, "model", "", "Protocol/model (e.g. litellm/qwen-turbo, openai/gpt-4o)") + cmd.Flags().StringVar(&apiBase, "api-base", "", "API base URL") + cmd.Flags().StringVar(&apiKey, "api-key", "", "API key") + cmd.Flags().StringVar(&proxy, "proxy", "", "HTTP proxy URL") + cmd.Flags().StringVar(&authMethod, "auth-method", "", "Auth method: oauth, token") + cmd.Flags().StringVar(&maxTokensFld, "max-tokens-field", "", "Field name for max tokens") + cmd.Flags().StringVar(&tokenURL, "token-url", "", "Keycloak token URL (for litellm)") + cmd.Flags().StringVar(&clientID, "client-id", "", "Client ID (for litellm)") + cmd.Flags().StringVar(&clientSecret, "client-secret", "", "Client secret (for litellm)") + + return cmd +} + +func runModelListAdd(modelName, model, apiBase, apiKey, proxy, authMethod, maxTokensFld, tokenURL, clientID, clientSecret string) error { + cfg, err := internal.LoadConfig() + if err != nil { + return fmt.Errorf("loading config: %w", err) + } + + configPath := internal.GetConfigPath() + if _, err := os.Stat(configPath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("config not found; run: picoclaw onboard") + } + return err + } + + isLiteLLM := strings.HasPrefix(strings.ToLower(model), "litellm/") + + // Interactive prompt for missing required fields when TTY + if IsTTY() { + if modelName == "" { + modelName, _ = Prompt("Model name (e.g. qwen-turbo): ") + } + if model == "" { + model, _ = Prompt("Model (e.g. litellm/qwen-turbo or openai/gpt-4o): ") + model = strings.TrimSpace(model) + isLiteLLM = strings.HasPrefix(strings.ToLower(model), "litellm/") + } + if apiBase == "" { + apiBase, _ = Prompt("API base URL: ") + } + if isLiteLLM { + if tokenURL == "" { + tokenURL, _ = Prompt("Token URL (Keycloak): ") + } + if clientID == "" { + clientID, _ = Prompt("Client ID: ") + } + if clientSecret == "" { + clientSecret, _ = Prompt("Client secret: ") + } + } + } + + // Validate required + if modelName == "" { + return fmt.Errorf("model_name is required") + } + if model == "" { + return fmt.Errorf("model is required (e.g. litellm/qwen-turbo)") + } + if isLiteLLM { + if apiBase == "" || tokenURL == "" || clientID == "" || clientSecret == "" { + return fmt.Errorf("litellm requires api_base, token_url, client_id, client_secret") + } + } + + entry := config.ModelConfig{ + ModelName: modelName, + Model: model, + APIBase: apiBase, + APIKey: apiKey, + Proxy: proxy, + AuthMethod: authMethod, + MaxTokensField: maxTokensFld, + TokenURL: tokenURL, + ClientID: clientID, + ClientSecret: clientSecret, + } + + cfg.ModelList = append(cfg.ModelList, entry) + if err := config.SaveConfig(configPath, cfg); err != nil { + return fmt.Errorf("saving config: %w", err) + } + + fmt.Printf("Added model %q to model_list.\n", modelName) + return nil +} diff --git a/cmd/picoclaw/internal/configcmd/model_list_get.go b/cmd/picoclaw/internal/configcmd/model_list_get.go new file mode 100644 index 000000000..d1dd4d65e --- /dev/null +++ b/cmd/picoclaw/internal/configcmd/model_list_get.go @@ -0,0 +1,99 @@ +package configcmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/sipeed/picoclaw/cmd/picoclaw/internal" + "github.com/sipeed/picoclaw/pkg/config" +) + +func newModelListGetCommand() *cobra.Command { + return &cobra.Command{ + Use: "get [key]", + Short: "Get one model's config or a single field", + Args: cobra.MatchAll(cobra.MinimumNArgs(1), cobra.MaximumNArgs(2)), + RunE: runModelListGet, + } +} + +func runModelListGet(_ *cobra.Command, args []string) error { + cfg, err := internal.LoadConfig() + if err != nil { + return fmt.Errorf("loading config: %w", err) + } + + configPath := internal.GetConfigPath() + if _, err := os.Stat(configPath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("config not found; run: picoclaw onboard") + } + return err + } + + modelName := args[0] + idx, err := findModelIndex(cfg, modelName) + if err != nil { + return err + } + entry := &cfg.ModelList[idx] + + if len(args) == 1 { + // No key: print all common fields (mask secrets) + for _, key := range modelConfigKeys { + value, mask := modelConfigGetValue(entry, key) + if mask && value != "" { + value = "***" + } + fmt.Printf("%s: %s\n", key, value) + } + return nil + } + + key := args[1] + if !isModelConfigKey(key) { + return fmt.Errorf("invalid key %q; allowed: %s", key, allowedModelConfigKeysString()) + } + value, _ := modelConfigGetValue(entry, key) + fmt.Println(value) + return nil +} + +// modelConfigGetValue returns the string value for key and whether it should be masked in "get all" output. +func modelConfigGetValue(m *config.ModelConfig, key string) (string, bool) { + switch key { + case "model_name": + return m.ModelName, false + case "model": + return m.Model, false + case "api_base": + return m.APIBase, false + case "api_key": + return m.APIKey, true + case "proxy": + return m.Proxy, false + case "auth_method": + return m.AuthMethod, false + case "connect_mode": + return m.ConnectMode, false + case "workspace": + return m.Workspace, false + case "token_url": + return m.TokenURL, false + case "client_id": + return m.ClientID, false + case "client_secret": + return m.ClientSecret, true + case "max_tokens_field": + return m.MaxTokensField, false + case "rpm": + if m.RPM == 0 { + return "0", false + } + return fmt.Sprintf("%d", m.RPM), false + default: + return "", false + } +} diff --git a/cmd/picoclaw/internal/configcmd/model_list_keys.go b/cmd/picoclaw/internal/configcmd/model_list_keys.go new file mode 100644 index 000000000..712891804 --- /dev/null +++ b/cmd/picoclaw/internal/configcmd/model_list_keys.go @@ -0,0 +1,50 @@ +package configcmd + +import ( + "fmt" + "strings" + + "github.com/sipeed/picoclaw/pkg/config" +) + +// modelConfigKeys defines allowed keys for get/set, in display order. +var modelConfigKeys = []string{ + "model_name", "model", "api_base", "api_key", "proxy", + "auth_method", "connect_mode", "workspace", + "token_url", "client_id", "client_secret", + "max_tokens_field", "rpm", +} + +// modelConfigKeySet is the set of allowed keys. +var modelConfigKeySet map[string]bool + +func init() { + modelConfigKeySet = make(map[string]bool, len(modelConfigKeys)) + for _, k := range modelConfigKeys { + modelConfigKeySet[k] = true + } +} + +func isModelConfigKey(key string) bool { + return modelConfigKeySet[key] +} + +// isIntModelConfigKey returns true for keys that must be set as int (e.g. rpm). +func isIntModelConfigKey(key string) bool { + return key == "rpm" +} + +// findModelIndex returns the index of the first ModelConfig with ModelName == name. +// It returns -1 and an error if not found. +func findModelIndex(cfg *config.Config, name string) (int, error) { + for i := range cfg.ModelList { + if cfg.ModelList[i].ModelName == name { + return i, nil + } + } + return -1, fmt.Errorf("no model with model_name %q", name) +} + +func allowedModelConfigKeysString() string { + return strings.Join(modelConfigKeys, ", ") +} diff --git a/cmd/picoclaw/internal/configcmd/model_list_list.go b/cmd/picoclaw/internal/configcmd/model_list_list.go new file mode 100644 index 000000000..faf384b55 --- /dev/null +++ b/cmd/picoclaw/internal/configcmd/model_list_list.go @@ -0,0 +1,78 @@ +package configcmd + +import ( + "fmt" + "os" + "strings" + + "github.com/spf13/cobra" + + "github.com/sipeed/picoclaw/cmd/picoclaw/internal" + "github.com/sipeed/picoclaw/pkg/config" +) + +func newModelListListCommand() *cobra.Command { + return &cobra.Command{ + Use: "list", + Short: "List all models in model_list", + Args: cobra.NoArgs, + RunE: runModelListList, + } +} + +func runModelListList(_ *cobra.Command, _ []string) error { + cfg, err := internal.LoadConfig() + if err != nil { + return fmt.Errorf("loading config: %w", err) + } + + configPath := internal.GetConfigPath() + if _, err := os.Stat(configPath); err != nil { + if os.IsNotExist(err) { + fmt.Println("No config file found. Run: picoclaw onboard") + return nil + } + return err + } + + if len(cfg.ModelList) == 0 { + fmt.Println("model_list is empty.") + return nil + } + + // Table header + fmt.Printf("%-20s %-35s %-40s %s\n", "MODEL_NAME", "MODEL", "API_BASE", "AUTH") + fmt.Println(strings.Repeat("-", 100)) + + for _, m := range cfg.ModelList { + auth := authSummary(m) + apiBase := m.APIBase + if len(apiBase) > 38 { + apiBase = apiBase[:35] + "..." + } + model := m.Model + if len(model) > 33 { + model = model[:30] + "..." + } + modelName := m.ModelName + if len(modelName) > 18 { + modelName = modelName[:15] + "..." + } + fmt.Printf("%-20s %-35s %-40s %s\n", modelName, model, apiBase, auth) + } + + return nil +} + +func authSummary(m config.ModelConfig) string { + if m.AuthMethod != "" { + return m.AuthMethod + } + if m.TokenURL != "" { + return "litellm (keycloak)" + } + if m.APIKey != "" { + return "api_key" + } + return "-" +} diff --git a/cmd/picoclaw/internal/configcmd/model_list_remove.go b/cmd/picoclaw/internal/configcmd/model_list_remove.go new file mode 100644 index 000000000..b2ba397f6 --- /dev/null +++ b/cmd/picoclaw/internal/configcmd/model_list_remove.go @@ -0,0 +1,72 @@ +package configcmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/sipeed/picoclaw/cmd/picoclaw/internal" + "github.com/sipeed/picoclaw/pkg/config" +) + +func newModelListRemoveCommand() *cobra.Command { + var first bool + + cmd := &cobra.Command{ + Use: "remove ", + Short: "Remove model(s) from model_list by model_name", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + return runModelListRemove(args[0], first) + }, + } + + cmd.Flags().BoolVar(&first, "first", false, "Remove only the first matching entry (default: remove all)") + + return cmd +} + +func runModelListRemove(modelName string, firstOnly bool) error { + cfg, err := internal.LoadConfig() + if err != nil { + return fmt.Errorf("loading config: %w", err) + } + + configPath := internal.GetConfigPath() + if _, err := os.Stat(configPath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("config not found; run: picoclaw onboard") + } + return err + } + + var kept []config.ModelConfig + removedFirst := false + for _, m := range cfg.ModelList { + if m.ModelName == modelName { + if firstOnly { + if !removedFirst { + removedFirst = true + continue + } + } else { + continue + } + } + kept = append(kept, m) + } + + removed := len(cfg.ModelList) - len(kept) + if removed == 0 { + return fmt.Errorf("no model with model_name %q", modelName) + } + + cfg.ModelList = kept + if err := config.SaveConfig(configPath, cfg); err != nil { + return fmt.Errorf("saving config: %w", err) + } + + fmt.Printf("Removed %d model(s) %q from model_list.\n", removed, modelName) + return nil +} diff --git a/cmd/picoclaw/internal/configcmd/model_list_set.go b/cmd/picoclaw/internal/configcmd/model_list_set.go new file mode 100644 index 000000000..c59527f6f --- /dev/null +++ b/cmd/picoclaw/internal/configcmd/model_list_set.go @@ -0,0 +1,100 @@ +package configcmd + +import ( + "fmt" + "os" + "strconv" + + "github.com/spf13/cobra" + + "github.com/sipeed/picoclaw/cmd/picoclaw/internal" + "github.com/sipeed/picoclaw/pkg/config" +) + +func newModelListSetCommand() *cobra.Command { + return &cobra.Command{ + Use: "set ", + Short: "Set a single field for a model in model_list", + Args: cobra.ExactArgs(3), + RunE: runModelListSet, + } +} + +func runModelListSet(_ *cobra.Command, args []string) error { + cfg, err := internal.LoadConfig() + if err != nil { + return fmt.Errorf("loading config: %w", err) + } + + configPath := internal.GetConfigPath() + if _, err := os.Stat(configPath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("config not found; run: picoclaw onboard") + } + return err + } + + modelName := args[0] + key := args[1] + value := args[2] + + if !isModelConfigKey(key) { + return fmt.Errorf("invalid key %q; allowed: %s", key, allowedModelConfigKeysString()) + } + + idx, err := findModelIndex(cfg, modelName) + if err != nil { + return err + } + entry := &cfg.ModelList[idx] + + if isIntModelConfigKey(key) { + n, err := strconv.Atoi(value) + if err != nil { + return fmt.Errorf("key %q requires an integer: %w", key, err) + } + entry.RPM = n + } else { + modelConfigSetString(entry, key, value) + } + + if err := entry.Validate(); err != nil { + return err + } + + if err := config.SaveConfig(configPath, cfg); err != nil { + return fmt.Errorf("saving config: %w", err) + } + + fmt.Printf("Set %s for model %q.\n", key, entry.ModelName) + return nil +} + +func modelConfigSetString(m *config.ModelConfig, key, value string) { + switch key { + case "model_name": + m.ModelName = value + case "model": + m.Model = value + case "api_base": + m.APIBase = value + case "api_key": + m.APIKey = value + case "proxy": + m.Proxy = value + case "auth_method": + m.AuthMethod = value + case "connect_mode": + m.ConnectMode = value + case "workspace": + m.Workspace = value + case "token_url": + m.TokenURL = value + case "client_id": + m.ClientID = value + case "client_secret": + m.ClientSecret = value + case "max_tokens_field": + m.MaxTokensField = value + } +} diff --git a/cmd/picoclaw/internal/configcmd/model_list_update.go b/cmd/picoclaw/internal/configcmd/model_list_update.go new file mode 100644 index 000000000..333c0d9b8 --- /dev/null +++ b/cmd/picoclaw/internal/configcmd/model_list_update.go @@ -0,0 +1,138 @@ +package configcmd + +import ( + "fmt" + "os" + "strings" + + "github.com/spf13/cobra" + + "github.com/sipeed/picoclaw/cmd/picoclaw/internal" + "github.com/sipeed/picoclaw/pkg/config" +) + +func newModelListUpdateCommand() *cobra.Command { + var ( + model string + apiBase string + apiKey string + proxy string + authMethod string + maxTokensFld string + tokenURL string + clientID string + clientSecret string + ) + + cmd := &cobra.Command{ + Use: "update ", + Short: "Update the first matching model in model_list", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + return runModelListUpdate(args[0], model, apiBase, apiKey, proxy, authMethod, maxTokensFld, tokenURL, clientID, clientSecret) + }, + } + + cmd.Flags().StringVar(&model, "model", "", "Protocol/model (e.g. litellm/qwen-turbo)") + cmd.Flags().StringVar(&apiBase, "api-base", "", "API base URL") + cmd.Flags().StringVar(&apiKey, "api-key", "", "API key") + cmd.Flags().StringVar(&proxy, "proxy", "", "HTTP proxy URL") + cmd.Flags().StringVar(&authMethod, "auth-method", "", "Auth method: oauth, token") + cmd.Flags().StringVar(&maxTokensFld, "max-tokens-field", "", "Field name for max tokens") + cmd.Flags().StringVar(&tokenURL, "token-url", "", "Keycloak token URL (for litellm)") + cmd.Flags().StringVar(&clientID, "client-id", "", "Client ID (for litellm)") + cmd.Flags().StringVar(&clientSecret, "client-secret", "", "Client secret (for litellm)") + + return cmd +} + +func runModelListUpdate(modelName, model, apiBase, apiKey, proxy, authMethod, maxTokensFld, tokenURL, clientID, clientSecret string) error { + cfg, err := internal.LoadConfig() + if err != nil { + return fmt.Errorf("loading config: %w", err) + } + + configPath := internal.GetConfigPath() + if _, err := os.Stat(configPath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("config not found; run: picoclaw onboard") + } + return err + } + + var idx int = -1 + for i := range cfg.ModelList { + if cfg.ModelList[i].ModelName == modelName { + idx = i + break + } + } + if idx < 0 { + return fmt.Errorf("no model with model_name %q", modelName) + } + + entry := &cfg.ModelList[idx] + + if model != "" { + entry.Model = model + } + if apiBase != "" { + entry.APIBase = apiBase + } + if apiKey != "" { + entry.APIKey = apiKey + } + if proxy != "" { + entry.Proxy = proxy + } + if authMethod != "" { + entry.AuthMethod = authMethod + } + if maxTokensFld != "" { + entry.MaxTokensField = maxTokensFld + } + if tokenURL != "" { + entry.TokenURL = tokenURL + } + if clientID != "" { + entry.ClientID = clientID + } + if clientSecret != "" { + entry.ClientSecret = clientSecret + } + + isLiteLLM := strings.HasPrefix(strings.ToLower(entry.Model), "litellm/") + if IsTTY() && isLiteLLM { + if entry.APIBase == "" { + v, _ := Prompt("API base URL: ") + entry.APIBase = v + } + if entry.TokenURL == "" { + v, _ := Prompt("Token URL (Keycloak): ") + entry.TokenURL = v + } + if entry.ClientID == "" { + v, _ := Prompt("Client ID: ") + entry.ClientID = v + } + if entry.ClientSecret == "" { + v, _ := Prompt("Client secret: ") + entry.ClientSecret = v + } + } + + if isLiteLLM && (entry.APIBase == "" || entry.TokenURL == "" || entry.ClientID == "" || entry.ClientSecret == "") { + return fmt.Errorf("litellm requires api_base, token_url, client_id, client_secret") + } + + if err := entry.Validate(); err != nil { + return err + } + + if err := config.SaveConfig(configPath, cfg); err != nil { + return fmt.Errorf("saving config: %w", err) + } + + fmt.Printf("Updated model %q in model_list.\n", modelName) + return nil +} diff --git a/cmd/picoclaw/internal/configcmd/prompt.go b/cmd/picoclaw/internal/configcmd/prompt.go new file mode 100644 index 000000000..7855eb158 --- /dev/null +++ b/cmd/picoclaw/internal/configcmd/prompt.go @@ -0,0 +1,30 @@ +package configcmd + +import ( + "bufio" + "fmt" + "os" + "strings" +) + +// IsTTY returns true if stdin is a terminal (interactive). +func IsTTY() bool { + fi, err := os.Stdin.Stat() + if err != nil { + return false + } + return (fi.Mode() & os.ModeCharDevice) != 0 +} + +// Prompt reads a line from stdin after printing the prompt label. The returned string is trimmed. +func Prompt(label string) (string, error) { + fmt.Print(label) + scanner := bufio.NewScanner(os.Stdin) + if !scanner.Scan() { + if err := scanner.Err(); err != nil { + return "", err + } + return "", nil + } + return strings.TrimSpace(scanner.Text()), nil +} diff --git a/cmd/picoclaw/main.go b/cmd/picoclaw/main.go index 6db69c990..d75017cde 100644 --- a/cmd/picoclaw/main.go +++ b/cmd/picoclaw/main.go @@ -15,6 +15,7 @@ import ( "github.com/sipeed/picoclaw/cmd/picoclaw/internal" "github.com/sipeed/picoclaw/cmd/picoclaw/internal/agent" "github.com/sipeed/picoclaw/cmd/picoclaw/internal/auth" + "github.com/sipeed/picoclaw/cmd/picoclaw/internal/configcmd" "github.com/sipeed/picoclaw/cmd/picoclaw/internal/cron" "github.com/sipeed/picoclaw/cmd/picoclaw/internal/gateway" "github.com/sipeed/picoclaw/cmd/picoclaw/internal/migrate" @@ -37,6 +38,7 @@ func NewPicoclawCommand() *cobra.Command { onboard.NewOnboardCommand(), agent.NewAgentCommand(), auth.NewAuthCommand(), + configcmd.NewConfigCommand(), gateway.NewGatewayCommand(), status.NewStatusCommand(), cron.NewCronCommand(),