@caretive/caret-cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.npmrc.tmp +2 -0
- package/README.md +72 -0
- package/cmd/cline/main.go +348 -0
- package/cmd/cline-host/main.go +71 -0
- package/e2e/default_update_test.go +154 -0
- package/e2e/helpers_test.go +378 -0
- package/e2e/main_test.go +47 -0
- package/e2e/mixed_stress_test.go +120 -0
- package/e2e/sqlite_helper.go +161 -0
- package/e2e/start_list_test.go +178 -0
- package/go.mod +64 -0
- package/go.sum +162 -0
- package/man/cline.1 +331 -0
- package/man/cline.1.md +332 -0
- package/package.json +54 -0
- package/pkg/cli/auth/auth_cline_provider.go +285 -0
- package/pkg/cli/auth/auth_menu.go +323 -0
- package/pkg/cli/auth/auth_subscription.go +130 -0
- package/pkg/cli/auth/byo_quick_setup.go +247 -0
- package/pkg/cli/auth/models_cline.go +141 -0
- package/pkg/cli/auth/models_list_fetch.go +156 -0
- package/pkg/cli/auth/models_list_static.go +69 -0
- package/pkg/cli/auth/providers_byo.go +184 -0
- package/pkg/cli/auth/providers_list.go +517 -0
- package/pkg/cli/auth/update_api_configurations.go +647 -0
- package/pkg/cli/auth/wizard_byo.go +764 -0
- package/pkg/cli/auth/wizard_byo_bedrock.go +193 -0
- package/pkg/cli/auth/wizard_byo_oca.go +366 -0
- package/pkg/cli/auth.go +43 -0
- package/pkg/cli/clerror/cline_error.go +187 -0
- package/pkg/cli/config/manager.go +208 -0
- package/pkg/cli/config/settings_renderer.go +198 -0
- package/pkg/cli/config.go +152 -0
- package/pkg/cli/display/ansi.go +27 -0
- package/pkg/cli/display/banner.go +211 -0
- package/pkg/cli/display/deduplicator.go +95 -0
- package/pkg/cli/display/markdown_renderer.go +139 -0
- package/pkg/cli/display/renderer.go +304 -0
- package/pkg/cli/display/segment_streamer.go +212 -0
- package/pkg/cli/display/streaming.go +134 -0
- package/pkg/cli/display/system_renderer.go +269 -0
- package/pkg/cli/display/tool_renderer.go +455 -0
- package/pkg/cli/display/tool_result_parser.go +371 -0
- package/pkg/cli/display/typewriter.go +210 -0
- package/pkg/cli/doctor.go +65 -0
- package/pkg/cli/global/cline-clients.go +501 -0
- package/pkg/cli/global/global.go +113 -0
- package/pkg/cli/global/registry.go +304 -0
- package/pkg/cli/handlers/ask_handlers.go +339 -0
- package/pkg/cli/handlers/handler.go +130 -0
- package/pkg/cli/handlers/say_handlers.go +521 -0
- package/pkg/cli/instances.go +506 -0
- package/pkg/cli/logs.go +382 -0
- package/pkg/cli/output/coordinator.go +167 -0
- package/pkg/cli/output/input_model.go +497 -0
- package/pkg/cli/sqlite/locks.go +366 -0
- package/pkg/cli/task/history_handler.go +72 -0
- package/pkg/cli/task/input_handler.go +577 -0
- package/pkg/cli/task/manager.go +1283 -0
- package/pkg/cli/task/settings_parser.go +754 -0
- package/pkg/cli/task/stream_coordinator.go +60 -0
- package/pkg/cli/task.go +675 -0
- package/pkg/cli/terminal/keyboard.go +695 -0
- package/pkg/cli/tui/HELP_WANTED.md +1 -0
- package/pkg/cli/types/history.go +17 -0
- package/pkg/cli/types/messages.go +329 -0
- package/pkg/cli/types/state.go +59 -0
- package/pkg/cli/updater/updater.go +409 -0
- package/pkg/cli/version.go +43 -0
- package/pkg/common/constants.go +6 -0
- package/pkg/common/schema.go +54 -0
- package/pkg/common/types.go +54 -0
- package/pkg/common/utils.go +185 -0
- package/pkg/generated/field_overrides.go +39 -0
- package/pkg/generated/providers.go +1584 -0
- package/pkg/hostbridge/diff.go +351 -0
- package/pkg/hostbridge/disabled/watch.go +39 -0
- package/pkg/hostbridge/disabled/window.go +63 -0
- package/pkg/hostbridge/disabled/workspace.go +66 -0
- package/pkg/hostbridge/env.go +166 -0
- package/pkg/hostbridge/grpc_server.go +113 -0
- package/pkg/hostbridge/simple.go +43 -0
- package/pkg/hostbridge/simple_workspace.go +85 -0
- package/pkg/hostbridge/window.go +129 -0
- package/scripts/publish-caret-cli.sh +39 -0
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
package auth
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"fmt"
|
|
7
|
+
"strings"
|
|
8
|
+
"time"
|
|
9
|
+
|
|
10
|
+
"github.com/charmbracelet/huh"
|
|
11
|
+
"github.com/cline/cli/pkg/cli/global"
|
|
12
|
+
"github.com/cline/cli/pkg/cli/task"
|
|
13
|
+
"github.com/cline/grpc-go/cline"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
// ProviderWizard handles the interactive provider configuration process
|
|
17
|
+
type ProviderWizard struct {
|
|
18
|
+
ctx context.Context
|
|
19
|
+
manager *task.Manager
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// NewProviderWizard prepares a new provider configuration wizard
|
|
23
|
+
func NewProviderWizard(ctx context.Context) (*ProviderWizard, error) {
|
|
24
|
+
// Create task manager using auth instance from context
|
|
25
|
+
manager, err := createTaskManager(ctx)
|
|
26
|
+
if err != nil {
|
|
27
|
+
return nil, fmt.Errorf("failed to create task manager: %w", err)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return &ProviderWizard{
|
|
31
|
+
ctx: ctx,
|
|
32
|
+
manager: manager,
|
|
33
|
+
}, nil
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// showMainMenu displays the main provider configuration menu
|
|
37
|
+
func (pw *ProviderWizard) showMainMenu() (string, error) {
|
|
38
|
+
var action string
|
|
39
|
+
form := huh.NewForm(
|
|
40
|
+
huh.NewGroup(
|
|
41
|
+
huh.NewSelect[string]().
|
|
42
|
+
Title("What would you like to do?").
|
|
43
|
+
Options(
|
|
44
|
+
huh.NewOption("Add or change an API provider", "add"),
|
|
45
|
+
huh.NewOption("Change model for API provider", "change-model"),
|
|
46
|
+
huh.NewOption("Remove a provider", "remove"),
|
|
47
|
+
huh.NewOption("List configured providers", "list"),
|
|
48
|
+
huh.NewOption("Return to main auth menu", "back"),
|
|
49
|
+
).
|
|
50
|
+
Value(&action),
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if err := form.Run(); err != nil {
|
|
55
|
+
return "", fmt.Errorf("failed to get menu choice: %w", err)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return action, nil
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Run runs the provider configuration wizard
|
|
62
|
+
func (pw *ProviderWizard) Run() error {
|
|
63
|
+
|
|
64
|
+
for {
|
|
65
|
+
action, err := pw.showMainMenu()
|
|
66
|
+
if err != nil {
|
|
67
|
+
return err
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
switch action {
|
|
71
|
+
case "add":
|
|
72
|
+
if err := pw.handleAddProvider(); err != nil {
|
|
73
|
+
return err
|
|
74
|
+
}
|
|
75
|
+
case "change-model":
|
|
76
|
+
if err := pw.handleChangeModel(); err != nil {
|
|
77
|
+
return err
|
|
78
|
+
}
|
|
79
|
+
case "remove":
|
|
80
|
+
if err := pw.handleRemoveProvider(); err != nil {
|
|
81
|
+
return err
|
|
82
|
+
}
|
|
83
|
+
case "list":
|
|
84
|
+
if err := pw.handleListProviders(); err != nil {
|
|
85
|
+
return err
|
|
86
|
+
}
|
|
87
|
+
case "back":
|
|
88
|
+
// Return to main auth menu
|
|
89
|
+
return HandleAuthMenuNoArgs(pw.ctx)
|
|
90
|
+
}
|
|
91
|
+
fmt.Println()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// "Add a new provider" > handleAddProvider
|
|
96
|
+
func (pw *ProviderWizard) handleAddProvider() error {
|
|
97
|
+
// Step 1: Select provider
|
|
98
|
+
provider, err := SelectBYOProvider()
|
|
99
|
+
if err != nil {
|
|
100
|
+
if strings.Contains(err.Error(), "cancelled") {
|
|
101
|
+
return nil
|
|
102
|
+
}
|
|
103
|
+
return fmt.Errorf("provider selection failed: %w", err)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Step 2: Special handling for Bedrock provider
|
|
107
|
+
if provider == cline.ApiProvider_BEDROCK {
|
|
108
|
+
return pw.handleAddBedrockProvider()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Step 2b: Special handling for OCA provider
|
|
112
|
+
if provider == cline.ApiProvider_OCA {
|
|
113
|
+
return pw.handleAddOcaProvider()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Step 3: Get API key first (for non-Bedrock providers)
|
|
117
|
+
apiKey, baseURL, err := PromptForAPIKey(provider)
|
|
118
|
+
if err != nil {
|
|
119
|
+
return fmt.Errorf("failed to get API key: %w", err)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Step 4: Try to fetch models and let user select (with fallback to manual entry for providers that don't support fetch)
|
|
123
|
+
modelID, modelInfo, err := pw.selectModel(provider, apiKey)
|
|
124
|
+
if err != nil {
|
|
125
|
+
return fmt.Errorf("model selection failed: %w", err)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Step 5: Apply configuration using AddProviderPartial
|
|
129
|
+
if err := AddProviderPartial(pw.ctx, pw.manager, provider, modelID, apiKey, baseURL, modelInfo); err != nil {
|
|
130
|
+
return fmt.Errorf("failed to save configuration: %w", err)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if err := setWelcomeViewCompleted(pw.ctx, pw.manager); err != nil {
|
|
134
|
+
verboseLog("Warning: Failed to mark welcome view as completed: %v", err)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fmt.Println("✓ Provider configured successfully!")
|
|
138
|
+
return nil
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// handleAddBedrockProvider handles the special case of adding Bedrock provider with its multi-field form
|
|
142
|
+
func (pw *ProviderWizard) handleAddBedrockProvider() error {
|
|
143
|
+
// Step 1: Get Bedrock configuration (all credentials and optional fields)
|
|
144
|
+
config, err := PromptForBedrockConfig(pw.ctx, pw.manager)
|
|
145
|
+
if err != nil {
|
|
146
|
+
if strings.Contains(err.Error(), "user declined profile authentication") {
|
|
147
|
+
return nil
|
|
148
|
+
}
|
|
149
|
+
return fmt.Errorf("failed to get Bedrock configuration: %w", err)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Step 2: Select model
|
|
153
|
+
modelID, modelInfo, err := pw.selectModel(cline.ApiProvider_BEDROCK, "")
|
|
154
|
+
if err != nil {
|
|
155
|
+
return fmt.Errorf("model selection failed: %w", err)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Step 3: Apply Bedrock configuration
|
|
159
|
+
if err := ApplyBedrockConfig(pw.ctx, pw.manager, config, modelID, modelInfo); err != nil {
|
|
160
|
+
return fmt.Errorf("failed to save Bedrock configuration: %w", err)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if err := setWelcomeViewCompleted(pw.ctx, pw.manager); err != nil {
|
|
164
|
+
verboseLog("Warning: Failed to mark welcome view as completed: %v", err)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
fmt.Println("✓ Bedrock provider configured successfully!")
|
|
168
|
+
return nil
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// handleAddOcaProvider handles adding Oracle Code Assist provider with optional settings and auth
|
|
172
|
+
func (pw *ProviderWizard) handleAddOcaProvider() error {
|
|
173
|
+
// Step 1: Get OCA configuration (base URL and mode)
|
|
174
|
+
config, err := PromptForOcaConfig(pw.ctx, pw.manager)
|
|
175
|
+
if err != nil {
|
|
176
|
+
if strings.Contains(err.Error(), "user aborted") || strings.Contains(err.Error(), "cancelled") {
|
|
177
|
+
return nil
|
|
178
|
+
}
|
|
179
|
+
return fmt.Errorf("failed to get OCA configuration: %w", err)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Apply OCA configuration (base URL and mode)
|
|
183
|
+
if err := ApplyOcaConfig(pw.ctx, pw.manager, config); err != nil {
|
|
184
|
+
return fmt.Errorf("failed to save OCA configuration: %w", err)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Step 2: Ensure OCA authentication
|
|
188
|
+
if err := ensureOcaAuthenticated(pw.ctx); err != nil {
|
|
189
|
+
return fmt.Errorf("failed to authenticate with OCA: %w", err)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Step 3: Select model
|
|
193
|
+
modelID, _, err := pw.selectModel(cline.ApiProvider_OCA, "")
|
|
194
|
+
if err != nil {
|
|
195
|
+
return fmt.Errorf("model selection failed: %w", err)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Step 4: Apply the OCA model configuration and set as active
|
|
199
|
+
updates := ProviderUpdatesPartial{
|
|
200
|
+
ModelID: &modelID,
|
|
201
|
+
ModelInfo: nil,
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if err := UpdateProviderPartial(pw.ctx, pw.manager, cline.ApiProvider_OCA, updates, true); err != nil {
|
|
205
|
+
return fmt.Errorf("failed to save OCA configuration: %w", err)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if err := setWelcomeViewCompleted(pw.ctx, pw.manager); err != nil {
|
|
209
|
+
verboseLog("Warning: Failed to mark welcome view as completed: %v", err)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
fmt.Println("✓ OCA provider configured successfully!")
|
|
213
|
+
return nil
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// handleListProviders retrieves and displays configured providers
|
|
217
|
+
func (pw *ProviderWizard) handleListProviders() error {
|
|
218
|
+
result, err := GetProviderConfigurations(pw.ctx, pw.manager)
|
|
219
|
+
if err != nil {
|
|
220
|
+
return fmt.Errorf("failed to retrieve provider configurations: %w", err)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
output := FormatProviderList(result)
|
|
224
|
+
fmt.Println(output)
|
|
225
|
+
|
|
226
|
+
return nil
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// selectModel attempts to fetch available models and let user select, or falls back to manual entry
|
|
230
|
+
func (pw *ProviderWizard) selectModel(provider cline.ApiProvider, apiKey string) (string, interface{}, error) {
|
|
231
|
+
// For providers that support model fetching, try to fetch and display models
|
|
232
|
+
canFetchModels := pw.supportsModelFetching(provider)
|
|
233
|
+
|
|
234
|
+
if canFetchModels {
|
|
235
|
+
fmt.Println("Fetching available models...")
|
|
236
|
+
models, modelInfoMap, err := pw.fetchModelsForProvider(provider, apiKey)
|
|
237
|
+
|
|
238
|
+
if err != nil {
|
|
239
|
+
fmt.Println("\n⚠ Unable to fetch model list from the provider. Please enter the model ID manually instead.")
|
|
240
|
+
if global.Config.Verbose {
|
|
241
|
+
fmt.Printf(" Error details: %v\n", err)
|
|
242
|
+
}
|
|
243
|
+
return pw.manualModelEntry(provider)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if len(models) == 0 {
|
|
247
|
+
fmt.Println("\n⚠ No models found from the provider. Please enter the model ID manually instead.")
|
|
248
|
+
return pw.manualModelEntry(provider)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Let user select from available models (includes manual entry option)
|
|
252
|
+
modelID, err := pw.selectFromAvailableModels(models)
|
|
253
|
+
if err != nil {
|
|
254
|
+
return "", nil, fmt.Errorf("model selection failed: %w", err)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Check if user chose manual entry
|
|
258
|
+
const manualEntryKey = "__MANUAL_ENTRY__"
|
|
259
|
+
if modelID == manualEntryKey {
|
|
260
|
+
return pw.manualModelEntry(provider)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Get the model info for the selected model
|
|
264
|
+
var modelInfo interface{}
|
|
265
|
+
if modelInfoMap != nil {
|
|
266
|
+
modelInfo = modelInfoMap[modelID]
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return modelID, modelInfo, nil
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// For providers without model fetching support, use manual entry
|
|
273
|
+
return pw.manualModelEntry(provider)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// supportsModelFetching returns true if the provider supports fetching models
|
|
277
|
+
func (pw *ProviderWizard) supportsModelFetching(provider cline.ApiProvider) bool {
|
|
278
|
+
return SupportsBYOModelFetching(provider)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// fetchModelsForProvider fetches models for a given provider
|
|
282
|
+
// Supports both dynamic API fetching (OpenRouter, OpenAI, Ollama) and static model lists (Anthropic, Bedrock, Gemini, X AI)
|
|
283
|
+
func (pw *ProviderWizard) fetchModelsForProvider(provider cline.ApiProvider, apiKey string) ([]string, map[string]interface{}, error) {
|
|
284
|
+
// Try dynamic/remote model fetching first
|
|
285
|
+
switch provider {
|
|
286
|
+
case cline.ApiProvider_OPENROUTER:
|
|
287
|
+
models, err := FetchOpenRouterModels(pw.ctx, pw.manager)
|
|
288
|
+
if err != nil {
|
|
289
|
+
return nil, nil, err
|
|
290
|
+
}
|
|
291
|
+
interfaceMap := ConvertOpenRouterModelsToInterface(models)
|
|
292
|
+
return ConvertModelsMapToSlice(interfaceMap), interfaceMap, nil
|
|
293
|
+
|
|
294
|
+
case cline.ApiProvider_OPENAI:
|
|
295
|
+
// For OpenAI, we need to pass the base URL and API key
|
|
296
|
+
baseURL := "https://api.openai.com/v1" // Default OpenAI API base URL
|
|
297
|
+
modelIDs, err := FetchOpenAiModels(pw.ctx, pw.manager, baseURL, apiKey)
|
|
298
|
+
if err != nil {
|
|
299
|
+
return nil, nil, err
|
|
300
|
+
}
|
|
301
|
+
// OpenAI returns just model IDs without additional info, so modelInfo map is nil
|
|
302
|
+
return modelIDs, nil, nil
|
|
303
|
+
|
|
304
|
+
case cline.ApiProvider_OLLAMA:
|
|
305
|
+
// For Ollama, apiKey actually contains the base URL (or empty for default)
|
|
306
|
+
baseURL := apiKey // The "API key" field for Ollama is actually the base URL
|
|
307
|
+
modelIDs, err := FetchOllamaModels(pw.ctx, pw.manager, baseURL)
|
|
308
|
+
if err != nil {
|
|
309
|
+
return nil, nil, err
|
|
310
|
+
}
|
|
311
|
+
// Ollama returns just model IDs without additional info, so modelInfo map is nil
|
|
312
|
+
return modelIDs, nil, nil
|
|
313
|
+
|
|
314
|
+
case cline.ApiProvider_OCA:
|
|
315
|
+
// OCA supports dynamic model fetching
|
|
316
|
+
models, err := FetchOcaModels(pw.ctx, pw.manager)
|
|
317
|
+
if err != nil {
|
|
318
|
+
return nil, nil, err
|
|
319
|
+
}
|
|
320
|
+
interfaceMap := ConvertOcaModelsToInterface(models)
|
|
321
|
+
return ConvertModelsMapToSlice(interfaceMap), interfaceMap, nil
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Fall back to static models for providers that don't support dynamic fetching
|
|
325
|
+
if SupportsStaticModelList(provider) {
|
|
326
|
+
modelIDs, _, err := FetchStaticModels(provider)
|
|
327
|
+
if err != nil {
|
|
328
|
+
return nil, nil, err
|
|
329
|
+
}
|
|
330
|
+
// Static models don't have detailed info maps for now, so modelInfo map is nil
|
|
331
|
+
return modelIDs, nil, nil
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return nil, nil, fmt.Errorf("model fetching not supported for provider: %v", provider)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// selectFromAvailableModels displays available models and lets user select one.
|
|
338
|
+
// Includes an option to enter a model ID manually in case the desired model isn't listed.
|
|
339
|
+
func (pw *ProviderWizard) selectFromAvailableModels(models []string) (string, error) {
|
|
340
|
+
if len(models) == 0 {
|
|
341
|
+
return "", fmt.Errorf("no models available")
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Add a special "manual entry" option at the end
|
|
345
|
+
const manualEntryKey = "__MANUAL_ENTRY__"
|
|
346
|
+
|
|
347
|
+
// Use model ID as the value (not index)
|
|
348
|
+
var selectedModel string
|
|
349
|
+
options := make([]huh.Option[string], len(models)+1)
|
|
350
|
+
for i, model := range models {
|
|
351
|
+
options[i] = huh.NewOption(model, model)
|
|
352
|
+
}
|
|
353
|
+
// Add manual entry option at the end
|
|
354
|
+
options[len(models)] = huh.NewOption("Enter model ID manually...", manualEntryKey)
|
|
355
|
+
|
|
356
|
+
form := huh.NewForm(
|
|
357
|
+
huh.NewGroup(
|
|
358
|
+
huh.NewSelect[string]().
|
|
359
|
+
Title("Select a model").
|
|
360
|
+
Options(options...).
|
|
361
|
+
Height(calculateSelectHeight()).
|
|
362
|
+
Filtering(true).
|
|
363
|
+
Value(&selectedModel),
|
|
364
|
+
),
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
if err := form.Run(); err != nil {
|
|
368
|
+
return "", fmt.Errorf("failed to select model: %w", err)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// If user selected manual entry, return special key to trigger manual input
|
|
372
|
+
if selectedModel == manualEntryKey {
|
|
373
|
+
return manualEntryKey, nil
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return selectedModel, nil
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// manualModelEntry prompts user to manually enter a model ID.
|
|
380
|
+
// Returns the model ID and an error. The modelInfo is always nil for manual entry.
|
|
381
|
+
func (pw *ProviderWizard) manualModelEntry(provider cline.ApiProvider) (string, interface{}, error) {
|
|
382
|
+
var modelID string
|
|
383
|
+
modelPlaceholder := GetBYOProviderPlaceholder(provider)
|
|
384
|
+
|
|
385
|
+
form := huh.NewForm(
|
|
386
|
+
huh.NewGroup(
|
|
387
|
+
huh.NewInput().
|
|
388
|
+
Title("Model ID").
|
|
389
|
+
Placeholder(modelPlaceholder).
|
|
390
|
+
Value(&modelID).
|
|
391
|
+
Validate(func(s string) error {
|
|
392
|
+
// Trim whitespace and validate
|
|
393
|
+
trimmed := strings.TrimSpace(s)
|
|
394
|
+
if trimmed == "" {
|
|
395
|
+
return fmt.Errorf("model ID cannot be empty")
|
|
396
|
+
}
|
|
397
|
+
return nil
|
|
398
|
+
}),
|
|
399
|
+
),
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
if err := form.Run(); err != nil {
|
|
403
|
+
return "", nil, fmt.Errorf("failed to get model ID: %w", err)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Trim whitespace from the final value
|
|
407
|
+
modelID = strings.TrimSpace(modelID)
|
|
408
|
+
|
|
409
|
+
// modelInfo is always nil for manual entry
|
|
410
|
+
return modelID, nil, nil
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// handleChangeModel allows changing the model for any configured provider
|
|
414
|
+
func (pw *ProviderWizard) handleChangeModel() error {
|
|
415
|
+
// Step 1: Get current provider configurations
|
|
416
|
+
result, err := GetProviderConfigurations(pw.ctx, pw.manager)
|
|
417
|
+
if err != nil {
|
|
418
|
+
return fmt.Errorf("failed to retrieve provider configurations: %w", err)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Step 2: Get all configured providers with models
|
|
422
|
+
readyProviders := result.GetAllReadyProviders()
|
|
423
|
+
|
|
424
|
+
// Filter out Cline provider (it has its own model changer in the main menu)
|
|
425
|
+
var configurableProviders []*ProviderDisplay
|
|
426
|
+
for _, provider := range readyProviders {
|
|
427
|
+
if provider.Provider != cline.ApiProvider_CLINE {
|
|
428
|
+
configurableProviders = append(configurableProviders, provider)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Step 3: Check if there are any configurable providers
|
|
433
|
+
if len(configurableProviders) == 0 {
|
|
434
|
+
fmt.Println("\nNo configurable providers found.")
|
|
435
|
+
fmt.Println("Note: Cline provider has its own model selection in the main menu.")
|
|
436
|
+
return nil
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Step 4: Let user select which provider to change the model for
|
|
440
|
+
var selectedIndex int
|
|
441
|
+
options := make([]huh.Option[int], len(configurableProviders)+1)
|
|
442
|
+
for i, providerDisplay := range configurableProviders {
|
|
443
|
+
displayName := fmt.Sprintf("%s (current: %s)",
|
|
444
|
+
GetProviderDisplayName(providerDisplay.Provider),
|
|
445
|
+
providerDisplay.ModelID)
|
|
446
|
+
options[i] = huh.NewOption(displayName, i)
|
|
447
|
+
}
|
|
448
|
+
options[len(configurableProviders)] = huh.NewOption("(Cancel)", -1)
|
|
449
|
+
|
|
450
|
+
form := huh.NewForm(
|
|
451
|
+
huh.NewGroup(
|
|
452
|
+
huh.NewSelect[int]().
|
|
453
|
+
Title("Select provider to change model for").
|
|
454
|
+
Options(options...).
|
|
455
|
+
Value(&selectedIndex),
|
|
456
|
+
),
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
if err := form.Run(); err != nil {
|
|
460
|
+
return fmt.Errorf("failed to select provider: %w", err)
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if selectedIndex == -1 {
|
|
464
|
+
return nil
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
selectedProvider := configurableProviders[selectedIndex]
|
|
468
|
+
provider := selectedProvider.Provider
|
|
469
|
+
|
|
470
|
+
fmt.Printf("\nChanging model for %s\n", GetProviderDisplayName(provider))
|
|
471
|
+
fmt.Printf("Current model: %s\n\n", selectedProvider.ModelID)
|
|
472
|
+
|
|
473
|
+
// Step 5: Retrieve API key if needed for model fetching
|
|
474
|
+
var apiKey string
|
|
475
|
+
if pw.supportsModelFetching(provider) {
|
|
476
|
+
// For providers that support fetching, we need to retrieve the API key from state
|
|
477
|
+
state, err := pw.manager.GetClient().State.GetLatestState(pw.ctx, &cline.EmptyRequest{})
|
|
478
|
+
if err != nil {
|
|
479
|
+
return fmt.Errorf("failed to get state: %w", err)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
var stateData map[string]interface{}
|
|
483
|
+
if err := json.Unmarshal([]byte(state.StateJson), &stateData); err != nil {
|
|
484
|
+
return fmt.Errorf("failed to parse state JSON: %w", err)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
apiConfig, ok := stateData["apiConfiguration"].(map[string]interface{})
|
|
488
|
+
if !ok {
|
|
489
|
+
return fmt.Errorf("no API configuration found in state")
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
apiKey = getProviderAPIKeyFromState(apiConfig, provider)
|
|
493
|
+
if apiKey == "" {
|
|
494
|
+
return fmt.Errorf("no API key found for provider %s", GetProviderDisplayName(provider))
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
modelID, modelInfo, err := pw.selectModel(provider, apiKey)
|
|
499
|
+
if err != nil {
|
|
500
|
+
return fmt.Errorf("model selection failed: %w", err)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Step 6: Apply the model change (for both Plan and Act modes)
|
|
504
|
+
if err := pw.applyModelChange(provider, modelID, modelInfo); err != nil {
|
|
505
|
+
return fmt.Errorf("failed to apply model change: %w", err)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
fmt.Printf("✓ Model changed successfully to: %s\n", modelID)
|
|
509
|
+
fmt.Println(" (Applied to both Plan and Act modes)")
|
|
510
|
+
return nil
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// applyModelChange applies a model change for both Plan and Act modes using UpdateProviderPartial
|
|
514
|
+
func (pw *ProviderWizard) applyModelChange(provider cline.ApiProvider, modelID string, modelInfo interface{}) error {
|
|
515
|
+
updates := ProviderUpdatesPartial{
|
|
516
|
+
ModelID: &modelID,
|
|
517
|
+
ModelInfo: modelInfo,
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return UpdateProviderPartial(pw.ctx, pw.manager, provider, updates, true)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// SwitchToBYOProvider switches to a BYO provider that's already configured.
|
|
524
|
+
// It retrieves the existing model configuration and sets it as the active provider for both Plan and Act modes.
|
|
525
|
+
func SwitchToBYOProvider(ctx context.Context, manager *task.Manager, provider cline.ApiProvider) error {
|
|
526
|
+
// Get the current state to retrieve the model ID and model info for this provider
|
|
527
|
+
state, err := manager.GetClient().State.GetLatestState(ctx, &cline.EmptyRequest{})
|
|
528
|
+
if err != nil {
|
|
529
|
+
return fmt.Errorf("failed to get state: %w", err)
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Parse state JSON
|
|
533
|
+
var stateData map[string]interface{}
|
|
534
|
+
if err := json.Unmarshal([]byte(state.StateJson), &stateData); err != nil {
|
|
535
|
+
return fmt.Errorf("failed to parse state JSON: %w", err)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Extract apiConfiguration
|
|
539
|
+
apiConfig, ok := stateData["apiConfiguration"].(map[string]interface{})
|
|
540
|
+
if !ok {
|
|
541
|
+
return fmt.Errorf("no API configuration found in state")
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Get the model ID for the selected provider
|
|
545
|
+
modelID := getProviderModelIDFromState(apiConfig, provider)
|
|
546
|
+
if modelID == "" {
|
|
547
|
+
return fmt.Errorf("no model configured for provider %s", GetProviderDisplayName(provider))
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Get model info if available (for OpenRouter/Cline)
|
|
551
|
+
var modelInfo interface{}
|
|
552
|
+
if provider == cline.ApiProvider_OPENROUTER || provider == cline.ApiProvider_CLINE {
|
|
553
|
+
if modelInfoData, ok := apiConfig["planModeOpenRouterModelInfo"].(map[string]interface{}); ok {
|
|
554
|
+
modelInfo = convertMapToOpenRouterModelInfo(modelInfoData)
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Use UpdateProviderPartial to switch to this provider
|
|
559
|
+
updates := ProviderUpdatesPartial{
|
|
560
|
+
ModelID: &modelID,
|
|
561
|
+
ModelInfo: modelInfo,
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if err := UpdateProviderPartial(ctx, manager, provider, updates, true); err != nil {
|
|
565
|
+
return fmt.Errorf("failed to switch provider: %w", err)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
verboseLog("✓ Switched to %s\n", GetProviderDisplayName(provider))
|
|
569
|
+
verboseLog(" Using model: %s\n", modelID)
|
|
570
|
+
|
|
571
|
+
return HandleAuthMenuNoArgs(ctx)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// getProviderModelIDFromState retrieves the model ID for a specific provider from state
|
|
575
|
+
func getProviderModelIDFromState(stateData map[string]interface{}, provider cline.ApiProvider) string {
|
|
576
|
+
modelKey, err := GetModelIDFieldName(provider, "plan")
|
|
577
|
+
if err != nil {
|
|
578
|
+
return ""
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if modelID, ok := stateData[modelKey].(string); ok {
|
|
582
|
+
return modelID
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
return ""
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// getProviderAPIKeyFromState retrieves the API key for a specific provider from state
|
|
589
|
+
func getProviderAPIKeyFromState(stateData map[string]interface{}, provider cline.ApiProvider) string {
|
|
590
|
+
// OCA uses account authentication, not API keys. Consider it "present" if authenticated.
|
|
591
|
+
if provider == cline.ApiProvider_OCA {
|
|
592
|
+
if state, _ := GetLatestOCAState(context.TODO(), 2 * time.Second); state != nil && state.User != nil {
|
|
593
|
+
// Return a sentinel non-empty string so upstream checks pass.
|
|
594
|
+
return "OCA_AUTH_VERIFIED"
|
|
595
|
+
}
|
|
596
|
+
return ""
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
fields, err := GetProviderFields(provider)
|
|
600
|
+
if err != nil {
|
|
601
|
+
return ""
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if apiKey, ok := stateData[fields.APIKeyField].(string); ok {
|
|
605
|
+
return apiKey
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return ""
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// convertMapToOpenRouterModelInfo converts a map to OpenRouterModelInfo
|
|
612
|
+
func convertMapToOpenRouterModelInfo(data map[string]interface{}) *cline.OpenRouterModelInfo {
|
|
613
|
+
info := &cline.OpenRouterModelInfo{}
|
|
614
|
+
|
|
615
|
+
if val, ok := data["description"].(string); ok {
|
|
616
|
+
info.Description = &val
|
|
617
|
+
}
|
|
618
|
+
if val, ok := data["contextWindow"].(float64); ok {
|
|
619
|
+
contextWindow := int64(val)
|
|
620
|
+
info.ContextWindow = &contextWindow
|
|
621
|
+
}
|
|
622
|
+
if val, ok := data["maxTokens"].(float64); ok {
|
|
623
|
+
maxTokens := int64(val)
|
|
624
|
+
info.MaxTokens = &maxTokens
|
|
625
|
+
}
|
|
626
|
+
if val, ok := data["inputPrice"].(float64); ok {
|
|
627
|
+
info.InputPrice = &val
|
|
628
|
+
}
|
|
629
|
+
if val, ok := data["outputPrice"].(float64); ok {
|
|
630
|
+
info.OutputPrice = &val
|
|
631
|
+
}
|
|
632
|
+
if val, ok := data["cacheWritesPrice"].(float64); ok {
|
|
633
|
+
info.CacheWritesPrice = &val
|
|
634
|
+
}
|
|
635
|
+
if val, ok := data["cacheReadsPrice"].(float64); ok {
|
|
636
|
+
info.CacheReadsPrice = &val
|
|
637
|
+
}
|
|
638
|
+
if val, ok := data["supportsImages"].(bool); ok {
|
|
639
|
+
info.SupportsImages = &val
|
|
640
|
+
}
|
|
641
|
+
if val, ok := data["supportsPromptCache"].(bool); ok {
|
|
642
|
+
info.SupportsPromptCache = val
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return info
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// handleRemoveProvider allows removing a configured provider by clearing its API key
|
|
649
|
+
func (pw *ProviderWizard) handleRemoveProvider() error {
|
|
650
|
+
// Step 1: Get current provider configurations
|
|
651
|
+
result, err := GetProviderConfigurations(pw.ctx, pw.manager)
|
|
652
|
+
if err != nil {
|
|
653
|
+
return fmt.Errorf("failed to retrieve provider configurations: %w", err)
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Step 2: Get all ready providers
|
|
657
|
+
readyProviders := result.GetAllReadyProviders()
|
|
658
|
+
|
|
659
|
+
// Filter out Cline provider (uses account auth, not API keys)
|
|
660
|
+
var removableProviders []*ProviderDisplay
|
|
661
|
+
for _, provider := range readyProviders {
|
|
662
|
+
if provider.Provider != cline.ApiProvider_CLINE {
|
|
663
|
+
removableProviders = append(removableProviders, provider)
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Step 3: Check if there are providers to remove
|
|
668
|
+
if len(removableProviders) == 0 {
|
|
669
|
+
fmt.Println("\nNo providers available to remove.")
|
|
670
|
+
fmt.Println("Note: Cline provider cannot be removed via this menu.")
|
|
671
|
+
return nil
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Step 4: Display selection menu
|
|
675
|
+
var selectedIndex int
|
|
676
|
+
options := make([]huh.Option[int], len(removableProviders))
|
|
677
|
+
for i, provider := range removableProviders {
|
|
678
|
+
// Mark active provider
|
|
679
|
+
displayName := GetProviderDisplayName(provider.Provider)
|
|
680
|
+
if result.ActProvider != nil && provider.Provider == result.ActProvider.Provider {
|
|
681
|
+
displayName += " (ACTIVE)"
|
|
682
|
+
}
|
|
683
|
+
options[i] = huh.NewOption(displayName, i)
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
form := huh.NewForm(
|
|
687
|
+
huh.NewGroup(
|
|
688
|
+
huh.NewSelect[int]().
|
|
689
|
+
Title("Select provider to remove").
|
|
690
|
+
Options(options...).
|
|
691
|
+
Value(&selectedIndex),
|
|
692
|
+
),
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
if err := form.Run(); err != nil {
|
|
696
|
+
return fmt.Errorf("failed to select provider: %w", err)
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
selectedProvider := removableProviders[selectedIndex]
|
|
700
|
+
|
|
701
|
+
// Step 5: Check if trying to remove the active provider
|
|
702
|
+
if result.ActProvider != nil && selectedProvider.Provider == result.ActProvider.Provider {
|
|
703
|
+
fmt.Printf("\nCannot remove %s because it is currently active.\n", GetProviderDisplayName(selectedProvider.Provider))
|
|
704
|
+
fmt.Println("Please switch to a different provider first, then try again.")
|
|
705
|
+
return nil
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Step 6: Confirm removal
|
|
709
|
+
var confirm bool
|
|
710
|
+
confirmForm := huh.NewForm(
|
|
711
|
+
huh.NewGroup(
|
|
712
|
+
huh.NewConfirm().
|
|
713
|
+
Title(fmt.Sprintf("Are you sure you want to remove %s?", GetProviderDisplayName(selectedProvider.Provider))).
|
|
714
|
+
Description("This will clear the API key but preserve the model configuration.").
|
|
715
|
+
Value(&confirm),
|
|
716
|
+
),
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
if err := confirmForm.Run(); err != nil {
|
|
720
|
+
return fmt.Errorf("failed to get confirmation: %w", err)
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if !confirm {
|
|
724
|
+
fmt.Println("Removal cancelled.")
|
|
725
|
+
return nil
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Step 7: If removing OCA, sign out first
|
|
729
|
+
if selectedProvider.Provider == cline.ApiProvider_OCA {
|
|
730
|
+
if err := signOutOca(pw.ctx); err != nil {
|
|
731
|
+
fmt.Printf("Warning: Failed to sign out of OCA: %v\n", err)
|
|
732
|
+
} else {
|
|
733
|
+
fmt.Println("Signed out of OCA.")
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Step 8: Clear the API key for the selected provider
|
|
738
|
+
if err := pw.clearProviderAPIKey(selectedProvider.Provider); err != nil {
|
|
739
|
+
return fmt.Errorf("failed to remove provider: %w", err)
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
fmt.Printf("\n✓ %s removed successfully\n", GetProviderDisplayName(selectedProvider.Provider))
|
|
743
|
+
return nil
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// clearProviderAPIKey clears the API key field for a specific provider using RemoveProviderPartial
|
|
747
|
+
func (pw *ProviderWizard) clearProviderAPIKey(provider cline.ApiProvider) error {
|
|
748
|
+
return RemoveProviderPartial(pw.ctx, pw.manager, provider)
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
func signOutOca(ctx context.Context) error {
|
|
753
|
+
client, err := global.GetDefaultClient(ctx)
|
|
754
|
+
if err != nil {
|
|
755
|
+
return err
|
|
756
|
+
}
|
|
757
|
+
_, err = client.Ocaaccount.OcaAccountLogoutClicked(ctx, &cline.EmptyRequest{})
|
|
758
|
+
return err
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
func setWelcomeViewCompleted(ctx context.Context, manager *task.Manager) error {
|
|
762
|
+
_, err := manager.GetClient().State.SetWelcomeViewCompleted(ctx, &cline.BooleanRequest{Value: true})
|
|
763
|
+
return err
|
|
764
|
+
}
|