@rool-dev/sdk 0.8.2-dev.02b70e5 → 0.8.2-dev.d82ea25
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 +465 -1005
- package/dist/channel.d.ts +93 -248
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +410 -577
- package/dist/channel.js.map +1 -1
- package/dist/client.d.ts +14 -46
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +31 -124
- package/dist/client.js.map +1 -1
- package/dist/graphql.d.ts +11 -36
- package/dist/graphql.d.ts.map +1 -1
- package/dist/graphql.js +72 -311
- package/dist/graphql.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/path.d.ts +6 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +47 -0
- package/dist/path.js.map +1 -0
- package/dist/reroute.d.ts +22 -0
- package/dist/reroute.d.ts.map +1 -0
- package/dist/reroute.js +61 -0
- package/dist/reroute.js.map +1 -0
- package/dist/rest.d.ts +27 -0
- package/dist/rest.d.ts.map +1 -0
- package/dist/rest.js +78 -0
- package/dist/rest.js.map +1 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +25 -10
- package/dist/router.js.map +1 -1
- package/dist/space.d.ts +23 -16
- package/dist/space.d.ts.map +1 -1
- package/dist/space.js +111 -78
- package/dist/space.js.map +1 -1
- package/dist/subscription.d.ts.map +1 -1
- package/dist/subscription.js +47 -40
- package/dist/subscription.js.map +1 -1
- package/dist/types.d.ts +85 -224
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -4
- package/dist/types.js.map +1 -1
- package/dist/webdav.d.ts +176 -0
- package/dist/webdav.d.ts.map +1 -0
- package/dist/webdav.js +495 -0
- package/dist/webdav.js.map +1 -0
- package/package.json +2 -1
- package/dist/apps.d.ts +0 -30
- package/dist/apps.d.ts.map +0 -1
- package/dist/apps.js +0 -81
- package/dist/apps.js.map +0 -1
- package/dist/media.d.ts +0 -76
- package/dist/media.d.ts.map +0 -1
- package/dist/media.js +0 -249
- package/dist/media.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
# Rool SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript SDK for Rool, a persistent collaborative workspace for objects, AI-assisted editing, and per-space files.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Core primitives:
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **Objects** — Key-value records with any fields you define. References between objects are data fields whose values are object IDs.
|
|
13
|
-
- **AI operations** — Create, update, or query objects using natural language and `{{placeholders}}`
|
|
7
|
+
- **Spaces** — containers for objects, schema, metadata, channels, collaborators, and files.
|
|
8
|
+
- **Channels** — named contexts within a space for object operations and AI conversations.
|
|
9
|
+
- **Conversations** — independent interaction histories within a channel.
|
|
10
|
+
- **Objects** — JSON records addressed by object paths such as `/space/article/welcome.json`.
|
|
11
|
+
- **Files** — user-visible files stored under `/rool-drive/...` through WebDAV.
|
|
14
12
|
|
|
15
13
|
## Installation
|
|
16
14
|
|
|
@@ -23,1074 +21,611 @@ npm install @rool-dev/sdk
|
|
|
23
21
|
```typescript
|
|
24
22
|
import { RoolClient } from '@rool-dev/sdk';
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
const
|
|
24
|
+
async function main() {
|
|
25
|
+
const client = new RoolClient();
|
|
28
26
|
|
|
29
|
-
if (!
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
if (!(await client.initialize())) {
|
|
28
|
+
await client.login('My App');
|
|
29
|
+
// Browser auth redirects away. Run startup again after the auth callback.
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
const channel = await space.openChannel('main');
|
|
33
|
+
const space = await client.createSpace('Solar System');
|
|
34
|
+
const channel = await space.openChannel('main');
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
]);
|
|
36
|
+
await channel.createCollection('body', [
|
|
37
|
+
{ name: 'name', type: { kind: 'string' } },
|
|
38
|
+
{ name: 'mass', type: { kind: 'string' } },
|
|
39
|
+
{ name: 'radius', type: { kind: 'string' } },
|
|
40
|
+
{ name: 'orbits', type: { kind: 'maybe', inner: { kind: 'ref' } } },
|
|
41
|
+
]);
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
const { object: sun } = await channel.createObject({
|
|
47
|
-
data: {
|
|
48
|
-
type: 'body', // Must match a collection name
|
|
43
|
+
const { object: sun } = await channel.putObject('/space/body/sun.json', {
|
|
49
44
|
name: 'Sun',
|
|
50
|
-
mass: '
|
|
51
|
-
radius: '
|
|
52
|
-
}
|
|
53
|
-
});
|
|
45
|
+
mass: '1 solar mass',
|
|
46
|
+
radius: '696,340 km',
|
|
47
|
+
});
|
|
54
48
|
|
|
55
|
-
const { object: earth } = await channel.
|
|
56
|
-
data: {
|
|
57
|
-
type: 'body',
|
|
49
|
+
const { object: earth } = await channel.putObject('/space/body/earth.json', {
|
|
58
50
|
name: 'Earth',
|
|
59
|
-
mass: '
|
|
60
|
-
radius: '
|
|
61
|
-
orbits: sun.
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// Use the AI agent to work with your data
|
|
66
|
-
const { message, objects } = await channel.prompt(
|
|
67
|
-
'Add the other planets in our solar system, each referencing the Sun'
|
|
68
|
-
);
|
|
69
|
-
console.log(message); // AI explains what it did
|
|
70
|
-
console.log(`Created ${objects.length} objects`);
|
|
71
|
-
|
|
72
|
-
// Query with natural language
|
|
73
|
-
const { objects: innerPlanets } = await channel.findObjects({
|
|
74
|
-
prompt: 'planets closer to the sun than Earth'
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// Clean up
|
|
78
|
-
channel.close();
|
|
79
|
-
```
|
|
51
|
+
mass: '1 Earth mass',
|
|
52
|
+
radius: '6,371 km',
|
|
53
|
+
orbits: sun.path,
|
|
54
|
+
});
|
|
80
55
|
|
|
81
|
-
|
|
56
|
+
const { message, objects } = await channel.prompt(
|
|
57
|
+
'Add the other planets in our solar system, each referencing the Sun.'
|
|
58
|
+
);
|
|
82
59
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
A **space** is a container that holds objects, schema, metadata, and channels. A **channel** is a named context within a space — it's the handle you use for all object and AI operations. Each channel contains one or more **conversations**, each with independent interaction history.
|
|
86
|
-
|
|
87
|
-
There are two main handles:
|
|
88
|
-
- **`RoolSpace`** — Live handle with SSE subscription for user management, link access, channel management, export, and channel lifecycle events. Extends `EventEmitter`.
|
|
89
|
-
- **`RoolChannel`** — Full real-time handle for objects, AI prompts, media, schema, and undo/redo.
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
// Open a space — live handle with SSE subscription
|
|
93
|
-
const space = await client.openSpace('space-id');
|
|
94
|
-
await space.addUser(userId, 'editor');
|
|
95
|
-
await space.setLinkAccess('viewer');
|
|
60
|
+
console.log(message);
|
|
61
|
+
console.log(`Modified ${objects.length} objects`);
|
|
96
62
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
space.on('channelUpdated', (channel) => console.log('Updated:', channel.id));
|
|
100
|
-
space.on('channelDeleted', (channelId) => console.log('Deleted:', channelId));
|
|
63
|
+
const loadedEarth = await channel.getObject(earth.path);
|
|
64
|
+
console.log(loadedEarth?.body.name);
|
|
101
65
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
await channel.prompt('Create some planets');
|
|
105
|
-
|
|
106
|
-
// Open another channel on the same space
|
|
107
|
-
const channel2 = await space.openChannel('research');
|
|
108
|
-
await channel2.prompt('Analyze the data'); // Independent channel
|
|
66
|
+
space.close();
|
|
67
|
+
}
|
|
109
68
|
|
|
110
|
-
|
|
111
|
-
space.close();
|
|
69
|
+
void main();
|
|
112
70
|
```
|
|
113
71
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
**Channel ID constraints:**
|
|
117
|
-
- 1–32 characters
|
|
118
|
-
- Only alphanumeric characters, hyphens (`-`), and underscores (`_`)
|
|
72
|
+
## Paths and Resource URIs
|
|
119
73
|
|
|
120
|
-
|
|
74
|
+
Most SDK methods take plain path strings:
|
|
121
75
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
For apps that need multiple independent interaction threads (e.g., a chat sidebar with multiple threads), use `channel.conversation()` to get a handle scoped to a specific conversation:
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
// Default conversation — most apps use this
|
|
128
|
-
const space = await client.openSpace('space-id');
|
|
129
|
-
const channel = await space.openChannel('main');
|
|
130
|
-
await channel.prompt('Hello'); // Uses 'default' conversation
|
|
131
|
-
|
|
132
|
-
// Conversation handle — for multi-thread UIs
|
|
133
|
-
const thread = channel.conversation('thread-42');
|
|
134
|
-
await thread.prompt('Hello'); // Uses 'thread-42' conversation
|
|
135
|
-
thread.getInteractions(); // Interactions for thread-42 only
|
|
136
|
-
```
|
|
76
|
+
- Object paths: `/space/<collection>/<name>.json` (exactly three segments; no dotfile collection or object names)
|
|
77
|
+
- File paths: `/rool-drive/<path/to/file>`
|
|
137
78
|
|
|
138
|
-
|
|
79
|
+
`rool-machine:/...` URIs are the user-facing/canonical form for resource references in prompt attachments and interaction history. The exported helpers normalize between these forms when you need them.
|
|
139
80
|
|
|
140
81
|
```typescript
|
|
141
|
-
|
|
142
|
-
const thread = channel.conversation('research');
|
|
143
|
-
await thread.setSystemInstruction('Respond in haiku');
|
|
82
|
+
import { machinePath, machineUri, isObjectPath } from '@rool-dev/sdk';
|
|
144
83
|
|
|
145
|
-
|
|
146
|
-
|
|
84
|
+
machinePath('rool-machine:/rool-drive/docs/read%20me.md');
|
|
85
|
+
// '/rool-drive/docs/read me.md'
|
|
147
86
|
|
|
148
|
-
|
|
149
|
-
|
|
87
|
+
machineUri('/space/article/welcome.json');
|
|
88
|
+
// 'rool-machine:/space/article/welcome.json'
|
|
150
89
|
|
|
151
|
-
//
|
|
152
|
-
await thread.rename('Research Thread');
|
|
90
|
+
isObjectPath('/space/article/welcome.json'); // true
|
|
153
91
|
```
|
|
154
92
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
The conversation history is a **tree**, not a flat list. Each interaction has a `parentId` pointing to the interaction it continues from. When you call `prompt()`, the SDK automatically continues from the current active leaf. To branch (edit/reroll), pass a different `parentInteractionId`:
|
|
93
|
+
Object APIs require full object paths. References between objects are ordinary body fields containing object paths:
|
|
158
94
|
|
|
159
95
|
```typescript
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
await thread.prompt('What is my favorite color?'); // Sees "blue"
|
|
165
|
-
|
|
166
|
-
// Branch: go back to the first message and say something different
|
|
167
|
-
const firstLeaf = thread.activeLeafId; // ID of the "blue" interaction
|
|
168
|
-
const tree = thread.getTree();
|
|
169
|
-
const firstInteractionId = tree[firstLeaf!].parentId!; // The root
|
|
170
|
-
|
|
171
|
-
await thread.prompt('My favorite color is red. Say OK.', {
|
|
172
|
-
parentInteractionId: firstInteractionId, // Sibling of "blue"
|
|
173
|
-
});
|
|
174
|
-
await thread.prompt('What is my favorite color?'); // Sees "red", not "blue"
|
|
175
|
-
|
|
176
|
-
// Switch back to the blue branch
|
|
177
|
-
thread.setActiveLeaf(firstLeaf!);
|
|
178
|
-
thread.getInteractions(); // Returns the blue branch (root → leaf)
|
|
96
|
+
{
|
|
97
|
+
path: '/space/body/earth.json',
|
|
98
|
+
body: { name: 'Earth', orbits: '/space/body/sun.json' },
|
|
99
|
+
}
|
|
179
100
|
```
|
|
180
101
|
|
|
181
|
-
|
|
182
|
-
- `getInteractions()` returns the active branch as a flat `Interaction[]` (root → leaf)
|
|
183
|
-
- `getTree()` returns the full `Record<string, Interaction>` for branch navigation UI
|
|
184
|
-
- `activeLeafId` is the tip of the branch the user is currently viewing
|
|
185
|
-
- `setActiveLeaf(id)` switches branches (emits `conversationUpdated` so reactive UIs refresh)
|
|
186
|
-
- `prompt()` with no `parentInteractionId` auto-continues from `activeLeafId`
|
|
187
|
-
- `prompt()` with `parentInteractionId: null` starts a new root-level branch
|
|
102
|
+
## Authentication
|
|
188
103
|
|
|
189
|
-
###
|
|
104
|
+
### Browser
|
|
190
105
|
|
|
191
|
-
|
|
106
|
+
The default auth provider stores tokens in browser storage and redirects to the Rool auth page.
|
|
192
107
|
|
|
193
108
|
```typescript
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
**References** between objects are data fields whose values are object IDs. The system detects these statistically — any string field whose value matches an existing object ID is recognized as a reference.
|
|
109
|
+
async function start() {
|
|
110
|
+
const client = new RoolClient();
|
|
198
111
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
// An array of references
|
|
204
|
-
{ id: 'team-a', type: 'team', name: 'Alpha', members: ['user-1', 'user-2', 'user-3'] }
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
References are just data — no special API is needed to create or remove them. Set a field to an object ID to create a reference; clear it to remove it.
|
|
208
|
-
|
|
209
|
-
### AI Placeholder Pattern
|
|
210
|
-
|
|
211
|
-
Use `{{description}}` in field values to have AI generate content:
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
// Create with AI-generated content
|
|
215
|
-
await channel.createObject({
|
|
216
|
-
data: {
|
|
217
|
-
type: 'article',
|
|
218
|
-
headline: '{{catchy headline about coffee}}',
|
|
219
|
-
body: '{{informative paragraph}}'
|
|
112
|
+
if (!(await client.initialize())) {
|
|
113
|
+
await client.login('My App');
|
|
114
|
+
// Browser auth redirects; stop startup until the callback reloads the app.
|
|
115
|
+
return;
|
|
220
116
|
}
|
|
221
|
-
});
|
|
222
117
|
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
prompt: 'Make the body shorter and more casual'
|
|
226
|
-
});
|
|
118
|
+
// Use the authenticated client here.
|
|
119
|
+
}
|
|
227
120
|
|
|
228
|
-
|
|
229
|
-
await channel.updateObject('abc123', {
|
|
230
|
-
data: { summary: '{{one-sentence summary}}' }
|
|
231
|
-
});
|
|
121
|
+
void start();
|
|
232
122
|
```
|
|
233
123
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
Placeholders are resolved by the AI during the mutation and replaced with concrete values. The `{{...}}` syntax is never stored — it only guides the agent while creating or updating the object.
|
|
237
|
-
|
|
238
|
-
### Checkpoints & Undo/Redo
|
|
124
|
+
### Node.js
|
|
239
125
|
|
|
240
|
-
|
|
126
|
+
Use the Node auth provider for CLIs and scripts. It stores endpoint-scoped credentials under `~/.config/rool/` by default (for example, `credentials-<hash>.json`) and opens a browser for login.
|
|
241
127
|
|
|
242
128
|
```typescript
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
await channel.deleteObjects([objectId]);
|
|
129
|
+
import { RoolClient } from '@rool-dev/sdk';
|
|
130
|
+
import { NodeAuthProvider } from '@rool-dev/sdk/node';
|
|
246
131
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
await channel.undo(); // Restores the deleted object
|
|
250
|
-
}
|
|
132
|
+
const client = new RoolClient({ authProvider: new NodeAuthProvider() });
|
|
133
|
+
let authenticated = await client.initialize();
|
|
251
134
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
135
|
+
if (!authenticated) {
|
|
136
|
+
await client.login('My CLI Tool');
|
|
137
|
+
// Re-run initialize after the non-redirect login to hydrate currentUser,
|
|
138
|
+
// user storage, and client-level event subscriptions.
|
|
139
|
+
authenticated = await client.initialize();
|
|
255
140
|
}
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
Checkpoints are **space-wide**: one shared stack across all channels and users. `undo()` restores the entire space — including any work others did since the checkpoint. Stacks are capped at 25 entries; identical-content checkpoints are deduped; a new checkpoint clears the redo stack.
|
|
259
|
-
|
|
260
|
-
### Hidden Fields
|
|
261
141
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
```typescript
|
|
265
|
-
await channel.createObject({
|
|
266
|
-
data: {
|
|
267
|
-
type: 'article',
|
|
268
|
-
title: 'My Article',
|
|
269
|
-
author: "John Doe",
|
|
270
|
-
_ui: { x: 100, y: 200, collapsed: false }
|
|
271
|
-
}
|
|
272
|
-
});
|
|
142
|
+
if (!authenticated) throw new Error('Login required');
|
|
273
143
|
```
|
|
274
144
|
|
|
275
|
-
###
|
|
276
|
-
|
|
277
|
-
Events fire for both local and remote changes. The `source` field indicates origin:
|
|
278
|
-
|
|
279
|
-
- `local_user` — This client made the change
|
|
280
|
-
- `remote_user` — Another user/client made the change
|
|
281
|
-
- `remote_agent` — AI agent made the change
|
|
282
|
-
- `system` — Resync after error
|
|
145
|
+
### Auth API
|
|
283
146
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
channel.updateObject(objectId, { prompt: 'expand this' });
|
|
295
|
-
```
|
|
147
|
+
| Method | Description |
|
|
148
|
+
| --- | --- |
|
|
149
|
+
| `initialize(): Promise<boolean>` | Call on startup. Initializes auth, refreshes user/storage state, and starts client events when authenticated. |
|
|
150
|
+
| `login(appName, params?): Promise<void>` | Start login flow. |
|
|
151
|
+
| `signup(appName, params?): Promise<void>` | Start signup flow. |
|
|
152
|
+
| `verify(token): Promise<boolean>` | Complete email verification token flow; returns `false` when the active auth provider does not implement verification. |
|
|
153
|
+
| `logout(): void` | Clear auth state and close open spaces. |
|
|
154
|
+
| `isAuthenticated(): Promise<boolean>` | Validate current auth state. |
|
|
155
|
+
| `getAuthUser(): AuthUser` | Return auth identity decoded from the token. |
|
|
156
|
+
| `setPassword(password): Promise<void>` | Set/change password for the current user. |
|
|
296
157
|
|
|
297
|
-
|
|
158
|
+
## Spaces and Channels
|
|
298
159
|
|
|
299
|
-
|
|
160
|
+
Open a space to receive live events and manage collaborators, file storage, and channels. Open a channel to work with objects, schema, metadata, and AI.
|
|
300
161
|
|
|
301
162
|
```typescript
|
|
302
|
-
await
|
|
303
|
-
```
|
|
163
|
+
const space = await client.openSpace('space-id');
|
|
304
164
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
- **Meaningful IDs** — Use domain-specific IDs like `user-123` or `doc-abc` for easier debugging and external references.
|
|
165
|
+
space.on('channelCreated', (channel) => console.log(channel.id));
|
|
166
|
+
space.on('filesChanged', () => console.log('files changed'));
|
|
308
167
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const id = RoolClient.generateId();
|
|
312
|
-
channel.createObject({ data: { id, type: 'note', text: '{{expand this idea}}' } });
|
|
313
|
-
channel.updateObject(parentId, { data: { notes: [...existingNotes, id] } }); // Add reference immediately
|
|
168
|
+
const channel = await space.openChannel('main');
|
|
169
|
+
await channel.prompt('Summarize this space');
|
|
314
170
|
```
|
|
315
171
|
|
|
316
|
-
|
|
317
|
-
- Must contain only alphanumeric characters, hyphens (`-`), and underscores (`_`)
|
|
318
|
-
- Must be unique within the space (throws if ID exists)
|
|
319
|
-
- Cannot be changed after creation (immutable)
|
|
172
|
+
Channel IDs must be 1–32 characters and contain only letters, numbers, `_`, and `-`.
|
|
320
173
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
## Authentication
|
|
174
|
+
## Object Operations
|
|
324
175
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
No configuration needed. Uses localStorage for tokens, redirects to login page.
|
|
176
|
+
Objects are JSON files under `/space`. Create the collection before writing objects in it.
|
|
328
177
|
|
|
329
178
|
```typescript
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
client.login('My App'); // Redirect to the auth page
|
|
335
|
-
}
|
|
336
|
-
```
|
|
179
|
+
await channel.createCollection('article', [
|
|
180
|
+
{ name: 'title', type: { kind: 'string' } },
|
|
181
|
+
{ name: 'status', type: { kind: 'string' } },
|
|
182
|
+
]);
|
|
337
183
|
|
|
338
|
-
|
|
184
|
+
// Create or replace an exact object path
|
|
185
|
+
const { object } = await channel.putObject('/space/article/welcome.json', {
|
|
186
|
+
title: 'Welcome',
|
|
187
|
+
status: 'draft',
|
|
188
|
+
});
|
|
339
189
|
|
|
340
|
-
|
|
190
|
+
// Patch fields; null or undefined deletes a field
|
|
191
|
+
await channel.patchObject(object.path, {
|
|
192
|
+
data: { status: 'published', obsoleteField: null },
|
|
193
|
+
});
|
|
341
194
|
|
|
342
|
-
|
|
343
|
-
|
|
195
|
+
// Read one or many objects
|
|
196
|
+
await channel.getObject('/space/article/welcome.json');
|
|
197
|
+
await channel.getObjects([
|
|
198
|
+
'/space/article/welcome.json',
|
|
199
|
+
'/space/article/intro.json',
|
|
200
|
+
]);
|
|
344
201
|
|
|
345
|
-
|
|
346
|
-
|
|
202
|
+
// Rename or move an object
|
|
203
|
+
await channel.moveObject(
|
|
204
|
+
'/space/article/welcome.json',
|
|
205
|
+
'/space/article/hello-world.json'
|
|
206
|
+
);
|
|
347
207
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
208
|
+
// Delete objects
|
|
209
|
+
await channel.deleteObjects(['/space/article/hello-world.json']);
|
|
351
210
|
```
|
|
352
211
|
|
|
353
|
-
### Auth Methods
|
|
354
|
-
|
|
355
212
|
| Method | Description |
|
|
356
|
-
|
|
357
|
-
| `
|
|
358
|
-
| `
|
|
359
|
-
| `
|
|
360
|
-
| `
|
|
361
|
-
| `
|
|
362
|
-
| `
|
|
363
|
-
| `
|
|
364
|
-
| `setPassword(password): Promise<void>` | Set or change the current user's password. Requires an authenticated session. Password must be at least 8 characters and contain both letters and either digits or symbols. Throws with a human-readable message on validation or server failure. |
|
|
213
|
+
| --- | --- |
|
|
214
|
+
| `getObject(path): Promise<RoolObject | undefined>` | Fetch one object by object path. |
|
|
215
|
+
| `getObjects(paths): Promise<GetObjectsResult>` | Fetch objects in bulk; returns `objects` and `missing`. |
|
|
216
|
+
| `stat(path): RoolObjectStat | undefined` | Cached audit info for an object. |
|
|
217
|
+
| `putObject(path, body): Promise<{ object, message }>` | Create or replace an object at an exact path. |
|
|
218
|
+
| `patchObject(path, { data }): Promise<{ object, message }>` | Patch an object's body; `null`/`undefined` deletes fields. |
|
|
219
|
+
| `moveObject(from, to, options?): Promise<{ object, message }>` | Rename or relocate an object; `options.body` can replace the body after moving. |
|
|
220
|
+
| `deleteObjects(paths): Promise<void>` | Delete object files. |
|
|
365
221
|
|
|
366
222
|
## AI Agent
|
|
367
223
|
|
|
368
|
-
|
|
224
|
+
`prompt()` invokes the AI agent. The agent can inspect space context and, unless `readOnly` or a read-only effort is used, create/modify/move/delete objects.
|
|
369
225
|
|
|
370
226
|
```typescript
|
|
371
227
|
const { message, objects } = await channel.prompt(
|
|
372
|
-
|
|
228
|
+
'Create a topic node for the solar system, then child nodes for each planet.'
|
|
373
229
|
);
|
|
374
|
-
console.log(`AI: ${message}`);
|
|
375
|
-
console.log(`Modified ${objects.length} objects:`, objects);
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
Use `checkpoint()` before prompting to make operations undoable.
|
|
379
230
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
```typescript
|
|
383
|
-
prompt(text: string, options?: PromptOptions): Promise<{ message: string; objects: RoolObject[] }>
|
|
231
|
+
console.log(message);
|
|
232
|
+
console.log(objects.map((object) => object.path));
|
|
384
233
|
```
|
|
385
234
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
### Options
|
|
235
|
+
### Prompt Options
|
|
389
236
|
|
|
390
237
|
| Option | Description |
|
|
391
|
-
|
|
392
|
-
| `
|
|
393
|
-
| `
|
|
394
|
-
| `
|
|
395
|
-
| `
|
|
396
|
-
| `
|
|
397
|
-
| `
|
|
398
|
-
| `
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
| `REASONING` | Extended reasoning for complex tasks. |
|
|
407
|
-
| `RESEARCH` | Most thorough mode with deep analysis. Slowest and most credit-intensive. |
|
|
408
|
-
|
|
409
|
-
### Examples
|
|
410
|
-
|
|
411
|
-
```typescript
|
|
412
|
-
// Reorganize existing objects
|
|
413
|
-
const { objects } = await channel.prompt(
|
|
414
|
-
"Group these notes by topic and create a parent node for each group."
|
|
415
|
-
);
|
|
416
|
-
|
|
417
|
-
// Work with specific objects
|
|
418
|
-
const result = await channel.prompt(
|
|
419
|
-
"Summarize these articles",
|
|
420
|
-
{ objectIds: ['article-1', 'article-2'] }
|
|
421
|
-
);
|
|
422
|
-
|
|
423
|
-
// Quick question without mutations (fast model + read-only)
|
|
424
|
-
const { message } = await channel.prompt(
|
|
425
|
-
"What topics are covered?",
|
|
426
|
-
{ effort: 'QUICK', readOnly: true }
|
|
427
|
-
);
|
|
428
|
-
|
|
429
|
-
// Complex analysis with extended reasoning
|
|
430
|
-
await channel.prompt(
|
|
431
|
-
"Analyze relationships and reorganize",
|
|
432
|
-
{ effort: 'REASONING' }
|
|
433
|
-
);
|
|
434
|
-
|
|
435
|
-
// Attach files for the AI to see (File from <input>, Blob, or base64)
|
|
436
|
-
const file = fileInput.files[0]; // from <input type="file">
|
|
437
|
-
await channel.prompt(
|
|
438
|
-
"Describe what's in this photo and create an object for it",
|
|
439
|
-
{ attachments: [file] }
|
|
440
|
-
);
|
|
441
|
-
```
|
|
238
|
+
| --- | --- |
|
|
239
|
+
| `responseSchema` | Request structured JSON text matching a JSON-schema-like shape. |
|
|
240
|
+
| `effort` | `'QUICK'` (fast/read-only), `'STANDARD'` (default), `'REASONING'`, or `'RESEARCH'`. |
|
|
241
|
+
| `ephemeral` | Do not record the prompt in interaction history. |
|
|
242
|
+
| `readOnly` | Disable mutation tools. |
|
|
243
|
+
| `parentInteractionId` | Conversation-tree parent. Omit to continue from the active leaf; pass `null` for a new root branch. |
|
|
244
|
+
| `attachments` | Existing object/file paths or `rool-machine:/...` URIs, plus local files (`File`, `Blob`, or `{ data, contentType, filename? }`). |
|
|
245
|
+
| `signal` | AbortSignal used to request that the server stop an in-flight prompt. |
|
|
246
|
+
| `eventName` | Optional telemetry event name. Defaults to `'prompt_user'`. |
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// Read-only quick question
|
|
250
|
+
await channel.prompt('What topics are covered?', {
|
|
251
|
+
effort: 'QUICK', // fast/read-only
|
|
252
|
+
});
|
|
442
253
|
|
|
443
|
-
|
|
254
|
+
// Focus on existing objects and files
|
|
255
|
+
await channel.prompt('Compare these resources', {
|
|
256
|
+
attachments: [
|
|
257
|
+
'/space/article/intro.json',
|
|
258
|
+
'rool-machine:/rool-drive/docs/report.pdf',
|
|
259
|
+
],
|
|
260
|
+
});
|
|
444
261
|
|
|
445
|
-
|
|
262
|
+
// Upload a local file as an attachment
|
|
263
|
+
await channel.prompt('Describe this image', {
|
|
264
|
+
attachments: [fileInput.files![0]],
|
|
265
|
+
});
|
|
446
266
|
|
|
447
|
-
|
|
448
|
-
const { message } = await channel.prompt(
|
|
449
|
-
objectIds: ['item-1', 'item-2', 'item-3'],
|
|
267
|
+
// Structured response
|
|
268
|
+
const { message } = await channel.prompt('Categorize these items', {
|
|
450
269
|
responseSchema: {
|
|
451
270
|
type: 'object',
|
|
452
271
|
properties: {
|
|
453
|
-
categories: {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
summary: { type: 'string' }
|
|
458
|
-
}
|
|
459
|
-
}
|
|
272
|
+
categories: { type: 'array', items: { type: 'string' } },
|
|
273
|
+
summary: { type: 'string' },
|
|
274
|
+
},
|
|
275
|
+
},
|
|
460
276
|
});
|
|
461
|
-
|
|
462
277
|
const result = JSON.parse(message);
|
|
463
|
-
console.log(result.categories, result.summary);
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
### Context Flow
|
|
467
278
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
279
|
+
// Stop a long prompt with a signal (when the caller holds the controller)
|
|
280
|
+
const ac = new AbortController();
|
|
281
|
+
const promptPromise = channel.prompt('Do a deep analysis', {
|
|
282
|
+
effort: 'RESEARCH',
|
|
283
|
+
signal: ac.signal,
|
|
284
|
+
});
|
|
285
|
+
ac.abort(); // asks the server to stop the in-flight interaction
|
|
286
|
+
await promptPromise;
|
|
287
|
+
```
|
|
476
288
|
|
|
477
|
-
###
|
|
289
|
+
### Stopping interactions
|
|
478
290
|
|
|
479
|
-
|
|
291
|
+
Use `signal` when the same call site cancels the prompt. When the Stop button
|
|
292
|
+
lives elsewhere — a different component, after a reload, or a prompt another
|
|
293
|
+
client started — stop by ID or stop the conversation's active interaction
|
|
294
|
+
instead. Both are best-effort: the server halts the agent loop and closes the
|
|
295
|
+
stream, but an LLM turn already in flight keeps generating server-side and is
|
|
296
|
+
billed.
|
|
480
297
|
|
|
481
298
|
```typescript
|
|
482
|
-
//
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
throw new Error('User not found');
|
|
486
|
-
}
|
|
299
|
+
// Stop whatever is in flight on this channel's (default) conversation.
|
|
300
|
+
// No-op returning false when nothing is running.
|
|
301
|
+
await channel.stop();
|
|
487
302
|
|
|
488
|
-
//
|
|
489
|
-
|
|
490
|
-
await
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
### Roles
|
|
494
|
-
|
|
495
|
-
| Role | Capabilities |
|
|
496
|
-
|------|--------------|
|
|
497
|
-
| `owner` | Full control, can delete space and manage all users |
|
|
498
|
-
| `admin` | All editor capabilities, plus can manage users (except other admins/owners) |
|
|
499
|
-
| `editor` | Can create, modify, and delete objects |
|
|
500
|
-
| `viewer` | Read-only access (can query with `prompt` and `findObjects`) |
|
|
501
|
-
|
|
502
|
-
### Space Collaboration Methods
|
|
303
|
+
// Stop a specific interaction by ID (e.g. from channel.activeLeafId or
|
|
304
|
+
// the interactions list). Returns whether the server stopped it.
|
|
305
|
+
await channel.stopInteraction(channel.activeLeafId!);
|
|
503
306
|
|
|
504
|
-
|
|
307
|
+
// Conversation handles stop their own in-flight interaction.
|
|
308
|
+
const thread = channel.conversation('thread-42');
|
|
309
|
+
await thread.stop();
|
|
310
|
+
```
|
|
505
311
|
|
|
506
312
|
| Method | Description |
|
|
507
|
-
|
|
508
|
-
| `
|
|
509
|
-
| `
|
|
510
|
-
| `
|
|
511
|
-
| `setLinkAccess(linkAccess): Promise<void>` | Set URL sharing level (requires owner or admin role) |
|
|
313
|
+
| --- | --- |
|
|
314
|
+
| `stop(): Promise<boolean>` | Stop the in-flight interaction on the default conversation; `false` if none. |
|
|
315
|
+
| `stopInteraction(id): Promise<boolean>` | Ask the server to stop a specific interaction by ID. |
|
|
316
|
+
| `conversation.stop(): Promise<boolean>` | Stop a specific conversation's in-flight interaction. |
|
|
512
317
|
|
|
513
|
-
|
|
318
|
+
## Conversations
|
|
514
319
|
|
|
515
|
-
|
|
320
|
+
Every channel has a default conversation. Use `channel.conversation(id)` for independent histories (for example, multiple chat threads). Conversations are represented as trees: interactions point at a `parentId`, and the SDK tracks an active leaf for each conversation.
|
|
516
321
|
|
|
517
322
|
```typescript
|
|
518
|
-
|
|
323
|
+
await channel.prompt('Hello'); // default conversation
|
|
519
324
|
|
|
520
|
-
|
|
521
|
-
await
|
|
522
|
-
|
|
523
|
-
// Allow anyone with the URL to edit
|
|
524
|
-
await space.setLinkAccess('editor');
|
|
325
|
+
const thread = channel.conversation('thread-42');
|
|
326
|
+
await thread.prompt('Hello from another thread');
|
|
327
|
+
await thread.setSystemInstruction('Answer in haiku');
|
|
525
328
|
|
|
526
|
-
//
|
|
527
|
-
|
|
329
|
+
const branch = thread.getInteractions(); // active branch, root → leaf
|
|
330
|
+
const tree = thread.getTree(); // full interaction tree
|
|
528
331
|
|
|
529
|
-
|
|
530
|
-
|
|
332
|
+
if (thread.activeLeafId) {
|
|
333
|
+
thread.setActiveLeaf(thread.activeLeafId);
|
|
334
|
+
}
|
|
531
335
|
```
|
|
532
336
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
337
|
+
| Method/property | Description |
|
|
338
|
+
| --- | --- |
|
|
339
|
+
| `channel.conversation(id): ConversationHandle` | Get a conversation-scoped handle. |
|
|
340
|
+
| `getInteractions(): Interaction[]` | Active branch as a flat list. |
|
|
341
|
+
| `getTree(): Record<string, Interaction>` | Full interaction tree. |
|
|
342
|
+
| `activeLeafId` | Current branch tip. |
|
|
343
|
+
| `setActiveLeaf(id): void` | Switch branches. |
|
|
344
|
+
| `getSystemInstruction()` / `setSystemInstruction(value)` | Manage conversation system instruction. Pass `null` to clear. |
|
|
345
|
+
| `getConversations(): ConversationInfo[]` | List channel conversations (on `RoolChannel`). |
|
|
346
|
+
| `deleteConversation(id): Promise<void>` | Delete a non-active conversation. |
|
|
347
|
+
| `renameConversation(name): Promise<void>` | Rename the current/default conversation (on `RoolChannel`). |
|
|
348
|
+
| `conversation.rename(name): Promise<void>` | Rename a specific conversation handle. |
|
|
536
349
|
|
|
537
|
-
|
|
538
|
-
|--------|-------------|
|
|
539
|
-
| `currentUser: CurrentUser \| null` | Cached user profile from `initialize()`. Use for sync access to user info (id, email, name, etc.). Returns `null` before `initialize()` is called. |
|
|
540
|
-
| `getCurrentUser(): Promise<CurrentUser>` | Fetch fresh user profile from server (id, email, name, photoUrl, slug, plan, creditsBalance, totalCreditsUsed, createdAt, lastActivity, processedAt, storage) |
|
|
541
|
-
| `updateCurrentUser(input): Promise<CurrentUser>` | Update the current user's profile (`name`, `slug`). Returns the updated user. Slug must be 3–32 chars, start with a letter, and contain only lowercase alphanumeric characters, hyphens, and underscores. |
|
|
542
|
-
| `searchUser(email): Promise<UserResult \| null>` | Find user by exact email address (no partial matching) |
|
|
350
|
+
`ConversationHandle` also supports conversation-scoped `putObject`, `patchObject`, `moveObject`, `deleteObjects`, `prompt`, `stop`, collection-schema methods, and `setMetadata`.
|
|
543
351
|
|
|
544
|
-
|
|
352
|
+
## Schema and Metadata
|
|
545
353
|
|
|
546
|
-
|
|
354
|
+
Collections define the schema visible to the AI agent. Hidden body fields whose names start with `_` are useful for app/UI state that should not be considered by AI.
|
|
547
355
|
|
|
548
356
|
```typescript
|
|
549
|
-
channel.
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
357
|
+
await channel.createCollection('article', {
|
|
358
|
+
schemaOrgType: 'Article',
|
|
359
|
+
fields: [
|
|
360
|
+
{ name: 'title', type: { kind: 'string' } },
|
|
361
|
+
{ name: 'status', type: { kind: 'enum', values: ['draft', 'published'] } },
|
|
362
|
+
{ name: 'tags', type: { kind: 'array', inner: { kind: 'string' } } },
|
|
363
|
+
{ name: 'author', type: { kind: 'ref' } },
|
|
364
|
+
],
|
|
554
365
|
});
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
See [Real-time Sync](#real-time-sync) for more on event sources.
|
|
558
366
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
### Logging
|
|
562
|
-
|
|
563
|
-
By default the SDK logs errors to the console. Pass a `logger` to see more or customize output:
|
|
564
|
-
|
|
565
|
-
```typescript
|
|
566
|
-
// Default — errors only
|
|
567
|
-
const client = new RoolClient();
|
|
367
|
+
const schema = channel.getSchema();
|
|
568
368
|
|
|
569
|
-
|
|
570
|
-
|
|
369
|
+
await channel.alterCollection('article', [
|
|
370
|
+
{ name: 'title', type: { kind: 'string' } },
|
|
371
|
+
{ name: 'status', type: { kind: 'string' } },
|
|
372
|
+
]);
|
|
571
373
|
|
|
572
|
-
|
|
573
|
-
const
|
|
574
|
-
logger: myLogger // any object with { debug, info, warn, error }
|
|
575
|
-
});
|
|
374
|
+
channel.setMetadata('viewport', { x: 0, y: 0, zoom: 1 });
|
|
375
|
+
const viewport = channel.getMetadata('viewport');
|
|
576
376
|
```
|
|
577
377
|
|
|
578
|
-
### Space & Channel Lifecycle
|
|
579
|
-
|
|
580
378
|
| Method | Description |
|
|
581
|
-
|
|
582
|
-
| `
|
|
583
|
-
| `
|
|
584
|
-
| `
|
|
585
|
-
| `
|
|
586
|
-
| `
|
|
587
|
-
|
|
588
|
-
|
|
379
|
+
| --- | --- |
|
|
380
|
+
| `getSchema(): SpaceSchema` | Get collection definitions. |
|
|
381
|
+
| `createCollection(name, fieldsOrDef, options?): Promise<CollectionDef>` | Create a collection. |
|
|
382
|
+
| `alterCollection(name, fieldsOrDef, options?): Promise<CollectionDef>` | Replace a collection definition. |
|
|
383
|
+
| `dropCollection(name): Promise<void>` | Remove a collection and its object directory. |
|
|
384
|
+
| `setMetadata(key, value): void` | Set space metadata (fire-and-forget sync). |
|
|
385
|
+
| `getMetadata(key): unknown` | Read metadata from local cache. |
|
|
386
|
+
| `getAllMetadata(): Record<string, unknown>` | Read all metadata from local cache. |
|
|
589
387
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
| Method | Description |
|
|
593
|
-
|--------|-------------|
|
|
594
|
-
| `space.channels: ChannelInfo[]` | Live channel list (auto-updates via SSE) |
|
|
595
|
-
| `space.getChannels(): ChannelInfo[]` | List channels (deprecated — use `space.channels` instead) |
|
|
596
|
-
| `space.renameChannel(channelId, name): Promise<void>` | Rename a channel |
|
|
597
|
-
| `space.deleteChannel(channelId): Promise<void>` | Delete a channel and its interaction history |
|
|
598
|
-
| `channel.rename(name): Promise<void>` | Rename the current open channel |
|
|
388
|
+
Field kinds: `string`, `number`, `boolean`, `ref`, `enum`, `literal`, `array`, and `maybe`.
|
|
599
389
|
|
|
600
|
-
|
|
390
|
+
## Undo/Redo
|
|
601
391
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
**Features:**
|
|
605
|
-
- Fresh data fetched from server on `initialize()` — cache is authoritative after init
|
|
606
|
-
- Sync reads from local cache (fast, no network round-trip)
|
|
607
|
-
- Automatic sync to server and across tabs/devices via SSE
|
|
608
|
-
- `userStorageChanged` event fires on all changes (local or remote)
|
|
609
|
-
- Total storage limited to 10MB per user
|
|
610
|
-
|
|
611
|
-
| Method | Description |
|
|
612
|
-
|--------|-------------|
|
|
613
|
-
| `getUserStorage<T>(key): T \| undefined` | Get a value (sync, from cache) |
|
|
614
|
-
| `setUserStorage(key, value): void` | Set a value (updates cache, syncs to server) |
|
|
615
|
-
| `getAllUserStorage(): Record<string, unknown>` | Get all stored data (sync, from cache) |
|
|
392
|
+
Undo/redo uses checkpoints for the current channel ID. A checkpoint captures space state; call `checkpoint()` before a user action you want to make undoable.
|
|
616
393
|
|
|
617
394
|
```typescript
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
// Sync reads are now trustworthy
|
|
622
|
-
const theme = client.getUserStorage<string>('theme');
|
|
623
|
-
applyTheme(theme ?? 'light');
|
|
624
|
-
|
|
625
|
-
// Write - updates immediately, syncs to server in background
|
|
626
|
-
client.setUserStorage('theme', 'dark');
|
|
627
|
-
client.setUserStorage('sidebar', { collapsed: true, width: 280 });
|
|
628
|
-
|
|
629
|
-
// Delete a key
|
|
630
|
-
client.setUserStorage('theme', null);
|
|
395
|
+
await channel.checkpoint('Delete article');
|
|
396
|
+
await channel.deleteObjects(['/space/article/welcome.json']);
|
|
631
397
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
if (key === 'theme') applyTheme(value as string);
|
|
636
|
-
});
|
|
398
|
+
if (await channel.canUndo()) {
|
|
399
|
+
await channel.undo();
|
|
400
|
+
}
|
|
637
401
|
```
|
|
638
402
|
|
|
639
|
-
### Extensions
|
|
640
|
-
|
|
641
|
-
Manage and publish extensions. See [`@rool-dev/extension`](/extension/) for building extensions.
|
|
642
|
-
|
|
643
|
-
There are two distinct domains: your **personal library** (extensions you've created or installed) and the **published extensions** (extensions discoverable by all users). Each has its own return type.
|
|
644
|
-
|
|
645
|
-
#### Your Library (`ExtensionInfo`)
|
|
646
|
-
|
|
647
|
-
Manage extensions you own. Each `ExtensionInfo` includes `published` (whether it's listed in the marketplace) and `marketplaceExtensionId` (non-null if you installed it from someone else's listing, null if you authored it).
|
|
648
|
-
|
|
649
|
-
| Method | Description |
|
|
650
|
-
|--------|-------------|
|
|
651
|
-
| `uploadExtension(extensionId, options): Promise<ExtensionInfo>` | Upload or update an extension (`options.bundle`: zip with `index.html` and `manifest.json`) |
|
|
652
|
-
| `listExtensions(): Promise<ExtensionInfo[]>` | List your extensions |
|
|
653
|
-
| `getExtensionInfo(extensionId): Promise<ExtensionInfo \| null>` | Get info for a specific extension |
|
|
654
|
-
| `deleteExtension(extensionId): Promise<void>` | Delete an extension permanently (removes files and DB row) |
|
|
655
|
-
|
|
656
|
-
#### Marketplace (`PublishedExtensionInfo`)
|
|
657
|
-
|
|
658
|
-
Discover and install extensions published by other users.
|
|
659
|
-
|
|
660
|
-
| Method | Description |
|
|
661
|
-
|--------|-------------|
|
|
662
|
-
| `findExtensions(options?): Promise<PublishedExtensionInfo[]>` | Search the marketplace. Options: `query` (semantic search string), `limit` (default 20, max 100). Omit `query` to browse all. |
|
|
663
|
-
| `installExtension(spaceId, extensionId, channelId): Promise<string>` | Install an extension into a space. If you own it, wires it directly. If it's a marketplace extension, copies and builds a new extension in your library. Returns the channel ID. |
|
|
664
|
-
| `publishToPublic(extensionId): Promise<void>` | Publish one of your extensions to the marketplace |
|
|
665
|
-
| `unpublishFromPublic(extensionId): Promise<void>` | Remove from the marketplace (keeps the extension in your library) |
|
|
666
|
-
|
|
667
|
-
### Utilities
|
|
668
|
-
|
|
669
403
|
| Method | Description |
|
|
670
|
-
|
|
671
|
-
| `
|
|
672
|
-
| `
|
|
404
|
+
| --- | --- |
|
|
405
|
+
| `checkpoint(label?): Promise<string>` | Save current space state. |
|
|
406
|
+
| `canUndo(): Promise<boolean>` | Check whether undo is available. |
|
|
407
|
+
| `canRedo(): Promise<boolean>` | Check whether redo is available. |
|
|
408
|
+
| `undo(): Promise<boolean>` | Restore the latest checkpoint. |
|
|
409
|
+
| `redo(): Promise<boolean>` | Reapply undone work. |
|
|
410
|
+
| `clearHistory(): Promise<void>` | Clear checkpoint history. |
|
|
673
411
|
|
|
674
|
-
|
|
412
|
+
Undo/redo availability and history are scoped to the channel handle (`channel.channelId`).
|
|
675
413
|
|
|
676
|
-
|
|
677
|
-
client.on('authStateChanged', (authenticated: boolean) => void)
|
|
678
|
-
client.on('spaceAdded', (space: RoolSpaceInfo) => void) // Space created or access granted
|
|
679
|
-
client.on('spaceRemoved', (spaceId: string) => void) // Space deleted or access revoked
|
|
680
|
-
client.on('spaceRenamed', (spaceId: string, newName: string) => void)
|
|
681
|
-
client.on('channelCreated', (spaceId: string, channel: ChannelInfo) => void)
|
|
682
|
-
client.on('channelUpdated', (spaceId: string, channel: ChannelInfo) => void)
|
|
683
|
-
client.on('channelDeleted', (spaceId: string, channelId: string) => void)
|
|
684
|
-
client.on('userStorageChanged', ({ key, value, source }: UserStorageChangedEvent) => void)
|
|
685
|
-
client.on('connectionStateChanged', (state: 'connected' | 'disconnected' | 'reconnecting') => void)
|
|
686
|
-
client.on('error', (error: Error, context?: string) => void)
|
|
687
|
-
```
|
|
414
|
+
## File Storage and WebDAV
|
|
688
415
|
|
|
689
|
-
|
|
416
|
+
Every space has authenticated WebDAV storage. WebDAV methods take SDK machine paths such as `/space/...`, `/rool-drive/...`, or `/` for the root collection.
|
|
690
417
|
|
|
691
|
-
**Space list management pattern:**
|
|
692
418
|
```typescript
|
|
693
|
-
const
|
|
419
|
+
const webdav = space.webdav;
|
|
694
420
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
if (space) spaces.set(id, { ...space, name });
|
|
421
|
+
await webdav.mkcol('/rool-drive/docs');
|
|
422
|
+
await webdav.put('/rool-drive/docs/readme.md', '# Hello', {
|
|
423
|
+
contentType: 'text/markdown',
|
|
424
|
+
ifNoneMatch: '*',
|
|
700
425
|
});
|
|
701
|
-
```
|
|
702
|
-
|
|
703
|
-
## RoolSpace API
|
|
704
|
-
|
|
705
|
-
A space handle with a live SSE subscription. Extends `EventEmitter`. Manages user access, link sharing, channels, and export. The `channels` property auto-updates via SSE, and channel lifecycle events fire in real-time.
|
|
706
426
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
427
|
+
const listing = await webdav.propfind('/rool-drive/docs', {
|
|
428
|
+
depth: '1',
|
|
429
|
+
props: ['displayname', 'getcontentlength', 'getcontenttype', 'getetag'],
|
|
430
|
+
});
|
|
710
431
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
| `id: string` | Space ID |
|
|
714
|
-
| `name: string` | Space name |
|
|
715
|
-
| `role: RoolUserRole` | User's role |
|
|
716
|
-
| `linkAccess: LinkAccess` | URL sharing level |
|
|
717
|
-
| `memberCount: number` | Number of users with access to the space |
|
|
718
|
-
| `channels: ChannelInfo[]` | Live channel list (auto-updates via SSE) |
|
|
432
|
+
const response = await webdav.get('/rool-drive/docs/readme.md');
|
|
433
|
+
console.log(await response.text());
|
|
719
434
|
|
|
720
|
-
|
|
435
|
+
const file = await space.fetchPath('/rool-drive/docs/readme.md');
|
|
436
|
+
console.log(file.headers.get('Content-Type'));
|
|
721
437
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
| `openChannel(channelId): Promise<RoolChannel>` | Open a channel on this space |
|
|
725
|
-
| `close(): void` | Stop SSE subscription and close all open channels |
|
|
726
|
-
| `rename(newName): Promise<void>` | Rename this space |
|
|
727
|
-
| `delete(): Promise<void>` | Permanently delete this space |
|
|
728
|
-
| `listUsers(): Promise<SpaceMember[]>` | List users with access |
|
|
729
|
-
| `addUser(userId, role): Promise<void>` | Add user to space |
|
|
730
|
-
| `removeUser(userId): Promise<void>` | Remove user from space |
|
|
731
|
-
| `setLinkAccess(linkAccess): Promise<void>` | Set URL sharing level |
|
|
732
|
-
| `getChannels(): ChannelInfo[]` | List channels (deprecated — use `channels` property instead) |
|
|
733
|
-
| `renameChannel(channelId, name): Promise<void>` | Rename a channel |
|
|
734
|
-
| `deleteChannel(channelId): Promise<void>` | Delete a channel |
|
|
735
|
-
| `exportArchive(): Promise<Blob>` | Export space as zip archive |
|
|
736
|
-
| `refresh(): Promise<void>` | Refresh space data from server |
|
|
737
|
-
|
|
738
|
-
### Space Events
|
|
739
|
-
|
|
740
|
-
```typescript
|
|
741
|
-
space.on('channelCreated', (channel: ChannelInfo) => void) // New channel added
|
|
742
|
-
space.on('channelUpdated', (channel: ChannelInfo) => void) // Channel metadata changed (name, extension, manifest)
|
|
743
|
-
space.on('channelDeleted', (channelId: string) => void) // Channel removed
|
|
744
|
-
space.on('connectionStateChanged', (state: 'connected' | 'disconnected' | 'reconnecting') => void)
|
|
438
|
+
const usage = await space.getStorageUsage();
|
|
439
|
+
console.log(usage.usedBytes, usage.availableBytes, usage.limitBytes);
|
|
745
440
|
```
|
|
746
441
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
A channel is a named context within a space. All object operations, AI prompts, and real-time sync go through a channel. The `channelId` is fixed at open time — to use a different channel, open a new one.
|
|
750
|
-
|
|
751
|
-
### Properties
|
|
752
|
-
|
|
753
|
-
| Property | Description |
|
|
754
|
-
|----------|-------------|
|
|
755
|
-
| `id: string` | Space ID |
|
|
756
|
-
| `name: string` | Space name |
|
|
757
|
-
| `role: RoolUserRole` | User's role (`'owner' \| 'admin' \| 'editor' \| 'viewer'`) |
|
|
758
|
-
| `linkAccess: LinkAccess` | URL sharing level (`'none' \| 'viewer' \| 'editor'`) |
|
|
759
|
-
| `userId: string` | Current user's ID |
|
|
760
|
-
| `channelId: string` | Channel ID (read-only, fixed at open time) |
|
|
761
|
-
| `isReadOnly: boolean` | True if viewer role |
|
|
762
|
-
| `extensionUrl: string \| null` | URL of the installed extension, or null if this is a plain channel |
|
|
763
|
-
| `extensionId: string \| null` | ID of the installed extension, or null if this is a plain channel |
|
|
764
|
-
| `manifest: ExtensionManifest \| null` | Extension manifest snapshot (name, icon, collections, etc.), or null |
|
|
765
|
-
|
|
766
|
-
### Lifecycle
|
|
767
|
-
|
|
768
|
-
| Method | Description |
|
|
769
|
-
|--------|-------------|
|
|
770
|
-
| `close(): void` | Clean up resources and stop receiving updates |
|
|
771
|
-
| `rename(name): Promise<void>` | Rename this channel |
|
|
772
|
-
| `conversation(conversationId): ConversationHandle` | Get a handle scoped to a specific conversation (see [Conversations](#conversations)) |
|
|
773
|
-
|
|
774
|
-
### Object Operations
|
|
775
|
-
|
|
776
|
-
Objects are plain key/value records. `id` and `type` are reserved; everything else is application-defined. References between objects are data fields whose values are object IDs. Every object must include a `type` field whose value names a collection in the schema (see [Collection Schema](#collection-schema)) — that binds the object to that collection. Before introducing a new kind of object, create the matching collection.
|
|
777
|
-
|
|
778
|
-
| Method | Description |
|
|
779
|
-
|--------|-------------|
|
|
780
|
-
| `getObject(objectId): Promise<RoolObject \| undefined>` | Get object data, or undefined if not found. |
|
|
781
|
-
| `stat(objectId): RoolObjectStat \| undefined` | Get object stat (audit info: modifiedAt, modifiedBy, modifiedByName, and the channel/conversation/interaction where the last write happened), or undefined if not found. Sync read from local cache. |
|
|
782
|
-
| `findObjects(options): Promise<{ objects, message }>` | Find objects using structured filters and natural language. Results sorted by modifiedAt (desc by default). |
|
|
783
|
-
| `getObjectIds(options?): string[]` | Get all object IDs. Sorted by modifiedAt (desc by default). Options: `{ limit?, order? }`. |
|
|
784
|
-
| `createObject(options): Promise<{ object, message }>` | Create a new object. Returns the object (with AI-filled content) and message. |
|
|
785
|
-
| `updateObject(objectId, options): Promise<{ object, message }>` | Update an existing object. Returns the updated object and message. |
|
|
786
|
-
| `deleteObjects(objectIds): Promise<void>` | Delete objects. Other objects referencing deleted objects retain stale ref values. |
|
|
787
|
-
|
|
788
|
-
#### createObject Options
|
|
789
|
-
|
|
790
|
-
| Option | Description |
|
|
791
|
-
|--------|-------------|
|
|
792
|
-
| `data` | Object data fields (required). Must include `type` naming an existing collection. Include `id` to use a custom ID. Use `{{placeholder}}` for AI-generated content. Fields prefixed with `_` are hidden from AI. |
|
|
793
|
-
| `ephemeral` | If true, the operation won't be recorded in interaction history. Useful for transient operations. |
|
|
794
|
-
|
|
795
|
-
#### updateObject Options
|
|
442
|
+
### Real-time file sync
|
|
796
443
|
|
|
797
|
-
|
|
798
|
-
|--------|-------------|
|
|
799
|
-
| `data` | Fields to add or update. Pass `null`/`undefined` to delete a field. Use `{{placeholder}}` for AI-generated content. Setting a new `type` retypes the object — the merged result must conform to the new collection. Fields prefixed with `_` are hidden from AI. |
|
|
800
|
-
| `prompt` | Natural language instruction for AI to modify content. |
|
|
801
|
-
| `ephemeral` | If true, the operation won't be recorded in interaction history. Useful for transient operations. |
|
|
802
|
-
|
|
803
|
-
#### findObjects Options
|
|
804
|
-
|
|
805
|
-
Find objects using structured filters and/or natural language.
|
|
806
|
-
|
|
807
|
-
- **`where` only** — exact-match filtering, no AI, no credits.
|
|
808
|
-
- **`collection` only** — filter by collection name (matches objects whose `type` field equals the name), no AI, no credits.
|
|
809
|
-
- **`prompt` only** — AI-powered semantic query over all objects.
|
|
810
|
-
- **`where` + `prompt`** — `where` (and `objectIds`) narrow the data set first, then the AI queries within the constrained set.
|
|
811
|
-
|
|
812
|
-
| Option | Description |
|
|
813
|
-
|--------|-------------|
|
|
814
|
-
| `where` | Exact-match field filter (e.g. `{ status: 'published' }`). Values must match literally — no operators or `{{placeholders}}`. When combined with `prompt`, constrains which objects the AI can see. |
|
|
815
|
-
| `collection` | Filter by collection name. Returns objects whose `type` field equals the given name. |
|
|
816
|
-
| `prompt` | Natural language query. Triggers AI evaluation (uses credits). |
|
|
817
|
-
| `limit` | Maximum number of results. |
|
|
818
|
-
| `objectIds` | Scope to specific object IDs. Constrains the candidate set in both structured and AI queries. |
|
|
819
|
-
| `order` | Sort order by modifiedAt: `'asc'` or `'desc'` (default: `'desc'`). |
|
|
820
|
-
| `ephemeral` | If true, the query won't be recorded in interaction history. Useful for responsive search. |
|
|
821
|
-
|
|
822
|
-
**Examples:**
|
|
444
|
+
Object and file changes are announced at the space level. Use WebDAV `syncCollection()` to reconcile changes.
|
|
823
445
|
|
|
824
446
|
```typescript
|
|
825
|
-
|
|
826
|
-
const { objects } = await channel.findObjects({
|
|
827
|
-
collection: 'article'
|
|
828
|
-
});
|
|
447
|
+
let token: string | null = null;
|
|
829
448
|
|
|
830
|
-
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
});
|
|
449
|
+
async function syncFiles() {
|
|
450
|
+
const result = await space.webdav.syncCollection('/', {
|
|
451
|
+
token,
|
|
452
|
+
level: 'infinite',
|
|
453
|
+
props: ['displayname', 'getetag', 'getlastmodified', 'resourcetype'],
|
|
454
|
+
});
|
|
455
|
+
token = result.token;
|
|
456
|
+
updateFileTree(result.responses);
|
|
457
|
+
}
|
|
840
458
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
459
|
+
space.on('filesChanged', syncFiles);
|
|
460
|
+
space.on('filesReset', () => {
|
|
461
|
+
token = null;
|
|
462
|
+
void syncFiles();
|
|
844
463
|
});
|
|
845
464
|
|
|
846
|
-
|
|
847
|
-
const { objects } = await channel.findObjects({
|
|
848
|
-
collection: 'article',
|
|
849
|
-
prompt: 'that discuss climate solutions positively',
|
|
850
|
-
limit: 10
|
|
851
|
-
});
|
|
465
|
+
await syncFiles();
|
|
852
466
|
```
|
|
853
467
|
|
|
854
|
-
When `where` or `objectIds` are provided with a `prompt`, the AI only sees the filtered subset — not the full space. The returned `message` explains the query result.
|
|
855
|
-
|
|
856
|
-
### Undo/Redo
|
|
857
|
-
|
|
858
|
-
| Method | Description |
|
|
859
|
-
|--------|-------------|
|
|
860
|
-
| `checkpoint(label?): Promise<string>` | Call before mutations. Saves current state for undo. |
|
|
861
|
-
| `canUndo(): Promise<boolean>` | Check if undo available |
|
|
862
|
-
| `canRedo(): Promise<boolean>` | Check if redo available |
|
|
863
|
-
| `undo(): Promise<boolean>` | Undo to previous checkpoint |
|
|
864
|
-
| `redo(): Promise<boolean>` | Redo undone action |
|
|
865
|
-
| `clearHistory(): Promise<void>` | Clear undo/redo stack |
|
|
866
|
-
|
|
867
|
-
See [Checkpoints & Undo/Redo](#checkpoints--undoredo) for semantics.
|
|
868
|
-
|
|
869
|
-
### Space Metadata
|
|
870
|
-
|
|
871
|
-
Store arbitrary data alongside the Space without it being part of the object data (e.g., viewport state, user preferences).
|
|
872
|
-
|
|
873
468
|
| Method | Description |
|
|
874
|
-
|
|
875
|
-
| `
|
|
876
|
-
| `
|
|
877
|
-
| `
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
469
|
+
| --- | --- |
|
|
470
|
+
| `webdav.href(path)` / `webdav.url(path)` | Return WebDAV href/URL for an absolute SDK path. |
|
|
471
|
+
| `webdav.options(path)` | Send `OPTIONS`. |
|
|
472
|
+
| `webdav.propfind(path, options)` | Read properties/list collections. `depth` is required. |
|
|
473
|
+
| `webdav.syncCollection(path, options)` | WebDAV `REPORT sync-collection`; returns changed responses and next token. |
|
|
474
|
+
| `webdav.get(path, options?)` / `webdav.head(path)` | Read a file; `get` supports byte ranges. |
|
|
475
|
+
| `webdav.put(path, body, options?)` | Write a file/object at an exact path. Parent collection must exist. |
|
|
476
|
+
| `webdav.mkcol(path)` | Create one collection. |
|
|
477
|
+
| `webdav.copy(source, destination, options?)` | Copy a file or collection. |
|
|
478
|
+
| `webdav.move(source, destination, options?)` | Move a file or collection. |
|
|
479
|
+
| `webdav.delete(path, options?)` | Delete a file or collection. |
|
|
480
|
+
| `webdav.lock(path, options)` / `refreshLock(path, token)` / `unlock(token)` | WebDAV write locks. |
|
|
481
|
+
| `webdav.request(method, path, init?)` | Raw authenticated WebDAV request. |
|
|
482
|
+
| `space.fetchPath(path, options?)` | Fetch a `/rool-drive/...` file path or `rool-machine:` file URI. |
|
|
483
|
+
| `space.getStorageUsage()` / `webdav.getStorageUsage()` | Storage quota usage. |
|
|
484
|
+
|
|
485
|
+
High-level WebDAV methods that validate response status throw `WebDAVError` with `status`, `statusText`, and `body`; raw `request()` and `options()` return `Response`.
|
|
882
486
|
|
|
883
|
-
|
|
884
|
-
|--------|-------------|
|
|
885
|
-
| `uploadMedia(file): Promise<string>` | Upload file, returns URL |
|
|
886
|
-
| `fetchMedia(url, options?): Promise<MediaResponse>` | Fetch any URL, returns headers and blob() method (adds auth for backend URLs, works for external URLs too). Pass `{ forceProxy: true }` to skip the direct fetch and route through the server proxy immediately. |
|
|
887
|
-
| `deleteMedia(url): Promise<void>` | Delete media file by URL |
|
|
888
|
-
| `listMedia(): Promise<MediaInfo[]>` | List all media with metadata |
|
|
487
|
+
## Collaboration
|
|
889
488
|
|
|
890
489
|
```typescript
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
await
|
|
894
|
-
|
|
895
|
-
// Or let AI generate one using a placeholder
|
|
896
|
-
await channel.createObject({
|
|
897
|
-
data: { type: 'photo', title: 'Mascot', image: '{{generate an image of a flying tortoise}}' }
|
|
898
|
-
});
|
|
899
|
-
|
|
900
|
-
// Display media (handles auth automatically)
|
|
901
|
-
const response = await channel.fetchMedia(object.image);
|
|
902
|
-
if (response.contentType.startsWith('image/')) {
|
|
903
|
-
const blob = await response.blob();
|
|
904
|
-
img.src = URL.createObjectURL(blob);
|
|
490
|
+
const user = await client.searchUser('colleague@example.com');
|
|
491
|
+
if (user) {
|
|
492
|
+
await space.addUser(user.id, 'editor');
|
|
905
493
|
}
|
|
906
|
-
```
|
|
907
|
-
|
|
908
|
-
### Proxied Fetch
|
|
909
|
-
|
|
910
|
-
Fetch external URLs via the server, bypassing CORS restrictions. Requires editor role or above. Private/internal IP ranges are blocked (SSRF protection).
|
|
911
|
-
|
|
912
|
-
| Method | Description |
|
|
913
|
-
|--------|-------------|
|
|
914
|
-
| `fetch(url, init?): Promise<Response>` | Fetch a URL via the server proxy. `init` accepts `method`, `headers`, and `body`. |
|
|
915
494
|
|
|
916
|
-
|
|
917
|
-
// GET request
|
|
918
|
-
const response = await channel.fetch('https://api.example.com/data');
|
|
919
|
-
const data = await response.json();
|
|
920
|
-
|
|
921
|
-
// POST with headers and body
|
|
922
|
-
const response = await channel.fetch('https://api.example.com/submit', {
|
|
923
|
-
method: 'POST',
|
|
924
|
-
headers: { 'Content-Type': 'application/json' },
|
|
925
|
-
body: { key: 'value' },
|
|
926
|
-
});
|
|
495
|
+
await space.setLinkAccess('viewer'); // 'none' | 'viewer' | 'editor'
|
|
927
496
|
```
|
|
928
497
|
|
|
929
|
-
|
|
498
|
+
Roles:
|
|
930
499
|
|
|
931
|
-
|
|
500
|
+
| Role | Capabilities |
|
|
501
|
+
| --- | --- |
|
|
502
|
+
| `owner` | Full control. |
|
|
503
|
+
| `admin` | Editor capabilities plus user/link management. |
|
|
504
|
+
| `editor` | Create, modify, move, and delete objects/files. |
|
|
505
|
+
| `viewer` | Read-only access. |
|
|
932
506
|
|
|
933
|
-
|
|
507
|
+
## RoolClient API
|
|
934
508
|
|
|
509
|
+
### Constructor config
|
|
935
510
|
|
|
936
511
|
```typescript
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
]);
|
|
944
|
-
|
|
945
|
-
// Read the current schema
|
|
946
|
-
const schema = channel.getSchema();
|
|
947
|
-
console.log(schema.article.fields); // FieldDef[]
|
|
948
|
-
|
|
949
|
-
// Modify an existing collection's fields
|
|
950
|
-
await channel.alterCollection('article', [
|
|
951
|
-
{ name: 'title', type: { kind: 'string' } },
|
|
952
|
-
{ name: 'status', type: { kind: 'enum', values: ['draft', 'review', 'published', 'archived'] } },
|
|
953
|
-
{ name: 'tags', type: { kind: 'array', inner: { kind: 'string' } } },
|
|
954
|
-
{ name: 'author', type: { kind: 'ref' } },
|
|
955
|
-
{ name: 'wordCount', type: { kind: 'number' } },
|
|
956
|
-
]);
|
|
957
|
-
|
|
958
|
-
// Remove a collection
|
|
959
|
-
await channel.dropCollection('article');
|
|
512
|
+
const client = new RoolClient({
|
|
513
|
+
apiUrl: 'https://api.rool.dev',
|
|
514
|
+
authUrl: 'https://rool.dev/auth',
|
|
515
|
+
graphqlUrl: 'https://api.rool.dev/graphql',
|
|
516
|
+
logger: console,
|
|
517
|
+
});
|
|
960
518
|
```
|
|
961
519
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
|
965
|
-
|
|
|
966
|
-
| `
|
|
967
|
-
| `
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
|
972
|
-
|
|
973
|
-
| `
|
|
974
|
-
| `
|
|
975
|
-
| `
|
|
976
|
-
| `
|
|
977
|
-
| `
|
|
978
|
-
| `
|
|
979
|
-
| `
|
|
980
|
-
| `
|
|
520
|
+
`apiUrl` defaults to `https://api.rool.dev`; `authUrl` is derived by stripping the `api.` hostname prefix unless provided. `baseUrl` is still accepted as a deprecated alias for `apiUrl`. Pass `authProvider` for Node.js, Electron, or custom auth flows.
|
|
521
|
+
|
|
522
|
+
| Method/property | Description |
|
|
523
|
+
| --- | --- |
|
|
524
|
+
| `currentUser: CurrentUser | null` | Cached user profile from initialization/fetch. |
|
|
525
|
+
| `getCurrentUser(): Promise<CurrentUser>` | Fetch current user. |
|
|
526
|
+
| `updateCurrentUser(input): Promise<CurrentUser>` | Update `name`, `slug`, or `marketingOptIn`. |
|
|
527
|
+
| `deleteCurrentUser(): Promise<void>` | Mark account for deletion and log out. |
|
|
528
|
+
| `searchUser(email): Promise<UserResult | null>` | Exact email lookup. |
|
|
529
|
+
| `listSpaces(): Promise<RoolSpaceInfo[]>` | List accessible spaces. |
|
|
530
|
+
| `openSpace(id): Promise<RoolSpace>` | Open/cached live space handle. |
|
|
531
|
+
| `createSpace(name): Promise<RoolSpace>` | Create and open a space. |
|
|
532
|
+
| `duplicateSpace(sourceId, name): Promise<RoolSpace>` | Duplicate a space. |
|
|
533
|
+
| `deleteSpace(id): Promise<void>` | Permanently delete a space. |
|
|
534
|
+
| `importArchive(name, archive): Promise<RoolSpace>` | Import a zip archive as a new space. |
|
|
535
|
+
| `getUserStorage<T>(key): T | undefined` | Sync read from user-storage cache. |
|
|
536
|
+
| `setUserStorage(key, value): void` | Update user storage; `null`/`undefined` deletes. |
|
|
537
|
+
| `getAllUserStorage(): Record<string, unknown>` | Copy all cached user storage. |
|
|
538
|
+
| `reportEvent(event, url?): void` | Fire-and-forget telemetry event. |
|
|
539
|
+
| `destroy(): void` | Close subscriptions, spaces, auth resources, and listeners. |
|
|
540
|
+
| `generateId(): string` | Generate a 6-character alphanumeric ID. |
|
|
541
|
+
|
|
542
|
+
### Client events
|
|
543
|
+
|
|
544
|
+
```typescript
|
|
545
|
+
client.on('authStateChanged', (authenticated) => void 0);
|
|
546
|
+
client.on('spaceAdded', (space) => void 0);
|
|
547
|
+
client.on('spaceRemoved', (spaceId) => void 0);
|
|
548
|
+
client.on('spaceRenamed', (spaceId, newName) => void 0);
|
|
549
|
+
client.on('channelCreated', (spaceId, channel) => void 0);
|
|
550
|
+
client.on('channelUpdated', (spaceId, channel) => void 0);
|
|
551
|
+
client.on('channelDeleted', (spaceId, channelId) => void 0);
|
|
552
|
+
client.on('userStorageChanged', ({ key, value, source }) => void 0);
|
|
553
|
+
client.on('connectionStateChanged', (state) => void 0);
|
|
554
|
+
client.on('error', (error, context) => void 0);
|
|
555
|
+
```
|
|
981
556
|
|
|
982
|
-
|
|
557
|
+
## RoolSpace API
|
|
983
558
|
|
|
984
|
-
|
|
559
|
+
Properties: `id`, `name`, `role`, `linkAccess`, `memberCount`, `channels`, `route`, `webdav`.
|
|
985
560
|
|
|
986
561
|
| Method | Description |
|
|
987
|
-
|
|
988
|
-
| `
|
|
989
|
-
| `
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
562
|
+
| --- | --- |
|
|
563
|
+
| `openChannel(channelId): Promise<RoolChannel>` | Open/create a channel. |
|
|
564
|
+
| `close(): void` | Stop subscription and close open channels. |
|
|
565
|
+
| `rename(newName): Promise<void>` | Rename the space. |
|
|
566
|
+
| `delete(): Promise<void>` | Permanently delete the space. |
|
|
567
|
+
| `listUsers(): Promise<SpaceMember[]>` | List collaborators. |
|
|
568
|
+
| `addUser(userId, role): Promise<void>` | Add collaborator. |
|
|
569
|
+
| `removeUser(userId): Promise<void>` | Remove collaborator. |
|
|
570
|
+
| `setLinkAccess(linkAccess): Promise<void>` | Set URL sharing level. |
|
|
571
|
+
| `renameChannel(channelId, name): Promise<void>` | Rename a channel. |
|
|
572
|
+
| `deleteChannel(channelId): Promise<void>` | Delete a channel and history. |
|
|
573
|
+
| `exportArchive(): Promise<Blob>` | Export a space archive. |
|
|
574
|
+
| `refresh(): Promise<void>` | Refresh cached space data. |
|
|
575
|
+
| `fetchPath(path, options?): Promise<Response>` | Fetch a `/rool-drive/...` file. |
|
|
576
|
+
| `getStorageUsage(): Promise<SpaceFileStorageUsage>` | File-storage quota usage. |
|
|
577
|
+
|
|
578
|
+
Events:
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
space.on('channelCreated', (channel) => void 0);
|
|
582
|
+
space.on('channelUpdated', (channel) => void 0);
|
|
583
|
+
space.on('channelDeleted', (channelId) => void 0);
|
|
584
|
+
space.on('filesChanged', ({ spaceId, source, timestamp }) => void 0);
|
|
585
|
+
space.on('filesReset', ({ spaceId, source, timestamp }) => void 0);
|
|
586
|
+
space.on('connectionStateChanged', (state) => void 0);
|
|
997
587
|
```
|
|
998
588
|
|
|
999
|
-
|
|
1000
|
-
```typescript
|
|
1001
|
-
const space = await client.importArchive('Imported Data', archiveBlob);
|
|
1002
|
-
const channel = await space.openChannel('main');
|
|
1003
|
-
```
|
|
589
|
+
## RoolChannel API
|
|
1004
590
|
|
|
1005
|
-
|
|
591
|
+
Properties: `id` (space ID), `name` (space name), `role`, `linkAccess`, `userId`, `channelId`, `channelName`, `conversationId`, `isReadOnly`, `activeLeafId`.
|
|
1006
592
|
|
|
1007
|
-
|
|
593
|
+
| Area | Methods |
|
|
594
|
+
| --- | --- |
|
|
595
|
+
| Lifecycle | `close()`, `rename(name)`, `conversation(id)` |
|
|
596
|
+
| Objects | `getObject`, `getObjects`, `stat`, `putObject`, `patchObject`, `moveObject`, `deleteObjects` |
|
|
597
|
+
| Schema | `getSchema`, `createCollection`, `alterCollection`, `dropCollection` |
|
|
598
|
+
| Metadata | `setMetadata`, `getMetadata`, `getAllMetadata` |
|
|
599
|
+
| Conversations | `getInteractions`, `getTree`, `setActiveLeaf`, `getConversations`, `deleteConversation`, `getSystemInstruction`, `setSystemInstruction`, `renameConversation` |
|
|
600
|
+
| AI | `prompt`, `stop`, `stopInteraction` |
|
|
601
|
+
| Undo/redo | `checkpoint`, `canUndo`, `canRedo`, `undo`, `redo`, `clearHistory` |
|
|
602
|
+
| Utilities | `fetch(url, init?)` server-side proxied fetch |
|
|
1008
603
|
|
|
1009
|
-
|
|
604
|
+
Channel events:
|
|
1010
605
|
|
|
1011
606
|
```typescript
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
// Object events
|
|
1019
|
-
channel.on('objectCreated', ({ objectId, object, source }) => void)
|
|
1020
|
-
channel.on('objectUpdated', ({ objectId, object, source }) => void)
|
|
1021
|
-
channel.on('objectDeleted', ({ objectId, source }) => void)
|
|
1022
|
-
|
|
1023
|
-
// Space metadata
|
|
1024
|
-
channel.on('metadataUpdated', ({ metadata, source }) => void)
|
|
1025
|
-
|
|
1026
|
-
// Collection schema changed
|
|
1027
|
-
channel.on('schemaUpdated', ({ schema, source }) => void)
|
|
1028
|
-
|
|
1029
|
-
// Channel metadata updated (name, extensionUrl)
|
|
1030
|
-
channel.on('channelUpdated', ({ channelId, source }) => void)
|
|
1031
|
-
|
|
1032
|
-
// Conversation interaction history updated
|
|
1033
|
-
channel.on('conversationUpdated', ({ conversationId, channelId, source }) => void)
|
|
1034
|
-
|
|
1035
|
-
// Full state replacement (undo/redo, resync after error)
|
|
1036
|
-
channel.on('reset', ({ source }) => void)
|
|
1037
|
-
|
|
1038
|
-
// Sync error occurred, channel resynced from server
|
|
1039
|
-
channel.on('syncError', (error: Error) => void)
|
|
607
|
+
channel.on('metadataUpdated', ({ metadata, source }) => void 0);
|
|
608
|
+
channel.on('schemaUpdated', ({ schema, source }) => void 0);
|
|
609
|
+
channel.on('channelUpdated', ({ channelId, source }) => void 0);
|
|
610
|
+
channel.on('conversationUpdated', ({ conversationId, channelId, source }) => void 0);
|
|
611
|
+
channel.on('reset', ({ source }) => void 0);
|
|
612
|
+
channel.on('syncError', (error) => void 0);
|
|
1040
613
|
```
|
|
1041
614
|
|
|
1042
|
-
|
|
615
|
+
`channel.fetch(url, init?)` proxies external HTTP requests through the server to bypass browser CORS.
|
|
1043
616
|
|
|
1044
|
-
|
|
617
|
+
## Import/Export
|
|
1045
618
|
|
|
1046
619
|
```typescript
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
} catch (error) {
|
|
1050
|
-
if (error.message.includes('temporarily unavailable')) {
|
|
1051
|
-
showToast('Service busy, please try again in a moment');
|
|
1052
|
-
} else {
|
|
1053
|
-
showToast(error.message);
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
620
|
+
const archive = await space.exportArchive();
|
|
621
|
+
const imported = await client.importArchive('Imported Data', archive);
|
|
1056
622
|
```
|
|
1057
623
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
Each channel contains one or more conversations, each with its own interaction history. History is stored as a tree (interactions linked by `parentId`) in the space data and syncs in real-time. Capped at 200 interactions per conversation.
|
|
1061
|
-
|
|
1062
|
-
### Conversation History Methods
|
|
1063
|
-
|
|
1064
|
-
| Method | Description |
|
|
1065
|
-
|--------|-------------|
|
|
1066
|
-
| `getInteractions(): Interaction[]` | Get the active branch as a flat array (root → leaf) |
|
|
1067
|
-
| `getTree(): Record<string, Interaction>` | Get the full interaction tree for branch navigation |
|
|
1068
|
-
| `activeLeafId: string \| undefined` | The tip of the currently active branch |
|
|
1069
|
-
| `setActiveLeaf(id: string): void` | Switch to a different branch (emits `conversationUpdated`) |
|
|
1070
|
-
| `getSystemInstruction(): string \| undefined` | Get system instruction for the default conversation |
|
|
1071
|
-
| `setSystemInstruction(instruction): Promise<void>` | Set system instruction for the default conversation. Pass `null` to clear. |
|
|
1072
|
-
| `getConversations(): ConversationInfo[]` | List all conversations in this channel |
|
|
1073
|
-
| `deleteConversation(conversationId): Promise<void>` | Delete a conversation (cannot delete `'default'`) |
|
|
1074
|
-
| `renameConversation(name): Promise<void>` | Rename the default conversation |
|
|
1075
|
-
|
|
1076
|
-
Channel management (listing, renaming, deleting channels) is done via the client — see [Channel Management](#channel-management).
|
|
1077
|
-
|
|
1078
|
-
### The ai Field
|
|
1079
|
-
|
|
1080
|
-
The `ai` field in interactions distinguishes AI-generated responses from synthetic confirmations:
|
|
1081
|
-
- `ai: true` — AI processed this operation (prompt, or createObject/updateObject with placeholders)
|
|
1082
|
-
- `ai: false` — System confirmation only (e.g., "Created object abc123")
|
|
1083
|
-
|
|
1084
|
-
### Tool Calls
|
|
1085
|
-
|
|
1086
|
-
The `toolCalls` array captures what the AI agent did during execution. The `conversationUpdated` event fires when each tool starts and completes. A tool call without a `result` is still running; once `result` is present, the tool has finished.
|
|
624
|
+
Archives include objects, metadata, channels/conversations, and file storage.
|
|
1087
625
|
|
|
1088
626
|
## Data Types
|
|
1089
627
|
|
|
1090
|
-
### Schema Types
|
|
1091
|
-
|
|
1092
628
|
```typescript
|
|
1093
|
-
// Allowed field types
|
|
1094
629
|
type FieldType =
|
|
1095
630
|
| { kind: 'string' }
|
|
1096
631
|
| { kind: 'number' }
|
|
@@ -1108,141 +643,66 @@ interface FieldDef {
|
|
|
1108
643
|
|
|
1109
644
|
interface CollectionDef {
|
|
1110
645
|
fields: FieldDef[];
|
|
646
|
+
schemaOrgType?: string;
|
|
1111
647
|
}
|
|
1112
648
|
|
|
1113
|
-
// Full schema — collection names to definitions
|
|
1114
649
|
type SpaceSchema = Record<string, CollectionDef>;
|
|
1115
|
-
```
|
|
1116
|
-
|
|
1117
|
-
### Object Data
|
|
1118
650
|
|
|
1119
|
-
```typescript
|
|
1120
|
-
// RoolObject represents the object data you work with
|
|
1121
|
-
// Always contains `id`, plus any additional fields
|
|
1122
|
-
// Fields prefixed with _ are hidden from AI
|
|
1123
|
-
// References between objects are fields whose values are object IDs
|
|
1124
651
|
interface RoolObject {
|
|
1125
|
-
|
|
1126
|
-
|
|
652
|
+
path: string;
|
|
653
|
+
body: Record<string, unknown>;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
interface GetObjectsResult {
|
|
657
|
+
objects: RoolObject[];
|
|
658
|
+
missing: string[];
|
|
1127
659
|
}
|
|
1128
660
|
|
|
1129
|
-
// Object stat - audit information returned by channel.stat()
|
|
1130
661
|
interface RoolObjectStat {
|
|
662
|
+
path: string;
|
|
1131
663
|
modifiedAt: number;
|
|
1132
664
|
modifiedBy: string;
|
|
1133
665
|
modifiedByName: string | null;
|
|
1134
|
-
modifiedInChannel: string;
|
|
1135
|
-
modifiedInConversation: string | null;
|
|
1136
|
-
modifiedInInteraction: string | null;
|
|
1137
|
-
}
|
|
1138
|
-
```
|
|
1139
|
-
|
|
1140
|
-
### Channels and Conversations
|
|
1141
|
-
|
|
1142
|
-
```typescript
|
|
1143
|
-
// Conversation — holds interaction tree and optional system instruction
|
|
1144
|
-
interface Conversation {
|
|
1145
|
-
name?: string; // Conversation name (optional)
|
|
1146
|
-
systemInstruction?: string; // Custom system instruction for AI
|
|
1147
|
-
createdAt: number; // Timestamp when conversation was created
|
|
1148
|
-
createdBy: string; // User ID who created the conversation
|
|
1149
|
-
interactions: Record<string, Interaction>; // Interaction tree (keyed by ID, linked by parentId)
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
// Conversation summary info (returned by channel.getConversations())
|
|
1153
|
-
interface ConversationInfo {
|
|
1154
|
-
id: string;
|
|
1155
|
-
name: string | null;
|
|
1156
|
-
systemInstruction: string | null;
|
|
1157
|
-
createdAt: number;
|
|
1158
|
-
createdBy: string;
|
|
1159
|
-
interactionCount: number;
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
// Channel container with metadata and conversations
|
|
1163
|
-
interface Channel {
|
|
1164
|
-
name?: string; // Channel name (optional)
|
|
1165
|
-
createdAt: number; // Timestamp when channel was created
|
|
1166
|
-
createdBy: string; // User ID who created the channel
|
|
1167
|
-
createdByName?: string; // Display name at time of creation
|
|
1168
|
-
extensionUrl?: string; // URL of installed extension (set by installExtension)
|
|
1169
|
-
extensionId?: string; // ID of installed extension (user_extensions.extension_id)
|
|
1170
|
-
manifest?: ExtensionManifest; // Extension manifest snapshot (set when extension is wired)
|
|
1171
|
-
conversations: Record<string, Conversation>; // Keyed by conversation ID
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
// Channel summary info (returned by client.getChannels)
|
|
1175
|
-
interface ChannelInfo {
|
|
1176
|
-
id: string;
|
|
1177
|
-
name: string | null;
|
|
1178
|
-
createdAt: number;
|
|
1179
|
-
createdBy: string;
|
|
1180
|
-
createdByName: string | null;
|
|
1181
|
-
interactionCount: number;
|
|
1182
|
-
extensionUrl: string | null; // URL of installed extension, or null
|
|
1183
|
-
extensionId: string | null; // ID of installed extension, or null
|
|
1184
|
-
manifest: ExtensionManifest | null; // Extension manifest snapshot, or null
|
|
666
|
+
modifiedInChannel: string;
|
|
667
|
+
modifiedInConversation: string | null;
|
|
668
|
+
modifiedInInteraction: string | null;
|
|
1185
669
|
}
|
|
1186
|
-
```
|
|
1187
670
|
|
|
1188
|
-
|
|
671
|
+
type PromptAttachment =
|
|
672
|
+
| File
|
|
673
|
+
| Blob
|
|
674
|
+
| { data: string; contentType: string; filename?: string }
|
|
675
|
+
| string;
|
|
1189
676
|
|
|
1190
|
-
|
|
677
|
+
type PromptEffort = 'QUICK' | 'STANDARD' | 'REASONING' | 'RESEARCH';
|
|
1191
678
|
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
679
|
+
interface PromptOptions {
|
|
680
|
+
responseSchema?: Record<string, unknown>;
|
|
681
|
+
effort?: PromptEffort;
|
|
682
|
+
parentInteractionId?: string | null;
|
|
683
|
+
ephemeral?: boolean;
|
|
684
|
+
readOnly?: boolean;
|
|
685
|
+
attachments?: PromptAttachment[];
|
|
686
|
+
signal?: AbortSignal;
|
|
687
|
+
eventName?: string;
|
|
1197
688
|
}
|
|
1198
689
|
|
|
1199
690
|
type InteractionStatus = 'pending' | 'streaming' | 'done' | 'error';
|
|
1200
691
|
|
|
1201
692
|
interface Interaction {
|
|
1202
|
-
id: string;
|
|
1203
|
-
parentId: string | null;
|
|
693
|
+
id: string;
|
|
694
|
+
parentId: string | null;
|
|
1204
695
|
timestamp: number;
|
|
1205
|
-
userId: string;
|
|
1206
|
-
userName?: string | null;
|
|
1207
|
-
operation: 'prompt' | '
|
|
1208
|
-
input: string;
|
|
1209
|
-
output: string | null;
|
|
1210
|
-
status: InteractionStatus;
|
|
1211
|
-
ai: boolean;
|
|
1212
|
-
|
|
1213
|
-
toolCalls: ToolCall[];
|
|
1214
|
-
attachments?: string[];
|
|
1215
|
-
}
|
|
1216
|
-
```
|
|
1217
|
-
|
|
1218
|
-
### Info Types
|
|
1219
|
-
|
|
1220
|
-
```typescript
|
|
1221
|
-
type RoolUserRole = 'owner' | 'admin' | 'editor' | 'viewer';
|
|
1222
|
-
type LinkAccess = 'none' | 'viewer' | 'editor';
|
|
1223
|
-
|
|
1224
|
-
interface RoolSpaceInfo { id: string; name: string; role: RoolUserRole; ownerId: string; size: number; createdAt: string; updatedAt: string; linkAccess: LinkAccess; memberCount: number; }
|
|
1225
|
-
interface SpaceMember { id: string; email: string; role: RoolUserRole; photoUrl: string | null; }
|
|
1226
|
-
interface UserResult { id: string; email: string; name: string | null; photoUrl: string | null; }
|
|
1227
|
-
interface CurrentUser { id: string; email: string; name: string | null; photoUrl: string | null; slug: string; plan: string; creditsBalance: number; totalCreditsUsed: number; createdAt: string; lastActivity: string; processedAt: string; storage: Record<string, unknown>; }
|
|
1228
|
-
interface MediaInfo { url: string; contentType: string; size: number; createdAt: string; }
|
|
1229
|
-
interface MediaResponse { contentType: string; size: number | null; blob(): Promise<Blob>; }
|
|
1230
|
-
type ChangeSource = 'local_user' | 'remote_user' | 'remote_agent' | 'system';
|
|
1231
|
-
```
|
|
1232
|
-
|
|
1233
|
-
### Prompt Options
|
|
1234
|
-
|
|
1235
|
-
```typescript
|
|
1236
|
-
type PromptEffort = 'QUICK' | 'STANDARD' | 'REASONING' | 'RESEARCH';
|
|
1237
|
-
|
|
1238
|
-
interface PromptOptions {
|
|
1239
|
-
objectIds?: string[]; // Scope to specific objects
|
|
1240
|
-
responseSchema?: Record<string, unknown>;
|
|
1241
|
-
effort?: PromptEffort; // Effort level (default: 'STANDARD')
|
|
1242
|
-
ephemeral?: boolean; // Don't record in interaction history
|
|
1243
|
-
readOnly?: boolean; // Disable mutation tools (default: false)
|
|
1244
|
-
parentInteractionId?: string | null; // Branch from a specific interaction (omit to auto-continue)
|
|
1245
|
-
attachments?: Array<File | Blob | { data: string; contentType: string }>; // Files to attach (uploaded to media store)
|
|
696
|
+
userId: string;
|
|
697
|
+
userName?: string | null;
|
|
698
|
+
operation: 'prompt' | 'putObject' | 'patchObject' | 'moveObject' | 'deleteObjects' | 'deletePaths' | string;
|
|
699
|
+
input: string;
|
|
700
|
+
output: string | null;
|
|
701
|
+
status: InteractionStatus;
|
|
702
|
+
ai: boolean;
|
|
703
|
+
modifiedObjectPaths: string[];
|
|
704
|
+
toolCalls: ToolCall[];
|
|
705
|
+
attachments?: string[];
|
|
1246
706
|
}
|
|
1247
707
|
```
|
|
1248
708
|
|