@wangyaoshen/remux 0.3.8-dev.a8ceb0c

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.
Files changed (183) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +47 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +38 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +28 -0
  4. package/.github/dependabot.yml +33 -0
  5. package/.github/workflows/ci.yml +65 -0
  6. package/.github/workflows/deploy.yml +65 -0
  7. package/.github/workflows/publish.yml +312 -0
  8. package/.github/workflows/release-please.yml +21 -0
  9. package/.gitmodules +3 -0
  10. package/.nvmrc +1 -0
  11. package/.release-please-manifest.json +3 -0
  12. package/CLAUDE.md +104 -0
  13. package/Dockerfile +23 -0
  14. package/LICENSE +21 -0
  15. package/README.md +120 -0
  16. package/apps/ios/Config/signing.xcconfig +4 -0
  17. package/apps/ios/Package.swift +26 -0
  18. package/apps/ios/Remux.xcodeproj/project.pbxproj +477 -0
  19. package/apps/ios/Remux.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  20. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/Contents.json +23 -0
  21. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.png +0 -0
  22. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_120x120.png +0 -0
  23. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_152x152.png +0 -0
  24. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_167x167.png +0 -0
  25. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_180x180.png +0 -0
  26. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_20x20.png +0 -0
  27. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_29x29.png +0 -0
  28. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_40x40.png +0 -0
  29. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_58x58.png +0 -0
  30. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_60x60.png +0 -0
  31. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_76x76.png +0 -0
  32. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_80x80.png +0 -0
  33. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_87x87.png +0 -0
  34. package/apps/ios/Sources/Remux/Assets.xcassets/Contents.json +6 -0
  35. package/apps/ios/Sources/Remux/Extensions/FaceIDManager.swift +29 -0
  36. package/apps/ios/Sources/Remux/Extensions/InspectCache.swift +66 -0
  37. package/apps/ios/Sources/Remux/MainTabView.swift +32 -0
  38. package/apps/ios/Sources/Remux/Remux.entitlements +8 -0
  39. package/apps/ios/Sources/Remux/RemuxiOSApp.swift +14 -0
  40. package/apps/ios/Sources/Remux/RootView.swift +130 -0
  41. package/apps/ios/Sources/Remux/Views/Control/ControlView.swift +102 -0
  42. package/apps/ios/Sources/Remux/Views/Inspect/InspectView.swift +98 -0
  43. package/apps/ios/Sources/Remux/Views/Live/LiveTerminalView.swift +132 -0
  44. package/apps/ios/Sources/Remux/Views/Now/NowView.swift +173 -0
  45. package/apps/ios/Sources/Remux/Views/Onboarding/ManualConnectView.swift +55 -0
  46. package/apps/ios/Sources/Remux/Views/Onboarding/OnboardingView.swift +70 -0
  47. package/apps/ios/Sources/Remux/Views/Onboarding/QRScannerView.swift +92 -0
  48. package/apps/ios/Sources/Remux/Views/Settings/MeView.swift +136 -0
  49. package/apps/macos/Package.swift +37 -0
  50. package/apps/macos/Resources/shell-integration/bash/bash-preexec.sh +382 -0
  51. package/apps/macos/Resources/shell-integration/bash/ghostty.bash +315 -0
  52. package/apps/macos/Resources/shell-integration/elvish/lib/ghostty-integration.elv +191 -0
  53. package/apps/macos/Resources/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish +246 -0
  54. package/apps/macos/Resources/shell-integration/nushell/vendor/autoload/ghostty.nu +110 -0
  55. package/apps/macos/Resources/shell-integration/zsh/.zshenv +61 -0
  56. package/apps/macos/Resources/shell-integration/zsh/ghostty-integration +458 -0
  57. package/apps/macos/Resources/terminfo/67/ghostty +0 -0
  58. package/apps/macos/Resources/terminfo/78/xterm-ghostty +0 -0
  59. package/apps/macos/Sources/Remux/AppDelegate.swift +257 -0
  60. package/apps/macos/Sources/Remux/CrashReporter.swift +210 -0
  61. package/apps/macos/Sources/Remux/FinderIntegration.swift +117 -0
  62. package/apps/macos/Sources/Remux/GhosttyConfig.swift +311 -0
  63. package/apps/macos/Sources/Remux/KeyboardShortcuts/ShortcutAction.swift +115 -0
  64. package/apps/macos/Sources/Remux/KeyboardShortcuts/ShortcutSettingsView.swift +271 -0
  65. package/apps/macos/Sources/Remux/KeyboardShortcuts/StoredShortcut.swift +149 -0
  66. package/apps/macos/Sources/Remux/MainContentView.swift +308 -0
  67. package/apps/macos/Sources/Remux/MenuBarManager.swift +275 -0
  68. package/apps/macos/Sources/Remux/NotificationManager.swift +145 -0
  69. package/apps/macos/Sources/Remux/PortScanner.swift +152 -0
  70. package/apps/macos/Sources/Remux/RemuxApp.swift +13 -0
  71. package/apps/macos/Sources/Remux/SSHDetector.swift +151 -0
  72. package/apps/macos/Sources/Remux/SessionPersistence.swift +226 -0
  73. package/apps/macos/Sources/Remux/SocketController.swift +258 -0
  74. package/apps/macos/Sources/Remux/UpdateChecker.swift +152 -0
  75. package/apps/macos/Sources/Remux/Views/CommandPalette.swift +198 -0
  76. package/apps/macos/Sources/Remux/Views/ConnectionView.swift +84 -0
  77. package/apps/macos/Sources/Remux/Views/InspectView.swift +127 -0
  78. package/apps/macos/Sources/Remux/Views/SettingsView.swift +77 -0
  79. package/apps/macos/Sources/Remux/Views/Sidebar/SidebarView.swift +410 -0
  80. package/apps/macos/Sources/Remux/Views/SplitTree/BrowserPanel.swift +193 -0
  81. package/apps/macos/Sources/Remux/Views/SplitTree/MarkdownPanel.swift +277 -0
  82. package/apps/macos/Sources/Remux/Views/SplitTree/PanelProtocol.swift +14 -0
  83. package/apps/macos/Sources/Remux/Views/SplitTree/SplitNode.swift +149 -0
  84. package/apps/macos/Sources/Remux/Views/SplitTree/SplitView.swift +234 -0
  85. package/apps/macos/Sources/Remux/Views/SplitTree/TerminalPanel.swift +26 -0
  86. package/apps/macos/Sources/Remux/Views/TabBarView.swift +94 -0
  87. package/apps/macos/Sources/Remux/Views/Terminal/ClipboardHelper.swift +101 -0
  88. package/apps/macos/Sources/Remux/Views/Terminal/CopyModeOverlay.swift +325 -0
  89. package/apps/macos/Sources/Remux/Views/Terminal/GhosttyNativeTerminalView.swift +39 -0
  90. package/apps/macos/Sources/Remux/Views/Terminal/GhosttyNativeView.swift +559 -0
  91. package/apps/macos/Sources/Remux/Views/Terminal/SurfaceSearchOverlay.swift +109 -0
  92. package/apps/macos/Sources/Remux/Views/Terminal/TerminalContainerView.swift +95 -0
  93. package/apps/macos/Sources/Remux/Views/Terminal/TerminalRelay.swift +117 -0
  94. package/build.mjs +33 -0
  95. package/native/android/DecodeGoldenPayloads.kt +487 -0
  96. package/native/android/ProtocolModels.kt +188 -0
  97. package/native/ios/DecodeGoldenPayloads.swift +711 -0
  98. package/native/ios/ProtocolModels.swift +200 -0
  99. package/package.json +45 -0
  100. package/packages/RemuxKit/Package.swift +27 -0
  101. package/packages/RemuxKit/Sources/RemuxKit/Device/DeviceManager.swift +27 -0
  102. package/packages/RemuxKit/Sources/RemuxKit/Models/ProtocolModels.swift +206 -0
  103. package/packages/RemuxKit/Sources/RemuxKit/Networking/MessageRouter.swift +108 -0
  104. package/packages/RemuxKit/Sources/RemuxKit/Networking/RemuxConnection.swift +395 -0
  105. package/packages/RemuxKit/Sources/RemuxKit/State/RemuxState.swift +188 -0
  106. package/packages/RemuxKit/Sources/RemuxKit/Storage/KeychainStore.swift +142 -0
  107. package/packages/RemuxKit/Sources/RemuxKit/Terminal/GhosttyBridge.swift +145 -0
  108. package/packages/RemuxKit/Sources/RemuxKit/Terminal/GhosttyTerminalView.swift +35 -0
  109. package/packages/RemuxKit/Sources/RemuxKit/Terminal/Resources/ghostty-terminal.html +91 -0
  110. package/packages/RemuxKit/Tests/RemuxKitTests/ConnectionIntegrationTest.swift +74 -0
  111. package/packages/RemuxKit/Tests/RemuxKitTests/KeychainStoreTests.swift +81 -0
  112. package/packages/RemuxKit/Tests/RemuxKitTests/ProtocolModelsTests.swift +179 -0
  113. package/packages/RemuxKit/Tests/RemuxKitTests/RemuxStateTests.swift +62 -0
  114. package/playwright.config.ts +17 -0
  115. package/pnpm-lock.yaml +1588 -0
  116. package/pty-daemon.js +303 -0
  117. package/release-please-config.json +14 -0
  118. package/scripts/auto-deploy.sh +46 -0
  119. package/scripts/build-dmg.sh +121 -0
  120. package/scripts/build-ghostty-kit.sh +43 -0
  121. package/scripts/check-active-terminology.mjs +132 -0
  122. package/scripts/setup-ci-secrets.sh +80 -0
  123. package/scripts/sync-ghostty-web.sh +28 -0
  124. package/scripts/upload-testflight.sh +100 -0
  125. package/server.js +7074 -0
  126. package/src/adapters/agent-events.ts +246 -0
  127. package/src/adapters/claude-code.ts +158 -0
  128. package/src/adapters/codex.ts +210 -0
  129. package/src/adapters/generic-shell.ts +58 -0
  130. package/src/adapters/index.ts +15 -0
  131. package/src/adapters/registry.ts +99 -0
  132. package/src/adapters/types.ts +41 -0
  133. package/src/auth.ts +174 -0
  134. package/src/e2ee.ts +236 -0
  135. package/src/git-service.ts +168 -0
  136. package/src/message-buffer.ts +137 -0
  137. package/src/pty-daemon.ts +357 -0
  138. package/src/push.ts +127 -0
  139. package/src/renderers.ts +455 -0
  140. package/src/server.ts +2407 -0
  141. package/src/service.ts +226 -0
  142. package/src/session.ts +978 -0
  143. package/src/store.ts +1422 -0
  144. package/src/team.ts +123 -0
  145. package/src/tunnel.ts +126 -0
  146. package/src/types.d.ts +50 -0
  147. package/src/vt-tracker.ts +188 -0
  148. package/src/workspace-head.ts +144 -0
  149. package/src/workspace.ts +153 -0
  150. package/src/ws-handler.ts +1526 -0
  151. package/start.ps1 +83 -0
  152. package/tests/adapters.test.js +171 -0
  153. package/tests/auth.test.js +243 -0
  154. package/tests/codex-adapter.test.js +535 -0
  155. package/tests/durable-stream.test.js +153 -0
  156. package/tests/e2e/app.spec.js +530 -0
  157. package/tests/e2ee.test.js +325 -0
  158. package/tests/message-buffer.test.js +245 -0
  159. package/tests/message-routing.test.js +305 -0
  160. package/tests/pty-daemon.test.js +346 -0
  161. package/tests/push.test.js +281 -0
  162. package/tests/renderers.test.js +391 -0
  163. package/tests/search-shell.test.js +499 -0
  164. package/tests/server.test.js +882 -0
  165. package/tests/service.test.js +267 -0
  166. package/tests/store.test.js +369 -0
  167. package/tests/tunnel.test.js +67 -0
  168. package/tests/workspace-head.test.js +116 -0
  169. package/tests/workspace.test.js +417 -0
  170. package/tsconfig.backend.json +11 -0
  171. package/tsconfig.json +15 -0
  172. package/tui/client/client_test.go +125 -0
  173. package/tui/client/connection.go +342 -0
  174. package/tui/client/host_manager.go +141 -0
  175. package/tui/config/cache.go +81 -0
  176. package/tui/config/config.go +53 -0
  177. package/tui/config/config_test.go +89 -0
  178. package/tui/go.mod +32 -0
  179. package/tui/go.sum +50 -0
  180. package/tui/main.go +261 -0
  181. package/tui/tests/integration_test.go +283 -0
  182. package/tui/ui/model.go +310 -0
  183. package/vitest.config.js +10 -0
