@lattices/cli 0.4.10 → 0.4.12
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/LICENSE +21 -0
- package/README.md +13 -13
- package/{app → apps/mac}/Lattices.app/Contents/Info.plist +10 -2
- package/{app → apps/mac}/Lattices.app/Contents/MacOS/Lattices +0 -0
- package/{app → apps/mac}/Package.swift +2 -1
- package/apps/mac/Resources/Pets/assistant-spark/pet.json +62 -0
- package/apps/mac/Resources/Pets/assistant-spark/spritesheet.webp +0 -0
- package/apps/mac/Resources/Pets/scout-ranger/pet.json +6 -0
- package/apps/mac/Resources/Pets/scout-ranger/spritesheet.webp +0 -0
- package/apps/mac/Sources/AppShell/AppActivationCoordinator.swift +27 -0
- package/apps/mac/Sources/AppShell/AppDelegate.swift +189 -0
- package/apps/mac/Sources/AppShell/AppServicesBootstrap.swift +25 -0
- package/{app → apps/mac}/Sources/AppShell/AppShellView.swift +18 -3
- package/{app → apps/mac}/Sources/AppShell/AppUpdater.swift +4 -3
- package/apps/mac/Sources/AppShell/HotkeyBootstrap.swift +87 -0
- package/{app → apps/mac}/Sources/AppShell/LatticesRuntime.swift +43 -0
- package/{app → apps/mac}/Sources/AppShell/MainView.swift +116 -63
- package/apps/mac/Sources/AppShell/MenuBarController.swift +177 -0
- package/{app → apps/mac}/Sources/AppShell/OnboardingView.swift +72 -60
- package/apps/mac/Sources/AppShell/PermissionsAssistantView.swift +366 -0
- package/apps/mac/Sources/AppShell/PermissionsAssistantWindow.swift +70 -0
- package/{app → apps/mac}/Sources/AppShell/Preferences.swift +37 -2
- package/{app → apps/mac}/Sources/AppShell/SettingsView.swift +815 -156
- package/{app → apps/mac}/Sources/AppShell/SettingsWindow.swift +10 -0
- package/apps/mac/Sources/AppShell/WorkspaceInspectorPresenter.swift +13 -0
- package/{app → apps/mac}/Sources/Core/Actions/HotkeyStore.swift +6 -1
- package/{app → apps/mac}/Sources/Core/Actions/IntentEngine.swift +2 -0
- package/{app → apps/mac}/Sources/Core/Daemon/DaemonServer.swift +5 -0
- package/{app → apps/mac}/Sources/Core/Daemon/LatticesApi.swift +365 -0
- package/{app → apps/mac}/Sources/Core/Desktop/OcrModel.swift +17 -13
- package/apps/mac/Sources/Core/Desktop/WindowCapture.swift +33 -0
- package/{app → apps/mac}/Sources/Core/Desktop/WindowDragSnapController.swift +18 -217
- package/{app → apps/mac}/Sources/Core/Desktop/WindowPreviewStore.swift +4 -5
- package/{app → apps/mac}/Sources/Core/Desktop/WindowTiler.swift +19 -13
- package/apps/mac/Sources/Core/Input/EventTapBreaker.swift +124 -0
- package/apps/mac/Sources/Core/Input/EventTapThread.swift +54 -0
- package/apps/mac/Sources/Core/Input/InputCaptureResetCenter.swift +20 -0
- package/apps/mac/Sources/Core/Input/KeyboardRemapController.swift +335 -0
- package/apps/mac/Sources/Core/Input/KeyboardRemapStore.swift +141 -0
- package/{app → apps/mac}/Sources/Core/Input/MouseGestureConfig.swift +155 -20
- package/apps/mac/Sources/Core/Input/MouseGestureController.swift +2271 -0
- package/apps/mac/Sources/Core/Input/MouseShortcutStore.swift +170 -0
- package/apps/mac/Sources/Core/Input/SecureEventInputMonitor.swift +39 -0
- package/apps/mac/Sources/Core/Input/ShapeRecognizer.swift +624 -0
- package/apps/mac/Sources/Core/Input/TapBudgetMeter.swift +56 -0
- package/{app → apps/mac}/Sources/Core/Overlays/ScreenMap/ScreenMapState.swift +8 -8
- package/apps/mac/Sources/Core/Overlays/ScreenOverlayCanvasController.swift +1264 -0
- package/{app → apps/mac}/Sources/Core/Overlays/Voice/VoiceCommandWindow.swift +11 -23
- package/{app → apps/mac}/Sources/Core/Pi/PiChatDock.swift +90 -43
- package/{app → apps/mac}/Sources/Core/Pi/PiChatSession.swift +676 -43
- package/{app → apps/mac}/Sources/Core/Pi/PiProviderSetupCallout.swift +5 -5
- package/{app → apps/mac}/Sources/Core/Pi/PiWorkspaceView.swift +93 -44
- package/apps/mac/Sources/Core/System/Capability.swift +79 -0
- package/{app → apps/mac}/Sources/Core/System/PermissionChecker.swift +43 -8
- package/{app → apps/mac}/Sources/Core/Voice/AudioProvider.swift +225 -56
- package/bin/handsoff-infer.ts +14 -5
- package/bin/handsoff-worker.ts +11 -7
- package/bin/infer.ts +406 -0
- package/bin/lattices-app.ts +57 -7
- package/bin/lattices-dev +40 -1
- package/bin/lattices.ts +1 -1
- package/docs/agent-execution-plan.md +9 -9
- package/docs/api.md +119 -0
- package/docs/app.md +1 -0
- package/docs/companion-deck.md +1 -1
- package/docs/gesture-customization-proposal.md +520 -0
- package/docs/mouse-gestures.md +79 -0
- package/docs/overview.md +2 -2
- package/docs/presentation-execution-review.md +9 -9
- package/docs/proposals/LAT-001-gesture-visual-customization.md +522 -0
- package/docs/proposals/LAT-002-shared-overlay-canvas.md +353 -0
- package/docs/proposals/LAT-003-menu-bar-controller-architecture.md +291 -0
- package/docs/proposals/LAT-004-interactive-overlay-actors.md +534 -0
- package/docs/reference/dewey.config.ts +74 -0
- package/docs/reference/install-agent.md +79 -0
- package/docs/repo-structure.md +100 -0
- package/docs/voice-error-model.md +7 -7
- package/docs/voice.md +18 -0
- package/package.json +23 -13
- package/swift/Package.swift +20 -0
- package/swift/Sources/DeckKit/DeckAction.swift +51 -0
- package/swift/Sources/DeckKit/DeckBridgeSecurity.swift +152 -0
- package/swift/Sources/DeckKit/DeckCockpit.swift +82 -0
- package/swift/Sources/DeckKit/DeckHost.swift +7 -0
- package/swift/Sources/DeckKit/DeckManifest.swift +145 -0
- package/swift/Sources/DeckKit/DeckRuntimeSnapshot.swift +533 -0
- package/swift/Sources/DeckKit/DeckTrackpad.swift +63 -0
- package/swift/Sources/DeckKit/DeckValue.swift +93 -0
- package/swift/Sources/DeckKit/DeckVoiceError.swift +88 -0
- package/swift/Tests/DeckKitTests/DeckKitTests.swift +286 -0
- package/app/Sources/AppShell/AppDelegate.swift +0 -408
- package/app/Sources/Core/Input/KeyboardRemapController.swift +0 -184
- package/app/Sources/Core/Input/KeyboardRemapStore.swift +0 -84
- package/app/Sources/Core/Input/MouseGestureController.swift +0 -1203
- package/app/Sources/Core/Input/MouseShortcutStore.swift +0 -107
- /package/{app → apps/mac}/Info.plist +0 -0
- /package/{app → apps/mac}/Lattices.app/Contents/Resources/AppIcon.icns +0 -0
- /package/{app → apps/mac}/Lattices.app/Contents/Resources/tap.wav +0 -0
- /package/{app → apps/mac}/Lattices.app/Contents/_CodeSignature/CodeResources +0 -0
- /package/{app → apps/mac}/Lattices.entitlements +0 -0
- /package/{app → apps/mac}/Resources/tap.wav +0 -0
- /package/{app → apps/mac}/Sources/AppShell/App.swift +0 -0
- /package/{app → apps/mac}/Sources/AppShell/CliActionLauncher.swift +0 -0
- /package/{app → apps/mac}/Sources/AppShell/HomeDashboardView.swift +0 -0
- /package/{app → apps/mac}/Sources/AppShell/KeyRecorderView.swift +0 -0
- /package/{app → apps/mac}/Sources/AppShell/MainWindow.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/HotkeyManager.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/IntentSchema.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/CreateLayerIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/DistributeIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/FocusIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/HelpIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/KillIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/LatticeIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/LaunchIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/ListSessionsIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/ListWindowsIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/ScanIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/SearchIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/SwitchLayerIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/TileIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/PaletteCommand.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/VoiceIntentResolver.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Companion/CompanionActivityLog.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Companion/CompanionKeyboardController.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionCockpit.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Companion/LatticesDeckHost.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Daemon/DaemonProtocol.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/AccessibilityTextExtractor.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/AppTypeClassifier.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/DesktopModel.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/DesktopModelTypes.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/InventoryManager.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/InventoryPath.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/MouseFinder.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/OcrStore.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/PlacementSpec.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/SessionWindowLocator.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/TilePickerView.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/WindowPreviewCard.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/WindowSelectionStore.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Input/KeyboardRemapConfig.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Input/MouseInputDeviceStore.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Input/MouseInputEventViewer.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/AppWindowShell.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/CommandMode/CommandModeState.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/CommandMode/CommandModeView.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/CommandMode/CommandModeWindow.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/CommandPalette/CommandPaletteView.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/CheatSheetHUD.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDBottomBar.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDController.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDLeftBar.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDMinimap.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDRightBar.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDState.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDTopBar.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/LauncherHUD.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/LayerBezel.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/OmniSearch/OmniSearchState.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/OmniSearch/OmniSearchView.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/OmniSearch/OmniSearchWindow.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/OverlayPanelShell.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/ScreenMap/ScreenMapView.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/ScreenMap/ScreenMapWindowController.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Pi/PiAuthNextStepCard.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Pi/PiAuthPromptCard.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Pi/PiInstallCallout.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/System/DiagnosticLog.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/System/EventBus.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/System/ProcessModel.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/System/ProcessQuery.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/System/SystemTelemetryMonitor.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Voice/AdvisorLearningStore.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Voice/AgentSession.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Voice/HandsOffSession.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Voice/VoiceChatView.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Voice/VoxClient.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/Project.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/ProjectScanner.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/SessionLayerStore.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/SessionManager.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/Terminal/Terminal.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/Terminal/TerminalQuery.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/Terminal/TerminalSynthesizer.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/Tmux/TmuxModel.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/Tmux/TmuxQuery.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/WorkspaceManager.swift +0 -0
- /package/{app → apps/mac}/Sources/UI/ActionRow.swift +0 -0
- /package/{app → apps/mac}/Sources/UI/OrphanRow.swift +0 -0
- /package/{app → apps/mac}/Sources/UI/ProjectRow.swift +0 -0
- /package/{app → apps/mac}/Sources/UI/TabGroupRow.swift +0 -0
- /package/{app → apps/mac}/Sources/UI/Theme.swift +0 -0
- /package/{app → apps/mac}/Tests/StageDragTests.swift +0 -0
- /package/{app → apps/mac}/Tests/StageJoinTests.swift +0 -0
- /package/{app → apps/mac}/Tests/StageManagerTests.swift +0 -0
- /package/{app → apps/mac}/Tests/StageTileTests.swift +0 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import SwiftUI
|
|
3
|
+
|
|
4
|
+
/// Calm, opt-in setup surface for capabilities that require an OS permission.
|
|
5
|
+
/// Never opens automatically — only from explicit entry points
|
|
6
|
+
/// (banner button, onboarding row, Settings, feature gate).
|
|
7
|
+
struct PermissionsAssistantView: View {
|
|
8
|
+
@ObservedObject private var permChecker = PermissionChecker.shared
|
|
9
|
+
@ObservedObject private var prefs = Preferences.shared
|
|
10
|
+
@Binding var selected: Capability
|
|
11
|
+
var onClose: () -> Void
|
|
12
|
+
|
|
13
|
+
var body: some View {
|
|
14
|
+
HStack(spacing: 0) {
|
|
15
|
+
sidebar
|
|
16
|
+
.frame(width: 240, alignment: .top)
|
|
17
|
+
|
|
18
|
+
Rectangle()
|
|
19
|
+
.fill(Palette.border)
|
|
20
|
+
.frame(width: 0.5)
|
|
21
|
+
.frame(maxHeight: .infinity)
|
|
22
|
+
|
|
23
|
+
detail
|
|
24
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
|
25
|
+
}
|
|
26
|
+
.background(PanelBackground())
|
|
27
|
+
.preferredColorScheme(.dark)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// MARK: - Sidebar
|
|
31
|
+
|
|
32
|
+
private var sidebar: some View {
|
|
33
|
+
VStack(alignment: .leading, spacing: 14) {
|
|
34
|
+
VStack(alignment: .leading, spacing: 6) {
|
|
35
|
+
Text("CAPABILITIES")
|
|
36
|
+
.font(Typo.pixel(14))
|
|
37
|
+
.foregroundColor(Palette.textDim)
|
|
38
|
+
.tracking(1)
|
|
39
|
+
Text("Lattices works with whichever of these you turn on. Nothing here runs unless you press the button.")
|
|
40
|
+
.font(Typo.caption(11))
|
|
41
|
+
.foregroundColor(Palette.textMuted)
|
|
42
|
+
.fixedSize(horizontal: false, vertical: true)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
VStack(spacing: 6) {
|
|
46
|
+
ForEach(Capability.allCases) { cap in
|
|
47
|
+
sidebarRow(cap)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
Spacer(minLength: 0)
|
|
52
|
+
}
|
|
53
|
+
.padding(16)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private func sidebarRow(_ cap: Capability) -> some View {
|
|
57
|
+
let active = selected == cap
|
|
58
|
+
let granted = cap.isGranted
|
|
59
|
+
|
|
60
|
+
return Button {
|
|
61
|
+
selected = cap
|
|
62
|
+
} label: {
|
|
63
|
+
HStack(alignment: .top, spacing: 10) {
|
|
64
|
+
Image(systemName: cap.iconName)
|
|
65
|
+
.font(.system(size: 11, weight: .semibold))
|
|
66
|
+
.foregroundColor(active ? Palette.text : Palette.textMuted)
|
|
67
|
+
.frame(width: 16, alignment: .center)
|
|
68
|
+
|
|
69
|
+
VStack(alignment: .leading, spacing: 3) {
|
|
70
|
+
Text(cap.title)
|
|
71
|
+
.font(Typo.mono(11))
|
|
72
|
+
.foregroundColor(active ? Palette.text : Palette.textMuted)
|
|
73
|
+
|
|
74
|
+
Text(cap.requirementLabel)
|
|
75
|
+
.font(Typo.caption(9.5))
|
|
76
|
+
.foregroundColor(Palette.textMuted.opacity(active ? 0.9 : 0.7))
|
|
77
|
+
.lineLimit(2)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
Spacer(minLength: 0)
|
|
81
|
+
|
|
82
|
+
statusDot(granted: granted)
|
|
83
|
+
.padding(.top, 2)
|
|
84
|
+
}
|
|
85
|
+
.padding(.horizontal, 10)
|
|
86
|
+
.padding(.vertical, 8)
|
|
87
|
+
.background(
|
|
88
|
+
RoundedRectangle(cornerRadius: 5)
|
|
89
|
+
.fill(active ? Palette.surfaceHov : Color.clear)
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
.buttonStyle(.plain)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private func statusDot(granted: Bool) -> some View {
|
|
96
|
+
Circle()
|
|
97
|
+
.fill(granted ? Palette.running : Palette.detach)
|
|
98
|
+
.frame(width: 6, height: 6)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// MARK: - Detail
|
|
102
|
+
|
|
103
|
+
private var detail: some View {
|
|
104
|
+
VStack(alignment: .leading, spacing: 0) {
|
|
105
|
+
hero(selected)
|
|
106
|
+
.padding(.horizontal, 24)
|
|
107
|
+
.padding(.top, 22)
|
|
108
|
+
.padding(.bottom, 16)
|
|
109
|
+
|
|
110
|
+
Rectangle().fill(Palette.border).frame(height: 0.5)
|
|
111
|
+
|
|
112
|
+
ScrollView {
|
|
113
|
+
VStack(alignment: .leading, spacing: 18) {
|
|
114
|
+
valueCard(selected)
|
|
115
|
+
statusCard(selected)
|
|
116
|
+
actionsCard(selected)
|
|
117
|
+
|
|
118
|
+
if prefs.isCapabilityDismissed(selected.rawValue) {
|
|
119
|
+
Text("You snoozed this earlier. We will not nag — opening this from a feature will surface it again.")
|
|
120
|
+
.font(Typo.caption(10))
|
|
121
|
+
.foregroundColor(Palette.textMuted)
|
|
122
|
+
.padding(.horizontal, 4)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
.padding(.horizontal, 24)
|
|
126
|
+
.padding(.vertical, 18)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private func hero(_ cap: Capability) -> some View {
|
|
132
|
+
HStack(alignment: .center, spacing: 14) {
|
|
133
|
+
ZStack {
|
|
134
|
+
RoundedRectangle(cornerRadius: 8)
|
|
135
|
+
.fill(Palette.surface)
|
|
136
|
+
.frame(width: 40, height: 40)
|
|
137
|
+
Image(systemName: cap.iconName)
|
|
138
|
+
.font(.system(size: 16, weight: .semibold))
|
|
139
|
+
.foregroundColor(.white.opacity(0.85))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
VStack(alignment: .leading, spacing: 4) {
|
|
143
|
+
Text(cap.title)
|
|
144
|
+
.font(Typo.heading(14))
|
|
145
|
+
.foregroundColor(Palette.text)
|
|
146
|
+
Text(cap.requirementLabel)
|
|
147
|
+
.font(Typo.mono(10))
|
|
148
|
+
.foregroundColor(Palette.textMuted)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
Spacer()
|
|
152
|
+
|
|
153
|
+
buildChannelBadge
|
|
154
|
+
statusBadge(cap)
|
|
155
|
+
|
|
156
|
+
Button(action: onClose) {
|
|
157
|
+
Image(systemName: "xmark")
|
|
158
|
+
.font(.system(size: 10, weight: .bold))
|
|
159
|
+
.foregroundColor(Palette.textMuted)
|
|
160
|
+
.frame(width: 22, height: 22)
|
|
161
|
+
}
|
|
162
|
+
.buttonStyle(.plain)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private func statusBadge(_ cap: Capability) -> some View {
|
|
167
|
+
let granted = cap.isGranted
|
|
168
|
+
let label = granted ? "ON" : (prefs.isCapabilityDismissed(cap.rawValue) ? "SNOOZED" : "OFF")
|
|
169
|
+
let color: Color = granted ? Palette.running : Palette.detach
|
|
170
|
+
|
|
171
|
+
return Text(label)
|
|
172
|
+
.font(Typo.monoBold(9))
|
|
173
|
+
.foregroundColor(color)
|
|
174
|
+
.padding(.horizontal, 6)
|
|
175
|
+
.padding(.vertical, 3)
|
|
176
|
+
.background(
|
|
177
|
+
Capsule().fill(color.opacity(0.12))
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private var buildChannelBadge: some View {
|
|
182
|
+
let tint = LatticesRuntime.isDevBuild ? Palette.detach : Palette.running
|
|
183
|
+
|
|
184
|
+
return Text(LatticesRuntime.buildChannelLabel)
|
|
185
|
+
.font(Typo.monoBold(9))
|
|
186
|
+
.foregroundColor(tint)
|
|
187
|
+
.padding(.horizontal, 6)
|
|
188
|
+
.padding(.vertical, 3)
|
|
189
|
+
.background(
|
|
190
|
+
Capsule()
|
|
191
|
+
.fill(tint.opacity(0.12))
|
|
192
|
+
.overlay(
|
|
193
|
+
Capsule()
|
|
194
|
+
.strokeBorder(tint.opacity(0.28), lineWidth: 0.5)
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private func valueCard(_ cap: Capability) -> some View {
|
|
200
|
+
sectionCard(title: "WHAT YOU GET") {
|
|
201
|
+
VStack(alignment: .leading, spacing: 6) {
|
|
202
|
+
Text(cap.pitch)
|
|
203
|
+
.font(Typo.body(12))
|
|
204
|
+
.foregroundColor(Palette.text)
|
|
205
|
+
.lineSpacing(3)
|
|
206
|
+
Text(cap.why)
|
|
207
|
+
.font(Typo.caption(10))
|
|
208
|
+
.foregroundColor(Palette.textMuted)
|
|
209
|
+
.lineSpacing(2)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private func statusCard(_ cap: Capability) -> some View {
|
|
215
|
+
sectionCard(title: "STATUS") {
|
|
216
|
+
HStack(spacing: 8) {
|
|
217
|
+
Image(systemName: cap.isGranted ? "checkmark.circle.fill" : "exclamationmark.circle")
|
|
218
|
+
.font(.system(size: 12))
|
|
219
|
+
.foregroundColor(cap.isGranted ? Palette.running : Palette.detach)
|
|
220
|
+
Text(cap.isGranted ? cap.whenGrantedDetail : "Not enabled. Lattices works without it; the rest of the app stays usable.")
|
|
221
|
+
.font(Typo.mono(11))
|
|
222
|
+
.foregroundColor(Palette.text)
|
|
223
|
+
Spacer(minLength: 0)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private func actionsCard(_ cap: Capability) -> some View {
|
|
229
|
+
sectionCard(title: "ACTIONS") {
|
|
230
|
+
VStack(alignment: .leading, spacing: 10) {
|
|
231
|
+
primaryAction(cap)
|
|
232
|
+
|
|
233
|
+
HStack(spacing: 10) {
|
|
234
|
+
if !cap.isGranted {
|
|
235
|
+
Button {
|
|
236
|
+
prefs.dismissCapability(cap.rawValue)
|
|
237
|
+
} label: {
|
|
238
|
+
Text(prefs.isCapabilityDismissed(cap.rawValue) ? "Snoozed" : "Maybe later")
|
|
239
|
+
.font(Typo.monoBold(10))
|
|
240
|
+
.foregroundColor(Palette.textMuted)
|
|
241
|
+
.padding(.horizontal, 10)
|
|
242
|
+
.padding(.vertical, 6)
|
|
243
|
+
.background(
|
|
244
|
+
RoundedRectangle(cornerRadius: 4)
|
|
245
|
+
.fill(Palette.surface)
|
|
246
|
+
.overlay(
|
|
247
|
+
RoundedRectangle(cornerRadius: 4)
|
|
248
|
+
.strokeBorder(Palette.border, lineWidth: 0.5)
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
.buttonStyle(.plain)
|
|
253
|
+
.disabled(prefs.isCapabilityDismissed(cap.rawValue))
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
Button {
|
|
257
|
+
openSystemSettings(cap)
|
|
258
|
+
} label: {
|
|
259
|
+
HStack(spacing: 4) {
|
|
260
|
+
Image(systemName: "arrow.up.forward.app")
|
|
261
|
+
.font(.system(size: 9))
|
|
262
|
+
Text("Open System Settings")
|
|
263
|
+
.font(Typo.monoBold(10))
|
|
264
|
+
}
|
|
265
|
+
.foregroundColor(Palette.textMuted)
|
|
266
|
+
.padding(.horizontal, 10)
|
|
267
|
+
.padding(.vertical, 6)
|
|
268
|
+
.background(
|
|
269
|
+
RoundedRectangle(cornerRadius: 4)
|
|
270
|
+
.fill(Palette.surface)
|
|
271
|
+
.overlay(
|
|
272
|
+
RoundedRectangle(cornerRadius: 4)
|
|
273
|
+
.strokeBorder(Palette.border, lineWidth: 0.5)
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
.buttonStyle(.plain)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
Text(actionFootnote(cap))
|
|
281
|
+
.font(Typo.caption(9.5))
|
|
282
|
+
.foregroundColor(Palette.textMuted)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
@ViewBuilder
|
|
288
|
+
private func primaryAction(_ cap: Capability) -> some View {
|
|
289
|
+
if cap.isGranted {
|
|
290
|
+
HStack(spacing: 6) {
|
|
291
|
+
Image(systemName: "checkmark.circle.fill")
|
|
292
|
+
.foregroundColor(Palette.running)
|
|
293
|
+
Text("Enabled")
|
|
294
|
+
.font(Typo.monoBold(11))
|
|
295
|
+
.foregroundColor(Palette.running)
|
|
296
|
+
}
|
|
297
|
+
.padding(.vertical, 4)
|
|
298
|
+
} else {
|
|
299
|
+
Button {
|
|
300
|
+
triggerPrimary(cap)
|
|
301
|
+
} label: {
|
|
302
|
+
Text(primaryLabel(cap))
|
|
303
|
+
.angularButton(.white, filled: false)
|
|
304
|
+
}
|
|
305
|
+
.buttonStyle(.plain)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private func primaryLabel(_ cap: Capability) -> String {
|
|
310
|
+
switch cap {
|
|
311
|
+
case .windowControl: return "Request Accessibility"
|
|
312
|
+
case .screenSearch: return "Enable OCR"
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private func actionFootnote(_ cap: Capability) -> String {
|
|
317
|
+
switch cap {
|
|
318
|
+
case .windowControl:
|
|
319
|
+
return "macOS will add Lattices to its Accessibility list. You finish the toggle in System Settings."
|
|
320
|
+
case .screenSearch:
|
|
321
|
+
return "Enabling this turns on OCR and asks macOS for Screen Recording on this Mac."
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private func triggerPrimary(_ cap: Capability) {
|
|
326
|
+
// The primary action is the explicit user gesture — clear any prior snooze.
|
|
327
|
+
prefs.clearDismissal(cap.rawValue)
|
|
328
|
+
|
|
329
|
+
switch cap {
|
|
330
|
+
case .windowControl:
|
|
331
|
+
permChecker.requestAccessibility()
|
|
332
|
+
case .screenSearch:
|
|
333
|
+
// Turning on OCR is the moment we ask for Screen Recording.
|
|
334
|
+
OcrModel.shared.setEnabled(true)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
private func openSystemSettings(_ cap: Capability) {
|
|
339
|
+
switch cap {
|
|
340
|
+
case .windowControl: permChecker.openAccessibilitySettings()
|
|
341
|
+
case .screenSearch: permChecker.openScreenRecordingSettings()
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// MARK: - Section card
|
|
346
|
+
|
|
347
|
+
private func sectionCard<Content: View>(title: String, @ViewBuilder content: () -> Content) -> some View {
|
|
348
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
349
|
+
Text(title)
|
|
350
|
+
.font(Typo.pixel(11))
|
|
351
|
+
.foregroundColor(Palette.textDim)
|
|
352
|
+
.tracking(1)
|
|
353
|
+
content()
|
|
354
|
+
.padding(12)
|
|
355
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
356
|
+
.background(
|
|
357
|
+
RoundedRectangle(cornerRadius: 6)
|
|
358
|
+
.fill(Palette.surface)
|
|
359
|
+
.overlay(
|
|
360
|
+
RoundedRectangle(cornerRadius: 6)
|
|
361
|
+
.strokeBorder(Palette.border, lineWidth: 0.5)
|
|
362
|
+
)
|
|
363
|
+
)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import SwiftUI
|
|
3
|
+
|
|
4
|
+
/// Dedicated window that hosts the Permissions Assistant. Singleton, opened
|
|
5
|
+
/// only on explicit user intent (banner button, onboarding row, Settings,
|
|
6
|
+
/// feature gate). Never shown automatically on app launch.
|
|
7
|
+
final class PermissionsAssistantWindowController: ObservableObject {
|
|
8
|
+
static let shared = PermissionsAssistantWindowController()
|
|
9
|
+
|
|
10
|
+
private var window: NSWindow?
|
|
11
|
+
@Published private(set) var focusedCapability: Capability = .windowControl
|
|
12
|
+
|
|
13
|
+
var isVisible: Bool { window?.isVisible ?? false }
|
|
14
|
+
|
|
15
|
+
/// Open the assistant focused on the given capability. If `cap` is nil,
|
|
16
|
+
/// the first missing capability is selected, falling back to `windowControl`.
|
|
17
|
+
func show(focus cap: Capability? = nil) {
|
|
18
|
+
let target = cap ?? Capability.missing.first ?? .windowControl
|
|
19
|
+
focusedCapability = target
|
|
20
|
+
|
|
21
|
+
if let existing = window {
|
|
22
|
+
existing.makeKeyAndOrderFront(nil)
|
|
23
|
+
NSApp.activate(ignoringOtherApps: true)
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let host = HostView(controller: self)
|
|
28
|
+
|
|
29
|
+
let w = AppWindowShell.makeWindow(
|
|
30
|
+
config: .init(
|
|
31
|
+
title: "Lattices Permissions",
|
|
32
|
+
titleVisible: false,
|
|
33
|
+
initialSize: NSSize(width: 720, height: 520),
|
|
34
|
+
minSize: NSSize(width: 640, height: 460),
|
|
35
|
+
maxSize: NSSize(width: 1100, height: 800)
|
|
36
|
+
),
|
|
37
|
+
rootView: host
|
|
38
|
+
)
|
|
39
|
+
AppWindowShell.positionCentered(w)
|
|
40
|
+
AppWindowShell.present(w)
|
|
41
|
+
self.window = w
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func close() {
|
|
45
|
+
window?.orderOut(nil)
|
|
46
|
+
window = nil
|
|
47
|
+
AppDelegate.updateActivationPolicy()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fileprivate func selectionBinding() -> Binding<Capability> {
|
|
51
|
+
Binding(
|
|
52
|
+
get: { self.focusedCapability },
|
|
53
|
+
set: { self.focusedCapability = $0 }
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// SwiftUI host that observes the controller so the assistant updates when
|
|
59
|
+
// `focusedCapability` is reassigned by an external caller (e.g. clicking a
|
|
60
|
+
// different feature gate while the window is already open).
|
|
61
|
+
private struct HostView: View {
|
|
62
|
+
@ObservedObject var controller: PermissionsAssistantWindowController
|
|
63
|
+
|
|
64
|
+
var body: some View {
|
|
65
|
+
PermissionsAssistantView(
|
|
66
|
+
selected: controller.selectionBinding(),
|
|
67
|
+
onClose: { controller.close() }
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -15,6 +15,8 @@ class Preferences: ObservableObject {
|
|
|
15
15
|
static let cockpitLayout = "companion.cockpit.layout"
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
private static let dismissedCapabilitiesKey = "permissions.dismissed"
|
|
19
|
+
|
|
18
20
|
@Published var terminal: Terminal {
|
|
19
21
|
didSet { UserDefaults.standard.set(terminal.rawValue, forKey: "terminal") }
|
|
20
22
|
}
|
|
@@ -140,6 +142,30 @@ class Preferences: ObservableObject {
|
|
|
140
142
|
didSet { UserDefaults.standard.set(ocrAccuracy, forKey: "ocr.accuracy") }
|
|
141
143
|
}
|
|
142
144
|
|
|
145
|
+
// MARK: - Permissions Assistant
|
|
146
|
+
|
|
147
|
+
/// Capabilities the user has explicitly snoozed. Cleared per-capability when
|
|
148
|
+
/// the user re-enters the relevant feature. Persisted as raw values.
|
|
149
|
+
@Published var dismissedCapabilities: Set<String> {
|
|
150
|
+
didSet {
|
|
151
|
+
UserDefaults.standard.set(Array(dismissedCapabilities), forKey: Self.dismissedCapabilitiesKey)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
func dismissCapability(_ rawValue: String) {
|
|
156
|
+
dismissedCapabilities.insert(rawValue)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
func clearDismissal(_ rawValue: String) {
|
|
160
|
+
if dismissedCapabilities.contains(rawValue) {
|
|
161
|
+
dismissedCapabilities.remove(rawValue)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
func isCapabilityDismissed(_ rawValue: String) -> Bool {
|
|
166
|
+
dismissedCapabilities.contains(rawValue)
|
|
167
|
+
}
|
|
168
|
+
|
|
143
169
|
init() {
|
|
144
170
|
if let saved = UserDefaults.standard.string(forKey: "terminal"),
|
|
145
171
|
let t = Terminal(rawValue: saved), t.isInstalled {
|
|
@@ -201,8 +227,14 @@ class Preferences: ObservableObject {
|
|
|
201
227
|
let savedBudgetUSD = UserDefaults.standard.double(forKey: "claude.advisorBudget")
|
|
202
228
|
self.advisorBudgetUSD = savedBudgetUSD > 0 ? savedBudgetUSD : 0.50
|
|
203
229
|
|
|
204
|
-
// Search & OCR
|
|
205
|
-
|
|
230
|
+
// Search & OCR. Default off until the user explicitly enables it from
|
|
231
|
+
// the Permissions Assistant or Search settings. Honors any explicit
|
|
232
|
+
// ocr.disabled value already saved (true=off, false=on).
|
|
233
|
+
if UserDefaults.standard.object(forKey: "ocr.disabled") != nil {
|
|
234
|
+
self.ocrEnabled = !UserDefaults.standard.bool(forKey: "ocr.disabled")
|
|
235
|
+
} else {
|
|
236
|
+
self.ocrEnabled = false
|
|
237
|
+
}
|
|
206
238
|
|
|
207
239
|
let savedInterval = UserDefaults.standard.double(forKey: "ocr.interval")
|
|
208
240
|
self.ocrQuickInterval = savedInterval > 0 ? savedInterval : 60
|
|
@@ -221,6 +253,9 @@ class Preferences: ObservableObject {
|
|
|
221
253
|
|
|
222
254
|
let savedAcc = UserDefaults.standard.string(forKey: "ocr.accuracy") ?? "accurate"
|
|
223
255
|
self.ocrAccuracy = savedAcc
|
|
256
|
+
|
|
257
|
+
let dismissed = UserDefaults.standard.stringArray(forKey: Self.dismissedCapabilitiesKey) ?? []
|
|
258
|
+
self.dismissedCapabilities = Set(dismissed)
|
|
224
259
|
}
|
|
225
260
|
|
|
226
261
|
func updateCompanionCockpitSlot(
|