@lattices/cli 0.4.2 → 0.4.5

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 (70) hide show
  1. package/README.md +3 -0
  2. package/app/Info.plist +2 -2
  3. package/app/Lattices.app/Contents/Info.plist +2 -2
  4. package/app/Lattices.app/Contents/MacOS/Lattices +0 -0
  5. package/app/Package.swift +6 -0
  6. package/app/Sources/App.swift +10 -0
  7. package/app/Sources/AppDelegate.swift +90 -34
  8. package/app/Sources/AppShellView.swift +2 -0
  9. package/app/Sources/AppTypeClassifier.swift +36 -0
  10. package/app/Sources/AppUpdater.swift +92 -0
  11. package/app/Sources/CheatSheetHUD.swift +1 -0
  12. package/app/Sources/CliActionLauncher.swift +50 -0
  13. package/app/Sources/CommandModeView.swift +4 -24
  14. package/app/Sources/CompanionActivityLog.swift +70 -0
  15. package/app/Sources/CompanionKeyboardController.swift +141 -0
  16. package/app/Sources/DesktopModel.swift +4 -0
  17. package/app/Sources/HandsOffSession.swift +15 -4
  18. package/app/Sources/HomeDashboardView.swift +18 -10
  19. package/app/Sources/HotkeyStore.swift +8 -5
  20. package/app/Sources/IntentEngine.swift +7 -1
  21. package/app/Sources/LatticesApi.swift +125 -4
  22. package/app/Sources/LatticesCompanionBridgeServer.swift +438 -0
  23. package/app/Sources/LatticesCompanionCockpit.swift +555 -0
  24. package/app/Sources/LatticesCompanionSecurityCoordinator.swift +594 -0
  25. package/app/Sources/LatticesCompanionTrackpadController.swift +204 -0
  26. package/app/Sources/LatticesDeckHost.swift +1463 -0
  27. package/app/Sources/LatticesRuntime.swift +61 -0
  28. package/app/Sources/MainView.swift +351 -191
  29. package/app/Sources/MouseFinder.swift +335 -30
  30. package/app/Sources/MouseGestureConfig.swift +364 -0
  31. package/app/Sources/MouseGestureController.swift +1203 -0
  32. package/app/Sources/MouseInputDeviceStore.swift +98 -0
  33. package/app/Sources/MouseInputEventViewer.swift +272 -0
  34. package/app/Sources/MouseShortcutStore.swift +107 -0
  35. package/app/Sources/OmniSearchView.swift +136 -2
  36. package/app/Sources/OmniSearchWindow.swift +65 -5
  37. package/app/Sources/OnboardingView.swift +30 -16
  38. package/app/Sources/PaletteCommand.swift +26 -6
  39. package/app/Sources/PermissionChecker.swift +76 -2
  40. package/app/Sources/PiAuthNextStepCard.swift +148 -0
  41. package/app/Sources/PiAuthPromptCard.swift +90 -0
  42. package/app/Sources/PiChatDock.swift +137 -74
  43. package/app/Sources/PiChatSession.swift +608 -108
  44. package/app/Sources/PiInstallCallout.swift +86 -0
  45. package/app/Sources/PiProviderSetupCallout.swift +99 -0
  46. package/app/Sources/PiWorkspaceView.swift +174 -77
  47. package/app/Sources/Preferences.swift +78 -0
  48. package/app/Sources/ScreenMapState.swift +91 -31
  49. package/app/Sources/ScreenMapView.swift +510 -524
  50. package/app/Sources/ScreenMapWindowController.swift +12 -4
  51. package/app/Sources/SettingsView.swift +869 -152
  52. package/app/Sources/SystemTelemetryMonitor.swift +273 -0
  53. package/app/Sources/VoiceCommandWindow.swift +23 -2
  54. package/app/Sources/WindowDragSnapController.swift +628 -0
  55. package/app/Sources/WindowTiler.swift +328 -65
  56. package/app/Sources/WorkspaceManager.swift +288 -0
  57. package/bin/assistant-intelligence.ts +874 -0
  58. package/bin/handsoff-infer.ts +16 -209
  59. package/bin/handsoff-worker.ts +45 -258
  60. package/bin/lattices-app.ts +62 -0
  61. package/bin/lattices-dev +4 -0
  62. package/bin/lattices.ts +125 -14
  63. package/docs/agents.md +14 -0
  64. package/docs/api.md +55 -0
  65. package/docs/app.md +3 -0
  66. package/docs/companion-deck.md +180 -0
  67. package/docs/config.md +25 -0
  68. package/docs/tiling-reference.md +55 -0
  69. package/docs/voice-error-model.md +73 -0
  70. package/package.json +2 -1
