@lattices/cli 0.4.14 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/README.md +5 -7
  2. package/apps/mac/Info.plist +4 -4
  3. package/apps/mac/Lattices.app/Contents/Info.plist +4 -12
  4. package/apps/mac/Lattices.app/Contents/MacOS/Lattices +0 -0
  5. package/bin/lattices-app.ts +110 -17
  6. package/bin/lattices-build +125 -0
  7. package/bin/lattices-dev +89 -16
  8. package/bin/lattices.ts +977 -16
  9. package/docs/agents.md +81 -4
  10. package/docs/ai-chat-ux-review.md +416 -0
  11. package/docs/api.md +135 -3
  12. package/docs/app.md +30 -8
  13. package/docs/config.md +4 -0
  14. package/docs/mouse-gestures.md +60 -1
  15. package/docs/proposals/LAT-004-interactive-overlay-actors.md +1 -1
  16. package/docs/proposals/LAT-005-action-runtime-product-spine.md +914 -0
  17. package/docs/proposals/LAT-006-mira-in-lattices.md +553 -0
  18. package/docs/proposals/LAT-007-unified-app-shell.md +128 -0
  19. package/docs/reference/dewey.config.ts +2 -2
  20. package/docs/release.md +171 -0
  21. package/docs/repo-structure.md +5 -5
  22. package/docs/voice.md +11 -27
  23. package/package.json +11 -10
  24. package/apps/mac/Package.swift +0 -27
  25. package/apps/mac/Sources/AppShell/App.swift +0 -26
  26. package/apps/mac/Sources/AppShell/AppActivationCoordinator.swift +0 -27
  27. package/apps/mac/Sources/AppShell/AppDelegate.swift +0 -189
  28. package/apps/mac/Sources/AppShell/AppServicesBootstrap.swift +0 -25
  29. package/apps/mac/Sources/AppShell/AppShellView.swift +0 -171
  30. package/apps/mac/Sources/AppShell/AppUpdater.swift +0 -305
  31. package/apps/mac/Sources/AppShell/CliActionLauncher.swift +0 -50
  32. package/apps/mac/Sources/AppShell/HomeDashboardView.swift +0 -133
  33. package/apps/mac/Sources/AppShell/HotkeyBootstrap.swift +0 -87
  34. package/apps/mac/Sources/AppShell/KeyRecorderView.swift +0 -210
  35. package/apps/mac/Sources/AppShell/LatticesRuntime.swift +0 -104
  36. package/apps/mac/Sources/AppShell/MainView.swift +0 -847
  37. package/apps/mac/Sources/AppShell/MainWindow.swift +0 -83
  38. package/apps/mac/Sources/AppShell/MenuBarController.swift +0 -177
  39. package/apps/mac/Sources/AppShell/OnboardingView.swift +0 -483
  40. package/apps/mac/Sources/AppShell/PermissionsAssistantView.swift +0 -366
  41. package/apps/mac/Sources/AppShell/PermissionsAssistantWindow.swift +0 -70
  42. package/apps/mac/Sources/AppShell/Preferences.swift +0 -297
  43. package/apps/mac/Sources/AppShell/SettingsView.swift +0 -3163
  44. package/apps/mac/Sources/AppShell/SettingsWindow.swift +0 -34
  45. package/apps/mac/Sources/AppShell/WorkspaceInspectorPresenter.swift +0 -13
  46. package/apps/mac/Sources/Core/Actions/HotkeyManager.swift +0 -256
  47. package/apps/mac/Sources/Core/Actions/HotkeyStore.swift +0 -399
  48. package/apps/mac/Sources/Core/Actions/IntentEngine.swift +0 -988
  49. package/apps/mac/Sources/Core/Actions/IntentSchema.swift +0 -94
  50. package/apps/mac/Sources/Core/Actions/Intents/CreateLayerIntent.swift +0 -54
  51. package/apps/mac/Sources/Core/Actions/Intents/DistributeIntent.swift +0 -56
  52. package/apps/mac/Sources/Core/Actions/Intents/FocusIntent.swift +0 -69
  53. package/apps/mac/Sources/Core/Actions/Intents/HelpIntent.swift +0 -41
  54. package/apps/mac/Sources/Core/Actions/Intents/KillIntent.swift +0 -47
  55. package/apps/mac/Sources/Core/Actions/Intents/LatticeIntent.swift +0 -53
  56. package/apps/mac/Sources/Core/Actions/Intents/LaunchIntent.swift +0 -67
  57. package/apps/mac/Sources/Core/Actions/Intents/ListSessionsIntent.swift +0 -32
  58. package/apps/mac/Sources/Core/Actions/Intents/ListWindowsIntent.swift +0 -30
  59. package/apps/mac/Sources/Core/Actions/Intents/ScanIntent.swift +0 -52
  60. package/apps/mac/Sources/Core/Actions/Intents/SearchIntent.swift +0 -190
  61. package/apps/mac/Sources/Core/Actions/Intents/SwitchLayerIntent.swift +0 -50
  62. package/apps/mac/Sources/Core/Actions/Intents/TileIntent.swift +0 -61
  63. package/apps/mac/Sources/Core/Actions/PaletteCommand.swift +0 -439
  64. package/apps/mac/Sources/Core/Actions/VoiceIntentResolver.swift +0 -713
  65. package/apps/mac/Sources/Core/Companion/CompanionActivityLog.swift +0 -70
  66. package/apps/mac/Sources/Core/Companion/CompanionKeyboardController.swift +0 -141
  67. package/apps/mac/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +0 -454
  68. package/apps/mac/Sources/Core/Companion/LatticesCompanionCockpit.swift +0 -555
  69. package/apps/mac/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +0 -629
  70. package/apps/mac/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +0 -204
  71. package/apps/mac/Sources/Core/Companion/LatticesDeckHost.swift +0 -1463
  72. package/apps/mac/Sources/Core/Daemon/DaemonProtocol.swift +0 -114
  73. package/apps/mac/Sources/Core/Daemon/DaemonServer.swift +0 -427
  74. package/apps/mac/Sources/Core/Daemon/LatticesApi.swift +0 -2965
  75. package/apps/mac/Sources/Core/Desktop/AccessibilityTextExtractor.swift +0 -111
  76. package/apps/mac/Sources/Core/Desktop/AppTypeClassifier.swift +0 -106
  77. package/apps/mac/Sources/Core/Desktop/DesktopModel.swift +0 -331
  78. package/apps/mac/Sources/Core/Desktop/DesktopModelTypes.swift +0 -73
  79. package/apps/mac/Sources/Core/Desktop/InventoryManager.swift +0 -35
  80. package/apps/mac/Sources/Core/Desktop/InventoryPath.swift +0 -43
  81. package/apps/mac/Sources/Core/Desktop/MouseFinder.swift +0 -527
  82. package/apps/mac/Sources/Core/Desktop/OcrModel.swift +0 -467
  83. package/apps/mac/Sources/Core/Desktop/OcrStore.swift +0 -329
  84. package/apps/mac/Sources/Core/Desktop/PlacementSpec.swift +0 -195
  85. package/apps/mac/Sources/Core/Desktop/SessionWindowLocator.swift +0 -139
  86. package/apps/mac/Sources/Core/Desktop/TilePickerView.swift +0 -209
  87. package/apps/mac/Sources/Core/Desktop/WindowCapture.swift +0 -33
  88. package/apps/mac/Sources/Core/Desktop/WindowDragSnapController.swift +0 -429
  89. package/apps/mac/Sources/Core/Desktop/WindowPreviewCard.swift +0 -100
  90. package/apps/mac/Sources/Core/Desktop/WindowPreviewStore.swift +0 -112
  91. package/apps/mac/Sources/Core/Desktop/WindowSelectionStore.swift +0 -76
  92. package/apps/mac/Sources/Core/Desktop/WindowTiler.swift +0 -2222
  93. package/apps/mac/Sources/Core/Input/EventTapBreaker.swift +0 -124
  94. package/apps/mac/Sources/Core/Input/EventTapThread.swift +0 -54
  95. package/apps/mac/Sources/Core/Input/InputCaptureResetCenter.swift +0 -20
  96. package/apps/mac/Sources/Core/Input/KeyboardRemapConfig.swift +0 -69
  97. package/apps/mac/Sources/Core/Input/KeyboardRemapController.swift +0 -346
  98. package/apps/mac/Sources/Core/Input/KeyboardRemapStore.swift +0 -141
  99. package/apps/mac/Sources/Core/Input/MouseGestureConfig.swift +0 -499
  100. package/apps/mac/Sources/Core/Input/MouseGestureController.swift +0 -2583
  101. package/apps/mac/Sources/Core/Input/MouseInputDeviceStore.swift +0 -98
  102. package/apps/mac/Sources/Core/Input/MouseInputEventViewer.swift +0 -272
  103. package/apps/mac/Sources/Core/Input/MouseShortcutStore.swift +0 -170
  104. package/apps/mac/Sources/Core/Input/SecureEventInputMonitor.swift +0 -39
  105. package/apps/mac/Sources/Core/Input/ShapeRecognizer.swift +0 -624
  106. package/apps/mac/Sources/Core/Input/TapBudgetMeter.swift +0 -56
  107. package/apps/mac/Sources/Core/Overlays/AppWindowShell.swift +0 -63
  108. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeState.swift +0 -1566
  109. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeView.swift +0 -1927
  110. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeWindow.swift +0 -196
  111. package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteView.swift +0 -307
  112. package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +0 -67
  113. package/apps/mac/Sources/Core/Overlays/HUD/CheatSheetHUD.swift +0 -576
  114. package/apps/mac/Sources/Core/Overlays/HUD/HUDBottomBar.swift +0 -279
  115. package/apps/mac/Sources/Core/Overlays/HUD/HUDController.swift +0 -1158
  116. package/apps/mac/Sources/Core/Overlays/HUD/HUDLeftBar.swift +0 -849
  117. package/apps/mac/Sources/Core/Overlays/HUD/HUDMinimap.swift +0 -179
  118. package/apps/mac/Sources/Core/Overlays/HUD/HUDRightBar.swift +0 -596
  119. package/apps/mac/Sources/Core/Overlays/HUD/HUDState.swift +0 -367
  120. package/apps/mac/Sources/Core/Overlays/HUD/HUDTopBar.swift +0 -243
  121. package/apps/mac/Sources/Core/Overlays/HUD/LauncherHUD.swift +0 -334
  122. package/apps/mac/Sources/Core/Overlays/HUD/LayerBezel.swift +0 -203
  123. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchState.swift +0 -280
  124. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchView.swift +0 -422
  125. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchWindow.swift +0 -94
  126. package/apps/mac/Sources/Core/Overlays/OverlayPanelShell.swift +0 -241
  127. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapState.swift +0 -3135
  128. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapView.swift +0 -3977
  129. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapWindowController.swift +0 -119
  130. package/apps/mac/Sources/Core/Overlays/ScreenOverlayCanvasController.swift +0 -1217
  131. package/apps/mac/Sources/Core/Overlays/Voice/VoiceCommandWindow.swift +0 -1575
  132. package/apps/mac/Sources/Core/Pi/PiAuthNextStepCard.swift +0 -148
  133. package/apps/mac/Sources/Core/Pi/PiAuthPromptCard.swift +0 -90
  134. package/apps/mac/Sources/Core/Pi/PiChatDock.swift +0 -564
  135. package/apps/mac/Sources/Core/Pi/PiChatSession.swift +0 -1948
  136. package/apps/mac/Sources/Core/Pi/PiInstallCallout.swift +0 -86
  137. package/apps/mac/Sources/Core/Pi/PiProviderSetupCallout.swift +0 -99
  138. package/apps/mac/Sources/Core/Pi/PiWorkspaceView.swift +0 -510
  139. package/apps/mac/Sources/Core/System/Capability.swift +0 -79
  140. package/apps/mac/Sources/Core/System/DiagnosticLog.swift +0 -373
  141. package/apps/mac/Sources/Core/System/EventBus.swift +0 -31
  142. package/apps/mac/Sources/Core/System/PermissionChecker.swift +0 -224
  143. package/apps/mac/Sources/Core/System/ProcessModel.swift +0 -199
  144. package/apps/mac/Sources/Core/System/ProcessQuery.swift +0 -151
  145. package/apps/mac/Sources/Core/System/SystemTelemetryMonitor.swift +0 -273
  146. package/apps/mac/Sources/Core/Voice/AdvisorLearningStore.swift +0 -90
  147. package/apps/mac/Sources/Core/Voice/AgentSession.swift +0 -377
  148. package/apps/mac/Sources/Core/Voice/AudioProvider.swift +0 -555
  149. package/apps/mac/Sources/Core/Voice/HandsOffSession.swift +0 -839
  150. package/apps/mac/Sources/Core/Voice/VoiceChatView.swift +0 -192
  151. package/apps/mac/Sources/Core/Voice/VoxClient.swift +0 -454
  152. package/apps/mac/Sources/Core/Workspace/Project.swift +0 -28
  153. package/apps/mac/Sources/Core/Workspace/ProjectScanner.swift +0 -141
  154. package/apps/mac/Sources/Core/Workspace/SessionLayerStore.swift +0 -285
  155. package/apps/mac/Sources/Core/Workspace/SessionManager.swift +0 -75
  156. package/apps/mac/Sources/Core/Workspace/Terminal/Terminal.swift +0 -259
  157. package/apps/mac/Sources/Core/Workspace/Terminal/TerminalQuery.swift +0 -156
  158. package/apps/mac/Sources/Core/Workspace/Terminal/TerminalSynthesizer.swift +0 -200
  159. package/apps/mac/Sources/Core/Workspace/Tmux/TmuxModel.swift +0 -60
  160. package/apps/mac/Sources/Core/Workspace/Tmux/TmuxQuery.swift +0 -105
  161. package/apps/mac/Sources/Core/Workspace/WorkspaceManager.swift +0 -1027
  162. package/apps/mac/Sources/UI/ActionRow.swift +0 -78
  163. package/apps/mac/Sources/UI/OrphanRow.swift +0 -129
  164. package/apps/mac/Sources/UI/ProjectRow.swift +0 -368
  165. package/apps/mac/Sources/UI/TabGroupRow.swift +0 -178
  166. package/apps/mac/Sources/UI/Theme.swift +0 -164
  167. package/apps/mac/Tests/StageDragTests.swift +0 -333
  168. package/apps/mac/Tests/StageJoinTests.swift +0 -313
  169. package/apps/mac/Tests/StageManagerTests.swift +0 -280
  170. package/apps/mac/Tests/StageTileTests.swift +0 -353
  171. package/swift/Package.swift +0 -20
  172. package/swift/Sources/DeckKit/DeckAction.swift +0 -51
  173. package/swift/Sources/DeckKit/DeckBridgeSecurity.swift +0 -152
  174. package/swift/Sources/DeckKit/DeckCockpit.swift +0 -82
  175. package/swift/Sources/DeckKit/DeckHost.swift +0 -7
  176. package/swift/Sources/DeckKit/DeckManifest.swift +0 -145
  177. package/swift/Sources/DeckKit/DeckRuntimeSnapshot.swift +0 -533
  178. package/swift/Sources/DeckKit/DeckTrackpad.swift +0 -63
  179. package/swift/Sources/DeckKit/DeckValue.swift +0 -93
  180. package/swift/Sources/DeckKit/DeckVoiceError.swift +0 -88
  181. package/swift/Tests/DeckKitTests/DeckKitTests.swift +0 -286
