hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/internal/cli
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-08-29 03:29:51 +0200
committerhachem <im@hachem.wtf>2026-08-29 03:29:51 +0200
commita221b4c5edd3ff4d4a9d74113d9d3b8c7473133d (patch)
treed902da32c84dad3d5fe0152be07d3bea6d4f9ee1 /internal/cli
parentf60177934a18daea3b24a37cf109bb2c7bfaaa72 (diff)
add: modrinth client and check command
Diffstat (limited to 'internal/cli')
-rw-r--r--internal/cli/check.go100
-rw-r--r--internal/cli/cli.go3
-rw-r--r--internal/cli/commands.go19
-rw-r--r--internal/cli/styles.go2
4 files changed, 118 insertions, 6 deletions
diff --git a/internal/cli/check.go b/internal/cli/check.go
new file mode 100644
index 0000000..1df5137
--- /dev/null
+++ b/internal/cli/check.go
@@ -0,0 +1,100 @@
+package cli
+
+import (
+ "fmt"
+
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/lipgloss"
+
+ "github.com/hachem-wtf/benson/internal/config"
+ "github.com/hachem-wtf/benson/internal/modrinth"
+)
+
+type compatMod struct {
+ slug string
+ filename string
+}
+
+type checkResult struct {
+ compatible []compatMod
+ incompatible []string
+ failed []string
+}
+
+func runCheck(m *model) tea.Cmd {
+ m.busy = true
+ cfg := m.cfg
+ return tea.Batch(m.spin.Tick, func() tea.Msg {
+ return outputMsg{text: checkReport(cfg)}
+ })
+}
+
+func checkReport(cfg *config.Config) string {
+ client := modrinth.NewClient()
+ var res checkResult
+
+ for _, slug := range cfg.ModrinthMods {
+ versions, err := client.GetVersions(slug)
+ if err != nil {
+ res.failed = append(res.failed, slug)
+ continue
+ }
+
+ match := modrinth.CompatibleVersion(versions, cfg.MinecraftVersion, "fabric")
+ if match == nil {
+ res.incompatible = append(res.incompatible, slug)
+ continue
+ }
+
+ filename := match.VersionNumber
+ if len(match.Files) > 0 {
+ filename = match.Files[0].Filename
+ }
+ res.compatible = append(res.compatible, compatMod{slug: slug, filename: filename})
+ }
+
+ return renderCheck(cfg, res)
+}
+
+func renderCheck(cfg *config.Config, res checkResult) string {
+ rows := []string{
+ headingStyle.Render(fmt.Sprintf("Compatibility · MC %s · Fabric", cfg.MinecraftVersion)),
+ "",
+ }
+
+ if len(res.compatible) > 0 {
+ rows = append(rows, successStyle.Render("Compatible"))
+ for _, c := range res.compatible {
+ rows = append(rows, " "+successStyle.Render("✓ ")+c.slug+mutedStyle.Render(" → "+c.filename))
+ }
+ rows = append(rows, "")
+ }
+
+ if len(res.incompatible) > 0 {
+ rows = append(rows, errorStyle.Render("No compatible version"))
+ for _, slug := range res.incompatible {
+ rows = append(rows, " "+errorStyle.Render("✗ ")+slug)
+ }
+ rows = append(rows, "")
+ }
+
+ if len(res.failed) > 0 {
+ rows = append(rows, warnStyle.Render("Failed to check"))
+ for _, slug := range res.failed {
+ rows = append(rows, " "+warnStyle.Render("! ")+slug)
+ }
+ rows = append(rows, "")
+ }
+
+ total := len(cfg.ModrinthMods)
+ ok := len(res.compatible)
+ rows = append(rows, mutedStyle.Render(fmt.Sprintf("Summary: %d/%d mods compatible", ok, total)))
+
+ if len(res.incompatible) == 0 && len(res.failed) == 0 {
+ rows = append(rows, successStyle.Render("Safe to generate."))
+ } else {
+ rows = append(rows, warnStyle.Render("Not all mods are compatible — generate may have issues."))
+ }
+
+ return lipgloss.JoinVertical(lipgloss.Left, rows...)
+}
diff --git a/internal/cli/cli.go b/internal/cli/cli.go
index 4e394e6..6609a7e 100644
--- a/internal/cli/cli.go
+++ b/internal/cli/cli.go
@@ -101,6 +101,9 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, tea.Println(msg.text)
case spinner.TickMsg:
+ if !m.busy {
+ return m, nil
+ }
var cmd tea.Cmd
m.spin, cmd = m.spin.Update(msg)
return m, cmd
diff --git a/internal/cli/commands.go b/internal/cli/commands.go
index 1f1f132..cf7cc90 100644
--- a/internal/cli/commands.go
+++ b/internal/cli/commands.go
@@ -20,6 +20,11 @@ func defaultCommands() []command {
},
},
{
+ name: "check",
+ desc: "Check whether the configured mods are compatible",
+ run: runCheck,
+ },
+ {
name: "help",
desc: "Show available commands",
run: func(m *model) tea.Cmd {
@@ -30,15 +35,19 @@ func defaultCommands() []command {
}
func welcomeBlock(cfg *config.Config, version string) string {
+ header := lipgloss.JoinHorizontal(
+ lipgloss.Bottom,
+ titleStyle.Render("BENSON"),
+ subtitleStyle.Render(" v"+version),
+ )
+
inner := lipgloss.JoinVertical(
lipgloss.Left,
- titleStyle.Render("BENSON"),
- subtitleStyle.Render("a Minecraft server manager from hell"),
- "",
- mutedStyle.Render(fmt.Sprintf("Fabric · MC %s · v%s", cfg.MinecraftVersion, version)),
+ header,
+ mutedStyle.Render("Fabric · MC "+cfg.MinecraftVersion),
)
- hint := mutedStyle.Render("Type a command below. Tab autocompletes · Ctrl+C quits.")
+ hint := mutedStyle.Render("Tab autocompletes · Ctrl+C quits")
return lipgloss.JoinVertical(lipgloss.Left, bannerStyle.Render(inner), "", hint)
}
diff --git a/internal/cli/styles.go b/internal/cli/styles.go
index c0a61e5..78d5cb4 100644
--- a/internal/cli/styles.go
+++ b/internal/cli/styles.go
@@ -14,7 +14,7 @@ var (
bannerStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(colPrimary).
- Padding(0, 3)
+ Padding(0, 2)
titleStyle = lipgloss.NewStyle().Bold(true).Foreground(colPrimary)
subtitleStyle = lipgloss.NewStyle().Foreground(colMuted)