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
|
var cmd *exec.Cmd
|
||||||
switch i.system.OS {
|
switch i.system.OS {
|
||||||
case platform.Linux, platform.MacOS:
|
case platform.Linux, platform.MacOS:
|
||||||
|
home, _ := os.UserHomeDir()
|
||||||
cmd = exec.Command("bash", "-c", "curl -LsSf https://astral.sh/uv/install.sh | sh")
|
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:
|
case platform.Windows:
|
||||||
cmd = exec.Command("powershell", "-Command", "irm https://astral.sh/uv/install.ps1 | iex")
|
cmd = exec.Command("powershell", "-Command", "irm https://astral.sh/uv/install.ps1 | iex")
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -163,6 +163,11 @@ type aiResponseMsg struct{ content string }
|
|||||||
type aiErrMsg struct{ err error }
|
type aiErrMsg struct{ err error }
|
||||||
type scanCompleteMsg struct{ result *scanner.ScanResult }
|
type scanCompleteMsg struct{ result *scanner.ScanResult }
|
||||||
type installCompleteMsg struct{ results []installer.InstallResult }
|
type installCompleteMsg struct{ results []installer.InstallResult }
|
||||||
|
type installProgressMsg struct {
|
||||||
|
tool string
|
||||||
|
current int
|
||||||
|
total int
|
||||||
|
}
|
||||||
type updateCheckMsg struct{ statuses []updater.UpdateStatus }
|
type updateCheckMsg struct{ statuses []updater.UpdateStatus }
|
||||||
type previewReadyMsg struct{ url string }
|
type previewReadyMsg struct{ url string }
|
||||||
type workflowPhaseMsg struct{ phase workflow.Phase }
|
type workflowPhaseMsg struct{ phase workflow.Phase }
|
||||||
@@ -206,6 +211,11 @@ type Model struct {
|
|||||||
|
|
||||||
ctrlCCount int
|
ctrlCCount int
|
||||||
lastCtrlC time.Time
|
lastCtrlC time.Time
|
||||||
|
|
||||||
|
installing bool
|
||||||
|
installCurrent int
|
||||||
|
installTotal int
|
||||||
|
installTool string
|
||||||
}
|
}
|
||||||
|
|
||||||
type keyMap struct {
|
type keyMap struct {
|
||||||
@@ -390,6 +400,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.viewport.SetContent(m.renderContent())
|
m.viewport.SetContent(m.renderContent())
|
||||||
return m, nil
|
return m, nil
|
||||||
case installCompleteMsg:
|
case installCompleteMsg:
|
||||||
|
m.installing = false
|
||||||
for _, r := range msg.results {
|
for _, r := range msg.results {
|
||||||
status := itemOKStyle.Render("[OK]")
|
status := itemOKStyle.Render("[OK]")
|
||||||
if !r.Success {
|
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.installLog = append(m.installLog, fmt.Sprintf(" %s %s: %s", status, r.Tool, r.Message))
|
||||||
}
|
}
|
||||||
m.scanResult = scanner.ScanSystem()
|
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())
|
m.viewport.SetContent(m.renderContent())
|
||||||
return m, nil
|
return m, nil
|
||||||
case updateCheckMsg:
|
case updateCheckMsg:
|
||||||
@@ -577,27 +617,35 @@ func (m Model) handleChatSubmit() (tea.Model, tea.Cmd) {
|
|||||||
func (m Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
func (m Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
switch msg.String() {
|
switch msg.String() {
|
||||||
case "i":
|
case "i":
|
||||||
return m, tea.Cmd(func() tea.Msg {
|
if m.installing {
|
||||||
needsSudo := checkNeedsSudo(m.scanResult)
|
return m, nil
|
||||||
if needsSudo && !hasSudo() {
|
}
|
||||||
return installCompleteMsg{results: []installer.InstallResult{
|
var missing []string
|
||||||
{Tool: "system", Success: false, Message: "some tools require sudo. Run: muyue install, or relaunch with sudo/pkexec"},
|
if m.scanResult != nil {
|
||||||
}}
|
for _, t := range m.scanResult.Tools {
|
||||||
}
|
if !t.Installed {
|
||||||
inst := installer.New(m.config)
|
missing = append(missing, t.Name)
|
||||||
var missing []string
|
|
||||||
if m.scanResult != nil {
|
|
||||||
for _, t := range m.scanResult.Tools {
|
|
||||||
if !t.Installed {
|
|
||||||
missing = append(missing, t.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(missing) == 0 {
|
}
|
||||||
return installCompleteMsg{results: []installer.InstallResult{}}
|
if len(missing) == 0 {
|
||||||
}
|
m.installLog = append(m.installLog, itemOKStyle.Render("All tools already installed!"))
|
||||||
return installCompleteMsg{results: inst.InstallAll(missing)}
|
m.viewport.SetContent(m.renderContent())
|
||||||
})
|
return m, nil
|
||||||
|
}
|
||||||
|
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":
|
case "u":
|
||||||
return m, tea.Cmd(func() tea.Msg {
|
return m, tea.Cmd(func() tea.Msg {
|
||||||
result := scanner.ScanSystem()
|
result := scanner.ScanSystem()
|
||||||
@@ -748,6 +796,30 @@ func hasSudo() bool {
|
|||||||
return false
|
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 {
|
func sendAIMessage(orch *orchestrator.Orchestrator, input string) tea.Cmd {
|
||||||
return tea.Cmd(func() tea.Msg {
|
return tea.Cmd(func() tea.Msg {
|
||||||
if orch == nil {
|
if orch == nil {
|
||||||
@@ -968,6 +1040,20 @@ func (m Model) renderDashboard() string {
|
|||||||
b.WriteString("\n")
|
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 {
|
if len(m.installLog) > 0 {
|
||||||
b.WriteString(sectionStyle.Render("Install Log"))
|
b.WriteString(sectionStyle.Render("Install Log"))
|
||||||
b.WriteString("\n")
|
b.WriteString("\n")
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func getLatestVersion(tool string) (string, error) {
|
|||||||
"crush": "charmbracelet/crush",
|
"crush": "charmbracelet/crush",
|
||||||
"gh": "cli/cli",
|
"gh": "cli/cli",
|
||||||
"starship": "starship/starship",
|
"starship": "starship/starship",
|
||||||
"docker": "docker/compose",
|
"docker": "moby/moby",
|
||||||
}
|
}
|
||||||
|
|
||||||
repo, ok := repos[tool]
|
repo, ok := repos[tool]
|
||||||
|
|||||||
Reference in New Issue
Block a user