@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,55 @@
1
+ import SwiftUI
2
+ import RemuxKit
3
+
4
+ /// Manual connection entry: URL + token.
5
+ struct ManualConnectView: View {
6
+ @Environment(RemuxState.self) private var state
7
+ @Environment(\.dismiss) private var dismiss
8
+ @State private var serverURL = ""
9
+ @State private var token = ""
10
+ @State private var errorMessage: String?
11
+
12
+ var body: some View {
13
+ NavigationStack {
14
+ Form {
15
+ Section("Server") {
16
+ TextField("URL (e.g. http://192.168.1.100:8767)", text: $serverURL)
17
+ .textContentType(.URL)
18
+ .autocorrectionDisabled()
19
+ .textInputAutocapitalization(.never)
20
+
21
+ SecureField("Token", text: $token)
22
+ }
23
+
24
+ if let error = errorMessage {
25
+ Section {
26
+ Text(error)
27
+ .foregroundStyle(.red)
28
+ }
29
+ }
30
+ }
31
+ .navigationTitle("Connect")
32
+ .navigationBarTitleDisplayMode(.inline)
33
+ .toolbar {
34
+ ToolbarItem(placement: .cancellationAction) {
35
+ Button("Cancel") { dismiss() }
36
+ }
37
+ ToolbarItem(placement: .confirmationAction) {
38
+ Button("Connect") { connect() }
39
+ .disabled(serverURL.isEmpty || token.isEmpty)
40
+ }
41
+ }
42
+ }
43
+ }
44
+
45
+ private func connect() {
46
+ guard let url = URL(string: serverURL) else {
47
+ errorMessage = "Invalid URL"
48
+ return
49
+ }
50
+ let keychain = KeychainStore()
51
+ try? keychain.saveServerToken(token, forServer: serverURL)
52
+ state.connect(url: url, credential: .token(token))
53
+ dismiss()
54
+ }
55
+ }
@@ -0,0 +1,70 @@
1
+ import SwiftUI
2
+ import RemuxKit
3
+
4
+ /// First-launch onboarding: scan QR or manual input.
5
+ struct OnboardingView: View {
6
+ @Environment(RemuxState.self) private var state
7
+ @State private var showScanner = false
8
+ @State private var showManualInput = false
9
+
10
+ var body: some View {
11
+ NavigationStack {
12
+ VStack(spacing: 32) {
13
+ Spacer()
14
+
15
+ Image(systemName: "terminal")
16
+ .font(.system(size: 64))
17
+ .foregroundStyle(.tint)
18
+
19
+ Text("Remux")
20
+ .font(.largeTitle.bold())
21
+
22
+ Text("Remote terminal workspace")
23
+ .font(.subheadline)
24
+ .foregroundStyle(.secondary)
25
+
26
+ Spacer()
27
+
28
+ VStack(spacing: 16) {
29
+ Button(action: { showScanner = true }) {
30
+ Label("Scan QR Code", systemImage: "qrcode.viewfinder")
31
+ .frame(maxWidth: .infinity)
32
+ }
33
+ .buttonStyle(.borderedProminent)
34
+ .controlSize(.large)
35
+
36
+ Button(action: { showManualInput = true }) {
37
+ Label("Enter Manually", systemImage: "keyboard")
38
+ .frame(maxWidth: .infinity)
39
+ }
40
+ .buttonStyle(.bordered)
41
+ .controlSize(.large)
42
+ }
43
+ .padding(.horizontal, 40)
44
+
45
+ Spacer()
46
+ }
47
+ .sheet(isPresented: $showScanner) {
48
+ QRScannerView { payload in
49
+ showScanner = false
50
+ handleQRPayload(payload)
51
+ }
52
+ }
53
+ .sheet(isPresented: $showManualInput) {
54
+ ManualConnectView()
55
+ }
56
+ }
57
+ }
58
+
59
+ private func handleQRPayload(_ json: String) {
60
+ guard let data = json.data(using: .utf8),
61
+ let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
62
+ let urlStr = dict["url"] as? String,
63
+ let token = dict["token"] as? String,
64
+ let url = URL(string: urlStr) else { return }
65
+
66
+ let keychain = KeychainStore()
67
+ try? keychain.saveServerToken(token, forServer: urlStr)
68
+ state.connect(url: url, credential: .token(token))
69
+ }
70
+ }
@@ -0,0 +1,92 @@
1
+ import SwiftUI
2
+ import AVFoundation
3
+
4
+ /// Camera QR code scanner for pairing.
5
+ struct QRScannerView: UIViewControllerRepresentable {
6
+ let onScanned: (String) -> Void
7
+
8
+ func makeUIViewController(context: Context) -> QRScannerController {
9
+ let controller = QRScannerController()
10
+ controller.onScanned = onScanned
11
+ return controller
12
+ }
13
+
14
+ func updateUIViewController(_ uiViewController: QRScannerController, context: Context) {}
15
+ }
16
+
17
+ final class QRScannerController: UIViewController, @preconcurrency AVCaptureMetadataOutputObjectsDelegate {
18
+ var onScanned: ((String) -> Void)?
19
+ private var captureSession: AVCaptureSession?
20
+ private var hasScanned = false
21
+
22
+ override func viewDidLoad() {
23
+ super.viewDidLoad()
24
+ view.backgroundColor = .black
25
+ setupCamera()
26
+ }
27
+
28
+ private func setupCamera() {
29
+ let session = AVCaptureSession()
30
+
31
+ guard let device = AVCaptureDevice.default(for: .video),
32
+ let input = try? AVCaptureDeviceInput(device: device) else {
33
+ showError("Camera not available")
34
+ return
35
+ }
36
+
37
+ session.addInput(input)
38
+
39
+ let output = AVCaptureMetadataOutput()
40
+ session.addOutput(output)
41
+ output.setMetadataObjectsDelegate(self, queue: .main)
42
+ output.metadataObjectTypes = [.qr]
43
+
44
+ let previewLayer = AVCaptureVideoPreviewLayer(session: session)
45
+ previewLayer.frame = view.bounds
46
+ previewLayer.videoGravity = .resizeAspectFill
47
+ view.layer.addSublayer(previewLayer)
48
+
49
+ captureSession = session
50
+
51
+ // Overlay instruction
52
+ let label = UILabel()
53
+ label.text = "Scan the QR code shown by your Remux server"
54
+ label.textColor = .white
55
+ label.font = .systemFont(ofSize: 14)
56
+ label.textAlignment = .center
57
+ label.translatesAutoresizingMaskIntoConstraints = false
58
+ view.addSubview(label)
59
+ NSLayoutConstraint.activate([
60
+ label.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40),
61
+ label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
62
+ ])
63
+
64
+ DispatchQueue.global(qos: .userInitiated).async {
65
+ session.startRunning()
66
+ }
67
+ }
68
+
69
+ func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
70
+ guard !hasScanned,
71
+ let object = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
72
+ let value = object.stringValue else { return }
73
+ hasScanned = true
74
+ captureSession?.stopRunning()
75
+
76
+ // Haptic feedback
77
+ UIImpactFeedbackGenerator(style: .medium).impactOccurred()
78
+
79
+ dismiss(animated: true) { [weak self] in
80
+ self?.onScanned?(value)
81
+ }
82
+ }
83
+
84
+ private func showError(_ message: String) {
85
+ let label = UILabel()
86
+ label.text = message
87
+ label.textColor = .white
88
+ label.textAlignment = .center
89
+ label.frame = view.bounds
90
+ view.addSubview(label)
91
+ }
92
+ }
@@ -0,0 +1,136 @@
1
+ import SwiftUI
2
+ import RemuxKit
3
+ import LocalAuthentication
4
+
5
+ /// Me tab: server management, devices, settings.
6
+ struct MeView: View {
7
+ @Environment(RemuxState.self) private var state
8
+ @AppStorage("faceIdEnabled") private var faceIdEnabled = false
9
+ private let keychain = KeychainStore()
10
+
11
+ var body: some View {
12
+ NavigationStack {
13
+ List {
14
+ // Connection
15
+ Section("Connection") {
16
+ HStack {
17
+ Circle()
18
+ .fill(state.connectionStatus.color)
19
+ .frame(width: 8, height: 8)
20
+ Text(state.connectionStatus.text)
21
+ Spacer()
22
+ if let url = state.serverURL {
23
+ Text(url.host ?? "")
24
+ .font(.caption)
25
+ .foregroundStyle(.secondary)
26
+ }
27
+ }
28
+
29
+ if case .connected = state.connectionStatus {
30
+ Button("Disconnect", role: .destructive) {
31
+ state.disconnect()
32
+ }
33
+ }
34
+ }
35
+
36
+ // Saved servers
37
+ Section("Servers") {
38
+ ForEach(keychain.savedServers(), id: \.self) { server in
39
+ HStack {
40
+ Image(systemName: "server.rack")
41
+ Text(server)
42
+ .font(.subheadline)
43
+ Spacer()
44
+ if state.serverURL?.absoluteString == server {
45
+ Image(systemName: "checkmark")
46
+ .foregroundStyle(.tint)
47
+ }
48
+ }
49
+ .swipeActions {
50
+ Button(role: .destructive) {
51
+ keychain.deleteAll(forServer: server)
52
+ } label: {
53
+ Label("Delete", systemImage: "trash")
54
+ }
55
+ }
56
+ }
57
+ }
58
+
59
+ // Devices
60
+ if !state.devices.isEmpty {
61
+ Section("Devices") {
62
+ ForEach(state.devices, id: \.id) { device in
63
+ HStack {
64
+ Image(systemName: deviceIcon(device.platform))
65
+ VStack(alignment: .leading) {
66
+ Text(device.name ?? device.id.prefix(8).description)
67
+ .font(.subheadline)
68
+ Text(device.trustLevel)
69
+ .font(.caption)
70
+ .foregroundStyle(device.trustLevel == "trusted" ? .green : .orange)
71
+ }
72
+ Spacer()
73
+ if let lastSeen = device.lastSeen {
74
+ Text(lastSeen.prefix(10).description)
75
+ .font(.caption2)
76
+ .foregroundStyle(.tertiary)
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ // Settings
84
+ Section("Security") {
85
+ Toggle("Face ID / Touch ID", isOn: $faceIdEnabled)
86
+ }
87
+
88
+ // App info
89
+ Section("About") {
90
+ HStack {
91
+ Text("Version")
92
+ Spacer()
93
+ Text(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.3.5")
94
+ .foregroundStyle(.secondary)
95
+ }
96
+ }
97
+ }
98
+ .navigationTitle("Me")
99
+ .onAppear {
100
+ // Request device list
101
+ state.sendJSON(["type": "list_devices"])
102
+ }
103
+ }
104
+ }
105
+
106
+ private func deviceIcon(_ platform: String?) -> String {
107
+ switch platform {
108
+ case "ios": "iphone"
109
+ case "macos": "desktopcomputer"
110
+ case "android": "apps.iphone"
111
+ case "web": "globe"
112
+ default: "questionmark.circle"
113
+ }
114
+ }
115
+ }
116
+
117
+ extension ConnectionStatus {
118
+ var color: Color {
119
+ switch self {
120
+ case .connected: .green
121
+ case .reconnecting: .yellow
122
+ case .connecting, .authenticating: .orange
123
+ case .disconnected: .red
124
+ }
125
+ }
126
+
127
+ var text: String {
128
+ switch self {
129
+ case .connected: "Connected"
130
+ case .reconnecting(let n): "Reconnecting (\(n))..."
131
+ case .connecting: "Connecting..."
132
+ case .authenticating: "Authenticating..."
133
+ case .disconnected: "Disconnected"
134
+ }
135
+ }
136
+ }
@@ -0,0 +1,37 @@
1
+ // swift-tools-version: 6.0
2
+
3
+ import PackageDescription
4
+
5
+ let package = Package(
6
+ name: "Remux",
7
+ platforms: [.macOS(.v14)],
8
+ dependencies: [
9
+ .package(path: "../../packages/RemuxKit"),
10
+ ],
11
+ targets: [
12
+ .executableTarget(
13
+ name: "Remux",
14
+ dependencies: ["RemuxKit", "GhosttyKit"],
15
+ path: "Sources/Remux",
16
+ linkerSettings: [
17
+ .linkedFramework("Cocoa"),
18
+ .linkedFramework("Metal"),
19
+ .linkedFramework("MetalKit"),
20
+ .linkedFramework("QuartzCore"),
21
+ .linkedFramework("Carbon"),
22
+ .linkedFramework("CoreText"),
23
+ .linkedFramework("CoreGraphics"),
24
+ .linkedFramework("Foundation"),
25
+ .linkedFramework("IOKit"),
26
+ .linkedFramework("IOSurface"),
27
+ .linkedFramework("UniformTypeIdentifiers"),
28
+ .linkedLibrary("c++"),
29
+ .linkedLibrary("z"),
30
+ ]
31
+ ),
32
+ .binaryTarget(
33
+ name: "GhosttyKit",
34
+ path: "../../vendor/ghostty/macos/GhosttyKit.xcframework"
35
+ ),
36
+ ]
37
+ )