@arach/lattices 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -0
- package/app/Lattices.app/Contents/Info.plist +24 -0
- package/app/Package.swift +13 -0
- package/app/Sources/App.swift +49 -0
- package/app/Sources/AppDelegate.swift +104 -0
- package/app/Sources/AppShellView.swift +62 -0
- package/app/Sources/AppTypeClassifier.swift +70 -0
- package/app/Sources/AppWindowShell.swift +63 -0
- package/app/Sources/CheatSheetHUD.swift +331 -0
- package/app/Sources/CommandModeState.swift +1341 -0
- package/app/Sources/CommandModeView.swift +1380 -0
- package/app/Sources/CommandModeWindow.swift +192 -0
- package/app/Sources/CommandPaletteView.swift +307 -0
- package/app/Sources/CommandPaletteWindow.swift +134 -0
- package/app/Sources/DaemonProtocol.swift +101 -0
- package/app/Sources/DaemonServer.swift +406 -0
- package/app/Sources/DesktopModel.swift +121 -0
- package/app/Sources/DesktopModelTypes.swift +71 -0
- package/app/Sources/DiagnosticLog.swift +253 -0
- package/app/Sources/EventBus.swift +29 -0
- package/app/Sources/HotkeyManager.swift +249 -0
- package/app/Sources/HotkeyStore.swift +330 -0
- package/app/Sources/InventoryManager.swift +35 -0
- package/app/Sources/InventoryPath.swift +43 -0
- package/app/Sources/KeyRecorderView.swift +210 -0
- package/app/Sources/LatticesApi.swift +915 -0
- package/app/Sources/MainView.swift +507 -0
- package/app/Sources/MainWindow.swift +70 -0
- package/app/Sources/OrphanRow.swift +129 -0
- package/app/Sources/PaletteCommand.swift +409 -0
- package/app/Sources/PermissionChecker.swift +115 -0
- package/app/Sources/Preferences.swift +48 -0
- package/app/Sources/ProcessModel.swift +199 -0
- package/app/Sources/ProcessQuery.swift +151 -0
- package/app/Sources/Project.swift +28 -0
- package/app/Sources/ProjectRow.swift +368 -0
- package/app/Sources/ProjectScanner.swift +121 -0
- package/app/Sources/ScreenMapState.swift +2397 -0
- package/app/Sources/ScreenMapView.swift +2817 -0
- package/app/Sources/ScreenMapWindowController.swift +89 -0
- package/app/Sources/SessionManager.swift +72 -0
- package/app/Sources/SettingsView.swift +641 -0
- package/app/Sources/SettingsWindow.swift +20 -0
- package/app/Sources/TabGroupRow.swift +178 -0
- package/app/Sources/Terminal.swift +259 -0
- package/app/Sources/TerminalQuery.swift +156 -0
- package/app/Sources/TerminalSynthesizer.swift +200 -0
- package/app/Sources/Theme.swift +124 -0
- package/app/Sources/TilePickerView.swift +209 -0
- package/app/Sources/TmuxModel.swift +53 -0
- package/app/Sources/TmuxQuery.swift +81 -0
- package/app/Sources/WindowTiler.swift +1752 -0
- package/app/Sources/WorkspaceManager.swift +434 -0
- package/bin/daemon-client.js +187 -0
- package/bin/lattices-app.js +205 -0
- package/bin/lattices.js +1295 -0
- package/docs/api.md +707 -0
- package/docs/app.md +250 -0
- package/docs/concepts.md +225 -0
- package/docs/config.md +234 -0
- package/docs/layers.md +317 -0
- package/docs/overview.md +74 -0
- package/docs/quickstart.md +82 -0
- package/package.json +38 -0
package/docs/api.md
ADDED
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Daemon API
|
|
3
|
+
description: WebSocket API reference for programmatic control of lattices
|
|
4
|
+
order: 5
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Daemon API
|
|
8
|
+
|
|
9
|
+
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).
|
|
24
|
+
|
|
25
|
+
## Quick start
|
|
26
|
+
|
|
27
|
+
1. Launch the daemon (it starts with the menu bar app):
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
lattices app
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
2. Check that it's running:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
lattices daemon status
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
3. Call a method from Node.js:
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
import { daemonCall } from 'lattices/daemon-client'
|
|
43
|
+
|
|
44
|
+
const windows = await daemonCall('windows.list')
|
|
45
|
+
console.log(windows) // [{ wid, app, title, frame, ... }, ...]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or from any language — it's a standard WebSocket:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Plain websocat example
|
|
52
|
+
echo '{"id":"1","method":"daemon.status"}' | websocat ws://127.0.0.1:9399
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Wire protocol
|
|
56
|
+
|
|
57
|
+
lattices uses a JSON-RPC-style protocol over WebSocket on port **9399**.
|
|
58
|
+
|
|
59
|
+
### Request
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"id": "unique-string",
|
|
64
|
+
"method": "windows.list",
|
|
65
|
+
"params": { "wid": 1234 }
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
| Field | Type | Required | Description |
|
|
70
|
+
|----------|---------|----------|--------------------------------------|
|
|
71
|
+
| `id` | string | yes | Caller-chosen ID, echoed in response |
|
|
72
|
+
| `method` | string | yes | Method name (see below) |
|
|
73
|
+
| `params` | object | no | Method-specific parameters |
|
|
74
|
+
|
|
75
|
+
### Response
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"id": "unique-string",
|
|
80
|
+
"result": [ ... ],
|
|
81
|
+
"error": null
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
| Field | Type | Description |
|
|
86
|
+
|----------|----------------|----------------------------------------------|
|
|
87
|
+
| `id` | string | Echoed from request |
|
|
88
|
+
| `result` | any \| null | Method return value (null on error) |
|
|
89
|
+
| `error` | string \| null | Error message (null on success) |
|
|
90
|
+
|
|
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
|
+
### Errors
|
|
104
|
+
|
|
105
|
+
Three error types:
|
|
106
|
+
|
|
107
|
+
| Error | Meaning |
|
|
108
|
+
|-----------------|--------------------------------------|
|
|
109
|
+
| Unknown method | The `method` string is not recognized |
|
|
110
|
+
| Missing parameter | A required param was not provided |
|
|
111
|
+
| Not found | The referenced resource doesn't exist |
|
|
112
|
+
|
|
113
|
+
## Node.js client
|
|
114
|
+
|
|
115
|
+
lattices ships a zero-dependency WebSocket client that works with
|
|
116
|
+
Node.js 18+. It handles connection, framing, and request/response
|
|
117
|
+
matching internally.
|
|
118
|
+
|
|
119
|
+
### `daemonCall(method, params?, timeoutMs?)`
|
|
120
|
+
|
|
121
|
+
Send an RPC call and await the response.
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
import { daemonCall } from 'lattices/daemon-client'
|
|
125
|
+
|
|
126
|
+
// Read-only
|
|
127
|
+
const status = await daemonCall('daemon.status')
|
|
128
|
+
const windows = await daemonCall('windows.list')
|
|
129
|
+
const win = await daemonCall('windows.get', { wid: 1234 })
|
|
130
|
+
|
|
131
|
+
// Mutations
|
|
132
|
+
await daemonCall('session.launch', { path: '/Users/you/dev/myapp' })
|
|
133
|
+
await daemonCall('window.tile', { session: 'myapp-a1b2c3', position: 'left' })
|
|
134
|
+
|
|
135
|
+
// Custom timeout (default: 5000ms)
|
|
136
|
+
await daemonCall('projects.scan', null, 10000)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Returns** the `result` field from the response.
|
|
140
|
+
**Throws** if the daemon returns an error, the connection fails, or the timeout is reached.
|
|
141
|
+
|
|
142
|
+
### `isDaemonRunning()`
|
|
143
|
+
|
|
144
|
+
Check if the daemon is reachable.
|
|
145
|
+
|
|
146
|
+
```js
|
|
147
|
+
import { isDaemonRunning } from 'lattices/daemon-client'
|
|
148
|
+
|
|
149
|
+
if (await isDaemonRunning()) {
|
|
150
|
+
console.log('daemon is up')
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Returns `true` if `daemon.status` responds within 1 second.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Read methods
|
|
159
|
+
|
|
160
|
+
### `daemon.status`
|
|
161
|
+
|
|
162
|
+
Health check and basic stats.
|
|
163
|
+
|
|
164
|
+
**Params**: none
|
|
165
|
+
|
|
166
|
+
**Returns**:
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"uptime": 3600.5,
|
|
171
|
+
"clientCount": 2,
|
|
172
|
+
"version": "1.0.0",
|
|
173
|
+
"windowCount": 12,
|
|
174
|
+
"tmuxSessionCount": 3
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
### `windows.list`
|
|
181
|
+
|
|
182
|
+
List all visible windows tracked by the desktop model.
|
|
183
|
+
|
|
184
|
+
**Params**: none
|
|
185
|
+
|
|
186
|
+
**Returns**: array of window objects:
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
[
|
|
190
|
+
{
|
|
191
|
+
"wid": 1234,
|
|
192
|
+
"app": "Terminal",
|
|
193
|
+
"pid": 5678,
|
|
194
|
+
"title": "[lattices:myapp-a1b2c3] zsh",
|
|
195
|
+
"frame": { "x": 0, "y": 25, "w": 960, "h": 1050 },
|
|
196
|
+
"spaceIds": [1],
|
|
197
|
+
"isOnScreen": true,
|
|
198
|
+
"latticesSession": "myapp-a1b2c3"
|
|
199
|
+
}
|
|
200
|
+
]
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The `latticesSession` field is present only on windows that belong to
|
|
204
|
+
a lattices session (matched via the `[lattices:name]` title tag).
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
### `windows.get`
|
|
209
|
+
|
|
210
|
+
Get a single window by its CGWindowID.
|
|
211
|
+
|
|
212
|
+
**Params**:
|
|
213
|
+
|
|
214
|
+
| Field | Type | Required | Description |
|
|
215
|
+
|-------|--------|----------|-------------------|
|
|
216
|
+
| `wid` | number | yes | CGWindowID |
|
|
217
|
+
|
|
218
|
+
**Returns**: a single window object (same shape as `windows.list` items).
|
|
219
|
+
|
|
220
|
+
**Errors**: `Not found` if the window ID doesn't exist.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### `tmux.sessions`
|
|
225
|
+
|
|
226
|
+
List tmux sessions that belong to lattices.
|
|
227
|
+
|
|
228
|
+
**Params**: none
|
|
229
|
+
|
|
230
|
+
**Returns**: array of session objects:
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
[
|
|
234
|
+
{
|
|
235
|
+
"name": "myapp-a1b2c3",
|
|
236
|
+
"windowCount": 1,
|
|
237
|
+
"attached": true,
|
|
238
|
+
"panes": [
|
|
239
|
+
{
|
|
240
|
+
"id": "%0",
|
|
241
|
+
"windowIndex": 0,
|
|
242
|
+
"windowName": "main",
|
|
243
|
+
"title": "claude",
|
|
244
|
+
"currentCommand": "claude",
|
|
245
|
+
"pid": 9876,
|
|
246
|
+
"isActive": true
|
|
247
|
+
}
|
|
248
|
+
]
|
|
249
|
+
}
|
|
250
|
+
]
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
### `tmux.inventory`
|
|
256
|
+
|
|
257
|
+
List all tmux sessions including orphans (sessions not tracked by lattices).
|
|
258
|
+
|
|
259
|
+
**Params**: none
|
|
260
|
+
|
|
261
|
+
**Returns**:
|
|
262
|
+
|
|
263
|
+
```json
|
|
264
|
+
{
|
|
265
|
+
"all": [ ... ],
|
|
266
|
+
"orphans": [ ... ]
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Both arrays contain session objects (same shape as `tmux.sessions`).
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
### `projects.list`
|
|
275
|
+
|
|
276
|
+
List all discovered projects.
|
|
277
|
+
|
|
278
|
+
**Params**: none
|
|
279
|
+
|
|
280
|
+
**Returns**: array of project objects:
|
|
281
|
+
|
|
282
|
+
```json
|
|
283
|
+
[
|
|
284
|
+
{
|
|
285
|
+
"path": "/Users/you/dev/myapp",
|
|
286
|
+
"name": "myapp",
|
|
287
|
+
"sessionName": "myapp-a1b2c3",
|
|
288
|
+
"isRunning": true,
|
|
289
|
+
"hasConfig": true,
|
|
290
|
+
"paneCount": 2,
|
|
291
|
+
"paneNames": ["claude", "server"],
|
|
292
|
+
"devCommand": "pnpm dev",
|
|
293
|
+
"packageManager": "pnpm"
|
|
294
|
+
}
|
|
295
|
+
]
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
`devCommand` and `packageManager` are present only when detected.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
### `spaces.list`
|
|
303
|
+
|
|
304
|
+
List macOS display spaces (virtual desktops).
|
|
305
|
+
|
|
306
|
+
**Params**: none
|
|
307
|
+
|
|
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`
|
|
327
|
+
|
|
328
|
+
List configured workspace layers and the active index.
|
|
329
|
+
|
|
330
|
+
**Params**: none
|
|
331
|
+
|
|
332
|
+
**Returns**:
|
|
333
|
+
|
|
334
|
+
```json
|
|
335
|
+
{
|
|
336
|
+
"layers": [
|
|
337
|
+
{ "id": "web", "label": "Web", "index": 0, "projectCount": 2 },
|
|
338
|
+
{ "id": "mobile", "label": "Mobile", "index": 1, "projectCount": 2 }
|
|
339
|
+
],
|
|
340
|
+
"active": 0
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Returns empty `layers` array if no workspace config is loaded.
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Write methods
|
|
349
|
+
|
|
350
|
+
### `session.launch`
|
|
351
|
+
|
|
352
|
+
Launch a new tmux session for a project.
|
|
353
|
+
|
|
354
|
+
**Params**:
|
|
355
|
+
|
|
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
|
+
---
|
|
370
|
+
|
|
371
|
+
### `session.kill`
|
|
372
|
+
|
|
373
|
+
Kill a tmux session by name.
|
|
374
|
+
|
|
375
|
+
**Params**:
|
|
376
|
+
|
|
377
|
+
| Field | Type | Required | Description |
|
|
378
|
+
|--------|--------|----------|---------------------|
|
|
379
|
+
| `name` | string | yes | Session name |
|
|
380
|
+
|
|
381
|
+
**Returns**: `{ "ok": true }`
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
### `session.detach`
|
|
386
|
+
|
|
387
|
+
Detach all clients from a session (keeps it running).
|
|
388
|
+
|
|
389
|
+
**Params**:
|
|
390
|
+
|
|
391
|
+
| Field | Type | Required | Description |
|
|
392
|
+
|--------|--------|----------|---------------------|
|
|
393
|
+
| `name` | string | yes | Session name |
|
|
394
|
+
|
|
395
|
+
**Returns**: `{ "ok": true }`
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
### `session.sync`
|
|
400
|
+
|
|
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.
|
|
404
|
+
|
|
405
|
+
**Params**:
|
|
406
|
+
|
|
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.
|
|
420
|
+
|
|
421
|
+
**Params**:
|
|
422
|
+
|
|
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) |
|
|
427
|
+
|
|
428
|
+
**Returns**: `{ "ok": true }`
|
|
429
|
+
|
|
430
|
+
**Errors**: `Not found` if the path isn't in the project list.
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
### `window.tile`
|
|
435
|
+
|
|
436
|
+
Tile a session's terminal window to a screen position.
|
|
437
|
+
|
|
438
|
+
**Params**:
|
|
439
|
+
|
|
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
|
+
---
|
|
451
|
+
|
|
452
|
+
### `window.focus`
|
|
453
|
+
|
|
454
|
+
Focus a window — bring it to front and switch Spaces if needed.
|
|
455
|
+
|
|
456
|
+
**Params** (one of):
|
|
457
|
+
|
|
458
|
+
| Field | Type | Required | Description |
|
|
459
|
+
|-----------|--------|----------|---------------------------------|
|
|
460
|
+
| `wid` | number | no | CGWindowID (any window) |
|
|
461
|
+
| `session` | string | no | Session name (lattices windows) |
|
|
462
|
+
|
|
463
|
+
Provide either `wid` or `session`. If `wid` is given, it takes priority.
|
|
464
|
+
|
|
465
|
+
**Returns**: `{ "ok": true }` (with `wid` and `app` if focused by wid)
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
### `window.move`
|
|
470
|
+
|
|
471
|
+
Move a session's window to a different macOS Space.
|
|
472
|
+
|
|
473
|
+
**Params**:
|
|
474
|
+
|
|
475
|
+
| Field | Type | Required | Description |
|
|
476
|
+
|-----------|--------|----------|----------------------------|
|
|
477
|
+
| `session` | string | yes | Session name |
|
|
478
|
+
| `spaceId` | number | yes | Target Space ID (from `spaces.list`) |
|
|
479
|
+
|
|
480
|
+
**Returns**: `{ "ok": true }`
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
### `layer.switch`
|
|
485
|
+
|
|
486
|
+
Switch the active workspace layer.
|
|
487
|
+
|
|
488
|
+
**Params**:
|
|
489
|
+
|
|
490
|
+
| Field | Type | Required | Description |
|
|
491
|
+
|---------|--------|----------|--------------------------------|
|
|
492
|
+
| `index` | number | yes | Layer index (0-based) |
|
|
493
|
+
|
|
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.
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
### `group.launch`
|
|
503
|
+
|
|
504
|
+
Launch a tab group session.
|
|
505
|
+
|
|
506
|
+
**Params**:
|
|
507
|
+
|
|
508
|
+
| Field | Type | Required | Description |
|
|
509
|
+
|-------|--------|----------|------------------|
|
|
510
|
+
| `id` | string | yes | Group ID |
|
|
511
|
+
|
|
512
|
+
**Returns**: `{ "ok": true }`
|
|
513
|
+
|
|
514
|
+
**Errors**: `Not found` if the group ID doesn't match any configured group.
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
### `group.kill`
|
|
519
|
+
|
|
520
|
+
Kill a tab group session.
|
|
521
|
+
|
|
522
|
+
**Params**:
|
|
523
|
+
|
|
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
|
+
---
|
|
533
|
+
|
|
534
|
+
### `projects.scan`
|
|
535
|
+
|
|
536
|
+
Trigger a re-scan of the project directory. Useful after cloning a new
|
|
537
|
+
repo or adding a `.lattices.json` config.
|
|
538
|
+
|
|
539
|
+
**Params**: none
|
|
540
|
+
|
|
541
|
+
**Returns**: `{ "ok": true }`
|
|
542
|
+
|
|
543
|
+
---
|
|
544
|
+
|
|
545
|
+
## Events
|
|
546
|
+
|
|
547
|
+
Events are pushed to all connected WebSocket clients when state changes.
|
|
548
|
+
They have no `id` field — listen for messages with an `event` field.
|
|
549
|
+
|
|
550
|
+
### `windows.changed`
|
|
551
|
+
|
|
552
|
+
Fired when the desktop window list changes (windows opened, closed,
|
|
553
|
+
moved, or resized).
|
|
554
|
+
|
|
555
|
+
```json
|
|
556
|
+
{
|
|
557
|
+
"event": "windows.changed",
|
|
558
|
+
"data": {
|
|
559
|
+
"windows": [ ... ],
|
|
560
|
+
"added": [1234],
|
|
561
|
+
"removed": [5678]
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
```
|
|
565
|
+
|
|
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 |
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
### `tmux.changed`
|
|
575
|
+
|
|
576
|
+
Fired when tmux sessions change (created, killed, panes added/removed).
|
|
577
|
+
|
|
578
|
+
```json
|
|
579
|
+
{
|
|
580
|
+
"event": "tmux.changed",
|
|
581
|
+
"data": {
|
|
582
|
+
"sessions": [ ... ]
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
| Field | Type | Description |
|
|
588
|
+
|------------|-------|--------------------------|
|
|
589
|
+
| `sessions` | array | Full current session list |
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
### `layer.switched`
|
|
594
|
+
|
|
595
|
+
Fired when the active workspace layer changes.
|
|
596
|
+
|
|
597
|
+
```json
|
|
598
|
+
{
|
|
599
|
+
"event": "layer.switched",
|
|
600
|
+
"data": {
|
|
601
|
+
"index": 1
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
| Field | Type | Description |
|
|
607
|
+
|---------|--------|------------------------------|
|
|
608
|
+
| `index` | number | Index of the now-active layer |
|
|
609
|
+
|
|
610
|
+
---
|
|
611
|
+
|
|
612
|
+
## Agent integration patterns
|
|
613
|
+
|
|
614
|
+
### CLAUDE.md snippet
|
|
615
|
+
|
|
616
|
+
Add this to your project's `CLAUDE.md` so any AI agent working in the
|
|
617
|
+
project knows how to control the workspace:
|
|
618
|
+
|
|
619
|
+
```markdown
|
|
620
|
+
## Workspace Control
|
|
621
|
+
|
|
622
|
+
This project uses lattices for workspace management. The daemon API
|
|
623
|
+
is available at ws://127.0.0.1:9399.
|
|
624
|
+
|
|
625
|
+
### Available commands
|
|
626
|
+
- List windows: `daemonCall('windows.list')`
|
|
627
|
+
- List sessions: `daemonCall('tmux.sessions')`
|
|
628
|
+
- Launch a project: `daemonCall('session.launch', { path: '/absolute/path' })`
|
|
629
|
+
- Tile a window: `daemonCall('window.tile', { session: 'name', position: 'left' })`
|
|
630
|
+
- Switch layer: `daemonCall('layer.switch', { index: 0 })`
|
|
631
|
+
|
|
632
|
+
### Import
|
|
633
|
+
\```js
|
|
634
|
+
import { daemonCall } from 'lattices/daemon-client'
|
|
635
|
+
\```
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### Multi-agent orchestration
|
|
639
|
+
|
|
640
|
+
An orchestrator agent can set up the full workspace for sub-agents:
|
|
641
|
+
|
|
642
|
+
```js
|
|
643
|
+
import { daemonCall } from 'lattices/daemon-client'
|
|
644
|
+
|
|
645
|
+
// Discover what's available
|
|
646
|
+
const projects = await daemonCall('projects.list')
|
|
647
|
+
|
|
648
|
+
// Launch the projects we need
|
|
649
|
+
await daemonCall('session.launch', { path: '/Users/you/dev/frontend' })
|
|
650
|
+
await daemonCall('session.launch', { path: '/Users/you/dev/api' })
|
|
651
|
+
|
|
652
|
+
// Tile them side by side
|
|
653
|
+
const sessions = await daemonCall('tmux.sessions')
|
|
654
|
+
const fe = sessions.find(s => s.name.startsWith('frontend'))
|
|
655
|
+
const api = sessions.find(s => s.name.startsWith('api'))
|
|
656
|
+
|
|
657
|
+
await daemonCall('window.tile', { session: fe.name, position: 'left' })
|
|
658
|
+
await daemonCall('window.tile', { session: api.name, position: 'right' })
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### Reactive event pattern
|
|
662
|
+
|
|
663
|
+
Subscribe to events for real-time workspace awareness:
|
|
664
|
+
|
|
665
|
+
```js
|
|
666
|
+
import WebSocket from 'ws' // or use the built-in client
|
|
667
|
+
|
|
668
|
+
const ws = new WebSocket('ws://127.0.0.1:9399')
|
|
669
|
+
|
|
670
|
+
ws.on('message', (raw) => {
|
|
671
|
+
const msg = JSON.parse(raw)
|
|
672
|
+
|
|
673
|
+
if (msg.event === 'tmux.changed') {
|
|
674
|
+
console.log('Sessions changed:', msg.data.sessions.length, 'active')
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (msg.event === 'windows.changed') {
|
|
678
|
+
const latticesWindows = msg.data.windows.filter(w => w.latticesSession)
|
|
679
|
+
console.log('Lattices windows:', latticesWindows.length)
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (msg.event === 'layer.switched') {
|
|
683
|
+
console.log('Switched to layer', msg.data.index)
|
|
684
|
+
}
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
// You can also send RPC calls on the same connection
|
|
688
|
+
ws.on('open', () => {
|
|
689
|
+
ws.send(JSON.stringify({ id: '1', method: 'daemon.status' }))
|
|
690
|
+
})
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
### Health check before use
|
|
694
|
+
|
|
695
|
+
Always verify the daemon is running before making calls:
|
|
696
|
+
|
|
697
|
+
```js
|
|
698
|
+
import { isDaemonRunning, daemonCall } from 'lattices/daemon-client'
|
|
699
|
+
|
|
700
|
+
if (!(await isDaemonRunning())) {
|
|
701
|
+
console.error('lattices daemon is not running — start it with: lattices app')
|
|
702
|
+
process.exit(1)
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const status = await daemonCall('daemon.status')
|
|
706
|
+
console.log(`Daemon up for ${Math.round(status.uptime)}s, tracking ${status.windowCount} windows`)
|
|
707
|
+
```
|