@mindstudio-ai/remy 0.1.26 → 0.1.27

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 (30) hide show
  1. package/README.md +149 -41
  2. package/dist/compiled/tables.md +53 -1
  3. package/dist/headless.d.ts +10 -2
  4. package/dist/headless.js +524 -270
  5. package/dist/index.js +567 -300
  6. package/dist/prompt/.notes.md +0 -1
  7. package/dist/prompt/compiled/tables.md +53 -1
  8. package/dist/prompt/static/authoring.md +10 -0
  9. package/dist/prompt/static/instructions.md +2 -1
  10. package/dist/prompt/static/team.md +1 -1
  11. package/dist/static/authoring.md +10 -0
  12. package/dist/static/instructions.md +2 -1
  13. package/dist/static/team.md +1 -1
  14. package/dist/subagents/.notes-background-agents.md +80 -0
  15. package/dist/subagents/browserAutomation/prompt.md +37 -2
  16. package/dist/subagents/codeSanityCheck/prompt.md +1 -0
  17. package/dist/subagents/designExpert/.notes.md +2 -2
  18. package/dist/subagents/designExpert/data/compile-font-descriptions.sh +125 -0
  19. package/dist/subagents/designExpert/data/compile-inspiration.sh +6 -1
  20. package/dist/subagents/designExpert/data/fonts.json +497 -869
  21. package/dist/subagents/designExpert/data/inspiration.json +97 -245
  22. package/dist/subagents/designExpert/data/inspiration.raw.json +1 -12
  23. package/dist/subagents/designExpert/prompts/animation.md +1 -1
  24. package/dist/subagents/designExpert/prompts/identity.md +4 -2
  25. package/dist/subagents/designExpert/prompts/instructions.md +2 -3
  26. package/dist/subagents/designExpert/prompts/layout.md +1 -13
  27. package/dist/subagents/designExpert/prompts/tool-prompts/design-analysis.md +22 -0
  28. package/dist/subagents/designExpert/prompts/tool-prompts/font-analysis.md +17 -0
  29. package/dist/subagents/productVision/prompt.md +1 -1
  30. package/package.json +1 -1
package/README.md CHANGED
@@ -45,13 +45,20 @@ Remy saves conversation history to `.remy-session.json` in the working directory
45
45
 
46
46
  Tool availability depends on the project's onboarding state, sent by the sandbox on each message.
47
47
 
48
- ### Always Available
48
+ ### Common Tools (all onboarding states)
49
49
 
50
50
  | Tool | Description |
51
51
  |------|-------------|
52
52
  | `setProjectOnboardingState` | Advance the onboarding flow (intake → initialSpecAuthoring → initialCodegen → onboardingFinished) |
53
+ | `setProjectName` | Set the project name |
53
54
  | `promptUser` | Ask the user structured questions (form or inline display) |
54
55
  | `confirmDestructiveAction` | Confirm a destructive or irreversible action with the user |
56
+ | `askMindStudioSdk` | MindStudio SDK expert — answers questions about actions, models, connectors, and configuration (sub-agent) |
57
+ | `fetchUrl` | Fetch a URL and return its contents |
58
+ | `searchGoogle` | Search Google and return results |
59
+ | `visualDesignExpert` | Visual design expert for fonts, colors, palettes, gradients, layouts, imagery, and icons (sub-agent) |
60
+ | `productVision` | Owns the product roadmap — creates/updates/deletes roadmap items in `src/roadmap/` (sub-agent) |
61
+ | `codeSanityCheck` | Quick readonly sanity check on architecture and package choices before building (sub-agent) |
55
62
 
56
63
  ### Spec Tools
57
64
 
@@ -78,7 +85,10 @@ Available from `initialCodegen` onward.
78
85
  | `glob` | Find files by pattern |
79
86
  | `listDir` | List directory contents |
80
87
  | `editsFinished` | Signal that file edits are complete for live preview |
81
- | `askMindStudioSdk` | Ask the MindStudio SDK assistant about actions, models, connectors, and integrations |
88
+ | `runScenario` | Run a scenario to seed the dev database with test data |
89
+ | `runMethod` | Run a method in the dev environment and return the result |
90
+ | `screenshot` | Capture a screenshot of the app preview and get a description of what's on screen |
91
+ | `runAutomatedBrowserTest` | Run an automated browser test against the live preview with DOM snapshots and interaction execution (sub-agent) |
82
92
 
