@@ -27,7 +27,7 @@ import (
2727 "github.com/spf13/cobra"
2828)
2929
30- const version = "0.1.36 "
30+ const version = "0.1.37 "
3131const defaultChromeExtensionID = "bgjoihaepiejlfjinojjfgokghnodnhd"
3232const defaultCLISessionID = "obu-cli"
3333const defaultMCPSessionID = "obu-mcp"
@@ -47,16 +47,24 @@ func main() {
4747}
4848
4949func run (args []string ) error {
50- if len ( args ) > 0 && isNativeMessagingLaunch (args [ 0 ] ) {
50+ if isNativeMessagingLaunch (args ) {
5151 return runHost (host .DefaultSocketDir , "" )
5252 }
5353 cmd := newRootCommand ()
5454 cmd .SetArgs (args )
5555 return cmd .Execute ()
5656}
5757
58- func isNativeMessagingLaunch (arg string ) bool {
59- return strings .HasPrefix (arg , "chrome-extension://" )
58+ func isNativeMessagingLaunch (args []string ) bool {
59+ for _ , arg := range args {
60+ if strings .HasPrefix (arg , "chrome-extension://" ) {
61+ return true
62+ }
63+ if runtime .GOOS == "windows" && strings .HasPrefix (arg , "--parent-window=" ) {
64+ return true
65+ }
66+ }
67+ return false
6068}
6169
6270func newRootCommand () * cobra.Command {
@@ -325,6 +333,12 @@ func installNativeManifestForBrowser(extensionID string, binaryPath string, outp
325333 if err := os .WriteFile (path , append (payload , '\n' ), 0o600 ); err != nil {
326334 return "" , err
327335 }
336+ if outputPath == "" && runtime .GOOS == "windows" {
337+ key := `HKCU\Software\Google\Chrome\NativeMessagingHosts\` + host .NativeHostName
338+ if err := regAddDefaultString (key , path ); err != nil {
339+ return "" , fmt .Errorf ("failed to register native messaging host %q: %w" , key , err )
340+ }
341+ }
328342 return path , nil
329343}
330344
@@ -892,6 +906,12 @@ func defaultChromeUserDataDir() (string, error) {
892906 return filepath .Join (home , "Library/Application Support/Google/Chrome" ), nil
893907 case "linux" :
894908 return filepath .Join (home , ".config/google-chrome" ), nil
909+ case "windows" :
910+ localAppData := os .Getenv ("LOCALAPPDATA" )
911+ if strings .TrimSpace (localAppData ) == "" {
912+ localAppData = filepath .Join (home , "AppData" , "Local" )
913+ }
914+ return filepath .Join (localAppData , "Google" , "Chrome" , "User Data" ), nil
895915 default :
896916 return "" , fmt .Errorf ("Chrome extension detection is not implemented for %s" , runtime .GOOS )
897917 }
@@ -1033,6 +1053,13 @@ func installChromeExternalExtensionForBrowser(extensionID string, outputPath str
10331053 if allowedExtensionID == "" {
10341054 allowedExtensionID = defaultChromeExtensionID
10351055 }
1056+ if runtime .GOOS == "windows" && outputPath == "" {
1057+ key := `HKCU\Software\Google\Chrome\Extensions\` + allowedExtensionID
1058+ if err := regAddString (key , "update_url" , chromeWebStoreUpdateURL ); err != nil {
1059+ return "" , fmt .Errorf ("failed to register Chrome external extension %q: %w" , key , err )
1060+ }
1061+ return "registry:" + key , nil
1062+ }
10361063 path := outputPath
10371064 if path == "" {
10381065 var err error
@@ -1521,6 +1548,23 @@ func supportedBrowserProfileRoots() ([]browserProfileRoot, error) {
15211548 BrowserName : "Google Chrome" ,
15221549 Root : filepath .Join (home , ".config/google-chrome" ),
15231550 }}, nil
1551+ case "windows" :
1552+ localAppData := os .Getenv ("LOCALAPPDATA" )
1553+ if strings .TrimSpace (localAppData ) == "" {
1554+ localAppData = filepath .Join (home , "AppData" , "Local" )
1555+ }
1556+ return []browserProfileRoot {
1557+ {
1558+ BrowserID : "chrome" ,
1559+ BrowserName : "Google Chrome" ,
1560+ Root : filepath .Join (localAppData , "Google" , "Chrome" , "User Data" ),
1561+ },
1562+ {
1563+ BrowserID : "chrome-beta" ,
1564+ BrowserName : "Google Chrome Beta" ,
1565+ Root : filepath .Join (localAppData , "Google" , "Chrome Beta" , "User Data" ),
1566+ },
1567+ }, nil
15241568 default :
15251569 return nil , fmt .Errorf ("browser profile detection is not implemented for %s" , runtime .GOOS )
15261570 }
@@ -2728,7 +2772,7 @@ func resolveNativeHostTarget(binaryPath string) (string, error) {
27282772 if info .IsDir () {
27292773 return "" , fmt .Errorf ("native host binary target is a directory: %s" , absolutePath )
27302774 }
2731- if info .Mode ()& 0o111 == 0 {
2775+ if runtime . GOOS != "windows" && info .Mode ()& 0o111 == 0 {
27322776 return "" , fmt .Errorf ("native host binary target is not executable: %s" , absolutePath )
27332777 }
27342778 return absolutePath , nil
@@ -2744,6 +2788,12 @@ func stableNativeHostPath() (string, error) {
27442788 return filepath .Join (home , "Library/Application Support/OpenBrowserUse/native-host/open-browser-use" ), nil
27452789 case "linux" :
27462790 return filepath .Join (home , ".local/share/open-browser-use/native-host/open-browser-use" ), nil
2791+ case "windows" :
2792+ localAppData := os .Getenv ("LOCALAPPDATA" )
2793+ if strings .TrimSpace (localAppData ) == "" {
2794+ localAppData = filepath .Join (home , "AppData" , "Local" )
2795+ }
2796+ return filepath .Join (localAppData , "OpenBrowserUse" , "native-host" , "open-browser-use.exe" ), nil
27472797 default :
27482798 return "" , fmt .Errorf ("stable native host link path is not implemented for %s" , runtime .GOOS )
27492799 }
@@ -2770,6 +2820,11 @@ func defaultChromeExternalExtensionPathForBrowser(extensionID string, browserSel
27702820 return filepath .Join (root .Root , "External Extensions" , filename ), nil
27712821 case "linux" :
27722822 return filepath .Join ("/opt/google/chrome/extensions" , filename ), nil
2823+ case "windows" :
2824+ if strings .TrimSpace (browserSelector ) != "" && ! browserSelectorMatches (browserSelector , connectedProfileInfo {Browser : "chrome" , BrowserName : "Google Chrome" }) {
2825+ return "" , fmt .Errorf ("Chrome external extension setup is not implemented for browser selector %q on %s" , browserSelector , runtime .GOOS )
2826+ }
2827+ return `HKCU\Software\Google\Chrome\Extensions\` + strings .TrimSpace (extensionID ), nil
27732828 default :
27742829 return "" , fmt .Errorf ("Chrome external extension setup is not implemented for %s" , runtime .GOOS )
27752830 }
@@ -2779,12 +2834,45 @@ func installStableNativeHostLink(targetPath string, linkPath string) error {
27792834 if err := os .MkdirAll (filepath .Dir (linkPath ), 0o700 ); err != nil {
27802835 return err
27812836 }
2837+ if runtime .GOOS == "windows" {
2838+ if samePath (targetPath , linkPath ) {
2839+ return nil
2840+ }
2841+ return copyFile (targetPath , linkPath , 0o700 )
2842+ }
27822843 if err := os .Remove (linkPath ); err != nil && ! errors .Is (err , os .ErrNotExist ) {
27832844 return err
27842845 }
27852846 return os .Symlink (targetPath , linkPath )
27862847}
27872848
2849+ func samePath (left string , right string ) bool {
2850+ leftAbs , leftErr := filepath .Abs (left )
2851+ rightAbs , rightErr := filepath .Abs (right )
2852+ if leftErr != nil || rightErr != nil {
2853+ return false
2854+ }
2855+ return strings .EqualFold (filepath .Clean (leftAbs ), filepath .Clean (rightAbs ))
2856+ }
2857+
2858+ func copyFile (sourcePath string , targetPath string , mode os.FileMode ) error {
2859+ source , err := os .Open (sourcePath )
2860+ if err != nil {
2861+ return err
2862+ }
2863+ defer source .Close ()
2864+ target , err := os .OpenFile (targetPath , os .O_CREATE | os .O_TRUNC | os .O_WRONLY , mode )
2865+ if err != nil {
2866+ return err
2867+ }
2868+ _ , copyErr := io .Copy (target , source )
2869+ closeErr := target .Close ()
2870+ if copyErr != nil {
2871+ return copyErr
2872+ }
2873+ return closeErr
2874+ }
2875+
27882876func defaultNativeHostManifestPath () (string , error ) {
27892877 return defaultNativeHostManifestPathForBrowser ("" )
27902878}
@@ -2804,6 +2892,12 @@ func defaultNativeHostManifestPathForBrowser(browserSelector string) (string, er
28042892 return filepath .Join (root .Root , "NativeMessagingHosts" , filename ), nil
28052893 case "linux" :
28062894 return filepath .Join (home , ".config/google-chrome/NativeMessagingHosts" , filename ), nil
2895+ case "windows" :
2896+ localAppData := os .Getenv ("LOCALAPPDATA" )
2897+ if strings .TrimSpace (localAppData ) == "" {
2898+ localAppData = filepath .Join (home , "AppData" , "Local" )
2899+ }
2900+ return filepath .Join (localAppData , "OpenBrowserUse" , "NativeMessagingHosts" , filename ), nil
28072901 default :
28082902 return "" , fmt .Errorf ("default manifest install path is not implemented for %s; pass --output" , runtime .GOOS )
28092903 }
@@ -3019,6 +3113,12 @@ func defaultUnpackedExtensionDir() (string, error) {
30193113 return filepath .Join (home , "Library/Application Support/OpenBrowserUse/chrome-extension/release" ), nil
30203114 case "linux" :
30213115 return filepath .Join (home , ".local/share/open-browser-use/chrome-extension/release" ), nil
3116+ case "windows" :
3117+ localAppData := os .Getenv ("LOCALAPPDATA" )
3118+ if strings .TrimSpace (localAppData ) == "" {
3119+ localAppData = filepath .Join (home , "AppData" , "Local" )
3120+ }
3121+ return filepath .Join (localAppData , "OpenBrowserUse" , "chrome-extension" , "release" ), nil
30223122 default :
30233123 return "" , fmt .Errorf ("release extension setup is not implemented for %s" , runtime .GOOS )
30243124 }
@@ -3254,6 +3354,8 @@ func openChromeExtensionsPage() error {
32543354 cmd = exec .Command ("open" , "-a" , "Google Chrome" , "chrome://extensions/" )
32553355 case "linux" :
32563356 cmd = exec .Command ("xdg-open" , "chrome://extensions/" )
3357+ case "windows" :
3358+ cmd = exec .Command ("rundll32" , "url.dll,FileProtocolHandler" , "chrome://extensions/" )
32573359 default :
32583360 return fmt .Errorf ("opening Chrome extensions page is not implemented for %s" , runtime .GOOS )
32593361 }
@@ -3281,6 +3383,28 @@ func openChromeWebStorePage() error {
32813383 return cmd .Process .Release ()
32823384}
32833385
3386+ func regAddDefaultString (key string , value string ) error {
3387+ return regAddString (key , "" , value )
3388+ }
3389+
3390+ func regAddString (key string , name string , value string ) error {
3391+ if runtime .GOOS != "windows" {
3392+ return fmt .Errorf ("registry setup is not implemented for %s" , runtime .GOOS )
3393+ }
3394+ args := []string {"ADD" , key }
3395+ if strings .TrimSpace (name ) == "" {
3396+ args = append (args , "/ve" )
3397+ } else {
3398+ args = append (args , "/v" , name )
3399+ }
3400+ args = append (args , "/t" , "REG_SZ" , "/d" , value , "/f" )
3401+ output , err := exec .Command ("reg.exe" , args ... ).CombinedOutput ()
3402+ if err != nil {
3403+ return fmt .Errorf ("%w: %s" , err , strings .TrimSpace (string (output )))
3404+ }
3405+ return nil
3406+ }
3407+
32843408func numberAsInt (value any ) (int , bool ) {
32853409 switch typed := value .(type ) {
32863410 case int :
0 commit comments