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.
Files changed (2) hide show
  1. package/README.md +634 -0
  2. 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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oblien",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Official TypeScript SDK for the Oblien Workspace API",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",