Add byre install-tool and a Homebrew row to the Server tab
This commit is contained in:
+29
-15
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -19,38 +18,48 @@ type check struct {
|
||||
Need string `json:"need"`
|
||||
For string `json:"for"`
|
||||
OK bool `json:"ok"`
|
||||
Command string `json:"command"`
|
||||
Tool string `json:"tool"`
|
||||
Fix string `json:"fix"`
|
||||
Blocking bool `json:"blocking"`
|
||||
}
|
||||
|
||||
func checks() []check {
|
||||
brew := command("Homebrew", "brew", needOptional, "installing codex, agy, node and Google Chrome", "brew")
|
||||
return []check{
|
||||
brew,
|
||||
gitCheck(),
|
||||
command("claude", needAgent, "Claude models", "curl -fsSL https://claude.ai/install.sh | bash", "Then run claude once to sign in."),
|
||||
command("codex", needAgent, "GPT models", "brew install --cask codex", "Then run codex once to sign in."),
|
||||
command("agy", needAgent, "Gemini models", "brew install --cask antigravity-cli", "Then run agy once to sign in."),
|
||||
command("npx", needOptional, "reviewers' Playwright browser tools", "brew install node", ""),
|
||||
appCheck("Google Chrome", needOptional, "browser tools for agents and reviewers", "brew install --cask google-chrome"),
|
||||
command("claude", "claude", needAgent, "Claude models", "claude"),
|
||||
withBrew(brew, command("codex", "codex", needAgent, "GPT models", "codex")),
|
||||
withBrew(brew, command("agy", "agy", needAgent, "Gemini models", "agy")),
|
||||
withBrew(brew, command("npx", "npx", needOptional, "reviewers' Playwright browser tools", "node")),
|
||||
withBrew(brew, appCheck("Google Chrome", needOptional, "browser tools for agents and reviewers", "chrome")),
|
||||
}
|
||||
}
|
||||
|
||||
func gitCheck() check {
|
||||
c := check{Name: "git", Need: needRequired, For: "worktrees, snapshots and merges", Command: "xcode-select --install"}
|
||||
c := check{Name: "git", Need: needRequired, For: "worktrees, snapshots and merges", Tool: "git"}
|
||||
path, err := exec.LookPath("git")
|
||||
// /usr/bin/git is a stub that offers to install the Command Line Tools when they're missing.
|
||||
c.OK = err == nil && (path != "/usr/bin/git" || exec.Command("xcode-select", "-p").Run() == nil)
|
||||
return c
|
||||
}
|
||||
|
||||
func command(name, need, purpose, install, fix string) check {
|
||||
_, err := exec.LookPath(name)
|
||||
return check{Name: name, Need: need, For: purpose, OK: err == nil, Command: install, Fix: fix}
|
||||
func command(name, binary, need, purpose, tool string) check {
|
||||
_, err := exec.LookPath(binary)
|
||||
return check{Name: name, Need: need, For: purpose, OK: err == nil, Tool: tool}
|
||||
}
|
||||
|
||||
func appCheck(name, need, purpose, install string) check {
|
||||
func appCheck(name, need, purpose, tool string) check {
|
||||
_, err := os.Stat("/Applications/" + name + ".app")
|
||||
return check{Name: name, Need: need, For: purpose, OK: err == nil, Command: install}
|
||||
return check{Name: name, Need: need, For: purpose, OK: err == nil, Tool: tool}
|
||||
}
|
||||
|
||||
func withBrew(brew, c check) check {
|
||||
if !c.OK && !brew.OK {
|
||||
c.Tool = ""
|
||||
c.Fix = "Install Homebrew first."
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func markBlocking(cs []check) bool {
|
||||
@@ -87,9 +96,14 @@ func requireChecks() {
|
||||
}
|
||||
fmt.Println("byre can't run on this Mac yet. It needs git and at least one of claude, codex and agy:")
|
||||
for _, c := range cs {
|
||||
if c.Blocking {
|
||||
fmt.Printf(" %s: %s\n", c.Name, strings.TrimSpace(c.Command+" "+c.Fix))
|
||||
if !c.Blocking {
|
||||
continue
|
||||
}
|
||||
if c.Tool == "" {
|
||||
fmt.Printf(" %s: %s %s\n", c.Name, c.Fix, installCommand("brew"))
|
||||
continue
|
||||
}
|
||||
fmt.Printf(" %s: %s\n", c.Name, installCommand(c.Tool))
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ func installCmd() {
|
||||
|
||||
func run(name string, args ...string) {
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
|
||||
+3
-1
@@ -18,6 +18,8 @@ func main() {
|
||||
installCmd()
|
||||
case "check":
|
||||
checkCmd()
|
||||
case "install-tool":
|
||||
installToolCmd(os.Args[2:])
|
||||
case "run":
|
||||
runCmd(os.Args[2:])
|
||||
case "signing":
|
||||
@@ -36,7 +38,7 @@ func main() {
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, "usage: byre serve | byre install | byre check | byre run [--resume ID] | byre signing CHECKOUT | byre grep [--glob GLOB] [--ignore-case] [--files-only] PATTERN PATH | byre list PATH | byre client-exec [--client NAME] [--timeout SECS] -- CMD... | byre screen")
|
||||
fmt.Fprintln(os.Stderr, "usage: byre serve | byre install | byre check | byre install-tool NAME |byre run [--resume ID] | byre signing CHECKOUT | byre grep [--glob GLOB] [--ignore-case] [--files-only] PATTERN PATH | byre list PATH | byre client-exec [--client NAME] [--timeout SECS] -- CMD... | byre screen")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type brewTool struct {
|
||||
args []string
|
||||
signIn []string
|
||||
}
|
||||
|
||||
var brewTools = map[string]brewTool{
|
||||
"codex": {args: []string{"--cask", "codex"}, signIn: []string{"codex", "login"}},
|
||||
"agy": {args: []string{"--cask", "antigravity-cli"}, signIn: []string{"agy"}},
|
||||
"node": {args: []string{"node"}},
|
||||
"chrome": {args: []string{"--cask", "google-chrome"}},
|
||||
}
|
||||
|
||||
const toolNames = "brew, git, claude, codex, agy, node or chrome"
|
||||
|
||||
func installToolCmd(args []string) {
|
||||
if len(args) != 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: byre install-tool NAME, where NAME is "+toolNames)
|
||||
os.Exit(2)
|
||||
}
|
||||
switch args[0] {
|
||||
case "brew":
|
||||
run("/bin/sh", "-c", `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`)
|
||||
addToProfile(".zprofile", `eval "$(/opt/homebrew/bin/brew shellenv)"`)
|
||||
case "git":
|
||||
run("xcode-select", "--install")
|
||||
fmt.Println("macOS is showing its own dialog to install the Command Line Tools: click Install there.")
|
||||
case "claude":
|
||||
run("/bin/sh", "-c", "curl -fsSL https://claude.ai/install.sh | bash")
|
||||
addToProfile(".zshrc", `export PATH="$HOME/.local/bin:$PATH"`)
|
||||
signIn(filepath.Join(homeDir(), ".local", "bin", "claude"))
|
||||
default:
|
||||
t, ok := brewTools[args[0]]
|
||||
if !ok {
|
||||
fmt.Fprintln(os.Stderr, "unknown tool "+args[0]+": NAME is "+toolNames)
|
||||
os.Exit(2)
|
||||
}
|
||||
brew, err := exec.LookPath("brew")
|
||||
if err != nil {
|
||||
fmt.Println("Homebrew isn't installed. Install it first:", installCommand("brew"))
|
||||
os.Exit(1)
|
||||
}
|
||||
run(brew, append([]string{"install"}, t.args...)...)
|
||||
if t.signIn != nil {
|
||||
signIn(filepath.Join(filepath.Dir(brew), t.signIn[0]), t.signIn[1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func installCommand(name string) string {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
fatal("find executable", "err", err)
|
||||
}
|
||||
return "'" + strings.ReplaceAll(exe, "'", `'\''`) + "' install-tool " + name
|
||||
}
|
||||
|
||||
func homeDir() string {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
fatal("home dir", "err", err)
|
||||
}
|
||||
return home
|
||||
}
|
||||
|
||||
func addToProfile(name, line string) {
|
||||
path := filepath.Join(homeDir(), name)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
fatal("read profile", "path", path, "err", err)
|
||||
}
|
||||
if strings.Contains(string(data), line) {
|
||||
return
|
||||
}
|
||||
text := line + "\n"
|
||||
if len(data) > 0 && !strings.HasSuffix(string(data), "\n") {
|
||||
text = "\n" + text
|
||||
}
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o644)
|
||||
if err != nil {
|
||||
fatal("open profile", "path", path, "err", err)
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = f.WriteString(text)
|
||||
if err != nil {
|
||||
fatal("write profile", "path", path, "err", err)
|
||||
}
|
||||
fmt.Println("Added", line, "to", path)
|
||||
}
|
||||
|
||||
func signIn(path string, args ...string) {
|
||||
fmt.Println("Starting", filepath.Base(path), "so you can sign in.")
|
||||
err := syscall.Exec(path, append([]string{path}, args...), os.Environ())
|
||||
fatal("start "+filepath.Base(path), "err", err)
|
||||
}
|
||||
Reference in New Issue
Block a user