@lattices/cli 0.4.14 → 0.6.0

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 (181) hide show
  1. package/README.md +5 -7
  2. package/apps/mac/Info.plist +4 -4
  3. package/apps/mac/Lattices.app/Contents/Info.plist +4 -12
  4. package/apps/mac/Lattices.app/Contents/MacOS/Lattices +0 -0
  5. package/bin/lattices-app.ts +110 -17
  6. package/bin/lattices-build +125 -0
  7. package/bin/lattices-dev +89 -16
  8. package/bin/lattices.ts +977 -16
  9. package/docs/agents.md +81 -4
  10. package/docs/ai-chat-ux-review.md +416 -0
  11. package/docs/api.md +135 -3
  12. package/docs/app.md +30 -8
  13. package/docs/config.md +4 -0
  14. package/docs/mouse-gestures.md +60 -1
  15. package/docs/proposals/LAT-004-interactive-overlay-actors.md +1 -1
  16. package/docs/proposals/LAT-005-action-runtime-product-spine.md +914 -0
  17. package/docs/proposals/LAT-006-mira-in-lattices.md +553 -0
  18. package/docs/proposals/LAT-007-unified-app-shell.md +128 -0
  19. package/docs/reference/dewey.config.ts +2 -2
  20. package/docs/release.md +171 -0
  21. package/docs/repo-structure.md +5 -5
  22. package/docs/voice.md +11 -27
  23. package/package.json +11 -10
  24. package/apps/mac/Package.swift +0 -27
  25. package/apps/mac/Sources/AppShell/App.swift +0 -26
  26. package/apps/mac/Sources/AppShell/AppActivationCoordinator.swift +0 -27
  27. package/apps/mac/Sources/AppShell/AppDelegate.swift +0 -189
  28. package/apps/mac/Sources/AppShell/AppServicesBootstrap.swift +0 -25
  29. package/apps/mac/Sources/AppShell/AppShellView.swift +0 -171
  30. package/apps/mac/Sources/AppShell/AppUpdater.swift +0 -305
  31. package/apps/mac/Sources/AppShell/CliActionLauncher.swift +0 -50
  32. package/apps/mac/Sources/AppShell/HomeDashboardView.swift +0 -133
  33. package/apps/mac/Sources/AppShell/HotkeyBootstrap.swift +0 -87
  34. package/apps/mac/Sources/AppShell/KeyRecorderView.swift +0 -210
  35. package/apps/mac/Sources/AppShell/LatticesRuntime.swift +0 -104
  36. package/apps/mac/Sources/AppShell/MainView.swift +0 -847
  37. package/apps/mac/Sources/AppShell/MainWindow.swift +0 -83
  38. package/apps/mac/Sources/AppShell/MenuBarController.swift +0 -177
  39. package/apps/mac/Sources/AppShell/OnboardingView.swift +0 -483
  40. package/apps/mac/Sources/AppShell/PermissionsAssistantView.swift +0 -366
  41. package/apps/mac/Sources/AppShell/PermissionsAssistantWindow.swift +0 -70
  42. package/apps/mac/Sources/AppShell/Preferences.swift +0 -297
  43. package/apps/mac/Sources/AppShell/SettingsView.swift +0 -3163
  44. package/apps/mac/Sources/AppShell/SettingsWindow.swift +0 -34
  45. package/apps/mac/Sources/AppShell/WorkspaceInspectorPresenter.swift +0 -13
  46. package/apps/mac/Sources/Core/Actions/HotkeyManager.swift +0 -256
  47. package/apps/mac/Sources/Core/Actions/HotkeyStore.swift +0 -399
  48. package/apps/mac/Sources/Core/Actions/IntentEngine.swift +0 -988
  49. package/apps/mac/Sources/Core/Actions/IntentSchema.swift +0 -94
  50. package/apps/mac/Sources/Core/Actions/Intents/CreateLayerIntent.swift +0 -54
  51. package/apps/mac/Sources/Core/Actions/Intents/DistributeIntent.swift +0 -56
  52. package/apps/mac/Sources/Core/Actions/Intents/FocusIntent.swift +0 -69
  53. package/apps/mac/Sources/Core/Actions/Intents/HelpIntent.swift +0 -41
  54. package/apps/mac/Sources/Core/Actions/Intents/KillIntent.swift +0 -47
  55. package/apps/mac/Sources/Core/Actions/Intents/LatticeIntent.swift +0 -53
  56. package/apps/mac/Sources/Core/Actions/Intents/LaunchIntent.swift +0 -67
  57. package/apps/mac/Sources/Core/Actions/Intents/ListSessionsIntent.swift +0 -32
  58. package/apps/mac/Sources/Core/Actions/Intents/ListWindowsIntent.swift +0 -30
  59. package/apps/mac/Sources/Core/Actions/Intents/ScanIntent.swift +0 -52
  60. package/apps/mac/Sources/Core/Actions/Intents/SearchIntent.swift +0 -190
  61. package/apps/mac/Sources/Core/Actions/Intents/SwitchLayerIntent.swift +0 -50
  62. package/apps/mac/Sources/Core/Actions/Intents/TileIntent.swift +0 -61
  63. package/apps/mac/Sources/Core/Actions/PaletteCommand.swift +0 -439
  64. package/apps/mac/Sources/Core/Actions/VoiceIntentResolver.swift +0 -713
  65. package/apps/mac/Sources/Core/Companion/CompanionActivityLog.swift +0 -70
  66. package/apps/mac/Sources/Core/Companion/CompanionKeyboardController.swift +0 -141
  67. package/apps/mac/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +0 -454
  68. package/apps/mac/Sources/Core/Companion/LatticesCompanionCockpit.swift +0 -555
  69. package/apps/mac/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +0 -629
  70. package/apps/mac/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +0 -204
  71. package/apps/mac/Sources/Core/Companion/LatticesDeckHost.swift +0 -1463
  72. package/apps/mac/Sources/Core/Daemon/DaemonProtocol.swift +0 -114
  73. package/apps/mac/Sources/Core/Daemon/DaemonServer.swift +0 -427
  74. package/apps/mac/Sources/Core/Daemon/LatticesApi.swift +0 -2965
  75. package/apps/mac/Sources/Core/Desktop/AccessibilityTextExtractor.swift +0 -111
  76. package/apps/mac/Sources/Core/Desktop/AppTypeClassifier.swift +0 -106
  77. package/apps/mac/Sources/Core/Desktop/DesktopModel.swift +0 -331
  78. package/apps/mac/Sources/Core/Desktop/DesktopModelTypes.swift +0 -73
  79. package/apps/mac/Sources/Core/Desktop/InventoryManager.swift +0 -35
  80. package/apps/mac/Sources/Core/Desktop/InventoryPath.swift +0 -43
  81. package/apps/mac/Sources/Core/Desktop/MouseFinder.swift +0 -527
  82. package/apps/mac/Sources/Core/Desktop/OcrModel.swift +0 -467
  83. package/apps/mac/Sources/Core/Desktop/OcrStore.swift +0 -329
  84. package/apps/mac/Sources/Core/Desktop/PlacementSpec.swift +0 -195
  85. package/apps/mac/Sources/Core/Desktop/SessionWindowLocator.swift +0 -139
  86. package/apps/mac/Sources/Core/Desktop/TilePickerView.swift +0 -209
  87. package/apps/mac/Sources/Core/Desktop/WindowCapture.swift +0 -33
  88. package/apps/mac/Sources/Core/Desktop/WindowDragSnapController.swift +0 -429
  89. package/apps/mac/Sources/Core/Desktop/WindowPreviewCard.swift +0 -100
  90. package/apps/mac/Sources/Core/Desktop/WindowPreviewStore.swift +0 -112
  91. package/apps/mac/Sources/Core/Desktop/WindowSelectionStore.swift +0 -76
  92. package/apps/mac/Sources/Core/Desktop/WindowTiler.swift +0 -2222
  93. package/apps/mac/Sources/Core/Input/EventTapBreaker.swift +0 -124
  94. package/apps/mac/Sources/Core/Input/EventTapThread.swift +0 -54
  95. package/apps/mac/Sources/Core/Input/InputCaptureResetCenter.swift +0 -20
  96. package/apps/mac/Sources/Core/Input/KeyboardRemapConfig.swift +0 -69
  97. package/apps/mac/Sources/Core/Input/KeyboardRemapController.swift +0 -346
  98. package/apps/mac/Sources/Core/Input/KeyboardRemapStore.swift +0 -141
  99. package/apps/mac/Sources/Core/Input/MouseGestureConfig.swift +0 -499
  100. package/apps/mac/Sources/Core/Input/MouseGestureController.swift +0 -2583
  101. package/apps/mac/Sources/Core/Input/MouseInputDeviceStore.swift +0 -98
  102. package/apps/mac/Sources/Core/Input/MouseInputEventViewer.swift +0 -272
  103. package/apps/mac/Sources/Core/Input/MouseShortcutStore.swift +0 -170
  104. package/apps/mac/Sources/Core/Input/SecureEventInputMonitor.swift +0 -39
  105. package/apps/mac/Sources/Core/Input/ShapeRecognizer.swift +0 -624
  106. package/apps/mac/Sources/Core/Input/TapBudgetMeter.swift +0 -56
  107. package/apps/mac/Sources/Core/Overlays/AppWindowShell.swift +0 -63
  108. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeState.swift +0 -1566
  109. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeView.swift +0 -1927
  110. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeWindow.swift +0 -196
  111. package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteView.swift +0 -307
  112. package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +0 -67
  113. package/apps/mac/Sources/Core/Overlays/HUD/CheatSheetHUD.swift +0 -576
  114. package/apps/mac/Sources/Core/Overlays/HUD/HUDBottomBar.swift +0 -279
  115. package/apps/mac/Sources/Core/Overlays/HUD/HUDController.swift +0 -1158
  116. package/apps/mac/Sources/Core/Overlays/HUD/HUDLeftBar.swift +0 -849
  117. package/apps/mac/Sources/Core/Overlays/HUD/HUDMinimap.swift +0 -179
  118. package/apps/mac/Sources/Core/Overlays/HUD/HUDRightBar.swift +0 -596
  119. package/apps/mac/Sources/Core/Overlays/HUD/HUDState.swift +0 -367
  120. package/apps/mac/Sources/Core/Overlays/HUD/HUDTopBar.swift +0 -243
  121. package/apps/mac/Sources/Core/Overlays/HUD/LauncherHUD.swift +0 -334
  122. package/apps/mac/Sources/Core/Overlays/HUD/LayerBezel.swift +0 -203
  123. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchState.swift +0 -280
  124. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchView.swift +0 -422
  125. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchWindow.swift +0 -94
  126. package/apps/mac/Sources/Core/Overlays/OverlayPanelShell.swift +0 -241
  127. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapState.swift +0 -3135
  128. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapView.swift +0 -3977
  129. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapWindowController.swift +0 -119
  130. package/apps/mac/Sources/Core/Overlays/ScreenOverlayCanvasController.swift +0 -1217
  131. package/apps/mac/Sources/Core/Overlays/Voice/VoiceCommandWindow.swift +0 -1575
  132. package/apps/mac/Sources/Core/Pi/PiAuthNextStepCard.swift +0 -148
  133. package/apps/mac/Sources/Core/Pi/PiAuthPromptCard.swift +0 -90
  134. package/apps/mac/Sources/Core/Pi/PiChatDock.swift +0 -564
  135. package/apps/mac/Sources/Core/Pi/PiChatSession.swift +0 -1948
  136. package/apps/mac/Sources/Core/Pi/PiInstallCallout.swift +0 -86
  137. package/apps/mac/Sources/Core/Pi/PiProviderSetupCallout.swift +0 -99
  138. package/apps/mac/Sources/Core/Pi/PiWorkspaceView.swift +0 -510
  139. package/apps/mac/Sources/Core/System/Capability.swift +0 -79
  140. package/apps/mac/Sources/Core/System/DiagnosticLog.swift +0 -373
  141. package/apps/mac/Sources/Core/System/EventBus.swift +0 -31
  142. package/apps/mac/Sources/Core/System/PermissionChecker.swift +0 -224
  143. package/apps/mac/Sources/Core/System/ProcessModel.swift +0 -199
  144. package/apps/mac/Sources/Core/System/ProcessQuery.swift +0 -151
  145. package/apps/mac/Sources/Core/System/SystemTelemetryMonitor.swift +0 -273
  146. package/apps/mac/Sources/Core/Voice/AdvisorLearningStore.swift +0 -90
  147. package/apps/mac/Sources/Core/Voice/AgentSession.swift +0 -377
  148. package/apps/mac/Sources/Core/Voice/AudioProvider.swift +0 -555
  149. package/apps/mac/Sources/Core/Voice/HandsOffSession.swift +0 -839
  150. package/apps/mac/Sources/Core/Voice/VoiceChatView.swift +0 -192
  151. package/apps/mac/Sources/Core/Voice/VoxClient.swift +0 -454
  152. package/apps/mac/Sources/Core/Workspace/Project.swift +0 -28
  153. package/apps/mac/Sources/Core/Workspace/ProjectScanner.swift +0 -141
  154. package/apps/mac/Sources/Core/Workspace/SessionLayerStore.swift +0 -285
  155. package/apps/mac/Sources/Core/Workspace/SessionManager.swift +0 -75
  156. package/apps/mac/Sources/Core/Workspace/Terminal/Terminal.swift +0 -259
  157. package/apps/mac/Sources/Core/Workspace/Terminal/TerminalQuery.swift +0 -156
  158. package/apps/mac/Sources/Core/Workspace/Terminal/TerminalSynthesizer.swift +0 -200
  159. package/apps/mac/Sources/Core/Workspace/Tmux/TmuxModel.swift +0 -60
  160. package/apps/mac/Sources/Core/Workspace/Tmux/TmuxQuery.swift +0 -105
  161. package/apps/mac/Sources/Core/Workspace/WorkspaceManager.swift +0 -1027
  162. package/apps/mac/Sources/UI/ActionRow.swift +0 -78
  163. package/apps/mac/Sources/UI/OrphanRow.swift +0 -129
  164. package/apps/mac/Sources/UI/ProjectRow.swift +0 -368
  165. package/apps/mac/Sources/UI/TabGroupRow.swift +0 -178
  166. package/apps/mac/Sources/UI/Theme.swift +0 -164
  167. package/apps/mac/Tests/StageDragTests.swift +0 -333
  168. package/apps/mac/Tests/StageJoinTests.swift +0 -313
  169. package/apps/mac/Tests/StageManagerTests.swift +0 -280
  170. package/apps/mac/Tests/StageTileTests.swift +0 -353
  171. package/swift/Package.swift +0 -20
  172. package/swift/Sources/DeckKit/DeckAction.swift +0 -51
  173. package/swift/Sources/DeckKit/DeckBridgeSecurity.swift +0 -152
  174. package/swift/Sources/DeckKit/DeckCockpit.swift +0 -82
  175. package/swift/Sources/DeckKit/DeckHost.swift +0 -7
  176. package/swift/Sources/DeckKit/DeckManifest.swift +0 -145
  177. package/swift/Sources/DeckKit/DeckRuntimeSnapshot.swift +0 -533
  178. package/swift/Sources/DeckKit/DeckTrackpad.swift +0 -63
  179. package/swift/Sources/DeckKit/DeckValue.swift +0 -93
  180. package/swift/Sources/DeckKit/DeckVoiceError.swift +0 -88
  181. package/swift/Tests/DeckKitTests/DeckKitTests.swift +0 -286
