@nimbus-sh/cli 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,127 @@
1
+ # @nimbus-sh/cli
2
+
3
+ CLI for Nimbus: scaffolding, token mint/verify, session helpers, and runtime
4
+ cache operations.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npx @nimbus-sh/cli --help
10
+ npx @nimbus-sh/cli init my-app
11
+ npx create-nimbus-app my-app
12
+ ```
13
+
14
+ From this repository:
15
+
16
+ ```bash
17
+ bun packages/cli/src/bin.ts --help
18
+ bun packages/cli/src/bin.ts init my-app
19
+ bun packages/cli/src/scaffold-bin.ts my-app
20
+ ```
21
+
22
+ ## Verbs
23
+
24
+ ### `nimbus init [directory]`
25
+
26
+ Scaffolds a Nimbus-powered Workers project. `.` uses the current directory.
27
+
28
+ ```bash
29
+ nimbus init my-nimbus
30
+ cd my-nimbus
31
+ npm install
32
+ wrangler secret put JWT_SECRET
33
+ wrangler deploy
34
+ ```
35
+
36
+ ### `create-nimbus-app <name>`
37
+
38
+ Scaffolds a new Nimbus-powered Workers project.
39
+
40
+ ```bash
41
+ create-nimbus-app my-nimbus
42
+ cd my-nimbus
43
+ npm install
44
+ wrangler secret put JWT_SECRET
45
+ wrangler deploy
46
+ ```
47
+
48
+ Flags:
49
+
50
+ | Flag | Default | What |
51
+ |---|---|---|
52
+ | `--name <wrangler-name>` | project name | Becomes the deployed Worker name. |
53
+ | `--template <name>` | `worker-only` | Only `worker-only` ships in v0.1. |
54
+ | `--force` | off | Overwrite existing directory. |
55
+
56
+ ### `nimbus token mint`
57
+
58
+ ```bash
59
+ JWT_SECRET=<hex> nimbus token mint --tenant acme --sub alice [--ttl 3600]
60
+ # Prints the JWT to stdout. Pipe with `> /tmp/jwt` or `| pbcopy`.
61
+ ```
62
+
63
+ Flags:
64
+
65
+ | Flag | What |
66
+ |---|---|
67
+ | `--tenant <id>` (required) | Sets `tn` claim. |
68
+ | `--sub <id>` | Sets `sub` claim. |
69
+ | `--ttl <sec>` | Token lifetime in seconds. Default 3600 (1h), max 2,592,000 (30d). |
70
+ | `--scopes <a,b>` | Comma-separated capability scopes. |
71
+ | `--sid <id>` | Pin token to a specific session ID. |
72
+
73
+ ### `nimbus token verify <token>`
74
+
75
+ ```bash
76
+ JWT_SECRET=<hex> nimbus token verify eyJhbGciOi...
77
+ # Prints the verified claims as JSON.
78
+ ```
79
+
80
+ Exit codes: 0 success, 65 token-validation failure, 78 env missing.
81
+
82
+ ### `nimbus runtime sync`
83
+
84
+ Uploads runtime blobs/manifests and updates the runtime catalog through the
85
+ public CLI wrapper. This is the user-facing path for Python, Ruby, and clang
86
+ runtime cache operations.
87
+
88
+ ```bash
89
+ CLOUDFLARE_ACCOUNT_ID=<id> nimbus runtime sync --bucket nimbus-runtime-cache-public clang
90
+ CLOUDFLARE_ACCOUNT_ID=<id> nimbus runtime sync --bucket nimbus-runtime-cache-public python
91
+ CLOUDFLARE_ACCOUNT_ID=<id> nimbus runtime sync --bucket nimbus-runtime-cache-public ruby
92
+ ```
93
+
94
+ Runtime versions default to the shipped catalog versions
95
+ (`clang@binji-2020`, `python@0.29.4`, `ruby@3.3.4`) unless explicitly
96
+ overridden as `name@version`.
97
+
98
+ ### `nimbus runtime list`
99
+
100
+ ```bash
101
+ nimbus runtime list
102
+ # JSON catalog: [{name, version, size_mb, license}, ...]
103
+ ```
104
+
105
+ ### `nimbus session new`
106
+
107
+ ```bash
108
+ NIMBUS_ENDPOINT=https://my-nimbus.workers.dev nimbus session new
109
+ # {"sessionId":"pretty-otter-1234","url":"https://.../s/pretty-otter-1234/"}
110
+ ```
111
+
112
+ ## Programmatic use
113
+
114
+ Every verb is also exported as a function:
115
+
116
+ ```ts
117
+ import { mintToken, syncRuntimes, scaffold } from '@nimbus-sh/cli';
118
+ ```
119
+
120
+ `mintToken(argv)` and friends return a Promise<number> (process exit
121
+ code). Stdout / stderr write directly via `process.stdout`/`process.stderr`.
122
+
123
+ ## Engines
124
+
125
+ Requires Node >= 20 (for native `fetch`, `crypto.subtle`, etc.).
126
+
127
+ MIT.
package/dist/bin.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * bin/nimbus — Multi-verb CLI dispatcher.
4
+ *
5
+ * Usage:
6
+ * nimbus token mint --tenant acme --sub alice [--ttl 3600]
7
+ * nimbus token verify <token>
8
+ * nimbus runtime sync # upload runtime blobs/catalog through CLI wrapper
9
+ * nimbus runtime list # show staged runtimes
10
+ * nimbus session new # mint a session via /new
11
+ * nimbus --version
12
+ * nimbus --help
13
+ *
14
+ * Design notes:
15
+ * - Zero dependencies on commander/yargs. The verb table is ~50 LOC.
16
+ * This keeps the install footprint tiny (matters for `npx`).
17
+ * - Every verb writes JSON to stdout on success (machine-parseable),
18
+ * a human-readable summary line to stderr, and exits 0. Failures
19
+ * emit JSON `{ error, code }` + exit code from CLI_EXIT_CODES.
20
+ */
21
+ export {};
22
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;GAkBG"}
package/dist/bin.js ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * bin/nimbus — Multi-verb CLI dispatcher.
4
+ *
5
+ * Usage:
6
+ * nimbus token mint --tenant acme --sub alice [--ttl 3600]
7
+ * nimbus token verify <token>
8
+ * nimbus runtime sync # upload runtime blobs/catalog through CLI wrapper
9
+ * nimbus runtime list # show staged runtimes
10
+ * nimbus session new # mint a session via /new
11
+ * nimbus --version
12
+ * nimbus --help
13
+ *
14
+ * Design notes:
15
+ * - Zero dependencies on commander/yargs. The verb table is ~50 LOC.
16
+ * This keeps the install footprint tiny (matters for `npx`).
17
+ * - Every verb writes JSON to stdout on success (machine-parseable),
18
+ * a human-readable summary line to stderr, and exits 0. Failures
19
+ * emit JSON `{ error, code }` + exit code from CLI_EXIT_CODES.
20
+ */
21
+ import { mintToken, verifyTokenCmd } from './commands/token.js';
22
+ import { syncRuntimes, listRuntimes } from './commands/runtime-sync.js';
23
+ import { newSession } from './commands/session.js';
24
+ import { CLI_VERSION } from './version.js';
25
+ const verbs = {
26
+ token: {
27
+ mint: mintToken,
28
+ verify: verifyTokenCmd,
29
+ },
30
+ runtime: {
31
+ sync: syncRuntimes,
32
+ list: listRuntimes,
33
+ },
34
+ session: {
35
+ new: newSession,
36
+ },
37
+ };
38
+ async function main(argv) {
39
+ if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
40
+ printHelp();
41
+ return 0;
42
+ }
43
+ if (argv[0] === '--version' || argv[0] === '-v') {
44
+ process.stdout.write(`${CLI_VERSION}\n`);
45
+ return 0;
46
+ }
47
+ if (argv[0] === 'init') {
48
+ const { scaffold } = await import('./commands/scaffold.js');
49
+ return scaffold(argv.slice(1).length > 0 ? argv.slice(1) : ['.']);
50
+ }
51
+ const [namespace, verb, ...rest] = argv;
52
+ if (!verbs[namespace] || !verbs[namespace][verb]) {
53
+ process.stderr.write(`nimbus: unknown command \`${namespace} ${verb || ''}\`. Try \`nimbus --help\`.\n`);
54
+ return 64;
55
+ }
56
+ return verbs[namespace][verb](rest);
57
+ }
58
+ function printHelp() {
59
+ process.stdout.write(`nimbus ${CLI_VERSION}
60
+
61
+ Usage:
62
+ nimbus token mint --tenant <t> [--sub <s>] [--ttl <sec>] [--scopes <a,b>]
63
+ nimbus token verify <token>
64
+ nimbus runtime sync [--bucket <name>] [runtime[@version]...]
65
+ nimbus runtime list
66
+ nimbus session new [--endpoint <url>]
67
+ nimbus init [directory]
68
+
69
+ For embedder scaffolding:
70
+ create-nimbus-app <project-name>
71
+
72
+ Env:
73
+ JWT_SECRET Shared secret for HS256 (required for token mint/verify).
74
+ NIMBUS_ENDPOINT Base URL for session new. Defaults to localhost:8787.
75
+ CLOUDFLARE_ACCOUNT_ID Required for runtime sync.
76
+ `);
77
+ }
78
+ main(process.argv.slice(2)).then((code) => process.exit(code), (err) => {
79
+ process.stderr.write(`nimbus: ${err?.stack || err}\n`);
80
+ process.exit(70);
81
+ });
@@ -0,0 +1,31 @@
1
+ /**
2
+ * cli/commands/runtime-sync — Re-runs the runtime-bundle pipeline that
3
+ * populates an R2 bucket with the clang / python / ruby blobs.
4
+ *
5
+ * Two modes:
6
+ * - Default (no --bucket): syncs the canonical Nimbus-operated bucket
7
+ * `nimbus-runtime-cache-public` for the catalog the project ships
8
+ * today. This is what we run; embedders typically don't need it.
9
+ * - `--bucket <name>`: BYOA mode. Runtime names may be positional
10
+ * (`nimbus runtime sync python clang`) or comma-separated via
11
+ * `--runtimes python,clang`.
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.
17
+ */
18
+ /**
19
+ * Sync runtime blobs to an R2 bucket via the bundled
20
+ * `bundle-runtime.mjs` script.
21
+ *
22
+ * @example
23
+ * ```bash
24
+ * # BYOA mode — sync into your own bucket.
25
+ * CLOUDFLARE_ACCOUNT_ID=… nimbus runtime sync --bucket my-runtime-cache python
26
+ * ```
27
+ */
28
+ export declare function syncRuntimes(args: string[]): Promise<number>;
29
+ /** `nimbus runtime list` — print the catalog the SDK ships against. */
30
+ export declare function listRuntimes(_args: string[]): Promise<number>;
31
+ //# sourceMappingURL=runtime-sync.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * cli/commands/runtime-sync — Re-runs the runtime-bundle pipeline that
3
+ * populates an R2 bucket with the clang / python / ruby blobs.
4
+ *
5
+ * Two modes:
6
+ * - Default (no --bucket): syncs the canonical Nimbus-operated bucket
7
+ * `nimbus-runtime-cache-public` for the catalog the project ships
8
+ * today. This is what we run; embedders typically don't need it.
9
+ * - `--bucket <name>`: BYOA mode. Runtime names may be positional
10
+ * (`nimbus runtime sync python clang`) or comma-separated via
11
+ * `--runtimes python,clang`.
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.
17
+ */
18
+ import { spawn } from 'node:child_process';
19
+ import { existsSync } from 'node:fs';
20
+ import { createRequire } from 'node:module';
21
+ import { dirname, join } from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
23
+ const nodeRequire = createRequire(import.meta.url);
24
+ const DEFAULT_RUNTIME_VERSIONS = {
25
+ clang: 'binji-2020',
26
+ python: '0.29.4',
27
+ ruby: '3.3.4',
28
+ };
29
+ /**
30
+ * Sync runtime blobs to an R2 bucket via the bundled
31
+ * `bundle-runtime.mjs` script.
32
+ *
33
+ * @example
34
+ * ```bash
35
+ * # BYOA mode — sync into your own bucket.
36
+ * CLOUDFLARE_ACCOUNT_ID=… nimbus runtime sync --bucket my-runtime-cache python
37
+ * ```
38
+ */
39
+ export async function syncRuntimes(args) {
40
+ const parsed = parseFlags(args);
41
+ const bucket = parsed.bucket ?? 'nimbus-runtime-cache-public';
42
+ const runtimes = parsed.runtimes.length > 0
43
+ ? parsed.runtimes
44
+ : ['clang', 'python', 'ruby'];
45
+ if (!process.env.CLOUDFLARE_ACCOUNT_ID) {
46
+ process.stderr.write('nimbus runtime sync: CLOUDFLARE_ACCOUNT_ID env var required\n');
47
+ return 78;
48
+ }
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.
52
+ const scriptPath = resolveBundleRuntimeScript();
53
+ if (!scriptPath) {
54
+ process.stderr.write('nimbus runtime sync: cannot locate @nimbus-sh/worker/scripts/bundle-runtime.mjs\n');
55
+ return 70;
56
+ }
57
+ process.stderr.write(`nimbus: syncing runtimes [${runtimes.join(', ')}] → r2://${bucket}\n`);
58
+ for (const rt of runtimes) {
59
+ const [name, explicitVersion] = rt.split('@');
60
+ const version = explicitVersion || DEFAULT_RUNTIME_VERSIONS[name];
61
+ if (!version) {
62
+ process.stderr.write(`nimbus runtime sync: unknown runtime "${rt}" (use name@version)\n`);
63
+ return 64;
64
+ }
65
+ const code = await runOne(scriptPath, [name, version, '--bucket', bucket]);
66
+ if (code !== 0) {
67
+ process.stderr.write(`nimbus runtime sync: ${rt} failed (exit ${code})\n`);
68
+ return code;
69
+ }
70
+ }
71
+ process.stdout.write(JSON.stringify({ ok: true, bucket, runtimes }) + '\n');
72
+ return 0;
73
+ }
74
+ /** `nimbus runtime list` — print the catalog the SDK ships against. */
75
+ export async function listRuntimes(_args) {
76
+ // v0.1: print the static known-list. v0.2 will fetch the live
77
+ // catalog.json from R2.
78
+ const catalog = [
79
+ { name: 'clang', version: 'binji-2020', size_mb: 9, license: 'Apache-2.0-LLVM' },
80
+ { name: 'python', version: 'pyodide-0.29.4', size_mb: 10, license: 'MPL-2.0' },
81
+ { name: 'ruby', version: 'ruby.wasm-2.9.4', size_mb: 25, license: 'BSD-2-Clause' },
82
+ ];
83
+ process.stdout.write(JSON.stringify(catalog, null, 2) + '\n');
84
+ return 0;
85
+ }
86
+ // ── helpers ──────────────────────────────────────────────────────────
87
+ function resolveBundleRuntimeScript() {
88
+ // 1. Try resolving via the @nimbus-sh/worker package (npm-installed).
89
+ try {
90
+ const packageJson = nodeRequire.resolve('@nimbus-sh/worker/package.json');
91
+ const script = join(dirname(packageJson), 'scripts', 'bundle-runtime.mjs');
92
+ if (existsSync(script))
93
+ return script;
94
+ }
95
+ catch {
96
+ // fallthrough
97
+ }
98
+ // 2. Workspace dogfood — relative path inside the monorepo.
99
+ try {
100
+ const wsUrl = new URL('../../../worker/scripts/bundle-runtime.mjs', import.meta.url);
101
+ const script = fileURLToPath(wsUrl);
102
+ return existsSync(script) ? script : null;
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ }
108
+ function runOne(scriptPath, args) {
109
+ return new Promise((resolveExit) => {
110
+ const child = spawn('node', [scriptPath, ...args], {
111
+ stdio: 'inherit',
112
+ env: process.env,
113
+ });
114
+ child.on('exit', (code) => resolveExit(code ?? 1));
115
+ child.on('error', (e) => {
116
+ process.stderr.write(`spawn error: ${e.message}\n`);
117
+ resolveExit(70);
118
+ });
119
+ });
120
+ }
121
+ function parseFlags(args) {
122
+ const out = { runtimes: [] };
123
+ for (let i = 0; i < args.length; i++) {
124
+ const a = args[i];
125
+ if (a === '--bucket') {
126
+ out.bucket = args[i + 1] ?? '';
127
+ i++;
128
+ continue;
129
+ }
130
+ if (a === '--runtimes') {
131
+ out.runtimes.push(...(args[i + 1] ?? '').split(',').map((s) => s.trim()).filter(Boolean));
132
+ i++;
133
+ continue;
134
+ }
135
+ if (a.startsWith('--'))
136
+ continue;
137
+ out.runtimes.push(a);
138
+ }
139
+ return out;
140
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * cli/commands/scaffold — `create-nimbus-app <project-name>`.
3
+ *
4
+ * v0.1 ships a single template: `worker-only`. The scaffolder writes:
5
+ *
6
+ * <project>/
7
+ * ├── package.json — deps: @nimbus-sh/worker, @nimbus-sh/sdk
8
+ * ├── wrangler.jsonc — the canonical 28-LOC embedder snippet
9
+ * ├── src/
10
+ * │ └── index.ts — 6 LOC default-export
11
+ * ├── README.md — install + deploy instructions
12
+ * └── .gitignore — node_modules, .wrangler
13
+ *
14
+ * The scaffolder does NOT call `wrangler login`, `wrangler secret put`,
15
+ * or `wrangler deploy` — those are interactive and operator-owned. We
16
+ * print the exact commands to run as a "next steps" trailer.
17
+ */
18
+ /**
19
+ * Scaffold a new Nimbus project at the given directory.
20
+ *
21
+ * @param args Argv: `[project-name, ...flags]`. Flags:
22
+ * `--template worker-only` (default)
23
+ * `--name <wrangler-name>` (default: project-name)
24
+ * `--force` overwrite existing files.
25
+ */
26
+ export declare function scaffold(args: string[]): Promise<number>;
27
+ //# sourceMappingURL=scaffold.d.ts.map
@@ -0,0 +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,CAkE9D"}
@@ -0,0 +1,290 @@
1
+ /**
2
+ * cli/commands/scaffold — `create-nimbus-app <project-name>`.
3
+ *
4
+ * v0.1 ships a single template: `worker-only`. The scaffolder writes:
5
+ *
6
+ * <project>/
7
+ * ├── package.json — deps: @nimbus-sh/worker, @nimbus-sh/sdk
8
+ * ├── wrangler.jsonc — the canonical 28-LOC embedder snippet
9
+ * ├── src/
10
+ * │ └── index.ts — 6 LOC default-export
11
+ * ├── README.md — install + deploy instructions
12
+ * └── .gitignore — node_modules, .wrangler
13
+ *
14
+ * The scaffolder does NOT call `wrangler login`, `wrangler secret put`,
15
+ * or `wrangler deploy` — those are interactive and operator-owned. We
16
+ * print the exact commands to run as a "next steps" trailer.
17
+ */
18
+ import { mkdirSync, writeFileSync, existsSync } from 'node:fs';
19
+ import { resolve, join, basename } from 'node:path';
20
+ /**
21
+ * Scaffold a new Nimbus project at the given directory.
22
+ *
23
+ * @param args Argv: `[project-name, ...flags]`. Flags:
24
+ * `--template worker-only` (default)
25
+ * `--name <wrangler-name>` (default: project-name)
26
+ * `--force` overwrite existing files.
27
+ */
28
+ export async function scaffold(args) {
29
+ const rawProjectName = args[0];
30
+ const projectName = rawProjectName === '.'
31
+ ? basename(process.cwd())
32
+ : rawProjectName;
33
+ if (!projectName || projectName.startsWith('-')) {
34
+ process.stderr.write('create-nimbus-app: <project-name> required\n');
35
+ process.stderr.write('Usage: create-nimbus-app my-app [--name my-worker] [--force]\n');
36
+ return 64;
37
+ }
38
+ const parsed = parseFlags(args.slice(1));
39
+ const wranglerName = parsed['--name'] || projectName;
40
+ const force = '--force' in parsed;
41
+ const template = parsed['--template'] || 'worker-only';
42
+ if (template !== 'worker-only') {
43
+ process.stderr.write(`create-nimbus-app: unknown template "${template}". Only "worker-only" ships in v0.1.\n`);
44
+ return 64;
45
+ }
46
+ const target = rawProjectName === '.'
47
+ ? process.cwd()
48
+ : resolve(process.cwd(), projectName);
49
+ if (existsSync(target) && !force) {
50
+ process.stderr.write(`create-nimbus-app: directory exists: ${target} (use --force to overwrite)\n`);
51
+ return 73;
52
+ }
53
+ mkdirSync(target, { recursive: true });
54
+ mkdirSync(join(target, 'src'), { recursive: true });
55
+ writeFileSync(join(target, 'package.json'), renderPackageJson(projectName));
56
+ writeFileSync(join(target, 'wrangler.jsonc'), renderWranglerJsonc(wranglerName));
57
+ writeFileSync(join(target, 'src', 'index.ts'), renderIndexTs());
58
+ writeFileSync(join(target, 'README.md'), renderReadme(projectName, wranglerName));
59
+ writeFileSync(join(target, '.gitignore'), '.wrangler\nnode_modules\ndist\n');
60
+ writeFileSync(join(target, 'tsconfig.json'), renderTsconfig());
61
+ // Stdout — JSON for machine, friendly stderr for humans.
62
+ process.stdout.write(JSON.stringify({
63
+ ok: true,
64
+ path: target,
65
+ nextSteps: [
66
+ `cd ${projectName}`,
67
+ 'npm install',
68
+ 'npx wrangler secret put JWT_SECRET',
69
+ 'npx wrangler deploy',
70
+ ],
71
+ }) + '\n');
72
+ process.stderr.write(`
73
+ ✨ Scaffolded ${projectName} at ${target}
74
+
75
+ Next steps:
76
+ cd ${projectName}
77
+ npm install
78
+ npx wrangler secret put JWT_SECRET # paste a 32+ char hex secret
79
+ npx wrangler deploy
80
+
81
+ Deploy will provision the DO + R2 buckets on first run. Visit the
82
+ returned URL and hit "Launch" to mint a session.
83
+
84
+ Docs: https://github.com/AshishKumar4/Nimbus#readme
85
+ `);
86
+ return 0;
87
+ }
88
+ // ── template renderers ──────────────────────────────────────────────
89
+ function renderPackageJson(name) {
90
+ return JSON.stringify({
91
+ name,
92
+ version: '0.1.0',
93
+ private: true,
94
+ type: 'module',
95
+ scripts: {
96
+ dev: 'wrangler dev --ip 0.0.0.0 --port 8787',
97
+ deploy: 'wrangler deploy',
98
+ typecheck: 'tsc --noEmit',
99
+ },
100
+ dependencies: {
101
+ '@nimbus-sh/worker': '^0.1.0',
102
+ '@nimbus-sh/sdk': '^0.1.0',
103
+ },
104
+ devDependencies: {
105
+ '@cloudflare/workers-types': '^4.20250327.0',
106
+ typescript: '^5.7.0',
107
+ wrangler: '^4.0.0',
108
+ },
109
+ }, null, 2) + '\n';
110
+ }
111
+ function renderWranglerJsonc(name) {
112
+ return `{
113
+ "$schema": "./node_modules/wrangler/config-schema.json",
114
+ "name": "${name}",
115
+ "main": "src/index.ts",
116
+ "compatibility_date": "2026-04-01",
117
+ "compatibility_flags": ["nodejs_compat"],
118
+ "placement": { "mode": "smart" },
119
+
120
+ "assets": {
121
+ "directory": "node_modules/@nimbus-sh/worker/public",
122
+ "binding": "ASSETS",
123
+ "run_worker_first": ["/s/*", "/new"]
124
+ },
125
+
126
+ // The Node-compat shims isomorphic-git + the npm installer need. Do
127
+ // not omit any of these — \`git clone\` will fail at runtime if any
128
+ // are missing.
129
+ "alias": {
130
+ "@lifo-sh/ui": "./node_modules/@nimbus-sh/worker/dist/stubs/lifo-ui.js",
131
+ "clean-git-ref": "clean-git-ref/lib/index.js",
132
+ "is-git-ref-name-valid": "is-git-ref-name-valid/index.js",
133
+ "crc-32": "crc-32",
134
+ "sha.js": "sha.js",
135
+ "pako": "pako",
136
+ "pify": "pify",
137
+ "diff": "diff",
138
+ "diff3": "diff3",
139
+ "ignore": "ignore",
140
+ "readable-stream": "readable-stream",
141
+ "simple-get": "simple-get",
142
+ "minimisted": "minimisted"
143
+ },
144
+
145
+ "durable_objects": {
146
+ "bindings": [{ "name": "NIMBUS_SESSION", "class_name": "NimbusSession" }]
147
+ },
148
+ "migrations": [
149
+ { "tag": "nimbus-v1", "new_sqlite_classes": ["NimbusSession"] }
150
+ ],
151
+
152
+ "worker_loaders": [{ "binding": "LOADER" }],
153
+
154
+ "r2_buckets": [
155
+ { "binding": "NPM_TARBALL_CACHE", "bucket_name": "${name}-npm-cache" },
156
+ { "binding": "NPM_PACKUMENT_CACHE", "bucket_name": "${name}-npm-packument-cache" },
157
+ { "binding": "NIMBUS_RUNTIME_CACHE", "bucket_name": "nimbus-runtime-cache-public" }
158
+ ]
159
+ }
160
+ `;
161
+ }
162
+ function renderIndexTs() {
163
+ return `import {
164
+ NimbusSession,
165
+ SupervisorRPC,
166
+ NimbusAssetsRPC,
167
+ NimbusLoaderRPC,
168
+ NimbusLoadedWorker,
169
+ NimbusLoadedEntrypoint,
170
+ NimbusDurableObjectNamespace,
171
+ NimbusDOStub,
172
+ CirrusHmrRPC,
173
+ createNimbusHandler,
174
+ } from '@nimbus-sh/worker';
175
+
176
+ // Re-export the DO class + every RPC class so wrangler's
177
+ // \`durable_objects.bindings[].class_name\` lookup + \`enable_ctx_exports\`
178
+ // auto-populate loopback bindings (env.SUPERVISOR, etc.).
179
+ export {
180
+ NimbusSession,
181
+ SupervisorRPC,
182
+ NimbusAssetsRPC,
183
+ NimbusLoaderRPC,
184
+ NimbusLoadedWorker,
185
+ NimbusLoadedEntrypoint,
186
+ NimbusDurableObjectNamespace,
187
+ NimbusDOStub,
188
+ CirrusHmrRPC,
189
+ };
190
+
191
+ export default createNimbusHandler();
192
+ `;
193
+ }
194
+ function renderTsconfig() {
195
+ return JSON.stringify({
196
+ compilerOptions: {
197
+ target: 'ES2022',
198
+ module: 'ES2022',
199
+ moduleResolution: 'bundler',
200
+ lib: ['ES2022'],
201
+ types: ['@cloudflare/workers-types'],
202
+ strict: true,
203
+ noEmit: true,
204
+ skipLibCheck: true,
205
+ },
206
+ include: ['src/**/*.ts'],
207
+ }, null, 2) + '\n';
208
+ }
209
+ function renderReadme(name, wranglerName) {
210
+ return `# ${name}
211
+
212
+ A Nimbus-powered Cloudflare Worker.
213
+
214
+ ## Setup
215
+
216
+ \`\`\`bash
217
+ npm install
218
+ npx wrangler secret put JWT_SECRET # paste a 32+ char hex secret
219
+ npx wrangler deploy
220
+ \`\`\`
221
+
222
+ Then visit the URL wrangler prints. Click "Launch" to mint a session.
223
+
224
+ ## Custom auth
225
+
226
+ By default tokens are enforced (\`JWT_SECRET\` set). To mint:
227
+
228
+ \`\`\`bash
229
+ JWT_SECRET=<your-secret> npx @nimbus-sh/cli token mint --tenant acme --sub alice
230
+ \`\`\`
231
+
232
+ Embed in a React app:
233
+
234
+ \`\`\`tsx
235
+ import { NimbusTerminal } from '@nimbus-sh/react';
236
+
237
+ <NimbusTerminal
238
+ endpoint="https://${wranglerName}.your-account.workers.dev"
239
+ token={jwt}
240
+ tenant="acme"
241
+ sub="alice"
242
+ />
243
+ \`\`\`
244
+
245
+ ## Customize
246
+
247
+ Edit \`src/index.ts\`:
248
+
249
+ \`\`\`ts
250
+ import { NimbusSession, createNimbusHandler } from '@nimbus-sh/worker';
251
+ import { issueNimbusToken } from '@nimbus-sh/sdk/token';
252
+
253
+ export { NimbusSession };
254
+
255
+ export default createNimbusHandler({
256
+ hooks: {
257
+ onSessionStart: ({ sessionId, tenantSegment }) => {
258
+ console.log(\`session \${sessionId} for \${tenantSegment}\`);
259
+ },
260
+ },
261
+ routes: async (req, env) => {
262
+ if (new URL(req.url).pathname === '/api/auth/mint') {
263
+ const { tenant, sub } = await req.json();
264
+ const token = await issueNimbusToken(env, { tn: tenant, sub });
265
+ return Response.json({ token });
266
+ }
267
+ return null;
268
+ },
269
+ });
270
+ \`\`\`
271
+
272
+ Docs: https://github.com/AshishKumar4/Nimbus
273
+ `;
274
+ }
275
+ function parseFlags(args) {
276
+ const out = {};
277
+ for (let i = 0; i < args.length; i++) {
278
+ const a = args[i];
279
+ if (!a.startsWith('--'))
280
+ continue;
281
+ if (args[i + 1] && !args[i + 1].startsWith('--')) {
282
+ out[a] = args[i + 1];
283
+ i++;
284
+ }
285
+ else {
286
+ out[a] = '';
287
+ }
288
+ }
289
+ return out;
290
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * cli/commands/session — `nimbus session new` — mint a session via POST /new.
3
+ */
4
+ /** Mint a fresh session and print its attach URL. */
5
+ export declare function newSession(args: string[]): Promise<number>;
6
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/commands/session.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,qDAAqD;AACrD,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA6BhE"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * cli/commands/session — `nimbus session new` — mint a session via POST /new.
3
+ */
4
+ /** Mint a fresh session and print its attach URL. */
5
+ export async function newSession(args) {
6
+ const parsed = parseFlags(args);
7
+ const endpoint = parsed['--endpoint']
8
+ ?? process.env.NIMBUS_ENDPOINT
9
+ ?? 'http://127.0.0.1:8787';
10
+ try {
11
+ const r = await fetch(`${endpoint.replace(/\/+$/, '')}/new`, {
12
+ method: 'POST',
13
+ redirect: 'manual',
14
+ });
15
+ const loc = r.headers.get('Location');
16
+ if (!loc) {
17
+ process.stderr.write(`nimbus session new: POST /new returned no Location (status ${r.status})\n`);
18
+ return 70;
19
+ }
20
+ const m = loc.match(/\/s\/([^/]+)/);
21
+ if (!m) {
22
+ process.stderr.write(`nimbus session new: unexpected Location: ${loc}\n`);
23
+ return 70;
24
+ }
25
+ const sessionId = m[1];
26
+ const url = `${endpoint.replace(/\/+$/, '')}/s/${sessionId}/`;
27
+ process.stdout.write(JSON.stringify({ sessionId, url }) + '\n');
28
+ return 0;
29
+ }
30
+ catch (e) {
31
+ process.stderr.write(`nimbus session new: ${e?.message || e}\n`);
32
+ return 70;
33
+ }
34
+ }
35
+ function parseFlags(args) {
36
+ const out = {};
37
+ for (let i = 0; i < args.length; i++) {
38
+ const a = args[i];
39
+ if (!a.startsWith('--'))
40
+ continue;
41
+ out[a] = args[i + 1] ?? '';
42
+ i++;
43
+ }
44
+ return out;
45
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * cli/commands/token — `nimbus token mint` + `nimbus token verify`.
3
+ *
4
+ * Both verbs require `JWT_SECRET` in env. Mint writes the JWT to stdout
5
+ * (so `nimbus token mint --tenant acme > /tmp/jwt` works in scripts);
6
+ * verify parses a token from argv and prints the claims as JSON.
7
+ */
8
+ /**
9
+ * Programmatic interface for token mint. Used by `bin.ts` and exported
10
+ * from `@nimbus-sh/cli` so embedder scripts can call it directly.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { mintToken } from '@nimbus-sh/cli';
15
+ * const code = await mintToken(['--tenant', 'acme', '--sub', 'alice']);
16
+ * ```
17
+ *
18
+ * @param args Raw CLI args (after the `token mint` prefix).
19
+ * @returns Process exit code (0 success, non-zero per
20
+ * `CLI_EXIT_CODES` semantics).
21
+ */
22
+ export declare function mintToken(args: string[]): Promise<number>;
23
+ /** `nimbus token verify <token>` — verify + print claims as JSON. */
24
+ export declare function verifyTokenCmd(args: string[]): Promise<number>;
25
+ //# sourceMappingURL=token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/commands/token.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH;;;;;;;;;;;;;GAaG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA6B/D;AAED,qEAAqE;AACrE,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBpE"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * cli/commands/token — `nimbus token mint` + `nimbus token verify`.
3
+ *
4
+ * Both verbs require `JWT_SECRET` in env. Mint writes the JWT to stdout
5
+ * (so `nimbus token mint --tenant acme > /tmp/jwt` works in scripts);
6
+ * verify parses a token from argv and prints the claims as JSON.
7
+ */
8
+ import { issueNimbusToken, verifyNimbusToken } from '@nimbus-sh/sdk/token';
9
+ import { NimbusAuthError } from '@nimbus-sh/sdk/errors';
10
+ /**
11
+ * Programmatic interface for token mint. Used by `bin.ts` and exported
12
+ * from `@nimbus-sh/cli` so embedder scripts can call it directly.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { mintToken } from '@nimbus-sh/cli';
17
+ * const code = await mintToken(['--tenant', 'acme', '--sub', 'alice']);
18
+ * ```
19
+ *
20
+ * @param args Raw CLI args (after the `token mint` prefix).
21
+ * @returns Process exit code (0 success, non-zero per
22
+ * `CLI_EXIT_CODES` semantics).
23
+ */
24
+ export async function mintToken(args) {
25
+ const parsed = parseArgs(args, ['--tenant', '--sub', '--ttl', '--scopes', '--sid']);
26
+ const tn = parsed['--tenant'];
27
+ const sub = parsed['--sub'];
28
+ const ttlSec = parsed['--ttl'] ? Number(parsed['--ttl']) : undefined;
29
+ const scopes = parsed['--scopes'] ? parsed['--scopes'].split(',').map((s) => s.trim()) : undefined;
30
+ const sid = parsed['--sid'];
31
+ if (!tn) {
32
+ process.stderr.write('nimbus token mint: --tenant required\n');
33
+ return 64;
34
+ }
35
+ const secret = process.env.JWT_SECRET;
36
+ if (!secret) {
37
+ process.stderr.write('nimbus token mint: JWT_SECRET env var required\n');
38
+ return 78;
39
+ }
40
+ try {
41
+ const token = await issueNimbusToken({ JWT_SECRET: secret }, { tn, sub, scopes, sid }, ttlSec !== undefined ? { ttlMs: ttlSec * 1000 } : {});
42
+ process.stdout.write(`${token}\n`);
43
+ return 0;
44
+ }
45
+ catch (e) {
46
+ return reportError(e);
47
+ }
48
+ }
49
+ /** `nimbus token verify <token>` — verify + print claims as JSON. */
50
+ export async function verifyTokenCmd(args) {
51
+ const token = args[0];
52
+ if (!token) {
53
+ process.stderr.write('nimbus token verify: <token> argument required\n');
54
+ return 64;
55
+ }
56
+ const secret = process.env.JWT_SECRET;
57
+ if (!secret) {
58
+ process.stderr.write('nimbus token verify: JWT_SECRET env var required\n');
59
+ return 78;
60
+ }
61
+ try {
62
+ const verified = await verifyNimbusToken({ JWT_SECRET: secret }, token);
63
+ process.stdout.write(`${JSON.stringify(verified, null, 2)}\n`);
64
+ return 0;
65
+ }
66
+ catch (e) {
67
+ return reportError(e);
68
+ }
69
+ }
70
+ /** Map a NimbusAuthError or unknown error to a JSON stderr report. */
71
+ function reportError(e) {
72
+ if (e instanceof NimbusAuthError) {
73
+ process.stderr.write(`${JSON.stringify({ error: e.message, code: e.code })}\n`);
74
+ return e.httpStatus >= 500 ? 70 : 65;
75
+ }
76
+ process.stderr.write(`${JSON.stringify({ error: String(e?.message ?? e), code: 'E_UNKNOWN' })}\n`);
77
+ return 70;
78
+ }
79
+ /**
80
+ * Mini argv parser. Long flags only (`--key value` or `--key=value`).
81
+ * Returns a record of seen flags. Unknown flags are silently dropped
82
+ * (matches Unix tradition).
83
+ */
84
+ function parseArgs(args, known) {
85
+ const out = {};
86
+ for (let i = 0; i < args.length; i++) {
87
+ const a = args[i];
88
+ if (!a.startsWith('--'))
89
+ continue;
90
+ const eq = a.indexOf('=');
91
+ let key, val;
92
+ if (eq >= 0) {
93
+ key = a.slice(0, eq);
94
+ val = a.slice(eq + 1);
95
+ }
96
+ else {
97
+ key = a;
98
+ val = args[i + 1] ?? '';
99
+ i++;
100
+ }
101
+ if (known.includes(key))
102
+ out[key] = val;
103
+ }
104
+ return out;
105
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @nimbus-sh/cli — Programmatic surface.
3
+ *
4
+ * The CLI's `bin` entries (`nimbus`, `create-nimbus-app`) call these
5
+ * functions. Embedders building custom dev tooling can import them
6
+ * directly:
7
+ *
8
+ * ```ts
9
+ * import { mintToken, syncRuntimes } from '@nimbus-sh/cli';
10
+ * ```
11
+ */
12
+ export { mintToken } from './commands/token.js';
13
+ export { syncRuntimes } from './commands/runtime-sync.js';
14
+ export { scaffold } from './commands/scaffold.js';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @nimbus-sh/cli — Programmatic surface.
3
+ *
4
+ * The CLI's `bin` entries (`nimbus`, `create-nimbus-app`) call these
5
+ * functions. Embedders building custom dev tooling can import them
6
+ * directly:
7
+ *
8
+ * ```ts
9
+ * import { mintToken, syncRuntimes } from '@nimbus-sh/cli';
10
+ * ```
11
+ */
12
+ export { mintToken } from './commands/token.js';
13
+ export { syncRuntimes } from './commands/runtime-sync.js';
14
+ export { scaffold } from './commands/scaffold.js';
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * bin/create-nimbus-app — scaffolder entry.
4
+ *
5
+ * Thin wrapper around `scaffold()` in commands/scaffold.ts. Exists as a
6
+ * separate bin so the eventual npm artifact can support
7
+ * `create-nimbus-app` without verb routing.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=scaffold-bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold-bin.d.ts","sourceRoot":"","sources":["../src/scaffold-bin.ts"],"names":[],"mappings":";AACA;;;;;;GAMG"}
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * bin/create-nimbus-app — scaffolder entry.
4
+ *
5
+ * Thin wrapper around `scaffold()` in commands/scaffold.ts. Exists as a
6
+ * separate bin so the eventual npm artifact can support
7
+ * `create-nimbus-app` without verb routing.
8
+ */
9
+ import { scaffold } from './commands/scaffold.js';
10
+ scaffold(process.argv.slice(2)).then((code) => process.exit(code), (err) => {
11
+ process.stderr.write(`create-nimbus-app: ${err?.stack || err}\n`);
12
+ process.exit(70);
13
+ });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Hardcoded version constant. Updated by release tooling. We avoid
3
+ * `require('./package.json')` because that breaks under bun's
4
+ * single-file binary mode.
5
+ */
6
+ export declare const CLI_VERSION = "0.1.0";
7
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,WAAW,UAAU,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Hardcoded version constant. Updated by release tooling. We avoid
3
+ * `require('./package.json')` because that breaks under bun's
4
+ * single-file binary mode.
5
+ */
6
+ export const CLI_VERSION = '0.1.0';
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@nimbus-sh/cli",
3
+ "version": "0.1.0",
4
+ "description": "Nimbus CLI — `create-nimbus-app` scaffolder + `token mint` + `runtime sync`.",
5
+ "homepage": "https://github.com/AshishKumar4/Nimbus",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/AshishKumar4/Nimbus.git",
9
+ "directory": "packages/cli"
10
+ },
11
+ "bugs": "https://github.com/AshishKumar4/Nimbus/issues",
12
+ "license": "MIT",
13
+ "type": "module",
14
+ "bin": {
15
+ "nimbus": "./dist/bin.js",
16
+ "create-nimbus-app": "./dist/scaffold-bin.js"
17
+ },
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "workspace": "./src/index.ts",
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js"
25
+ },
26
+ "./package.json": "./package.json"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md"
31
+ ],
32
+ "scripts": {
33
+ "build": "tsc -p tsconfig.json --noCheck --noEmit false --declaration true --declarationMap true --outDir dist --rootDir src && chmod +x dist/bin.js dist/scaffold-bin.js",
34
+ "prepack": "npm run build",
35
+ "typecheck": "tsc --noEmit"
36
+ },
37
+ "dependencies": {
38
+ "@nimbus-sh/sdk": "^0.1.0",
39
+ "@nimbus-sh/worker": "^0.1.0"
40
+ },
41
+ "engines": {
42
+ "node": ">=20"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^22.0.0",
46
+ "typescript": "^5.7.0"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ }
51
+ }