@wangyaoshen/remux 0.3.8-dev.bab6c95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +47 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +38 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +28 -0
- package/.github/dependabot.yml +33 -0
- package/.github/workflows/ci.yml +65 -0
- package/.github/workflows/deploy.yml +65 -0
- package/.github/workflows/publish.yml +138 -0
- package/.github/workflows/release-please.yml +21 -0
- package/.gitmodules +3 -0
- package/.nvmrc +1 -0
- package/.release-please-manifest.json +3 -0
- package/CLAUDE.md +104 -0
- package/Dockerfile +23 -0
- package/LICENSE +21 -0
- package/README.md +120 -0
- package/apps/ios/Config/signing.xcconfig +4 -0
- package/apps/ios/Package.swift +26 -0
- package/apps/ios/Remux.xcodeproj/project.pbxproj +456 -0
- package/apps/ios/Remux.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/apps/ios/Sources/Remux/Extensions/FaceIDManager.swift +29 -0
- package/apps/ios/Sources/Remux/Extensions/InspectCache.swift +66 -0
- package/apps/ios/Sources/Remux/MainTabView.swift +32 -0
- package/apps/ios/Sources/Remux/Remux.entitlements +8 -0
- package/apps/ios/Sources/Remux/RemuxiOSApp.swift +14 -0
- package/apps/ios/Sources/Remux/RootView.swift +130 -0
- package/apps/ios/Sources/Remux/Views/Control/ControlView.swift +102 -0
- package/apps/ios/Sources/Remux/Views/Inspect/InspectView.swift +98 -0
- package/apps/ios/Sources/Remux/Views/Live/LiveTerminalView.swift +132 -0
- package/apps/ios/Sources/Remux/Views/Now/NowView.swift +173 -0
- package/apps/ios/Sources/Remux/Views/Onboarding/ManualConnectView.swift +55 -0
- package/apps/ios/Sources/Remux/Views/Onboarding/OnboardingView.swift +70 -0
- package/apps/ios/Sources/Remux/Views/Onboarding/QRScannerView.swift +92 -0
- package/apps/ios/Sources/Remux/Views/Settings/MeView.swift +136 -0
- package/apps/macos/Package.swift +37 -0
- package/apps/macos/Resources/shell-integration/bash/bash-preexec.sh +382 -0
- package/apps/macos/Resources/shell-integration/bash/ghostty.bash +315 -0
- package/apps/macos/Resources/shell-integration/elvish/lib/ghostty-integration.elv +191 -0
- package/apps/macos/Resources/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish +246 -0
- package/apps/macos/Resources/shell-integration/nushell/vendor/autoload/ghostty.nu +110 -0
- package/apps/macos/Resources/shell-integration/zsh/.zshenv +61 -0
- package/apps/macos/Resources/shell-integration/zsh/ghostty-integration +458 -0
- package/apps/macos/Resources/terminfo/67/ghostty +0 -0
- package/apps/macos/Resources/terminfo/78/xterm-ghostty +0 -0
- package/apps/macos/Sources/Remux/AppDelegate.swift +257 -0
- package/apps/macos/Sources/Remux/CrashReporter.swift +210 -0
- package/apps/macos/Sources/Remux/FinderIntegration.swift +117 -0
- package/apps/macos/Sources/Remux/GhosttyConfig.swift +311 -0
- package/apps/macos/Sources/Remux/KeyboardShortcuts/ShortcutAction.swift +115 -0
- package/apps/macos/Sources/Remux/KeyboardShortcuts/ShortcutSettingsView.swift +271 -0
- package/apps/macos/Sources/Remux/KeyboardShortcuts/StoredShortcut.swift +149 -0
- package/apps/macos/Sources/Remux/MainContentView.swift +308 -0
- package/apps/macos/Sources/Remux/MenuBarManager.swift +275 -0
- package/apps/macos/Sources/Remux/NotificationManager.swift +145 -0
- package/apps/macos/Sources/Remux/PortScanner.swift +152 -0
- package/apps/macos/Sources/Remux/RemuxApp.swift +13 -0
- package/apps/macos/Sources/Remux/SSHDetector.swift +151 -0
- package/apps/macos/Sources/Remux/SessionPersistence.swift +226 -0
- package/apps/macos/Sources/Remux/SocketController.swift +258 -0
- package/apps/macos/Sources/Remux/UpdateChecker.swift +152 -0
- package/apps/macos/Sources/Remux/Views/CommandPalette.swift +198 -0
- package/apps/macos/Sources/Remux/Views/ConnectionView.swift +84 -0
- package/apps/macos/Sources/Remux/Views/InspectView.swift +127 -0
- package/apps/macos/Sources/Remux/Views/SettingsView.swift +77 -0
- package/apps/macos/Sources/Remux/Views/Sidebar/SidebarView.swift +410 -0
- package/apps/macos/Sources/Remux/Views/SplitTree/BrowserPanel.swift +193 -0
- package/apps/macos/Sources/Remux/Views/SplitTree/MarkdownPanel.swift +277 -0
- package/apps/macos/Sources/Remux/Views/SplitTree/PanelProtocol.swift +14 -0
- package/apps/macos/Sources/Remux/Views/SplitTree/SplitNode.swift +149 -0
- package/apps/macos/Sources/Remux/Views/SplitTree/SplitView.swift +234 -0
- package/apps/macos/Sources/Remux/Views/SplitTree/TerminalPanel.swift +26 -0
- package/apps/macos/Sources/Remux/Views/TabBarView.swift +94 -0
- package/apps/macos/Sources/Remux/Views/Terminal/ClipboardHelper.swift +101 -0
- package/apps/macos/Sources/Remux/Views/Terminal/CopyModeOverlay.swift +325 -0
- package/apps/macos/Sources/Remux/Views/Terminal/GhosttyNativeTerminalView.swift +39 -0
- package/apps/macos/Sources/Remux/Views/Terminal/GhosttyNativeView.swift +559 -0
- package/apps/macos/Sources/Remux/Views/Terminal/SurfaceSearchOverlay.swift +109 -0
- package/apps/macos/Sources/Remux/Views/Terminal/TerminalContainerView.swift +95 -0
- package/apps/macos/Sources/Remux/Views/Terminal/TerminalRelay.swift +117 -0
- package/build.mjs +33 -0
- package/native/android/DecodeGoldenPayloads.kt +487 -0
- package/native/android/ProtocolModels.kt +188 -0
- package/native/ios/DecodeGoldenPayloads.swift +711 -0
- package/native/ios/ProtocolModels.swift +200 -0
- package/package.json +45 -0
- package/packages/RemuxKit/Package.swift +27 -0
- package/packages/RemuxKit/Sources/RemuxKit/Device/DeviceManager.swift +27 -0
- package/packages/RemuxKit/Sources/RemuxKit/Models/ProtocolModels.swift +206 -0
- package/packages/RemuxKit/Sources/RemuxKit/Networking/MessageRouter.swift +108 -0
- package/packages/RemuxKit/Sources/RemuxKit/Networking/RemuxConnection.swift +395 -0
- package/packages/RemuxKit/Sources/RemuxKit/State/RemuxState.swift +188 -0
- package/packages/RemuxKit/Sources/RemuxKit/Storage/KeychainStore.swift +142 -0
- package/packages/RemuxKit/Sources/RemuxKit/Terminal/GhosttyBridge.swift +145 -0
- package/packages/RemuxKit/Sources/RemuxKit/Terminal/GhosttyTerminalView.swift +35 -0
- package/packages/RemuxKit/Sources/RemuxKit/Terminal/Resources/ghostty-terminal.html +91 -0
- package/packages/RemuxKit/Tests/RemuxKitTests/ConnectionIntegrationTest.swift +74 -0
- package/packages/RemuxKit/Tests/RemuxKitTests/KeychainStoreTests.swift +81 -0
- package/packages/RemuxKit/Tests/RemuxKitTests/ProtocolModelsTests.swift +179 -0
- package/packages/RemuxKit/Tests/RemuxKitTests/RemuxStateTests.swift +62 -0
- package/playwright.config.ts +17 -0
- package/pnpm-lock.yaml +1588 -0
- package/pty-daemon.js +303 -0
- package/release-please-config.json +14 -0
- package/scripts/auto-deploy.sh +46 -0
- package/scripts/build-dmg.sh +121 -0
- package/scripts/build-ghostty-kit.sh +43 -0
- package/scripts/check-active-terminology.mjs +132 -0
- package/scripts/sync-ghostty-web.sh +28 -0
- package/server.js +7074 -0
- package/src/adapters/agent-events.ts +246 -0
- package/src/adapters/claude-code.ts +158 -0
- package/src/adapters/codex.ts +210 -0
- package/src/adapters/generic-shell.ts +58 -0
- package/src/adapters/index.ts +15 -0
- package/src/adapters/registry.ts +99 -0
- package/src/adapters/types.ts +41 -0
- package/src/auth.ts +174 -0
- package/src/e2ee.ts +236 -0
- package/src/git-service.ts +168 -0
- package/src/message-buffer.ts +137 -0
- package/src/pty-daemon.ts +357 -0
- package/src/push.ts +127 -0
- package/src/renderers.ts +455 -0
- package/src/server.ts +2407 -0
- package/src/service.ts +226 -0
- package/src/session.ts +978 -0
- package/src/store.ts +1422 -0
- package/src/team.ts +123 -0
- package/src/tunnel.ts +126 -0
- package/src/types.d.ts +50 -0
- package/src/vt-tracker.ts +188 -0
- package/src/workspace-head.ts +144 -0
- package/src/workspace.ts +153 -0
- package/src/ws-handler.ts +1526 -0
- package/start.ps1 +83 -0
- package/tests/adapters.test.js +171 -0
- package/tests/auth.test.js +243 -0
- package/tests/codex-adapter.test.js +535 -0
- package/tests/durable-stream.test.js +153 -0
- package/tests/e2e/app.spec.js +530 -0
- package/tests/e2ee.test.js +325 -0
- package/tests/message-buffer.test.js +245 -0
- package/tests/message-routing.test.js +305 -0
- package/tests/pty-daemon.test.js +346 -0
- package/tests/push.test.js +281 -0
- package/tests/renderers.test.js +391 -0
- package/tests/search-shell.test.js +499 -0
- package/tests/server.test.js +882 -0
- package/tests/service.test.js +267 -0
- package/tests/store.test.js +369 -0
- package/tests/tunnel.test.js +67 -0
- package/tests/workspace-head.test.js +116 -0
- package/tests/workspace.test.js +417 -0
- package/tsconfig.backend.json +11 -0
- package/tsconfig.json +15 -0
- package/tui/client/client_test.go +125 -0
- package/tui/client/connection.go +342 -0
- package/tui/client/host_manager.go +141 -0
- package/tui/config/cache.go +81 -0
- package/tui/config/config.go +53 -0
- package/tui/config/config_test.go +89 -0
- package/tui/go.mod +32 -0
- package/tui/go.sum +50 -0
- package/tui/main.go +261 -0
- package/tui/tests/integration_test.go +283 -0
- package/tui/ui/model.go +310 -0
- package/vitest.config.js +10 -0
package/tui/ui/model.go
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
// Package tui implements the terminal user interface using bubbletea.
|
|
2
|
+
package tui
|
|
3
|
+
|
|
4
|
+
import (
|
|
5
|
+
"fmt"
|
|
6
|
+
"strings"
|
|
7
|
+
|
|
8
|
+
tea "github.com/charmbracelet/bubbletea"
|
|
9
|
+
"github.com/charmbracelet/lipgloss"
|
|
10
|
+
"github.com/eisber/remux/tui/client"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
// View mode for the TUI.
|
|
14
|
+
type viewMode int
|
|
15
|
+
|
|
16
|
+
const (
|
|
17
|
+
viewTerminal viewMode = iota
|
|
18
|
+
viewSessionPicker
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
// Model is the bubbletea model for the remux TUI.
|
|
22
|
+
type Model struct {
|
|
23
|
+
manager *client.HostManager
|
|
24
|
+
activeHost string
|
|
25
|
+
activeSession string
|
|
26
|
+
mode viewMode
|
|
27
|
+
width, height int
|
|
28
|
+
|
|
29
|
+
// Terminal output buffer (raw bytes from the remote PTY).
|
|
30
|
+
terminalOutput strings.Builder
|
|
31
|
+
|
|
32
|
+
// Session picker state.
|
|
33
|
+
pickerCursor int
|
|
34
|
+
|
|
35
|
+
// Status messages.
|
|
36
|
+
statusMsg string
|
|
37
|
+
errMsg string
|
|
38
|
+
|
|
39
|
+
// Whether we've received terminal data.
|
|
40
|
+
connected bool
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// terminalDataMsg is sent when terminal output arrives from the server.
|
|
44
|
+
type terminalDataMsg struct {
|
|
45
|
+
data []byte
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// stateUpdateMsg is sent when the session list changes.
|
|
49
|
+
type stateUpdateMsg struct{}
|
|
50
|
+
|
|
51
|
+
// attachedMsg is sent when we've attached to a session.
|
|
52
|
+
type attachedMsg struct {
|
|
53
|
+
session string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// errMsg is sent on connection errors.
|
|
57
|
+
type connectionErrMsg struct {
|
|
58
|
+
err error
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// NewModel creates a new TUI model.
|
|
62
|
+
func NewModel(manager *client.HostManager) Model {
|
|
63
|
+
return Model{
|
|
64
|
+
manager: manager,
|
|
65
|
+
mode: viewTerminal,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Init implements tea.Model.
|
|
70
|
+
func (m Model) Init() tea.Cmd {
|
|
71
|
+
return tea.SetWindowTitle("remux")
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Update implements tea.Model.
|
|
75
|
+
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
76
|
+
switch msg := msg.(type) {
|
|
77
|
+
case tea.KeyMsg:
|
|
78
|
+
return m.handleKey(msg)
|
|
79
|
+
|
|
80
|
+
case tea.WindowSizeMsg:
|
|
81
|
+
m.width = msg.Width
|
|
82
|
+
m.height = msg.Height
|
|
83
|
+
// Resize the active terminal session.
|
|
84
|
+
if m.activeHost != "" {
|
|
85
|
+
conn := m.manager.GetConnection(m.activeHost)
|
|
86
|
+
if conn != nil {
|
|
87
|
+
_ = conn.SendResize(msg.Width, msg.Height-2) // Reserve 2 lines for status bar
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return m, nil
|
|
91
|
+
|
|
92
|
+
case terminalDataMsg:
|
|
93
|
+
m.terminalOutput.Write(msg.data)
|
|
94
|
+
m.connected = true
|
|
95
|
+
return m, nil
|
|
96
|
+
|
|
97
|
+
case stateUpdateMsg:
|
|
98
|
+
return m, nil
|
|
99
|
+
|
|
100
|
+
case attachedMsg:
|
|
101
|
+
m.activeSession = msg.session
|
|
102
|
+
m.statusMsg = fmt.Sprintf("attached to %s", msg.session)
|
|
103
|
+
m.mode = viewTerminal
|
|
104
|
+
return m, nil
|
|
105
|
+
|
|
106
|
+
case connectionErrMsg:
|
|
107
|
+
m.errMsg = msg.err.Error()
|
|
108
|
+
return m, nil
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return m, nil
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
func (m Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|
115
|
+
switch m.mode {
|
|
116
|
+
case viewTerminal:
|
|
117
|
+
return m.handleTerminalKey(msg)
|
|
118
|
+
case viewSessionPicker:
|
|
119
|
+
return m.handlePickerKey(msg)
|
|
120
|
+
}
|
|
121
|
+
return m, nil
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
func (m Model) handleTerminalKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|
125
|
+
switch msg.String() {
|
|
126
|
+
case "ctrl+o":
|
|
127
|
+
// Open session picker.
|
|
128
|
+
m.mode = viewSessionPicker
|
|
129
|
+
m.pickerCursor = 0
|
|
130
|
+
return m, nil
|
|
131
|
+
|
|
132
|
+
case "ctrl+d":
|
|
133
|
+
// Detach — quit the TUI (sessions keep running).
|
|
134
|
+
return m, tea.Quit
|
|
135
|
+
|
|
136
|
+
case "ctrl+c":
|
|
137
|
+
return m, tea.Quit
|
|
138
|
+
|
|
139
|
+
default:
|
|
140
|
+
// Forward all other input to the active terminal.
|
|
141
|
+
if m.activeHost != "" {
|
|
142
|
+
conn := m.manager.GetConnection(m.activeHost)
|
|
143
|
+
if conn != nil {
|
|
144
|
+
_ = conn.SendInput(msg.String())
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return m, nil
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
func (m Model) handlePickerKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|
152
|
+
sessions := m.manager.Sessions()
|
|
153
|
+
|
|
154
|
+
switch msg.String() {
|
|
155
|
+
case "up", "k":
|
|
156
|
+
if m.pickerCursor > 0 {
|
|
157
|
+
m.pickerCursor--
|
|
158
|
+
}
|
|
159
|
+
case "down", "j":
|
|
160
|
+
if m.pickerCursor < len(sessions)-1 {
|
|
161
|
+
m.pickerCursor++
|
|
162
|
+
}
|
|
163
|
+
case "enter":
|
|
164
|
+
if m.pickerCursor < len(sessions) {
|
|
165
|
+
selected := sessions[m.pickerCursor]
|
|
166
|
+
m.activeHost = selected.HostName
|
|
167
|
+
conn := m.manager.GetConnection(selected.HostName)
|
|
168
|
+
if conn != nil {
|
|
169
|
+
_ = conn.SelectSession(selected.Name)
|
|
170
|
+
}
|
|
171
|
+
m.mode = viewTerminal
|
|
172
|
+
m.terminalOutput.Reset()
|
|
173
|
+
m.statusMsg = fmt.Sprintf("switching to %s/%s...", selected.HostName, selected.Name)
|
|
174
|
+
}
|
|
175
|
+
case "escape", "ctrl+o":
|
|
176
|
+
m.mode = viewTerminal
|
|
177
|
+
case "ctrl+c", "ctrl+d":
|
|
178
|
+
return m, tea.Quit
|
|
179
|
+
}
|
|
180
|
+
return m, nil
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// View implements tea.Model.
|
|
184
|
+
func (m Model) View() string {
|
|
185
|
+
if m.width == 0 {
|
|
186
|
+
return "initializing..."
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
switch m.mode {
|
|
190
|
+
case viewSessionPicker:
|
|
191
|
+
return m.renderPicker()
|
|
192
|
+
default:
|
|
193
|
+
return m.renderTerminal()
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
func (m Model) renderTerminal() string {
|
|
198
|
+
// Terminal content fills the screen minus the status bar.
|
|
199
|
+
contentHeight := m.height - 2
|
|
200
|
+
if contentHeight < 1 {
|
|
201
|
+
contentHeight = 1
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Get terminal output and take the last N lines.
|
|
205
|
+
output := m.terminalOutput.String()
|
|
206
|
+
lines := strings.Split(output, "\n")
|
|
207
|
+
if len(lines) > contentHeight {
|
|
208
|
+
lines = lines[len(lines)-contentHeight:]
|
|
209
|
+
}
|
|
210
|
+
content := strings.Join(lines, "\n")
|
|
211
|
+
|
|
212
|
+
// Status bar.
|
|
213
|
+
statusStyle := lipgloss.NewStyle().
|
|
214
|
+
Background(lipgloss.Color("236")).
|
|
215
|
+
Foreground(lipgloss.Color("252")).
|
|
216
|
+
Width(m.width).
|
|
217
|
+
Padding(0, 1)
|
|
218
|
+
|
|
219
|
+
var statusLeft string
|
|
220
|
+
if m.activeHost != "" && m.activeSession != "" {
|
|
221
|
+
statusLeft = fmt.Sprintf("⬤ %s/%s", m.activeHost, m.activeSession)
|
|
222
|
+
} else if m.errMsg != "" {
|
|
223
|
+
statusLeft = fmt.Sprintf("✗ %s", m.errMsg)
|
|
224
|
+
} else {
|
|
225
|
+
statusLeft = m.statusMsg
|
|
226
|
+
}
|
|
227
|
+
statusRight := "Ctrl-O: sessions │ Ctrl-D: detach"
|
|
228
|
+
|
|
229
|
+
padding := m.width - lipgloss.Width(statusLeft) - lipgloss.Width(statusRight) - 2
|
|
230
|
+
if padding < 1 {
|
|
231
|
+
padding = 1
|
|
232
|
+
}
|
|
233
|
+
statusBar := statusStyle.Render(statusLeft + strings.Repeat(" ", padding) + statusRight)
|
|
234
|
+
|
|
235
|
+
return content + "\n" + statusBar
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
func (m Model) renderPicker() string {
|
|
239
|
+
sessions := m.manager.Sessions()
|
|
240
|
+
|
|
241
|
+
titleStyle := lipgloss.NewStyle().
|
|
242
|
+
Bold(true).
|
|
243
|
+
Foreground(lipgloss.Color("212")).
|
|
244
|
+
Padding(1, 2)
|
|
245
|
+
|
|
246
|
+
itemStyle := lipgloss.NewStyle().Padding(0, 2)
|
|
247
|
+
selectedStyle := itemStyle.Background(lipgloss.Color("236")).Foreground(lipgloss.Color("212"))
|
|
248
|
+
|
|
249
|
+
var b strings.Builder
|
|
250
|
+
b.WriteString(titleStyle.Render("Sessions (↑/↓ select, Enter attach, Esc cancel)"))
|
|
251
|
+
b.WriteString("\n\n")
|
|
252
|
+
|
|
253
|
+
if len(sessions) == 0 {
|
|
254
|
+
b.WriteString(itemStyle.Render(" No sessions available"))
|
|
255
|
+
return b.String()
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
currentHost := ""
|
|
259
|
+
for i, s := range sessions {
|
|
260
|
+
// Group header for host.
|
|
261
|
+
if s.HostName != currentHost {
|
|
262
|
+
currentHost = s.HostName
|
|
263
|
+
headerStyle := lipgloss.NewStyle().
|
|
264
|
+
Bold(true).
|
|
265
|
+
Foreground(lipgloss.Color("245")).
|
|
266
|
+
Padding(0, 2)
|
|
267
|
+
b.WriteString(headerStyle.Render(fmt.Sprintf("── %s ──", currentHost)))
|
|
268
|
+
b.WriteString("\n")
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
prefix := " "
|
|
272
|
+
style := itemStyle
|
|
273
|
+
if i == m.pickerCursor {
|
|
274
|
+
prefix = "▸ "
|
|
275
|
+
style = selectedStyle
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
attached := ""
|
|
279
|
+
if s.Attached {
|
|
280
|
+
attached = " (attached)"
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
line := fmt.Sprintf("%s%s%s", prefix, s.Name, attached)
|
|
284
|
+
b.WriteString(style.Render(line))
|
|
285
|
+
b.WriteString("\n")
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return b.String()
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// SetupCallbacks wires the HostManager callbacks to bubbletea messages.
|
|
292
|
+
func SetupCallbacks(manager *client.HostManager, p *tea.Program) {
|
|
293
|
+
manager.OnChange(func() {
|
|
294
|
+
p.Send(stateUpdateMsg{})
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// SetupConnectionCallbacks wires a Connection's callbacks to bubbletea messages.
|
|
299
|
+
func SetupConnectionCallbacks(conn *client.Connection, p *tea.Program) {
|
|
300
|
+
conn.OnTerminalData(func(data []byte) {
|
|
301
|
+
p.Send(terminalDataMsg{data: data})
|
|
302
|
+
})
|
|
303
|
+
conn.OnAttached(func(session string) {
|
|
304
|
+
p.Send(attachedMsg{session: session})
|
|
305
|
+
})
|
|
306
|
+
conn.OnError(func(err error) {
|
|
307
|
+
p.Send(connectionErrMsg{err: err})
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
|
package/vitest.config.js
ADDED