@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,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,6 @@
1
+ package common
2
+
3
+ // WE WILL HAVE TO MIGRATE THIS FROM DATA TO v1 LATER
4
+ const SETTINGS_SUBFOLDER = "data"
5
+
6
+ const DEFAULT_CLINE_CORE_PORT = 50052
@@ -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
+ }