all: support popping a browser from termui
- Add 'browser', 'open', and 'b' command aliases to termui
- Open the current conversation URL in default browser
- Add help documentation for the new command
Add browser launch endpoint to Git server for Docker support.
We'll probably want to set up a proper mux for the no-longer-just-git
server pretty soon.
Co-Authored-By: sketch <hello@sketch.dev>
diff --git a/loop/agent.go b/loop/agent.go
index b12a5f5..cc861ed 100644
--- a/loop/agent.go
+++ b/loop/agent.go
@@ -6,6 +6,7 @@
_ "embed"
"encoding/json"
"fmt"
+ "io"
"log/slog"
"net/http"
"os"
@@ -17,6 +18,7 @@
"time"
"sketch.dev/ant"
+ "sketch.dev/browser"
"sketch.dev/claudetool"
"sketch.dev/claudetool/bashkit"
)
@@ -92,6 +94,8 @@
OutsideHostname() string
OutsideWorkingDir() string
GitOrigin() string
+ // OpenBrowser is a best-effort attempt to open a browser at url in outside sketch.
+ OpenBrowser(url string)
// RestartConversation resets the conversation history
RestartConversation(ctx context.Context, rev string, initialPrompt string) error
@@ -274,6 +278,7 @@
lastHEAD string // hash of the last HEAD that was pushed to the host (only when under docker)
initialCommit string // hash of the Git HEAD when the agent was instantiated or Init()
gitRemoteAddr string // HTTP URL of the host git repo (only when under docker)
+ outsideHTTP string // base address of the outside webserver (only when under docker)
ready chan struct{} // closed when the agent is initialized (only when under docker)
startedAt time.Time
originalBudget ant.Budget
@@ -393,6 +398,27 @@
return a.gitOrigin
}
+func (a *Agent) OpenBrowser(url string) {
+ if !a.IsInContainer() {
+ browser.Open(url)
+ return
+ }
+ // We're in Docker, need to send a request to the Git server
+ // to signal that the outer process should open the browser.
+ httpc := &http.Client{Timeout: 5 * time.Second}
+ resp, err := httpc.Post(a.outsideHTTP+"/browser", "text/plain", strings.NewReader(url))
+ if err != nil {
+ slog.Debug("browser launch request connection failed", "err", err, "url", url)
+ return
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode == http.StatusOK {
+ return
+ }
+ body, _ := io.ReadAll(resp.Body)
+ slog.Debug("browser launch request execution failed", "status", resp.Status, "body", string(body))
+}
+
// CurrentState returns the current state of the agent's state machine.
func (a *Agent) CurrentState() State {
return a.stateMachine.CurrentState()
@@ -595,6 +621,7 @@
InDocker bool
Commit string
+ OutsideHTTP string
GitRemoteAddr string
HostAddr string
}
@@ -627,6 +654,7 @@
}
a.lastHEAD = ini.Commit
a.gitRemoteAddr = ini.GitRemoteAddr
+ a.outsideHTTP = ini.OutsideHTTP
a.initialCommit = ini.Commit
if ini.HostAddr != "" {
a.url = "http://" + ini.HostAddr