83
93
  ### LSP Tools (sandbox only)
84
94
 
@@ -119,6 +129,7 @@ User input
119
129
  ← text, thinking, tool_input_delta, tool_input_args, tool_use events
120
130
  → Execute tools locally in parallel
121
131
  → External tools wait for sandbox response
132
+ → Sub-agent tools run their own nested LLM loops
122
133
  → Send tool results back
123
134
  → Loop until done
124
135
  → Save session to .remy-session.json
@@ -126,6 +137,31 @@ User input
126
137
 
127
138
  The agent core (`src/agent.ts`) is a pure async function with no UI dependencies. The TUI (`src/tui/`) is an Ink + React layer on top. Headless mode (`src/headless.ts`) provides the same agent over a stdin/stdout JSON protocol for the sandbox.
128
139
 
140
+ ### Sub-Agents
141
+
142
+ Some tools are backed by sub-agents — they run their own nested LLM loops with specialized system prompts and tool subsets. Sub-agent events are tagged with `parentToolId` so the caller can associate them with the parent tool call.
143
+
144
+ | Sub-Agent | Tool Name | Location |
145
+ |-----------|-----------|----------|
146
+ | SDK Consultant | `askMindStudioSdk` | `src/subagents/sdkConsultant/` |
147
+ | Design Expert | `visualDesignExpert` | `src/subagents/designExpert/` |
148
+ | Product Vision | `productVision` | `src/subagents/productVision/` |
149
+ | Code Sanity Check | `codeSanityCheck` | `src/subagents/codeSanityCheck/` |
150
+ | Browser Automation | `runAutomatedBrowserTest` | `src/subagents/browserAutomation/` |
151
+
152
+ ### External Tools
153
+
154
+ Some tools are resolved by the sandbox rather than executed locally. Remy emits `tool_start`, then waits for the sandbox to send back a `tool_result` via stdin:
155
+
156
+ - `promptUser` — renders a form or inline prompt, blocks until user responds
157
+ - `setProjectOnboardingState` — advances the onboarding flow
158
+ - `setProjectName` — sets the project name
159
+ - `confirmDestructiveAction` — renders a confirmation dialog
160
+ - `clearSyncStatus` — clears sync dirty flags and updates git sync ref
161
+ - `presentSyncPlan` — renders a full-screen markdown plan for user approval
162
+ - `presentPublishPlan` — renders a full-screen changelog for user approval
163
+ - `presentPlan` — renders a full-screen implementation plan for user approval
164
+
129
165
  ### Project Structure
130
166
 
131
167
  ```
@@ -133,11 +169,14 @@ src/
133
169
  index.tsx CLI entry point
134
170
  agent.ts Core tool-call loop (pure async, no UI)
135
171
  api.ts SSE streaming client for platform API
136
- parsePartialJson.ts Partial JSON parser for streaming tool input
172
+ types.ts Shared types (AgentEvent, StdinCommand, etc.)
173
+ headless.ts stdin/stdout JSON protocol for sandbox
137
174
  session.ts .remy-session.json persistence
138
175
  config.ts API key/URL resolution
176
+ errors.ts Friendly error message mapping
177
+ statusWatcher.ts Background status label polling
178
+ parsePartialJson.ts Partial JSON parser for streaming tool input
139
179
  logger.ts Structured logging
140
- headless.ts stdin/stdout JSON protocol for sandbox
141
180
 
142
181
  prompt/
143
182
  index.ts System prompt builder (onboarding-state-aware)
@@ -149,7 +188,9 @@ src/
149
188
  identity.md
150
189
  intake.md
151
190
  authoring.md
191
+ coding.md
152
192
  instructions.md
193
+ team.md
153
194
  lsp.md
154
195
  projectContext.ts Reads manifest, spec metadata, file listing at runtime
155
196
  compiled/ Platform docs distilled for agent consumption
@@ -160,14 +201,18 @@ src/
160
201
  _helpers/
161
202
  diff.ts Unified diff generator
162
203
  lsp.ts LSP sidecar HTTP client
163
- spec/ Spec and external tools
204
+ common/ Always-available tools
205
+ promptUser.ts
206
+ confirmDestructiveAction.ts
207
+ setProjectOnboardingState.ts
208
+ setProjectName.ts
209
+ fetchUrl.ts
210
+ searchGoogle.ts
211
+ spec/ Spec tools
164
212
  readSpec.ts
165
213
  writeSpec.ts
166
214
  editSpec.ts
167
215
  listSpecFiles.ts
168
- setProjectOnboardingState.ts
169
- promptUser.ts
170
- confirmDestructiveAction.ts
171
216
  clearSyncStatus.ts
172
217
  presentSyncPlan.ts
173
218
  presentPublishPlan.ts
@@ -184,10 +229,20 @@ src/
184
229
  glob.ts
185
230
  listDir.ts
186
231
  editsFinished.ts
187
- askMindStudioSdk.ts
232
+ runScenario.ts
233
+ runMethod.ts
234
+ screenshot.ts
188
235
  lspDiagnostics.ts
189
236
  restartProcess.ts
190
237
 
238
+ subagents/
239
+ runner.ts Sub-agent LLM loop runner
240
+ sdkConsultant/ MindStudio SDK expert
241
+ designExpert/ Visual design expert
242
+ productVision/ Product roadmap manager
243
+ codeSanityCheck/ Architecture sanity checker
244
+ browserAutomation/ Automated browser testing
245
+
191
246
  tui/ Interactive terminal UI (Ink + React)
192
247
  App.tsx
193
248
  InputPrompt.tsx
@@ -196,18 +251,6 @@ src/
196
251
  ToolCall.tsx
197
252
  ```