@@ -63,6 +63,273 @@ struct LayoutConfig: Codable {
63
63
  struct GridFile: Codable {
64
64
  let presets: [String: GridPreset]?
65
65
  let layouts: [String: LayoutConfig]?
66
+ let snapZones: SnapZonesConfig?
67
+ }
68
+
69
+ enum SnapModifierKey: String, Codable, Equatable {
70
+ case command
71
+ case option
72
+ case control
73
+ case shift
74
+
75
+ var eventFlags: NSEvent.ModifierFlags {
76
+ switch self {
77
+ case .command:
78
+ return .command
79
+ case .option:
80
+ return .option
81
+ case .control:
82
+ return .control
83
+ case .shift:
84
+ return .shift
85
+ }
86
+ }
87
+
88
+ var cgEventFlags: CGEventFlags {
89
+ switch self {
90
+ case .command:
91
+ return .maskCommand
92
+ case .option:
93
+ return .maskAlternate
94
+ case .control:
95
+ return .maskControl
96
+ case .shift:
97
+ return .maskShift
98
+ }
99
+ }
100
+
101
+ var label: String {
102
+ rawValue.capitalized
103
+ }
104
+ }
105
+
106
+ enum SnapZoneTriggerSpec: Codable, Equatable {
107
+ case named(String)
108
+ case fractions(FractionalPlacement)
109
+
110
+ init(from decoder: Decoder) throws {
111
+ let container = try decoder.singleValueContainer()
112
+ if let named = try? container.decode(String.self) {
113
+ self = .named(named)
114
+ return
115
+ }
116
+
117
+ let preset = try container.decode(GridPreset.self)
118
+ guard let placement = FractionalPlacement(x: preset.x, y: preset.y, w: preset.w, h: preset.h) else {
119
+ throw DecodingError.dataCorruptedError(
120
+ in: container,
121
+ debugDescription: "snap zone trigger fractions must stay within 0...1"
122
+ )
123
+ }
124
+ self = .fractions(placement)
125
+ }
126
+
127
+ func encode(to encoder: Encoder) throws {
128
+ var container = encoder.singleValueContainer()
129
+ switch self {
130
+ case .named(let name):
131
+ try container.encode(name)
132
+ case .fractions(let placement):
133
+ try container.encode(GridPreset(x: placement.x, y: placement.y, w: placement.w, h: placement.h))
134
+ }
135
+ }
136
+ }
137
+
138
+ enum SnapZonePlacementSpec: Codable, Equatable {
139
+ case named(String)
140
+ case fractions(FractionalPlacement)
141
+
142
+ init(from decoder: Decoder) throws {
143
+ let container = try decoder.singleValueContainer()
144
+ if let named = try? container.decode(String.self) {
145
+ self = .named(named)
146
+ return
147
+ }
148
+
149
+ let preset = try container.decode(GridPreset.self)
150
+ guard let placement = FractionalPlacement(x: preset.x, y: preset.y, w: preset.w, h: preset.h) else {
151
+ throw DecodingError.dataCorruptedError(
152
+ in: container,
153
+ debugDescription: "snap zone placement fractions must stay within 0...1"
154
+ )
155
+ }
156
+ self = .fractions(placement)
157
+ }
158
+
159
+ func encode(to encoder: Encoder) throws {
160
+ var container = encoder.singleValueContainer()
161
+ switch self {
162
+ case .named(let name):
163
+ try container.encode(name)
164
+ case .fractions(let placement):
165
+ try container.encode(GridPreset(x: placement.x, y: placement.y, w: placement.w, h: placement.h))
166
+ }
167
+ }
168
+ }
169
+
170
+ struct SnapZoneDefinition: Codable, Equatable, Identifiable {
171
+ let rawID: String?
172
+ let label: String?
173
+ let placement: SnapZonePlacementSpec
174
+ let trigger: SnapZoneTriggerSpec
175
+ let priority: Int?
176
+
177
+ enum CodingKeys: String, CodingKey {
178
+ case rawID = "id"
179
+ case label
180
+ case placement
181
+ case trigger
182
+ case priority
183
+ }
184
+
185
+ var id: String {
186
+ let trimmed = rawID?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
187
+ return trimmed.isEmpty ? fallbackID : trimmed
188
+ }
189
+
190
+ private var fallbackID: String {
191
+ switch placement {
192
+ case .named(let name):
193
+ return name
194
+ case .fractions(let fractions):
195
+ return "fractions-\(fractions.x)-\(fractions.y)-\(fractions.w)-\(fractions.h)"
196
+ }
197
+ }
198
+ }
199
+
200
+ struct SnapZonesConfig: Codable, Equatable {
201
+ let enabled: Bool?
202
+ let modifier: SnapModifierKey?
203
+ let zoneOpacity: Double?
204
+ let highlightOpacity: Double?
205
+ let previewOpacity: Double?
206
+ let cornerRadius: CGFloat?
207
+ let rules: [SnapZoneDefinition]?
208
+
209
+ enum CodingKeys: String, CodingKey {
210
+ case enabled
211
+ case modifier
212
+ case zoneOpacity
213
+ case highlightOpacity
214
+ case previewOpacity
215
+ case cornerRadius
216
+ case rules
217
+ case zones
218
+ }
219
+
220
+ init(
221
+ enabled: Bool?,
222
+ modifier: SnapModifierKey?,
223
+ zoneOpacity: Double?,
224
+ highlightOpacity: Double?,
225
+ previewOpacity: Double?,
226
+ cornerRadius: CGFloat?,
227
+ rules: [SnapZoneDefinition]?
228
+ ) {
229
+ self.enabled = enabled
230
+ self.modifier = modifier
231
+ self.zoneOpacity = zoneOpacity
232
+ self.highlightOpacity = highlightOpacity
233
+ self.previewOpacity = previewOpacity
234
+ self.cornerRadius = cornerRadius
235
+ self.rules = rules
236
+ }
237
+
238
+ init(from decoder: Decoder) throws {
239
+ let container = try decoder.container(keyedBy: CodingKeys.self)
240
+ enabled = try container.decodeIfPresent(Bool.self, forKey: .enabled)
241
+ modifier = try container.decodeIfPresent(SnapModifierKey.self, forKey: .modifier)
242
+ zoneOpacity = try container.decodeIfPresent(Double.self, forKey: .zoneOpacity)
243
+ highlightOpacity = try container.decodeIfPresent(Double.self, forKey: .highlightOpacity)
244
+ previewOpacity = try container.decodeIfPresent(Double.self, forKey: .previewOpacity)
245
+ cornerRadius = try container.decodeIfPresent(CGFloat.self, forKey: .cornerRadius)
246
+ let decodedRules = try container.decodeIfPresent([SnapZoneDefinition].self, forKey: .rules)
247
+ let decodedZones = try container.decodeIfPresent([SnapZoneDefinition].self, forKey: .zones)
248
+ rules = decodedRules ?? decodedZones
249
+ }
250
+
251
+ func encode(to encoder: Encoder) throws {
252
+ var container = encoder.container(keyedBy: CodingKeys.self)
253
+ try container.encodeIfPresent(enabled, forKey: .enabled)
254
+ try container.encodeIfPresent(modifier, forKey: .modifier)
255
+ try container.encodeIfPresent(zoneOpacity, forKey: .zoneOpacity)
256
+ try container.encodeIfPresent(highlightOpacity, forKey: .highlightOpacity)
257
+ try container.encodeIfPresent(previewOpacity, forKey: .previewOpacity)
258
+ try container.encodeIfPresent(cornerRadius, forKey: .cornerRadius)
259
+ try container.encodeIfPresent(rules, forKey: .rules)
260
+ }
261
+
262
+ static let defaults = SnapZonesConfig(
263
+ enabled: true,
264
+ modifier: .command,
265
+ zoneOpacity: 0.10,
266
+ highlightOpacity: 0.22,
267
+ previewOpacity: 0.18,
268
+ cornerRadius: 18,
269
+ rules: [
270
+ SnapZoneDefinition(
271
+ rawID: "top-left",
272
+ label: "Top Left",
273
+ placement: .named("top-left"),
274
+ trigger: .fractions(FractionalPlacement(x: 0.00, y: 0.00, w: 0.24, h: 0.18)!),
275
+ priority: 40
276
+ ),
277
+ SnapZoneDefinition(
278
+ rawID: "maximize",
279
+ label: "Maximize",
280
+ placement: .named("maximize"),
281
+ trigger: .fractions(FractionalPlacement(x: 0.24, y: 0.00, w: 0.52, h: 0.12)!),
282
+ priority: 20
283
+ ),
284
+ SnapZoneDefinition(
285
+ rawID: "top-right",
286
+ label: "Top Right",
287
+ placement: .named("top-right"),
288
+ trigger: .fractions(FractionalPlacement(x: 0.76, y: 0.00, w: 0.24, h: 0.18)!),
289
+ priority: 40
290
+ ),
291
+ SnapZoneDefinition(
292
+ rawID: "left",
293
+ label: "Left",
294
+ placement: .named("left"),
295
+ trigger: .fractions(FractionalPlacement(x: 0.00, y: 0.18, w: 0.12, h: 0.64)!),
296
+ priority: 10
297
+ ),
298
+ SnapZoneDefinition(
299
+ rawID: "right",
300
+ label: "Right",
301
+ placement: .named("right"),
302
+ trigger: .fractions(FractionalPlacement(x: 0.88, y: 0.18, w: 0.12, h: 0.64)!),
303
+ priority: 10
304
+ ),
305
+ SnapZoneDefinition(
306
+ rawID: "bottom-left",
307
+ label: "Bottom Left",
308
+ placement: .named("bottom-left"),
309
+ trigger: .fractions(FractionalPlacement(x: 0.00, y: 0.82, w: 0.24, h: 0.18)!),
310
+ priority: 40
311
+ ),
312
+ SnapZoneDefinition(
313
+ rawID: "bottom-right",
314
+ label: "Bottom Right",
315
+ placement: .named("bottom-right"),
316
+ trigger: .fractions(FractionalPlacement(x: 0.76, y: 0.82, w: 0.24, h: 0.18)!),
317
+ priority: 40
318
+ ),
319
+ ]
320
+ )
321
+
322
+ func merged(over defaults: SnapZonesConfig = .defaults) -> SnapZonesConfig {
323
+ SnapZonesConfig(
324
+ enabled: enabled ?? defaults.enabled,
325
+ modifier: modifier ?? defaults.modifier,
326
+ zoneOpacity: zoneOpacity ?? defaults.zoneOpacity,
327
+ highlightOpacity: highlightOpacity ?? defaults.highlightOpacity,
328
+ previewOpacity: previewOpacity ?? defaults.previewOpacity,
329
+ cornerRadius: cornerRadius ?? defaults.cornerRadius,
330
+ rules: rules ?? defaults.rules
331
+ )
332
+ }
66
333
  }
67
334
 
68
335
  // MARK: - Manager
@@ -75,9 +342,11 @@ class WorkspaceManager: ObservableObject {
75
342
  @Published var isSwitching: Bool = false
76
343
  @Published var gridPresets: [String: GridPreset] = [:]
77
344
  @Published var gridLayouts: [String: LayoutConfig] = [:]
345
+ @Published var snapZonesConfig: SnapZonesConfig = .defaults
78
346
 
79
347
  private let configPath: String
80
348
  private let gridConfigPath: String
349
+ private let snapZonesConfigPath: String
81
350
  private var tmuxPath: String { TmuxQuery.resolvedPath ?? "/opt/homebrew/bin/tmux" }
82
351
  private let activeLayerKey = "lattices.activeLayerIndex"
83
352
 
@@ -85,6 +354,7 @@ class WorkspaceManager: ObservableObject {
85
354
  let home = FileManager.default.homeDirectoryForCurrentUser.path
86
355
  self.configPath = (home as NSString).appendingPathComponent(".lattices/workspace.json")
87
356
  self.gridConfigPath = (home as NSString).appendingPathComponent(".lattices/grid.json")
357
+ self.snapZonesConfigPath = (home as NSString).appendingPathComponent(".lattices/snap-zones.json")
88
358
  self.activeLayerIndex = UserDefaults.standard.integer(forKey: activeLayerKey)
89
359
  loadConfig()
90
360
  loadGridConfig()
@@ -137,6 +407,7 @@ class WorkspaceManager: ObservableObject {
137
407
  func loadGridConfig() {
138
408
  var presets: [String: GridPreset] = [:]
139
409
  var layouts: [String: LayoutConfig] = [:]
410
+ var snapZones = SnapZonesConfig.defaults
140
411
 
141
412
  // Load global ~/.lattices/grid.json
142
413
  if FileManager.default.fileExists(atPath: gridConfigPath),
@@ -145,11 +416,24 @@ class WorkspaceManager: ObservableObject {
145
416
  let gridFile = try JSONDecoder().decode(GridFile.self, from: data)
146
417
  if let p = gridFile.presets { presets.merge(p) { _, new in new } }
147
418
  if let l = gridFile.layouts { layouts.merge(l) { _, new in new } }
419
+ if let snap = gridFile.snapZones {
420
+ snapZones = snap.merged(over: snapZones)
421
+ }
148
422
  } catch {
149
423
  DiagnosticLog.shared.error("WorkspaceManager: failed to decode grid.json — \(error.localizedDescription)")
150
424
  }
151
425
  }
152
426
 
427
+ if FileManager.default.fileExists(atPath: snapZonesConfigPath),
428
+ let data = FileManager.default.contents(atPath: snapZonesConfigPath) {
429
+ do {
430
+ let config = try JSONDecoder().decode(SnapZonesConfig.self, from: data)
431
+ snapZones = config.merged(over: snapZones)
432
+ } catch {
433
+ DiagnosticLog.shared.error("WorkspaceManager: failed to decode snap-zones.json — \(error.localizedDescription)")
434
+ }
435
+ }
436
+
153
437
  // Merge per-project .lattices.json "grid" section on top
154
438
  let projectGridPath = ".lattices.json"
155
439
  if FileManager.default.fileExists(atPath: projectGridPath),
@@ -161,6 +445,9 @@ class WorkspaceManager: ObservableObject {
161
445
  let gridFile = try JSONDecoder().decode(GridFile.self, from: gridData)
162
446
  if let p = gridFile.presets { presets.merge(p) { _, new in new } }
163
447
  if let l = gridFile.layouts { layouts.merge(l) { _, new in new } }
448
+ if let snap = gridFile.snapZones {
449
+ snapZones = snap.merged(over: snapZones)
450
+ }
164
451
  }
165
452
  } catch {
166
453
  DiagnosticLog.shared.error("WorkspaceManager: failed to decode .lattices.json grid — \(error.localizedDescription)")
@@ -169,6 +456,7 @@ class WorkspaceManager: ObservableObject {
169
456
 
170
457
  self.gridPresets = presets
171
458
  self.gridLayouts = layouts
459
+ self.snapZonesConfig = snapZones
172
460
  }
173
461
 
174
462
  /// Resolve a tile string to fractions: check user presets first, then built-in TilePosition