@@ -17,6 +17,8 @@ const BASE: &str = "http://127.0.0.1:11434";
1717/// install locations. A macOS app launched from Finder/Dock inherits only a
1818/// minimal PATH (`/usr/bin:/bin:/usr/sbin:/sbin`) — *not* `/opt/homebrew/bin` —
1919/// so without this, `brew` and `ollama` look "missing" even when installed.
20+ /// On Windows the equivalent gap is a PATH captured before Ollama's installer
21+ /// appended to the *user* PATH, so its default install dir is added explicitly.
2022fn search_dirs ( ) -> Vec < PathBuf > {
2123 let mut dirs: Vec < PathBuf > = std:: env:: var_os ( "PATH" )
2224 . map ( |p| std:: env:: split_paths ( & p) . collect ( ) )
@@ -26,30 +28,57 @@ fn search_dirs() -> Vec<PathBuf> {
2628 dirs. push ( d) ;
2729 }
2830 } ;
29- for d in [
30- "/opt/homebrew/bin" , // Apple Silicon Homebrew
31- "/opt/homebrew/sbin" ,
32- "/usr/local/bin" , // Intel Homebrew + Ollama.app CLI symlink
33- "/usr/local/sbin" ,
34- ] {
35- push ( & mut dirs, PathBuf :: from ( d) ) ;
31+ #[ cfg( unix) ]
32+ {
33+ for d in [
34+ "/opt/homebrew/bin" , // Apple Silicon Homebrew
35+ "/opt/homebrew/sbin" ,
36+ "/usr/local/bin" , // Intel Homebrew + Ollama.app CLI symlink
37+ "/usr/local/sbin" ,
38+ ] {
39+ push ( & mut dirs, PathBuf :: from ( d) ) ;
40+ }
41+ if let Some ( home) = std:: env:: var_os ( "HOME" ) {
42+ for sub in [ ".local/bin" , ".ollama/bin" ] {
43+ push ( & mut dirs, PathBuf :: from ( & home) . join ( sub) ) ;
44+ }
45+ }
3646 }
37- if let Some ( home) = std:: env:: var_os ( "HOME" ) {
38- for sub in [ ".local/bin" , ".ollama/bin" ] {
39- push ( & mut dirs, PathBuf :: from ( & home) . join ( sub) ) ;
47+ #[ cfg( windows) ]
48+ {
49+ if let Some ( local) = std:: env:: var_os ( "LOCALAPPDATA" ) {
50+ // OllamaSetup.exe's per-user default.
51+ push ( & mut dirs, PathBuf :: from ( & local) . join ( "Programs" ) . join ( "Ollama" ) ) ;
52+ }
53+ if let Some ( pf) = std:: env:: var_os ( "ProgramFiles" ) {
54+ push ( & mut dirs, PathBuf :: from ( & pf) . join ( "Ollama" ) ) ;
4055 }
4156 }
4257 dirs
4358}
4459
4560/// Absolute path of an executable found across [`search_dirs`], if any.
4661fn find_bin ( name : & str ) -> Option < PathBuf > {
62+ let file = format ! ( "{name}{}" , std:: env:: consts:: EXE_SUFFIX ) ;
4763 search_dirs ( )
4864 . into_iter ( )
49- . map ( |d| d. join ( name ) )
65+ . map ( |d| d. join ( & file ) )
5066 . find ( |p| p. is_file ( ) )
5167}
5268
69+ /// Keep a spawned console tool from flashing a terminal window on Windows
70+ /// (CREATE_NO_WINDOW). The app itself is `windows_subsystem = "windows"`, so
71+ /// every `Command` would otherwise pop one open. No-op elsewhere.
72+ pub fn hide_console ( cmd : & mut Command ) {
73+ #[ cfg( windows) ]
74+ {
75+ use std:: os:: windows:: process:: CommandExt ;
76+ cmd. creation_flags ( 0x0800_0000 ) ;
77+ }
78+ #[ cfg( not( windows) ) ]
79+ let _ = cmd;
80+ }
81+
5382/// A `Command` for a CLI tool, resolved to its absolute path and with an
5483/// augmented PATH exported so any subprocess it spawns resolves too. Falls back
5584/// to the bare name (letting the OS search) when the binary isn't found.
@@ -60,6 +89,7 @@ fn command(name: &str) -> Command {
6089 None => Command :: new ( name) ,
6190 } ;
6291 cmd. env ( "PATH" , path) ;
92+ hide_console ( & mut cmd) ;
6393 cmd
6494}
6595
@@ -129,7 +159,8 @@ fn run_streamed(app: &AppHandle, event: &str, mut cmd: Command) -> Result<(), St
129159 }
130160}
131161
132- /// Install Ollama (macOS: Homebrew; Linux: official script), streaming progress.
162+ /// Install Ollama (macOS: Homebrew; Linux: official script; Windows: WinGet),
163+ /// streaming progress.
133164pub fn install ( app : & AppHandle ) -> Result < ( ) , String > {
134165 let _ = app. emit ( "ai-progress" , "Installing Ollama…" . to_string ( ) ) ;
135166
@@ -149,7 +180,21 @@ pub fn install(app: &AppHandle) -> Result<(), String> {
149180 cmd. args ( [ "-c" , "curl -fsSL https://ollama.com/install.sh | sh" ] ) ;
150181 run_streamed ( app, "ai-progress" , cmd) ?;
151182 }
152- #[ cfg( not( any( target_os = "macos" , target_os = "linux" ) ) ) ]
183+ #[ cfg( target_os = "windows" ) ]
184+ {
185+ let has_winget = command ( "winget" ) . arg ( "--version" ) . stdout ( Stdio :: null ( ) ) . stderr ( Stdio :: null ( ) ) . status ( ) . map ( |s| s. success ( ) ) . unwrap_or ( false ) ;
186+ if !has_winget {
187+ return Err ( "WinGet isn't available. Install Ollama from https://ollama.com/download, then reopen this." . into ( ) ) ;
188+ }
189+ let mut cmd = command ( "winget" ) ;
190+ cmd. args ( [
191+ "install" , "--exact" , "--id" , "Ollama.Ollama" ,
192+ "--silent" , "--accept-source-agreements" , "--accept-package-agreements" ,
193+ "--disable-interactivity" ,
194+ ] ) ;
195+ run_streamed ( app, "ai-progress" , cmd) ?;
196+ }
197+ #[ cfg( not( any( target_os = "macos" , target_os = "linux" , target_os = "windows" ) ) ) ]
153198 {
154199 return Err ( "Automatic install isn't supported on this platform. Install Ollama from https://ollama.com/download." . into ( ) ) ;
155200 }
0 commit comments