@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.
Files changed (201) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +13 -13
  3. package/{app → apps/mac}/Lattices.app/Contents/Info.plist +10 -2
  4. package/{app → apps/mac}/Lattices.app/Contents/MacOS/Lattices +0 -0
  5. package/{app → apps/mac}/Package.swift +2 -1
  6. package/apps/mac/Resources/Pets/assistant-spark/pet.json +62 -0
  7. package/apps/mac/Resources/Pets/assistant-spark/spritesheet.webp +0 -0
  8. package/apps/mac/Resources/Pets/scout-ranger/pet.json +6 -0
  9. package/apps/mac/Resources/Pets/scout-ranger/spritesheet.webp +0 -0
  10. package/apps/mac/Sources/AppShell/AppActivationCoordinator.swift +27 -0
  11. package/apps/mac/Sources/AppShell/AppDelegate.swift +189 -0
  12. package/apps/mac/Sources/AppShell/AppServicesBootstrap.swift +25 -0
  13. package/{app → apps/mac}/Sources/AppShell/AppShellView.swift +18 -3
  14. package/{app → apps/mac}/Sources/AppShell/AppUpdater.swift +4 -3
  15. package/apps/mac/Sources/AppShell/HotkeyBootstrap.swift +87 -0
  16. package/{app → apps/mac}/Sources/AppShell/LatticesRuntime.swift +43 -0
  17. package/{app → apps/mac}/Sources/AppShell/MainView.swift +116 -63
  18. package/apps/mac/Sources/AppShell/MenuBarController.swift +177 -0
  19. package/{app → apps/mac}/Sources/AppShell/OnboardingView.swift +72 -60
  20. package/apps/mac/Sources/AppShell/PermissionsAssistantView.swift +366 -0
  21. package/apps/mac/Sources/AppShell/PermissionsAssistantWindow.swift +70 -0
  22. package/{app → apps/mac}/Sources/AppShell/Preferences.swift +37 -2
  23. package/{app → apps/mac}/Sources/AppShell/SettingsView.swift +815 -156
  24. package/{app → apps/mac}/Sources/AppShell/SettingsWindow.swift +10 -0
  25. package/apps/mac/Sources/AppShell/WorkspaceInspectorPresenter.swift +13 -0
  26. package/{app → apps/mac}/Sources/Core/Actions/HotkeyStore.swift +6 -1
  27. package/{app → apps/mac}/Sources/Core/Actions/IntentEngine.swift +2 -0
  28. package/{app → apps/mac}/Sources/Core/Daemon/DaemonServer.swift +5 -0
  29. package/{app → apps/mac}/Sources/Core/Daemon/LatticesApi.swift +365 -0
  30. package/{app → apps/mac}/Sources/Core/Desktop/OcrModel.swift +17 -13
  31. package/apps/mac/Sources/Core/Desktop/WindowCapture.swift +33 -0
  32. package/{app → apps/mac}/Sources/Core/Desktop/WindowDragSnapController.swift +18 -217
  33. package/{app → apps/mac}/Sources/Core/Desktop/WindowPreviewStore.swift +4 -5
  34. package/{app → apps/mac}/Sources/Core/Desktop/WindowTiler.swift +19 -13
  35. package/apps/mac/Sources/Core/Input/EventTapBreaker.swift +124 -0
  36. package/apps/mac/Sources/Core/Input/EventTapThread.swift +54 -0
  37. package/apps/mac/Sources/Core/Input/InputCaptureResetCenter.swift +20 -0
  38. package/apps/mac/Sources/Core/Input/KeyboardRemapController.swift +335 -0
  39. package/apps/mac/Sources/Core/Input/KeyboardRemapStore.swift +141 -0
  40. package/{app → apps/mac}/Sources/Core/Input/MouseGestureConfig.swift +155 -20
  41. package/apps/mac/Sources/Core/Input/MouseGestureController.swift +2271 -0
  42. package/apps/mac/Sources/Core/Input/MouseShortcutStore.swift +170 -0
  43. package/apps/mac/Sources/Core/Input/SecureEventInputMonitor.swift +39 -0
  44. package/apps/mac/Sources/Core/Input/ShapeRecognizer.swift +624 -0
  45. package/apps/mac/Sources/Core/Input/TapBudgetMeter.swift +56 -0
  46. package/{app → apps/mac}/Sources/Core/Overlays/ScreenMap/ScreenMapState.swift +8 -8
  47. package/apps/mac/Sources/Core/Overlays/ScreenOverlayCanvasController.swift +1264 -0
  48. package/{app → apps/mac}/Sources/Core/Overlays/Voice/VoiceCommandWindow.swift +11 -23
  49. package/{app → apps/mac}/Sources/Core/Pi/PiChatDock.swift +90 -43
  50. package/{app → apps/mac}/Sources/Core/Pi/PiChatSession.swift +676 -43
  51. package/{app → apps/mac}/Sources/Core/Pi/PiProviderSetupCallout.swift +5 -5
  52. package/{app → apps/mac}/Sources/Core/Pi/PiWorkspaceView.swift +93 -44
  53. package/apps/mac/Sources/Core/System/Capability.swift +79 -0
  54. package/{app → apps/mac}/Sources/Core/System/PermissionChecker.swift +43 -8
  55. package/{app → apps/mac}/Sources/Core/Voice/AudioProvider.swift +225 -56
  56. package/bin/handsoff-infer.ts +14 -5
  57. package/bin/handsoff-worker.ts +11 -7
  58. package/bin/infer.ts +406 -0
  59. package/bin/lattices-app.ts +57 -7
  60. package/bin/lattices-dev +40 -1
  61. package/bin/lattices.ts +1 -1
  62. package/docs/agent-execution-plan.md +9 -9
  63. package/docs/api.md +119 -0
  64. package/docs/app.md +1 -0
  65. package/docs/companion-deck.md +1 -1
  66. package/docs/gesture-customization-proposal.md +520 -0
  67. package/docs/mouse-gestures.md +79 -0
  68. package/docs/overview.md +2 -2
  69. package/docs/presentation-execution-review.md +9 -9
  70. package/docs/proposals/LAT-001-gesture-visual-customization.md +522 -0
  71. package/docs/proposals/LAT-002-shared-overlay-canvas.md +353 -0
  72. package/docs/proposals/LAT-003-menu-bar-controller-architecture.md +291 -0
  73. package/docs/proposals/LAT-004-interactive-overlay-actors.md +534 -0
  74. package/docs/reference/dewey.config.ts +74 -0
  75. package/docs/reference/install-agent.md +79 -0
  76. package/docs/repo-structure.md +100 -0
  77. package/docs/voice-error-model.md +7 -7
  78. package/docs/voice.md +18 -0
  79. package/package.json +23 -13
  80. package/swift/Package.swift +20 -0
  81. package/swift/Sources/DeckKit/DeckAction.swift +51 -0
  82. package/swift/Sources/DeckKit/DeckBridgeSecurity.swift +152 -0
  83. package/swift/Sources/DeckKit/DeckCockpit.swift +82 -0
  84. package/swift/Sources/DeckKit/DeckHost.swift +7 -0
  85. package/swift/Sources/DeckKit/DeckManifest.swift +145 -0
  86. package/swift/Sources/DeckKit/DeckRuntimeSnapshot.swift +533 -0
  87. package/swift/Sources/DeckKit/DeckTrackpad.swift +63 -0
  88. package/swift/Sources/DeckKit/DeckValue.swift +93 -0
  89. package/swift/Sources/DeckKit/DeckVoiceError.swift +88 -0
  90. package/swift/Tests/DeckKitTests/DeckKitTests.swift +286 -0
  91. package/app/Sources/AppShell/AppDelegate.swift +0 -408
  92. package/app/Sources/Core/Input/KeyboardRemapController.swift +0 -184
  93. package/app/Sources/Core/Input/KeyboardRemapStore.swift +0 -84
  94. package/app/Sources/Core/Input/MouseGestureController.swift +0 -1203
  95. package/app/Sources/Core/Input/MouseShortcutStore.swift +0 -107
  96. /package/{app → apps/mac}/Info.plist +0 -0
  97. /package/{app → apps/mac}/Lattices.app/Contents/Resources/AppIcon.icns +0 -0
  98. /package/{app → apps/mac}/Lattices.app/Contents/Resources/tap.wav +0 -0
  99. /package/{app → apps/mac}/Lattices.app/Contents/_CodeSignature/CodeResources +0 -0
  100. /package/{app → apps/mac}/Lattices.entitlements +0 -0
  101. /package/{app → apps/mac}/Resources/tap.wav +0 -0
  102. /package/{app → apps/mac}/Sources/AppShell/App.swift +0 -0
  103. /package/{app → apps/mac}/Sources/AppShell/CliActionLauncher.swift +0 -0
  104. /package/{app → apps/mac}/Sources/AppShell/HomeDashboardView.swift +0 -0
  105. /package/{app → apps/mac}/Sources/AppShell/KeyRecorderView.swift +0 -0
  106. /package/{app → apps/mac}/Sources/AppShell/MainWindow.swift +0 -0
  107. /package/{app → apps/mac}/Sources/Core/Actions/HotkeyManager.swift +0 -0
  108. /package/{app → apps/mac}/Sources/Core/Actions/IntentSchema.swift +0 -0
  109. /package/{app → apps/mac}/Sources/Core/Actions/Intents/CreateLayerIntent.swift +0 -0
  110. /package/{app → apps/mac}/Sources/Core/Actions/Intents/DistributeIntent.swift +0 -0
  111. /package/{app → apps/mac}/Sources/Core/Actions/Intents/FocusIntent.swift +0 -0
  112. /package/{app → apps/mac}/Sources/Core/Actions/Intents/HelpIntent.swift +0 -0
  113. /package/{app → apps/mac}/Sources/Core/Actions/Intents/KillIntent.swift +0 -0
  114. /package/{app → apps/mac}/Sources/Core/Actions/Intents/LatticeIntent.swift +0 -0
  115. /package/{app → apps/mac}/Sources/Core/Actions/Intents/LaunchIntent.swift +0 -0
  116. /package/{app → apps/mac}/Sources/Core/Actions/Intents/ListSessionsIntent.swift +0 -0
  117. /package/{app → apps/mac}/Sources/Core/Actions/Intents/ListWindowsIntent.swift +0 -0
  118. /package/{app → apps/mac}/Sources/Core/Actions/Intents/ScanIntent.swift +0 -0
  119. /package/{app → apps/mac}/Sources/Core/Actions/Intents/SearchIntent.swift +0 -0
  120. /package/{app → apps/mac}/Sources/Core/Actions/Intents/SwitchLayerIntent.swift +0 -0
  121. /package/{app → apps/mac}/Sources/Core/Actions/Intents/TileIntent.swift +0 -0
  122. /package/{app → apps/mac}/Sources/Core/Actions/PaletteCommand.swift +0 -0
  123. /package/{app → apps/mac}/Sources/Core/Actions/VoiceIntentResolver.swift +0 -0
  124. /package/{app → apps/mac}/Sources/Core/Companion/CompanionActivityLog.swift +0 -0
  125. /package/{app → apps/mac}/Sources/Core/Companion/CompanionKeyboardController.swift +0 -0
  126. /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +0 -0
  127. /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionCockpit.swift +0 -0
  128. /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +0 -0
  129. /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +0 -0
  130. /package/{app → apps/mac}/Sources/Core/Companion/LatticesDeckHost.swift +0 -0
  131. /package/{app → apps/mac}/Sources/Core/Daemon/DaemonProtocol.swift +0 -0
  132. /package/{app → apps/mac}/Sources/Core/Desktop/AccessibilityTextExtractor.swift +0 -0
  133. /package/{app → apps/mac}/Sources/Core/Desktop/AppTypeClassifier.swift +0 -0
  134. /package/{app → apps/mac}/Sources/Core/Desktop/DesktopModel.swift +0 -0
  135. /package/{app → apps/mac}/Sources/Core/Desktop/DesktopModelTypes.swift +0 -0
  136. /package/{app → apps/mac}/Sources/Core/Desktop/InventoryManager.swift +0 -0
  137. /package/{app → apps/mac}/Sources/Core/Desktop/InventoryPath.swift +0 -0
  138. /package/{app → apps/mac}/Sources/Core/Desktop/MouseFinder.swift +0 -0
  139. /package/{app → apps/mac}/Sources/Core/Desktop/OcrStore.swift +0 -0
  140. /package/{app → apps/mac}/Sources/Core/Desktop/PlacementSpec.swift +0 -0
  141. /package/{app → apps/mac}/Sources/Core/Desktop/SessionWindowLocator.swift +0 -0
  142. /package/{app → apps/mac}/Sources/Core/Desktop/TilePickerView.swift +0 -0
  143. /package/{app → apps/mac}/Sources/Core/Desktop/WindowPreviewCard.swift +0 -0
  144. /package/{app → apps/mac}/Sources/Core/Desktop/WindowSelectionStore.swift +0 -0
  145. /package/{app → apps/mac}/Sources/Core/Input/KeyboardRemapConfig.swift +0 -0
  146. /package/{app → apps/mac}/Sources/Core/Input/MouseInputDeviceStore.swift +0 -0
  147. /package/{app → apps/mac}/Sources/Core/Input/MouseInputEventViewer.swift +0 -0
  148. /package/{app → apps/mac}/Sources/Core/Overlays/AppWindowShell.swift +0 -0
  149. /package/{app → apps/mac}/Sources/Core/Overlays/CommandMode/CommandModeState.swift +0 -0
  150. /package/{app → apps/mac}/Sources/Core/Overlays/CommandMode/CommandModeView.swift +0 -0
  151. /package/{app → apps/mac}/Sources/Core/Overlays/CommandMode/CommandModeWindow.swift +0 -0
  152. /package/{app → apps/mac}/Sources/Core/Overlays/CommandPalette/CommandPaletteView.swift +0 -0
  153. /package/{app → apps/mac}/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +0 -0
  154. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/CheatSheetHUD.swift +0 -0
  155. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDBottomBar.swift +0 -0
  156. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDController.swift +0 -0
  157. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDLeftBar.swift +0 -0
  158. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDMinimap.swift +0 -0
  159. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDRightBar.swift +0 -0
  160. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDState.swift +0 -0
  161. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDTopBar.swift +0 -0
  162. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/LauncherHUD.swift +0 -0
  163. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/LayerBezel.swift +0 -0
  164. /package/{app → apps/mac}/Sources/Core/Overlays/OmniSearch/OmniSearchState.swift +0 -0
  165. /package/{app → apps/mac}/Sources/Core/Overlays/OmniSearch/OmniSearchView.swift +0 -0
  166. /package/{app → apps/mac}/Sources/Core/Overlays/OmniSearch/OmniSearchWindow.swift +0 -0
  167. /package/{app → apps/mac}/Sources/Core/Overlays/OverlayPanelShell.swift +0 -0
  168. /package/{app → apps/mac}/Sources/Core/Overlays/ScreenMap/ScreenMapView.swift +0 -0
  169. /package/{app → apps/mac}/Sources/Core/Overlays/ScreenMap/ScreenMapWindowController.swift +0 -0
  170. /package/{app → apps/mac}/Sources/Core/Pi/PiAuthNextStepCard.swift +0 -0
  171. /package/{app → apps/mac}/Sources/Core/Pi/PiAuthPromptCard.swift +0 -0
  172. /package/{app → apps/mac}/Sources/Core/Pi/PiInstallCallout.swift +0 -0
  173. /package/{app → apps/mac}/Sources/Core/System/DiagnosticLog.swift +0 -0
  174. /package/{app → apps/mac}/Sources/Core/System/EventBus.swift +0 -0
  175. /package/{app → apps/mac}/Sources/Core/System/ProcessModel.swift +0 -0
  176. /package/{app → apps/mac}/Sources/Core/System/ProcessQuery.swift +0 -0
  177. /package/{app → apps/mac}/Sources/Core/System/SystemTelemetryMonitor.swift +0 -0
  178. /package/{app → apps/mac}/Sources/Core/Voice/AdvisorLearningStore.swift +0 -0
  179. /package/{app → apps/mac}/Sources/Core/Voice/AgentSession.swift +0 -0
  180. /package/{app → apps/mac}/Sources/Core/Voice/HandsOffSession.swift +0 -0
  181. /package/{app → apps/mac}/Sources/Core/Voice/VoiceChatView.swift +0 -0
  182. /package/{app → apps/mac}/Sources/Core/Voice/VoxClient.swift +0 -0
  183. /package/{app → apps/mac}/Sources/Core/Workspace/Project.swift +0 -0
  184. /package/{app → apps/mac}/Sources/Core/Workspace/ProjectScanner.swift +0 -0
  185. /package/{app → apps/mac}/Sources/Core/Workspace/SessionLayerStore.swift +0 -0
  186. /package/{app → apps/mac}/Sources/Core/Workspace/SessionManager.swift +0 -0
  187. /package/{app → apps/mac}/Sources/Core/Workspace/Terminal/Terminal.swift +0 -0
  188. /package/{app → apps/mac}/Sources/Core/Workspace/Terminal/TerminalQuery.swift +0 -0
  189. /package/{app → apps/mac}/Sources/Core/Workspace/Terminal/TerminalSynthesizer.swift +0 -0
  190. /package/{app → apps/mac}/Sources/Core/Workspace/Tmux/TmuxModel.swift +0 -0
  191. /package/{app → apps/mac}/Sources/Core/Workspace/Tmux/TmuxQuery.swift +0 -0
  192. /package/{app → apps/mac}/Sources/Core/Workspace/WorkspaceManager.swift +0 -0
  193. /package/{app → apps/mac}/Sources/UI/ActionRow.swift +0 -0
  194. /package/{app → apps/mac}/Sources/UI/OrphanRow.swift +0 -0
  195. /package/{app → apps/mac}/Sources/UI/ProjectRow.swift +0 -0
  196. /package/{app → apps/mac}/Sources/UI/TabGroupRow.swift +0 -0
  197. /package/{app → apps/mac}/Sources/UI/Theme.swift +0 -0
  198. /package/{app → apps/mac}/Tests/StageDragTests.swift +0 -0
  199. /package/{app → apps/mac}/Tests/StageJoinTests.swift +0 -0
  200. /package/{app → apps/mac}/Tests/StageManagerTests.swift +0 -0
  201. /package/{app → apps/mac}/Tests/StageTileTests.swift +0 -0
