@lattices/cli 0.4.10 → 0.4.11

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 (201) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +13 -13
  3. package/{app → apps/mac}/Lattices.app/Contents/Info.plist +10 -2
  4. package/{app → apps/mac}/Lattices.app/Contents/MacOS/Lattices +0 -0
  5. package/{app → apps/mac}/Package.swift +2 -1
  6. package/apps/mac/Resources/Pets/assistant-spark/pet.json +62 -0
  7. package/apps/mac/Resources/Pets/assistant-spark/spritesheet.webp +0 -0
  8. package/apps/mac/Resources/Pets/scout-ranger/pet.json +6 -0
  9. package/apps/mac/Resources/Pets/scout-ranger/spritesheet.webp +0 -0
  10. package/apps/mac/Sources/AppShell/AppActivationCoordinator.swift +27 -0
  11. package/apps/mac/Sources/AppShell/AppDelegate.swift +189 -0
  12. package/apps/mac/Sources/AppShell/AppServicesBootstrap.swift +25 -0
  13. package/{app → apps/mac}/Sources/AppShell/AppShellView.swift +18 -3
  14. package/{app → apps/mac}/Sources/AppShell/AppUpdater.swift +4 -3
  15. package/apps/mac/Sources/AppShell/HotkeyBootstrap.swift +87 -0
  16. package/{app → apps/mac}/Sources/AppShell/LatticesRuntime.swift +43 -0
  17. package/{app → apps/mac}/Sources/AppShell/MainView.swift +116 -63
  18. package/apps/mac/Sources/AppShell/MenuBarController.swift +177 -0
  19. package/{app → apps/mac}/Sources/AppShell/OnboardingView.swift +72 -60
  20. package/apps/mac/Sources/AppShell/PermissionsAssistantView.swift +366 -0
  21. package/apps/mac/Sources/AppShell/PermissionsAssistantWindow.swift +70 -0
  22. package/{app → apps/mac}/Sources/AppShell/Preferences.swift +37 -2
  23. package/{app → apps/mac}/Sources/AppShell/SettingsView.swift +815 -156
  24. package/{app → apps/mac}/Sources/AppShell/SettingsWindow.swift +10 -0
  25. package/apps/mac/Sources/AppShell/WorkspaceInspectorPresenter.swift +13 -0
  26. package/{app → apps/mac}/Sources/Core/Actions/HotkeyStore.swift +6 -1
  27. package/{app → apps/mac}/Sources/Core/Actions/IntentEngine.swift +2 -0
  28. package/{app → apps/mac}/Sources/Core/Daemon/DaemonServer.swift +5 -0
  29. package/{app → apps/mac}/Sources/Core/Daemon/LatticesApi.swift +365 -0
  30. package/{app → apps/mac}/Sources/Core/Desktop/OcrModel.swift +17 -13
  31. package/apps/mac/Sources/Core/Desktop/WindowCapture.swift +33 -0
  32. package/{app → apps/mac}/Sources/Core/Desktop/WindowDragSnapController.swift +18 -217
  33. package/{app → apps/mac}/Sources/Core/Desktop/WindowPreviewStore.swift +4 -5
  34. package/{app → apps/mac}/Sources/Core/Desktop/WindowTiler.swift +19 -13
  35. package/apps/mac/Sources/Core/Input/EventTapBreaker.swift +124 -0
  36. package/apps/mac/Sources/Core/Input/EventTapThread.swift +54 -0
  37. package/apps/mac/Sources/Core/Input/InputCaptureResetCenter.swift +20 -0
  38. package/apps/mac/Sources/Core/Input/KeyboardRemapController.swift +335 -0
  39. package/apps/mac/Sources/Core/Input/KeyboardRemapStore.swift +141 -0
  40. package/{app → apps/mac}/Sources/Core/Input/MouseGestureConfig.swift +155 -20
  41. package/apps/mac/Sources/Core/Input/MouseGestureController.swift +2259 -0
  42. package/apps/mac/Sources/Core/Input/MouseShortcutStore.swift +170 -0
  43. package/apps/mac/Sources/Core/Input/SecureEventInputMonitor.swift +39 -0
  44. package/apps/mac/Sources/Core/Input/ShapeRecognizer.swift +624 -0
  45. package/apps/mac/Sources/Core/Input/TapBudgetMeter.swift +56 -0
  46. package/{app → apps/mac}/Sources/Core/Overlays/ScreenMap/ScreenMapState.swift +8 -8
  47. package/apps/mac/Sources/Core/Overlays/ScreenOverlayCanvasController.swift +1240 -0
  48. package/{app → apps/mac}/Sources/Core/Overlays/Voice/VoiceCommandWindow.swift +11 -23
  49. package/{app → apps/mac}/Sources/Core/Pi/PiChatDock.swift +90 -43
  50. package/{app → apps/mac}/Sources/Core/Pi/PiChatSession.swift +676 -43
  51. package/{app → apps/mac}/Sources/Core/Pi/PiProviderSetupCallout.swift +5 -5
  52. package/{app → apps/mac}/Sources/Core/Pi/PiWorkspaceView.swift +93 -44
  53. package/apps/mac/Sources/Core/System/Capability.swift +79 -0
  54. package/{app → apps/mac}/Sources/Core/System/PermissionChecker.swift +43 -8
  55. package/{app → apps/mac}/Sources/Core/Voice/AudioProvider.swift +225 -56
  56. package/bin/handsoff-infer.ts +14 -5
  57. package/bin/handsoff-worker.ts +11 -7
  58. package/bin/infer.ts +406 -0
  59. package/bin/lattices-app.ts +57 -7
  60. package/bin/lattices-dev +40 -1
  61. package/bin/lattices.ts +1 -1
  62. package/docs/agent-execution-plan.md +9 -9
  63. package/docs/api.md +119 -0
  64. package/docs/app.md +1 -0
  65. package/docs/companion-deck.md +1 -1
  66. package/docs/gesture-customization-proposal.md +520 -0
  67. package/docs/mouse-gestures.md +79 -0
  68. package/docs/overview.md +2 -2
  69. package/docs/presentation-execution-review.md +9 -9
  70. package/docs/proposals/LAT-001-gesture-visual-customization.md +522 -0
  71. package/docs/proposals/LAT-002-shared-overlay-canvas.md +353 -0
  72. package/docs/proposals/LAT-003-menu-bar-controller-architecture.md +291 -0
  73. package/docs/proposals/LAT-004-interactive-overlay-actors.md +534 -0
  74. package/docs/reference/dewey.config.ts +74 -0
  75. package/docs/reference/install-agent.md +79 -0
  76. package/docs/repo-structure.md +100 -0
  77. package/docs/voice-error-model.md +7 -7
  78. package/docs/voice.md +18 -0
  79. package/package.json +23 -13
  80. package/swift/Package.swift +20 -0
  81. package/swift/Sources/DeckKit/DeckAction.swift +51 -0
  82. package/swift/Sources/DeckKit/DeckBridgeSecurity.swift +152 -0
  83. package/swift/Sources/DeckKit/DeckCockpit.swift +82 -0
  84. package/swift/Sources/DeckKit/DeckHost.swift +7 -0
  85. package/swift/Sources/DeckKit/DeckManifest.swift +145 -0
  86. package/swift/Sources/DeckKit/DeckRuntimeSnapshot.swift +533 -0
  87. package/swift/Sources/DeckKit/DeckTrackpad.swift +63 -0
  88. package/swift/Sources/DeckKit/DeckValue.swift +93 -0
  89. package/swift/Sources/DeckKit/DeckVoiceError.swift +88 -0
  90. package/swift/Tests/DeckKitTests/DeckKitTests.swift +286 -0
  91. package/app/Sources/AppShell/AppDelegate.swift +0 -408
  92. package/app/Sources/Core/Input/KeyboardRemapController.swift +0 -184
  93. package/app/Sources/Core/Input/KeyboardRemapStore.swift +0 -84
  94. package/app/Sources/Core/Input/MouseGestureController.swift +0 -1203
  95. package/app/Sources/Core/Input/MouseShortcutStore.swift +0 -107
  96. /package/{app → apps/mac}/Info.plist +0 -0
  97. /package/{app → apps/mac}/Lattices.app/Contents/Resources/AppIcon.icns +0 -0
  98. /package/{app → apps/mac}/Lattices.app/Contents/Resources/tap.wav +0 -0
  99. /package/{app → apps/mac}/Lattices.app/Contents/_CodeSignature/CodeResources +0 -0
  100. /package/{app → apps/mac}/Lattices.entitlements +0 -0
  101. /package/{app → apps/mac}/Resources/tap.wav +0 -0
  102. /package/{app → apps/mac}/Sources/AppShell/App.swift +0 -0
  103. /package/{app → apps/mac}/Sources/AppShell/CliActionLauncher.swift +0 -0
  104. /package/{app → apps/mac}/Sources/AppShell/HomeDashboardView.swift +0 -0
  105. /package/{app → apps/mac}/Sources/AppShell/KeyRecorderView.swift +0 -0
  106. /package/{app → apps/mac}/Sources/AppShell/MainWindow.swift +0 -0
  107. /package/{app → apps/mac}/Sources/Core/Actions/HotkeyManager.swift +0 -0
  108. /package/{app → apps/mac}/Sources/Core/Actions/IntentSchema.swift +0 -0
  109. /package/{app → apps/mac}/Sources/Core/Actions/Intents/CreateLayerIntent.swift +0 -0
  110. /package/{app → apps/mac}/Sources/Core/Actions/Intents/DistributeIntent.swift +0 -0
  111. /package/{app → apps/mac}/Sources/Core/Actions/Intents/FocusIntent.swift +0 -0
  112. /package/{app → apps/mac}/Sources/Core/Actions/Intents/HelpIntent.swift +0 -0
  113. /package/{app → apps/mac}/Sources/Core/Actions/Intents/KillIntent.swift +0 -0
  114. /package/{app → apps/mac}/Sources/Core/Actions/Intents/LatticeIntent.swift +0 -0
  115. /package/{app → apps/mac}/Sources/Core/Actions/Intents/LaunchIntent.swift +0 -0
  116. /package/{app → apps/mac}/Sources/Core/Actions/Intents/ListSessionsIntent.swift +0 -0
  117. /package/{app → apps/mac}/Sources/Core/Actions/Intents/ListWindowsIntent.swift +0 -0
  118. /package/{app → apps/mac}/Sources/Core/Actions/Intents/ScanIntent.swift +0 -0
  119. /package/{app → apps/mac}/Sources/Core/Actions/Intents/SearchIntent.swift +0 -0
  120. /package/{app → apps/mac}/Sources/Core/Actions/Intents/SwitchLayerIntent.swift +0 -0
  121. /package/{app → apps/mac}/Sources/Core/Actions/Intents/TileIntent.swift +0 -0
  122. /package/{app → apps/mac}/Sources/Core/Actions/PaletteCommand.swift +0 -0
  123. /package/{app → apps/mac}/Sources/Core/Actions/VoiceIntentResolver.swift +0 -0
  124. /package/{app → apps/mac}/Sources/Core/Companion/CompanionActivityLog.swift +0 -0
  125. /package/{app → apps/mac}/Sources/Core/Companion/CompanionKeyboardController.swift +0 -0
  126. /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +0 -0
  127. /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionCockpit.swift +0 -0
  128. /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +0 -0
  129. /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +0 -0
  130. /package/{app → apps/mac}/Sources/Core/Companion/LatticesDeckHost.swift +0 -0
  131. /package/{app → apps/mac}/Sources/Core/Daemon/DaemonProtocol.swift +0 -0
  132. /package/{app → apps/mac}/Sources/Core/Desktop/AccessibilityTextExtractor.swift +0 -0
  133. /package/{app → apps/mac}/Sources/Core/Desktop/AppTypeClassifier.swift +0 -0
  134. /package/{app → apps/mac}/Sources/Core/Desktop/DesktopModel.swift +0 -0
  135. /package/{app → apps/mac}/Sources/Core/Desktop/DesktopModelTypes.swift +0 -0
  136. /package/{app → apps/mac}/Sources/Core/Desktop/InventoryManager.swift +0 -0
  137. /package/{app → apps/mac}/Sources/Core/Desktop/InventoryPath.swift +0 -0
  138. /package/{app → apps/mac}/Sources/Core/Desktop/MouseFinder.swift +0 -0
  139. /package/{app → apps/mac}/Sources/Core/Desktop/OcrStore.swift +0 -0
  140. /package/{app → apps/mac}/Sources/Core/Desktop/PlacementSpec.swift +0 -0
  141. /package/{app → apps/mac}/Sources/Core/Desktop/SessionWindowLocator.swift +0 -0
  142. /package/{app → apps/mac}/Sources/Core/Desktop/TilePickerView.swift +0 -0
  143. /package/{app → apps/mac}/Sources/Core/Desktop/WindowPreviewCard.swift +0 -0
  144. /package/{app → apps/mac}/Sources/Core/Desktop/WindowSelectionStore.swift +0 -0
  145. /package/{app → apps/mac}/Sources/Core/Input/KeyboardRemapConfig.swift +0 -0
  146. /package/{app → apps/mac}/Sources/Core/Input/MouseInputDeviceStore.swift +0 -0
  147. /package/{app → apps/mac}/Sources/Core/Input/MouseInputEventViewer.swift +0 -0
  148. /package/{app → apps/mac}/Sources/Core/Overlays/AppWindowShell.swift +0 -0
  149. /package/{app → apps/mac}/Sources/Core/Overlays/CommandMode/CommandModeState.swift +0 -0
  150. /package/{app → apps/mac}/Sources/Core/Overlays/CommandMode/CommandModeView.swift +0 -0
  151. /package/{app → apps/mac}/Sources/Core/Overlays/CommandMode/CommandModeWindow.swift +0 -0
  152. /package/{app → apps/mac}/Sources/Core/Overlays/CommandPalette/CommandPaletteView.swift +0 -0
  153. /package/{app → apps/mac}/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +0 -0
  154. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/CheatSheetHUD.swift +0 -0
  155. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDBottomBar.swift +0 -0
  156. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDController.swift +0 -0
  157. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDLeftBar.swift +0 -0
  158. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDMinimap.swift +0 -0
  159. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDRightBar.swift +0 -0
  160. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDState.swift +0 -0
  161. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDTopBar.swift +0 -0
  162. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/LauncherHUD.swift +0 -0
  163. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/LayerBezel.swift +0 -0
  164. /package/{app → apps/mac}/Sources/Core/Overlays/OmniSearch/OmniSearchState.swift +0 -0
  165. /package/{app → apps/mac}/Sources/Core/Overlays/OmniSearch/OmniSearchView.swift +0 -0
  166. /package/{app → apps/mac}/Sources/Core/Overlays/OmniSearch/OmniSearchWindow.swift +0 -0
  167. /package/{app → apps/mac}/Sources/Core/Overlays/OverlayPanelShell.swift +0 -0
  168. /package/{app → apps/mac}/Sources/Core/Overlays/ScreenMap/ScreenMapView.swift +0 -0
  169. /package/{app → apps/mac}/Sources/Core/Overlays/ScreenMap/ScreenMapWindowController.swift +0 -0
  170. /package/{app → apps/mac}/Sources/Core/Pi/PiAuthNextStepCard.swift +0 -0
  171. /package/{app → apps/mac}/Sources/Core/Pi/PiAuthPromptCard.swift +0 -0
  172. /package/{app → apps/mac}/Sources/Core/Pi/PiInstallCallout.swift +0 -0
  173. /package/{app → apps/mac}/Sources/Core/System/DiagnosticLog.swift +0 -0
  174. /package/{app → apps/mac}/Sources/Core/System/EventBus.swift +0 -0
  175. /package/{app → apps/mac}/Sources/Core/System/ProcessModel.swift +0 -0
  176. /package/{app → apps/mac}/Sources/Core/System/ProcessQuery.swift +0 -0
  177. /package/{app → apps/mac}/Sources/Core/System/SystemTelemetryMonitor.swift +0 -0
  178. /package/{app → apps/mac}/Sources/Core/Voice/AdvisorLearningStore.swift +0 -0
  179. /package/{app → apps/mac}/Sources/Core/Voice/AgentSession.swift +0 -0
  180. /package/{app → apps/mac}/Sources/Core/Voice/HandsOffSession.swift +0 -0
  181. /package/{app → apps/mac}/Sources/Core/Voice/VoiceChatView.swift +0 -0
  182. /package/{app → apps/mac}/Sources/Core/Voice/VoxClient.swift +0 -0
  183. /package/{app → apps/mac}/Sources/Core/Workspace/Project.swift +0 -0
  184. /package/{app → apps/mac}/Sources/Core/Workspace/ProjectScanner.swift +0 -0
  185. /package/{app → apps/mac}/Sources/Core/Workspace/SessionLayerStore.swift +0 -0
  186. /package/{app → apps/mac}/Sources/Core/Workspace/SessionManager.swift +0 -0
  187. /package/{app → apps/mac}/Sources/Core/Workspace/Terminal/Terminal.swift +0 -0
  188. /package/{app → apps/mac}/Sources/Core/Workspace/Terminal/TerminalQuery.swift +0 -0
  189. /package/{app → apps/mac}/Sources/Core/Workspace/Terminal/TerminalSynthesizer.swift +0 -0
  190. /package/{app → apps/mac}/Sources/Core/Workspace/Tmux/TmuxModel.swift +0 -0
  191. /package/{app → apps/mac}/Sources/Core/Workspace/Tmux/TmuxQuery.swift +0 -0
  192. /package/{app → apps/mac}/Sources/Core/Workspace/WorkspaceManager.swift +0 -0
  193. /package/{app → apps/mac}/Sources/UI/ActionRow.swift +0 -0
  194. /package/{app → apps/mac}/Sources/UI/OrphanRow.swift +0 -0
  195. /package/{app → apps/mac}/Sources/UI/ProjectRow.swift +0 -0
  196. /package/{app → apps/mac}/Sources/UI/TabGroupRow.swift +0 -0
  197. /package/{app → apps/mac}/Sources/UI/Theme.swift +0 -0
  198. /package/{app → apps/mac}/Tests/StageDragTests.swift +0 -0
  199. /package/{app → apps/mac}/Tests/StageJoinTests.swift +0 -0
  200. /package/{app → apps/mac}/Tests/StageManagerTests.swift +0 -0
  201. /package/{app → apps/mac}/Tests/StageTileTests.swift +0 -0