@@ -1,713 +0,0 @@
1
- import AppKit
2
- import Foundation
3
- import NaturalLanguage
4
-
5
- private struct IntentCandidate {
6
- let intent: IntentDef
7
- let slots: [String: JSON]
8
- let score: Double
9
- let semanticScore: Double
10
- let keywordBoost: Double
11
- let slotBoost: Double
12
- let matchedExample: String
13
- }
14
-
15
- private struct ExtractedSlots {
16
- let slots: [String: JSON]
17
- let boost: Double
18
- }
19
-
20
- final class VoiceIntentResolver {
21
- static let shared = VoiceIntentResolver()
22
-
23
- private let embedding = NLEmbedding.sentenceEmbedding(for: .english)
24
-
25
- private init() {}
26
-
27
- func match(text: String) -> IntentMatch? {
28
- let input = normalizeUtterance(text)
29
- guard !input.isEmpty else { return nil }
30
-
31
- if let direct = directMatch(for: input) {
32
- return direct
33
- }
34
-
35
- var candidates = IntentEngine.shared.definitions().compactMap { candidate(for: $0, input: input) }
36
- candidates.sort { lhs, rhs in
37
- if lhs.score == rhs.score {
38
- return lhs.semanticScore > rhs.semanticScore
39
- }
40
- return lhs.score > rhs.score
41
- }
42
-
43
- guard let best = candidates.first else { return nil }
44
-
45
- let minimumScore = best.intent.slots.contains(where: \.required) ? 0.42 : 0.36
46
- guard best.score >= minimumScore else { return nil }
47
-
48
- if let runnerUp = candidates.dropFirst().first,
49
- best.score - runnerUp.score < 0.05,
50
- best.keywordBoost < 0.18,
51
- best.slotBoost < 0.12 {
52
- return nil
53
- }
54
-
55
- return IntentMatch(
56
- intentName: best.intent.name,
57
- slots: best.slots,
58
- confidence: min(0.98, max(0.35, best.score)),
59
- matchedPhrase: best.matchedExample
60
- )
61
- }
62
-
63
- func execute(_ match: IntentMatch) throws -> JSON {
64
- try IntentEngine.shared.execute(IntentRequest(
65
- intent: match.intentName,
66
- slots: match.slots,
67
- rawText: nil,
68
- confidence: match.confidence,
69
- source: "voice-local"
70
- ))
71
- }
72
-
73
- func catalog() -> JSON {
74
- IntentEngine.shared.catalog()
75
- }
76
-
77
- private func candidate(for intent: IntentDef, input: String) -> IntentCandidate? {
78
- let extracted = extractSlots(for: intent.name, input: input)
79
- let requiredMissing = intent.slots.contains { $0.required && extracted.slots[$0.name] == nil }
80
- let exampleMatch = bestExampleMatch(for: intent, input: input)
81
- let keywordBoost = keywordBoost(for: intent.name, input: input)
82
- let exactBoost = normalizeUtterance(exampleMatch.example) == input ? 0.20 : 0.0
83
- let missingPenalty = requiredMissing ? 0.22 : 0.0
84
- let score = exampleMatch.score + keywordBoost + extracted.boost + exactBoost - missingPenalty
85
-
86
- if score <= 0 {
87
- return nil
88
- }
89
-
90
- return IntentCandidate(
91
- intent: intent,
92
- slots: extracted.slots,
93
- score: score,
94
- semanticScore: exampleMatch.score,
95
- keywordBoost: keywordBoost,
96
- slotBoost: extracted.boost,
97
- matchedExample: exampleMatch.example
98
- )
99
- }
100
-
101
- private func bestExampleMatch(for intent: IntentDef, input: String) -> (example: String, score: Double) {
102
- let examples = intent.examples + supplementalExamples[intent.name, default: []]
103
- guard !examples.isEmpty else { return ("", 0) }
104
-
105
- let best = examples
106
- .map { example -> (String, Double) in
107
- let normalizedExample = normalizeUtterance(example)
108
- if normalizedExample == input {
109
- return (example, 0.62)
110
- }
111
-
112
- let distance = semanticDistance(between: input, and: normalizedExample)
113
- let semantic = max(0, 1.18 - distance) * 0.48
114
- let overlap = tokenOverlap(input, normalizedExample) * 0.24
115
- return (example, semantic + overlap)
116
- }
117
- .max { $0.1 < $1.1 } ?? ("", 0)
118
-
119
- return best
120
- }
121
-
122
- private func semanticDistance(between lhs: String, and rhs: String) -> Double {
123
- guard let embedding else {
124
- return lhs == rhs ? 0 : 2
125
- }
126
- return Double(embedding.distance(between: lhs, and: rhs))
127
- }
128
-
129
- private func keywordBoost(for intentName: String, input: String) -> Double {
130
- guard let keywords = intentKeywords[intentName] else { return 0 }
131
-
132
- var boost = 0.0
133
- for keyword in keywords {
134
- if input.contains(keyword) {
135
- boost += keyword.contains(" ") ? 0.12 : 0.08
136
- }
137
- }
138
- return min(boost, 0.28)
139
- }
140
-
141
- private func extractSlots(for intentName: String, input: String) -> ExtractedSlots {
142
- switch intentName {
143
- case "tile_window":
144
- var slots: [String: JSON] = [:]
145
- var boost = 0.0
146
-
147
- if let position = resolvePosition(in: input) {
148
- slots["position"] = .string(position)
149
- boost += 0.28
150
- } else {
151
- return ExtractedSlots(slots: [:], boost: 0)
152
- }
153
-
154
- if refersToSelection(in: input) {
155
- slots["selection"] = .bool(true)
156
- boost += 0.08
157
- }
158
-
159
- return ExtractedSlots(slots: slots, boost: boost)
160
-
161
- case "distribute":
162
- var slots: [String: JSON] = [:]
163
- var boost = 0.0
164
-
165
- if let region = resolvePosition(in: input) {
166
- slots["region"] = .string(region)
167
- boost += 0.18
168
- }
169
-
170
- if let app = detectKnownApp(in: input) {
171
- slots["app"] = .string(app)
172
- boost += 0.14
173
- }
174
-
175
- if refersToSelection(in: input) {
176
- slots["selection"] = .bool(true)
177
- boost += 0.12
178
- }
179
-
180
- return ExtractedSlots(slots: slots, boost: boost)
181
-
182
- case "focus":
183
- if let app = detectKnownApp(in: input) ?? extractEntity(in: input, prefixes: focusPrefixes) {
184
- let resolved = resolveApp(app)
185
- guard !resolved.isEmpty else { return ExtractedSlots(slots: [:], boost: 0) }
186
- return ExtractedSlots(slots: ["app": .string(resolved)], boost: 0.18)
187
- }
188
- return ExtractedSlots(slots: [:], boost: 0)
189
-
190
- case "launch":
191
- if let project = extractEntity(in: input, prefixes: launchPrefixes) {
192
- let cleaned = cleanEntity(project)
193
- guard !cleaned.isEmpty else { return ExtractedSlots(slots: [:], boost: 0) }
194
- return ExtractedSlots(slots: ["project": .string(cleaned)], boost: 0.16)
195
- }
196
- return ExtractedSlots(slots: [:], boost: 0)
197
-
198
- case "switch_layer":
199
- if let layer = extractLayer(in: input) {
200
- guard !layer.isEmpty else { return ExtractedSlots(slots: [:], boost: 0) }
201
- return ExtractedSlots(slots: ["layer": .string(layer)], boost: 0.18)
202
- }
203
- return ExtractedSlots(slots: [:], boost: 0)
204
-
205
- case "search":
206
- if let query = extractSearchQuery(from: input) {
207
- let cleaned = cleanQuery(query)
208
- guard !cleaned.isEmpty else { return ExtractedSlots(slots: [:], boost: 0) }
209
- return ExtractedSlots(slots: ["query": .string(cleaned)], boost: 0.16)
210
- }
211
- return ExtractedSlots(slots: [:], boost: 0)
212
-
213
- case "create_layer":
214
- if let name = extractLayerName(from: input) {
215
- let cleaned = cleanEntity(name)
216
- guard !cleaned.isEmpty else { return ExtractedSlots(slots: [:], boost: 0) }
217
- return ExtractedSlots(slots: ["name": .string(cleaned)], boost: 0.14)
218
- }
219
- return ExtractedSlots(slots: [:], boost: 0)
220
-
221
- case "kill":
222
- if let session = extractEntity(in: input, prefixes: killPrefixes) {
223
- let cleaned = cleanEntity(session)
224
- guard !cleaned.isEmpty else { return ExtractedSlots(slots: [:], boost: 0) }
225
- return ExtractedSlots(slots: ["session": .string(cleaned)], boost: 0.16)
226
- }
227
- return ExtractedSlots(slots: [:], boost: 0)
228
-
229
- default:
230
- return ExtractedSlots(slots: [:], boost: 0)
231
- }
232
- }
233
-
234
- private func extractSearchQuery(from input: String) -> String? {
235
- let prefixes = [
236
- "find all the ", "find all ", "find ",
237
- "search for all ", "search for ", "search ",
238
- "look for ", "look up ", "locate all ", "locate ",
239
- "where is ", "where s ", "where does it say ", "where did i see ",
240
- "which window has ", "which window shows ",
241
- "help me find ", "can you find ",
242
- "show me all the ", "show me all ", "show all the ", "show all ",
243
- "open up all the ", "open up all ", "open all the ", "open all ",
244
- "pull up everything with ", "pull up all ", "pull up ",
245
- "bring up all the ", "bring up all ", "bring up my ",
246
- "where d my ", "i lost my ", "i lost ", "where the hell is ",
247
- "see all my ", "see all ", "see where s ", "see where is "
248
- ]
249
-
250
- if let entity = extractEntity(in: input, prefixes: prefixes) {
251
- return entity
252
- }
253
-
254
- if input.hasSuffix(" windows") || input.hasSuffix(" window") {
255
- return cleanQuery(input)
256
- }
257
-
258
- let wordCount = input.split(separator: " ").count
259
- if wordCount <= 3, !genericNonCommandPhrases.contains(input) {
260
- return input
261
- }
262
-
263
- return nil
264
- }
265
-
266
- private func extractLayerName(from input: String) -> String? {
267
- if let called = extractEntity(in: input, prefixes: [
268
- "create a layer called ", "create layer called ", "make a layer called ",
269
- "make layer called ", "new layer called ", "name this layer "
270
- ]) {
271
- return called
272
- }
273
-
274
- return extractEntity(in: input, prefixes: [
275
- "save this layout as ", "save layout as ", "save as layer ", "save as ",
276
- "create a layer ", "create layer ", "create new layer ",
277
- "make a layer ", "make layer ", "make a new layer ", "new layer "
278
- ])
279
- }
280
-
281
- private func extractLayer(in input: String) -> String? {
282
- if input == "next layer" || input == "previous layer" {
283
- return input
284
- }
285
-
286
- if let literal = [
287
- "layer one": "1",
288
- "layer two": "2",
289
- "layer three": "3",
290
- "first layer": "1",
291
- "second layer": "2",
292
- "third layer": "3",
293
- ][input] {
294
- return literal
295
- }
296
-
297
- if let entity = extractEntity(in: input, prefixes: [
298
- "switch to layer ", "switch to the ", "switch to ",
299
- "go to layer ", "go to the ", "go to ",
300
- "activate layer ", "activate the ", "change to layer ",
301
- "change layer to ", "layer "
302
- ]) {
303
- return cleanLayer(entity)
304
- }
305
-
306
- return nil
307
- }
308
-
309
- private func extractEntity(in input: String, prefixes: [String]) -> String? {
310
- for prefix in prefixes.sorted(by: { $0.count > $1.count }) {
311
- if input.hasPrefix(prefix) {
312
- return String(input.dropFirst(prefix.count))
313
- }
314
- }
315
- return nil
316
- }
317
-
318
- private func normalizeUtterance(_ text: String) -> String {
319
- var input = text.lowercased()
320
- .replacingOccurrences(of: #"[^\w\s-]"#, with: " ", options: .regularExpression)
321
- .split(separator: " ").joined(separator: " ")
322
- .trimmingCharacters(in: .whitespacesAndNewlines)
323
-
324
- var changed = true
325
- while changed {
326
- changed = false
327
- for prefix in leadingNoise {
328
- if input.hasPrefix(prefix) {
329
- input = String(input.dropFirst(prefix.count)).trimmingCharacters(in: .whitespaces)
330
- changed = true
331
- break
332
- }
333
- }
334
- }
335
-
336
- var stripped = true
337
- while stripped {
338
- stripped = false
339
- for suffix in trailingNoise {
340
- if input.hasSuffix(suffix) {
341
- input = String(input.dropLast(suffix.count)).trimmingCharacters(in: .whitespaces)
342
- stripped = true
343
- break
344
- }
345
- }
346
- }
347
-
348
- return input
349
- }
350
-
351
- private func resolvePosition(in input: String) -> String? {
352
- let map: [(keywords: [String], position: String)] = [
353
- (["top left", "upper left", "top-left"], "top-left"),
354
- (["top right", "upper right", "top-right"], "top-right"),
355
- (["bottom left", "lower left", "bottom-left"], "bottom-left"),
356
- (["bottom right", "lower right", "bottom-right"], "bottom-right"),
357
- (["left half", "left side", "the left", "left"], "left"),
358
- (["right half", "right side", "the right", "right"], "right"),
359
- (["maximize", "full screen", "full", "big", "max"], "maximize"),
360
- (["center", "middle", "centre"], "center"),
361
- (["top half", "top"], "top"),
362
- (["bottom half", "bottom"], "bottom"),
363
- ]
364
-
365
- for entry in map {
366
- if entry.keywords.contains(where: input.contains) {
367
- return entry.position
368
- }
369
- }
370
- return nil
371
- }
372
-
373
- private func directMatch(for input: String) -> IntentMatch? {
374
- if let intent = exactIntentMatches[input] {
375
- return IntentMatch(intentName: intent, slots: [:], confidence: 0.99, matchedPhrase: input)
376
- }
377
-
378
- if let query = exactSearchQuery(for: input) {
379
- return IntentMatch(
380
- intentName: "search",
381
- slots: ["query": .string(query)],
382
- confidence: 0.98,
383
- matchedPhrase: input
384
- )
385
- }
386
-
387
- if let app = exactFocusApp(for: input) {
388
- return IntentMatch(
389
- intentName: "focus",
390
- slots: ["app": .string(app)],
391
- confidence: 0.98,
392
- matchedPhrase: input
393
- )
394
- }
395
-
396
- if let position = exactTilePosition(for: input) {
397
- return IntentMatch(
398
- intentName: "tile_window",
399
- slots: ["position": .string(position)],
400
- confidence: 0.99,
401
- matchedPhrase: input
402
- )
403
- }
404
-
405
- if let session = exactKillSession(for: input) {
406
- return IntentMatch(
407
- intentName: "kill",
408
- slots: ["session": .string(session)],
409
- confidence: 0.98,
410
- matchedPhrase: input
411
- )
412
- }
413
-
414
- return nil
415
- }
416
-
417
- private func exactSearchQuery(for input: String) -> String? {
418
- if let query = extractSearchQuery(from: input), !query.isEmpty,
419
- searchPrefixes.contains(where: input.hasPrefix) {
420
- return cleanQuery(query)
421
- }
422
-
423
- if input.hasSuffix(" windows"), input != "list windows" {
424
- let query = cleanQuery(input)
425
- return query.isEmpty ? nil : query
426
- }
427
-
428
- return nil
429
- }
430
-
431
- private func exactFocusApp(for input: String) -> String? {
432
- if input.hasPrefix("see "), let app = detectKnownApp(in: input) ?? extractEntity(in: input, prefixes: ["see "]) {
433
- let resolved = resolveApp(app)
434
- return resolved.isEmpty ? nil : resolved
435
- }
436
-
437
- if input.hasSuffix(" on screen"), input.hasPrefix("get "),
438
- let app = extractEntity(in: input, prefixes: ["get "]) {
439
- let resolved = resolveApp(cleanEntity(app.replacingOccurrences(of: " on screen", with: "")))
440
- return resolved.isEmpty ? nil : resolved
441
- }
442
-
443
- return nil
444
- }
445
-
446
- private func exactTilePosition(for input: String) -> String? {
447
- if input == "right side" { return "right" }
448
- if input == "left side" { return "left" }
449
- return nil
450
- }
451
-
452
- private func exactKillSession(for input: String) -> String? {
453
- if input.hasPrefix("kill "), let session = extractEntity(in: input, prefixes: ["kill "]) {
454
- return session
455
- }
456
- if input.hasPrefix("stop "), let session = extractEntity(in: input, prefixes: ["stop "]) {
457
- return session
458
- }
459
- return nil
460
- }
461
-
462
- private func refersToSelection(in input: String) -> Bool {
463
- let markers = [
464
- "grid that", "grid these", "grid those",
465
- "tile that", "tile these", "tile those",
466
- "selected windows", "selection", "selected", "these windows", "those windows", "them"
467
- ]
468
- return markers.contains(where: input.contains)
469
- }
470
-
471
- private func detectKnownApp(in input: String) -> String? {
472
- for app in knownApps() {
473
- let lower = app.lowercased()
474
- if input.contains(lower) {
475
- return app
476
- }
477
- }
478
- return nil
479
- }
480
-
481
- private func knownApps() -> [String] {
482
- var names = Set(DesktopModel.shared.windows.values.map(\.app))
483
- for app in NSWorkspace.shared.runningApplications {
484
- if let name = app.localizedName, !name.isEmpty {
485
- names.insert(name)
486
- }
487
- }
488
- return names.sorted { $0.count > $1.count }
489
- }
490
-
491
- private func resolveApp(_ raw: String) -> String {
492
- let trimmed = cleanEntity(raw)
493
- let knownAliases: [String: String] = [
494
- "visual studio code": "Visual Studio Code",
495
- "vs code": "Visual Studio Code",
496
- "vscode": "Visual Studio Code",
497
- "google chrome": "Google Chrome",
498
- "iterm2": "iTerm2",
499
- "iterm": "iTerm2",
500
- ]
501
-
502
- if let alias = knownAliases[trimmed.lowercased()] {
503
- return alias
504
- }
505
-
506
- return trimmed
507
- .split(separator: " ")
508
- .map { $0.prefix(1).uppercased() + $0.dropFirst() }
509
- .joined(separator: " ")
510
- }
511
-
512
- private func cleanLayer(_ raw: String) -> String {
513
- cleanEntity(raw)
514
- .replacingOccurrences(of: " layer", with: "")
515
- .trimmingCharacters(in: .whitespaces)
516
- }
517
-
518
- private func cleanEntity(_ raw: String) -> String {
519
- var value = raw.trimmingCharacters(in: .whitespacesAndNewlines)
520
- let leading = ["the ", "my ", "a ", "an ", "this ", "that ", "like ", "um ", "uh "]
521
- let trailing = [
522
- " please", " for me", " right now", " real quick", " quickly",
523
- " session", " project", " app", " windows", " window", " layer"
524
- ]
525
-
526
- var changed = true
527
- while changed {
528
- changed = false
529
-
530
- for prefix in leading {
531
- if value.hasPrefix(prefix) {
532
- value = String(value.dropFirst(prefix.count)).trimmingCharacters(in: .whitespaces)
533
- changed = true
534
- }
535
- }
536
-
537
- for suffix in trailing {
538
- if value.hasSuffix(suffix) {
539
- value = String(value.dropLast(suffix.count)).trimmingCharacters(in: .whitespaces)
540
- changed = true
541
- }
542
- }
543
- }
544
-
545
- return value.trimmingCharacters(in: .whitespacesAndNewlines)
546
- }
547
-
548
- private func cleanQuery(_ raw: String) -> String {
549
- var query = raw
550
- let noise = [
551
- "all instances of ", "all of the ", "all the ", "all ",
552
- "instances of ", "of the ",
553
- "that mentioned ", "that mention ", "that say ", "that says ",
554
- "that have ", "that has ", "that are ", "that is ",
555
- "everything with ", "everything that has ",
556
- "windows ", "window ", "windows", "window",
557
- "stuff ", "stuff", "project ", "project", "app ", "app",
558
- "with the name ", "named ", "called ",
559
- "in the title", "in my title", "in the name", "on my screen", "on screen",
560
- " in it", " in there", " at", " go"
561
- ]
562
-
563
- for item in noise {
564
- query = query.replacingOccurrences(of: item, with: " ")
565
- }
566
-
567
- return query
568
- .split(separator: " ")
569
- .joined(separator: " ")
570
- .trimmingCharacters(in: .whitespacesAndNewlines)
571
- }
572
-
573
- private func tokenOverlap(_ lhs: String, _ rhs: String) -> Double {
574
- let lhsTokens = Set(lhs.split(separator: " ").map(String.init))
575
- let rhsTokens = Set(rhs.split(separator: " ").map(String.init))
576
- guard !lhsTokens.isEmpty, !rhsTokens.isEmpty else { return 0 }
577
- let intersection = lhsTokens.intersection(rhsTokens).count
578
- let union = lhsTokens.union(rhsTokens).count
579
- return Double(intersection) / Double(union)
580
- }
581
-
582
- private let leadingNoise = [
583
- "alright let s go ahead and ", "alright let s go ahead ", "let s go ahead and ",
584
- "alright let s ", "all right let s ",
585
- "okay let s ", "ok let s ",
586
- "can you please ", "could you please ", "would you please ",
587
- "i think i want to ", "i think i need to ",
588
- "can you ", "could you ", "would you ", "will you ",
589
- "i want to ", "i d like to ", "i would like to ",
590
- "i want you to ", "i need to ", "i need you to ",
591
- "i wanna ", "i think ", "i need ",
592
- "let s ", "let me ", "please ", "go ahead and ", "just ", "now ",
593
- "alright ", "all right ",
594
- "no sorry ", "sorry ", "no wait ", "wait ",
595
- "actually ", "okay ", "ok ",
596
- "um ", "uh ", "like ", "hmm ", "yeah ", "hey ", "yo ", "so "
597
- ]
598
-
599
- private let trailingNoise = [
600
- " please", " for me", " real quick", " right now", " quickly",
601
- " if you can", " when you get a chance", " at", " up"
602
- ]
603
-
604
- private let focusPrefixes = [
605
- "show me the ", "show me ", "show ", "focus on ", "focus the ", "focus ",
606
- "switch over to ", "switch to ", "go back to ", "go to ",
607
- "bring up the ", "bring up ", "bring forward ", "raise the ", "raise ",
608
- "pull up the ", "pull up ", "i want to see ", "let me see ",
609
- "take me to ", "give me the ", "give me ", "activate the ", "activate ",
610
- "jump to ", "can i get ", "see "
611
- ]
612
-
613
- private let launchPrefixes = [
614
- "open up ", "open my ", "open the ", "open ",
615
- "launch the ", "launch my ", "launch ",
616
- "start working on ", "start up ", "start the ", "start my ", "start ",
617
- "work on the ", "work on ",
618
- "begin working on ", "begin ",
619
- "fire up the ", "fire up ", "spin up ", "boot up ",
620
- "load up ", "load ", "run the ", "run "
621
- ]
622
-
623
- private let killPrefixes = [
624
- "kill the ", "kill ", "stop the ", "stop ",
625
- "shut down the ", "shut down ", "close the ", "close ",
626
- "terminate the ", "terminate ", "end the ", "end "
627
- ]
628
-
629
- private let genericNonCommandPhrases: Set<String> = [
630
- "what time is it",
631
- "tell me a joke",
632
- "how are you doing",
633
- "the weather today",
634
- "play some music",
635
- "set a timer for five minutes"
636
- ]
637
-
638
- private let intentKeywords: [String: [String]] = [
639
- "tile_window": ["tile", "snap", "move", "put", "throw", "left", "right", "top", "bottom", "center", "maximize", "full screen"],
640
- "focus": ["show", "focus", "switch to", "go to", "bring up", "pull up", "activate", "jump to"],
641
- "launch": ["open", "launch", "start", "begin", "fire up", "boot up", "work on", "run"],
642
- "switch_layer": ["layer", "switch to", "next layer", "previous layer"],
643
- "search": ["find", "search", "look for", "where is", "where d", "locate", "lost", "show me all", "windows"],
644
- "list_windows": ["what s open", "list windows", "which windows", "what do i have open"],
645
- "list_sessions": ["list sessions", "what s running", "which projects", "show my sessions"],
646
- "distribute": ["distribute", "spread", "organize", "arrange", "tidy", "clean up", "grid", "selected", "selection"],
647
- "create_layer": ["create layer", "save layout", "snapshot", "remember this layout"],
648
- "kill": ["kill", "stop", "shut down", "close", "terminate", "end"],
649
- "scan": ["scan", "rescan", "ocr", "read the screen", "what s on my screen", "screen text"],
650
- "help": ["help", "what can i do", "what can you do", "commands", "options"]
651
- ]
652
-
653
- private let searchPrefixes = [
654
- "find all the ", "find all ", "find ",
655
- "search for all ", "search for ", "search ",
656
- "look for ", "look up ", "locate all ", "locate ",
657
- "where is ", "where s ", "where does it say ", "where did i see ",
658
- "which window has ", "which window shows ",
659
- "help me find ", "can you find ",
660
- "show me all the ", "show me all ", "show all the ", "show all ",
661
- "open up all the ", "open up all ", "open all the ", "open all ",
662
- "pull up everything with ", "pull up all ", "pull up ",
663
- "bring up all the ", "bring up all ", "bring up my ",
664
- "where d my ", "i lost my ", "i lost ", "where the hell is ",
665
- "see all my ", "see all "
666
- ]
667
-
668
- private let exactIntentMatches: [String: String] = [
669
- "help": "help",
670
- "help me": "help",
671
- "what can i do": "help",
672
- "what can you do": "help",
673
- "how does this work": "help",
674
- "what can i say": "help",
675
- "what are my options": "help",
676
- "show me the commands": "help",
677
- "list windows": "list_windows",
678
- "what windows do i have": "list_windows",
679
- "list sessions": "list_sessions",
680
- "show me my sessions": "list_sessions",
681
- "rescan": "scan",
682
- "do a scan": "scan",
683
- "do a quick scan": "scan",
684
- "scan the screen": "scan",
685
- "read the screen": "scan",
686
- "refresh the screen text": "scan",
687
- "what s on my screen": "scan",
688
- "what s on the screen": "scan",
689
- "show me what s on the screen": "scan",
690
- "organize": "distribute",
691
- "organize my windows": "distribute",
692
- "line everything up": "distribute",
693
- "let s get everything organized": "distribute",
694
- "get everything organized": "distribute",
695
- "clean up the windows": "distribute",
696
- "tidy up": "distribute"
697
- ]
698
-
699
- private let supplementalExamples: [String: [String]] = [
700
- "tile_window": ["put this on the left side", "move this over to the right", "maximize", "center it"],
701
- "focus": ["i need to see chrome", "can i get safari up", "show me visual studio code"],
702
- "launch": ["fire up vox", "start working on lattices", "open my notes app"],
703
- "switch_layer": ["next layer", "previous layer", "switch to review"],
704
- "search": ["where d my slack go", "pull up everything with dewey in it", "show me all the chrome windows", "dewey"],
705
- "list_windows": ["what do i have open", "what windows do i have"],
706
- "list_sessions": ["show me my sessions", "which projects are active"],
707
- "distribute": ["tidy up", "line everything up", "clean up the windows", "grid that in the bottom half", "arrange the selected windows"],
708
- "create_layer": ["snapshot this", "remember this layout"],
709
- "kill": ["close the dewey session", "stop my session"],
710
- "scan": ["what s on my screen", "read the screen", "give me a fresh scan"],
711
- "help": ["what can i say", "show me the commands"]
712
- ]
713
- }