@@ -18,7 +18,7 @@ struct PiProviderSetupCallout: View {
18
18
 
19
19
  Text(session.isAuthenticating
20
20
  ? "Finish the setup above. As soon as that one step is done, the chat box unlocks."
21
- : "Next step: connect \(session.currentProvider.name). You only have to do this once.")
21
+ : "Chat is optional. Connect \(session.currentProvider.name) when you want the in-app assistant.")
22
22
  .font(Typo.mono(compact ? 10 : 11))
23
23
  .foregroundColor(Palette.textDim)
24
24
  .fixedSize(horizontal: false, vertical: true)
@@ -32,18 +32,18 @@ struct PiProviderSetupCallout: View {
32
32
  PiAuthNextStepCard(session: session, compact: compact)
33
33
  } else {
34
34
  Text(session.currentProvider.authMode == .oauth
35
- ? "The setup panel above is already open, so you can connect right now."
36
- : "Paste your key in the setup panel above, save it once, and you are done.")
35
+ ? "This opens the provider flow in your browser."
36
+ : "Open setup when you are ready to paste and save a key.")
37
37
  .font(Typo.mono(compact ? 9 : 10))
38
38
  .foregroundColor(Palette.textMuted)
39
39
  }
40
40
 
41
41
  if session.currentProvider.authMode == .oauth && !session.isAuthenticating {
42
42
  primaryActionButton(
43
- "CONNECT \(session.currentProvider.name.uppercased())",
43
+ "SET UP \(session.currentProvider.name.uppercased())",
44
44
  tint: Palette.running
45
45
  ) {
46
- session.startSelectedAuthFlow()
46
+ session.toggleAuthPanel()
47
47
  }
48
48
  }
