retiam 1.0.0 → 1.0.2

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/bin/init.js CHANGED
@@ -1,425 +1,425 @@
1
- #!/usr/bin/env node
2
-
3
- // =========================================================================
4
- // File: bin/init.js - Retiam Project Starter & Skeleton Generator
5
- // =========================================================================
6
-
7
- import fs from 'fs';
8
- import path from 'path';
9
- import readline from 'readline/promises';
10
- import { spawnSync } from 'child_process';
11
-
12
- const args = process.argv.slice(2);
13
-
14
- // Check for --help
15
- const isHelp = args.includes('--help') || args.includes('-h') || args.includes('help');
16
- if (isHelp) {
17
- console.log(`
18
- 🚀 Retiam WebGPU Engine - Project Initializer
19
-
20
- Usage:
21
- npx retiam init [directory] [options]
22
- npx retiam create [directory] [options]
23
-
24
- Arguments:
25
- [directory] Destination directory (default: ./retiam-animation)
26
-
27
- Options:
28
- -n, --name <name> Project name for package.json (default: directory name)
29
- --title <text> Hero text to display in Hello World scene (default: Hello World)
30
- --pm <manager> Package manager: npm, pnpm, yarn, bun (default: auto-detected or npm)
31
- -f, --force Overwrite destination directory if files already exist
32
- -y, --yes Skip interactive prompts and use defaults
33
- --install Automatically install dependencies after generation
34
- --git Initialize a Git repository (git init)
35
- --json Output structured machine-readable JSON for agents and CI
36
- --help, -h Show this help message and exit
37
-
38
- Examples:
39
- npx retiam init
40
- npx retiam init my-animation
41
- npx retiam create intro-scene --title "Quantum Mechanics" --pm pnpm
42
- npx retiam init my-project -y --install
43
- npx retiam init agent-project --json --yes
44
- `);
45
- process.exit(0);
46
- }
47
-
48
- // Auto-detect package manager from npm_config_user_agent
49
- function detectPackageManager() {
50
- const userAgent = process.env.npm_config_user_agent || '';
51
- if (userAgent.startsWith('pnpm')) return 'pnpm';
52
- if (userAgent.startsWith('yarn')) return 'yarn';
53
- if (userAgent.startsWith('bun')) return 'bun';
54
- return 'npm';
55
- }
56
-
57
- // Parse CLI flags
58
- let targetDir = null;
59
- let projectName = null;
60
- let heroTitle = 'Hello World';
61
- let packageManager = detectPackageManager();
62
- let force = false;
63
- let nonInteractive = false;
64
- let runInstall = false;
65
- let initGit = false;
66
- let jsonOutput = false;
67
-
68
- for (let i = 0; i < args.length; i++) {
69
- const arg = args[i];
70
- if (arg === '--json') {
71
- jsonOutput = true;
72
- nonInteractive = true;
73
- } else if (arg === '-y' || arg === '--yes') {
74
- nonInteractive = true;
75
- } else if (arg === '-f' || arg === '--force') {
76
- force = true;
77
- } else if (arg === '--install') {
78
- runInstall = true;
79
- } else if (arg === '--git') {
80
- initGit = true;
81
- } else if (arg === '-n' || arg === '--name') {
82
- projectName = args[++i];
83
- } else if (arg === '--title') {
84
- heroTitle = args[++i] || 'Hello World';
85
- } else if (arg === '--pm') {
86
- packageManager = args[++i] || packageManager;
87
- } else if (!arg.startsWith('-') && !targetDir) {
88
- targetDir = arg;
89
- }
90
- }
91
-
92
- async function main() {
93
- // Interactive setup if TTY and not in --yes / --json mode
94
- const isTTY = process.stdin.isTTY && !nonInteractive;
95
-
96
- if (isTTY) {
97
- const rl = readline.createInterface({
98
- input: process.stdin,
99
- output: process.stdout
100
- });
101
-
102
- try {
103
- console.log(`\n✨ Welcome to Retiam - WebGPU Animation Project Generator\n`);
104
-
105
- if (!targetDir) {
106
- const inputDir = await rl.question(`📁 Destination directory (./retiam-animation): `);
107
- targetDir = inputDir.trim() || 'retiam-animation';
108
- }
109
-
110
- const defaultName = path.basename(path.resolve(process.cwd(), targetDir))
111
- .toLowerCase()
112
- .replace(/[^a-z0-9_-]/g, '-');
113
-
114
- if (!projectName) {
115
- const inputName = await rl.question(`📦 Project name (${defaultName}): `);
116
- projectName = inputName.trim() || defaultName;
117
- }
118
-
119
- const inputTitle = await rl.question(`🎨 Starter animation text (${heroTitle}): `);
120
- if (inputTitle.trim()) {
121
- heroTitle = inputTitle.trim();
122
- }
123
-
124
- const inputPm = await rl.question(`⚡ Package manager [npm/pnpm/yarn/bun] (${packageManager}): `);
125
- if (inputPm.trim()) {
126
- packageManager = inputPm.trim().toLowerCase();
127
- }
128
-
129
- const inputInstall = await rl.question(`📥 Run '${packageManager} install' now? [y/N]: `);
130
- if (inputInstall.trim().toLowerCase().startsWith('y')) {
131
- runInstall = true;
132
- }
133
- } finally {
134
- rl.close();
135
- }
136
- }
137
-
138
- if (!targetDir) {
139
- targetDir = 'retiam-animation';
140
- }
141
-
142
- const resolvedDir = path.resolve(process.cwd(), targetDir);
143
- const resolvedName = projectName || path.basename(resolvedDir)
144
- .toLowerCase()
145
- .replace(/[^a-z0-9_-]/g, '-') || 'retiam-animation';
146
-
147
- // Conflict check
148
- if (fs.existsSync(resolvedDir)) {
149
- const existingFiles = fs.readdirSync(resolvedDir).filter(f => !f.startsWith('.'));
150
- if (existingFiles.length > 0 && !force) {
151
- if (jsonOutput) {
152
- console.log(JSON.stringify({
153
- status: 'error',
154
- message: `Target directory '${targetDir}' already exists and is not empty. Use -f or --force to overwrite.`
155
- }));
156
- } else {
157
- console.error(`\n❌ Error: Directory '${targetDir}' is not empty (${existingFiles.length} files found).`);
158
- console.error(` Use -f or --force to proceed anyway.\n`);
159
- }
160
- process.exit(1);
161
- }
162
- }
163
-
164
- // Ensure directory structure
165
- const srcDir = path.join(resolvedDir, 'src');
166
- const scenesDir = path.join(srcDir, 'scenes');
167
- fs.mkdirSync(scenesDir, { recursive: true });
168
-
169
- // 1. package.json
170
- const packageJson = {
171
- name: resolvedName,
172
- version: '0.1.0',
173
- private: true,
174
- type: 'module',
175
- scripts: {
176
- dev: 'vite',
177
- build: 'tsc && vite build',
178
- preview: 'vite preview',
179
- render: 'retiam render src/scenes/HelloWorld.ts'
180
- },
181
- dependencies: {
182
- retiam: '^1.0.0'
183
- },
184
- devDependencies: {
185
- '@webgpu/types': '^0.1.31',
186
- typescript: '^5.5.0',
187
- vite: '^5.4.0'
188
- }
189
- };
190
-
191
- // 2. tsconfig.json
192
- const tsconfigJson = {
193
- compilerOptions: {
194
- target: 'ES2022',
195
- module: 'ESNext',
196
- moduleResolution: 'bundler',
197
- lib: ['ES2022', 'DOM', 'DOM.Iterable'],
198
- types: ['@webgpu/types', 'vite/client'],
199
- strict: true,
200
- esModuleInterop: true,
201
- skipLibCheck: true,
202
- isolatedModules: true
203
- },
204
- include: ['src']
205
- };
206
-
207
- // 3. vite.config.ts
208
- const viteConfig = `import { defineConfig } from 'vite';
209
-
210
- export default defineConfig({
211
- optimizeDeps: {
212
- esbuildOptions: {
213
- target: 'es2022',
214
- supported: {
215
- 'top-level-await': true
216
- }
217
- }
218
- },
219
- esbuild: {
220
- target: 'es2022'
221
- },
222
- server: {
223
- port: 3000,
224
- open: true
225
- }
226
- });
227
- `;
228
-
229
- // 4. index.html
230
- const indexHtml = `<!DOCTYPE html>
231
- <html lang="en">
232
- <head>
233
- <meta charset="UTF-8" />
234
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
235
- <title>${resolvedName} - Retiam WebGPU Animation</title>
236
- <style>
237
- * {
238
- box-sizing: border-box;
239
- }
240
- body {
241
- margin: 0;
242
- overflow: hidden;
243
- background-color: #080C14;
244
- width: 100vw;
245
- height: 100vh;
246
- }
247
- canvas {
248
- display: block;
249
- width: 100vw;
250
- height: 100vh;
251
- }
252
- </style>
253
- </head>
254
- <body>
255
- <canvas id="canvas"></canvas>
256
- <script type="module" src="/src/main.ts"></script>
257
- </body>
258
- </html>
259
- `;
260
-
261
- // 5. src/main.ts
262
- const mainTs = `import HelloWorldScene from './scenes/HelloWorld';
263
-
264
- async function bootstrap() {
265
- const canvas = document.getElementById('canvas') as HTMLCanvasElement;
266
- if (!canvas) {
267
- console.error('Canvas element not found');
268
- return;
269
- }
270
-
271
- canvas.width = window.innerWidth;
272
- canvas.height = window.innerHeight;
273
-
274
- window.addEventListener('resize', () => {
275
- canvas.width = window.innerWidth;
276
- canvas.height = window.innerHeight;
277
- });
278
-
279
- const scene = new HelloWorldScene();
280
- await scene.start(canvas);
281
- }
282
-
283
- bootstrap().catch((err) => {
284
- console.error('Failed to initialize Retiam animation:', err);
285
- });
286
- `;
287
-
288
- // 6. src/scenes/HelloWorld.ts
289
- const helloWorldTs = `import { Scene, hex, SDFText, Write } from "retiam";
290
-
291
- /**
292
- * Starter Retiam Scene: Hello World
293
- * Features a cinematic Write animation over an obsidian dark substrate.
294
- */
295
- export default class HelloWorldScene extends Scene {
296
- public async run(): Promise<void> {
297
- // 1. Setup camera and obsidian canvas
298
- this.camera.position.set([0, 0, 14]);
299
- this.clearColor(hex("#080C14"));
300
-
301
- // 2. Create the hero typography
302
- const text = new SDFText("${heroTitle.replace(/"/g, '\\"')}", { fontSize: 1.1 });
303
- text.paint.fill = hex("#38BDF8");
304
- text.center();
305
-
306
- // 3. Register entity and frame camera with balanced padding
307
- this.add(text);
308
- this.camera.frame(text, { padding: 0.3 });
309
-
310
- // 4. Choreographed motion: Write animation
311
- await this.play(new Write(text, { duration: 1500 }));
312
- await this.wait(2000);
313
- }
314
- }
315
- `;
316
-
317
- // 7. .gitignore
318
- const gitignore = `node_modules
319
- dist
320
- outputs
321
- .DS_Store
322
- *.local
323
- `;
324
-
325
- // 8. README.md
326
- const readme = `# ${resolvedName}
327
-
328
- A cinematic WebGPU mathematical animation built with [Retiam](https://github.com/abhilasbiswas/Retiam).
329
-
330
- ## 🚀 Quick Start
331
-
332
- 1. **Install dependencies**:
333
- \`\`\`bash
334
- ${packageManager} install
335
- \`\`\`
336
-
337
- 2. **Start real-time development server (60-144 FPS)**:
338
- \`\`\`bash
339
- ${packageManager} run dev
340
- \`\`\`
341
-
342
- 3. **Render broadcast video (MP4/WebM)**:
343
- \`\`\`bash
344
- ${packageManager} run render
345
- \`\`\`
346
-
347
- ## 📁 Project Structure
348
-
349
- - \`src/main.ts\`: Entrypoint mounting the WebGPU canvas.
350
- - \`src/scenes/HelloWorld.ts\`: Starter scene with hero typography and \`Write\` animation.
351
- - \`vite.config.ts\`: Real-time WebGPU bundler configuration with hot module reload.
352
- `;
353
-
354
- // Write all template files
355
- fs.writeFileSync(path.join(resolvedDir, 'package.json'), JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
356
- fs.writeFileSync(path.join(resolvedDir, 'tsconfig.json'), JSON.stringify(tsconfigJson, null, 2) + '\n', 'utf8');
357
- fs.writeFileSync(path.join(resolvedDir, 'vite.config.ts'), viteConfig, 'utf8');
358
- fs.writeFileSync(path.join(resolvedDir, 'index.html'), indexHtml, 'utf8');
359
- fs.writeFileSync(path.join(resolvedDir, '.gitignore'), gitignore, 'utf8');
360
- fs.writeFileSync(path.join(resolvedDir, 'README.md'), readme, 'utf8');
361
- fs.writeFileSync(path.join(srcDir, 'main.ts'), mainTs, 'utf8');
362
- fs.writeFileSync(path.join(scenesDir, 'HelloWorld.ts'), helloWorldTs, 'utf8');
363
-
364
- const createdFiles = [
365
- 'package.json',
366
- 'tsconfig.json',
367
- 'vite.config.ts',
368
- 'index.html',
369
- '.gitignore',
370
- 'README.md',
371
- 'src/main.ts',
372
- 'src/scenes/HelloWorld.ts'
373
- ];
374
-
375
- // Optional git init
376
- if (initGit) {
377
- try {
378
- spawnSync('git', ['init'], { cwd: resolvedDir, stdio: 'ignore' });
379
- } catch (_) {}
380
- }
381
-
382
- // Optional install
383
- if (runInstall) {
384
- if (!jsonOutput) {
385
- console.log(`\n📦 Installing dependencies with ${packageManager}...`);
386
- }
387
- try {
388
- spawnSync(packageManager, ['install'], { cwd: resolvedDir, stdio: jsonOutput ? 'ignore' : 'inherit' });
389
- } catch (err) {
390
- if (!jsonOutput) {
391
- console.warn(`⚠️ Failed to run '${packageManager} install'. Please run it manually.`);
392
- }
393
- }
394
- }
395
-
396
- if (jsonOutput) {
397
- console.log(JSON.stringify({
398
- status: 'success',
399
- directory: targetDir,
400
- absolutePath: resolvedDir,
401
- projectName: resolvedName,
402
- packageManager,
403
- files: createdFiles
404
- }, null, 2));
405
- } else {
406
- console.log(`\n✨ Successfully created Retiam project in '${targetDir}'!\n`);
407
- console.log(`Next steps:`);
408
- if (path.resolve(process.cwd()) !== resolvedDir) {
409
- console.log(` 1. cd ${targetDir}`);
410
- }
411
- if (!runInstall) {
412
- console.log(` 2. ${packageManager} install`);
413
- console.log(` 3. ${packageManager} run dev # Real-time WebGPU preview`);
414
- console.log(` 4. ${packageManager} run render # Export MP4 broadcast video\n`);
415
- } else {
416
- console.log(` 2. ${packageManager} run dev # Real-time WebGPU preview`);
417
- console.log(` 3. ${packageManager} run render # Export MP4 broadcast video\n`);
418
- }
419
- }
420
- }
421
-
422
- main().catch((err) => {
423
- console.error('Initialization failed:', err);
424
- process.exit(1);
425
- });
1
+ #!/usr/bin/env node
2
+
3
+ // =========================================================================
4
+ // File: bin/init.js - Retiam Project Starter & Skeleton Generator
5
+ // =========================================================================
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import readline from 'readline/promises';
10
+ import { spawnSync } from 'child_process';
11
+
12
+ const args = process.argv.slice(2);
13
+
14
+ // Check for --help
15
+ const isHelp = args.includes('--help') || args.includes('-h') || args.includes('help');
16
+ if (isHelp) {
17
+ console.log(`
18
+ 🚀 Retiam WebGPU Engine - Project Initializer
19
+
20
+ Usage:
21
+ npx retiam init [directory] [options]
22
+ npx retiam create [directory] [options]
23
+
24
+ Arguments:
25
+ [directory] Destination directory (default: ./retiam-animation)
26
+
27
+ Options:
28
+ -n, --name <name> Project name for package.json (default: directory name)
29
+ --title <text> Hero text to display in Hello World scene (default: Hello World)
30
+ --pm <manager> Package manager: npm, pnpm, yarn, bun (default: auto-detected or npm)
31
+ -f, --force Overwrite destination directory if files already exist
32
+ -y, --yes Skip interactive prompts and use defaults
33
+ --install Automatically install dependencies after generation
34
+ --git Initialize a Git repository (git init)
35
+ --json Output structured machine-readable JSON for agents and CI
36
+ --help, -h Show this help message and exit
37
+
38
+ Examples:
39
+ npx retiam init
40
+ npx retiam init my-animation
41
+ npx retiam create intro-scene --title "Quantum Mechanics" --pm pnpm
42
+ npx retiam init my-project -y --install
43
+ npx retiam init agent-project --json --yes
44
+ `);
45
+ process.exit(0);
46
+ }
47
+
48
+ // Auto-detect package manager from npm_config_user_agent
49
+ function detectPackageManager() {
50
+ const userAgent = process.env.npm_config_user_agent || '';
51
+ if (userAgent.startsWith('pnpm')) return 'pnpm';
52
+ if (userAgent.startsWith('yarn')) return 'yarn';
53
+ if (userAgent.startsWith('bun')) return 'bun';
54
+ return 'npm';
55
+ }
56
+
57
+ // Parse CLI flags
58
+ let targetDir = null;
59
+ let projectName = null;
60
+ let heroTitle = 'Hello World';
61
+ let packageManager = detectPackageManager();
62
+ let force = false;
63
+ let nonInteractive = false;
64
+ let runInstall = false;
65
+ let initGit = false;
66
+ let jsonOutput = false;
67
+
68
+ for (let i = 0; i < args.length; i++) {
69
+ const arg = args[i];
70
+ if (arg === '--json') {
71
+ jsonOutput = true;
72
+ nonInteractive = true;
73
+ } else if (arg === '-y' || arg === '--yes') {
74
+ nonInteractive = true;
75
+ } else if (arg === '-f' || arg === '--force') {
76
+ force = true;
77
+ } else if (arg === '--install') {
78
+ runInstall = true;
79
+ } else if (arg === '--git') {
80
+ initGit = true;
81
+ } else if (arg === '-n' || arg === '--name') {
82
+ projectName = args[++i];
83
+ } else if (arg === '--title') {
84
+ heroTitle = args[++i] || 'Hello World';
85
+ } else if (arg === '--pm') {
86
+ packageManager = args[++i] || packageManager;
87
+ } else if (!arg.startsWith('-') && !targetDir) {
88
+ targetDir = arg;
89
+ }
90
+ }
91
+
92
+ async function main() {
93
+ // Interactive setup if TTY and not in --yes / --json mode
94
+ const isTTY = process.stdin.isTTY && !nonInteractive;
95
+
96
+ if (isTTY) {
97
+ const rl = readline.createInterface({
98
+ input: process.stdin,
99
+ output: process.stdout
100
+ });
101
+
102
+ try {
103
+ console.log(`\n✨ Welcome to Retiam - WebGPU Animation Project Generator\n`);
104
+
105
+ if (!targetDir) {
106
+ const inputDir = await rl.question(`📁 Destination directory (./retiam-animation): `);
107
+ targetDir = inputDir.trim() || 'retiam-animation';
108
+ }
109
+
110
+ const defaultName = path.basename(path.resolve(process.cwd(), targetDir))
111
+ .toLowerCase()
112
+ .replace(/[^a-z0-9_-]/g, '-');
113
+
114
+ if (!projectName) {
115
+ const inputName = await rl.question(`📦 Project name (${defaultName}): `);
116
+ projectName = inputName.trim() || defaultName;
117
+ }
118
+
119
+ const inputTitle = await rl.question(`🎨 Starter animation text (${heroTitle}): `);
120
+ if (inputTitle.trim()) {
121
+ heroTitle = inputTitle.trim();
122
+ }
123
+
124
+ const inputPm = await rl.question(`⚡ Package manager [npm/pnpm/yarn/bun] (${packageManager}): `);
125
+ if (inputPm.trim()) {
126
+ packageManager = inputPm.trim().toLowerCase();
127
+ }
128
+
129
+ const inputInstall = await rl.question(`📥 Run '${packageManager} install' now? [y/N]: `);
130
+ if (inputInstall.trim().toLowerCase().startsWith('y')) {
131
+ runInstall = true;
132
+ }
133
+ } finally {
134
+ rl.close();
135
+ }
136
+ }
137
+
138
+ if (!targetDir) {
139
+ targetDir = 'retiam-animation';
140
+ }
141
+
142
+ const resolvedDir = path.resolve(process.cwd(), targetDir);
143
+ const resolvedName = projectName || path.basename(resolvedDir)
144
+ .toLowerCase()
145
+ .replace(/[^a-z0-9_-]/g, '-') || 'retiam-animation';
146
+
147
+ // Conflict check
148
+ if (fs.existsSync(resolvedDir)) {
149
+ const existingFiles = fs.readdirSync(resolvedDir).filter(f => !f.startsWith('.'));
150
+ if (existingFiles.length > 0 && !force) {
151
+ if (jsonOutput) {
152
+ console.log(JSON.stringify({
153
+ status: 'error',
154
+ message: `Target directory '${targetDir}' already exists and is not empty. Use -f or --force to overwrite.`
155
+ }));
156
+ } else {
157
+ console.error(`\n❌ Error: Directory '${targetDir}' is not empty (${existingFiles.length} files found).`);
158
+ console.error(` Use -f or --force to proceed anyway.\n`);
159
+ }
160
+ process.exit(1);
161
+ }
162
+ }
163
+
164
+ // Ensure directory structure
165
+ const srcDir = path.join(resolvedDir, 'src');
166
+ const scenesDir = path.join(srcDir, 'scenes');
167
+ fs.mkdirSync(scenesDir, { recursive: true });
168
+
169
+ // 1. package.json
170
+ const packageJson = {
171
+ name: resolvedName,
172
+ version: '0.1.0',
173
+ private: true,
174
+ type: 'module',
175
+ scripts: {
176
+ dev: 'vite',
177
+ build: 'tsc && vite build',
178
+ preview: 'vite preview',
179
+ render: 'retiam render src/scenes/HelloWorld.ts'
180
+ },
181
+ dependencies: {
182
+ retiam: '^1.0.0'
183
+ },
184
+ devDependencies: {
185
+ '@webgpu/types': '^0.1.31',
186
+ typescript: '^5.5.0',
187
+ vite: '^5.4.0'
188
+ }
189
+ };
190
+
191
+ // 2. tsconfig.json
192
+ const tsconfigJson = {
193
+ compilerOptions: {
194
+ target: 'ES2022',
195
+ module: 'ESNext',
196
+ moduleResolution: 'bundler',
197
+ lib: ['ES2022', 'DOM', 'DOM.Iterable'],
198
+ types: ['@webgpu/types', 'vite/client'],
199
+ strict: true,
200
+ esModuleInterop: true,
201
+ skipLibCheck: true,
202
+ isolatedModules: true
203
+ },
204
+ include: ['src']
205
+ };
206
+
207
+ // 3. vite.config.ts
208
+ const viteConfig = `import { defineConfig } from 'vite';
209
+
210
+ export default defineConfig({
211
+ optimizeDeps: {
212
+ esbuildOptions: {
213
+ target: 'es2022',
214
+ supported: {
215
+ 'top-level-await': true
216
+ }
217
+ }
218
+ },
219
+ esbuild: {
220
+ target: 'es2022'
221
+ },
222
+ server: {
223
+ port: 3000,
224
+ open: true
225
+ }
226
+ });
227
+ `;
228
+
229
+ // 4. index.html
230
+ const indexHtml = `<!DOCTYPE html>
231
+ <html lang="en">
232
+ <head>
233
+ <meta charset="UTF-8" />
234
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
235
+ <title>${resolvedName} - Retiam WebGPU Animation</title>
236
+ <style>
237
+ * {
238
+ box-sizing: border-box;
239
+ }
240
+ body {
241
+ margin: 0;
242
+ overflow: hidden;
243
+ background-color: #080C14;
244
+ width: 100vw;
245
+ height: 100vh;
246
+ }
247
+ canvas {
248
+ display: block;
249
+ width: 100vw;
250
+ height: 100vh;
251
+ }
252
+ </style>
253
+ </head>
254
+ <body>
255
+ <canvas id="canvas"></canvas>
256
+ <script type="module" src="/src/main.ts"></script>
257
+ </body>
258
+ </html>
259
+ `;
260
+
261
+ // 5. src/main.ts
262
+ const mainTs = `import HelloWorldScene from './scenes/HelloWorld';
263
+
264
+ async function bootstrap() {
265
+ const canvas = document.getElementById('canvas') as HTMLCanvasElement;
266
+ if (!canvas) {
267
+ console.error('Canvas element not found');
268
+ return;
269
+ }
270
+
271
+ canvas.width = window.innerWidth;
272
+ canvas.height = window.innerHeight;
273
+
274
+ window.addEventListener('resize', () => {
275
+ canvas.width = window.innerWidth;
276
+ canvas.height = window.innerHeight;
277
+ });
278
+
279
+ const scene = new HelloWorldScene();
280
+ await scene.start(canvas);
281
+ }
282
+
283
+ bootstrap().catch((err) => {
284
+ console.error('Failed to initialize Retiam animation:', err);
285
+ });
286
+ `;
287
+
288
+ // 6. src/scenes/HelloWorld.ts
289
+ const helloWorldTs = `import { Scene, hex, SDFText, Write } from "retiam";
290
+
291
+ /**
292
+ * Starter Retiam Scene: Hello World
293
+ * Features a cinematic Write animation over an obsidian dark substrate.
294
+ */
295
+ export default class HelloWorldScene extends Scene {
296
+ public async run(): Promise<void> {
297
+ // 1. Setup camera and obsidian canvas
298
+ this.camera.position.set([0, 0, 14]);
299
+ this.clearColor(hex("#080C14"));
300
+
301
+ // 2. Create the hero typography
302
+ const text = new SDFText("${heroTitle.replace(/"/g, '\\"')}", { fontSize: 1.1 });
303
+ text.paint.fill = hex("#38BDF8");
304
+ text.center();
305
+
306
+ // 3. Register entity and frame camera with balanced padding
307
+ this.add(text);
308
+ this.camera.frame(text, { padding: 0.3 });
309
+
310
+ // 4. Choreographed motion: Write animation
311
+ await this.play(new Write(text, { duration: 1500 }));
312
+ await this.wait(2000);
313
+ }
314
+ }
315
+ `;
316
+
317
+ // 7. .gitignore
318
+ const gitignore = `node_modules
319
+ dist
320
+ outputs
321
+ .DS_Store
322
+ *.local
323
+ `;
324
+
325
+ // 8. README.md
326
+ const readme = `# ${resolvedName}
327
+
328
+ A cinematic WebGPU mathematical animation built with [Retiam](https://github.com/abhilasbiswas/Retiam).
329
+
330
+ ## 🚀 Quick Start
331
+
332
+ 1. **Install dependencies**:
333
+ \`\`\`bash
334
+ ${packageManager} install
335
+ \`\`\`
336
+
337
+ 2. **Start real-time development server (60-144 FPS)**:
338
+ \`\`\`bash
339
+ ${packageManager} run dev
340
+ \`\`\`
341
+
342
+ 3. **Render broadcast video (MP4/WebM)**:
343
+ \`\`\`bash
344
+ ${packageManager} run render
345
+ \`\`\`
346
+
347
+ ## 📁 Project Structure
348
+
349
+ - \`src/main.ts\`: Entrypoint mounting the WebGPU canvas.
350
+ - \`src/scenes/HelloWorld.ts\`: Starter scene with hero typography and \`Write\` animation.
351
+ - \`vite.config.ts\`: Real-time WebGPU bundler configuration with hot module reload.
352
+ `;
353
+
354
+ // Write all template files
355
+ fs.writeFileSync(path.join(resolvedDir, 'package.json'), JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
356
+ fs.writeFileSync(path.join(resolvedDir, 'tsconfig.json'), JSON.stringify(tsconfigJson, null, 2) + '\n', 'utf8');
357
+ fs.writeFileSync(path.join(resolvedDir, 'vite.config.ts'), viteConfig, 'utf8');
358
+ fs.writeFileSync(path.join(resolvedDir, 'index.html'), indexHtml, 'utf8');
359
+ fs.writeFileSync(path.join(resolvedDir, '.gitignore'), gitignore, 'utf8');
360
+ fs.writeFileSync(path.join(resolvedDir, 'README.md'), readme, 'utf8');
361
+ fs.writeFileSync(path.join(srcDir, 'main.ts'), mainTs, 'utf8');
362
+ fs.writeFileSync(path.join(scenesDir, 'HelloWorld.ts'), helloWorldTs, 'utf8');
363
+
364
+ const createdFiles = [
365
+ 'package.json',
366
+ 'tsconfig.json',
367
+ 'vite.config.ts',
368
+ 'index.html',
369
+ '.gitignore',
370
+ 'README.md',
371
+ 'src/main.ts',
372
+ 'src/scenes/HelloWorld.ts'
373
+ ];
374
+
375
+ // Optional git init
376
+ if (initGit) {
377
+ try {
378
+ spawnSync('git', ['init'], { cwd: resolvedDir, stdio: 'ignore' });
379
+ } catch (_) {}
380
+ }
381
+
382
+ // Optional install
383
+ if (runInstall) {
384
+ if (!jsonOutput) {
385
+ console.log(`\n📦 Installing dependencies with ${packageManager}...`);
386
+ }
387
+ try {
388
+ spawnSync(packageManager, ['install'], { cwd: resolvedDir, stdio: jsonOutput ? 'ignore' : 'inherit' });
389
+ } catch (err) {
390
+ if (!jsonOutput) {
391
+ console.warn(`⚠️ Failed to run '${packageManager} install'. Please run it manually.`);
392
+ }
393
+ }
394
+ }
395
+
396
+ if (jsonOutput) {
397
+ console.log(JSON.stringify({
398
+ status: 'success',
399
+ directory: targetDir,
400
+ absolutePath: resolvedDir,
401
+ projectName: resolvedName,
402
+ packageManager,
403
+ files: createdFiles
404
+ }, null, 2));
405
+ } else {
406
+ console.log(`\n✨ Successfully created Retiam project in '${targetDir}'!\n`);
407
+ console.log(`Next steps:`);
408
+ if (path.resolve(process.cwd()) !== resolvedDir) {
409
+ console.log(` 1. cd ${targetDir}`);
410
+ }
411
+ if (!runInstall) {
412
+ console.log(` 2. ${packageManager} install`);
413
+ console.log(` 3. ${packageManager} run dev # Real-time WebGPU preview`);
414
+ console.log(` 4. ${packageManager} run render # Export MP4 broadcast video\n`);
415
+ } else {
416
+ console.log(` 2. ${packageManager} run dev # Real-time WebGPU preview`);
417
+ console.log(` 3. ${packageManager} run render # Export MP4 broadcast video\n`);
418
+ }
419
+ }
420
+ }
421
+
422
+ main().catch((err) => {
423
+ console.error('Initialization failed:', err);
424
+ process.exit(1);
425
+ });