oblien 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +634 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
# Oblien SDK
|
|
2
|
+
|
|
3
|
+
Cloud workspaces that boot in milliseconds and run anything.
|
|
4
|
+
|
|
5
|
+
Oblien gives you hardware-isolated microVMs -- each with its own kernel, its own memory, and full root access. Use them to give an AI agent a live environment, deploy a service with a public URL, run untrusted code in a throwaway sandbox, or spin up a dev machine you can SSH into. The workspace is the only primitive. What it becomes is up to you.
|
|
6
|
+
|
|
7
|
+
This is the official TypeScript SDK. It covers the full Oblien API surface: workspace lifecycle, networking, snapshots, workloads, metrics, SSH, public access, and the in-workspace runtime (files, exec, terminal, search, watchers, WebSocket).
|
|
8
|
+
|
|
9
|
+
**Documentation:** [https://oblien.com/docs](https://oblien.com/docs)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Platform
|
|
14
|
+
|
|
15
|
+
Oblien is a workspace platform. Every workspace is a microVM that boots in under a second from any Docker image. Workspaces can be temporary (auto-delete after a TTL) or permanent (run indefinitely). They can be air-gapped or wired to other workspaces over a private internal network. They can expose ports to the internet through Oblien's edge proxy with automatic TLS -- or stay completely invisible.
|
|
16
|
+
|
|
17
|
+
The architecture is split into two planes:
|
|
18
|
+
|
|
19
|
+
- **Control plane** (`api.oblien.com`) -- Create, configure, start, stop, snapshot, and destroy workspaces. Manage networking, firewall rules, SSH keys, workloads, images, billing, and metadata.
|
|
20
|
+
- **Data plane** (`workspace.oblien.com`) -- Interact with a running workspace's filesystem, execute commands, open terminal sessions, watch files, and stream events over WebSocket.
|
|
21
|
+
|
|
22
|
+
The SDK wraps both planes in a single client.
|
|
23
|
+
|
|
24
|
+
### What people build with it
|
|
25
|
+
|
|
26
|
+
- **AI agent environments** -- A permanent workspace is the agent's home. It creates short-lived sandboxes on demand for tasks, user code, or deployments.
|
|
27
|
+
- **Service hosting** -- Run an API server, a queue worker, or a database as a permanent workspace. Map it to a custom domain.
|
|
28
|
+
- **Remote development** -- SSH into a workspace, install your stack, expose a port for live preview.
|
|
29
|
+
- **CI/CD** -- Spin up ephemeral workspaces per build, run tests in full isolation, auto-destroy when done.
|
|
30
|
+
- **Code sandboxes** -- Air-gapped throwaway VMs that auto-delete after a TTL. No internet, no persistence.
|
|
31
|
+
- **Per-user environments** -- One workspace per customer with scoped networking, credit quotas, and full isolation.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install oblien
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Requires Node.js 18 or later. Zero runtime dependencies.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Quick start
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import Oblien from 'oblien';
|
|
49
|
+
|
|
50
|
+
const client = new Oblien({
|
|
51
|
+
clientId: process.env.OBLIEN_CLIENT_ID,
|
|
52
|
+
clientSecret: process.env.OBLIEN_CLIENT_SECRET,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const ws = client.workspaces;
|
|
56
|
+
|
|
57
|
+
// Create a workspace
|
|
58
|
+
const workspace = await ws.create({
|
|
59
|
+
image: 'node-20',
|
|
60
|
+
mode: 'permanent',
|
|
61
|
+
config: { cpus: 2, memory_mb: 4096 },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Start it
|
|
65
|
+
await ws.start(workspace.id);
|
|
66
|
+
|
|
67
|
+
// Connect to the runtime -- filesystem, exec, terminal
|
|
68
|
+
const rt = await ws.runtime(workspace.id);
|
|
69
|
+
|
|
70
|
+
// Run a command
|
|
71
|
+
const result = await rt.exec.run(['node', '--version']);
|
|
72
|
+
console.log(result.stdout);
|
|
73
|
+
|
|
74
|
+
// Read a file
|
|
75
|
+
const file = await rt.files.read({ filePath: '/etc/os-release' });
|
|
76
|
+
console.log(file.content);
|
|
77
|
+
|
|
78
|
+
// Expose port 3000 to the internet
|
|
79
|
+
await ws.publicAccess.expose(workspace.id, { port: 3000 });
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Architecture
|
|
85
|
+
|
|
86
|
+
### Client and workspaces
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import Oblien from 'oblien';
|
|
90
|
+
|
|
91
|
+
const client = new Oblien({ clientId, clientSecret });
|
|
92
|
+
const ws = client.workspaces;
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`client.workspaces` is the entry point for all control-plane operations. It provides CRUD methods, power controls, and 13 sub-resource namespaces:
|
|
96
|
+
|
|
97
|
+
| Namespace | Description |
|
|
98
|
+
|-----------|-------------|
|
|
99
|
+
| `ws.lifecycle` | Permanent/temporary mode, TTL, ping, destroy |
|
|
100
|
+
| `ws.network` | Firewall rules, private links, outbound IP |
|
|
101
|
+
| `ws.ssh` | Enable SSH, set password or public key |
|
|
102
|
+
| `ws.publicAccess` | Expose and revoke public ports |
|
|
103
|
+
| `ws.resources` | CPU, memory, disk allocation |
|
|
104
|
+
| `ws.snapshots` | Create, restore, archive, and manage snapshots |
|
|
105
|
+
| `ws.workloads` | Managed background processes with log streaming |
|
|
106
|
+
| `ws.metrics` | Live CPU/memory/disk/network stats, VM info and config |
|
|
107
|
+
| `ws.usage` | Credit usage, activity tracking |
|
|
108
|
+
| `ws.metadata` | Key-value metadata store |
|
|
109
|
+
| `ws.apiAccess` | Internal API server -- enable, disable, rotate tokens |
|
|
110
|
+
| `ws.logs` | Boot and command logs |
|
|
111
|
+
| `ws.images` | Available workspace images |
|
|
112
|
+
|
|
113
|
+
### CRUD
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
// Create
|
|
117
|
+
const workspace = await ws.create({ image: 'node-20', mode: 'permanent' });
|
|
118
|
+
|
|
119
|
+
// List
|
|
120
|
+
const { workspaces } = await ws.list({ page: 1, limit: 20 });
|
|
121
|
+
|
|
122
|
+
// Get
|
|
123
|
+
const data = await ws.get('ws_a1b2c3d4');
|
|
124
|
+
|
|
125
|
+
// Update
|
|
126
|
+
await ws.update('ws_a1b2c3d4', { name: 'production-api' });
|
|
127
|
+
|
|
128
|
+
// Delete
|
|
129
|
+
await ws.delete('ws_a1b2c3d4');
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Power controls
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
await ws.start(id);
|
|
136
|
+
await ws.stop(id);
|
|
137
|
+
await ws.restart(id);
|
|
138
|
+
await ws.pause(id); // preserves memory state
|
|
139
|
+
await ws.resume(id);
|
|
140
|
+
await ws.ping(id); // renews TTL for temporary workspaces
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Account-level queries
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
const quota = await ws.getQuota();
|
|
147
|
+
const estimate = await ws.getEstimate({ cpu: 4, ram_mb: 8192 });
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Runtime (data plane)
|
|
153
|
+
|
|
154
|
+
The runtime connects to a running workspace and provides direct access to its filesystem, command execution, terminal sessions, search, and file watchers.
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
const rt = await ws.runtime('ws_a1b2c3d4');
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
This call enables the workspace's internal API server (if not already enabled), fetches a gateway JWT, and returns a `Runtime` instance. The token is cached -- subsequent calls for the same workspace return instantly.
|
|
161
|
+
|
|
162
|
+
### Files
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// List directory
|
|
166
|
+
const entries = await rt.files.list({ dirPath: '/app' });
|
|
167
|
+
|
|
168
|
+
// Read file
|
|
169
|
+
const { content } = await rt.files.read({ filePath: '/app/index.js' });
|
|
170
|
+
|
|
171
|
+
// Write file
|
|
172
|
+
await rt.files.write({ fullPath: '/app/config.json', content: '{}' });
|
|
173
|
+
|
|
174
|
+
// Create directory
|
|
175
|
+
await rt.files.mkdir({ path: '/app/logs' });
|
|
176
|
+
|
|
177
|
+
// Stat
|
|
178
|
+
const stat = await rt.files.stat({ path: '/app/index.js' });
|
|
179
|
+
|
|
180
|
+
// Delete
|
|
181
|
+
await rt.files.delete({ path: '/app/tmp' });
|
|
182
|
+
|
|
183
|
+
// Stream directory tree (async generator)
|
|
184
|
+
for await (const entry of rt.files.stream({ dirPath: '/app' })) {
|
|
185
|
+
console.log(entry.path);
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Exec
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
// Run a command and wait for result
|
|
193
|
+
const result = await rt.exec.run(['npm', 'test']);
|
|
194
|
+
console.log(result.exitCode, result.stdout, result.stderr);
|
|
195
|
+
|
|
196
|
+
// Stream output as it happens (async generator)
|
|
197
|
+
for await (const event of rt.exec.stream(['npm', 'run', 'build'])) {
|
|
198
|
+
process.stdout.write(event.data);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// List running tasks
|
|
202
|
+
const tasks = await rt.exec.list();
|
|
203
|
+
|
|
204
|
+
// Kill a task
|
|
205
|
+
await rt.exec.kill(taskId);
|
|
206
|
+
|
|
207
|
+
// Send stdin to a running task
|
|
208
|
+
await rt.exec.input(taskId, 'yes\n');
|
|
209
|
+
|
|
210
|
+
// Subscribe to a running task's output
|
|
211
|
+
for await (const event of rt.exec.subscribe(taskId)) {
|
|
212
|
+
console.log(event);
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Terminal
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
// Create a terminal session
|
|
220
|
+
const session = await rt.terminal.create({ shell: '/bin/bash' });
|
|
221
|
+
|
|
222
|
+
// List sessions
|
|
223
|
+
const sessions = await rt.terminal.list();
|
|
224
|
+
|
|
225
|
+
// Get scrollback buffer
|
|
226
|
+
const { data } = await rt.terminal.scrollback(session.id, { bytes: 4096 });
|
|
227
|
+
|
|
228
|
+
// Close session
|
|
229
|
+
await rt.terminal.close(session.id);
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Search
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
// Search file contents (ripgrep)
|
|
236
|
+
const hits = await rt.search.content({ query: 'TODO', path: '/app' });
|
|
237
|
+
|
|
238
|
+
// Search file names
|
|
239
|
+
const files = await rt.search.files({ query: 'config', path: '/app' });
|
|
240
|
+
|
|
241
|
+
// Index status and initialization
|
|
242
|
+
const status = await rt.search.status();
|
|
243
|
+
await rt.search.init();
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Watcher
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// Watch a directory for changes
|
|
250
|
+
const watcher = await rt.watcher.create({ path: '/app/src' });
|
|
251
|
+
|
|
252
|
+
// List active watchers
|
|
253
|
+
const watchers = await rt.watcher.list();
|
|
254
|
+
|
|
255
|
+
// Get watcher info
|
|
256
|
+
const info = await rt.watcher.get(watcher.id);
|
|
257
|
+
|
|
258
|
+
// Stop watching
|
|
259
|
+
await rt.watcher.delete(watcher.id);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## WebSocket
|
|
265
|
+
|
|
266
|
+
For real-time terminal I/O and file watcher events, open a persistent WebSocket connection:
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
const socket = rt.ws();
|
|
270
|
+
|
|
271
|
+
socket.onOpen(() => {
|
|
272
|
+
console.log('connected');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
socket.onTerminalOutput((data) => {
|
|
276
|
+
process.stdout.write(data.output);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
socket.onWatcherEvent((event) => {
|
|
280
|
+
console.log(event.type, event.path);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
socket.onClose(() => {
|
|
284
|
+
console.log('disconnected');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
socket.onError((err) => {
|
|
288
|
+
console.error(err);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
await socket.connect();
|
|
292
|
+
|
|
293
|
+
// Send input to a terminal
|
|
294
|
+
socket.writeTerminalInput(sessionId, 'ls -la\n');
|
|
295
|
+
|
|
296
|
+
// Resize terminal
|
|
297
|
+
socket.resizeTerminal(sessionId, { cols: 120, rows: 40 });
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## SSE streaming
|
|
303
|
+
|
|
304
|
+
Several endpoints return server-sent events as async generators. Consume them with `for await`:
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
// Stream workspace logs
|
|
308
|
+
for await (const line of ws.logs.streamBoot(id)) {
|
|
309
|
+
console.log(line);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Stream workload output
|
|
313
|
+
for await (const event of ws.workloads.logsStream(id, workloadId)) {
|
|
314
|
+
process.stdout.write(event.data);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Stream live metrics
|
|
318
|
+
for await (const snapshot of ws.metrics.statsStream(id)) {
|
|
319
|
+
console.log(snapshot.cpu_percent, snapshot.memory_used_mb);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Stream workload stats
|
|
323
|
+
for await (const stats of ws.workloads.statsStream(id, workloadId)) {
|
|
324
|
+
console.log(stats);
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Sub-resources
|
|
331
|
+
|
|
332
|
+
### Lifecycle
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
const status = await ws.lifecycle.get(id);
|
|
336
|
+
|
|
337
|
+
// Convert to permanent
|
|
338
|
+
await ws.lifecycle.makePermanent(id);
|
|
339
|
+
|
|
340
|
+
// Convert to temporary with TTL
|
|
341
|
+
await ws.lifecycle.makeTemporary(id, { ttl_seconds: 3600 });
|
|
342
|
+
|
|
343
|
+
// Update TTL
|
|
344
|
+
await ws.lifecycle.updateTtl(id, { ttl_seconds: 7200 });
|
|
345
|
+
|
|
346
|
+
// Destroy (irreversible)
|
|
347
|
+
await ws.lifecycle.destroy(id);
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Network
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
const network = await ws.network.get(id);
|
|
354
|
+
|
|
355
|
+
await ws.network.update(id, {
|
|
356
|
+
ingress: ['10.0.1.5'],
|
|
357
|
+
egress: ['10.0.1.10'],
|
|
358
|
+
allow_internet: false,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Assign a dedicated outbound IP
|
|
362
|
+
await ws.network.applyOutboundIp(id, { ip: '203.0.113.50' });
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### SSH
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
await ws.ssh.enable(id, { password: 'secure-password' });
|
|
369
|
+
await ws.ssh.enable(id, { publicKey: 'ssh-ed25519 AAAA...' });
|
|
370
|
+
await ws.ssh.disable(id);
|
|
371
|
+
const sshStatus = await ws.ssh.status(id);
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Public access
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
// Expose a port
|
|
378
|
+
const endpoint = await ws.publicAccess.expose(id, {
|
|
379
|
+
port: 3000,
|
|
380
|
+
domain: 'app.yourdomain.com',
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// List exposed ports
|
|
384
|
+
const ports = await ws.publicAccess.list(id);
|
|
385
|
+
|
|
386
|
+
// Revoke
|
|
387
|
+
await ws.publicAccess.revoke(id, { port: 3000 });
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Snapshots
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
// Create a snapshot
|
|
394
|
+
await ws.snapshots.create(id, { label: 'before-deploy' });
|
|
395
|
+
|
|
396
|
+
// Restore
|
|
397
|
+
await ws.snapshots.restore(id);
|
|
398
|
+
|
|
399
|
+
// Archives
|
|
400
|
+
await ws.snapshots.createArchive(id, { label: 'v1.0' });
|
|
401
|
+
const archives = await ws.snapshots.listArchives(id);
|
|
402
|
+
const archive = await ws.snapshots.getArchive(id, 'v1');
|
|
403
|
+
await ws.snapshots.deleteArchive(id, 'v1');
|
|
404
|
+
await ws.snapshots.deleteAllArchives(id);
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Workloads
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
// Create a managed workload
|
|
411
|
+
await ws.workloads.create(id, {
|
|
412
|
+
name: 'server',
|
|
413
|
+
cmd: ['node', 'server.js'],
|
|
414
|
+
env: { PORT: '3000' },
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const workloads = await ws.workloads.list(id);
|
|
418
|
+
const workload = await ws.workloads.get(id, workloadId);
|
|
419
|
+
await ws.workloads.start(id, workloadId);
|
|
420
|
+
await ws.workloads.stop(id, workloadId);
|
|
421
|
+
await ws.workloads.delete(id, workloadId);
|
|
422
|
+
|
|
423
|
+
// Logs and stats (async generators)
|
|
424
|
+
for await (const line of ws.workloads.logsStream(id, workloadId)) {
|
|
425
|
+
console.log(line);
|
|
426
|
+
}
|
|
427
|
+
for await (const snapshot of ws.workloads.statsStream(id, workloadId)) {
|
|
428
|
+
console.log(snapshot);
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Metrics
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
const stats = await ws.metrics.stats(id);
|
|
436
|
+
const info = await ws.metrics.info(id);
|
|
437
|
+
const config = await ws.metrics.config(id);
|
|
438
|
+
|
|
439
|
+
for await (const snapshot of ws.metrics.statsStream(id)) {
|
|
440
|
+
console.log(snapshot);
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Usage
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
const usage = await ws.usage.get(id);
|
|
448
|
+
const totals = await ws.usage.totals(id);
|
|
449
|
+
const chart = await ws.usage.creditsChart(id);
|
|
450
|
+
const global = await ws.usage.global();
|
|
451
|
+
const activity = await ws.usage.activity();
|
|
452
|
+
await ws.usage.wipe(id);
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Metadata
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
const meta = await ws.metadata.get(id);
|
|
459
|
+
await ws.metadata.update(id, { key: 'env', value: 'production' });
|
|
460
|
+
await ws.metadata.patch(id, { key: 'version', value: '2.1.0' });
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### API access
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
const status = await ws.apiAccess.status(id);
|
|
467
|
+
const access = await ws.apiAccess.enable(id);
|
|
468
|
+
await ws.apiAccess.disable(id);
|
|
469
|
+
await ws.apiAccess.rotateToken(id);
|
|
470
|
+
const token = await ws.apiAccess.getToken(id);
|
|
471
|
+
const raw = await ws.apiAccess.rawToken(id);
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Logs
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
const log = await ws.logs.get(id);
|
|
478
|
+
await ws.logs.clear(id);
|
|
479
|
+
const files = await ws.logs.listFiles(id);
|
|
480
|
+
const file = await ws.logs.getFile(id, 'boot.log');
|
|
481
|
+
|
|
482
|
+
for await (const line of ws.logs.streamBoot(id)) {
|
|
483
|
+
console.log(line);
|
|
484
|
+
}
|
|
485
|
+
for await (const line of ws.logs.streamCmd(id)) {
|
|
486
|
+
console.log(line);
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Images
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
const images = await ws.images.list();
|
|
494
|
+
const image = await ws.images.get('node-20');
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## Standalone imports
|
|
500
|
+
|
|
501
|
+
The SDK exports `Workspace` and `Runtime` as standalone classes for cases where you need direct access without going through the main client:
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
// Workspace (control plane) -- standalone
|
|
505
|
+
import { Workspace } from 'oblien/workspace';
|
|
506
|
+
|
|
507
|
+
const ws = new Workspace(client);
|
|
508
|
+
await ws.create({ image: 'python-3.12' });
|
|
509
|
+
|
|
510
|
+
// Runtime (data plane) -- standalone with a raw token
|
|
511
|
+
import { Runtime } from 'oblien/runtime';
|
|
512
|
+
|
|
513
|
+
const rt = new Runtime({
|
|
514
|
+
token: 'your_gateway_jwt',
|
|
515
|
+
});
|
|
516
|
+
await rt.files.list({ dirPath: '/' });
|
|
517
|
+
|
|
518
|
+
// Runtime with a direct IP (bypass edge proxy)
|
|
519
|
+
const directRt = new Runtime({
|
|
520
|
+
token: 'raw_token',
|
|
521
|
+
baseUrl: 'http://10.0.1.5:9990',
|
|
522
|
+
});
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
## Error handling
|
|
528
|
+
|
|
529
|
+
Every API error is an instance of `OblienError` with typed subclasses:
|
|
530
|
+
|
|
531
|
+
```typescript
|
|
532
|
+
import Oblien, {
|
|
533
|
+
OblienError,
|
|
534
|
+
AuthenticationError,
|
|
535
|
+
NotFoundError,
|
|
536
|
+
RateLimitError,
|
|
537
|
+
ValidationError,
|
|
538
|
+
PaymentRequiredError,
|
|
539
|
+
ConflictError,
|
|
540
|
+
} from 'oblien';
|
|
541
|
+
|
|
542
|
+
try {
|
|
543
|
+
await ws.get('ws_nonexistent');
|
|
544
|
+
} catch (err) {
|
|
545
|
+
if (err instanceof NotFoundError) {
|
|
546
|
+
console.log('Workspace does not exist');
|
|
547
|
+
} else if (err instanceof AuthenticationError) {
|
|
548
|
+
console.log('Bad credentials');
|
|
549
|
+
} else if (err instanceof RateLimitError) {
|
|
550
|
+
console.log('Slow down -- retry after backoff');
|
|
551
|
+
} else if (err instanceof PaymentRequiredError) {
|
|
552
|
+
console.log('Quota exceeded -- upgrade your plan');
|
|
553
|
+
} else if (err instanceof ValidationError) {
|
|
554
|
+
console.log('Invalid parameters:', err.message);
|
|
555
|
+
} else if (err instanceof ConflictError) {
|
|
556
|
+
console.log('Workspace is in the wrong state for this operation');
|
|
557
|
+
} else if (err instanceof OblienError) {
|
|
558
|
+
console.log(`API error ${err.status}: ${err.message}`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
| Error class | HTTP status | When |
|
|
564
|
+
|-------------|-------------|------|
|
|
565
|
+
| `AuthenticationError` | 401 | Invalid or missing credentials |
|
|
566
|
+
| `PaymentRequiredError` | 402 | Credit quota exceeded |
|
|
567
|
+
| `NotFoundError` | 404 | Resource does not exist |
|
|
568
|
+
| `ConflictError` | 409 | Resource in wrong state for the operation |
|
|
569
|
+
| `ValidationError` | 422 | Invalid request parameters |
|
|
570
|
+
| `RateLimitError` | 429 | Too many requests |
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
## Configuration
|
|
575
|
+
|
|
576
|
+
```typescript
|
|
577
|
+
const client = new Oblien({
|
|
578
|
+
clientId: 'your_client_id', // required
|
|
579
|
+
clientSecret: 'your_client_secret', // required
|
|
580
|
+
baseUrl: 'https://api.oblien.com', // optional, default
|
|
581
|
+
});
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
| Option | Type | Default | Description |
|
|
585
|
+
|--------|------|---------|-------------|
|
|
586
|
+
| `clientId` | `string` | -- | Your API client ID |
|
|
587
|
+
| `clientSecret` | `string` | -- | Your API client secret |
|
|
588
|
+
| `baseUrl` | `string` | `https://api.oblien.com` | Control plane API base URL |
|
|
589
|
+
|
|
590
|
+
Get your API keys from the [dashboard](https://oblien.com/dashboard).
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
594
|
+
## TypeScript
|
|
595
|
+
|
|
596
|
+
The SDK is written in TypeScript and ships its own type declarations. All request parameters, response shapes, and event types are fully typed and exported from the top-level package:
|
|
597
|
+
|
|
598
|
+
```typescript
|
|
599
|
+
import type {
|
|
600
|
+
WorkspaceCreateParams,
|
|
601
|
+
WorkspaceData,
|
|
602
|
+
ExecTask,
|
|
603
|
+
ExecStreamEvent,
|
|
604
|
+
FileEntry,
|
|
605
|
+
WSTerminalEvent,
|
|
606
|
+
WSWatcherEvent,
|
|
607
|
+
} from 'oblien';
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
---
|
|
611
|
+
|
|
612
|
+
## Requirements
|
|
613
|
+
|
|
614
|
+
- Node.js 18+
|
|
615
|
+
- TypeScript 5.0+ (if using TypeScript)
|
|
616
|
+
- ESM (`"type": "module"` in your package.json, or use dynamic `import()`)
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
620
|
+
## Links
|
|
621
|
+
|
|
622
|
+
- [Documentation](https://oblien.com/docs)
|
|
623
|
+
- [SDK setup guide](https://oblien.com/docs/workspace/sdk-setup)
|
|
624
|
+
- [Quickstart](https://oblien.com/docs/workspace/quickstart)
|
|
625
|
+
- [API reference](https://oblien.com/docs/api)
|
|
626
|
+
- [Runtime / Internal API](https://oblien.com/docs/internal-api)
|
|
627
|
+
- [Dashboard](https://oblien.com/dashboard)
|
|
628
|
+
- [GitHub](https://github.com/oblien/sdk)
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
|
|
632
|
+
## License
|
|
633
|
+
|
|
634
|
+
MIT
|