browser: small new package

Move browser opening functionality from dockerimg to a new browser package.
Updated all references to use browser.Open directly.
This avoids importing dockerimg widely.

Co-Authored-By: sketch <hello@sketch.dev>
diff --git a/browser/browser.go b/browser/browser.go
new file mode 100644
index 0000000..f6e1443
--- /dev/null
+++ b/browser/browser.go
@@ -0,0 +1,30 @@
+// Package browser provides functions for opening URLs in a web browser.
+package browser
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"os/exec"
+	"runtime"
+)
+
+// Open opens the specified URL in the system's default browser.
+// It detects the OS and uses the appropriate command:
+// - 'open' for macOS
+// - 'cmd /c start' for Windows
+// - 'xdg-open' for Linux and other Unix-like systems
+func Open(ctx context.Context, url string) {
+	var cmd *exec.Cmd
+	switch runtime.GOOS {
+	case "darwin":
+		cmd = exec.CommandContext(ctx, "open", url)
+	case "windows":
+		cmd = exec.CommandContext(ctx, "cmd", "/c", "start", url)
+	default: // Linux and other Unix-like systems
+		cmd = exec.CommandContext(ctx, "xdg-open", url)
+	}
+	if b, err := cmd.CombinedOutput(); err != nil {
+		fmt.Fprintf(os.Stderr, "failed to open browser: %v: %s\n", err, b)
+	}
+}
diff --git a/cmd/sketch/main.go b/cmd/sketch/main.go
index 23654ab..cb0cee6 100644
--- a/cmd/sketch/main.go
+++ b/cmd/sketch/main.go
@@ -18,6 +18,7 @@
 
 	"github.com/richardlehane/crock32"
 	"sketch.dev/ant"
+	"sketch.dev/browser"
 	"sketch.dev/dockerimg"
 	"sketch.dev/httprr"
 	"sketch.dev/loop"
@@ -307,7 +308,7 @@
 
 	// Open the web UI URL in the system browser if requested
 	if *openBrowser {
-		dockerimg.OpenBrowser(ctx, ps1URL)
+		browser.Open(ctx, ps1URL)
 	}
 
 	// Create the termui instance
diff --git a/dockerimg/dockerimg.go b/dockerimg/dockerimg.go
index 9ca188b..03fc400 100644
--- a/dockerimg/dockerimg.go
+++ b/dockerimg/dockerimg.go
@@ -20,6 +20,7 @@
 	"strings"
 	"time"
 
+	"sketch.dev/browser"
 	"sketch.dev/loop/server"
 	"sketch.dev/skribe"
 	"sketch.dev/webui"
@@ -310,9 +311,9 @@
 		// We open the browser after the init config because the above waits for the web server to be serving.
 		if config.OpenBrowser {
 			if config.SkabandAddr != "" {
-				OpenBrowser(ctx, fmt.Sprintf("%s/s/%s", config.SkabandAddr, config.SessionID))
+				browser.Open(ctx, fmt.Sprintf("%s/s/%s", config.SkabandAddr, config.SessionID))
 			} else {
-				OpenBrowser(ctx, "http://"+localAddr)
+				browser.Open(ctx, "http://"+localAddr)
 			}
 		}
 	}()
@@ -764,21 +765,6 @@
 	return filepath.Dir(absGitDir), err
 }
 
-func OpenBrowser(ctx context.Context, url string) {
-	var cmd *exec.Cmd
-	switch runtime.GOOS {
-	case "darwin":
-		cmd = exec.CommandContext(ctx, "open", url)
-	case "windows":
-		cmd = exec.CommandContext(ctx, "cmd", "/c", "start", url)
-	default: // Linux and other Unix-like systems
-		cmd = exec.CommandContext(ctx, "xdg-open", url)
-	}
-	if b, err := cmd.CombinedOutput(); err != nil {
-		fmt.Fprintf(os.Stderr, "failed to open browser: %v: %s\n", err, b)
-	}
-}
-
 // moveFile is like Python's shutil.move, in that it tries a rename, and, if that fails,
 // copies and deletes
 func moveFile(src, dst string) error {