@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,711 @@
1
+ @main
2
+ struct DecodeGoldenPayloads {
3
+ static func main() throws {
4
+ let arguments = CommandLine.arguments
5
+ guard arguments.count == 2 else {
6
+ throw DecodeError.message("expected compiled fixture JSON string")
7
+ }
8
+
9
+ var parser = SimpleJSONParser(arguments[1])
10
+ let fixtures = try parser.parseArray()
11
+ var decodedTargets: [String] = []
12
+
13
+ for fixture in fixtures {
14
+ let fixtureObject = try fixture.asObject()
15
+ let target = try fixtureObject.requireString("target")
16
+ let json = try fixtureObject.requireValue("json")
17
+
18
+ switch target {
19
+ case "LegacyAuth":
20
+ let value = try decodeLegacyAuth(from: json)
21
+ try check(value.type == "auth", "unexpected legacy auth type")
22
+ decodedTargets.append(target)
23
+ case "AuthEnvelope":
24
+ let value = try decodeEnvelope(from: json, payloadDecoder: decodeAuthPayload)
25
+ try check(value.domain == "core", "unexpected auth domain")
26
+ decodedTargets.append(target)
27
+ case "LegacyAuthOk":
28
+ let value = try decodeLegacyAuthOk(from: json)
29
+ try check(value.type == "auth_ok", "unexpected auth ok type")
30
+ decodedTargets.append(target)
31
+ case "AuthOkEnvelope":
32
+ let value = try decodeEnvelope(from: json, payloadDecoder: decodeAuthOkPayload)
33
+ try check(value.domain == "core", "unexpected auth ok domain")
34
+ decodedTargets.append(target)
35
+ case "LegacyAuthError":
36
+ let value = try decodeLegacyAuthError(from: json)
37
+ try check(value.type == "auth_error", "unexpected auth error type")
38
+ decodedTargets.append(target)
39
+ case "LegacyErrorMessage":
40
+ let value = try decodeLegacyErrorMessage(from: json)
41
+ try check(value.type == "error", "unexpected legacy error type")
42
+ decodedTargets.append(target)
43
+ case "LegacyPong":
44
+ let value = try decodeLegacyPong(from: json)
45
+ try check(value.type == "pong", "unexpected legacy pong type")
46
+ decodedTargets.append(target)
47
+ case "AuthErrorEnvelope":
48
+ let value = try decodeEnvelope(from: json, payloadDecoder: decodeAuthErrorPayload)
49
+ try check(value.domain == "core", "unexpected auth error domain")
50
+ decodedTargets.append(target)
51
+ case "LegacyInspectContent":
52
+ let value = try decodeLegacyInspectContent(from: json)
53
+ try check(value.type == "inspect_content", "unexpected legacy inspect content type")
54
+ decodedTargets.append(target)
55
+ case "InspectContentEnvelope":
56
+ let value = try decodeEnvelope(from: json, payloadDecoder: decodeInspectContentPayload)
57
+ try check(value.domain == "core", "unexpected inspect content domain")
58
+ decodedTargets.append(target)
59
+ case "LegacyRequestInspect":
60
+ let value = try decodeLegacyInspectRequest(from: json)
61
+ try check(value.type == "request_inspect", "unexpected legacy inspect request type")
62
+ decodedTargets.append(target)
63
+ case "RequestInspectEnvelope":
64
+ let value = try decodeEnvelope(from: json, payloadDecoder: decodeInspectRequest)
65
+ try check(value.domain == "inspect", "unexpected inspect request domain")
66
+ decodedTargets.append(target)
67
+ case "LegacyInspectSnapshot":
68
+ let value = try decodeLegacyInspectSnapshot(from: json)
69
+ try check(value.type == "inspect_snapshot", "unexpected legacy inspect snapshot type")
70
+ decodedTargets.append(target)
71
+ case "InspectSnapshotEnvelope":
72
+ let value = try decodeEnvelope(from: json, payloadDecoder: decodeInspectSnapshot)
73
+ try check(value.domain == "inspect", "unexpected inspect snapshot domain")
74
+ decodedTargets.append(target)
75
+ case "LegacyWorkspaceState":
76
+ let value = try decodeLegacyWorkspaceState(from: json)
77
+ try check(value.type == "workspace_state", "unexpected legacy workspace state type")
78
+ decodedTargets.append(target)
79
+ case "WorkspaceStateEnvelope":
80
+ let value = try decodeEnvelope(from: json, payloadDecoder: decodeWorkspaceState)
81
+ try check(value.domain == "runtime", "unexpected workspace state domain")
82
+ decodedTargets.append(target)
83
+ case "LegacyBandwidthStats":
84
+ let value = try decodeLegacyBandwidthStats(from: json)
85
+ try check(value.type == "bandwidth_stats", "unexpected legacy bandwidth stats type")
86
+ decodedTargets.append(target)
87
+ case "BandwidthStatsEnvelope":
88
+ let value = try decodeEnvelope(from: json, payloadDecoder: decodeBandwidthStatsPayload)
89
+ try check(value.domain == "admin", "unexpected bandwidth stats domain")
90
+ decodedTargets.append(target)
91
+ default:
92
+ throw DecodeError.message("unknown fixture target \(target)")
93
+ }
94
+ }
95
+
96
+ print(decodedTargets.joined(separator: ","))
97
+ }
98
+
99
+ private static func decodeLegacyAuth(from value: JSONValue) throws -> LegacyAuth {
100
+ let object = try value.asObject()
101
+ return LegacyAuth(
102
+ type: try object.requireString("type"),
103
+ token: try object.requireString("token"),
104
+ password: object.optionalString("password"),
105
+ cols: object.optionalInt("cols"),
106
+ rows: object.optionalInt("rows"),
107
+ capabilities: try object.optionalDecoded("capabilities", decodeProtocolCapabilities)
108
+ )
109
+ }
110
+
111
+ private static func decodeAuthPayload(from value: JSONValue) throws -> AuthPayload {
112
+ let object = try value.asObject()
113
+ return AuthPayload(
114
+ token: try object.requireString("token"),
115
+ password: object.optionalString("password"),
116
+ cols: object.optionalInt("cols"),
117
+ rows: object.optionalInt("rows"),
118
+ capabilities: try object.optionalDecoded("capabilities", decodeProtocolCapabilities)
119
+ )
120
+ }
121
+
122
+ private static func decodeLegacyAuthOk(from value: JSONValue) throws -> LegacyAuthOk {
123
+ let object = try value.asObject()
124
+ return LegacyAuthOk(
125
+ type: try object.requireString("type"),
126
+ capabilities: try decodeProtocolCapabilities(from: object.requireValue("capabilities"))
127
+ )
128
+ }
129
+
130
+ private static func decodeAuthOkPayload(from value: JSONValue) throws -> AuthOkPayload {
131
+ let object = try value.asObject()
132
+ return AuthOkPayload(
133
+ capabilities: try decodeProtocolCapabilities(from: object.requireValue("capabilities"))
134
+ )
135
+ }
136
+
137
+ private static func decodeLegacyAuthError(from value: JSONValue) throws -> LegacyAuthError {
138
+ let object = try value.asObject()
139
+ return LegacyAuthError(
140
+ type: try object.requireString("type"),
141
+ reason: try object.requireString("reason")
142
+ )
143
+ }
144
+
145
+ private static func decodeAuthErrorPayload(from value: JSONValue) throws -> AuthErrorPayload {
146
+ let object = try value.asObject()
147
+ return AuthErrorPayload(reason: try object.requireString("reason"))
148
+ }
149
+
150
+ private static func decodeLegacyErrorMessage(from value: JSONValue) throws -> LegacyErrorMessage {
151
+ let object = try value.asObject()
152
+ return LegacyErrorMessage(
153
+ type: try object.requireString("type"),
154
+ code: object.optionalInt("code"),
155
+ message: try object.requireString("message")
156
+ )
157
+ }
158
+
159
+ private static func decodeLegacyPong(from value: JSONValue) throws -> LegacyPong {
160
+ let object = try value.asObject()
161
+ return LegacyPong(
162
+ type: try object.requireString("type"),
163
+ timestamp: try object.requireDouble("timestamp")
164
+ )
165
+ }
166
+
167
+ private static func decodeLegacyInspectContent(from value: JSONValue) throws -> LegacyInspectContent {
168
+ let object = try value.asObject()
169
+ return LegacyInspectContent(
170
+ type: try object.requireString("type"),
171
+ content: try object.requireString("content")
172
+ )
173
+ }
174
+
175
+ private static func decodeInspectContentPayload(from value: JSONValue) throws -> InspectContentPayload {
176
+ let object = try value.asObject()
177
+ return InspectContentPayload(content: try object.requireString("content"))
178
+ }
179
+
180
+ private static func decodeProtocolCapabilities(from value: JSONValue) throws -> ProtocolCapabilities {
181
+ let object = try value.asObject()
182
+ return ProtocolCapabilities(
183
+ envelope: try object.requireBool("envelope"),
184
+ inspectV2: try object.requireBool("inspectV2"),
185
+ deviceTrust: try object.requireBool("deviceTrust")
186
+ )
187
+ }
188
+
189
+ private static func decodeLegacyWorkspaceState(from value: JSONValue) throws -> LegacyWorkspaceState {
190
+ let object = try value.asObject()
191
+ let tabs = try object.requireArray("tabs").map(decodeWorkspaceTab)
192
+ return LegacyWorkspaceState(
193
+ type: try object.requireString("type"),
194
+ session: try object.requireString("session"),
195
+ tabs: tabs,
196
+ activeTabIndex: try object.requireInt("activeTabIndex")
197
+ )
198
+ }
199
+
200
+ private static func decodeWorkspaceState(from value: JSONValue) throws -> WorkspaceState {
201
+ let object = try value.asObject()
202
+ let tabs = try object.requireArray("tabs").map(decodeWorkspaceTab)
203
+ return WorkspaceState(
204
+ session: try object.requireString("session"),
205
+ tabs: tabs,
206
+ activeTabIndex: try object.requireInt("activeTabIndex")
207
+ )
208
+ }
209
+
210
+ private static func decodeWorkspaceTab(from value: JSONValue) throws -> WorkspaceTab {
211
+ let object = try value.asObject()
212
+ let panes = try object.requireArray("panes").map(decodeWorkspacePane)
213
+ return WorkspaceTab(
214
+ index: try object.requireInt("index"),
215
+ name: try object.requireString("name"),
216
+ active: try object.requireBool("active"),
217
+ isFullscreen: try object.requireBool("isFullscreen"),
218
+ hasBell: try object.requireBool("hasBell"),
219
+ panes: panes
220
+ )
221
+ }
222
+
223
+ private static func decodeWorkspacePane(from value: JSONValue) throws -> WorkspacePane {
224
+ let object = try value.asObject()
225
+ return WorkspacePane(
226
+ id: try object.requireString("id"),
227
+ focused: try object.requireBool("focused"),
228
+ title: try object.requireString("title"),
229
+ command: object.optionalString("command"),
230
+ cwd: object.optionalString("cwd"),
231
+ rows: try object.requireInt("rows"),
232
+ cols: try object.requireInt("cols"),
233
+ x: try object.requireInt("x"),
234
+ y: try object.requireInt("y")
235
+ )
236
+ }
237
+
238
+ private static func decodeLegacyInspectRequest(from value: JSONValue) throws -> LegacyInspectRequest {
239
+ let object = try value.asObject()
240
+ return LegacyInspectRequest(
241
+ type: try object.requireString("type"),
242
+ scope: try object.requireString("scope"),
243
+ paneId: object.optionalString("paneId"),
244
+ tabIndex: object.optionalInt("tabIndex"),
245
+ cursor: object.optionalString("cursor"),
246
+ query: object.optionalString("query"),
247
+ limit: object.optionalInt("limit")
248
+ )
249
+ }
250
+
251
+ private static func decodeInspectRequest(from value: JSONValue) throws -> InspectRequest {
252
+ let object = try value.asObject()
253
+ return InspectRequest(
254
+ scope: try object.requireString("scope"),
255
+ paneId: object.optionalString("paneId"),
256
+ tabIndex: object.optionalInt("tabIndex"),
257
+ cursor: object.optionalString("cursor"),
258
+ query: object.optionalString("query"),
259
+ limit: object.optionalInt("limit")
260
+ )
261
+ }
262
+
263
+ private static func decodeLegacyInspectSnapshot(from value: JSONValue) throws -> LegacyInspectSnapshot {
264
+ let object = try value.asObject()
265
+ let items = try object.requireArray("items").map(decodeInspectItem)
266
+ return LegacyInspectSnapshot(
267
+ type: try object.requireString("type"),
268
+ descriptor: try decodeInspectDescriptor(from: object.requireValue("descriptor")),
269
+ items: items,
270
+ cursor: object.optionalString("cursor"),
271
+ truncated: try object.requireBool("truncated")
272
+ )
273
+ }
274
+
275
+ private static func decodeInspectSnapshot(from value: JSONValue) throws -> InspectSnapshot {
276
+ let object = try value.asObject()
277
+ let items = try object.requireArray("items").map(decodeInspectItem)
278
+ return InspectSnapshot(
279
+ descriptor: try decodeInspectDescriptor(from: object.requireValue("descriptor")),
280
+ items: items,
281
+ cursor: object.optionalString("cursor"),
282
+ truncated: try object.requireBool("truncated")
283
+ )
284
+ }
285
+
286
+ private static func decodeInspectDescriptor(from value: JSONValue) throws -> InspectDescriptor {
287
+ let object = try value.asObject()
288
+ return InspectDescriptor(
289
+ scope: try object.requireString("scope"),
290
+ source: try object.requireString("source"),
291
+ precision: try object.requireString("precision"),
292
+ staleness: try object.requireString("staleness"),
293
+ capturedAt: try object.requireString("capturedAt"),
294
+ paneId: object.optionalString("paneId"),
295
+ tabIndex: object.optionalInt("tabIndex"),
296
+ totalItems: object.optionalInt("totalItems")
297
+ )
298
+ }
299
+
300
+ private static func decodeInspectItem(from value: JSONValue) throws -> InspectItem {
301
+ let object = try value.asObject()
302
+ let highlights = try object.optionalArray("highlights")?.map(decodeInspectHighlight)
303
+ return InspectItem(
304
+ type: try object.requireString("type"),
305
+ content: try object.requireString("content"),
306
+ lineNumber: object.optionalInt("lineNumber"),
307
+ timestamp: try object.requireString("timestamp"),
308
+ paneId: object.optionalString("paneId"),
309
+ highlights: highlights
310
+ )
311
+ }
312
+
313
+ private static func decodeInspectHighlight(from value: JSONValue) throws -> InspectHighlight {
314
+ let object = try value.asObject()
315
+ return InspectHighlight(
316
+ start: try object.requireInt("start"),
317
+ end: try object.requireInt("end")
318
+ )
319
+ }
320
+
321
+ private static func decodeLegacyBandwidthStats(from value: JSONValue) throws -> LegacyBandwidthStats {
322
+ let object = try value.asObject()
323
+ return LegacyBandwidthStats(
324
+ type: try object.requireString("type"),
325
+ stats: try decodeBandwidthStats(from: object.requireValue("stats"))
326
+ )
327
+ }
328
+
329
+ private static func decodeBandwidthStatsPayload(from value: JSONValue) throws -> BandwidthStatsPayload {
330
+ let object = try value.asObject()
331
+ return BandwidthStatsPayload(
332
+ stats: try decodeBandwidthStats(from: object.requireValue("stats"))
333
+ )
334
+ }
335
+
336
+ private static func decodeBandwidthStats(from value: JSONValue) throws -> BandwidthStats {
337
+ let object = try value.asObject()
338
+ return BandwidthStats(
339
+ rawBytesPerSec: try object.requireDouble("rawBytesPerSec"),
340
+ compressedBytesPerSec: try object.requireDouble("compressedBytesPerSec"),
341
+ savedPercent: try object.requireDouble("savedPercent"),
342
+ fullSnapshotsSent: try object.requireInt("fullSnapshotsSent"),
343
+ diffUpdatesSent: try object.requireInt("diffUpdatesSent"),
344
+ avgChangedRowsPerDiff: try object.requireDouble("avgChangedRowsPerDiff"),
345
+ totalRawBytes: try object.requireInt("totalRawBytes"),
346
+ totalCompressedBytes: try object.requireInt("totalCompressedBytes"),
347
+ totalSavedBytes: try object.requireInt("totalSavedBytes"),
348
+ rttMs: object.optionalInt("rttMs"),
349
+ protocolName: try object.requireString("protocol")
350
+ )
351
+ }
352
+
353
+ private static func decodeEnvelope<T>(
354
+ from value: JSONValue,
355
+ payloadDecoder: (JSONValue) throws -> T
356
+ ) throws -> RemuxEnvelope<T> {
357
+ let object = try value.asObject()
358
+ return RemuxEnvelope(
359
+ domain: try object.requireString("domain"),
360
+ type: try object.requireString("type"),
361
+ version: try object.requireInt("version"),
362
+ requestId: object.optionalString("requestId"),
363
+ emittedAt: try object.requireString("emittedAt"),
364
+ source: try object.requireString("source"),
365
+ payload: try payloadDecoder(object.requireValue("payload"))
366
+ )
367
+ }
368
+
369
+ private static func check(_ condition: Bool, _ message: String) throws {
370
+ if !condition {
371
+ throw DecodeError.message(message)
372
+ }
373
+ }
374
+ }
375
+
376
+ private enum JSONValue {
377
+ case string(String)
378
+ case number(Double)
379
+ case bool(Bool)
380
+ case object([String: JSONValue])
381
+ case array([JSONValue])
382
+ case null
383
+
384
+ func asObject() throws -> [String: JSONValue] {
385
+ guard case let .object(value) = self else {
386
+ throw DecodeError.message("expected object")
387
+ }
388
+ return value
389
+ }
390
+ }
391
+
392
+ private struct SimpleJSONParser {
393
+ let input: [Character]
394
+ var index: Int = 0
395
+
396
+ init(_ input: String) {
397
+ self.input = Array(input)
398
+ }
399
+
400
+ mutating func parseArray() throws -> [JSONValue] {
401
+ try skipWhitespace()
402
+ try expect("[")
403
+ try skipWhitespace()
404
+ if peek() == "]" {
405
+ index += 1
406
+ return []
407
+ }
408
+
409
+ var values: [JSONValue] = []
410
+ while true {
411
+ values.append(try parseValue())
412
+ try skipWhitespace()
413
+ if peek() == "," {
414
+ index += 1
415
+ try skipWhitespace()
416
+ continue
417
+ }
418
+ if peek() == "]" {
419
+ index += 1
420
+ return values
421
+ }
422
+ throw parserError("expected ',' or ']'")
423
+ }
424
+ }
425
+
426
+ mutating func parseValue() throws -> JSONValue {
427
+ try skipWhitespace()
428
+ switch peek() {
429
+ case "\"":
430
+ return .string(try parseString())
431
+ case "{":
432
+ return try parseObject()
433
+ case "[":
434
+ return try parseArrayValue()
435
+ case "t":
436
+ try expect("true")
437
+ return .bool(true)
438
+ case "f":
439
+ try expect("false")
440
+ return .bool(false)
441
+ case "n":
442
+ try expect("null")
443
+ return .null
444
+ default:
445
+ return .number(try parseNumber())
446
+ }
447
+ }
448
+
449
+ private mutating func parseObject() throws -> JSONValue {
450
+ try expect("{")
451
+ try skipWhitespace()
452
+ if peek() == "}" {
453
+ index += 1
454
+ return .object([:])
455
+ }
456
+
457
+ var values: [String: JSONValue] = [:]
458
+ while true {
459
+ let key = try parseString()
460
+ try skipWhitespace()
461
+ try expect(":")
462
+ values[key] = try parseValue()
463
+ try skipWhitespace()
464
+ if peek() == "," {
465
+ index += 1
466
+ try skipWhitespace()
467
+ continue
468
+ }
469
+ if peek() == "}" {
470
+ index += 1
471
+ return .object(values)
472
+ }
473
+ throw parserError("expected ',' or '}'")
474
+ }
475
+ }
476
+
477
+ private mutating func parseArrayValue() throws -> JSONValue {
478
+ try expect("[")
479
+ try skipWhitespace()
480
+ if peek() == "]" {
481
+ index += 1
482
+ return .array([])
483
+ }
484
+
485
+ var values: [JSONValue] = []
486
+ while true {
487
+ values.append(try parseValue())
488
+ try skipWhitespace()
489
+ if peek() == "," {
490
+ index += 1
491
+ try skipWhitespace()
492
+ continue
493
+ }
494
+ if peek() == "]" {
495
+ index += 1
496
+ return .array(values)
497
+ }
498
+ throw parserError("expected ',' or ']'")
499
+ }
500
+ }
501
+
502
+ private mutating func parseString() throws -> String {
503
+ try expect("\"")
504
+ var result = ""
505
+ while index < input.count {
506
+ let character = input[index]
507
+ index += 1
508
+ if character == "\"" {
509
+ return result
510
+ }
511
+ if character == "\\" {
512
+ let escaped = try nextCharacter()
513
+ switch escaped {
514
+ case "\"", "\\", "/":
515
+ result.append(escaped)
516
+ case "b":
517
+ result.append("\u{8}")
518
+ case "f":
519
+ result.append("\u{c}")
520
+ case "n":
521
+ result.append("\n")
522
+ case "r":
523
+ result.append("\r")
524
+ case "t":
525
+ result.append("\t")
526
+ case "u":
527
+ let scalar = try parseUnicodeEscape()
528
+ result.append(Character(scalar))
529
+ default:
530
+ throw parserError("unsupported escape sequence")
531
+ }
532
+ continue
533
+ }
534
+ result.append(character)
535
+ }
536
+
537
+ throw parserError("unterminated string")
538
+ }
539
+
540
+ private mutating func parseUnicodeEscape() throws -> UnicodeScalar {
541
+ var hex = ""
542
+ for _ in 0..<4 {
543
+ hex.append(try nextCharacter())
544
+ }
545
+ guard let value = UInt32(hex, radix: 16), let scalar = UnicodeScalar(value) else {
546
+ throw parserError("invalid unicode escape")
547
+ }
548
+ return scalar
549
+ }
550
+
551
+ private mutating func parseNumber() throws -> Double {
552
+ let start = index
553
+ if peek() == "-" {
554
+ index += 1
555
+ }
556
+ while peek().isNumber {
557
+ index += 1
558
+ }
559
+ if peek() == "." {
560
+ index += 1
561
+ while peek().isNumber {
562
+ index += 1
563
+ }
564
+ }
565
+ if peek() == "e" || peek() == "E" {
566
+ index += 1
567
+ if peek() == "+" || peek() == "-" {
568
+ index += 1
569
+ }
570
+ while peek().isNumber {
571
+ index += 1
572
+ }
573
+ }
574
+ let literal = String(input[start..<index])
575
+ guard let value = Double(literal) else {
576
+ throw parserError("invalid number")
577
+ }
578
+ return value
579
+ }
580
+
581
+ private mutating func skipWhitespace() throws {
582
+ while peek().isWhitespace {
583
+ index += 1
584
+ }
585
+ }
586
+
587
+ private func peek() -> Character {
588
+ guard index < input.count else {
589
+ return "\0"
590
+ }
591
+ return input[index]
592
+ }
593
+
594
+ private mutating func nextCharacter() throws -> Character {
595
+ guard index < input.count else {
596
+ throw parserError("unexpected end of input")
597
+ }
598
+ defer { index += 1 }
599
+ return input[index]
600
+ }
601
+
602
+ private mutating func expect(_ literal: String) throws {
603
+ for expected in literal {
604
+ guard index < input.count, input[index] == expected else {
605
+ throw parserError("expected '\(literal)'")
606
+ }
607
+ index += 1
608
+ }
609
+ }
610
+
611
+ private func parserError(_ message: String) -> DecodeError {
612
+ .message("\(message) at index \(index)")
613
+ }
614
+ }
615
+
616
+ private extension Dictionary where Key == String, Value == JSONValue {
617
+ func requireValue(_ key: String) throws -> JSONValue {
618
+ guard let value = self[key] else {
619
+ throw DecodeError.message("missing key \(key)")
620
+ }
621
+ return value
622
+ }
623
+
624
+ func requireString(_ key: String) throws -> String {
625
+ guard case let .string(value) = try requireValue(key) else {
626
+ throw DecodeError.message("expected string for \(key)")
627
+ }
628
+ return value
629
+ }
630
+
631
+ func optionalString(_ key: String) -> String? {
632
+ guard let value = self[key] else {
633
+ return nil
634
+ }
635
+ if case .null = value {
636
+ return nil
637
+ }
638
+ if case let .string(string) = value {
639
+ return string
640
+ }
641
+ return nil
642
+ }
643
+
644
+ func requireBool(_ key: String) throws -> Bool {
645
+ guard case let .bool(value) = try requireValue(key) else {
646
+ throw DecodeError.message("expected bool for \(key)")
647
+ }
648
+ return value
649
+ }
650
+
651
+ func requireInt(_ key: String) throws -> Int {
652
+ Int(try requireDouble(key))
653
+ }
654
+
655
+ func optionalInt(_ key: String) -> Int? {
656
+ guard let value = self[key] else {
657
+ return nil
658
+ }
659
+ if case .null = value {
660
+ return nil
661
+ }
662
+ if case let .number(number) = value {
663
+ return Int(number)
664
+ }
665
+ return nil
666
+ }
667
+
668
+ func requireDouble(_ key: String) throws -> Double {
669
+ guard case let .number(value) = try requireValue(key) else {
670
+ throw DecodeError.message("expected number for \(key)")
671
+ }
672
+ return value
673
+ }
674
+
675
+ func requireArray(_ key: String) throws -> [JSONValue] {
676
+ guard case let .array(value) = try requireValue(key) else {
677
+ throw DecodeError.message("expected array for \(key)")
678
+ }
679
+ return value
680
+ }
681
+
682
+ func optionalArray(_ key: String) throws -> [JSONValue]? {
683
+ guard let value = self[key] else {
684
+ return nil
685
+ }
686
+ if case .null = value {
687
+ return nil
688
+ }
689
+ guard case let .array(array) = value else {
690
+ throw DecodeError.message("expected array for \(key)")
691
+ }
692
+ return array
693
+ }
694
+
695
+ func optionalDecoded<T>(
696
+ _ key: String,
697
+ _ decoder: (JSONValue) throws -> T
698
+ ) throws -> T? {
699
+ guard let value = self[key] else {
700
+ return nil
701
+ }
702
+ if case .null = value {
703
+ return nil
704
+ }
705
+ return try decoder(value)
706
+ }
707
+ }
708
+
709
+ private enum DecodeError: Error {
710
+ case message(String)
711
+ }