@lattices/cli 0.3.0 → 0.4.1

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 (111) hide show
  1. package/README.md +85 -9
  2. package/app/Info.plist +30 -0
  3. package/app/Lattices.app/Contents/Info.plist +8 -2
  4. package/app/Lattices.app/Contents/MacOS/Lattices +0 -0
  5. package/app/Lattices.app/Contents/Resources/AppIcon.icns +0 -0
  6. package/app/Lattices.app/Contents/Resources/tap.wav +0 -0
  7. package/app/Lattices.app/Contents/_CodeSignature/CodeResources +139 -0
  8. package/app/Lattices.entitlements +15 -0
  9. package/app/Package.swift +8 -1
  10. package/app/Resources/tap.wav +0 -0
  11. package/app/Sources/AdvisorLearningStore.swift +90 -0
  12. package/app/Sources/AgentSession.swift +377 -0
  13. package/app/Sources/AppDelegate.swift +45 -12
  14. package/app/Sources/AppShellView.swift +81 -8
  15. package/app/Sources/AudioProvider.swift +386 -0
  16. package/app/Sources/CheatSheetHUD.swift +261 -19
  17. package/app/Sources/DaemonProtocol.swift +13 -0
  18. package/app/Sources/DaemonServer.swift +8 -0
  19. package/app/Sources/DesktopModel.swift +189 -6
  20. package/app/Sources/DesktopModelTypes.swift +2 -0
  21. package/app/Sources/DiagnosticLog.swift +104 -2
  22. package/app/Sources/EventBus.swift +1 -0
  23. package/app/Sources/HUDBottomBar.swift +279 -0
  24. package/app/Sources/HUDController.swift +1158 -0
  25. package/app/Sources/HUDLeftBar.swift +849 -0
  26. package/app/Sources/HUDMinimap.swift +179 -0
  27. package/app/Sources/HUDRightBar.swift +774 -0
  28. package/app/Sources/HUDState.swift +367 -0
  29. package/app/Sources/HUDTopBar.swift +243 -0
  30. package/app/Sources/HandsOffSession.swift +802 -0
  31. package/app/Sources/HomeDashboardView.swift +125 -0
  32. package/app/Sources/HotkeyManager.swift +2 -0
  33. package/app/Sources/HotkeyStore.swift +49 -9
  34. package/app/Sources/IntentEngine.swift +962 -0
  35. package/app/Sources/Intents/CreateLayerIntent.swift +54 -0
  36. package/app/Sources/Intents/DistributeIntent.swift +56 -0
  37. package/app/Sources/Intents/FocusIntent.swift +69 -0
  38. package/app/Sources/Intents/HelpIntent.swift +41 -0
  39. package/app/Sources/Intents/KillIntent.swift +47 -0
  40. package/app/Sources/Intents/LatticeIntent.swift +78 -0
  41. package/app/Sources/Intents/LaunchIntent.swift +67 -0
  42. package/app/Sources/Intents/ListSessionsIntent.swift +32 -0
  43. package/app/Sources/Intents/ListWindowsIntent.swift +30 -0
  44. package/app/Sources/Intents/ScanIntent.swift +52 -0
  45. package/app/Sources/Intents/SearchIntent.swift +190 -0
  46. package/app/Sources/Intents/SwitchLayerIntent.swift +50 -0
  47. package/app/Sources/Intents/TileIntent.swift +61 -0
  48. package/app/Sources/LatticesApi.swift +1275 -30
  49. package/app/Sources/LauncherHUD.swift +348 -0
  50. package/app/Sources/MainView.swift +147 -44
  51. package/app/Sources/MouseFinder.swift +222 -0
  52. package/app/Sources/OcrModel.swift +34 -1
  53. package/app/Sources/OmniSearchState.swift +99 -102
  54. package/app/Sources/OnboardingView.swift +457 -0
  55. package/app/Sources/PermissionChecker.swift +2 -12
  56. package/app/Sources/PiChatDock.swift +454 -0
  57. package/app/Sources/PiChatSession.swift +815 -0
  58. package/app/Sources/PiWorkspaceView.swift +364 -0
  59. package/app/Sources/PlacementSpec.swift +195 -0
  60. package/app/Sources/Preferences.swift +59 -0
  61. package/app/Sources/ProjectScanner.swift +58 -45
  62. package/app/Sources/ScreenMapState.swift +701 -55
  63. package/app/Sources/ScreenMapView.swift +843 -103
  64. package/app/Sources/ScreenMapWindowController.swift +22 -0
  65. package/app/Sources/SessionLayerStore.swift +285 -0
  66. package/app/Sources/SessionManager.swift +4 -1
  67. package/app/Sources/SettingsView.swift +186 -3
  68. package/app/Sources/Theme.swift +9 -8
  69. package/app/Sources/TmuxModel.swift +7 -0
  70. package/app/Sources/TmuxQuery.swift +27 -3
  71. package/app/Sources/VoiceChatView.swift +192 -0
  72. package/app/Sources/VoiceCommandWindow.swift +1594 -0
  73. package/app/Sources/VoiceIntentResolver.swift +671 -0
  74. package/app/Sources/VoxClient.swift +454 -0
  75. package/app/Sources/WindowTiler.swift +348 -87
  76. package/app/Sources/WorkspaceManager.swift +127 -18
  77. package/app/Tests/StageDragTests.swift +333 -0
  78. package/app/Tests/StageJoinTests.swift +313 -0
  79. package/app/Tests/StageManagerTests.swift +280 -0
  80. package/app/Tests/StageTileTests.swift +353 -0
  81. package/assets/AppIcon.icns +0 -0
  82. package/bin/client.ts +16 -0
  83. package/bin/{daemon-client.js → daemon-client.ts} +49 -30
  84. package/bin/handsoff-infer.ts +280 -0
  85. package/bin/handsoff-worker.ts +740 -0
  86. package/bin/lattices-app.ts +338 -0
  87. package/bin/lattices-dev +208 -0
  88. package/bin/{lattices.js → lattices.ts} +777 -140
  89. package/bin/project-twin.ts +645 -0
  90. package/docs/agent-execution-plan.md +562 -0
  91. package/docs/agent-layer-guide.md +207 -0
  92. package/docs/agents.md +142 -0
  93. package/docs/api.md +153 -34
  94. package/docs/app.md +29 -1
  95. package/docs/config.md +5 -1
  96. package/docs/handsoff-test-scenarios.md +84 -0
  97. package/docs/layers.md +20 -20
  98. package/docs/ocr.md +14 -5
  99. package/docs/overview.md +5 -1
  100. package/docs/presentation-execution-review.md +491 -0
  101. package/docs/prompts/hands-off-system.md +374 -0
  102. package/docs/prompts/hands-off-turn.md +30 -0
  103. package/docs/prompts/voice-advisor.md +31 -0
  104. package/docs/prompts/voice-fallback.md +23 -0
  105. package/docs/tiling-reference.md +167 -0
  106. package/docs/twins.md +138 -0
  107. package/docs/voice-command-protocol.md +278 -0
  108. package/docs/voice.md +219 -0
  109. package/package.json +29 -11
  110. package/bin/client.js +0 -4
  111. package/bin/lattices-app.js +0 -221
