@soederpop/luca 0.0.29 → 0.0.30

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 (46) hide show
  1. package/commands/try-all-challenges.ts +1 -1
  2. package/docs/TABLE-OF-CONTENTS.md +0 -3
  3. package/docs/tutorials/20-browser-esm.md +234 -0
  4. package/package.json +1 -1
  5. package/src/agi/container.server.ts +4 -0
  6. package/src/agi/features/assistant.ts +62 -1
  7. package/src/agi/features/browser-use.ts +623 -0
  8. package/src/bootstrap/generated.ts +236 -308
  9. package/src/cli/build-info.ts +2 -2
  10. package/src/clients/rest.ts +7 -7
  11. package/src/commands/chat.ts +22 -0
  12. package/src/commands/describe.ts +67 -2
  13. package/src/commands/prompt.ts +23 -3
  14. package/src/container.ts +411 -113
  15. package/src/helper.ts +189 -5
  16. package/src/introspection/generated.agi.ts +17148 -11148
  17. package/src/introspection/generated.node.ts +5179 -2200
  18. package/src/introspection/generated.web.ts +379 -291
  19. package/src/introspection/index.ts +7 -0
  20. package/src/introspection/scan.ts +224 -7
  21. package/src/node/container.ts +31 -10
  22. package/src/node/features/content-db.ts +7 -7
  23. package/src/node/features/disk-cache.ts +11 -11
  24. package/src/node/features/esbuild.ts +3 -3
  25. package/src/node/features/file-manager.ts +15 -15
  26. package/src/node/features/fs.ts +23 -22
  27. package/src/node/features/git.ts +10 -10
  28. package/src/node/features/ink.ts +13 -13
  29. package/src/node/features/ipc-socket.ts +8 -8
  30. package/src/node/features/networking.ts +3 -3
  31. package/src/node/features/os.ts +7 -7
  32. package/src/node/features/package-finder.ts +15 -15
  33. package/src/node/features/proc.ts +1 -1
  34. package/src/node/features/ui.ts +13 -13
  35. package/src/node/features/vm.ts +4 -4
  36. package/src/scaffolds/generated.ts +1 -1
  37. package/src/servers/express.ts +6 -6
  38. package/src/servers/mcp.ts +4 -4
  39. package/src/servers/socket.ts +6 -6
  40. package/docs/apis/features/node/window-manager.md +0 -445
  41. package/docs/examples/window-manager-layouts.md +0 -180
  42. package/docs/examples/window-manager.md +0 -125
  43. package/docs/window-manager-fix.md +0 -249
  44. package/scripts/test-window-manager-lifecycle.ts +0 -86
  45. package/scripts/test-window-manager.ts +0 -43
  46. package/src/node/features/window-manager.ts +0 -1603
