@lattices/cli 0.4.13 → 0.5.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.
- package/README.md +5 -7
- package/apps/mac/Info.plist +2 -2
- package/apps/mac/Lattices.app/Contents/Info.plist +4 -12
- package/apps/mac/Lattices.app/Contents/MacOS/Lattices +0 -0
- package/bin/lattices-app.ts +110 -17
- package/bin/lattices-build +125 -0
- package/bin/lattices-dev +89 -16
- package/bin/lattices.ts +977 -16
- package/docs/agents.md +81 -4
- package/docs/ai-chat-ux-review.md +416 -0
- package/docs/api.md +135 -3
- package/docs/app.md +30 -8
- package/docs/config.md +4 -0
- package/docs/mouse-gestures.md +191 -63
- package/docs/proposals/LAT-004-interactive-overlay-actors.md +1 -1
- package/docs/proposals/LAT-005-action-runtime-product-spine.md +914 -0
- package/docs/proposals/LAT-006-mira-in-lattices.md +553 -0
- package/docs/reference/dewey.config.ts +2 -2
- package/docs/release.md +171 -0
- package/docs/repo-structure.md +4 -5
- package/docs/voice.md +11 -27
- package/package.json +9 -10
- package/apps/mac/Package.swift +0 -27
- package/apps/mac/Sources/AppShell/App.swift +0 -26
- package/apps/mac/Sources/AppShell/AppActivationCoordinator.swift +0 -27
- package/apps/mac/Sources/AppShell/AppDelegate.swift +0 -189
- package/apps/mac/Sources/AppShell/AppServicesBootstrap.swift +0 -25
- package/apps/mac/Sources/AppShell/AppShellView.swift +0 -171
- package/apps/mac/Sources/AppShell/AppUpdater.swift +0 -305
- package/apps/mac/Sources/AppShell/CliActionLauncher.swift +0 -50
- package/apps/mac/Sources/AppShell/HomeDashboardView.swift +0 -133
- package/apps/mac/Sources/AppShell/HotkeyBootstrap.swift +0 -87
- package/apps/mac/Sources/AppShell/KeyRecorderView.swift +0 -210
- package/apps/mac/Sources/AppShell/LatticesRuntime.swift +0 -104
- package/apps/mac/Sources/AppShell/MainView.swift +0 -847
- package/apps/mac/Sources/AppShell/MainWindow.swift +0 -83
- package/apps/mac/Sources/AppShell/MenuBarController.swift +0 -177
- package/apps/mac/Sources/AppShell/OnboardingView.swift +0 -483
- package/apps/mac/Sources/AppShell/PermissionsAssistantView.swift +0 -366
- package/apps/mac/Sources/AppShell/PermissionsAssistantWindow.swift +0 -70
- package/apps/mac/Sources/AppShell/Preferences.swift +0 -297
- package/apps/mac/Sources/AppShell/SettingsView.swift +0 -3163
- package/apps/mac/Sources/AppShell/SettingsWindow.swift +0 -34
- package/apps/mac/Sources/AppShell/WorkspaceInspectorPresenter.swift +0 -13
- package/apps/mac/Sources/Core/Actions/HotkeyManager.swift +0 -256
- package/apps/mac/Sources/Core/Actions/HotkeyStore.swift +0 -399
- package/apps/mac/Sources/Core/Actions/IntentEngine.swift +0 -988
- package/apps/mac/Sources/Core/Actions/IntentSchema.swift +0 -94
- package/apps/mac/Sources/Core/Actions/Intents/CreateLayerIntent.swift +0 -54
- package/apps/mac/Sources/Core/Actions/Intents/DistributeIntent.swift +0 -56
- package/apps/mac/Sources/Core/Actions/Intents/FocusIntent.swift +0 -69
- package/apps/mac/Sources/Core/Actions/Intents/HelpIntent.swift +0 -41
- package/apps/mac/Sources/Core/Actions/Intents/KillIntent.swift +0 -47
- package/apps/mac/Sources/Core/Actions/Intents/LatticeIntent.swift +0 -53
- package/apps/mac/Sources/Core/Actions/Intents/LaunchIntent.swift +0 -67
- package/apps/mac/Sources/Core/Actions/Intents/ListSessionsIntent.swift +0 -32
- package/apps/mac/Sources/Core/Actions/Intents/ListWindowsIntent.swift +0 -30
- package/apps/mac/Sources/Core/Actions/Intents/ScanIntent.swift +0 -52
- package/apps/mac/Sources/Core/Actions/Intents/SearchIntent.swift +0 -190
- package/apps/mac/Sources/Core/Actions/Intents/SwitchLayerIntent.swift +0 -50
- package/apps/mac/Sources/Core/Actions/Intents/TileIntent.swift +0 -61
- package/apps/mac/Sources/Core/Actions/PaletteCommand.swift +0 -439
- package/apps/mac/Sources/Core/Actions/VoiceIntentResolver.swift +0 -713
- package/apps/mac/Sources/Core/Companion/CompanionActivityLog.swift +0 -70
- package/apps/mac/Sources/Core/Companion/CompanionKeyboardController.swift +0 -141
- package/apps/mac/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +0 -454
- package/apps/mac/Sources/Core/Companion/LatticesCompanionCockpit.swift +0 -555
- package/apps/mac/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +0 -629
- package/apps/mac/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +0 -204
- package/apps/mac/Sources/Core/Companion/LatticesDeckHost.swift +0 -1463
- package/apps/mac/Sources/Core/Daemon/DaemonProtocol.swift +0 -114
- package/apps/mac/Sources/Core/Daemon/DaemonServer.swift +0 -427
- package/apps/mac/Sources/Core/Daemon/LatticesApi.swift +0 -2965
- package/apps/mac/Sources/Core/Desktop/AccessibilityTextExtractor.swift +0 -111
- package/apps/mac/Sources/Core/Desktop/AppTypeClassifier.swift +0 -106
- package/apps/mac/Sources/Core/Desktop/DesktopModel.swift +0 -331
- package/apps/mac/Sources/Core/Desktop/DesktopModelTypes.swift +0 -73
- package/apps/mac/Sources/Core/Desktop/InventoryManager.swift +0 -35
- package/apps/mac/Sources/Core/Desktop/InventoryPath.swift +0 -43
- package/apps/mac/Sources/Core/Desktop/MouseFinder.swift +0 -527
- package/apps/mac/Sources/Core/Desktop/OcrModel.swift +0 -467
- package/apps/mac/Sources/Core/Desktop/OcrStore.swift +0 -329
- package/apps/mac/Sources/Core/Desktop/PlacementSpec.swift +0 -195
- package/apps/mac/Sources/Core/Desktop/SessionWindowLocator.swift +0 -139
- package/apps/mac/Sources/Core/Desktop/TilePickerView.swift +0 -209
- package/apps/mac/Sources/Core/Desktop/WindowCapture.swift +0 -33
- package/apps/mac/Sources/Core/Desktop/WindowDragSnapController.swift +0 -429
- package/apps/mac/Sources/Core/Desktop/WindowPreviewCard.swift +0 -100
- package/apps/mac/Sources/Core/Desktop/WindowPreviewStore.swift +0 -112
- package/apps/mac/Sources/Core/Desktop/WindowSelectionStore.swift +0 -76
- package/apps/mac/Sources/Core/Desktop/WindowTiler.swift +0 -2222
- package/apps/mac/Sources/Core/Input/EventTapBreaker.swift +0 -124
- package/apps/mac/Sources/Core/Input/EventTapThread.swift +0 -54
- package/apps/mac/Sources/Core/Input/InputCaptureResetCenter.swift +0 -20
- package/apps/mac/Sources/Core/Input/KeyboardRemapConfig.swift +0 -69
- package/apps/mac/Sources/Core/Input/KeyboardRemapController.swift +0 -346
- package/apps/mac/Sources/Core/Input/KeyboardRemapStore.swift +0 -141
- package/apps/mac/Sources/Core/Input/MouseGestureConfig.swift +0 -499
- package/apps/mac/Sources/Core/Input/MouseGestureController.swift +0 -2271
- package/apps/mac/Sources/Core/Input/MouseInputDeviceStore.swift +0 -98
- package/apps/mac/Sources/Core/Input/MouseInputEventViewer.swift +0 -272
- package/apps/mac/Sources/Core/Input/MouseShortcutStore.swift +0 -170
- package/apps/mac/Sources/Core/Input/SecureEventInputMonitor.swift +0 -39
- package/apps/mac/Sources/Core/Input/ShapeRecognizer.swift +0 -624
- package/apps/mac/Sources/Core/Input/TapBudgetMeter.swift +0 -56
- package/apps/mac/Sources/Core/Overlays/AppWindowShell.swift +0 -63
- package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeState.swift +0 -1566
- package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeView.swift +0 -1927
- package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeWindow.swift +0 -196
- package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteView.swift +0 -307
- package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +0 -67
- package/apps/mac/Sources/Core/Overlays/HUD/CheatSheetHUD.swift +0 -576
- package/apps/mac/Sources/Core/Overlays/HUD/HUDBottomBar.swift +0 -279
- package/apps/mac/Sources/Core/Overlays/HUD/HUDController.swift +0 -1158
- package/apps/mac/Sources/Core/Overlays/HUD/HUDLeftBar.swift +0 -849
- package/apps/mac/Sources/Core/Overlays/HUD/HUDMinimap.swift +0 -179
- package/apps/mac/Sources/Core/Overlays/HUD/HUDRightBar.swift +0 -596
- package/apps/mac/Sources/Core/Overlays/HUD/HUDState.swift +0 -367
- package/apps/mac/Sources/Core/Overlays/HUD/HUDTopBar.swift +0 -243
- package/apps/mac/Sources/Core/Overlays/HUD/LauncherHUD.swift +0 -334
- package/apps/mac/Sources/Core/Overlays/HUD/LayerBezel.swift +0 -203
- package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchState.swift +0 -280
- package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchView.swift +0 -422
- package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchWindow.swift +0 -94
- package/apps/mac/Sources/Core/Overlays/OverlayPanelShell.swift +0 -241
- package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapState.swift +0 -3135
- package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapView.swift +0 -3977
- package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapWindowController.swift +0 -119
- package/apps/mac/Sources/Core/Overlays/ScreenOverlayCanvasController.swift +0 -1217
- package/apps/mac/Sources/Core/Overlays/Voice/VoiceCommandWindow.swift +0 -1575
- package/apps/mac/Sources/Core/Pi/PiAuthNextStepCard.swift +0 -148
- package/apps/mac/Sources/Core/Pi/PiAuthPromptCard.swift +0 -90
- package/apps/mac/Sources/Core/Pi/PiChatDock.swift +0 -564
- package/apps/mac/Sources/Core/Pi/PiChatSession.swift +0 -1948
- package/apps/mac/Sources/Core/Pi/PiInstallCallout.swift +0 -86
- package/apps/mac/Sources/Core/Pi/PiProviderSetupCallout.swift +0 -99
- package/apps/mac/Sources/Core/Pi/PiWorkspaceView.swift +0 -510
- package/apps/mac/Sources/Core/System/Capability.swift +0 -79
- package/apps/mac/Sources/Core/System/DiagnosticLog.swift +0 -373
- package/apps/mac/Sources/Core/System/EventBus.swift +0 -31
- package/apps/mac/Sources/Core/System/PermissionChecker.swift +0 -224
- package/apps/mac/Sources/Core/System/ProcessModel.swift +0 -199
- package/apps/mac/Sources/Core/System/ProcessQuery.swift +0 -151
- package/apps/mac/Sources/Core/System/SystemTelemetryMonitor.swift +0 -273
- package/apps/mac/Sources/Core/Voice/AdvisorLearningStore.swift +0 -90
- package/apps/mac/Sources/Core/Voice/AgentSession.swift +0 -377
- package/apps/mac/Sources/Core/Voice/AudioProvider.swift +0 -555
- package/apps/mac/Sources/Core/Voice/HandsOffSession.swift +0 -839
- package/apps/mac/Sources/Core/Voice/VoiceChatView.swift +0 -192
- package/apps/mac/Sources/Core/Voice/VoxClient.swift +0 -454
- package/apps/mac/Sources/Core/Workspace/Project.swift +0 -28
- package/apps/mac/Sources/Core/Workspace/ProjectScanner.swift +0 -141
- package/apps/mac/Sources/Core/Workspace/SessionLayerStore.swift +0 -285
- package/apps/mac/Sources/Core/Workspace/SessionManager.swift +0 -75
- package/apps/mac/Sources/Core/Workspace/Terminal/Terminal.swift +0 -259
- package/apps/mac/Sources/Core/Workspace/Terminal/TerminalQuery.swift +0 -156
- package/apps/mac/Sources/Core/Workspace/Terminal/TerminalSynthesizer.swift +0 -200
- package/apps/mac/Sources/Core/Workspace/Tmux/TmuxModel.swift +0 -60
- package/apps/mac/Sources/Core/Workspace/Tmux/TmuxQuery.swift +0 -105
- package/apps/mac/Sources/Core/Workspace/WorkspaceManager.swift +0 -1027
- package/apps/mac/Sources/UI/ActionRow.swift +0 -78
- package/apps/mac/Sources/UI/OrphanRow.swift +0 -129
- package/apps/mac/Sources/UI/ProjectRow.swift +0 -368
- package/apps/mac/Sources/UI/TabGroupRow.swift +0 -178
- package/apps/mac/Sources/UI/Theme.swift +0 -164
- package/apps/mac/Tests/StageDragTests.swift +0 -333
- package/apps/mac/Tests/StageJoinTests.swift +0 -313
- package/apps/mac/Tests/StageManagerTests.swift +0 -280
- package/apps/mac/Tests/StageTileTests.swift +0 -353
- package/swift/Package.swift +0 -20
- package/swift/Sources/DeckKit/DeckAction.swift +0 -51
- package/swift/Sources/DeckKit/DeckBridgeSecurity.swift +0 -152
- package/swift/Sources/DeckKit/DeckCockpit.swift +0 -82
- package/swift/Sources/DeckKit/DeckHost.swift +0 -7
- package/swift/Sources/DeckKit/DeckManifest.swift +0 -145
- package/swift/Sources/DeckKit/DeckRuntimeSnapshot.swift +0 -533
- package/swift/Sources/DeckKit/DeckTrackpad.swift +0 -63
- package/swift/Sources/DeckKit/DeckValue.swift +0 -93
- package/swift/Sources/DeckKit/DeckVoiceError.swift +0 -88
- 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
|
-
}
|