@@ -0,0 +1,457 @@
1
+ import SwiftUI
2
+ import AppKit
3
+
4
+ // MARK: - Onboarding Flow
5
+
6
+ /// A step-by-step welcome screen shown on first launch.
7
+ /// Walks the user through granting Accessibility, Screen Recording,
8
+ /// choosing a project root, and optionally installing tmux.
9
+ struct OnboardingView: View {
10
+ @ObservedObject private var permChecker = PermissionChecker.shared
11
+ @ObservedObject private var prefs = Preferences.shared
12
+ @ObservedObject private var tmux = TmuxModel.shared
13
+ @State private var step: Step = .welcome
14
+ var onComplete: () -> Void
15
+
16
+ enum Step: Int, CaseIterable {
17
+ case welcome
18
+ case accessibility
19
+ case screenRecording
20
+ case projectRoot
21
+ case tmux
22
+ case done
23
+ }
24
+
25
+ var body: some View {
26
+ VStack(spacing: 0) {
27
+ // Progress dots
28
+ HStack(spacing: 6) {
29
+ ForEach(Step.allCases, id: \.rawValue) { s in
30
+ Circle()
31
+ .fill(s.rawValue <= step.rawValue ? Color.white : Color.white.opacity(0.20))
32
+ .frame(width: 6, height: 6)
33
+ }
34
+ }
35
+ .padding(.top, 28)
36
+ .padding(.bottom, 24)
37
+
38
+ // Step content
39
+ Group {
40
+ switch step {
41
+ case .welcome: welcomeStep
42
+ case .accessibility: accessibilityStep
43
+ case .screenRecording: screenRecordingStep
44
+ case .projectRoot: projectRootStep
45
+ case .tmux: tmuxStep
46
+ case .done: doneStep
47
+ }
48
+ }
49
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
50
+ .padding(.horizontal, 40)
51
+
52
+ Spacer(minLength: 0)
53
+
54
+ // Navigation
55
+ HStack {
56
+ if step != .welcome {
57
+ Button("Back") { withAnimation(.easeInOut(duration: 0.2)) { goBack() } }
58
+ .buttonStyle(.plain)
59
+ .font(Typo.mono(11))
60
+ .foregroundColor(Palette.textMuted)
61
+ }
62
+ Spacer()
63
+ if step == .done {
64
+ Button(action: { onComplete() }) {
65
+ Text("Get started")
66
+ .angularButton(Palette.running)
67
+ }
68
+ .buttonStyle(.plain)
69
+ } else {
70
+ Button(action: { withAnimation(.easeInOut(duration: 0.2)) { advance() } }) {
71
+ Text(nextLabel)
72
+ .angularButton(.white)
73
+ }
74
+ .buttonStyle(.plain)
75
+ }
76
+ }
77
+ .padding(.horizontal, 40)
78
+ .padding(.bottom, 28)
79
+ }
80
+ .frame(width: 480, height: 420)
81
+ .background(Palette.bg)
82
+ .preferredColorScheme(.dark)
83
+ }
84
+
85
+ // MARK: - Steps
86
+
87
+ private var welcomeStep: some View {
88
+ VStack(spacing: 16) {
89
+ latticesIcon
90
+ Text("Welcome to Lattices")
91
+ .font(Typo.title(18))
92
+ .foregroundColor(Palette.text)
93
+ Text("Workspace control plane for macOS.\nLet's get you set up in under a minute.")
94
+ .font(Typo.body(12))
95
+ .foregroundColor(Palette.textDim)
96
+ .multilineTextAlignment(.center)
97
+ .lineSpacing(3)
98
+ }
99
+ }
100
+
101
+ private var accessibilityStep: some View {
102
+ permissionStep(
103
+ icon: "hand.raised.fill",
104
+ title: "Accessibility",
105
+ description: "Lattices needs Accessibility access to read window titles, move and resize windows, and tile your workspace.",
106
+ granted: permChecker.accessibility,
107
+ action: { permChecker.requestAccessibility() }
108
+ )
109
+ }
110
+
111
+ private var screenRecordingStep: some View {
112
+ permissionStep(
113
+ icon: "rectangle.dashed.badge.record",
114
+ title: "Screen Recording",
115
+ description: "Allows Lattices to index on-screen text with OCR so you can search across all your windows.",
116
+ granted: permChecker.screenRecording,
117
+ action: { permChecker.requestScreenRecording() }
118
+ )
119
+ }
120
+
121
+ private var projectRootStep: some View {
122
+ VStack(spacing: 16) {
123
+ Image(systemName: "folder.fill")
124
+ .font(.system(size: 28))
125
+ .foregroundColor(.white.opacity(0.7))
126
+
127
+ Text("Project directory")
128
+ .font(Typo.title(16))
129
+ .foregroundColor(Palette.text)
130
+
131
+ Text("Where do your projects live? Lattices scans this folder to find workspaces.")
132
+ .font(Typo.body(12))
133
+ .foregroundColor(Palette.textDim)
134
+ .multilineTextAlignment(.center)
135
+ .lineSpacing(3)
136
+
137
+ HStack(spacing: 8) {
138
+ Text(prefs.scanRoot.isEmpty ? "Not set" : abbreviatePath(prefs.scanRoot))
139
+ .font(Typo.mono(11))
140
+ .foregroundColor(prefs.scanRoot.isEmpty ? Palette.textMuted : Palette.text)
141
+ .lineLimit(1)
142
+ .truncationMode(.middle)
143
+ .frame(maxWidth: .infinity, alignment: .leading)
144
+ .padding(.horizontal, 10)
145
+ .padding(.vertical, 8)
146
+ .background(
147
+ RoundedRectangle(cornerRadius: 5)
148
+ .fill(Palette.surface)
149
+ .overlay(
150
+ RoundedRectangle(cornerRadius: 5)
151
+ .strokeBorder(Palette.border, lineWidth: 0.5)
152
+ )
153
+ )
154
+
155
+ Button("Browse") {
156
+ let panel = NSOpenPanel()
157
+ panel.canChooseFiles = false
158
+ panel.canChooseDirectories = true
159
+ panel.allowsMultipleSelection = false
160
+ panel.directoryURL = URL(fileURLWithPath: prefs.scanRoot.isEmpty ? NSHomeDirectory() : prefs.scanRoot)
161
+ if panel.runModal() == .OK, let url = panel.url {
162
+ prefs.scanRoot = url.path
163
+ }
164
+ }
165
+ .buttonStyle(.plain)
166
+ .font(Typo.monoBold(10))
167
+ .foregroundColor(.white)
168
+ .padding(.horizontal, 10)
169
+ .padding(.vertical, 6)
170
+ .background(
171
+ RoundedRectangle(cornerRadius: 5)
172
+ .fill(Palette.surface)
173
+ .overlay(
174
+ RoundedRectangle(cornerRadius: 5)
175
+ .strokeBorder(Palette.borderLit, lineWidth: 0.5)
176
+ )
177
+ )
178
+ }
179
+
180
+ if !prefs.scanRoot.isEmpty {
181
+ HStack(spacing: 4) {
182
+ Image(systemName: "checkmark.circle.fill")
183
+ .font(.system(size: 10))
184
+ .foregroundColor(Palette.running)
185
+ Text(abbreviatePath(prefs.scanRoot))
186
+ .font(Typo.mono(10))
187
+ .foregroundColor(Palette.running)
188
+ }
189
+ }
190
+ }
191
+ }
192
+
193
+ private var tmuxStep: some View {
194
+ VStack(spacing: 16) {
195
+ Image(systemName: "terminal.fill")
196
+ .font(.system(size: 28))
197
+ .foregroundColor(.white.opacity(0.7))
198
+
199
+ Text("Terminal sessions")
200
+ .font(Typo.title(16))
201
+ .foregroundColor(Palette.text)
202
+
203
+ if tmux.isAvailable {
204
+ HStack(spacing: 6) {
205
+ Image(systemName: "checkmark.circle.fill")
206
+ .foregroundColor(Palette.running)
207
+ Text("tmux is installed")
208
+ .font(Typo.mono(12))
209
+ .foregroundColor(Palette.running)
210
+ }
211
+ Text("Lattices can manage tmux sessions, pane layouts, and terminal workspaces for you.")
212
+ .font(Typo.body(12))
213
+ .foregroundColor(Palette.textDim)
214
+ .multilineTextAlignment(.center)
215
+ .lineSpacing(3)
216
+ } else {
217
+ Text("tmux is optional but recommended. It enables managed terminal sessions with persistent pane layouts.")
218
+ .font(Typo.body(12))
219
+ .foregroundColor(Palette.textDim)
220
+ .multilineTextAlignment(.center)
221
+ .lineSpacing(3)
222
+
223
+ Button(action: {
224
+ let task = Process()
225
+ task.executableURL = URL(fileURLWithPath: "/bin/zsh")
226
+ task.arguments = ["-lc", "brew install tmux"]
227
+ task.standardOutput = FileHandle.nullDevice
228
+ task.standardError = FileHandle.nullDevice
229
+ try? task.run()
230
+ }) {
231
+ HStack(spacing: 6) {
232
+ Image(systemName: "arrow.down.circle")
233
+ .font(.system(size: 11))
234
+ Text("brew install tmux")
235
+ .font(Typo.monoBold(11))
236
+ }
237
+ .angularButton(.white, filled: false)
238
+ }
239
+ .buttonStyle(.plain)
240
+
241
+ Text("You can always install it later. Window tiling, search, and OCR work without tmux.")
242
+ .font(Typo.mono(10))
243
+ .foregroundColor(Palette.textMuted)
244
+ .multilineTextAlignment(.center)
245
+ .lineSpacing(2)
246
+ }
247
+ }
248
+ }
249
+
250
+ private var doneStep: some View {
251
+ VStack(spacing: 16) {
252
+ Image(systemName: "checkmark.circle.fill")
253
+ .font(.system(size: 36))
254
+ .foregroundColor(Palette.running)
255
+
256
+ Text("You're all set")
257
+ .font(Typo.title(18))
258
+ .foregroundColor(Palette.text)
259
+
260
+ VStack(alignment: .leading, spacing: 8) {
261
+ statusRow("Accessibility", granted: permChecker.accessibility)
262
+ statusRow("Screen Recording", granted: permChecker.screenRecording)
263
+ statusRow("Project root", granted: !prefs.scanRoot.isEmpty,
264
+ detail: prefs.scanRoot.isEmpty ? "not set" : abbreviatePath(prefs.scanRoot))
265
+ statusRow("tmux", granted: tmux.isAvailable,
266
+ detail: tmux.isAvailable ? "installed" : "skipped")
267
+ }
268
+ .padding(16)
269
+ .background(
270
+ RoundedRectangle(cornerRadius: 6)
271
+ .fill(Palette.surface)
272
+ .overlay(
273
+ RoundedRectangle(cornerRadius: 6)
274
+ .strokeBorder(Palette.border, lineWidth: 0.5)
275
+ )
276
+ )
277
+ }
278
+ }
279
+
280
+ // MARK: - Shared helpers
281
+
282
+ private func permissionStep(icon: String, title: String, description: String, granted: Bool, action: @escaping () -> Void) -> some View {
283
+ VStack(spacing: 16) {
284
+ Image(systemName: icon)
285
+ .font(.system(size: 28))
286
+ .foregroundColor(.white.opacity(0.7))
287
+
288
+ Text(title)
289
+ .font(Typo.title(16))
290
+ .foregroundColor(Palette.text)
291
+
292
+ Text(description)
293
+ .font(Typo.body(12))
294
+ .foregroundColor(Palette.textDim)
295
+ .multilineTextAlignment(.center)
296
+ .lineSpacing(3)
297
+
298
+ if granted {
299
+ HStack(spacing: 6) {
300
+ Image(systemName: "checkmark.circle.fill")
301
+ .foregroundColor(Palette.running)
302
+ Text("Granted")
303
+ .font(Typo.monoBold(11))
304
+ .foregroundColor(Palette.running)
305
+ }
306
+ } else {
307
+ Button(action: action) {
308
+ Text("Grant \(title)")
309
+ .angularButton(.white, filled: false)
310
+ }
311
+ .buttonStyle(.plain)
312
+
313
+ Text("macOS will ask you to toggle this on in System Settings.")
314
+ .font(Typo.mono(10))
315
+ .foregroundColor(Palette.textMuted)
316
+ .multilineTextAlignment(.center)
317
+ }
318
+ }
319
+ }
320
+
321
+ private func statusRow(_ label: String, granted: Bool, detail: String? = nil) -> some View {
322
+ HStack(spacing: 8) {
323
+ Image(systemName: granted ? "checkmark.circle.fill" : "circle")
324
+ .font(.system(size: 11))
325
+ .foregroundColor(granted ? Palette.running : Palette.detach)
326
+ Text(label)
327
+ .font(Typo.mono(11))
328
+ .foregroundColor(Palette.text)
329
+ Spacer()
330
+ if let detail {
331
+ Text(detail)
332
+ .font(Typo.mono(10))
333
+ .foregroundColor(granted ? Palette.textDim : Palette.detach)
334
+ } else {
335
+ Text(granted ? "granted" : "not set")
336
+ .font(Typo.mono(10))
337
+ .foregroundColor(granted ? Palette.running : Palette.detach)
338
+ }
339
+ }
340
+ }
341
+
342
+ private var latticesIcon: some View {
343
+ // 3x3 grid — L-shape pattern
344
+ let cells = [true, false, false, true, false, false, true, true, true]
345
+ let size: CGFloat = 40
346
+ let pad: CGFloat = 4
347
+ let gap: CGFloat = 2.5
348
+ let cell = (size - 2 * pad - 2 * gap) / 3
349
+ return Canvas { context, _ in
350
+ for (i, bright) in cells.enumerated() {
351
+ let row = i / 3
352
+ let col = i % 3
353
+ let rect = CGRect(
354
+ x: pad + CGFloat(col) * (cell + gap),
355
+ y: pad + CGFloat(row) * (cell + gap),
356
+ width: cell, height: cell
357
+ )
358
+ context.fill(
359
+ RoundedRectangle(cornerRadius: 2).path(in: rect),
360
+ with: .color(bright ? .white : .white.opacity(0.18))
361
+ )
362
+ }
363
+ }
364
+ .frame(width: size, height: size)
365
+ }
366
+
367
+ // MARK: - Navigation
368
+
369
+ private var nextLabel: String {
370
+ switch step {
371
+ case .accessibility where !permChecker.accessibility: return "Continue anyway"
372
+ case .screenRecording where !permChecker.screenRecording: return "Continue anyway"
373
+ case .projectRoot where prefs.scanRoot.isEmpty: return "Skip for now"
374
+ case .tmux where !tmux.isAvailable: return "Skip"
375
+ default: return "Continue"
376
+ }
377
+ }
378
+
379
+ private func advance() {
380
+ guard let next = Step(rawValue: step.rawValue + 1) else { return }
381
+ step = next
382
+ }
383
+
384
+ private func goBack() {
385
+ guard let prev = Step(rawValue: step.rawValue - 1) else { return }
386
+ step = prev
387
+ }
388
+
389
+ private func abbreviatePath(_ path: String) -> String {
390
+ let home = NSHomeDirectory()
391
+ if path.hasPrefix(home) {
392
+ return "~" + path.dropFirst(home.count)
393
+ }
394
+ return path
395
+ }
396
+ }
397
+
398
+ // MARK: - Window Controller
399
+
400
+ final class OnboardingWindowController {
401
+ static let shared = OnboardingWindowController()
402
+
403
+ private var window: NSWindow?
404
+ private static let completedKey = "onboarding.completed"
405
+
406
+ var hasCompleted: Bool {
407
+ UserDefaults.standard.bool(forKey: Self.completedKey)
408
+ }
409
+
410
+ /// Show the onboarding window if not yet completed.
411
+ /// Returns true if onboarding was shown.
412
+ @discardableResult
413
+ func showIfNeeded() -> Bool {
414
+ guard !hasCompleted else { return false }
415
+ show()
416
+ return true
417
+ }
418
+
419
+ func show() {
420
+ if let w = window {
421
+ w.makeKeyAndOrderFront(nil)
422
+ NSApp.activate(ignoringOtherApps: true)
423
+ return
424
+ }
425
+
426
+ let view = OnboardingView {
427
+ self.complete()
428
+ }
429
+
430
+ let w = AppWindowShell.makeWindow(
431
+ config: .init(
432
+ title: "Welcome to Lattices",
433
+ titleVisible: false,
434
+ initialSize: NSSize(width: 480, height: 420),
435
+ minSize: NSSize(width: 480, height: 420),
436
+ maxSize: NSSize(width: 480, height: 420),
437
+ miniaturizable: false
438
+ ),
439
+ rootView: view
440
+ )
441
+ w.styleMask.remove(.resizable)
442
+ AppWindowShell.positionCentered(w)
443
+ AppWindowShell.present(w)
444
+ self.window = w
445
+ }
446
+
447
+ private func complete() {
448
+ UserDefaults.standard.set(true, forKey: Self.completedKey)
449
+ window?.orderOut(nil)
450
+ window = nil
451
+ AppDelegate.updateActivationPolicy()
452
+ }
453
+
454
+ func reset() {
455
+ UserDefaults.standard.removeObject(forKey: Self.completedKey)
456
+ }
457
+ }
@@ -13,14 +13,14 @@ final class PermissionChecker: ObservableObject {
13
13
 
14
14
  var allGranted: Bool { accessibility && screenRecording }
15
15
 
16
- /// Check current permission state, prompting on first launch if not granted.
16
+ /// Check current permission state without prompting.
17
17
  func check() {
18
18
  let diag = DiagnosticLog.shared
19
19
 
20
20
  let ax = AXIsProcessTrusted()
21
21
  let sr = CGPreflightScreenCaptureAccess()
22
22
 
23
- // First check: log identity info and prompt if needed
23
+ // First check: log identity info only
24
24
  if !hasLoggedInitial {
25
25
  hasLoggedInitial = true
26
26
  let bundleId = Bundle.main.bundleIdentifier ?? "<no bundle id>"
@@ -30,16 +30,6 @@ final class PermissionChecker: ObservableObject {
30
30
  diag.info("PermissionChecker: exec=\(execPath)")
31
31
  diag.info("AXIsProcessTrusted() → \(ax)")
32
32
  diag.info("CGPreflightScreenCaptureAccess() → \(sr)")
33
-
34
- // Prompt for missing permissions on first check
35
- if !ax {
36
- requestAccessibility()
37
- return
38
- }
39
- if !sr {
40
- requestScreenRecording()
41
- return
42
- }
43
33
  }
44
34
 
45
35
  // Log on state changes