49
49
  }
@@ -19,14 +19,6 @@ struct PiWorkspaceView: View {
19
19
  .fill(Palette.border)
20
20
  .frame(height: 0.5)
21
21
 
22
- if session.hasPiBinary && session.isAuthPanelVisible {
23
- authPanel
24
-
25
- Rectangle()
26
- .fill(Palette.border)
27
- .frame(height: 0.5)
28
- }
29
-
30
22
  transcript
31
23
 
32
24
  Rectangle()
@@ -36,14 +28,7 @@ struct PiWorkspaceView: View {
36
28
  if session.hasPiBinary && !session.needsProviderSetup {
37
29
  composer
38
30
  } else if session.needsProviderSetup {
39
- if session.isAuthPanelVisible {
40
- setupLockedPanel
41
- } else {
42
- PiProviderSetupCallout(session: session, compact: false)
43
- .padding(.horizontal, 16)
44
- .padding(.vertical, 14)
45
- .background(Palette.surface.opacity(0.22))
46
- }
31
+ providerSettingsPrompt
47
32
  } else {
48
33
  PiInstallCallout(session: session, compact: false)
49
34
  .padding(.horizontal, 16)
@@ -70,7 +55,7 @@ struct PiWorkspaceView: View {
70
55
  .fill(session.hasPiBinary ? Palette.running : Palette.kill)
71
56
  .frame(width: 7, height: 7)
72
57
 
73
- Text("WORKSPACE CHAT")
58
+ Text("WORKSPACE ASSISTANT")
74
59
  .font(Typo.geistMonoBold(11))
75
60
  .foregroundColor(Palette.text)
76
61
 
@@ -89,8 +74,8 @@ struct PiWorkspaceView: View {
89
74
  ? session.authStepDescription
90
75
  : (session.needsProviderSetup
91
76
  ? "Next step: connect a provider to unlock chat."
92
- : "Full conversation surface for longer prompts, auth, and provider switching."))
93
- : "Install Pi to unlock longer prompts, provider auth, and the full in-app assistant surface.")
77
+ : "Full conversation surface for settings, longer prompts, planning, debugging, and second opinions."))
78
+ : "Settings chat is ready here. Install the provider runtime to unlock longer prompts and provider-backed chat.")
94
79
  .font(Typo.mono(10))
