fizzy-cli 0.2.0 → 0.3.0

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.
@@ -0,0 +1,51 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "strings"
7
+
8
+ "github.com/rogeriopvl/fizzy/internal/app"
9
+ "github.com/rogeriopvl/fizzy/internal/ui"
10
+ "github.com/spf13/cobra"
11
+ )
12
+
13
+ var notificationReadCmd = &cobra.Command{
14
+ Use: "read <notification_id>",
15
+ Short: "Mark notification as read and display it",
16
+ Long: `Mark a notification as read and display its content`,
17
+ Args: cobra.ExactArgs(1),
18
+ Run: func(cmd *cobra.Command, args []string) {
19
+ if err := handleReadNotification(cmd, args[0]); err != nil {
20
+ fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
21
+ }
22
+ },
23
+ }
24
+
25
+ func handleReadNotification(cmd *cobra.Command, notificationID string) error {
26
+ a := app.FromContext(cmd.Context())
27
+ if a == nil || a.Client == nil {
28
+ return fmt.Errorf("API client not available")
29
+ }
30
+
31
+ if _, err := a.Client.PostNotificationReading(context.Background(), notificationID); err != nil {
32
+ if strings.Contains(err.Error(), "404") {
33
+ return fmt.Errorf("notification not found")
34
+ }
35
+ return fmt.Errorf("marking notification as read: %w", err)
36
+ }
37
+
38
+ notification, err := a.Client.GetNotification(context.Background(), notificationID)
39
+ if err != nil {
40
+ if strings.Contains(err.Error(), "404") {
41
+ return fmt.Errorf("notification not found")
42
+ }
43
+ return fmt.Errorf("fetching notification: %w", err)
44
+ }
45
+
46
+ return ui.DisplayNotification(notification)
47
+ }
48
+
49
+ func init() {
50
+ notificationCmd.AddCommand(notificationReadCmd)
51
+ }
@@ -0,0 +1,38 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+
7
+ "github.com/rogeriopvl/fizzy/internal/app"
8
+ "github.com/spf13/cobra"
9
+ )
10
+
11
+ var notificationReadAllCmd = &cobra.Command{
12
+ Use: "read-all",
13
+ Short: "Mark all unread notifications as read",
14
+ Long: `Mark all unread notifications as read`,
15
+ Run: func(cmd *cobra.Command, args []string) {
16
+ if err := handleReadAllNotifications(cmd); err != nil {
17
+ fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
18
+ }
19
+ },
20
+ }
21
+
22
+ func handleReadAllNotifications(cmd *cobra.Command) error {
23
+ a := app.FromContext(cmd.Context())
24
+ if a == nil || a.Client == nil {
25
+ return fmt.Errorf("API client not available")
26
+ }
27
+
28
+ if _, err := a.Client.PostBulkNotificationsReading(context.Background()); err != nil {
29
+ return fmt.Errorf("marking all notifications as read: %w", err)
30
+ }
31
+
32
+ fmt.Println("✓ All notifications marked as read successfully")
33
+ return nil
34
+ }
35
+
36
+ func init() {
37
+ notificationCmd.AddCommand(notificationReadAllCmd)
38
+ }
@@ -0,0 +1,75 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "net/http"
6
+ "net/http/httptest"
7
+ "testing"
8
+
9
+ "github.com/rogeriopvl/fizzy/internal/app"
10
+ "github.com/rogeriopvl/fizzy/internal/config"
11
+ "github.com/rogeriopvl/fizzy/internal/testutil"
12
+ )
13
+
14
+ func TestNotificationReadAllCommand(t *testing.T) {
15
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
16
+ if r.URL.Path == "/notifications/bulk_reading" && r.Method == http.MethodPost {
17
+ w.WriteHeader(http.StatusNoContent)
18
+ return
19
+ }
20
+
21
+ t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
22
+ w.WriteHeader(http.StatusNotFound)
23
+ }))
24
+ defer server.Close()
25
+
26
+ client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
27
+ testApp := &app.App{
28
+ Client: client,
29
+ Config: &config.Config{SelectedBoard: "board-123"},
30
+ }
31
+
32
+ cmd := notificationReadAllCmd
33
+ cmd.SetContext(testApp.ToContext(context.Background()))
34
+
35
+ if err := handleReadAllNotifications(cmd); err != nil {
36
+ t.Fatalf("handleReadAllNotifications failed: %v", err)
37
+ }
38
+ }
39
+
40
+ func TestNotificationReadAllCommandAPIError(t *testing.T) {
41
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
42
+ w.WriteHeader(http.StatusInternalServerError)
43
+ w.Write([]byte("Internal Server Error"))
44
+ }))
45
+ defer server.Close()
46
+
47
+ client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
48
+ testApp := &app.App{
49
+ Client: client,
50
+ Config: &config.Config{SelectedBoard: "board-123"},
51
+ }
52
+
53
+ cmd := notificationReadAllCmd
54
+ cmd.SetContext(testApp.ToContext(context.Background()))
55
+
56
+ err := handleReadAllNotifications(cmd)
57
+ if err == nil {
58
+ t.Errorf("expected error for API failure")
59
+ }
60
+ }
61
+
62
+ func TestNotificationReadAllCommandNoClient(t *testing.T) {
63
+ testApp := &app.App{}
64
+
65
+ cmd := notificationReadAllCmd
66
+ cmd.SetContext(testApp.ToContext(context.Background()))
67
+
68
+ err := handleReadAllNotifications(cmd)
69
+ if err == nil {
70
+ t.Errorf("expected error when client not available")
71
+ }
72
+ if err.Error() != "API client not available" {
73
+ t.Errorf("expected 'client not available' error, got %v", err)
74
+ }
75
+ }
@@ -0,0 +1,138 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "net/http"
7
+ "net/http/httptest"
8
+ "testing"
9
+
10
+ "github.com/rogeriopvl/fizzy/internal/api"
11
+ "github.com/rogeriopvl/fizzy/internal/app"
12
+ "github.com/rogeriopvl/fizzy/internal/config"
13
+ "github.com/rogeriopvl/fizzy/internal/testutil"
14
+ )
15
+
16
+ func TestNotificationReadCommand(t *testing.T) {
17
+ requestCount := 0
18
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
19
+ requestCount++
20
+
21
+ if r.URL.Path == "/notifications/notif-123/reading" && r.Method == http.MethodPost {
22
+ w.WriteHeader(http.StatusNoContent)
23
+ return
24
+ }
25
+
26
+ if r.URL.Path == "/notifications/notif-123" && r.Method == http.MethodGet {
27
+ w.Header().Set("Content-Type", "application/json")
28
+ response := api.Notification{
29
+ ID: "notif-123",
30
+ Read: true,
31
+ ReadAt: "2025-01-01T00:00:00Z",
32
+ CreatedAt: "2025-01-01T00:00:00Z",
33
+ Title: "Test Notification",
34
+ Body: "This is a test notification",
35
+ Creator: api.User{
36
+ ID: "user-123",
37
+ Name: "David Heinemeier Hansson",
38
+ Email: "david@example.com",
39
+ Role: "owner",
40
+ Active: true,
41
+ CreatedAt: "2025-12-05T19:36:35.401Z",
42
+ },
43
+ Card: api.CardReference{
44
+ ID: "card-123",
45
+ Title: "Test Card",
46
+ Status: "published",
47
+ URL: "http://fizzy.localhost:3006/897362094/cards/1",
48
+ },
49
+ URL: "http://fizzy.localhost:3006/897362094/notifications/notif-123",
50
+ }
51
+ json.NewEncoder(w).Encode(response)
52
+ return
53
+ }
54
+
55
+ t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
56
+ w.WriteHeader(http.StatusNotFound)
57
+ }))
58
+ defer server.Close()
59
+
60
+ client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
61
+ testApp := &app.App{
62
+ Client: client,
63
+ Config: &config.Config{SelectedBoard: "board-123"},
64
+ }
65
+
66
+ cmd := notificationReadCmd
67
+ cmd.SetContext(testApp.ToContext(context.Background()))
68
+
69
+ if err := handleReadNotification(cmd, "notif-123"); err != nil {
70
+ t.Fatalf("handleReadNotification failed: %v", err)
71
+ }
72
+
73
+ if requestCount != 2 {
74
+ t.Errorf("expected 2 requests (POST and GET), got %d", requestCount)
75
+ }
76
+ }
77
+
78
+ func TestNotificationReadCommandNotFound(t *testing.T) {
79
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
80
+ w.WriteHeader(http.StatusNotFound)
81
+ w.Write([]byte("Notification not found"))
82
+ }))
83
+ defer server.Close()
84
+
85
+ client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
86
+ testApp := &app.App{
87
+ Client: client,
88
+ Config: &config.Config{SelectedBoard: "board-123"},
89
+ }
90
+
91
+ cmd := notificationReadCmd
92
+ cmd.SetContext(testApp.ToContext(context.Background()))
93
+
94
+ err := handleReadNotification(cmd, "notif-invalid")
95
+ if err == nil {
96
+ t.Errorf("expected error for invalid notification")
97
+ }
98
+ if err.Error() != "notification not found" {
99
+ t.Errorf("expected 'notification not found' error, got %v", err)
100
+ }
101
+ }
102
+
103
+ func TestNotificationReadCommandAPIError(t *testing.T) {
104
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
105
+ w.WriteHeader(http.StatusInternalServerError)
106
+ w.Write([]byte("Internal Server Error"))
107
+ }))
108
+ defer server.Close()
109
+
110
+ client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
111
+ testApp := &app.App{
112
+ Client: client,
113
+ Config: &config.Config{SelectedBoard: "board-123"},
114
+ }
115
+
116
+ cmd := notificationReadCmd
117
+ cmd.SetContext(testApp.ToContext(context.Background()))
118
+
119
+ err := handleReadNotification(cmd, "notif-123")
120
+ if err == nil {
121
+ t.Errorf("expected error for API failure")
122
+ }
123
+ }
124
+
125
+ func TestNotificationReadCommandNoClient(t *testing.T) {
126
+ testApp := &app.App{}
127
+
128
+ cmd := notificationReadCmd
129
+ cmd.SetContext(testApp.ToContext(context.Background()))
130
+
131
+ err := handleReadNotification(cmd, "notif-123")
132
+ if err == nil {
133
+ t.Errorf("expected error when client not available")
134
+ }
135
+ if err.Error() != "API client not available" {
136
+ t.Errorf("expected 'client not available' error, got %v", err)
137
+ }
138
+ }
@@ -0,0 +1,44 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "strings"
7
+
8
+ "github.com/rogeriopvl/fizzy/internal/app"
9
+ "github.com/spf13/cobra"
10
+ )
11
+
12
+ var notificationUnreadCmd = &cobra.Command{
13
+ Use: "unread <notification_id>",
14
+ Short: "Mark notification as unread",
15
+ Long: `Mark a notification as unread`,
16
+ Args: cobra.ExactArgs(1),
17
+ Run: func(cmd *cobra.Command, args []string) {
18
+ if err := handleUnreadNotification(cmd, args[0]); err != nil {
19
+ fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
20
+ }
21
+ },
22
+ }
23
+
24
+ func handleUnreadNotification(cmd *cobra.Command, notificationID string) error {
25
+ a := app.FromContext(cmd.Context())
26
+ if a == nil || a.Client == nil {
27
+ return fmt.Errorf("API client not available")
28
+ }
29
+
30
+ _, err := a.Client.DeleteNotificationReading(context.Background(), notificationID)
31
+ if err != nil {
32
+ if strings.Contains(err.Error(), "404") {
33
+ return fmt.Errorf("notification not found")
34
+ }
35
+ return fmt.Errorf("marking notification as unread: %w", err)
36
+ }
37
+
38
+ fmt.Printf("✓ Notification marked as unread successfully\n")
39
+ return nil
40
+ }
41
+
42
+ func init() {
43
+ notificationCmd.AddCommand(notificationUnreadCmd)
44
+ }
@@ -0,0 +1,99 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "net/http"
6
+ "net/http/httptest"
7
+ "testing"
8
+
9
+ "github.com/rogeriopvl/fizzy/internal/app"
10
+ "github.com/rogeriopvl/fizzy/internal/testutil"
11
+ )
12
+
13
+ func TestNotificationUnreadCommand(t *testing.T) {
14
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
15
+ if r.URL.Path != "/notifications/notif-123/reading" {
16
+ t.Errorf("expected /notifications/notif-123/reading, got %s", r.URL.Path)
17
+ }
18
+ if r.Method != http.MethodDelete {
19
+ t.Errorf("expected DELETE, got %s", r.Method)
20
+ }
21
+
22
+ auth := r.Header.Get("Authorization")
23
+ if auth != "Bearer test-token" {
24
+ t.Errorf("expected Bearer test-token, got %s", auth)
25
+ }
26
+
27
+ w.WriteHeader(http.StatusNoContent)
28
+ }))
29
+ defer server.Close()
30
+
31
+ client := testutil.NewTestClient(server.URL, "", "", "test-token")
32
+ testApp := &app.App{Client: client}
33
+
34
+ cmd := notificationUnreadCmd
35
+ cmd.SetContext(testApp.ToContext(context.Background()))
36
+
37
+ if err := handleUnreadNotification(cmd, "notif-123"); err != nil {
38
+ t.Fatalf("handleUnreadNotification failed: %v", err)
39
+ }
40
+ }
41
+
42
+ func TestNotificationUnreadCommandNotFound(t *testing.T) {
43
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
44
+ w.WriteHeader(http.StatusNotFound)
45
+ w.Write([]byte("Notification not found"))
46
+ }))
47
+ defer server.Close()
48
+
49
+ client := testutil.NewTestClient(server.URL, "", "", "test-token")
50
+ testApp := &app.App{Client: client}
51
+
52
+ cmd := notificationUnreadCmd
53
+ cmd.SetContext(testApp.ToContext(context.Background()))
54
+
55
+ err := handleUnreadNotification(cmd, "notif-invalid")
56
+ if err == nil {
57
+ t.Errorf("expected error for invalid notification")
58
+ }
59
+ if err.Error() != "notification not found" {
60
+ t.Errorf("expected 'notification not found' error, got %v", err)
61
+ }
62
+ }
63
+
64
+ func TestNotificationUnreadCommandAPIError(t *testing.T) {
65
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
66
+ w.WriteHeader(http.StatusInternalServerError)
67
+ w.Write([]byte("Internal Server Error"))
68
+ }))
69
+ defer server.Close()
70
+
71
+ client := testutil.NewTestClient(server.URL, "", "", "test-token")
72
+ testApp := &app.App{Client: client}
73
+
74
+ cmd := notificationUnreadCmd
75
+ cmd.SetContext(testApp.ToContext(context.Background()))
76
+
77
+ err := handleUnreadNotification(cmd, "notif-123")
78
+ if err == nil {
79
+ t.Errorf("expected error for API failure")
80
+ }
81
+ if err.Error() != "marking notification as unread: unexpected status code 500: Internal Server Error" {
82
+ t.Errorf("expected API error, got %v", err)
83
+ }
84
+ }
85
+
86
+ func TestNotificationUnreadCommandNoClient(t *testing.T) {
87
+ testApp := &app.App{}
88
+
89
+ cmd := notificationUnreadCmd
90
+ cmd.SetContext(testApp.ToContext(context.Background()))
91
+
92
+ err := handleUnreadNotification(cmd, "notif-123")
93
+ if err == nil {
94
+ t.Errorf("expected error when client not available")
95
+ }
96
+ if err.Error() != "API client not available" {
97
+ t.Errorf("expected 'client not available' error, got %v", err)
98
+ }
99
+ }
package/docs/API.md CHANGED
@@ -5,6 +5,13 @@ a bot to perform various actions for you.
5
5
 
