feat: redesign TUI + Ctrl+C quit confirm + version logic + sudo handling
All checks were successful
CI / build (push) Successful in 24s

- Replace 'q' quit with Ctrl+C confirmation dialog (Yes/No overlay)
  Second Ctrl+C within 2s force quits
- Redesign TUI with Charm bubbles components: spinner, progress bar,
  help bar, key bindings, better color palette, rounded borders
- Add Shift+Tab to cycle tabs backward
- Fix version: bump to 0.2.0, release workflow checks existing tags
  before publishing (no more overwriting releases)
- Handle sudo: CLI auto-relaunches with sudo/pkexec for tools that
  need elevated privileges, TUI shows clear error message

💘 Generated with Crush

Assisted-by: GLM-5.1 via Crush <crush@charm.land>
This commit is contained in:
Augustin
2026-04-19 23:22:04 +02:00
parent e58e00da13
commit e3cd618cb1
6 changed files with 624 additions and 191 deletions

View File

@@ -3,6 +3,8 @@ package main
import (
"fmt"
"os"
"os/exec"
"strings"
tea "github.com/charmbracelet/bubbletea"
"github.com/muyue/muyue/internal/config"
@@ -71,7 +73,7 @@ Usage:
Commands:
version Show version
scan Scan your system for tools and runtimes
install [tools] Install missing tools (crush, claude, bmad, starship, go, node, python, git)
install [tools] Install missing tools (needs sudo for some tools)
update Check and apply updates for all tools
setup Run first-time setup wizard
config Show current configuration
@@ -82,8 +84,8 @@ Commands:
TUI Controls:
1-5 Switch tabs (Dashboard/Chat/Workflow/Agents/Config)
Tab Cycle to next tab
q / Ctrl+C Quit
Tab / Shift+Tab Cycle tabs
Ctrl+C Show quit confirmation (press twice quickly to force quit)
Chat Commands:
/plan <goal> Start a structured Plan→Execute workflow
@@ -94,6 +96,10 @@ Workflow Controls:
[g] Generate plan (after answering questions)
[n] Execute next step
[x] Cancel/reset workflow
Note:
Some tools (docker, gh, etc.) require elevated privileges.
Run 'sudo muyue install' or use 'pkexec muyue install' if needed.
`, version.FullVersion())
}
@@ -174,6 +180,38 @@ func runInstall(tools []string) {
tools = missing
}
if needsSudo(tools) && os.Geteuid() != 0 {
fmt.Println("Some tools require elevated privileges.")
if path, err := exec.LookPath("sudo"); err == nil {
fmt.Printf("Re-running with sudo...\n")
cmd := exec.Command(path, append([]string{os.Args[0], "install"}, tools...)...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "sudo install failed: %v\n", err)
os.Exit(1)
}
config.Save(cfg)
return
}
if path, err := exec.LookPath("pkexec"); err == nil {
fmt.Printf("Re-running with pkexec...\n")
cmd := exec.Command(path, append([]string{os.Args[0], "install"}, tools...)...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "pkexec install failed: %v\n", err)
os.Exit(1)
}
config.Save(cfg)
return
}
fmt.Println("Neither sudo nor pkexec found. Some installs may fail.")
fmt.Println("Try running: sudo muyue install")
}
results := inst.InstallAll(tools)
for _, r := range results {
status := "[OK]"
@@ -186,6 +224,18 @@ func runInstall(tools []string) {
config.Save(cfg)
}
func needsSudo(tools []string) bool {
sudoTools := map[string]bool{
"docker": true, "git": true, "gh": true, "node": true, "python": true,
}
for _, t := range tools {
if sudoTools[t] {
return true
}
}
return false
}
func runUpdate() {
fmt.Println("Checking for updates...")
result := scanner.ScanSystem()
@@ -432,7 +482,7 @@ func runSkills(args []string) {
Description: description,
Content: resp,
Author: "muyue-generated",
Version: "1.0.0",
Version: "0.1.0",
Target: target,
Tags: []string{"generated"},
}
@@ -468,3 +518,22 @@ func runSkills(args []string) {
fmt.Println("Available: list, show, generate, deploy, init, delete")
}
}
func checkConfigProviders(cfg *config.MuyueConfig) {
for i := range cfg.AI.Providers {
if cfg.AI.Providers[i].Active {
return
}
}
if len(cfg.AI.Providers) > 0 {
cfg.AI.Providers[0].Active = true
}
}
func joinWithQuotes(items []string) string {
quoted := make([]string, len(items))
for i, item := range items {
quoted[i] = fmt.Sprintf("%q", item)
}
return strings.Join(quoted, ", ")
}