@arach/lattices 0.1.0 → 0.2.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 (39) hide show
  1. package/README.md +101 -90
  2. package/app/Sources/ActionRow.swift +61 -0
  3. package/app/Sources/App.swift +1 -40
  4. package/app/Sources/AppDelegate.swift +154 -24
  5. package/app/Sources/CheatSheetHUD.swift +1 -0
  6. package/app/Sources/CommandModeState.swift +40 -19
  7. package/app/Sources/CommandModeView.swift +27 -2
  8. package/app/Sources/DaemonServer.swift +8 -0
  9. package/app/Sources/DiagnosticLog.swift +19 -1
  10. package/app/Sources/EventBus.swift +1 -0
  11. package/app/Sources/HotkeyManager.swift +1 -0
  12. package/app/Sources/HotkeyStore.swift +9 -1
  13. package/app/Sources/LatticesApi.swift +210 -0
  14. package/app/Sources/MainView.swift +46 -86
  15. package/app/Sources/MainWindow.swift +13 -0
  16. package/app/Sources/OcrModel.swift +309 -0
  17. package/app/Sources/OcrStore.swift +295 -0
  18. package/app/Sources/OmniSearchState.swift +283 -0
  19. package/app/Sources/OmniSearchView.swift +288 -0
  20. package/app/Sources/OmniSearchWindow.swift +105 -0
  21. package/app/Sources/PaletteCommand.swift +11 -1
  22. package/app/Sources/PermissionChecker.swift +12 -2
  23. package/app/Sources/Preferences.swift +44 -0
  24. package/app/Sources/ScreenMapState.swift +7 -17
  25. package/app/Sources/ScreenMapView.swift +3 -0
  26. package/app/Sources/SettingsView.swift +534 -122
  27. package/app/Sources/Theme.swift +39 -0
  28. package/app/Sources/WindowTiler.swift +59 -56
  29. package/bin/lattices-app.js +23 -7
  30. package/bin/lattices.js +123 -0
  31. package/docs/api.md +390 -249
  32. package/docs/app.md +75 -28
  33. package/docs/concepts.md +45 -136
  34. package/docs/config.md +8 -7
  35. package/docs/layers.md +16 -18
  36. package/docs/ocr.md +185 -0
  37. package/docs/overview.md +39 -34
  38. package/docs/quickstart.md +34 -35
  39. package/package.json +6 -2
package/docs/api.md CHANGED
@@ -4,23 +4,8 @@ description: WebSocket API reference for programmatic control of lattices
4
4
  order: 5
5
5
  ---
6
6
 
7
- # Daemon API
8
-
9
7
  The lattices menu bar app runs a WebSocket daemon on `ws://127.0.0.1:9399`.
10
- It exposes 20 RPC methods and 3 real-time events — everything the app
11
- can do, agents and scripts can do too.
12
-
13
- ## Who this is for
14
-
15
- - **AI coding agents** that need to discover projects, launch sessions,
16
- tile windows, and switch contexts without human interaction
17
- - **Scripts and automation** — CI, dotfile bootstraps, workspace setup
18
- - **Custom tools** — build your own launcher, dashboard, or orchestrator
19
-
20
- > New to lattices? Start with the [Overview](/docs/overview) and
21
- > [Quickstart](/docs/quickstart). For the `.lattices.json` config format
22
- > and CLI commands, see [Configuration](/docs/config). For architecture
23
- > details, see [Concepts](/docs/concepts).
8
+ 30 RPC methods and 5 real-time events.
24
9
 
25
10
  ## Quick start
26
11
 
@@ -39,7 +24,7 @@ lattices daemon status
39
24
  3. Call a method from Node.js:
40
25
 
