Skip to content

Commit 97ae9d1

Browse files
committed
feat(show): support multiple bean IDs
- `beans show` now accepts multiple IDs: `beans show id1 id2 id3` - JSON output returns array when multiple beans are requested - Styled output separates beans with a visual divider - Update prompt.tmpl with Finding Work examples This is useful for agents to view full details of several candidate beans at once when deciding what to work on.
1 parent 74d84b0 commit 97ae9d1

File tree

2 files changed

+112
-74
lines changed

2 files changed

+112
-74
lines changed

cmd/prompt.tmpl

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,14 @@ If you identify follow-up work, create a new bean instead of doing it immediatel
2626
When the user asks what to work on next:
2727

2828
```bash
29+
# Check for in-progress beans first (finish what's started)
30+
beans list --json --full -s in-progress
31+
2932
# Find beans ready to start (not blocked, excludes in-progress/completed/scrapped/draft)
30-
beans list --json --ready
33+
beans list --json --full --ready
34+
35+
# View full details of specific beans (supports multiple IDs)
36+
beans show --json <id> [id...]
3137
```
3238
</EXTREMELY_IMPORTANT>
3339

@@ -41,8 +47,8 @@ beans list --json -t bug -s todo # Filter by type and status
4147
beans list --json -S "authentication" # Full-text search
4248
beans list --help # Full options
4349
44-
# View a bean
45-
beans show --json <id>
50+
# View beans (supports multiple IDs)
51+
beans show --json <id> [id...]
4652
4753
# Create a bean (always specify -t type)
4854
beans create --json "Title" -t task -d "Description..." -s todo

cmd/show.go

Lines changed: 103 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -21,113 +21,145 @@ var (
2121
)
2222

