@rool-dev/sdk 2.0.0-dev.66fd5c8 → 2.0.0-dev.69ff8af

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,6 +1,18 @@
1
- # Rool SDK
1
+ <p align="center" class="sdk-brand">
2
+ <a href="https://rool.dev">
3
+ <img class="sdk-brand-logo" src="./assets/rool-logo.svg" alt="Rool logo" width="44" height="44">
4
+ <picture>
5
+ <source media="(prefers-color-scheme: dark)" srcset="./assets/rool-wordmark-dark.svg">
6
+ <img class="sdk-brand-wordmark" src="./assets/rool-wordmark.svg" alt="Rool" width="128">
7
+ </picture>
8
+ </a>
9
+ </p>
2
10
 
3
- TypeScript SDK for Rool Machines.
11
+ <h1 align="center" class="sdk-title">TypeScript SDK</h1>
12
+
13
+ The official TypeScript SDK for building apps and automations with [Rool Machines](https://rool.dev).
14
+
15
+ A Rool Machine is a persistent cloud VM with snapshots, files, and embedded AI agents. The SDK provides one typed interface for authentication, machine management, file access, agent conversations, and live updates.
4
16
 
5
17
  ## Installation
6
18
 
@@ -8,683 +20,107 @@ TypeScript SDK for Rool Machines.
8
20
  npm install @rool-dev/sdk
9
21
  ```
10
22
 
11
- ## User and session API
23
+ ## Connect from Node.js
12
24
 
13
- ```typescript
14
- import { RoolClient } from "@rool-dev/sdk";
15
-
16
- const client = new RoolClient({
17
- getTokens: () => ({
18
- accessToken: currentAccessToken,
19
- roolToken: currentRoolToken,
20
- }),
21
- });
22
-
23
- const session = await client.getSession();
24
- const account = await client.getAccount();
25
- const profile = await client.getProfile();
26
- const userAppData = await client.getUserAppData();
27
- const greeting = await client.getGreeting("en");
28
-
29
- await client.replaceProfile({
30
- name: "Ada",
31
- marketingOptIn: true,
32
- });
33
- await client.setUserAppData("theme", "dark");
34
- await client.deleteUserAppData("theme");
35
- await client.deleteAccount();
36
- ```
37
-
38
- `UserAppData` is an opaque cross-device JSON object. App data is changed one key at a time so unrelated settings cannot overwrite each other.
39
-
40
- ## Account events
25
+ `NodeAuth` opens the user's browser for login, stores their credentials locally, and refreshes them when needed.
41
26
 
42
27
  ```typescript
43
- const unsubscribe = client.events.subscribe(async (event) => {
44
- if (event.type === "session") {
45
- renderSession(event.session);
46
- } else if (event.type === "account_changed") {
47
- renderAccount(await client.getAccount());
48
- } else if (event.type === "profile_changed") {
49
- renderProfile(await client.getProfile());
50
- } else if (event.type === "user_app_data_changed") {
51
- renderUserAppData(await client.getUserAppData());
52
- } else if (event.type === "machines_changed") {
53
- renderMachines(await client.listMachines());
54
- } else if (event.type === "machine_members_changed") {
55
- renderMembers(
56
- event.machineId,
57
- await client.machine(event.machineId).members.list(),
58
- );
59
- }
60
- });
61
-
62
- unsubscribe();
63
- ```
64
-
65
- Account events tell the client when something changed. Fetch the relevant route to get the latest data. The SDK starts with `/v2/session`, then long-polls for changes using its account sync token. Empty polls and network retries do not produce events or refresh the session. If a token expires, the SDK fetches a new session and sends another `session` event.
66
-
67
- `getTokens` may return tokens synchronously or asynchronously. The SDK sends the access token as `Authorization: Bearer …` and the Rool token as `X-Rool-Token`. Bearer-only integrations can use `getAccessToken` instead. Use `apiUrl` to target a non-production server and `fetch` to provide a custom transport.
68
-
69
- ## Authentication
70
-
71
- Auth clients own login, credential storage, and refresh. `RoolClient` only asks one for tokens and tells it when authentication is invalidated.
72
-
73
- ### Browser authentication
74
-
75
- ```typescript
76
- import { BrowserAuth, RoolClient } from "@rool-dev/sdk";
77
-
78
- const auth = new BrowserAuth();
79
- const client = new RoolClient({
80
- getTokens: auth.getTokens,
81
- onAuthInvalidated: auth.logout,
82
- });
28
+ import { RoolClient } from "@rool-dev/sdk";
29
+ import { NodeAuth } from "@rool-dev/sdk/node";
83
30
 
84
- auth.onAuthStateChanged(renderSignedInState);
31
+ const auth = new NodeAuth();
85
32
  if (!(await auth.initialize())) {
86
- await auth.login("My App");
33
+ await auth.login("My app");
87
34
  }
88
- ```
89
-
90
- `initialize()` processes an auth callback in the URL. Tokens are stored in endpoint-scoped browser storage and refreshed when requested.
91
35
 
92
- ### Native authentication
93
-
94
- `NativeAuth` uses system-browser PKCE for Google and Apple while also supporting password and magic-link sign-in.
95
-
96
- ```typescript
97
- import { App } from "@capacitor/app";
98
- import { Browser } from "@capacitor/browser";
99
- import { NativeAuth, RoolClient } from "@rool-dev/sdk";
100
-
101
- const auth = new NativeAuth({
102
- redirectUri: "roolandroidauth://auth/callback",
103
- defaultProvider: "google",
104
- openExternal: (url) => Browser.open({ url }),
105
- });
106
36
  const client = new RoolClient({
107
37
  getTokens: auth.getTokens,
108
38
  onAuthInvalidated: auth.logout,
109
39
  });
110
-
111
- App.addListener("appUrlOpen", async ({ url }) => {
112
- await Browser.close();
113
- await auth.handleRedirect(url);
114
- });
115
-
116
- if (!(await auth.initialize())) await auth.login("My App");
117
40
  ```
118
41
 
119
- The redirect URI must exactly match the app setup and auth server allowlist. Pass `{ provider: "apple" }` to `login()` or `signup()` to override the default provider.
42
+ The package also includes:
120
43
 
121
- ```typescript
122
- const result = await auth.signInWithPassword(email, password);
123
- if (result.status === "verify_required") showCheckYourEmailMessage();
44
+ - `BrowserAuth` for web apps using browser redirects and browser storage.
45
+ - `NativeAuth` for mobile apps that open sign-in in the system browser and return through a deep link. It also supports passwords and magic links.
124
46
 
125
- await auth.requestMagicLink(email);
126
- await auth.verify(verifyToken);
127
- ```
47
+ ## Put a machine to work
128
48
 
129
- On native, an HTTPS magic link only returns to the app when Universal Links or App Links are configured for that domain.
130
-
131
- ### Password and account methods
132
-
133
- Browser and native auth clients also provide:
134
-
135
- - `setPassword(password)`
136
- - `requestEmailChange(newEmail)`
137
- - `verify(token)`
138
- - `logout()`
139
- - `isAuthenticated()`
140
-
141
- ### Node.js authentication
49
+ This example creates a machine, gives an agent a job, uploads a receipt, and asks the agent to file it. The file API writes to the machine's WebDAV storage, so the agent and the app see the same files.
142
50
 
143
51
  ```typescript
144
- import { RoolClient } from "@rool-dev/sdk";
145
- import { NodeAuth } from "@rool-dev/sdk/node";
52
+ import { readFile } from "node:fs/promises";
146
53
 
147
- const auth = new NodeAuth();
148
- if (!(await auth.initialize())) await auth.login("My CLI");
54
+ const created = await client.createMachine({ name: "Receipts" });
55
+ const machine = client.machine(created.id);
149
56
 
150
- const client = new RoolClient({
151
- getTokens: auth.getTokens,
152
- onAuthInvalidated: auth.logout,
57
+ const agent = await machine.agents.create("receipt-filer", {
58
+ system:
59
+ "File receipt attachments under /rool-drive/receipts. " +
60
+ "Choose useful subfolders based on the receipt's contents.",
153
61
  });
154
- ```
155
-
156
- `NodeAuth` opens the system browser for login and stores endpoint-scoped credentials under `~/.config/rool/`. It refreshes the access and Rool tokens when requested. Pass `apiUrl` to select another deployed environment; the corresponding auth URL is derived from it. When the API uses a loopback URL, pass `authUrl` explicitly.
157
-
158
- Use `profile` or `credentialsPath` when an application needs multiple independent accounts:
159
-
160
- ```typescript
161
- const auth = new NodeAuth({
162
- apiUrl: "https://api.example.com",
163
- profile: "automation",
62
+ const conversation = await agent.createConversation({
63
+ name: "Receipt inbox",
64
+ visibility: "private",
164
65
  });
165
- ```
166
-
167
- API errors are thrown as `RoolProblem` with the server's stable `code`, HTTP `status`, `title`, and `detail`.
168
-
169
- ## Machine API
170
-
171
- ```typescript
172
- const created = await client.createMachine({ name: "Research" });
173
- const machine = client.machine(created.id);
174
-
175
- const details = await machine.get();
176
- await machine.settings.replace({ name: "Field research" });
177
- const copy = await machine.duplicate({ name: "Research copy" });
178
- const response = await machine.fetchUrl("https://example.com/data.json");
179
- await client.machine(copy.id).delete();
180
- ```
181
-
182
- `client.machine(id)` returns a stable machine handle that owns machine-scoped APIs and synchronization state. Creation, listing, sessions, duplication, and `machine.get()` all return the same point-in-time `MachineSummary`; bind its ID to a handle before performing machine operations. Summaries include the machine's inbound email address, lifecycle `state`, and an opaque `meta` JSON object. `fetchUrl()` returns the upstream `Response`, including non-success statuses.
183
-
184
- ## Machine checkpoints
185
-
186
- ```typescript
187
- const history = await machine.checkpoints.list();
188
- const checkpoint = history.checkpoints.at(-1);
189
- if (checkpoint) {
190
- await machine.checkpoints.restore(checkpoint.id);
191
- }
192
- ```
193
-
194
- The checkpoint collection contains the currently restorable timeline and the `baseCheckpointId` underlying the live filesystem. The live filesystem can contain newer uncheckpointed changes. Restoring a checkpoint preserves those changes as a new checkpoint and causes a watched machine file tree to reconcile completely. Moving backward does not discard later checkpoints, but modifying the filesystem from that earlier position replaces the later timeline.
195
-
196
- ## Machine files
197
66
 
198
- ```typescript
199
- const files = machine.files;
200
- const path = "/rool-drive/documents/report.pdf";
201
-
202
- const storage = await files.getStorageUsage();
203
- console.log(storage.usedBytes, storage.availableBytes);
204
- const { maxUploadBytes } = await files.options(path);
205
- console.log(maxUploadBytes);
206
- await files.createDirectory("/rool-drive/documents");
207
- const written = await files.write(path, reportBlob, {
67
+ const receiptPath = "/rool-drive/receipts/unsorted/receipt.pdf";
68
+ await machine.files.write(receiptPath, await readFile("./receipt.pdf"), {
208
69
  contentType: "application/pdf",
209
- ifNoneMatch: "*",
210
- onUploadProgress: ({ transferredBytes, totalBytes }) => {
211
- if (totalBytes) renderUploadProgress(transferredBytes / totalBytes);
212
- },
213
- });
214
- await files.write(
215
- "/rool-drive/documents/archive.bin",
216
- () => createArchiveStream(),
217
- { contentType: "application/octet-stream" },
218
- );
219
- const info = await files.stat(path);
220
- const documents = await files.list("/rool-drive/documents");
221
- const response = await files.read(path, {
222
- range: { start: 0, end: 1023 },
223
- ifMatch: info.etag,
224
- });
225
- const hydrated = await files.readMultiple([
226
- "/space/.meta.json",
227
- "/rool-drive/documents/notes.json",
228
- ]);
229
- await files.copy(path, "/rool-drive/documents/report-backup.pdf", {
230
- overwrite: false,
231
- ifMatch: written.etag,
70
+ createParents: true,
232
71
  });
233
- await files.move(path, "/rool-drive/documents/final-report.pdf");
234
- const deleted = await files.deleteMultiple([
235
- "/rool-drive/documents/final-report.pdf",
236
- { path: "/rool-drive/documents/notes.json", ifMatch: '"notes-etag"' },
237
- ]);
238
- for (const result of deleted) {
239
- if (!result.ok)
240
- console.error(`Failed to delete ${result.path}`, result.error);
241
- }
242
- ```
243
-
244
- Paths are absolute machine paths under `/space` or `/rool-drive`. `list()` without a path enumerates those storage roots; pass `{ recursive: true }` to enumerate a complete subtree. File and directory metadata has a discriminating `kind` field. Reads return the native `Response` so callers can stream the body. `readMultiple()` hydrates ordered small files in one request and returns an `ok` result with binary-safe bytes and validators, or a per-file HTTP failure. A batch accepts at most 128 paths, 2 MiB per successful file, and 16 MiB across successful files. Writes accept any `BodyInit`, including `Blob` and `ReadableStream`, and return the same complete file metadata as `stat()` and `list()`. Pass a function that creates a fresh `ReadableStream` when an upload must be replayable after a machine route change; a directly passed stream remains one-shot. `onUploadProgress` reports transferred bytes and includes the total size when it is known; successful completion is confirmed by the `write()` promise. `options(path)` reports `maxUploadBytes` for that path's storage root. Calling `options()` without a path reports whole-DAV capabilities and returns `null` for `maxUploadBytes` because `/space` and `/rool-drive` have different limits. Copy and move operations overwrite by default; pass `{ overwrite: false }` for create-only behavior.
245
-
246
- `deleteMultiple()` sends independent DAV requests with at most eight in flight. It accepts plain paths and targets carrying their own HTTP preconditions, and returns one ordered success or failure result per target. The requests are not atomic. A directory target recursively deletes its contents, so callers should omit redundant descendants; duplicate and overlapping targets otherwise remain independent and can race.
247
72
 
248
- Every file and directory has protected `access` metadata. `currentUser` says whether the requesting user can read or write it. `readableBy` and `writableBy` describe its filesystem audiences as `resource-owner`, `machine-admins`, `machine-editors`, and `machine-members`. For a file, write means changing its contents. For a directory, write means adding, removing, or renaming entries. Members without read access to a directory do not receive that directory through listing, direct lookup, or synchronization.
249
-
250
- `getStorageUsage()` reports the used and writable bytes on the machine's complete live persistent filesystem, including public files and private runtime or system state. It excludes checkpoint history and the ephemeral operating-system overlay.
251
-
252
- Watch the files when an application needs a live machine file tree:
253
-
254
- ```typescript
255
- await files.watch();
256
-
257
- const unsubscribe = files.tree.subscribe(({ reset, changed, deleted }) => {
258
- renderFileChanges({ reset, changed, deleted });
73
+ await conversation.prompt("File this receipt.", {
74
+ attachments: [receiptPath],
259
75
  });
260
-
261
- const cached = files.tree.get("/rool-drive/documents/final-report.pdf");
262
- const etag = files.tree.etag("/rool-drive/documents/final-report.pdf");
263
- const allDocuments = files.tree.list("/rool-drive/documents", {
264
- recursive: true,
76
+ await conversation.follow({
77
+ onEvent(event) {
78
+ if (event.type === "output.delta" && event.content.type === "text") {
79
+ process.stdout.write(event.content.text);
80
+ }
81
+ },
265
82
  });
266
-
267
- unsubscribe();
268
- files.unwatch();
269
83
  ```
270
84
 
271
- `files.watch()` performs a complete `sync-collection`, then keeps the machine's file metadata and ETag cache current with long-poll incremental reports. DAV writes and guest-program changes enter the same tree. An invalid or expired sync token causes an atomic complete reconciliation and a change event with `reset: true`. Transient sync errors are retried and available as `files.watchError`; `files.unwatch()` aborts the active long poll.
85
+ Prompting is asynchronous: `prompt()` returns when Rool accepts the work, while the agent keeps running on the machine. An app can follow the current run, leave, and reconnect later. UI clients can instead watch a conversation and receive a current view as its saved turns and live output change.
272
86
 
273
- ## Rool Object Collections
87
+ ## Give agents data your app understands
274
88
 
275
- Objects are JSON stored under `/space`. Collections are directories with a `.schema.json` definition and objects are schema-checked JSON files.
89
+ A machine can hold records as well as documents. A collection defines the fields in each record, and Rool rejects writes that do not match. For example, an app can keep an index alongside the receipt files:
276
90
 
277
91
  ```typescript
278
- await machine.files.watch();
279
-
280
- const task = await machine.collections.create("task", {
92
+ await machine.collections.create("receipts", {
281
93
  fields: [
282
- { name: "title", type: { kind: "string" } },
283
- { name: "done", type: { kind: "boolean" } },
94
+ { name: "vendor", type: { kind: "string" } },
95
+ { name: "amount", type: { kind: "number" } },
96
+ { name: "currency", type: { kind: "string" } },
97
+ { name: "document", type: { kind: "string" } },
284
98
  ],
285
99
  });
286
100
 
287
- const first = await machine.objects.create("/space/task/first.json", {
288
- title: "First task",
289
- done: false,
290
- });
291
- const objectPaths = machine.objects.list({ collection: "task" });
292
- const object = await machine.objects.get(first.path);
293
- const [sameObject, missing] = await machine.objects.getMultiple([
294
- first.path,
295
- "/space/task/missing.json",
296
- ]);
297
-
298
- await machine.objects.patch(first.path, { done: true });
299
- await machine.objects.move(first.path, "/space/task/renamed.json");
300
- const [removal] = await machine.objects.removeMultiple([
301
- "/space/task/renamed.json",
302
- ]);
303
- if (!removal) throw new Error("Object removal returned no result");
304
- if (!removal.ok) throw removal.error;
305
- await machine.collections.remove(task.name);
306
- ```
307
-
308
- `machine.objects.list()` returns object paths from the synchronized file tree without reading their bodies. `get()` reads one object and `getMultiple()` preserves the input positions and returns `undefined` for missing objects. Every call reads the current bodies from DAV, with multiple reads using bounded `read-multiple` batches. Collection schemas are read in the same way and schema replacement follows the guest's lazy-migration rule: existing objects are checked again only when edited.
309
-
310
- Creates are create-only. Metadata, schema, and object replacements plus object patches, moves, and removals use ETags and report status `412` when state changed concurrently. `removeMultiple()` uses a separate conditional DAV request for each object and returns ordered per-object results. Removing a collection recursively deletes its contents. Object moves do not overwrite by default; pass `{ overwrite: true }` explicitly. Patch values of `null` or `undefined` remove fields.
311
-
312
- The semantic APIs use the machine's shared file tree and sync loop. They do not create separate synchronization state. Body reads and mutations also work without `machine.files.watch()` by reading current DAV state directly; `objects.list()` reflects the shared file tree, so watch the machine's files before enumerating paths.
313
-
314
- ## Agents
315
-
316
- Agents and conversations use stable JSON routes; their private machine files are not part of the SDK. Prompting does not require `machine.files.watch()`.
317
-
318
- ```typescript
319
- const defaultAgent = await machine.agents.get("rool");
320
- if (!defaultAgent) throw new Error("Rool agent is unavailable");
321
-
322
- const conversation = defaultAgent.conversation("research-chat");
323
- const stopWatching = conversation.watch((view) => {
324
- renderConversation({
325
- turns: view.turns,
326
- output: view.output,
327
- isRunning: view.isRunning,
328
- loading: view.loading,
329
- error: view.error,
330
- });
331
- });
332
-
333
- await conversation.prompt("Explain the result.", { effort: "reasoning" });
334
-
335
- // When this conversation leaves the UI:
336
- stopWatching();
337
- ```
338
-
339
- `prompt()` starts the conversation's current run and resolves once the server accepts it. The agent runs as a detached job. A conversation can only have one current run; call `cancel()` and wait for `follow()` to finish before prompting again. The `readOnly` option is accepted for compatibility with legacy prompting but currently has no effect.
340
-
341
- `watch()` is the normal UI API. It fetches only turns after the last turn it has seen, follows the current run, and refreshes the durable turns when that stream ends. `turns` contains saved history through the current user message while a run is active; `output` contains that run's replayed and live output. The first listener starts the work and removing the last listener stops it. Saved turns remain cached on the conversation handle for the next listener.
342
-
343
- `follow()` is the lower-level streaming API. It performs one `GET` of the conversation's current run. It receives the complete current-run output and then continues with new events until that response ends. Tool calls and their results arrive as `output.delta` events with matching IDs. A tool result contains nested content parts and an optional `error` flag. `follow()` returns `false` when there is no current run. Aborting `follow()` only stops that request; call `cancel()` to stop the detached job.
344
-
345
- The SDK uses `conversation_changed` account events to wake active watchers. A watcher also refreshes after prompting, cancellation, stream completion, connection failure, and account event-token replacement.
346
-
347
- Prompt attachments are existing `/space` or `/rool-drive` paths. Pass a durable user turn's `id` as `replaceTurnId` to replace that message and everything after it. This supports edits and rerolls, including the first message. A replacement gets a new user turn ID; use that ID to edit it again.
348
-
349
- Pass a JSON Schema as `responseSchema` to request structured output. Tools are skipped for that run. The successful assistant turn contains one JSON content part; the value is JSON directly, not a JSON string.
350
-
351
- ```typescript
352
- await conversation.prompt("Return the number of records.", {
353
- responseSchema: {
354
- type: "object",
355
- properties: { count: { type: "integer" } },
356
- required: ["count"],
357
- additionalProperties: false,
358
- },
359
- });
360
- await conversation.follow();
361
-
362
- const turns = await conversation.listTurns();
363
- const part = turns.at(-1)?.content[0];
364
- if (part?.type !== "json") throw new Error("No structured result");
365
- console.log(part.value); // { count: ... }
366
- ```
367
-
368
- A custom agent's `system` field contains instructions added after Rool's built-in machine context. Each conversation can add another instruction layer without changing the agent or its metadata.
369
-
370
- ```typescript
371
- const researcher = await machine.agents.create("researcher", {
372
- system: "Investigate carefully and distinguish facts from uncertainty.",
373
- });
374
- const customConversation = await researcher.createConversation({
375
- name: "Climate report",
376
- visibility: "private",
377
- });
378
- await customConversation.replaceInstructions(
379
- "For this conversation, compare at least two sources.",
380
- );
381
- await customConversation.prompt("Investigate this claim.");
382
- ```
383
-
384
- `getInstructions()` returns the conversation instructions. `replaceInstructions("")` clears them. Metadata changes do not affect them. New instructions apply to the next run; a run already in progress keeps the instructions it started with.
385
-
386
- Agents expose `replace()` and `delete()`. Conversations expose instruction and metadata replacement, listing, durable turn reads, rename, and deletion. Listed and fetched conversation metadata includes server-managed ISO 8601 `createdAt` and `updatedAt` timestamps plus `isRunning`, which can drive a running indicator without opening every run stream. Visibility defaults to private. The built-in `rool` agent cannot be replaced or deleted.
387
-
388
- ## Members and invites
389
-
390
- ```typescript
391
- const invite = await machine.invites.create({
392
- role: "editor",
393
- maxUses: 1,
394
- });
395
- const token = invite.url.split("/").at(-1)!;
396
-
397
- await client.getInvitePreview(token);
398
- await client.redeemInvite(token);
399
-
400
- const members = await machine.members.list();
401
- console.log(members[1].name ?? members[1].email);
402
- await machine.members.replaceRole(members[1].userId, { role: "viewer" });
403
- await machine.members.remove(members[1].userId);
404
- await machine.invites.revoke(invite.id);
405
- ```
406
-
407
- Invite URLs contain a secret token and are returned only when an invite is created. Invites can optionally be bound to an email address. Role replacement never creates membership or transfers ownership.
408
-
409
- ## Gifts
410
-
411
- A gift carries something of value in a short code. Users receive gifts from Rool and give the codes away themselves. Claiming a gift is single-use, and claiming your own gift is allowed.
412
-
413
- ```typescript
414
- const { gifts } = await client.listGifts();
415
- for (const gift of gifts) {
416
- console.log(gift.code, gift.url, gift.description, gift.claimedAt);
417
- }
418
-
419
- const preview = await client.previewGift(code); // no auth required
420
- console.log(
421
- `${preview.holderName ?? "Someone"} gave you ${preview.description}`,
422
- );
423
-
424
- const { gift } = await client.claimGift(code);
425
- if (gift.kind === "credits") console.log(`+${gift.credits} credits`);
426
- ```
427
-
428
- Codes are case-insensitive and the dash is optional. Prefer the server-rendered `description` for display. Narrow `gift.kind` when using the structured payload because new gift kinds may be added.
429
-
430
- A holder can add a note, archive a gift, or replace an unclaimed gift's code. These actions do not change what the gift grants. Archiving only hides it from the holder's normal view.
431
-
432
- ```typescript
433
- await client.updateGift(giftId, { note: "sent to Peter" });
434
- await client.updateGift(giftId, { archived: true });
435
- await client.updateGift(giftId, { note: null });
436
- const updated = await client.rotateGiftCode(giftId);
437
- ```
438
-
439
- Gift failures are `RoolProblem` errors. `gift_invalid` means the code or gift is unavailable to the caller. `gift_claimed` means it was already claimed.
440
-
441
- ## Speechmatics
442
-
443
- Voice input transcribes the user's speech with Speechmatics' real-time API. Rool mints a short-lived key without exposing its long-lived provider key; hand the temporary key to the Speechmatics real-time SDK and stream the user's mic audio to it.
444
-
445
- ```typescript
446
- const { token, expiresAt, ttl } = await client.getSpeechmaticsToken({
447
- ttl: 300,
101
+ await machine.objects.create("/space/receipts/cafe.json", {
102
+ vendor: "Cafe",
103
+ amount: 12,
104
+ currency: "EUR",
105
+ document: receiptPath,
448
106
  });
449
- // speechmaticsRealtimeClient.start(token, { transcription_config: { language: "en" } })
450
- ```
451
-
452
- The key expires after `ttl` seconds (60–3600, default 300). `expiresAt` is the epoch-milliseconds moment the key stops being accepted. A token request fails with `insufficient_credits` when the account's balance is too low and with `speechmatics_unavailable` when Speechmatics cannot issue a key.
453
-
454
- ## API problems
455
-
456
- Each problem `type` links to its entry below.
457
-
458
- <a id="problem-authentication_required"></a>
459
-
460
- ### `authentication_required`
461
-
462
- **Documentation placeholder.**
463
-
464
- <a id="problem-invalid_authentication"></a>
465
-
466
- ### `invalid_authentication`
467
-
468
- **Documentation placeholder.**
469
-
470
- <a id="problem-email_unverified"></a>
471
-
472
- ### `email_unverified`
473
-
474
- **Documentation placeholder.**
475
-
476
- <a id="problem-account_suspended"></a>
477
-
478
- ### `account_suspended`
479
-
480
- **Documentation placeholder.**
481
-
482
- <a id="problem-invalid_profile"></a>
483
-
484
- ### `invalid_profile`
485
-
486
- **Documentation placeholder.**
487
-
488
- <a id="problem-invalid_user_app_data"></a>
489
-
490
- ### `invalid_user_app_data`
491
-
492
- **Documentation placeholder.**
493
-
494
- <a id="problem-invalid_json"></a>
495
-
496
- ### `invalid_json`
497
-
498
- **Documentation placeholder.**
499
-
500
- <a id="problem-payload_too_large"></a>
501
-
502
- ### `payload_too_large`
503
-
504
- **Documentation placeholder.**
505
-
506
- <a id="problem-user_app_data_too_large"></a>
507
-
508
- ### `user_app_data_too_large`
509
-
510
- **Documentation placeholder.**
511
-
512
- <a id="problem-not_found"></a>
513
-
514
- ### `not_found`
515
-
516
- **Documentation placeholder.**
517
-
518
- <a id="problem-checkpoint_not_found"></a>
519
-
520
- ### `checkpoint_not_found`
521
-
522
- **Documentation placeholder.**
523
-
524
- <a id="problem-sync_token_required"></a>
525
-
526
- ### `sync_token_required`
527
-
528
- The account event route requires the sync token returned by `/v2/session`.
529
-
530
- <a id="problem-invalid_sync_token"></a>
531
-
532
- ### `invalid_sync_token`
533
-
534
- The account event history no longer contains everything after this token. Fetch `/v2/session` and continue with its new token.
535
-
536
- <a id="problem-invalid_wait_preference"></a>
537
-
538
- ### `invalid_wait_preference`
539
-
540
- The account event wait must be an integer from 0 through 50 seconds.
541
-
542
- <a id="problem-internal_error"></a>
543
-
544
- ### `internal_error`
545
-
546
- **Documentation placeholder.**
547
-
548
- <a id="problem-server_misconfigured"></a>
549
-
550
- ### `server_misconfigured`
551
-
552
- **Documentation placeholder.**
553
-
554
- <a id="problem-current_run_exists"></a>
555
-
556
- ### `current_run_exists`
557
-
558
- The conversation is already running. Cancel or follow that run before prompting again. This also applies to prompts with `replaceTurnId`.
559
-
560
- <a id="problem-replace_turn_not_found"></a>
561
-
562
- ### `replace_turn_not_found`
563
-
564
- The user turn supplied as `replaceTurnId` is no longer in the conversation. Fetch the turns again before retrying the edit.
565
-
566
- <a id="problem-invalid_member_role"></a>
567
-
568
- ### `invalid_member_role`
569
-
570
- **Documentation placeholder.**
571
-
572
- <a id="problem-role_not_replaceable"></a>
573
-
574
- ### `role_not_replaceable`
575
-
576
- **Documentation placeholder.**
577
-
578
- <a id="problem-membership_not_removable"></a>
579
-
580
- ### `membership_not_removable`
581
-
582
- **Documentation placeholder.**
583
-
584
- <a id="problem-invalid_invite"></a>
585
-
586
- ### `invalid_invite`
587
-
588
- **Documentation placeholder.**
589
-
590
- <a id="problem-invite_invalid"></a>
591
-
592
- ### `invite_invalid`
593
-
594
- **Documentation placeholder.**
595
-
596
- <a id="problem-invite_expired"></a>
597
-
598
- ### `invite_expired`
599
-
600
- **Documentation placeholder.**
601
-
602
- <a id="problem-invite_revoked"></a>
603
-
604
- ### `invite_revoked`
605
-
606
- **Documentation placeholder.**
607
-
608
- <a id="problem-invite_exhausted"></a>
609
-
610
- ### `invite_exhausted`
611
-
612
- **Documentation placeholder.**
613
-
614
- <a id="problem-invite_email_mismatch"></a>
615
-
616
- ### `invite_email_mismatch`
617
-
618
- **Documentation placeholder.**
619
-
620
- <a id="problem-gift_invalid"></a>
621
-
622
- ### `gift_invalid`
623
-
624
- The code is not valid, or the requested gift does not belong to the current user.
625
-
626
- <a id="problem-gift_claimed"></a>
627
-
628
- ### `gift_claimed`
629
-
630
- The gift has already been claimed. A claimed gift cannot be claimed again or given a new code.
631
-
632
- <a id="problem-invalid_input"></a>
633
-
634
- ### `invalid_input`
635
-
636
- The gift update is empty or contains an invalid note or archived value.
637
-
638
- <a id="problem-insufficient_credits"></a>
639
-
640
- ### `insufficient_credits`
641
-
642
- The account's credit balance is too low to mint a speech transcription key. Top up the balance and try again.
643
-
644
- <a id="problem-speechmatics_unavailable"></a>
645
-
646
- ### `speechmatics_unavailable`
647
-
648
- Speechmatics could not issue a transcription key right now. Try again shortly.
649
-
650
- ## Development
651
-
652
- ```bash
653
- pnpm build
654
- pnpm typecheck
655
107
  ```
656
108
 
657
- The user-route smoke test must target a dedicated non-production account. It replaces profile and app data, then schedules and cancels account deletion. The member-route smoke test requires a second non-production account to exercise invite redemption and membership changes. The local gift fixture accounts are deleted after the gift smoke test.
658
-
659
- Copy `.env.example` to the ignored `.env` file and configure the local endpoints and expected primary account ID.
109
+ The record is also a normal JSON file at `/space/receipts/cafe.json`. The app can work with it through `machine.objects`, while agents and programs inside the machine can use normal file tools. Changes from either side enter the same watched file tree.
660
110
 
661
- | Variable | Purpose |
662
- | ------------------------------- | ---------------------------------------------------------------------- |
663
- | `ROOL_TEST_API_URL` | API origin. HTTPS is required except for loopback development servers. |
664
- | `ROOL_TEST_AUTH_URL` | Auth endpoint override required for a loopback API. |
665
- | `ROOL_TEST_ROUTER_URL` | Local machine-router origin. |
666
- | `ROOL_TEST_USER_ID` | Expected primary account ID, preventing mutation of the wrong account. |
667
- | `ROOL_TEST_GIFT_HOLDER_EMAIL` | Local fixture account that holds a gift. |
668
- | `ROOL_TEST_GIFT_CLAIMANT_EMAIL` | Local fixture account that claims the gift. |
669
- | `ROOL_TEST_INTERNAL_SECRET` | Local rool-server secret used to create and remove fixtures. |
111
+ Collections can power task lists, catalogues, contact records, or a memory view with links between people, projects, and notes. The app gets predictable fields for its UI without hiding the data from the agent.
670
112
 
671
- Log the `sdk-v2-primary` and `sdk-v2-member` Node profiles into two dedicated accounts:
113
+ ## What the SDK handles
672
114
 
673
- ```bash
674
- node --env-file=.env --import tsx test/integration/v2/login.ts
675
- ```
676
-
677
- Then run the complete v2 smoke-test suite:
678
-
679
- ```bash
680
- pnpm test:v2
681
- ```
115
+ - **Machines:** create, configure, duplicate, checkpoint, share, and delete persistent VMs.
116
+ - **Files:** upload, stream, move, and watch files under `/space` and `/rool-drive`.
117
+ - **Agents:** define agents, keep conversation history, attach machine files, stream runs, and request structured output.
118
+ - **Live state:** learn when account data changes, keep a file tree current, and stream conversation updates.
119
+ - **Shared app data:** define records that apps, agents, and programs inside the machine can all read and edit.
682
120
 
683
- To run one smoke test manually, invoke its script directly, for example:
121
+ Long-running work belongs to the machine rather than the client connection. File changes made by the app, a user, or an agent enter the same synchronized file tree, while account events tell the app when to fetch fresh account or machine data.
684
122
 
685
- ```bash
686
- node --env-file=.env --import tsx test/integration/v2/machine-routes.test.ts
687
- ```
123
+ The package exports its public TypeScript types, so editor autocomplete shows the detailed options and results. See [docs.rool.dev](https://docs.rool.dev/sdk/) for the published documentation.
688
124
 
689
125
  ## License
690
126
 
@@ -0,0 +1,5 @@
1
+ <svg width="424" height="424" viewBox="0 0 424 424" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M261.985 0C326.485 0 358.735 -0.000114441 382.009 15.2881C392.64 22.2717 401.728 31.3597 408.712 41.9912C424 65.2653 424 97.5154 424 162.015V261.985C424 326.485 424 358.735 408.712 382.009C401.728 392.64 392.64 401.728 382.009 408.712C358.735 424 326.485 424 261.985 424H162.015C97.5154 424 65.2653 424 41.9912 408.712C31.3597 401.728 22.2717 392.64 15.2881 382.009C-0.000114441 358.735 0 326.485 0 261.985V162.015C0 97.5154 -0.000128746 65.2653 15.2881 41.9912C22.2717 31.3597 31.3597 22.2717 41.9912 15.2881C65.2653 -0.000128746 97.5154 0 162.015 0H261.985Z" fill="black"/>
3
+ <path d="M161.583 119.289C190.445 119.289 213.843 142.686 213.843 171.549V264.457C213.843 293.319 190.445 316.717 161.583 316.717C132.72 316.717 109.322 293.32 109.322 264.457V171.549C109.322 142.686 132.72 119.289 161.583 119.289Z" fill="white"/>
4
+ <path d="M280.739 119.289C309.602 119.289 332.999 142.686 332.999 171.549C332.999 200.411 309.602 223.809 280.739 223.81C251.877 223.81 228.479 200.411 228.479 171.549C228.479 142.686 251.877 119.289 280.739 119.289Z" fill="white"/>
5
+ </svg>
@@ -0,0 +1,7 @@
1
+ <svg width="2706" height="1190" viewBox="780 535 2706 1190" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M3338.88 572.35V562.079H3387.27V572.35H3369.23V621H3356.92V572.35H3338.88ZM3396.86 562.079H3412.22L3428.45 601.667H3429.14L3445.36 562.079H3460.73V621H3448.64V582.65H3448.15L3432.91 620.712H3424.68L3409.43 582.506H3408.94V621H3396.86V562.079Z" fill="white"/>
3
+ <rect x="3138.39" y="560" width="151" height="1120" fill="white"/>
4
+ <path d="M1604.89 876C1831.06 876 2014.39 1060.46 2014.39 1288C2014.39 1515.54 1831.06 1700 1604.89 1700C1378.73 1700 1195.39 1515.54 1195.39 1288C1195.39 1060.46 1378.73 876 1604.89 876ZM1605.39 1019C1460.7 1019 1343.39 1139.44 1343.39 1288C1343.39 1436.56 1460.7 1557 1605.39 1557C1750.09 1557 1867.39 1436.56 1867.39 1288C1867.39 1139.44 1750.09 1019 1605.39 1019Z" fill="white"/>
5
+ <path d="M2555.89 876C2782.06 876 2965.39 1060.46 2965.39 1288C2965.39 1515.54 2782.06 1700 2555.89 1700C2329.73 1700 2146.39 1515.54 2146.39 1288C2146.39 1060.46 2329.73 876 2555.89 876ZM2556.39 1019C2411.14 1019 2293.39 1139.44 2293.39 1288C2293.39 1436.56 2411.14 1557 2556.39 1557C2701.65 1557 2819.39 1436.56 2819.39 1288C2819.39 1139.44 2701.65 1019 2556.39 1019Z" fill="white"/>
6
+ <path d="M1068 1029.28C1002.53 1049.7 955 1110.8 955 1183V1193L955.006 1193V1680H804.006V1184.45C804.004 1183.97 804 1183.48 804 1183C804 1027.01 918.476 897.76 1068 874.672V1029.28Z" fill="white"/>
7
+ </svg>
@@ -0,0 +1,7 @@
1
+ <svg width="2706" height="1190" viewBox="780 535 2706 1190" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M3338.88 572.35V562.079H3387.27V572.35H3369.23V621H3356.92V572.35H3338.88ZM3396.86 562.079H3412.22L3428.45 601.667H3429.14L3445.36 562.079H3460.73V621H3448.64V582.65H3448.15L3432.91 620.712H3424.68L3409.43 582.506H3408.94V621H3396.86V562.079Z" fill="black"/>
3
+ <rect x="3138.39" y="560" width="151" height="1120" fill="black"/>
4
+ <path d="M1604.89 876C1831.06 876 2014.39 1060.46 2014.39 1288C2014.39 1515.54 1831.06 1700 1604.89 1700C1378.73 1700 1195.39 1515.54 1195.39 1288C1195.39 1060.46 1378.73 876 1604.89 876ZM1605.39 1019C1460.7 1019 1343.39 1139.44 1343.39 1288C1343.39 1436.56 1460.7 1557 1605.39 1557C1750.09 1557 1867.39 1436.56 1867.39 1288C1867.39 1139.44 1750.09 1019 1605.39 1019Z" fill="black"/>
5
+ <path d="M2555.89 876C2782.06 876 2965.39 1060.46 2965.39 1288C2965.39 1515.54 2782.06 1700 2555.89 1700C2329.73 1700 2146.39 1515.54 2146.39 1288C2146.39 1060.46 2329.73 876 2555.89 876ZM2556.39 1019C2411.14 1019 2293.39 1139.44 2293.39 1288C2293.39 1436.56 2411.14 1557 2556.39 1557C2701.65 1557 2819.39 1436.56 2819.39 1288C2819.39 1139.44 2701.65 1019 2556.39 1019Z" fill="black"/>
6
+ <path d="M1068 1029.28C1002.53 1049.7 955 1110.8 955 1183V1193L955.006 1193V1680H804.006V1184.45C804.004 1183.97 804 1183.48 804 1183C804 1027.01 918.476 897.76 1068 874.672V1029.28Z" fill="black"/>
7
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rool-dev/sdk",
3
- "version": "2.0.0-dev.66fd5c8",
3
+ "version": "2.0.0-dev.69ff8af",
4
4
  "description": "TypeScript SDK for Rool Machines",
5
5
  "packageManager": "pnpm@10.17.1",
6
6
  "type": "module",
@@ -18,6 +18,7 @@
18
18
  }
19
19
  },
20
20
  "files": [
21
+ "assets",
21
22
  "dist"
22
23
  ],
23
24
  "scripts": {