@abraca/orchestrator 2.3.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 ADDED
@@ -0,0 +1,237 @@
1
+ # @abraca/orchestrator
2
+
3
+ Orchestrate simulated actors on an Abracadabra CRDT server for screen recording. Define a cast of actors, script their actions on a timeline, and run the scene — the orchestrator connects to the server and performs everything in real time while you record the dashboard.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @abraca/orchestrator
9
+ ```
10
+
11
+ Peer dependencies: `@abraca/dabra`, `yjs`, `y-protocols`
12
+
13
+ ## Quick Start
14
+
15
+ ```ts
16
+ import { defineScene, actor, actions } from '@abraca/orchestrator'
17
+
18
+ export default defineScene({
19
+ server: { url: 'ws://localhost:8080' },
20
+ actors: [
21
+ actor('Alice', { color: '#F97066' }),
22
+ actor('Bob', { color: '#4A90D9' }),
23
+ ],
24
+ timeline: [
25
+ { at: 0, actor: 'Alice', action: actions.connect() },
26
+ { at: 0, actor: 'Bob', action: actions.connect() },
27
+ { at: 1000, actor: 'Alice', action: actions.navigate('my-doc') },
28
+ { at: 2000, actor: 'Alice', action: actions.type('my-doc', 'Hello from Alice!') },
29
+ { at: 3000, actor: 'Bob', action: actions.navigate('my-doc') },
30
+ { at: 4000, actor: 'Bob', action: actions.type('my-doc', '\nHello from Bob!') },
31
+ { at: 8000, actor: 'Alice', action: actions.disconnect() },
32
+ { at: 8000, actor: 'Bob', action: actions.disconnect() },
33
+ ],
34
+ })
35
+ ```
36
+
37
+ ## CLI Usage
38
+
39
+ ```bash
40
+ # Build packages first
41
+ pnpm build:packages
42
+
43
+ # Run a scene script (from repo root)
44
+ node --experimental-transform-types \
45
+ packages/orchestrator/dist/abracadabra-orchestrator.esm.js ./my-scene.ts
46
+
47
+ # Validate without connecting
48
+ node --experimental-transform-types \
49
+ packages/orchestrator/dist/abracadabra-orchestrator.esm.js --dry-run ./my-scene.ts
50
+ ```
51
+
52
+ Scene scripts are TypeScript files loaded via `--experimental-transform-types`. The `--dry-run` flag validates the scene structure (actor names, action types, required fields) and prints the timeline without connecting to the server.
53
+
54
+ > **Note:** Do not use `--conditions=source` — Node's type stripping does not support files under `node_modules`, which would break the `@abraca/dabra` peer dependency resolution.
55
+
56
+ ## Scene Definition
57
+
58
+ A scene is defined with `defineScene()` and must export as the default export.
59
+
60
+ ```ts
61
+ defineScene({
62
+ server: { url: 'ws://localhost:8080', inviteCode: 'optional-invite' },
63
+ actors: [ ... ],
64
+ timeline: [ ... ],
65
+ vars: { hubDocId: 'abc-123' }, // pre-defined variables
66
+ duration: 30000, // auto-stop after 30s
67
+ onStart: async () => { ... }, // runs before timeline
68
+ onEnd: async () => { ... }, // runs after timeline
69
+ })
70
+ ```
71
+
72
+ ### Actors
73
+
74
+ ```ts
75
+ actor('Name', {
76
+ color: '#hex', // cursor / avatar color
77
+ keyFile: './keys/name', // optional Ed25519 key path (auto-generated if missing)
78
+ avatar: 'https://...', // optional avatar URL
79
+ })
80
+ ```
81
+
82
+ Actors authenticate via Ed25519 challenge-response. If no `keyFile` is provided, a deterministic keypair is derived from the actor name.
83
+
84
+ ## Actions Reference
85
+
86
+ All actions are created via the `actions` factory object.
87
+
88
+ ### Connection
89
+
90
+ | Action | Factory | Description |
91
+ |--------|---------|-------------|
92
+ | `connect` | `actions.connect()` | Connect actor to server, authenticate, sync root doc |
93
+ | `disconnect` | `actions.disconnect()` | Clear awareness and disconnect gracefully |
94
+
95
+ ### Navigation
96
+
97
+ | Action | Factory | Description |
98
+ |--------|---------|-------------|
99
+ | `navigate` | `actions.navigate(docId)` | Set actor's active document (awareness `docId` field) |
100
+
101
+ ### Text Editing
102
+
103
+ | Action | Factory | Description |
104
+ |--------|---------|-------------|
105
+ | `type` | `actions.type(docId, text, opts?)` | Type text character-by-character with realistic timing |
106
+ | `typeDelete` | `actions.typeDelete(docId, count, opts?)` | Delete characters one at a time (backspace) |
107
+ | `select` | `actions.select(docId, anchor, head)` | Set a text selection range |
108
+ | `moveCursor` | `actions.moveCursor(docId, from, to, duration, easing?)` | Animate cursor movement with easing |
109
+
110
+ Options for `type`: `{ speed?: number, variance?: number, position?: number }`
111
+ Options for `typeDelete`: `{ speed?: number, variance?: number, position?: number }`
112
+ Easing: `'linear'` | `'easeIn'` | `'easeOut'` | `'easeInOut'`
113
+
114
+ ### Document Operations
115
+
116
+ | Action | Factory | Description |
117
+ |--------|---------|-------------|
118
+ | `createDocument` | `actions.createDocument(parentId, label, opts?)` | Create a new doc in the tree |
119
+ | `moveDocument` | `actions.moveDocument(docId, newParentId, order?)` | Move a doc to a new parent |
120
+ | `renameDocument` | `actions.renameDocument(docId, label)` | Rename a document |
121
+ | `writeContent` | `actions.writeContent(docId, markdown)` | Set doc content from markdown (bulk) |
122
+ | `deleteContent` | `actions.deleteContent(docId, from, length)` | Delete a range of content elements |
123
+ | `setMeta` | `actions.setMeta(docId, meta)` | Merge metadata fields (icon, color, etc.) |
124
+
125
+ Options for `createDocument`: `{ docType?: string, meta?: Record<string, unknown>, assignId?: string }`
126
+
127
+ The `assignId` option stores the newly created document's ID in the scene `vars` map, so later actions can reference it with `${varName}`.
128
+
129
+ ### Awareness & UI
130
+
131
+ | Action | Factory | Description |
132
+ |--------|---------|-------------|
133
+ | `setStatus` | `actions.setStatus(status)` | Set actor's status text (or `null` to clear) |
134
+ | `setAwareness` | `actions.setAwareness(fields, docId?)` | Set arbitrary awareness fields |
135
+ | `clearAwareness` | `actions.clearAwareness(fields, docId?)` | Remove awareness fields |
136
+ | `pointerMove` | `actions.pointerMove(docId, from, to, duration, easing?)` | Animate pointer movement |
137
+ | `scrollTo` | `actions.scrollTo(docId, position)` | Set scroll position (0–1) |
138
+
139
+ ### Kanban
140
+
141
+ | Action | Factory | Description |
142
+ |--------|---------|-------------|
143
+ | `kanbanHover` | `actions.kanbanHover(docId, cardId)` | Hover a kanban card (or `null` to clear) |
144
+ | `kanbanDrag` | `actions.kanbanDrag(docId, cardId, toColumnId, duration)` | Animate dragging a card to a column |
145
+
146
+ ### Chat
147
+
148
+ | Action | Factory | Description |
149
+ |--------|---------|-------------|
150
+ | `sendChat` | `actions.sendChat(channel, message)` | Send a chat message via stateless protocol |
151
+
152
+ ### Flow Control
153
+
154
+ | Action | Factory | Description |
155
+ |--------|---------|-------------|
156
+ | `wait` | `actions.wait(duration)` | Pause for N milliseconds |
157
+ | `parallel` | `actions.parallel(entries)` | Run timeline entries concurrently |
158
+ | `sequence` | `actions.sequence(entries)` | Run timeline entries one after another |
159
+ | `repeat` | `actions.repeat(times, entries)` | Loop entries N times |
160
+
161
+ ## Timeline
162
+
163
+ Timeline entries are scheduled by their `at` field (milliseconds from scene start). Entries with the same `at` value run in parallel. Within `parallel` and `sequence` blocks, `at` is relative to the block's start.
164
+
165
+ ```ts
166
+ timeline: [
167
+ // These run at the same time (both at 0ms)
168
+ { at: 0, actor: 'Alice', action: actions.connect() },
169
+ { at: 0, actor: 'Bob', action: actions.connect() },
170
+
171
+ // Nested parallel block with relative offsets
172
+ {
173
+ at: 2000,
174
+ action: actions.parallel([
175
+ { actor: 'Alice', action: actions.type('doc', 'Hello') },
176
+ { at: 500, actor: 'Bob', action: actions.type('doc', 'World') },
177
+ ]),
178
+ },
179
+
180
+ // Sequence: each runs after the previous completes
181
+ {
182
+ at: 5000,
183
+ action: actions.sequence([
184
+ { actor: 'Alice', action: actions.type('doc', 'Line 1\n') },
185
+ { actor: 'Alice', action: actions.type('doc', 'Line 2\n') },
186
+ ]),
187
+ },
188
+
189
+ // Repeat: loop 3 times
190
+ {
191
+ at: 10000,
192
+ action: actions.repeat(3, [
193
+ { actor: 'Bob', action: actions.type('doc', '.') },
194
+ { action: actions.wait(1000) },
195
+ ]),
196
+ },
197
+ ]
198
+ ```
199
+
200
+ ## Variables
201
+
202
+ Define variables in `vars` and reference them with `${name}` in string fields (docId, parentId, text, label, markdown, etc.).
203
+
204
+ ```ts
205
+ defineScene({
206
+ vars: { hubDoc: 'abc-123' },
207
+ // ...
208
+ timeline: [
209
+ // Create a doc and store its ID as "newDoc"
210
+ {
211
+ at: 1000,
212
+ actor: 'Alice',
213
+ action: actions.createDocument('${hubDoc}', 'My Page', { assignId: 'newDoc' }),
214
+ },
215
+ // Reference the created doc later
216
+ {
217
+ at: 3000,
218
+ actor: 'Alice',
219
+ action: actions.type('${newDoc}', 'Content goes here'),
220
+ },
221
+ ],
222
+ })
223
+ ```
224
+
225
+ ## Programmatic API
226
+
227
+ ```ts
228
+ import { Orchestrator } from '@abraca/orchestrator'
229
+
230
+ const orchestrator = new Orchestrator()
231
+
232
+ await orchestrator.load('./my-scene.ts')
233
+ orchestrator.prepare()
234
+ orchestrator.dryRun() // optional: validate + print timeline
235
+ await orchestrator.run()
236
+ await orchestrator.cleanup()
237
+ ```