fcemail 0.1.11

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/cmd/root.go ADDED
@@ -0,0 +1,678 @@
1
+ package cmd
2
+
3
+ import (
4
+ "fmt"
5
+ "math/rand"
6
+ "os"
7
+ "os/exec"
8
+ "strings"
9
+ "time"
10
+
11
+ "github.com/DishIs/fce-cli/internal/api"
12
+ "github.com/DishIs/fce-cli/internal/auth"
13
+ "github.com/DishIs/fce-cli/internal/config"
14
+ "github.com/DishIs/fce-cli/internal/display"
15
+ "github.com/DishIs/fce-cli/internal/ws"
16
+ "github.com/spf13/cobra"
17
+ )
18
+
19
+ var (
20
+ Version = "dev"
21
+ Commit = "none"
22
+ Date = "unknown"
23
+ )
24
+
25
+ // ── Root command ──────────────────────────────────────────────────────────────
26
+
27
+ var rootCmd = &cobra.Command{
28
+ Use: "fce",
29
+ Short: "FreeCustom.Email CLI — disposable inboxes, OTP extraction, real-time email",
30
+ Long: `
31
+ ◉ fce — FreeCustom.Email CLI
32
+
33
+ Manage disposable inboxes, extract OTPs, and stream
34
+ real-time email events from your terminal.
35
+
36
+ Get started:
37
+ fce login Authenticate with your account
38
+ fce watch random Watch a random inbox for emails
39
+ fce status View your account and plan
40
+
41
+ Docs: https://www.freecustom.email/api/cli
42
+ `,
43
+ SilenceUsage: true,
44
+ SilenceErrors: true,
45
+ }
46
+
47
+ func Execute() {
48
+ if err := rootCmd.Execute(); err != nil {
49
+ display.Error(err.Error())
50
+ os.Exit(1)
51
+ }
52
+ }
53
+
54
+ func init() {
55
+ rootCmd.AddCommand(
56
+ loginCmd,
57
+ logoutCmd,
58
+ statusCmd,
59
+ usageCmd,
60
+ watchCmd,
61
+ inboxCmd,
62
+ messagesCmd,
63
+ otpCmd,
64
+ domainsCmd,
65
+ versionCmd,
66
+ devCmd,
67
+ updateCmd,
68
+ uninstallCmd,
69
+ )
70
+ }
71
+
72
+ // ── fce uninstall ─────────────────────────────────────────────────────────────
73
+
74
+ var uninstallCmd = &cobra.Command{
75
+ Use: "uninstall",
76
+ Short: "Remove all local configuration and credentials",
77
+ RunE: func(cmd *cobra.Command, args []string) error {
78
+ fmt.Print("Are you sure you want to remove all local configuration and logout? (y/N): ")
79
+ var confirm string
80
+ fmt.Scanln(&confirm)
81
+ if strings.ToLower(confirm) != "y" {
82
+ display.Info("Aborted.")
83
+ return nil
84
+ }
85
+
86
+ if err := config.Purge(); err != nil {
87
+ return err
88
+ }
89
+
90
+ display.Success("Local configuration and credentials cleared.")
91
+
92
+ // Platform-specific instructions
93
+ display.Header("Next Steps")
94
+ fmt.Println("To completely remove the fce binary, run the command for your platform:")
95
+ fmt.Println()
96
+ fmt.Println(display.TableBadge("Homebrew", "brew uninstall fce"))
97
+ fmt.Println(display.TableBadge("Scoop", "scoop uninstall fce"))
98
+ fmt.Println(display.TableBadge("Choco", "choco uninstall fce"))
99
+ fmt.Println(display.TableBadge("NPM", "npm uninstall -g fce-cli"))
100
+ fmt.Println(display.TableBadge("Manual", "sudo rm /usr/local/bin/fce"))
101
+ fmt.Println()
102
+
103
+ return nil
104
+ },
105
+ }
106
+
107
+ // ── fce dev ───────────────────────────────────────────────────────────────────
108
+
109
+ var devCmd = &cobra.Command{
110
+ Use: "dev",
111
+ Short: "Instantly register a dev inbox and start watching for emails",
112
+ RunE: func(cmd *cobra.Command, args []string) error {
113
+ client, err := requireAuth()
114
+ if err != nil {
115
+ return err
116
+ }
117
+
118
+ inbox := devInbox()
119
+ display.Info(fmt.Sprintf("Temporary inbox: %s", inbox))
120
+
121
+ if _, err := client.RegisterInbox(inbox); err != nil {
122
+ return fmt.Errorf("failed to register inbox: %w", err)
123
+ }
124
+
125
+ display.Success("Watching for emails...")
126
+ fmt.Println()
127
+
128
+ return ws.Watch(client.GetAPIKey(), inbox)
129
+ },
130
+ }
131
+
132
+ // ── fce update ────────────────────────────────────────────────────────────────
133
+
134
+ var updateCmd = &cobra.Command{
135
+ Use: "update",
136
+ Short: "Update the CLI to the latest version",
137
+ RunE: func(cmd *cobra.Command, args []string) error {
138
+ display.Info("Checking for updates...")
139
+
140
+ installCmd := "curl -fsSL https://raw.githubusercontent.com/DishIs/fce-cli/main/scripts/install.sh | sh"
141
+ fmt.Printf("Running installer: %s\n", installCmd)
142
+
143
+ // Execute the installation command
144
+ var c *exec.Cmd
145
+ if os.Getenv("OS") == "Windows_NT" {
146
+ display.Warn("Auto-update on Windows is currently best handled via Scoop or Chocolatey.")
147
+ fmt.Println("Try: scoop update fce OR choco upgrade fce")
148
+ return nil
149
+ } else {
150
+ c = exec.Command("sh", "-c", installCmd)
151
+ }
152
+
153
+ c.Stdout = os.Stdout
154
+ c.Stderr = os.Stderr
155
+ if err := c.Run(); err != nil {
156
+ return fmt.Errorf("update failed: %w", err)
157
+ }
158
+
159
+ display.Success("Update complete!")
160
+ return nil
161
+ },
162
+ }
163
+
164
+ // ── fce version ───────────────────────────────────────────────────────────────
165
+
166
+ var versionCmd = &cobra.Command{
167
+ Use: "version",
168
+ Short: "Show version info",
169
+ Run: func(cmd *cobra.Command, args []string) {
170
+ fmt.Printf("fce %s (%s) %s\n", Version, Commit, Date)
171
+ },
172
+ }
173
+
174
+ // ── fce login ─────────────────────────────────────────────────────────────────
175
+
176
+ var loginCmd = &cobra.Command{
177
+ Use: "login",
178
+ Short: "Authenticate with your FreeCustom.Email account",
179
+ Long: `Opens your browser to complete authentication.
180
+ After logging in on the website, your API key is saved
181
+ securely in your OS keychain.`,
182
+ RunE: func(cmd *cobra.Command, args []string) error {
183
+ return auth.Login()
184
+ },
185
+ }
186
+
187
+ // ── fce logout ────────────────────────────────────────────────────────────────
188
+
189
+ var logoutCmd = &cobra.Command{
190
+ Use: "logout",
191
+ Short: "Remove stored credentials",
192
+ RunE: func(cmd *cobra.Command, args []string) error {
193
+ return auth.Logout()
194
+ },
195
+ }
196
+
197
+ // ── fce status ────────────────────────────────────────────────────────────────
198
+
199
+ var statusCmd = &cobra.Command{
200
+ Use: "status",
201
+ Short: "Show account info, plan, and inbox counts",
202
+ Example: ` fce status`,
203
+ RunE: func(cmd *cobra.Command, args []string) error {
204
+ client, err := requireAuth()
205
+ if err != nil {
206
+ return err
207
+ }
208
+
209
+ result, err := client.GetMe()
210
+ if err != nil {
211
+ return err
212
+ }
213
+
214
+ d, _ := result["data"].(map[string]interface{})
215
+ if d == nil {
216
+ d = result
217
+ }
218
+
219
+ plan := strVal(d, "plan")
220
+ planLabel := strVal(d, "plan_label")
221
+ price := strVal(d, "price")
222
+ credits := fmt.Sprintf("%v", intVal(d, "credits"))
223
+ apiInboxes := fmt.Sprintf("%v", intVal(d, "api_inbox_count"))
224
+ appInboxes := fmt.Sprintf("%v", intVal(d, "app_inbox_count"))
225
+
226
+ display.Header("Account")
227
+ display.Table([]display.Row{
228
+ {Key: "Plan", Value: planLabel + " " + display.PlanBadge(plan)},
229
+ {Key: "Price", Value: price},
230
+ {Key: "Credits", Value: credits + " remaining"},
231
+ {Key: "API inboxes", Value: apiInboxes},
232
+ {Key: "App inboxes", Value: appInboxes},
233
+ })
234
+
235
+ // Cache plan in config
236
+ cfg, _ := config.LoadConfig()
237
+ cfg.Plan = plan
238
+ cfg.PlanLabel = planLabel
239
+ _ = config.SaveConfig(cfg)
240
+
241
+ return nil
242
+ },
243
+ }
244
+
245
+ // ── fce usage ─────────────────────────────────────────────────────────────────
246
+
247
+ var usageCmd = &cobra.Command{
248
+ Use: "usage",
249
+ Short: "Show request usage for the current billing period",
250
+ RunE: func(cmd *cobra.Command, args []string) error {
251
+ client, err := requireAuth()
252
+ if err != nil {
253
+ return err
254
+ }
255
+
256
+ result, err := client.GetUsage()
257
+ if err != nil {
258
+ return err
259
+ }
260
+
261
+ d, _ := result["data"].(map[string]interface{})
262
+ if d == nil {
263
+ d = result
264
+ }
265
+
266
+ used := fmt.Sprintf("%v", d["requests_used"])
267
+ limit := fmt.Sprintf("%v", d["requests_limit"])
268
+ remaining := fmt.Sprintf("%v", d["requests_remaining"])
269
+ pct := strVal(d, "percent_used")
270
+ credits := fmt.Sprintf("%v", d["credits_remaining"])
271
+ resets := strVal(d, "resets")
272
+
273
+ display.Header("Usage")
274
+ display.Table([]display.Row{
275
+ {Key: "Requests used", Value: used + " / " + limit},
276
+ {Key: "Remaining", Value: remaining},
277
+ {Key: "Percent used", Value: pct},
278
+ {Key: "Credits remaining", Value: credits},
279
+ {Key: "Resets approx", Value: resets},
280
+ })
281
+
282
+ return nil
283
+ },
284
+ }
285
+
286
+ // ── fce watch ─────────────────────────────────────────────────────────────────
287
+
288
+ var watchCmd = &cobra.Command{
289
+ Use: "watch [inbox|random]",
290
+ Short: "Stream emails in real time via WebSocket [Startup plan+]",
291
+ Long: `Connects to a WebSocket and streams incoming emails to your terminal.
292
+
293
+ fce watch Watch all registered inboxes
294
+ fce watch random Watch a new random inbox (auto-registers)
295
+ fce watch test@ditmail.info Watch a specific inbox
296
+
297
+ Requires Startup plan or above. Emails arrive in under 200ms.`,
298
+ Example: ` fce watch
299
+ fce watch random
300
+ fce watch mytest@ditube.info`,
301
+ RunE: func(cmd *cobra.Command, args []string) error {
302
+ client, err := requireAuth()
303
+ if err != nil {
304
+ return err
305
+ }
306
+
307
+ // Plan gate
308
+ me, err := client.GetMe()
309
+ if err != nil {
310
+ return err
311
+ }
312
+ d, _ := me["data"].(map[string]interface{})
313
+ if d == nil {
314
+ d = me
315
+ }
316
+ plan := strVal(d, "plan")
317
+ if !api.HasPlan(plan, api.PlanStartup) {
318
+ display.PlanGate("startup", "WebSocket watch")
319
+ return nil
320
+ }
321
+
322
+ apiKey := client.GetAPIKey()
323
+ mailbox := ""
324
+
325
+ if len(args) > 0 {
326
+ arg := args[0]
327
+ if arg == "random" {
328
+ // Generate a random inbox and register it
329
+ mailbox = randomInbox()
330
+ display.Info(fmt.Sprintf("Registering random inbox: %s", mailbox))
331
+ if _, err := client.RegisterInbox(mailbox); err != nil {
332
+ return fmt.Errorf("failed to register inbox: %w", err)
333
+ }
334
+ display.Success(fmt.Sprintf("Inbox ready: %s", mailbox))
335
+ fmt.Println()
336
+ } else {
337
+ mailbox = arg
338
+ }
339
+ }
340
+
341
+ return ws.Watch(apiKey, mailbox)
342
+ },
343
+ }
344
+
345
+ // ── fce inbox ─────────────────────────────────────────────────────────────────
346
+
347
+ var inboxCmd = &cobra.Command{
348
+ Use: "inbox",
349
+ Short: "Manage registered inboxes",
350
+ }
351
+
352
+ var inboxListCmd = &cobra.Command{
353
+ Use: "list",
354
+ Aliases: []string{"ls"},
355
+ Short: "List all registered inboxes",
356
+ RunE: func(cmd *cobra.Command, args []string) error {
357
+ client, err := requireAuth()
358
+ if err != nil {
359
+ return err
360
+ }
361
+
362
+ inboxes, err := client.ListInboxes()
363
+ if err != nil {
364
+ return err
365
+ }
366
+
367
+ if len(inboxes) == 0 {
368
+ display.Info("No inboxes registered.")
369
+ display.Info("Add one with: fce inbox add <address>")
370
+ return nil
371
+ }
372
+
373
+ display.Header(fmt.Sprintf("Inboxes (%d)", len(inboxes)))
374
+ items := make([]string, 0, len(inboxes))
375
+ for _, item := range inboxes {
376
+ if m, ok := item.(map[string]interface{}); ok {
377
+ items = append(items, strVal(m, "inbox"))
378
+ } else if s, ok := item.(string); ok {
379
+ items = append(items, s)
380
+ }
381
+ }
382
+ display.List(items)
383
+ return nil
384
+ },
385
+ }
386
+
387
+ var inboxAddCmd = &cobra.Command{
388
+ Use: "add <address>",
389
+ Aliases: []string{"register"},
390
+ Short: "Register a new inbox",
391
+ Example: ` fce inbox add mytest@ditmail.info
392
+ fce inbox add random`,
393
+ Args: cobra.ExactArgs(1),
394
+ RunE: func(cmd *cobra.Command, args []string) error {
395
+ client, err := requireAuth()
396
+ if err != nil {
397
+ return err
398
+ }
399
+
400
+ inbox := args[0]
401
+ if inbox == "random" {
402
+ inbox = randomInbox()
403
+ display.Info(fmt.Sprintf("Generated inbox: %s", inbox))
404
+ }
405
+
406
+ result, err := client.RegisterInbox(inbox)
407
+ if err != nil {
408
+ return err
409
+ }
410
+
411
+ registered := strVal(result, "inbox")
412
+ if registered == "" {
413
+ registered = inbox
414
+ }
415
+ display.Success(fmt.Sprintf("Registered: %s", registered))
416
+ return nil
417
+ },
418
+ }
419
+
420
+ var inboxRemoveCmd = &cobra.Command{
421
+ Use: "remove <address>",
422
+ Aliases: []string{"rm", "delete", "unregister"},
423
+ Short: "Unregister an inbox",
424
+ Args: cobra.ExactArgs(1),
425
+ RunE: func(cmd *cobra.Command, args []string) error {
426
+ client, err := requireAuth()
427
+ if err != nil {
428
+ return err
429
+ }
430
+
431
+ if _, err := client.UnregisterInbox(args[0]); err != nil {
432
+ return err
433
+ }
434
+ display.Success(fmt.Sprintf("Removed: %s", args[0]))
435
+ return nil
436
+ },
437
+ }
438
+
439
+ func init() {
440
+ inboxCmd.AddCommand(inboxListCmd, inboxAddCmd, inboxRemoveCmd)
441
+ }
442
+
443
+ // ── fce messages ──────────────────────────────────────────────────────────────
444
+
445
+ var messagesCmd = &cobra.Command{
446
+ Use: "messages <inbox> [id]",
447
+ Aliases: []string{"msgs", "mail"},
448
+ Short: "List messages in an inbox or view a specific message",
449
+ Example: ` fce messages mytest@ditmail.info
450
+ fce messages mytest@ditmail.info u7hPpV5sA`,
451
+ Args: cobra.MinimumNArgs(1),
452
+ RunE: func(cmd *cobra.Command, args []string) error {
453
+ client, err := requireAuth()
454
+ if err != nil {
455
+ return err
456
+ }
457
+
458
+ inbox := args[0]
459
+
460
+ // View specific message
461
+ if len(args) > 1 {
462
+ id := args[1]
463
+ msg, err := client.GetMessage(inbox, id)
464
+ if err != nil {
465
+ return err
466
+ }
467
+ display.MessageContent(msg)
468
+ return nil
469
+ }
470
+
471
+ // List messages
472
+ msgs, err := client.ListMessages(inbox)
473
+ if err != nil {
474
+ return err
475
+ }
476
+
477
+ if len(msgs) == 0 {
478
+ display.Info("No messages in this inbox.")
479
+ return nil
480
+ }
481
+
482
+ display.Header(fmt.Sprintf("Messages in %s (%d)", inbox, len(msgs)))
483
+ fmt.Println()
484
+
485
+ for _, item := range msgs {
486
+ m, ok := item.(map[string]interface{})
487
+ if !ok {
488
+ continue
489
+ }
490
+ id := strVal(m, "id")
491
+ from := strVal(m, "from")
492
+ subject := strVal(m, "subject")
493
+ date := strVal(m, "date")
494
+ otp := strVal(m, "otp")
495
+
496
+ display.Table([]display.Row{
497
+ {Key: "ID", Value: id},
498
+ {Key: "From", Value: from},
499
+ {Key: "Subject", Value: subject},
500
+ {Key: "Date", Value: date},
501
+ {Key: "OTP", Value: func() string {
502
+ if otp == "" || otp == "__DETECTED__" {
503
+ return "—"
504
+ }
505
+ return otp
506
+ }()},
507
+ })
508
+ display.Divider()
509
+ }
510
+ fmt.Println()
511
+ return nil
512
+ },
513
+ }
514
+
515
+ // ── fce otp ───────────────────────────────────────────────────────────────────
516
+
517
+ var otpCmd = &cobra.Command{
518
+ Use: "otp <inbox>",
519
+ Short: "Get the latest OTP from an inbox [Growth plan+]",
520
+ Long: `Fetches the most recent one-time password from an inbox.
521
+ OTP is extracted automatically — no regex needed.
522
+
523
+ Requires Growth plan or above.`,
524
+ Example: ` fce otp mytest@ditmail.info`,
525
+ Args: cobra.ExactArgs(1),
526
+ RunE: func(cmd *cobra.Command, args []string) error {
527
+ client, err := requireAuth()
528
+ if err != nil {
529
+ return err
530
+ }
531
+
532
+ // Plan gate
533
+ me, err := client.GetMe()
534
+ if err != nil {
535
+ return err
536
+ }
537
+ d, _ := me["data"].(map[string]interface{})
538
+ if d == nil {
539
+ d = me
540
+ }
541
+ plan := strVal(d, "plan")
542
+ if !api.HasPlan(plan, api.PlanGrowth) {
543
+ display.PlanGate("growth", "OTP extraction")
544
+ return nil
545
+ }
546
+
547
+ result, err := client.GetOTP(args[0])
548
+ if err != nil {
549
+ return err
550
+ }
551
+
552
+ otp := strVal(result, "otp")
553
+ link := strVal(result, "verification_link")
554
+ from := strVal(result, "from")
555
+ subj := strVal(result, "subject")
556
+ ts := fmt.Sprintf("%v", result["timestamp"])
557
+
558
+ if otp == "" || otp == "null" {
559
+ display.Info("No OTP found in recent messages.")
560
+ return nil
561
+ }
562
+
563
+ display.Header("OTP")
564
+ rows := []display.Row{
565
+ {Key: "OTP", Value: otp},
566
+ {Key: "From", Value: from},
567
+ {Key: "Subj", Value: subj},
568
+ {Key: "Time", Value: ts},
569
+ }
570
+ if link != "" && link != "null" {
571
+ rows = append(rows, display.Row{Key: "Link", Value: link})
572
+ }
573
+ display.Table(rows)
574
+ return nil
575
+ },
576
+ }
577
+
578
+ // ── fce domains ───────────────────────────────────────────────────────────────
579
+
580
+ var domainsCmd = &cobra.Command{
581
+ Use: "domains",
582
+ Short: "List available domains on your plan",
583
+ RunE: func(cmd *cobra.Command, args []string) error {
584
+ client, err := requireAuth()
585
+ if err != nil {
586
+ return err
587
+ }
588
+
589
+ domains, err := client.ListDomains()
590
+ if err != nil {
591
+ return err
592
+ }
593
+
594
+ if len(domains) == 0 {
595
+ display.Info("No domains available.")
596
+ return nil
597
+ }
598
+
599
+ display.Header(fmt.Sprintf("Available Domains (%d)", len(domains)))
600
+ items := make([]string, 0, len(domains))
601
+ for _, item := range domains {
602
+ if m, ok := item.(map[string]interface{}); ok {
603
+ domain := strVal(m, "domain")
604
+ tier := strVal(m, "tier")
605
+ suffix := ""
606
+ if tier == "pro" {
607
+ suffix = " " + display.PlanBadge("pro")
608
+ }
609
+ items = append(items, domain+suffix)
610
+ }
611
+ }
612
+ display.List(items)
613
+ return nil
614
+ },
615
+ }
616
+
617
+ // ── Helpers ───────────────────────────────────────────────────────────────────
618
+
619
+ func requireAuth() (*api.Client, error) {
620
+ client, err := api.New()
621
+ if err != nil {
622
+ display.Error("Not logged in.")
623
+ display.Info("Run: fce login")
624
+ return nil, fmt.Errorf("not authenticated")
625
+ }
626
+ return client, nil
627
+ }
628
+
629
+ func strVal(m map[string]interface{}, key string) string {
630
+ v, _ := m[key].(string)
631
+ return v
632
+ }
633
+
634
+ func intVal(m map[string]interface{}, key string) int {
635
+ switch v := m[key].(type) {
636
+ case float64:
637
+ return int(v)
638
+ case int:
639
+ return v
640
+ }
641
+ return 0
642
+ }
643
+
644
+ // randomInbox generates a random inbox address using a free-tier domain
645
+ var randomDomains = []string{
646
+ "ditube.info", "ditmail.info", "ditapi.info",
647
+ "ditgame.info", "ditplay.info", "ditcloud.info",
648
+ }
649
+
650
+ var adjectives = []string{
651
+ "swift", "clear", "quiet", "bright", "calm",
652
+ "sharp", "bold", "cool", "crisp", "light",
653
+ }
654
+
655
+ var nouns = []string{
656
+ "fox", "hawk", "mint", "wave", "peak",
657
+ "pine", "vale", "reef", "beam", "dusk",
658
+ }
659
+
660
+ func randomInbox() string {
661
+ rng := rand.New(rand.NewSource(time.Now().UnixNano()))
662
+ adj := adjectives[rng.Intn(len(adjectives))]
663
+ noun := nouns[rng.Intn(len(nouns))]
664
+ n := rng.Intn(9000) + 1000
665
+ domain := randomDomains[rng.Intn(len(randomDomains))]
666
+ return strings.ToLower(fmt.Sprintf("%s%s%d@%s", adj, noun, n, domain))
667
+ }
668
+
669
+ func devInbox() string {
670
+ rng := rand.New(rand.NewSource(time.Now().UnixNano()))
671
+ chars := "abcdefghijklmnopqrstuvwxyz0123456789"
672
+ suffix := make([]byte, 4)
673
+ for i := range suffix {
674
+ suffix[i] = chars[rng.Intn(len(chars))]
675
+ }
676
+ domain := randomDomains[rng.Intn(len(randomDomains))]
677
+ return fmt.Sprintf("dev-%s@%s", string(suffix), domain)
678
+ }
package/go.mod ADDED
@@ -0,0 +1,26 @@
1
+ module github.com/DishIs/fce-cli
2
+
3
+ go 1.22
4
+
5
+ require (
6
+ github.com/charmbracelet/lipgloss v0.12.1
7
+ github.com/gorilla/websocket v1.5.3
8
+ github.com/spf13/cobra v1.8.1
9
+ github.com/zalando/go-keyring v0.2.5
10
+ )
11
+
12
+ require (
13
+ github.com/alessio/shellescape v1.4.1 // indirect
14
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
15
+ github.com/charmbracelet/x/ansi v0.1.4 // indirect
16
+ github.com/danieljoos/wincred v1.2.0 // indirect
17
+ github.com/godbus/dbus/v5 v5.1.0 // indirect
18
+ github.com/inconshreveable/mousetrap v1.1.0 // indirect
19
+ github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
20
+ github.com/mattn/go-isatty v0.0.20 // indirect
21
+ github.com/mattn/go-runewidth v0.0.15 // indirect
22
+ github.com/muesli/termenv v0.15.2 // indirect
23
+ github.com/rivo/uniseg v0.4.7 // indirect
24
+ github.com/spf13/pflag v1.0.5 // indirect
25
+ golang.org/x/sys v0.19.0 // indirect
26
+ )