fizzy-cli 0.2.1 → 0.4.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.
- package/CHANGELOG.md +29 -0
- package/IMPLEMENTATION_PLAN.md +338 -0
- package/bin/fizzy +0 -0
- package/cmd/board.go +1 -1
- package/cmd/card_assign.go +55 -0
- package/cmd/card_assign_test.go +130 -0
- package/cmd/card_golden.go +46 -0
- package/cmd/card_golden_test.go +92 -0
- package/cmd/card_not_now.go +46 -0
- package/cmd/card_not_now_test.go +92 -0
- package/cmd/card_tag.go +51 -0
- package/cmd/card_tag_test.go +112 -0
- package/cmd/card_triage.go +46 -0
- package/cmd/card_ungolden.go +46 -0
- package/cmd/card_ungolden_test.go +92 -0
- package/cmd/card_untriage.go +46 -0
- package/cmd/card_untriage_test.go +92 -0
- package/cmd/card_unwatch.go +46 -0
- package/cmd/card_unwatch_test.go +92 -0
- package/cmd/card_update.go +0 -1
- package/cmd/card_update_test.go +0 -2
- package/cmd/card_watch.go +46 -0
- package/cmd/card_watch_test.go +92 -0
- package/cmd/login.go +2 -1
- package/cmd/notification.go +14 -0
- package/cmd/notification_list.go +69 -0
- package/cmd/notification_list_test.go +288 -0
- package/cmd/notification_read.go +51 -0
- package/cmd/notification_read_all.go +38 -0
- package/cmd/notification_read_all_test.go +75 -0
- package/cmd/notification_read_test.go +138 -0
- package/cmd/notification_unread.go +44 -0
- package/cmd/notification_unread_test.go +99 -0
- package/cmd/tag.go +15 -0
- package/cmd/tag_list.go +47 -0
- package/cmd/tag_list_test.go +109 -0
- package/docs/API.md +180 -1
- package/go.mod +1 -1
- package/internal/api/client.go +275 -0
- package/internal/config/config.go +1 -0
- package/internal/ui/notification_list.go +27 -0
- package/package.json +1 -1
|
@@ -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/cmd/tag.go
ADDED
package/cmd/tag_list.go
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
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 tagListCmd = &cobra.Command{
|
|
12
|
+
Use: "list",
|
|
13
|
+
Short: "List all tags",
|
|
14
|
+
Long: `Retrieve and display all tags in the account`,
|
|
15
|
+
Run: func(cmd *cobra.Command, args []string) {
|
|
16
|
+
if err := handleListTags(cmd); err != nil {
|
|
17
|
+
fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func handleListTags(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
|
+
tags, err := a.Client.GetTags(context.Background())
|
|
29
|
+
if err != nil {
|
|
30
|
+
return fmt.Errorf("fetching tags: %w", err)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if len(tags) == 0 {
|
|
34
|
+
fmt.Println("No tags found")
|
|
35
|
+
return nil
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for _, tag := range tags {
|
|
39
|
+
fmt.Printf("%s\n", tag.Title)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return nil
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func init() {
|
|
46
|
+
tagCmd.AddCommand(tagListCmd)
|
|
47
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
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/testutil"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
func TestTagListCommand(t *testing.T) {
|
|
16
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
17
|
+
if r.URL.Path != "/tags" {
|
|
18
|
+
t.Errorf("expected /tags, got %s", r.URL.Path)
|
|
19
|
+
}
|
|
20
|
+
if r.Method != http.MethodGet {
|
|
21
|
+
t.Errorf("expected GET, got %s", r.Method)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
auth := r.Header.Get("Authorization")
|
|
25
|
+
if auth != "Bearer test-token" {
|
|
26
|
+
t.Errorf("expected Bearer test-token, got %s", auth)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
w.Header().Set("Content-Type", "application/json")
|
|
30
|
+
response := []api.Tag{
|
|
31
|
+
{
|
|
32
|
+
ID: "tag-123",
|
|
33
|
+
Title: "bug",
|
|
34
|
+
CreatedAt: "2025-01-01T00:00:00Z",
|
|
35
|
+
URL: "http://fizzy.localhost:3006/897362094/cards?tag_ids[]=tag-123",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
ID: "tag-456",
|
|
39
|
+
Title: "feature",
|
|
40
|
+
CreatedAt: "2025-01-02T00:00:00Z",
|
|
41
|
+
URL: "http://fizzy.localhost:3006/897362094/cards?tag_ids[]=tag-456",
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
json.NewEncoder(w).Encode(response)
|
|
45
|
+
}))
|
|
46
|
+
defer server.Close()
|
|
47
|
+
|
|
48
|
+
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
49
|
+
testApp := &app.App{Client: client}
|
|
50
|
+
|
|
51
|
+
cmd := tagListCmd
|
|
52
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
53
|
+
|
|
54
|
+
if err := handleListTags(cmd); err != nil {
|
|
55
|
+
t.Fatalf("handleListTags failed: %v", err)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func TestTagListCommandNoTags(t *testing.T) {
|
|
60
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
61
|
+
w.Header().Set("Content-Type", "application/json")
|
|
62
|
+
w.Write([]byte("[]"))
|
|
63
|
+
}))
|
|
64
|
+
defer server.Close()
|
|
65
|
+
|
|
66
|
+
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
67
|
+
testApp := &app.App{Client: client}
|
|
68
|
+
|
|
69
|
+
cmd := tagListCmd
|
|
70
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
71
|
+
|
|
72
|
+
if err := handleListTags(cmd); err != nil {
|
|
73
|
+
t.Fatalf("handleListTags with no tags failed: %v", err)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
func TestTagListCommandAPIError(t *testing.T) {
|
|
78
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
79
|
+
w.WriteHeader(http.StatusInternalServerError)
|
|
80
|
+
w.Write([]byte("Internal Server Error"))
|
|
81
|
+
}))
|
|
82
|
+
defer server.Close()
|
|
83
|
+
|
|
84
|
+
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
85
|
+
testApp := &app.App{Client: client}
|
|
86
|
+
|
|
87
|
+
cmd := tagListCmd
|
|
88
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
89
|
+
|
|
90
|
+
err := handleListTags(cmd)
|
|
91
|
+
if err == nil {
|
|
92
|
+
t.Errorf("expected error for API failure")
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func TestTagListCommandNoClient(t *testing.T) {
|
|
97
|
+
testApp := &app.App{}
|
|
98
|
+
|
|
99
|
+
cmd := tagListCmd
|
|
100
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
101
|
+
|
|
102
|
+
err := handleListTags(cmd)
|
|
103
|
+
if err == nil {
|
|
104
|
+
t.Errorf("expected error when client not available")
|
|
105
|
+
}
|
|
106
|
+
if err.Error() != "API client not available" {
|
|
107
|
+
t.Errorf("expected 'client not available' error, got %v", err)
|
|
108
|
+
}
|
|
109
|
+
}
|
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,72 @@ Returns a specific card by its number.
|
|
|
482
564
|
|
|
483
565
|
__Response:__
|
|
484
566
|
|
|
485
|
-
|
|
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
|
+
"closed": false,
|
|
578
|
+
"golden": false,
|
|
579
|
+
"last_active_at": "2025-12-05T19:38:48.553Z",
|
|
580
|
+
"created_at": "2025-12-05T19:38:48.540Z",
|
|
581
|
+
"url": "http://fizzy.localhost:3006/897362094/cards/4",
|
|
582
|
+
"board": {
|
|
583
|
+
"id": "03f5v9zkft4hj9qq0lsn9ohcm",
|
|
584
|
+
"name": "Fizzy",
|
|
585
|
+
"all_access": true,
|
|
586
|
+
"created_at": "2025-12-05T19:36:35.534Z",
|
|
587
|
+
"url": "http://fizzy.localhost:3006/897362094/boards/03f5v9zkft4hj9qq0lsn9ohcm",
|
|
588
|
+
"creator": {
|
|
589
|
+
"id": "03f5v9zjw7pz8717a4no1h8a7",
|
|
590
|
+
"name": "David Heinemeier Hansson",
|
|
591
|
+
"role": "owner",
|
|
592
|
+
"active": true,
|
|
593
|
+
"email_address": "david@example.com",
|
|
594
|
+
"created_at": "2025-12-05T19:36:35.401Z",
|
|
595
|
+
"url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7"
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
"column": {
|
|
599
|
+
"id": "03f5v9zkft4hj9qq0lsn9ohcn",
|
|
600
|
+
"name": "In Progress",
|
|
601
|
+
"color": {
|
|
602
|
+
"name": "Lime",
|
|
603
|
+
"value": "var(--color-card-4)"
|
|
604
|
+
},
|
|
605
|
+
"created_at": "2025-12-05T19:36:35.534Z"
|
|
606
|
+
},
|
|
607
|
+
"creator": {
|
|
608
|
+
"id": "03f5v9zjw7pz8717a4no1h8a7",
|
|
609
|
+
"name": "David Heinemeier Hansson",
|
|
610
|
+
"role": "owner",
|
|
611
|
+
"active": true,
|
|
612
|
+
"email_address": "david@example.com",
|
|
613
|
+
"created_at": "2025-12-05T19:36:35.401Z",
|
|
614
|
+
"url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7"
|
|
615
|
+
},
|
|
616
|
+
"comments_url": "http://fizzy.localhost:3006/897362094/cards/4/comments",
|
|
617
|
+
"steps": [
|
|
618
|
+
{
|
|
619
|
+
"id": "03f8huu0sog76g3s975963b5e",
|
|
620
|
+
"content": "This is the first step",
|
|
621
|
+
"completed": false
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
"id": "03f8huu0sog76g3s975969734",
|
|
625
|
+
"content": "This is the second step",
|
|
626
|
+
"completed": false
|
|
627
|
+
}
|
|
628
|
+
]
|
|
629
|
+
}
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
> **Note:** The `closed` field indicates whether the card is in the "Done" state. The `column` field is only present when the card has been triaged into a column; cards in "Maybe?", "Not Now" or "Done" will not have this field.
|
|
486
633
|
|
|
487
634
|
### `POST /:account_slug/boards/:board_id/cards`
|
|
488
635
|
|
|
@@ -548,6 +695,14 @@ __Response:__
|
|
|
548
695
|
|
|
549
696
|
Returns `204 No Content` on success.
|
|
550
697
|
|
|
698
|
+
### `DELETE /:account_slug/cards/:card_number/image`
|
|
699
|
+
|
|
700
|
+
Removes the header image from a card.
|
|
701
|
+
|
|
702
|
+
__Response:__
|
|
703
|
+
|
|
704
|
+
Returns `204 No Content` on success.
|
|
705
|
+
|
|
551
706
|
### `POST /:account_slug/cards/:card_number/closure`
|
|
552
707
|
|
|
553
708
|
Closes a card.
|
|
@@ -632,6 +787,22 @@ __Response:__
|
|
|
632
787
|
|
|
633
788
|
Returns `204 No Content` on success.
|
|
634
789
|
|
|
790
|
+
### `POST /:account_slug/cards/:card_number/goldness`
|
|
791
|
+
|
|
792
|
+
Marks a card as golden.
|
|
793
|
+
|
|
794
|
+
__Response:__
|
|
795
|
+
|
|
796
|
+
Returns `204 No Content` on success.
|
|
797
|
+
|
|
798
|
+
### `DELETE /:account_slug/cards/:card_number/goldness`
|
|
799
|
+
|
|
800
|
+
Removes golden status from a card.
|
|
801
|
+
|
|
802
|
+
__Response:__
|
|
803
|
+
|
|
804
|
+
Returns `204 No Content` on success.
|
|
805
|
+
|
|
635
806
|
## Comments
|
|
636
807
|
|
|
637
808
|
Comments are attached to cards and support rich text.
|
|
@@ -661,6 +832,10 @@ __Response:__
|
|
|
661
832
|
"created_at": "2025-12-05T19:36:35.401Z",
|
|
662
833
|
"url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7"
|
|
663
834
|
},
|
|
835
|
+
"card": {
|
|
836
|
+
"id": "03f5v9zo9qlcwwpyc0ascnikz",
|
|
837
|
+
"url": "http://fizzy.localhost:3006/897362094/cards/03f5v9zo9qlcwwpyc0ascnikz"
|
|
838
|
+
},
|
|
664
839
|
"reactions_url": "http://fizzy.localhost:3006/897362094/cards/3/comments/03f5v9zo9qlcwwpyc0ascnikz/reactions",
|
|
665
840
|
"url": "http://fizzy.localhost:3006/897362094/cards/3/comments/03f5v9zo9qlcwwpyc0ascnikz"
|
|
666
841
|
}
|
|
@@ -691,6 +866,10 @@ __Response:__
|
|
|
691
866
|
"created_at": "2025-12-05T19:36:35.401Z",
|
|
692
867
|
"url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7"
|
|
693
868
|
},
|
|
869
|
+
"card": {
|
|
870
|
+
"id": "03f5v9zo9qlcwwpyc0ascnikz",
|
|
871
|
+
"url": "http://fizzy.localhost:3006/897362094/cards/03f5v9zo9qlcwwpyc0ascnikz"
|
|
872
|
+
},
|
|
694
873
|
"reactions_url": "http://fizzy.localhost:3006/897362094/cards/3/comments/03f5v9zo9qlcwwpyc0ascnikz/reactions",
|
|
695
874
|
"url": "http://fizzy.localhost:3006/897362094/cards/3/comments/03f5v9zo9qlcwwpyc0ascnikz"
|
|
696
875
|
}
|
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
|