@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,285 @@
|
|
|
1
|
+
package auth
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"time"
|
|
7
|
+
|
|
8
|
+
"github.com/charmbracelet/huh"
|
|
9
|
+
"github.com/cline/cli/pkg/cli/global"
|
|
10
|
+
"github.com/cline/cli/pkg/cli/task"
|
|
11
|
+
"github.com/cline/grpc-go/cline"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
var isSessionAuthenticated bool
|
|
15
|
+
|
|
16
|
+
// Cline provider specific code
|
|
17
|
+
|
|
18
|
+
func HandleClineAuth(ctx context.Context) error {
|
|
19
|
+
verboseLog("Authenticating with Cline...")
|
|
20
|
+
|
|
21
|
+
// Check if already authenticated
|
|
22
|
+
if IsAuthenticated(ctx) {
|
|
23
|
+
return signOutDialog(ctx)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Perform sign in
|
|
27
|
+
if err := signIn(ctx); err != nil {
|
|
28
|
+
return err
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fmt.Println()
|
|
32
|
+
|
|
33
|
+
verboseLog("✓ You are signed in!")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
// Configure default Cline model after successful authentication
|
|
37
|
+
if err := configureDefaultClineModel(ctx); err != nil {
|
|
38
|
+
fmt.Printf("Warning: Could not configure default Cline model: %v\n", err)
|
|
39
|
+
fmt.Println("You can configure a model later with 'cline auth' and selecting 'Change Cline model'")
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Return to main auth menu after successful authentication
|
|
43
|
+
return HandleAuthMenuNoArgs(ctx)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func signOut(ctx context.Context) error {
|
|
47
|
+
client, err := global.GetDefaultClient(ctx)
|
|
48
|
+
if err != nil {
|
|
49
|
+
return err
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if _, err = client.Account.AccountLogoutClicked(ctx, &cline.EmptyRequest{}); err != nil {
|
|
53
|
+
return err
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
isSessionAuthenticated = false
|
|
57
|
+
fmt.Println("You have been signed out of Cline.")
|
|
58
|
+
return nil
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func signOutDialog(ctx context.Context) error {
|
|
62
|
+
var confirm bool
|
|
63
|
+
form := huh.NewForm(
|
|
64
|
+
huh.NewGroup(
|
|
65
|
+
huh.NewConfirm().
|
|
66
|
+
Title("You are already signed in to Cline.").
|
|
67
|
+
Description("Would you like to sign out?").
|
|
68
|
+
Value(&confirm),
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if err := form.Run(); err != nil {
|
|
73
|
+
return nil
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if confirm {
|
|
77
|
+
if err := signOut(ctx); err != nil {
|
|
78
|
+
fmt.Printf("Failed to sign out: %v\n", err)
|
|
79
|
+
return err
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return HandleAuthMenuNoArgs(ctx)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func signIn(ctx context.Context) error {
|
|
86
|
+
if IsAuthenticated(ctx) {
|
|
87
|
+
return nil
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Subscribe to auth updates before initiating login
|
|
91
|
+
verboseLog("Subscribing to auth status updates...")
|
|
92
|
+
listener, err := NewAuthStatusListener(ctx)
|
|
93
|
+
if err != nil {
|
|
94
|
+
verboseLog("Failed to subscribe to auth updates: %v", err)
|
|
95
|
+
return fmt.Errorf("failed to subscribe to auth updates: %w", err)
|
|
96
|
+
}
|
|
97
|
+
defer listener.Stop()
|
|
98
|
+
|
|
99
|
+
if err := listener.Start(); err != nil {
|
|
100
|
+
verboseLog("Failed to start auth listener: %v", err)
|
|
101
|
+
return fmt.Errorf("failed to start auth listener: %w", err)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Initiate login (opens browser with callback URL from cline-core's AuthHandler)
|
|
105
|
+
verboseLog("Initiating login...")
|
|
106
|
+
client, err := global.GetDefaultClient(ctx)
|
|
107
|
+
if err != nil {
|
|
108
|
+
verboseLog("Failed to obtain client: %v", err)
|
|
109
|
+
return fmt.Errorf("failed to obtain client: %w", err)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
response, err := client.Account.AccountLoginClicked(ctx, &cline.EmptyRequest{})
|
|
113
|
+
if err != nil {
|
|
114
|
+
verboseLog("Failed to initiate login: %v", err)
|
|
115
|
+
return fmt.Errorf("failed to initiate login: %w", err)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
fmt.Println("\n Opening browser for authentication...")
|
|
119
|
+
if response != nil && response.Value != "" {
|
|
120
|
+
fmt.Printf(" If the browser doesn't open automatically, visit this URL:\n %s\n\n", response.Value)
|
|
121
|
+
}
|
|
122
|
+
fmt.Println(" Waiting for you to complete authentication in your browser...")
|
|
123
|
+
fmt.Println(" (This may take a few moments. Timeout: 5 minutes)")
|
|
124
|
+
|
|
125
|
+
// Wait for auth status update confirming success
|
|
126
|
+
verboseLog("Waiting for authentication to complete...")
|
|
127
|
+
if err := listener.WaitForAuthentication(5 * time.Minute); err != nil {
|
|
128
|
+
verboseLog("Authentication failed or timed out: %v", err)
|
|
129
|
+
fmt.Println("\n Authentication failed or timed out.")
|
|
130
|
+
fmt.Println(" Please try again with 'cline auth'")
|
|
131
|
+
return err
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Only NOW set the session flag after confirmed authentication
|
|
135
|
+
isSessionAuthenticated = true
|
|
136
|
+
verboseLog("Login successful")
|
|
137
|
+
return nil
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
func IsAuthenticated(ctx context.Context) bool {
|
|
141
|
+
if isSessionAuthenticated {
|
|
142
|
+
verboseLog("Session is already authenticated")
|
|
143
|
+
return true
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
verboseLog("Verifying authentication with server...")
|
|
147
|
+
client, err := global.GetDefaultClient(ctx)
|
|
148
|
+
if err != nil {
|
|
149
|
+
verboseLog("Failed to get client for auth check: %v", err)
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
_, err = client.Account.GetUserCredits(ctx, &cline.EmptyRequest{})
|
|
154
|
+
if err == nil {
|
|
155
|
+
// Update session variable for future fast-path checks
|
|
156
|
+
verboseLog("Server verification successful, updating session flag")
|
|
157
|
+
isSessionAuthenticated = true
|
|
158
|
+
return true
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
verboseLog("Server verification failed: %v", err)
|
|
162
|
+
return false
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// HandleChangeClineModel allows Cline-authenticated users to change their Cline model selection. Hidden when not authenticated.
|
|
166
|
+
func HandleChangeClineModel(ctx context.Context) error {
|
|
167
|
+
// Ensure user is authenticated
|
|
168
|
+
if !IsAuthenticated(ctx) {
|
|
169
|
+
return fmt.Errorf("you must be authenticated with Cline to change models. Run 'cline auth' to sign in")
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Get task manager
|
|
173
|
+
manager, err := createTaskManager(ctx)
|
|
174
|
+
if err != nil {
|
|
175
|
+
return fmt.Errorf("failed to create task manager: %w", err)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Launch Cline model selection
|
|
179
|
+
return SelectClineModel(ctx, manager)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// configureDefaultClineModel configures the default Cline model after authentication
|
|
183
|
+
func configureDefaultClineModel(ctx context.Context) error {
|
|
184
|
+
verboseLog("Configuring default Cline model...")
|
|
185
|
+
|
|
186
|
+
// Create task manager
|
|
187
|
+
manager, err := task.NewManagerForDefault(ctx)
|
|
188
|
+
if err != nil {
|
|
189
|
+
return fmt.Errorf("failed to create task manager: %w", err)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Set default Cline model
|
|
193
|
+
return SetDefaultClineModel(ctx, manager)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// HandleSelectOrganization allows Cline-authenticated users to select which organization to use
|
|
197
|
+
func HandleSelectOrganization(ctx context.Context) error {
|
|
198
|
+
// Ensure user is authenticated
|
|
199
|
+
if !IsAuthenticated(ctx) {
|
|
200
|
+
return fmt.Errorf("you must be authenticated with Cline to select an organization. Run 'cline auth' to sign in")
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Get client
|
|
204
|
+
client, err := global.GetDefaultClient(ctx)
|
|
205
|
+
if err != nil {
|
|
206
|
+
return fmt.Errorf("failed to get client: %w", err)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Fetch user organizations
|
|
210
|
+
orgsResponse, err := client.Account.GetUserOrganizations(ctx, &cline.EmptyRequest{})
|
|
211
|
+
if err != nil {
|
|
212
|
+
return fmt.Errorf("failed to fetch organizations: %w", err)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
organizations := orgsResponse.GetOrganizations()
|
|
216
|
+
if len(organizations) == 0 {
|
|
217
|
+
fmt.Println("You don't have any organizations yet.")
|
|
218
|
+
fmt.Println("Visit https://app.cline.bot/dashboard to create an organization.")
|
|
219
|
+
return HandleAuthMenuNoArgs(ctx)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Build options list: Personal + Organizations
|
|
223
|
+
var options []huh.Option[string]
|
|
224
|
+
options = append(options, huh.NewOption("Personal", "personal"))
|
|
225
|
+
|
|
226
|
+
for _, org := range organizations {
|
|
227
|
+
displayName := org.Name
|
|
228
|
+
// Show active indicator
|
|
229
|
+
if org.Active {
|
|
230
|
+
displayName = fmt.Sprintf("%s (active)", displayName)
|
|
231
|
+
}
|
|
232
|
+
options = append(options, huh.NewOption(displayName, org.OrganizationId))
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
options = append(options, huh.NewOption("(Cancel)", "cancel"))
|
|
236
|
+
|
|
237
|
+
// Show selection menu
|
|
238
|
+
var selected string
|
|
239
|
+
form := huh.NewForm(
|
|
240
|
+
huh.NewGroup(
|
|
241
|
+
huh.NewSelect[string]().
|
|
242
|
+
Title("Select which account to use").
|
|
243
|
+
Options(options...).
|
|
244
|
+
Value(&selected),
|
|
245
|
+
),
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
if err := form.Run(); err != nil {
|
|
249
|
+
return fmt.Errorf("failed to select organization: %w", err)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if selected == "cancel" {
|
|
253
|
+
return HandleAuthMenuNoArgs(ctx)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Set the organization
|
|
257
|
+
var orgId *string
|
|
258
|
+
if selected != "personal" {
|
|
259
|
+
orgId = &selected
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
req := &cline.UserOrganizationUpdateRequest{
|
|
263
|
+
OrganizationId: orgId,
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if _, err := client.Account.SetUserOrganization(ctx, req); err != nil {
|
|
267
|
+
return fmt.Errorf("failed to set organization: %w", err)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if selected == "personal" {
|
|
271
|
+
fmt.Println("✓ Switched to personal account")
|
|
272
|
+
} else {
|
|
273
|
+
// Find the org name to display
|
|
274
|
+
var orgName string
|
|
275
|
+
for _, org := range organizations {
|
|
276
|
+
if org.OrganizationId == selected {
|
|
277
|
+
orgName = org.Name
|
|
278
|
+
break
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
fmt.Printf("✓ Switched to organization: %s\n", orgName)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return HandleAuthMenuNoArgs(ctx)
|
|
285
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
package auth
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
|
|
7
|
+
"github.com/charmbracelet/huh"
|
|
8
|
+
"github.com/cline/cli/pkg/cli/display"
|
|
9
|
+
"github.com/cline/cli/pkg/cli/global"
|
|
10
|
+
"github.com/cline/cli/pkg/cli/task"
|
|
11
|
+
"github.com/cline/grpc-go/cline"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
// contextKey is a distinct type for context keys to avoid collisions
|
|
15
|
+
type contextKey string
|
|
16
|
+
|
|
17
|
+
const authInstanceAddressKey contextKey = "authInstanceAddress"
|
|
18
|
+
|
|
19
|
+
// AuthAction represents the type of authentication action
|
|
20
|
+
type AuthAction string
|
|
21
|
+
|
|
22
|
+
const (
|
|
23
|
+
AuthActionClineLogin AuthAction = "cline_login"
|
|
24
|
+
AuthActionBYOSetup AuthAction = "provider_setup"
|
|
25
|
+
AuthActionChangeClineModel AuthAction = "change_cline_model"
|
|
26
|
+
AuthActionSelectOrganization AuthAction = "select_organization"
|
|
27
|
+
AuthActionSelectProvider AuthAction = "select_provider"
|
|
28
|
+
AuthActionExit AuthAction = "exit_wizard"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
// Cline Auth Menu
|
|
32
|
+
// Example Layout
|
|
33
|
+
//
|
|
34
|
+
// ┃ Cline Account: <authenticated/not authenticated>
|
|
35
|
+
// ┃ Active Provider: <provider name or none configured>
|
|
36
|
+
// ┃ Active Model: <model name or none configured>
|
|
37
|
+
// ┃
|
|
38
|
+
// ┃ What would you like to do?
|
|
39
|
+
// ┃ Change Cline model (only if authenticated) - hidden if not authenticated
|
|
40
|
+
// ┃ Authenticate with Cline account / Sign out of Cline - changes based on auth status
|
|
41
|
+
// ┃ Select active provider (Cline or BYO) - always shown. Used to switch between Cline and BYO providers
|
|
42
|
+
// ┃ Configure BYO API providers - always shown. Launches provider setup wizard
|
|
43
|
+
// ┃ Exit authorization wizard - always shown. Exits the auth menu
|
|
44
|
+
|
|
45
|
+
// RunAuthFlow is the entry point for the entire auth flow with instance management
|
|
46
|
+
// It spawns a fresh instance for auth operations and cleans it up when done
|
|
47
|
+
func RunAuthFlow(ctx context.Context, args []string) error {
|
|
48
|
+
// Spawn a fresh instance for auth operations
|
|
49
|
+
instanceInfo, err := global.Clients.StartNewInstance(ctx)
|
|
50
|
+
if err != nil {
|
|
51
|
+
return fmt.Errorf("failed to start auth instance: %w", err)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Cleanup when done (success, error, or panic)
|
|
55
|
+
defer func() {
|
|
56
|
+
verboseLog("Shutting down auth instance at %s", instanceInfo.Address)
|
|
57
|
+
if err := global.KillInstanceByAddress(context.Background(), global.Clients.GetRegistry(), instanceInfo.Address); err != nil {
|
|
58
|
+
verboseLog("Warning: Failed to kill auth instance: %v", err)
|
|
59
|
+
}
|
|
60
|
+
}()
|
|
61
|
+
|
|
62
|
+
// Store instance address in context for all auth handlers to use
|
|
63
|
+
authCtx := context.WithValue(ctx, authInstanceAddressKey, instanceInfo.Address)
|
|
64
|
+
|
|
65
|
+
// Route to existing auth flow
|
|
66
|
+
return HandleAuthCommand(authCtx, args)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Main entry point for handling the `cline auth` command
|
|
70
|
+
// HandleAuthCommand routes the auth command based on the number of arguments
|
|
71
|
+
func HandleAuthCommand(ctx context.Context, args []string) error {
|
|
72
|
+
|
|
73
|
+
// Check if flags are provided for quick setup
|
|
74
|
+
if QuickProvider != "" || QuickAPIKey != "" || QuickModelID != "" || QuickBaseURL != "" {
|
|
75
|
+
if QuickProvider == "" || QuickAPIKey == "" || QuickModelID == "" {
|
|
76
|
+
return fmt.Errorf("quick setup requires --provider, --apikey, and --modelid flags. Use 'cline auth --help' for more information")
|
|
77
|
+
}
|
|
78
|
+
return QuickSetupFromFlags(ctx, QuickProvider, QuickAPIKey, QuickModelID, QuickBaseURL)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
switch len(args) {
|
|
82
|
+
case 0:
|
|
83
|
+
// No args: Show uth wizard
|
|
84
|
+
return HandleAuthMenuNoArgs(ctx)
|
|
85
|
+
case 1, 2, 3, 4:
|
|
86
|
+
fmt.Println("Invalid positional arguments. Correct usage:")
|
|
87
|
+
fmt.Println(" cline auth --provider <provider> --apikey <key> --modelid <model> --baseurl <optional>")
|
|
88
|
+
return nil
|
|
89
|
+
default:
|
|
90
|
+
return fmt.Errorf("too many arguments. Use flags for quick setup: --provider, --apikey, --modelid --baseurl(optional)")
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// getAuthInstanceAddress retrieves the auth instance address from context
|
|
95
|
+
// Returns empty string if not found (falls back to default behavior)
|
|
96
|
+
func getAuthInstanceAddress(ctx context.Context) string {
|
|
97
|
+
if addr, ok := ctx.Value(authInstanceAddressKey).(string); ok {
|
|
98
|
+
return addr
|
|
99
|
+
}
|
|
100
|
+
return ""
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// HandleAuthMenuNoArgs prepares the auth menu when no arguments are provided
|
|
104
|
+
func HandleAuthMenuNoArgs(ctx context.Context) error {
|
|
105
|
+
// Check if Cline is authenticated
|
|
106
|
+
isClineAuth := IsAuthenticated(ctx)
|
|
107
|
+
|
|
108
|
+
// Get current provider config for display
|
|
109
|
+
var currentProvider string
|
|
110
|
+
var currentModel string
|
|
111
|
+
if manager, err := createTaskManager(ctx); err == nil {
|
|
112
|
+
if providerList, err := GetProviderConfigurations(ctx, manager); err == nil {
|
|
113
|
+
if providerList.ActProvider != nil {
|
|
114
|
+
currentProvider = GetProviderDisplayName(providerList.ActProvider.Provider)
|
|
115
|
+
currentModel = providerList.ActProvider.ModelID
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Fetch organizations if authenticated
|
|
121
|
+
var hasOrganizations bool
|
|
122
|
+
if isClineAuth {
|
|
123
|
+
if client, err := global.GetDefaultClient(ctx); err == nil {
|
|
124
|
+
if orgsResponse, err := client.Account.GetUserOrganizations(ctx, &cline.EmptyRequest{}); err == nil {
|
|
125
|
+
hasOrganizations = len(orgsResponse.GetOrganizations()) > 0
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
action, err := ShowAuthMenuWithStatus(isClineAuth, hasOrganizations, currentProvider, currentModel)
|
|
131
|
+
if err != nil {
|
|
132
|
+
// Check if user cancelled - propagate for clean exit
|
|
133
|
+
if err == huh.ErrUserAborted {
|
|
134
|
+
return huh.ErrUserAborted
|
|
135
|
+
}
|
|
136
|
+
return err
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
switch action {
|
|
140
|
+
case AuthActionClineLogin:
|
|
141
|
+
return HandleClineAuth(ctx)
|
|
142
|
+
case AuthActionBYOSetup:
|
|
143
|
+
return HandleAPIProviderSetup(ctx)
|
|
144
|
+
case AuthActionChangeClineModel:
|
|
145
|
+
return HandleChangeClineModel(ctx)
|
|
146
|
+
case AuthActionSelectOrganization:
|
|
147
|
+
return HandleSelectOrganization(ctx)
|
|
148
|
+
case AuthActionSelectProvider:
|
|
149
|
+
return HandleSelectProvider(ctx)
|
|
150
|
+
case AuthActionExit:
|
|
151
|
+
return nil
|
|
152
|
+
default:
|
|
153
|
+
return fmt.Errorf("invalid action")
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ShowAuthMenuWithStatus displays the main auth menu with Cline + provider status
|
|
158
|
+
func ShowAuthMenuWithStatus(isClineAuthenticated bool, hasOrganizations bool, currentProvider, currentModel string) (AuthAction, error) {
|
|
159
|
+
var action AuthAction
|
|
160
|
+
var options []huh.Option[AuthAction]
|
|
161
|
+
|
|
162
|
+
// Build menu options based on authentication status
|
|
163
|
+
if isClineAuthenticated {
|
|
164
|
+
options = []huh.Option[AuthAction]{
|
|
165
|
+
huh.NewOption("Change Cline model", AuthActionChangeClineModel),
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Add organization selection if user has organizations
|
|
169
|
+
if hasOrganizations {
|
|
170
|
+
options = append(options, huh.NewOption("Select organization", AuthActionSelectOrganization))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
options = append(options,
|
|
174
|
+
huh.NewOption("Sign out of Cline", AuthActionClineLogin),
|
|
175
|
+
huh.NewOption("Select active provider (Cline or BYO)", AuthActionSelectProvider),
|
|
176
|
+
huh.NewOption("Configure BYO API providers", AuthActionBYOSetup),
|
|
177
|
+
huh.NewOption("Exit authorization wizard", AuthActionExit),
|
|
178
|
+
)
|
|
179
|
+
} else {
|
|
180
|
+
options = []huh.Option[AuthAction]{
|
|
181
|
+
huh.NewOption("Authenticate with Cline account", AuthActionClineLogin),
|
|
182
|
+
huh.NewOption("Select active provider (Cline or BYO)", AuthActionSelectProvider),
|
|
183
|
+
huh.NewOption("Configure BYO API providers", AuthActionBYOSetup),
|
|
184
|
+
huh.NewOption("Exit authorization wizard", AuthActionExit),
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Determine menu title based on status
|
|
189
|
+
var title string
|
|
190
|
+
renderer := display.NewRenderer(global.Config.OutputFormat)
|
|
191
|
+
|
|
192
|
+
// Always show Cline authentication status
|
|
193
|
+
if isClineAuthenticated {
|
|
194
|
+
title = fmt.Sprintf("Cline Account: %s Authenticated\n", renderer.Green("✓"))
|
|
195
|
+
} else {
|
|
196
|
+
title = fmt.Sprintf("Cline Account: %s Not authenticated\n", renderer.Red("✗"))
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Show active provider and model if configured (regardless of Cline auth status)
|
|
200
|
+
if currentProvider != "" && currentModel != "" {
|
|
201
|
+
title += fmt.Sprintf("Active Provider: %s\nActive Model: %s\n",
|
|
202
|
+
renderer.White(currentProvider),
|
|
203
|
+
renderer.White(currentModel))
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Always end with a huh?
|
|
207
|
+
title += "\nWhat would you like to do?"
|
|
208
|
+
|
|
209
|
+
form := huh.NewForm(
|
|
210
|
+
huh.NewGroup(
|
|
211
|
+
huh.NewSelect[AuthAction]().
|
|
212
|
+
Title(title).
|
|
213
|
+
Options(options...).
|
|
214
|
+
Value(&action),
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
if err := form.Run(); err != nil {
|
|
219
|
+
// Check if user cancelled with Control-C
|
|
220
|
+
if err == huh.ErrUserAborted {
|
|
221
|
+
// Return the error to allow deferred cleanup to run
|
|
222
|
+
return "", huh.ErrUserAborted
|
|
223
|
+
}
|
|
224
|
+
return "", fmt.Errorf("failed to get menu choice: %w", err)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return action, nil
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// HandleAPIProviderSetup launches the API provider configuration wizard
|
|
231
|
+
func HandleAPIProviderSetup(ctx context.Context) error {
|
|
232
|
+
wizard, err := NewProviderWizard(ctx)
|
|
233
|
+
if err != nil {
|
|
234
|
+
return fmt.Errorf("failed to create provider wizard: %w", err)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return wizard.Run()
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// HandleSelectProvider allows users to switch between Cline provider and BYO providers
|
|
241
|
+
func HandleSelectProvider(ctx context.Context) error {
|
|
242
|
+
// Get task manager
|
|
243
|
+
manager, err := createTaskManager(ctx)
|
|
244
|
+
if err != nil {
|
|
245
|
+
return fmt.Errorf("failed to create task manager: %w", err)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Detect all providers with valid configurations (is an API key present)
|
|
249
|
+
availableProviders, err := DetectAllConfiguredProviders(ctx, manager)
|
|
250
|
+
if err != nil {
|
|
251
|
+
return fmt.Errorf("failed to detect configured providers: %w", err)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Build list of available providers
|
|
255
|
+
var providerOptions []huh.Option[string]
|
|
256
|
+
var providerMapping = make(map[string]cline.ApiProvider)
|
|
257
|
+
|
|
258
|
+
// Add each configured provider to the selection menu
|
|
259
|
+
for _, provider := range availableProviders {
|
|
260
|
+
providerName := GetProviderDisplayName(provider)
|
|
261
|
+
providerKey := fmt.Sprintf("provider_%d", provider)
|
|
262
|
+
providerOptions = append(providerOptions, huh.NewOption(providerName, providerKey))
|
|
263
|
+
providerMapping[providerKey] = provider
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if len(providerOptions) == 0 {
|
|
267
|
+
fmt.Println("No providers available. Please configure a provider first.")
|
|
268
|
+
return HandleAuthMenuNoArgs(ctx)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
providerOptions = append(providerOptions, huh.NewOption("(Cancel)", "cancel"))
|
|
272
|
+
|
|
273
|
+
// Show selection menu
|
|
274
|
+
var selected string
|
|
275
|
+
form := huh.NewForm(
|
|
276
|
+
huh.NewGroup(
|
|
277
|
+
huh.NewSelect[string]().
|
|
278
|
+
Title("Select which provider to use").
|
|
279
|
+
Options(providerOptions...).
|
|
280
|
+
Value(&selected),
|
|
281
|
+
),
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
if err := form.Run(); err != nil {
|
|
285
|
+
// Check if user cancelled with Control-C
|
|
286
|
+
if err == huh.ErrUserAborted {
|
|
287
|
+
return huh.ErrUserAborted
|
|
288
|
+
}
|
|
289
|
+
return fmt.Errorf("failed to select provider: %w", err)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if selected == "cancel" {
|
|
293
|
+
return HandleAuthMenuNoArgs(ctx)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Get the selected provider
|
|
297
|
+
selectedProvider := providerMapping[selected]
|
|
298
|
+
|
|
299
|
+
// Apply the selected provider
|
|
300
|
+
if selectedProvider == cline.ApiProvider_CLINE {
|
|
301
|
+
// Configure Cline as the active provider
|
|
302
|
+
return SelectClineModel(ctx, manager)
|
|
303
|
+
} else {
|
|
304
|
+
// Switch to the selected BYO provider
|
|
305
|
+
return SwitchToBYOProvider(ctx, manager, selectedProvider)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// createTaskManager is a helper to create a task manager (avoids import cycles)
|
|
310
|
+
// Uses the auth instance address from context if available, otherwise falls back to default
|
|
311
|
+
func createTaskManager(ctx context.Context) (*task.Manager, error) {
|
|
312
|
+
authAddr := getAuthInstanceAddress(ctx)
|
|
313
|
+
if authAddr != "" {
|
|
314
|
+
return task.NewManagerForAddress(ctx, authAddr)
|
|
315
|
+
}
|
|
316
|
+
return task.NewManagerForDefault(ctx)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
func verboseLog(format string, args ...interface{}) {
|
|
320
|
+
if global.Config != nil && global.Config.Verbose {
|
|
321
|
+
fmt.Printf("[VERBOSE] "+format+"\n", args...)
|
|
322
|
+
}
|
|
323
|
+
}
|