@nimbus-sh/sdk 0.1.0

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 ADDED
@@ -0,0 +1,254 @@
1
+ # @nimbus-sh/sdk
2
+
3
+ SDK for Nimbus — programmatic sandbox handles, token mint/verify, typed
4
+ errors, and session URL helpers.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npm install @nimbus-sh/sdk @nimbus-sh/worker @nimbus-sh/config
10
+ ```
11
+
12
+ The sandbox SDK is meant to run inside the Worker or Durable Object that owns
13
+ the `NIMBUS_SESSION` binding. A minimal project setup is:
14
+
15
+ ```bash
16
+ npx @nimbus-sh/cli init my-nimbus-worker
17
+ cd my-nimbus-worker
18
+ npm install
19
+ wrangler secret put JWT_SECRET
20
+ wrangler deploy
21
+ ```
22
+
23
+ From this repository, use the source CLI:
24
+
25
+ ```bash
26
+ bun packages/cli/src/bin.ts init my-nimbus-worker
27
+ cd my-nimbus-worker
28
+ bun install
29
+ wrangler secret put JWT_SECRET
30
+ wrangler deploy
31
+ ```
32
+
33
+ ## Programmatic sandbox quickstart
34
+
35
+ Use `@nimbus-sh/sdk/sandbox` inside the Worker or Durable Object that embeds
36
+ Nimbus. The SDK talks to the `NIMBUS_SESSION` Durable Object binding via DO
37
+ RPC; it does not need a browser WebSocket.
38
+
39
+ ```ts
40
+ import { Nimbus } from '@nimbus-sh/sdk/sandbox';
41
+ import { defineNimbusConfig } from '@nimbus-sh/config';
42
+
43
+ const nimbusConfig = defineNimbusConfig({
44
+ endpoint: 'https://my-nimbus.workers.dev',
45
+ sandboxes: {
46
+ proteus: {
47
+ root: '/home/user',
48
+ tools: { namespace: 'sandbox', kind: 'sandbox' },
49
+ runtimes: {
50
+ preinstall: ['python', 'clang'],
51
+ onDemand: true,
52
+ allow: ['node', 'bun', 'npm', 'git', 'python', 'ruby', 'clang', 'shell'],
53
+ },
54
+ },
55
+ },
56
+ });
57
+
58
+ export default {
59
+ async fetch(_request: Request, env: Env) {
60
+ const box = Nimbus.fromEnv(env, nimbusConfig).sandbox('job-123', {
61
+ profile: 'proteus',
62
+ tenant: 'acme',
63
+ subject: 'agent',
64
+ });
65
+
66
+ await box.files.write('/home/user/app/main.py', 'print(2 + 2)\n');
67
+ await box.runtimes.ensure('python');
68
+ const result = await box.exec('python /home/user/app/main.py');
69
+
70
+ return Response.json(result);
71
+ },
72
+ };
73
+ ```
74
+
75
+ ## Sandbox handle API
76
+
77
+ ```ts
78
+ const nimbus = Nimbus.fromEnv(env, config, { binding: 'NIMBUS_SESSION' });
79
+ const box = nimbus.sandbox('session-or-job-id', {
80
+ profile: 'default',
81
+ tenant: 'acme',
82
+ subject: 'agent-7',
83
+ root: '/home/user',
84
+ });
85
+
86
+ await box.ready();
87
+ await box.exec('node -e "console.log(2 + 2)"');
88
+ await box.startProcess('node --watch /home/user/app/server.js');
89
+ await box.runCode('print(2 + 2)', { language: 'python', install: 'ifMissing' });
90
+
91
+ await box.files.write('/home/user/app/a.txt', 'hello');
92
+ await box.files.read('/home/user/app/a.txt');
93
+ await box.files.list('/home/user/app');
94
+ await box.files.delete('/home/user/app/a.txt');
95
+
96
+ await box.runtimes.available();
97
+ await box.runtimes.installed();
98
+ await box.runtimes.install('python');
99
+ await box.runtimes.ensure(['python', 'clang']);
100
+
101
+ await box.processes.list();
102
+ await box.processes.logs(7);
103
+ await box.processes.kill(7);
104
+
105
+ const port = await box.ports.expose(3000);
106
+ // port.url => https://my-nimbus.workers.dev/s/<session-or-job-id>/port/3000/
107
+ await box.ports.unexpose(3000);
108
+ ```
109
+
110
+ Runtime policy comes from the sandbox profile:
111
+
112
+ - `runtimes.preinstall` is applied by `box.ready()`.
113
+ - `runtimes.allow` gates SDK runtime operations and `runCode()` language use.
114
+ - `runtimes.onDemand: false` blocks installing runtimes that are not listed
115
+ in `preinstall`.
116
+
117
+ ## Proteus-style tools
118
+
119
+ `box.tools({ namespace: 'sandbox', kind: 'sandbox' })` returns a provider-like
120
+ object with `tools.exec.execute`, `runCode`, `readFile`, `writeFile`,
121
+ `listFiles`/`readdir`, `deleteFile`, `exists`, `startProcess`, `killProcess`,
122
+ `logs`, `exposePort`, `unexposePort`, `listPorts`, `installRuntime`, and
123
+ `listRuntimes`.
124
+
125
+ `provider.capabilities` is intentionally honest: Nimbus claims shell,
126
+ JavaScript/TypeScript, npm, git, owned filesystem, outbound fetch, inbound
127
+ HTTP-like port routing, process spawn/long-running processes, Python/Ruby
128
+ when allowed, and clang as WASI-native. It does not claim Docker, apt, GPU,
129
+ custom Linux images, native Linux ELF execution, or raw TCP listeners.
130
+
131
+ ## Quickstart — mint a session token
132
+
133
+ ```ts
134
+ import { issueNimbusToken } from '@nimbus-sh/sdk/token';
135
+
136
+ const token = await issueNimbusToken(
137
+ { JWT_SECRET: process.env.JWT_SECRET! },
138
+ { tn: 'acme', sub: 'alice' },
139
+ { ttlMs: 60 * 60 * 1000 }, // 1h
140
+ );
141
+ // → 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6Im5pbWJ1cyIsInRu...'
142
+ ```
143
+
144
+ ## Verify a token (typically in a Worker)
145
+
146
+ ```ts
147
+ import { verifyNimbusToken } from '@nimbus-sh/sdk/token';
148
+
149
+ try {
150
+ const { claims, doInstanceName } = await verifyNimbusToken(env, token);
151
+ // claims.tn, claims.sub, claims.scopes, claims.sid
152
+ // doInstanceName = `${tn}:${sub || '_'}` — feed to idFromName
153
+ } catch (e) {
154
+ // every error extends NimbusAuthError with stable .code + .httpStatus
155
+ }
156
+ ```
157
+
158
+ ## Typed errors
159
+
160
+ ```ts
161
+ import {
162
+ NimbusAuthError,
163
+ NimbusTokenExpiredError,
164
+ NimbusScopeError,
165
+ } from '@nimbus-sh/sdk/errors';
166
+
167
+ try { await verifyNimbusToken(env, token); }
168
+ catch (e) {
169
+ if (e instanceof NimbusTokenExpiredError) return Response.json({ refresh: true }, { status: 401 });
170
+ if (e instanceof NimbusScopeError) return Response.json({ scope: e.requiredScope }, { status: 403 });
171
+ if (e instanceof NimbusAuthError) return Response.json({ error: e.message, code: e.code }, { status: e.httpStatus });
172
+ throw e;
173
+ }
174
+ ```
175
+
176
+ | Error class | code | httpStatus |
177
+ |---|---|---|
178
+ | `NimbusAuthConfigError` | `E_AUTH_CONFIG_MISSING` | 500 |
179
+ | `NimbusTokenMalformedError` | `E_TOKEN_MALFORMED` | 401 |
180
+ | `NimbusTokenSignatureError` | `E_TOKEN_SIGNATURE` | 401 |
181
+ | `NimbusTokenClaimsError` | `E_TOKEN_CLAIMS` | 401 |
182
+ | `NimbusTokenExpiredError` | `E_TOKEN_EXPIRED` | 401 |
183
+ | `NimbusTokenTtlError` | `E_TOKEN_TTL_TOO_LARGE` | 400 |
184
+ | `NimbusScopeError` | `E_SCOPE_MISSING` | 403 |
185
+ | `NimbusSessionPinError` | `E_SESSION_PIN_MISMATCH` | 403 |
186
+
187
+ ## Session URL helpers
188
+
189
+ ```ts
190
+ import { sessionAttachUrl, mintAndAttach } from '@nimbus-sh/sdk';
191
+
192
+ const url = sessionAttachUrl(
193
+ 'https://my-nimbus.workers.dev',
194
+ 'pretty-otter-1234',
195
+ token,
196
+ );
197
+ // → "https://my-nimbus.workers.dev/s/pretty-otter-1234/?nimbus_token=…"
198
+
199
+ // Or combined in one call:
200
+ const { token, url } = await mintAndAttach(
201
+ env,
202
+ { tn: 'acme', sub: 'alice' },
203
+ { endpoint: 'https://my-nimbus.workers.dev', sessionId: 'pretty-otter-1234' },
204
+ );
205
+ ```
206
+
207
+ ## Subpath exports
208
+
209
+ | Subpath | What |
210
+ |---|---|
211
+ | `@nimbus-sh/sdk` | Everything re-exported from a single entry. |
212
+ | `@nimbus-sh/sdk/token` | `issueNimbusToken`, `verifyNimbusToken`, types. |
213
+ | `@nimbus-sh/sdk/errors` | `NimbusAuthError` class hierarchy. |
214
+ | `@nimbus-sh/sdk/session` | `sessionAttachUrl`, `mintAndAttach`. |
215
+ | `@nimbus-sh/sdk/sandbox` | `Nimbus`, `NimbusSandbox`, programmatic exec/files/runtimes/processes/ports/tools. |
216
+
217
+ ## Token wire format
218
+
219
+ JWT (HS256) with these claims:
220
+
221
+ ```ts
222
+ {
223
+ scope: 'nimbus', // always — discriminator vs other JWTs
224
+ tn: 'acme', // tenant (required)
225
+ sub?: 'alice', // user within tenant (optional)
226
+ scopes?: ['session:create','session:attach'], // capability list
227
+ sid?: 'pretty-otter-1234', // pin to a specific session (optional)
228
+ iat: 1731612345, // issued-at (UNIX seconds)
229
+ exp: 1731615945, // expires-at (UNIX seconds)
230
+ }
231
+ ```
232
+
233
+ Both `tn` and `sub` must match `[A-Za-z0-9._-]{1,128}` — exported as
234
+ `ID_COMPONENT_RE` if you need to validate user input.
235
+
236
+ The `scope` discriminator means a token minted for another product
237
+ (e.g. Mossaic VFS, `scope: "vfs"`) is rejected even when signed with
238
+ the same secret.
239
+
240
+ ## Secret rotation
241
+
242
+ Set `JWT_SECRET_PREVIOUS` during a rotation window. Both old and new
243
+ secrets verify; new tokens are always signed with the primary.
244
+
245
+ ```bash
246
+ # Phase 1: New secret in place, old secret as fallback.
247
+ wrangler secret put JWT_SECRET # the new one
248
+ wrangler secret put JWT_SECRET_PREVIOUS # the old one
249
+
250
+ # Phase 2 (after the longest token TTL has elapsed):
251
+ wrangler secret delete JWT_SECRET_PREVIOUS
252
+ ```
253
+
254
+ MIT.
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @nimbus-sh/sdk/errors — Typed error classes re-exported from the
3
+ * worker auth module.
4
+ *
5
+ * Every error in `@nimbus-sh/sdk` extends `NimbusAuthError`, which has
6
+ * a stable `.code` property for catch-site discrimination and an
7
+ * `.httpStatus` hint for response mapping. The class hierarchy is
8
+ * `instanceof`-friendly: a blanket `catch (e instanceof NimbusAuthError)`
9
+ * catches every subclass.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import {
14
+ * NimbusAuthError,
15
+ * NimbusTokenExpiredError,
16
+ * NimbusScopeError,
17
+ * } from '@nimbus-sh/sdk/errors';
18
+ *
19
+ * try {
20
+ * await verifyNimbusToken(env, token);
21
+ * } catch (e) {
22
+ * if (e instanceof NimbusTokenExpiredError) {
23
+ * return Response.json({ refresh: true }, { status: 401 });
24
+ * }
25
+ * if (e instanceof NimbusScopeError) {
26
+ * return Response.json({ missingScope: e.requiredScope }, { status: 403 });
27
+ * }
28
+ * if (e instanceof NimbusAuthError) {
29
+ * return Response.json({ error: e.message, code: e.code }, { status: e.httpStatus });
30
+ * }
31
+ * throw e;
32
+ * }
33
+ * ```
34
+ */
35
+ export { NimbusAuthError, NimbusAuthConfigError, NimbusTokenMalformedError, NimbusTokenSignatureError, NimbusTokenClaimsError, NimbusTokenExpiredError, NimbusTokenTtlError, NimbusScopeError, NimbusSessionPinError, } from '@nimbus-sh/worker/auth';
36
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,yBAAyB,EACzB,yBAAyB,EACzB,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,EACnB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC"}
package/dist/errors.js ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @nimbus-sh/sdk/errors — Typed error classes re-exported from the
3
+ * worker auth module.
4
+ *
5
+ * Every error in `@nimbus-sh/sdk` extends `NimbusAuthError`, which has
6
+ * a stable `.code` property for catch-site discrimination and an
7
+ * `.httpStatus` hint for response mapping. The class hierarchy is
8
+ * `instanceof`-friendly: a blanket `catch (e instanceof NimbusAuthError)`
9
+ * catches every subclass.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import {
14
+ * NimbusAuthError,
15
+ * NimbusTokenExpiredError,
16
+ * NimbusScopeError,
17
+ * } from '@nimbus-sh/sdk/errors';
18
+ *
19
+ * try {
20
+ * await verifyNimbusToken(env, token);
21
+ * } catch (e) {
22
+ * if (e instanceof NimbusTokenExpiredError) {
23
+ * return Response.json({ refresh: true }, { status: 401 });
24
+ * }
25
+ * if (e instanceof NimbusScopeError) {
26
+ * return Response.json({ missingScope: e.requiredScope }, { status: 403 });
27
+ * }
28
+ * if (e instanceof NimbusAuthError) {
29
+ * return Response.json({ error: e.message, code: e.code }, { status: e.httpStatus });
30
+ * }
31
+ * throw e;
32
+ * }
33
+ * ```
34
+ */
35
+ export { NimbusAuthError, NimbusAuthConfigError, NimbusTokenMalformedError, NimbusTokenSignatureError, NimbusTokenClaimsError, NimbusTokenExpiredError, NimbusTokenTtlError, NimbusScopeError, NimbusSessionPinError, } from '@nimbus-sh/worker/auth';
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @nimbus-sh/sdk — Client SDK for Nimbus.
3
+ *
4
+ * The SDK is the **environment-agnostic** half of Nimbus: token mint /
5
+ * verify, typed errors, session-handle types, and the HTTP fallback for
6
+ * non-Worker consumers. Everything in this package is safe to import
7
+ * from a Node SSR layer, a Worker, an Edge function, or a browser
8
+ * build.
9
+ *
10
+ * For the Worker runtime itself (the Durable Object, the router, the
11
+ * facet machinery), see `@nimbus-sh/worker`.
12
+ *
13
+ * @example mint a token in your Worker's `/api/auth/mint` route
14
+ * ```ts
15
+ * import { issueNimbusToken } from '@nimbus-sh/sdk/token';
16
+ * export default {
17
+ * async fetch(req: Request, env: Env) {
18
+ * const { tenant, sub } = await req.json();
19
+ * const token = await issueNimbusToken(env, { tn: tenant, sub });
20
+ * return Response.json({ token });
21
+ * },
22
+ * };
23
+ * ```
24
+ */
25
+ export * from './token.js';
26
+ export * from './errors.js';
27
+ export * from './session.js';
28
+ export * from './sandbox.js';
29
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @nimbus-sh/sdk — Client SDK for Nimbus.
3
+ *
4
+ * The SDK is the **environment-agnostic** half of Nimbus: token mint /
5
+ * verify, typed errors, session-handle types, and the HTTP fallback for
6
+ * non-Worker consumers. Everything in this package is safe to import
7
+ * from a Node SSR layer, a Worker, an Edge function, or a browser
8
+ * build.
9
+ *
10
+ * For the Worker runtime itself (the Durable Object, the router, the
11
+ * facet machinery), see `@nimbus-sh/worker`.
12
+ *
13
+ * @example mint a token in your Worker's `/api/auth/mint` route
14
+ * ```ts
15
+ * import { issueNimbusToken } from '@nimbus-sh/sdk/token';
16
+ * export default {
17
+ * async fetch(req: Request, env: Env) {
18
+ * const { tenant, sub } = await req.json();
19
+ * const token = await issueNimbusToken(env, { tn: tenant, sub });
20
+ * return Response.json({ token });
21
+ * },
22
+ * };
23
+ * ```
24
+ */
25
+ export * from './token.js';
26
+ export * from './errors.js';
27
+ export * from './session.js';
28
+ export * from './sandbox.js';
@@ -0,0 +1,271 @@
1
+ /**
2
+ * @nimbus-sh/sdk/sandbox - programmatic Nimbus sandbox handle.
3
+ */
4
+ export type RuntimeSpec = string;
5
+ export type RuntimeName = 'node' | 'bun' | 'npm' | 'git' | 'python' | 'ruby' | 'clang' | 'shell' | (string & {});
6
+ export interface NimbusRuntimePolicy {
7
+ preinstall?: RuntimeSpec[];
8
+ onDemand?: boolean;
9
+ allow?: RuntimeName[];
10
+ }
11
+ export interface NimbusSandboxProfile {
12
+ root?: string;
13
+ runtimes?: NimbusRuntimePolicy;
14
+ tools?: {
15
+ namespace?: string;
16
+ kind?: string;
17
+ };
18
+ preview?: {
19
+ baseUrl?: string;
20
+ pathStyle?: boolean;
21
+ };
22
+ }
23
+ export interface NimbusConfig {
24
+ endpoint?: string;
25
+ sandboxes?: Record<string, NimbusSandboxProfile>;
26
+ }
27
+ export interface NimbusFromEnvOptions {
28
+ binding?: string;
29
+ endpoint?: string;
30
+ }
31
+ export interface NimbusSandboxOptions {
32
+ profile?: string;
33
+ tenant?: string;
34
+ subject?: string;
35
+ root?: string;
36
+ }
37
+ export interface NimbusExecOptions {
38
+ cwd?: string;
39
+ env?: Record<string, string>;
40
+ timeoutMs?: number;
41
+ stdin?: string;
42
+ }
43
+ export interface NimbusExecResult {
44
+ command: string;
45
+ exitCode: number;
46
+ success: boolean;
47
+ stdout: string;
48
+ stderr: string;
49
+ duration: number;
50
+ timestamp: number;
51
+ }
52
+ export interface NimbusStartResult extends NimbusExecResult {
53
+ pid: number | null;
54
+ process: NimbusProcess | null;
55
+ ports: NimbusPort[];
56
+ }
57
+ export interface NimbusProcess {
58
+ pid: number;
59
+ command: string;
60
+ argv: string[];
61
+ cwd: string;
62
+ state: string;
63
+ exitCode: number | null;
64
+ startTime: number;
65
+ endTime: number | null;
66
+ longRunning: boolean;
67
+ }
68
+ export interface NimbusPort {
69
+ port: number;
70
+ pid: number;
71
+ registeredAt: number;
72
+ }
73
+ export interface NimbusRuntimeSummary {
74
+ name: string;
75
+ version: string;
76
+ root: string;
77
+ bins: string[];
78
+ sizeBytes: number;
79
+ license: string;
80
+ }
81
+ export interface NimbusAvailableRuntime {
82
+ name: string;
83
+ defaultVersion: string;
84
+ versions: Array<{
85
+ version: string;
86
+ sizeBytes: number;
87
+ license: string;
88
+ }>;
89
+ }
90
+ type NimbusSessionNamespace = DurableObjectNamespace<any>;
91
+ export declare class Nimbus {
92
+ private readonly namespace;
93
+ private readonly config;
94
+ static fromEnv(env: Record<string, unknown>, config?: NimbusConfig, options?: NimbusFromEnvOptions): Nimbus;
95
+ constructor(namespace: NimbusSessionNamespace, config?: NimbusConfig);
96
+ sandbox(id: string, options?: NimbusSandboxOptions): NimbusSandbox;
97
+ }
98
+ export declare class NimbusSandbox {
99
+ private readonly namespace;
100
+ private readonly options;
101
+ private readonly config;
102
+ readonly id: string;
103
+ readonly profileName: string;
104
+ private readonly profile;
105
+ private readyPromise;
106
+ constructor(namespace: NimbusSessionNamespace, id: string, options: NimbusSandboxOptions, config: NimbusConfig);
107
+ private get tenantSegment();
108
+ private get doName();
109
+ private get root();
110
+ private stub;
111
+ ready(): Promise<void>;
112
+ exec(command: string, options?: NimbusExecOptions): Promise<NimbusExecResult>;
113
+ startProcess(command: string, options?: NimbusExecOptions): Promise<NimbusStartResult>;
114
+ runCode(code: string, options?: NimbusExecOptions & {
115
+ language?: 'javascript' | 'typescript' | 'python' | 'ruby' | 'shell';
116
+ install?: 'never' | 'ifMissing';
117
+ }): Promise<NimbusExecResult>;
118
+ files: {
119
+ read: (path: string) => Promise<string | null>;
120
+ readBytes: (path: string) => Promise<Uint8Array | null>;
121
+ write: (path: string, content: string | Uint8Array) => Promise<void>;
122
+ list: (path?: string) => Promise<{
123
+ name: string;
124
+ type: string;
125
+ }[]>;
126
+ mkdir: (path: string) => Promise<void>;
127
+ exists: (path: string) => Promise<boolean>;
128
+ delete: (path: string, options?: {
129
+ recursive?: boolean;
130
+ }) => Promise<void>;
131
+ };
132
+ runtimes: {
133
+ available: () => Promise<NimbusAvailableRuntime[]>;
134
+ installed: () => Promise<NimbusRuntimeSummary[]>;
135
+ list: () => Promise<{
136
+ installed: NimbusRuntimeSummary[];
137
+ available: NimbusAvailableRuntime[];
138
+ }>;
139
+ install: (spec: RuntimeSpec, options?: {
140
+ force?: boolean;
141
+ }) => Promise<unknown>;
142
+ ensure: (specs: RuntimeSpec | RuntimeSpec[], options?: {
143
+ force?: boolean;
144
+ }) => Promise<unknown>;
145
+ };
146
+ processes: {
147
+ list: () => Promise<NimbusProcess[]>;
148
+ kill: (pid: number) => Promise<{
149
+ ok: boolean;
150
+ pid: number;
151
+ }>;
152
+ logs: (pid: number, options?: {
153
+ lines?: number;
154
+ bytes?: number;
155
+ }) => Promise<unknown>;
156
+ };
157
+ ports: {
158
+ list: () => Promise<NimbusPort[]>;
159
+ expose: (port: number) => Promise<{
160
+ url: string | undefined;
161
+ port: number;
162
+ listening: boolean;
163
+ pid: number | null;
164
+ registeredAt: number | null;
165
+ }>;
166
+ unexpose: (port: number) => Promise<{
167
+ port: number;
168
+ ok: boolean;
169
+ }>;
170
+ url: (port: number) => string | undefined;
171
+ };
172
+ tools(options?: {
173
+ namespace?: string;
174
+ kind?: string;
175
+ name?: string;
176
+ }): {
177
+ name: string;
178
+ kind: string;
179
+ capabilities: string[];
180
+ isAvailable: () => Promise<boolean>;
181
+ connect: () => Promise<void>;
182
+ disconnect: () => Promise<undefined>;
183
+ tools: {
184
+ exec: {
185
+ execute: (command: string, opts?: NimbusExecOptions) => Promise<NimbusExecResult>;
186
+ };
187
+ runCode: {
188
+ execute: (code: string, opts?: Parameters<NimbusSandbox["runCode"]>[1]) => Promise<NimbusExecResult>;
189
+ };
190
+ readFile: {
191
+ execute: (input: unknown) => Promise<string | null>;
192
+ };
193
+ writeFile: {
194
+ execute: (input: any) => Promise<void>;
195
+ };
196
+ listFiles: {
197
+ execute: (input?: unknown) => Promise<{
198
+ name: string;
199
+ type: string;
200
+ }[]>;
201
+ };
202
+ readdir: {
203
+ execute: (input?: unknown) => Promise<{
204
+ name: string;
205
+ type: string;
206
+ }[]>;
207
+ };
208
+ deleteFile: {
209
+ execute: (input: any) => Promise<void>;
210
+ };
211
+ exists: {
212
+ execute: (input: unknown) => Promise<boolean>;
213
+ };
214
+ startProcess: {
215
+ execute: (command: string, opts?: NimbusExecOptions) => Promise<NimbusStartResult>;
216
+ };
217
+ killProcess: {
218
+ execute: (input: number | {
219
+ pid: number;
220
+ }) => Promise<{
221
+ ok: boolean;
222
+ pid: number;
223
+ }>;
224
+ };
225
+ logs: {
226
+ execute: (input: number | {
227
+ pid: number;
228
+ lines?: number;
229
+ bytes?: number;
230
+ }) => Promise<unknown>;
231
+ };
232
+ exposePort: {
233
+ execute: (input: number | {
234
+ port: number;
235
+ }) => Promise<{
236
+ url: string | undefined;
237
+ port: number;
238
+ listening: boolean;
239
+ pid: number | null;
240
+ registeredAt: number | null;
241
+ }>;
242
+ };
243
+ unexposePort: {
244
+ execute: (input: number | {
245
+ port: number;
246
+ }) => Promise<{
247
+ port: number;
248
+ ok: boolean;
249
+ }>;
250
+ };
251
+ listPorts: {
252
+ execute: () => Promise<NimbusPort[]>;
253
+ };
254
+ installRuntime: {
255
+ execute: (spec: RuntimeSpec) => Promise<unknown>;
256
+ };
257
+ listRuntimes: {
258
+ execute: () => Promise<{
259
+ installed: NimbusRuntimeSummary[];
260
+ available: NimbusAvailableRuntime[];
261
+ }>;
262
+ };
263
+ };
264
+ };
265
+ capabilities(): string[];
266
+ private execOptions;
267
+ private assertRuntimeAllowed;
268
+ private portUrl;
269
+ }
270
+ export {};
271
+ //# sourceMappingURL=sandbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,259 @@
1
+ /**
2
+ * @nimbus-sh/sdk/sandbox - programmatic Nimbus sandbox handle.
3
+ */
4
+ export class Nimbus {
5
+ namespace;
6
+ config;
7
+ static fromEnv(env, config = {}, options = {}) {
8
+ const bindingName = options.binding ?? 'NIMBUS_SESSION';
9
+ const binding = env[bindingName];
10
+ if (!binding) {
11
+ throw new Error(`Nimbus.fromEnv: env.${bindingName} Durable Object binding is missing`);
12
+ }
13
+ return new Nimbus(binding, {
14
+ ...config,
15
+ endpoint: options.endpoint ?? config.endpoint,
16
+ });
17
+ }
18
+ constructor(namespace, config = {}) {
19
+ this.namespace = namespace;
20
+ this.config = config;
21
+ }
22
+ sandbox(id, options = {}) {
23
+ return new NimbusSandbox(this.namespace, String(id), options, this.config);
24
+ }
25
+ }
26
+ export class NimbusSandbox {
27
+ namespace;
28
+ options;
29
+ config;
30
+ id;
31
+ profileName;
32
+ profile;
33
+ readyPromise = null;
34
+ constructor(namespace, id, options, config) {
35
+ this.namespace = namespace;
36
+ this.options = options;
37
+ this.config = config;
38
+ this.id = id;
39
+ this.profileName = options.profile ?? 'default';
40
+ this.profile = config.sandboxes?.[this.profileName] ?? config.sandboxes?.default ?? {};
41
+ }
42
+ get tenantSegment() {
43
+ const tenant = sanitizeSegment(this.options.tenant ?? 'default');
44
+ const subject = sanitizeSegment(this.options.subject ?? '_');
45
+ return `${tenant}:${subject}`;
46
+ }
47
+ get doName() {
48
+ return `${this.tenantSegment}:${this.id}`;
49
+ }
50
+ get root() {
51
+ return this.options.root ?? this.profile.root ?? '/home/user';
52
+ }
53
+ stub() {
54
+ const namespace = this.namespace;
55
+ const id = namespace.idFromName(this.doName);
56
+ return namespace.get(id);
57
+ }
58
+ async ready() {
59
+ if (!this.readyPromise) {
60
+ const preinstall = this.profile.runtimes?.preinstall ?? [];
61
+ for (const spec of preinstall)
62
+ this.assertRuntimeAllowed(spec, 'preinstall');
63
+ this.readyPromise = this.stub()._rpcReady({ preinstall }).then(() => undefined);
64
+ }
65
+ return this.readyPromise;
66
+ }
67
+ async exec(command, options = {}) {
68
+ await this.ready();
69
+ return this.stub()._rpcExec(command, this.execOptions(options));
70
+ }
71
+ async startProcess(command, options = {}) {
72
+ await this.ready();
73
+ return this.stub()._rpcStartProcess(command, this.execOptions(options));
74
+ }
75
+ async runCode(code, options = {}) {
76
+ const language = options.language ?? 'javascript';
77
+ if (language === 'python' || language === 'ruby') {
78
+ this.assertRuntimeAllowed(language, options.install === 'ifMissing' ? 'onDemand' : 'use');
79
+ }
80
+ await this.ready();
81
+ return this.stub()._rpcRunCode(code, {
82
+ ...this.execOptions(options),
83
+ language,
84
+ install: options.install ?? 'never',
85
+ });
86
+ }
87
+ files = {
88
+ read: async (path) => {
89
+ await this.ready();
90
+ return this.stub()._rpcReadFile(path);
91
+ },
92
+ readBytes: async (path) => {
93
+ await this.ready();
94
+ return this.stub()._rpcReadFileBytes(path);
95
+ },
96
+ write: async (path, content) => {
97
+ await this.ready();
98
+ return this.stub()._rpcWriteFile(path, content);
99
+ },
100
+ list: async (path = this.root) => {
101
+ await this.ready();
102
+ return this.stub()._rpcReaddir(path);
103
+ },
104
+ mkdir: async (path) => {
105
+ await this.ready();
106
+ return this.stub()._rpcMkdir(path);
107
+ },
108
+ exists: async (path) => {
109
+ await this.ready();
110
+ return this.stub()._rpcExists(path);
111
+ },
112
+ delete: async (path, options = {}) => {
113
+ await this.ready();
114
+ return this.stub()._rpcDeleteFile(path, options);
115
+ },
116
+ };
117
+ runtimes = {
118
+ available: async () => {
119
+ await this.ready();
120
+ return (await this.stub()._rpcListRuntimes()).available;
121
+ },
122
+ installed: async () => {
123
+ await this.ready();
124
+ return (await this.stub()._rpcListRuntimes()).installed;
125
+ },
126
+ list: async () => {
127
+ await this.ready();
128
+ return this.stub()._rpcListRuntimes();
129
+ },
130
+ install: async (spec, options = {}) => {
131
+ this.assertRuntimeAllowed(spec, 'onDemand');
132
+ await this.ready();
133
+ return this.stub()._rpcInstallRuntime(spec, options);
134
+ },
135
+ ensure: async (specs, options = {}) => {
136
+ const list = Array.isArray(specs) ? specs : [specs];
137
+ for (const spec of list)
138
+ this.assertRuntimeAllowed(spec, 'onDemand');
139
+ await this.ready();
140
+ return this.stub()._rpcEnsureRuntimes(list, options);
141
+ },
142
+ };
143
+ processes = {
144
+ list: async () => {
145
+ await this.ready();
146
+ return this.stub()._rpcListProcesses();
147
+ },
148
+ kill: async (pid) => {
149
+ await this.ready();
150
+ return this.stub()._rpcKillProcess(pid);
151
+ },
152
+ logs: async (pid, options = {}) => {
153
+ await this.ready();
154
+ return this.stub()._rpcProcessLogs(pid, options);
155
+ },
156
+ };
157
+ ports = {
158
+ list: async () => {
159
+ await this.ready();
160
+ return this.stub()._rpcListPorts();
161
+ },
162
+ expose: async (port) => {
163
+ await this.ready();
164
+ const result = await this.stub()._rpcExposePort(port);
165
+ return { ...result, url: this.portUrl(port) };
166
+ },
167
+ unexpose: async (port) => {
168
+ await this.ready();
169
+ return this.stub()._rpcUnexposePort(port);
170
+ },
171
+ url: (port) => this.portUrl(port),
172
+ };
173
+ tools(options = {}) {
174
+ const namespace = options.namespace ?? this.profile.tools?.namespace ?? 'nimbus';
175
+ const kind = options.kind ?? this.profile.tools?.kind ?? 'nimbus';
176
+ const callPath = (input) => typeof input === 'string' ? input : String(input?.path ?? '');
177
+ return {
178
+ name: options.name ?? namespace,
179
+ kind,
180
+ capabilities: this.capabilities(),
181
+ isAvailable: async () => true,
182
+ connect: async () => this.ready(),
183
+ disconnect: async () => undefined,
184
+ tools: {
185
+ exec: { execute: (command, opts) => this.exec(command, opts) },
186
+ runCode: { execute: (code, opts) => this.runCode(code, opts) },
187
+ readFile: { execute: (input) => this.files.read(callPath(input)) },
188
+ writeFile: { execute: (input) => this.files.write(callPath(input), input.content ?? input.data ?? '') },
189
+ listFiles: { execute: (input = this.root) => this.files.list(callPath(input) || this.root) },
190
+ readdir: { execute: (input = this.root) => this.files.list(callPath(input) || this.root) },
191
+ deleteFile: { execute: (input) => this.files.delete(callPath(input), { recursive: !!input?.recursive }) },
192
+ exists: { execute: (input) => this.files.exists(callPath(input)) },
193
+ startProcess: { execute: (command, opts) => this.startProcess(command, opts) },
194
+ killProcess: { execute: (input) => this.processes.kill(typeof input === 'number' ? input : input.pid) },
195
+ logs: { execute: (input) => this.processes.logs(typeof input === 'number' ? input : input.pid, typeof input === 'number' ? {} : input) },
196
+ exposePort: { execute: (input) => this.ports.expose(typeof input === 'number' ? input : input.port) },
197
+ unexposePort: { execute: (input) => this.ports.unexpose(typeof input === 'number' ? input : input.port) },
198
+ listPorts: { execute: () => this.ports.list() },
199
+ installRuntime: { execute: (spec) => this.runtimes.install(spec) },
200
+ listRuntimes: { execute: () => this.runtimes.list() },
201
+ },
202
+ };
203
+ }
204
+ capabilities() {
205
+ const allow = this.profile.runtimes?.allow;
206
+ const hasRuntime = (name) => !allow || allow.includes(name);
207
+ const caps = [
208
+ 'javascript',
209
+ 'typescript',
210
+ 'shell',
211
+ 'npm',
212
+ 'git',
213
+ 'fs_owned',
214
+ 'net_outbound',
215
+ 'net_inbound',
216
+ 'process_spawn',
217
+ 'process_long',
218
+ ];
219
+ if (hasRuntime('python'))
220
+ caps.push('python');
221
+ if (hasRuntime('clang'))
222
+ caps.push('native_binary');
223
+ return caps;
224
+ }
225
+ execOptions(options) {
226
+ return {
227
+ ...options,
228
+ cwd: options.cwd ?? this.root,
229
+ };
230
+ }
231
+ assertRuntimeAllowed(spec, action) {
232
+ const policy = this.profile.runtimes;
233
+ const allow = policy?.allow;
234
+ const name = String(spec).split('@')[0];
235
+ if (allow && !allow.includes(name)) {
236
+ throw new Error(`Nimbus runtime '${name}' is not allowed by sandbox profile '${this.profileName}'`);
237
+ }
238
+ if (action !== 'onDemand' || policy?.onDemand !== false)
239
+ return;
240
+ const preinstalled = new Set((policy.preinstall ?? []).map((s) => String(s).split('@')[0]));
241
+ if (!preinstalled.has(name)) {
242
+ throw new Error(`Nimbus runtime '${name}' is not preinstalled and on-demand runtime installs are disabled by sandbox profile '${this.profileName}'`);
243
+ }
244
+ }
245
+ portUrl(port) {
246
+ const explicit = this.profile.preview?.baseUrl;
247
+ if (explicit) {
248
+ const base = explicit.replace('{sessionId}', encodeURIComponent(this.id)).replace(/\/+$/, '');
249
+ return `${base}/port/${port}/`;
250
+ }
251
+ const endpoint = this.config.endpoint?.replace(/\/+$/, '');
252
+ if (!endpoint)
253
+ return undefined;
254
+ return `${endpoint}/s/${encodeURIComponent(this.id)}/port/${port}/`;
255
+ }
256
+ }
257
+ function sanitizeSegment(value) {
258
+ return String(value).replace(/[^A-Za-z0-9_.:-]/g, '_') || '_';
259
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @nimbus-sh/sdk/session — High-level session handle helpers.
3
+ *
4
+ * Useful for embedders who want to compute a session URL ahead of time
5
+ * (e.g. for sharing) or attach to a known session ID. The helpers in
6
+ * this module are pure URL/token plumbing; no network calls.
7
+ */
8
+ import { type NimbusAuthEnv } from '@nimbus-sh/worker/auth';
9
+ import type { IssueTokenOptions } from '@nimbus-sh/worker/auth';
10
+ /**
11
+ * Compute the canonical attach URL for a session. The returned URL
12
+ * embeds the token in the `?nimbus_token=` query so an `<iframe src>`
13
+ * can carry it without custom headers.
14
+ *
15
+ * @param endpoint Base URL of the Nimbus deploy (e.g. `https://my.workers.dev`).
16
+ * No trailing slash required.
17
+ * @param sessionId Session ID (e.g. `pretty-otter-1234`).
18
+ * @param token Verified Nimbus JWT.
19
+ * @returns Fully-qualified URL: `${endpoint}/s/${sessionId}/?nimbus_token=${token}`.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import { sessionAttachUrl, issueNimbusToken } from '@nimbus-sh/sdk';
24
+ * const token = await issueNimbusToken(env, { tn: 'acme', sub: 'alice' });
25
+ * const url = sessionAttachUrl('https://my-nimbus.workers.dev', 'pretty-otter-1234', token);
26
+ * // → "https://my-nimbus.workers.dev/s/pretty-otter-1234/?nimbus_token=eyJ…"
27
+ * ```
28
+ */
29
+ export declare function sessionAttachUrl(endpoint: string, sessionId: string, token: string): string;
30
+ /**
31
+ * Convenience: mint a token AND build the attach URL in one call.
32
+ *
33
+ * @param env Env with `JWT_SECRET` (and optionally `JWT_SECRET_PREVIOUS`).
34
+ * @param input Token claims minus iat/exp/scope.
35
+ * @param opts Mint options ({@link IssueTokenOptions}) plus endpoint + sessionId.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * const { token, url } = await mintAndAttach(env,
40
+ * { tn: 'acme', sub: 'alice' },
41
+ * { endpoint: 'https://my-nimbus.workers.dev', sessionId: 'pretty-otter-1234' });
42
+ * ```
43
+ */
44
+ export declare function mintAndAttach(env: NimbusAuthEnv, input: {
45
+ tn: string;
46
+ sub?: string;
47
+ scopes?: string[];
48
+ sid?: string;
49
+ }, opts: IssueTokenOptions & {
50
+ endpoint: string;
51
+ sessionId: string;
52
+ }): Promise<{
53
+ token: string;
54
+ url: string;
55
+ }>;
56
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,MAAM,CAGR;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,aAAa,EAClB,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,EACpE,IAAI,EAAE,iBAAiB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAChE,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAIzC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @nimbus-sh/sdk/session — High-level session handle helpers.
3
+ *
4
+ * Useful for embedders who want to compute a session URL ahead of time
5
+ * (e.g. for sharing) or attach to a known session ID. The helpers in
6
+ * this module are pure URL/token plumbing; no network calls.
7
+ */
8
+ import { issueNimbusToken } from '@nimbus-sh/worker/auth';
9
+ /**
10
+ * Compute the canonical attach URL for a session. The returned URL
11
+ * embeds the token in the `?nimbus_token=` query so an `<iframe src>`
12
+ * can carry it without custom headers.
13
+ *
14
+ * @param endpoint Base URL of the Nimbus deploy (e.g. `https://my.workers.dev`).
15
+ * No trailing slash required.
16
+ * @param sessionId Session ID (e.g. `pretty-otter-1234`).
17
+ * @param token Verified Nimbus JWT.
18
+ * @returns Fully-qualified URL: `${endpoint}/s/${sessionId}/?nimbus_token=${token}`.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { sessionAttachUrl, issueNimbusToken } from '@nimbus-sh/sdk';
23
+ * const token = await issueNimbusToken(env, { tn: 'acme', sub: 'alice' });
24
+ * const url = sessionAttachUrl('https://my-nimbus.workers.dev', 'pretty-otter-1234', token);
25
+ * // → "https://my-nimbus.workers.dev/s/pretty-otter-1234/?nimbus_token=eyJ…"
26
+ * ```
27
+ */
28
+ export function sessionAttachUrl(endpoint, sessionId, token) {
29
+ const base = endpoint.replace(/\/+$/, '');
30
+ return `${base}/s/${sessionId}/?nimbus_token=${encodeURIComponent(token)}`;
31
+ }
32
+ /**
33
+ * Convenience: mint a token AND build the attach URL in one call.
34
+ *
35
+ * @param env Env with `JWT_SECRET` (and optionally `JWT_SECRET_PREVIOUS`).
36
+ * @param input Token claims minus iat/exp/scope.
37
+ * @param opts Mint options ({@link IssueTokenOptions}) plus endpoint + sessionId.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * const { token, url } = await mintAndAttach(env,
42
+ * { tn: 'acme', sub: 'alice' },
43
+ * { endpoint: 'https://my-nimbus.workers.dev', sessionId: 'pretty-otter-1234' });
44
+ * ```
45
+ */
46
+ export async function mintAndAttach(env, input, opts) {
47
+ const token = await issueNimbusToken(env, input, opts);
48
+ const url = sessionAttachUrl(opts.endpoint, opts.sessionId, token);
49
+ return { token, url };
50
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @nimbus-sh/sdk/token — JWT mint + verify, re-exported from the worker
3
+ * auth module.
4
+ *
5
+ * Why a re-export and not a copy? The auth module is the single source
6
+ * of truth — both Worker (verify) and SDK (mint) call the same code
7
+ * paths so wire-format drift is structurally impossible. The
8
+ * `@nimbus-sh/worker` peer-dependency is OPTIONAL (declared in
9
+ * package.json#peerDependenciesMeta); consumers who only mint tokens
10
+ * outside a Worker can install just `@nimbus-sh/sdk` and the
11
+ * worker peer-dep is tree-shaken to its auth subpath.
12
+ *
13
+ * @example mint
14
+ * ```ts
15
+ * import { issueNimbusToken } from '@nimbus-sh/sdk/token';
16
+ * const jwt = await issueNimbusToken({ JWT_SECRET: 'hex' }, {
17
+ * tn: 'acme', sub: 'alice'
18
+ * });
19
+ * ```
20
+ *
21
+ * @example verify in a Worker
22
+ * ```ts
23
+ * import { verifyNimbusToken } from '@nimbus-sh/sdk/token';
24
+ * const { claims, doInstanceName } = await verifyNimbusToken(env, jwt);
25
+ * ```
26
+ */
27
+ export { issueNimbusToken, verifyNimbusToken, type NimbusAuthEnv, } from '@nimbus-sh/worker/auth';
28
+ export { DEFAULT_TOKEN_TTL_MS, MAX_TOKEN_TTL_MS, ID_COMPONENT_RE, type NimbusTokenClaims, type VerifiedNimbusToken, type IssueTokenOptions, } from '@nimbus-sh/worker/auth';
29
+ //# sourceMappingURL=token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,aAAa,GACnB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,GACvB,MAAM,wBAAwB,CAAC"}
package/dist/token.js ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @nimbus-sh/sdk/token — JWT mint + verify, re-exported from the worker
3
+ * auth module.
4
+ *
5
+ * Why a re-export and not a copy? The auth module is the single source
6
+ * of truth — both Worker (verify) and SDK (mint) call the same code
7
+ * paths so wire-format drift is structurally impossible. The
8
+ * `@nimbus-sh/worker` peer-dependency is OPTIONAL (declared in
9
+ * package.json#peerDependenciesMeta); consumers who only mint tokens
10
+ * outside a Worker can install just `@nimbus-sh/sdk` and the
11
+ * worker peer-dep is tree-shaken to its auth subpath.
12
+ *
13
+ * @example mint
14
+ * ```ts
15
+ * import { issueNimbusToken } from '@nimbus-sh/sdk/token';
16
+ * const jwt = await issueNimbusToken({ JWT_SECRET: 'hex' }, {
17
+ * tn: 'acme', sub: 'alice'
18
+ * });
19
+ * ```
20
+ *
21
+ * @example verify in a Worker
22
+ * ```ts
23
+ * import { verifyNimbusToken } from '@nimbus-sh/sdk/token';
24
+ * const { claims, doInstanceName } = await verifyNimbusToken(env, jwt);
25
+ * ```
26
+ */
27
+ export { issueNimbusToken, verifyNimbusToken, } from '@nimbus-sh/worker/auth';
28
+ export { DEFAULT_TOKEN_TTL_MS, MAX_TOKEN_TTL_MS, ID_COMPONENT_RE, } from '@nimbus-sh/worker/auth';
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@nimbus-sh/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Nimbus SDK — programmatic sandboxes, token mint, types, and Worker client helpers.",
5
+ "homepage": "https://github.com/AshishKumar4/Nimbus",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/AshishKumar4/Nimbus.git",
9
+ "directory": "packages/sdk"
10
+ },
11
+ "bugs": "https://github.com/AshishKumar4/Nimbus/issues",
12
+ "license": "MIT",
13
+ "type": "module",
14
+ "sideEffects": false,
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "workspace": "./src/index.ts",
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ },
23
+ "./token": {
24
+ "workspace": "./src/token.ts",
25
+ "types": "./dist/token.d.ts",
26
+ "import": "./dist/token.js"
27
+ },
28
+ "./session": {
29
+ "workspace": "./src/session.ts",
30
+ "types": "./dist/session.d.ts",
31
+ "import": "./dist/session.js"
32
+ },
33
+ "./sandbox": {
34
+ "workspace": "./src/sandbox.ts",
35
+ "types": "./dist/sandbox.d.ts",
36
+ "import": "./dist/sandbox.js"
37
+ },
38
+ "./errors": {
39
+ "workspace": "./src/errors.ts",
40
+ "types": "./dist/errors.d.ts",
41
+ "import": "./dist/errors.js"
42
+ },
43
+ "./package.json": "./package.json"
44
+ },
45
+ "files": [
46
+ "dist",
47
+ "README.md"
48
+ ],
49
+ "scripts": {
50
+ "build": "tsc -p tsconfig.json --noCheck --noEmit false --declaration true --declarationMap true --outDir dist --rootDir src",
51
+ "prepack": "npm run build",
52
+ "typecheck": "tsc --noEmit"
53
+ },
54
+ "peerDependencies": {
55
+ "@nimbus-sh/worker": "^0.1.0"
56
+ },
57
+ "peerDependenciesMeta": {
58
+ "@nimbus-sh/worker": { "optional": true }
59
+ },
60
+ "devDependencies": {
61
+ "@cloudflare/workers-types": "^4.20250327.0",
62
+ "@nimbus-sh/worker": "^0.1.0",
63
+ "typescript": "^5.7.0"
64
+ },
65
+ "publishConfig": {
66
+ "access": "public"
67
+ }
68
+ }