198
253
 
199
- ### External Tools
200
-
201
- Some tools are resolved by the sandbox rather than executed locally. Remy emits `tool_start`, then waits for the sandbox to send back a `tool_result` via stdin:
202
-
203
- - `promptUser` — renders a form or inline prompt, blocks until user responds
204
- - `setProjectOnboardingState` — advances the onboarding flow
205
- - `confirmDestructiveAction` — renders a confirmation dialog
206
- - `clearSyncStatus` — clears sync dirty flags and updates git sync ref
207
- - `presentSyncPlan` — renders a full-screen markdown plan for user approval
208
- - `presentPublishPlan` — renders a full-screen changelog for user approval
209
- - `presentPlan` — renders a full-screen implementation plan for user approval
210
-
211
254
  ### Project Instructions
212
255
 
213
256
  Remy automatically loads project-level agent instructions on startup. It checks for these files in order (first match wins):
@@ -218,19 +261,32 @@ Remy automatically loads project-level agent instructions on startup. It checks
218
261
 
219
262
  Run `remy --headless` for programmatic control via newline-delimited JSON. This is how the sandbox C&C server runs remy as a managed child process.
220
263
 
264
+ ### Protocol Overview
265
+
266
+ The headless IPC protocol uses request correlation and a unified response pattern:
267
+
268
+ - Every stdin command includes a caller-provided `requestId`
269
+ - Every stdout response to a command includes the same `requestId`
270
+ - System events (lifecycle, shutdown) never have a `requestId`
271
+ - Every command ends with exactly one `completed` event: `{event:"completed", requestId, success, error?}`
272
+ - The caller distinguishes command responses from system events with a single check: `if (msg.requestId)`
273
+
274
+ This enables a simple promise-based RPC layer: send a command with a unique ID, store a pending promise keyed by that ID, resolve it when you see `completed` with the matching ID.
275
+
221
276
  ### Input Actions (stdin)
222
277
 
223
- Send JSON commands, one per line.
278
+ Send JSON commands, one per line. Every command should include a `requestId`.
224
279
 
225
280
  #### `message`
226
281
 
227
282
  Send a user message to the agent.
228
283
 
229
284
  ```json
230
- {"action": "message", "text": "fix the bug in auth.ts", "onboardingState": "onboardingFinished"}
285
+ {"action": "message", "requestId": "r1", "text": "fix the bug in auth.ts", "onboardingState": "onboardingFinished"}
231
286
  ```
232
287
 
233
288
  Fields:
289
+ - `requestId` — caller-provided correlation ID (echoed on all response events)
234
290
  - `text` — the user message (required unless `runCommand` is set)
235
291
  - `onboardingState` — controls tool availability and prompt context. One of: `intake`, `initialSpecAuthoring`, `initialCodegen`, `onboardingFinished` (default: `onboardingFinished`)
