@lattices/cli 0.3.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 (74) hide show
  1. package/README.md +155 -0
  2. package/app/Lattices.app/Contents/Info.plist +24 -0
  3. package/app/Package.swift +13 -0
  4. package/app/Sources/AccessibilityTextExtractor.swift +111 -0
  5. package/app/Sources/ActionRow.swift +61 -0
  6. package/app/Sources/App.swift +10 -0
  7. package/app/Sources/AppDelegate.swift +242 -0
  8. package/app/Sources/AppShellView.swift +62 -0
  9. package/app/Sources/AppTypeClassifier.swift +70 -0
  10. package/app/Sources/AppWindowShell.swift +63 -0
  11. package/app/Sources/CheatSheetHUD.swift +332 -0
  12. package/app/Sources/CommandModeState.swift +1362 -0
  13. package/app/Sources/CommandModeView.swift +1405 -0
  14. package/app/Sources/CommandModeWindow.swift +192 -0
  15. package/app/Sources/CommandPaletteView.swift +307 -0
  16. package/app/Sources/CommandPaletteWindow.swift +134 -0
  17. package/app/Sources/DaemonProtocol.swift +101 -0
  18. package/app/Sources/DaemonServer.swift +414 -0
  19. package/app/Sources/DesktopModel.swift +149 -0
  20. package/app/Sources/DesktopModelTypes.swift +71 -0
  21. package/app/Sources/DiagnosticLog.swift +271 -0
  22. package/app/Sources/EventBus.swift +30 -0
  23. package/app/Sources/HotkeyManager.swift +254 -0
  24. package/app/Sources/HotkeyStore.swift +338 -0
  25. package/app/Sources/InventoryManager.swift +35 -0
  26. package/app/Sources/InventoryPath.swift +43 -0
  27. package/app/Sources/KeyRecorderView.swift +210 -0
  28. package/app/Sources/LatticesApi.swift +1234 -0
  29. package/app/Sources/LayerBezel.swift +203 -0
  30. package/app/Sources/MainView.swift +479 -0
  31. package/app/Sources/MainWindow.swift +83 -0
  32. package/app/Sources/OcrModel.swift +430 -0
  33. package/app/Sources/OcrStore.swift +329 -0
  34. package/app/Sources/OmniSearchState.swift +283 -0
  35. package/app/Sources/OmniSearchView.swift +288 -0
  36. package/app/Sources/OmniSearchWindow.swift +105 -0
  37. package/app/Sources/OrphanRow.swift +129 -0
  38. package/app/Sources/PaletteCommand.swift +419 -0
  39. package/app/Sources/PermissionChecker.swift +125 -0
  40. package/app/Sources/Preferences.swift +99 -0
  41. package/app/Sources/ProcessModel.swift +199 -0
  42. package/app/Sources/ProcessQuery.swift +151 -0
  43. package/app/Sources/Project.swift +28 -0
  44. package/app/Sources/ProjectRow.swift +368 -0
  45. package/app/Sources/ProjectScanner.swift +128 -0
  46. package/app/Sources/ScreenMapState.swift +2387 -0
  47. package/app/Sources/ScreenMapView.swift +2820 -0
  48. package/app/Sources/ScreenMapWindowController.swift +89 -0
  49. package/app/Sources/SessionManager.swift +72 -0
  50. package/app/Sources/SettingsView.swift +1064 -0
  51. package/app/Sources/SettingsWindow.swift +20 -0
  52. package/app/Sources/TabGroupRow.swift +178 -0
  53. package/app/Sources/Terminal.swift +259 -0
  54. package/app/Sources/TerminalQuery.swift +156 -0
  55. package/app/Sources/TerminalSynthesizer.swift +200 -0
  56. package/app/Sources/Theme.swift +163 -0
  57. package/app/Sources/TilePickerView.swift +209 -0
  58. package/app/Sources/TmuxModel.swift +53 -0
  59. package/app/Sources/TmuxQuery.swift +81 -0
  60. package/app/Sources/WindowTiler.swift +1778 -0
  61. package/app/Sources/WorkspaceManager.swift +575 -0
  62. package/bin/client.js +4 -0
  63. package/bin/daemon-client.js +187 -0
  64. package/bin/lattices-app.js +221 -0
  65. package/bin/lattices.js +1551 -0
  66. package/docs/api.md +924 -0
  67. package/docs/app.md +297 -0
  68. package/docs/concepts.md +135 -0
  69. package/docs/config.md +245 -0
  70. package/docs/layers.md +410 -0
  71. package/docs/ocr.md +185 -0
  72. package/docs/overview.md +94 -0
  73. package/docs/quickstart.md +75 -0
  74. package/package.json +42 -0
