bluesky viewer in the terminal
1package main
2
3import (
4 "context"
5 "fmt"
6 "time"
7
8 "github.com/stormlightlabs/skypanel/cli/internal/registry"
9 "github.com/stormlightlabs/skypanel/cli/internal/setup"
10 "github.com/stormlightlabs/skypanel/cli/internal/store"
11 "github.com/stormlightlabs/skypanel/cli/internal/ui"
12 "github.com/urfave/cli/v3"
13)
14
15// ListPostsAction lists the authenticated user's own posts
16func ListPostsAction(ctx context.Context, cmd *cli.Command) error {
17 if err := setup.EnsurePersistenceReady(ctx); err != nil {
18 return fmt.Errorf("persistence layer not ready: %w", err)
19 }
20
21 reg := registry.Get()
22
23 service, err := reg.GetService()
24 if err != nil {
25 return fmt.Errorf("failed to get service: %w", err)
26 }
27
28 if !service.Authenticated() {
29 return fmt.Errorf("not authenticated: run 'skycli login' first")
30 }
31
32 sessionRepo, err := reg.GetSessionRepo()
33 if err != nil {
34 return fmt.Errorf("failed to get session repository: %w", err)
35 }
36
37 did, err := sessionRepo.GetDid(ctx)
38 if err != nil {
39 return fmt.Errorf("failed to get user DID: %w", err)
40 }
41
42 limit := cmd.Int("limit")
43 asJSON := cmd.Bool("json")
44
45 logger.Debug("Fetching user's posts", "did", did, "limit", limit)
46
47 response, err := service.GetAuthorFeed(ctx, did, limit, "")
48 if err != nil {
49 return fmt.Errorf("failed to fetch user posts: %w", err)
50 }
51
52 if asJSON {
53 return ui.DisplayJSON(response)
54 }
55
56 ui.Titleln("Your Posts")
57 ui.DisplayFeed(response.Feed, response.Cursor)
58 return nil
59}
60
61// ListFeedsAction lists user's feeds (from local cache or refetch from API)
62func ListFeedsAction(ctx context.Context, cmd *cli.Command) error {
63 if err := setup.EnsurePersistenceReady(ctx); err != nil {
64 return fmt.Errorf("persistence layer not ready: %w", err)
65 }
66
67 reg := registry.Get()
68
69 refetch := cmd.Bool("refetch")
70 asJSON := cmd.Bool("json")
71
72 if refetch {
73 service, err := reg.GetService()
74 if err != nil {
75 return fmt.Errorf("failed to get service: %w", err)
76 }
77
78 if !service.Authenticated() {
79 return fmt.Errorf("not authenticated: run 'skycli login' first")
80 }
81
82 sessionRepo, err := reg.GetSessionRepo()
83 if err != nil {
84 return fmt.Errorf("failed to get session repository: %w", err)
85 }
86
87 handle, err := sessionRepo.GetHandle(ctx)
88 if err != nil {
89 return fmt.Errorf("failed to get user handle: %w", err)
90 }
91
92 logger.Debug("Refetching feeds from API", "handle", handle)
93
94 // TODO: Implement GetUserFeeds in BlueskyService
95 // For now, fall back to local feeds
96 ui.Warningln("API refetch not yet implemented, showing local feeds")
97 }
98
99 feedRepo, err := reg.GetFeedRepo()
100 if err != nil {
101 return fmt.Errorf("failed to get feed repository: %w", err)
102 }
103
104 feeds, err := feedRepo.List(ctx)
105 if err != nil {
106 logger.Error("Failed to list feeds", "error", err)
107 return err
108 }
109
110 if len(feeds) == 0 {
111 ui.Infoln("No feeds found.")
112 return nil
113 }
114
115 if asJSON {
116 return ui.DisplayJSON(feeds)
117 }
118
119 ui.Titleln("Your Feeds")
120 fmt.Println()
121
122 for i, model := range feeds {
123 if feed, ok := model.(*store.FeedModel); ok {
124 ui.Subtitleln("[%d] %s", i+1, feed.Name)
125 ui.Infoln(" ID: %s", feed.ID())
126 ui.Infoln(" Source: %s", feed.Source)
127 ui.Infoln(" Local: %t", feed.IsLocal)
128 ui.Infoln(" Created: %s", feed.CreatedAt().Format(time.RFC3339))
129 fmt.Println()
130 }
131 }
132
133 ui.Successln("Total: %d feed(s)", len(feeds))
134 return nil
135}
136
137// ListCommand returns the list command with subcommands for posts and feeds
138func ListCommand() *cli.Command {
139 commonFlags := []cli.Flag{
140 &cli.BoolFlag{
141 Name: "refetch",
142 Aliases: []string{"r"},
143 Usage: "Refetch from API instead of using local cache",
144 },
145 &cli.BoolFlag{
146 Name: "json",
147 Aliases: []string{"j"},
148 Usage: "Output raw JSON response",
149 },
150 }
151
152 return &cli.Command{
153 Name: "list",
154 Usage: "List user's posts or feeds",
155 Commands: []*cli.Command{
156 {
157 Name: "posts",
158 Usage: "List authenticated user's own posts",
159 ArgsUsage: " ",
160 Flags: []cli.Flag{
161 &cli.IntFlag{
162 Name: "limit",
163 Aliases: []string{"l"},
164 Usage: "Maximum number of posts to list",
165 Value: 25,
166 },
167 &cli.BoolFlag{
168 Name: "json",
169 Aliases: []string{"j"},
170 Usage: "Output raw JSON response",
171 },
172 },
173 Action: ListPostsAction,
174 },
175 {
176 Name: "feeds",
177 Usage: "List user's feeds (local cache or refetch with -r)",
178 ArgsUsage: " ",
179 Flags: commonFlags,
180 Action: ListFeedsAction,
181 },
182 },
183 // Default action when no subcommand is provided
184 Action: func(ctx context.Context, cmd *cli.Command) error {
185 // Default to posts
186 return ListPostsAction(ctx, cmd)
187 },
188 Flags: []cli.Flag{
189 &cli.IntFlag{
190 Name: "limit",
191 Aliases: []string{"l"},
192 Usage: "Maximum number of posts to list",
193 Value: 25,
194 },
195 &cli.BoolFlag{
196 Name: "json",
197 Aliases: []string{"j"},
198 Usage: "Output raw JSON response",
199 },
200 },
201 }
202}