@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,366 @@
1
+ package sqlite
2
+
3
+ import (
4
+ "context"
5
+ "database/sql"
6
+ "encoding/json"
7
+ "fmt"
8
+ "net"
9
+ "os"
10
+ "path/filepath"
11
+ "time"
12
+
13
+ "github.com/cline/cli/pkg/common"
14
+ _ "github.com/glebarez/go-sqlite"
15
+ "google.golang.org/grpc/health/grpc_health_v1"
16
+ )
17
+
18
+ // normalizeAddressVariants returns address variants to try when querying SQLite.
19
+ // Handles localhost/127.0.0.1 equivalence by returning both forms.
20
+ func normalizeAddressVariants(address string) []string {
21
+ variants := []string{address}
22
+
23
+ // Extract host and port
24
+ host, port, err := net.SplitHostPort(address)
25
+ if err != nil {
26
+ return variants
27
+ }
28
+
29
+ // Add the alternate form for localhost/127.0.0.1
30
+ if host == "localhost" {
31
+ variants = append(variants, net.JoinHostPort("127.0.0.1", port))
32
+ } else if host == "127.0.0.1" {
33
+ variants = append(variants, net.JoinHostPort("localhost", port))
34
+ }
35
+
36
+ return variants
37
+ }
38
+
39
+ // LockManager provides access to the SQLite locks database
40
+ type LockManager struct {
41
+ dbPath string
42
+ db *sql.DB
43
+ }
44
+
45
+ // NewLockManager creates a new lock manager
46
+ func NewLockManager(clineDir string) (*LockManager, error) {
47
+ dbPath := filepath.Join(clineDir, common.SETTINGS_SUBFOLDER, "locks.db")
48
+
49
+ // Ensure the directory exists (for future DB creation by cline-core)
50
+ dbDir := filepath.Dir(dbPath)
51
+ if err := os.MkdirAll(dbDir, 0755); err != nil {
52
+ return nil, fmt.Errorf("failed to create database directory: %w", err)
53
+ }
54
+
55
+ // Check if database exists
56
+ if _, err := os.Stat(dbPath); os.IsNotExist(err) {
57
+ // Database doesn't exist - return manager with nil db
58
+ // All methods already handle this gracefully!
59
+ return &LockManager{dbPath: dbPath, db: nil}, nil
60
+ }
61
+
62
+ // Database exists - open it normally (no schema creation)
63
+ db, err := sql.Open("sqlite", dbPath)
64
+ if err != nil {
65
+ // If we can't open existing database, return nil db manager
66
+ return &LockManager{dbPath: dbPath, db: nil}, nil
67
+ }
68
+
69
+ // Test the connection
70
+ if err := db.Ping(); err != nil {
71
+ db.Close()
72
+ // If connection fails, return nil db manager
73
+ return &LockManager{dbPath: dbPath, db: nil}, nil
74
+ }
75
+
76
+ return &LockManager{
77
+ dbPath: dbPath,
78
+ db: db,
79
+ }, nil
80
+ }
81
+
82
+ // ensureConnection attempts to establish a database connection if one doesn't exist
83
+ func (lm *LockManager) ensureConnection() error {
84
+ // If we already have a connection, we're done
85
+ if lm.db != nil {
86
+ return nil
87
+ }
88
+
89
+ // Check if database exists now (created by cline-core)
90
+ if _, err := os.Stat(lm.dbPath); os.IsNotExist(err) {
91
+ return fmt.Errorf("database not available")
92
+ }
93
+
94
+ // Database exists, try to connect
95
+ db, err := sql.Open("sqlite", lm.dbPath)
96
+ if err != nil {
97
+ return fmt.Errorf("failed to connect to database: %w", err)
98
+ }
99
+
100
+ if err := db.Ping(); err != nil {
101
+ db.Close()
102
+ return fmt.Errorf("database connection failed: %w", err)
103
+ }
104
+
105
+ // Success! Update our connection permanently
106
+ lm.db = db
107
+ return nil
108
+ }
109
+
110
+ // Close closes the database connection
111
+ func (lm *LockManager) Close() error {
112
+ if lm.db != nil {
113
+ return lm.db.Close()
114
+ }
115
+ return nil
116
+ }
117
+
118
+ // GetInstanceLocks returns all instance locks
119
+ func (lm *LockManager) GetInstanceLocks() ([]common.LockRow, error) {
120
+ if err := lm.ensureConnection(); err != nil {
121
+ return []common.LockRow{}, nil
122
+ }
123
+
124
+ query := common.SelectInstanceLocksSQL
125
+
126
+ rows, err := lm.db.Query(query)
127
+ if err != nil {
128
+ return nil, fmt.Errorf("failed to query instance locks: %w", err)
129
+ }
130
+ defer rows.Close()
131
+
132
+ var locks []common.LockRow
133
+ for rows.Next() {
134
+ var lock common.LockRow
135
+ err := rows.Scan(&lock.ID, &lock.HeldBy, &lock.LockType, &lock.LockTarget, &lock.LockedAt)
136
+ if err != nil {
137
+ return nil, fmt.Errorf("failed to scan lock row: %w", err)
138
+ }
139
+ locks = append(locks, lock)
140
+ }
141
+
142
+ return locks, nil
143
+ }
144
+
145
+ // RemoveInstanceLock removes an instance lock by address
146
+ func (lm *LockManager) RemoveInstanceLock(address string) error {
147
+ if err := lm.ensureConnection(); err != nil {
148
+ return nil // Gracefully handle missing database for cleanup operations
149
+ }
150
+
151
+ query := common.DeleteInstanceLockSQL
152
+ _, err := lm.db.Exec(query, address)
153
+ if err != nil {
154
+ return fmt.Errorf("failed to remove instance lock: %w", err)
155
+ }
156
+
157
+ return nil
158
+ }
159
+
160
+ // HasInstanceAtAddress checks if an instance exists at the given address
161
+ func (lm *LockManager) HasInstanceAtAddress(address string) (bool, error) {
162
+ if err := lm.ensureConnection(); err != nil {
163
+ return false, err
164
+ }
165
+
166
+ query := common.CountInstanceLockSQL
167
+ var count int
168
+ err := lm.db.QueryRow(query, address).Scan(&count)
169
+ if err != nil {
170
+ return false, fmt.Errorf("failed to check instance existence: %w", err)
171
+ }
172
+
173
+ return count > 0, nil
174
+ }
175
+
176
+ // GetInstanceInfo returns instance information directly from SQLite.
177
+ // Handles localhost/127.0.0.1 equivalence by trying both variants.
178
+ func (lm *LockManager) GetInstanceInfo(address string) (*common.CoreInstanceInfo, error) {
179
+ if err := lm.ensureConnection(); err != nil {
180
+ return nil, err
181
+ }
182
+
183
+ query := common.SelectInstanceLockByHolderSQL
184
+ variants := normalizeAddressVariants(address)
185
+
186
+ var heldBy, lockTarget string
187
+ var lockedAt int64
188
+ var lastErr error
189
+
190
+ // Try each address variant (e.g., localhost:50607 and 127.0.0.1:50607)
191
+ for _, variant := range variants {
192
+ err := lm.db.QueryRow(query, variant).Scan(&heldBy, &lockTarget, &lockedAt)
193
+ if err == nil {
194
+ // Found it!
195
+ return &common.CoreInstanceInfo{
196
+ Address: heldBy,
197
+ HostServiceAddress: lockTarget,
198
+ Status: grpc_health_v1.HealthCheckResponse_UNKNOWN,
199
+ LastSeen: time.Unix(lockedAt/1000, 0),
200
+ }, nil
201
+ }
202
+ if err != sql.ErrNoRows {
203
+ // Real error (not just "not found"), save it
204
+ lastErr = err
205
+ }
206
+ }
207
+
208
+ // None of the variants were found
209
+ if lastErr != nil {
210
+ return nil, fmt.Errorf("failed to query instance: %w", lastErr)
211
+ }
212
+ return nil, fmt.Errorf("instance %s not found", address)
213
+ }
214
+
215
+ // ListInstancesWithHealthCheck returns all instances with real-time health checks
216
+ func (lm *LockManager) ListInstancesWithHealthCheck(ctx context.Context) ([]*common.CoreInstanceInfo, error) {
217
+ if err := lm.ensureConnection(); err != nil {
218
+ return []*common.CoreInstanceInfo{}, nil
219
+ }
220
+
221
+ // Get all instance locks
222
+ locks, err := lm.GetInstanceLocks()
223
+ if err != nil {
224
+ return nil, fmt.Errorf("failed to get instance locks: %w", err)
225
+ }
226
+
227
+ var instances []*common.CoreInstanceInfo
228
+
229
+ for _, lock := range locks {
230
+ // Create instance info using actual SQLite data
231
+ status, err := common.PerformHealthCheck(ctx, lock.HeldBy)
232
+ if status != grpc_health_v1.HealthCheckResponse_SERVING || err != nil {
233
+ time.Sleep(1 * time.Second)
234
+ status, err = common.PerformHealthCheck(ctx, lock.HeldBy)
235
+ }
236
+
237
+ info := &common.CoreInstanceInfo{
238
+ Address: lock.HeldBy,
239
+ HostServiceAddress: lock.LockTarget,
240
+ Status: status,
241
+ LastSeen: time.Unix(lock.LockedAt/1000, 0),
242
+ }
243
+
244
+ instances = append(instances, info)
245
+ }
246
+
247
+ return instances, nil
248
+ }
249
+
250
+ // GetDefaultInstance reads the default instance from the settings file
251
+ func GetDefaultInstance(clineDir string) (string, error) {
252
+ settingsPath := filepath.Join(clineDir, common.SETTINGS_SUBFOLDER, "settings", "cli-default-instance.json")
253
+
254
+ data, err := os.ReadFile(settingsPath)
255
+ if err != nil {
256
+ if os.IsNotExist(err) {
257
+ return "", nil
258
+ }
259
+ return "", fmt.Errorf("failed to read default instance file: %w", err)
260
+ }
261
+
262
+ var defaultInstance common.DefaultCoreInstance
263
+ if err := json.Unmarshal(data, &defaultInstance); err != nil {
264
+ return "", fmt.Errorf("failed to parse default instance JSON: %w", err)
265
+ }
266
+
267
+ if defaultInstance.Address == "" {
268
+ return "", fmt.Errorf("default instance not set in settings file")
269
+ }
270
+
271
+ return defaultInstance.Address, nil
272
+ }
273
+
274
+ // SetDefaultInstance writes the default instance to the settings file with proper locking
275
+ func SetDefaultInstance(clineDir, address string) error {
276
+ // Create lock manager for this operation
277
+ lockManager, err := NewLockManager(clineDir)
278
+ if err != nil {
279
+ return fmt.Errorf("Warning: SQLite unavailable, writing without lock: %v\n", err)
280
+ }
281
+ defer lockManager.Close()
282
+
283
+ settingsPath := filepath.Join(clineDir, common.SETTINGS_SUBFOLDER, "settings", "cli-default-instance.json")
284
+
285
+ // Generate a unique identifier for this CLI process
286
+ heldBy := fmt.Sprintf("cli-process-%d", os.Getpid())
287
+
288
+ // Use file lock for the write operation
289
+ return lockManager.WithFileLock(settingsPath, heldBy, func() error {
290
+ return writeDefaultInstanceJSONToDisk(clineDir, address)
291
+ })
292
+ }
293
+
294
+ func writeDefaultInstanceJSONToDisk(clineDir, address string) error {
295
+ settingsDir := filepath.Join(clineDir, common.SETTINGS_SUBFOLDER, "settings")
296
+ if err := os.MkdirAll(settingsDir, 0755); err != nil {
297
+ return fmt.Errorf("failed to create settings directory: %w", err)
298
+ }
299
+
300
+ settingsPath := filepath.Join(settingsDir, "cli-default-instance.json")
301
+
302
+ payload := common.DefaultCoreInstance{
303
+ Address: address,
304
+ LastUpdated: time.Now().Format(time.RFC3339),
305
+ }
306
+
307
+ data, err := json.MarshalIndent(payload, "", " ")
308
+ if err != nil {
309
+ return fmt.Errorf("failed to marshal default instance JSON: %w", err)
310
+ }
311
+
312
+ if err := os.WriteFile(settingsPath, data, 0644); err != nil {
313
+ return fmt.Errorf("failed to write default instance file: %w", err)
314
+ }
315
+
316
+ return nil
317
+ }
318
+
319
+ // AcquireFileLock attempts to acquire a file lock
320
+ func (lm *LockManager) AcquireFileLock(filePath, heldBy string) error {
321
+ if err := lm.ensureConnection(); err != nil {
322
+ return err
323
+ }
324
+
325
+ now := time.Now().Unix() * 1000 // Convert to milliseconds
326
+
327
+ query := common.InsertFileLockSQL
328
+
329
+ _, err := lm.db.Exec(query, heldBy, filePath, now)
330
+ if err != nil {
331
+ return fmt.Errorf("failed to acquire file lock for %s: %w", filePath, err)
332
+ }
333
+
334
+ return nil
335
+ }
336
+
337
+ // ReleaseFileLock releases a file lock
338
+ func (lm *LockManager) ReleaseFileLock(filePath, heldBy string) error {
339
+ if lm.db == nil {
340
+ return nil
341
+ }
342
+
343
+ query := common.DeleteFileLockSQL
344
+
345
+ _, err := lm.db.Exec(query, heldBy, filePath)
346
+ if err != nil {
347
+ return fmt.Errorf("failed to release file lock for %s: %w", filePath, err)
348
+ }
349
+
350
+ return nil
351
+ }
352
+
353
+ // WithFileLock executes a function while holding a file lock
354
+ func (lm *LockManager) WithFileLock(filePath, heldBy string, fn func() error) error {
355
+ if err := lm.AcquireFileLock(filePath, heldBy); err != nil {
356
+ return err
357
+ }
358
+
359
+ defer func() {
360
+ if releaseErr := lm.ReleaseFileLock(filePath, heldBy); releaseErr != nil {
361
+ fmt.Printf("Warning: Failed to release file lock for %s: %v\n", filePath, releaseErr)
362
+ }
363
+ }()
364
+
365
+ return fn()
366
+ }
@@ -0,0 +1,72 @@
1
+ package task
2
+
3
+ import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "os"
7
+ "path/filepath"
8
+ "sort"
9
+
10
+ "github.com/cline/cli/pkg/cli/display"
11
+ "github.com/cline/cli/pkg/cli/global"
12
+ "github.com/cline/cli/pkg/cli/types"
13
+ "github.com/cline/grpc-go/cline"
14
+ )
15
+
16
+ // ListTasksFromDisk reads task history directly from disk
17
+ func ListTasksFromDisk() error {
18
+ // Get the task history file path
19
+ homeDir, err := os.UserHomeDir()
20
+ if err != nil {
21
+ return fmt.Errorf("failed to get home directory: %w", err)
22
+ }
23
+
24
+ filePath := filepath.Join(homeDir, ".cline", "data", "state", "taskHistory.json")
25
+
26
+ // Read the file
27
+ data, err := os.ReadFile(filePath)
28
+ if err != nil {
29
+ if os.IsNotExist(err) {
30
+ fmt.Println("No task history found.")
31
+ return nil
32
+ }
33
+ return fmt.Errorf("failed to read task history: %w", err)
34
+ }
35
+
36
+ // Parse JSON into intermediate struct
37
+ var historyItems []types.HistoryItem
38
+ if err := json.Unmarshal(data, &historyItems); err != nil {
39
+ return fmt.Errorf("failed to parse task history: %w", err)
40
+ }
41
+
42
+ if len(historyItems) == 0 {
43
+ fmt.Println("No task history found.")
44
+ return nil
45
+ }
46
+
47
+ // Sort by timestamp ascending (oldest first, newest last)
48
+ sort.Slice(historyItems, func(i, j int) bool {
49
+ return historyItems[i].Ts < historyItems[j].Ts
50
+ })
51
+
52
+ // Convert to protobuf TaskItem format for rendering
53
+ tasks := make([]*cline.TaskItem, len(historyItems))
54
+ for i, item := range historyItems {
55
+ tasks[i] = &cline.TaskItem{
56
+ Id: item.Id,
57
+ Task: item.Task,
58
+ Ts: item.Ts,
59
+ IsFavorited: item.IsFavorited,
60
+ Size: item.Size,
61
+ TotalCost: item.TotalCost,
62
+ TokensIn: item.TokensIn,
63
+ TokensOut: item.TokensOut,
64
+ CacheWrites: item.CacheWrites,
65
+ CacheReads: item.CacheReads,
66
+ }
67
+ }
68
+
69
+ // Use existing renderer
70
+ renderer := display.NewRenderer(global.Config.OutputFormat)
71
+ return renderer.RenderTaskList(tasks)
72
+ }