6
6
  ## Authentication
7
7
 
8
+ There are two ways to authenticate with the Fizzy API:
9
+
10
+ 1. **Personal access tokens** - Long-lived tokens for scripts and integrations
11
+ 2. **Magic link authentication** - Session-based authentication for native apps
12
+
13
+ ### Personal Access Tokens
14
+
8
15
  To use the API you'll need an access token. To get one, go to your profile, then,
9
16
  in the API section, click on "Personal access tokens" and then click on
10
17
  "Generate new access token".
@@ -36,6 +43,81 @@ To authenticate a request using your access token, include it in the `Authorizat
36
43
  curl -H "Authorization: Bearer put-your-access-token-here" -H "Accept: application/json" https://app.fizzy.do/my/identity
37
44
  ```
38
45
 
46
+ ### Magic Link Authentication
47
+
48
+ For native apps, you can authenticate users via magic links. This is a two-step process:
49
+
50
+ #### 1. Request a magic link
51
+
52
+ Send the user's email address to request a magic link be sent to them:
53
+
54
+ ```bash
55
+ curl -X POST \
56
+ -H "Content-Type: application/json" \
57
+ -H "Accept: application/json" \
58
+ -d '{"email_address": "user@example.com"}' \
59
+ https://app.fizzy.do/session
60
+ ```
61
+
62
+ __Response:__
63
+
64
+ ```
65
+ HTTP/1.1 201 Created
66
+ Set-Cookie: pending_authentication_token=...; HttpOnly; SameSite=Lax
67
+ ```
68
+
69
+ ```json
70
+ {
71
+ "pending_authentication_token": "eyJfcmFpbHMi..."
72
+ }
73
+ ```
74
+
75
+ The response includes a `pending_authentication_token` both in the JSON body and as a cookie.
76
+ Native apps should store this token and include it as a cookie when submitting the magic link code.
77
+
78
+ __Error responses:__
79
+
80
+ | Status Code | Description |
81
+ |--------|-------------|
82
+ | `422 Unprocessable entity` | Invalid email address, if sign ups are enabled and the value isn't a valid email address |
83
+ | `429 Too Many Requests` | Rate limit exceeded |
84
+
85
+ #### 2. Submit the magic link code
86
+
87
+ Once the user receives the magic link email, they'll have a 6-character code. Submit it to complete authentication:
88
+
89
+ ```bash
90
+ curl -X POST \
91
+ -H "Content-Type: application/json" \
92
+ -H "Accept: application/json" \
93
+ -H "Cookie: pending_authentication_token=eyJfcmFpbHMi..." \
94
+ -d '{"code": "ABC123"}' \
95
+ https://app.fizzy.do/session/magic_link
96
+ ```
97
+
98
+ __Response:__
99
+
100
+ ```json
101
+ {
102
+ "session_token": "eyJfcmFpbHMi..."
103
+ }
104
+ ```
105
+
106
+ The `session_token` can be used to authenticate subsequent requests by including it as a cookie:
107
+
108
+ ```bash
109
+ curl -H "Cookie: session_token=eyJfcmFpbHMi..." \
110
+ -H "Accept: application/json" \
111
+ https://app.fizzy.do/my/identity
112
+ ```
113
+
114
+ __Error responses:__
115
+
116
+ | Status Code | Description |
117
+ |--------|-------------|
118
+ | `401 Unauthorized` | Invalid `pending_authentication_token` or `code` |
119
+ | `429 Too Many Requests` | Rate limit exceeded |
120
+
39
121
  ## Caching
