Skip to content

Commit 1845035

Browse files
znsoftznsoft
authored andcommitted
feat(tools): Implement ToolManager for tool detection and auto-installation
1 parent b5eeecb commit 1845035

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

tool_manager.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os/exec"
6+
"runtime"
7+
"strings"
8+
)
9+
10+
type ToolStatus struct {
11+
Name string `json:"name"`
12+
Installed bool `json:"installed"`
13+
Version string `json:"version"`
14+
Path string `json:"path"`
15+
}
16+
17+
type ToolManager struct {
18+
app *App
19+
}
20+
21+
func NewToolManager(app *App) *ToolManager {
22+
return &ToolManager{app: app}
23+
}
24+
25+
func (tm *ToolManager) GetToolStatus(name string) ToolStatus {
26+
status := ToolStatus{Name: name}
27+
path, err := exec.LookPath(name)
28+
if err != nil {
29+
// Try common aliases or specific checks if needed
30+
if name == "claude" {
31+
// Already handled in app.go, but let's centralize here
32+
}
33+
return status
34+
}
35+
36+
status.Installed = true
37+
status.Path = path
38+
39+
version, err := tm.getToolVersion(name, path)
40+
if err == nil {
41+
status.Version = version
42+
}
43+
44+
return status
45+
}
46+
47+
func (tm *ToolManager) getToolVersion(name, path string) (string, error) {
48+
var cmd *exec.Command
49+
switch name {
50+
case "claude":
51+
cmd = exec.Command(path, "--version")
52+
case "gemini":
53+
cmd = exec.Command(path, "--version")
54+
case "codex":
55+
cmd = exec.Command(path, "--version")
56+
default:
57+
cmd = exec.Command(path, "--version")
58+
}
59+
60+
out, err := cmd.Output()
61+
if err != nil {
62+
return "", err
63+
}
64+
65+
output := strings.TrimSpace(string(out))
66+
// Parse version based on tool output format
67+
if name == "claude" {
68+
// claude-code/0.2.29 darwin-arm64 node-v22.12.0
69+
parts := strings.Split(output, " ")
70+
if len(parts) > 0 {
71+
verParts := strings.Split(parts[0], "/")
72+
if len(verParts) == 2 {
73+
return verParts[1], nil
74+
}
75+
}
76+
}
77+
78+
// Default fallback: return the first thing that looks like a version
79+
return output, nil
80+
}
81+
82+
func (tm *ToolManager) InstallTool(name string) error {
83+
var cmd *exec.Command
84+
switch name {
85+
case "claude":
86+
cmd = exec.Command("npm", "install", "-g", "@anthropic-ai/claude-code")
87+
case "gemini":
88+
// Assuming gemini-chat-cli or similar
89+
cmd = exec.Command("npm", "install", "-g", "gemini-chat-cli")
90+
case "codex":
91+
// Assuming an npm package for codex if it exists, or just placeholder
92+
cmd = exec.Command("npm", "install", "-g", "openai-codex-cli")
93+
default:
94+
return fmt.Errorf("unknown tool: %s", name)
95+
}
96+
97+
// For Windows, we might need to handle the .cmd extension for npm
98+
if runtime.GOOS == "windows" {
99+
cmd.Args = append([]string{"/c", "npm"}, cmd.Args[1:]...)
100+
cmd.Path = "cmd"
101+
}
102+
103+
out, err := cmd.CombinedOutput()
104+
if err != nil {
105+
return fmt.Errorf("failed to install %s: %v\nOutput: %s", name, err, string(out))
106+
}
107+
return nil
108+
}
109+
110+
func (a *App) InstallTool(name string) error {
111+
tm := NewToolManager(a)
112+
return tm.InstallTool(name)
113+
}
114+
115+
func (a *App) CheckToolsStatus() []ToolStatus {
116+
tm := NewToolManager(a)
117+
tools := []string{"claude", "gemini", "codex"}
118+
statuses := make([]ToolStatus, len(tools))
119+
for i, name := range tools {
120+
statuses[i] = tm.GetToolStatus(name)
121+
}
122+
return statuses
123+
}

0 commit comments

Comments
 (0)