41
26
  ```js
42
- import { daemonCall } from 'lattices/daemon-client'
27
+ import { daemonCall } from '@arach/lattices/daemon-client'
43
28
 
44
29
  const windows = await daemonCall('windows.list')
45
30
  console.log(windows) // [{ wid, app, title, frame, ... }, ...]
@@ -88,28 +73,25 @@ lattices uses a JSON-RPC-style protocol over WebSocket on port **9399**.
88
73
  | `result` | any \| null | Method return value (null on error) |
89
74
  | `error` | string \| null | Error message (null on success) |
90
75
 
91
- ### Event (server-pushed)
92
-
93
- ```json
94
- {
95
- "event": "windows.changed",
96
- "data": { ... }
97
- }
98
- ```
99
-
100
- Events have no `id` — they are broadcast to all connected clients
101
- whenever state changes.
102
-
103
76
  ### Errors
104
77
 
105
- Three error types:
106
-
107
78
  | Error | Meaning |
108
79
  |-----------------|--------------------------------------|
109
80
  | Unknown method | The `method` string is not recognized |
110
81
  | Missing parameter | A required param was not provided |
111
82
  | Not found | The referenced resource doesn't exist |
112
83
 
84
+ ### Connection lifecycle
85
+
86
+ - The daemon starts when the menu bar app launches and stops when it quits.
87
+ - Connections are plain WebSocket. No handshake, no auth, no heartbeat.
88
+ - The Node.js `daemonCall()` client opens a fresh connection per call and
89
+ closes it when the response arrives. For event subscriptions, hold the
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.
94
+
113
95
  ## Node.js client
114
96
 
115
97
  lattices ships a zero-dependency WebSocket client that works with
@@ -121,7 +103,7 @@ matching internally.
121
103
  Send an RPC call and await the response.
122
104
 
123
105
  ```js
124
- import { daemonCall } from 'lattices/daemon-client'
106
+ import { daemonCall } from '@arach/lattices/daemon-client'
125
107
 
126
108
  // Read-only
127
109
  const status = await daemonCall('daemon.status')
@@ -132,7 +114,7 @@ const win = await daemonCall('windows.get', { wid: 1234 })
132
114
  await daemonCall('session.launch', { path: '/Users/you/dev/myapp' })
133
115
  await daemonCall('window.tile', { session: 'myapp-a1b2c3', position: 'left' })
134
116
 
135
- // Custom timeout (default: 5000ms)
117
+ // Custom timeout (default: 3000ms)
136
118
  await daemonCall('projects.scan', null, 10000)
137
119
  ```
138
120
 
@@ -144,7 +126,7 @@ await daemonCall('projects.scan', null, 10000)
144
126
  Check if the daemon is reachable.
145
127
 
146
128
  ```js
147
- import { isDaemonRunning } from 'lattices/daemon-client'
129
+ import { isDaemonRunning } from '@arach/lattices/daemon-client'
148
130
 
