@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.
Files changed (85) hide show
  1. package/.npmrc.tmp +2 -0
  2. package/README.md +72 -0
  3. package/cmd/cline/main.go +348 -0
  4. package/cmd/cline-host/main.go +71 -0
  5. package/e2e/default_update_test.go +154 -0
  6. package/e2e/helpers_test.go +378 -0
  7. package/e2e/main_test.go +47 -0
  8. package/e2e/mixed_stress_test.go +120 -0
  9. package/e2e/sqlite_helper.go +161 -0
  10. package/e2e/start_list_test.go +178 -0
  11. package/go.mod +64 -0
  12. package/go.sum +162 -0
  13. package/man/cline.1 +331 -0
  14. package/man/cline.1.md +332 -0
  15. package/package.json +54 -0
  16. package/pkg/cli/auth/auth_cline_provider.go +285 -0
  17. package/pkg/cli/auth/auth_menu.go +323 -0
  18. package/pkg/cli/auth/auth_subscription.go +130 -0
  19. package/pkg/cli/auth/byo_quick_setup.go +247 -0
  20. package/pkg/cli/auth/models_cline.go +141 -0
  21. package/pkg/cli/auth/models_list_fetch.go +156 -0
  22. package/pkg/cli/auth/models_list_static.go +69 -0
  23. package/pkg/cli/auth/providers_byo.go +184 -0
  24. package/pkg/cli/auth/providers_list.go +517 -0
  25. package/pkg/cli/auth/update_api_configurations.go +647 -0
  26. package/pkg/cli/auth/wizard_byo.go +764 -0
  27. package/pkg/cli/auth/wizard_byo_bedrock.go +193 -0
  28. package/pkg/cli/auth/wizard_byo_oca.go +366 -0
  29. package/pkg/cli/auth.go +43 -0
  30. package/pkg/cli/clerror/cline_error.go +187 -0
  31. package/pkg/cli/config/manager.go +208 -0
  32. package/pkg/cli/config/settings_renderer.go +198 -0
  33. package/pkg/cli/config.go +152 -0
  34. package/pkg/cli/display/ansi.go +27 -0
  35. package/pkg/cli/display/banner.go +211 -0
  36. package/pkg/cli/display/deduplicator.go +95 -0
  37. package/pkg/cli/display/markdown_renderer.go +139 -0
  38. package/pkg/cli/display/renderer.go +304 -0
  39. package/pkg/cli/display/segment_streamer.go +212 -0
  40. package/pkg/cli/display/streaming.go +134 -0
  41. package/pkg/cli/display/system_renderer.go +269 -0
  42. package/pkg/cli/display/tool_renderer.go +455 -0
  43. package/pkg/cli/display/tool_result_parser.go +371 -0
  44. package/pkg/cli/display/typewriter.go +210 -0
  45. package/pkg/cli/doctor.go +65 -0
  46. package/pkg/cli/global/cline-clients.go +501 -0
  47. package/pkg/cli/global/global.go +113 -0
  48. package/pkg/cli/global/registry.go +304 -0
  49. package/pkg/cli/handlers/ask_handlers.go +339 -0
  50. package/pkg/cli/handlers/handler.go +130 -0
  51. package/pkg/cli/handlers/say_handlers.go +521 -0
  52. package/pkg/cli/instances.go +506 -0
  53. package/pkg/cli/logs.go +382 -0
  54. package/pkg/cli/output/coordinator.go +167 -0
  55. package/pkg/cli/output/input_model.go +497 -0
  56. package/pkg/cli/sqlite/locks.go +366 -0
  57. package/pkg/cli/task/history_handler.go +72 -0
  58. package/pkg/cli/task/input_handler.go +577 -0
  59. package/pkg/cli/task/manager.go +1283 -0
  60. package/pkg/cli/task/settings_parser.go +754 -0
  61. package/pkg/cli/task/stream_coordinator.go +60 -0
  62. package/pkg/cli/task.go +675 -0
  63. package/pkg/cli/terminal/keyboard.go +695 -0
  64. package/pkg/cli/tui/HELP_WANTED.md +1 -0
  65. package/pkg/cli/types/history.go +17 -0
  66. package/pkg/cli/types/messages.go +329 -0
  67. package/pkg/cli/types/state.go +59 -0
  68. package/pkg/cli/updater/updater.go +409 -0
  69. package/pkg/cli/version.go +43 -0
  70. package/pkg/common/constants.go +6 -0
  71. package/pkg/common/schema.go +54 -0
  72. package/pkg/common/types.go +54 -0
  73. package/pkg/common/utils.go +185 -0
  74. package/pkg/generated/field_overrides.go +39 -0
  75. package/pkg/generated/providers.go +1584 -0
  76. package/pkg/hostbridge/diff.go +351 -0
  77. package/pkg/hostbridge/disabled/watch.go +39 -0
  78. package/pkg/hostbridge/disabled/window.go +63 -0
  79. package/pkg/hostbridge/disabled/workspace.go +66 -0
  80. package/pkg/hostbridge/env.go +166 -0
  81. package/pkg/hostbridge/grpc_server.go +113 -0
  82. package/pkg/hostbridge/simple.go +43 -0
  83. package/pkg/hostbridge/simple_workspace.go +85 -0
  84. package/pkg/hostbridge/window.go +129 -0
  85. 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
+ }