| 1 | package cli |
| 2 | |
| 3 | import ( |
| 4 | "bufio" |
| 5 | "errors" |
| 6 | "fmt" |
| 7 | "io" |
| 8 | "os" |
| 9 | |
| 10 | "reasonix/internal/config" |
| 11 | "reasonix/internal/i18n" |
| 12 | ) |
| 13 | |
| 14 | func (s *providerSetupSession) addPrompted(in *bufio.Scanner, w io.Writer, result providerPromptResult) bool { |
| 15 | if result.keyEnvTyped && len(result.credentials) > 0 && len(result.entries) > 0 { |
| 16 | names := make([]string, 0, len(result.entries)) |
| 17 | for _, entry := range result.entries { |
| 18 | names = append(names, entry.Name) |
| 19 | } |
| 20 | from := result.entries[0].APIKeyEnv |
| 21 | to, typed := s.settleTypedKeyEnv(in, w, keyEnvPromptLabel(result.entries[0].Kind), names, from, result.keyEnvDraft) |
| 22 | result.renameKeyEnv(from, to) |
| 23 | result.keyEnvTyped = typed |
| 24 | } |
| 25 | for _, entry := range result.entries { |
| 26 | if !confirmSharedCredential(s.cfg, entry, "") { |
| 27 | return false |
| 28 | } |
| 29 | } |
| 30 | if err := s.add(result.entries); err != nil { |
| 31 | fmt.Fprintln(os.Stderr, err) |
| 32 | return false |
| 33 | } |
| 34 | s.addProviderAccess(result.entries) |
| 35 | for key, value := range result.credentials { |
| 36 | var names []string |
| 37 | for _, entry := range result.entries { |
| 38 | if entry.APIKeyEnv == key { |
| 39 | names = append(names, entry.Name) |
| 40 | } |
| 41 | } |
| 42 | if err := s.setCredentialForProviders(names, key, value); err != nil { |
| 43 | fmt.Fprintln(os.Stderr, err) |
| 44 | return false |
| 45 | } |
| 46 | if result.keyEnvTyped { |
| 47 | for _, name := range names { |
| 48 | s.typedKeyEnv[name] = key |
| 49 | } |
| 50 | } |
| 51 | } |
| 52 | // After the new keys are staged, so usability sees them. |
| 53 | s.promoteDefaultToNewProviders(result.entries) |
| 54 | return true |
| 55 | } |
| 56 | |
| 57 | func (r *providerPromptResult) renameKeyEnv(from, to string) { |
| 58 | if from == to { |
| 59 | return |
| 60 | } |
| 61 | for i := range r.entries { |
| 62 | if r.entries[i].APIKeyEnv == from { |
| 63 | r.entries[i].APIKeyEnv = to |
| 64 | } |
| 65 | } |
| 66 | if value, ok := r.credentials[from]; ok { |
| 67 | delete(r.credentials, from) |
| 68 | r.credentials[to] = value |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | // settleTypedKeyEnv asks again while providers may not claim key, saying who |
| 73 | // holds it. Pressing Enter returns draft with typed=false: a private slot. |
| 74 | func (s *providerSetupSession) settleTypedKeyEnv(in *bufio.Scanner, w io.Writer, label string, providers []string, key, draft string) (string, bool) { |
| 75 | for { |
| 76 | var held error |
| 77 | for _, name := range providers { |
| 78 | if held = s.cfg.CredentialKeyClaimable(key, name); held != nil { |
| 79 | break |
| 80 | } |
| 81 | } |
| 82 | if held == nil { |
| 83 | return key, true |
| 84 | } |
| 85 | fmt.Fprintln(w, keyEnvInUseText(held)) |
| 86 | fmt.Fprintln(w, i18n.M.SetupKeyEnvRetry) |
| 87 | var typed bool |
| 88 | if key, typed = promptAPIKeyEnvName(in, w, label, draft); !typed { |
| 89 | return key, false |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | func keyEnvPromptLabel(kind string) string { |
| 95 | if kind == "anthropic" { |
| 96 | return i18n.M.AnthropicPromptKeyEnv |
| 97 | } |
| 98 | return i18n.M.CustomPromptKeyEnv |
| 99 | } |
| 100 | |
| 101 | func keyEnvInUseText(err error) string { |
| 102 | var inUse *config.CredentialKeyInUseError |
| 103 | if !errors.As(err, &inUse) { |
| 104 | return err.Error() |
| 105 | } |
| 106 | switch inUse.Holder { |
| 107 | case config.CredentialKeyHeldByProvider: |
| 108 | return fmt.Sprintf(i18n.M.SetupKeyEnvTakenFmt, inUse.Key, inUse.Provider) |
| 109 | case config.CredentialKeyHeldBySetting: |
| 110 | return fmt.Sprintf(i18n.M.SetupKeyEnvSettingFmt, inUse.Key) |
| 111 | case config.CredentialKeyHeldByEnvironment: |
| 112 | return fmt.Sprintf(i18n.M.SetupKeyEnvShellFmt, inUse.Key) |
| 113 | default: |
| 114 | return fmt.Sprintf(i18n.M.SetupKeyEnvStoredFmt, inUse.Key) |
| 115 | } |
| 116 | } |
| 117 |