236
292
  - `viewContext` — `{ mode, openFiles?, activeFile? }` for prompt context
@@ -241,7 +297,7 @@ When `runCommand` is set, the message text is replaced with a built-in prompt an
241
297
 
242
298
  #### `tool_result`
243
299
 
244
- Send the result of an external tool back to the agent.
300
+ Send the result of an external tool back to the agent. Fire-and-forget — no `completed` event is emitted.
245
301
 
246
302
  ```json
247
303
  {"action": "tool_result", "id": "toolu_abc123", "result": "ok"}
@@ -252,17 +308,17 @@ Send the result of an external tool back to the agent.
252
308
  Return the full conversation history.
253
309
 
254
310
  ```json
255
- {"action": "get_history"}
311
+ {"action": "get_history", "requestId": "r2"}
256
312
  ```
257
313
 
258
314
  Messages with `hidden: true` were generated by `runCommand` actions and should not be displayed in the UI.
259
315
 
260
316
  #### `cancel`
261
317
 
262
- Cancel the current turn.
318
+ Cancel the current turn. The cancel command gets `completed(success:true)`. The in-flight message command (if any) gets its own `completed(success:false, error:"cancelled")`.
263
319
 
264
320
  ```json
265
- {"action": "cancel"}
321
+ {"action": "cancel", "requestId": "r3"}
266
322
  ```
267
323
 
268
324
  #### `clear`
@@ -270,37 +326,56 @@ Cancel the current turn.
270
326
  Clear conversation history and delete the session file.
271
327
 
272
328
  ```json
