integrations: hermes windows install (#16487)
This commit is contained in:
@@ -24,6 +24,8 @@ import (
|
||||
|
||||
const (
|
||||
hermesInstallScript = "curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash -s -- --skip-setup"
|
||||
hermesWindowsInstallURL = "https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.ps1"
|
||||
hermesWindowsInstallCmd = "& ([scriptblock]::Create((irm " + hermesWindowsInstallURL + "))) -SkipSetup"
|
||||
hermesProviderName = "Ollama"
|
||||
hermesProviderKey = "ollama-launch"
|
||||
hermesLegacyKey = "ollama"
|
||||
@@ -187,14 +189,12 @@ func (h *Hermes) ensureInstalled() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if hermesGOOS == "windows" {
|
||||
return hermesWindowsHint()
|
||||
}
|
||||
|
||||
var missing []string
|
||||
for _, dep := range []string{"bash", "curl", "git"} {
|
||||
if _, err := hermesLookPath(dep); err != nil {
|
||||
missing = append(missing, dep)
|
||||
if hermesGOOS != "windows" {
|
||||
for _, dep := range []string{"bash", "curl", "git"} {
|
||||
if _, err := hermesLookPath(dep); err != nil {
|
||||
missing = append(missing, dep)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(missing) > 0 {
|
||||
@@ -210,7 +210,7 @@ func (h *Hermes) ensureInstalled() error {
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "\nInstalling Hermes...\n")
|
||||
if err := hermesAttachedCommand("bash", "-lc", hermesInstallScript).Run(); err != nil {
|
||||
if err := h.runInstallScript(); err != nil {
|
||||
return fmt.Errorf("failed to install hermes: %w", err)
|
||||
}
|
||||
|
||||
@@ -222,6 +222,13 @@ func (h *Hermes) ensureInstalled() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Hermes) runInstallScript() error {
|
||||
if hermesGOOS == "windows" {
|
||||
return hermesAttachedCommand("powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", hermesWindowsInstallCmd).Run()
|
||||
}
|
||||
return hermesAttachedCommand("bash", "-lc", hermesInstallScript).Run()
|
||||
}
|
||||
|
||||
func (h *Hermes) listModels(defaultModel string) []string {
|
||||
client := hermesOllamaClient()
|
||||
resp, err := client.List(context.Background())
|
||||
@@ -259,7 +266,12 @@ func (h *Hermes) binary() (string, error) {
|
||||
}
|
||||
|
||||
if hermesGOOS == "windows" {
|
||||
return "", hermesWindowsHint()
|
||||
for _, fallback := range hermesWindowsBinaryFallbacks() {
|
||||
if _, err := os.Stat(fallback); err == nil {
|
||||
return fallback, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("hermes is not installed")
|
||||
}
|
||||
|
||||
home, err := hermesUserHome()
|
||||
@@ -274,6 +286,36 @@ func (h *Hermes) binary() (string, error) {
|
||||
return "", fmt.Errorf("hermes is not installed")
|
||||
}
|
||||
|
||||
func hermesWindowsBinaryFallbacks() []string {
|
||||
var roots []string
|
||||
add := func(root string) {
|
||||
root = strings.TrimSpace(root)
|
||||
if root != "" {
|
||||
roots = append(roots, filepath.Clean(root))
|
||||
}
|
||||
}
|
||||
|
||||
add(os.Getenv("HERMES_HOME"))
|
||||
add(os.Getenv("LOCALAPPDATA"))
|
||||
if home, err := hermesUserHome(); err == nil {
|
||||
add(filepath.Join(home, "AppData", "Local"))
|
||||
}
|
||||
|
||||
seen := make(map[string]bool, len(roots))
|
||||
var fallbacks []string
|
||||
for _, root := range roots {
|
||||
if seen[root] {
|
||||
continue
|
||||
}
|
||||
seen[root] = true
|
||||
fallbacks = append(fallbacks, filepath.Join(root, "hermes-agent", "venv", "Scripts", "hermes.exe"))
|
||||
if filepath.Base(root) != "hermes" {
|
||||
fallbacks = append(fallbacks, filepath.Join(root, "hermes", "hermes-agent", "venv", "Scripts", "hermes.exe"))
|
||||
}
|
||||
}
|
||||
return fallbacks
|
||||
}
|
||||
|
||||
func hermesConfigPath() (string, error) {
|
||||
home, err := hermesUserHome()
|
||||
if err != nil {
|
||||
@@ -671,9 +713,3 @@ func hermesAttachedCommand(name string, args ...string) *exec.Cmd {
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd
|
||||
}
|
||||
|
||||
func hermesWindowsHint() error {
|
||||
return fmt.Errorf("Hermes on Windows requires WSL2. Install WSL with: wsl --install\n" +
|
||||
"Then run 'ollama launch hermes' from inside your WSL shell.\n" +
|
||||
"Docs: https://hermes-agent.nousresearch.com/docs/getting-started/installation/")
|
||||
}
|
||||
|
||||
@@ -943,26 +943,59 @@ func TestHermesMessagingConfiguredRecognizesSupportedGatewayVars(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHermesEnsureInstalledWindowsShowsWSLGuidance(t *testing.T) {
|
||||
func TestHermesEnsureInstalledWindowsRunsPowerShellInstaller(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("uses a POSIX shell test binary")
|
||||
}
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
setTestHome(t, tmpDir)
|
||||
withLauncherHooks(t)
|
||||
withHermesPlatform(t, "windows")
|
||||
t.Setenv("PATH", tmpDir)
|
||||
t.Setenv("LOCALAPPDATA", filepath.Join(tmpDir, "AppData", "Local"))
|
||||
|
||||
powershell := filepath.Join(tmpDir, "powershell.exe")
|
||||
script := fmt.Sprintf(`#!/bin/sh
|
||||
printf '%%s\n' "$*" >> %q
|
||||
/bin/mkdir -p %q
|
||||
/bin/cat > %q <<'EOS'
|
||||
#!/bin/sh
|
||||
exit 0
|
||||
EOS
|
||||
/bin/chmod +x %q
|
||||
exit 0
|
||||
`,
|
||||
filepath.Join(tmpDir, "powershell.log"),
|
||||
filepath.Dir(filepath.Join(tmpDir, "AppData", "Local", "hermes", "hermes-agent", "venv", "Scripts", "hermes.exe")),
|
||||
filepath.Join(tmpDir, "AppData", "Local", "hermes", "hermes-agent", "venv", "Scripts", "hermes.exe"),
|
||||
filepath.Join(tmpDir, "AppData", "Local", "hermes", "hermes-agent", "venv", "Scripts", "hermes.exe"),
|
||||
)
|
||||
if err := os.WriteFile(powershell, []byte(script), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
DefaultConfirmPrompt = func(prompt string, options ConfirmOptions) (bool, error) {
|
||||
if prompt != "Hermes is not installed. Install now?" {
|
||||
t.Fatalf("unexpected install prompt %q", prompt)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
h := &Hermes{}
|
||||
err := h.ensureInstalled()
|
||||
if err == nil {
|
||||
t.Fatal("expected WSL guidance error")
|
||||
if err := h.ensureInstalled(); err != nil {
|
||||
t.Fatalf("ensureInstalled returned error: %v", err)
|
||||
}
|
||||
msg := err.Error()
|
||||
if !strings.Contains(msg, "wsl --install") {
|
||||
t.Fatalf("expected install command in guidance, got %v", err)
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(tmpDir, "powershell.log"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(msg, "hermes-agent.nousresearch.com") {
|
||||
t.Fatalf("expected docs link in guidance, got %v", err)
|
||||
}
|
||||
if strings.Contains(msg, "hermes is not installed") {
|
||||
t.Fatalf("guidance should not lead with 'hermes is not installed', got %v", err)
|
||||
logs := string(data)
|
||||
for _, want := range []string{"-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", hermesWindowsInstallURL, "-SkipSetup"} {
|
||||
if !strings.Contains(logs, want) {
|
||||
t.Fatalf("expected PowerShell installer args to contain %q, got logs:\n%s", want, logs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user