@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,179 @@
1
+ import Testing
2
+ import Foundation
3
+ @testable import RemuxKit
4
+
5
+ @Suite("Protocol Models")
6
+ struct ProtocolModelsTests {
7
+
8
+ @Test("Decode auth_ok envelope")
9
+ func decodeAuthOkEnvelope() throws {
10
+ let json = """
11
+ {
12
+ "v": 1,
13
+ "type": "auth_ok",
14
+ "domain": "core",
15
+ "emittedAt": "2026-04-01T00:00:00Z",
16
+ "source": "server",
17
+ "requestId": null,
18
+ "payload": {
19
+ "capabilities": {
20
+ "envelope": true,
21
+ "inspectV2": true,
22
+ "deviceTrust": true
23
+ }
24
+ }
25
+ }
26
+ """
27
+ let data = Data(json.utf8)
28
+ let envelope = try JSONDecoder().decode(RemuxEnvelope<AuthOkPayload>.self, from: data)
29
+ #expect(envelope.domain == "core")
30
+ #expect(envelope.type == "auth_ok")
31
+ #expect(envelope.version == 1)
32
+ #expect(envelope.payload.capabilities.envelope == true)
33
+ #expect(envelope.payload.capabilities.inspectV2 == true)
34
+ #expect(envelope.payload.capabilities.deviceTrust == true)
35
+ }
36
+
37
+ @Test("Decode workspace state")
38
+ func decodeWorkspaceState() throws {
39
+ let json = """
40
+ {
41
+ "session": "main",
42
+ "tabs": [
43
+ {
44
+ "index": 0,
45
+ "name": "zsh",
46
+ "active": true,
47
+ "isFullscreen": false,
48
+ "hasBell": false,
49
+ "panes": [
50
+ {
51
+ "id": "pane-1",
52
+ "focused": true,
53
+ "title": "zsh",
54
+ "command": null,
55
+ "cwd": "/Users/test",
56
+ "rows": 24,
57
+ "cols": 80,
58
+ "x": 0,
59
+ "y": 0
60
+ }
61
+ ]
62
+ }
63
+ ],
64
+ "activeTabIndex": 0
65
+ }
66
+ """
67
+ let data = Data(json.utf8)
68
+ let state = try JSONDecoder().decode(WorkspaceState.self, from: data)
69
+ #expect(state.session == "main")
70
+ #expect(state.tabs.count == 1)
71
+ #expect(state.tabs[0].name == "zsh")
72
+ #expect(state.tabs[0].panes[0].id == "pane-1")
73
+ #expect(state.tabs[0].panes[0].cols == 80)
74
+ }
75
+
76
+ @Test("Decode inspect snapshot")
77
+ func decodeInspectSnapshot() throws {
78
+ let json = """
79
+ {
80
+ "descriptor": {
81
+ "scope": "tab",
82
+ "source": "state_tracker",
83
+ "precision": "precise",
84
+ "staleness": "fresh",
85
+ "capturedAt": "2026-04-01T00:00:00Z",
86
+ "paneId": null,
87
+ "tabIndex": 0,
88
+ "totalItems": 3
89
+ },
90
+ "items": [
91
+ {
92
+ "type": "output",
93
+ "content": "$ ls",
94
+ "lineNumber": 1,
95
+ "timestamp": "2026-04-01T00:00:00Z",
96
+ "paneId": "pane-1",
97
+ "highlights": null
98
+ }
99
+ ],
100
+ "cursor": null,
101
+ "truncated": false
102
+ }
103
+ """
104
+ let data = Data(json.utf8)
105
+ let snapshot = try JSONDecoder().decode(InspectSnapshot.self, from: data)
106
+ #expect(snapshot.descriptor.scope == "tab")
107
+ #expect(snapshot.descriptor.precision == "precise")
108
+ #expect(snapshot.items.count == 1)
109
+ #expect(snapshot.items[0].content == "$ ls")
110
+ #expect(snapshot.truncated == false)
111
+ }
112
+ }
113
+
114
+ @Suite("Message Router")
115
+ struct MessageRouterTests {
116
+
117
+ @Test("Route workspace state message")
118
+ func routeStateMessage() throws {
119
+ let router = MessageRouter()
120
+ let json = """
121
+ {"v":1,"type":"state","domain":"runtime","emittedAt":"2026-04-01T00:00:00Z","source":"server","payload":{"session":"main","tabs":[],"activeTabIndex":0}}
122
+ """
123
+ let result = router.route(json)
124
+ guard case .state(let state) = result else {
125
+ Issue.record("Expected .state, got \(String(describing: result))")
126
+ return
127
+ }
128
+ #expect(state.session == "main")
129
+ }
130
+
131
+ @Test("Route legacy state message")
132
+ func routeLegacyStateMessage() throws {
133
+ let router = MessageRouter()
134
+ let json = """
135
+ {"type":"state","session":"dev","tabs":[],"activeTabIndex":0}
136
+ """
137
+ let result = router.route(json)
138
+ guard case .state(let state) = result else {
139
+ Issue.record("Expected .state, got \(String(describing: result))")
140
+ return
141
+ }
142
+ #expect(state.session == "dev")
143
+ }
144
+
145
+ @Test("Route role_changed message")
146
+ func routeRoleChanged() throws {
147
+ let router = MessageRouter()
148
+ let json = """
149
+ {"v":1,"type":"role_changed","domain":"core","emittedAt":"","source":"server","payload":{"role":"observer"}}
150
+ """
151
+ let result = router.route(json)
152
+ guard case .roleChanged(let role) = result else {
153
+ Issue.record("Expected .roleChanged")
154
+ return
155
+ }
156
+ #expect(role == "observer")
157
+ }
158
+
159
+ @Test("Route unknown message type returns .unknown")
160
+ func routeUnknownType() throws {
161
+ let router = MessageRouter()
162
+ let json = """
163
+ {"type":"future_message","foo":"bar"}
164
+ """
165
+ let result = router.route(json)
166
+ guard case .unknown(let type, _) = result else {
167
+ Issue.record("Expected .unknown")
168
+ return
169
+ }
170
+ #expect(type == "future_message")
171
+ }
172
+
173
+ @Test("Invalid JSON returns nil")
174
+ func routeInvalidJSON() throws {
175
+ let router = MessageRouter()
176
+ let result = router.route("not json")
177
+ #expect(result == nil)
178
+ }
179
+ }
@@ -0,0 +1,62 @@
1
+ import Testing
2
+ import Foundation
3
+ @testable import RemuxKit
4
+
5
+ @Suite("RemuxState")
6
+ struct RemuxStateTests {
7
+
8
+ @Test("Initial state is disconnected")
9
+ @MainActor
10
+ func initialState() {
11
+ let state = RemuxState()
12
+ #expect(state.connectionStatus == .disconnected)
13
+ #expect(state.currentSession == "")
14
+ #expect(state.tabs.isEmpty)
15
+ #expect(state.clientRole == "active")
16
+ }
17
+
18
+ @Test("Process workspace state message")
19
+ @MainActor
20
+ func processWorkspaceState() {
21
+ let state = RemuxState()
22
+ let json = """
23
+ {"type":"state","session":"main","tabs":[{"index":0,"name":"zsh","active":true,"isFullscreen":false,"hasBell":false,"panes":[{"id":"p1","focused":true,"title":"zsh","command":null,"cwd":"/tmp","rows":24,"cols":80,"x":0,"y":0}]}],"activeTabIndex":0}
24
+ """
25
+ state.connectionDidReceiveMessage(json)
26
+ #expect(state.currentSession == "main")
27
+ #expect(state.tabs.count == 1)
28
+ #expect(state.tabs[0].name == "zsh")
29
+ }
30
+
31
+ @Test("Process role_changed message")
32
+ @MainActor
33
+ func processRoleChanged() {
34
+ let state = RemuxState()
35
+ let json = """
36
+ {"type":"role_changed","role":"observer"}
37
+ """
38
+ state.connectionDidReceiveMessage(json)
39
+ #expect(state.clientRole == "observer")
40
+ }
41
+
42
+ @Test("Process inspect_result message")
43
+ @MainActor
44
+ func processInspectResult() {
45
+ let state = RemuxState()
46
+ let json = """
47
+ {"type":"inspect_result","descriptor":{"scope":"tab","source":"state_tracker","precision":"precise","staleness":"fresh","capturedAt":"2026-04-01T00:00:00Z","paneId":null,"tabIndex":0,"totalItems":1},"items":[{"type":"output","content":"hello","lineNumber":1,"timestamp":"2026-04-01T00:00:00Z","paneId":"p1","highlights":null}],"cursor":null,"truncated":false}
48
+ """
49
+ state.connectionDidReceiveMessage(json)
50
+ #expect(state.inspectSnapshot != nil)
51
+ #expect(state.inspectSnapshot?.items.count == 1)
52
+ #expect(state.inspectSnapshot?.items[0].content == "hello")
53
+ }
54
+
55
+ @Test("ConnectionStatus is Equatable")
56
+ func statusEquatable() {
57
+ #expect(ConnectionStatus.connected == ConnectionStatus.connected)
58
+ #expect(ConnectionStatus.disconnected != ConnectionStatus.connected)
59
+ #expect(ConnectionStatus.reconnecting(attempt: 1) == ConnectionStatus.reconnecting(attempt: 1))
60
+ #expect(ConnectionStatus.reconnecting(attempt: 1) != ConnectionStatus.reconnecting(attempt: 2))
61
+ }
62
+ }
@@ -0,0 +1,17 @@
1
+ import { defineConfig } from "@playwright/test";
2
+
3
+ export default defineConfig({
4
+ testDir: "tests/e2e",
5
+ testIgnore: ["tests/e2e/legacy/**"],
6
+ timeout: 30_000,
7
+ fullyParallel: false,
8
+ workers: 1,
9
+ use: {
10
+ browserName: "chromium",
11
+ headless: true,
12
+ viewport: { width: 1280, height: 720 },
13
+ trace: "retain-on-failure",
14
+ screenshot: "only-on-failure",
15
+ video: "retain-on-failure",
16
+ },
17
+ });