@lattices/cli 0.4.13 → 0.5.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 (180) hide show
  1. package/README.md +5 -7
  2. package/apps/mac/Info.plist +2 -2
  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 +191 -63
  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/reference/dewey.config.ts +2 -2
  19. package/docs/release.md +171 -0
  20. package/docs/repo-structure.md +4 -5
  21. package/docs/voice.md +11 -27
  22. package/package.json +9 -10
  23. package/apps/mac/Package.swift +0 -27
  24. package/apps/mac/Sources/AppShell/App.swift +0 -26
  25. package/apps/mac/Sources/AppShell/AppActivationCoordinator.swift +0 -27
  26. package/apps/mac/Sources/AppShell/AppDelegate.swift +0 -189
  27. package/apps/mac/Sources/AppShell/AppServicesBootstrap.swift +0 -25
  28. package/apps/mac/Sources/AppShell/AppShellView.swift +0 -171
  29. package/apps/mac/Sources/AppShell/AppUpdater.swift +0 -305
  30. package/apps/mac/Sources/AppShell/CliActionLauncher.swift +0 -50
  31. package/apps/mac/Sources/AppShell/HomeDashboardView.swift +0 -133
  32. package/apps/mac/Sources/AppShell/HotkeyBootstrap.swift +0 -87
  33. package/apps/mac/Sources/AppShell/KeyRecorderView.swift +0 -210
  34. package/apps/mac/Sources/AppShell/LatticesRuntime.swift +0 -104
  35. package/apps/mac/Sources/AppShell/MainView.swift +0 -847
  36. package/apps/mac/Sources/AppShell/MainWindow.swift +0 -83
  37. package/apps/mac/Sources/AppShell/MenuBarController.swift +0 -177
  38. package/apps/mac/Sources/AppShell/OnboardingView.swift +0 -483
  39. package/apps/mac/Sources/AppShell/PermissionsAssistantView.swift +0 -366
  40. package/apps/mac/Sources/AppShell/PermissionsAssistantWindow.swift +0 -70
  41. package/apps/mac/Sources/AppShell/Preferences.swift +0 -297
  42. package/apps/mac/Sources/AppShell/SettingsView.swift +0 -3163
  43. package/apps/mac/Sources/AppShell/SettingsWindow.swift +0 -34
  44. package/apps/mac/Sources/AppShell/WorkspaceInspectorPresenter.swift +0 -13
  45. package/apps/mac/Sources/Core/Actions/HotkeyManager.swift +0 -256
  46. package/apps/mac/Sources/Core/Actions/HotkeyStore.swift +0 -399
  47. package/apps/mac/Sources/Core/Actions/IntentEngine.swift +0 -988
  48. package/apps/mac/Sources/Core/Actions/IntentSchema.swift +0 -94
  49. package/apps/mac/Sources/Core/Actions/Intents/CreateLayerIntent.swift +0 -54
  50. package/apps/mac/Sources/Core/Actions/Intents/DistributeIntent.swift +0 -56
  51. package/apps/mac/Sources/Core/Actions/Intents/FocusIntent.swift +0 -69
  52. package/apps/mac/Sources/Core/Actions/Intents/HelpIntent.swift +0 -41
  53. package/apps/mac/Sources/Core/Actions/Intents/KillIntent.swift +0 -47
  54. package/apps/mac/Sources/Core/Actions/Intents/LatticeIntent.swift +0 -53
  55. package/apps/mac/Sources/Core/Actions/Intents/LaunchIntent.swift +0 -67
  56. package/apps/mac/Sources/Core/Actions/Intents/ListSessionsIntent.swift +0 -32
  57. package/apps/mac/Sources/Core/Actions/Intents/ListWindowsIntent.swift +0 -30
  58. package/apps/mac/Sources/Core/Actions/Intents/ScanIntent.swift +0 -52
  59. package/apps/mac/Sources/Core/Actions/Intents/SearchIntent.swift +0 -190
  60. package/apps/mac/Sources/Core/Actions/Intents/SwitchLayerIntent.swift +0 -50
  61. package/apps/mac/Sources/Core/Actions/Intents/TileIntent.swift +0 -61
  62. package/apps/mac/Sources/Core/Actions/PaletteCommand.swift +0 -439
  63. package/apps/mac/Sources/Core/Actions/VoiceIntentResolver.swift +0 -713
  64. package/apps/mac/Sources/Core/Companion/CompanionActivityLog.swift +0 -70
  65. package/apps/mac/Sources/Core/Companion/CompanionKeyboardController.swift +0 -141
  66. package/apps/mac/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +0 -454
  67. package/apps/mac/Sources/Core/Companion/LatticesCompanionCockpit.swift +0 -555
  68. package/apps/mac/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +0 -629
  69. package/apps/mac/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +0 -204
  70. package/apps/mac/Sources/Core/Companion/LatticesDeckHost.swift +0 -1463
  71. package/apps/mac/Sources/Core/Daemon/DaemonProtocol.swift +0 -114
  72. package/apps/mac/Sources/Core/Daemon/DaemonServer.swift +0 -427
  73. package/apps/mac/Sources/Core/Daemon/LatticesApi.swift +0 -2965
  74. package/apps/mac/Sources/Core/Desktop/AccessibilityTextExtractor.swift +0 -111
  75. package/apps/mac/Sources/Core/Desktop/AppTypeClassifier.swift +0 -106
  76. package/apps/mac/Sources/Core/Desktop/DesktopModel.swift +0 -331
  77. package/apps/mac/Sources/Core/Desktop/DesktopModelTypes.swift +0 -73
  78. package/apps/mac/Sources/Core/Desktop/InventoryManager.swift +0 -35
  79. package/apps/mac/Sources/Core/Desktop/InventoryPath.swift +0 -43
  80. package/apps/mac/Sources/Core/Desktop/MouseFinder.swift +0 -527
  81. package/apps/mac/Sources/Core/Desktop/OcrModel.swift +0 -467
  82. package/apps/mac/Sources/Core/Desktop/OcrStore.swift +0 -329
  83. package/apps/mac/Sources/Core/Desktop/PlacementSpec.swift +0 -195
  84. package/apps/mac/Sources/Core/Desktop/SessionWindowLocator.swift +0 -139
  85. package/apps/mac/Sources/Core/Desktop/TilePickerView.swift +0 -209
  86. package/apps/mac/Sources/Core/Desktop/WindowCapture.swift +0 -33
  87. package/apps/mac/Sources/Core/Desktop/WindowDragSnapController.swift +0 -429
  88. package/apps/mac/Sources/Core/Desktop/WindowPreviewCard.swift +0 -100
  89. package/apps/mac/Sources/Core/Desktop/WindowPreviewStore.swift +0 -112
  90. package/apps/mac/Sources/Core/Desktop/WindowSelectionStore.swift +0 -76
  91. package/apps/mac/Sources/Core/Desktop/WindowTiler.swift +0 -2222
  92. package/apps/mac/Sources/Core/Input/EventTapBreaker.swift +0 -124
  93. package/apps/mac/Sources/Core/Input/EventTapThread.swift +0 -54
  94. package/apps/mac/Sources/Core/Input/InputCaptureResetCenter.swift +0 -20
  95. package/apps/mac/Sources/Core/Input/KeyboardRemapConfig.swift +0 -69
  96. package/apps/mac/Sources/Core/Input/KeyboardRemapController.swift +0 -346
  97. package/apps/mac/Sources/Core/Input/KeyboardRemapStore.swift +0 -141
  98. package/apps/mac/Sources/Core/Input/MouseGestureConfig.swift +0 -499
  99. package/apps/mac/Sources/Core/Input/MouseGestureController.swift +0 -2271
  100. package/apps/mac/Sources/Core/Input/MouseInputDeviceStore.swift +0 -98
  101. package/apps/mac/Sources/Core/Input/MouseInputEventViewer.swift +0 -272
  102. package/apps/mac/Sources/Core/Input/MouseShortcutStore.swift +0 -170
  103. package/apps/mac/Sources/Core/Input/SecureEventInputMonitor.swift +0 -39
  104. package/apps/mac/Sources/Core/Input/ShapeRecognizer.swift +0 -624
  105. package/apps/mac/Sources/Core/Input/TapBudgetMeter.swift +0 -56
  106. package/apps/mac/Sources/Core/Overlays/AppWindowShell.swift +0 -63
  107. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeState.swift +0 -1566
  108. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeView.swift +0 -1927
  109. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeWindow.swift +0 -196
  110. package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteView.swift +0 -307
  111. package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +0 -67
  112. package/apps/mac/Sources/Core/Overlays/HUD/CheatSheetHUD.swift +0 -576
  113. package/apps/mac/Sources/Core/Overlays/HUD/HUDBottomBar.swift +0 -279
  114. package/apps/mac/Sources/Core/Overlays/HUD/HUDController.swift +0 -1158
  115. package/apps/mac/Sources/Core/Overlays/HUD/HUDLeftBar.swift +0 -849
  116. package/apps/mac/Sources/Core/Overlays/HUD/HUDMinimap.swift +0 -179
  117. package/apps/mac/Sources/Core/Overlays/HUD/HUDRightBar.swift +0 -596
  118. package/apps/mac/Sources/Core/Overlays/HUD/HUDState.swift +0 -367
  119. package/apps/mac/Sources/Core/Overlays/HUD/HUDTopBar.swift +0 -243
  120. package/apps/mac/Sources/Core/Overlays/HUD/LauncherHUD.swift +0 -334
  121. package/apps/mac/Sources/Core/Overlays/HUD/LayerBezel.swift +0 -203
  122. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchState.swift +0 -280
  123. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchView.swift +0 -422
  124. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchWindow.swift +0 -94
  125. package/apps/mac/Sources/Core/Overlays/OverlayPanelShell.swift +0 -241
  126. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapState.swift +0 -3135
  127. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapView.swift +0 -3977
  128. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapWindowController.swift +0 -119
  129. package/apps/mac/Sources/Core/Overlays/ScreenOverlayCanvasController.swift +0 -1217
  130. package/apps/mac/Sources/Core/Overlays/Voice/VoiceCommandWindow.swift +0 -1575
  131. package/apps/mac/Sources/Core/Pi/PiAuthNextStepCard.swift +0 -148
  132. package/apps/mac/Sources/Core/Pi/PiAuthPromptCard.swift +0 -90
  133. package/apps/mac/Sources/Core/Pi/PiChatDock.swift +0 -564
  134. package/apps/mac/Sources/Core/Pi/PiChatSession.swift +0 -1948
  135. package/apps/mac/Sources/Core/Pi/PiInstallCallout.swift +0 -86
  136. package/apps/mac/Sources/Core/Pi/PiProviderSetupCallout.swift +0 -99
  137. package/apps/mac/Sources/Core/Pi/PiWorkspaceView.swift +0 -510
  138. package/apps/mac/Sources/Core/System/Capability.swift +0 -79
  139. package/apps/mac/Sources/Core/System/DiagnosticLog.swift +0 -373
  140. package/apps/mac/Sources/Core/System/EventBus.swift +0 -31
  141. package/apps/mac/Sources/Core/System/PermissionChecker.swift +0 -224
  142. package/apps/mac/Sources/Core/System/ProcessModel.swift +0 -199
  143. package/apps/mac/Sources/Core/System/ProcessQuery.swift +0 -151
  144. package/apps/mac/Sources/Core/System/SystemTelemetryMonitor.swift +0 -273
  145. package/apps/mac/Sources/Core/Voice/AdvisorLearningStore.swift +0 -90
  146. package/apps/mac/Sources/Core/Voice/AgentSession.swift +0 -377
  147. package/apps/mac/Sources/Core/Voice/AudioProvider.swift +0 -555
  148. package/apps/mac/Sources/Core/Voice/HandsOffSession.swift +0 -839
  149. package/apps/mac/Sources/Core/Voice/VoiceChatView.swift +0 -192
  150. package/apps/mac/Sources/Core/Voice/VoxClient.swift +0 -454
  151. package/apps/mac/Sources/Core/Workspace/Project.swift +0 -28
  152. package/apps/mac/Sources/Core/Workspace/ProjectScanner.swift +0 -141
  153. package/apps/mac/Sources/Core/Workspace/SessionLayerStore.swift +0 -285
  154. package/apps/mac/Sources/Core/Workspace/SessionManager.swift +0 -75
  155. package/apps/mac/Sources/Core/Workspace/Terminal/Terminal.swift +0 -259
  156. package/apps/mac/Sources/Core/Workspace/Terminal/TerminalQuery.swift +0 -156
  157. package/apps/mac/Sources/Core/Workspace/Terminal/TerminalSynthesizer.swift +0 -200
  158. package/apps/mac/Sources/Core/Workspace/Tmux/TmuxModel.swift +0 -60
  159. package/apps/mac/Sources/Core/Workspace/Tmux/TmuxQuery.swift +0 -105
  160. package/apps/mac/Sources/Core/Workspace/WorkspaceManager.swift +0 -1027
  161. package/apps/mac/Sources/UI/ActionRow.swift +0 -78
  162. package/apps/mac/Sources/UI/OrphanRow.swift +0 -129
  163. package/apps/mac/Sources/UI/ProjectRow.swift +0 -368
  164. package/apps/mac/Sources/UI/TabGroupRow.swift +0 -178
  165. package/apps/mac/Sources/UI/Theme.swift +0 -164
  166. package/apps/mac/Tests/StageDragTests.swift +0 -333
  167. package/apps/mac/Tests/StageJoinTests.swift +0 -313
  168. package/apps/mac/Tests/StageManagerTests.swift +0 -280
  169. package/apps/mac/Tests/StageTileTests.swift +0 -353
  170. package/swift/Package.swift +0 -20
  171. package/swift/Sources/DeckKit/DeckAction.swift +0 -51
  172. package/swift/Sources/DeckKit/DeckBridgeSecurity.swift +0 -152
  173. package/swift/Sources/DeckKit/DeckCockpit.swift +0 -82
  174. package/swift/Sources/DeckKit/DeckHost.swift +0 -7
  175. package/swift/Sources/DeckKit/DeckManifest.swift +0 -145
  176. package/swift/Sources/DeckKit/DeckRuntimeSnapshot.swift +0 -533
  177. package/swift/Sources/DeckKit/DeckTrackpad.swift +0 -63
  178. package/swift/Sources/DeckKit/DeckValue.swift +0 -93
  179. package/swift/Sources/DeckKit/DeckVoiceError.swift +0 -88
  180. 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
- }