@@ -0,0 +1,288 @@
1
+ import SwiftUI
2
+
3
+ struct OmniSearchView: View {
4
+ @ObservedObject var state: OmniSearchState
5
+ var onDismiss: () -> Void
6
+
7
+ @FocusState private var searchFocused: Bool
8
+
9
+ var body: some View {
10
+ VStack(spacing: 0) {
11
+ // Search field
12
+ HStack(spacing: 8) {
13
+ Image(systemName: "magnifyingglass")
14
+ .foregroundColor(Palette.textMuted)
15
+ .font(.system(size: 13))
16
+
17
+ TextField("Search windows, projects, sessions...", text: $state.query)
18
+ .textFieldStyle(.plain)
19
+ .font(Typo.mono(14))
20
+ .foregroundColor(Palette.text)
21
+ .focused($searchFocused)
22
+
23
+ if !state.query.isEmpty {
24
+ Button { state.query = "" } label: {
25
+ Image(systemName: "xmark.circle.fill")
26
+ .foregroundColor(Palette.textMuted)
27
+ .font(.system(size: 12))
28
+ }
29
+ .buttonStyle(.plain)
30
+ }
31
+ }
32
+ .padding(.horizontal, 14)
33
+ .padding(.vertical, 12)
34
+ .background(Palette.surface)
35
+
36
+ Rectangle()
37
+ .fill(Palette.border)
38
+ .frame(height: 0.5)
39
+
40
+ // Content
41
+ if state.query.isEmpty {
42
+ summaryView
43
+ } else if state.results.isEmpty {
44
+ emptyResults
45
+ } else {
46
+ resultsView
47
+ }
48
+ }
49
+ .frame(minWidth: 520, idealWidth: 520, maxWidth: 700, minHeight: 360, idealHeight: 480, maxHeight: 600)
50
+ .background(PanelBackground())
51
+ .preferredColorScheme(.dark)
52
+ .onAppear {
53
+ searchFocused = true
54
+ state.refreshSummary()
55
+ }
56
+ }
57
+
58
+ // MARK: - Results
59
+
60
+ private var resultsView: some View {
61
+ ScrollViewReader { proxy in
62
+ ScrollView {
63
+ LazyVStack(alignment: .leading, spacing: 2) {
64
+ var flatIndex = 0
65
+ ForEach(state.groupedResults, id: \.0) { group, items in
66
+ // Group header
67
+ Text(group.uppercased())
68
+ .font(Typo.caption(9))
69
+ .foregroundColor(Palette.textMuted)
70
+ .padding(.horizontal, 14)
71
+ .padding(.top, 8)
72
+ .padding(.bottom, 2)
73
+
74
+ ForEach(items) { item in
75
+ let idx = flatIndex
76
+ let _ = { flatIndex += 1 }()
77
+ resultRow(item, index: idx)
78
+ .id(item.id)
79
+ }
80
+ }
81
+ }
82
+ .padding(.vertical, 4)
83
+ }
84
+ .onChange(of: state.selectedIndex) { newVal in
85
+ if newVal < state.results.count {
86
+ let item = state.results[newVal]
87
+ withAnimation(.easeOut(duration: 0.1)) {
88
+ proxy.scrollTo(item.id, anchor: .center)
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ private func resultRow(_ item: OmniResult, index: Int) -> some View {
96
+ let isSelected = index == state.selectedIndex
97
+ return Button {
98
+ item.action()
99
+ onDismiss()
100
+ } label: {
101
+ HStack(spacing: 10) {
102
+ Image(systemName: item.icon)
103
+ .font(.system(size: 11, weight: .medium))
104
+ .foregroundColor(isSelected ? Palette.text : Palette.textDim)
105
+ .frame(width: 16)
106
+
107
+ VStack(alignment: .leading, spacing: 1) {
108
+ Text(item.title)
109
+ .font(Typo.mono(12))
110
+ .foregroundColor(isSelected ? Palette.text : Palette.textDim)
111
+ .lineLimit(1)
112
+
113
+ Text(item.subtitle)
114
+ .font(Typo.mono(10))
115
+ .foregroundColor(Palette.textMuted)
116
+ .lineLimit(1)
117
+ }
118
+
119
+ Spacer()
120
+
121
+ Text(item.kind.rawValue)
122
+ .font(Typo.mono(9))
123
+ .foregroundColor(Palette.textMuted)
124
+ .padding(.horizontal, 5)
125
+ .padding(.vertical, 2)
126
+ .background(
127
+ RoundedRectangle(cornerRadius: 3)
128
+ .fill(Palette.surface)
129
+ )
130
+ }
131
+ .padding(.horizontal, 14)
132
+ .padding(.vertical, 6)
133
+ .background(
134
+ RoundedRectangle(cornerRadius: 5)
135
+ .fill(isSelected ? Palette.surfaceHov : Color.clear)
136
+ )
137
+ .contentShape(Rectangle())
138
+ }
139
+ .buttonStyle(.plain)
140
+ }
141
+
142
+ // MARK: - Empty Results
143
+
144
+ private var emptyResults: some View {
145
+ VStack(spacing: 12) {
146
+ Spacer()
147
+ Image(systemName: "magnifyingglass")
148
+ .font(.system(size: 24, weight: .light))
149
+ .foregroundColor(Palette.textMuted)
150
+ Text("No results for \"\(state.query)\"")
151
+ .font(Typo.mono(12))
152
+ .foregroundColor(Palette.textDim)
153
+ Spacer()
154
+ }
155
+ }
156
+
157
+ // MARK: - Activity Summary
158
+
159
+ private var summaryView: some View {
160
+ ScrollView {
161
+ VStack(alignment: .leading, spacing: 14) {
162
+ if let summary = state.activitySummary {
163
+ // Windows by app
164
+ summarySection("WINDOWS", icon: "macwindow", count: summary.totalWindows) {
165
+ ForEach(summary.windowsByApp) { app in
166
+ HStack {
167
+ Text(app.appName)
168
+ .font(Typo.mono(11))
169
+ .foregroundColor(Palette.textDim)
170
+ .lineLimit(1)
171
+ Spacer()
172
+ Text("\(app.count)")
173
+ .font(Typo.monoBold(11))
174
+ .foregroundColor(Palette.text)
175
+ }
176
+ }
177
+ }
178
+
179
+ // Sessions
180
+ if !summary.sessions.isEmpty {
181
+ summarySection("TMUX SESSIONS", icon: "terminal", count: summary.sessions.count) {
182
+ ForEach(summary.sessions) { session in
183
+ HStack {
184
+ Circle()
185
+ .fill(session.attached ? Palette.running : Palette.textMuted)
186
+ .frame(width: 6, height: 6)
187
+ Text(session.name)
188
+ .font(Typo.mono(11))
189
+ .foregroundColor(Palette.textDim)
190
+ Spacer()
191
+ Text("\(session.paneCount) panes")
192
+ .font(Typo.mono(10))
193
+ .foregroundColor(Palette.textMuted)
194
+ }
195
+ }
196
+ }
197
+ }
198
+
199
+ // Processes
200
+ if !summary.interestingProcesses.isEmpty {
201
+ summarySection("PROCESSES", icon: "gearshape", count: summary.interestingProcesses.count) {
202
+ ForEach(Array(summary.interestingProcesses.prefix(10).enumerated()), id: \.offset) { _, proc in
203
+ HStack {
204
+ Text(proc.comm)
205
+ .font(Typo.monoBold(11))
206
+ .foregroundColor(Palette.textDim)
207
+ if let cwd = proc.cwd {
208
+ Text(cwd.replacingOccurrences(of: NSHomeDirectory(), with: "~"))
209
+ .font(Typo.mono(10))
210
+ .foregroundColor(Palette.textMuted)
211
+ .lineLimit(1)
212
+ }
213
+ Spacer()
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+ // OCR info
220
+ if summary.ocrWindowCount > 0 {
221
+ HStack(spacing: 6) {
222
+ Image(systemName: "doc.text.magnifyingglass")
223
+ .font(.system(size: 10))
224
+ .foregroundColor(Palette.textMuted)
225
+ Text("OCR: \(summary.ocrWindowCount) windows scanned")
226
+ .font(Typo.mono(10))
227
+ .foregroundColor(Palette.textMuted)
228
+ if let t = summary.lastOcrScan {
229
+ Spacer()
230
+ Text(relativeTime(t))
231
+ .font(Typo.mono(9))
232
+ .foregroundColor(Palette.textMuted)
233
+ }
234
+ }
235
+ .padding(.horizontal, 14)
236
+ }
237
+ } else {
238
+ Text("Loading...")
239
+ .font(Typo.mono(11))
240
+ .foregroundColor(Palette.textMuted)
241
+ .padding(14)
242
+ }
243
+ }
244
+ .padding(.vertical, 10)
245
+ }
246
+ }
247
+
248
+ private func summarySection<Content: View>(
249
+ _ title: String,
250
+ icon: String,
251
+ count: Int,
252
+ @ViewBuilder content: () -> Content
253
+ ) -> some View {
254
+ VStack(alignment: .leading, spacing: 6) {
255
+ HStack(spacing: 6) {
256
+ Image(systemName: icon)
257
+ .font(.system(size: 10, weight: .medium))
258
+ .foregroundColor(Palette.textMuted)
259
+ Text(title)
260
+ .font(Typo.caption(9))
261
+ .foregroundColor(Palette.textMuted)
262
+ Text("\(count)")
263
+ .font(Typo.monoBold(9))
264
+ .foregroundColor(Palette.running)
265
+ .padding(.horizontal, 4)
266
+ .padding(.vertical, 1)
267
+ .background(
268
+ RoundedRectangle(cornerRadius: 3)
269
+ .fill(Palette.running.opacity(0.12))
270
+ )
271
+ Spacer()
272
+ }
273
+ .padding(.horizontal, 14)
274
+
275
+ VStack(spacing: 3) {
276
+ content()
277
+ }
278
+ .padding(.horizontal, 14)
279
+ }
280
+ }
281
+
282
+ private func relativeTime(_ date: Date) -> String {
283
+ let seconds = Int(Date().timeIntervalSince(date))
284
+ if seconds < 60 { return "\(seconds)s ago" }
285
+ if seconds < 3600 { return "\(seconds / 60)m ago" }
286
+ return "\(seconds / 3600)h ago"
287
+ }
288
+ }
@@ -0,0 +1,105 @@
1
+ import AppKit
2
+ import SwiftUI
3
+
4
+ final class OmniSearchWindow {
5
+ static let shared = OmniSearchWindow()
6
+
7
+ private var panel: NSPanel?
8
+ private var keyMonitor: Any?
9
+ private var state: OmniSearchState?
10
+
11
+ var isVisible: Bool { panel?.isVisible ?? false }
12
+
13
+ func toggle() {
14
+ if isVisible {
15
+ dismiss()
16
+ } else {
17
+ show()
18
+ }
19
+ }
20
+
21
+ func show() {
22
+ if let p = panel, p.isVisible {
23
+ p.makeKeyAndOrderFront(nil)
24
+ NSApp.activate(ignoringOtherApps: true)
25
+ return
26
+ }
27
+
28
+ // Fresh state each time
29
+ let searchState = OmniSearchState()
30
+ state = searchState
31
+
32
+ let view = OmniSearchView(state: searchState) { [weak self] in
33
+ self?.dismiss()
34
+ }
35
+ .preferredColorScheme(.dark)
36
+
37
+ let hosting = NSHostingController(rootView: view)
38
+ hosting.preferredContentSize = NSSize(width: 520, height: 480)
39
+
40
+ let p = NSPanel(
41
+ contentRect: NSRect(x: 0, y: 0, width: 520, height: 480),
42
+ styleMask: [.titled, .closable, .resizable, .utilityWindow, .nonactivatingPanel],
43
+ backing: .buffered,
44
+ defer: false
45
+ )
46
+ p.contentViewController = hosting
47
+ p.title = "Omni Search"
48
+ p.titlebarAppearsTransparent = true
49
+ p.titleVisibility = .hidden
50
+ p.isMovableByWindowBackground = true
51
+ p.level = .floating
52
+ p.isOpaque = false
53
+ p.backgroundColor = NSColor(red: 0.11, green: 0.11, blue: 0.12, alpha: 1.0)
54
+ p.hasShadow = true
55
+ p.appearance = NSAppearance(named: .darkAqua)
56
+ p.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
57
+ p.minSize = NSSize(width: 400, height: 300)
58
+ p.maxSize = NSSize(width: 700, height: 700)
59
+
60
+ // Center on screen
61
+ if let screen = NSScreen.main {
62
+ let visibleFrame = screen.visibleFrame
63
+ let x = visibleFrame.midX - 260
64
+ let y = visibleFrame.midY + 60 // slightly above center
65
+ p.setFrameOrigin(NSPoint(x: x, y: y))
66
+ }
67
+
68
+ p.makeKeyAndOrderFront(nil)
69
+ NSApp.activate(ignoringOtherApps: true)
70
+ panel = p
71
+
72
+ // Key monitor: Escape → dismiss, arrow keys → navigate, Enter → activate
73
+ keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
74
+ guard self?.panel?.isKeyWindow == true else { return event }
75
+
76
+ switch event.keyCode {
77
+ case 53: // Escape
78
+ self?.dismiss()
79
+ return nil
80
+ case 125: // ↓
81
+ self?.state?.moveSelection(1)
82
+ return nil
83
+ case 126: // ↑
84
+ self?.state?.moveSelection(-1)
85
+ return nil
86
+ case 36: // Enter
87
+ self?.state?.activateSelected()
88
+ self?.dismiss()
89
+ return nil
90
+ default:
91
+ return event
92
+ }
93
+ }
94
+ }
95
+
96
+ func dismiss() {
97
+ panel?.orderOut(nil)
98
+ panel = nil
99
+ state = nil
100
+ if let monitor = keyMonitor {
101
+ NSEvent.removeMonitor(monitor)
102
+ keyMonitor = nil
103
+ }
104
+ }
105
+ }
@@ -0,0 +1,129 @@
1
+ import SwiftUI
2
+
3
+ struct OrphanRow: View {
4
+ let session: TmuxSession
5
+ var onAttach: () -> Void
6
+ var onKill: () -> Void
7
+
8
+ @State private var isHovered = false
9
+ @State private var isExpanded = false
10
+
11
+ private var commandSummary: String {
12
+ let commands = session.panes
13
+ .map(\.currentCommand)
14
+ .filter { !$0.isEmpty }
15
+ let unique = commands.count <= 3 ? commands : Array(commands.prefix(3)) + ["..."]
16
+ return "\(session.panes.count) pane\(session.panes.count == 1 ? "" : "s") \u{2014} \(unique.joined(separator: ", "))"
17
+ }
18
+
19
+ var body: some View {
20
+ VStack(spacing: 0) {
21
+ // Header row
22
+ HStack(spacing: 10) {
23
+ // Status bar — amber for orphan
24
+ RoundedRectangle(cornerRadius: 1)
25
+ .fill(Palette.detach)
26
+ .frame(width: 3, height: 32)
27
+
28
+ // Expand chevron
29
+ Button {
30
+ withAnimation(.easeOut(duration: 0.15)) { isExpanded.toggle() }
31
+ } label: {
32
+ Image(systemName: isExpanded ? "chevron.down" : "chevron.right")
33
+ .font(.system(size: 9, weight: .semibold))
34
+ .foregroundColor(Palette.textMuted)
35
+ .frame(width: 14)
36
+ }
37
+ .buttonStyle(.plain)
38
+
39
+ // Info
40
+ VStack(alignment: .leading, spacing: 3) {
41
+ HStack(spacing: 6) {
42
+ Text(session.name)
43
+ .font(Typo.heading(13))
44
+ .foregroundColor(Palette.text)
45
+ .lineLimit(1)
46
+
47
+ if session.attached {
48
+ Text("attached")
49
+ .font(Typo.mono(9))
50
+ .foregroundColor(Palette.detach)
51
+ .padding(.horizontal, 5)
52
+ .padding(.vertical, 1)
53
+ .background(
54
+ RoundedRectangle(cornerRadius: 3)
55
+ .fill(Palette.detach.opacity(0.12))
56
+ )
57
+ }
58
+ }
59
+
60
+ Text(commandSummary)
61
+ .font(Typo.mono(10))
62
+ .foregroundColor(Palette.textMuted)
63
+ .lineLimit(1)
64
+ }
65
+
66
+ Spacer()
67
+
68
+ // Actions
69
+ HStack(spacing: 4) {
70
+ Button(action: onKill) {
71
+ Text("Kill")
72
+ .angularButton(Palette.kill, filled: false)
73
+ }
74
+ .buttonStyle(.plain)
75
+
76
+ Button(action: onAttach) {
77
+ Text("Attach")
78
+ .angularButton(Palette.running)
79
+ }
80
+ .buttonStyle(.plain)
81
+ }
82
+ }
83
+ .padding(.horizontal, 10)
84
+ .padding(.vertical, 8)
85
+ .glassCard(hovered: isHovered)
86
+
87
+ // Expanded pane list
88
+ if isExpanded {
89
+ VStack(spacing: 2) {
90
+ ForEach(session.panes) { pane in
91
+ paneRow(pane)
92
+ }
93
+ }
94
+ .padding(.leading, 36)
95
+ .padding(.trailing, 10)
96
+ .padding(.vertical, 4)
97
+ .transition(.opacity.combined(with: .move(edge: .top)))
98
+ }
99
+ }
100
+ .contentShape(Rectangle())
101
+ .onHover { isHovered = $0 }
102
+ .contextMenu {
103
+ Button("Attach") { onAttach() }
104
+ Divider()
105
+ Button("Kill Session") { onKill() }
106
+ }
107
+ }
108
+
109
+ private func paneRow(_ pane: TmuxPane) -> some View {
110
+ HStack(spacing: 8) {
111
+ Circle()
112
+ .fill(pane.isActive ? Palette.detach.opacity(0.7) : Palette.textMuted)
113
+ .frame(width: 5, height: 5)
114
+
115
+ Text(pane.title.isEmpty ? pane.currentCommand : pane.title)
116
+ .font(Typo.mono(11))
117
+ .foregroundColor(Palette.text)
118
+ .lineLimit(1)
119
+
120
+ Spacer()
121
+
122
+ Text(pane.currentCommand)
123
+ .font(Typo.mono(9))
124
+ .foregroundColor(Palette.textDim)
125
+ }
126
+ .padding(.horizontal, 8)
127
+ .padding(.vertical, 4)
128
+ }
129
+ }