@nimbus-sh/cli 0.1.3 → 0.1.5

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 CHANGED
@@ -24,6 +24,9 @@ bun packages/cli/src/scaffold-bin.ts my-app
24
24
  ### `nimbus init [directory]`
25
25
 
26
26
  Scaffolds a Nimbus-powered Workers project. `.` uses the current directory.
27
+ The generated app embeds the interactive Nimbus UI and enables the
28
+ authenticated remote sandbox API. Add application-specific auth routes in
29
+ `src/index.ts` when you are ready to mint user tokens from your backend.
27
30
 
28
31
  ```bash
29
32
  nimbus init my-nimbus
@@ -36,7 +39,8 @@ npx wrangler deploy
36
39
 
37
40
  ### `create-nimbus-app <name>`
38
41
 
39
- Scaffolds a new Nimbus-powered Workers project.
42
+ Scaffolds a new Nimbus-powered Workers project with the same template as
43
+ `nimbus init`.
40
44
 
41
45
  ```bash
42
46
  create-nimbus-app my-nimbus
@@ -93,6 +97,9 @@ Flags:
93
97
  | `--scopes <a,b>` | Comma-separated capability scopes. |
94
98
  | `--sid <id>` | Pin token to a specific session ID. |
95
99
 
100
+ Use `--scopes sandbox:use --sid <sandbox-id>` for remote SDK calls through
101
+ `Nimbus.connect(...)`.
102
+
96
103
  ### `nimbus token verify <token>`
97
104
 
