@lattices/cli 0.4.2 → 0.4.6
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.
- package/README.md +3 -0
- package/app/Info.plist +2 -2
- package/app/Lattices.app/Contents/Info.plist +2 -2
- package/app/Lattices.app/Contents/MacOS/Lattices +0 -0
- package/app/Package.swift +6 -0
- package/app/Sources/AppShell/App.swift +20 -0
- package/app/Sources/{AppDelegate.swift → AppShell/AppDelegate.swift} +94 -34
- package/app/Sources/{AppShellView.swift → AppShell/AppShellView.swift} +12 -1
- package/app/Sources/AppShell/AppUpdater.swift +92 -0
- package/app/Sources/AppShell/CliActionLauncher.swift +50 -0
- package/app/Sources/{HomeDashboardView.swift → AppShell/HomeDashboardView.swift} +18 -10
- package/app/Sources/AppShell/LatticesRuntime.swift +61 -0
- package/app/Sources/{MainView.swift → AppShell/MainView.swift} +351 -191
- package/app/Sources/{OnboardingView.swift → AppShell/OnboardingView.swift} +30 -16
- package/app/Sources/{Preferences.swift → AppShell/Preferences.swift} +78 -0
- package/app/Sources/{SettingsView.swift → AppShell/SettingsView.swift} +869 -152
- package/app/Sources/{HotkeyStore.swift → Core/Actions/HotkeyStore.swift} +9 -5
- package/app/Sources/{IntentEngine.swift → Core/Actions/IntentEngine.swift} +51 -27
- package/app/Sources/Core/Actions/IntentSchema.swift +94 -0
- package/app/Sources/{Intents → Core/Actions/Intents}/LatticeIntent.swift +0 -25
- package/app/Sources/{PaletteCommand.swift → Core/Actions/PaletteCommand.swift} +26 -6
- package/app/Sources/{VoiceIntentResolver.swift → Core/Actions/VoiceIntentResolver.swift} +46 -4
- package/app/Sources/Core/Companion/CompanionActivityLog.swift +70 -0
- package/app/Sources/Core/Companion/CompanionKeyboardController.swift +141 -0
- package/app/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +438 -0
- package/app/Sources/Core/Companion/LatticesCompanionCockpit.swift +555 -0
- package/app/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +594 -0
- package/app/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +204 -0
- package/app/Sources/Core/Companion/LatticesDeckHost.swift +1463 -0
- package/app/Sources/{LatticesApi.swift → Core/Daemon/LatticesApi.swift} +125 -4
- package/app/Sources/{AppTypeClassifier.swift → Core/Desktop/AppTypeClassifier.swift} +36 -0
- package/app/Sources/{DesktopModel.swift → Core/Desktop/DesktopModel.swift} +6 -8
- package/app/Sources/Core/Desktop/MouseFinder.swift +527 -0
- package/app/Sources/Core/Desktop/SessionWindowLocator.swift +139 -0
- package/app/Sources/Core/Desktop/WindowDragSnapController.swift +628 -0
- package/app/Sources/Core/Desktop/WindowPreviewCard.swift +100 -0
- package/app/Sources/Core/Desktop/WindowPreviewStore.swift +113 -0
- package/app/Sources/Core/Desktop/WindowSelectionStore.swift +76 -0
- package/app/Sources/{WindowTiler.swift → Core/Desktop/WindowTiler.swift} +351 -172
- package/app/Sources/Core/Input/MouseGestureConfig.swift +364 -0
- package/app/Sources/Core/Input/MouseGestureController.swift +1203 -0
- package/app/Sources/Core/Input/MouseInputDeviceStore.swift +98 -0
- package/app/Sources/Core/Input/MouseInputEventViewer.swift +272 -0
- package/app/Sources/Core/Input/MouseShortcutStore.swift +107 -0
- package/app/Sources/{CommandModeState.swift → Core/Overlays/CommandMode/CommandModeState.swift} +127 -24
- package/app/Sources/{CommandModeView.swift → Core/Overlays/CommandMode/CommandModeView.swift} +492 -79
- package/app/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +67 -0
- package/app/Sources/{CheatSheetHUD.swift → Core/Overlays/HUD/CheatSheetHUD.swift} +1 -0
- package/app/Sources/{HUDRightBar.swift → Core/Overlays/HUD/HUDRightBar.swift} +23 -201
- package/app/Sources/{LauncherHUD.swift → Core/Overlays/HUD/LauncherHUD.swift} +12 -26
- package/app/Sources/{OmniSearchView.swift → Core/Overlays/OmniSearch/OmniSearchView.swift} +136 -2
- package/app/Sources/{OmniSearchWindow.swift → Core/Overlays/OmniSearch/OmniSearchWindow.swift} +21 -32
- package/app/Sources/Core/Overlays/OverlayPanelShell.swift +241 -0
- package/app/Sources/{ScreenMapState.swift → Core/Overlays/ScreenMap/ScreenMapState.swift} +116 -32
- package/app/Sources/{ScreenMapView.swift → Core/Overlays/ScreenMap/ScreenMapView.swift} +510 -524
- package/app/Sources/{ScreenMapWindowController.swift → Core/Overlays/ScreenMap/ScreenMapWindowController.swift} +12 -4
- package/app/Sources/{VoiceCommandWindow.swift → Core/Overlays/Voice/VoiceCommandWindow.swift} +46 -53
- package/app/Sources/Core/Pi/PiAuthNextStepCard.swift +148 -0
- package/app/Sources/Core/Pi/PiAuthPromptCard.swift +90 -0
- package/app/Sources/{PiChatDock.swift → Core/Pi/PiChatDock.swift} +137 -74
- package/app/Sources/{PiChatSession.swift → Core/Pi/PiChatSession.swift} +608 -108
- package/app/Sources/Core/Pi/PiInstallCallout.swift +86 -0
- package/app/Sources/Core/Pi/PiProviderSetupCallout.swift +99 -0
- package/app/Sources/{PiWorkspaceView.swift → Core/Pi/PiWorkspaceView.swift} +174 -77
- package/app/Sources/{PermissionChecker.swift → Core/System/PermissionChecker.swift} +76 -2
- package/app/Sources/Core/System/SystemTelemetryMonitor.swift +273 -0
- package/app/Sources/{HandsOffSession.swift → Core/Voice/HandsOffSession.swift} +15 -4
- package/app/Sources/{WorkspaceManager.swift → Core/Workspace/WorkspaceManager.swift} +288 -0
- package/bin/assistant-intelligence.ts +874 -0
- package/bin/handsoff-infer.ts +16 -209
- package/bin/handsoff-worker.ts +45 -258
- package/bin/lattices-app.ts +62 -0
- package/bin/lattices-dev +4 -0
- package/bin/lattices.ts +125 -14
- package/docs/agents.md +14 -0
- package/docs/api.md +55 -0
- package/docs/app.md +3 -0
- package/docs/companion-deck.md +180 -0
- package/docs/component-extraction-roadmap.md +392 -0
- package/docs/config.md +25 -0
- package/docs/tiling-reference.md +55 -0
- package/docs/voice-error-model.md +73 -0
- package/package.json +4 -1
- package/app/Sources/App.swift +0 -10
- package/app/Sources/CommandPaletteWindow.swift +0 -134
- package/app/Sources/MouseFinder.swift +0 -222
- /package/app/Sources/{KeyRecorderView.swift → AppShell/KeyRecorderView.swift} +0 -0
- /package/app/Sources/{MainWindow.swift → AppShell/MainWindow.swift} +0 -0
- /package/app/Sources/{SettingsWindow.swift → AppShell/SettingsWindow.swift} +0 -0
- /package/app/Sources/{HotkeyManager.swift → Core/Actions/HotkeyManager.swift} +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/CreateLayerIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/DistributeIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/FocusIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/HelpIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/KillIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/LaunchIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/ListSessionsIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/ListWindowsIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/ScanIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/SearchIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/SwitchLayerIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/TileIntent.swift +0 -0
- /package/app/Sources/{DaemonProtocol.swift → Core/Daemon/DaemonProtocol.swift} +0 -0
- /package/app/Sources/{DaemonServer.swift → Core/Daemon/DaemonServer.swift} +0 -0
- /package/app/Sources/{AccessibilityTextExtractor.swift → Core/Desktop/AccessibilityTextExtractor.swift} +0 -0
- /package/app/Sources/{DesktopModelTypes.swift → Core/Desktop/DesktopModelTypes.swift} +0 -0
- /package/app/Sources/{InventoryManager.swift → Core/Desktop/InventoryManager.swift} +0 -0
- /package/app/Sources/{InventoryPath.swift → Core/Desktop/InventoryPath.swift} +0 -0
- /package/app/Sources/{OcrModel.swift → Core/Desktop/OcrModel.swift} +0 -0
- /package/app/Sources/{OcrStore.swift → Core/Desktop/OcrStore.swift} +0 -0
- /package/app/Sources/{PlacementSpec.swift → Core/Desktop/PlacementSpec.swift} +0 -0
- /package/app/Sources/{TilePickerView.swift → Core/Desktop/TilePickerView.swift} +0 -0
- /package/app/Sources/{AppWindowShell.swift → Core/Overlays/AppWindowShell.swift} +0 -0
- /package/app/Sources/{CommandModeWindow.swift → Core/Overlays/CommandMode/CommandModeWindow.swift} +0 -0
- /package/app/Sources/{CommandPaletteView.swift → Core/Overlays/CommandPalette/CommandPaletteView.swift} +0 -0
- /package/app/Sources/{HUDBottomBar.swift → Core/Overlays/HUD/HUDBottomBar.swift} +0 -0
- /package/app/Sources/{HUDController.swift → Core/Overlays/HUD/HUDController.swift} +0 -0
- /package/app/Sources/{HUDLeftBar.swift → Core/Overlays/HUD/HUDLeftBar.swift} +0 -0
- /package/app/Sources/{HUDMinimap.swift → Core/Overlays/HUD/HUDMinimap.swift} +0 -0
- /package/app/Sources/{HUDState.swift → Core/Overlays/HUD/HUDState.swift} +0 -0
- /package/app/Sources/{HUDTopBar.swift → Core/Overlays/HUD/HUDTopBar.swift} +0 -0
- /package/app/Sources/{LayerBezel.swift → Core/Overlays/HUD/LayerBezel.swift} +0 -0
- /package/app/Sources/{OmniSearchState.swift → Core/Overlays/OmniSearch/OmniSearchState.swift} +0 -0
- /package/app/Sources/{DiagnosticLog.swift → Core/System/DiagnosticLog.swift} +0 -0
- /package/app/Sources/{EventBus.swift → Core/System/EventBus.swift} +0 -0
- /package/app/Sources/{ProcessModel.swift → Core/System/ProcessModel.swift} +0 -0
- /package/app/Sources/{ProcessQuery.swift → Core/System/ProcessQuery.swift} +0 -0
- /package/app/Sources/{AdvisorLearningStore.swift → Core/Voice/AdvisorLearningStore.swift} +0 -0
- /package/app/Sources/{AgentSession.swift → Core/Voice/AgentSession.swift} +0 -0
- /package/app/Sources/{AudioProvider.swift → Core/Voice/AudioProvider.swift} +0 -0
- /package/app/Sources/{VoiceChatView.swift → Core/Voice/VoiceChatView.swift} +0 -0
- /package/app/Sources/{VoxClient.swift → Core/Voice/VoxClient.swift} +0 -0
- /package/app/Sources/{Project.swift → Core/Workspace/Project.swift} +0 -0
- /package/app/Sources/{ProjectScanner.swift → Core/Workspace/ProjectScanner.swift} +0 -0
- /package/app/Sources/{SessionLayerStore.swift → Core/Workspace/SessionLayerStore.swift} +0 -0
- /package/app/Sources/{SessionManager.swift → Core/Workspace/SessionManager.swift} +0 -0
- /package/app/Sources/{Terminal.swift → Core/Workspace/Terminal/Terminal.swift} +0 -0
- /package/app/Sources/{TerminalQuery.swift → Core/Workspace/Terminal/TerminalQuery.swift} +0 -0
- /package/app/Sources/{TerminalSynthesizer.swift → Core/Workspace/Terminal/TerminalSynthesizer.swift} +0 -0
- /package/app/Sources/{TmuxModel.swift → Core/Workspace/Tmux/TmuxModel.swift} +0 -0
- /package/app/Sources/{TmuxQuery.swift → Core/Workspace/Tmux/TmuxQuery.swift} +0 -0
- /package/app/Sources/{ActionRow.swift → UI/ActionRow.swift} +0 -0
- /package/app/Sources/{OrphanRow.swift → UI/OrphanRow.swift} +0 -0
- /package/app/Sources/{ProjectRow.swift → UI/ProjectRow.swift} +0 -0
- /package/app/Sources/{TabGroupRow.swift → UI/TabGroupRow.swift} +0 -0
- /package/app/Sources/{Theme.swift → UI/Theme.swift} +0 -0
package/README.md
CHANGED
|
@@ -58,6 +58,9 @@ To build a signed, notarized DMG for distribution:
|
|
|
58
58
|
```sh
|
|
59
59
|
# Requires a Developer ID certificate and notarytool keychain profile
|
|
60
60
|
./scripts/build-dmg.sh
|
|
61
|
+
|
|
62
|
+
# Update v<package.json version> and upload the DMG to GitHub Releases
|
|
63
|
+
./scripts/ship.sh
|
|
61
64
|
```
|
|
62
65
|
|
|
63
66
|
## Quick start
|
package/app/Info.plist
CHANGED
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
<key>CFBundlePackageType</key>
|
|
16
16
|
<string>APPL</string>
|
|
17
17
|
<key>CFBundleVersion</key>
|
|
18
|
-
<string>0.4.
|
|
18
|
+
<string>0.4.6</string>
|
|
19
19
|
<key>CFBundleShortVersionString</key>
|
|
20
|
-
<string>0.4.
|
|
20
|
+
<string>0.4.6</string>
|
|
21
21
|
<key>LSMinimumSystemVersion</key>
|
|
22
22
|
<string>13.0</string>
|
|
23
23
|
<key>LSUIElement</key>
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
<key>CFBundlePackageType</key>
|
|
16
16
|
<string>APPL</string>
|
|
17
17
|
<key>CFBundleVersion</key>
|
|
18
|
-
<string>0.4.
|
|
18
|
+
<string>0.4.6</string>
|
|
19
19
|
<key>CFBundleShortVersionString</key>
|
|
20
|
-
<string>0.4.
|
|
20
|
+
<string>0.4.6</string>
|
|
21
21
|
<key>LSMinimumSystemVersion</key>
|
|
22
22
|
<string>13.0</string>
|
|
23
23
|
<key>LSUIElement</key>
|
|
Binary file
|
package/app/Package.swift
CHANGED
|
@@ -4,9 +4,15 @@ import PackageDescription
|
|
|
4
4
|
let package = Package(
|
|
5
5
|
name: "Lattices",
|
|
6
6
|
platforms: [.macOS(.v13)],
|
|
7
|
+
dependencies: [
|
|
8
|
+
.package(path: "../swift")
|
|
9
|
+
],
|
|
7
10
|
targets: [
|
|
8
11
|
.executableTarget(
|
|
9
12
|
name: "Lattices",
|
|
13
|
+
dependencies: [
|
|
14
|
+
.product(name: "DeckKit", package: "swift")
|
|
15
|
+
],
|
|
10
16
|
path: "Sources",
|
|
11
17
|
resources: [
|
|
12
18
|
.copy("../Resources/tap.wav"),
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
@main
|
|
4
|
+
struct LatticesApp: App {
|
|
5
|
+
@NSApplicationDelegateAdaptor(AppDelegate.self) var delegate
|
|
6
|
+
|
|
7
|
+
var body: some Scene {
|
|
8
|
+
Settings { EmptyView() }
|
|
9
|
+
.commands {
|
|
10
|
+
CommandGroup(after: .appInfo) {
|
|
11
|
+
Button("Update Lattices…") {
|
|
12
|
+
AppUpdater.shared.promptForUpdate()
|
|
13
|
+
}
|
|
14
|
+
.disabled(!AppUpdater.shared.canUpdate)
|
|
15
|
+
|
|
16
|
+
Divider()
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import AppKit
|
|
2
2
|
import SwiftUI
|
|
3
3
|
|
|
4
|
+
extension Notification.Name {
|
|
5
|
+
static let latticesPopoverWillShow = Notification.Name("latticesPopoverWillShow")
|
|
6
|
+
}
|
|
7
|
+
|
|
4
8
|
/// Manages the NSStatusItem (menu bar icon), left-click popover, and right-click context menu.
|
|
5
9
|
/// Replaces the previous SwiftUI MenuBarExtra approach for full click-event control.
|
|
6
10
|
class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|
11
|
+
private static weak var shared: AppDelegate?
|
|
7
12
|
|
|
8
13
|
private var statusItem: NSStatusItem!
|
|
9
14
|
private var popover: NSPopover?
|
|
@@ -45,6 +50,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|
|
45
50
|
/// based on whether any managed windows are open.
|
|
46
51
|
static func updateActivationPolicy() {
|
|
47
52
|
let hasVisibleWindow =
|
|
53
|
+
(Self.shared?.popover?.isShown == true) ||
|
|
48
54
|
CommandModeWindow.shared.isVisible ||
|
|
49
55
|
CommandPaletteWindow.shared.isVisible ||
|
|
50
56
|
MainWindow.shared.isVisible ||
|
|
@@ -60,6 +66,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
69
|
+
Self.shared = self
|
|
63
70
|
NSApp.setActivationPolicy(.accessory)
|
|
64
71
|
NSApp.appearance = NSAppearance(named: .darkAqua)
|
|
65
72
|
|
|
@@ -82,8 +89,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|
|
82
89
|
let store = HotkeyStore.shared
|
|
83
90
|
store.register(action: .palette) { CommandPaletteWindow.shared.toggle() }
|
|
84
91
|
store.register(action: .unifiedWindow) { ScreenMapWindowController.shared.toggle() }
|
|
85
|
-
store.register(action: .bezel) {
|
|
86
|
-
store.register(action: .cheatSheet) {
|
|
92
|
+
store.register(action: .bezel) { Self.showWorkspaceInspector() }
|
|
93
|
+
store.register(action: .cheatSheet) { SettingsWindowController.shared.show() }
|
|
94
|
+
store.register(action: .desktopInventory) {
|
|
95
|
+
DiagnosticLog.shared.info("Hotkey: desktopInventory triggered")
|
|
96
|
+
ScreenMapWindowController.shared.showPage(.desktopInventory)
|
|
97
|
+
}
|
|
87
98
|
store.register(action: .voiceCommand) {
|
|
88
99
|
DiagnosticLog.shared.info("Hotkey: voiceCommand triggered")
|
|
89
100
|
VoiceCommandWindow.shared.toggle()
|
|
@@ -103,7 +114,16 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|
|
103
114
|
|
|
104
115
|
// Pre-render HUD panels off-screen for instant first open
|
|
105
116
|
DispatchQueue.main.async { HUDController.shared.warmUp() }
|
|
117
|
+
// Pre-build the menu bar popover so the first click doesn't pay the SwiftUI mount cost.
|
|
118
|
+
// Touching `.view` forces NSHostingController to materialize the SwiftUI view tree.
|
|
119
|
+
DispatchQueue.main.async { [weak self] in
|
|
120
|
+
guard let self = self else { return }
|
|
121
|
+
let p = self.makePopover()
|
|
122
|
+
_ = p.contentViewController?.view
|
|
123
|
+
}
|
|
106
124
|
store.register(action: .omniSearch) { OmniSearchWindow.shared.toggle() }
|
|
125
|
+
WindowDragSnapController.shared.start()
|
|
126
|
+
MouseGestureController.shared.start()
|
|
107
127
|
|
|
108
128
|
// Session layer cycling
|
|
109
129
|
store.register(action: .layerNext) { SessionLayerStore.shared.cycleNext() }
|
|
@@ -140,7 +160,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|
|
140
160
|
for (action, position) in tileMap {
|
|
141
161
|
store.register(action: action) { WindowTiler.tileFrontmostViaAX(to: position) }
|
|
142
162
|
}
|
|
143
|
-
store.register(action: .tileDistribute) { WindowTiler.distributeVisible() }
|
|
163
|
+
store.register(action: .tileDistribute) { WindowTiler.distributeVisible(reactivateLattices: false) }
|
|
164
|
+
store.register(action: .tileTypeGrid) { WindowTiler.distributeVisibleByFrontmostType(reactivateLattices: false) }
|
|
144
165
|
|
|
145
166
|
// Onboarding on first launch; otherwise just check permissions
|
|
146
167
|
if !OnboardingWindowController.shared.showIfNeeded() {
|
|
@@ -157,6 +178,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|
|
157
178
|
ProcessModel.shared.start()
|
|
158
179
|
LatticesApi.setup()
|
|
159
180
|
DaemonServer.shared.start()
|
|
181
|
+
LatticesCompanionBridgeServer.shared.start()
|
|
160
182
|
AgentPool.shared.start()
|
|
161
183
|
diag.finish(tBoot)
|
|
162
184
|
|
|
@@ -167,14 +189,19 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|
|
167
189
|
}
|
|
168
190
|
}
|
|
169
191
|
|
|
170
|
-
// --screen-map flag: auto-open
|
|
192
|
+
// --screen-map flag: auto-open layout on launch
|
|
171
193
|
if CommandLine.arguments.contains("--screen-map") {
|
|
172
194
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
|
173
|
-
ScreenMapWindowController.shared.
|
|
195
|
+
ScreenMapWindowController.shared.showPage(.screenMap)
|
|
174
196
|
}
|
|
175
197
|
}
|
|
176
198
|
}
|
|
177
199
|
|
|
200
|
+
func applicationWillTerminate(_ notification: Notification) {
|
|
201
|
+
LatticesCompanionBridgeServer.shared.stop()
|
|
202
|
+
DaemonServer.shared.stop()
|
|
203
|
+
}
|
|
204
|
+
|
|
178
205
|
// MARK: - Status item click handler
|
|
179
206
|
|
|
180
207
|
@objc private func statusItemClicked(_ sender: Any?) {
|
|
@@ -184,13 +211,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|
|
184
211
|
// Right-click → context menu
|
|
185
212
|
contextMenu.popUp(positioning: nil, at: NSPoint(x: 0, y: button.bounds.height + 4), in: button)
|
|
186
213
|
} else {
|
|
187
|
-
// Left-click → toggle popover
|
|
214
|
+
// Left-click → toggle the menu bar projects popover.
|
|
188
215
|
if let shown = popover, shown.isShown {
|
|
189
216
|
shown.performClose(sender)
|
|
190
217
|
} else {
|
|
191
|
-
|
|
192
|
-
p.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
|
|
193
|
-
p.contentViewController?.view.window?.makeKey()
|
|
218
|
+
showProjectsPopover()
|
|
194
219
|
}
|
|
195
220
|
}
|
|
196
221
|
}
|
|
@@ -200,14 +225,16 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|
|
200
225
|
popover?.performClose(nil)
|
|
201
226
|
}
|
|
202
227
|
|
|
203
|
-
///
|
|
204
|
-
///
|
|
228
|
+
/// Cached popover — built lazily on first click, reused on every subsequent open.
|
|
229
|
+
/// Keeping the SwiftUI view tree alive avoids rebuilding on each click (slow first paint).
|
|
230
|
+
/// Data refresh is driven from `popoverWillShow` + a notification MainView listens to.
|
|
205
231
|
private func makePopover() -> NSPopover {
|
|
232
|
+
if let p = popover { return p }
|
|
206
233
|
let t = DiagnosticLog.shared.startTimed("makePopover")
|
|
207
234
|
let p = NSPopover()
|
|
208
235
|
p.contentViewController = NSHostingController(rootView: MainView(scanner: ProjectScanner.shared))
|
|
209
236
|
p.behavior = .transient
|
|
210
|
-
p.contentSize = NSSize(width: 380, height:
|
|
237
|
+
p.contentSize = NSSize(width: 380, height: 300)
|
|
211
238
|
p.appearance = NSAppearance(named: .darkAqua)
|
|
212
239
|
p.delegate = self
|
|
213
240
|
popover = p
|
|
@@ -215,10 +242,20 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|
|
215
242
|
return p
|
|
216
243
|
}
|
|
217
244
|
|
|
245
|
+
private func showProjectsPopover() {
|
|
246
|
+
guard let button = statusItem.button else { return }
|
|
247
|
+
let p = makePopover()
|
|
248
|
+
p.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
|
|
249
|
+
p.contentViewController?.view.window?.makeKey()
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
func popoverWillShow(_ notification: Notification) {
|
|
253
|
+
Self.updateActivationPolicy()
|
|
254
|
+
NotificationCenter.default.post(name: .latticesPopoverWillShow, object: nil)
|
|
255
|
+
}
|
|
256
|
+
|
|
218
257
|
func popoverDidClose(_ notification: Notification) {
|
|
219
|
-
|
|
220
|
-
popover?.contentViewController = nil
|
|
221
|
-
popover = nil
|
|
258
|
+
Self.updateActivationPolicy()
|
|
222
259
|
}
|
|
223
260
|
|
|
224
261
|
// MARK: - Context menu
|
|
@@ -227,10 +264,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|
|
227
264
|
let menu = NSMenu()
|
|
228
265
|
|
|
229
266
|
let actions: [(String, String, Selector)] = [
|
|
267
|
+
("Home", "", #selector(menuWorkspace)),
|
|
268
|
+
("Layout", "", #selector(menuLayout)),
|
|
269
|
+
("Search", "", #selector(menuSearch)),
|
|
230
270
|
("Command Palette", "⌘⇧M", #selector(menuCommandPalette)),
|
|
231
|
-
("Workspace", "", #selector(menuWorkspace)),
|
|
232
|
-
("Assistant", "", #selector(menuAssistant)),
|
|
233
|
-
("Help & Shortcuts", "", #selector(menuDocs)),
|
|
234
271
|
]
|
|
235
272
|
for (title, shortcut, action) in actions {
|
|
236
273
|
let item = NSMenuItem(title: title, action: action, keyEquivalent: "")
|
|
@@ -243,14 +280,29 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|
|
243
280
|
|
|
244
281
|
menu.addItem(.separator())
|
|
245
282
|
|
|
246
|
-
let
|
|
283
|
+
let cliActions: [(String, Selector)] = [
|
|
284
|
+
("Projects…", #selector(menuProjects)),
|
|
285
|
+
("Initialize Project in Terminal…", #selector(menuInitializeProject)),
|
|
286
|
+
("Launch Project in Terminal…", #selector(menuLaunchProject)),
|
|
287
|
+
]
|
|
288
|
+
for (title, action) in cliActions {
|
|
289
|
+
let item = NSMenuItem(title: title, action: action, keyEquivalent: "")
|
|
290
|
+
item.target = self
|
|
291
|
+
menu.addItem(item)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
menu.addItem(.separator())
|
|
295
|
+
|
|
296
|
+
let update = NSMenuItem(title: "Update Lattices…", action: #selector(menuUpdate), keyEquivalent: "")
|
|
297
|
+
update.target = self
|
|
298
|
+
menu.addItem(update)
|
|
299
|
+
|
|
300
|
+
menu.addItem(.separator())
|
|
301
|
+
|
|
302
|
+
let settings = NSMenuItem(title: "Help & Settings…", action: #selector(menuSettings), keyEquivalent: ",")
|
|
247
303
|
settings.target = self
|
|
248
304
|
menu.addItem(settings)
|
|
249
305
|
|
|
250
|
-
let diag = NSMenuItem(title: "Diagnostics", action: #selector(menuDiagnostics), keyEquivalent: "")
|
|
251
|
-
diag.target = self
|
|
252
|
-
menu.addItem(diag)
|
|
253
|
-
|
|
254
306
|
menu.addItem(.separator())
|
|
255
307
|
|
|
256
308
|
let quit = NSMenuItem(title: "Quit Lattices", action: #selector(menuQuit), keyEquivalent: "q")
|
|
@@ -262,19 +314,27 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|
|
262
314
|
|
|
263
315
|
@objc private func menuCommandPalette() { CommandPaletteWindow.shared.toggle() }
|
|
264
316
|
@objc private func menuWorkspace() { ScreenMapWindowController.shared.showPage(.home) }
|
|
265
|
-
@objc private func
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
@objc private func menuDocs() { ScreenMapWindowController.shared.showPage(.docs) }
|
|
317
|
+
@objc private func menuLayout() { ScreenMapWindowController.shared.showPage(.screenMap) }
|
|
318
|
+
@objc private func menuSearch() { ScreenMapWindowController.shared.showPage(.desktopInventory) }
|
|
319
|
+
@objc private func menuDocs() { SettingsWindowController.shared.show() }
|
|
320
|
+
@objc private func menuProjects() { DispatchQueue.main.async { self.showProjectsPopover() } }
|
|
321
|
+
@objc private func menuInitializeProject() { CliActionLauncher.initializeProjectInTerminal() }
|
|
322
|
+
@objc private func menuLaunchProject() { CliActionLauncher.launchProjectInTerminal() }
|
|
273
323
|
@objc private func menuHUD() { HUDController.shared.toggle() }
|
|
274
|
-
@objc private func menuWindowBezel() {
|
|
275
|
-
@objc private func menuCheatSheet() {
|
|
324
|
+
@objc private func menuWindowBezel() { Self.showWorkspaceInspector() }
|
|
325
|
+
@objc private func menuCheatSheet() { SettingsWindowController.shared.show() }
|
|
276
326
|
@objc private func menuOmniSearch() { OmniSearchWindow.shared.toggle() }
|
|
327
|
+
@MainActor @objc private func menuUpdate() { AppUpdater.shared.promptForUpdate() }
|
|
277
328
|
@objc private func menuSettings() { SettingsWindowController.shared.show() }
|
|
278
|
-
@objc private func menuDiagnostics() { DiagnosticWindow.shared.toggle() }
|
|
279
329
|
@objc private func menuQuit() { NSApp.terminate(nil) }
|
|
330
|
+
|
|
331
|
+
private static func showWorkspaceInspector() {
|
|
332
|
+
guard let entry = DesktopModel.shared.frontmostWindow(),
|
|
333
|
+
entry.app != "Lattices" else {
|
|
334
|
+
ScreenMapWindowController.shared.showPage(.screenMap)
|
|
335
|
+
return
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
ScreenMapWindowController.shared.showWindow(wid: entry.wid)
|
|
339
|
+
}
|
|
280
340
|
}
|
|
@@ -56,6 +56,10 @@ struct AppShellView: View {
|
|
|
56
56
|
.background(Palette.bg)
|
|
57
57
|
.onAppear {
|
|
58
58
|
commandState.onDismiss = { windowController.activePage = .home }
|
|
59
|
+
syncPageState(windowController.activePage)
|
|
60
|
+
}
|
|
61
|
+
.onChange(of: windowController.activePage) { page in
|
|
62
|
+
syncPageState(page)
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
65
|
|
|
@@ -66,6 +70,7 @@ struct AppShellView: View {
|
|
|
66
70
|
ForEach(AppPage.primaryTabs, id: \.rawValue) { tab in
|
|
67
71
|
tabButton(tab)
|
|
68
72
|
}
|
|
73
|
+
|
|
69
74
|
Spacer()
|
|
70
75
|
}
|
|
71
76
|
.padding(.horizontal, 12)
|
|
@@ -90,6 +95,7 @@ struct AppShellView: View {
|
|
|
90
95
|
.foregroundColor(isActive ? Palette.text : Palette.textMuted)
|
|
91
96
|
.padding(.horizontal, 12)
|
|
92
97
|
.padding(.vertical, 6)
|
|
98
|
+
.contentShape(Rectangle())
|
|
93
99
|
.background(
|
|
94
100
|
RoundedRectangle(cornerRadius: 6)
|
|
95
101
|
.fill(isActive ? Palette.surfaceHov : Color.clear)
|
|
@@ -114,7 +120,7 @@ struct AppShellView: View {
|
|
|
114
120
|
windowController.activePage = page
|
|
115
121
|
})
|
|
116
122
|
case .desktopInventory:
|
|
117
|
-
CommandModeView(state: commandState)
|
|
123
|
+
CommandModeView(state: commandState, presentation: .embedded)
|
|
118
124
|
case .pi:
|
|
119
125
|
PiWorkspaceView()
|
|
120
126
|
case .settings:
|
|
@@ -132,4 +138,9 @@ struct AppShellView: View {
|
|
|
132
138
|
)
|
|
133
139
|
}
|
|
134
140
|
}
|
|
141
|
+
|
|
142
|
+
private func syncPageState(_ page: AppPage) {
|
|
143
|
+
if page == .screenMap { controller.enter() }
|
|
144
|
+
if page == .desktopInventory { commandState.enter() }
|
|
145
|
+
}
|
|
135
146
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import Combine
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
@MainActor
|
|
6
|
+
final class AppUpdater: ObservableObject {
|
|
7
|
+
static let shared = AppUpdater()
|
|
8
|
+
|
|
9
|
+
@Published private(set) var isUpdating = false
|
|
10
|
+
@Published private(set) var statusMessage: String?
|
|
11
|
+
|
|
12
|
+
private init() {}
|
|
13
|
+
|
|
14
|
+
var currentVersion: String { LatticesRuntime.appVersion }
|
|
15
|
+
|
|
16
|
+
var canUpdate: Bool {
|
|
17
|
+
LatticesRuntime.bunPath != nil && LatticesRuntime.appHelperScriptPath != nil
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
var unavailableReason: String? {
|
|
21
|
+
if LatticesRuntime.bunPath == nil {
|
|
22
|
+
return "Install Bun to enable in-app updates."
|
|
23
|
+
}
|
|
24
|
+
if LatticesRuntime.appHelperScriptPath == nil {
|
|
25
|
+
return "Launch Lattices via `lattices app` so the updater can find the CLI bundle."
|
|
26
|
+
}
|
|
27
|
+
return nil
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func promptForUpdate() {
|
|
31
|
+
guard canUpdate else {
|
|
32
|
+
presentAlert(
|
|
33
|
+
title: "Update Unavailable",
|
|
34
|
+
message: unavailableReason ?? "Lattices could not locate its updater."
|
|
35
|
+
)
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let alert = NSAlert()
|
|
40
|
+
alert.alertStyle = .informational
|
|
41
|
+
alert.messageText = "Install the latest Lattices app update?"
|
|
42
|
+
alert.informativeText = "Lattices will download the latest released app bundle, close, and relaunch when the update is ready."
|
|
43
|
+
alert.addButton(withTitle: "Update")
|
|
44
|
+
alert.addButton(withTitle: "Cancel")
|
|
45
|
+
|
|
46
|
+
guard alert.runModal() == .alertFirstButtonReturn else { return }
|
|
47
|
+
startDetachedUpdate()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private func startDetachedUpdate() {
|
|
51
|
+
guard !isUpdating else { return }
|
|
52
|
+
guard let bunPath = LatticesRuntime.bunPath,
|
|
53
|
+
let scriptPath = LatticesRuntime.appHelperScriptPath else {
|
|
54
|
+
presentAlert(
|
|
55
|
+
title: "Update Unavailable",
|
|
56
|
+
message: unavailableReason ?? "Lattices could not locate its updater."
|
|
57
|
+
)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let proc = Process()
|
|
62
|
+
proc.executableURL = URL(fileURLWithPath: bunPath)
|
|
63
|
+
proc.arguments = [scriptPath, "update", "--detach", "--launch"]
|
|
64
|
+
if let cliRoot = LatticesRuntime.cliRoot {
|
|
65
|
+
proc.currentDirectoryURL = URL(fileURLWithPath: cliRoot)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
var env = ProcessInfo.processInfo.environment
|
|
69
|
+
env.removeValue(forKey: "CLAUDECODE")
|
|
70
|
+
proc.environment = env
|
|
71
|
+
|
|
72
|
+
do {
|
|
73
|
+
try proc.run()
|
|
74
|
+
isUpdating = true
|
|
75
|
+
statusMessage = "Updating to the latest release. Lattices will relaunch when it's ready."
|
|
76
|
+
} catch {
|
|
77
|
+
presentAlert(
|
|
78
|
+
title: "Update Failed",
|
|
79
|
+
message: "Lattices could not start the updater.\n\n\(error.localizedDescription)"
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private func presentAlert(title: String, message: String) {
|
|
85
|
+
let alert = NSAlert()
|
|
86
|
+
alert.alertStyle = .warning
|
|
87
|
+
alert.messageText = title
|
|
88
|
+
alert.informativeText = message
|
|
89
|
+
alert.addButton(withTitle: "OK")
|
|
90
|
+
alert.runModal()
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
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",
|
|
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",
|
|
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
|
+
}
|
|
@@ -4,6 +4,7 @@ struct HomeDashboardView: View {
|
|
|
4
4
|
var onNavigate: ((AppPage) -> Void)? = nil
|
|
5
5
|
|
|
6
6
|
@ObservedObject private var scanner = ProjectScanner.shared
|
|
7
|
+
@ObservedObject private var piSession = PiChatSession.shared
|
|
7
8
|
|
|
8
9
|
var body: some View {
|
|
9
10
|
VStack(spacing: 0) {
|
|
@@ -16,6 +17,9 @@ struct HomeDashboardView: View {
|
|
|
16
17
|
MainView(scanner: scanner, layout: .embedded)
|
|
17
18
|
}
|
|
18
19
|
.background(Palette.bg)
|
|
20
|
+
.onAppear {
|
|
21
|
+
piSession.refreshBinaryAvailability()
|
|
22
|
+
}
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
private var hero: some View {
|
|
@@ -26,7 +30,7 @@ struct HomeDashboardView: View {
|
|
|
26
30
|
.font(Typo.heading(18))
|
|
27
31
|
.foregroundColor(Palette.text)
|
|
28
32
|
|
|
29
|
-
Text("
|
|
33
|
+
Text("Workspace status, project launch, layout, search, and chat in one place.")
|
|
30
34
|
.font(Typo.mono(11))
|
|
31
35
|
.foregroundColor(Palette.textDim)
|
|
32
36
|
.fixedSize(horizontal: false, vertical: true)
|
|
@@ -37,8 +41,8 @@ struct HomeDashboardView: View {
|
|
|
37
41
|
|
|
38
42
|
HStack(spacing: 10) {
|
|
39
43
|
homeActionCard(
|
|
40
|
-
title: "
|
|
41
|
-
subtitle: "
|
|
44
|
+
title: "Layout",
|
|
45
|
+
subtitle: "Arrange windows",
|
|
42
46
|
icon: "rectangle.3.group",
|
|
43
47
|
tint: Palette.running
|
|
44
48
|
) {
|
|
@@ -46,19 +50,23 @@ struct HomeDashboardView: View {
|
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
homeActionCard(
|
|
49
|
-
title: "
|
|
50
|
-
subtitle: "
|
|
51
|
-
icon: "
|
|
53
|
+
title: "Search",
|
|
54
|
+
subtitle: "Find workspace context",
|
|
55
|
+
icon: "magnifyingglass",
|
|
52
56
|
tint: Palette.detach
|
|
53
57
|
) {
|
|
54
58
|
onNavigate?(.desktopInventory)
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
homeActionCard(
|
|
58
|
-
title: "
|
|
59
|
-
subtitle:
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
62
70
|
) {
|
|
63
71
|
onNavigate?(.pi)
|
|
64
72
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
enum LatticesRuntime {
|
|
4
|
+
static var cliRoot: String? {
|
|
5
|
+
if let idx = CommandLine.arguments.firstIndex(of: "--lattices-cli-root"),
|
|
6
|
+
CommandLine.arguments.indices.contains(idx + 1) {
|
|
7
|
+
let root = CommandLine.arguments[idx + 1]
|
|
8
|
+
if hasAppHelper(in: root) { return root }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let bundleDerivedRoot = Bundle.main.bundleURL
|
|
12
|
+
.deletingLastPathComponent()
|
|
13
|
+
.deletingLastPathComponent()
|
|
14
|
+
.path
|
|
15
|
+
if hasAppHelper(in: bundleDerivedRoot) {
|
|
16
|
+
return bundleDerivedRoot
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let devRoot = NSHomeDirectory() + "/dev/lattices"
|
|
20
|
+
if hasAppHelper(in: devRoot) {
|
|
21
|
+
return devRoot
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return nil
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static var appHelperScriptPath: String? {
|
|
28
|
+
guard let cliRoot else { return nil }
|
|
29
|
+
let path = cliRoot + "/bin/lattices-app.ts"
|
|
30
|
+
return FileManager.default.fileExists(atPath: path) ? path : nil
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static var bunPath: String? {
|
|
34
|
+
let candidates = [
|
|
35
|
+
NSHomeDirectory() + "/.bun/bin/bun",
|
|
36
|
+
"/usr/local/bin/bun",
|
|
37
|
+
"/opt/homebrew/bin/bun",
|
|
38
|
+
]
|
|
39
|
+
if let path = candidates.first(where: { FileManager.default.isExecutableFile(atPath: $0) }) {
|
|
40
|
+
return path
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let resolved = ProcessQuery.shell(["/bin/zsh", "-lc", "command -v bun 2>/dev/null"])
|
|
44
|
+
if !resolved.isEmpty, FileManager.default.isExecutableFile(atPath: resolved) {
|
|
45
|
+
return resolved
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return nil
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static var appVersion: String {
|
|
52
|
+
let info = Bundle.main.infoDictionary
|
|
53
|
+
return (info?["CFBundleShortVersionString"] as? String)
|
|
54
|
+
?? (info?["CFBundleVersion"] as? String)
|
|
55
|
+
?? "unknown"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private static func hasAppHelper(in root: String) -> Bool {
|
|
59
|
+
FileManager.default.fileExists(atPath: root + "/bin/lattices-app.ts")
|
|
60
|
+
}
|
|
61
|
+
}
|