@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,409 @@
|
|
|
1
|
+
package updater
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"fmt"
|
|
7
|
+
"net/http"
|
|
8
|
+
"os"
|
|
9
|
+
"os/exec"
|
|
10
|
+
"path/filepath"
|
|
11
|
+
"strings"
|
|
12
|
+
"time"
|
|
13
|
+
|
|
14
|
+
"github.com/charmbracelet/lipgloss"
|
|
15
|
+
"github.com/cline/cli/pkg/cli/global"
|
|
16
|
+
"github.com/cline/cli/pkg/cli/output"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
type cacheData struct {
|
|
20
|
+
LastCheck time.Time `json:"last_check"`
|
|
21
|
+
LatestVersion string `json:"latest_version"`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type npmRegistryResponse struct {
|
|
25
|
+
DistTags struct {
|
|
26
|
+
Latest string `json:"latest"`
|
|
27
|
+
Nightly string `json:"nightly"`
|
|
28
|
+
} `json:"dist-tags"`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const (
|
|
32
|
+
checkInterval = 24 * time.Hour
|
|
33
|
+
requestTimeout = 3 * time.Second
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
var (
|
|
37
|
+
successStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("2")).Bold(true)
|
|
38
|
+
errorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("1")).Bold(true)
|
|
39
|
+
dimStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("8"))
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
var verbose bool
|
|
43
|
+
|
|
44
|
+
// CheckAndUpdate performs a background update check and attempts to auto-update if needed.
|
|
45
|
+
// This is non-blocking and safe to call on CLI startup.
|
|
46
|
+
func CheckAndUpdate(isVerbose bool) {
|
|
47
|
+
verbose = isVerbose
|
|
48
|
+
|
|
49
|
+
// Skip in CI environments
|
|
50
|
+
if os.Getenv("CI") != "" {
|
|
51
|
+
if verbose {
|
|
52
|
+
output.Printf("[updater] Skipping update check (CI environment)\n")
|
|
53
|
+
}
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Skip if user disabled auto-updates
|
|
58
|
+
if os.Getenv("NO_AUTO_UPDATE") != "" {
|
|
59
|
+
if verbose {
|
|
60
|
+
output.Printf("[updater] Skipping update check (NO_AUTO_UPDATE set)\n")
|
|
61
|
+
}
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if verbose {
|
|
66
|
+
output.Printf("[updater] Starting background update check...\n")
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Run in background so we don't block CLI startup
|
|
70
|
+
go func() {
|
|
71
|
+
if err := checkAndUpdateInternal(false); err != nil {
|
|
72
|
+
if verbose {
|
|
73
|
+
output.Printf("[updater] Update check failed: %v\n", err)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// CheckAndUpdateSync performs a synchronous update check (blocks until complete).
|
|
80
|
+
// If bypassCache is true, ignores the 24-hour cache and always checks npm registry.
|
|
81
|
+
// This is used by the doctor command.
|
|
82
|
+
func CheckAndUpdateSync(isVerbose bool, bypassCache bool) {
|
|
83
|
+
verbose = isVerbose
|
|
84
|
+
|
|
85
|
+
// Skip in CI environments
|
|
86
|
+
if os.Getenv("CI") != "" {
|
|
87
|
+
if verbose {
|
|
88
|
+
output.Printf("[updater] Skipping update check (CI environment)\n")
|
|
89
|
+
}
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Skip if user disabled auto-updates
|
|
94
|
+
if os.Getenv("NO_AUTO_UPDATE") != "" {
|
|
95
|
+
if verbose {
|
|
96
|
+
output.Printf("[updater] Skipping update check (NO_AUTO_UPDATE set)\n")
|
|
97
|
+
}
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if verbose {
|
|
102
|
+
output.Printf("[updater] Starting update check...\n")
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Run synchronously
|
|
106
|
+
if err := checkAndUpdateInternal(bypassCache); err != nil {
|
|
107
|
+
if verbose {
|
|
108
|
+
output.Printf("[updater] Update check failed: %v\n", err)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
func checkAndUpdateInternal(bypassCache bool) error {
|
|
114
|
+
if verbose {
|
|
115
|
+
output.Printf("[updater] Loading update cache...\n")
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Load cache
|
|
119
|
+
cache, err := loadCache()
|
|
120
|
+
if !bypassCache && err == nil && time.Since(cache.LastCheck) < checkInterval {
|
|
121
|
+
// Checked recently, skip (unless cache is bypassed)
|
|
122
|
+
if verbose {
|
|
123
|
+
output.Printf("[updater] Cache is fresh (last checked %v ago), skipping\n", time.Since(cache.LastCheck))
|
|
124
|
+
}
|
|
125
|
+
return nil
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if err != nil && verbose {
|
|
129
|
+
output.Printf("[updater] Cache load failed or doesn't exist: %v\n", err)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Determine channel
|
|
133
|
+
distTag := "latest"
|
|
134
|
+
if strings.Contains(global.CliVersion, "nightly") {
|
|
135
|
+
distTag = "nightly"
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if verbose {
|
|
139
|
+
output.Printf("[updater] Current version: %s (channel: %s)\n", global.CliVersion, distTag)
|
|
140
|
+
output.Printf("[updater] Fetching latest version from npm registry...\n")
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Fetch latest version from npm
|
|
144
|
+
latestVersion, err := fetchLatestVersion()
|
|
145
|
+
if err != nil {
|
|
146
|
+
if verbose {
|
|
147
|
+
output.Printf("[updater] Failed to fetch latest version: %v\n", err)
|
|
148
|
+
}
|
|
149
|
+
return err
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if verbose {
|
|
153
|
+
output.Printf("[updater] Latest version on npm: %s\n", latestVersion)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Update cache
|
|
157
|
+
cache = cacheData{
|
|
158
|
+
LastCheck: time.Now(),
|
|
159
|
+
LatestVersion: latestVersion,
|
|
160
|
+
}
|
|
161
|
+
saveCache(cache)
|
|
162
|
+
|
|
163
|
+
if verbose {
|
|
164
|
+
output.Printf("[updater] Updated cache\n")
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Compare versions
|
|
168
|
+
currentVersion := strings.TrimPrefix(global.CliVersion, "v")
|
|
169
|
+
latestVersion = strings.TrimPrefix(latestVersion, "v")
|
|
170
|
+
|
|
171
|
+
if verbose {
|
|
172
|
+
output.Printf("[updater] Comparing versions: current=%s latest=%s\n", currentVersion, latestVersion)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if !isNewer(latestVersion, currentVersion) {
|
|
176
|
+
// Already up to date
|
|
177
|
+
if verbose {
|
|
178
|
+
output.Printf("[updater] Already on latest version, no update needed\n")
|
|
179
|
+
}
|
|
180
|
+
return nil
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if verbose {
|
|
184
|
+
output.Printf("[updater] Update available! Attempting to install...\n")
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Determine channel for update command
|
|
188
|
+
channel := "latest"
|
|
189
|
+
if strings.Contains(global.CliVersion, "nightly") {
|
|
190
|
+
channel = "nightly"
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Attempt update
|
|
194
|
+
if verbose {
|
|
195
|
+
output.Printf("[updater] Running: npm install -g cline%s\n",
|
|
196
|
+
map[bool]string{true: "@"+channel, false: ""}[channel == "nightly"])
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if err := attemptUpdate(channel); err != nil {
|
|
200
|
+
if verbose {
|
|
201
|
+
output.Printf("[updater] Update failed: %v\n", err)
|
|
202
|
+
}
|
|
203
|
+
showFailureMessage(channel)
|
|
204
|
+
return err
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if verbose {
|
|
208
|
+
output.Printf("[updater] Update completed successfully!\n")
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
showSuccessMessage(latestVersion)
|
|
212
|
+
return nil
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
func fetchLatestVersion() (string, error) {
|
|
216
|
+
// Determine dist-tag from current version
|
|
217
|
+
distTag := "latest"
|
|
218
|
+
if strings.Contains(global.CliVersion, "nightly") {
|
|
219
|
+
distTag = "nightly"
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
|
|
223
|
+
defer cancel()
|
|
224
|
+
|
|
225
|
+
req, err := http.NewRequestWithContext(ctx, "GET", "https://registry.npmjs.org/cline", nil)
|
|
226
|
+
if err != nil {
|
|
227
|
+
return "", err
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
resp, err := http.DefaultClient.Do(req)
|
|
231
|
+
if err != nil {
|
|
232
|
+
return "", err
|
|
233
|
+
}
|
|
234
|
+
defer resp.Body.Close()
|
|
235
|
+
|
|
236
|
+
if resp.StatusCode != http.StatusOK {
|
|
237
|
+
return "", fmt.Errorf("npm registry returned status %d", resp.StatusCode)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
var data npmRegistryResponse
|
|
241
|
+
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
|
242
|
+
return "", err
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if distTag == "nightly" {
|
|
246
|
+
return data.DistTags.Nightly, nil
|
|
247
|
+
}
|
|
248
|
+
return data.DistTags.Latest, nil
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
func attemptUpdate(channel string) error {
|
|
252
|
+
packageName := "cline"
|
|
253
|
+
if channel == "nightly" {
|
|
254
|
+
packageName = "cline@nightly"
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
cmd := exec.Command("npm", "install", "-g", packageName)
|
|
258
|
+
cmd.Stdout = nil
|
|
259
|
+
cmd.Stderr = nil
|
|
260
|
+
|
|
261
|
+
return cmd.Run()
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
func isNewer(latest, current string) bool {
|
|
265
|
+
// Parse version strings (e.g., "1.0.0-nightly.19")
|
|
266
|
+
latestBase, latestSuffix := parseVersion(latest)
|
|
267
|
+
currentBase, currentSuffix := parseVersion(current)
|
|
268
|
+
|
|
269
|
+
// Compare base versions (1.0.0)
|
|
270
|
+
comparison := compareVersionParts(latestBase, currentBase)
|
|
271
|
+
if comparison != 0 {
|
|
272
|
+
return comparison > 0
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Base versions are equal, compare suffixes (nightly.19)
|
|
276
|
+
return compareSuffix(latestSuffix, currentSuffix) > 0
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
func parseVersion(version string) (string, string) {
|
|
280
|
+
parts := strings.SplitN(version, "-", 2)
|
|
281
|
+
if len(parts) == 2 {
|
|
282
|
+
return parts[0], parts[1]
|
|
283
|
+
}
|
|
284
|
+
return parts[0], ""
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
func compareVersionParts(v1, v2 string) int {
|
|
288
|
+
parts1 := strings.Split(v1, ".")
|
|
289
|
+
parts2 := strings.Split(v2, ".")
|
|
290
|
+
|
|
291
|
+
for i := 0; i < len(parts1) && i < len(parts2); i++ {
|
|
292
|
+
// Convert to int for proper numeric comparison
|
|
293
|
+
n1 := parseInt(parts1[i])
|
|
294
|
+
n2 := parseInt(parts2[i])
|
|
295
|
+
|
|
296
|
+
if n1 > n2 {
|
|
297
|
+
return 1
|
|
298
|
+
}
|
|
299
|
+
if n1 < n2 {
|
|
300
|
+
return -1
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// If all parts are equal, longer version is newer
|
|
305
|
+
if len(parts1) > len(parts2) {
|
|
306
|
+
return 1
|
|
307
|
+
}
|
|
308
|
+
if len(parts1) < len(parts2) {
|
|
309
|
+
return -1
|
|
310
|
+
}
|
|
311
|
+
return 0
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
func compareSuffix(s1, s2 string) int {
|
|
315
|
+
// If one has no suffix, stable > prerelease
|
|
316
|
+
if s1 == "" && s2 == "" {
|
|
317
|
+
return 0
|
|
318
|
+
}
|
|
319
|
+
if s1 == "" {
|
|
320
|
+
return 1 // Stable is newer than prerelease
|
|
321
|
+
}
|
|
322
|
+
if s2 == "" {
|
|
323
|
+
return -1 // Prerelease is older than stable
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Both have suffixes (e.g., "nightly.19" vs "nightly.18")
|
|
327
|
+
// Extract the numeric part after the last dot
|
|
328
|
+
n1 := extractBuildNumber(s1)
|
|
329
|
+
n2 := extractBuildNumber(s2)
|
|
330
|
+
|
|
331
|
+
if n1 > n2 {
|
|
332
|
+
return 1
|
|
333
|
+
}
|
|
334
|
+
if n1 < n2 {
|
|
335
|
+
return -1
|
|
336
|
+
}
|
|
337
|
+
return 0
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
func extractBuildNumber(suffix string) int {
|
|
341
|
+
// Extract number from "nightly.19" -> 19
|
|
342
|
+
parts := strings.Split(suffix, ".")
|
|
343
|
+
if len(parts) > 1 {
|
|
344
|
+
return parseInt(parts[len(parts)-1])
|
|
345
|
+
}
|
|
346
|
+
return 0
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
func parseInt(s string) int {
|
|
350
|
+
var result int
|
|
351
|
+
fmt.Sscanf(s, "%d", &result)
|
|
352
|
+
return result
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
func showSuccessMessage(version string) {
|
|
356
|
+
output.Printf("\n%s Updated to %s %s Changes will take effect next session\n\n",
|
|
357
|
+
successStyle.Render("✓"),
|
|
358
|
+
successStyle.Render("v"+version),
|
|
359
|
+
dimStyle.Render("→"),
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
func showFailureMessage(channel string) {
|
|
364
|
+
packageName := "cline"
|
|
365
|
+
if channel == "nightly" {
|
|
366
|
+
packageName = "cline@nightly"
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
output.Printf("\n%s Auto-update failed %s Try: %s\n\n",
|
|
370
|
+
errorStyle.Render("✗"),
|
|
371
|
+
dimStyle.Render("·"),
|
|
372
|
+
"npm install -g "+packageName,
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
func getCacheFilePath() string {
|
|
377
|
+
configDir := filepath.Join(os.Getenv("HOME"), ".cline", "data")
|
|
378
|
+
return filepath.Join(configDir, "cli-update-cache")
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
func loadCache() (cacheData, error) {
|
|
382
|
+
var cache cacheData
|
|
383
|
+
cacheFile := getCacheFilePath()
|
|
384
|
+
|
|
385
|
+
data, err := os.ReadFile(cacheFile)
|
|
386
|
+
if err != nil {
|
|
387
|
+
return cache, err
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
err = json.Unmarshal(data, &cache)
|
|
391
|
+
return cache, err
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
func saveCache(cache cacheData) error {
|
|
395
|
+
cacheFile := getCacheFilePath()
|
|
396
|
+
|
|
397
|
+
// Ensure config directory exists
|
|
398
|
+
configDir := filepath.Dir(cacheFile)
|
|
399
|
+
if err := os.MkdirAll(configDir, 0755); err != nil {
|
|
400
|
+
return err
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
data, err := json.Marshal(cache)
|
|
404
|
+
if err != nil {
|
|
405
|
+
return err
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return os.WriteFile(cacheFile, data, 0644)
|
|
409
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
package cli
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"runtime"
|
|
6
|
+
|
|
7
|
+
"github.com/cline/cli/pkg/cli/global"
|
|
8
|
+
"github.com/spf13/cobra"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// NewVersionCommand creates the version command
|
|
12
|
+
func NewVersionCommand() *cobra.Command {
|
|
13
|
+
var short bool
|
|
14
|
+
|
|
15
|
+
cmd := &cobra.Command{
|
|
16
|
+
Use: "version",
|
|
17
|
+
Aliases: []string{"v"},
|
|
18
|
+
Short: "Show version information",
|
|
19
|
+
Long: `Display version information for the Caret CLI.`,
|
|
20
|
+
RunE: func(cmd *cobra.Command, args []string) error {
|
|
21
|
+
// Versions are injected at build time via ldflags
|
|
22
|
+
if short {
|
|
23
|
+
fmt.Println(global.CliVersion)
|
|
24
|
+
return nil
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
fmt.Printf("Caret CLI\n")
|
|
28
|
+
fmt.Printf("Caret CLI Version: %s\n", global.CliVersion)
|
|
29
|
+
fmt.Printf("Caret Core Version: %s\n", global.Version)
|
|
30
|
+
fmt.Printf("Commit: %s\n", global.Commit)
|
|
31
|
+
fmt.Printf("Built: %s\n", global.Date)
|
|
32
|
+
fmt.Printf("Built by: %s\n", global.BuiltBy)
|
|
33
|
+
fmt.Printf("Go version: %s\n", runtime.Version())
|
|
34
|
+
fmt.Printf("OS/Arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
|
35
|
+
|
|
36
|
+
return nil
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
cmd.Flags().BoolVar(&short, "short", false, "show only version number")
|
|
41
|
+
|
|
42
|
+
return cmd
|
|
43
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
package common
|
|
2
|
+
|
|
3
|
+
// Database query constants for the SQLite locks database
|
|
4
|
+
const (
|
|
5
|
+
|
|
6
|
+
// SelectInstanceLocksSQL selects all instance locks ordered by creation time
|
|
7
|
+
SelectInstanceLocksSQL = `
|
|
8
|
+
SELECT id, held_by, lock_type, lock_target, locked_at
|
|
9
|
+
FROM locks
|
|
10
|
+
WHERE lock_type = 'instance'
|
|
11
|
+
ORDER BY locked_at ASC
|
|
12
|
+
`
|
|
13
|
+
|
|
14
|
+
SelectInstanceLockByHolderSQL = `
|
|
15
|
+
SELECT held_by, lock_target, locked_at
|
|
16
|
+
FROM locks
|
|
17
|
+
WHERE held_by = ? AND lock_type = 'instance'
|
|
18
|
+
`
|
|
19
|
+
SelectInstanceLockHoldersAscSQL = `
|
|
20
|
+
SELECT held_by, lock_target, locked_at
|
|
21
|
+
FROM locks
|
|
22
|
+
WHERE lock_type = 'instance'
|
|
23
|
+
ORDER BY locked_at ASC
|
|
24
|
+
`
|
|
25
|
+
|
|
26
|
+
// DeleteInstanceLockSQL deletes an instance lock by address
|
|
27
|
+
DeleteInstanceLockSQL = `
|
|
28
|
+
DELETE FROM locks
|
|
29
|
+
WHERE held_by = ? AND lock_type = 'instance'
|
|
30
|
+
`
|
|
31
|
+
|
|
32
|
+
InsertFileLockSQL = `
|
|
33
|
+
INSERT INTO locks (held_by, lock_type, lock_target, locked_at)
|
|
34
|
+
VALUES (?, 'file', ?, ?)
|
|
35
|
+
`
|
|
36
|
+
|
|
37
|
+
// DeleteFileLockSQL deletes a file lock by holder and target
|
|
38
|
+
DeleteFileLockSQL = `
|
|
39
|
+
DELETE FROM locks
|
|
40
|
+
WHERE held_by = ? AND lock_type = 'file' AND lock_target = ?
|
|
41
|
+
`
|
|
42
|
+
|
|
43
|
+
// CountInstanceLockSQL counts instance locks for a given address
|
|
44
|
+
CountInstanceLockSQL = `
|
|
45
|
+
SELECT COUNT(*) FROM locks
|
|
46
|
+
WHERE held_by = ? AND lock_type = 'instance'
|
|
47
|
+
`
|
|
48
|
+
|
|
49
|
+
// InsertInstanceLockSQL inserts or replaces an instance lock
|
|
50
|
+
InsertInstanceLockSQL = `
|
|
51
|
+
INSERT OR REPLACE INTO locks (held_by, lock_type, lock_target, locked_at)
|
|
52
|
+
VALUES (?, 'instance', ?, ?)
|
|
53
|
+
`
|
|
54
|
+
)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
package common
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"time"
|
|
5
|
+
|
|
6
|
+
"google.golang.org/grpc/health/grpc_health_v1"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// CoreInstanceInfo represents a discovered Cline instance
|
|
10
|
+
// This is the canonical definition used across all CLI packages
|
|
11
|
+
type CoreInstanceInfo struct {
|
|
12
|
+
// Full core address including port
|
|
13
|
+
Address string `json:"address"`
|
|
14
|
+
// Host bridge service address that core holds (host is ALWAYS running on localhost FYI)
|
|
15
|
+
HostServiceAddress string `json:"host_port"`
|
|
16
|
+
Status grpc_health_v1.HealthCheckResponse_ServingStatus `json:"status"`
|
|
17
|
+
LastSeen time.Time `json:"last_seen"`
|
|
18
|
+
ProcessPID int `json:"process_pid,omitempty"`
|
|
19
|
+
Version string `json:"version,omitempty"`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func (c *CoreInstanceInfo) CorePort() int {
|
|
23
|
+
_, port, _ := ParseHostPort(c.Address)
|
|
24
|
+
return port
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func (c *CoreInstanceInfo) HostPort() int {
|
|
28
|
+
_, port, _ := ParseHostPort(c.HostServiceAddress)
|
|
29
|
+
return port
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func (c *CoreInstanceInfo) StatusString() string {
|
|
33
|
+
return c.Status.String()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// LockRow represents a row in the locks table
|
|
37
|
+
type LockRow struct {
|
|
38
|
+
ID int64 `json:"id"`
|
|
39
|
+
HeldBy string `json:"held_by"`
|
|
40
|
+
LockType string `json:"lock_type"`
|
|
41
|
+
LockTarget string `json:"lock_target"`
|
|
42
|
+
LockedAt int64 `json:"locked_at"`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// InstancesOutput represents the JSON output format for instance listing
|
|
46
|
+
type InstancesOutput struct {
|
|
47
|
+
DefaultInstance string `json:"default_instance"`
|
|
48
|
+
CoreInstances []CoreInstanceInfo `json:"instances"`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type DefaultCoreInstance struct {
|
|
52
|
+
Address string `json:"default_instance"`
|
|
53
|
+
LastUpdated string `json:"last_updated"`
|
|
54
|
+
}
|