@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,454 @@
1
+ import SwiftUI
2
+
3
+ struct PiChatDock: View {
4
+ @ObservedObject var session: PiChatSession
5
+ @FocusState private var composerFocused: Bool
6
+ @FocusState private var authFieldFocused: Bool
7
+ @State private var resizeStartHeight: CGFloat?
8
+
9
+ private static let timeFormatter: DateFormatter = {
10
+ let formatter = DateFormatter()
11
+ formatter.dateFormat = "HH:mm"
12
+ return formatter
13
+ }()
14
+
15
+ var body: some View {
16
+ VStack(spacing: 0) {
17
+ topHandle
18
+
19
+ if session.isAuthPanelVisible {
20
+ Rectangle()
21
+ .fill(Palette.border)
22
+ .frame(height: 0.5)
23
+
24
+ authPanel
25
+
26
+ Rectangle()
27
+ .fill(Palette.border)
28
+ .frame(height: 0.5)
29
+ }
30
+
31
+ transcript
32
+
33
+ Rectangle()
34
+ .fill(Palette.border)
35
+ .frame(height: 0.5)
36
+
37
+ composer
38
+
39
+ Rectangle()
40
+ .fill(Palette.border)
41
+ .frame(height: 0.5)
42
+
43
+ footerBar
44
+ }
45
+ .frame(maxWidth: .infinity)
46
+ .frame(height: session.dockHeight)
47
+ .background(
48
+ LinearGradient(
49
+ colors: [
50
+ Color.black.opacity(0.96),
51
+ Color(red: 0.02, green: 0.05, blue: 0.03),
52
+ ],
53
+ startPoint: .top,
54
+ endPoint: .bottom
55
+ )
56
+ )
57
+ .overlay(
58
+ RoundedRectangle(cornerRadius: 0)
59
+ .strokeBorder(Palette.border, lineWidth: 0.5)
60
+ )
61
+ .onAppear {
62
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
63
+ composerFocused = true
64
+ }
65
+ }
66
+ }
67
+
68
+ private var topHandle: some View {
69
+ HStack {
70
+ Spacer()
71
+
72
+ Capsule()
73
+ .fill(Palette.borderLit)
74
+ .frame(width: 64, height: 4)
75
+
76
+ Spacer()
77
+
78
+ Button {
79
+ session.isVisible = false
80
+ } label: {
81
+ Image(systemName: "xmark")
82
+ .font(.system(size: 9, weight: .bold))
83
+ .foregroundColor(Palette.textMuted)
84
+ .padding(6)
85
+ .background(
86
+ Circle()
87
+ .fill(Color.white.opacity(0.03))
88
+ .overlay(
89
+ Circle()
90
+ .strokeBorder(Palette.border, lineWidth: 0.5)
91
+ )
92
+ )
93
+ }
94
+ .buttonStyle(.plain)
95
+ }
96
+ .padding(.horizontal, 10)
97
+ .padding(.vertical, 8)
98
+ .contentShape(Rectangle())
99
+ .gesture(
100
+ DragGesture(minimumDistance: 1)
101
+ .onChanged { value in
102
+ if resizeStartHeight == nil {
103
+ resizeStartHeight = session.dockHeight
104
+ }
105
+ let start = resizeStartHeight ?? session.dockHeight
106
+ session.dockHeight = start - value.translation.height
107
+ }
108
+ .onEnded { _ in
109
+ resizeStartHeight = nil
110
+ }
111
+ )
112
+ }
113
+
114
+ private var authPanel: some View {
115
+ VStack(alignment: .leading, spacing: 10) {
116
+ HStack(spacing: 8) {
117
+ Text("provider")
118
+ .font(Typo.geistMonoBold(9))
119
+ .foregroundColor(Palette.textMuted)
120
+
121
+ Picker("Provider", selection: $session.authProviderID) {
122
+ ForEach(session.providerOptions) { provider in
123
+ Text(provider.name).tag(provider.id)
124
+ }
125
+ }
126
+ .labelsHidden()
127
+ .pickerStyle(.menu)
128
+ .font(Typo.mono(10))
129
+
130
+ Spacer()
131
+
132
+ capsuleLabel(
133
+ session.currentProvider.authMode == .oauth ? "OAUTH" : "TOKEN",
134
+ tint: session.currentProvider.authMode == .oauth ? Palette.detach : Palette.running
135
+ )
136
+ }
137
+
138
+ Text(session.currentProvider.helpText)
139
+ .font(Typo.mono(10))
140
+ .foregroundColor(Palette.textDim)
141
+ .fixedSize(horizontal: false, vertical: true)
142
+
143
+ if session.currentProvider.authMode == .apiKey {
144
+ HStack(spacing: 8) {
145
+ SecureField(session.currentProvider.tokenPlaceholder, text: $session.authToken)
146
+ .textFieldStyle(.plain)
147
+ .font(Typo.mono(11))
148
+ .foregroundColor(Palette.text)
149
+ .focused($authFieldFocused)
150
+ .onSubmit {
151
+ session.saveSelectedToken()
152
+ }
153
+
154
+ footerButton("save") {
155
+ session.saveSelectedToken()
156
+ }
157
+
158
+ if session.hasSelectedCredential {
159
+ footerButton("clear") {
160
+ session.removeSelectedCredential()
161
+ }
162
+ }
163
+ }
164
+ .padding(.horizontal, 10)
165
+ .padding(.vertical, 8)
166
+ .background(authCardBackground(tint: Palette.running))
167
+ } else {
168
+ HStack(spacing: 8) {
169
+ footerButton(session.isAuthenticating ? "cancel" : "login") {
170
+ if session.isAuthenticating {
171
+ session.cancelAuthFlow()
172
+ } else {
173
+ session.startSelectedAuthFlow()
174
+ }
175
+ }
176
+
177
+ if session.hasSelectedCredential {
178
+ footerButton("clear") {
179
+ session.removeSelectedCredential()
180
+ }
181
+ }
182
+ }
183
+ .padding(.horizontal, 10)
184
+ .padding(.vertical, 8)
185
+ .background(authCardBackground(tint: session.isAuthenticating ? Palette.detach : Palette.running))
186
+
187
+ if let prompt = session.pendingAuthPrompt {
188
+ HStack(spacing: 8) {
189
+ TextField(prompt.placeholder ?? prompt.message, text: $session.authPromptInput)
190
+ .textFieldStyle(.plain)
191
+ .font(Typo.mono(11))
192
+ .foregroundColor(Palette.text)
193
+ .focused($authFieldFocused)
194
+ .onSubmit {
195
+ session.submitAuthPrompt()
196
+ }
197
+
198
+ footerButton("continue") {
199
+ session.submitAuthPrompt()
200
+ }
201
+ }
202
+ .padding(.horizontal, 10)
203
+ .padding(.vertical, 8)
204
+ .background(authCardBackground(tint: Palette.detach))
205
+ }
206
+ }
207
+
208
+ if let notice = session.authNoticeText, !notice.isEmpty {
209
+ Text(notice)
210
+ .font(Typo.mono(9))
211
+ .foregroundColor(Palette.textDim)
212
+ .fixedSize(horizontal: false, vertical: true)
213
+ }
214
+
215
+ if let error = session.authErrorText, !error.isEmpty {
216
+ Text(error)
217
+ .font(Typo.mono(9))
218
+ .foregroundColor(Palette.kill)
219
+ .fixedSize(horizontal: false, vertical: true)
220
+ }
221
+ }
222
+ .padding(.horizontal, 10)
223
+ .padding(.vertical, 10)
224
+ .background(
225
+ LinearGradient(
226
+ colors: [
227
+ Palette.running.opacity(0.07),
228
+ Color.black.opacity(0.26),
229
+ ],
230
+ startPoint: .topLeading,
231
+ endPoint: .bottomTrailing
232
+ )
233
+ )
234
+ .onAppear {
235
+ focusAuthFieldIfNeeded()
236
+ }
237
+ .onChange(of: session.authProviderID) { _ in
238
+ focusAuthFieldIfNeeded()
239
+ }
240
+ .onChange(of: session.pendingAuthPrompt?.message) { prompt in
241
+ if prompt != nil {
242
+ focusAuthFieldIfNeeded()
243
+ }
244
+ }
245
+ }
246
+
247
+ private var transcript: some View {
248
+ ScrollViewReader { proxy in
249
+ ScrollView(.vertical, showsIndicators: true) {
250
+ LazyVStack(alignment: .leading, spacing: 8) {
251
+ ForEach(session.messages) { message in
252
+ row(message)
253
+ .id(message.id)
254
+ }
255
+ }
256
+ .padding(.horizontal, 10)
257
+ .padding(.vertical, 10)
258
+ }
259
+ .background(Color.black.opacity(0.35))
260
+ .onAppear {
261
+ if let last = session.messages.last?.id {
262
+ proxy.scrollTo(last, anchor: .bottom)
263
+ }
264
+ }
265
+ .onChange(of: session.messages.count) { _ in
266
+ if let last = session.messages.last?.id {
267
+ withAnimation(.easeOut(duration: 0.15)) {
268
+ proxy.scrollTo(last, anchor: .bottom)
269
+ }
270
+ }
271
+ }
272
+ }
273
+ }
274
+
275
+ private func row(_ message: PiChatMessage) -> some View {
276
+ HStack(alignment: .top, spacing: 10) {
277
+ VStack(alignment: .leading, spacing: 4) {
278
+ capsuleLabel(roleLabel(for: message.role).uppercased(), tint: roleColor(for: message.role))
279
+
280
+ Text(timestamp(for: message.timestamp))
281
+ .font(Typo.mono(8))
282
+ .foregroundColor(Palette.textMuted)
283
+ }
284
+ .frame(width: 52, alignment: .leading)
285
+
286
+ VStack(alignment: .leading, spacing: 6) {
287
+ Rectangle()
288
+ .fill(roleColor(for: message.role).opacity(0.9))
289
+ .frame(width: 14, height: 1.5)
290
+
291
+ Text(message.text)
292
+ .font(Typo.mono(11))
293
+ .foregroundColor(Palette.text)
294
+ .textSelection(.enabled)
295
+ .frame(maxWidth: .infinity, alignment: .leading)
296
+ }
297
+ }
298
+ .padding(.horizontal, 10)
299
+ .padding(.vertical, 9)
300
+ .background(
301
+ RoundedRectangle(cornerRadius: 6)
302
+ .fill(roleColor(for: message.role).opacity(message.role == .assistant ? 0.11 : 0.06))
303
+ .overlay(
304
+ RoundedRectangle(cornerRadius: 6)
305
+ .strokeBorder(roleColor(for: message.role).opacity(0.22), lineWidth: 0.5)
306
+ )
307
+ )
308
+ }
309
+
310
+ private var composer: some View {
311
+ HStack(spacing: 10) {
312
+ HStack(spacing: 8) {
313
+ Text(">")
314
+ .font(Typo.geistMonoBold(11))
315
+ .foregroundColor(Palette.running)
316
+
317
+ TextField("Ask Pi something lightweight...", text: $session.draft, axis: .vertical)
318
+ .textFieldStyle(.plain)
319
+ .font(Typo.mono(11))
320
+ .foregroundColor(Palette.text)
321
+ .lineLimit(1...4)
322
+ .focused($composerFocused)
323
+ .onSubmit {
324
+ session.sendDraft()
325
+ }
326
+ }
327
+ .padding(.horizontal, 10)
328
+ .padding(.vertical, 9)
329
+ .background(
330
+ RoundedRectangle(cornerRadius: 6)
331
+ .fill(Color.black.opacity(0.38))
332
+ .overlay(
333
+ RoundedRectangle(cornerRadius: 6)
334
+ .strokeBorder(Palette.running.opacity(0.16), lineWidth: 0.5)
335
+ )
336
+ )
337
+
338
+ footerButton("send", tint: Palette.running) {
339
+ session.sendDraft()
340
+ }
341
+ .disabled(session.isSending)
342
+ }
343
+ .padding(.horizontal, 10)
344
+ .padding(.vertical, 10)
345
+ .background(Color.black.opacity(0.62))
346
+ }
347
+
348
+ private var footerBar: some View {
349
+ HStack(spacing: 8) {
350
+ Circle()
351
+ .fill(session.hasPiBinary ? Palette.running : Palette.kill)
352
+ .frame(width: 6, height: 6)
353
+
354
+ Text("PI DOCK")
355
+ .font(Typo.geistMonoBold(9))
356
+ .foregroundColor(Palette.text)
357
+
358
+ Text(footerStatusText)
359
+ .font(Typo.mono(9))
360
+ .foregroundColor(Palette.textMuted)
361
+ .lineLimit(1)
362
+
363
+ Spacer()
364
+
365
+ footerButton(session.isAuthPanelVisible ? "auth -" : "auth +") {
366
+ session.toggleAuthPanel()
367
+ }
368
+
369
+ footerButton("reset") {
370
+ session.clearConversation()
371
+ }
372
+ }
373
+ .padding(.horizontal, 10)
374
+ .padding(.vertical, 7)
375
+ .background(Color.white.opacity(0.015))
376
+ }
377
+
378
+ private var footerStatusText: String {
379
+ if session.statusText == "idle" {
380
+ return session.currentProvider.name
381
+ }
382
+ return "\(session.currentProvider.name) · \(session.statusText)"
383
+ }
384
+
385
+ private func focusAuthFieldIfNeeded() {
386
+ if session.currentProvider.authMode == .apiKey || session.pendingAuthPrompt != nil {
387
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
388
+ authFieldFocused = true
389
+ }
390
+ }
391
+ }
392
+
393
+ private func roleLabel(for role: PiChatMessage.Role) -> String {
394
+ switch role {
395
+ case .system: return "system"
396
+ case .user: return "you"
397
+ case .assistant: return "pi"
398
+ }
399
+ }
400
+
401
+ private func roleColor(for role: PiChatMessage.Role) -> Color {
402
+ switch role {
403
+ case .system: return Palette.detach
404
+ case .user: return Palette.textDim
405
+ case .assistant: return Palette.running
406
+ }
407
+ }
408
+
409
+ private func timestamp(for date: Date) -> String {
410
+ Self.timeFormatter.string(from: date)
411
+ }
412
+
413
+ private func capsuleLabel(_ text: String, tint: Color) -> some View {
414
+ Text(text)
415
+ .font(Typo.geistMonoBold(9))
416
+ .foregroundColor(tint.opacity(0.95))
417
+ .padding(.horizontal, 7)
418
+ .padding(.vertical, 4)
419
+ .background(
420
+ Capsule()
421
+ .fill(tint.opacity(0.10))
422
+ .overlay(
423
+ Capsule()
424
+ .strokeBorder(tint.opacity(0.28), lineWidth: 0.5)
425
+ )
426
+ )
427
+ }
428
+
429
+ private func footerButton(_ label: String, tint: Color = Palette.textMuted, action: @escaping () -> Void) -> some View {
430
+ Button(label, action: action)
431
+ .buttonStyle(.plain)
432
+ .font(Typo.geistMonoBold(9))
433
+ .foregroundColor(tint)
434
+ .padding(.horizontal, 8)
435
+ .padding(.vertical, 5)
436
+ .background(
437
+ Capsule()
438
+ .fill(Color.white.opacity(0.03))
439
+ .overlay(
440
+ Capsule()
441
+ .strokeBorder(Palette.border, lineWidth: 0.5)
442
+ )
443
+ )
444
+ }
445
+
446
+ private func authCardBackground(tint: Color) -> some View {
447
+ RoundedRectangle(cornerRadius: 6)
448
+ .fill(tint.opacity(0.06))
449
+ .overlay(
450
+ RoundedRectangle(cornerRadius: 6)
451
+ .strokeBorder(tint.opacity(0.24), lineWidth: 0.5)
452
+ )
453
+ }
454
+ }