@@ -1,445 +0,0 @@
1
- # WindowManager (features.windowManager)
2
-
3
- WindowManager Feature — Native window control via LucaVoiceLauncher Acts as an IPC server that the native macOS launcher app connects to. Communicates over a Unix domain socket using NDJSON (newline-delimited JSON). **Protocol:** - Bun listens on a Unix domain socket; the native app connects as a client - Window dispatch commands are sent as NDJSON with a `window` field - The app executes window commands and sends back `windowAck` messages - Any non-windowAck message from the app is emitted as a `message` event - Other features can use `send()` to write arbitrary NDJSON to the app **Capabilities:** - Spawn native browser windows with configurable chrome - Navigate, focus, close, and eval JavaScript in windows - Automatic socket file cleanup and fallback paths
4
-
5
- ## Usage
6
-
7
- ```ts
8
- container.feature('windowManager', {
9
- // Path to the Unix domain socket the server listens on
10
- socketPath,
11
- // Automatically start listening when the feature is enabled
12
- autoListen,
13
- // Per-request timeout in milliseconds for window operations
14
- requestTimeoutMs,
15
- })
16
- ```
17
-
18
- ## Options (Zod v4 schema)
19
-
20
- | Property | Type | Description |
21
- |----------|------|-------------|
22
- | `socketPath` | `string` | Path to the Unix domain socket the server listens on |
23
- | `autoListen` | `boolean` | Automatically start listening when the feature is enabled |
24
- | `requestTimeoutMs` | `number` | Per-request timeout in milliseconds for window operations |
25
-
26
- ## Methods
27
-
28
- ### enable
29
-
30
- **Parameters:**
31
-
32
- | Name | Type | Required | Description |
33
- |------|------|----------|-------------|
34
- | `options` | `any` | | Parameter options |
35
-
36
- **Returns:** `Promise<this>`
37
-
38
-
39
-
40
- ### listen
41
-
42
- Start listening on the Unix domain socket for the native app to connect. Fire-and-forget — binds the socket and returns immediately. Sits quietly until the native app connects; does nothing visible if it never does.
43
-
44
- **Parameters:**
45
-
46
- | Name | Type | Required | Description |
47
- |------|------|----------|-------------|
48
- | `socketPath` | `string` | | Override the configured socket path |
49
-
50
- **Returns:** `this`
51
-
52
-
53
-
54
- ### stop
55
-
56
- Stop the IPC server and clean up all connections. Rejects any pending window operation requests.
57
-
58
- **Returns:** `Promise<this>`
59
-
60
-
61
-
62
- ### spawn
63
-
64
- Spawn a new native browser window. Sends a window dispatch to the app and waits for the ack.
65
-
66
- **Parameters:**
67
-
68
- | Name | Type | Required | Description |
69
- |------|------|----------|-------------|
70
- | `opts` | `SpawnOptions` | | Window configuration (url, dimensions, chrome options) |
71
-
72
- `SpawnOptions` properties:
73
-
74
- | Property | Type | Description |
75
- |----------|------|-------------|
76
- | `url` | `string` | |
77
- | `width` | `DimensionValue` | |
78
- | `height` | `DimensionValue` | |
79
- | `x` | `DimensionValue` | |
80
- | `y` | `DimensionValue` | |
81
- | `alwaysOnTop` | `boolean` | |
82
- | `window` | `{
83
- decorations?: 'normal' | 'hiddenTitleBar' | 'none'
84
- transparent?: boolean
85
- shadow?: boolean
86
- alwaysOnTop?: boolean
87
- opacity?: number
88
- clickThrough?: boolean
89
- }` | |
90
-
91
- **Returns:** `Promise<WindowHandle>`
92
-
93
-
94
-
95
- ### spawnTTY
96
-
97
- Spawn a native terminal window running a command. The terminal is read-only — stdout/stderr are rendered with ANSI support. Closing the window terminates the process.
98
-
99
- **Parameters:**
100
-
101
- | Name | Type | Required | Description |
102
- |------|------|----------|-------------|
103
- | `opts` | `SpawnTTYOptions` | ✓ | Terminal configuration (command, args, cwd, dimensions, etc.) |
104
-
105
- `SpawnTTYOptions` properties:
106
-
107
- | Property | Type | Description |
108
- |----------|------|-------------|
109
- | `command` | `string` | Executable name or path (required). |
110
- | `args` | `string[]` | Arguments passed after the command. |
111
- | `cwd` | `string` | Working directory for the process. |
112
- | `env` | `Record<string, string>` | Environment variable overrides. |
113
- | `cols` | `number` | Initial terminal columns. |
114
- | `rows` | `number` | Initial terminal rows. |
115
- | `title` | `string` | Window title. |
116
- | `width` | `DimensionValue` | Window width in points. |
117
- | `height` | `DimensionValue` | Window height in points. |
118
- | `x` | `DimensionValue` | Window x position. |
119
- | `y` | `DimensionValue` | Window y position. |
120
- | `window` | `SpawnOptions['window']` | Chrome options (decorations, alwaysOnTop, etc.) |
121
-
122
- **Returns:** `Promise<WindowHandle>`
123
-
124
-
125
-
126
- ### focus
127
-
128
- Bring a window to the front.
129
-
130
- **Parameters:**
131
-
132
- | Name | Type | Required | Description |
133
- |------|------|----------|-------------|
134
- | `windowId` | `string` | | The window ID. If omitted, the app uses the most recent window. |
135
-
136
- **Returns:** `Promise<WindowAckResult>`
137
-
138
-
139
-
140
- ### close
141
-
142
- Close a window.
143
-
144
- **Parameters:**
145
-
146
- | Name | Type | Required | Description |
147
- |------|------|----------|-------------|
148
- | `windowId` | `string` | | The window ID. If omitted, the app closes the most recent window. |
149
-
150
- **Returns:** `Promise<WindowAckResult>`
151
-
152
-
153
-
154
- ### navigate
155
-
156
- Navigate a window to a new URL.
157
-
158
- **Parameters:**
159
-
160
- | Name | Type | Required | Description |
161
- |------|------|----------|-------------|
162
- | `windowId` | `string` | ✓ | The window ID |
163
- | `url` | `string` | ✓ | The URL to navigate to |
164
-
165
- **Returns:** `Promise<WindowAckResult>`
166
-
167
-
168
-
169
- ### eval
170
-
171
- Evaluate JavaScript in a window's web view.
172
-
173
- **Parameters:**
174
-
175
- | Name | Type | Required | Description |
176
- |------|------|----------|-------------|
177
- | `windowId` | `string` | ✓ | The window ID |
178
- | `code` | `string` | ✓ | JavaScript code to evaluate |
179
- | `opts` | `{ timeoutMs?: number; returnJson?: boolean }` | | timeoutMs (default 5000), returnJson (default true) |
180
-
181
- **Returns:** `Promise<WindowAckResult>`
182
-
183
-
184
-
185
- ### screengrab
186
-
187
- Capture a PNG screenshot from a window.
188
-
189
- **Parameters:**
190
-
191
- | Name | Type | Required | Description |
192
- |------|------|----------|-------------|
193
- | `opts` | `WindowScreenGrabOptions` | ✓ | Window target and output path |
194
-
195
- `WindowScreenGrabOptions` properties:
196
-
197
- | Property | Type | Description |
198
- |----------|------|-------------|
199
- | `windowId` | `string` | Window ID. If omitted, the launcher uses the most recent window. |
200
- | `path` | `string` | Output file path for the PNG image. |
201
-
202
- **Returns:** `Promise<WindowAckResult>`
203
-
204
-
205
-
206
- ### video
207
-
208
- Record a video from a window to disk.
209
-
210
- **Parameters:**
211
-
212
- | Name | Type | Required | Description |
213
- |------|------|----------|-------------|
214
- | `opts` | `WindowVideoOptions` | ✓ | Window target, output path, and optional duration |
215
-
216
- `WindowVideoOptions` properties:
217
-
218
- | Property | Type | Description |
219
- |----------|------|-------------|
220
- | `windowId` | `string` | Window ID. If omitted, the launcher uses the most recent window. |
221
- | `path` | `string` | Output file path for the video file. |
222
- | `durationMs` | `number` | Recording duration in milliseconds. |
223
-
224
- **Returns:** `Promise<WindowAckResult>`
225
-
226
-
227
-
228
- ### window
229
-
230
- Get a WindowHandle for chainable operations on a specific window. Returns the tracked handle if one exists, otherwise creates a new one.
231
-
232
- **Parameters:**
233
-
234
- | Name | Type | Required | Description |
235
- |------|------|----------|-------------|
236
- | `windowId` | `string` | ✓ | The window ID |
237
-
238
- **Returns:** `WindowHandle`
239
-
240
-
241
-
242
- ### spawnLayout
243
-
244
- Spawn multiple windows in parallel from a layout configuration. Returns handles in the same order as the config entries.
245
-
246
- **Parameters:**
247
-
248
- | Name | Type | Required | Description |
249
- |------|------|----------|-------------|
250
- | `config` | `LayoutEntry[]` | ✓ | Array of layout entries (window or tty) |
251
-
252
- **Returns:** `Promise<WindowHandle[]>`
253
-
254
- ```ts
255
- const handles = await wm.spawnLayout([
256
- { type: 'window', url: 'https://google.com', width: 800, height: 600 },
257
- { type: 'tty', command: 'htop' },
258
- { url: 'https://github.com' }, // defaults to window
259
- ])
260
- ```
261
-
262
-
263
-
264
- ### spawnLayouts
265
-
266
- Spawn multiple layouts sequentially. Each layout's windows spawn in parallel, but the next layout waits for the previous one to fully complete.
267
-
268
- **Parameters:**
269
-
270
- | Name | Type | Required | Description |
271
- |------|------|----------|-------------|
272
- | `configs` | `LayoutEntry[][]` | ✓ | Array of layout configurations |
273
-
274
- **Returns:** `Promise<WindowHandle[][]>`
275
-
276
- ```ts
277
- const [firstBatch, secondBatch] = await wm.spawnLayouts([
278
- [{ url: 'https://google.com' }, { url: 'https://github.com' }],
279
- [{ type: 'tty', command: 'htop' }],
280
- ])
281
- ```
282
-
283
-
284
-
285
- ### send
286
-
287
- Write an NDJSON message to the connected app client. Public so other features can send arbitrary protocol messages over the same socket.
288
-
289
- **Parameters:**
290
-
291
- | Name | Type | Required | Description |
292
- |------|------|----------|-------------|
293
- | `msg` | `Record<string, any>` | ✓ | The message object to send (will be JSON-serialized + newline) |
294
-
295
- **Returns:** `boolean`
296
-
297
-
298
-
299
- ## Getters
300
-
301
- | Property | Type | Description |
302
- |----------|------|-------------|
303
- | `isListening` | `boolean` | Whether the IPC server is currently listening. |
304
- | `isClientConnected` | `boolean` | Whether the native app client is currently connected. |
305
-
306
- ## Events (Zod v4 schema)
307
-
308
- ### listening
309
-
310
- Emitted when the IPC server starts listening
311
-
312
-
313
-
314
- ### clientConnected
315
-
316
- Emitted when the native app connects
317
-
318
- **Event Arguments:**
319
-
320
- | Name | Type | Description |
321
- |------|------|-------------|
322
- | `arg0` | `any` | The client socket |
323
-
324
-
325
-
326
- ### clientDisconnected
327
-
328
- Emitted when the native app disconnects
329
-
330
-
331
-
332
- ### windowAck
333
-
334
- Emitted when a window ack is received from the app
335
-
336
- **Event Arguments:**
337
-
338
- | Name | Type | Description |
339
- |------|------|-------------|
340
- | `arg0` | `any` | The window ack payload |
341
-
342
-
343
-
344
- ### windowClosed
345
-
346
- Emitted when the native app reports a window closed event
347
-
348
- **Event Arguments:**
349
-
350
- | Name | Type | Description |
351
- |------|------|-------------|
352
- | `arg0` | `any` | Window lifecycle payload emitted when a window closes |
353
-
354
-
355
-
356
- ### terminalExited
357
-
358
- Emitted when the native app reports a terminal process exit event
359
-
360
- **Event Arguments:**
361
-
362
- | Name | Type | Description |
363
- |------|------|-------------|
364
- | `arg0` | `any` | Terminal lifecycle payload emitted when a terminal process exits |
365
-
366
-
367
-
368
- ### message
369
-
370
- Emitted for any incoming message that is not a windowAck
371
-
372
- **Event Arguments:**
373
-
374
- | Name | Type | Description |
375
- |------|------|-------------|
376
- | `arg0` | `any` | The parsed message object |
377
-
378
-
379
-
380
- ### error
381
-
382
- Emitted on error
383
-
384
- **Event Arguments:**
385
-
386
- | Name | Type | Description |
387
- |------|------|-------------|
388
- | `arg0` | `any` | The error |
389
-
390
-
391
-
392
- ## State (Zod v4 schema)
393
-
394
- | Property | Type | Description |
395
- |----------|------|-------------|
396
- | `enabled` | `boolean` | Whether this feature is currently enabled |
397
- | `listening` | `boolean` | Whether the IPC server is listening |
398
- | `clientConnected` | `boolean` | Whether the native launcher app is connected |
399
- | `socketPath` | `string` | The socket path in use |
400
- | `windowCount` | `number` | Number of tracked windows |
401
- | `lastError` | `string` | Last error message |
402
-
403
- ## Examples
404
-
405
- **features.windowManager**
406
-
407
- ```ts
408
- const wm = container.feature('windowManager', { enable: true, autoListen: true })
409
-
410
- const handle = await wm.spawn({ url: 'https://google.com', width: 800, height: 600 })
411
- handle.on('close', (msg) => console.log('window closed'))
412
- await handle.navigate('https://news.ycombinator.com')
413
- const title = await handle.eval('document.title')
414
- await handle.close()
415
-
416
- // Other features can listen for non-window messages
417
- wm.on('message', (msg) => console.log('App says:', msg))
418
-
419
- // Other features can write raw NDJSON to the app
420
- wm.send({ id: 'abc', status: 'processing', speech: 'Working on it' })
421
- ```
422
-
423
-
424
-
425
- **spawnLayout**
426
-
427
- ```ts
428
- const handles = await wm.spawnLayout([
429
- { type: 'window', url: 'https://google.com', width: 800, height: 600 },
430
- { type: 'tty', command: 'htop' },
431
- { url: 'https://github.com' }, // defaults to window
432
- ])
433
- ```
434
-
435
-
436
-
437
- **spawnLayouts**
438
-
439
- ```ts
440
- const [firstBatch, secondBatch] = await wm.spawnLayouts([
441
- [{ url: 'https://google.com' }, { url: 'https://github.com' }],
442
- [{ type: 'tty', command: 'htop' }],
443
- ])
444
- ```
445
-
@@ -1,180 +0,0 @@
1
- ---
2
- title: "Window Manager Layouts"
3
- tags: [windowManager, layout, native, ipc, macos, multi-window]
4
- lastTested: null
5
- lastTestPassed: null
6
- ---
7
-
8
- # Window Manager Layouts
9
-
10
- Spawn and manage multiple windows at once using the layout API. Layouts let you declare groups of browser and terminal windows that open in parallel, or sequence multiple groups so each batch waits for the previous one to finish.
11
-
12
- ## Overview
13
-
14
- The `windowManager` feature exposes two layout methods:
15
-
16
- - **`spawnLayout(config)`** — spawns all entries in parallel, returns `WindowHandle[]`
17
- - **`spawnLayouts(configs)`** — spawns multiple layouts sequentially (each layout's windows still spawn in parallel), returns `WindowHandle[][]`
18
-
19
- Each entry in a layout is a `LayoutEntry` — either a browser window or a TTY window. Type detection is automatic: if the entry has a `command` field or `type: 'tty'`, it's a terminal. Otherwise it's a browser window.
20
-
21
- ## Setup
22
-
23
- ```ts
24
- const wm = container.feature('windowManager', {
25
- autoListen: true,
26
- requestTimeoutMs: 10000
27
- })
28
- console.log('Window Manager ready')
29
- ```
30
-
31
- ## Single Layout — Parallel Windows
32
-
33
- Spawn a mix of browser and terminal windows that all open at the same time.
34
-
35
- ```ts
36
- const handles = await wm.spawnLayout([
37
- { url: 'https://github.com', width: '50%', height: '100%', x: 0, y: 0 },
38
- { url: 'https://soederpop.com', width: '50%', height: '100%', x: '50%', y: 0 },
39
- { command: 'top', title: 'System Monitor', width: 900, height: 400, x: 0, y: 720 },
40
- ])
41
-
42
- console.log('Spawned', handles.length, 'windows')
43
- handles.forEach((h, i) => console.log(` [${i}] windowId: ${h.windowId}`))
44
- ```
45
-
46
- All three windows open simultaneously. The returned `handles` array preserves the same order as the config entries, so `handles[0]` corresponds to the GitHub window, `handles[1]` to HN, and `handles[2]` to htop.
47
-
48
- ## Percentage-Based Dimensions
49
-
50
- Dimensions (`width`, `height`, `x`, `y`) accept percentage strings resolved against the primary display. This makes layouts portable across different screen resolutions.
51
-
52
- ```ts skip
53
- // Side-by-side: two windows each taking half the screen
54
- const handles = await wm.spawnLayout([
55
- { url: 'https://github.com', width: '50%', height: '100%', x: '0%', y: '0%' },
56
- { url: 'https://news.ycombinator.com', width: '50%', height: '100%', x: '50%', y: '0%' },
57
- ])
58
- ```
59
-
60
- You can mix absolute and percentage values freely:
61
-
62
- ```ts skip
63
- const handles = await wm.spawnLayout([
64
- { url: 'https://example.com', width: '75%', height: 600, x: '12.5%', y: 50 },
65
- { command: 'htop', width: '100%', height: '30%', x: '0%', y: '70%' },
66
- ])
67
- ```
68
-
69
- ## Explicit Type Field
70
-
71
- You can be explicit about entry types using the `type` field. This is equivalent to the implicit detection but more readable when mixing window types.
72
-
73
- ```ts skip
74
- const handles = await wm.spawnLayout([
75
- { type: 'window', url: 'https://example.com', width: 800, height: 600 },
76
- { type: 'tty', command: 'tail -f /var/log/system.log', title: 'Logs' },
77
- ])
78
-
79
- console.log('Browser window:', handles[0].windowId)
80
- console.log('TTY window:', handles[1].windowId)
81
- ```
82
-
83
- ## Sequential Layouts
84
-
85
- Use `spawnLayouts()` when you need windows to appear in stages. Each layout batch spawns in parallel, but the next batch waits until the previous one is fully ready.
86
-
87
- ```ts skip
88
- const [dashboards, tools] = await wm.spawnLayouts([
89
- // First batch: main content
90
- [
91
- { url: 'https://grafana.internal/d/api-latency', width: 960, height: 800, x: 0, y: 0 },
92
- { url: 'https://grafana.internal/d/error-rate', width: 960, height: 800, x: 970, y: 0 },
93
- ],
94
- // Second batch: supporting tools (opens after dashboards are ready)
95
- [
96
- { command: 'htop', title: 'CPU', width: 640, height: 400, x: 0, y: 820 },
97
- { command: 'tail -f /var/log/system.log', title: 'Logs', width: 640, height: 400, x: 650, y: 820 },
98
- ],
99
- ])
100
-
101
- console.log('Dashboards:', dashboards.map(h => h.windowId))
102
- console.log('Tools:', tools.map(h => h.windowId))
103
- ```
104
-
105
- This is useful when the second batch depends on the first being visible — for example, positioning tool windows below dashboard windows.
106
-
107
- ## Lifecycle Events on Layout Handles
108
-
109
- Every handle returned from a layout supports the same event API as a single `spawn()` call. You can listen for `close` and `terminalExited` events on each handle independently.
110
-
111
- ```ts skip
112
- const handles = await wm.spawnLayout([
113
- { url: 'https://example.com', width: 800, height: 600 },
114
- { command: 'sleep 5 && echo done', title: 'Short Task' },
115
- ])
116
-
117
- const [browser, terminal] = handles
118
-
119
- browser.on('close', () => console.log('Browser window closed'))
120
-
121
- terminal.on('terminalExited', (info) => {
122
- console.log('Terminal process finished:', info)
123
- })
124
- ```
125
-
126
- ## Window Chrome Options
127
-
128
- Layout entries support all the same window chrome options as regular `spawn()` calls.
129
-
130
- ```ts skip
131
- const handles = await wm.spawnLayout([
132
- {
133
- url: 'https://example.com',
134
- width: 400,
135
- height: 300,
136
- alwaysOnTop: true,
137
- window: {
138
- decorations: 'hiddenTitleBar',
139
- transparent: true,
140
- shadow: true,
141
- opacity: 0.9,
142
- }
143
- },
144
- {
145
- url: 'https://example.com/overlay',
146
- width: 200,
147
- height: 100,
148
- window: {
149
- decorations: 'none',
150
- clickThrough: true,
151
- transparent: true,
152
- }
153
- },
154
- ])
155
- ```
156
-
157
- ## Operating on All Handles
158
-
159
- Since layouts return arrays of `WindowHandle`, you can easily batch operations across all windows.
160
-
161
- ```ts skip
162
- const handles = await wm.spawnLayout([
163
- { url: 'https://example.com/a', width: 600, height: 400 },
164
- { url: 'https://example.com/b', width: 600, height: 400 },
165
- { url: 'https://example.com/c', width: 600, height: 400 },
166
- ])
167
-
168
- // Navigate all windows to the same URL
169
- await Promise.all(handles.map(h => h.navigate('https://example.com/updated')))
170
-
171
- // Screenshot all windows
172
- await Promise.all(handles.map((h, i) => h.screengrab(`./layout-${i}.png`)))
173
-
174
- // Close all windows
175
- await Promise.all(handles.map(h => h.close()))
176
- ```
177
-
178
- ## Summary
179
-
180
- The layout API builds on top of `spawn()` and `spawnTTY()` to orchestrate multi-window setups. Use `spawnLayout()` for a single batch of parallel windows, and `spawnLayouts()` when you need staged sequences. Every returned handle supports the full `WindowHandle` API — events, navigation, eval, screenshots, and more.