@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,171 +0,0 @@
1
- import SwiftUI
2
-
3
- // MARK: - Navigation Pages
4
-
5
- enum AppPage: String, CaseIterable {
6
- case home
7
- case screenMap
8
- case desktopInventory
9
- case pi
10
- case settings
11
- case companionSettings
12
- case docs
13
-
14
- var label: String {
15
- switch self {
16
- case .home: return "Home"
17
- case .screenMap: return "Layout"
18
- case .desktopInventory: return "Desktop Inventory"
19
- case .pi: return "Assistant"
20
- case .settings: return "Settings"
21
- case .companionSettings:return "Settings"
22
- case .docs: return "Docs"
23
- }
24
- }
25
-
26
- var icon: String {
27
- switch self {
28
- case .home: return "house"
29
- case .screenMap: return "rectangle.3.group"
30
- case .desktopInventory: return "macwindow.on.rectangle"
31
- case .pi: return "bubble.left.and.bubble.right"
32
- case .settings: return "gearshape"
33
- case .companionSettings:return "ipad.and.iphone"
34
- case .docs: return "book"
35
- }
36
- }
37
-
38
- /// Pages shown as primary tabs in the unified window
39
- static var primaryTabs: [AppPage] { [.home, .screenMap, .desktopInventory, .pi] }
40
- }
41
-
42
- // MARK: - App Shell View
43
-
44
- struct AppShellView: View {
45
- @ObservedObject var controller: ScreenMapController
46
- @ObservedObject var windowController = ScreenMapWindowController.shared
47
- @StateObject private var commandState = CommandModeState()
48
-
49
- var body: some View {
50
- VStack(spacing: 0) {
51
- // Tab bar (only on primary pages)
52
- if AppPage.primaryTabs.contains(windowController.activePage) {
53
- tabBar
54
- Rectangle().fill(Palette.border).frame(height: 0.5)
55
- }
56
-
57
- contentArea
58
- }
59
- .background(Palette.bg)
60
- .onAppear {
61
- commandState.onDismiss = { windowController.activePage = .home }
62
- syncPageState(windowController.activePage)
63
- }
64
- .onChange(of: windowController.activePage) { page in
65
- syncPageState(page)
66
- clearRelevantDismissals(for: page)
67
- }
68
- }
69
-
70
- /// Entering a feature page clears its capability snooze — the user is
71
- /// telling us they want this to work, so the banner can resurface.
72
- private func clearRelevantDismissals(for page: AppPage) {
73
- let prefs = Preferences.shared
74
- switch page {
75
- case .screenMap:
76
- prefs.clearDismissal(Capability.windowControl.rawValue)
77
- case .desktopInventory:
78
- prefs.clearDismissal(Capability.screenSearch.rawValue)
79
- default:
80
- break
81
- }
82
- }
83
-
84
- // MARK: - Tab Bar
85
-
86
- private var tabBar: some View {
87
- HStack(spacing: 0) {
88
- ForEach(AppPage.primaryTabs, id: \.rawValue) { tab in
89
- tabButton(tab)
90
- }
91
-
92
- Spacer()
93
- }
94
- .padding(.horizontal, 12)
95
- .padding(.top, 8)
96
- .padding(.bottom, 4)
97
- }
98
-
99
- private func tabButton(_ tab: AppPage) -> some View {
100
- let isActive = windowController.activePage == tab
101
-
102
- return Button {
103
- windowController.activePage = tab
104
- if tab == .screenMap { controller.enter() }
105
- if tab == .desktopInventory { commandState.enter() }
106
- } label: {
107
- HStack(spacing: 5) {
108
- Image(systemName: tab.icon)
109
- .font(.system(size: 10))
110
- Text(tab.label)
111
- .font(Typo.monoBold(11))
112
- }
113
- .foregroundColor(isActive ? Palette.text : Palette.textMuted)
114
- .padding(.horizontal, 12)
115
- .padding(.vertical, 6)
116
- .contentShape(Rectangle())
117
- .background(
118
- RoundedRectangle(cornerRadius: 6)
119
- .fill(isActive ? Palette.surfaceHov : Color.clear)
120
- )
121
- }
122
- .buttonStyle(.plain)
123
- }
124
-
125
- // MARK: - Content Area
126
-
127
- @ViewBuilder
128
- private var contentArea: some View {
129
- switch windowController.activePage {
130
- case .home:
131
- HomeDashboardView(onNavigate: { page in
132
- windowController.activePage = page
133
- if page == .screenMap { controller.enter() }
134
- if page == .desktopInventory { commandState.enter() }
135
- })
136
- case .screenMap:
137
- ScreenMapView(controller: controller, onNavigate: { page in
138
- windowController.activePage = page
139
- })
140
- case .desktopInventory:
141
- CommandModeView(state: commandState, presentation: .embedded)
142
- case .pi:
143
- PiWorkspaceView()
144
- case .settings:
145
- SettingsContentView(
146
- prefs: Preferences.shared,
147
- scanner: ProjectScanner.shared,
148
- onBack: { windowController.activePage = .screenMap; controller.enter() }
149
- )
150
- case .companionSettings:
151
- SettingsContentView(
152
- page: .companionSettings,
153
- prefs: Preferences.shared,
154
- scanner: ProjectScanner.shared,
155
- onBack: { windowController.activePage = .screenMap; controller.enter() }
156
- )
157
- case .docs:
158
- SettingsContentView(
159
- page: .docs,
160
- prefs: Preferences.shared,
161
- scanner: ProjectScanner.shared,
162
- onBack: { windowController.activePage = .screenMap; controller.enter() }
163
- )
164
- }
165
- }
166
-
167
- private func syncPageState(_ page: AppPage) {
168
- if page == .screenMap { controller.enter() }
169
- if page == .desktopInventory { commandState.enter() }
170
- }
171
- }
@@ -1,305 +0,0 @@
1
- import AppKit
2
- import Combine
3
- import Foundation
4
- import SwiftUI
5
-
6
- struct LatticesUpdateInfo: Equatable {
7
- let version: String
8
- let downloadURL: URL
9
- let releaseNotes: String
10
- let publishedAt: Date
11
- let htmlURL: URL
12
- }
13
-
14
- @MainActor
15
- final class AppUpdater: ObservableObject {
16
- static let shared = AppUpdater()
17
-
18
- @Published private(set) var isUpdating = false
19
- @Published private(set) var statusMessage: String?
20
- @Published private(set) var availableUpdate: LatticesUpdateInfo?
21
- @Published private(set) var isChecking = false
22
- @Published private(set) var lastChecked: Date?
23
- @Published private(set) var lastError: String?
24
-
25
- @AppStorage("appUpdater.autoCheck") var autoCheckEnabled = true
26
- @AppStorage("appUpdater.lastCheckTime") private var lastCheckTimeInterval: Double = 0
27
- @AppStorage("appUpdater.skippedVersion") private var skippedVersion = ""
28
-
29
- private let checkInterval: TimeInterval = 24 * 60 * 60
30
-
31
- private init() {}
32
-
33
- var currentVersion: String { LatticesRuntime.appVersion }
34
- var currentDisplayVersion: String { LatticesRuntime.appDisplayVersion }
35
-
36
- var canUpdate: Bool {
37
- LatticesRuntime.bunPath != nil && LatticesRuntime.appHelperScriptPath != nil
38
- }
39
-
40
- var unavailableReason: String? {
41
- if LatticesRuntime.bunPath == nil {
42
- return "Install Bun to enable in-app updates."
43
- }
44
- if LatticesRuntime.appHelperScriptPath == nil {
45
- return "Launch Lattices via `lattices app` so the updater can find the CLI bundle."
46
- }
47
- return nil
48
- }
49
-
50
- func checkIfNeeded() async {
51
- guard autoCheckEnabled else { return }
52
-
53
- let now = Date()
54
- let lastCheck = Date(timeIntervalSince1970: lastCheckTimeInterval)
55
- if now.timeIntervalSince(lastCheck) < checkInterval { return }
56
-
57
- await check()
58
- }
59
-
60
- func check() async {
61
- guard !isChecking else { return }
62
-
63
- isChecking = true
64
- lastError = nil
65
-
66
- defer {
67
- isChecking = false
68
- lastChecked = Date()
69
- lastCheckTimeInterval = Date().timeIntervalSince1970
70
- }
71
-
72
- do {
73
- let release = try await fetchLatestRelease()
74
- guard let update = parseRelease(release), isNewerVersion(update.version) else {
75
- availableUpdate = nil
76
- return
77
- }
78
-
79
- if update.version == skippedVersion {
80
- availableUpdate = nil
81
- return
82
- }
83
-
84
- availableUpdate = update
85
- } catch UpdateCheckError.noRelease {
86
- availableUpdate = nil
87
- } catch {
88
- lastError = error.localizedDescription
89
- }
90
- }
91
-
92
- func skipCurrentUpdate() {
93
- guard let update = availableUpdate else { return }
94
- skippedVersion = update.version
95
- availableUpdate = nil
96
- }
97
-
98
- func viewCurrentRelease() {
99
- if let update = availableUpdate {
100
- NSWorkspace.shared.open(update.htmlURL)
101
- } else if let url = URL(string: "https://github.com/arach/lattices/releases/latest") {
102
- NSWorkspace.shared.open(url)
103
- }
104
- }
105
-
106
- func promptForUpdate() {
107
- guard canUpdate else {
108
- presentAlert(
109
- title: "Update Unavailable",
110
- message: unavailableReason ?? "Lattices could not locate its updater."
111
- )
112
- return
113
- }
114
-
115
- guard availableUpdate != nil else {
116
- Task {
117
- await check()
118
- if availableUpdate != nil {
119
- presentUpdateConfirmation()
120
- } else if let error = lastError {
121
- presentAlert(
122
- title: "Could Not Check for Updates",
123
- message: error
124
- )
125
- } else {
126
- presentAlert(
127
- title: "Lattices Is Up to Date",
128
- message: "You’re running \(currentDisplayVersion), which is the latest available build for this install."
129
- )
130
- }
131
- }
132
- return
133
- }
134
-
135
- presentUpdateConfirmation()
136
- }
137
-
138
- private func presentUpdateConfirmation() {
139
- let alert = NSAlert()
140
- alert.alertStyle = .informational
141
- if let update = availableUpdate {
142
- alert.messageText = "Update Lattices?"
143
- alert.informativeText = """
144
- Current version: \(currentDisplayVersion)
145
- New version: \(update.version)
146
-
147
- Lattices will download the signed release, quit briefly, replace the app, and relaunch when the update is ready.
148
- """
149
- } else {
150
- alert.messageText = "Check and update Lattices?"
151
- alert.informativeText = """
152
- Current version: \(currentDisplayVersion)
153
- New version: latest published release
154
-
155
- Lattices will download the signed release, quit briefly, replace the app, and relaunch when the update is ready.
156
- """
157
- }
158
- alert.addButton(withTitle: availableUpdate == nil ? "Check & Update" : "Update")
159
- alert.addButton(withTitle: "Cancel")
160
-
161
- guard alert.runModal() == .alertFirstButtonReturn else { return }
162
- startDetachedUpdate()
163
- }
164
-
165
- private func startDetachedUpdate() {
166
- guard !isUpdating else { return }
167
- guard let bunPath = LatticesRuntime.bunPath,
168
- let scriptPath = LatticesRuntime.appHelperScriptPath else {
169
- presentAlert(
170
- title: "Update Unavailable",
171
- message: unavailableReason ?? "Lattices could not locate its updater."
172
- )
173
- return
174
- }
175
-
176
- let proc = Process()
177
- proc.executableURL = URL(fileURLWithPath: bunPath)
178
- proc.arguments = [scriptPath, "update", "--detach", "--launch"]
179
- if let cliRoot = LatticesRuntime.cliRoot {
180
- proc.currentDirectoryURL = URL(fileURLWithPath: cliRoot)
181
- }
182
-
183
- var env = ProcessInfo.processInfo.environment
184
- env.removeValue(forKey: "CLAUDECODE")
185
- proc.environment = env
186
-
187
- do {
188
- try proc.run()
189
- isUpdating = true
190
- if let update = availableUpdate {
191
- statusMessage = "Preparing Lattices \(update.version). The app will relaunch when the update is ready."
192
- } else {
193
- statusMessage = "Preparing the latest Lattices release. The app will relaunch when the update is ready."
194
- }
195
- } catch {
196
- presentAlert(
197
- title: "Update Failed",
198
- message: "Lattices could not start the updater.\n\n\(error.localizedDescription)"
199
- )
200
- }
201
- }
202
-
203
- private func presentAlert(title: String, message: String) {
204
- let alert = NSAlert()
205
- alert.alertStyle = .warning
206
- alert.messageText = title
207
- alert.informativeText = message
208
- alert.addButton(withTitle: "OK")
209
- alert.runModal()
210
- }
211
-
212
- private func fetchLatestRelease() async throws -> GitHubRelease {
213
- let url = URL(string: "https://api.github.com/repos/arach/lattices/releases/latest")!
214
- var request = URLRequest(url: url)
215
- request.setValue("application/vnd.github+json", forHTTPHeaderField: "Accept")
216
- request.setValue("Lattices/\(currentVersion)", forHTTPHeaderField: "User-Agent")
217
-
218
- let (data, response) = try await URLSession.shared.data(for: request)
219
- guard let http = response as? HTTPURLResponse else {
220
- throw UpdateCheckError.invalidResponse
221
- }
222
-
223
- switch http.statusCode {
224
- case 200:
225
- let decoder = JSONDecoder()
226
- decoder.keyDecodingStrategy = .convertFromSnakeCase
227
- decoder.dateDecodingStrategy = .iso8601
228
- return try decoder.decode(GitHubRelease.self, from: data)
229
- case 404:
230
- throw UpdateCheckError.noRelease
231
- case 403:
232
- throw UpdateCheckError.rateLimited
233
- default:
234
- throw UpdateCheckError.httpError(http.statusCode)
235
- }
236
- }
237
-
238
- private func parseRelease(_ release: GitHubRelease) -> LatticesUpdateInfo? {
239
- guard !release.draft, !release.prerelease else { return nil }
240
-
241
- let asset = release.assets.first { asset in
242
- asset.name == "Lattices.dmg" ||
243
- (asset.name.hasPrefix("Lattices") && asset.name.hasSuffix(".dmg"))
244
- }
245
-
246
- guard let asset,
247
- let downloadURL = URL(string: asset.browserDownloadUrl),
248
- let htmlURL = URL(string: release.htmlUrl) else {
249
- return nil
250
- }
251
-
252
- let version = release.tagName.hasPrefix("v")
253
- ? String(release.tagName.dropFirst())
254
- : release.tagName
255
-
256
- return LatticesUpdateInfo(
257
- version: version,
258
- downloadURL: downloadURL,
259
- releaseNotes: release.body ?? "",
260
- publishedAt: release.publishedAt,
261
- htmlURL: htmlURL
262
- )
263
- }
264
-
265
- private func isNewerVersion(_ remoteVersion: String) -> Bool {
266
- guard currentVersion != "unknown" else { return false }
267
- return remoteVersion.compare(currentVersion, options: .numeric) == .orderedDescending
268
- }
269
- }
270
-
271
- private struct GitHubRelease: Decodable {
272
- let tagName: String
273
- let name: String
274
- let body: String?
275
- let htmlUrl: String
276
- let publishedAt: Date
277
- let assets: [GitHubAsset]
278
- let prerelease: Bool
279
- let draft: Bool
280
- }
281
-
282
- private struct GitHubAsset: Decodable {
283
- let name: String
284
- let browserDownloadUrl: String
285
- }
286
-
287
- private enum UpdateCheckError: LocalizedError {
288
- case invalidResponse
289
- case noRelease
290
- case rateLimited
291
- case httpError(Int)
292
-
293
- var errorDescription: String? {
294
- switch self {
295
- case .invalidResponse:
296
- return "Invalid response from GitHub."
297
- case .noRelease:
298
- return "No published release found."
299
- case .rateLimited:
300
- return "GitHub rate limited the update check."
301
- case .httpError(let code):
302
- return "GitHub returned HTTP \(code)."
303
- }
304
- }
305
- }
@@ -1,50 +0,0 @@
1
- import AppKit
2
-
3
- enum CliActionLauncher {
4
- private static var defaultDirectory: String {
5
- let root = Preferences.shared.scanRoot
6
- return root.isEmpty ? NSHomeDirectory() : root
7
- }
8
-
9
- private static func chooseProjectDirectory(message: String, prompt: String) -> String? {
10
- let panel = NSOpenPanel()
11
- panel.message = message
12
- panel.prompt = prompt
13
- panel.canChooseFiles = false
14
- panel.canChooseDirectories = true
15
- panel.allowsMultipleSelection = false
16
- panel.directoryURL = URL(fileURLWithPath: defaultDirectory)
17
- return panel.runModal() == .OK ? panel.url?.path : nil
18
- }
19
-
20
- static func initializeProjectInTerminal() {
21
- guard let directory = chooseProjectDirectory(
22
- message: "Choose a project folder to initialize with Lattices.",
23
- prompt: "Initialize"
24
- ) else { return }
25
-
26
- Preferences.shared.terminal.launch(
27
- command: "lattices init && lattices start",
28
- in: directory
29
- )
30
- }
31
-
32
- static func launchProjectInTerminal() {
33
- guard let directory = chooseProjectDirectory(
34
- message: "Choose a project folder to launch with Lattices.",
35
- prompt: "Launch"
36
- ) else { return }
37
-
38
- Preferences.shared.terminal.launch(
39
- command: "lattices start",
40
- in: directory
41
- )
42
- }
43
-
44
- static func installTmuxInTerminal() {
45
- Preferences.shared.terminal.launch(
46
- command: "brew install tmux",
47
- in: defaultDirectory
48
- )
49
- }
50
- }
@@ -1,133 +0,0 @@
1
- import SwiftUI
2
-
3
- struct HomeDashboardView: View {
4
- var onNavigate: ((AppPage) -> Void)? = nil
5
-
6
- @ObservedObject private var scanner = ProjectScanner.shared
7
- @ObservedObject private var piSession = PiChatSession.shared
8
-
9
- var body: some View {
10
- VStack(spacing: 0) {
11
- hero
12
-
13
- Rectangle()
14
- .fill(Palette.border)
15
- .frame(height: 0.5)
16
-
17
- MainView(scanner: scanner, layout: .embedded)
18
- }
19
- .background(Palette.bg)
20
- .onAppear {
21
- piSession.refreshBinaryAvailability()
22
- }
23
- }
24
-
25
- private var hero: some View {
26
- VStack(alignment: .leading, spacing: 18) {
27
- HStack(alignment: .top) {
28
- VStack(alignment: .leading, spacing: 8) {
29
- Text("Lattices Home")
30
- .font(Typo.heading(18))
31
- .foregroundColor(Palette.text)
32
-
33
- Text("Workspace status, project launch, layout, search, and chat in one place.")
34
- .font(Typo.mono(11))
35
- .foregroundColor(Palette.textDim)
36
- .fixedSize(horizontal: false, vertical: true)
37
- }
38
-
39
- Spacer()
40
- }
41
-
42
- HStack(spacing: 10) {
43
- homeActionCard(
44
- title: "Layout",
45
- subtitle: "Arrange windows",
46
- icon: "rectangle.3.group",
47
- tint: Palette.running
48
- ) {
49
- onNavigate?(.screenMap)
50
- }
51
-
52
- homeActionCard(
53
- title: "Search",
54
- subtitle: "Find workspace context",
55
- icon: "magnifyingglass",
56
- tint: Palette.detach
57
- ) {
58
- onNavigate?(.desktopInventory)
59
- }
60
-
61
- homeActionCard(
62
- title: "Chat",
63
- subtitle: piSession.hasPiBinary
64
- ? (piSession.needsProviderSetup || piSession.isAuthenticating
65
- ? piSession.setupStatusSummary
66
- : "Standalone conversation surface")
67
- : "Install Pi to enable the assistant",
68
- icon: "bubble.left.and.bubble.right",
69
- tint: piSession.hasPiBinary ? Palette.text : Palette.kill
70
- ) {
71
- onNavigate?(.pi)
72
- }
73
- }
74
- }
75
- .padding(.horizontal, 16)
76
- .padding(.vertical, 16)
77
- .background(
78
- LinearGradient(
79
- colors: [
80
- Palette.running.opacity(0.08),
81
- Color.black.opacity(0.18),
82
- ],
83
- startPoint: .topLeading,
84
- endPoint: .bottomTrailing
85
- )
86
- )
87
- }
88
-
89
- private func homeActionCard(
90
- title: String,
91
- subtitle: String,
92
- icon: String,
93
- tint: Color,
94
- action: @escaping () -> Void
95
- ) -> some View {
96
- Button(action: action) {
97
- VStack(alignment: .leading, spacing: 10) {
98
- HStack {
99
- Image(systemName: icon)
100
- .font(.system(size: 12, weight: .semibold))
101
- .foregroundColor(tint)
102
-
103
- Spacer()
104
-
105
- Circle()
106
- .fill(tint.opacity(0.85))
107
- .frame(width: 6, height: 6)
108
- }
109
-
110
- Text(title)
111
- .font(Typo.monoBold(12))
112
- .foregroundColor(Palette.text)
113
-
114
- Text(subtitle)
115
- .font(Typo.mono(10))
116
- .foregroundColor(Palette.textMuted)
117
- .multilineTextAlignment(.leading)
118
- .fixedSize(horizontal: false, vertical: true)
119
- }
120
- .frame(maxWidth: .infinity, alignment: .leading)
121
- .padding(12)
122
- .background(
123
- RoundedRectangle(cornerRadius: 8)
124
- .fill(Palette.surface.opacity(0.7))
125
- .overlay(
126
- RoundedRectangle(cornerRadius: 8)
127
- .strokeBorder(tint.opacity(0.18), lineWidth: 0.5)
128
- )
129
- )
130
- }
131
- .buttonStyle(.plain)
132
- }
133
- }