@@ -0,0 +1,200 @@
1
+ public struct ProtocolCapabilities: Codable, Equatable {
2
+ public let envelope: Bool
3
+ public let inspectV2: Bool
4
+ public let deviceTrust: Bool
5
+ }
6
+
7
+ public struct AuthPayload: Codable, Equatable {
8
+ public let token: String
9
+ public let password: String?
10
+ public let cols: Int?
11
+ public let rows: Int?
12
+ public let capabilities: ProtocolCapabilities?
13
+ }
14
+
15
+ public struct AuthOkPayload: Codable, Equatable {
16
+ public let capabilities: ProtocolCapabilities
17
+ }
18
+
19
+ public struct AuthErrorPayload: Codable, Equatable {
20
+ public let reason: String
21
+ }
22
+
23
+ public struct InspectContentPayload: Codable, Equatable {
24
+ public let content: String
25
+ }
26
+
27
+ public struct RemuxEnvelope<Payload: Codable & Equatable>: Codable, Equatable {
28
+ public let domain: String
29
+ public let type: String
30
+ public let version: Int
31
+ public let requestId: String?
32
+ public let emittedAt: String
33
+ public let source: String
34
+ public let payload: Payload
35
+ }
36
+
37
+ public struct WorkspacePane: Codable, Equatable {
38
+ public let id: String
39
+ public let focused: Bool
40
+ public let title: String
41
+ public let command: String?
42
+ public let cwd: String?
43
+ public let rows: Int
44
+ public let cols: Int
45
+ public let x: Int
46
+ public let y: Int
47
+ }
48
+
49
+ public struct WorkspaceTab: Codable, Equatable {
50
+ public let index: Int
51
+ public let name: String
52
+ public let active: Bool
53
+ public let isFullscreen: Bool
54
+ public let hasBell: Bool
55
+ public let panes: [WorkspacePane]
56
+ }
57
+
58
+ public struct WorkspaceState: Codable, Equatable {
59
+ public let session: String
60
+ public let tabs: [WorkspaceTab]
61
+ public let activeTabIndex: Int
62
+ }
63
+
64
+ public struct InspectHighlight: Codable, Equatable {
65
+ public let start: Int
66
+ public let end: Int
67
+ }
68
+
69
+ public struct InspectDescriptor: Codable, Equatable {
70
+ public let scope: String
71
+ public let source: String
72
+ public let precision: String
73
+ public let staleness: String
74
+ public let capturedAt: String
75
+ public let paneId: String?
76
+ public let tabIndex: Int?
77
+ public let totalItems: Int?
78
+ }
79
+
80
+ public struct InspectItem: Codable, Equatable {
81
+ public let type: String
82
+ public let content: String
83
+ public let lineNumber: Int?
84
+ public let timestamp: String
85
+ public let paneId: String?
86
+ public let highlights: [InspectHighlight]?
87
+ }
88
+
89
+ public struct InspectSnapshot: Codable, Equatable {
90
+ public let descriptor: InspectDescriptor
91
+ public let items: [InspectItem]
92
+ public let cursor: String?
93
+ public let truncated: Bool
94
+ }
95
+
96
+ public struct InspectRequest: Codable, Equatable {
97
+ public let scope: String
98
+ public let paneId: String?
99
+ public let tabIndex: Int?
100
+ public let cursor: String?
101
+ public let query: String?
102
+ public let limit: Int?
103
+ }
104
+
105
+ public struct BandwidthStats: Codable, Equatable {
106
+ public let rawBytesPerSec: Double
107
+ public let compressedBytesPerSec: Double
108
+ public let savedPercent: Double
109
+ public let fullSnapshotsSent: Int
110
+ public let diffUpdatesSent: Int
111
+ public let avgChangedRowsPerDiff: Double
112
+ public let totalRawBytes: Int
113
+ public let totalCompressedBytes: Int
114
+ public let totalSavedBytes: Int
115
+ public let rttMs: Int?
116
+ public let protocolName: String
117
+
118
+ enum CodingKeys: String, CodingKey {
119
+ case rawBytesPerSec
120
+ case compressedBytesPerSec
121
+ case savedPercent
122
+ case fullSnapshotsSent
123
+ case diffUpdatesSent
124
+ case avgChangedRowsPerDiff
125
+ case totalRawBytes
126
+ case totalCompressedBytes
127
+ case totalSavedBytes
128
+ case rttMs
129
+ case protocolName = "protocol"
130
+ }
131
+ }
132
+
133
+ public struct BandwidthStatsPayload: Codable, Equatable {
134
+ public let stats: BandwidthStats
135
+ }
136
+
137
+ public struct LegacyAuthOk: Codable, Equatable {
138
+ public let type: String
139
+ public let capabilities: ProtocolCapabilities
140
+ }
141
+
142
+ public struct LegacyAuth: Codable, Equatable {
143
+ public let type: String
144
+ public let token: String
145
+ public let password: String?
146
+ public let cols: Int?
147
+ public let rows: Int?
148
+ public let capabilities: ProtocolCapabilities?
149
+ }
150
+
151
+ public struct LegacyAuthError: Codable, Equatable {
152
+ public let type: String
153
+ public let reason: String
154
+ }
155
+
156
+ public struct LegacyErrorMessage: Codable, Equatable {
157
+ public let type: String
158
+ public let code: Int?
159
+ public let message: String
160
+ }
161
+
162
+ public struct LegacyPong: Codable, Equatable {
163
+ public let type: String
164
+ public let timestamp: Double
165
+ }
166
+
167
+ public struct LegacyWorkspaceState: Codable, Equatable {
168
+ public let type: String
169
+ public let session: String
170
+ public let tabs: [WorkspaceTab]
171
+ public let activeTabIndex: Int
172
+ }
173
+
174
+ public struct LegacyInspectRequest: Codable, Equatable {
175
+ public let type: String
176
+ public let scope: String
177
+ public let paneId: String?
178
+ public let tabIndex: Int?
179
+ public let cursor: String?
180
+ public let query: String?
181
+ public let limit: Int?
182
+ }
183
+
184
+ public struct LegacyInspectSnapshot: Codable, Equatable {
185
+ public let type: String
186
+ public let descriptor: InspectDescriptor
187
+ public let items: [InspectItem]
188
+ public let cursor: String?
189
+ public let truncated: Bool
190
+ }
191
+
192
+ public struct LegacyBandwidthStats: Codable, Equatable {
193
+ public let type: String
194
+ public let stats: BandwidthStats
195
+ }
196
+
197
+ public struct LegacyInspectContent: Codable, Equatable {
198
+ public let type: String
199
+ public let content: String
200
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@wangyaoshen/remux",
3
+ "version": "0.3.8-dev.a8ceb0c",
4
+ "description": "Remote terminal workspace — powered by ghostty-web",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/yaoshenwang/remux.git"
9
+ },
10
+ "type": "module",
11
+ "bin": {
12
+ "remux": "server.js"
13
+ },
14
+ "engines": {
15
+ "node": ">=20"
16
+ },
17
+ "dependencies": {
18
+ "better-sqlite3": "^12.8.0",
19
+ "ghostty-web": "^0.4.0",
20
+ "node-pty": "^1.1.0",
21
+ "qrcode-terminal": "^0.12.0",
22
+ "simple-git": "^3.33.0",
23
+ "web-push": "^3.6.7",
24
+ "ws": "^8.20.0"
25
+ },
26
+ "devDependencies": {
27
+ "@playwright/test": "^1.58.2",
28
+ "@types/better-sqlite3": "^7.6.13",
29
+ "@types/node": "^25.5.0",
30
+ "@types/ws": "^8.18.1",
31
+ "esbuild": "^0.27.4",
32
+ "tsx": "^4.21.0",
33
+ "typescript": "^6.0.2",
34
+ "vitest": "^4.1.2"
35
+ },
36
+ "scripts": {
37
+ "start": "node server.js",
38
+ "dev": "pnpm build && node server.js",
39
+ "build": "node build.mjs",
40
+ "typecheck": "tsc --noEmit",
41
+ "test": "vitest run",
42
+ "test:e2e": "npx playwright test",
43
+ "postinstall": "node --input-type=commonjs -e \"var fs=require('fs'),p=require('path');['darwin-arm64','darwin-x64'].forEach(function(d){var f=p.join('node_modules','node-pty','prebuilds',d,'spawn-helper');try{fs.chmodSync(f,0o755)}catch(e){}})\""
44
+ }
45
+ }
@@ -0,0 +1,27 @@
1
+ // swift-tools-version: 6.0
2
+
3
+ import PackageDescription
4
+
5
+ let package = Package(
6
+ name: "RemuxKit",
7
+ platforms: [
8
+ .iOS(.v17),
9
+ .macOS(.v14),
10
+ ],
11
+ products: [
12
+ .library(
13
+ name: "RemuxKit",
14
+ targets: ["RemuxKit"]
15
+ ),
16
+ ],
17
+ targets: [
18
+ .target(
19
+ name: "RemuxKit",
20
+ exclude: ["Terminal/Resources"]
21
+ ),
22
+ .testTarget(
23
+ name: "RemuxKitTests",
24
+ dependencies: ["RemuxKit"]
25
+ ),
26
+ ]
27
+ )
@@ -0,0 +1,27 @@
1
+ import Foundation
2
+
3
+ /// Manages device trust operations via WebSocket messages.
4
+ /// All messages use JSONSerialization for safe construction.
5
+ @MainActor
6
+ public final class DeviceManager {
7
+
8
+ private weak var connection: RemuxConnection?
9
+
10
+ public init(connection: RemuxConnection) {
11
+ self.connection = connection
12
+ }
13
+
14
+ public func listDevices() { sendJSON(["type": "list_devices"]) }
15
+ public func trustDevice(id: String) { sendJSON(["type": "trust_device", "deviceId": id]) }
16
+ public func blockDevice(id: String) { sendJSON(["type": "block_device", "deviceId": id]) }
17
+ public func renameDevice(id: String, name: String) { sendJSON(["type": "rename_device", "deviceId": id, "name": name]) }
18
+ public func revokeDevice(id: String) { sendJSON(["type": "revoke_device", "deviceId": id]) }
19
+ public func generatePairCode() { sendJSON(["type": "generate_pair_code"]) }
20
+ public func pair(code: String) { sendJSON(["type": "pair", "code": code]) }
21
+
22
+ private func sendJSON(_ dict: [String: Any]) {
23
+ guard let data = try? JSONSerialization.data(withJSONObject: dict),
24
+ let str = String(data: data, encoding: .utf8) else { return }
25
+ connection?.sendString(str)
26
+ }
27
+ }
@@ -0,0 +1,206 @@
1
+ public struct ProtocolCapabilities: Codable, Equatable, Sendable {
2
+ public let envelope: Bool
3
+ public let inspectV2: Bool
4
+ public let deviceTrust: Bool
5
+ }
6
+
7
+ public struct AuthPayload: Codable, Equatable, Sendable {
8
+ public let token: String
9
+ public let password: String?
10
+ public let cols: Int?
11
+ public let rows: Int?
12
+ public let capabilities: ProtocolCapabilities?
13
+ }
14
+
15
+ public struct AuthOkPayload: Codable, Equatable, Sendable {
16
+ public let capabilities: ProtocolCapabilities
17
+ }
18
+
19
+ public struct AuthErrorPayload: Codable, Equatable, Sendable {
20
+ public let reason: String
21
+ }
22
+
23
+ public struct InspectContentPayload: Codable, Equatable, Sendable {
24
+ public let content: String
25
+ }
26
+
27
+ public struct RemuxEnvelope<Payload: Codable & Equatable & Sendable>: Codable, Equatable, Sendable {
28
+ public let domain: String
29
+ public let type: String
30
+ public let version: Int
31
+ public let requestId: String?
32
+ public let emittedAt: String
33
+ public let source: String
34
+ public let payload: Payload
35
+
36
+ enum CodingKeys: String, CodingKey {
37
+ case domain, type
38
+ case version = "v"
39
+ case requestId, emittedAt, source, payload
40
+ }
41
+ }
42
+
43
+ public struct WorkspacePane: Codable, Equatable, Sendable {
44
+ public let id: String
45
+ public let focused: Bool
46
+ public let title: String
47
+ public let command: String?
48
+ public let cwd: String?
49
+ public let rows: Int
50
+ public let cols: Int
51
+ public let x: Int
52
+ public let y: Int
53
+ }
54
+
55
+ public struct WorkspaceTab: Codable, Equatable, Sendable {
56
+ public let index: Int
57
+ public let name: String
58
+ public let active: Bool
59
+ public let isFullscreen: Bool
60
+ public let hasBell: Bool
61
+ public let panes: [WorkspacePane]
62
+ }
63
+
64
+ public struct WorkspaceState: Codable, Equatable, Sendable {
65
+ public let session: String
66
+ public let tabs: [WorkspaceTab]
67
+ public let activeTabIndex: Int
68
+ }
69
+
70
+ public struct InspectHighlight: Codable, Equatable, Sendable {
71
+ public let start: Int
72
+ public let end: Int
73
+ }
74
+
75
+ public struct InspectDescriptor: Codable, Equatable, Sendable {
76
+ public let scope: String
77
+ public let source: String
78
+ public let precision: String
79
+ public let staleness: String
80
+ public let capturedAt: String
81
+ public let paneId: String?
82
+ public let tabIndex: Int?
83
+ public let totalItems: Int?
84
+ }
85
+
86
+ public struct InspectItem: Codable, Equatable, Sendable {
87
+ public let type: String
88
+ public let content: String
89
+ public let lineNumber: Int?
90
+ public let timestamp: String
91
+ public let paneId: String?
92
+ public let highlights: [InspectHighlight]?
93
+ }
94
+
95
+ public struct InspectSnapshot: Codable, Equatable, Sendable {
96
+ public let descriptor: InspectDescriptor
97
+ public let items: [InspectItem]
98
+ public let cursor: String?
99
+ public let truncated: Bool
100
+ }
101
+
102
+ public struct InspectRequest: Codable, Equatable, Sendable {
103
+ public let scope: String
104
+ public let paneId: String?
105
+ public let tabIndex: Int?
106
+ public let cursor: String?
107
+ public let query: String?
108
+ public let limit: Int?
109
+ }
110
+
111
+ public struct BandwidthStats: Codable, Equatable, Sendable {
112
+ public let rawBytesPerSec: Double
113
+ public let compressedBytesPerSec: Double
114
+ public let savedPercent: Double
115
+ public let fullSnapshotsSent: Int
116
+ public let diffUpdatesSent: Int
117
+ public let avgChangedRowsPerDiff: Double
118
+ public let totalRawBytes: Int
119
+ public let totalCompressedBytes: Int
120
+ public let totalSavedBytes: Int
121
+ public let rttMs: Int?
122
+ public let protocolName: String
123
+
124
+ enum CodingKeys: String, CodingKey {
125
+ case rawBytesPerSec
126
+ case compressedBytesPerSec
127
+ case savedPercent
128
+ case fullSnapshotsSent
129
+ case diffUpdatesSent
130
+ case avgChangedRowsPerDiff
131
+ case totalRawBytes
132
+ case totalCompressedBytes
133
+ case totalSavedBytes
134
+ case rttMs
135
+ case protocolName = "protocol"
136
+ }
137
+ }
138
+
139
+ public struct BandwidthStatsPayload: Codable, Equatable, Sendable {
140
+ public let stats: BandwidthStats
141
+ }
142
+
143
+ public struct LegacyAuthOk: Codable, Equatable, Sendable {
144
+ public let type: String
145
+ public let capabilities: ProtocolCapabilities
146
+ }
147
+
148
+ public struct LegacyAuth: Codable, Equatable, Sendable {
149
+ public let type: String
150
+ public let token: String
151
+ public let password: String?
152
+ public let cols: Int?
153
+ public let rows: Int?
154
+ public let capabilities: ProtocolCapabilities?
155
+ }
156
+
157
+ public struct LegacyAuthError: Codable, Equatable, Sendable {
158
+ public let type: String
159
+ public let reason: String
160
+ }
161
+
162
+ public struct LegacyErrorMessage: Codable, Equatable, Sendable {
163
+ public let type: String
164
+ public let code: Int?
165
+ public let message: String
166
+ }
167
+
168
+ public struct LegacyPong: Codable, Equatable, Sendable {
169
+ public let type: String
170
+ public let timestamp: Double
171
+ }
172
+
173
+ public struct LegacyWorkspaceState: Codable, Equatable, Sendable {
174
+ public let type: String
175
+ public let session: String
176
+ public let tabs: [WorkspaceTab]
177
+ public let activeTabIndex: Int
178
+ }
179
+
180
+ public struct LegacyInspectRequest: Codable, Equatable, Sendable {
181
+ public let type: String
182
+ public let scope: String
183
+ public let paneId: String?
184
+ public let tabIndex: Int?
185
+ public let cursor: String?
186
+ public let query: String?
187
+ public let limit: Int?
188
+ }
189
+
190
+ public struct LegacyInspectSnapshot: Codable, Equatable, Sendable {
191
+ public let type: String
192
+ public let descriptor: InspectDescriptor
193
+ public let items: [InspectItem]
194
+ public let cursor: String?
195
+ public let truncated: Bool
196
+ }
197
+
198
+ public struct LegacyBandwidthStats: Codable, Equatable, Sendable {
199
+ public let type: String
200
+ public let stats: BandwidthStats
201
+ }
202
+
203
+ public struct LegacyInspectContent: Codable, Equatable, Sendable {
204
+ public let type: String
205
+ public let content: String
206
+ }
@@ -0,0 +1,108 @@
1
+ import Foundation
2
+
3
+ /// Routes incoming WebSocket messages to the appropriate handler.
4
+ /// Supports both envelope format (v:1) and legacy bare messages.
5
+ public struct MessageRouter: Sendable {
6
+
7
+ /// Parsed message with type info extracted
8
+ public enum RoutedMessage: Sendable {
9
+ case state(WorkspaceState)
10
+ case inspectResult(InspectSnapshot)
11
+ case roleChanged(String) // "active" or "observer"
12
+ case deviceList([DeviceInfo])
13
+ case pairResult(PairResultPayload)
14
+ case pushStatus(PushStatusPayload)
15
+ case error(String)
16
+ case unknown(type: String, raw: String)
17
+ }
18
+
19
+ public init() {}
20
+
21
+ /// Parse and route a raw JSON message string.
22
+ public func route(_ text: String) -> RoutedMessage? {
23
+ guard let data = text.data(using: .utf8),
24
+ let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
25
+ return nil
26
+ }
27
+
28
+ // Extract type — handle both envelope (v:1) and legacy format
29
+ let msgType: String?
30
+ let payload: [String: Any]?
31
+
32
+ if let v = json["v"] as? Int, v >= 1 {
33
+ // Envelope format
34
+ msgType = json["type"] as? String
35
+ payload = json["payload"] as? [String: Any]
36
+ } else {
37
+ // Legacy format — type is top-level
38
+ msgType = json["type"] as? String
39
+ payload = json
40
+ }
41
+
42
+ guard let type = msgType else { return nil }
43
+
44
+ switch type {
45
+ case "state":
46
+ if let p = payload, let state = decode(WorkspaceState.self, from: p) {
47
+ return .state(state)
48
+ }
49
+ case "inspect_result":
50
+ if let p = payload, let snapshot = decode(InspectSnapshot.self, from: p) {
51
+ return .inspectResult(snapshot)
52
+ }
53
+ case "role_changed":
54
+ if let role = payload?["role"] as? String {
55
+ return .roleChanged(role)
56
+ }
57
+ case "device_list":
58
+ if let p = payload, let list = decode(DeviceListPayload.self, from: p) {
59
+ return .deviceList(list.devices)
60
+ }
61
+ case "pair_result":
62
+ if let p = payload, let result = decode(PairResultPayload.self, from: p) {
63
+ return .pairResult(result)
64
+ }
65
+ case "push_status":
66
+ if let p = payload, let status = decode(PushStatusPayload.self, from: p) {
67
+ return .pushStatus(status)
68
+ }
69
+ case "error":
70
+ let message = payload?["message"] as? String ?? "Unknown error"
71
+ return .error(message)
72
+ default:
73
+ return .unknown(type: type, raw: text)
74
+ }
75
+
76
+ return .unknown(type: type, raw: text)
77
+ }
78
+
79
+ private func decode<T: Decodable>(_ type: T.Type, from dict: [String: Any]) -> T? {
80
+ guard let data = try? JSONSerialization.data(withJSONObject: dict) else { return nil }
81
+ return try? JSONDecoder().decode(type, from: data)
82
+ }
83
+ }
84
+
85
+ // MARK: - Additional payload types needed by router
86
+
87
+ public struct DeviceInfo: Codable, Equatable, Sendable {
88
+ public let id: String
89
+ public let fingerprint: String?
90
+ public let name: String?
91
+ public let platform: String?
92
+ public let trustLevel: String
93
+ public let lastSeen: String?
94
+ }
95
+
96
+ public struct DeviceListPayload: Codable, Equatable, Sendable {
97
+ public let devices: [DeviceInfo]
98
+ }
99
+
100
+ public struct PairResultPayload: Codable, Equatable, Sendable {
101
+ public let success: Bool
102
+ public let deviceId: String?
103
+ public let error: String?
104
+ }
105
+
106
+ public struct PushStatusPayload: Codable, Equatable, Sendable {
107
+ public let subscribed: Bool
108
+ }