@arach/lattices 0.2.1 → 0.6.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 (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -69
  3. package/apps/mac/Info.plist +43 -0
  4. package/apps/mac/Lattices.app/Contents/Info.plist +43 -0
  5. package/apps/mac/Lattices.app/Contents/MacOS/Lattices +0 -0
  6. package/apps/mac/Lattices.app/Contents/Resources/AppIcon.icns +0 -0
  7. package/apps/mac/Lattices.app/Contents/Resources/docs/assistant-knowledge.md +130 -0
  8. package/apps/mac/Lattices.app/Contents/Resources/tap.wav +0 -0
  9. package/apps/mac/Lattices.app/Contents/_CodeSignature/CodeResources +150 -0
  10. package/apps/mac/Lattices.entitlements +21 -0
  11. package/apps/mac/Resources/Pets/assistant-spark/pet.json +62 -0
  12. package/apps/mac/Resources/Pets/assistant-spark/spritesheet.webp +0 -0
  13. package/apps/mac/Resources/Pets/scout-ranger/pet.json +6 -0
  14. package/apps/mac/Resources/Pets/scout-ranger/spritesheet.webp +0 -0
  15. package/apps/mac/Resources/tap.wav +0 -0
  16. package/assets/AppIcon.icns +0 -0
  17. package/bin/assistant-intelligence.ts +912 -0
  18. package/bin/cli/capture.ts +252 -0
  19. package/bin/cli/daemon.ts +22 -0
  20. package/bin/cli/helpers.ts +105 -0
  21. package/bin/cli/layer.ts +178 -0
  22. package/bin/cli/runs.ts +43 -0
  23. package/bin/cli/search.ts +141 -0
  24. package/bin/cli/session.ts +32 -0
  25. package/bin/client.ts +17 -0
  26. package/bin/cua.ts +26 -0
  27. package/bin/{daemon-client.js → daemon-client.ts} +49 -30
  28. package/bin/handsoff-infer.ts +96 -0
  29. package/bin/handsoff-worker.ts +531 -0
  30. package/bin/infer.ts +424 -0
  31. package/bin/keychain.ts +75 -0
  32. package/bin/lattices-app.ts +655 -0
  33. package/bin/lattices-build +125 -0
  34. package/bin/lattices-build-env.ts +77 -0
  35. package/bin/lattices-dev +362 -0
  36. package/bin/lattices.ts +3260 -0
  37. package/bin/project-twin.ts +645 -0
  38. package/docs/agent-execution-plan.md +562 -0
  39. package/docs/agent-layer-guide.md +207 -0
  40. package/docs/agents.md +233 -0
  41. package/docs/ai-chat-ux-review.md +416 -0
  42. package/docs/api.md +1041 -47
  43. package/docs/app.md +96 -13
  44. package/docs/assistant-knowledge.md +130 -0
  45. package/docs/companion-deck.md +209 -0
  46. package/docs/component-extraction-roadmap.md +392 -0
  47. package/docs/concepts.md +13 -12
  48. package/docs/config.md +83 -10
  49. package/docs/gesture-customization-proposal.md +520 -0
  50. package/docs/handsoff-test-scenarios.md +84 -0
  51. package/docs/hyperspace-grid-snappiness.md +210 -0
  52. package/docs/layers.md +176 -28
  53. package/docs/mouse-gestures.md +244 -0
  54. package/docs/ocr.md +21 -9
  55. package/docs/overview.md +42 -23
  56. package/docs/presentation-execution-review.md +491 -0
  57. package/docs/prompts/hands-off-system.md +382 -0
  58. package/docs/prompts/hands-off-turn.md +30 -0
  59. package/docs/prompts/voice-advisor.md +31 -0
  60. package/docs/prompts/voice-fallback.md +23 -0
  61. package/docs/proposals/LAT-001-gesture-visual-customization.md +522 -0
  62. package/docs/proposals/LAT-002-shared-overlay-canvas.md +353 -0
  63. package/docs/proposals/LAT-003-menu-bar-controller-architecture.md +291 -0
  64. package/docs/proposals/LAT-004-interactive-overlay-actors.md +534 -0
  65. package/docs/proposals/LAT-005-action-runtime-product-spine.md +914 -0
  66. package/docs/proposals/LAT-006-followup-gaps.md +103 -0
  67. package/docs/proposals/LAT-006-runs-and-capture-in-lattices.md +566 -0
  68. package/docs/proposals/LAT-007-unified-app-shell.md +128 -0
  69. package/docs/quickstart.md +8 -12
  70. package/docs/reference/dewey.config.ts +74 -0
  71. package/docs/reference/install-agent.md +79 -0
  72. package/docs/release.md +172 -0
  73. package/docs/repo-structure.md +100 -0
  74. package/docs/terminal-kit.md +87 -0
  75. package/docs/tiling-reference.md +224 -0
  76. package/docs/twins.md +138 -0
  77. package/docs/voice-command-protocol.md +278 -0
  78. package/docs/voice-error-model.md +73 -0
  79. package/docs/voice.md +221 -0
  80. package/package.json +69 -16
  81. package/packages/npm/sdk/cua.d.mts +1 -0
  82. package/packages/npm/sdk/cua.d.ts +188 -0
  83. package/packages/npm/sdk/cua.mjs +376 -0
  84. package/app/Lattices.app/Contents/Info.plist +0 -24
  85. package/app/Package.swift +0 -13
  86. package/app/Sources/ActionRow.swift +0 -61
  87. package/app/Sources/App.swift +0 -10
  88. package/app/Sources/AppDelegate.swift +0 -234
  89. package/app/Sources/AppShellView.swift +0 -62
  90. package/app/Sources/AppTypeClassifier.swift +0 -70
  91. package/app/Sources/AppWindowShell.swift +0 -63
  92. package/app/Sources/CheatSheetHUD.swift +0 -332
  93. package/app/Sources/CommandModeState.swift +0 -1362
  94. package/app/Sources/CommandModeView.swift +0 -1405
  95. package/app/Sources/CommandModeWindow.swift +0 -192
  96. package/app/Sources/CommandPaletteView.swift +0 -307
  97. package/app/Sources/CommandPaletteWindow.swift +0 -134
  98. package/app/Sources/DaemonProtocol.swift +0 -101
  99. package/app/Sources/DaemonServer.swift +0 -414
  100. package/app/Sources/DesktopModel.swift +0 -121
  101. package/app/Sources/DesktopModelTypes.swift +0 -71
  102. package/app/Sources/DiagnosticLog.swift +0 -271
  103. package/app/Sources/EventBus.swift +0 -30
  104. package/app/Sources/HotkeyManager.swift +0 -250
  105. package/app/Sources/HotkeyStore.swift +0 -338
  106. package/app/Sources/InventoryManager.swift +0 -35
  107. package/app/Sources/InventoryPath.swift +0 -43
  108. package/app/Sources/KeyRecorderView.swift +0 -210
  109. package/app/Sources/LatticesApi.swift +0 -1125
  110. package/app/Sources/MainView.swift +0 -467
  111. package/app/Sources/MainWindow.swift +0 -83
  112. package/app/Sources/OcrModel.swift +0 -309
  113. package/app/Sources/OcrStore.swift +0 -295
  114. package/app/Sources/OmniSearchState.swift +0 -283
  115. package/app/Sources/OmniSearchView.swift +0 -288
  116. package/app/Sources/OmniSearchWindow.swift +0 -105
  117. package/app/Sources/OrphanRow.swift +0 -129
  118. package/app/Sources/PaletteCommand.swift +0 -419
  119. package/app/Sources/PermissionChecker.swift +0 -125
  120. package/app/Sources/Preferences.swift +0 -92
  121. package/app/Sources/ProcessModel.swift +0 -199
  122. package/app/Sources/ProcessQuery.swift +0 -151
  123. package/app/Sources/Project.swift +0 -28
  124. package/app/Sources/ProjectRow.swift +0 -368
  125. package/app/Sources/ProjectScanner.swift +0 -121
  126. package/app/Sources/ScreenMapState.swift +0 -2387
  127. package/app/Sources/ScreenMapView.swift +0 -2820
  128. package/app/Sources/ScreenMapWindowController.swift +0 -89
  129. package/app/Sources/SessionManager.swift +0 -72
  130. package/app/Sources/SettingsView.swift +0 -1053
  131. package/app/Sources/SettingsWindow.swift +0 -20
  132. package/app/Sources/TabGroupRow.swift +0 -178
  133. package/app/Sources/Terminal.swift +0 -259
  134. package/app/Sources/TerminalQuery.swift +0 -156
  135. package/app/Sources/TerminalSynthesizer.swift +0 -200
  136. package/app/Sources/Theme.swift +0 -163
  137. package/app/Sources/TilePickerView.swift +0 -209
  138. package/app/Sources/TmuxModel.swift +0 -53
  139. package/app/Sources/TmuxQuery.swift +0 -81
  140. package/app/Sources/WindowTiler.swift +0 -1755
  141. package/app/Sources/WorkspaceManager.swift +0 -434
  142. package/bin/lattices-app.js +0 -221
  143. package/bin/lattices.js +0 -1418
package/docs/api.md CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
- title: Daemon API
2
+ title: Agent API
3
3
  description: WebSocket API reference for programmatic control of lattices
4
4
  order: 5
5
5
  ---
6
6
 
7
- The lattices menu bar app runs a WebSocket daemon on `ws://127.0.0.1:9399`.
8
- 30 RPC methods and 5 real-time events.
7
+ The lattices menu bar app runs a WebSocket server on `ws://127.0.0.1:9399`.
8
+ 35+ RPC methods and 5 real-time events.
9
9
 
10
10
  ## Quick start
11
11
 
12
- 1. Launch the daemon (it starts with the menu bar app):
12
+ 1. Launch the server (it starts with the menu bar app):
13
13
 
14
14
  ```bash
15
15
  lattices app
@@ -24,7 +24,7 @@ lattices daemon status
24
24
  3. Call a method from Node.js:
25
25
 
26
26
  ```js
27
- import { daemonCall } from '@arach/lattices/daemon-client'
27
+ import { daemonCall } from '@lattices/cli'
28
28
 
29
29
  const windows = await daemonCall('windows.list')
30
30
  console.log(windows) // [{ wid, app, title, frame, ... }, ...]
@@ -83,14 +83,14 @@ lattices uses a JSON-RPC-style protocol over WebSocket on port **9399**.
83
83
 
84
84
  ### Connection lifecycle
85
85
 
86
- - The daemon starts when the menu bar app launches and stops when it quits.
86
+ - The server starts when the menu bar app launches and stops when it quits.
87
87
  - Connections are plain WebSocket. No handshake, no auth, no heartbeat.
88
88
  - The Node.js `daemonCall()` client opens a fresh connection per call and
89
89
  closes it when the response arrives. For event subscriptions, hold the
90
90
  connection open (see [Reactive event pattern](#agent-integration)).
91
- - If the daemon restarts (e.g. after `lattices app restart`), existing
92
- connections are dropped. Clients should reconnect and treat the daemon
93
- as stateless. There is no session resumption.
91
+ - If the server restarts (e.g. after `lattices app restart`), existing
92
+ connections are dropped. Clients should reconnect and treat it as
93
+ stateless. There is no session resumption.
94
94
 
95
95
  ## Node.js client
96
96
 
@@ -103,7 +103,7 @@ matching internally.
103
103
  Send an RPC call and await the response.
104
104
 
105
105
  ```js
106
- import { daemonCall } from '@arach/lattices/daemon-client'
106
+ import { daemonCall } from '@lattices/cli'
107
107
 
108
108
  // Read-only
109
109
  const status = await daemonCall('daemon.status')
@@ -112,21 +112,24 @@ const win = await daemonCall('windows.get', { wid: 1234 })
112
112
 
113
113
  // Mutations
114
114
  await daemonCall('session.launch', { path: '/Users/you/dev/myapp' })
115
- await daemonCall('window.tile', { session: 'myapp-a1b2c3', position: 'left' })
115
+ await daemonCall('window.place', {
116
+ session: 'myapp-a1b2c3',
117
+ placement: { kind: 'tile', value: 'left' }
118
+ })
116
119
 
117
120
  // Custom timeout (default: 3000ms)
118
121
  await daemonCall('projects.scan', null, 10000)
119
122
  ```
120
123
 
121
124
  **Returns** the `result` field from the response.
122
- **Throws** if the daemon returns an error, the connection fails, or the timeout is reached.
125
+ **Throws** if the server returns an error, the connection fails, or the timeout is reached.
123
126
 
124
127
  ### `isDaemonRunning()`
125
128
 
126
- Check if the daemon is reachable.
129
+ Check if the server is reachable.
127
130
 
128
131
  ```js
129
- import { isDaemonRunning } from '@arach/lattices/daemon-client'
132
+ import { isDaemonRunning } from '@lattices/cli'
130
133
 
131
134
  if (await isDaemonRunning()) {
132
135
  console.log('daemon is up')
@@ -135,12 +138,50 @@ if (await isDaemonRunning()) {
135
138
 
136
139
  Returns `true` if `daemon.status` responds within 1 second.
137
140
 
141
+ ## TypeScript SDK facade
142
+
143
+ The CLI is a human/debug surface. Product code and agents should prefer the
144
+ typed SDK facade, which validates params with Zod and calls the same daemon
145
+ methods directly.
146
+
147
+ ```ts
148
+ import { cua } from '@lattices/sdk'
149
+
150
+ await cua.magicCursor({
151
+ app: 'Scout',
152
+ xRatio: 0.52,
153
+ yRatio: 0.91,
154
+ text: 'What are the most important docs in this project, and what would an agent say after reading them?',
155
+ treatment: 'execute',
156
+ trail: 'comet',
157
+ motion: 'rush',
158
+ trajectory: 'overshoot',
159
+ glow: 'halo',
160
+ idle: 'wiggle',
161
+ edge: 'ripple',
162
+ })
163
+
164
+ await cua.click({
165
+ app: 'Scout',
166
+ xRatio: 0.74,
167
+ yRatio: 0.95,
168
+ transport: 'ax',
169
+ axLabel: 'Send',
170
+ noFocus: true,
171
+ treatment: 'execute',
172
+ })
173
+ ```
174
+
175
+ `@lattices/cli/cua` exposes the same CUA module for CLI-adjacent scripts, but
176
+ new app and agent code should use `@lattices/sdk` or `@lattices/sdk/cua` so the
177
+ import names the product surface instead of the CLI package.
178
+
138
179
  ### Error handling
139
180
 
140
181
  `daemonCall` throws on errors — always wrap calls in try/catch:
141
182
 
142
183
  ```js
143
- import { daemonCall } from '@arach/lattices/daemon-client'
184
+ import { daemonCall } from '@lattices/cli'
144
185
 
145
186
  try {
146
187
  await daemonCall('session.launch', { path: '/nonexistent' })
@@ -157,12 +198,707 @@ try {
157
198
 
158
199
  ---
159
200
 
201
+ ## Runs And Capture
202
+
203
+ Runs are local executions that produce trace events and artifacts. Capture
204
+ methods write into the Lattices run store under
205
+ `~/Library/Application Support/Lattices/Runs/`.
206
+
207
+ | Method | Type | Description |
208
+ |--------|------|-------------|
209
+ | `runs.create` | write | Create a run record and artifact directory |
210
+ | `runs.list` | read | List recent runs |
211
+ | `runs.get` | read | Inspect one run, including artifacts and trace events |
212
+ | `runs.artifacts` | read | List artifacts for one run |
213
+ | `capture.screenshotWindow` | write | Capture a window screenshot as a run artifact |
214
+ | `computer.prepare` | write | Resolve and optionally capture a terminal target without mutating it |
215
+ | `computer.focusWindow` | write | Resolve, capture, focus, and verify a target window |
216
+ | `computer.showCursor` | write | Show a visible cursor appearance and record it as a run |
217
+ | `computer.launchApp` | write | Launch or focus a normal macOS app and record the run |
218
+ | `computer.typeWindowText` | write | Type or paste into a normal app window, optionally after a click |
219
+ | `computer.click` | write | Stage or execute a window-relative click target; prefers no-focus `AXPress` in auto/ax transport |
220
+ | `computer.demoScout` | write | Scout warm-up run for memo/demo recording |
221
+ | `computer.typeText` | write | Insert text into a safe terminal using the least intrusive transport |
222
+ | `computer.demoTerminal` | write | Compatibility wrapper for a bounded terminal text action |
223
+
224
+ #### `capture.screenshotWindow`
225
+
226
+ Capture a window as a PNG artifact. If no target is provided, Lattices captures
227
+ the frontmost non-Lattices window.
228
+
229
+ **Params**:
230
+
231
+ | Field | Type | Required | Description |
232
+ |-------|------|----------|-------------|
233
+ | `wid` | uint32 | no | Target CGWindowID |
234
+ | `session` | string | no | Target lattices session |
235
+ | `app` | string | no | Target app name |
236
+ | `title` | string | no | Optional title filter for `app`, or run title |
237
+ | `runId` | string | no | Existing run to append to |
238
+ | `source` | string | no | Calling surface label |
239
+ | `filename` | string | no | Optional artifact filename |
240
+
241
+ ```js
242
+ await daemonCall('capture.screenshotWindow', { source: 'agent' })
243
+
244
+ await daemonCall('capture.screenshotWindow', {
245
+ session: 'frontend-a1b2c3',
246
+ filename: 'before-layout.png'
247
+ })
248
+ ```
249
+
250
+ CLI:
251
+
252
+ ```bash
253
+ lattices capture window
254
+ lattices runs
255
+ lattices runs run_20260617-120000_a1b2c3
256
+ lattices terminals
257
+ lattices terminals --refresh
258
+ lattices computer prepare --text "# hello" --treatment stage
259
+ lattices computer focus-window --wid 7258 --treatment present
260
+ lattices computer cursor --style marker --shape chevron --angle-deg -8 --label typing
261
+ lattices computer launch-app Scout
262
+ lattices computer scout --treatment present
263
+ lattices computer scout "Draft memo text" --execute
264
+ lattices computer type-window --app Scout --text "Draft memo text" --x-ratio .5 --y-ratio .86 --execute
265
+ lattices computer click --app Scout --x-ratio .5 --y-ratio .86 --execute
266
+ lattices cua click --app Scout --x-ratio .74 --y-ratio .95 --transport ax --ax-label Send --execute
267
+ lattices computer type-text --text "# hello from lattices"
268
+ lattices computer demo-terminal --dry-run
269
+ ```
270
+
271
+ ---
272
+
273
+ #### Computer Action Treatments
274
+
275
+ Computer-use endpoints accept a `treatment` field that controls how intrusive
276
+ the action may be:
277
+
278
+ | Treatment | Behavior |
279
+ |-----------|----------|
280
+ | `observe` | Resolve target and record a run, without focus or input |
281
+ | `stage` | Resolve target and stage intent/artifacts, without focus or input |
282
+ | `present` | Focus or present the target, without input |
283
+ | `execute` | Perform the action after safety checks |
284
+
285
+ The legacy `dryRun: true` flag maps to `stage`.
286
+
287
+ #### `computer.prepare`
288
+
289
+ Resolve and score terminal candidates for a future computer-use action. This is
290
+ the least intrusive endpoint: by default it observes the target and captures an
291
+ artifact, but it does not focus or type.
292
+
293
+ **Params**:
294
+
295
+ | Field | Type | Required | Description |
296
+ |-------|------|----------|-------------|
297
+ | `wid` | uint32 | no | Specific terminal window id |
298
+ | `tty` | string | no | Specific terminal TTY |
299
+ | `app` | string | no | Preferred terminal app, such as `iTerm2` |
300
+ | `text` | string | no | Text to stage in the run trace |
301
+ | `treatment` | string | no | `observe`, `stage`, `present`, or `execute` |
302
+ | `capture` | bool | no | Capture target screenshot artifact. Defaults to `true` |
303
+ | `source` | string | no | Calling surface label |
304
+
305
+ ```js
306
+ await daemonCall('computer.prepare', {
307
+ text: '# review before typing',
308
+ treatment: 'stage'
309
+ })
310
+ ```
311
+
312
+ #### `computer.focusWindow`
313
+
314
+ Resolve a target window, optionally capture it, focus it, and verify the focused
315
+ window id. Use `treatment: 'observe'` or `stage` to plan without presenting.
316
+
317
+ **Params**:
318
+
319
+ | Field | Type | Required | Description |
320
+ |-------|------|----------|-------------|
321
+ | `wid` | uint32 | no | Target window id |
322
+ | `session` | string | no | Target lattices session |
323
+ | `app` | string | no | Target app name |
324
+ | `title` | string | no | Optional title substring for `app` |
325
+ | `treatment` | string | no | `observe`, `stage`, `present`, or `execute` |
326
+ | `dryRun` | bool | no | Stage without focusing |
327
+ | `capture` | bool | no | Capture before/after artifacts. Defaults to `true` |
328
+ | `source` | string | no | Calling surface label |
329
+
330
+ ```js
331
+ await daemonCall('computer.focusWindow', {
332
+ app: 'iTerm2',
333
+ treatment: 'present'
334
+ })
335
+ ```
336
+
337
+ #### `computer.showCursor`
338
+
339
+ Resolve the current cursor location and show a visible cursor appearance. This
340
+ is the cursor equivalent of a typing action: it records a run, cursor target,
341
+ appearance parameters, and trace events. Use `observe` or `stage` to plan
342
+ without showing anything.
343
+
344
+ **Params**:
345
+
346
+ | Field | Type | Required | Description |
347
+ |-------|------|----------|-------------|
348
+ | `x` | double | no | Screen x coordinate. Defaults to current cursor |
349
+ | `y` | double | no | Screen y coordinate. Defaults to current cursor |
350
+ | `treatment` | string | no | `observe`, `stage`, `present`, or `execute` |
351
+ | `style` | string | no | `spotlight`, `pulse`, or `marker` |
352
+ | `appearance` | string | no | Alias for `style` |
353
+ | `shape` | string | no | Marker shape: `chevron`, `facet`, `shard`, `wedge`, `prism`, or `notch` |
354
+ | `angleDeg` | double | no | Marker rotation in degrees. Positive rotates visually clockwise; default is `-8` for marker |
355
+ | `size` | string | no | Marker size: `small`, `regular`, or `large`. Defaults to Settings |
356
+ | `color` | string | no | `blue`, `green`, `amber`, `pink`, `red`, or `white` |
357
+ | `durationMs` | int | no | Appearance duration in milliseconds |
358
+ | `label` | string | no | Optional marker label |
359
+ | `dryRun` | bool | no | Stage without showing |
360
+ | `source` | string | no | Calling surface label |
361
+
362
+ ```js
363
+ await daemonCall('computer.showCursor', {
364
+ style: 'marker',
365
+ shape: 'chevron',
366
+ angleDeg: -8,
367
+ size: 'regular',
368
+ color: 'white',
369
+ treatment: 'present'
370
+ })
371
+ ```
372
+
373
+ #### `computer.launchApp`
374
+
375
+ Launch or focus a normal macOS app and record the result as a run. Use
376
+ `dryRun: true` or `treatment: 'stage'` to plan without launching.
377
+
378
+ **Params**:
379
+
380
+ | Field | Type | Required | Description |
381
+ |-------|------|----------|-------------|
382
+ | `app` | string | yes | App name, such as `Scout`, `Slack`, or `Notes` |
383
+ | `bundleId` | string | no | Bundle identifier fallback for precise launch |
384
+ | `path` | string | no | Explicit `.app` bundle path |
385
+ | `title` | string | no | Optional title substring for app window selection |
386
+ | `treatment` | string | no | `observe`, `stage`, `present`, or `execute` |
387
+ | `dryRun` | bool | no | Stage without launching |
388
+ | `capture` | bool | no | Capture the launched app window. Defaults to `true` outside dry-run |
389
+ | `source` | string | no | Calling surface label |
390
+
391
+ ```js
392
+ await daemonCall('computer.launchApp', {
393
+ app: 'Scout',
394
+ treatment: 'present'
395
+ })
396
+ ```
397
+
398
+ #### `computer.typeWindowText`
399
+
400
+ Focus a normal app window and insert text. If click coordinates are provided,
401
+ Lattices clicks that target before typing. Coordinates can be absolute screen
402
+ points (`x`, `y`) or ratios inside the target window (`xRatio`, `yRatio`).
403
+ For window ratios, `0,0` is the top-left of the window and `1,1` is the
404
+ bottom-right.
405
+
406
+ **Params**:
407
+
408
+ | Field | Type | Required | Description |
409
+ |-------|------|----------|-------------|
410
+ | `wid` | uint32 | no | Target window id |
411
+ | `app` | string | no | Target app name |
412
+ | `title` | string | no | Optional title substring for app target |
413
+ | `text` | string | yes | Text to insert |
414
+ | `enter` | bool | no | Press Enter after typing. Defaults to `false` |
415
+ | `send` | bool | no | Alias for `enter` in chat-style demos |
416
+ | `x`, `y` | double | no | Absolute click point before typing |
417
+ | `xRatio`, `yRatio` | double | no | Window-relative click point before typing |
418
+ | `treatment` | string | no | `observe`, `stage`, `present`, or `execute` |
419
+ | `dryRun` | bool | no | Stage without typing |
420
+ | `capture` | bool | no | Capture before/after artifacts. Defaults to `true` |
421
+ | `source` | string | no | Calling surface label |
422
+
423
+ ```js
424
+ await daemonCall('computer.typeWindowText', {
425
+ app: 'Scout',
426
+ text: 'Draft memo text',
427
+ xRatio: 0.5,
428
+ yRatio: 0.86,
429
+ treatment: 'execute'
430
+ })
431
+ ```
432
+
433
+ #### `computer.click`
434
+
435
+ Stage or execute a click target. `stage` records the target without clicking.
436
+ In `execute`, `transport: "auto"` prefers `AXPress` on the resolved accessibility
437
+ button/control before falling back to a pointer click. Use `transport: "ax"` or
438
+ `noFocus: true` when the action must not focus the app or move the hardware
439
+ pointer. When a window target is provided, ratios are relative to that window.
440
+
441
+ **Params**:
442
+
443
+ | Field | Type | Required | Description |
444
+ |-------|------|----------|-------------|
445
+ | `wid` | uint32 | no | Target window id |
446
+ | `app` | string | no | Target app name |
447
+ | `title` | string | no | Optional title substring for app target |
448
+ | `x`, `y` | double | no | Absolute click point |
449
+ | `xRatio`, `yRatio` | double | no | Window-relative click point |
450
+ | `button` | string | no | `left` or `right`; defaults to `left` |
451
+ | `transport` | string | no | `auto`, `ax`, or `pointer`; defaults to `auto` |
452
+ | `axLabel` | string | no | Optional AX text/title hint, such as `Send` |
453
+ | `noFocus` | bool | no | Require no-focus AX execution; disable pointer fallback |
454
+ | `treatment` | string | no | `stage`, `present`, or `execute` |
455
+ | `dryRun` | bool | no | Stage without clicking |
456
+ | `capture` | bool | no | Capture before/after artifacts when targeting a window |
457
+ | `source` | string | no | Calling surface label |
458
+
459
+ ```js
460
+ await daemonCall('computer.click', {
461
+ app: 'Scout',
462
+ xRatio: 0.5,
463
+ yRatio: 0.86,
464
+ treatment: 'execute'
465
+ })
466
+
467
+ await daemonCall('computer.click', {
468
+ app: 'Scout',
469
+ xRatio: 0.74,
470
+ yRatio: 0.95,
471
+ transport: 'ax',
472
+ axLabel: 'Send',
473
+ noFocus: true,
474
+ treatment: 'execute'
475
+ })
476
+ ```
477
+
478
+ #### `computer.demoScout`
479
+
480
+ Warm up a Scout memo/demo recording run. In `present` mode it launches or
481
+ focuses Scout and records a run without typing. In `execute` mode it can click
482
+ the likely composer area, type a draft, and optionally press Enter when
483
+ `enter` or `send` is true. Dry-run/stage mode does not capture by default, so it
484
+ works before Screen Recording permission is granted.
485
+
486
+ **Params**:
487
+
488
+ | Field | Type | Required | Description |
489
+ |-------|------|----------|-------------|
490
+ | `app` | string | no | Scout app name override. Defaults to `Scout` |
491
+ | `title` | string | no | Optional title substring for the Scout window |
492
+ | `text` | string | no | Draft text to type in `execute` mode |
493
+ | `enter` | bool | no | Press Enter after typing. Defaults to `false` |
494
+ | `send` | bool | no | Alias for `enter` |
495
+ | `click` | bool | no | Click the likely composer area before typing |
496
+ | `xRatio`, `yRatio` | double | no | Composer click point; defaults to `0.5`, `0.86` |
497
+ | `treatment` | string | no | `observe`, `stage`, `present`, or `execute` |
498
+ | `dryRun` | bool | no | Stage without launching or typing |
499
+ | `capture` | bool | no | Capture before/after artifacts. Defaults to `true` outside dry-run |
500
+ | `source` | string | no | Calling surface label |
501
+
502
+ ```js
503
+ await daemonCall('computer.demoScout', { dryRun: true })
504
+
505
+ await daemonCall('computer.demoScout', {
506
+ treatment: 'present',
507
+ capture: false
508
+ })
509
+
510
+ await daemonCall('computer.demoScout', {
511
+ text: 'Draft memo text',
512
+ treatment: 'execute',
513
+ send: false
514
+ })
515
+ ```
516
+
517
+ #### `computer.typeText`
518
+
519
+ Resolve a terminal target and insert text after safety checks. Lattices prefers
520
+ the least intrusive available transport: tmux pane input when a tmux pane is
521
+ known, then target-pid key events/pasteboard insertion when window focus is
522
+ required. Enter is never pressed unless `enter: true` is provided.
523
+
524
+ **Params**:
525
+
526
+ | Field | Type | Required | Description |
527
+ |-------|------|----------|-------------|
528
+ | `wid` | uint32 | no | Specific terminal window id |
529
+ | `tty` | string | no | Specific terminal TTY |
530
+ | `app` | string | no | Preferred terminal app, such as `iTerm2` |
531
+ | `text` | string | yes | Text to insert |
532
+ | `enter` | bool | no | Press Enter after typing. Defaults to `false` |
533
+ | `treatment` | string | no | `observe`, `stage`, `present`, or `execute` |
534
+ | `transport` | string | no | `auto`, `tmux`, or `pasteboard`. Defaults to `auto` |
535
+ | `dryRun` | bool | no | Stage without typing |
536
+ | `capture` | bool | no | Capture before/after artifacts. Defaults to `true` |
537
+ | `source` | string | no | Calling surface label |
538
+
539
+ ```js
540
+ await daemonCall('computer.typeText', {
541
+ text: '# hello from lattices',
542
+ transport: 'auto',
543
+ enter: false
544
+ })
545
+ ```
546
+
547
+ #### `computer.demoTerminal`
548
+
549
+ Compatibility endpoint for the original terminal demo. It follows the same
550
+ treatment, safety, capture, and transport rules as `computer.typeText`, but
551
+ provides a default text payload when `text` is omitted.
552
+
553
+ Run a bounded computer-use sequence against a terminal window:
554
+
555
+ 1. synthesize and score terminal candidates
556
+ 2. choose a safe shell-like terminal unless `wid` or `tty` is supplied
557
+ 3. capture a `before` screenshot artifact
558
+ 4. focus the terminal window
559
+ 5. insert text without pressing Enter by default
560
+ 6. capture an `after` screenshot artifact
561
+
562
+ **Params**:
563
+
564
+ | Field | Type | Required | Description |
565
+ |-------|------|----------|-------------|
566
+ | `wid` | uint32 | no | Specific terminal window id |
567
+ | `tty` | string | no | Specific terminal TTY |
568
+ | `app` | string | no | Preferred terminal app, such as `iTerm2` |
569
+ | `text` | string | no | Text to insert |
570
+ | `enter` | bool | no | Press Enter after typing. Defaults to `false` |
571
+ | `treatment` | string | no | `observe`, `stage`, `present`, or `execute` |
572
+ | `transport` | string | no | `auto`, `tmux`, or `pasteboard`. Defaults to `auto` |
573
+ | `dryRun` | bool | no | Plan and capture without typing |
574
+ | `capture` | bool | no | Capture before/after artifacts. Defaults to `true` |
575
+ | `source` | string | no | Calling surface label |
576
+
577
+ ```js
578
+ await daemonCall('computer.demoTerminal', { dryRun: true })
579
+
580
+ await daemonCall('computer.demoTerminal', {
581
+ app: 'iTerm2',
582
+ text: '# hello from lattices',
583
+ enter: false
584
+ })
585
+ ```
586
+
587
+ ---
588
+
589
+ ## Overlay UI
590
+
591
+ The macOS app exposes a shared desktop overlay canvas for lightweight
592
+ agent-visible UI. Use `overlay.publish` for transient passive visuals,
593
+ and `overlay.actor.*` for persistent, movable actor surfaces.
594
+
595
+ Persistent actors are useful for representing agents or processes on the
596
+ desktop. Each actor has a stable `id`, can be moved independently through the
597
+ API, dragged by the user, hidden/restored with **Hyper+B**, and closed with
598
+ right-click. Click event callbacks and action surfaces are planned follow-on
599
+ capabilities.
600
+
601
+ | Method | Type | Description |
602
+ |--------|------|-------------|
603
+ | `overlay.publish` | write | Publish a transient toast, label, highlight, or pet layer |
604
+ | `overlay.clear` | write | Clear one overlay layer by id, or clear an owner namespace |
605
+ | `overlay.actor.publish` | write | Create or update a persistent generative overlay actor |
606
+ | `overlay.actor.moveTo` | write | Move an actor with app-owned easing |
607
+ | `overlay.actor.hud` | write | Attach, update, or clear a hover web HUD for an actor |
608
+ | `overlay.actor.visibility` | write | Show, hide, toggle, or inspect the sticky actor layer |
609
+
610
+ #### `overlay.publish`
611
+
612
+ Publish a transient layer on the screen overlay canvas.
613
+
614
+ **Params**:
615
+
616
+ | Field | Type | Required | Description |
617
+ |-------|------|----------|-------------|
618
+ | `kind` | string | yes | `toast`, `label`, `highlight`, or `pet` |
619
+ | `id` | string | no | Stable layer id; generated if omitted |
620
+ | `text` | string | no | Toast/label text or pet message fallback |
621
+ | `detail` | string | no | Secondary toast/label text |
622
+ | `message` | string | no | Pet message |
623
+ | `petId` | string | no | Bundled pet id from `apps/mac/Resources/Pets` |
624
+ | `state` | string | no | Pet animation state |
625
+ | `placement` | string | no | `top`, `bottom`, `center`, `cursor`, or `point` |
626
+ | `x`, `y` | double | no | Screen-local point for `point` placement |
627
+ | `w`, `h` | double | no | Highlight size |
628
+ | `ttlMs` | int | no | Time to live in milliseconds |
629
+ | `dismissible` | bool | no | Whether click-away dismissal removes the layer; defaults `true` |
630
+
631
+ Example:
632
+
633
+ ```js
634
+ await daemonCall('overlay.publish', {
635
+ kind: 'highlight',
636
+ x: 160,
637
+ y: 120,
638
+ w: 480,
639
+ h: 260,
640
+ text: 'Needs review',
641
+ style: 'warning',
642
+ ttlMs: 3000
643
+ })
644
+ ```
645
+
646
+ #### `overlay.actor.publish`
647
+
648
+ Create or update a generative overlay actor. Actors default to persistent:
649
+ omit `ttlMs` or pass `0`, and `dismissible` defaults to `false`.
650
+
651
+ **Params**:
652
+
653
+ | Field | Type | Required | Description |
654
+ |-------|------|----------|-------------|
655
+ | `id` | string | no | Stable actor id; generated if omitted |
656
+ | `renderer` | string | no | `sprite` is currently supported |
657
+ | `asset` | string | no | Bundled sprite asset id, such as `scout-ranger` |
658
+ | `state` | string | no | Actor state or animation name |
659
+ | `name` | string | no | Actor display name |
660
+ | `message` | string | no | Attached message text |
661
+ | `targetApp` | string | no | App name to activate when the actor is clicked |
662
+ | `targetBundleId` | string | no | Bundle identifier to activate when the actor is clicked |
663
+ | `targetAppPath` | string | no | `.app` bundle path to open when the actor is clicked |
664
+ | `scale` | double | no | Actor scale multiplier |
665
+ | `labelHidden` | bool | no | Hide the actor label/message |
666
+ | `closeOnActivate` | bool | no | Remove the actor after activating its target app |
667
+ | `hudUrl` | string | no | URL to render in a transparent hover HUD web view |
668
+ | `hudHTML` | string | no | Inline HTML to render in a transparent hover HUD web view |
669
+ | `hudWidth` | double | no | Hover HUD width |
670
+ | `hudHeight` | double | no | Hover HUD height |
671
+ | `hudReadAccess` | string | no | Local folder a file-backed HUD may read |
672
+ | `placement` | string | no | `top`, `bottom`, `center`, `cursor`, or `point` |
673
+ | `x`, `y` | double | no | Screen-local point for `point` placement |
674
+ | `ttlMs` | int | no | Time to live; `0` means persistent |
675
+ | `dismissible` | bool | no | Whether click-away dismissal removes the actor |
676
+
677
+ Bundled sprite assets:
678
+
679
+ | Asset | Notes |
680
+ |-------|-------|
681
+ | `assistant-spark` | Animated states include `idle`, `run_right`, `run_left`, `waving`, `jumping`, `failed`, `waiting`, `running`, and `review` |
682
+ | `scout-ranger` | Bundled sprite asset with default frame fallback |
683
+
684
+ Example:
685
+
686
+ ```js
687
+ await daemonCall('overlay.actor.publish', {
688
+ id: 'agent-scout',
689
+ renderer: 'sprite',
690
+ asset: 'scout-ranger',
691
+ state: 'waiting',
692
+ name: 'Scout',
693
+ message: 'Waiting for feedback',
694
+ placement: 'point',
695
+ x: 640,
696
+ y: 320,
697
+ ttlMs: 0
698
+ })
699
+ ```
700
+
701
+ #### `overlay.actor.moveTo`
702
+
703
+ Move an actor with app-owned animation. The app interpolates position and
704
+ switches directional sprite states while moving.
705
+
706
+ **Params**:
707
+
708
+ | Field | Type | Required | Description |
709
+ |-------|------|----------|-------------|
710
+ | `id` | string | yes | Actor id |
711
+ | `x`, `y` | double | yes | Target screen-local point |
712
+ | `durationMs` | int | no | Animation duration |
713
+ | `easing` | string | no | `linear`, `easeInOut`, or `spring` |
714
+
715
+ Example:
716
+
717
+ ```js
718
+ await daemonCall('overlay.actor.moveTo', {
719
+ id: 'agent-scout',
720
+ x: 820,
721
+ y: 280,
722
+ durationMs: 800,
723
+ easing: 'spring'
724
+ })
725
+ ```
726
+
727
+ #### `overlay.actor.hud`
728
+
729
+ Attach a transparent, blurred hover HUD to an actor. The HUD is backed by a
730
+ native `WKWebView`, so apps can point it at a local static HTML dashboard or,
731
+ in development, a local URL.
732
+
733
+ **Params**:
734
+
735
+ | Field | Type | Required | Description |
736
+ |-------|------|----------|-------------|
737
+ | `id` | string | yes | Actor id |
738
+ | `hudUrl` | string | no | URL or file path to render |
739
+ | `hudHTML` | string | no | Inline HTML to render instead of a URL |
740
+ | `hudTitle` | string | no | HUD title metadata |
741
+ | `hudWidth` | double | no | HUD width |
742
+ | `hudHeight` | double | no | HUD height |
743
+ | `hudReadAccess` | string | no | Local folder a file-backed HUD may read |
744
+ | `clear` | bool | no | Remove the actor HUD |
745
+
746
+ Example:
747
+
748
+ ```js
749
+ await daemonCall('overlay.actor.hud', {
750
+ id: 'switch-talkie',
751
+ hudUrl: '/Users/you/dev/talkie/.lattices/hud/index.html',
752
+ hudReadAccess: '/Users/you/Library/Application Support/Talkie/HUD',
753
+ hudWidth: 380,
754
+ hudHeight: 260
755
+ })
756
+ ```
757
+
758
+ #### `overlay.actor.visibility`
759
+
760
+ Show, hide, toggle, or inspect the persistent actor layer without destroying
761
+ the actors. This is the daemon equivalent of the app's **Hyper+B** shortcut.
762
+
763
+ **Params**:
764
+
765
+ | Field | Type | Required | Description |
766
+ |-------|------|----------|-------------|
767
+ | `action` | string | no | `show`, `hide`, `toggle`, or `status` |
768
+ | `visible` | bool | no | Set layer visibility directly |
769
+ | `hidden` | bool | no | Set layer hidden state directly |
770
+ | `feedback` | bool | no | Show a short desktop feedback toast |
771
+
772
+ Example:
773
+
774
+ ```js
775
+ await daemonCall('overlay.actor.visibility', { action: 'toggle' })
776
+ ```
777
+
778
+ ### Static HUD Manifests
779
+
780
+ Apps and projects can expose a local hover dashboard without running a web
781
+ server by publishing a static bundle at:
782
+
783
+ ```txt
784
+ .lattices/hud/
785
+ manifest.json
786
+ index.html
787
+ assets/
788
+ ```
789
+
790
+ Minimal manifest:
791
+
792
+ ```json
793
+ {
794
+ "version": 1,
795
+ "id": "talkie",
796
+ "name": "Talkie",
797
+ "bundleId": "com.usabletalkie.Talkie",
798
+ "icon": "./assets/icon.png",
799
+ "entry": "./index.html",
800
+ "readAccess": "~/Library/Application Support/Talkie/HUD",
801
+ "surface": { "width": 380, "height": 260 },
802
+ "actor": {
803
+ "labelHidden": true,
804
+ "click": "activateApp"
805
+ },
806
+ "sources": [
807
+ {
808
+ "path": "~/Library/Application Support/Talkie/HUD/activity.jsonl",
809
+ "format": "jsonl",
810
+ "schema": "talkie.activity.v1",
811
+ "presentation": "timeline"
812
+ }
813
+ ]
814
+ }
815
+ ```
816
+
817
+ The CLI resolves this manifest into `overlay.actor.publish` with a file-backed
818
+ HUD URL. The macOS app loads `entry` through `WKWebView.loadFileURL`, allowing
819
+ read access to the HUD folder by default, or to the manifest's `readAccess`
820
+ folder when one is declared.
821
+
822
+ `sources` is descriptive metadata for app-owned state, logs, or event streams.
823
+ Lattices does not append to those logs. The app writes them in its normal
824
+ runtime location, and the custom HUD renderer decides how to present them.
825
+
826
+ Useful commands:
827
+
828
+ ```bash
829
+ lattices hud register .lattices/hud/manifest.json --publish
830
+ lattices hud publish talkie
831
+ lattices hud sync
832
+ lattices hud discover ~/dev --register
833
+ ```
834
+
835
+ For packaged apps, keep the renderer files in the app bundle and point mutable
836
+ sources at an app-owned folder such as `~/Library/Application Support/...`.
837
+
838
+ ---
839
+
160
840
  ## System
161
841
 
162
842
  | Method | Type | Description |
163
843
  |--------|------|-------------|
844
+ | `deck.manifest` | read | Shared companion deck manifest |
845
+ | `deck.snapshot` | read | Current companion deck runtime snapshot |
846
+ | `deck.perform` | write | Perform a companion deck action |
164
847
  | `daemon.status` | read | Health check and stats |
165
848
  | `api.schema` | read | Full API schema for self-discovery |
849
+ | `diagnostics.list` | read | Recent diagnostic entries |
850
+
851
+ #### `deck.manifest`
852
+
853
+ Return the shared `DeckKit` manifest exposed by the macOS app. This is
854
+ the contract a future Lattices companion can consume to discover pages,
855
+ capabilities, and security mode.
856
+
857
+ **Params**: none
858
+
859
+ #### `deck.snapshot`
860
+
861
+ Return the current `DeckKit` runtime snapshot for the macOS host.
862
+
863
+ **Params**: none
864
+
865
+ **Returns**: a `DeckRuntimeSnapshot` object containing:
866
+
867
+ - `voice`
868
+ - `desktop`
869
+ - `switcher`
870
+ - `history`
871
+ - `questions`
872
+
873
+ #### `deck.perform`
874
+
875
+ Perform a `DeckKit` action against the running macOS host and return a
876
+ `DeckActionResult`.
877
+
878
+ **Params**:
879
+
880
+ | Field | Type | Required | Description |
881
+ |-------|------|----------|-------------|
882
+ | `pageID` | string | no | Deck page ID |
883
+ | `actionID` | string | yes | Deck action identifier |
884
+ | `payload` | object | no | Deck action payload |
885
+
886
+ Example:
887
+
888
+ ```json
889
+ {
890
+ "id": "1",
891
+ "method": "deck.perform",
892
+ "params": {
893
+ "pageID": "layout",
894
+ "actionID": "layout.optimize",
895
+ "payload": {
896
+ "strategy": "balanced",
897
+ "region": "right"
898
+ }
899
+ }
900
+ }
901
+ ```
166
902
 
167
903
  #### `daemon.status`
168
904
 
@@ -189,6 +925,84 @@ Useful for agent self-discovery.
189
925
 
190
926
  **Params**: none
191
927
 
928
+ CLI shortcut:
929
+
930
+ ```bash
931
+ lattices call api.schema
932
+ ```
933
+
934
+ #### `diagnostics.list`
935
+
936
+ Return recent diagnostic log entries from the daemon.
937
+
938
+ **Params**:
939
+
940
+ | Field | Type | Required | Description |
941
+ |---------|--------|----------|--------------------------------|
942
+ | `limit` | number | no | Max entries to return (default 50) |
943
+
944
+ ---
945
+
946
+ ## Mouse & Input
947
+
948
+ | Method | Type | Description |
949
+ |--------|------|-------------|
950
+ | `mouse.find` | read | Show a sonar pulse at the current cursor |
951
+ | `mouse.summon` | write | Move the cursor to a point or screen center |
952
+ | `mouse.shortcuts.get` | read | Return the live mouse shortcut config |
953
+ | `mouse.shortcuts.reload` | write | Reload `~/.lattices/mouse-shortcuts.json` without restarting |
954
+ | `mouse.shortcuts.set` | write | Replace the full mouse shortcut config and activate it |
955
+ | `mouse.shortcuts.upsert` | write | Create or replace one mouse shortcut rule and activate it |
956
+ | `mouse.shortcuts.remove` | write | Remove one mouse shortcut rule and activate the new config |
957
+ | `mouse.shortcuts.restoreDefaults` | write | Restore default mouse shortcuts |
958
+
959
+ Mouse shortcut rules are data. Prefer `shortcut.send` for hotkeys an agent can
960
+ define or change directly; do not add a named action unless the behavior cannot
961
+ be expressed as data.
962
+
963
+ Create or replace a gesture that sends Hyper+D:
964
+
965
+ ```js
966
+ await daemonCall('mouse.shortcuts.upsert', {
967
+ rule: {
968
+ id: 'middle-up-voice',
969
+ enabled: true,
970
+ device: 'any',
971
+ trigger: { button: 'middle', kind: 'drag', direction: 'up' },
972
+ action: {
973
+ type: 'shortcut.send',
974
+ shortcut: {
975
+ key: 'd',
976
+ keyCode: 2,
977
+ modifiers: ['control', 'option', 'shift', 'command']
978
+ }
979
+ }
980
+ }
981
+ })
982
+ ```
983
+
984
+ If an agent edits `~/.lattices/mouse-shortcuts.json` itself, refresh the running
985
+ app explicitly:
986
+
987
+ ```js
988
+ await daemonCall('mouse.shortcuts.reload')
989
+ ```
990
+
991
+ All write methods persist the config, checkpoint the previous version in
992
+ `~/.lattices/mouse-shortcuts.history/`, and update the active event-tap snapshot
993
+ immediately. No app restart is required.
994
+
995
+ Supported action types:
996
+
997
+ | Type | Purpose |
998
+ |------|---------|
999
+ | `shortcut.send` | Send a data-defined key or keyCode with modifiers |
1000
+ | `app.activate` | Activate an app by name |
1001
+ | `space.previous` | Switch to the previous macOS Space |
1002
+ | `space.next` | Switch to the next macOS Space |
1003
+ | `screenmap.toggle` | Open the Screen Map overview |
1004
+ | `dictation.start` | Legacy alias that presses the configured Voice Command hotkey |
1005
+
192
1006
  ---
193
1007
 
194
1008
  ## Windows & Spaces
@@ -197,11 +1011,17 @@ Useful for agent self-discovery.
197
1011
  |--------|------|-------------|
198
1012
  | `windows.list` | read | All visible windows |
199
1013
  | `windows.get` | read | Single window by ID |
1014
+ | `windows.search` | read | Search windows by query |
200
1015
  | `spaces.list` | read | macOS display spaces |
201
- | `window.tile` | write | Tile a window to a position |
1016
+ | `window.place` | write | Place a window or session using a typed placement spec |
1017
+ | `window.tile` | write | Compatibility wrapper for session tiling |
202
1018
  | `window.focus` | write | Focus a window / switch Spaces |
203
1019
  | `window.move` | write | Move a window to another Space |
204
- | `layout.distribute` | write | Distribute windows evenly |
1020
+ | `window.assignLayer` | write | Tag a window to a layer |
1021
+ | `window.removeLayer` | write | Remove a window's layer tag |
1022
+ | `window.layerMap` | read | All window→layer assignments |
1023
+ | `space.optimize` | write | Optimize a set of windows using an explicit scope and strategy |
1024
+ | `layout.distribute` | write | Compatibility wrapper for visible-window balancing |
205
1025
 
206
1026
  #### `windows.list`
207
1027
 
@@ -221,7 +1041,8 @@ List all visible windows tracked by the desktop model.
221
1041
  "frame": { "x": 0, "y": 25, "w": 960, "h": 1050 },
222
1042
  "spaceIds": [1],
223
1043
  "isOnScreen": true,
224
- "latticesSession": "myapp-a1b2c3"
1044
+ "latticesSession": "myapp-a1b2c3",
1045
+ "layerTag": "web"
225
1046
  }
226
1047
  ]
227
1048
  ```
@@ -229,6 +1050,9 @@ List all visible windows tracked by the desktop model.
229
1050
  The `latticesSession` field is present only on windows that belong to
230
1051
  a lattices session (matched via the `[lattices:name]` title tag).
231
1052
 
1053
+ The `layerTag` field is present when a window has been manually assigned
1054
+ to a layer via `window.assignLayer`.
1055
+
232
1056
  #### `windows.get`
233
1057
 
234
1058
  Get a single window by its CGWindowID.
@@ -242,6 +1066,56 @@ Get a single window by its CGWindowID.
242
1066
  **Returns**: a single window object (same shape as `windows.list` items).
243
1067
  **Errors**: `Not found` if the window ID doesn't exist.
244
1068
 
1069
+ #### `windows.search`
1070
+
1071
+ Search windows by text query across title, app name, session tags, and OCR content. Returns results with `matchSource` indicating how the match was found, and `ocrSnippet` for OCR matches.
1072
+
1073
+ **Params**:
1074
+
1075
+ | Field | Type | Required | Description |
1076
+ |---------|--------|----------|--------------------------------|
1077
+ | `query` | string | yes | Search query (matches title, app, session, OCR text) |
1078
+ | `ocr` | boolean| no | Include OCR text in search (default true) |
1079
+ | `limit` | number | no | Max results (default 50) |
1080
+
1081
+ **Returns**: array of window objects with additional search fields:
1082
+
1083
+ ```json
1084
+ [
1085
+ {
1086
+ "wid": 265,
1087
+ "app": "iTerm2",
1088
+ "title": "✳ Claude Code",
1089
+ "matchSource": "ocr",
1090
+ "ocrSnippet": "…~/dev/vox StatusBarIconFolder…",
1091
+ "frame": { "x": 688, "y": 3, "w": 1720, "h": 720 },
1092
+ "isOnScreen": true
1093
+ }
1094
+ ]
1095
+ ```
1096
+
1097
+ `matchSource` values: `"title"`, `"app"`, `"session"`, `"ocr"`.
1098
+
1099
+ **CLI usage**:
1100
+
1101
+ ```bash
1102
+ # Basic search (uses windows.search)
1103
+ lattices search vox
1104
+
1105
+ # Deep search — adds terminal tab/process inspection for ranking
1106
+ lattices search vox --deep
1107
+
1108
+ # Same as --deep (all search sources)
1109
+ lattices search vox --all
1110
+
1111
+ # Pipeable output
1112
+ lattices search vox --wid
1113
+ lattices search vox --json
1114
+
1115
+ # Search + focus + tile in one step
1116
+ lattices place vox right
1117
+ ```
1118
+
245
1119
  #### `spaces.list`
246
1120
 
247
1121
  List macOS display spaces (virtual desktops).
@@ -264,20 +1138,55 @@ List macOS display spaces (virtual desktops).
264
1138
  ]
265
1139
  ```
266
1140
 
267
- #### `window.tile`
1141
+ #### `window.place`
268
1142
 
269
- Tile a session's terminal window to a screen position.
1143
+ Canonical window placement mutation. Use this when an agent needs a
1144
+ single, typed placement contract across voice, CLI, and daemon clients.
270
1145
 
271
1146
  **Params**:
272
1147
 
273
- | Field | Type | Required | Description |
274
- |------------|--------|----------|-------------------------------------|
275
- | `session` | string | yes | Session name |
276
- | `position` | string | yes | Tile position (see below) |
1148
+ | Field | Type | Required | Description |
1149
+ |-------------|-----------------|----------|------------------------------------------|
1150
+ | `wid` | number | no | Target window ID |
1151
+ | `session` | string | no | Target lattices session |
1152
+ | `app` | string | no | Target app name |
1153
+ | `title` | string | no | Optional title substring for app matching |
1154
+ | `display` | number | no | Target display index |
1155
+ | `placement` | string \| object | yes | Placement shorthand or typed object |
1156
+
1157
+ Target resolution priority is `wid` → `session` → `app/title` → frontmost window.
277
1158
 
278
- **Positions**: `left`, `right`, `top`, `bottom`, `top-left`, `top-right`,
1159
+ **Placement strings**: `left`, `right`, `top`, `bottom`, `top-left`, `top-right`,
279
1160
  `bottom-left`, `bottom-right`, `left-third`, `center-third`, `right-third`,
280
- `maximize`, `center`
1161
+ `top-third`, `middle-third`, `bottom-third`, `left-quarter`, `right-quarter`,
1162
+ `top-quarter`, `bottom-quarter`, `maximize`, `center`, `grid:CxR:C,R`, or
1163
+ compact `CxR:C,R`. The canonical `grid:` form is 0-indexed; the compact form is
1164
+ 1-indexed for command entry.
1165
+
1166
+ **Typed placement examples**:
1167
+
1168
+ ```json
1169
+ { "kind": "tile", "value": "top-right" }
1170
+ { "kind": "grid", "columns": 3, "rows": 2, "column": 2, "row": 0 }
1171
+ { "kind": "fractions", "x": 0.5, "y": 0, "w": 0.5, "h": 1 }
1172
+ ```
1173
+
1174
+ **Returns**: execution receipt including resolved target, placement, and trace.
1175
+
1176
+ #### `window.tile`
1177
+
1178
+ Compatibility wrapper for `window.place` when the target is a lattices
1179
+ session window.
1180
+
1181
+ **Params**:
1182
+
1183
+ | Field | Type | Required | Description |
1184
+ |------------|--------|----------|-----------------------------------------|
1185
+ | `session` | string | yes | Session name |
1186
+ | `position` | string | yes | Placement shorthand or grid syntax |
1187
+
1188
+ This method exists for compatibility. New integrations should prefer
1189
+ `window.place`.
281
1190
 
282
1191
  #### `window.focus`
283
1192
 
@@ -303,9 +1212,70 @@ Move a session's window to a different macOS Space.
303
1212
  | `session` | string | yes | Session name |
304
1213
  | `spaceId` | number | yes | Target Space ID (from `spaces.list`) |
305
1214
 
1215
+ #### `window.assignLayer`
1216
+
1217
+ Manually tag a window to a layer. Tagged windows are raised and tiled
1218
+ when that layer activates, even if they aren't declared in `workspace.json`.
1219
+
1220
+ **Params**:
1221
+
1222
+ | Field | Type | Required | Description |
1223
+ |---------|--------|----------|--------------------------------|
1224
+ | `wid` | number | yes | CGWindowID |
1225
+ | `layer` | string | yes | Layer ID to assign |
1226
+
1227
+ #### `window.removeLayer`
1228
+
1229
+ Remove a window's layer tag.
1230
+
1231
+ **Params**:
1232
+
1233
+ | Field | Type | Required | Description |
1234
+ |-------|--------|----------|----------------|
1235
+ | `wid` | number | yes | CGWindowID |
1236
+
1237
+ #### `window.layerMap`
1238
+
1239
+ Return all current window→layer assignments.
1240
+
1241
+ **Params**: none
1242
+
1243
+ **Returns**:
1244
+
1245
+ ```json
1246
+ {
1247
+ "1234": "web",
1248
+ "5678": "mobile"
1249
+ }
1250
+ ```
1251
+
1252
+ Keys are CGWindowIDs (as strings), values are layer IDs.
1253
+
1254
+ #### `space.optimize`
1255
+
1256
+ Canonical space-balancing mutation. Use this when the goal is to make
1257
+ the current workspace coherent rather than placing one specific window.
1258
+
1259
+ **Params**:
1260
+
1261
+ | Field | Type | Required | Description |
1262
+ |-------------|----------|----------|-----------------------------------------------------|
1263
+ | `scope` | string | no | `visible`, `active-app`, `app`, or `selection` |
1264
+ | `strategy` | string | no | `balanced` or `mosaic` |
1265
+ | `app` | string | no | App name for `app` scope |
1266
+ | `title` | string | no | Optional title substring for app matching |
1267
+ | `windowIds` | number[] | no | Explicit window IDs for `selection` scope |
1268
+
1269
+ If `windowIds` is provided, scope is inferred as `selection`.
1270
+ If `app` is provided and `scope` is omitted, scope is inferred as `app`.
1271
+
1272
+ **Returns**: execution receipt including resolved scope, strategy,
1273
+ affected window IDs, and trace.
1274
+
306
1275
  #### `layout.distribute`
307
1276
 
308
- Distribute all visible lattices windows evenly across the screen.
1277
+ Compatibility wrapper for `space.optimize` with `scope=visible` and
1278
+ `strategy=balanced`.
309
1279
 
310
1280
  **Params**: none
311
1281
 
@@ -440,7 +1410,8 @@ Restart a specific pane's process within a session.
440
1410
  | `projects.list` | read | Discovered projects |
441
1411
  | `projects.scan` | write | Re-scan project directory |
442
1412
  | `layers.list` | read | Workspace layers and active index |
443
- | `layer.switch` | write | Switch workspace layer |
1413
+ | `layer.activate` | write | Activate a workspace layer using an explicit mode |
1414
+ | `layer.switch` | write | Compatibility wrapper for launch-style layer activation |
444
1415
  | `group.launch` | write | Launch a tab group |
445
1416
  | `group.kill` | write | Kill a tab group |
446
1417
 
@@ -497,17 +1468,33 @@ List configured workspace layers and the active index.
497
1468
 
498
1469
  Returns empty `layers` array if no workspace config is loaded.
499
1470
 
500
- #### `layer.switch`
1471
+ #### `layer.activate`
501
1472
 
502
- Switch the active workspace layer. Focuses and tiles all windows in the
503
- target layer, launches any projects that aren't running yet, and posts
504
- a `layer.switched` event.
1473
+ Canonical layer mutation. Use this when an agent wants an explicit
1474
+ activation mode instead of implicit "switch" behavior.
505
1475
 
506
1476
  **Params**:
507
1477
 
508
- | Field | Type | Required | Description |
509
- |---------|--------|----------|--------------------------------|
510
- | `index` | number | yes | Layer index (0-based) |
1478
+ | Field | Type | Required | Description |
1479
+ |---------|--------|----------|---------------------------------------------|
1480
+ | `index` | number | no | Layer index (0-based) |
1481
+ | `name` | string | no | Layer ID or label |
1482
+ | `mode` | string | no | `launch`, `focus`, or `retile` |
1483
+
1484
+ Provide either `index` or `name`.
1485
+
1486
+ **Modes**:
1487
+
1488
+ - `launch` — bring up the layer, launching missing projects and retiling
1489
+ - `focus` — raise the layer's windows in place
1490
+ - `retile` — re-apply the layer layout without launch semantics
1491
+
1492
+ **Returns**: execution receipt including resolved layer, mode, and trace.
1493
+
1494
+ #### `layer.switch`
1495
+
1496
+ Compatibility wrapper for `layer.activate` with `mode=launch`.
1497
+ It keeps the old semantics and still posts a `layer.switched` event.
511
1498
 
512
1499
  #### `group.launch`
513
1500
 
@@ -589,7 +1576,7 @@ List all discovered terminal instances with their processes, tabs, and tmux asso
589
1576
 
590
1577
  | Field | Type | Required | Description |
591
1578
  |-----------|---------|----------|--------------------------------------|
592
- | `refresh` | boolean | no | Force-refresh the terminal tab cache |
1579
+ | `refresh` | boolean | no | Explicitly refresh terminal-tab metadata through terminal app scripting |
593
1580
 
594
1581
  **Returns**: array of terminal instance objects:
595
1582
 
@@ -734,7 +1721,7 @@ They have no `id` field — listen for messages with an `event` field.
734
1721
  #### `layer.switched`
735
1722
 
736
1723
  ```json
737
- { "event": "layer.switched", "data": { "index": 1 } }
1724
+ { "event": "layer.switched", "data": { "index": 1, "name": "mobile" } }
738
1725
  ```
739
1726
 
740
1727
  #### `ocr.scanComplete`
@@ -764,16 +1751,23 @@ project knows how to control the workspace:
764
1751
  This project uses lattices for workspace management. The daemon API
765
1752
  is available at ws://127.0.0.1:9399.
766
1753
 
767
- ### Available commands
768
- - List windows: `daemonCall('windows.list')`
769
- - List sessions: `daemonCall('tmux.sessions')`
1754
+ ### Search (find windows)
1755
+ - Search by content: `daemonCall('windows.search', { query: 'myproject' })`
1756
+ Returns windows with `matchSource` ("title", "app", "session", "ocr") and `ocrSnippet`
1757
+ - Search terminals: `daemonCall('terminals.search', {})` — tabs, cwds, processes
1758
+ - CLI: `lattices search myproject`, `lattices search myproject --deep`, or `lattices search myproject --all` (same as `--deep`)
1759
+
1760
+ ### Actions
1761
+ - Focus a window: `daemonCall('window.focus', { wid: 1234 })`
1762
+ - Place a window: `daemonCall('window.place', { session: 'name', placement: 'left' })`
770
1763
  - Launch a project: `daemonCall('session.launch', { path: '/absolute/path' })`
771
- - Tile a window: `daemonCall('window.tile', { session: 'name', position: 'left' })`
772
- - Switch layer: `daemonCall('layer.switch', { index: 0 })`
1764
+ - Activate a layer: `daemonCall('layer.activate', { name: 'web', mode: 'launch' })`
1765
+ - Optimize the workspace: `daemonCall('space.optimize', { scope: 'visible', strategy: 'balanced' })`
1766
+ - CLI: `lattices place myproject left` (search + focus + tile in one step)
773
1767
 
774
1768
  ### Import
775
1769
  \```js
776
- import { daemonCall } from '@arach/lattices/daemon-client'
1770
+ import { daemonCall } from '@lattices/cli'
777
1771
  \```
778
1772
  ```
779
1773
 
@@ -782,7 +1776,7 @@ import { daemonCall } from '@arach/lattices/daemon-client'
782
1776
  An orchestrator agent can set up the full workspace for sub-agents:
783
1777
 
784
1778
  ```js
785
- import { daemonCall } from '@arach/lattices/daemon-client'
1779
+ import { daemonCall } from '@lattices/cli'
786
1780
 
787
1781
  // Discover what's available
788
1782
  const projects = await daemonCall('projects.list')
@@ -796,8 +1790,8 @@ const sessions = await daemonCall('tmux.sessions')
796
1790
  const fe = sessions.find(s => s.name.startsWith('frontend'))
797
1791
  const api = sessions.find(s => s.name.startsWith('api'))
798
1792
 
799
- await daemonCall('window.tile', { session: fe.name, position: 'left' })
800
- await daemonCall('window.tile', { session: api.name, position: 'right' })
1793
+ await daemonCall('window.place', { session: fe.name, placement: 'left' })
1794
+ await daemonCall('window.place', { session: api.name, placement: 'right' })
801
1795
  ```
802
1796
 
803
1797
  ### Reactive event pattern
@@ -836,7 +1830,7 @@ ws.on('open', () => {
836
1830
  Always verify the daemon is running before making calls:
837
1831
 
838
1832
  ```js
839
- import { isDaemonRunning, daemonCall } from '@arach/lattices/daemon-client'
1833
+ import { isDaemonRunning, daemonCall } from '@lattices/cli'
840
1834
 
841
1835
  if (!(await isDaemonRunning())) {
842
1836
  console.error('lattices daemon is not running — start it with: lattices app')