- Add Go/Bubble Tea TUI app under tui/ with dashboard (status checks, launch, validate) and profile editor (commands, filesystem, network, environment sub-tabs) - Add Makefile targets: tui, tui-build, tui-install - Add CI step to build TUI - Relicense MIT -> GPL-3.0 - Rewrite wrapper to resolve real opencode binary from PATH (supports brew, npm, volta) - Add PATH ordering detection to install script - Expand README with how-it-works diagram, TUI docs, keybinding table
106 lines
2.8 KiB
Go
106 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
type CheckStatus int
|
|
|
|
const (
|
|
StatusUnknown CheckStatus = iota
|
|
StatusOK
|
|
StatusFail
|
|
)
|
|
|
|
type CheckResult struct {
|
|
Label string
|
|
Status CheckStatus
|
|
Detail string
|
|
}
|
|
|
|
func RunChecks() []CheckResult {
|
|
var results []CheckResult
|
|
|
|
if path, err := exec.LookPath("nono"); err == nil {
|
|
version := getNonoVersion()
|
|
detail := path
|
|
if version != "" {
|
|
detail += " " + version
|
|
}
|
|
results = append(results, CheckResult{"nono installed", StatusOK, detail})
|
|
} else {
|
|
results = append(results, CheckResult{"nono installed", StatusFail, "not found in PATH"})
|
|
}
|
|
|
|
profilePath, _ := DefaultProfilePath()
|
|
if profilePath != "" {
|
|
if info, err := os.Stat(profilePath); err == nil {
|
|
results = append(results, CheckResult{"profile installed", StatusOK,
|
|
profilePath + " (" + info.ModTime().Format("2006-01-02") + ")"})
|
|
} else {
|
|
results = append(results, CheckResult{"profile installed", StatusFail, "not found"})
|
|
}
|
|
}
|
|
|
|
home, _ := os.UserHomeDir()
|
|
wrapperPath := filepath.Join(home, ".local", "bin", "opencode")
|
|
if _, err := os.Stat(wrapperPath); err == nil {
|
|
results = append(results, CheckResult{"wrapper installed", StatusOK, wrapperPath})
|
|
} else {
|
|
results = append(results, CheckResult{"wrapper installed", StatusFail, "not found"})
|
|
}
|
|
|
|
if path, err := exec.LookPath("opencode"); err == nil {
|
|
if path == wrapperPath {
|
|
results = append(results, CheckResult{"wrapper first on PATH", StatusOK, path})
|
|
} else {
|
|
results = append(results, CheckResult{"wrapper first on PATH", StatusFail, "resolves to " + path})
|
|
}
|
|
} else {
|
|
results = append(results, CheckResult{"wrapper first on PATH", StatusFail, "opencode not in PATH"})
|
|
}
|
|
|
|
opencodePath := findRealOpencode(wrapperPath)
|
|
if opencodePath != "" {
|
|
results = append(results, CheckResult{"opencode binary", StatusOK, opencodePath})
|
|
} else {
|
|
results = append(results, CheckResult{"opencode binary", StatusFail, "real opencode not found"})
|
|
}
|
|
|
|
tinfoilDir := filepath.Join(home, ".tinfoil")
|
|
if info, err := os.Stat(tinfoilDir); err == nil && info.IsDir() {
|
|
results = append(results, CheckResult{"tinfoil credentials", StatusOK, tinfoilDir})
|
|
} else {
|
|
results = append(results, CheckResult{"tinfoil credentials", StatusFail, "no ~/.tinfoil directory"})
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
func getNonoVersion() string {
|
|
cmd := exec.Command("nono", "--version")
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(string(out))
|
|
}
|
|
|
|
func findRealOpencode(wrapperPath string) string {
|
|
path := os.Getenv("PATH")
|
|
for _, dir := range filepath.SplitList(path) {
|
|
for _, name := range []string{"opencode", "opencode.exe"} {
|
|
candidate := filepath.Join(dir, name)
|
|
if info, err := os.Stat(candidate); err == nil && !info.IsDir() {
|
|
if candidate != wrapperPath {
|
|
return candidate
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|