Add config command with model list and prompt subcommands
Made-with: Cursor
This commit is contained in:
parent
094d65916d
commit
0e8cae377c
10 changed files with 736 additions and 0 deletions
38
cmd/picoclaw/internal/configcmd/command.go
Normal file
38
cmd/picoclaw/internal/configcmd/command.go
Normal file
|
|
@ -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
|
||||
}
|
||||
129
cmd/picoclaw/internal/configcmd/model_list_add.go
Normal file
129
cmd/picoclaw/internal/configcmd/model_list_add.go
Normal file
|
|
@ -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
|
||||
}
|
||||
99
cmd/picoclaw/internal/configcmd/model_list_get.go
Normal file
99
cmd/picoclaw/internal/configcmd/model_list_get.go
Normal file
|
|
@ -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 <model_name> [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
|
||||
}
|
||||
}
|
||||
50
cmd/picoclaw/internal/configcmd/model_list_keys.go
Normal file
50
cmd/picoclaw/internal/configcmd/model_list_keys.go
Normal file
|
|
@ -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, ", ")
|
||||
}
|
||||
78
cmd/picoclaw/internal/configcmd/model_list_list.go
Normal file
78
cmd/picoclaw/internal/configcmd/model_list_list.go
Normal file
|
|
@ -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 "-"
|
||||
}
|
||||
72
cmd/picoclaw/internal/configcmd/model_list_remove.go
Normal file
72
cmd/picoclaw/internal/configcmd/model_list_remove.go
Normal file
|
|
@ -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 <model_name>",
|
||||
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
|
||||
}
|
||||
100
cmd/picoclaw/internal/configcmd/model_list_set.go
Normal file
100
cmd/picoclaw/internal/configcmd/model_list_set.go
Normal file
|
|
@ -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 <model_name> <key> <value>",
|
||||
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
|
||||
}
|
||||
}
|
||||
138
cmd/picoclaw/internal/configcmd/model_list_update.go
Normal file
138
cmd/picoclaw/internal/configcmd/model_list_update.go
Normal file
|
|
@ -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 <model_name>",
|
||||
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
|
||||
}
|
||||
30
cmd/picoclaw/internal/configcmd/prompt.go
Normal file
30
cmd/picoclaw/internal/configcmd/prompt.go
Normal file
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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(),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue