Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
a1950e8
docs: design secure remote desktop
codepilotios Jun 27, 2026
c24de64
docs: plan secure remote desktop
codepilotios Jun 27, 2026
b713427
chore: ignore local worktrees
codepilotios Jun 27, 2026
ce1876e
build: add remote desktop wire contracts
codepilotios Jun 27, 2026
375c834
fix: finalize remote desktop lease revocation
codepilotios Jun 27, 2026
6dd2d36
feat: add remote desktop native IPC
codepilotios Jun 27, 2026
807be31
fix: harden remote desktop native IPC
codepilotios Jun 27, 2026
c4575b0
feat: add remote desktop gateway APIs
codepilotios Jun 27, 2026
cf61d84
feat: add remote desktop trust management UI
codepilotios Jun 27, 2026
17f79a8
feat: stream and control the Mac desktop
codepilotios Jun 27, 2026
a98048e
feat: pair trusted CodePilot devices
codepilotios Jun 27, 2026
237c3ba
feat: add iPhone remote desktop controls
codepilotios Jun 27, 2026
54a3e88
feat: harden remote desktop sessions
codepilotios Jun 27, 2026
5183761
docs: specify WebRTC remote desktop transport
codepilotios Jun 28, 2026
0ade8bf
feat: add secure remote desktop foundation
codepilotios Jun 28, 2026
bbe8fa6
feat: follow remote cursor while zoomed
codepilotios Jun 28, 2026
7433da3
feat: stream display frames with ScreenCaptureKit
codepilotios Jun 28, 2026
1eab830
feat: negotiate Mac WebRTC remote desktop peer
codepilotios Jun 28, 2026
29db0ff
feat: render WebRTC remote desktop video on iOS
codepilotios Jun 28, 2026
d21b07b
feat: send remote desktop input over WebRTC
codepilotios Jun 28, 2026
f7f6868
fix: smooth remote desktop cursor overlay
codepilotios Jun 28, 2026
f073ee4
fix: align remote cursor coordinate space
codepilotios Jun 28, 2026
97ae3fe
fix: sync remote cursor from host
codepilotios Jun 28, 2026
1b60864
fix: align remote cursor hotspot
codepilotios Jun 28, 2026
1cf93c0
fix: scale remote pointer deltas by viewport
codepilotios Jun 28, 2026
0901b33
fix: use host cursor for remote desktop video
codepilotios Jun 28, 2026
b71c802
fix: remove remote desktop cursor overlay
codepilotios Jun 28, 2026
1e26028
fix: show host cursor marker
codepilotios Jun 28, 2026
635154a
fix: preserve remote input ordering
codepilotios Jun 28, 2026
453ccb0
fix: click at remote cursor marker
codepilotios Jun 28, 2026
e431a17
fix: align click injection with remote cursor
codepilotios Jun 28, 2026
30edfee
fix: match WebRTC video layout to viewport
codepilotios Jun 28, 2026
d1ed054
fix: align remote desktop cursor marker
codepilotios Jun 28, 2026
42711c2
fix: lower remote desktop cursor marker
codepilotios Jun 28, 2026
e7490f9
fix: preserve chat message line breaks
codepilotios Jun 28, 2026
7474bb5
feat: open Mac localhost URLs in iOS app
codepilotios Jun 28, 2026
c6b2969
docs: specify CodePilot production readiness
codepilotios Jun 28, 2026
85939e9
docs: plan CodePilot production readiness work
codepilotios Jun 28, 2026
fee4f5e
feat: expose stable gateway health and errors
codepilotios Jun 28, 2026
7661c80
feat: clarify CodePilot setup and gateway operations
codepilotios Jun 28, 2026
24bd6a6
feat: add CodePilot iOS connection setup
codepilotios Jun 28, 2026
52951f6
docs: prepare CodePilot public beta guides
codepilotios Jun 28, 2026
42fa143
test: isolate gateway tests from local account state
codepilotios Jun 28, 2026
5735508
docs: specify Cloudflare setup wizard
codepilotios Jun 29, 2026
be5e971
docs: plan Cloudflare setup wizard
codepilotios Jun 30, 2026
89949da
feat: add Cloudflare setup automation
codepilotios Jul 1, 2026
a575e4b
feat: model Cloudflare setup state
codepilotios Jul 1, 2026
b181f77
feat: add Cloudflare setup wizard
codepilotios Jul 1, 2026
1ef553b
docs: document Cloudflare setup wizard
codepilotios Jul 1, 2026
c783dff
docs: plan launch agent system
codepilotios Jul 1, 2026
85c6bcb
fix: open markdown file links in codepilot app
codepilotios Jul 1, 2026
a36684e
fix: add dismiss controls to file preview
codepilotios Jul 1, 2026
5b3346b
docs: record codepilot launch decisions
codepilotios Jul 1, 2026
1fdcb7a
chore: add codepilot agent fallback runner
codepilotios Jul 1, 2026
b1dbaae
Guard Cloudflare tunnel setup without ID
codepilotios Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ remote-projects-*.js
# Generated OTA artifacts
ios/CodexPhone/build/
ios/CodexPhone/fastlane/report.xml
.worktrees/

# macOS
.DS_Store
14 changes: 14 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ let package = Package(
platforms: [
.macOS(.v13)
],
dependencies: [
.package(url: "https://github.com/livekit/webrtc-xcframework-static.git", exact: "144.7559.10")
],
targets: [
.executableTarget(
name: "CodexAccountSwitcher"
name: "CodexAccountSwitcher",
dependencies: [
.product(name: "LiveKitWebRTC", package: "webrtc-xcframework-static")
]
),
.testTarget(
name: "CodexAccountSwitcherTests",
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ See:
- [Cloudflare Setup](docs/CLOUDFLARE_SETUP.md)
- [Security](docs/SECURITY.md)
- [Architecture](docs/ARCHITECTURE.md)
- [Troubleshooting](docs/TROUBLESHOOTING.md)
- [Release Checklist](docs/RELEASE_CHECKLIST.md)

## Features

Expand All @@ -76,4 +78,3 @@ Screenshots will be added before the first public release.
## Public Release Status

CodePilot is being prepared for broader publishing. Expect some internal names, bundle identifiers, LaunchAgent labels, and local state paths to still contain legacy Codex-specific names during the first migration phase.

182 changes: 182 additions & 0 deletions Sources/CodexAccountSwitcher/RemoteDesktop/InputInjector.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import CoreGraphics
import Foundation

enum DisplayCoordinateMapper {
static func map(x: Double, y: Double, into frame: CGRect) -> CGPoint {
let clampedX = min(1, max(0, x))
let clampedY = min(1, max(0, y))
return CGPoint(
x: frame.minX + frame.width * clampedX,
y: frame.minY + frame.height * clampedY
)
}
}

protocol RemoteInputLeaseValidating: AnyObject {
func validateInput(sessionID: String, sequence: UInt64) throws
}

protocol RemoteInputPosting: AnyObject {
func currentPointerPosition() -> CGPoint
func movePointer(to point: CGPoint)
func buttonDown(_ button: Int, at point: CGPoint)
func buttonUp(_ button: Int, at point: CGPoint)
func scroll(deltaX: Double, deltaY: Double)
func keyDown(_ keyCode: UInt16)
func keyUp(_ keyCode: UInt16)
func text(_ text: String)
}

final class RemoteInputInjector {
private let validator: RemoteInputLeaseValidating
private let sink: RemoteInputPosting
private var pressedKeys = Set<UInt16>()
private var pressedButtons: [Int: CGPoint] = [:]

init(validator: RemoteInputLeaseValidating, sink: RemoteInputPosting = CGEventRemoteInputSink()) {
self.validator = validator
self.sink = sink
}

func handle(_ event: RemoteInputEvent, displayFrame: CGRect) throws {
try validator.validateInput(sessionID: event.sessionId, sequence: event.sequence)

switch event.kind {
case .pointer:
if let deltaX = event.deltaX, let deltaY = event.deltaY, event.x == nil, event.y == nil {
let current = sink.currentPointerPosition()
sink.movePointer(to: CGPoint(
x: min(displayFrame.maxX, max(displayFrame.minX, current.x + deltaX)),
y: min(displayFrame.maxY, max(displayFrame.minY, current.y + deltaY))
))
} else {
sink.movePointer(to: try point(for: event, displayFrame: displayFrame))
}
case .buttonDown:
let button = event.button ?? 0
let point = try pointOrCurrent(for: event, displayFrame: displayFrame)
if event.x != nil || event.y != nil {
sink.movePointer(to: point)
}
pressedButtons[button] = point
sink.buttonDown(button, at: point)
case .buttonUp:
let button = event.button ?? 0
let point = try pointOrCurrent(for: event, displayFrame: displayFrame)
if event.x != nil || event.y != nil {
sink.movePointer(to: point)
}
pressedButtons.removeValue(forKey: button)
sink.buttonUp(button, at: point)
case .scroll:
sink.scroll(deltaX: event.deltaX ?? 0, deltaY: event.deltaY ?? 0)
case .keyDown:
guard let keyCode = event.keyCode else { throw RemoteDesktopSecurityError.invalidSignature }
pressedKeys.insert(keyCode)
sink.keyDown(keyCode)
case .keyUp:
guard let keyCode = event.keyCode else { throw RemoteDesktopSecurityError.invalidSignature }
pressedKeys.remove(keyCode)
sink.keyUp(keyCode)
case .text:
guard let text = event.text else { throw RemoteDesktopSecurityError.invalidSignature }
sink.text(text)
}
}

func releaseAll() {
for key in pressedKeys.sorted() {
sink.keyUp(key)
}
pressedKeys.removeAll()

for button in pressedButtons.keys.sorted() {
sink.buttonUp(button, at: pressedButtons[button] ?? .zero)
}
pressedButtons.removeAll()
}

private func point(for event: RemoteInputEvent, displayFrame: CGRect) throws -> CGPoint {
guard let x = event.x, let y = event.y else {
throw RemoteDesktopSecurityError.invalidSignature
}
return DisplayCoordinateMapper.map(x: x, y: y, into: displayFrame)
}

private func pointOrCurrent(for event: RemoteInputEvent, displayFrame: CGRect) throws -> CGPoint {
if event.x == nil, event.y == nil {
return sink.currentPointerPosition()
}
return try point(for: event, displayFrame: displayFrame)
}
}

final class CGEventRemoteInputSink: RemoteInputPosting {
func currentPointerPosition() -> CGPoint {
CGEvent(source: nil)?.location ?? .zero
}

func movePointer(to point: CGPoint) {
CGEvent(mouseEventSource: nil, mouseType: .mouseMoved, mouseCursorPosition: point, mouseButton: .left)?
.post(tap: .cghidEventTap)
}

func buttonDown(_ button: Int, at point: CGPoint) {
CGEvent(mouseEventSource: nil, mouseType: mouseType(for: button, down: true), mouseCursorPosition: point, mouseButton: mouseButton(for: button))?
.post(tap: .cghidEventTap)
}

func buttonUp(_ button: Int, at point: CGPoint) {
CGEvent(mouseEventSource: nil, mouseType: mouseType(for: button, down: false), mouseCursorPosition: point, mouseButton: mouseButton(for: button))?
.post(tap: .cghidEventTap)
}

func scroll(deltaX: Double, deltaY: Double) {
CGEvent(
scrollWheelEvent2Source: nil,
units: .pixel,
wheelCount: 2,
wheel1: Int32(deltaY),
wheel2: Int32(deltaX),
wheel3: 0
)?.post(tap: .cghidEventTap)
}

func keyDown(_ keyCode: UInt16) {
CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: true)?
.post(tap: .cghidEventTap)
}

func keyUp(_ keyCode: UInt16) {
CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: false)?
.post(tap: .cghidEventTap)
}

func text(_ text: String) {
let utf16 = Array(text.utf16)
let event = CGEvent(keyboardEventSource: nil, virtualKey: 0, keyDown: true)
utf16.withUnsafeBufferPointer { buffer in
event?.keyboardSetUnicodeString(stringLength: utf16.count, unicodeString: buffer.baseAddress)
}
event?.post(tap: .cghidEventTap)
}

private func mouseButton(for button: Int) -> CGMouseButton {
switch button {
case 1: return .right
case 2: return .center
default: return .left
}
}

private func mouseType(for button: Int, down: Bool) -> CGEventType {
switch mouseButton(for: button) {
case .right:
return down ? .rightMouseDown : .rightMouseUp
case .center:
return down ? .otherMouseDown : .otherMouseUp
default:
return down ? .leftMouseDown : .leftMouseUp
}
}
}
Loading
Loading