@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
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
import RemuxKit
|
|
3
|
+
|
|
4
|
+
/// Container connecting GhosttyNativeTerminalView to RemuxState via TerminalRelay.
|
|
5
|
+
/// Data flow:
|
|
6
|
+
/// Remote PTY -> WebSocket -> RemuxState -> relay.writeToTerminal() -> socket -> nc stdout -> ghostty Metal render
|
|
7
|
+
/// User types -> ghostty -> nc stdin -> socket -> relay.onDataFromClient -> state.sendTerminalData() -> WebSocket -> remote PTY
|
|
8
|
+
struct TerminalContainerView: View {
|
|
9
|
+
@Environment(RemuxState.self) private var state
|
|
10
|
+
@State private var terminalView: GhosttyNativeView?
|
|
11
|
+
@State private var relay = TerminalRelay()
|
|
12
|
+
|
|
13
|
+
// Search state
|
|
14
|
+
@State private var searchVisible = false
|
|
15
|
+
@State private var searchText = ""
|
|
16
|
+
@State private var searchTotal = 0
|
|
17
|
+
@State private var searchSelected = -1
|
|
18
|
+
|
|
19
|
+
var body: some View {
|
|
20
|
+
ZStack(alignment: .topTrailing) {
|
|
21
|
+
GhosttyNativeTerminalView(
|
|
22
|
+
socketPath: relay.socketPath,
|
|
23
|
+
viewRef: $terminalView,
|
|
24
|
+
onResize: { cols, rows in
|
|
25
|
+
state.sendJSON(["type": "resize", "cols": cols, "rows": rows])
|
|
26
|
+
},
|
|
27
|
+
onBell: {},
|
|
28
|
+
onTitle: { title in
|
|
29
|
+
NSApp.mainWindow?.title = "Remux — \(title)"
|
|
30
|
+
},
|
|
31
|
+
onSearchStart: { needle in
|
|
32
|
+
searchVisible = true
|
|
33
|
+
if let needle, !needle.isEmpty {
|
|
34
|
+
searchText = needle
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
onSearchEnd: {
|
|
38
|
+
searchVisible = false
|
|
39
|
+
searchText = ""
|
|
40
|
+
searchTotal = 0
|
|
41
|
+
searchSelected = -1
|
|
42
|
+
},
|
|
43
|
+
onSearchTotal: { total in
|
|
44
|
+
searchTotal = total
|
|
45
|
+
},
|
|
46
|
+
onSearchSelected: { selected in
|
|
47
|
+
searchSelected = selected
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
SurfaceSearchOverlay(
|
|
52
|
+
isVisible: $searchVisible,
|
|
53
|
+
searchText: $searchText,
|
|
54
|
+
totalMatches: $searchTotal,
|
|
55
|
+
selectedMatch: $searchSelected,
|
|
56
|
+
onSearch: { query in
|
|
57
|
+
guard let view = terminalView else { return }
|
|
58
|
+
if query.isEmpty {
|
|
59
|
+
searchTotal = 0
|
|
60
|
+
searchSelected = -1
|
|
61
|
+
}
|
|
62
|
+
// Ghostty search is triggered by typing into the search bar,
|
|
63
|
+
// which sends the text via binding_action
|
|
64
|
+
let action = "search:\(query)"
|
|
65
|
+
view.sendText("") // ensure surface has focus
|
|
66
|
+
_ = action // search is driven by the overlay text field
|
|
67
|
+
},
|
|
68
|
+
onNext: {
|
|
69
|
+
terminalView?.searchForward()
|
|
70
|
+
},
|
|
71
|
+
onPrevious: {
|
|
72
|
+
terminalView?.searchBackward()
|
|
73
|
+
},
|
|
74
|
+
onClose: {
|
|
75
|
+
searchTotal = 0
|
|
76
|
+
searchSelected = -1
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
.onReceive(NotificationCenter.default.publisher(for: .remuxTerminalData)) { notification in
|
|
81
|
+
if let data = notification.userInfo?["data"] as? Data {
|
|
82
|
+
relay.writeToTerminal(data)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
.onAppear {
|
|
86
|
+
relay.onDataFromClient = { data in
|
|
87
|
+
state.sendTerminalData(data)
|
|
88
|
+
}
|
|
89
|
+
relay.start()
|
|
90
|
+
}
|
|
91
|
+
.onDisappear {
|
|
92
|
+
relay.stop()
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Bidirectional relay between a Unix domain socket and the WebSocket connection.
|
|
4
|
+
/// Ghostty spawns `nc -U <socketPath>` as its "shell" process.
|
|
5
|
+
/// Data flow:
|
|
6
|
+
/// Remote PTY → WebSocket → onDataFromServer → socket → nc stdout → ghostty renders
|
|
7
|
+
/// User types → ghostty → nc stdin → socket → onDataFromClient → WebSocket → remote PTY
|
|
8
|
+
@MainActor
|
|
9
|
+
final class TerminalRelay {
|
|
10
|
+
|
|
11
|
+
let socketPath: String
|
|
12
|
+
var onDataFromClient: ((Data) -> Void)? // user keystrokes → send to WebSocket
|
|
13
|
+
|
|
14
|
+
private var serverSocket: Int32 = -1
|
|
15
|
+
private var clientSocket: Int32 = -1
|
|
16
|
+
private var readSource: DispatchSourceRead?
|
|
17
|
+
private let queue = DispatchQueue(label: "remux.terminal-relay")
|
|
18
|
+
|
|
19
|
+
init() {
|
|
20
|
+
socketPath = NSTemporaryDirectory() + "remux-relay-\(ProcessInfo.processInfo.processIdentifier)-\(UUID().uuidString.prefix(8)).sock"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
deinit {
|
|
24
|
+
stop()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Start listening on the Unix socket. Call before creating the ghostty surface.
|
|
28
|
+
func start() {
|
|
29
|
+
// Create Unix domain socket
|
|
30
|
+
serverSocket = socket(AF_UNIX, SOCK_STREAM, 0)
|
|
31
|
+
guard serverSocket >= 0 else { return }
|
|
32
|
+
|
|
33
|
+
// Remove stale socket file
|
|
34
|
+
unlink(socketPath)
|
|
35
|
+
|
|
36
|
+
var addr = sockaddr_un()
|
|
37
|
+
addr.sun_family = sa_family_t(AF_UNIX)
|
|
38
|
+
let pathBytes = socketPath.utf8CString
|
|
39
|
+
pathBytes.withUnsafeBufferPointer { buf in
|
|
40
|
+
withUnsafeMutableBytes(of: &addr.sun_path) { rawPath in
|
|
41
|
+
let count = min(buf.count, rawPath.count)
|
|
42
|
+
rawPath.copyBytes(from: UnsafeRawBufferPointer(buf).prefix(count))
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let bindResult = withUnsafePointer(to: &addr) { ptr in
|
|
47
|
+
ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockPtr in
|
|
48
|
+
Darwin.bind(serverSocket, sockPtr, socklen_t(MemoryLayout<sockaddr_un>.size))
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
guard bindResult == 0 else {
|
|
52
|
+
close(serverSocket); serverSocket = -1; return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
listen(serverSocket, 1)
|
|
56
|
+
|
|
57
|
+
// Accept connection async (nc will connect when ghostty spawns)
|
|
58
|
+
let fd = serverSocket
|
|
59
|
+
queue.async { [weak self] in
|
|
60
|
+
let client = accept(fd, nil, nil)
|
|
61
|
+
guard client >= 0 else { return }
|
|
62
|
+
DispatchQueue.main.async {
|
|
63
|
+
self?.clientSocket = client
|
|
64
|
+
self?.startReading()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Write data to the socket → nc → ghostty PTY stdout → rendered on screen.
|
|
70
|
+
func writeToTerminal(_ data: Data) {
|
|
71
|
+
guard clientSocket >= 0 else { return }
|
|
72
|
+
data.withUnsafeBytes { ptr in
|
|
73
|
+
guard let base = ptr.baseAddress else { return }
|
|
74
|
+
_ = send(clientSocket, base, ptr.count, 0)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// Stop the relay and clean up.
|
|
79
|
+
nonisolated func stop() {
|
|
80
|
+
MainActor.assumeIsolated {
|
|
81
|
+
readSource?.cancel()
|
|
82
|
+
readSource = nil
|
|
83
|
+
if clientSocket >= 0 { close(clientSocket); clientSocket = -1 }
|
|
84
|
+
if serverSocket >= 0 { close(serverSocket); serverSocket = -1 }
|
|
85
|
+
unlink(socketPath)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// MARK: - Private
|
|
90
|
+
|
|
91
|
+
private func startReading() {
|
|
92
|
+
guard clientSocket >= 0 else { return }
|
|
93
|
+
let source = DispatchSource.makeReadSource(fileDescriptor: clientSocket, queue: queue)
|
|
94
|
+
source.setEventHandler { [weak self] in
|
|
95
|
+
guard let self else { return }
|
|
96
|
+
var buf = [UInt8](repeating: 0, count: 65536)
|
|
97
|
+
let n = recv(self.clientSocket, &buf, buf.count, 0)
|
|
98
|
+
if n > 0 {
|
|
99
|
+
let data = Data(buf[0..<n])
|
|
100
|
+
DispatchQueue.main.async {
|
|
101
|
+
self.onDataFromClient?(data)
|
|
102
|
+
}
|
|
103
|
+
} else if n == 0 {
|
|
104
|
+
// nc disconnected
|
|
105
|
+
source.cancel()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
source.setCancelHandler { [weak self] in
|
|
109
|
+
guard let self else { return }
|
|
110
|
+
DispatchQueue.main.async {
|
|
111
|
+
self.clientSocket = -1
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
source.resume()
|
|
115
|
+
readSource = source
|
|
116
|
+
}
|
|
117
|
+
}
|
package/build.mjs
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* esbuild script — bundles src/ TypeScript modules into output files.
|
|
3
|
+
* Two entry points:
|
|
4
|
+
* 1. server.ts → server.js (main server)
|
|
5
|
+
* 2. pty-daemon.ts → pty-daemon.js (independent PTY daemon process)
|
|
6
|
+
* Externalizes native/npm dependencies (node-pty, ws, ghostty-web, qrcode-terminal).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import esbuild from "esbuild";
|
|
10
|
+
|
|
11
|
+
const commonOptions = {
|
|
12
|
+
bundle: true,
|
|
13
|
+
platform: "node",
|
|
14
|
+
target: "node20",
|
|
15
|
+
format: "esm",
|
|
16
|
+
external: ["node-pty", "ws", "ghostty-web", "qrcode-terminal", "better-sqlite3", "web-push", "simple-git"],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Main server bundle
|
|
20
|
+
esbuild.buildSync({
|
|
21
|
+
...commonOptions,
|
|
22
|
+
entryPoints: ["src/server.ts"],
|
|
23
|
+
outfile: "server.js",
|
|
24
|
+
banner: { js: "#!/usr/bin/env node" },
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// PTY daemon bundle (independent process)
|
|
28
|
+
esbuild.buildSync({
|
|
29
|
+
...commonOptions,
|
|
30
|
+
entryPoints: ["src/pty-daemon.ts"],
|
|
31
|
+
outfile: "pty-daemon.js",
|
|
32
|
+
banner: { js: "#!/usr/bin/env node" },
|
|
33
|
+
});
|