@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,193 @@
1
+ package auth
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "strings"
7
+
8
+ "github.com/charmbracelet/huh"
9
+ "github.com/cline/cli/pkg/cli/task"
10
+ "github.com/cline/grpc-go/cline"
11
+ "google.golang.org/protobuf/proto"
12
+ "google.golang.org/protobuf/types/known/fieldmaskpb"
13
+ )
14
+
15
+ // BedrockConfig holds all AWS Bedrock-specific configuration fields
16
+ type BedrockConfig struct {
17
+ // Profile authentication fields
18
+ UseProfile bool // Always true for successful config
19
+ Profile string // Optional: AWS profile name (empty = default)
20
+ Region string // Required: AWS region
21
+ Endpoint string // Optional: Custom VPC endpoint URL
22
+
23
+ // Optional features
24
+ UseCrossRegionInference bool // Optional: Enable cross-region inference
25
+ UseGlobalInference bool // Optional: Use global inference endpoint
26
+ UsePromptCache bool // Optional: Enable prompt caching
27
+
28
+ // Authentication method (always "profile")
29
+ Authentication string // Always set to "profile"
30
+
31
+ // Legacy fields (no longer used in profile-only flow)
32
+ AccessKey string // No longer used
33
+ SecretKey string // No longer used
34
+ SessionToken string // No longer used
35
+ }
36
+
37
+ // PromptForBedrockConfig displays a profile-first authentication form for Bedrock configuration
38
+ func PromptForBedrockConfig(ctx context.Context, manager *task.Manager) (*BedrockConfig, error) {
39
+ config := &BedrockConfig{}
40
+
41
+ // First, ask if user wants to use AWS profile authentication
42
+ var useProfile bool
43
+ profileQuestion := huh.NewForm(
44
+ huh.NewGroup(
45
+ huh.NewConfirm().
46
+ Title("Do you want to use an AWS profile for authentication?").
47
+ Description("AWS profiles are managed via 'aws configure'").
48
+ Value(&useProfile).
49
+ Affirmative("Yes").
50
+ Negative("No").
51
+ Inline(true),
52
+ ),
53
+ )
54
+
55
+ if err := profileQuestion.Run(); err != nil {
56
+ return nil, fmt.Errorf("failed to get authentication method: %w", err)
57
+ }
58
+
59
+ // If user declines profile authentication, show message and return error
60
+ if !useProfile {
61
+ fmt.Println("\nAWS profile authentication is currently the only supported method in the CLI.")
62
+ fmt.Println("Please configure an AWS profile using 'aws configure' and try again.")
63
+ return nil, fmt.Errorf("user declined profile authentication")
64
+ }
65
+
66
+ // User wants profile auth - collect profile configuration
67
+ config.UseProfile = true
68
+ config.Authentication = "profile"
69
+
70
+ // Collect profile name, region, and optional settings
71
+ configForm := huh.NewForm(
72
+ huh.NewGroup(
73
+ huh.NewInput().
74
+ Title("AWS Profile Name (optional, press Enter for default profile)").
75
+ Value(&config.Profile).
76
+ Description("Leave empty to use default AWS profile"),
77
+
78
+ huh.NewInput().
79
+ Title("AWS Region (required, e.g., us-east-1)").
80
+ Value(&config.Region).
81
+ Validate(func(s string) error {
82
+ if strings.TrimSpace(s) == "" {
83
+ return fmt.Errorf("AWS Region is required")
84
+ }
85
+ return nil
86
+ }),
87
+
88
+ huh.NewInput().
89
+ Title("Custom VPC Endpoint URL (optional)").
90
+ Value(&config.Endpoint).
91
+ Description("Press Enter to skip"),
92
+
93
+ huh.NewConfirm().
94
+ Title("Enable Prompt Cache? ").
95
+ Value(&config.UsePromptCache).
96
+ Affirmative("Yes").
97
+ Negative("No").
98
+ Inline(true),
99
+
100
+ huh.NewConfirm().
101
+ Title("Enable Cross-Region Inference? ").
102
+ Value(&config.UseCrossRegionInference).
103
+ Affirmative("Yes").
104
+ Negative("No").
105
+ Inline(true),
106
+
107
+ huh.NewConfirm().
108
+ Title("Use Global Inference Endpoint? ").
109
+ Value(&config.UseGlobalInference).
110
+ Affirmative("Yes").
111
+ Negative("No").
112
+ Inline(true),
113
+ ),
114
+ )
115
+
116
+ if err := configForm.Run(); err != nil {
117
+ return nil, fmt.Errorf("failed to get Bedrock configuration: %w", err)
118
+ }
119
+
120
+ // Trim whitespace from string fields
121
+ config.Profile = strings.TrimSpace(config.Profile)
122
+ config.Region = strings.TrimSpace(config.Region)
123
+ config.Endpoint = strings.TrimSpace(config.Endpoint)
124
+
125
+ return config, nil
126
+ }
127
+
128
+ // ApplyBedrockConfig applies Bedrock configuration using partial updates (profile-only)
129
+ func ApplyBedrockConfig(ctx context.Context, manager *task.Manager, config *BedrockConfig, modelID string, modelInfo interface{}) error {
130
+ // Build the API configuration with all Bedrock fields
131
+ apiConfig := &cline.ModelsApiConfiguration{}
132
+
133
+ // Set model ID fields
134
+ apiConfig.PlanModeApiModelId = proto.String(modelID)
135
+ apiConfig.ActModeApiModelId = proto.String(modelID)
136
+ apiConfig.PlanModeAwsBedrockCustomModelBaseId = proto.String(modelID)
137
+ apiConfig.ActModeAwsBedrockCustomModelBaseId = proto.String(modelID)
138
+
139
+ // Set profile authentication fields (always required)
140
+ optionalFields := &BedrockOptionalFields{}
141
+ optionalFields.Authentication = proto.String("profile")
142
+ optionalFields.UseProfile = proto.Bool(true)
143
+ optionalFields.Region = proto.String(config.Region)
144
+
145
+ // Set profile name (can be empty for default profile)
146
+ if config.Profile != "" {
147
+ optionalFields.Profile = proto.String(config.Profile)
148
+ }
149
+
150
+ // Set optional fields if provided
151
+ if config.Endpoint != "" {
152
+ optionalFields.Endpoint = proto.String(config.Endpoint)
153
+ }
154
+ if config.UseCrossRegionInference {
155
+ optionalFields.UseCrossRegionInference = proto.Bool(true)
156
+ }
157
+ if config.UseGlobalInference {
158
+ optionalFields.UseGlobalInference = proto.Bool(true)
159
+ }
160
+ if config.UsePromptCache {
161
+ optionalFields.UsePromptCache = proto.Bool(true)
162
+ }
163
+
164
+ // Apply all fields to the config
165
+ setBedrockOptionalFields(apiConfig, optionalFields)
166
+
167
+ // Build field mask including all fields we're setting (excluding access keys)
168
+ fieldPaths := []string{
169
+ "planModeApiModelId",
170
+ "actModeApiModelId",
171
+ "planModeAwsBedrockCustomModelBaseId",
172
+ "actModeAwsBedrockCustomModelBaseId",
173
+ }
174
+
175
+ // Add profile authentication field paths
176
+ optionalPaths := buildBedrockOptionalFieldMask(optionalFields)
177
+ fieldPaths = append(fieldPaths, optionalPaths...)
178
+
179
+ // Create field mask
180
+ fieldMask := &fieldmaskpb.FieldMask{Paths: fieldPaths}
181
+
182
+ // Apply the partial update
183
+ request := &cline.UpdateApiConfigurationPartialRequest{
184
+ ApiConfiguration: apiConfig,
185
+ UpdateMask: fieldMask,
186
+ }
187
+
188
+ if err := updateApiConfigurationPartial(ctx, manager, request); err != nil {
189
+ return fmt.Errorf("failed to apply Bedrock configuration: %w", err)
190
+ }
191
+
192
+ return nil
193
+ }
@@ -0,0 +1,366 @@
1
+ package auth
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "io"
7
+ "strings"
8
+ "sync"
9
+ "time"
10
+
11
+ "github.com/charmbracelet/huh"
12
+ "github.com/cline/cli/pkg/cli/global"
13
+ "github.com/cline/cli/pkg/cli/task"
14
+ "github.com/cline/grpc-go/cline"
15
+ "google.golang.org/protobuf/proto"
16
+ "google.golang.org/protobuf/types/known/fieldmaskpb"
17
+ )
18
+
19
+ // OcaConfig holds Oracle Code Assist (OCA) configuration fields
20
+ type OcaConfig struct {
21
+ BaseURL string
22
+ Mode string
23
+ }
24
+
25
+ // PromptForOcaConfig displays a form for OCA configuration (base URL and mode)
26
+ func PromptForOcaConfig(ctx context.Context, manager *task.Manager) (*OcaConfig, error) {
27
+ config := &OcaConfig{}
28
+ var mode string
29
+
30
+ // Collect optional settings
31
+ configForm := huh.NewForm(
32
+ huh.NewGroup(
33
+ huh.NewInput().
34
+ Title("Base URL").
35
+ Value(&config.BaseURL).
36
+ Description("Leave empty to use default Base URL"),
37
+
38
+ huh.NewSelect[string]().
39
+ Title("Choose OCA mode (used for authentication)").
40
+ Description("Select 'Internal' to use Cline's internal OCA, or 'External' for your own OCA instance").
41
+ Options(
42
+ huh.NewOption("Internal", "internal"),
43
+ huh.NewOption("External", "external"),
44
+ ).
45
+ Value(&mode),
46
+ ),
47
+ )
48
+
49
+ if err := configForm.Run(); err != nil {
50
+ return nil, fmt.Errorf("failed to get OCA configuration: %w", err)
51
+ }
52
+
53
+ // Trim whitespace from string fields
54
+ config.BaseURL = strings.TrimSpace(config.BaseURL)
55
+ config.Mode = strings.TrimSpace(mode)
56
+
57
+ return config, nil
58
+ }
59
+
60
+ // ApplyOcaConfig applies OCA configuration using partial updates
61
+ func ApplyOcaConfig(ctx context.Context, manager *task.Manager, config *OcaConfig) error {
62
+ // Build the API configuration with all OCA fields
63
+ apiConfig := &cline.ModelsApiConfiguration{}
64
+
65
+ // Set profile authentication fields (always required)
66
+ optionalFields := &OcaOptionalFields{}
67
+
68
+ // Set profile name (can be empty for default profile)
69
+ if config.BaseURL != "" {
70
+ optionalFields.BaseURL = proto.String(config.BaseURL)
71
+ }
72
+
73
+ // Set optional fields if provided
74
+ if config.Mode != "" {
75
+ optionalFields.Mode = proto.String(config.Mode)
76
+ }
77
+
78
+ // Apply all fields to the config
79
+ setOcaOptionalFields(apiConfig, optionalFields)
80
+
81
+ // Add profile authentication field paths
82
+ optionalPaths := buildOcaOptionalFieldMask(optionalFields)
83
+
84
+ // Create field mask
85
+ fieldMask := &fieldmaskpb.FieldMask{Paths: optionalPaths}
86
+
87
+ // Apply the partial update
88
+ request := &cline.UpdateApiConfigurationPartialRequest{
89
+ ApiConfiguration: apiConfig,
90
+ UpdateMask: fieldMask,
91
+ }
92
+
93
+ if err := updateApiConfigurationPartial(ctx, manager, request); err != nil {
94
+ return fmt.Errorf("failed to apply OCA configuration: %w", err)
95
+ }
96
+
97
+ return nil
98
+ }
99
+
100
+ // ===========================
101
+ // OCA Auth Listener Singleton
102
+ // ===========================
103
+
104
+ type ocaAuthStream interface {
105
+ Recv() (*cline.OcaAuthState, error)
106
+ }
107
+
108
+ // OcaAuthStatusListener manages subscription to OCA auth status updates
109
+ type OcaAuthStatusListener struct {
110
+ stream ocaAuthStream
111
+ updatesCh chan *cline.OcaAuthState
112
+ errCh chan error
113
+ ctx context.Context
114
+ cancel context.CancelFunc
115
+ mu sync.RWMutex
116
+ lastState *cline.OcaAuthState
117
+ firstEventCh chan struct{}
118
+ firstEventOnce sync.Once
119
+ }
120
+
121
+ // NewOcaAuthStatusListener creates a new OCA auth status listener
122
+ func NewOcaAuthStatusListener(parentCtx context.Context) (*OcaAuthStatusListener, error) {
123
+ client, err := global.GetDefaultClient(parentCtx)
124
+ if err != nil {
125
+ return nil, fmt.Errorf("failed to get client: %w", err)
126
+ }
127
+
128
+ // Keep the listener alive independently of short-lived caller contexts
129
+ ctx, cancel := context.WithCancel(context.Background())
130
+
131
+ // Subscribe to OCA auth status updates
132
+ stream, err := client.Ocaaccount.OcaSubscribeToAuthStatusUpdate(ctx, &cline.EmptyRequest{})
133
+ if err != nil {
134
+ cancel()
135
+ return nil, fmt.Errorf("failed to subscribe to OCA auth updates: %w", err)
136
+ }
137
+
138
+ return &OcaAuthStatusListener{
139
+ stream: stream,
140
+ updatesCh: make(chan *cline.OcaAuthState, 10),
141
+ errCh: make(chan error, 1),
142
+ ctx: ctx,
143
+ cancel: cancel,
144
+ firstEventCh: make(chan struct{}),
145
+ }, nil
146
+ }
147
+
148
+ // Start begins listening to the auth status update stream
149
+ func (l *OcaAuthStatusListener) Start() error {
150
+ go l.readStream()
151
+ return nil
152
+ }
153
+
154
+ func (l *OcaAuthStatusListener) readStream() {
155
+ defer close(l.updatesCh)
156
+ defer close(l.errCh)
157
+
158
+ for {
159
+ select {
160
+ case <-l.ctx.Done():
161
+ return
162
+ default:
163
+ state, err := l.stream.Recv()
164
+ if err != nil {
165
+ // Propagate error and exit
166
+ if err == io.EOF {
167
+ // Treat as error to notify waiters
168
+ err = fmt.Errorf("OCA auth status stream closed")
169
+ }
170
+ select {
171
+ case l.errCh <- err:
172
+ case <-l.ctx.Done():
173
+ }
174
+ return
175
+ }
176
+
177
+ l.mu.Lock()
178
+ l.lastState = state
179
+ l.mu.Unlock()
180
+
181
+ // Notify first event waiters
182
+ l.firstEventOnce.Do(func() { close(l.firstEventCh) })
183
+
184
+ select {
185
+ case l.updatesCh <- state:
186
+ case <-l.ctx.Done():
187
+ return
188
+ }
189
+ }
190
+ }
191
+ }
192
+
193
+ // WaitForFirstEvent blocks until the first event is received or timeout occurs
194
+ func (l *OcaAuthStatusListener) WaitForFirstEvent(timeout time.Duration) error {
195
+ // Fast-path if already have a state
196
+ l.mu.RLock()
197
+ ready := l.lastState != nil
198
+ l.mu.RUnlock()
199
+ if ready {
200
+ return nil
201
+ }
202
+
203
+ timer := time.NewTimer(timeout)
204
+ defer timer.Stop()
205
+
206
+ select {
207
+ case <-l.firstEventCh:
208
+ return nil
209
+ case <-timer.C:
210
+ return fmt.Errorf("timeout waiting for initial OCA auth event")
211
+ case <-l.ctx.Done():
212
+ return fmt.Errorf("OCA auth listener cancelled")
213
+ }
214
+ }
215
+
216
+ // IsAuthenticated returns true if the last known OCA auth state is authenticated
217
+ func (l *OcaAuthStatusListener) IsAuthenticated() bool {
218
+ l.mu.RLock()
219
+ defer l.mu.RUnlock()
220
+ return isOCAStateAuthenticated(l.lastState)
221
+ }
222
+
223
+ // WaitForAuthentication waits until OCA authentication succeeds or timeout occurs
224
+ func (l *OcaAuthStatusListener) WaitForAuthentication(timeout time.Duration) error {
225
+ timer := time.NewTimer(timeout)
226
+ defer timer.Stop()
227
+
228
+ // If already authenticated, return immediately
229
+ if l.IsAuthenticated() {
230
+ return nil
231
+ }
232
+
233
+ for {
234
+ select {
235
+ case <-timer.C:
236
+ return fmt.Errorf("OCA authentication timeout after %v - please try again", timeout)
237
+ case <-l.ctx.Done():
238
+ return fmt.Errorf("OCA authentication cancelled")
239
+ case err := <-l.errCh:
240
+ return fmt.Errorf("OCA authentication stream error: %w", err)
241
+ case state := <-l.updatesCh:
242
+ if isOCAStateAuthenticated(state) {
243
+ return nil
244
+ }
245
+ }
246
+ }
247
+ }
248
+
249
+ // Stop closes the stream and cleans up resources
250
+ func (l *OcaAuthStatusListener) Stop() {
251
+ l.cancel()
252
+ }
253
+
254
+ func isOCAStateAuthenticated(state *cline.OcaAuthState) bool {
255
+ return state != nil && state.User != nil
256
+ }
257
+
258
+ // Singleton holder
259
+ var (
260
+ ocaListener *OcaAuthStatusListener
261
+ ocaListenerOnce sync.Once
262
+ ocaListenerErr error
263
+ )
264
+
265
+ // GetOcaAuthListener returns the OCA auth listener singleton
266
+ func GetOcaAuthListener(ctx context.Context) (*OcaAuthStatusListener, error) {
267
+ // Allow optional ctx: if nil, use context.TODO(). If already initialized, return singleton.
268
+ if ctx == nil {
269
+ ctx = context.TODO()
270
+ }
271
+
272
+ ocaListenerOnce.Do(func() {
273
+ l, err := NewOcaAuthStatusListener(ctx)
274
+ if err != nil {
275
+ ocaListenerErr = err
276
+ return
277
+ }
278
+ if err := l.Start(); err != nil {
279
+ ocaListenerErr = err
280
+ return
281
+ }
282
+ ocaListener = l
283
+ })
284
+ return ocaListener, ocaListenerErr
285
+ }
286
+
287
+ // IsOCAAuthenticated returns true if the global OCA auth status is authenticated.
288
+ // It attempts a brief wait for the first event to avoid stale reads.
289
+ func IsOCAAuthenticated(ctx context.Context) bool {
290
+ l, err := GetOcaAuthListener(ctx)
291
+ if err != nil {
292
+ return false
293
+ }
294
+ _ = l.WaitForFirstEvent(1 * time.Second) // best-effort
295
+ return l.IsAuthenticated()
296
+ }
297
+
298
+ // LatestState returns the last received OCA auth state (may be nil)
299
+ func (l *OcaAuthStatusListener) LatestState() *cline.OcaAuthState {
300
+ l.mu.RLock()
301
+ defer l.mu.RUnlock()
302
+ return l.lastState
303
+ }
304
+
305
+ // GetLatestOCAState returns the latest known OCA auth state, optionally waiting for the first event
306
+ func GetLatestOCAState(ctx context.Context, timeout time.Duration) (*cline.OcaAuthState, error) {
307
+ l, err := GetOcaAuthListener(ctx)
308
+ if err != nil {
309
+ return nil, err
310
+ }
311
+ if timeout > 0 {
312
+ if err := l.WaitForFirstEvent(timeout); err != nil {
313
+ return nil, err
314
+ }
315
+ }
316
+ return l.LatestState(), nil
317
+ }
318
+
319
+ // ensureOcaAuthenticated initiates OCA login (if needed) and waits for success using the singleton listener
320
+ func ensureOcaAuthenticated(ctx context.Context) error {
321
+ // Ensure listener exists
322
+ listener, err := GetOcaAuthListener(ctx)
323
+ if err != nil {
324
+ return fmt.Errorf("failed to initialize OCA auth listener: %w", err)
325
+ }
326
+
327
+ // Briefly wait for first event to know current state
328
+ _ = listener.WaitForFirstEvent(1 * time.Second)
329
+
330
+ // If already authenticated, nothing to do
331
+ if listener.IsAuthenticated() {
332
+ fmt.Println("✓ OCA authentication already active.")
333
+ return nil
334
+ }
335
+
336
+ // Create gRPC client for initiating login
337
+ client, err := global.GetDefaultClient(ctx)
338
+ if err != nil {
339
+ return fmt.Errorf("failed to obtain client: %w", err)
340
+ }
341
+
342
+ // Start login and wait for authentication
343
+ waitCtx, cancel := context.WithTimeout(ctx, 5*time.Minute)
344
+ defer cancel()
345
+
346
+ // Initiate login (opens the browser with a callback URL from Cline Core)
347
+ response, err := client.Ocaaccount.OcaAccountLoginClicked(waitCtx, &cline.EmptyRequest{})
348
+ if err != nil {
349
+ return fmt.Errorf("failed to initiate OCA login: %w", err)
350
+ }
351
+
352
+ fmt.Println("\nOpening browser for OCA authentication...")
353
+ if response != nil && response.Value != "" {
354
+ fmt.Printf("If the browser doesn't open automatically, visit this URL:\n%s\n\n", response.Value)
355
+ }
356
+ fmt.Println("Waiting for you to complete OCA authentication in your browser...")
357
+ fmt.Println("(This may take a few moments. Timeout: 5 minutes)")
358
+
359
+ // Block until authenticated or timeout
360
+ if err := listener.WaitForAuthentication(5 * time.Minute); err != nil {
361
+ return err
362
+ }
363
+
364
+ fmt.Println("✓ OCA authentication successful!")
365
+ return nil
366
+ }
@@ -0,0 +1,43 @@
1
+ package cli
2
+
3
+ import (
4
+ "github.com/cline/cli/pkg/cli/auth"
5
+ "github.com/spf13/cobra"
6
+ )
7
+
8
+ func NewAuthCommand() *cobra.Command {
9
+ cmd := &cobra.Command{
10
+ Use: "auth",
11
+ Short: "Authenticate a provider and configure what model is used",
12
+ Long: `Authenticate a provider and configure what model is used
13
+
14
+ Interactive Mode:
15
+ Run without flags to open an interactive menu where you can:
16
+ - Sign in to your Cline account
17
+ - Configure other LLM providers (Anthropic, OpenAI, etc.)
18
+ - Select and switch between AI models
19
+ - Manage provider settings
20
+
21
+ Quick Setup Mode:
22
+ Use flags to quickly configure a BYO provider non-interactively:
23
+
24
+ Examples:
25
+ cline auth --provider openai-native --apikey sk-xxx --modelid gpt-5
26
+ cline auth -p anthropic -k sk-ant-xxx -m claude-sonnet-4-5-20250929
27
+ cline auth -p openai-compatible -k xxx -m gpt-4 -b https://api.example.com/v1
28
+
29
+ Supported providers: openai-native, openai, anthropic, gemini, openrouter, xai, cerebras, ollama
30
+ Note: Bedrock provider requires interactive setup due to complex auth fields`,
31
+ RunE: func(cmd *cobra.Command, args []string) error {
32
+ return auth.RunAuthFlow(cmd.Context(), args)
33
+ },
34
+ }
35
+
36
+ // Add flags for quick setup mode
37
+ cmd.Flags().StringVarP(&auth.QuickProvider, "provider", "p", "", "Provider ID for quick setup (e.g., openai-native, anthropic)")
38
+ cmd.Flags().StringVarP(&auth.QuickAPIKey, "apikey", "k", "", "API key for the provider")
39
+ cmd.Flags().StringVarP(&auth.QuickModelID, "modelid", "m", "", "Model ID to configure (e.g., gpt-4o, claude-sonnet-4-5-20250929)")
40
+ cmd.Flags().StringVarP(&auth.QuickBaseURL, "baseurl", "b", "", "Base URL (optional, only for openai provider)")
41
+
42
+ return cmd
43
+ }