149
131
  if (await isDaemonRunning()) {
150
132
  console.log('daemon is up')
@@ -153,11 +135,36 @@ if (await isDaemonRunning()) {
153
135
 
154
136
  Returns `true` if `daemon.status` responds within 1 second.
155
137
 
138
+ ### Error handling
139
+
140
+ `daemonCall` throws on errors — always wrap calls in try/catch:
141
+
142
+ ```js
143
+ import { daemonCall } from '@arach/lattices/daemon-client'
144
+
145
+ try {
146
+ await daemonCall('session.launch', { path: '/nonexistent' })
147
+ } catch (err) {
148
+ // err.message is one of:
149
+ // "Not found" — resource doesn't exist
150
+ // "Missing parameter: ..." — required param missing
151
+ // "Unknown method: ..." — bad method name
152
+ // "Daemon request timed out" — no response within timeout
153
+ // ECONNREFUSED — daemon not running
154
+ console.error('Daemon error:', err.message)
155
+ }
156
+ ```
157
+
156
158
  ---
157
159
 
158
- ## Read methods
160
+ ## System
161
+
162
+ | Method | Type | Description |
163
+ |--------|------|-------------|
164
+ | `daemon.status` | read | Health check and stats |
165
+ | `api.schema` | read | Full API schema for self-discovery |
159
166
 
160
- ### `daemon.status`
167
+ #### `daemon.status`
161
168
 
162
169
  Health check and basic stats.
163
170
 
@@ -175,9 +182,28 @@ Health check and basic stats.
175
182
  }
176
183
  ```
177
184
 
185
+ #### `api.schema`
186
+
187
+ Return the full API schema including version, models, and method definitions.
188
+ Useful for agent self-discovery.
189
+
190
+ **Params**: none
191
+
178
192
  ---
179
193
 
180
- ### `windows.list`
194
+ ## Windows & Spaces
195
+
196
+ | Method | Type | Description |
197
+ |--------|------|-------------|
198
+ | `windows.list` | read | All visible windows |
199
+ | `windows.get` | read | Single window by ID |
200
+ | `spaces.list` | read | macOS display spaces |
201
+ | `window.tile` | write | Tile a window to a position |
202
+ | `window.focus` | write | Focus a window / switch Spaces |
203
+ | `window.move` | write | Move a window to another Space |
204
+ | `layout.distribute` | write | Distribute windows evenly |
205
+
206
+ #### `windows.list`
181
207
 
182
208
  List all visible windows tracked by the desktop model.
183
209
 
@@ -203,9 +229,7 @@ List all visible windows tracked by the desktop model.
203
229
  The `latticesSession` field is present only on windows that belong to
204
230
  a lattices session (matched via the `[lattices:name]` title tag).
205
231
 
206
- ---
207
-
208
- ### `windows.get`
232
+ #### `windows.get`
209
233
 
210
234
  Get a single window by its CGWindowID.
211
235
 
@@ -216,12 +240,92 @@ Get a single window by its CGWindowID.
216
240
  | `wid` | number | yes | CGWindowID |
217
241
 
218
242
  **Returns**: a single window object (same shape as `windows.list` items).
219
-
220
243
  **Errors**: `Not found` if the window ID doesn't exist.
221
244
 
245
+ #### `spaces.list`
246
+
247
+ List macOS display spaces (virtual desktops).
248
+
249
+ **Params**: none
250
+
251
+ **Returns**: array of display objects:
252
+
253
+ ```json
254
+ [
255
+ {
256
+ "displayIndex": 0,
257
+ "displayId": "main",
258
+ "currentSpaceId": 1,
259
+ "spaces": [
260
+ { "id": 1, "index": 0, "display": 0, "isCurrent": true },
261
+ { "id": 2, "index": 1, "display": 0, "isCurrent": false }
262
+ ]
263
+ }
264
+ ]
265
+ ```
266
+
267
+ #### `window.tile`
268
+
269
+ Tile a session's terminal window to a screen position.
270
+
271
+ **Params**:
272
+
273
+ | Field | Type | Required | Description |
274
+ |------------|--------|----------|-------------------------------------|
275
+ | `session` | string | yes | Session name |
276
+ | `position` | string | yes | Tile position (see below) |
277
+
278
+ **Positions**: `left`, `right`, `top`, `bottom`, `top-left`, `top-right`,
279
+ `bottom-left`, `bottom-right`, `left-third`, `center-third`, `right-third`,
280
+ `maximize`, `center`
281
+
282
+ #### `window.focus`
283
+
284
+ Focus a window — bring it to front and switch Spaces if needed.
285
+
286
+ **Params** (one of):
287
+
288
+ | Field | Type | Required | Description |
289
+ |-----------|--------|----------|---------------------------------|
290
+ | `wid` | number | no | CGWindowID (any window) |
291
+ | `session` | string | no | Session name (lattices windows) |
292
+
293
+ Provide either `wid` or `session`. If `wid` is given, it takes priority.
294
+
295
+ #### `window.move`
296
+
297
+ Move a session's window to a different macOS Space.
298
+
299
+ **Params**:
300
+
301
+ | Field | Type | Required | Description |
302
+ |-----------|--------|----------|----------------------------|
303
+ | `session` | string | yes | Session name |
304
+ | `spaceId` | number | yes | Target Space ID (from `spaces.list`) |
305
+
306
+ #### `layout.distribute`
307
+
308
+ Distribute all visible lattices windows evenly across the screen.
309
+
310
+ **Params**: none
311
+
222
312
  ---
223
313
 
224
- ### `tmux.sessions`
314
+ ## Sessions
315
+
316
+ | Method | Type | Description |
317
+ |--------|------|-------------|
318
+ | `tmux.sessions` | read | Lattices tmux sessions |
319
+ | `tmux.inventory` | read | All sessions including orphans |
320
+ | `session.launch` | write | Launch a project session |
321
+ | `session.kill` | write | Kill a session |
322
+ | `session.detach` | write | Detach clients from a session |
323
+ | `session.sync` | write | Reconcile session to config |
324
+ | `session.restart` | write | Restart a pane's process |
325
+
326
+ All session methods require tmux to be installed.
327
+
328
+ #### `tmux.sessions`
225
329
 
226
330
  List tmux sessions that belong to lattices.
227
331
 
@@ -250,9 +354,7 @@ List tmux sessions that belong to lattices.
250
354
  ]
251
355
  ```
252
356
 
253
- ---
254
-
255
- ### `tmux.inventory`
357
+ #### `tmux.inventory`
256
358
 
257
359
  List all tmux sessions including orphans (sessions not tracked by lattices).
258
360
 
@@ -269,9 +371,80 @@ List all tmux sessions including orphans (sessions not tracked by lattices).
269
371
 
270
372
  Both arrays contain session objects (same shape as `tmux.sessions`).
271
373
 
374
+ #### `session.launch`
375
+
376
+ Launch a new tmux session for a project. If a session already exists,
377
+ it will be reattached. The project must be in the scanned project list —
378
+ call `projects.list` to check, or `projects.scan` to refresh.
379
+
380
+ **Params**:
381
+
382
+ | Field | Type | Required | Description |
383
+ |--------|--------|----------|----------------------------------|
384
+ | `path` | string | yes | Absolute path to project directory |
385
+
386
+ **Returns**: `{ "ok": true }`
387
+ **Errors**: `Not found` if the path isn't in the scanned project list.
388
+
389
+ #### `session.kill`
390
+
391
+ Kill a tmux session by name.
392
+
393
+ **Params**:
394
+
395
+ | Field | Type | Required | Description |
396
+ |--------|--------|----------|---------------------|
397
+ | `name` | string | yes | Session name |
398
+
399
+ #### `session.detach`
400
+
401
+ Detach all clients from a session (keeps it running).
402
+
403
+ **Params**:
404
+
405
+ | Field | Type | Required | Description |
406
+ |--------|--------|----------|---------------------|
407
+ | `name` | string | yes | Session name |
408
+
409
+ #### `session.sync`
410
+
411
+ Reconcile a running session to match its declared `.lattices.json` config.
412
+ Recreates missing panes, re-applies layout, restores labels, re-runs
413
+ commands in idle panes.
414
+
415
+ **Params**:
416
+
417
+ | Field | Type | Required | Description |
418
+ |--------|--------|----------|----------------------------------|
419
+ | `path` | string | yes | Absolute path to project directory |
420
+
421
+ **Errors**: `Not found` if the path isn't in the project list.
422
+
423
+ #### `session.restart`
424
+
425
+ Restart a specific pane's process within a session.
426
+
427
+ **Params**:
428
+
429
+ | Field | Type | Required | Description |
430
+ |--------|--------|----------|----------------------------------|
431
+ | `path` | string | yes | Absolute path to project directory |
432
+ | `pane` | string | no | Pane name to restart (defaults to first pane) |
433
+
272
434
  ---
273
435
 
274
- ### `projects.list`
436
+ ## Projects & Layers
437
+
438
+ | Method | Type | Description |
439
+ |--------|------|-------------|
440
+ | `projects.list` | read | Discovered projects |
441
+ | `projects.scan` | write | Re-scan project directory |
442
+ | `layers.list` | read | Workspace layers and active index |
443
+ | `layer.switch` | write | Switch workspace layer |
444
+ | `group.launch` | write | Launch a tab group |
445
+ | `group.kill` | write | Kill a tab group |
446
+
447
+ #### `projects.list`
275
448
 
276
449
  List all discovered projects.
277
450
 
@@ -297,33 +470,14 @@ List all discovered projects.
297
470
 
298
471
  `devCommand` and `packageManager` are present only when detected.
299
472
 
300
- ---
473
+ #### `projects.scan`
301
474
 
302
- ### `spaces.list`
303
-
304
- List macOS display spaces (virtual desktops).
475
+ Trigger a re-scan of the project directory. Useful after cloning a new
476
+ repo or adding a `.lattices.json` config.
305
477
 
306
478
  **Params**: none
307
479
 
308
- **Returns**: array of display objects:
309
-
310
- ```json
311
- [
312
- {
313
- "displayIndex": 0,
314
- "displayId": "main",
315
- "currentSpaceId": 1,
316
- "spaces": [
317
- { "id": 1, "index": 0, "display": 0, "isCurrent": true },
318
- { "id": 2, "index": 1, "display": 0, "isCurrent": false }
319
- ]
320
- }
321
- ]
322
- ```
323
-
324
- ---
325
-
326
- ### `layers.list`
480
+ #### `layers.list`
327
481
 
328
482
  List configured workspace layers and the active index.
329
483
 
@@ -343,203 +497,213 @@ List configured workspace layers and the active index.
343
497
 
344
498
  Returns empty `layers` array if no workspace config is loaded.
345
499
 
346
- ---
500
+ #### `layer.switch`
347
501
 
348
- ## Write methods
349
-
350
- ### `session.launch`
351
-
352
- Launch a new tmux session for a project.
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.
353
505
 
354
506
  **Params**:
355
507
 
356
- | Field | Type | Required | Description |
357
- |--------|--------|----------|----------------------------------|
358
- | `path` | string | yes | Absolute path to project directory |
359
-
360
- **Returns**: `{ "ok": true }`
361
-
362
- **Errors**: `Not found` if the path isn't in the scanned project list.
363
- Run `projects.scan` first if needed.
364
-
365
- **Notes**: If a session already exists for this project, it will be
366
- reattached. The project must be in the scanned project list — call
367
- `projects.list` to check, or `projects.scan` to refresh.
368
-
369
- ---
508
+ | Field | Type | Required | Description |
509
+ |---------|--------|----------|--------------------------------|
510
+ | `index` | number | yes | Layer index (0-based) |
370
511
 
371
- ### `session.kill`
512
+ #### `group.launch`
372
513
 
373
- Kill a tmux session by name.
514
+ Launch a tab group session.
374
515
 
375
516
  **Params**:
376
517
 
377
- | Field | Type | Required | Description |
378
- |--------|--------|----------|---------------------|
379
- | `name` | string | yes | Session name |
380
-
381
- **Returns**: `{ "ok": true }`
518
+ | Field | Type | Required | Description |
519
+ |-------|--------|----------|------------------|
520
+ | `id` | string | yes | Group ID |
382
521
 
383
- ---
522
+ **Errors**: `Not found` if the group ID doesn't match any configured group.
384
523
 
385
- ### `session.detach`
524
+ #### `group.kill`
386
525
 
387
- Detach all clients from a session (keeps it running).
526
+ Kill a tab group session.
388
527
 
389
528
  **Params**:
390
529
 
391
- | Field | Type | Required | Description |
392
- |--------|--------|----------|---------------------|
393
- | `name` | string | yes | Session name |
394
-
395
- **Returns**: `{ "ok": true }`
530
+ | Field | Type | Required | Description |
531
+ |-------|--------|----------|------------------|
532
+ | `id` | string | yes | Group ID |
396
533
 
397
534
  ---
398
535
 
399
- ### `session.sync`
536
+ ## Processes & Terminals
400
537
 
401
- Reconcile a running session to match its declared `.lattices.json` config.
402
- Recreates missing panes, re-applies layout, restores labels, re-runs
403
- commands in idle panes.
538
+ | Method | Type | Description |
539
+ |--------|------|-------------|
540
+ | `processes.list` | read | Running developer processes |
541
+ | `processes.tree` | read | Process tree from a PID |
542
+ | `terminals.list` | read | Terminal instances with processes |
543
+ | `terminals.search` | read | Search terminals by criteria |
404
544
 
405
- **Params**:
545
+ #### `processes.list`
406
546
 
407
- | Field | Type | Required | Description |
408
- |--------|--------|----------|----------------------------------|
409
- | `path` | string | yes | Absolute path to project directory |
410
-
411
- **Returns**: `{ "ok": true }`
412
-
413
- **Errors**: `Not found` if the path isn't in the project list.
414
-
415
- ---
416
-
417
- ### `session.restart`
418
-
419
- Restart a specific pane's process within a session.
547
+ List running processes relevant to development (editors, servers, build tools).
420
548
 
421
549
  **Params**:
422
550
 
423
- | Field | Type | Required | Description |
424
- |--------|--------|----------|----------------------------------|
425
- | `path` | string | yes | Absolute path to project directory |
426
- | `pane` | string | no | Pane name to restart (defaults to first pane) |
551
+ | Field | Type | Required | Description |
552
+ |-----------|--------|----------|------------------------------------|
553
+ | `command` | string | no | Filter by command name substring |
427
554
 
428
- **Returns**: `{ "ok": true }`
429
-
430
- **Errors**: `Not found` if the path isn't in the project list.
555
+ **Returns**: array of process objects:
431
556
 
432
- ---
557
+ ```json
558
+ [
559
+ {
560
+ "pid": 1234,
561
+ "ppid": 567,
562
+ "command": "node",
563
+ "args": "server.js",
564
+ "cwd": "/Users/you/dev/myapp",
565
+ "tty": "/dev/ttys003",
566
+ "tmuxSession": "myapp-a1b2c3",
567
+ "tmuxPaneId": "%0"
568
+ }
569
+ ]
570
+ ```
433
571
 
434
- ### `window.tile`
572
+ #### `processes.tree`
435
573
 
436
- Tile a session's terminal window to a screen position.
574
+ Get the process tree rooted at a given PID.
437
575
 
438
576
  **Params**:
439
577
 
440
- | Field | Type | Required | Description |
441
- |------------|--------|----------|-------------------------------------|
442
- | `session` | string | yes | Session name |
443
- | `position` | string | yes | Tile position (see below) |
444
-
445
- **Positions**: `left`, `right`, `top`, `bottom`, `top-left`, `top-right`,
446
- `bottom-left`, `bottom-right`, `maximize`, `center`
447
-
448
- **Returns**: `{ "ok": true }`
449
-
450
- ---
578
+ | Field | Type | Required | Description |
579
+ |-------|--------|----------|---------------|
580
+ | `pid` | number | yes | Root PID |
451
581
 
452
- ### `window.focus`
582
+ **Returns**: array of process objects (same shape as `processes.list`).
453
583
 
454
- Focus a window — bring it to front and switch Spaces if needed.
584
+ #### `terminals.list`
455
585
 
456
- **Params** (one of):
586
+ List all discovered terminal instances with their processes, tabs, and tmux associations.
457
587
 
458
- | Field | Type | Required | Description |
459
- |-----------|--------|----------|---------------------------------|
460
- | `wid` | number | no | CGWindowID (any window) |
461
- | `session` | string | no | Session name (lattices windows) |
588
+ **Params**:
462
589
 
463
- Provide either `wid` or `session`. If `wid` is given, it takes priority.
590
+ | Field | Type | Required | Description |
591
+ |-----------|---------|----------|--------------------------------------|
592
+ | `refresh` | boolean | no | Force-refresh the terminal tab cache |
464
593
 
465
- **Returns**: `{ "ok": true }` (with `wid` and `app` if focused by wid)
594
+ **Returns**: array of terminal instance objects:
466
595
 
467
- ---
596
+ ```json
597
+ [
598
+ {
599
+ "tty": "/dev/ttys003",
600
+ "app": "Terminal",
601
+ "windowIndex": 0,
602
+ "tabIndex": 0,
603
+ "isActiveTab": true,
604
+ "tabTitle": "myapp",
605
+ "processes": [ ... ],
606
+ "shellPid": 1234,
607
+ "cwd": "/Users/you/dev/myapp",
608
+ "tmuxSession": "myapp-a1b2c3",
609
+ "tmuxPaneId": "%0",
610
+ "hasClaude": true,
611
+ "displayName": "Terminal — myapp"
612
+ }
613
+ ]
614
+ ```
468
615
 
469
- ### `window.move`
616
+ #### `terminals.search`
470
617
 
471
- Move a session's window to a different macOS Space.
618
+ Search terminal instances by various criteria.
472
619
 
473
620
  **Params**:
474
621
 
475
- | Field | Type | Required | Description |
476
- |-----------|--------|----------|----------------------------|
477
- | `session` | string | yes | Session name |
478
- | `spaceId` | number | yes | Target Space ID (from `spaces.list`) |
622
+ | Field | Type | Required | Description |
623
+ |------------|---------|----------|--------------------------------------|
624
+ | `command` | string | no | Filter by command name substring |
625
+ | `cwd` | string | no | Filter by working directory substring |
626
+ | `app` | string | no | Filter by terminal app name |
627
+ | `session` | string | no | Filter by tmux session name |
628
+ | `hasClaude`| boolean | no | Filter to only Claude-running TTYs |
479
629
 
480
- **Returns**: `{ "ok": true }`
630
+ **Returns**: filtered array of terminal instance objects (same shape as `terminals.list`).
481
631
 
482
632
  ---
483
633
 
484
- ### `layer.switch`
634
+ ## OCR
485
635
 
486
- Switch the active workspace layer.
636
+ | Method | Type | Description |
637
+ |--------|------|-------------|
638
+ | `ocr.snapshot` | read | Current OCR results for all visible windows |
639
+ | `ocr.search` | read | Full-text search across OCR history |
640
+ | `ocr.history` | read | OCR timeline for a specific window |
641
+ | `ocr.scan` | write | Trigger an immediate OCR scan |
487
642
 
488
- **Params**:
643
+ See [Screen OCR](/docs/ocr) for configuration, scan schedules, and storage details.
489
644
 
490
- | Field | Type | Required | Description |
491
- |---------|--------|----------|--------------------------------|
492
- | `index` | number | yes | Layer index (0-based) |
645
+ #### `ocr.snapshot`
493
646
 
494
- **Returns**: `{ "ok": true }`
495
-
496
- **Notes**: This focuses and tiles all windows in the target layer,
497
- launches any projects that aren't running yet, and posts a
498
- `layer.switched` event.
647
+ Get the current in-memory OCR results for all visible windows.
499
648
 
500
- ---
649
+ **Params**: none
501
650
 
502
- ### `group.launch`
651
+ **Returns**: array of OCR result objects:
503
652
 
504
- Launch a tab group session.
653
+ ```json
654
+ [
655
+ {
656
+ "wid": 1234,
657
+ "app": "Terminal",
658
+ "title": "zsh",
659
+ "frame": { "x": 0, "y": 25, "w": 960, "h": 1050 },
660
+ "fullText": "~/dev/myapp $ npm run dev\nready on port 3000",
661
+ "blocks": [
662
+ {
663
+ "text": "~/dev/myapp $ npm run dev",
664
+ "confidence": 0.95,
665
+ "x": 0.02, "y": 0.05, "w": 0.6, "h": 0.04
666
+ }
667
+ ],
668
+ "timestamp": 1709568000.0
669
+ }
670
+ ]
671
+ ```
505
672
 
506
- **Params**:
673
+ #### `ocr.search`
507
674
 
508
- | Field | Type | Required | Description |
509
- |-------|--------|----------|------------------|
510
- | `id` | string | yes | Group ID |
675
+ Full-text search across OCR history using SQLite FTS5.
511
676
 
512
- **Returns**: `{ "ok": true }`
677
+ **Params**:
513
678
 
514
- **Errors**: `Not found` if the group ID doesn't match any configured group.
679
+ | Field | Type | Required | Description |
680
+ |---------|---------|----------|------------------------------------------|
681
+ | `query` | string | yes | FTS5 search query |
682
+ | `app` | string | no | Filter by application name |
683
+ | `limit` | number | no | Max results (default 50) |
684
+ | `live` | boolean | no | Search live snapshot instead of history (default false) |
515
685
 
516
- ---
686
+ **FTS5 query examples**: `error`, `"build failed"`, `error OR warning`, `npm AND dev`, `react*`
517
687
 
518
- ### `group.kill`
688
+ #### `ocr.history`
519
689
 
520
- Kill a tab group session.
690
+ Get the OCR timeline for a specific window, ordered by most recent first.
521
691
 
522
692
  **Params**:
523
693
 
524
- | Field | Type | Required | Description |
525
- |-------|--------|----------|------------------|
526
- | `id` | string | yes | Group ID |
527
-
528
- **Returns**: `{ "ok": true }`
529
-
530
- **Errors**: `Not found` if the group ID doesn't match any configured group.
531
-
532
- ---
694
+ | Field | Type | Required | Description |
695
+ |---------|--------|----------|----------------------------|
696
+ | `wid` | number | yes | CGWindowID |
697
+ | `limit` | number | no | Max results (default 50) |
533
698
 
534
- ### `projects.scan`
699
+ #### `ocr.scan`
535
700
 
536
- Trigger a re-scan of the project directory. Useful after cloning a new
537
- repo or adding a `.lattices.json` config.
701
+ Trigger an immediate OCR scan of all visible windows, bypassing the
702
+ periodic timer. Results available via `ocr.snapshot` once complete;
703
+ an `ocr.scanComplete` event is broadcast to all clients.
538
704
 
539
705
  **Params**: none
540
706
 
541
- **Returns**: `{ "ok": true }`
542
-
543
707
  ---
544
708
 
545
709
  ## Events
@@ -547,69 +711,47 @@ repo or adding a `.lattices.json` config.
547
711
  Events are pushed to all connected WebSocket clients when state changes.
548
712
  They have no `id` field — listen for messages with an `event` field.
549
713
 
550
- ### `windows.changed`
714
+ | Event | Trigger |
715
+ |-------|---------|
716
+ | `windows.changed` | Desktop window list changes |
717
+ | `tmux.changed` | Sessions created, killed, or modified |
718
+ | `layer.switched` | Active workspace layer changes |
719
+ | `ocr.scanComplete` | OCR scan cycle finishes |
720
+ | `processes.changed` | Developer processes start or stop |
551
721
 
552
- Fired when the desktop window list changes (windows opened, closed,
553
- moved, or resized).
722
+ #### `windows.changed`
554
723
 
555
724
  ```json
556
- {
557
- "event": "windows.changed",
558
- "data": {
559
- "windows": [ ... ],
560
- "added": [1234],
561
- "removed": [5678]
562
- }
563
- }
725
+ { "event": "windows.changed", "data": { "windowCount": 12, "added": [1234], "removed": [5678] } }
564
726
  ```
565
727
 
566
- | Field | Type | Description |
567
- |-----------|----------|------------------------------------|
568
- | `windows` | array | Full current window list |
569
- | `added` | number[] | Window IDs that appeared |
570
- | `removed` | number[] | Window IDs that disappeared |
728
+ #### `tmux.changed`
571
729
 
572
- ---
573
-
574
- ### `tmux.changed`
730
+ ```json
731
+ { "event": "tmux.changed", "data": { "sessionCount": 3, "sessions": ["myapp-a1b2c3"] } }
732
+ ```
575
733
 
576
- Fired when tmux sessions change (created, killed, panes added/removed).
734
+ #### `layer.switched`
577
735
 
578
736
  ```json
579
- {
580
- "event": "tmux.changed",
581
- "data": {
582
- "sessions": [ ... ]
583
- }
584
- }
737
+ { "event": "layer.switched", "data": { "index": 1 } }
585
738
  ```
586
739
 
587
- | Field | Type | Description |
588
- |------------|-------|--------------------------|
589
- | `sessions` | array | Full current session list |
740
+ #### `ocr.scanComplete`
590
741
 
591
- ---
592
-
593
- ### `layer.switched`
742
+ ```json
743
+ { "event": "ocr.scanComplete", "data": { "windowCount": 12, "totalBlocks": 342 } }
744
+ ```
594
745
 
595
- Fired when the active workspace layer changes.
746
+ #### `processes.changed`
596
747
 
597
748
  ```json
598
- {
599
- "event": "layer.switched",
600
- "data": {
601
- "index": 1
602
- }
603
- }
749
+ { "event": "processes.changed", "data": { "interestingCount": 5, "pids": [1234, 5678] } }
604
750
  ```
605
751
 
606
- | Field | Type | Description |
607
- |---------|--------|------------------------------|
608
- | `index` | number | Index of the now-active layer |
609
-
610
752
  ---
611
753
 
612
- ## Agent integration patterns
754
+ ## Agent integration
613
755
 
614
756
  ### CLAUDE.md snippet
615
757
 
@@ -631,7 +773,7 @@ is available at ws://127.0.0.1:9399.
631
773
 
632
774
  ### Import
633
775
  \```js
634
- import { daemonCall } from 'lattices/daemon-client'
776
+ import { daemonCall } from '@arach/lattices/daemon-client'
635
777
  \```
636
778
  ```
637
779
 
@@ -640,7 +782,7 @@ import { daemonCall } from 'lattices/daemon-client'
640
782
  An orchestrator agent can set up the full workspace for sub-agents:
641
783
 
642
784
  ```js
643
- import { daemonCall } from 'lattices/daemon-client'
785
+ import { daemonCall } from '@arach/lattices/daemon-client'
644
786
 
645
787
  // Discover what's available
646
788
  const projects = await daemonCall('projects.list')
@@ -660,10 +802,10 @@ await daemonCall('window.tile', { session: api.name, position: 'right' })
660
802
 
661
803
  ### Reactive event pattern
662
804
 
663
- Subscribe to events for real-time workspace awareness:
805
+ Subscribe to events to react to workspace changes:
664
806
 
665
807
  ```js
666
- import WebSocket from 'ws' // or use the built-in client
808
+ import WebSocket from 'ws'
667
809
 
668
810
  const ws = new WebSocket('ws://127.0.0.1:9399')
669
811
 
@@ -671,12 +813,11 @@ ws.on('message', (raw) => {
671
813
  const msg = JSON.parse(raw)
672
814
 
673
815
  if (msg.event === 'tmux.changed') {
674
- console.log('Sessions changed:', msg.data.sessions.length, 'active')
816
+ console.log('Sessions:', msg.data.sessions.join(', '))
675
817
  }
676
818
 
677
819
  if (msg.event === 'windows.changed') {
678
- const latticesWindows = msg.data.windows.filter(w => w.latticesSession)
679
- console.log('Lattices windows:', latticesWindows.length)
820
+ console.log('Windows:', msg.data.windowCount, 'total')
680
821
  }
681
822
 
682
823
  if (msg.event === 'layer.switched') {
@@ -695,7 +836,7 @@ ws.on('open', () => {
695
836
  Always verify the daemon is running before making calls:
696
837
 
697
838
  ```js
698
- import { isDaemonRunning, daemonCall } from 'lattices/daemon-client'
839
+ import { isDaemonRunning, daemonCall } from '@arach/lattices/daemon-client'
699
840
 
700
841
  if (!(await isDaemonRunning())) {
701
842
  console.error('lattices daemon is not running — start it with: lattices app')