@@ -1,408 +0,0 @@
1
- import AppKit
2
- import Carbon
3
- import SwiftUI
4
-
5
- extension Notification.Name {
6
- static let latticesPopoverWillShow = Notification.Name("latticesPopoverWillShow")
7
- }
8
-
9
- /// Manages the NSStatusItem (menu bar icon), left-click popover, and right-click context menu.
10
- /// Replaces the previous SwiftUI MenuBarExtra approach for full click-event control.
11
- class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
12
- private static weak var shared: AppDelegate?
13
-
14
- private var statusItem: NSStatusItem!
15
- private var popover: NSPopover?
16
- private var contextMenu: NSMenu!
17
-
18
- /// 3×3 grid icon for the menu bar — L-shape bright, rest dim (template for auto light/dark)
19
- private static let menuBarIcon: NSImage = {
20
- let size: CGFloat = 18
21
- let img = NSImage(size: NSSize(width: size, height: size), flipped: true) { _ in
22
- let pad: CGFloat = 2
23
- let gap: CGFloat = 1.5
24
- let cellSize = (size - 2 * pad - 2 * gap) / 3
25
-
26
- let solidCells: Set<Int> = [0, 3, 6, 7, 8]
27
-
28
- for row in 0..<3 {
29
- for col in 0..<3 {
30
- let idx = row * 3 + col
31
- let x = pad + CGFloat(col) * (cellSize + gap)
32
- let y = pad + CGFloat(row) * (cellSize + gap)
33
- let rect = NSRect(x: x, y: y, width: cellSize, height: cellSize)
34
-
35
- if solidCells.contains(idx) {
36
- NSColor.black.setFill()
37
- } else {
38
- NSColor.black.withAlphaComponent(0.25).setFill()
39
- }
40
- let path = NSBezierPath(roundedRect: rect, xRadius: 0.8, yRadius: 0.8)
41
- path.fill()
42
- }
43
- }
44
- return true
45
- }
46
- img.isTemplate = true
47
- return img
48
- }()
49
-
50
- /// Toggle between .accessory (hidden from Dock/Cmd+Tab) and .regular (visible)
51
- /// based on whether any managed windows are open.
52
- static func updateActivationPolicy() {
53
- let hasVisibleWindow =
54
- (Self.shared?.popover?.isShown == true) ||
55
- CommandModeWindow.shared.isVisible ||
56
- CommandPaletteWindow.shared.isVisible ||
57
- MainWindow.shared.isVisible ||
58
- ScreenMapWindowController.shared.isVisible ||
59
- OmniSearchWindow.shared.isVisible
60
- let desired: NSApplication.ActivationPolicy = hasVisibleWindow ? .regular : .accessory
61
- if NSApp.activationPolicy() != desired {
62
- NSApp.setActivationPolicy(desired)
63
- if desired == .regular {
64
- NSApp.activate(ignoringOtherApps: true)
65
- }
66
- }
67
- }
68
-
69
- func applicationDidFinishLaunching(_ notification: Notification) {
70
- Self.shared = self
71
- NSApp.setActivationPolicy(.accessory)
72
- NSApp.appearance = NSAppearance(named: .darkAqua)
73
- registerDeepLinkHandler()
74
-
75
- // --- Status item ---
76
- statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
77
- if let button = statusItem.button {
78
- button.image = Self.menuBarIcon
79
- button.action = #selector(statusItemClicked(_:))
80
- button.sendAction(on: [.leftMouseUp, .rightMouseUp])
81
- button.target = self
82
- }
83
-
84
- // --- Context menu (right-click) ---
85
- contextMenu = buildContextMenu()
86
-
87
- // --- Hotkey registration ---
88
- let scanner = ProjectScanner.shared
89
- CommandPaletteWindow.shared.configure(scanner: scanner)
90
-
91
- let store = HotkeyStore.shared
92
- store.register(action: .palette) { CommandPaletteWindow.shared.toggle() }
93
- store.register(action: .unifiedWindow) { ScreenMapWindowController.shared.toggle() }
94
- store.register(action: .bezel) { Self.showWorkspaceInspector() }
95
- store.register(action: .cheatSheet) { SettingsWindowController.shared.show() }
96
- store.register(action: .desktopInventory) {
97
- DiagnosticLog.shared.info("Hotkey: desktopInventory triggered")
98
- ScreenMapWindowController.shared.showPage(.desktopInventory)
99
- }
100
- store.register(action: .voiceCommand) {
101
- DiagnosticLog.shared.info("Hotkey: voiceCommand triggered")
102
- VoiceCommandWindow.shared.toggle()
103
- }
104
- store.register(action: .handsOff) {
105
- DiagnosticLog.shared.info("Hotkey: handsOff triggered")
106
- HandsOffSession.shared.toggle()
107
- // Show voice bar when starting, hide when stopping
108
- if HandsOffSession.shared.state != .idle {
109
- HUDController.shared.showVoiceBar()
110
- } else {
111
- HUDController.shared.hideVoiceBar()
112
- }
113
- }
114
- store.register(action: .hud) { HUDController.shared.toggle() }
115
- store.register(action: .mouseFinder) { MouseFinder.shared.find() }
116
-
117
- // Pre-render HUD panels off-screen for instant first open
118
- DispatchQueue.main.async { HUDController.shared.warmUp() }
119
- // Pre-build the menu bar popover so the first click doesn't pay the SwiftUI mount cost.
120
- // Touching `.view` forces NSHostingController to materialize the SwiftUI view tree.
121
- DispatchQueue.main.async { [weak self] in
122
- guard let self = self else { return }
123
- let p = self.makePopover()
124
- _ = p.contentViewController?.view
125
- }
126
- store.register(action: .omniSearch) { OmniSearchWindow.shared.toggle() }
127
- WindowDragSnapController.shared.start()
128
- MouseGestureController.shared.start()
129
- KeyboardRemapController.shared.start()
130
-
131
- // Session layer cycling
132
- store.register(action: .layerNext) { SessionLayerStore.shared.cycleNext() }
133
- store.register(action: .layerPrev) { SessionLayerStore.shared.cyclePrev() }
134
- store.register(action: .layerTag) { SessionLayerStore.shared.tagFrontmostWindow() }
135
-
136
- // Layer-switching hotkeys (1-9): session layers take priority
137
- let workspace = WorkspaceManager.shared
138
- let configLayerCount = (workspace.config?.layers ?? []).count
139
- let maxLayers = max(configLayerCount, 9)
140
- for (i, action) in HotkeyAction.layerActions.prefix(maxLayers).enumerated() {
141
- let index = i
142
- store.register(action: action) {
143
- let session = SessionLayerStore.shared
144
- if !session.layers.isEmpty && index < session.layers.count {
145
- session.switchTo(index: index)
146
- } else {
147
- workspace.focusLayer(index: index)
148
- }
149
- EventBus.shared.post(.layerSwitched(index: index))
150
- }
151
- }
152
-
153
- // Tiling hotkeys
154
- let tileMap: [(HotkeyAction, TilePosition)] = [
155
- (.tileLeft, .left), (.tileRight, .right),
156
- (.tileMaximize, .maximize), (.tileCenter, .center),
157
- (.tileTopLeft, .topLeft), (.tileTopRight, .topRight),
158
- (.tileBottomLeft, .bottomLeft), (.tileBottomRight, .bottomRight),
159
- (.tileTop, .top), (.tileBottom, .bottom),
160
- (.tileLeftThird, .leftThird), (.tileCenterThird, .centerThird),
161
- (.tileRightThird, .rightThird),
162
- ]
163
- for (action, position) in tileMap {
164
- store.register(action: action) { WindowTiler.tileFrontmostViaAX(to: position) }
165
- }
166
- store.register(action: .tileDistribute) { WindowTiler.distributeVisible(reactivateLattices: false) }
167
- store.register(action: .tileTypeGrid) { WindowTiler.distributeVisibleByFrontmostType(reactivateLattices: false) }
168
- store.register(action: .tileOrganize) {
169
- let appName = DesktopModel.shared.frontmostWindow()?.app
170
- ?? NSWorkspace.shared.frontmostApplication?.localizedName
171
- CommandModeWindow.shared.show(launchMode: .organize(appName: appName))
172
- }
173
-
174
- // Onboarding on first launch; otherwise just check permissions
175
- if !OnboardingWindowController.shared.showIfNeeded() {
176
- PermissionChecker.shared.check()
177
- }
178
-
179
- // Start daemon services
180
- let diag = DiagnosticLog.shared
181
- let tBoot = diag.startTimed("Daemon services boot")
182
- OcrStore.shared.open()
183
- DesktopModel.shared.start()
184
- OcrModel.shared.start()
185
- TmuxModel.shared.start()
186
- ProcessModel.shared.start()
187
- LatticesApi.setup()
188
- DaemonServer.shared.start()
189
- if Preferences.shared.companionBridgeEnabled {
190
- LatticesCompanionBridgeServer.shared.start()
191
- } else {
192
- diag.info("CompanionBridge: disabled by preference")
193
- }
194
- AgentPool.shared.start()
195
- diag.finish(tBoot)
196
-
197
- Task {
198
- await AppUpdater.shared.checkIfNeeded()
199
- }
200
-
201
- // --diagnostics flag: auto-open diagnostics panel on launch
202
- if CommandLine.arguments.contains("--diagnostics") {
203
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
204
- DiagnosticWindow.shared.show()
205
- }
206
- }
207
-
208
- // --screen-map flag: auto-open layout on launch
209
- if CommandLine.arguments.contains("--screen-map") {
210
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
211
- ScreenMapWindowController.shared.showPage(.screenMap)
212
- }
213
- }
214
- }
215
-
216
- func applicationWillTerminate(_ notification: Notification) {
217
- KeyboardRemapController.shared.stop()
218
- LatticesCompanionBridgeServer.shared.stop()
219
- DaemonServer.shared.stop()
220
- }
221
-
222
- // MARK: - Status item click handler
223
-
224
- @objc private func statusItemClicked(_ sender: Any?) {
225
- guard let event = NSApp.currentEvent, let button = statusItem.button else { return }
226
-
227
- if event.type == .rightMouseUp {
228
- // Right-click → context menu
229
- contextMenu.popUp(positioning: nil, at: NSPoint(x: 0, y: button.bounds.height + 4), in: button)
230
- } else {
231
- // Left-click → toggle the menu bar projects popover.
232
- if let shown = popover, shown.isShown {
233
- shown.performClose(sender)
234
- } else {
235
- showProjectsPopover()
236
- }
237
- }
238
- }
239
-
240
- /// Dismiss the popover programmatically (e.g. from the pop-out button).
241
- func dismissPopover() {
242
- popover?.performClose(nil)
243
- }
244
-
245
- /// Cached popover — built lazily on first click, reused on every subsequent open.
246
- /// Keeping the SwiftUI view tree alive avoids rebuilding on each click (slow first paint).
247
- /// Data refresh is driven from `popoverWillShow` + a notification MainView listens to.
248
- private func makePopover() -> NSPopover {
249
- if let p = popover { return p }
250
- let t = DiagnosticLog.shared.startTimed("makePopover")
251
- let p = NSPopover()
252
- p.contentViewController = NSHostingController(rootView: MainView(scanner: ProjectScanner.shared))
253
- p.behavior = .transient
254
- p.contentSize = NSSize(width: 380, height: 300)
255
- p.appearance = NSAppearance(named: .darkAqua)
256
- p.delegate = self
257
- popover = p
258
- DiagnosticLog.shared.finish(t)
259
- return p
260
- }
261
-
262
- private func showProjectsPopover() {
263
- guard let button = statusItem.button else { return }
264
- let p = makePopover()
265
- p.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
266
- p.contentViewController?.view.window?.makeKey()
267
- }
268
-
269
- func popoverWillShow(_ notification: Notification) {
270
- Self.updateActivationPolicy()
271
- NotificationCenter.default.post(name: .latticesPopoverWillShow, object: nil)
272
- }
273
-
274
- func popoverDidClose(_ notification: Notification) {
275
- Self.updateActivationPolicy()
276
- }
277
-
278
- // MARK: - Context menu
279
-
280
- private func buildContextMenu() -> NSMenu {
281
- let menu = NSMenu()
282
-
283
- let actions: [(String, String, Selector)] = [
284
- ("Home", "", #selector(menuWorkspace)),
285
- ("Layout", "", #selector(menuLayout)),
286
- ("Search", "", #selector(menuSearch)),
287
- ("Command Palette", "⌘⇧M", #selector(menuCommandPalette)),
288
- ]
289
- for (title, shortcut, action) in actions {
290
- let item = NSMenuItem(title: title, action: action, keyEquivalent: "")
291
- item.target = self
292
- if !shortcut.isEmpty {
293
- // Display-only; the actual hotkey is global
294
- }
295
- menu.addItem(item)
296
- }
297
-
298
- menu.addItem(.separator())
299
-
300
- let cliActions: [(String, Selector)] = [
301
- ("Projects…", #selector(menuProjects)),
302
- ("Initialize Project in Terminal…", #selector(menuInitializeProject)),
303
- ("Launch Project in Terminal…", #selector(menuLaunchProject)),
304
- ]
305
- for (title, action) in cliActions {
306
- let item = NSMenuItem(title: title, action: action, keyEquivalent: "")
307
- item.target = self
308
- menu.addItem(item)
309
- }
310
-
311
- menu.addItem(.separator())
312
-
313
- let update = NSMenuItem(title: "Update Lattices…", action: #selector(menuUpdate), keyEquivalent: "")
314
- update.target = self
315
- menu.addItem(update)
316
-
317
- menu.addItem(.separator())
318
-
319
- let settings = NSMenuItem(title: "Help & Settings…", action: #selector(menuSettings), keyEquivalent: ",")
320
- settings.target = self
321
- menu.addItem(settings)
322
-
323
- menu.addItem(.separator())
324
-
325
- let quit = NSMenuItem(title: "Quit Lattices", action: #selector(menuQuit), keyEquivalent: "q")
326
- quit.target = self
327
- menu.addItem(quit)
328
-
329
- return menu
330
- }
331
-
332
- @objc private func menuCommandPalette() { CommandPaletteWindow.shared.toggle() }
333
- @objc private func menuWorkspace() { ScreenMapWindowController.shared.showPage(.home) }
334
- @objc private func menuLayout() { ScreenMapWindowController.shared.showPage(.screenMap) }
335
- @objc private func menuSearch() { ScreenMapWindowController.shared.showPage(.desktopInventory) }
336
- @objc private func menuDocs() { SettingsWindowController.shared.show() }
337
- @objc private func menuProjects() { DispatchQueue.main.async { self.showProjectsPopover() } }
338
- @objc private func menuInitializeProject() { CliActionLauncher.initializeProjectInTerminal() }
339
- @objc private func menuLaunchProject() { CliActionLauncher.launchProjectInTerminal() }
340
- @objc private func menuHUD() { HUDController.shared.toggle() }
341
- @objc private func menuWindowBezel() { Self.showWorkspaceInspector() }
342
- @objc private func menuCheatSheet() { SettingsWindowController.shared.show() }
343
- @objc private func menuOmniSearch() { OmniSearchWindow.shared.toggle() }
344
- @MainActor @objc private func menuUpdate() { AppUpdater.shared.promptForUpdate() }
345
- @objc private func menuSettings() { SettingsWindowController.shared.show() }
346
- @objc private func menuQuit() { NSApp.terminate(nil) }
347
-
348
- // MARK: - Deep Links
349
-
350
- private func registerDeepLinkHandler() {
351
- NSAppleEventManager.shared().setEventHandler(
352
- self,
353
- andSelector: #selector(handleGetURLEvent(_:withReplyEvent:)),
354
- forEventClass: AEEventClass(kInternetEventClass),
355
- andEventID: AEEventID(kAEGetURL)
356
- )
357
- }
358
-
359
- @objc private func handleGetURLEvent(
360
- _ event: NSAppleEventDescriptor,
361
- withReplyEvent replyEvent: NSAppleEventDescriptor
362
- ) {
363
- guard
364
- let value = event.paramDescriptor(forKeyword: keyDirectObject)?.stringValue,
365
- let url = URL(string: value)
366
- else {
367
- return
368
- }
369
- handleDeepLink(url)
370
- }
371
-
372
- private func handleDeepLink(_ url: URL) {
373
- guard url.scheme?.localizedCaseInsensitiveCompare("lattices") == .orderedSame else {
374
- return
375
- }
376
-
377
- let host = url.host?.lowercased()
378
- let action = url.pathComponents
379
- .first { $0 != "/" }?
380
- .lowercased()
381
-
382
- guard host == "companion" else {
383
- SettingsWindowController.shared.show()
384
- return
385
- }
386
-
387
- switch action {
388
- case "enable", "start":
389
- Preferences.shared.companionBridgeEnabled = true
390
- SettingsWindowController.shared.showCompanion()
391
- case "disable", "stop":
392
- Preferences.shared.companionBridgeEnabled = false
393
- SettingsWindowController.shared.showCompanion()
394
- default:
395
- SettingsWindowController.shared.showCompanion()
396
- }
397
- }
398
-
399
- private static func showWorkspaceInspector() {
400
- guard let entry = DesktopModel.shared.frontmostWindow(),
401
- entry.app != "Lattices" else {
402
- ScreenMapWindowController.shared.showPage(.screenMap)
403
- return
404
- }
405
-
406
- ScreenMapWindowController.shared.showWindow(wid: entry.wid)
407
- }
408
- }
@@ -1,184 +0,0 @@
1
- import AppKit
2
- import Combine
3
- import CoreGraphics
4
-
5
- final class KeyboardRemapController {
6
- static let shared = KeyboardRemapController()
7
-
8
- private static let syntheticMarker: Int64 = 0x4C4B524D
9
-
10
- private var eventTap: CFMachPort?
11
- private var runLoopSource: CFRunLoopSource?
12
- private var subscriptions: Set<AnyCancellable> = []
13
- private var installedObservers = false
14
- private var capsLayerActive = false
15
- private var capsUsedAsModifier = false
16
-
17
- private init() {}
18
-
19
- func start() {
20
- installObserversIfNeeded()
21
- refresh()
22
- }
23
-
24
- func stop() {
25
- removeEventTap()
26
- capsLayerActive = false
27
- capsUsedAsModifier = false
28
- }
29
-
30
- private func installObserversIfNeeded() {
31
- guard !installedObservers else { return }
32
- installedObservers = true
33
-
34
- Preferences.shared.$keyboardRemapsEnabled
35
- .receive(on: RunLoop.main)
36
- .sink { [weak self] _ in self?.refresh() }
37
- .store(in: &subscriptions)
38
-
39
- PermissionChecker.shared.$accessibility
40
- .receive(on: RunLoop.main)
41
- .sink { [weak self] _ in self?.refresh() }
42
- .store(in: &subscriptions)
43
- }
44
-
45
- private func refresh() {
46
- guard Preferences.shared.keyboardRemapsEnabled,
47
- PermissionChecker.shared.accessibility else {
48
- removeEventTap()
49
- return
50
- }
51
-
52
- KeyboardRemapStore.shared.ensureConfigFile()
53
- if eventTap == nil {
54
- installEventTap()
55
- } else if let eventTap {
56
- CGEvent.tapEnable(tap: eventTap, enable: true)
57
- }
58
- }
59
-
60
- private func installEventTap() {
61
- var mask = CGEventMask(0)
62
- mask |= CGEventMask(1) << CGEventType.keyDown.rawValue
63
- mask |= CGEventMask(1) << CGEventType.keyUp.rawValue
64
- mask |= CGEventMask(1) << CGEventType.flagsChanged.rawValue
65
-
66
- let tap = CGEvent.tapCreate(
67
- tap: .cgSessionEventTap,
68
- place: .headInsertEventTap,
69
- options: .defaultTap,
70
- eventsOfInterest: mask,
71
- callback: Self.eventTapCallback,
72
- userInfo: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
73
- )
74
-
75
- guard let tap else {
76
- DiagnosticLog.shared.warn("KeyboardRemap: failed to install keyboard event tap")
77
- return
78
- }
79
-
80
- let source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)
81
- eventTap = tap
82
- runLoopSource = source
83
-
84
- if let source {
85
- CFRunLoopAddSource(CFRunLoopGetMain(), source, .commonModes)
86
- }
87
- CGEvent.tapEnable(tap: tap, enable: true)
88
- DiagnosticLog.shared.info("KeyboardRemap: keyboard event tap installed")
89
- }
90
-
91
- private func removeEventTap() {
92
- if let source = runLoopSource {
93
- CFRunLoopRemoveSource(CFRunLoopGetMain(), source, .commonModes)
94
- }
95
- runLoopSource = nil
96
- if let tap = eventTap {
97
- CFMachPortInvalidate(tap)
98
- }
99
- eventTap = nil
100
- }
101
-
102
- private static let eventTapCallback: CGEventTapCallBack = { _, type, event, userInfo in
103
- guard let userInfo else { return Unmanaged.passUnretained(event) }
104
- let controller = Unmanaged<KeyboardRemapController>.fromOpaque(userInfo).takeUnretainedValue()
105
- return controller.handleEvent(type: type, event: event)
106
- }
107
-
108
- private func handleEvent(type: CGEventType, event: CGEvent) -> Unmanaged<CGEvent>? {
109
- if type == .tapDisabledByTimeout || type == .tapDisabledByUserInput {
110
- if let eventTap {
111
- CGEvent.tapEnable(tap: eventTap, enable: true)
112
- }
113
- return Unmanaged.passUnretained(event)
114
- }
115
-
116
- if event.getIntegerValueField(.eventSourceUserData) == Self.syntheticMarker {
117
- return Unmanaged.passUnretained(event)
118
- }
119
-
120
- KeyboardRemapStore.shared.reloadIfNeeded()
121
- guard let rule = KeyboardRemapStore.shared.capsLockRule,
122
- rule.toIfHeld == .hyper else {
123
- return Unmanaged.passUnretained(event)
124
- }
125
-
126
- let keyCode = event.getIntegerValueField(.keyboardEventKeycode)
127
- if type == .flagsChanged, keyCode == rule.from.keyCode {
128
- return handleCapsLockFlagsChanged(event, rule: rule)
129
- }
130
-
131
- guard capsLayerActive else {
132
- return Unmanaged.passUnretained(event)
133
- }
134
-
135
- switch type {
136
- case .keyDown:
137
- capsUsedAsModifier = true
138
- event.flags = normalizedFlags(event.flags).union(.latticesHyper)
139
- return Unmanaged.passUnretained(event)
140
- case .keyUp:
141
- event.flags = normalizedFlags(event.flags).union(.latticesHyper)
142
- return Unmanaged.passUnretained(event)
143
- default:
144
- return Unmanaged.passUnretained(event)
145
- }
146
- }
147
-
148
- private func handleCapsLockFlagsChanged(_ event: CGEvent, rule: KeyboardRemapRule) -> Unmanaged<CGEvent>? {
149
- let isDown = event.flags.contains(.maskAlphaShift)
150
- if isDown {
151
- capsLayerActive = true
152
- capsUsedAsModifier = false
153
- DiagnosticLog.shared.info("KeyboardRemap: Caps Lock layer active")
154
- } else {
155
- let shouldTap = capsLayerActive && !capsUsedAsModifier && rule.toIfAlone == .escape
156
- capsLayerActive = false
157
- capsUsedAsModifier = false
158
- if shouldTap {
159
- postKeyTap(keyCode: 53)
160
- }
161
- DiagnosticLog.shared.info("KeyboardRemap: Caps Lock layer inactive")
162
- }
163
-
164
- return nil
165
- }
166
-
167
- private func normalizedFlags(_ flags: CGEventFlags) -> CGEventFlags {
168
- var normalized = flags
169
- normalized.remove(.maskAlphaShift)
170
- return normalized
171
- }
172
-
173
- private func postKeyTap(keyCode: CGKeyCode) {
174
- guard let source = CGEventSource(stateID: .combinedSessionState),
175
- let down = CGEvent(keyboardEventSource: source, virtualKey: keyCode, keyDown: true),
176
- let up = CGEvent(keyboardEventSource: source, virtualKey: keyCode, keyDown: false) else {
177
- return
178
- }
179
- down.setIntegerValueField(.eventSourceUserData, value: Self.syntheticMarker)
180
- up.setIntegerValueField(.eventSourceUserData, value: Self.syntheticMarker)
181
- down.post(tap: .cghidEventTap)
182
- up.post(tap: .cghidEventTap)
183
- }
184
- }
@@ -1,84 +0,0 @@
1
- import AppKit
2
- import Combine
3
- import Foundation
4
-
5
- final class KeyboardRemapStore: ObservableObject {
6
- static let shared = KeyboardRemapStore()
7
-
8
- @Published private(set) var config: KeyboardRemapConfig
9
-
10
- let configURL: URL
11
- private var lastLoadedModifiedDate: Date?
12
-
13
- private init() {
14
- let dir = FileManager.default.homeDirectoryForCurrentUser
15
- .appendingPathComponent(".lattices")
16
- try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
17
- self.configURL = dir.appendingPathComponent("keyboard-remaps.json")
18
- self.config = .defaults
19
- ensureConfigFile()
20
- reload()
21
- }
22
-
23
- var enabledRules: [KeyboardRemapRule] {
24
- config.rules.filter(\.enabled)
25
- }
26
-
27
- var summaryLines: [String] {
28
- enabledRules.map(\.summaryLine)
29
- }
30
-
31
- var capsLockRule: KeyboardRemapRule? {
32
- enabledRules.first { $0.from == .capsLock }
33
- }
34
-
35
- func ensureConfigFile() {
36
- guard !FileManager.default.fileExists(atPath: configURL.path) else { return }
37
- write(config: .defaults)
38
- }
39
-
40
- func reload() {
41
- guard let data = FileManager.default.contents(atPath: configURL.path) else {
42
- config = .defaults
43
- return
44
- }
45
-
46
- do {
47
- config = try JSONDecoder().decode(KeyboardRemapConfig.self, from: data)
48
- lastLoadedModifiedDate = modifiedDate()
49
- } catch {
50
- DiagnosticLog.shared.error("KeyboardRemapStore: failed to decode keyboard-remaps.json - \(error.localizedDescription)")
51
- config = .defaults
52
- }
53
- }
54
-
55
- func reloadIfNeeded() {
56
- let currentModifiedDate = modifiedDate()
57
- guard currentModifiedDate != lastLoadedModifiedDate else { return }
58
- reload()
59
- }
60
-
61
- func restoreDefaults() {
62
- write(config: .defaults)
63
- reload()
64
- DiagnosticLog.shared.info("Keyboard remaps restored to defaults")
65
- }
66
-
67
- func openConfiguration() {
68
- ensureConfigFile()
69
- NSWorkspace.shared.open(configURL)
70
- }
71
-
72
- private func write(config: KeyboardRemapConfig) {
73
- let encoder = JSONEncoder()
74
- encoder.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]
75
- guard let data = try? encoder.encode(config) else { return }
76
- try? data.write(to: configURL, options: .atomic)
77
- lastLoadedModifiedDate = modifiedDate()
78
- }
79
-
80
- private func modifiedDate() -> Date? {
81
- let attrs = try? FileManager.default.attributesOfItem(atPath: configURL.path)
82
- return attrs?[.modificationDate] as? Date
83
- }
84
- }