oblien 2.2.36 → 2.2.38
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 +53 -274
- package/dist/cdn.d.ts +105 -0
- package/dist/cdn.d.ts.map +1 -0
- package/dist/cdn.js +201 -0
- package/dist/cdn.js.map +1 -0
- package/dist/cli/commands/cdn.d.ts +3 -0
- package/dist/cli/commands/cdn.d.ts.map +1 -0
- package/dist/cli/commands/cdn.js +216 -0
- package/dist/cli/commands/cdn.js.map +1 -0
- package/dist/cli/commands/namespaces.js +19 -6
- package/dist/cli/commands/namespaces.js.map +1 -1
- package/dist/cli/commands/serve.d.ts +17 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/serve.js +113 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/tunnel.d.ts +1 -0
- package/dist/cli/commands/tunnel.d.ts.map +1 -1
- package/dist/cli/commands/tunnel.js +100 -3
- package/dist/cli/commands/tunnel.js.map +1 -1
- package/dist/cli/index.js +14 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/client.d.ts +12 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +16 -1
- package/dist/client.js.map +1 -1
- package/dist/domain.d.ts +20 -1
- package/dist/domain.d.ts.map +1 -1
- package/dist/domain.js +37 -0
- package/dist/domain.js.map +1 -1
- package/dist/edge-proxy.d.ts +50 -11
- package/dist/edge-proxy.d.ts.map +1 -1
- package/dist/edge-proxy.js +55 -10
- package/dist/edge-proxy.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +145 -7
- package/dist/mcp/tools.js.map +1 -1
- package/dist/namespace.d.ts +27 -5
- package/dist/namespace.d.ts.map +1 -1
- package/dist/namespace.js +30 -4
- package/dist/namespace.js.map +1 -1
- package/dist/notifications.d.ts +72 -0
- package/dist/notifications.d.ts.map +1 -0
- package/dist/notifications.js +93 -0
- package/dist/notifications.js.map +1 -0
- package/dist/runtime/proxy.d.ts +55 -0
- package/dist/runtime/proxy.d.ts.map +1 -0
- package/dist/runtime/proxy.js +79 -0
- package/dist/runtime/proxy.js.map +1 -0
- package/dist/runtime.d.ts +16 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +18 -0
- package/dist/runtime.js.map +1 -1
- package/dist/server/static.d.ts +81 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +465 -0
- package/dist/server/static.js.map +1 -0
- package/dist/types/cdn.d.ts +301 -0
- package/dist/types/cdn.d.ts.map +1 -0
- package/dist/types/cdn.js +10 -0
- package/dist/types/cdn.js.map +1 -0
- package/dist/types/client.d.ts +10 -0
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/edge-proxy.d.ts +50 -1
- package/dist/types/edge-proxy.d.ts.map +1 -1
- package/dist/types/index.d.ts +6 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/namespace.d.ts +55 -9
- package/dist/types/namespace.d.ts.map +1 -1
- package/dist/types/notifications.d.ts +77 -0
- package/dist/types/notifications.d.ts.map +1 -0
- package/dist/types/notifications.js +3 -0
- package/dist/types/notifications.js.map +1 -0
- package/dist/types/webhooks.d.ts +52 -0
- package/dist/types/webhooks.d.ts.map +1 -0
- package/dist/types/webhooks.js +3 -0
- package/dist/types/webhooks.js.map +1 -0
- package/dist/types/workspace-resources.d.ts +48 -0
- package/dist/types/workspace-resources.d.ts.map +1 -1
- package/dist/webhooks.d.ts +53 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/dist/webhooks.js +60 -0
- package/dist/webhooks.js.map +1 -0
- package/docs/analytics.md +36 -0
- package/docs/cdn.md +119 -0
- package/docs/domains.md +47 -0
- package/docs/edge-proxy.md +44 -0
- package/docs/edge-tunnel.md +42 -0
- package/docs/errors.md +34 -0
- package/docs/namespaces.md +69 -0
- package/docs/notifications.md +40 -0
- package/docs/pages.md +26 -0
- package/docs/runtime.md +83 -0
- package/docs/serve.md +84 -0
- package/docs/tokens.md +24 -0
- package/docs/webhooks.md +42 -0
- package/docs/workspaces.md +82 -0
- package/package.json +5 -2
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Edge Tunnel
|
|
2
|
+
|
|
3
|
+
Expose a **local** port to the internet through the Oblien edge — like a built-in ngrok. The tunnel client forwards incoming edge traffic to `localhost:<port>`.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
const et = client.edgeTunnel;
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## One-liner: create + connect
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
const tunnel = await et.connect({ name: 'dev-server', port: 3000 });
|
|
13
|
+
console.log(tunnel.url); // https://dev-xyz.preview.oblien.com
|
|
14
|
+
|
|
15
|
+
tunnel.on('request', (id, port) => console.log(`→ localhost:${port}`));
|
|
16
|
+
tunnel.on('close', () => console.log('disconnected'));
|
|
17
|
+
|
|
18
|
+
// Later…
|
|
19
|
+
tunnel.close();
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Low-level
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
const { tunnel } = await et.create({ name: 'api', port: 8080 });
|
|
26
|
+
const { token, connect_url } = await et.issueToken(tunnel.id);
|
|
27
|
+
// → drive a TunnelClient yourself with { connectUrl: connect_url, token }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Manage
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
const { tunnels } = await et.list();
|
|
34
|
+
await et.checkDomain({ host: 'tunnel.example.com' }); // pre-flight custom domain (TXT/CNAME)
|
|
35
|
+
await et.update(tunnel.id, { name: 'api-v2' });
|
|
36
|
+
await et.enable(tunnel.id);
|
|
37
|
+
await et.disable(tunnel.id);
|
|
38
|
+
await et.renewSSL(tunnel.id);
|
|
39
|
+
await et.delete(tunnel.id);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Full reference:** [Edge Tunnel API](https://oblien.com/docs/api/edge-tunnel)
|
package/docs/errors.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Error handling
|
|
2
|
+
|
|
3
|
+
Every API error is an instance of `OblienError` with typed subclasses. The SDK preserves the machine-readable `code`, human-readable `message`, optional `details`, and optional `requestId` from the API response.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import {
|
|
7
|
+
OblienError,
|
|
8
|
+
NotFoundError,
|
|
9
|
+
RateLimitError,
|
|
10
|
+
PaymentRequiredError,
|
|
11
|
+
} from 'oblien';
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
await client.workspaces.get('ws_nonexistent');
|
|
15
|
+
} catch (err) {
|
|
16
|
+
if (err instanceof NotFoundError) { /* 404 */ }
|
|
17
|
+
if (err instanceof RateLimitError) { /* 429 — back off */ }
|
|
18
|
+
if (err instanceof PaymentRequiredError) { /* 402 — quota exceeded */ }
|
|
19
|
+
if (err instanceof OblienError) {
|
|
20
|
+
console.error(err.code, err.message, err.details, err.requestId);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
| Error class | HTTP | When |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `AuthenticationError` | 401 | Invalid or missing credentials |
|
|
28
|
+
| `PaymentRequiredError` | 402 | Credit quota exceeded |
|
|
29
|
+
| `NotFoundError` | 404 | Resource does not exist |
|
|
30
|
+
| `ConflictError` | 409 | Resource in wrong state |
|
|
31
|
+
| `ValidationError` | 422 | Invalid parameters |
|
|
32
|
+
| `RateLimitError` | 429 | Too many requests |
|
|
33
|
+
|
|
34
|
+
All subclasses extend `OblienError`, so a single `instanceof OblienError` catch handles everything while still exposing `.code`/`.message`/`.details`/`.requestId`.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Namespaces
|
|
2
|
+
|
|
3
|
+
Group workspaces into isolated namespaces with **resource limits**, **spending quotas**, lifecycle controls, and **per-namespace usage** — the building block for multi-tenant setups (one namespace per customer/team).
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
const ns = client.namespaces;
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Create + resource limits
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
const { data } = await ns.create({
|
|
13
|
+
name: 'production',
|
|
14
|
+
resource_limits: {
|
|
15
|
+
max_workspaces: 50,
|
|
16
|
+
max_vcpus: 16, // per-workspace cap
|
|
17
|
+
max_ram_mb: 32768,
|
|
18
|
+
max_disk_gb: 100,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Resource limits are enforced **atomically at workspace create and resource upgrade** — over-limit returns `409 NAMESPACE_LIMIT_REACHED`. `max_workspaces` is race-proof (the namespace row is locked + the count re-checked inside the insert transaction). Update them with `ns.update(id, { resource_limits })`.
|
|
24
|
+
|
|
25
|
+
## Lifecycle
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
await ns.suspend(id); // freeze — blocks new creates/starts in the namespace
|
|
29
|
+
await ns.activate(id);
|
|
30
|
+
await ns.stopWorkspaces(id);
|
|
31
|
+
const { namespaces } = await ns.list();
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quotas (optional)
|
|
35
|
+
|
|
36
|
+
Spending limits per service, with overdraft + threshold webhooks. Skip these if you bill externally (use **usage units** below instead).
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
await ns.setQuota({
|
|
40
|
+
namespace: 'production', service: 'sandbox',
|
|
41
|
+
quotaLimit: 500, overdraft: 50,
|
|
42
|
+
onOverdraftAction: 'stop_workspaces',
|
|
43
|
+
notificationThresholds: [80, 95], // fires namespace.quota.threshold webhooks
|
|
44
|
+
});
|
|
45
|
+
await ns.resetQuota({ namespace: 'production', service: 'sandbox' });
|
|
46
|
+
await ns.listWithQuotas();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// Credits view (Oblien's converted units, grouped by service)
|
|
53
|
+
const usage = await ns.usage(id, { days: 30 });
|
|
54
|
+
const details = await ns.getDetails('production'); // quota + usage + transactions
|
|
55
|
+
|
|
56
|
+
// RAW metered units — for external billing with your own rates
|
|
57
|
+
const { data } = await ns.usageUnits('production', {
|
|
58
|
+
from: '2026-06-01T00:00:00Z', to: '2026-06-21T00:00:00Z', groupBy: 'day',
|
|
59
|
+
});
|
|
60
|
+
data.totals.vcpu_hours; // cpu_time_minutes/60, gb_hours, network_gb, disk_io_gb …
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`usageUnits` is deterministic for a range — poll it and idempotency-key on `(namespace, bucket.timestamp)`. The same raw units are also pushed per cycle on the `credits.usage` [webhook](./webhooks.md). Edge metrics (requests/bandwidth) per namespace come from [Analytics](./analytics.md) `home({ namespace })`.
|
|
64
|
+
|
|
65
|
+
## Namespace-scoped API keys
|
|
66
|
+
|
|
67
|
+
A namespace-scoped key is pinned to its namespace: list/get/usage/webhooks all auto-filter to it, and it can't widen scope. `user_id` is always the tenant gate.
|
|
68
|
+
|
|
69
|
+
**Full reference:** [Namespaces API](https://oblien.com/docs/api/namespaces)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Notifications
|
|
2
|
+
|
|
3
|
+
Push notifications to the Oblien mobile app. Flow: **inside a workspace ▶ Oblien ▶ FCM ▶ device**. The app registers a device; you mint **virtual** send tokens scoped to a workspace; code inside that workspace sends with a token. The real device FCM token never leaves Oblien, and a virtual token can be revoked independently.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
const nt = client.notifications;
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Devices
|
|
10
|
+
|
|
11
|
+
The mobile app registers itself. Keyed by a stable `device_id` (the FCM token rotates under it); the token is **validated with FCM before storage**.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
await nt.registerDevice({ device_id: 'dev_abc', fcm_token: '<fcm>', platform: 'ios' });
|
|
15
|
+
await nt.listDevices();
|
|
16
|
+
await nt.removeDevice(deviceId);
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Send tokens (per workspace)
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// Plaintext token returned ONCE — store it.
|
|
23
|
+
const { token } = await nt.createToken({ workspace_id: 'ws_123', name: 'agent' });
|
|
24
|
+
|
|
25
|
+
await nt.listTokens('ws_123');
|
|
26
|
+
await nt.revokeToken(token.id); // soft — stops routing
|
|
27
|
+
await nt.deleteToken(token.id); // permanent
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Send
|
|
31
|
+
|
|
32
|
+
Authenticated by the virtual token itself (not client credentials) — callable from inside a workspace. Fans out to the token owner's active devices.
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
await nt.send(token.token, { title: 'Build done', body: 'Agent finished', data: { run_id: 'abc' } });
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Dead device tokens (app uninstalled) are auto-pruned on send.
|
|
39
|
+
|
|
40
|
+
**Full reference:** [Notifications concept](https://oblien.com/docs/concepts/notifications) · [API](https://oblien.com/docs/api/notifications)
|
package/docs/pages.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Pages
|
|
2
|
+
|
|
3
|
+
Deploy static files from a workspace to the edge CDN. The workspace can stop or be deleted after export — the page stays live with automatic TLS. No running VM required.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// Deploy build output from a workspace
|
|
7
|
+
const { page } = await client.pages.create({
|
|
8
|
+
workspace_id: 'ws_abc',
|
|
9
|
+
path: '/app/dist',
|
|
10
|
+
name: 'my-app',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Re-deploy with fresh files
|
|
14
|
+
await client.pages.deploy(page.slug, { workspace_id: 'ws_abc', path: '/app/dist' });
|
|
15
|
+
|
|
16
|
+
// Custom domain (automatic SSL)
|
|
17
|
+
await client.pages.connectDomain(page.slug, { domain: 'app.example.com' });
|
|
18
|
+
|
|
19
|
+
// Toggle
|
|
20
|
+
await client.pages.disable(page.slug);
|
|
21
|
+
await client.pages.enable(page.slug);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Pages are namespace-aware — pass `namespace` on create to group them and apply namespace scoping.
|
|
25
|
+
|
|
26
|
+
**Full reference:** [Pages concept](https://oblien.com/docs/concepts/pages) · [Pages API](https://oblien.com/docs/api/pages)
|
package/docs/runtime.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Runtime (data plane)
|
|
2
|
+
|
|
3
|
+
The runtime connects to a **running** workspace and gives direct access to its filesystem, command execution, terminal sessions, code search, and file watchers — over the data plane (`workspace.oblien.com`).
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
const rt = await client.workspaces.runtime('ws_abc');
|
|
7
|
+
// or from a scoped handle:
|
|
8
|
+
const rt = await client.workspace('ws_abc').runtime();
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This enables the Runtime API server (if needed), fetches a gateway JWT, and returns a `Runtime` instance. The token is cached — subsequent calls for the same workspace return instantly.
|
|
12
|
+
|
|
13
|
+
## Namespaces
|
|
14
|
+
|
|
15
|
+
| Namespace | What it does |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `rt.files` | List, read, write, stat, mkdir, delete, stream directory trees |
|
|
18
|
+
| `rt.transfer` | Download/upload `tar.gz` archives, with optional progress callbacks |
|
|
19
|
+
| `rt.exec` | Run commands, stream output, list/kill tasks, send stdin |
|
|
20
|
+
| `rt.terminal` | Create PTY sessions, get scrollback, close sessions |
|
|
21
|
+
| `rt.search` | Content search (ripgrep) + filename search |
|
|
22
|
+
| `rt.watcher` | Watch directories for real-time file-change events |
|
|
23
|
+
| `rt.ws()` | WebSocket — persistent connection for terminal I/O and watcher events |
|
|
24
|
+
| `rt.proxy(port)` | Reverse-proxy HTTP/WebSocket to a **loopback** service inside the workspace |
|
|
25
|
+
|
|
26
|
+
## Files
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
const { entries } = await rt.files.list({ dirPath: '/app/src' });
|
|
30
|
+
const file = await rt.files.read({ filePath: '/app/index.js' });
|
|
31
|
+
await rt.files.write({ fullPath: '/app/hello.txt', content: 'hi', createDirs: true });
|
|
32
|
+
await rt.files.delete({ path: '/app/tmp' });
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Exec
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
const result = await rt.exec.run(['node', '--version']);
|
|
39
|
+
console.log(result.stdout);
|
|
40
|
+
|
|
41
|
+
// Stream long-running output
|
|
42
|
+
for await (const event of rt.exec.stream(['npm', 'install'])) {
|
|
43
|
+
process.stdout.write(event.chunk ?? '');
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Search
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
const matches = await rt.search.content({ query: 'handleRequest', path: '/app' });
|
|
51
|
+
const files = await rt.search.files({ query: 'controller' });
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Proxy
|
|
55
|
+
|
|
56
|
+
Reach an HTTP or WebSocket server running on a **loopback port inside the workspace** (e.g. a dev server on `127.0.0.1:3000`) as if you were talking to it directly. Headers, streaming/SSE, and WebSocket upgrades pass through near-natively. For safety the proxy is **loopback-only** — it cannot reach the VM's private network or the internet.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// HTTP — returns a raw fetch Response (status, headers, body/stream)
|
|
60
|
+
const res = await rt.proxy(3000).fetch('/api/users?limit=10');
|
|
61
|
+
const users = await res.json();
|
|
62
|
+
|
|
63
|
+
// POST with a body
|
|
64
|
+
await rt.proxy(3000).fetch('/api/users', {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: { 'content-type': 'application/json' },
|
|
67
|
+
body: JSON.stringify({ name: 'ada' }),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Streaming (SSE) passes through untouched
|
|
71
|
+
const stream = await rt.proxy(8080).fetch('/events');
|
|
72
|
+
for await (const chunk of stream.body!) { /* handle chunk */ }
|
|
73
|
+
|
|
74
|
+
// WebSocket
|
|
75
|
+
const ws = rt.proxy(3000).ws('/socket');
|
|
76
|
+
ws.onmessage = (e) => console.log(e.data);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The runtime connection token is attached automatically (via the `Authorization` header for HTTP, and the `token` query param for WebSocket) and is **stripped before the request reaches the in-VM app**, so it never leaks to your code running inside the workspace.
|
|
80
|
+
|
|
81
|
+
**HTTP transport (advanced).** The SDK calls the runtime endpoint `GET|POST|… /proxy<path>` and selects the target with the `X-Oblien-Proxy-Target: <port>` header. Equivalent forms the runtime also accepts: the `x_oblien_target=<port>` query param (used for WebSocket, since browsers can't set headers), or the path form `/proxy/<port>/<path>`. `token` and `x_oblien_target` are reserved query params and are removed before forwarding.
|
|
82
|
+
|
|
83
|
+
**Full reference:** [Runtime API docs](https://oblien.com/docs/runtime-api)
|
package/docs/serve.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Static server
|
|
2
|
+
|
|
3
|
+
Serve a folder over HTTP and put it on the public internet in one step. The
|
|
4
|
+
server is built entirely on Node's `http`/`fs` — **no extra dependencies** — and
|
|
5
|
+
the CLI wires it straight into the [edge tunnel](./edge-tunnel.md), so a
|
|
6
|
+
directory becomes a public HTTPS URL with a single command.
|
|
7
|
+
|
|
8
|
+
## CLI
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
oblien serve # serve ./public
|
|
12
|
+
oblien serve ./dist # serve ./dist
|
|
13
|
+
oblien serve ./dist --slug my-app # pick the public subdomain
|
|
14
|
+
oblien serve ./build --spa # single-page-app fallback
|
|
15
|
+
oblien serve ./site --domain preview.example.com
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`oblien serve` starts a static server on a loopback port and then forwards it
|
|
19
|
+
through the edge exactly like `oblien tunnel <port>` — so every tunnel flag
|
|
20
|
+
works here too:
|
|
21
|
+
|
|
22
|
+
| Flag | Description |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `--port <n>` | Local port to serve on (default: an OS-assigned free port) |
|
|
25
|
+
| `--slug <slug>` | Custom subdomain slug for the public URL |
|
|
26
|
+
| `--name <name>` | Tunnel display name (default: the folder name) |
|
|
27
|
+
| `--domain <domain>` | Base domain or exact custom hostname (see [edge-tunnel](./edge-tunnel.md)) |
|
|
28
|
+
| `--expires-in <ttl>` | Tunnel token TTL, e.g. `30s`, `5m`, `1h`, `7d` |
|
|
29
|
+
| `--spa` | Serve `index.html` for any unmatched path (client-side routing) |
|
|
30
|
+
| `--no-listing` | Disable auto-generated directory listings |
|
|
31
|
+
| `--index <file>` | Directory index filename (default: `index.html`) |
|
|
32
|
+
|
|
33
|
+
Press `Ctrl+C` to stop — the tunnel disconnects and the local server shuts down.
|
|
34
|
+
|
|
35
|
+
## What the server handles
|
|
36
|
+
|
|
37
|
+
- **Correct MIME types** for common web, font, image, media, and document extensions.
|
|
38
|
+
- **Streamed responses** (`createReadStream`) — large files never buffer in memory.
|
|
39
|
+
- **`Range` requests** — media seeking / resumable downloads return `206 Partial Content`.
|
|
40
|
+
- **Revalidation** — `ETag` + `Last-Modified` yield `304 Not Modified` on conditional requests.
|
|
41
|
+
- **Directory handling** — serves `index.html`, redirects `/dir` → `/dir/`, and renders a clean listing when there's no index (unless `--no-listing`).
|
|
42
|
+
- **SPA fallback** (`--spa`) — unmatched paths return the root `index.html`.
|
|
43
|
+
- **Path-traversal protection** — requests are resolved against the root; anything escaping it lexically is rejected (`403`), and a file whose canonical path (following symlinks) leaves the root is rejected (`404`).
|
|
44
|
+
- **Dotfiles denied by default** — `.git/`, `.env`, and other dot-paths return `404` (the server is often public), except `.well-known/`. Opt in with `allowDotfiles`.
|
|
45
|
+
|
|
46
|
+
## Programmatic use
|
|
47
|
+
|
|
48
|
+
The same server is exported from the SDK, so you can start it yourself and pair
|
|
49
|
+
it with the [`TunnelClient`](./edge-tunnel.md) or use it standalone:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { startStaticServer } from 'oblien';
|
|
53
|
+
|
|
54
|
+
const site = await startStaticServer({
|
|
55
|
+
root: './public',
|
|
56
|
+
spa: true, // optional: index.html fallback
|
|
57
|
+
// port: 8080, // optional: default is an OS-assigned free port
|
|
58
|
+
// host: '127.0.0.1' // optional: default loopback only
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
console.log(`serving on ${site.url}`); // http://127.0.0.1:<port>
|
|
62
|
+
|
|
63
|
+
// ...later
|
|
64
|
+
await site.close();
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`startStaticServer(options)` resolves once the server is listening and returns a
|
|
68
|
+
handle: `{ port, host, url, server, close() }`. The `port` is always resolved —
|
|
69
|
+
even when you omit it and let the OS choose. For finer control, `createStaticServer(options)`
|
|
70
|
+
returns a non-listening `http.Server`, and `createStaticHandler(options)` returns a
|
|
71
|
+
raw `(req, res)` handler you can mount on your own server.
|
|
72
|
+
|
|
73
|
+
### Options
|
|
74
|
+
|
|
75
|
+
| Option | Type | Default | Description |
|
|
76
|
+
| --- | --- | --- | --- |
|
|
77
|
+
| `root` | `string` | — | Directory to serve (resolved against cwd) |
|
|
78
|
+
| `host` | `string` | `127.0.0.1` | Interface to bind |
|
|
79
|
+
| `port` | `number` | `0` (auto) | Port to listen on |
|
|
80
|
+
| `index` | `string` | `index.html` | Directory index filename |
|
|
81
|
+
| `listing` | `boolean` | `true` | Render a listing when a directory has no index |
|
|
82
|
+
| `spa` | `boolean` | `false` | Serve the root index for unmatched paths |
|
|
83
|
+
| `allowDotfiles` | `boolean` | `false` | Serve dotfiles/dot-directories (`.well-known/` is always allowed) |
|
|
84
|
+
| `onRequest` | `(info) => void` | — | Per-request hook: `{ method, path, status }` |
|
package/docs/tokens.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Scoped Tokens
|
|
2
|
+
|
|
3
|
+
Issue short-lived, scoped JWTs for your end-users instead of sharing long-lived `clientId`/`clientSecret`. Ideal for SaaS: hand each tenant a token bound to their namespace (or a single workspace) that expires on its own.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
const { token, expiresAt, scope } = await client.tokens.create({
|
|
7
|
+
scope: 'namespace', // 'namespace' | 'workspace'
|
|
8
|
+
namespace: 'team-a', // required for namespace scope
|
|
9
|
+
ttl: 900, // seconds (max 3600, default 900)
|
|
10
|
+
label: 'tenant-team-a',
|
|
11
|
+
});
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Use the returned token as the `token` option on a new client:
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
const scoped = new Oblien({ token });
|
|
18
|
+
// scoped is pinned to team-a — list/get/usage/webhooks auto-filter to it,
|
|
19
|
+
// and it can't widen scope.
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The token is self-contained (signature + expiry) — no DB lookup. Revoke early by blacklisting its `jti` server-side if needed.
|
|
23
|
+
|
|
24
|
+
**Full reference:** [Scoped tokens API](https://oblien.com/docs/api/scoped-tokens)
|
package/docs/webhooks.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Webhooks
|
|
2
|
+
|
|
3
|
+
Subscribe an HTTPS endpoint to platform events (vm/workload lifecycle, credit usage, namespace quota thresholds), signed with HMAC-SHA256 (`X-Webhook-Signature`). A webhook can be **scoped to a namespace** so it only receives that namespace's events — or left account-wide.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
const wh = client.webhooks;
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Event types
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
const { events } = await wh.events();
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Includes: `vm.stopped`, `vm.archived`, `workload.started|exited|failed|stopped|restart_loop`, `credits.usage`, `credits.low`, `credits.depleted`, `namespace.quota.threshold`.
|
|
16
|
+
|
|
17
|
+
## Create (namespace-scoped or account-wide)
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
const { webhook } = await wh.create({
|
|
21
|
+
url: 'https://example.com/hooks/oblien',
|
|
22
|
+
events: ['credits.usage', 'namespace.quota.threshold', 'credits.depleted'],
|
|
23
|
+
namespace: 'team-a', // ← only team-a's events; omit for account-wide
|
|
24
|
+
secret: process.env.WH_SECRET, // signs deliveries
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
A namespace-bound webhook receives only events that carry that namespace; account-wide webhooks receive everything. Namespace-scoped API keys are auto-pinned to their namespace.
|
|
29
|
+
|
|
30
|
+
## Manage
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
const { webhooks } = await wh.list({ namespace: 'team-a' });
|
|
34
|
+
await wh.update(webhook.id, { active: false });
|
|
35
|
+
await wh.delete(webhook.id);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage metering via webhook
|
|
39
|
+
|
|
40
|
+
`credits.usage` is emitted per billing cycle with `{ namespace, workspace_id, usage: { cpu_time_minutes, memory_gb_minutes, disk_io_gb, network_gb } }` — a push alternative to polling [`namespaces.usageUnits`](./namespaces.md). Idempotent per event.
|
|
41
|
+
|
|
42
|
+
**Full reference:** [Webhooks concept](https://oblien.com/docs/concepts/webhooks)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Workspaces
|
|
2
|
+
|
|
3
|
+
A workspace is a hardware-isolated microVM that boots in under a second from any Docker image. It's the only primitive in Oblien — everything else (pages, proxies, domains) hangs off a workspace or the account.
|
|
4
|
+
|
|
5
|
+
`client.workspaces` is the control-plane resource: create, configure, power, and destroy workspaces, plus a tree of sub-resources. To interact with a *running* workspace's filesystem/exec/terminal, see **[Runtime](./runtime.md)**.
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
const ws = client.workspaces;
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## CRUD
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
const workspace = await ws.create({
|
|
15
|
+
image: 'node-20', // any Docker image
|
|
16
|
+
mode: 'permanent', // 'permanent' | 'temporary'
|
|
17
|
+
cpus: 2,
|
|
18
|
+
memory_mb: 4096,
|
|
19
|
+
namespace: 'team-a', // optional — groups + applies namespace limits/quotas
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const { workspaces } = await ws.list();
|
|
23
|
+
const data = await ws.get(workspace.id);
|
|
24
|
+
await ws.update(workspace.id, { name: 'my-api' });
|
|
25
|
+
await ws.delete(workspace.id);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Creating in a namespace enforces that namespace's **resource limits** (max workspaces, per-workspace vCPU/RAM/disk) atomically, and **status** (a suspended/over-quota namespace rejects new workspaces). See **[Namespaces](./namespaces.md)**.
|
|
29
|
+
|
|
30
|
+
## Power
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
await ws.start(id);
|
|
34
|
+
await ws.stop(id);
|
|
35
|
+
await ws.restart(id);
|
|
36
|
+
await ws.pause(id); // freeze to disk
|
|
37
|
+
await ws.resume(id); // thaw
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Scoped handle
|
|
41
|
+
|
|
42
|
+
Bind to one workspace so you never pass the ID again:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
const handle = client.workspace('ws_abc');
|
|
46
|
+
|
|
47
|
+
await handle.start();
|
|
48
|
+
const rt = await handle.runtime();
|
|
49
|
+
await rt.exec.run(['npm', 'test']);
|
|
50
|
+
await handle.stop();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Runtime
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
const rt = await client.workspaces.runtime('ws_abc');
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Enables the Runtime API server, fetches + caches a gateway JWT, and returns a `Runtime` instance for the data plane (files, exec, terminal, search, watcher, transfer, WebSocket). → **[Runtime](./runtime.md)**.
|
|
60
|
+
|
|
61
|
+
## Sub-resources
|
|
62
|
+
|
|
63
|
+
Accessed as namespaces on `client.workspaces`:
|
|
64
|
+
|
|
65
|
+
| Sub-resource | What it does |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `lifecycle` | Permanent/temporary mode, TTL, ping |
|
|
68
|
+
| `network` | Firewall, private links, outbound IP |
|
|
69
|
+
| `ssh` | SSH enable/disable, password/key |
|
|
70
|
+
| `publicAccess` | Expose ports with public URLs + automatic TLS |
|
|
71
|
+
| `domains` | Custom domains with automatic SSL ([Domains](./domains.md)) |
|
|
72
|
+
| `resources` | CPU / memory / disk allocation (upgrade — namespace caps enforced) |
|
|
73
|
+
| `snapshots` | Snapshots and versioned archives |
|
|
74
|
+
| `workloads` | Managed background processes |
|
|
75
|
+
| `metrics` | Live stats, VM info/config |
|
|
76
|
+
| `usage` | Credit usage + activity tracking |
|
|
77
|
+
| `metadata` | Key-value metadata store |
|
|
78
|
+
| `apiAccess` | Runtime API tokens (gateway JWT / raw) |
|
|
79
|
+
| `logs` | Boot and command logs |
|
|
80
|
+
| `images` | Available base images |
|
|
81
|
+
|
|
82
|
+
**Full reference:** [API docs](https://oblien.com/docs/api/workspaces)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oblien",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.38",
|
|
4
4
|
"description": "Official TypeScript SDK for the Oblien Workspace API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -24,7 +24,10 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
|
-
"dist"
|
|
27
|
+
"dist",
|
|
28
|
+
"docs",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
28
31
|
],
|
|
29
32
|
"scripts": {
|
|
30
33
|
"build": "tsc",
|