kunchenguid--no-mistakes
26f897c1ec
release / release-please (push) Failing after 1m49s
docs / build (push) Failing after 6m34s
release / build-and-upload (arm64, linux) (push) Has been cancelled
release / build-and-upload (arm64, windows) (push) Has been cancelled
release / build-darwin (amd64, darwin) (push) Has been cancelled
release / checksums (push) Has been cancelled
release / finalize (push) Has been cancelled
release / build-darwin (arm64, darwin) (push) Has been cancelled
release / build-and-upload (amd64, linux) (push) Has been cancelled
release / build-and-upload (amd64, windows) (push) Has been cancelled
docs / deploy (push) Has been cancelled
154 行
4.1 KiB
Go
154 行
4.1 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"github.com/kunchenguid/no-mistakes/internal/config"
|
|
"github.com/kunchenguid/no-mistakes/internal/daemon"
|
|
"github.com/kunchenguid/no-mistakes/internal/db"
|
|
"github.com/kunchenguid/no-mistakes/internal/paths"
|
|
"github.com/kunchenguid/no-mistakes/internal/winproc"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func newDoctorCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "doctor",
|
|
Short: "Check system health and dependencies",
|
|
Args: cobra.NoArgs,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return trackCommandStatus("doctor", func() (string, error) {
|
|
w := cmd.OutOrStdout()
|
|
allOK := true
|
|
|
|
ok := func(label, detail string) {
|
|
fmt.Fprintf(w, " %s %s %s\n", sGreen.Render("✓"), sDim.Render(label), detail)
|
|
}
|
|
warn := func(label, detail string) {
|
|
fmt.Fprintf(w, " %s %s %s\n", sYellow.Render("–"), sDim.Render(label), detail)
|
|
}
|
|
fail := func(label, detail string) {
|
|
fmt.Fprintf(w, " %s %s %s\n", sRed.Render("✗"), sDim.Render(label), detail)
|
|
}
|
|
|
|
fmt.Fprintf(w, " %s\n", sCyan.Render("System"))
|
|
|
|
if _, err := exec.LookPath("git"); err != nil {
|
|
fail("git ", "not found")
|
|
allOK = false
|
|
} else {
|
|
gitCmd := exec.Command("git", "--version")
|
|
winproc.Harden(gitCmd)
|
|
out, err := gitCmd.Output()
|
|
if err != nil {
|
|
fail("git ", fmt.Sprintf("error (%v)", err))
|
|
allOK = false
|
|
} else {
|
|
ok("git ", strings.TrimSpace(string(out)))
|
|
}
|
|
}
|
|
|
|
if _, err := exec.LookPath("gh"); err != nil {
|
|
warn("gh ", "not found "+sDim.Render("(optional, needed for PR/CI)"))
|
|
} else {
|
|
ok("gh ", "ok")
|
|
}
|
|
|
|
if _, err := exec.LookPath("az"); err != nil {
|
|
warn("az ", "not found "+sDim.Render("(optional, needed for Azure DevOps PR/CI)"))
|
|
} else {
|
|
ok("az ", "ok")
|
|
}
|
|
|
|
p, err := paths.New()
|
|
if err != nil {
|
|
fail("data directory", fmt.Sprintf("error resolving paths (%v)", err))
|
|
allOK = false
|
|
} else if _, err := os.Stat(p.Root()); os.IsNotExist(err) {
|
|
fail("data directory", fmt.Sprintf("not found (%s)", p.Root()))
|
|
allOK = false
|
|
} else {
|
|
ok("data directory", p.Root())
|
|
}
|
|
|
|
if p != nil {
|
|
if _, err := os.Stat(p.DB()); os.IsNotExist(err) {
|
|
warn("database ", "not found "+sDim.Render("(will be created on first use)"))
|
|
} else {
|
|
d, err := db.Open(p.DB())
|
|
if err != nil {
|
|
fail("database ", fmt.Sprintf("error (%v)", err))
|
|
allOK = false
|
|
} else {
|
|
d.Close()
|
|
ok("database ", "ok")
|
|
}
|
|
}
|
|
}
|
|
|
|
if p != nil {
|
|
alive, _ := daemon.IsRunning(p)
|
|
if alive {
|
|
ok("daemon ", "running")
|
|
} else {
|
|
warn("daemon ", "stopped")
|
|
}
|
|
}
|
|
|
|
agents := []struct {
|
|
name string
|
|
binary string
|
|
}{
|
|
{"claude", "claude"},
|
|
{"codex", "codex"},
|
|
{"rovodev", "acli"},
|
|
{"opencode", "opencode"},
|
|
{"pi", "pi"},
|
|
{"copilot", "copilot"},
|
|
{"acpx", "acpx"},
|
|
}
|
|
fmt.Fprintln(w)
|
|
fmt.Fprintf(w, " %s\n", sCyan.Render("Agents"))
|
|
for _, a := range agents {
|
|
label := fmt.Sprintf("%-14s", a.name)
|
|
if path, err := exec.LookPath(a.binary); err != nil {
|
|
warn(label, "not found")
|
|
} else {
|
|
ok(label, path)
|
|
}
|
|
}
|
|
|
|
if p == nil {
|
|
fail("gate validation", "unavailable: data directory could not be resolved")
|
|
allOK = false
|
|
} else {
|
|
globalCfg, err := config.LoadGlobal(p.ConfigFile())
|
|
if err != nil {
|
|
fail("gate validation", fmt.Sprintf("unavailable: load config (%v)", err))
|
|
allOK = false
|
|
} else {
|
|
cfg := config.Merge(globalCfg, &config.RepoConfig{})
|
|
if err := cfg.ResolveAgent(cmd.Context(), exec.LookPath); err != nil {
|
|
fail("gate validation", err.Error())
|
|
allOK = false
|
|
} else {
|
|
ok("gate validation", fmt.Sprintf("%s is runnable", cfg.Agent))
|
|
}
|
|
}
|
|
}
|
|
|
|
if !allOK {
|
|
fmt.Fprintln(w)
|
|
fmt.Fprintf(w, " %s\n", sRed.Render("some checks failed"))
|
|
return "error", nil
|
|
}
|
|
|
|
return "success", nil
|
|
})
|
|
},
|
|
}
|
|
}
|