273
- {"action": "clear"}
329
+ {"action": "clear", "requestId": "r4"}
274
330
  ```
275
331
 
276
332
  ### Output Events (stdout)
277
333
 
278
- Events are emitted as newline-delimited JSON.
334
+ Events are emitted as newline-delimited JSON. Command responses include `requestId`; system events do not.
279
335
 
280
- #### Lifecycle Events
336
+ #### System Events
281
337
 
282
338
  | Event | Fields | Description |
283
339
  |-------|--------|-------------|
284
340
  | `ready` | | Headless mode initialized, ready for input |
285
341
  | `session_restored` | `messageCount` | Previous session loaded |
286
- | `session_cleared` | | Session history cleared |
287
342
  | `stopping` | | Shutdown initiated |
288
343
  | `stopped` | | Shutdown complete |
289
344
 
290
- #### Agent Events (streamed during message processing)
345
+ #### Command Responses
346
+
347
+ All command responses include the `requestId` from the originating command.
291
348
 
292
349
  | Event | Fields | Description |
293
350
  |-------|--------|-------------|
294
- | `turn_started` | | Agent began processing a message |
295
- | `text` | `text` | Streaming text chunk |
296
- | `thinking` | `text` | Agent's internal reasoning |
297
- | `tool_start` | `id`, `name`, `input`, `partial?` | Tool execution started. `partial: true` means more `tool_start` events will follow for this id (progressive input streaming). |
298
- | `tool_input_delta` | `id`, `name`, `result` | Progressive tool content (streaming tools only) |
299
- | `tool_done` | `id`, `name`, `result`, `isError` | Tool execution completed |
300
- | `turn_done` | | Agent finished responding |
301
- | `turn_cancelled` | | Turn was cancelled |
302
- | `error` | `error` | Error message |
351
+ | `text` | `text`, `parentToolId?` | Streaming text chunk |
352
+ | `thinking` | `text`, `parentToolId?` | Agent's internal reasoning |
353
+ | `tool_start` | `id`, `name`, `input`, `partial?`, `parentToolId?` | Tool execution started. `partial: true` means more `tool_start` events will follow for this id (progressive input streaming). |
354
+ | `tool_input_delta` | `id`, `name`, `result`, `parentToolId?` | Progressive tool content (streaming tools only) |
355
+ | `tool_done` | `id`, `name`, `result`, `isError`, `parentToolId?` | Tool execution completed |
356
+ | `status` | `message` | Contextual status label (e.g., "Writing files...") |
357
+ | `error` | `error` | Error message (may precede `completed`) |
303
358
  | `history` | `messages` | Response to `get_history` |
359
+ | `session_cleared` | | Response to `clear` |
360
+ | `completed` | `success`, `error?` | Terminal event — exactly one per command |
361
+
362
+ #### Example Session
363
+
364
+ ```jsonl
365
+ ← {"event":"ready"}
366
+ ← {"event":"session_restored","messageCount":5}
367
+ → {"action":"message","requestId":"r1","text":"fix the login bug"}
368
+ ← {"event":"thinking","requestId":"r1","text":"Let me look at the auth code..."}
369
+ ← {"event":"text","requestId":"r1","text":"I'll fix the login validation."}
370
+ ← {"event":"tool_start","requestId":"r1","id":"tc_1","name":"editFile","input":{"path":"auth.ts","old":"...","new":"..."}}
371
+ ← {"event":"tool_done","requestId":"r1","id":"tc_1","name":"editFile","result":"OK","isError":false}
372
+ ← {"event":"completed","requestId":"r1","success":true}
373
+ → {"action":"get_history","requestId":"r2"}
374
+ ← {"event":"history","requestId":"r2","messages":[...]}
375
+ ← {"event":"completed","requestId":"r2","success":true}
376
+ → {"action":"cancel","requestId":"r3"}
377
+ ← {"event":"completed","requestId":"r3","success":true}
378
+ ```
304
379
 
305
380
  ### Logging
306
381
 
@@ -308,6 +383,39 @@ In headless mode, structured logs go to **stderr**. Stdout is reserved for the J
308
383
 
309
384
  In interactive mode, logs go to `.remy-debug.log` in the working directory (default level: `error`). Override with `--log-level`.
310
385
 
386
+ ## Design Data Dev Tool
387
+
388
+ A lightweight local tool for browsing and managing the design expert's font catalog and inspiration image library.
389
+
390
+ ```bash
391
+ node src/subagents/designExpert/data/dev/serve.mjs
392
+ # Opens http://localhost:3333
393
+ ```
394
+
395
+ Three tabs:
396
+ - **Fonts** — browse all fonts rendered in their actual typefaces, search/filter by category and source, delete
397
+ - **Pairings** — heading + body font pairings rendered live, delete
398
+ - **Inspiration** — filmstrip browser for curated design reference screenshots with analyses, add new images with auto-analyze
399
+
400
+ ### Data compilation scripts
401
+
402
+ ```bash
403
+ # Regenerate inspiration analyses from raw image URLs
404
+ bash src/subagents/designExpert/data/compile-inspiration.sh
405
+
406
+ # Generate typographic descriptions for all fonts from specimen images
407
+ bash src/subagents/designExpert/data/compile-font-descriptions.sh
408
+ ```
409
+
410
+ ### Specimen pages
411
+
412
+ For generating font specimen images (used by the font description pipeline):
413
+
414
+ - `http://localhost:3333/specimens/fonts?page=1` — paginated font specimens (40 per page)
415
+ - `http://localhost:3333/specimens/pairings?page=1` — paginated pairing specimens (33 per page)
416
+
417
+ Screenshot these as full-page PNGs, then slice into individual images with the Python script in the session history. Specimen images live in `src/subagents/designExpert/data/specimens/` and are hosted at `https://i.mscdn.ai/remy-font-specimens/`.
418
+
311
419
  ## Development
312
420
 
313
421
  ```bash
@@ -20,6 +20,23 @@ export const Vendors = db.defineTable<Vendor>('vendors');
20
20
 
21
21
  One export per file. The export name is referenced in `mindstudio.json` and imported in methods. Only define your own columns in the interface — do not add `id`, `created_at`, `updated_at`, or `last_updated_by` (they're provided automatically, see below).
22
22
 
23
+ ### Table Options
24
+
25
+ `defineTable<T>()` accepts an optional second argument with table-level configuration:
26
+
27
+ ```typescript
28
+ export const Users = db.defineTable<User>('users', {
29
+ unique: [['email']],
30
+ defaults: { role: 'member', status: 'active' },
31
+ });
32
+ ```
33
+
34
+ - **`unique`** — `(keyof T & string)[][]` — Column groups that form unique constraints. Each entry is a string array of column names. These are required for `upsert()` (see below). Schema sync on the platform creates the actual SQLite UNIQUE indexes.
35
+ - Single column: `[['email']]`
36
+ - Compound: `[['userId', 'orgId']]`
37
+ - Multiple constraints: `[['email'], ['slug']]`
38
+ - **`defaults`** — `Partial<T>` — Default values applied client-side in `push()` and `upsert()` before building the INSERT. Explicit values in the input override defaults.
39
+
23
40
  ### Column Types
24
41
 
25
42
  | TypeScript type | SQLite type | Notes |
@@ -79,6 +96,30 @@ const vendors = await Vendors.push([
79
96
  ]);
80
97
  ```
