Skip to content

Commit 336fc4e

Browse files
authored
Merge pull request #1877 from dgageot/commands-dialog
Improve Commands dialog
2 parents 61eb1bb + 51133e6 commit 336fc4e

1 file changed

Lines changed: 142 additions & 114 deletions

File tree

pkg/tui/commands/commands.go

Lines changed: 142 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package commands
33
import (
44
"context"
55
"fmt"
6+
"slices"
67
"strings"
78

89
tea "charm.land/bubbletea/v2"
@@ -37,69 +38,13 @@ type Item struct {
3738
func builtInSessionCommands() []Item {
3839
cmds := []Item{
3940
{
40-
ID: "session.exit",
41-
Label: "Exit",
42-
SlashCommand: "/exit",
43-
Description: "Exit the application",
44-
Category: "Session",
45-
Execute: func(string) tea.Cmd {
46-
return core.CmdHandler(messages.ExitSessionMsg{})
47-
},
48-
},
49-
{
50-
ID: "session.new",
51-
Label: "New",
52-
SlashCommand: "/new",
53-
Description: "Start a new conversation",
54-
Category: "Session",
55-
Execute: func(string) tea.Cmd {
56-
return core.CmdHandler(messages.NewSessionMsg{})
57-
},
58-
},
59-
{
60-
ID: "session.history",
61-
Label: "Sessions",
62-
SlashCommand: "/sessions",
63-
Description: "Browse and load past sessions",
64-
Category: "Session",
65-
Execute: func(string) tea.Cmd {
66-
return core.CmdHandler(messages.OpenSessionBrowserMsg{})
67-
},
68-
},
69-
{
70-
ID: "session.star",
71-
Label: "Star",
72-
SlashCommand: "/star",
73-
Description: "Toggle star on current session",
74-
Category: "Session",
75-
Execute: func(string) tea.Cmd {
76-
return core.CmdHandler(messages.ToggleSessionStarMsg{})
77-
},
78-
},
79-
{
80-
ID: "session.title",
81-
Label: "Title",
82-
SlashCommand: "/title",
83-
Description: "Set or regenerate session title (usage: /title [new title])",
41+
ID: "session.attach",
42+
Label: "Attach",
43+
SlashCommand: "/attach",
44+
Description: "Attach a file to your message (usage: /attach [path])",
8445
Category: "Session",
8546
Execute: func(arg string) tea.Cmd {
86-
arg = strings.TrimSpace(arg)
87-
if arg == "" {
88-
// No argument: regenerate title
89-
return core.CmdHandler(messages.RegenerateTitleMsg{})
90-
}
91-
// With argument: set title
92-
return core.CmdHandler(messages.SetSessionTitleMsg{Title: arg})
93-
},
94-
},
95-
{
96-
ID: "session.model",
97-
Label: "Model",
98-
SlashCommand: "/model",
99-
Description: "Change the model for the current agent",
100-
Category: "Session",
101-
Execute: func(string) tea.Cmd {
102-
return core.CmdHandler(messages.OpenModelPickerMsg{})
47+
return core.CmdHandler(messages.AttachFileMsg{FilePath: arg})
10348
},
10449
},
10550
{
@@ -132,6 +77,16 @@ func builtInSessionCommands() []Item {
13277
return core.CmdHandler(messages.CopyLastResponseToClipboardMsg{})
13378
},
13479
},
80+
{
81+
ID: "session.cost",
82+
Label: "Cost",
83+
SlashCommand: "/cost",
84+
Description: "Show detailed cost breakdown for this session",
85+
Category: "Session",
86+
Execute: func(string) tea.Cmd {
87+
return core.CmdHandler(messages.ShowCostDialogMsg{})
88+
},
89+
},
13590
{
13691
ID: "session.eval",
13792
Label: "Eval",
@@ -142,6 +97,16 @@ func builtInSessionCommands() []Item {
14297
return core.CmdHandler(messages.EvalSessionMsg{Filename: arg})
14398
},
14499
},
100+
{
101+
ID: "session.exit",
102+
Label: "Exit",
103+
SlashCommand: "/exit",
104+
Description: "Exit the application",
105+
Category: "Session",
106+
Execute: func(string) tea.Cmd {
107+
return core.CmdHandler(messages.ExitSessionMsg{})
108+
},
109+
},
145110
{
146111
ID: "session.export",
147112
Label: "Export",
@@ -153,23 +118,43 @@ func builtInSessionCommands() []Item {
153118
},
154119
},
155120
{
156-
ID: "session.yolo",
157-
Label: "Yolo",
158-
SlashCommand: "/yolo",
159-
Description: "Toggle automatic approval of tool calls",
121+
ID: "session.model",
122+
Label: "Model",
123+
SlashCommand: "/model",
124+
Description: "Change the model for the current agent",
160125
Category: "Session",
161126
Execute: func(string) tea.Cmd {
162-
return core.CmdHandler(messages.ToggleYoloMsg{})
127+
return core.CmdHandler(messages.OpenModelPickerMsg{})
163128
},
164129
},
165130
{
166-
ID: "session.think",
167-
Label: "Think",
168-
SlashCommand: "/think",
169-
Description: "Toggle thinking/reasoning mode",
131+
ID: "session.new",
132+
Label: "New",
133+
SlashCommand: "/new",
134+
Description: "Start a new conversation",
170135
Category: "Session",
171136
Execute: func(string) tea.Cmd {
172-
return core.CmdHandler(messages.ToggleThinkingMsg{})
137+
return core.CmdHandler(messages.NewSessionMsg{})
138+
},
139+
},
140+
{
141+
ID: "session.permissions",
142+
Label: "Permissions",
143+
SlashCommand: "/permissions",
144+
Description: "Show tool permission rules for this session",
145+
Category: "Session",
146+
Execute: func(string) tea.Cmd {
147+
return core.CmdHandler(messages.ShowPermissionsDialogMsg{})
148+
},
149+
},
150+
{
151+
ID: "session.history",
152+
Label: "Sessions",
153+
SlashCommand: "/sessions",
154+
Description: "Browse and load past sessions",
155+
Category: "Session",
156+
Execute: func(string) tea.Cmd {
157+
return core.CmdHandler(messages.OpenSessionBrowserMsg{})
173158
},
174159
},
175160
{
@@ -183,45 +168,63 @@ func builtInSessionCommands() []Item {
183168
},
184169
},
185170
{
186-
ID: "session.cost",
187-
Label: "Cost",
188-
SlashCommand: "/cost",
189-
Description: "Show detailed cost breakdown for this session",
171+
ID: "session.star",
172+
Label: "Star",
173+
SlashCommand: "/star",
174+
Description: "Toggle star on current session",
190175
Category: "Session",
191176
Execute: func(string) tea.Cmd {
192-
return core.CmdHandler(messages.ShowCostDialogMsg{})
177+
return core.CmdHandler(messages.ToggleSessionStarMsg{})
193178
},
194179
},
195180
{
196-
ID: "session.permissions",
197-
Label: "Permissions",
198-
SlashCommand: "/permissions",
199-
Description: "Show tool permission rules for this session",
181+
ID: "session.think",
182+
Label: "Think",
183+
SlashCommand: "/think",
184+
Description: "Toggle thinking/reasoning mode",
200185
Category: "Session",
201186
Execute: func(string) tea.Cmd {
202-
return core.CmdHandler(messages.ShowPermissionsDialogMsg{})
187+
return core.CmdHandler(messages.ToggleThinkingMsg{})
203188
},
204189
},
205190
{
206-
ID: "session.attach",
207-
Label: "Attach",
208-
SlashCommand: "/attach",
209-
Description: "Attach a file to your message (usage: /attach [path])",
191+
ID: "session.title",
192+
Label: "Title",
193+
SlashCommand: "/title",
194+
Description: "Set or regenerate session title (usage: /title [new title])",
210195
Category: "Session",
211196
Execute: func(arg string) tea.Cmd {
212-
return core.CmdHandler(messages.AttachFileMsg{FilePath: arg})
197+
arg = strings.TrimSpace(arg)
198+
if arg == "" {
199+
// No argument: regenerate title
200+
return core.CmdHandler(messages.RegenerateTitleMsg{})
201+
}
202+
// With argument: set title
203+
return core.CmdHandler(messages.SetSessionTitleMsg{Title: arg})
213204
},
214205
},
215206
{
216-
ID: "settings.theme",
217-
Label: "Theme",
218-
SlashCommand: "/theme",
219-
Description: "Change the color theme",
220-
Category: "Settings",
207+
ID: "session.yolo",
208+
Label: "Yolo",
209+
SlashCommand: "/yolo",
210+
Description: "Toggle automatic approval of tool calls",
211+
Category: "Session",
221212
Execute: func(string) tea.Cmd {
222-
return core.CmdHandler(messages.OpenThemePickerMsg{})
213+
return core.CmdHandler(messages.ToggleYoloMsg{})
223214
},
224215
},
216+
}
217+
218+
// Add speak command on supported platforms (macOS only)
219+
if speak := speakCommand(); speak != nil {
220+
cmds = append(cmds, *speak)
221+
}
222+
223+
return cmds
224+
}
225+
226+
func builtInSettingsCommands() []Item {
227+
return []Item{
225228
{
226229
ID: "settings.split-diff",
227230
Label: "Split Diff",
@@ -232,39 +235,50 @@ func builtInSessionCommands() []Item {
232235
return core.CmdHandler(messages.ToggleSplitDiffMsg{})
233236
},
234237
},
238+
{
239+
ID: "settings.theme",
240+
Label: "Theme",
241+
SlashCommand: "/theme",
242+
Description: "Change the color theme",
243+
Category: "Settings",
244+
Execute: func(string) tea.Cmd {
245+
return core.CmdHandler(messages.OpenThemePickerMsg{})
246+
},
247+
},
235248
}
236-
237-
// Add speak command on supported platforms (macOS only)
238-
if speak := speakCommand(); speak != nil {
239-
cmds = append(cmds, *speak)
240-
}
241-
242-
return cmds
243249
}
244250

245251
func builtInFeedbackCommands() []Item {
246252
return []Item{
247253
{
248-
ID: "feedback.bug",
249-
Label: "Report Bug",
250-
Description: "Report a bug or issue",
254+
ID: "feedback.feedback",
255+
Label: "Give Feedback",
256+
Description: "Provide feedback about cagent",
251257
Category: "Feedback",
252258
Execute: func(string) tea.Cmd {
253-
return core.CmdHandler(messages.OpenURLMsg{URL: "https://github.com/docker/cagent/issues/new/choose"})
259+
return core.CmdHandler(messages.OpenURLMsg{URL: feedback.Link})
254260
},
255261
},
256262
{
257-
ID: "feedback.feedback",
258-
Label: "Give Feedback",
259-
Description: "Provide feedback about cagent",
263+
ID: "feedback.bug",
264+
Label: "Report Bug",
265+
Description: "Report a bug or issue",
260266
Category: "Feedback",
261267
Execute: func(string) tea.Cmd {
262-
return core.CmdHandler(messages.OpenURLMsg{URL: feedback.Link})
268+
return core.CmdHandler(messages.OpenURLMsg{URL: "https://github.com/docker/cagent/issues/new/choose"})
263269
},
264270
},
265271
}
266272
}
267273

274+
// sortByLabel returns items sorted alphabetically by label.
275+
func sortByLabel(items []Item) []Item {
276+
slices.SortFunc(items, func(a, b Item) int {
277+
return strings.Compare(strings.ToLower(a.Label), strings.ToLower(b.Label))
278+
})
279+
return items
280+
}
281+
268282
// BuildCommandCategories builds the list of command categories for the command palette
269283
func BuildCommandCategories(ctx context.Context, application *app.App) []Category {
270284
// Get session commands and filter based on model capabilities
@@ -298,10 +312,6 @@ func BuildCommandCategories(ctx context.Context, application *app.App) []Categor
298312
Name: "Session",
299313
Commands: sessionCommands,
300314
},
301-
{
302-
Name: "Feedback",
303-
Commands: builtInFeedbackCommands(),
304-
},
305315
}
306316

307317
agentCommands := application.CurrentAgentCommands(ctx)
@@ -322,7 +332,7 @@ func BuildCommandCategories(ctx context.Context, application *app.App) []Categor
322332

323333
categories = append(categories, Category{
324334
Name: "Agent Commands",
325-
Commands: commands,
335+
Commands: sortByLabel(commands),
326336
})
327337
}
328338

