@lattices/cli 0.4.7 → 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 (31) 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 +60 -1
  7. package/app/Sources/AppShell/AppShellView.swift +10 -0
  8. package/app/Sources/AppShell/CliActionLauncher.swift +2 -2
  9. package/app/Sources/AppShell/MainView.swift +1 -1
  10. package/app/Sources/AppShell/Preferences.swift +29 -1
  11. package/app/Sources/AppShell/SettingsView.swift +498 -58
  12. package/app/Sources/AppShell/SettingsWindow.swift +4 -0
  13. package/app/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +23 -7
  14. package/app/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +35 -0
  15. package/app/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +1 -1
  16. package/app/Sources/Core/Input/KeyboardRemapConfig.swift +69 -0
  17. package/app/Sources/Core/Input/KeyboardRemapController.swift +184 -0
  18. package/app/Sources/Core/Input/KeyboardRemapStore.swift +84 -0
  19. package/app/Sources/Core/Workspace/SessionManager.swift +1 -1
  20. package/app/Sources/Core/Workspace/WorkspaceManager.swift +3 -3
  21. package/bin/lattices-app.ts +11 -0
  22. package/bin/lattices-dev +11 -0
  23. package/bin/lattices.ts +57 -17
  24. package/docs/app.md +30 -2
  25. package/docs/companion-deck.md +29 -0
  26. package/docs/concepts.md +5 -5
  27. package/docs/config.md +34 -9
  28. package/docs/layers.md +1 -1
  29. package/docs/overview.md +1 -1
  30. package/docs/quickstart.md +4 -4
  31. 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.7</string>
29
+ <string>0.4.8</string>
19
30
  <key>CFBundleShortVersionString</key>
20
- <string>0.4.7</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.7</string>
29
+ <string>0.4.8</string>
19
30
  <key>CFBundleShortVersionString</key>
20
- <string>0.4.7</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() }
@@ -183,7 +186,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
183
186
  ProcessModel.shared.start()
184
187
  LatticesApi.setup()
185
188
  DaemonServer.shared.start()
186
- LatticesCompanionBridgeServer.shared.start()
189
+ if Preferences.shared.companionBridgeEnabled {
190
+ LatticesCompanionBridgeServer.shared.start()
191
+ } else {
192
+ diag.info("CompanionBridge: disabled by preference")
193
+ }
187
194
  AgentPool.shared.start()
188
195
  diag.finish(tBoot)
189
196
 
@@ -203,6 +210,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
203
210
  }
204
211
 
205
212
  func applicationWillTerminate(_ notification: Notification) {
213
+ KeyboardRemapController.shared.stop()
206
214
  LatticesCompanionBridgeServer.shared.stop()
207
215
  DaemonServer.shared.stop()
208
216
  }
@@ -333,6 +341,57 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
333
341
  @objc private func menuSettings() { SettingsWindowController.shared.show() }
334
342
  @objc private func menuQuit() { NSApp.terminate(nil) }
335
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
+
336
395
  private static func showWorkspaceInspector() {
337
396
  guard let entry = DesktopModel.shared.frontmostWindow(),
338
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
  }
@@ -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
  }
@@ -41,6 +53,10 @@ class Preferences: ObservableObject {
41
53
  didSet { UserDefaults.standard.set(mouseGesturesEnabled, forKey: "mouseGestures.enabled") }
42
54
  }
43
55
 
56
+ @Published var keyboardRemapsEnabled: Bool {
57
+ didSet { UserDefaults.standard.set(keyboardRemapsEnabled, forKey: "keyboardRemaps.enabled") }
58
+ }
59
+
44
60
  // MARK: - AI / Claude
45
61
 
46
62
  @Published var claudePath: String {
@@ -155,10 +171,16 @@ class Preferences: ObservableObject {
155
171
  self.dragSnapEnabled = true
156
172
  }
157
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
+
158
180
  if UserDefaults.standard.object(forKey: CompanionDefaultsKey.trackpadEnabled) != nil {
159
181
  self.companionTrackpadEnabled = UserDefaults.standard.bool(forKey: CompanionDefaultsKey.trackpadEnabled)
160
182
  } else {
161
- self.companionTrackpadEnabled = true
183
+ self.companionTrackpadEnabled = false
162
184
  }
163
185
 
164
186
  self.companionCockpitLayout = Self.loadCompanionCockpitLayout()
@@ -167,6 +189,12 @@ class Preferences: ObservableObject {
167
189
  } else {
168
190
  self.mouseGesturesEnabled = false
169
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
+ }
170
198
  // AI / Claude
171
199
  self.claudePath = UserDefaults.standard.string(forKey: "claude.path") ?? ""
172
200
  self.advisorModel = UserDefaults.standard.string(forKey: "claude.advisorModel") ?? "haiku"