40
122
 
41
123
  Most endpoints return [ETag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag) and [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control) headers. You can use these to avoid re-downloading unchanged data.
@@ -482,7 +564,60 @@ Returns a specific card by its number.
482
564
 
483
565
  __Response:__
484
566
 
485
- Same as the card object in the list response.
567
+ ```json
568
+ {
569
+ "id": "03f5vaeq985jlvwv3arl4srq2",
570
+ "number": 1,
571
+ "title": "First!",
572
+ "status": "published",
573
+ "description": "Hello, World!",
574
+ "description_html": "<div class=\"action-text-content\"><p>Hello, World!</p></div>",
575
+ "image_url": null,
576
+ "tags": ["programming"],
577
+ "golden": false,
578
+ "last_active_at": "2025-12-05T19:38:48.553Z",
579
+ "created_at": "2025-12-05T19:38:48.540Z",
580
+ "url": "http://fizzy.localhost:3006/897362094/cards/4",
581
+ "board": {
582
+ "id": "03f5v9zkft4hj9qq0lsn9ohcm",
583
+ "name": "Fizzy",
584
+ "all_access": true,
585
+ "created_at": "2025-12-05T19:36:35.534Z",
586
+ "url": "http://fizzy.localhost:3006/897362094/boards/03f5v9zkft4hj9qq0lsn9ohcm",
587
+ "creator": {
588
+ "id": "03f5v9zjw7pz8717a4no1h8a7",
589
+ "name": "David Heinemeier Hansson",
590
+ "role": "owner",
591
+ "active": true,
592
+ "email_address": "david@example.com",
593
+ "created_at": "2025-12-05T19:36:35.401Z",
594
+ "url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7"
595
+ }
596
+ },
597
+ "creator": {
598
+ "id": "03f5v9zjw7pz8717a4no1h8a7",
599
+ "name": "David Heinemeier Hansson",
600
+ "role": "owner",
601
+ "active": true,
602
+ "email_address": "david@example.com",
603
+ "created_at": "2025-12-05T19:36:35.401Z",
604
+ "url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7"
605
+ },
606
+ "comments_url": "http://fizzy.localhost:3006/897362094/cards/4/comments",
607
+ "steps": [
608
+ {
609
+ "id": "03f8huu0sog76g3s975963b5e",
610
+ "content": "This is the first step",
611
+ "completed": false
612
+ },
613
+ {
614
+ "id": "03f8huu0sog76g3s975969734",
615
+ "content": "This is the second step",
616
+ "completed": false
617
+ }
618
+ ]
619
+ }
620
+ ```
486
621
 
