@lattices/cli 0.4.6 → 0.4.8

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 (40) hide show
  1. package/README.md +8 -6
  2. package/app/Info.plist +13 -2
  3. package/app/Lattices.app/Contents/Info.plist +13 -2
  4. package/app/Lattices.app/Contents/MacOS/Lattices +0 -0
  5. package/app/Sources/AppShell/App.swift +7 -1
  6. package/app/Sources/AppShell/AppDelegate.swift +65 -1
  7. package/app/Sources/AppShell/AppShellView.swift +10 -0
  8. package/app/Sources/AppShell/CliActionLauncher.swift +2 -2
  9. package/app/Sources/AppShell/KeyRecorderView.swift +1 -1
  10. package/app/Sources/AppShell/MainView.swift +1 -1
  11. package/app/Sources/AppShell/Preferences.swift +29 -3
  12. package/app/Sources/AppShell/SettingsView.swift +525 -60
  13. package/app/Sources/AppShell/SettingsWindow.swift +4 -0
  14. package/app/Sources/Core/Actions/HotkeyStore.swift +13 -1
  15. package/app/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +23 -7
  16. package/app/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +35 -0
  17. package/app/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +1 -1
  18. package/app/Sources/Core/Desktop/WindowTiler.swift +0 -2
  19. package/app/Sources/Core/Input/KeyboardRemapConfig.swift +69 -0
  20. package/app/Sources/Core/Input/KeyboardRemapController.swift +184 -0
  21. package/app/Sources/Core/Input/KeyboardRemapStore.swift +84 -0
  22. package/app/Sources/Core/Overlays/CommandMode/CommandModeState.swift +101 -0
  23. package/app/Sources/Core/Overlays/CommandMode/CommandModeView.swift +113 -4
  24. package/app/Sources/Core/Overlays/CommandMode/CommandModeWindow.swift +9 -5
  25. package/app/Sources/Core/Overlays/HUD/CheatSheetHUD.swift +1 -0
  26. package/app/Sources/Core/Overlays/ScreenMap/ScreenMapState.swift +0 -1
  27. package/app/Sources/Core/Overlays/ScreenMap/ScreenMapView.swift +20 -7
  28. package/app/Sources/Core/Workspace/SessionManager.swift +1 -1
  29. package/app/Sources/Core/Workspace/WorkspaceManager.swift +62 -7
  30. package/bin/lattices-app.ts +11 -0
  31. package/bin/lattices-dev +11 -0
  32. package/bin/lattices.ts +57 -17
  33. package/docs/app.md +30 -2
  34. package/docs/companion-deck.md +29 -0
  35. package/docs/concepts.md +5 -5
  36. package/docs/config.md +34 -9
  37. package/docs/layers.md +1 -1
  38. package/docs/overview.md +1 -1
  39. package/docs/quickstart.md +4 -4
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -81,11 +81,11 @@ Close your laptop, reboot, come back a week later — your editor, dev
81
81
  server, and test runner are exactly where you left them.
82
82
 
83
83
  ```sh
84
- cd my-project && lattices
84
+ cd my-project && lattices start
85
85
  ```
86
86
 
87
- No config? It reads your `package.json` and picks the right dev command
88
- automatically.
87
+ No config? It opens a shell in the project and, when it can, starts your
88
+ detected dev command in a second pane.
89
89
 
90
90
  ### Configuration
91
91
 
