fix: docker version check, uv PATH, install progress bar
All checks were successful
CI / build (push) Successful in 1m45s
All checks were successful
CI / build (push) Successful in 1m45s
- Fix Docker latest version: use moby/moby instead of docker/compose - Fix uv install: add ~/.local/bin to PATH in shell rc after install - Add progress bar during tool installation in dashboard - Show spinner + per-tool progress with X/Y counter - Disable install key while installation is running 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
This commit is contained in:
@@ -344,7 +344,14 @@ func (i *Installer) installUv() InstallResult {
|
||||
var cmd *exec.Cmd
|
||||
switch i.system.OS {
|
||||
case platform.Linux, platform.MacOS:
|
||||
home, _ := os.UserHomeDir()
|
||||
cmd = exec.Command("bash", "-c", "curl -LsSf https://astral.sh/uv/install.sh | sh")
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return InstallResult{Tool: "uv", Success: false, Message: fmt.Sprintf("install failed: %s: %s", err, string(output))}
|
||||
}
|
||||
rcFile := i.getRCFile()
|
||||
appendLine(rcFile, "export PATH="+home+"/.local/bin:$PATH")
|
||||
return InstallResult{Tool: "uv", Success: true, Message: "installed (added ~/.local/bin to PATH)"}
|
||||
case platform.Windows:
|
||||
cmd = exec.Command("powershell", "-Command", "irm https://astral.sh/uv/install.ps1 | iex")
|
||||
default:
|
||||
|
||||
@@ -163,6 +163,11 @@ type aiResponseMsg struct{ content string }
|
||||
type aiErrMsg struct{ err error }
|
||||
type scanCompleteMsg struct{ result *scanner.ScanResult }
|
||||
type installCompleteMsg struct{ results []installer.InstallResult }
|
||||
type installProgressMsg struct {
|
||||
tool string
|
||||
current int
|
||||
total int
|
||||
}
|
||||
type updateCheckMsg struct{ statuses []updater.UpdateStatus }
|
||||
type previewReadyMsg struct{ url string }
|
||||
type workflowPhaseMsg struct{ phase workflow.Phase }
|
||||
@@ -206,6 +211,11 @@ type Model struct {
|
||||
|
||||
ctrlCCount int
|
||||
lastCtrlC time.Time
|
||||
|
||||
installing bool
|
||||
installCurrent int
|
||||
installTotal int
|
||||
installTool string
|
||||
}
|
||||
|
||||
type keyMap struct {
|
||||
@@ -390,6 +400,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.viewport.SetContent(m.renderContent())
|
||||
return m, nil
|
||||
case installCompleteMsg:
|
||||
m.installing = false
|
||||
for _, r := range msg.results {
|
||||
status := itemOKStyle.Render("[OK]")
|
||||
if !r.Success {
|
||||
@@ -398,6 +409,35 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.installLog = append(m.installLog, fmt.Sprintf(" %s %s: %s", status, r.Tool, r.Message))
|
||||
}
|
||||
m.scanResult = scanner.ScanSystem()
|
||||
m.progressBar.SetPercent(1)
|
||||
m.viewport.SetContent(m.renderContent())
|
||||
return m, nil
|
||||
case installProgressMsg:
|
||||
status := itemOKStyle.Render("[OK]")
|
||||
m.installLog = append(m.installLog, fmt.Sprintf(" %s %s installed", status, msg.tool))
|
||||
m.installCurrent = msg.current
|
||||
m.installTool = ""
|
||||
pct := float64(msg.current) / float64(max(msg.total, 1))
|
||||
m.progressBar.SetPercent(pct)
|
||||
m.viewport.SetContent(m.renderContent())
|
||||
return m, nil
|
||||
case installBatchMsg:
|
||||
status := itemOKStyle.Render("[OK]")
|
||||
if !msg.result.Success {
|
||||
status = itemMissingStyle.Render("[FAIL]")
|
||||
}
|
||||
m.installLog = append(m.installLog, fmt.Sprintf(" %s %s: %s", status, msg.result.Tool, msg.result.Message))
|
||||
m.installCurrent = msg.index + 1
|
||||
m.installTotal = len(msg.tools)
|
||||
pct := float64(m.installCurrent) / float64(max(m.installTotal, 1))
|
||||
m.progressBar.SetPercent(pct)
|
||||
if msg.index+1 < len(msg.tools) {
|
||||
m.installTool = msg.tools[msg.index+1]
|
||||
m.viewport.SetContent(m.renderContent())
|
||||
return m, startInstallCmd(msg.config, msg.tools, msg.index+1)
|
||||
}
|
||||
m.installing = false
|
||||
m.scanResult = scanner.ScanSystem()
|
||||
m.viewport.SetContent(m.renderContent())
|
||||
return m, nil
|
||||
case updateCheckMsg:
|
||||
@@ -577,14 +617,9 @@ func (m Model) handleChatSubmit() (tea.Model, tea.Cmd) {
|
||||
func (m Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.String() {
|
||||
case "i":
|
||||
return m, tea.Cmd(func() tea.Msg {
|
||||
needsSudo := checkNeedsSudo(m.scanResult)
|
||||
if needsSudo && !hasSudo() {
|
||||
return installCompleteMsg{results: []installer.InstallResult{
|
||||
{Tool: "system", Success: false, Message: "some tools require sudo. Run: muyue install, or relaunch with sudo/pkexec"},
|
||||
}}
|
||||
if m.installing {
|
||||
return m, nil
|
||||
}
|
||||
inst := installer.New(m.config)
|
||||
var missing []string
|
||||
if m.scanResult != nil {
|
||||
for _, t := range m.scanResult.Tools {
|
||||
@@ -594,10 +629,23 @@ func (m Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
}
|
||||
if len(missing) == 0 {
|
||||
return installCompleteMsg{results: []installer.InstallResult{}}
|
||||
m.installLog = append(m.installLog, itemOKStyle.Render("All tools already installed!"))
|
||||
m.viewport.SetContent(m.renderContent())
|
||||
return m, nil
|
||||
}
|
||||
return installCompleteMsg{results: inst.InstallAll(missing)}
|
||||
})
|
||||
needsSudo := checkNeedsSudo(m.scanResult)
|
||||
if needsSudo && !hasSudo() {
|
||||
m.installLog = append(m.installLog, errMsgStyle.Render("Some tools require sudo. Run: sudo muyue install"))
|
||||
m.viewport.SetContent(m.renderContent())
|
||||
return m, nil
|
||||
}
|
||||
m.installing = true
|
||||
m.installCurrent = 0
|
||||
m.installTotal = len(missing)
|
||||
m.installTool = missing[0]
|
||||
m.progressBar.SetPercent(0)
|
||||
m.viewport.SetContent(m.renderContent())
|
||||
return m, startInstallCmd(m.config, missing, 0)
|
||||
case "u":
|
||||
return m, tea.Cmd(func() tea.Msg {
|
||||
result := scanner.ScanSystem()
|
||||
@@ -748,6 +796,30 @@ func hasSudo() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func startInstallCmd(cfg *config.MuyueConfig, tools []string, index int) tea.Cmd {
|
||||
return tea.Cmd(func() tea.Msg {
|
||||
inst := installer.New(cfg)
|
||||
result := inst.InstallTool(tools[index])
|
||||
|
||||
if index+1 < len(tools) {
|
||||
return installBatchMsg{
|
||||
result: result,
|
||||
tools: tools,
|
||||
index: index,
|
||||
config: cfg,
|
||||
}
|
||||
}
|
||||
return installCompleteMsg{results: []installer.InstallResult{result}}
|
||||
})
|
||||
}
|
||||
|
||||
type installBatchMsg struct {
|
||||
result installer.InstallResult
|
||||
tools []string
|
||||
index int
|
||||
config *config.MuyueConfig
|
||||
}
|
||||
|
||||
func sendAIMessage(orch *orchestrator.Orchestrator, input string) tea.Cmd {
|
||||
return tea.Cmd(func() tea.Msg {
|
||||
if orch == nil {
|
||||
@@ -968,6 +1040,20 @@ func (m Model) renderDashboard() string {
|
||||
b.WriteString("\n")
|
||||
}
|
||||
|
||||
if m.installing {
|
||||
b.WriteString(sectionStyle.Render("Installing..."))
|
||||
b.WriteString("\n")
|
||||
progBar := m.progressBar.View()
|
||||
label := ""
|
||||
if m.installTool != "" {
|
||||
label = fmt.Sprintf(" %d/%d - %s", m.installCurrent+1, m.installTotal, m.installTool)
|
||||
} else {
|
||||
label = fmt.Sprintf(" %d/%d", m.installCurrent, m.installTotal)
|
||||
}
|
||||
b.WriteString(fmt.Sprintf(" %s%s\n", progBar, label))
|
||||
b.WriteString("\n")
|
||||
}
|
||||
|
||||
if len(m.installLog) > 0 {
|
||||
b.WriteString(sectionStyle.Render("Install Log"))
|
||||
b.WriteString("\n")
|
||||
|
||||
@@ -60,7 +60,7 @@ func getLatestVersion(tool string) (string, error) {
|
||||
"crush": "charmbracelet/crush",
|
||||
"gh": "cli/cli",
|
||||
"starship": "starship/starship",
|
||||
"docker": "docker/compose",
|
||||
"docker": "moby/moby",
|
||||
}
|
||||
|
||||
repo, ok := repos[tool]
|
||||
|
||||
Reference in New Issue
Block a user