2323
var showCmd = &cobra.Command{
24-
Use: "show <id>",
24+
Use: "show <id> [id...]",
2525
Short: "Show a bean's contents",
26-
Long: `Displays the full contents of a bean, including front matter and body.`,
27-
Args: cobra.ExactArgs(1),
26+
Long: `Displays the full contents of one or more beans, including front matter and body.`,
27+
Args: cobra.MinimumNArgs(1),
2828
RunE: func(cmd *cobra.Command, args []string) error {
29-
// Query bean via GraphQL resolver
3029
resolver := &graph.Resolver{Core: core}
31-
b, err := resolver.Query().Bean(context.Background(), args[0])
32-
if err != nil {
33-
if showJSON {
34-
return output.Error(output.ErrNotFound, err.Error())
30+
31+
// Collect all beans
32+
var beans []*bean.Bean
33+
for _, id := range args {
34+
b, err := resolver.Query().Bean(context.Background(), id)
35+
if err != nil {
36+
if showJSON {
37+
return output.Error(output.ErrNotFound, err.Error())
38+
}
39+
return fmt.Errorf("failed to find bean: %w", err)
3540
}
36-
return fmt.Errorf("failed to find bean: %w", err)
37-
}
38-
if b == nil {
39-
if showJSON {
40-
return output.Error(output.ErrNotFound, "bean not found")
41+
if b == nil {
42+
if showJSON {
43+
return output.Error(output.ErrNotFound, fmt.Sprintf("bean not found: %s", id))
44+
}
45+
return fmt.Errorf("bean not found: %s", id)
4146
}
42-
return fmt.Errorf("bean not found: %s", args[0])
47+
beans = append(beans, b)
4348
}
4449

4550
// JSON output
4651
if showJSON {
47-
return output.SuccessSingle(b)
52+
if len(beans) == 1 {
53+
return output.SuccessSingle(beans[0])
54+
}
55+
return output.SuccessMultiple(beans)
4856
}
4957

5058
// Raw markdown output (frontmatter + body)
5159
if showRaw {
52-
content, err := b.Render()
53-
if err != nil {
54-
return fmt.Errorf("failed to render bean: %w", err)
60+
for i, b := range beans {
61+
if i > 0 {
62+
fmt.Print("\n---\n\n")
63+
}
64+
content, err := b.Render()
65+
if err != nil {
66+
return fmt.Errorf("failed to render bean: %w", err)
67+
}
68+
fmt.Print(string(content))
5569
}
56-
fmt.Print(string(content))
5770
return nil
5871
}
5972

6073
// Body only (no header, no styling)
6174
if showBodyOnly {
62-
fmt.Print(b.Body)
75+
for i, b := range beans {
76+
if i > 0 {
77+
fmt.Print("\n---\n\n")
78+
}
79+
fmt.Print(b.Body)
80+
}
6381
return nil
6482
}
6583

6684
// Default: styled human-friendly output
67-
statusCfg := cfg.GetStatus(b.Status)
68-
statusColor := "gray"
69-
if statusCfg != nil {
70-
statusColor = statusCfg.Color
71-
}
72-
isArchive := cfg.IsArchiveStatus(b.Status)
73-
74-
var header strings.Builder
75-
header.WriteString(ui.ID.Render(b.ID))
76-
header.WriteString(" ")
77-
header.WriteString(ui.RenderStatusWithColor(b.Status, statusColor, isArchive))
78-
if b.Priority != "" {
79-
priorityCfg := cfg.GetPriority(b.Priority)
80-
priorityColor := "gray"
81-
if priorityCfg != nil {
82-
priorityColor = priorityCfg.Color
85+
for i, b := range beans {
86+
if i > 0 {
87+
fmt.Println()
88+
fmt.Println(ui.Muted.Render(strings.Repeat("═", 60)))
89+
fmt.Println()
8390
}
84-
header.WriteString(" ")
85-
header.WriteString(ui.RenderPriorityWithColor(b.Priority, priorityColor))
86-
}
87-
if len(b.Tags) > 0 {
88-
header.WriteString(" ")
89-
header.WriteString(ui.Muted.Render(strings.Join(b.Tags, ", ")))
91+
showStyledBean(b)
9092
}
91-
header.WriteString("\n")
92-
header.WriteString(ui.Title.Render(b.Title))
93-
94-
// Display relationships
95-
if b.Parent != "" || len(b.Blocking) > 0 {
96-
header.WriteString("\n")
97-
header.WriteString(ui.Muted.Render(strings.Repeat("─", 50)))
98-
header.WriteString("\n")
99-
header.WriteString(formatRelationships(b))
93+
94+
return nil
95+
},
96+
}
97+
98+
// showStyledBean displays a single bean with styled output.
99+
func showStyledBean(b *bean.Bean) {
100+
statusCfg := cfg.GetStatus(b.Status)
101+
statusColor := "gray"
102+
if statusCfg != nil {
103+
statusColor = statusCfg.Color
104+
}
105+
isArchive := cfg.IsArchiveStatus(b.Status)
106+
107+
var header strings.Builder
108+
header.WriteString(ui.ID.Render(b.ID))
109+
header.WriteString(" ")
110+
header.WriteString(ui.RenderStatusWithColor(b.Status, statusColor, isArchive))
111+
if b.Priority != "" {
112+
priorityCfg := cfg.GetPriority(b.Priority)
113+
priorityColor := "gray"
114+
if priorityCfg != nil {
115+
priorityColor = priorityCfg.Color
100116
}
117+
header.WriteString(" ")
118+
header.WriteString(ui.RenderPriorityWithColor(b.Priority, priorityColor))
119+
}
120+
if len(b.Tags) > 0 {
121+
header.WriteString(" ")
122+
header.WriteString(ui.Muted.Render(strings.Join(b.Tags, ", ")))
123+
}
124+
header.WriteString("\n")
125+
header.WriteString(ui.Title.Render(b.Title))
101126

127+
// Display relationships
128+
if b.Parent != "" || len(b.Blocking) > 0 {
102129
header.WriteString("\n")
103130
header.WriteString(ui.Muted.Render(strings.Repeat("─", 50)))
131+
header.WriteString("\n")
132+
header.WriteString(formatRelationships(b))
133+
}
104134

105-
headerBox := lipgloss.NewStyle().
106-
MarginBottom(1).
107-
Render(header.String())
135+
header.WriteString("\n")
136+
header.WriteString(ui.Muted.Render(strings.Repeat("─", 50)))
108137

109-
fmt.Println(headerBox)
138+
headerBox := lipgloss.NewStyle().
139+
MarginBottom(1).
140+
Render(header.String())
110141

111-
// Render the body with Glamour
112-
if b.Body != "" {
113-
renderer, err := glamour.NewTermRenderer(
114-
glamour.WithAutoStyle(),
115-
glamour.WithWordWrap(80),
116-
)
117-
if err != nil {
118-
return fmt.Errorf("failed to create renderer: %w", err)
119-
}
142+
fmt.Println(headerBox)
120143

121-
rendered, err := renderer.Render(b.Body)
122-
if err != nil {
123-
return fmt.Errorf("failed to render markdown: %w", err)
124-
}
144+
// Render the body with Glamour
145+
if b.Body != "" {
146+
renderer, err := glamour.NewTermRenderer(
147+
glamour.WithAutoStyle(),
148+
glamour.WithWordWrap(80),
149+
)
150+
if err != nil {
151+
fmt.Printf("failed to create renderer: %v\n", err)
152+
return
153+
}
125154

126-
fmt.Print(rendered)
155+
rendered, err := renderer.Render(b.Body)
156+
if err != nil {
157+
fmt.Printf("failed to render markdown: %v\n", err)
158+
return
127159
}
128160

129-
return nil
130-
},
161+
fmt.Print(rendered)
162+
}
131163
}
132164

133165
// formatRelationships formats parent and blocks for display.

0 commit comments

Comments
 (0)