487
622
  ### `POST /:account_slug/boards/:board_id/cards`
488
623
 
@@ -661,6 +796,10 @@ __Response:__
661
796
  "created_at": "2025-12-05T19:36:35.401Z",
662
797
  "url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7"
663
798
  },
799
+ "card": {
800
+ "id": "03f5v9zo9qlcwwpyc0ascnikz",
801
+ "url": "http://fizzy.localhost:3006/897362094/cards/03f5v9zo9qlcwwpyc0ascnikz"
802
+ },
664
803
  "reactions_url": "http://fizzy.localhost:3006/897362094/cards/3/comments/03f5v9zo9qlcwwpyc0ascnikz/reactions",
665
804
  "url": "http://fizzy.localhost:3006/897362094/cards/3/comments/03f5v9zo9qlcwwpyc0ascnikz"
666
805
  }
@@ -691,6 +830,10 @@ __Response:__
691
830
  "created_at": "2025-12-05T19:36:35.401Z",
692
831
  "url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7"
693
832
  },
833
+ "card": {
834
+ "id": "03f5v9zo9qlcwwpyc0ascnikz",
835
+ "url": "http://fizzy.localhost:3006/897362094/cards/03f5v9zo9qlcwwpyc0ascnikz"
836
+ },
694
837
  "reactions_url": "http://fizzy.localhost:3006/897362094/cards/3/comments/03f5v9zo9qlcwwpyc0ascnikz/reactions",
695
838
  "url": "http://fizzy.localhost:3006/897362094/cards/3/comments/03f5v9zo9qlcwwpyc0ascnikz"
696
839
  }
package/go.mod CHANGED
@@ -4,13 +4,13 @@ go 1.25.1
4
4
 
5
5
  require (
6
6
  github.com/charmbracelet/bubbletea v1.3.10
7
+ github.com/charmbracelet/lipgloss v1.1.0
7
8
  github.com/spf13/cobra v1.10.2
8
9
  )
9
10
 
10
11
  require (
11
12
  github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
12
13
  github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
13
- github.com/charmbracelet/lipgloss v1.1.0 // indirect
14
14
  github.com/charmbracelet/x/ansi v0.10.1 // indirect
15
15
  github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
16
16
  github.com/charmbracelet/x/term v0.2.1 // indirect