@@ -394,7 +404,7 @@ func BuildCommandCategories(ctx context.Context, application *app.App) []Categor
394404

395405
categories = append(categories, Category{
396406
Name: "MCP Prompts",
397-
Commands: mcpCommands,
407+
Commands: sortByLabel(mcpCommands),
398408
})
399409
}
400410

@@ -424,10 +434,22 @@ func BuildCommandCategories(ctx context.Context, application *app.App) []Categor
424434

425435
categories = append(categories, Category{
426436
Name: "Skills",
427-
Commands: skillCommands,
437+
Commands: sortByLabel(skillCommands),
428438
})
429439
}
430440

441+
// Settings and Feedback are always last, in that order.
442+
categories = append(categories,
443+
Category{
444+
Name: "Settings",
445+
Commands: builtInSettingsCommands(),
446+
},
447+
Category{
448+
Name: "Feedback",
449+
Commands: builtInFeedbackCommands(),
450+
},
451+
)
452+
431453
return categories
432454
}
433455

@@ -449,5 +471,11 @@ func ParseSlashCommand(input string) tea.Cmd {
449471
}
450472
}
451473

474+
for _, item := range builtInSettingsCommands() {
475+
if item.SlashCommand == cmd {
476+
return item.Execute(arg)
477+
}
478+
}
479+
452480
return nil
453481
}

0 commit comments

Comments
 (0)