From 682c7524c753b61209f1b6ca95304180cdc82326 Mon Sep 17 00:00:00 2001 From: mirchaemanuel Date: Sat, 28 Mar 2026 19:09:31 +0100 Subject: [PATCH 1/9] feat: add RemoteURL field to Session struct --- internal/model/types.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/model/types.go b/internal/model/types.go index e3381ee..bba69cc 100644 --- a/internal/model/types.go +++ b/internal/model/types.go @@ -100,6 +100,9 @@ type Session struct { // Desktop metadata (non-nil if session was started via Claude Desktop) Desktop *DesktopMeta + + // Remote control (Claude Code only, empty if unavailable) + RemoteURL string } // Clone returns a deep copy of the Session suitable for use as a base From 9822efe67459e244628c1201138e09436b7ce090 Mon Sep 17 00:00:00 2001 From: mirchaemanuel Date: Sat, 28 Mar 2026 19:11:02 +0100 Subject: [PATCH 2/9] feat: extract remote control URL from bridge_status JSONL entries Add Subtype and URL fields to jsonlHeader and parse bridge_status system entries to populate Session.RemoteURL with the Claude Code remote control link. --- internal/claude/jsonl.go | 6 +++++ internal/claude/jsonl_test.go | 48 +++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 internal/claude/jsonl_test.go diff --git a/internal/claude/jsonl.go b/internal/claude/jsonl.go index fd899a7..b831855 100644 --- a/internal/claude/jsonl.go +++ b/internal/claude/jsonl.go @@ -18,6 +18,8 @@ import ( // Only cheap fields are decoded; the heavy Message field is skipped. type jsonlHeader struct { Type string `json:"type"` + Subtype string `json:"subtype"` + URL string `json:"url"` CWD string `json:"cwd"` Version string `json:"version"` GitBranch string `json:"gitBranch"` @@ -118,6 +120,10 @@ func scanEntries(scanner *bufio.Scanner, session *model.Session, initialOffset i } } + if h.Type == "system" && h.Subtype == "bridge_status" && h.URL != "" { + session.RemoteURL = h.URL + } + if !ts.IsZero() { prevTimestamp = ts entryTimestamps = append(entryTimestamps, ts) diff --git a/internal/claude/jsonl_test.go b/internal/claude/jsonl_test.go new file mode 100644 index 0000000..d139d07 --- /dev/null +++ b/internal/claude/jsonl_test.go @@ -0,0 +1,48 @@ +package claude + +import ( + "os" + "path/filepath" + "testing" +) + +func TestParseJSONL_BridgeStatus(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "test.jsonl") + + data := `{"type":"system","subtype":"bridge_status","url":"https://claude.ai/code/session_ABC123","sessionId":"abc","version":"2.1.86","timestamp":"2026-01-15T10:00:00Z"} +{"type":"user","timestamp":"2026-01-15T10:00:01Z","message":{"role":"user","content":"hello"},"cwd":"/proj","version":"1.0","gitBranch":"main"} +{"type":"assistant","timestamp":"2026-01-15T10:00:05Z","message":{"role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"text","text":"Hi!"}],"usage":{"input_tokens":10,"output_tokens":5}}} +` + if err := os.WriteFile(path, []byte(data), 0644); err != nil { + t.Fatal(err) + } + + sess, _, err := ParseJSONL(path) + if err != nil { + t.Fatal(err) + } + if sess.RemoteURL != "https://claude.ai/code/session_ABC123" { + t.Errorf("RemoteURL = %q, want %q", sess.RemoteURL, "https://claude.ai/code/session_ABC123") + } +} + +func TestParseJSONL_NoBridgeStatus(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "test.jsonl") + + data := `{"type":"user","timestamp":"2026-01-15T10:00:01Z","message":{"role":"user","content":"hello"},"cwd":"/proj","version":"1.0","gitBranch":"main"} +{"type":"assistant","timestamp":"2026-01-15T10:00:05Z","message":{"role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"text","text":"Hi!"}],"usage":{"input_tokens":10,"output_tokens":5}}} +` + if err := os.WriteFile(path, []byte(data), 0644); err != nil { + t.Fatal(err) + } + + sess, _, err := ParseJSONL(path) + if err != nil { + t.Fatal(err) + } + if sess.RemoteURL != "" { + t.Errorf("RemoteURL = %q, want empty", sess.RemoteURL) + } +} From 2dc0fee6b8455c5f8c403e1588ddefcf5cf0310e Mon Sep 17 00:00:00 2001 From: mirchaemanuel Date: Sat, 28 Mar 2026 19:12:45 +0100 Subject: [PATCH 3/9] feat: expose remote_url in REST API session detail --- internal/api/server.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/api/server.go b/internal/api/server.go index c615340..e9f098c 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -375,6 +375,7 @@ type SessionFull struct { DesktopTitle string `json:"desktop_title,omitempty"` DesktopID string `json:"desktop_id,omitempty"` PermissionMode string `json:"permission_mode,omitempty"` + RemoteURL string `json:"remote_url,omitempty"` } // ToolItem represents a recent tool call. @@ -464,6 +465,7 @@ func (s *Server) buildSessionFull(detail *core.SessionDetailView) SessionFull { DesktopTitle: desktopTitle(sess), DesktopID: desktopID(sess), PermissionMode: desktopPermission(sess), + RemoteURL: sess.RemoteURL, } } From d1cd1cc128c1345d82e8edc10cdc4e5099a74aa0 Mon Sep 17 00:00:00 2001 From: mirchaemanuel Date: Sat, 28 Mar 2026 19:12:51 +0100 Subject: [PATCH 4/9] feat: expose remoteUrl in Wails GUI bindings --- internal/tray/service.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/tray/service.go b/internal/tray/service.go index 1a0be2c..2a9e4d6 100644 --- a/internal/tray/service.go +++ b/internal/tray/service.go @@ -152,6 +152,7 @@ type SessionFull struct { DesktopTitle string `json:"desktopTitle,omitempty"` DesktopID string `json:"desktopId,omitempty"` PermissionMode string `json:"permissionMode,omitempty"` + RemoteURL string `json:"remoteUrl,omitempty"` } // ToolItem is a tool call for the detail view. @@ -253,6 +254,7 @@ func (s *SessionService) GetSessionDetail(id string) *SessionFull { full.DesktopID = sess.Desktop.DesktopID full.PermissionMode = sess.Desktop.PermissionMode } + full.RemoteURL = sess.RemoteURL return full } From c1fd7874c06c58e154ddd1eb872595260385075b Mon Sep 17 00:00:00 2001 From: mirchaemanuel Date: Sat, 28 Mar 2026 19:13:10 +0100 Subject: [PATCH 5/9] feat: display remote control link in GUI session detail --- frontend/src/lib/SessionDetail.svelte | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frontend/src/lib/SessionDetail.svelte b/frontend/src/lib/SessionDetail.svelte index 8e1bc2a..a76610e 100644 --- a/frontend/src/lib/SessionDetail.svelte +++ b/frontend/src/lib/SessionDetail.svelte @@ -167,6 +167,14 @@
Worktree
yes {#if detail.mainRepo}({detail.mainRepo}){/if}
{/if} + {#if detail.remoteUrl} +
Remote
+
+ + {detail.remoteUrl} + +
+ {/if} {#if detail.lastFileWrite}
Last file
From ef33bab99efa965cba8d655e48609a2cdfe29722 Mon Sep 17 00:00:00 2001 From: mirchaemanuel Date: Sat, 28 Mar 2026 19:13:30 +0100 Subject: [PATCH 6/9] feat: display remote control URL in TUI session detail --- internal/ui/app.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/ui/app.go b/internal/ui/app.go index 81c7c5e..5ef59b6 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -934,6 +934,9 @@ func (m Model) buildDetailLines(s *model.Session, width int) []string { if s.GitBranch != "" && s.GitBranch != "HEAD" { add(row("Git Branch", s.GitBranch)) } + if s.RemoteURL != "" { + add(row("Remote", lipgloss.NewStyle().Foreground(colorAccent).Render(s.RemoteURL))) + } wtStr := "no" if s.IsWorktree { From 07f361162e6a3a8db51322cf71247c9601a70316 Mon Sep 17 00:00:00 2001 From: mirchaemanuel Date: Sat, 28 Mar 2026 19:16:49 +0100 Subject: [PATCH 7/9] fix: add remoteUrl to TypeScript bindings --- .../github.com/illegalstudio/lazyagent/internal/tray/models.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/bindings/github.com/illegalstudio/lazyagent/internal/tray/models.ts b/frontend/src/bindings/github.com/illegalstudio/lazyagent/internal/tray/models.ts index eff21fd..599cce7 100644 --- a/frontend/src/bindings/github.com/illegalstudio/lazyagent/internal/tray/models.ts +++ b/frontend/src/bindings/github.com/illegalstudio/lazyagent/internal/tray/models.ts @@ -77,6 +77,7 @@ export class SessionFull { "desktopTitle"?: string; "desktopId"?: string; "permissionMode"?: string; + "remoteUrl"?: string; /** Creates a new SessionFull instance. */ constructor($$source: Partial = {}) { From 8d8ea8c8bf74f47c424242f23db34be4817f2586 Mon Sep 17 00:00:00 2001 From: mirchaemanuel Date: Sat, 28 Mar 2026 20:10:22 +0100 Subject: [PATCH 8/9] feat: copy remote URL to clipboard on detail panel click --- internal/ui/app.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/internal/ui/app.go b/internal/ui/app.go index 5ef59b6..eea4051 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -494,6 +494,17 @@ func (m *Model) handleMouse(msg tea.MouseMsg) { } } else { m.focus = 1 + // Copy remote URL to clipboard on detail panel click. + if m.cursor >= 0 && m.cursor < len(m.visible) { + if u := m.visible[m.cursor].RemoteURL; u != "" { + if cmd := exec.Command("pbcopy"); cmd != nil { + cmd.Stdin = strings.NewReader(u) + if cmd.Run() == nil { + m.flashMsg = "Remote URL copied!" + } + } + } + } } } } From 023bc5802d698af9a7d71e0e78169883f9751485 Mon Sep 17 00:00:00 2001 From: mirchaemanuel Date: Sat, 28 Mar 2026 20:14:52 +0100 Subject: [PATCH 9/9] fix: replace modal flash with inline "copied!" indicator for remote URL --- internal/ui/app.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/internal/ui/app.go b/internal/ui/app.go index eea4051..1815172 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -60,6 +60,9 @@ type Model struct { // Flash message (modal popup, dismissed by any key) flashMsg string + // Inline "copied!" indicator for remote URL + copiedAt time.Time + // Update notification shown in footer updateVersion string @@ -500,7 +503,7 @@ func (m *Model) handleMouse(msg tea.MouseMsg) { if cmd := exec.Command("pbcopy"); cmd != nil { cmd.Stdin = strings.NewReader(u) if cmd.Run() == nil { - m.flashMsg = "Remote URL copied!" + m.copiedAt = time.Now() } } } @@ -946,7 +949,11 @@ func (m Model) buildDetailLines(s *model.Session, width int) []string { add(row("Git Branch", s.GitBranch)) } if s.RemoteURL != "" { - add(row("Remote", lipgloss.NewStyle().Foreground(colorAccent).Render(s.RemoteURL))) + remoteVal := lipgloss.NewStyle().Foreground(colorAccent).Render(s.RemoteURL) + if time.Since(m.copiedAt) < 2*time.Second { + remoteVal += lipgloss.NewStyle().Foreground(colorMuted).Render(" copied!") + } + add(row("Remote", remoteVal)) } wtStr := "no"