@statelyai/sdk 0.4.1 → 0.5.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.
package/README.md CHANGED
@@ -1,16 +1,34 @@
1
1
  # @statelyai/sdk
2
2
 
3
- Embed the [Stately editor](https://stately.ai) in your app and talk to the Stately Studio API. Zero dependencies, fully typed.
3
+ <!-- package.json#name and #description top-level summary -->
4
+
5
+ Embed the [Stately editor](https://stately.ai), inspect running actor systems over WebSockets, talk to the Stately Studio API, and convert between Studio graph data and code. Fully typed.
4
6
 
5
7
  ## Install
6
8
 
9
+ <!-- install command derived from package.json#name -->
10
+
7
11
  ```bash
8
12
  npm install @statelyai/sdk
9
13
  ```
10
14
 
15
+ ## What It Includes
16
+
17
+ <!-- top-level runtime exports from src/index.ts and CLI bin from package.json#bin -->
18
+
19
+ - `createStatelyEmbed()` for browser embeds backed by `postMessage`
20
+ - `createStatelyInspector()` for inspecting live actor systems over WebSockets
21
+ - `createStatelyClient()` for Stately Studio API access
22
+ - graph conversion and codegen helpers such as `fromStudioMachine()`, `toStudioMachine()`, `graphToMachineConfig()`, and `graphToXStateTS()`
23
+ - sync helpers under `@statelyai/sdk/sync` and a `statelyai` CLI binary
24
+
11
25
  ## Authentication
12
26
 
13
- The editor uses a pluggable authentication system. How you configure it depends on whether you're using the hosted Stately platform or self-hosting.
27
+ The embed supports three common deployment models:
28
+
29
+ - Hosted Stately: pass an API key to `createStatelyEmbed()`
30
+ - Same-origin deployments: rely on the host application's session/cookie auth
31
+ - Self-hosted deployments: configure auth in the editor server and omit `apiKey` when no token is required
14
32
 
15
33
  ### With Stately (default)
16
34
 
@@ -32,56 +50,39 @@ const embed = createStatelyEmbed({
32
50
  });
33
51
  ```
34
52
 
35
- The server validates the key against the Stately registry API.
36
-
37
- ### Self-hosting
38
-
39
- When self-hosting the editor, authentication is controlled by the `AUTH_PROVIDER` environment variable on the server:
40
-
41
- | Value | Behavior |
42
- | ---------- | ----------------------------------------------------------- |
43
- | `stately` | Validates tokens against the Stately registry API |
44
- | `none` | Allows all requests (no authentication) |
45
- | _(unset)_ | `none` in development, `stately` in production |
46
-
47
- For a fully self-contained deployment with no Stately dependency:
48
-
49
- ```bash
50
- AUTH_PROVIDER=none
51
- ```
52
-
53
- ### Custom authentication
53
+ ### Same-origin embed (cookie auth)
54
54
 
55
- You can implement your own auth by writing a custom `AuthValidator` an async function that receives a token and returns whether it's valid:
55
+ When the embed host and the editor share a domain, you can omit `apiKey` and rely on the host application's auth/session layer:
56
56
 
57
57
  ```ts
58
- import type { AuthValidator } from '@/lib/auth';
59
-
60
- const myValidator: AuthValidator = async (token) => {
61
- const res = await fetch('https://my-auth-server.com/verify', {
62
- headers: { Authorization: `Bearer ${token}` },
63
- });
64
- return res.ok;
65
- };
58
+ const embed = createStatelyEmbed({
59
+ baseUrl: process.env.NEXT_PUBLIC_BETA_EDITOR_URL ?? window.location.origin,
60
+ });
66
61
  ```
67
62
 
68
- The built-in adapters are:
69
-
70
- - **`createStatelyValidator(baseUrl?)`** — verifies against the Stately registry API (`/registry/api/v1/verify`)
71
- - **`allowAllValidator`** — always returns `true`
63
+ ### Self-hosting
72
64
 
73
- To use a custom validator, call `getAuthValidator()` from `src/lib/auth` in the server-side page code, or replace the resolver logic in `src/lib/auth/index.tsx` to return your implementation.
65
+ When self-hosting the editor, authentication is enforced by the editor server, not by this npm package.
74
66
 
75
- ### Environment variables
67
+ The common environment variables are:
76
68
 
77
69
  | Variable | Purpose |
78
- | ----------------------- | -------------------------------------------- |
79
- | `AUTH_PROVIDER` | Auth strategy: `stately`, `none`, or unset |
70
+ | --- | --- |
71
+ | `AUTH_PROVIDER` | Auth strategy used by the editor host |
72
+ | `EDITOR_SYNC_AUTH_REQUIRED` | Set to `false` to disable API-key checks for editor-sync endpoints only |
80
73
  | `STATELY_API_KEY` | Server-side API key for Stately data fetching |
81
- | `STATELY_API_URL` | Stately API base URL (server-side override) |
82
- | `NEXT_PUBLIC_BASE_URL` | Public-facing base URL |
74
+ | `STATELY_API_URL` | Stately API base URL override |
75
+ | `NEXT_PUBLIC_BASE_URL` | Public-facing editor URL |
76
+
77
+ For a fully self-contained deployment with no auth, omit `apiKey` in the SDK and configure the host/editor to allow unauthenticated access:
78
+
79
+ ```ts
80
+ const embed = createStatelyEmbed({
81
+ baseUrl: 'https://your-editor.example.com',
82
+ });
83
+ ```
83
84
 
84
- ## Quick start
85
+ ## Quick Start
85
86
 
86
87
  ### Third-party embed (with API key)
87
88
 
@@ -97,39 +98,15 @@ embed.mount(document.getElementById('editor')!);
97
98
 
98
99
  embed.init({
99
100
  machine: myMachineConfig,
101
+ format: 'xstate',
100
102
  mode: 'editing',
101
103
  theme: 'dark',
102
104
  });
103
105
  ```
104
106
 
105
- ### Same-origin embed (cookie auth)
106
-
107
- When the embed host and the editor share a domain (e.g. Stately Studio embedding the beta editor), Supabase session cookies handle auth automatically — no API key needed:
108
-
109
- ```ts
110
- const embed = createStatelyEmbed({
111
- baseUrl: process.env.NEXT_PUBLIC_BETA_EDITOR_URL ?? window.location.origin,
112
- });
113
-
114
- embed.mount(container);
115
- ```
116
-
117
- ### Self-hosted (no auth)
118
-
119
- When self-hosting with `AUTH_PROVIDER=none`, no token is required:
120
-
121
- ```ts
122
- const embed = createStatelyEmbed({
123
- baseUrl: 'https://your-editor.example.com',
124
- });
125
-
126
- embed.mount(container);
127
- ```
128
-
129
107
  ### Comments
130
108
 
131
- Comments are optional and integrator-configured. Pass a `comments` object to
132
- `embed.init()` when you want Liveblocks-backed commenting enabled.
109
+ Comments are optional and integrator-configured. Pass a `comments` object to `embed.init()` when you want Liveblocks-backed commenting enabled.
133
110
 
134
111
  ```ts
135
112
  embed.init({
@@ -156,30 +133,38 @@ embed.init({
156
133
  });
157
134
  ```
158
135
 
159
- `roomId` is required when comments are enabled. `userId` is optional and only
160
- used for comment identity metadata.
136
+ `roomId` is required when comments are enabled. `userId` is optional and only used for comment identity metadata.
161
137
 
162
- ## Module layout
138
+ ## Module Layout
163
139
 
164
- The SDK ships direct root exports for the common entry points:
140
+ <!-- subpath imports derived from package.json#exports -->
141
+
142
+ The SDK ships root exports for the most common entry points and helpers:
165
143
 
166
144
  ```ts
167
145
  import {
168
- createStatelyInspector,
169
- createStatelyEmbed,
170
146
  createStatelyClient,
147
+ createStatelyEmbed,
148
+ createStatelyInspector,
149
+ fromStudioMachine,
150
+ graphToMachineConfig,
151
+ graphToXStateTS,
152
+ toStudioMachine,
171
153
  } from '@statelyai/sdk';
172
154
  ```
173
155
 
174
- It also supports subpath imports when you want a narrower surface:
156
+ It also supports narrower subpath imports:
175
157
 
176
158
  ```ts
177
159
  import { createStatelyClient } from '@statelyai/sdk/studio';
178
160
  import { createStatelyInspector } from '@statelyai/sdk/inspect';
179
161
  import { createStatelyEmbed } from '@statelyai/sdk/embed';
162
+ import { fromStudioMachine, toStudioMachine } from '@statelyai/sdk/graph';
163
+ import { planSync, pullSync } from '@statelyai/sdk/sync';
164
+ import type { GraphPatch } from '@statelyai/sdk/patchTypes';
180
165
  ```
181
166
 
182
- ## Studio API client
167
+ ## Studio API Client
183
168
 
184
169
  ```ts
185
170
  import { createStatelyClient } from '@statelyai/sdk';
@@ -193,35 +178,90 @@ const machine = await studio.machines.get('machine-id', { version: '42' });
193
178
  const extracted = await studio.code.extractMachines(sourceCode);
194
179
  ```
195
180
 
196
- ## API
181
+ <!-- public methods of StudioClient from src/studio.ts -->
182
+
183
+ Available client methods:
184
+
185
+ | Method | Description |
186
+ | --- | --- |
187
+ | `studio.auth.verify(apiKey?)` | Verify an API key against the registry API |
188
+ | `studio.projects.get(projectId)` | Fetch a project and its machines |
189
+ | `studio.machines.get(machineId, { version? })` | Fetch a machine, optionally pinned to a version |
190
+ | `studio.code.extractMachines(code, { apiKey? })` | Extract machine configs from source text |
191
+
192
+ ## Inspector
193
+
194
+ <!-- public API of createStatelyInspector from src/inspect.ts -->
195
+
196
+ `createStatelyInspector()` streams actor-system state to the Stately inspector over WebSockets. It supports both automatic XState actor adoption and manual actor registration.
197
+
198
+ ```ts
199
+ import { createActor } from 'xstate';
200
+ import { createStatelyInspector } from '@statelyai/sdk';
201
+
202
+ const actor = createActor(machine);
203
+ const inspector = createStatelyInspector({
204
+ actor,
205
+ url: 'ws://localhost:4242',
206
+ autoOpen: true,
207
+ });
208
+
209
+ actor.start();
210
+ ```
211
+
212
+ Key options:
213
+
214
+ | Option | Description |
215
+ | --- | --- |
216
+ | `actor` | Root actor to adopt and inspect automatically |
217
+ | `url` | Devtools relay URL. Defaults to `ws://localhost:4242` |
218
+ | `autoOpen` | Whether to ask the relay to open the inspector UI |
219
+ | `sessionId` | Override the relay session id |
220
+ | `name` | Display name shown to the inspector |
221
+ | `serializeSnapshot` | Customize snapshot serialization before sending it over the wire |
222
+ | `extractMachineConfig` | Customize how machine config is derived from an actor |
223
+ | `selectedActorId` | Focus a specific actor first |
224
+ | `panels`, `theme`, `readOnly`, `depth` | Initial inspector UI options |
225
+ | `transport` | Inject an existing transport instead of opening a new WebSocket |
226
+
227
+ Key methods:
228
+
229
+ - `inspector.export(format, options?)`
230
+ - `inspector.actor(id, options?)`
231
+ - `inspector.snapshot(actorId, snapshot, event?)`
232
+ - `inspector.event(actorId, event, { source? })`
233
+ - `inspector.stop(actorId)`
234
+ - `inspector.destroy()`
235
+
236
+ ## Embed API
197
237
 
198
238
  ### `createStatelyEmbed(options)`
199
239
 
200
240
  Creates an embed instance.
201
241
 
202
- | Option | Type | Description |
203
- | ---------- | -------------------------------- | ----------------------------------------- |
204
- | `baseUrl` | `string` | **Required.** Base URL of the Stately app |
205
- | `apiKey` | `string` | API key for authentication (see [Authentication](#authentication)) |
206
- | `origin` | `string` | Custom target origin for postMessage |
207
- | `assets` | `AssetConfig` | Asset upload configuration (see [Asset uploads](#asset-uploads)) |
208
- | `onReady` | `() => void` | Called when embed is ready |
209
- | `onLoaded` | `(graph) => void` | Called when machine is loaded |
210
- | `onChange` | `(graph, machineConfig) => void` | Called on every change |
211
- | `onSave` | `(graph, machineConfig) => void` | Called on save |
212
- | `onError` | `({ code, message }) => void` | Called on error |
242
+ <!-- public options of StatelyEmbedOptions from src/embed.ts -->
243
+
244
+ | Option | Type | Description |
245
+ | --- | --- | --- |
246
+ | `baseUrl` | `string` | **Required.** Base URL of the Stately app |
247
+ | `apiKey` | `string` | API key for hosted Stately deployments |
248
+ | `origin` | `string` | Optional strict target origin for `postMessage`; defaults to permissive wildcard messaging for local/self-hosted testing |
249
+ | `assets` | `AssetConfig` | Asset upload configuration |
250
+ | `onReady` | `() => void` | Called when the embed is ready |
251
+ | `onLoaded` | `(graph) => void` | Called when a machine is loaded |
252
+ | `onChange` | `(graph, machineConfig) => void` | Called on every change |
253
+ | `onSave` | `(graph, machineConfig) => void` | Called on save |
254
+ | `onError` | `({ code, message }) => void` | Called when the embed reports an error |
213
255
 
214
256
  ### Embed methods
215
257
 
216
258
  #### `embed.mount(container)` / `embed.attach(iframe)`
217
259
 
218
- Mount creates an iframe inside a container element. Attach connects to an existing iframe.
260
+ `mount()` creates an iframe inside a container element. `attach()` connects to an existing iframe.
219
261
 
220
262
  ```ts
221
- // Create a new iframe
222
263
  const iframe = embed.mount(document.getElementById('editor')!);
223
264
 
224
- // Or attach to an existing one
225
265
  embed.attach(document.querySelector('iframe')!);
226
266
  ```
227
267
 
@@ -232,9 +272,9 @@ Initialize the embed with a machine and display options.
232
272
  ```ts
233
273
  embed.init({
234
274
  machine: machineConfig,
235
- format: 'xstate', // optional
236
- mode: 'editing', // 'editing' | 'viewing' | 'simulating'
237
- theme: 'dark', // 'light' | 'dark'
275
+ format: 'xstate',
276
+ mode: 'editing',
277
+ theme: 'dark',
238
278
  readOnly: false,
239
279
  depth: 3,
240
280
  panels: {
@@ -242,6 +282,10 @@ embed.init({
242
282
  rightPanels: ['events'],
243
283
  activePanels: ['code'],
244
284
  },
285
+ unsavedIndicator: {
286
+ enabled: true,
287
+ mode: 'structural',
288
+ },
245
289
  comments: {
246
290
  roomId: 'machine:checkout',
247
291
  publicApiKey: 'pk_live_...',
@@ -252,13 +296,20 @@ embed.init({
252
296
  `comments` accepts:
253
297
 
254
298
  | Field | Type | Description |
255
- | ---------- | ---------- | ----------------------------------------------- |
299
+ | --- | --- | --- |
256
300
  | `roomId` | `string` | **Required.** Liveblocks room identifier |
257
301
  | `publicApiKey` | `string` | Liveblocks public key |
258
302
  | `authEndpoint` | `string` | Custom Liveblocks auth endpoint |
259
303
  | `baseUrl` | `string` | Custom Liveblocks base URL for self-hosting |
260
304
  | `userId` | `string \| null` | Optional user identity metadata |
261
305
 
306
+ `unsavedIndicator` accepts:
307
+
308
+ | Field | Type | Description |
309
+ | --- | --- | --- |
310
+ | `enabled` | `boolean` | Show the persistent "Save to apply" pill |
311
+ | `mode` | `'structural' \| 'all'` | Track only structural graph edits or all edits |
312
+
262
313
  #### `embed.updateMachine(machine, format?)`
263
314
 
264
315
  Update the displayed machine.
@@ -269,7 +320,7 @@ Change the embed mode or theme at runtime.
269
320
 
270
321
  #### `embed.setSettings(settings)`
271
322
 
272
- Update editor settings at runtime. Merges with existing settings.
323
+ Update editor settings at runtime. Settings are merged with the existing editor settings.
273
324
 
274
325
  ```ts
275
326
  embed.setSettings({
@@ -281,7 +332,7 @@ embed.setSettings({
281
332
  Available core settings:
282
333
 
283
334
  | Path | Type | Default |
284
- |------|------|---------|
335
+ | --- | --- | --- |
285
336
  | `appearance.colorMode` | `'light' \| 'dark' \| 'system'` | `'dark'` |
286
337
  | `canvas.showGrid` | `boolean` | `true` |
287
338
  | `canvas.viewMode` | `'graph' \| 'list'` | `'graph'` |
@@ -296,20 +347,31 @@ Available core settings:
296
347
  Export the current machine. Returns a promise.
297
348
 
298
349
  ```ts
299
- const code = await embed.export('xstate', { version: 5 });
300
- const json = await embed.export('json');
301
- const mermaid = await embed.export('mermaid');
350
+ const xstateCode = await embed.export('xstate', { version: 5 });
351
+ const digraph = await embed.export('digraph');
352
+ const rtk = await embed.export('rtk');
353
+ const aslYaml = await embed.export('asl-yaml');
302
354
  ```
303
355
 
304
- Supported formats: `xstate`, `json`, `digraph`, `mermaid`, `scxml`
356
+ <!-- supported export formats from ExportFormatMap in src/protocol.ts -->
357
+
358
+ Supported formats: `xstate`, `json`, `xgraph`, `digraph`, `mermaid`, `rtk`, `zustand`, `asl-json`, `asl-yaml`, `scxml`
305
359
 
306
360
  #### `embed.on(event, handler)` / `embed.off(event, handler)`
307
361
 
308
- Subscribe/unsubscribe to embed events: `ready`, `loaded`, `change`, `save`, `error`.
362
+ <!-- keys of EmbedEventMap in src/protocol.ts -->
363
+
364
+ Event names are `ready`, `loaded`, `change`, `save`, `error`, and `snapshot`.
365
+
366
+ `createStatelyEmbed()` emits `ready`, `loaded`, `change`, `save`, and `error` for browser embeds:
309
367
 
310
368
  ```ts
311
- embed.on('change', ({ graph, machineConfig }) => {
312
- console.log('Machine changed', machineConfig);
369
+ embed.on('change', ({ graph, machineConfig, patches }) => {
370
+ console.log('Machine changed', graph, machineConfig, patches);
371
+ });
372
+
373
+ embed.on('save', ({ validations }) => {
374
+ console.log('Save validations', validations);
313
375
  });
314
376
  ```
315
377
 
@@ -323,96 +385,142 @@ Tear down the embed. Removes listeners, rejects pending promises, and removes th
323
385
 
324
386
  ### Asset uploads
325
387
 
326
- By default, dropped images are stored as base64 data URLs. To upload assets to your own storage, pass an `assets` config:
388
+ By default, dropped files are stored as base64 data URLs. To upload assets to your own storage, pass an `assets` config:
327
389
 
328
390
  ```ts
329
391
  const embed = createStatelyEmbed({
330
392
  baseUrl: 'https://stately.ai',
331
393
  assets: {
332
394
  onUploadRequest: async (file, { stateNodeId }) => {
333
- // upload `file` (a File object) to your storage
334
- // return { url } at minimum
395
+ return { url: await uploadToStorage(file, stateNodeId) };
335
396
  },
336
- accept: ['image/*'], // MIME patterns (default: ['image/*'])
337
- maxFileSize: 5 * 1024 * 1024, // bytes (default: 10 MB)
397
+ accept: ['image/*'],
398
+ maxFileSize: 5 * 1024 * 1024,
338
399
  },
339
400
  });
340
401
  ```
341
402
 
342
403
  | Option | Type | Description |
343
404
  | --- | --- | --- |
344
- | `onUploadRequest` | `(file: File, context: { stateNodeId: string }) => Promise<UploadResult>` | **Required.** Called when the editor needs to upload a file. Return the hosted URL. |
345
- | `accept` | `string[]` | Accepted MIME types. Supports wildcards (`image/*`). Default: `['image/*']` |
346
- | `maxFileSize` | `number` | Max file size in bytes. Default: `10_485_760` (10 MB) |
405
+ | `onUploadRequest` | `(file: File, context: { stateNodeId: string }) => Promise<UploadResult>` | **Required.** Called when the editor needs to upload a file |
406
+ | `accept` | `string[]` | Accepted MIME types. Supports wildcards like `image/*` |
407
+ | `maxFileSize` | `number` | Max file size in bytes. Defaults to `10_485_760` |
347
408
 
348
409
  `UploadResult`:
349
410
 
350
411
  ```ts
351
412
  interface UploadResult {
352
- url: string; // hosted URL (required)
353
- name?: string; // display name (defaults to filename)
354
- metadata?: Record<string, unknown>; // arbitrary metadata stored on the asset
413
+ url: string;
414
+ name?: string;
415
+ metadata?: Record<string, unknown>;
355
416
  }
356
417
  ```
357
418
 
358
- #### Example: AWS S3 (presigned URL)
419
+ If `onUploadRequest` throws or rejects, the editor shows an error toast. If no `assets` config is provided, files are stored inline and no upload request is sent.
420
+
421
+ ## Graph And Codegen Helpers
422
+
423
+ <!-- root helper exports from src/index.ts -->
424
+
425
+ Use the conversion helpers to move between Studio digraph data, generic Stately graphs, machine config objects, and XState TypeScript source.
359
426
 
360
427
  ```ts
361
- import { createStatelyEmbed } from '@statelyai/sdk';
428
+ import {
429
+ fromStudioMachine,
430
+ graphToMachineConfig,
431
+ graphToXStateTS,
432
+ toStudioMachine,
433
+ } from '@statelyai/sdk';
362
434
 
363
- const embed = createStatelyEmbed({
364
- baseUrl: 'https://stately.ai',
365
- assets: {
366
- onUploadRequest: async (file) => {
367
- // 1. Get a presigned upload URL from your backend
368
- const { uploadUrl, publicUrl } = await fetch('/api/s3/presign', {
369
- method: 'POST',
370
- headers: { 'Content-Type': 'application/json' },
371
- body: JSON.stringify({
372
- filename: file.name,
373
- contentType: file.type,
374
- }),
375
- }).then((r) => r.json());
376
-
377
- // 2. PUT the file directly to S3
378
- await fetch(uploadUrl, {
379
- method: 'PUT',
380
- headers: { 'Content-Type': file.type },
381
- body: file,
382
- });
383
-
384
- return { url: publicUrl, name: file.name };
385
- },
386
- accept: ['image/*'],
387
- maxFileSize: 10 * 1024 * 1024,
388
- },
435
+ const graph = fromStudioMachine(studioMachine);
436
+ const machineConfig = graphToMachineConfig(graph, {
437
+ showDescriptions: true,
438
+ showMeta: true,
439
+ });
440
+ const source = graphToXStateTS(graph, {
441
+ exportStyle: 'named',
389
442
  });
443
+ const digraph = toStudioMachine(graph);
390
444
  ```
391
445
 
392
- #### Example: Cloudflare R2 (worker upload)
446
+ Other exported helpers:
447
+
448
+ - `studioMachineConverter` for reusable format conversion
449
+ - `serializeJS()`, `raw()`, and `RawCode` for emitting JavaScript source
450
+ - `jsonSchemaToTSType()`, `contextSchemaToTSType()`, and `eventsSchemaToTSType()` for generating inline TypeScript types from JSON Schema
451
+ - `GraphPatch` and `ActionLocation` types from `@statelyai/sdk/patchTypes`
452
+
453
+ ## Sync Helpers And CLI
454
+
455
+ <!-- sync helpers from src/sync.ts and CLI commands from src/cli.ts#COMMANDS -->
456
+
457
+ The sync helpers compare or materialize machines across local files, Stately machine IDs, and Stately URLs.
458
+
459
+ Programmatic usage:
393
460
 
394
461
  ```ts
395
- import { createStatelyEmbed } from '@statelyai/sdk';
462
+ import { planSync, pullSync } from '@statelyai/sdk/sync';
396
463
 
397
- const embed = createStatelyEmbed({
398
- baseUrl: 'https://stately.ai',
399
- assets: {
400
- onUploadRequest: async (file) => {
401
- const form = new FormData();
402
- form.append('file', file);
464
+ const plan = await planSync({
465
+ source: './checkout.machine.ts',
466
+ target: 'machine-id',
467
+ apiKey: process.env.STATELY_API_KEY,
468
+ });
403
469
 
404
- // Upload via a Cloudflare Worker that writes to R2
405
- const { url } = await fetch('https://uploads.example.com/assets', {
406
- method: 'POST',
407
- body: form,
408
- }).then((r) => r.json());
470
+ if (plan.summary.hasChanges) {
471
+ console.log(plan.summary);
472
+ }
409
473
 
410
- return { url, name: file.name };
411
- },
412
- accept: ['image/*', 'application/pdf'],
413
- maxFileSize: 25 * 1024 * 1024,
414
- },
474
+ await pullSync({
475
+ source: 'machine-id',
476
+ target: './checkout.machine.ts',
477
+ apiKey: process.env.STATELY_API_KEY,
415
478
  });
416
479
  ```
417
480
 
418
- If `onUploadRequest` throws or rejects, the editor shows an error toast. If no `assets` config is provided, files are stored as inline base64 data URLs (no network requests).
481
+ Supported locators:
482
+
483
+ - local files
484
+ - Stately machine IDs
485
+ - full Stately machine URLs
486
+
487
+ Installing the package also exposes a `statelyai` binary:
488
+
489
+ ```bash
490
+ npx statelyai open ./checkout.machine.ts
491
+
492
+ statelyai plan ./checkout.machine.ts machine-id
493
+ statelyai diff ./checkout.machine.ts machine-id --fail-on-changes
494
+ statelyai pull machine-id ./checkout.machine.ts
495
+ statelyai open ./checkout.machine.ts
496
+ ```
497
+
498
+ For one-off use, `npx statelyai ...` installs the small `statelyai` CLI package, which delegates to this SDK CLI.
499
+
500
+ Available commands:
501
+
502
+ | Command | Description |
503
+ | --- | --- |
504
+ | `statelyai plan <source> <target>` | Print a semantic sync summary |
505
+ | `statelyai diff <source> <target>` | Diff two locators and optionally fail on changes |
506
+ | `statelyai pull <source> <target>` | Materialize a source into a local target file |
507
+ | `statelyai open <file>` | Open a local file in a browser-backed visual editor session |
508
+
509
+ Common flags:
510
+
511
+ - `--api-key` for remote machine resolution or editor servers that require auth
512
+ - `--base-url` for self-hosted or non-default Stately deployments
513
+ - `--fail-on-changes` to return a nonzero exit code when a diff is detected
514
+
515
+ `statelyai open` also supports `--api-key`, `--editor-url`, `--host`, `--port`, `--no-open`, and `--debug`. It watches the local file on disk and sends source snapshots to `/api/editor-sync/*` endpoints, which return the text replacements to apply locally. Pass `--api-key` or set `STATELY_API_KEY` when the editor server requires auth. Self-hosted servers can disable editor-sync API-key checks with `EDITOR_SYNC_AUTH_REQUIRED=false`. The private source reconciliation engine is not bundled into the published CLI.
516
+
517
+ ## Transport Helpers
518
+
519
+ <!-- transport helpers exported from src/index.ts -->
520
+
521
+ For advanced integrations, the root package also exports:
522
+
523
+ - `createPostMessageTransport()` for iframe-based clients
524
+ - `createWebSocketTransport()` for relay-based integrations
525
+
526
+ These power the embed and inspector internals, but they are also available when you need lower-level control over the `@statelyai.*` protocol.
package/dist/cli.d.mts CHANGED
@@ -10,16 +10,16 @@ declare abstract class BaseSyncCommand extends Command {
10
10
  static flags: {
11
11
  help: _oclif_core_interfaces0.BooleanFlag<void>;
12
12
  'fail-on-changes': _oclif_core_interfaces0.BooleanFlag<boolean>;
13
- 'api-key': _oclif_core_interfaces0.OptionFlag<string, _oclif_core_interfaces0.CustomOptions>;
14
- 'base-url': _oclif_core_interfaces0.OptionFlag<string, _oclif_core_interfaces0.CustomOptions>;
13
+ 'api-key': _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
14
+ 'base-url': _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
15
15
  };
16
16
  }
17
17
  declare abstract class ParsedSyncCommand extends BaseSyncCommand {
18
18
  protected parseSync<T extends typeof BaseSyncCommand>(command: T): Promise<_oclif_core_interfaces0.ParserOutput<{
19
19
  help: void;
20
20
  'fail-on-changes': boolean;
21
- 'api-key': string;
22
- 'base-url': string;
21
+ 'api-key': string | undefined;
22
+ 'base-url': string | undefined;
23
23
  }, {
24
24
  [flag: string]: any;
25
25
  }, {
@@ -53,11 +53,30 @@ declare class PullCommand extends ParsedSyncCommand {
53
53
  };
54
54
  run(): Promise<void>;
55
55
  }
56
+ declare class OpenCommand extends Command {
57
+ static enableJsonFlag: boolean;
58
+ static summary: string;
59
+ static description: string;
60
+ static args: {
61
+ file: _oclif_core_interfaces0.Arg<string, Record<string, unknown>>;
62
+ };
63
+ static flags: {
64
+ help: _oclif_core_interfaces0.BooleanFlag<void>;
65
+ 'api-key': _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
66
+ 'editor-url': _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
67
+ host: _oclif_core_interfaces0.OptionFlag<string, _oclif_core_interfaces0.CustomOptions>;
68
+ port: _oclif_core_interfaces0.OptionFlag<number, _oclif_core_interfaces0.CustomOptions>;
69
+ open: _oclif_core_interfaces0.BooleanFlag<boolean>;
70
+ debug: _oclif_core_interfaces0.BooleanFlag<boolean>;
71
+ };
72
+ run(): Promise<void>;
73
+ }
56
74
  declare const COMMANDS: {
57
75
  plan: typeof PlanCommand;
58
76
  diff: typeof DiffCommand;
59
77
  pull: typeof PullCommand;
78
+ open: typeof OpenCommand;
60
79
  };
61
- declare function run(argv?: string[]): Promise<void>;
80
+ declare function run(argv?: string[], entryUrl?: string): Promise<void>;
62
81
  //#endregion
63
82
  export { COMMANDS, formatPlanSummary, run };