@@ -87,6 +87,7 @@ type (
OpenExternalEditorMsg struct{}
ToggleYoloModeMsg struct{}
OpenLazygitMsg struct{}
+ OpenGhDashMsg struct{}
CompactMsg struct {
SessionID string
}
@@ -452,6 +453,20 @@ func (c *commandDialogCmp) defaultCommands() []Command {
})
}
+ // Add gh-dash command if gh CLI with dash extension is installed.
+ if out, _, err := sh.Exec(c.ctx, "gh extension list"); err == nil {
+ if strings.Contains(out, "dash") {
+ commands = append(commands, Command{
+ ID: "ghdash",
+ Title: "Open GitHub Dashboard",
+ Description: "Open gh-dash for GitHub pull requests and issues",
+ Handler: func(cmd Command) tea.Cmd {
+ return util.CmdHandler(OpenGhDashMsg{})
+ },
+ })
+ }
+ }
+
return append(commands, []Command{
{
ID: "toggle_yolo",
@@ -0,0 +1,91 @@
+// Package ghdash provides a dialog component for embedding gh-dash in the TUI.
+package ghdash
+
+import (
+ "context"
+ "fmt"
+ "image/color"
+ "os"
+
+ "github.com/charmbracelet/crush/internal/terminal"
+ "github.com/charmbracelet/crush/internal/tui/components/dialogs"
+ "github.com/charmbracelet/crush/internal/tui/components/dialogs/termdialog"
+ "github.com/charmbracelet/crush/internal/tui/styles"
+)
+
+// DialogID is the unique identifier for the gh-dash dialog.
+const DialogID dialogs.DialogID = "ghdash"
+
+// NewDialog creates a new gh-dash dialog. The context controls the lifetime
+// of the gh-dash process - when cancelled, the process will be killed.
+func NewDialog(ctx context.Context, workingDir string) *termdialog.Dialog {
+ configFile := createThemedConfig()
+
+ cmd := terminal.PrepareCmd(
+ ctx,
+ "gh",
+ []string{"dash", "--config", configFile},
+ workingDir,
+ nil,
+ )
+
+ return termdialog.New(termdialog.Config{
+ ID: DialogID,
+ Title: "GitHub Dashboard",
+ LoadingMsg: "Starting gh-dash...",
+ Term: terminal.New(terminal.Config{Context: ctx, Cmd: cmd}),
+ OnClose: func() {
+ if configFile != "" {
+ _ = os.Remove(configFile)
+ }
+ },
+ })
+}
+
+// colorToHex converts a color.Color to a hex string.
+func colorToHex(c color.Color) string {
+ r, g, b, _ := c.RGBA()
+ return fmt.Sprintf("#%02x%02x%02x", r>>8, g>>8, b>>8)
+}
+
+// createThemedConfig creates a temporary gh-dash config file with Crush theme.
+func createThemedConfig() string {
+ t := styles.CurrentTheme()
+
+ config := fmt.Sprintf(`theme:
+ colors:
+ text:
+ primary: "%s"
+ secondary: "%s"
+ inverted: "%s"
+ faint: "%s"
+ warning: "%s"
+ success: "%s"
+ background:
+ selected: "%s"
+ border:
+ primary: "%s"
+ secondary: "%s"
+ faint: "%s"
+`,
+ colorToHex(t.FgBase),
+ colorToHex(t.FgMuted),
+ colorToHex(t.BgBase),
+ colorToHex(t.FgHalfMuted),
+ colorToHex(t.Warning),
+ colorToHex(t.Success),
+ colorToHex(t.Primary),
+ colorToHex(t.BorderFocus),
+ colorToHex(t.Border),
+ colorToHex(t.BgSubtle),
+ )
+
+ f, err := os.CreateTemp("", "crush-ghdash-*.yml")
+ if err != nil {
+ return ""
+ }
+ defer f.Close()
+
+ _, _ = f.WriteString(config)
+ return f.Name()
+}
@@ -28,6 +28,7 @@ import (
"github.com/charmbracelet/crush/internal/tui/components/dialogs"
"github.com/charmbracelet/crush/internal/tui/components/dialogs/commands"
"github.com/charmbracelet/crush/internal/tui/components/dialogs/filepicker"
+ "github.com/charmbracelet/crush/internal/tui/components/dialogs/ghdash"
"github.com/charmbracelet/crush/internal/tui/components/dialogs/lazygit"
"github.com/charmbracelet/crush/internal/tui/components/dialogs/models"
"github.com/charmbracelet/crush/internal/tui/components/dialogs/permissions"
@@ -312,6 +313,14 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return a, util.CmdHandler(dialogs.OpenDialogMsg{
Model: lazygit.NewDialog(a.app.Context(), a.app.Config().WorkingDir()),
})
+ // GhDash
+ case commands.OpenGhDashMsg:
+ if a.dialog.ActiveDialogID() == ghdash.DialogID {
+ return a, util.CmdHandler(dialogs.CloseDialogMsg{})
+ }
+ return a, util.CmdHandler(dialogs.OpenDialogMsg{
+ Model: ghdash.NewDialog(a.app.Context(), a.app.Config().WorkingDir()),
+ })
// Permissions
case pubsub.Event[permission.PermissionNotification]:
item, ok := a.pages[a.currentPage]