Files
2026-07-13 13:33:09 +08:00

379 lines
9.6 KiB
Go

package cmd
import (
"fmt"
"plandex-cli/api"
"plandex-cli/auth"
"plandex-cli/lib"
"plandex-cli/term"
"slices"
"strconv"
"strings"
shared "plandex-shared"
"github.com/fatih/color"
"github.com/spf13/cobra"
)
func init() {
RootCmd.AddCommand(setConfigCmd)
setConfigCmd.AddCommand(defaultSetConfigCmd)
RootCmd.AddCommand(setAutoCmd)
setAutoCmd.AddCommand(setAutoDefaultCmd)
}
var setConfigCmd = &cobra.Command{
Use: "set-config [setting] [value]",
Short: "Update current plan config",
Run: setConfig,
Args: cobra.MaximumNArgs(2),
}
var defaultSetConfigCmd = &cobra.Command{
Use: "default [setting] [value]",
Short: "Update default plan config",
Run: defaultSetConfig,
Args: cobra.MaximumNArgs(2),
}
var setAutoCmd = &cobra.Command{
Use: "set-auto [value]",
Short: "Update config auto-mode",
Run: setAuto,
Args: cobra.MaximumNArgs(1),
}
var setAutoDefaultCmd = &cobra.Command{
Use: "default [value]",
Short: "Update default config auto-mode",
Run: setAutoDefault,
Args: cobra.MaximumNArgs(1),
}
func setAuto(cmd *cobra.Command, args []string) {
setConfig(cmd, append([]string{"auto-mode"}, args...))
}
func setAutoDefault(cmd *cobra.Command, args []string) {
defaultSetConfig(cmd, append([]string{"auto-mode"}, args...))
}
func setConfig(cmd *cobra.Command, args []string) {
auth.MustResolveAuthWithOrg()
lib.MustResolveProject()
if lib.CurrentPlanId == "" {
term.OutputNoCurrentPlanErrorAndExit()
}
term.StartSpinner("")
config, apiErr := api.Client.GetPlanConfig(lib.CurrentPlanId)
term.StopSpinner()
if apiErr != nil {
term.OutputErrorAndExit("Error getting current config: %v", apiErr)
return
}
if config == nil {
config = &shared.PlanConfig{}
}
key, updatedConfig := updateConfig(args, config)
if updatedConfig == nil {
return
}
term.StartSpinner("")
apiErr = api.Client.UpdatePlanConfig(lib.CurrentPlanId, shared.UpdatePlanConfigRequest{
Config: updatedConfig,
})
term.StopSpinner()
if apiErr != nil {
term.OutputErrorAndExit("Error updating config: %v", apiErr)
return
}
fmt.Println("✅ Config updated")
lib.ShowPlanConfig(updatedConfig, key)
fmt.Println()
loadMapIfNeeded(config, updatedConfig)
removeMapIfNeeded(config, updatedConfig)
if !(config.AutoApply && config.AutoExec) && updatedConfig.AutoApply && updatedConfig.AutoExec {
color.New(term.ColorHiYellow, color.Bold).Println("⚠️ You enabled automatic apply and execution.")
fmt.Println()
} else if !config.AutoApply && updatedConfig.AutoApply {
color.New(term.ColorHiYellow, color.Bold).Println("⚠️ You enabled automatic apply.")
fmt.Println()
} else if !config.AutoExec && updatedConfig.AutoExec {
color.New(term.ColorHiYellow, color.Bold).Println("⚠️ You enabled automatic execution.")
fmt.Println()
}
term.StopSpinner()
term.PrintCmds("", "config", "config default", "set-config default")
}
func defaultSetConfig(cmd *cobra.Command, args []string) {
auth.MustResolveAuthWithOrg()
term.StartSpinner("")
config, apiErr := api.Client.GetDefaultPlanConfig()
term.StopSpinner()
if apiErr != nil {
term.OutputErrorAndExit("Error getting current config: %v", apiErr)
return
}
if config == nil {
config = &shared.PlanConfig{}
}
key, updatedConfig := updateConfig(args, config)
if updatedConfig == nil {
return
}
term.StartSpinner("")
apiErr = api.Client.UpdateDefaultPlanConfig(shared.UpdateDefaultPlanConfigRequest{
Config: updatedConfig,
})
term.StopSpinner()
if apiErr != nil {
term.OutputErrorAndExit("Error updating config: %v", apiErr)
return
}
fmt.Println("✅ Default config updated")
lib.ShowPlanConfig(updatedConfig, key)
fmt.Println()
term.PrintCmds("", "config default", "config", "set-config")
}
type sortableSetting struct {
sortKey string
cfg shared.ConfigSetting
}
func updateConfig(args []string, originalConfig *shared.PlanConfig) (string, *shared.PlanConfig) {
var setting, value string
if len(args) > 0 {
setting = strings.ToLower(strings.ReplaceAll(args[0], "-", ""))
}
if len(args) > 1 {
value = args[1]
}
if setting == "" {
var sorted []sortableSetting
for key, cfg := range shared.ConfigSettingsByKey {
var sortKey string
if cfg.SortKey != "" {
sortKey = cfg.SortKey
} else {
sortKey = key
}
sorted = append(sorted, sortableSetting{sortKey, cfg})
}
slices.SortFunc(sorted, func(a, b sortableSetting) int {
return strings.Compare(a.sortKey, b.sortKey)
})
var opts []string
for _, opt := range sorted {
opts = append(opts, fmt.Sprintf("%s → %s", opt.cfg.Name, opt.cfg.Desc))
}
selection, err := term.SelectFromList("Choose a setting to update:", opts)
if err != nil {
if err.Error() == "interrupt" {
return "", nil
}
term.OutputErrorAndExit("Error selecting setting: %v", err)
return "", nil
}
setting = strings.Split(selection, " →")[0]
setting = strings.ToLower(strings.ReplaceAll(setting, "-", ""))
}
config := *originalConfig
cfgSetting, exists := shared.ConfigSettingsByKey[setting]
if !exists {
term.OutputErrorAndExit("Unknown setting: %s\n", setting)
return "", nil
}
if value == "" {
if cfgSetting.BoolSetter != nil {
options := []string{"Enabled", "Disabled"}
selection, err := term.SelectFromList(fmt.Sprintf("Set %s:", cfgSetting.Name), options)
if err != nil {
if err.Error() == "interrupt" {
return "", nil
}
term.OutputErrorAndExit("Error selecting value: %v", err)
return "", nil
}
cfgSetting.BoolSetter(&config, selection == "Enabled")
} else if cfgSetting.IntSetter != nil {
value, err := term.GetRequiredUserStringInput(fmt.Sprintf("Set %s (number)", cfgSetting.Name))
if err != nil {
if err.Error() == "interrupt" {
return "", nil
}
term.OutputErrorAndExit("Error getting value: %v", err)
return "", nil
}
n, err := strconv.Atoi(value)
if err != nil {
term.OutputErrorAndExit("Invalid number value for %s (%s)", cfgSetting.Name, value)
return "", nil
}
cfgSetting.IntSetter(&config, n)
} else if cfgSetting.StringSetter != nil {
var selection string
var err error
choices := *cfgSetting.Choices
if len(choices) > 0 {
if cfgSetting.HasCustomChoice {
choices = append(choices, "Other")
}
selection, err = term.SelectFromList(fmt.Sprintf("Set %s:", cfgSetting.Name), choices)
if err != nil {
if err.Error() == "interrupt" {
return "", nil
}
term.OutputErrorAndExit("Error selecting value: %v", err)
return "", nil
}
if selection == "Other" {
selection, err = term.GetRequiredUserStringInput(fmt.Sprintf("Enter value for %s", cfgSetting.Name))
if err != nil {
if err.Error() == "interrupt" {
return "", nil
}
term.OutputErrorAndExit("Error getting value: %v", err)
return "", nil
}
} else if cfgSetting.ChoiceToKey != nil {
selection = cfgSetting.ChoiceToKey(selection)
}
} else {
selection, err = term.GetRequiredUserStringInput(fmt.Sprintf("Set %s", cfgSetting.Name))
if err != nil {
if err.Error() == "interrupt" {
return "", nil
}
term.OutputErrorAndExit("Error getting value: %v", err)
return "", nil
}
}
cfgSetting.StringSetter(&config, selection)
} else if cfgSetting.EditorSetter != nil {
editor := lib.SelectEditor(false)
cfgSetting.EditorSetter(&config, editor.Name, editor.Cmd, editor.Args)
}
} else {
if cfgSetting.BoolSetter != nil {
b, err := parseBooleanArg(value)
if err != nil {
term.OutputErrorAndExit("Invalid value for %s (%s)", cfgSetting.Name, value)
return "", nil
}
cfgSetting.BoolSetter(&config, b)
} else if cfgSetting.IntSetter != nil {
n, err := strconv.Atoi(value)
if err != nil {
term.OutputErrorAndExit("Invalid number value for %s (%s)", cfgSetting.Name, value)
return "", nil
}
cfgSetting.IntSetter(&config, n)
} else if cfgSetting.StringSetter != nil {
cfgSetting.StringSetter(&config, value)
} else if cfgSetting.EditorSetter != nil {
fields := strings.Fields(value)
cmd := fields[0]
var cmdArgs []string
if len(fields) > 1 {
cmdArgs = fields[1:]
}
cfgSetting.EditorSetter(&config, value, cmd, cmdArgs)
}
}
return setting, &config
}
func parseBooleanArg(value string) (bool, error) {
switch value {
case "enabled", "true", "t", "yes", "y", "1":
return true, nil
case "disabled", "false", "f", "no", "n", "0":
return false, nil
default:
return false, fmt.Errorf("invalid value: %s", value)
}
}
func loadMapIfNeeded(originalConfig, updatedConfig *shared.PlanConfig) {
if updatedConfig.AutoLoadContext && !originalConfig.AutoLoadContext {
hasMap := false
term.StartSpinner("")
context, err := api.Client.ListContext(lib.CurrentPlanId, lib.CurrentBranch)
if err == nil {
for _, c := range context {
if c.ContextType == shared.ContextMapType {
hasMap = true
break
}
}
if !hasMap {
lib.MustLoadAutoContextMap()
fmt.Println()
}
}
}
}
func removeMapIfNeeded(originalConfig, updatedConfig *shared.PlanConfig) {
if originalConfig.AutoLoadContext && !updatedConfig.AutoLoadContext {
term.StartSpinner("")
context, err := api.Client.ListContext(lib.CurrentPlanId, lib.CurrentBranch)
if err == nil {
for _, c := range context {
if c.ContextType == shared.ContextMapType && (c.AutoLoaded || c.FilePath == ".") {
res, err := api.Client.DeleteContext(lib.CurrentPlanId, lib.CurrentBranch, shared.DeleteContextRequest{
Ids: map[string]bool{c.Id: true},
})
term.StopSpinner()
if err != nil {
term.OutputErrorAndExit("Error deleting context: %v", err)
return
}
fmt.Println("✅ " + res.Msg)
break
}
}
}
}
}