95
80
  .foregroundColor(Palette.textDim)
96
81
  }
@@ -98,13 +83,7 @@ struct PiWorkspaceView: View {
98
83
  Spacer()
99
84
 
100
85
  HStack(spacing: 6) {
101
- capsuleLabel(session.currentProvider.name.uppercased(), tint: Palette.textDim)
102
-
103
- if session.hasPiBinary && !session.needsProviderSetup {
104
- actionChip(session.isAuthPanelVisible ? "AUTH -" : "AUTH +") {
105
- session.toggleAuthPanel()
106
- }
107
- }
86
+ settingsGearButton
108
87
 
109
88
  if session.hasConversationHistory {
110
89
  actionChip("RESET") {
@@ -117,6 +96,51 @@ struct PiWorkspaceView: View {
117
96
  .padding(.vertical, 14)
118
97
  }
119
98
 
99
+ private var settingsGearButton: some View {
100
+ Button {
101
+ SettingsWindowController.shared.showAssistant()
102
+ } label: {
103
+ Image(systemName: "gearshape")
104
+ .font(.system(size: 11, weight: .semibold))
105
+ .foregroundColor(Palette.textMuted)
106
+ .frame(width: 26, height: 24)
107
+ .background(
108
+ Capsule()
109
+ .fill(Color.white.opacity(0.03))
110
+ .overlay(Capsule().strokeBorder(Palette.border, lineWidth: 0.5))
111
+ )
112
+ }
113
+ .buttonStyle(.plain)
114
+ }
115
+
116
+ private var providerSettingsPrompt: some View {
117
+ HStack(alignment: .center, spacing: 10) {
118
+ Circle()
119
+ .fill(Palette.detach)
120
+ .frame(width: 7, height: 7)
121
+
122
+ VStack(alignment: .leading, spacing: 3) {
123
+ Text("CONNECT A PROVIDER")
124
+ .font(Typo.geistMonoBold(10))
125
+ .foregroundColor(Palette.text)
126
+
127
+ Text("Choose OpenAI, Groq, OpenRouter, or MiniMax in Settings to unlock provider-backed chat.")
128
+ .font(Typo.mono(10))
129
+ .foregroundColor(Palette.textDim)
130
+ .fixedSize(horizontal: false, vertical: true)
131
+ }
132
+
133
+ Spacer()
134
+
135
+ actionChip("SETTINGS", tint: Palette.running) {
136
+ SettingsWindowController.shared.showAssistant()
137
+ }
138
+ }
139
+ .padding(.horizontal, 16)
140
+ .padding(.vertical, 14)
141
+ .background(Palette.surface.opacity(0.22))
142
+ }
143
+
120
144
  private var authPanel: some View {
121
145
  VStack(alignment: .leading, spacing: 12) {
122
146
  if session.isAuthenticating {
@@ -184,29 +208,54 @@ struct PiWorkspaceView: View {
184
208
  .fixedSize(horizontal: false, vertical: true)
185
209
 
186
210
  if session.currentProvider.authMode == .apiKey {
187
- HStack(spacing: 8) {
188
- SecureField(session.currentProvider.tokenPlaceholder, text: $session.authToken)
189
- .textFieldStyle(.plain)
190
- .font(Typo.mono(11))
191
- .foregroundColor(Palette.text)
192
- .focused($authFieldFocused)
193
- .onSubmit {
194
- session.saveSelectedToken()
195
- }
211
+ if session.hasSelectedCredential && !session.isEditingStoredCredential {
212
+ HStack(spacing: 8) {
213
+ Circle()
214
+ .fill(Palette.running)
215
+ .frame(width: 6, height: 6)
196
216
 
197
- actionChip("SAVE KEY") {
198
- session.saveSelectedToken()
199
- }
217
+ Text("\(session.currentProvider.name) credential saved")
218
+ .font(Typo.mono(10))
219
+ .foregroundColor(Palette.textDim)
220
+
221
+ Spacer()
222
+
223
+ actionChip("REPLACE") {
224
+ session.beginReplacingSelectedCredential()
225
+ }
200
226
 
201
- if session.hasSelectedCredential {
202
227
  actionChip("CLEAR") {
203
228
  session.removeSelectedCredential()
204
229
  }
205
230
  }
231
+ .padding(.horizontal, 12)
232
+ .padding(.vertical, 10)
233
+ .background(authCardBackground(tint: Palette.running))
234
+ } else {
235
+ HStack(spacing: 8) {
236
+ SecureField(session.currentProvider.tokenPlaceholder, text: $session.authToken)
237
+ .textFieldStyle(.plain)
238
+ .font(Typo.mono(11))
239
+ .foregroundColor(Palette.text)
240
+ .focused($authFieldFocused)
241
+ .onSubmit {
242
+ session.saveSelectedToken()
243
+ }
244
+
245
+ actionChip("SAVE KEY") {
246
+ session.saveSelectedToken()
247
+ }
248
+
249
+ if session.hasSelectedCredential {
250
+ actionChip("CANCEL") {
251
+ session.cancelReplacingSelectedCredential()
252
+ }
253
+ }
254
+ }
255
+ .padding(.horizontal, 12)
256
+ .padding(.vertical, 10)
257
+ .background(authCardBackground(tint: Palette.running))
206
258
  }
207
- .padding(.horizontal, 12)
208
- .padding(.vertical, 10)
209
- .background(authCardBackground(tint: Palette.running))
210
259
  } else {
211
260
  HStack(spacing: 8) {
212
261
  actionChip("CONNECT") {
@@ -343,7 +392,7 @@ struct PiWorkspaceView: View {
343
392
  .font(Typo.geistMonoBold(12))
344
393
  .foregroundColor(Palette.running)
345
394
 
346
- TextField("Ask Pi something heavier...", text: $session.draft, axis: .vertical)
395
+ TextField("Ask about settings or planning...", text: $session.draft, axis: .vertical)
347
396
  .textFieldStyle(.plain)
348
397
  .font(Typo.mono(12))
349
398
  .foregroundColor(Palette.text)
@@ -395,7 +444,7 @@ struct PiWorkspaceView: View {
395
444
  switch role {
396
445
  case .system: return "system"
397
446
  case .user: return "you"
398
- case .assistant: return "pi"
447
+ case .assistant: return "assistant"
399
448
  }
400
449
  }
401
450
 
@@ -0,0 +1,79 @@
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
+ }
@@ -14,12 +14,20 @@ final class PermissionChecker: ObservableObject {
14
14
 
15
15
  var allGranted: Bool { accessibility && screenRecording }
16
16
 
17
+ var isSimulatingMissingPermissions: Bool {
18
+ CommandLine.arguments.contains("--simulate-missing-permissions")
19
+ || UserDefaults.standard.bool(forKey: "permissions.simulateMissing")
20
+ }
21
+
17
22
  /// Check current permission state without prompting.
18
- func check() {
23
+ func check(pollIfMissing: Bool = false) {
19
24
  let diag = DiagnosticLog.shared
20
25
 
21
- let ax = AXIsProcessTrusted()
22
- let sr = CGPreflightScreenCaptureAccess()
26
+ let realAX = AXIsProcessTrusted()
27
+ let realSR = CGPreflightScreenCaptureAccess()
28
+ let simulating = isSimulatingMissingPermissions
29
+ let ax = simulating ? false : realAX
30
+ let sr = simulating ? false : realSR
23
31
 
24
32
  // First check: log identity info only
25
33
  if !hasLoggedInitial {
@@ -29,8 +37,11 @@ final class PermissionChecker: ObservableObject {
29
37
  let pid = ProcessInfo.processInfo.processIdentifier
30
38
  diag.info("PermissionChecker: bundleId=\(bundleId) pid=\(pid)")
31
39
  diag.info("PermissionChecker: exec=\(execPath)")
32
- diag.info("AXIsProcessTrusted() → \(ax)")
33
- diag.info("CGPreflightScreenCaptureAccess() → \(sr)")
40
+ diag.info("AXIsProcessTrusted() → \(realAX)")
41
+ diag.info("CGPreflightScreenCaptureAccess() → \(realSR)")
42
+ if simulating {
43
+ diag.warn("PermissionChecker: simulating missing permissions for UX preview")
44
+ }
34
45
  }
35
46
 
36
47
  // Log on state changes
@@ -41,11 +52,11 @@ final class PermissionChecker: ObservableObject {
41
52
  accessibility = ax
42
53
  screenRecording = sr
43
54
 
44
- // If not all granted, start polling so we detect changes while user is in Settings.
45
- // Once all granted, stop polling.
55
+ // Only poll after an intentional permission request. A passive launch-time
56
+ // check should not keep nudging macOS privacy state in the background.
46
57
  if allGranted {
47
58
  stopPolling()
48
- } else {
59
+ } else if pollIfMissing {
49
60
  startPolling()
50
61
  }
51
62
  }
@@ -54,6 +65,11 @@ final class PermissionChecker: ObservableObject {
54
65
  /// which adds lattices to the Accessibility list and asks the user to toggle it on.
55
66
  func requestAccessibility() {
56
67
  let diag = DiagnosticLog.shared
68
+ if isSimulatingMissingPermissions {
69
+ diag.warn("requestAccessibility: skipped because missing-permission simulation is enabled")
70
+ accessibility = false
71
+ return
72
+ }
57
73
  let beforeCheck = AXIsProcessTrusted()
58
74
  diag.info("requestAccessibility: before=\(beforeCheck), prompting…")
59
75
  let opts = [kAXTrustedCheckOptionPrompt.takeUnretainedValue(): true] as CFDictionary
@@ -71,6 +87,11 @@ final class PermissionChecker: ObservableObject {
71
87
  /// which adds lattices to the Screen Recording list. The user toggles it on in Settings.
72
88
  func requestScreenRecording() {
73
89
  let diag = DiagnosticLog.shared
90
+ if isSimulatingMissingPermissions {
91
+ diag.warn("requestScreenRecording: skipped because missing-permission simulation is enabled")
92
+ screenRecording = false
93
+ return
94
+ }
74
95
  let beforeCheck = CGPreflightScreenCaptureAccess()
75
96
  diag.info("requestScreenRecording: before=\(beforeCheck), probing…")
76
97
 
@@ -131,6 +152,20 @@ final class PermissionChecker: ObservableObject {
131
152
  }
132
153
  }
133
154
 
155
+ /// Opens System Settings → Privacy & Security → Automation.
156
+ func openAutomationSettings() {
157
+ if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation") {
158
+ NSWorkspace.shared.open(url)
159
+ }
160
+ }
161
+
162
+ /// Opens System Settings → Privacy & Security → Input Monitoring.
163
+ func openInputMonitoringSettings() {
164
+ if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_ListenEvent") {
165
+ NSWorkspace.shared.open(url)
166
+ }
167
+ }
168
+
134
169
  @available(macOS 15.0, *)
135
170
  private func probeScreenCaptureShareableContent() async -> String {
136
171
  await withCheckedContinuation { continuation in