@@ -1,79 +0,0 @@
1
- import AppKit
2
- import Foundation
3
-
4
- /// An OS permission Lattices can ask for. The Permissions Assistant introduces
5
- /// these and only requests the underlying grant when the user explicitly opts
6
- /// in. Product configuration like Pi/provider auth is intentionally excluded —
7
- /// that lives in the Chat/Pi surface, not here.
8
- enum Capability: String, CaseIterable, Identifiable {
9
- case windowControl // macOS Accessibility — tiling, focus, snap
10
- case screenSearch // macOS Screen Recording — OCR / on-screen text search
11
-
12
- var id: String { rawValue }
13
-
14
- var title: String {
15
- switch self {
16
- case .windowControl: return "Tiling & focus"
17
- case .screenSearch: return "Screen text search"
18
- }
19
- }
20
-
21
- var iconName: String {
22
- switch self {
23
- case .windowControl: return "rectangle.3.group"
24
- case .screenSearch: return "text.viewfinder"
25
- }
26
- }
27
-
28
- var requirementLabel: String {
29
- switch self {
30
- case .windowControl: return "Accessibility"
31
- case .screenSearch: return "Screen & System Audio Recording"
32
- }
33
- }
34
-
35
- var pitch: String {
36
- switch self {
37
- case .windowControl:
38
- return "Move, resize, snap, and arrange windows from the menu bar, command palette, and gestures."
39
- case .screenSearch:
40
- return "Index on-screen text with OCR so the omni search can jump to any window by what it shows."
41
- }
42
- }
43
-
44
- var why: String {
45
- switch self {
46
- case .windowControl:
47
- return "macOS Accessibility lets Lattices read window titles and move or resize windows. No keystrokes are recorded."
48
- case .screenSearch:
49
- return "Screen Recording lets Lattices read pixels to OCR what is on-screen. Captures stay on this Mac."
50
- }
51
- }
52
-
53
- /// Optional one-liner shown when the capability is on, summarising current behavior.
54
- var whenGrantedDetail: String {
55
- switch self {
56
- case .windowControl: return "Lattices can move and tile windows."
57
- case .screenSearch: return "OCR can index visible windows for omni search."
58
- }
59
- }
60
-
61
- /// Live status — read directly from the system, never cached.
62
- var isGranted: Bool {
63
- switch self {
64
- case .windowControl: return PermissionChecker.shared.accessibility
65
- case .screenSearch: return PermissionChecker.shared.screenRecording
66
- }
67
- }
68
-
69
- /// All capabilities that are not yet granted.
70
- static var missing: [Capability] {
71
- Capability.allCases.filter { !$0.isGranted }
72
- }
73
-
74
- /// Capabilities that are missing AND have not been dismissed-for-now.
75
- static var visiblyMissing: [Capability] {
76
- let dismissed = Preferences.shared.dismissedCapabilities
77
- return missing.filter { !dismissed.contains($0.rawValue) }
78
- }
79
- }
@@ -1,373 +0,0 @@
1
- import AppKit
2
- import SwiftUI
3
-
4
- // MARK: - Log Store
5
-
6
- final class DiagnosticLog: ObservableObject {
7
- static let shared = DiagnosticLog()
8
-
9
- struct Entry: Identifiable {
10
- let id = UUID()
11
- let time: Date
12
- let message: String
13
- let level: Level
14
-
15
- enum Level: String { case info, success, warning, error }
16
-
17
- var icon: String {
18
- switch level {
19
- case .info: return "›"
20
- case .success: return "✓"
21
- case .warning: return "⚠"
22
- case .error: return "✗"
23
- }
24
- }
25
- }
26
-
27
- @Published var entries: [Entry] = []
28
- private let maxEntries = 80
29
-
30
- // Disk persistence
31
- private let logFile: URL
32
- private let fileHandle: FileHandle?
33
- private let diskQueue = DispatchQueue(label: "com.lattices.log-writer")
34
- private static let timeFmt: DateFormatter = {
35
- let f = DateFormatter()
36
- f.dateFormat = "HH:mm:ss.SSS"
37
- return f
38
- }()
39
-
40
- private init() {
41
- let dir = FileManager.default.homeDirectoryForCurrentUser
42
- .appendingPathComponent(".lattices")
43
- try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
44
-
45
- logFile = dir.appendingPathComponent("lattices.log")
46
-
47
- // Rotate if > 1MB
48
- if let attrs = try? FileManager.default.attributesOfItem(atPath: logFile.path),
49
- let size = attrs[.size] as? UInt64, size > 1_000_000 {
50
- let prev = dir.appendingPathComponent("lattices.log.1")
51
- try? FileManager.default.removeItem(at: prev)
52
- try? FileManager.default.moveItem(at: logFile, to: prev)
53
- }
54
-
55
- // Create file if needed and open for appending
56
- if !FileManager.default.fileExists(atPath: logFile.path) {
57
- FileManager.default.createFile(atPath: logFile.path, contents: nil)
58
- }
59
- fileHandle = try? FileHandle(forWritingTo: logFile)
60
- fileHandle?.seekToEndOfFile()
61
-
62
- // Write session header
63
- let header = "\n──── Lattices launched \(ISO8601DateFormatter().string(from: Date())) ────\n"
64
- if let data = header.data(using: .utf8) {
65
- fileHandle?.write(data)
66
- }
67
- }
68
-
69
- deinit {
70
- fileHandle?.closeFile()
71
- }
72
-
73
- func log(_ message: String, level: Entry.Level = .info) {
74
- let entry = Entry(time: Date(), message: message, level: level)
75
-
76
- // In-memory for UI
77
- DispatchQueue.main.async {
78
- self.entries.append(entry)
79
- if self.entries.count > self.maxEntries {
80
- self.entries.removeFirst(self.entries.count - self.maxEntries)
81
- }
82
- }
83
-
84
- // Disk
85
- diskQueue.async { [weak self] in
86
- let ts = Self.timeFmt.string(from: entry.time)
87
- let line = "\(ts) \(entry.icon) [\(level.rawValue)] \(message)\n"
88
- if let data = line.data(using: .utf8) {
89
- self?.fileHandle?.write(data)
90
- }
91
- }
92
- }
93
-
94
- func info(_ msg: String) { log(msg, level: .info) }
95
- func success(_ msg: String) { log(msg, level: .success) }
96
- func warn(_ msg: String) { log(msg, level: .warning) }
97
- func error(_ msg: String) { log(msg, level: .error) }
98
- func clear() { DispatchQueue.main.async { self.entries.removeAll() } }
99
-
100
- // MARK: - Per-Action Timing
101
-
102
- struct TimedAction {
103
- let label: String
104
- let start: Date
105
- }
106
-
107
- func startTimed(_ label: String) -> TimedAction {
108
- info("▸ \(label)")
109
- return TimedAction(label: label, start: Date())
110
- }
111
-
112
- func finish(_ action: TimedAction) {
113
- let ms = Date().timeIntervalSince(action.start) * 1000
114
- success("▸ \(action.label) — \(String(format: "%.0f", ms))ms")
115
- }
116
- }
117
-
118
- // MARK: - Interaction Feedback
119
-
120
- final class AppFeedback {
121
- static let shared = AppFeedback()
122
-
123
- private lazy var tapSound: NSSound? = {
124
- guard let url = Bundle.main.url(forResource: "tap", withExtension: "wav") else { return nil }
125
- return NSSound(contentsOf: url, byReference: true)
126
- }()
127
-
128
- private init() {}
129
-
130
- @discardableResult
131
- func beginTimed(_ label: String, state: HUDState? = nil, feedback: String? = nil, playSound: Bool = true) -> DiagnosticLog.TimedAction {
132
- if playSound {
133
- playTap()
134
- }
135
- if let feedback, let state {
136
- state.showFeedback(feedback)
137
- }
138
- return DiagnosticLog.shared.startTimed(label)
139
- }
140
-
141
- func finish(_ action: DiagnosticLog.TimedAction, state: HUDState? = nil, feedback: String? = nil) {
142
- if let feedback, let state {
143
- state.showFeedback(feedback)
144
- }
145
- DiagnosticLog.shared.finish(action)
146
- }
147
-
148
- func acknowledge(_ label: String, state: HUDState? = nil, feedback: String? = nil, playSound: Bool = true) {
149
- if playSound {
150
- playTap()
151
- }
152
- if let feedback, let state {
153
- state.showFeedback(feedback)
154
- }
155
- DiagnosticLog.shared.info(label)
156
- }
157
-
158
- private func playTap() {
159
- DispatchQueue.main.async {
160
- self.tapSound?.stop()
161
- self.tapSound?.play()
162
- }
163
- }
164
- }
165
-
166
- // MARK: - Diagnostic Window
167
-
168
- final class DiagnosticWindow {
169
- static let shared = DiagnosticWindow()
170
-
171
- private var window: NSWindow?
172
- private var keyMonitor: Any?
173
- private let log = DiagnosticLog.shared
174
-
175
- var isVisible: Bool { window?.isVisible ?? false }
176
-
177
- func toggle() {
178
- if let w = window, w.isVisible {
179
- dismiss()
180
- } else {
181
- show()
182
- }
183
- }
184
-
185
- func dismiss() {
186
- window?.orderOut(nil)
187
- if let monitor = keyMonitor {
188
- NSEvent.removeMonitor(monitor)
189
- keyMonitor = nil
190
- }
191
- }
192
-
193
- func show() {
194
- if let w = window {
195
- w.orderFrontRegardless()
196
- return
197
- }
198
-
199
- let view = DiagnosticOverlayView()
200
-
201
- let hosting = NSHostingController(rootView: view)
202
- let screen = NSScreen.main
203
- let screenFrame = screen?.visibleFrame ?? NSRect(x: 0, y: 0, width: 1920, height: 1080)
204
- let panelWidth: CGFloat = 480
205
- let panelHeight: CGFloat = max(600, floor(screenFrame.height * 0.55))
206
- hosting.preferredContentSize = NSSize(width: panelWidth, height: panelHeight)
207
-
208
- let w = NSPanel(
209
- contentRect: NSRect(x: 0, y: 0, width: panelWidth, height: panelHeight),
210
- styleMask: [.titled, .closable, .resizable, .utilityWindow, .nonactivatingPanel],
211
- backing: .buffered,
212
- defer: false
213
- )
214
- w.contentViewController = hosting
215
- w.title = "Lattices Diagnostics"
216
- w.titlebarAppearsTransparent = true
217
- w.isMovableByWindowBackground = true
218
- w.level = .floating
219
- w.isOpaque = false
220
- w.backgroundColor = NSColor(red: 0.1, green: 0.1, blue: 0.12, alpha: 1.0)
221
- w.hasShadow = true
222
- w.alphaValue = 1.0
223
- w.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
224
-
225
- // Position: right edge, vertically centered
226
- let x = screenFrame.maxX - panelWidth - 12
227
- let y = screenFrame.minY + floor((screenFrame.height - panelHeight) / 2)
228
- w.setFrameOrigin(NSPoint(x: x, y: y))
229
-
230
- w.orderFrontRegardless()
231
- window = w
232
-
233
- // Escape key → dismiss
234
- keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
235
- guard event.keyCode == 53,
236
- let win = self?.window,
237
- event.window === win || win.isKeyWindow else { return event }
238
- self?.dismiss()
239
- return nil
240
- }
241
-
242
- // Startup log
243
- let diag = DiagnosticLog.shared
244
- diag.info("Diagnostics opened")
245
- diag.info("Terminal: \(Preferences.shared.terminal.rawValue) (\(Preferences.shared.terminal.bundleId))")
246
- diag.info("Installed: \(Terminal.installed.map(\.rawValue).joined(separator: ", "))")
247
-
248
- // Show running sessions
249
- let task = Process()
250
- task.executableURL = URL(fileURLWithPath: TmuxQuery.resolvedPath ?? "/opt/homebrew/bin/tmux")
251
- task.arguments = ["list-sessions", "-F", "#{session_name}"]
252
- let pipe = Pipe()
253
- task.standardOutput = pipe
254
- task.standardError = FileHandle.nullDevice
255
- try? task.run()
256
- task.waitUntilExit()
257
- let sessions = String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "none"
258
- diag.info("tmux sessions: \(sessions)")
259
- }
260
- }
261
-
262
- // MARK: - SwiftUI Overlay
263
-
264
- struct DiagnosticOverlayView: View {
265
- @StateObject private var log = DiagnosticLog.shared
266
- @State private var autoScroll = true
267
- @State private var refreshTick = 0
268
-
269
- private static let timeFmt: DateFormatter = {
270
- let f = DateFormatter()
271
- f.dateFormat = "HH:mm:ss.SSS"
272
- return f
273
- }()
274
-
275
- // Fallback timer to catch any missed updates
276
- private let refreshTimer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
277
-
278
- var body: some View {
279
- VStack(spacing: 0) {
280
- // Header
281
- HStack {
282
- Text("DIAGNOSTICS")
283
- .font(.system(size: 10, weight: .bold, design: .monospaced))
284
- .foregroundColor(.green.opacity(0.8))
285
- Spacer()
286
- let _ = refreshTick // force re-render on timer
287
- Text("\(log.entries.count) events")
288
- .font(.system(size: 9, design: .monospaced))
289
- .foregroundColor(.white.opacity(0.4))
290
- Button("Copy") {
291
- let text = log.entries.map { entry in
292
- let t = Self.timeFmt.string(from: entry.time)
293
- return "\(t) \(entry.icon) \(entry.message)"
294
- }.joined(separator: "\n")
295
- NSPasteboard.general.clearContents()
296
- NSPasteboard.general.setString(text, forType: .string)
297
- }
298
- .font(.system(size: 9, design: .monospaced))
299
- .foregroundColor(.white.opacity(0.5))
300
- .buttonStyle(.plain)
301
- Button("Clear") { log.clear() }
302
- .font(.system(size: 9, design: .monospaced))
303
- .foregroundColor(.white.opacity(0.5))
304
- .buttonStyle(.plain)
305
- }
306
- .padding(.horizontal, 10)
307
- .padding(.vertical, 6)
308
- .background(Color.black.opacity(0.3))
309
- .onReceive(refreshTimer) { _ in refreshTick += 1 }
310
-
311
- // Log entries
312
- ScrollViewReader { proxy in
313
- ScrollView {
314
- LazyVStack(alignment: .leading, spacing: 1) {
315
- ForEach(log.entries) { entry in
316
- logRow(entry)
317
- .id(entry.id)
318
- }
319
- }
320
- .padding(.horizontal, 8)
321
- .padding(.vertical, 4)
322
- }
323
- .onChange(of: log.entries.count) { _ in
324
- if autoScroll, let last = log.entries.last {
325
- withAnimation(.easeOut(duration: 0.1)) {
326
- proxy.scrollTo(last.id, anchor: .bottom)
327
- }
328
- }
329
- }
330
- }
331
- }
332
- .frame(minWidth: 420, idealWidth: 480, minHeight: 400, idealHeight: 600)
333
- .background(Color.black.opacity(0.75))
334
- }
335
-
336
- private func logRow(_ entry: DiagnosticLog.Entry) -> some View {
337
- HStack(alignment: .top, spacing: 6) {
338
- Text(Self.timeFmt.string(from: entry.time))
339
- .font(.system(size: 9, design: .monospaced))
340
- .foregroundColor(.white.opacity(0.3))
341
-
342
- Text(entry.icon)
343
- .font(.system(size: 9, design: .monospaced))
344
- .foregroundColor(iconColor(entry.level))
345
- .frame(width: 10)
346
-
347
- Text(entry.message)
348
- .font(.system(size: 10, design: .monospaced))
349
- .foregroundColor(textColor(entry.level))
350
- .lineLimit(3)
351
- .fixedSize(horizontal: false, vertical: true)
352
- }
353
- .padding(.vertical, 1)
354
- }
355
-
356
- private func iconColor(_ level: DiagnosticLog.Entry.Level) -> Color {
357
- switch level {
358
- case .info: return .white.opacity(0.5)
359
- case .success: return .green
360
- case .warning: return .yellow
361
- case .error: return .red
362
- }
363
- }
364
-
365
- private func textColor(_ level: DiagnosticLog.Entry.Level) -> Color {
366
- switch level {
367
- case .info: return .white.opacity(0.7)
368
- case .success: return .green.opacity(0.9)
369
- case .warning: return .yellow.opacity(0.9)
370
- case .error: return .red.opacity(0.9)
371
- }
372
- }
373
- }
@@ -1,31 +0,0 @@
1
- import Foundation
2
-
3
- enum ModelEvent {
4
- case windowsChanged(windows: [WindowEntry], added: [UInt32], removed: [UInt32])
5
- case tmuxChanged(sessions: [TmuxSession])
6
- case layerSwitched(index: Int)
7
- case processesChanged(interesting: [Int])
8
- case ocrScanComplete(windowCount: Int, totalBlocks: Int)
9
- case voiceCommand(text: String, confidence: Double)
10
- }
11
-
12
- final class EventBus {
13
- static let shared = EventBus()
14
- private var handlers: [(ModelEvent) -> Void] = []
15
- private let lock = NSLock()
16
-
17
- func subscribe(_ handler: @escaping (ModelEvent) -> Void) {
18
- lock.lock()
19
- handlers.append(handler)
20
- lock.unlock()
21
- }
22
-
23
- func post(_ event: ModelEvent) {
24
- lock.lock()
25
- let copy = handlers
26
- lock.unlock()
27
- for handler in copy {
28
- handler(event)
29
- }
30
- }
31
- }