@@ -95,7 +95,7 @@ Drop a `.lattices.json` in your project root:
95
95
  {
96
96
  "ensure": true,
97
97
  "panes": [
98
- { "name": "claude", "cmd": "claude", "size": 60 },
98
+ { "name": "shell", "size": 60 },
99
99
  { "name": "server", "cmd": "pnpm dev" },
100
100
  { "name": "tests", "cmd": "pnpm test --watch" }
101
101
  ]
@@ -108,7 +108,7 @@ Drop a `.lattices.json` in your project root:
108
108
  2 panes 3+ panes
109
109
 
110
110
  ┌──────────┬───────┐ ┌──────────┬───────┐
111
- claude │server │ │ claude │server │
111
+ shell │server │ │ shell │server │
112
112
  │ (60%) │(40%) │ │ (60%) ├───────┤
113
113
  └──────────┴───────┘ │ │tests │
114
114
  └──────────┴───────┘
@@ -193,7 +193,9 @@ desktop the same way you do.
193
193
  ## CLI
194
194
 
195
195
  ```
196
- lattices Create or reattach to session
196
+ lattices Show workspace status and common commands
197
+ lattices start Create or reattach to current project session
198
+ lattices tmux Alias for lattices start
197
199
  lattices init Generate .lattices.json
198
200
  lattices ls List active sessions
199
201
  lattices kill [name] Kill a session
package/app/Info.plist CHANGED
@@ -14,10 +14,21 @@
14
14
  <string>AppIcon</string>
15
15
  <key>CFBundlePackageType</key>
16
16
  <string>APPL</string>
17
+ <key>CFBundleURLTypes</key>
18
+ <array>
19
+ <dict>
20
+ <key>CFBundleURLName</key>
21
+ <string>com.arach.lattices</string>
22
+ <key>CFBundleURLSchemes</key>
23
+ <array>
24
+ <string>lattices</string>
25
+ </array>
26
+ </dict>
27
+ </array>
17
28
  <key>CFBundleVersion</key>
18
- <string>0.4.6</string>
29
+ <string>0.4.8</string>
19
30
  <key>CFBundleShortVersionString</key>
20
- <string>0.4.6</string>
31
+ <string>0.4.8</string>
21
32
  <key>LSMinimumSystemVersion</key>
22
33
  <string>13.0</string>
23
34
  <key>LSUIElement</key>
@@ -14,10 +14,21 @@
14
14
  <string>AppIcon</string>
15
15
  <key>CFBundlePackageType</key>
16
16
  <string>APPL</string>
17
+ <key>CFBundleURLTypes</key>
18
+ <array>
19
+ <dict>
20
+ <key>CFBundleURLName</key>
21
+ <string>com.arach.lattices</string>
22
+ <key>CFBundleURLSchemes</key>
23
+ <array>
24
+ <string>lattices</string>
25
+ </array>
26
+ </dict>
27
+ </array>
17
28
  <key>CFBundleVersion</key>
18
- <string>0.4.6</string>
29
+ <string>0.4.8</string>
19
30
  <key>CFBundleShortVersionString</key>
20
- <string>0.4.6</string>
31
+ <string>0.4.8</string>
21
32
  <key>LSMinimumSystemVersion</key>
22
33
  <string>13.0</string>
23
34
  <key>LSUIElement</key>
@@ -5,7 +5,13 @@ struct LatticesApp: App {
5
5
  @NSApplicationDelegateAdaptor(AppDelegate.self) var delegate
6
6
 
7
7
  var body: some Scene {
8
- Settings { EmptyView() }
8
+ Settings {
9
+ SettingsContentView(
10
+ prefs: Preferences.shared,
11
+ scanner: ProjectScanner.shared
12
+ )
13
+ .frame(width: 900, height: 640)
14
+ }
9
15
  .commands {
10
16
  CommandGroup(after: .appInfo) {
11
17
  Button("Update Lattices…") {
@@ -1,4 +1,5 @@
1
1
  import AppKit
2
+ import Carbon
2
3
  import SwiftUI
3
4
 
4
5
  extension Notification.Name {
@@ -69,6 +70,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
69
70
  Self.shared = self
70
71
  NSApp.setActivationPolicy(.accessory)
71
72
  NSApp.appearance = NSAppearance(named: .darkAqua)
73
+ registerDeepLinkHandler()
72
74
 
73
75
  // --- Status item ---
74
76
  statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
@@ -124,6 +126,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
124
126
  store.register(action: .omniSearch) { OmniSearchWindow.shared.toggle() }
125
127
  WindowDragSnapController.shared.start()
126
128
  MouseGestureController.shared.start()
129
+ KeyboardRemapController.shared.start()
127
130
 
128
131
  // Session layer cycling
129
132
  store.register(action: .layerNext) { SessionLayerStore.shared.cycleNext() }
@@ -162,6 +165,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
162
165
  }
163
166
  store.register(action: .tileDistribute) { WindowTiler.distributeVisible(reactivateLattices: false) }
164
167
  store.register(action: .tileTypeGrid) { WindowTiler.distributeVisibleByFrontmostType(reactivateLattices: false) }
168
+ store.register(action: .tileOrganize) {
169
+ let appName = DesktopModel.shared.frontmostWindow()?.app
170
+ ?? NSWorkspace.shared.frontmostApplication?.localizedName
171
+ CommandModeWindow.shared.show(launchMode: .organize(appName: appName))
172
+ }
165
173
 
166
174
  // Onboarding on first launch; otherwise just check permissions
167
175
  if !OnboardingWindowController.shared.showIfNeeded() {
@@ -178,7 +186,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
178
186
  ProcessModel.shared.start()
179
187
  LatticesApi.setup()
180
188
  DaemonServer.shared.start()
181
- LatticesCompanionBridgeServer.shared.start()
189
+ if Preferences.shared.companionBridgeEnabled {
190
+ LatticesCompanionBridgeServer.shared.start()
191
+ } else {
192
+ diag.info("CompanionBridge: disabled by preference")
193
+ }
182
194
  AgentPool.shared.start()
183
195
  diag.finish(tBoot)
184
196
 
@@ -198,6 +210,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
198
210
  }
199
211
 
200
212
  func applicationWillTerminate(_ notification: Notification) {
213
+ KeyboardRemapController.shared.stop()
201
214
  LatticesCompanionBridgeServer.shared.stop()
202
215
  DaemonServer.shared.stop()
203
216
  }
@@ -328,6 +341,57 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
328
341
  @objc private func menuSettings() { SettingsWindowController.shared.show() }
329
342
  @objc private func menuQuit() { NSApp.terminate(nil) }
330
343
 
344
+ // MARK: - Deep Links
345
+
346
+ private func registerDeepLinkHandler() {
347
+ NSAppleEventManager.shared().setEventHandler(
348
+ self,
349
+ andSelector: #selector(handleGetURLEvent(_:withReplyEvent:)),
350
+ forEventClass: AEEventClass(kInternetEventClass),
351
+ andEventID: AEEventID(kAEGetURL)
352
+ )
353
+ }
354
+
355
+ @objc private func handleGetURLEvent(
356
+ _ event: NSAppleEventDescriptor,
357
+ withReplyEvent replyEvent: NSAppleEventDescriptor
358
+ ) {
359
+ guard
360
+ let value = event.paramDescriptor(forKeyword: keyDirectObject)?.stringValue,
361
+ let url = URL(string: value)
362
+ else {
363
+ return
364
+ }
365
+ handleDeepLink(url)
366
+ }
367
+
368
+ private func handleDeepLink(_ url: URL) {
369
+ guard url.scheme?.localizedCaseInsensitiveCompare("lattices") == .orderedSame else {
370
+ return
371
+ }
372
+
373
+ let host = url.host?.lowercased()
374
+ let action = url.pathComponents
375
+ .first { $0 != "/" }?
376
+ .lowercased()
377
+
378
+ guard host == "companion" else {
379
+ SettingsWindowController.shared.show()
380
+ return
381
+ }
382
+
383
+ switch action {
384
+ case "enable", "start":
385
+ Preferences.shared.companionBridgeEnabled = true
386
+ SettingsWindowController.shared.showCompanion()
387
+ case "disable", "stop":
388
+ Preferences.shared.companionBridgeEnabled = false
389
+ SettingsWindowController.shared.showCompanion()
390
+ default:
391
+ SettingsWindowController.shared.showCompanion()
392
+ }
393
+ }
394
+
331
395
  private static func showWorkspaceInspector() {
332
396
  guard let entry = DesktopModel.shared.frontmostWindow(),
333
397
  entry.app != "Lattices" else {
@@ -8,6 +8,7 @@ enum AppPage: String, CaseIterable {
8
8
  case desktopInventory
9
9
  case pi
10
10
  case settings
11
+ case companionSettings
11
12
  case docs
12
13
 
13
14
  var label: String {
@@ -17,6 +18,7 @@ enum AppPage: String, CaseIterable {
17
18
  case .desktopInventory: return "Desktop Inventory"
18
19
  case .pi: return "Pi"
19
20
  case .settings: return "Settings"
21
+ case .companionSettings:return "Settings"
20
22
  case .docs: return "Docs"
21
23
  }
22
24
  }
@@ -28,6 +30,7 @@ enum AppPage: String, CaseIterable {
28
30
  case .desktopInventory: return "macwindow.on.rectangle"
29
31
  case .pi: return "terminal"
30
32
  case .settings: return "gearshape"
33
+ case .companionSettings:return "ipad.and.iphone"
31
34
  case .docs: return "book"
32
35
  }
33
36
  }
@@ -129,6 +132,13 @@ struct AppShellView: View {
129
132
  scanner: ProjectScanner.shared,
130
133
  onBack: { windowController.activePage = .screenMap; controller.enter() }
131
134
  )
135
+ case .companionSettings:
136
+ SettingsContentView(
137
+ page: .companionSettings,
138
+ prefs: Preferences.shared,
139
+ scanner: ProjectScanner.shared,
140
+ onBack: { windowController.activePage = .screenMap; controller.enter() }
141
+ )
132
142
  case .docs:
133
143
  SettingsContentView(
134
144
  page: .docs,
@@ -24,7 +24,7 @@ enum CliActionLauncher {
24
24
  ) else { return }
25
25
 
26
26
  Preferences.shared.terminal.launch(
27
- command: "lattices init && lattices",
27
+ command: "lattices init && lattices start",
28
28
  in: directory
29
29
  )
30
30
  }
@@ -36,7 +36,7 @@ enum CliActionLauncher {
36
36
  ) else { return }
37
37
 
38
38
  Preferences.shared.terminal.launch(
39
- command: "lattices",
39
+ command: "lattices start",
40
40
  in: directory
41
41
  )
42
42
  }
@@ -34,7 +34,7 @@ struct KeyRecorderView: View {
34
34
  .frame(minWidth: 80, alignment: .leading)
35
35
  } else if let binding = binding {
36
36
  HStack(spacing: 4) {
37
- ForEach(binding.displayParts, id: \.self) { part in
37
+ ForEach(binding.compactDisplayParts, id: \.self) { part in
38
38
  keyBadge(part)
39
39
  }
40
40
  }
@@ -490,7 +490,7 @@ struct MainView: View {
490
490
  .buttonStyle(.plain)
491
491
  }
492
492
 
493
- Text("Initialize runs lattices init && lattices in the folder you choose.")
493
+ Text("Initialize runs lattices init && lattices start in the folder you choose.")
494
494
  .font(Typo.mono(9))
495
495
  .foregroundColor(Palette.textMuted)
496
496
  .multilineTextAlignment(.center)
@@ -10,6 +10,7 @@ class Preferences: ObservableObject {
10
10
  static let shared = Preferences()
11
11
 
12
12
  private enum CompanionDefaultsKey {
13
+ static let bridgeEnabled = "companion.bridge.enabled"
13
14
  static let trackpadEnabled = "companion.trackpad.enabled"
14
15
  static let cockpitLayout = "companion.cockpit.layout"
15
16
  }
@@ -30,6 +31,17 @@ class Preferences: ObservableObject {
30
31
  didSet { UserDefaults.standard.set(dragSnapEnabled, forKey: "windowSnap.enabled") }
31
32
  }
32
33
 
34
+ @Published var companionBridgeEnabled: Bool {
35
+ didSet {
36
+ UserDefaults.standard.set(companionBridgeEnabled, forKey: CompanionDefaultsKey.bridgeEnabled)
37
+ if companionBridgeEnabled {
38
+ LatticesCompanionBridgeServer.shared.start()
39
+ } else {
40
+ LatticesCompanionBridgeServer.shared.stop()
41
+ }
42
+ }
43
+ }
44
+
33
45
  @Published var companionTrackpadEnabled: Bool {
34
46
  didSet { UserDefaults.standard.set(companionTrackpadEnabled, forKey: CompanionDefaultsKey.trackpadEnabled) }
35
47
  }
@@ -37,11 +49,14 @@ class Preferences: ObservableObject {
37
49
  @Published var companionCockpitLayout: LatticesCompanionCockpitLayout {
38
50
  didSet { persistCompanionCockpitLayout() }
39
51
  }
40
-
41
52
  @Published var mouseGesturesEnabled: Bool {
42
53
  didSet { UserDefaults.standard.set(mouseGesturesEnabled, forKey: "mouseGestures.enabled") }
43
54
  }
44
55
 
56
+ @Published var keyboardRemapsEnabled: Bool {
57
+ didSet { UserDefaults.standard.set(keyboardRemapsEnabled, forKey: "keyboardRemaps.enabled") }
58
+ }
59
+
45
60
  // MARK: - AI / Claude
46
61
 
47
62
  @Published var claudePath: String {
@@ -156,19 +171,30 @@ class Preferences: ObservableObject {
156
171
  self.dragSnapEnabled = true
157
172
  }
158
173
 
174
+ if UserDefaults.standard.object(forKey: CompanionDefaultsKey.bridgeEnabled) != nil {
175
+ self.companionBridgeEnabled = UserDefaults.standard.bool(forKey: CompanionDefaultsKey.bridgeEnabled)
176
+ } else {
177
+ self.companionBridgeEnabled = false
178
+ }
179
+
159
180
  if UserDefaults.standard.object(forKey: CompanionDefaultsKey.trackpadEnabled) != nil {
160
181
  self.companionTrackpadEnabled = UserDefaults.standard.bool(forKey: CompanionDefaultsKey.trackpadEnabled)
161
182
  } else {
162
- self.companionTrackpadEnabled = true
183
+ self.companionTrackpadEnabled = false
163
184
  }
164
185
 
165
186
  self.companionCockpitLayout = Self.loadCompanionCockpitLayout()
166
-
167
187
  if UserDefaults.standard.object(forKey: "mouseGestures.enabled") != nil {
168
188
  self.mouseGesturesEnabled = UserDefaults.standard.bool(forKey: "mouseGestures.enabled")
169
189
  } else {
170
190
  self.mouseGesturesEnabled = false
171
191
  }
192
+
193
+ if UserDefaults.standard.object(forKey: "keyboardRemaps.enabled") != nil {
194
+ self.keyboardRemapsEnabled = UserDefaults.standard.bool(forKey: "keyboardRemaps.enabled")
195
+ } else {
196
+ self.keyboardRemapsEnabled = true
197
+ }
172
198
  // AI / Claude
173
199
  self.claudePath = UserDefaults.standard.string(forKey: "claude.path") ?? ""
174
200
  self.advisorModel = UserDefaults.standard.string(forKey: "claude.advisorModel") ?? "haiku"