81
98
 
99
+ If the table has `defaults` configured, missing fields are filled in automatically. Explicit values override defaults.
100
+
101
+ ### Upsert (Insert or Update)
102
+
103
+ ```typescript
104
+ // Insert if no conflict on 'email', otherwise update the existing row
105
+ const user = await Users.upsert('email', {
106
+ email: 'alice@acme.com',
107
+ name: 'Alice',
108
+ role: 'admin',
109
+ });
110
+
111
+ // Compound conflict key — pass an array
112
+ const membership = await Memberships.upsert(['userId', 'orgId'], {
113
+ userId: user.id,
114
+ orgId: org.id,
115
+ role: 'member',
116
+ });
117
+ ```
118
+
119
+ `upsert(conflictKey, data)` generates `INSERT ... ON CONFLICT(...) DO UPDATE SET ...` using SQLite's `excluded.` syntax. All non-conflict columns are updated on conflict. Returns a `Mutation<T>` — works with `await` standalone or inside `db.batch()`, same as `push()` and `update()`.
120
+
121
+ The conflict key must match a declared `unique` constraint on the table. Throws `MindStudioError` with code `no_unique_constraint` if no match.
122
+
82
123
  ### Reading Records
83
124
 
84
125
  ```typescript
@@ -162,9 +203,20 @@ db.ago(db.days(7) + db.hours(12)) // composable — 7.5 days ago
162
203
  Invoices.filter(i => i.dueDate < db.ago(db.days(30)))
163
204
  ```
164
205
 
206
+ ### Error Handling on Queries
207
+
208
+ Both `Query<T>` and `Mutation<T>` support `.then()` and `.catch()` directly:
209
+
210
+ ```typescript
211
+ const user = await Users.upsert('email', data).catch(err => {
212
+ if (err.code === 'no_unique_constraint') { /* ... */ }
213
+ throw err;
214
+ });
215
+ ```
216
+
165
217
  ### Batching
166
218
 
167
- `db.batch()` combines multiple operations into a single HTTP round-trip. Every `await` on a table operation is a network call, so batching is critical for performance. Use it whenever you have multiple reads, writes, or a mix of both:
219
+ `db.batch()` combines multiple operations into a single HTTP round-trip. Every `await` on a table operation is a network call, so batching is critical for performance. Use it whenever you have multiple reads, writes, or a mix of both. `upsert()` works in batches just like `push()` and `update()`:
168
220
 
169
221
  ```typescript
170
222
  // Reads: fetch related data in one call instead of sequential awaits
@@ -2,8 +2,16 @@
2
2
  * Headless mode — stdin/stdout JSON protocol for programmatic control.
3
3
  *
4
4
  * Designed for parent processes like the mindstudio-sandbox C&C server.
5
- * Input: newline-delimited JSON on stdin (e.g. {"action":"message","text":"..."})
6
- * Output: newline-delimited JSON on stdout (e.g. {"event":"text","text":"..."})
5
+ * Input: newline-delimited JSON on stdin (e.g. {"action":"message","requestId":"r1","text":"..."})
6
+ * Output: newline-delimited JSON on stdout (e.g. {"event":"text","requestId":"r1","text":"..."})
7
+ *
8
+ * Protocol rules:
9
+ * - Every stdin command includes an `action` and a caller-provided `requestId`.
10
+ * - Every stdout event that is a response to a command includes the `requestId`.
11
+ * - System events (ready, session_restored, stopping, stopped) never have a requestId.
12
+ * - Every command ends with exactly one `completed` event:
13
+ * {event:"completed", requestId, success:true|false, error?:string}
14
+ * - `tool_result` is fire-and-forget (resolves an in-flight promise, no completed event).
7
15
  */
8
16
  interface HeadlessOptions {
9
17
  apiKey?: string;