98
105
  ```bash
@@ -10,14 +10,11 @@
10
10
  * (`nimbus runtime sync python clang`) or comma-separated via
11
11
  * `--runtimes python,clang`.
12
12
  *
13
- * Implementation: shells out to wrangler r2 object put for each blob
14
- * listed in `@nimbus-sh/worker`'s bundled `scripts/bundle-runtime.mjs`.
15
- * The actual fetch+verify+upload work lives in that script; this CLI
16
- * is a thin proxy.
13
+ * Implementation: shells out to the runtime bundling helper shipped in
14
+ * `@nimbus-sh/worker`. The CLI is the supported operator entrypoint.
17
15
  */
18
16
  /**
19
- * Sync runtime blobs to an R2 bucket via the bundled
20
- * `bundle-runtime.mjs` script.
17
+ * Sync runtime blobs to an R2 bucket via the bundled worker helper.
21
18
  *
22
19
  * @example
23
20
  * ```bash
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-sync.d.ts","sourceRoot":"","sources":["../../src/commands/runtime-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAeH;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAsClE;AAED,uEAAuE;AACvE,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAUnE"}
1
+ {"version":3,"file":"runtime-sync.d.ts","sourceRoot":"","sources":["../../src/commands/runtime-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAcH;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAoClE;AAED,uEAAuE;AACvE,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAUnE"}
@@ -10,15 +10,12 @@
10
10
  * (`nimbus runtime sync python clang`) or comma-separated via
11
11
  * `--runtimes python,clang`.
12
12
  *
13
- * Implementation: shells out to wrangler r2 object put for each blob
14
- * listed in `@nimbus-sh/worker`'s bundled `scripts/bundle-runtime.mjs`.
15
- * The actual fetch+verify+upload work lives in that script; this CLI
16
- * is a thin proxy.
13
+ * Implementation: shells out to the runtime bundling helper shipped in
14
+ * `@nimbus-sh/worker`. The CLI is the supported operator entrypoint.
17
15
  */
18
16
  import { spawn } from 'node:child_process';
19
17
  import { existsSync } from 'node:fs';
20
18
  import { createRequire } from 'node:module';
21
- import { dirname, join } from 'node:path';
22
19
  import { fileURLToPath } from 'node:url';
23
20
  const nodeRequire = createRequire(import.meta.url);
24
21
  const DEFAULT_RUNTIME_VERSIONS = {
@@ -27,8 +24,7 @@ const DEFAULT_RUNTIME_VERSIONS = {
27
24
  ruby: '3.3.4',
28
25
  };
29
26
  /**
30
- * Sync runtime blobs to an R2 bucket via the bundled
31
- * `bundle-runtime.mjs` script.
27
+ * Sync runtime blobs to an R2 bucket via the bundled worker helper.
32
28
  *
33
29
  * @example
34
30
  * ```bash
@@ -46,12 +42,10 @@ export async function syncRuntimes(args) {
46
42
  process.stderr.write('nimbus runtime sync: CLOUDFLARE_ACCOUNT_ID env var required\n');
47
43
  return 78;
48
44
  }
49
- // Locate the bundle-runtime.mjs script in `@nimbus-sh/worker`. When
50
- // installed via npm, resolve via require; in workspace dogfood, use
51
- // a relative path.
45
+ // Locate the runtime sync helper in `@nimbus-sh/worker`.
52
46
  const scriptPath = resolveBundleRuntimeScript();
53
47
  if (!scriptPath) {
54
- process.stderr.write('nimbus runtime sync: cannot locate @nimbus-sh/worker/scripts/bundle-runtime.mjs\n');
48
+ process.stderr.write('nimbus runtime sync: cannot locate Nimbus runtime sync helper\n');
55
49
  return 70;
56
50
  }
57
51
  process.stderr.write(`nimbus: syncing runtimes [${runtimes.join(', ')}] → r2://${bucket}\n`);
@@ -85,20 +79,16 @@ export async function listRuntimes(_args) {
85
79
  }
86
80
  // ── helpers ──────────────────────────────────────────────────────────
87
81
  function resolveBundleRuntimeScript() {
88
- // 1. Try resolving via the @nimbus-sh/worker package (npm-installed).
89
82
  try {
90
- const packageJson = nodeRequire.resolve('@nimbus-sh/worker/package.json');
91
- const script = join(dirname(packageJson), 'scripts', 'bundle-runtime.mjs');
83
+ const script = nodeRequire.resolve('@nimbus-sh/worker/runtime-sync-helper');
92
84
  if (existsSync(script))
93
85
  return script;
94
86
  }
95
87
  catch {
96
- // fallthrough
88
+ // Source-workspace mode can run before workspace packages are linked.
97
89
  }
98
- // 2. Workspace dogfood — relative path inside the monorepo.
99
90
  try {
100
- const wsUrl = new URL('../../../worker/scripts/bundle-runtime.mjs', import.meta.url);
101
- const script = fileURLToPath(wsUrl);
91
+ const script = fileURLToPath(new URL('../../../worker/scripts/bundle-runtime.mjs', import.meta.url));
102
92
  return existsSync(script) ? script : null;
103
93
  }
104
94
  catch {
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/commands/scaffold.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAsF9D"}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/commands/scaffold.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuF9D"}
@@ -16,7 +16,7 @@
16
16
  * print the exact commands to run as a "next steps" trailer.
17
17
  */
18
18
  import { mkdirSync, writeFileSync, existsSync } from 'node:fs';
19
- import { resolve, join, basename } from 'node:path';
19
+ import { resolve, join, basename, relative } from 'node:path';
20
20
  /**
21
21
  * Scaffold a new Nimbus project at the given directory.
22
22
  *
@@ -41,15 +41,16 @@ Options:
41
41
  return 0;
42
42
  }
43
43
  const rawProjectName = args[0];
44
- const projectName = rawProjectName === '.'
45
- ? basename(process.cwd())
46
- : rawProjectName;
47
- if (!projectName || projectName.startsWith('-')) {
44
+ if (!rawProjectName || rawProjectName.startsWith('-')) {
48
45
  process.stderr.write('create-nimbus-app: <project-name> required\n');
49
46
  process.stderr.write('Usage: create-nimbus-app my-app [--name my-worker] [--force]\n');
50
47
  return 64;
51
48
  }
52
49
  const parsed = parseFlags(args.slice(1));
50
+ const target = rawProjectName === '.'
51
+ ? process.cwd()
52
+ : resolve(process.cwd(), rawProjectName);
53
+ const projectName = basename(target);
53
54
  const wranglerName = parsed['--name'] || projectName;
54
55
  const force = '--force' in parsed;
55
56
  const template = parsed['--template'] || 'worker-only';
@@ -57,9 +58,6 @@ Options:
57
58
  process.stderr.write(`create-nimbus-app: unknown template "${template}". Only "worker-only" ships in v0.1.\n`);
58
59
  return 64;
59
60
  }
60
- const target = rawProjectName === '.'
61
- ? process.cwd()
62
- : resolve(process.cwd(), projectName);
63
61
  if (existsSync(target) && !force) {
64
62
  process.stderr.write(`create-nimbus-app: directory exists: ${target} (use --force to overwrite)\n`);
65
63
  return 73;
@@ -72,12 +70,13 @@ Options:
72
70
  writeFileSync(join(target, 'README.md'), renderReadme(projectName, wranglerName));
73
71
  writeFileSync(join(target, '.gitignore'), '.wrangler\nnode_modules\ndist\n');
74
72
  writeFileSync(join(target, 'tsconfig.json'), renderTsconfig());
73
+ const cdTarget = renderCdTarget(target);
75
74
  // Stdout — JSON for machine, friendly stderr for humans.
76
75
  process.stdout.write(JSON.stringify({
77
76
  ok: true,
78
77
  path: target,
79
78
  nextSteps: [
80
- `cd ${projectName}`,
79
+ `cd ${cdTarget}`,
81
80
  'npm install',
82
81
  `CLOUDFLARE_ACCOUNT_ID=<account-id> npx @nimbus-sh/cli setup cloudflare --name ${wranglerName}`,
83
82
  'npx wrangler secret put JWT_SECRET',
@@ -88,7 +87,7 @@ Options:
88
87
  ✨ Scaffolded ${projectName} at ${target}
89
88
 
90
89
  Next steps:
91
- cd ${projectName}
90
+ cd ${cdTarget}
92
91
  npm install
93
92
  CLOUDFLARE_ACCOUNT_ID=<account-id> npx @nimbus-sh/cli setup cloudflare --name ${wranglerName}
94
93
  npx wrangler secret put JWT_SECRET # paste a 32+ char hex secret
@@ -97,14 +96,23 @@ Next steps:
97
96
  If setup reports Cloudflare R2 error 10042, enable R2 in the Cloudflare
98
97
  Dashboard once for this account, then rerun the setup command.
99
98
 
100
- Deploy will provision the Durable Object on first run. Visit the returned
101
- URL and hit "Launch" to mint a session.
99
+ Deploy will provision the Durable Object on first use. Mint a token with
100
+ \`nimbus token mint\` before attaching an interactive session or remote SDK
101
+ sandbox.
102
102
 
103
103
  Docs: https://github.com/AshishKumar4/Nimbus#readme
104
104
  `);
105
105
  return 0;
106
106
  }
107
107
  // ── template renderers ──────────────────────────────────────────────
108
+ function renderCdTarget(target) {
109
+ const rel = relative(process.cwd(), target);
110
+ if (!rel)
111
+ return '.';
112
+ if (!rel.startsWith('..') && !rel.startsWith('/'))
113
+ return rel;
114
+ return target;
115
+ }
108
116
  function renderPackageJson(name) {
109
117
  return JSON.stringify({
110
118
  name,
@@ -117,8 +125,9 @@ function renderPackageJson(name) {
117
125
  typecheck: 'tsc --noEmit',
118
126
  },
119
127
  dependencies: {
120
- '@nimbus-sh/worker': '^0.1.1',
121
- '@nimbus-sh/sdk': '^0.1.1',
128
+ '@nimbus-sh/config': '^0.1.1',
129
+ '@nimbus-sh/worker': '^0.1.2',
130
+ '@nimbus-sh/sdk': '^0.1.2',
122
131
  },
123
132
  devDependencies: {
124
133
  '@cloudflare/workers-types': '^4.20250327.0',
@@ -139,7 +148,7 @@ function renderWranglerJsonc(name) {
139
148
  "assets": {
140
149
  "directory": "node_modules/@nimbus-sh/worker/public",
141
150
  "binding": "ASSETS",
142
- "run_worker_first": ["/s/*", "/new"]
151
+ "run_worker_first": ["/api/*", "/s/*", "/new"]
143
152
  },
144
153
 
145
154
  // The Node-compat shims isomorphic-git + the npm installer need. Do
@@ -191,6 +200,7 @@ function renderIndexTs() {
191
200
  CirrusHmrRPC,
192
201
  createNimbusHandler,
193
202
  } from '@nimbus-sh/sdk/worker';
203
+ import { defineNimbusConfig } from '@nimbus-sh/config';
194
204
 
195
205
  // Re-export the DO class + every RPC class so wrangler's
196
206
  // \`durable_objects.bindings[].class_name\` lookup + \`enable_ctx_exports\`
@@ -207,7 +217,26 @@ export {
207
217
  CirrusHmrRPC,
208
218
  };
209
219
 
210
- export default createNimbusHandler();
220
+ const nimbusConfig = defineNimbusConfig({
221
+ sandboxes: {
222
+ default: {
223
+ root: '/home/user',
224
+ runtimes: {
225
+ preinstall: ['python'],
226
+ onDemand: true,
227
+ allow: ['node', 'bun', 'npm', 'git', 'python', 'ruby', 'clang', 'shell'],
228
+ },
229
+ tools: { namespace: 'sandbox', kind: 'sandbox' },
230
+ },
231
+ },
232
+ });
233
+
234
+ export default createNimbusHandler({
235
+ sdk: {
236
+ remote: true,
237
+ config: nimbusConfig,
238
+ },
239
+ });
211
240
  `;
212
241
  }
213
242
  function renderTsconfig() {
@@ -239,19 +268,27 @@ npx wrangler secret put JWT_SECRET # paste a 32+ char hex secret
239
268
  npx wrangler deploy
240
269
  \`\`\`
241
270
 
242
- Then visit the URL wrangler prints. Click "Launch" to mint a session.
271
+ Then visit the URL wrangler prints.
243
272
 
244
273
  If setup reports Cloudflare R2 error 10042, enable R2 in the Cloudflare
245
274
  Dashboard once for this account, then rerun the setup command.
246
275
 
247
- ## Custom auth
276
+ ## Tokens
248
277
 
249
- By default tokens are enforced (\`JWT_SECRET\` set). To mint:
278
+ By default tokens are enforced because \`JWT_SECRET\` is set. Mint a token for
279
+ an interactive session or remote SDK sandbox:
250
280
 
251
281
  \`\`\`bash
252
- JWT_SECRET=<your-secret> npx @nimbus-sh/cli token mint --tenant acme --sub alice
282
+ JWT_SECRET=<your-secret> npx @nimbus-sh/cli token mint \\
283
+ --tenant acme \\
284
+ --sub alice \\
285
+ --scopes sandbox:use \\
286
+ --sid job-123
253
287
  \`\`\`
254
288
 
289
+ Use the token with \`@nimbus-sh/react\`, \`sessionAttachUrl(...)\`, or
290
+ \`Nimbus.connect({ endpoint, token })\`.
291
+
255
292
  Embed in a React app:
256
293
 
257
294
  \`\`\`tsx
@@ -267,15 +304,17 @@ import { NimbusTerminal } from '@nimbus-sh/react';
267
304
 
268
305
  ## Customize
269
306
 
270
- Edit \`src/index.ts\`:
307
+ In \`src/index.ts\`, keep the generated exports and \`nimbusConfig\`, then
308
+ replace the \`export default\` block:
271
309
 
272
310
  \`\`\`ts
273
- import { NimbusSession, createNimbusHandler } from '@nimbus-sh/sdk/worker';
274
311
  import { issueNimbusToken } from '@nimbus-sh/sdk/token';
275
312
 
276
- export { NimbusSession };
277
-
278
313
  export default createNimbusHandler({
314
+ sdk: {
315
+ remote: true,
316
+ config: nimbusConfig,
317
+ },
279
318
  hooks: {
280
319
  onSessionStart: ({ sessionId, tenantSegment }) => {
281
320
  console.log(\`session \${sessionId} for \${tenantSegment}\`);
@@ -283,8 +322,13 @@ export default createNimbusHandler({
283
322
  },
284
323
  routes: async (req, env) => {
285
324
  if (new URL(req.url).pathname === '/api/auth/mint') {
325
+ // Authenticate your application user before minting.
286
326
  const { tenant, sub } = await req.json();
287
- const token = await issueNimbusToken(env, { tn: tenant, sub });
327
+ const token = await issueNimbusToken(env, {
328
+ tn: tenant,
329
+ sub,
330
+ scopes: ['sandbox:use'],
331
+ });
288
332
  return Response.json({ token });
289
333
  }
290
334
  return null;
@@ -8,7 +8,8 @@ export async function newSession(args) {
8
8
  ?? process.env.NIMBUS_ENDPOINT
9
9
  ?? 'http://127.0.0.1:8787';
10
10
  try {
11
- const r = await fetch(`${endpoint.replace(/\/+$/, '')}/new`, {
11
+ const baseUrl = new URL(endpoint);
12
+ const r = await fetch(new URL('/new', baseUrl), {
12
13
  method: 'POST',
13
14
  redirect: 'manual',
14
15
  });
@@ -17,13 +18,12 @@ export async function newSession(args) {
17
18
  process.stderr.write(`nimbus session new: POST /new returned no Location (status ${r.status})\n`);
18
19
  return 70;
19
20
  }
20
- const m = loc.match(/\/s\/([^/]+)/);
21
- if (!m) {
21
+ const sessionId = sessionIdFromLocation(loc, baseUrl);
22
+ if (!sessionId) {
22
23
  process.stderr.write(`nimbus session new: unexpected Location: ${loc}\n`);
23
24
  return 70;
24
25
  }
25
- const sessionId = m[1];
26
- const url = `${endpoint.replace(/\/+$/, '')}/s/${sessionId}/`;
26
+ const url = new URL(`/s/${encodeURIComponent(sessionId)}/`, baseUrl).toString();
27
27
  process.stdout.write(JSON.stringify({ sessionId, url }) + '\n');
28
28
  return 0;
29
29
  }
@@ -43,3 +43,21 @@ function parseFlags(args) {
43
43
  }
44
44
  return out;
45
45
  }
46
+ function sessionIdFromLocation(location, baseUrl) {
47
+ let url;
48
+ try {
49
+ url = new URL(location, baseUrl);
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ const segments = url.pathname.split('/').filter(Boolean);
55
+ if (segments.length < 2 || segments[0] !== 's')
56
+ return null;
57
+ try {
58
+ return decodeURIComponent(segments[1]);
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
package/dist/version.d.ts CHANGED
@@ -3,5 +3,5 @@
3
3
  * `require('./package.json')` because that breaks under bun's
4
4
  * single-file binary mode.
5
5
  */
6
- export declare const CLI_VERSION = "0.1.3";
6
+ export declare const CLI_VERSION = "0.1.5";
7
7
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -3,4 +3,4 @@
3
3
  * `require('./package.json')` because that breaks under bun's
4
4
  * single-file binary mode.
5
5
  */
6
- export const CLI_VERSION = '0.1.3';
6
+ export const CLI_VERSION = '0.1.5';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimbus-sh/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Nimbus CLI — `create-nimbus-app` scaffolder + `token mint` + `runtime sync`.",
5
5
  "homepage": "https://github.com/AshishKumar4/Nimbus",
6
6
  "repository": {
@@ -12,9 +12,9 @@
12
12
  "license": "MIT",
13
13
  "type": "module",
14
14
  "bin": {
15
- "cli": "./dist/bin.js",
16
- "nimbus": "./dist/bin.js",
17
- "create-nimbus-app": "./dist/scaffold-bin.js"
15
+ "cli": "dist/bin.js",
16
+ "nimbus": "dist/bin.js",
17
+ "create-nimbus-app": "dist/scaffold-bin.js"
18
18
  },
19
19
  "main": "./dist/index.js",
20
20
  "types": "./dist/index.d.ts",
@@ -36,8 +36,8 @@
36
36
  "typecheck": "tsc --noEmit"
37
37
  },
38
38
  "dependencies": {
39
- "@nimbus-sh/sdk": "^0.1.1",
40
- "@nimbus-sh/worker": "^0.1.1"
39
+ "@nimbus-sh/sdk": "^0.1.2",
40
+ "@nimbus-sh/worker": "^0.1.2"
41
41
  },
42
42
  "engines": {
43
43
  "node": ">=20"