@@ -136,14 +136,15 @@ func newSetupCommand() *cobra.Command {
136136 if err != nil {
137137 return err
138138 }
139- if ! noOpen {
139+ status := detectBrowserExtension (host .DefaultSocketDir , 700 * time .Millisecond )
140+ shouldOpenStore := shouldOpenStoreSetup (status , noOpen )
141+ if shouldOpenStore {
140142 if err := openChromeWebStorePage (); err != nil {
141143 result .StoreOpenError = err .Error ()
142144 } else {
143145 result .OpenedStore = true
144146 }
145147 }
146- status := detectBrowserExtension (host .DefaultSocketDir , 700 * time .Millisecond )
147148 return renderStoreSetupResult (cmd .OutOrStdout (), result , status )
148149 },
149150 }
@@ -198,24 +199,27 @@ func newSetupBetaCommand() *cobra.Command {
198199 if err != nil {
199200 return err
200201 }
201- if ! noOpen {
202+ status := detectBrowserExtension (host .DefaultSocketDir , 700 * time .Millisecond )
203+ status .InstallCommand = "open-browser-use setup beta"
204+ status .UpgradeCommand = "open-browser-use setup beta"
205+ shouldOpen := shouldOpenManualSetup (status , noOpen )
206+ if shouldOpen {
202207 if err := openChromeExtensionsPage (); err != nil {
203208 return err
204209 }
205210 if err := revealFile (installZIPPath ); err != nil {
206211 return err
207212 }
208213 }
209- status := detectBrowserExtension (host .DefaultSocketDir , 700 * time .Millisecond )
210- status .InstallCommand = "open-browser-use setup beta"
211- status .UpgradeCommand = "open-browser-use setup beta"
214+ skillUpdate := maybeUpdateInstalledSkill ()
212215 return renderManualSetupResult (cmd .OutOrStdout (), manualSetupResult {
213216 NativeManifestPath : manifestPath ,
214217 ExtensionID : effectiveExtensionID ,
215218 ZIPPath : installZIPPath ,
216219 UnpackedPath : unpackedPath ,
217- OpenedChrome : ! noOpen ,
218- OpenedFileManager : ! noOpen ,
220+ OpenedChrome : shouldOpen ,
221+ OpenedFileManager : shouldOpen ,
222+ SkillUpdate : skillUpdate ,
219223 }, status )
220224 },
221225 }
@@ -311,6 +315,7 @@ type setupResult struct {
311315 StoreURL string
312316 OpenedStore bool
313317 StoreOpenError string
318+ SkillUpdate skillUpdateStatus
314319}
315320
316321type manualSetupResult struct {
@@ -320,6 +325,7 @@ type manualSetupResult struct {
320325 UnpackedPath string
321326 OpenedChrome bool
322327 OpenedFileManager bool
328+ SkillUpdate skillUpdateStatus
323329}
324330
325331type browserExtensionStatus struct {
@@ -334,6 +340,13 @@ type browserExtensionStatus struct {
334340 Error string
335341}
336342
343+ type skillUpdateStatus struct {
344+ Checked bool
345+ Attempted bool
346+ Updated bool
347+ Error string
348+ }
349+
337350func setupChrome (extensionID string , binaryPath string , externalExtensionOutput string ) (setupResult , error ) {
338351 manifestPath , err := installNativeManifest (extensionID , binaryPath , "" )
339352 if err != nil {
@@ -347,6 +360,7 @@ func setupChrome(extensionID string, binaryPath string, externalExtensionOutput
347360 NativeManifestPath : manifestPath ,
348361 ExternalExtensionPath : extensionPath ,
349362 StoreURL : chromeWebStoreExtensionURL ,
363+ SkillUpdate : maybeUpdateInstalledSkill (),
350364 }, nil
351365}
352366
@@ -391,19 +405,25 @@ func renderStoreSetupResult(writer io.Writer, result setupResult, status browser
391405 fmt .Fprintf (writer , "3. Open Chrome Web Store\n %s\n " , result .StoreURL )
392406 }
393407 fmt .Fprintf (writer , "4. 🧩 Browser extension\n %s\n " , status .summaryForSetup ("Not installed yet. Install or enable it from the Chrome Web Store page." ))
408+ renderSkillUpdateResult (writer , result .SkillUpdate )
394409 fmt .Fprintln (writer )
395410 if status .isReady () {
396- fmt .Fprintln (writer , "All set. The browser extension is installed, connected, and on the expected version." )
411+ fmt .Fprintln (writer , "All set. Browser extension is installed, connected, and on the expected version." )
397412 return nil
398413 }
399414 if status .needsUpgrade () {
400415 fmt .Fprintf (writer , "Next: upgrade the browser extension with `%s`, then run `open-browser-use info`.\n " , status .UpgradeCommand )
401416 return nil
402417 }
403- fmt .Fprintln (writer , "Next:" )
404- fmt .Fprintln (writer , " 1. Install or enable Open Browser Use from the Chrome Web Store page." )
405- fmt .Fprintln (writer , " 2. Restart Chrome if Chrome asks or the extension does not appear immediately." )
406- fmt .Fprintln (writer , " 3. Verify the connection: open-browser-use info" )
418+ if result .OpenedStore {
419+ fmt .Fprintln (writer , "Next: the Chrome Web Store page is open; install or enable the extension, then run `open-browser-use info`." )
420+ return nil
421+ }
422+ if result .StoreOpenError != "" {
423+ fmt .Fprintln (writer , "Next: open the Chrome Web Store page manually, install or enable the extension, then run `open-browser-use info`." )
424+ return nil
425+ }
426+ fmt .Fprintln (writer , "Next: restart Chrome if needed, approve or enable the extension if Chrome asks, then run `open-browser-use info`." )
407427 return nil
408428}
409429
@@ -413,29 +433,35 @@ func renderManualSetupResult(writer io.Writer, result manualSetupResult, status
413433 fmt .Fprintf (writer , "2. ✅ Prepared browser extension package\n Extension id: %s\n ZIP: %s\n Includes stable extension key for this id.\n " , result .ExtensionID , result .ZIPPath )
414434 fmt .Fprintf (writer , "3. ✅ Prepared unpacked extension directory\n %s\n " , result .UnpackedPath )
415435 fmt .Fprintf (writer , "4. 🧩 Browser extension\n %s\n " , status .summaryForSetup ("Not installed yet. Finish the ZIP install below." ))
436+ renderSkillUpdateResult (writer , result .SkillUpdate )
416437 fmt .Fprintln (writer )
417438 if status .isReady () {
418- fmt .Fprintln (writer , "All set. The browser extension is installed, connected, and on the expected version." )
439+ fmt .Fprintln (writer , "All set. Browser extension is installed, connected, and on the expected version." )
419440 return nil
420441 }
421- if result .OpenedChrome {
422- fmt .Fprintln (writer , "Opened chrome://extensions for you." )
423- } else {
424- fmt .Fprintln (writer , "Open chrome://extensions manually." )
425- }
426- if result .OpenedFileManager {
427- fmt .Fprintln (writer , "Opened the extension package in Finder/file manager." )
428- } else {
429- fmt .Fprintf (writer , "Open the folder containing the package: %s\n " , filepath .Dir (result .ZIPPath ))
442+ if result .OpenedChrome && result .OpenedFileManager {
443+ fmt .Fprintln (writer , "Next: chrome://extensions and Finder/file manager are open; enable Developer mode, drag the ZIP into Chrome, then run `open-browser-use info`." )
444+ return nil
430445 }
431- fmt .Fprintln (writer , "Next:" )
432- fmt .Fprintln (writer , " 1. Turn on Developer mode in chrome://extensions." )
433- fmt .Fprintln (writer , " 2. Drag the ZIP file into the Chrome extensions page to install it manually." )
434- fmt .Fprintln (writer , " 3. Approve or enable the Open Browser Use extension if Chrome asks." )
435- fmt .Fprintln (writer , " 4. Verify the connection: open-browser-use info" )
446+ fmt .Fprintf (writer , "Next: open chrome://extensions, enable Developer mode, drag in %s, then run `open-browser-use info`.\n " , result .ZIPPath )
436447 return nil
437448}
438449
450+ func renderSkillUpdateResult (writer io.Writer , status skillUpdateStatus ) {
451+ if status .Updated {
452+ fmt .Fprintln (writer , "5. ✅ Agent skill\n Updated existing open-browser-use skill." )
453+ return
454+ }
455+ if status .Attempted && status .Error != "" {
456+ fmt .Fprintf (writer , "5. ⚠️ Agent skill\n Existing skill update failed: %s\n " , status .Error )
457+ return
458+ }
459+ if ! status .Checked {
460+ return
461+ }
462+ fmt .Fprintln (writer , "5. ℹ️ Agent skill install commands\n Codex: npx skills add iFurySt/open-codex-browser-use -g -a codex --skill open-browser-use --copy -y\n Claude Code: npx skills add iFurySt/open-codex-browser-use -g -a claude-code --skill open-browser-use --copy -y" )
463+ }
464+
439465func detectBrowserExtension (socketDir string , timeout time.Duration ) browserExtensionStatus {
440466 status := browserExtensionStatus {
441467 ExpectedVersion : version ,
@@ -607,6 +633,51 @@ func (status browserExtensionStatus) needsUpgrade() bool {
607633 return status .Installed && status .Version != "" && compareChromeVersions (status .Version , status .ExpectedVersion ) < 0
608634}
609635
636+ func shouldOpenManualSetup (status browserExtensionStatus , noOpen bool ) bool {
637+ if noOpen {
638+ return false
639+ }
640+ return status .needsInstall () || status .needsUpgrade ()
641+ }
642+
643+ func shouldOpenStoreSetup (status browserExtensionStatus , noOpen bool ) bool {
644+ if noOpen {
645+ return false
646+ }
647+ return status .needsInstall () || status .needsUpgrade ()
648+ }
649+
650+ func maybeUpdateInstalledSkill () skillUpdateStatus {
651+ npxPath , err := exec .LookPath ("npx" )
652+ if err != nil {
653+ return skillUpdateStatus {}
654+ }
655+ status := skillUpdateStatus {Checked : true }
656+ if err := runSilentCommand (20 * time .Second , npxPath , "skills" ); err != nil {
657+ return status
658+ }
659+ status .Attempted = true
660+ if err := runSilentCommand (2 * time .Minute , npxPath , "skills" , "update" , "open-browser-use" , "-g" , "-y" ); err != nil {
661+ status .Error = err .Error ()
662+ return status
663+ }
664+ status .Updated = true
665+ return status
666+ }
667+
668+ func runSilentCommand (timeout time.Duration , name string , args ... string ) error {
669+ ctx , cancel := context .WithTimeout (context .Background (), timeout )
670+ defer cancel ()
671+ cmd := exec .CommandContext (ctx , name , args ... )
672+ cmd .Stdout = io .Discard
673+ cmd .Stderr = io .Discard
674+ err := cmd .Run ()
675+ if ctx .Err () == context .DeadlineExceeded {
676+ return fmt .Errorf ("%s timed out" , strings .Join (append ([]string {name }, args ... ), " " ))
677+ }
678+ return err
679+ }
680+
610681func compareChromeVersions (left string , right string ) int {
611682 leftParts := strings .Split (left , "." )
612683 rightParts := strings .Split (right , "." )
0 commit comments