realtimex-crm 0.1.3 → 0.2.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 CHANGED
@@ -20,7 +20,29 @@ RealTimeX CRM is free and open-source.
20
20
  - 🛠️ **Customize Everything**: Add custom fields, change the theme, and replace any component to fit your needs.
21
21
  - 🗄️ **Bring Your Own Database**: Configure Supabase connection via UI or environment variables.
22
22
 
23
- ## Installation
23
+ ## Quick Start
24
+
25
+ ### Option 1: Use the CLI (Recommended)
26
+
27
+ Create a new CRM project in seconds:
28
+
29
+ ```bash
30
+ npx create-realtimex-crm@latest
31
+ ```
32
+
33
+ The CLI will guide you through:
34
+ 1. Choosing a template (standalone app, RealTimeX Local App, or component)
35
+ 2. Configuring Supabase (optional)
36
+ 3. Setting up your project structure
37
+
38
+ Then:
39
+ ```bash
40
+ cd my-crm
41
+ npm install
42
+ npm run dev
43
+ ```
44
+
45
+ ### Option 2: Clone and Customize
24
46
 
25
47
  To run this project locally, you will need the following tools installed on your computer:
26
48
 
@@ -118,6 +140,31 @@ RealTimeX CRM components are published as a Shadcn Registry file:
118
140
  > [!WARNING]
119
141
  > If the `registry.json` misses some changes you made, you MUST update the `scripts/generate-registry.mjs` to include those changes.
120
142
 
143
+ ## CLI Templates
144
+
145
+ The `create-realtimex-crm` CLI provides three templates:
146
+
147
+ ### 1. Standalone App
148
+ A complete CRM application ready to deploy:
149
+ - Full Vite + React setup
150
+ - TypeScript configuration
151
+ - Production build scripts
152
+ - Environment-based configuration
153
+
154
+ ### 2. RealTimeX Local App
155
+ CRM integrated with RealTimeX.ai platform:
156
+ - `@realtimex/app-sdk` integration
157
+ - Auto-scoped data by user
158
+ - Parent-child user support
159
+ - Platform authentication
160
+ - See `LOCAL_APP.md` in generated project for details
161
+
162
+ ### 3. Component Integration
163
+ Use RealTimeX CRM as a component in your existing app:
164
+ - Install via npm
165
+ - Import and configure
166
+ - Example integration code included
167
+
121
168
  ## NPM Package
122
169
 
123
170
  RealTimeX CRM is available as an npm package:
@@ -0,0 +1,655 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { fileURLToPath } from "node:url";
4
+ import { dirname, join } from "node:path";
5
+ import { mkdir, writeFile, copyFile } from "node:fs/promises";
6
+ import { existsSync } from "node:fs";
7
+ import { execSync } from "node:child_process";
8
+ import { confirm, input, select } from "@inquirer/prompts";
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+
13
+ const TEMPLATES = {
14
+ standalone: "Create a standalone CRM application",
15
+ localapp: "Create a RealTimeX Local App integration",
16
+ component: "Use as a React component in existing app",
17
+ };
18
+
19
+ async function main() {
20
+ console.log(`
21
+ ╔═══════════════════════════════════════╗
22
+ ║ ║
23
+ ║ RealTimeX CRM Setup ║
24
+ ║ ║
25
+ ╘═══════════════════════════════════════╝
26
+ `);
27
+
28
+ // Get project name
29
+ const projectName = await input({
30
+ message: "Project name:",
31
+ default: "my-crm",
32
+ validate: (value) => {
33
+ if (!value.trim()) return "Project name is required";
34
+ if (!/^[a-z0-9-_]+$/.test(value))
35
+ return "Project name must be lowercase alphanumeric with dashes/underscores only";
36
+ return true;
37
+ },
38
+ });
39
+
40
+ const projectPath = join(process.cwd(), projectName);
41
+
42
+ // Check if directory exists
43
+ if (existsSync(projectPath)) {
44
+ const overwrite = await confirm({
45
+ message: `Directory "${projectName}" already exists. Overwrite?`,
46
+ default: false,
47
+ });
48
+ if (!overwrite) {
49
+ console.log("Setup cancelled.");
50
+ process.exit(0);
51
+ }
52
+ }
53
+
54
+ // Select template
55
+ const template = await select({
56
+ message: "Select a template:",
57
+ choices: Object.entries(TEMPLATES).map(([value, name]) => ({
58
+ value,
59
+ name,
60
+ })),
61
+ });
62
+
63
+ // Get Supabase configuration
64
+ const configureSupabase = await confirm({
65
+ message: "Configure Supabase now?",
66
+ default: false,
67
+ });
68
+
69
+ let supabaseUrl = "";
70
+ let supabaseAnonKey = "";
71
+
72
+ if (configureSupabase) {
73
+ supabaseUrl = await input({
74
+ message: "Supabase URL:",
75
+ validate: (value) => {
76
+ if (!value.trim()) return "Supabase URL is required";
77
+ if (!value.includes("supabase.co"))
78
+ return "URL should be a valid Supabase project URL";
79
+ return true;
80
+ },
81
+ });
82
+
83
+ supabaseAnonKey = await input({
84
+ message: "Supabase Anon Key:",
85
+ validate: (value) => {
86
+ if (!value.trim()) return "Supabase Anon Key is required";
87
+ return true;
88
+ },
89
+ });
90
+ }
91
+
92
+ console.log("\n📦 Creating project...\n");
93
+
94
+ // Create project directory
95
+ await mkdir(projectPath, { recursive: true });
96
+
97
+ // Generate files based on template
98
+ if (template === "standalone") {
99
+ await createStandaloneApp(projectPath, projectName, {
100
+ supabaseUrl,
101
+ supabaseAnonKey,
102
+ });
103
+ } else if (template === "localapp") {
104
+ await createLocalApp(projectPath, projectName, {
105
+ supabaseUrl,
106
+ supabaseAnonKey,
107
+ });
108
+ } else if (template === "component") {
109
+ await createComponentSetup(projectPath, projectName, {
110
+ supabaseUrl,
111
+ supabaseAnonKey,
112
+ });
113
+ }
114
+
115
+ console.log("\n✅ Project created successfully!\n");
116
+ console.log("Next steps:\n");
117
+ console.log(` cd ${projectName}`);
118
+ console.log(` npm install`);
119
+
120
+ if (!configureSupabase) {
121
+ console.log(
122
+ ` # Configure Supabase in .env or via Settings → Database in the app`,
123
+ );
124
+ }
125
+
126
+ if (template === "standalone") {
127
+ console.log(` npm run dev # Start development server`);
128
+ console.log(` npm run build # Build for production`);
129
+ } else if (template === "localapp") {
130
+ console.log(
131
+ ` npm run dev # Start as RealTimeX Local App (localhost:5173)`,
132
+ );
133
+ console.log(` npm run build # Build for production`);
134
+ console.log(
135
+ `\n📖 See LOCAL_APP.md for RealTimeX.ai integration instructions`,
136
+ );
137
+ }
138
+
139
+ console.log("");
140
+ }
141
+
142
+ async function createStandaloneApp(projectPath, projectName, config) {
143
+ const packageJson = {
144
+ name: projectName,
145
+ version: "0.1.0",
146
+ private: true,
147
+ type: "module",
148
+ scripts: {
149
+ dev: "vite --force",
150
+ build: "tsc && vite build",
151
+ preview: "vite preview",
152
+ typecheck: "tsc --noEmit",
153
+ lint: "eslint **/*.{mjs,ts,tsx}",
154
+ },
155
+ dependencies: {
156
+ "realtimex-crm": "latest",
157
+ react: "^19.1.0",
158
+ "react-dom": "^19.1.0",
159
+ },
160
+ devDependencies: {
161
+ "@types/react": "^19.1.8",
162
+ "@types/react-dom": "^19.1.6",
163
+ "@vitejs/plugin-react": "^4.6.0",
164
+ typescript: "~5.8.3",
165
+ vite: "^7.0.4",
166
+ },
167
+ };
168
+
169
+ await writeFile(
170
+ join(projectPath, "package.json"),
171
+ JSON.stringify(packageJson, null, 2),
172
+ );
173
+
174
+ // Create src/App.tsx
175
+ const appTsx = `import { CRM } from "realtimex-crm";
176
+ import "realtimex-crm/dist/style.css";
177
+
178
+ function App() {
179
+ return (
180
+ <CRM
181
+ title="My CRM"
182
+ // Customize your CRM here
183
+ // darkModeLogo="./logos/dark.svg"
184
+ // lightModeLogo="./logos/light.svg"
185
+ // contactGender={[...]}
186
+ // dealStages={[...]}
187
+ />
188
+ );
189
+ }
190
+
191
+ export default App;
192
+ `;
193
+
194
+ await mkdir(join(projectPath, "src"), { recursive: true });
195
+ await writeFile(join(projectPath, "src/App.tsx"), appTsx);
196
+
197
+ // Create src/main.tsx
198
+ const mainTsx = `import React from "react";
199
+ import ReactDOM from "react-dom/client";
200
+ import App from "./App";
201
+
202
+ ReactDOM.createRoot(document.getElementById("root")!).render(
203
+ <React.StrictMode>
204
+ <App />
205
+ </React.StrictMode>
206
+ );
207
+ `;
208
+
209
+ await writeFile(join(projectPath, "src/main.tsx"), mainTsx);
210
+
211
+ // Create index.html
212
+ const indexHtml = `<!DOCTYPE html>
213
+ <html lang="en">
214
+ <head>
215
+ <meta charset="UTF-8" />
216
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
217
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
218
+ <title>CRM</title>
219
+ </head>
220
+ <body>
221
+ <div id="root"></div>
222
+ <script type="module" src="/src/main.tsx"></script>
223
+ </body>
224
+ </html>
225
+ `;
226
+
227
+ await writeFile(join(projectPath, "index.html"), indexHtml);
228
+
229
+ // Create vite.config.ts
230
+ const viteConfig = `import { defineConfig } from "vite";
231
+ import react from "@vitejs/plugin-react";
232
+
233
+ export default defineConfig({
234
+ plugins: [react()],
235
+ server: {
236
+ port: 5173,
237
+ },
238
+ });
239
+ `;
240
+
241
+ await writeFile(join(projectPath, "vite.config.ts"), viteConfig);
242
+
243
+ // Create tsconfig.json
244
+ const tsConfig = {
245
+ compilerOptions: {
246
+ target: "ES2020",
247
+ useDefineForClassFields: true,
248
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
249
+ module: "ESNext",
250
+ skipLibCheck: true,
251
+ moduleResolution: "bundler",
252
+ allowImportingTsExtensions: true,
253
+ resolveJsonModule: true,
254
+ isolatedModules: true,
255
+ noEmit: true,
256
+ jsx: "react-jsx",
257
+ strict: true,
258
+ noUnusedLocals: true,
259
+ noUnusedParameters: true,
260
+ noFallthroughCasesInSwitch: true,
261
+ },
262
+ include: ["src"],
263
+ };
264
+
265
+ await writeFile(
266
+ join(projectPath, "tsconfig.json"),
267
+ JSON.stringify(tsConfig, null, 2),
268
+ );
269
+
270
+ // Create .env file if config provided
271
+ if (config.supabaseUrl && config.supabaseAnonKey) {
272
+ const envContent = `VITE_SUPABASE_URL=${config.supabaseUrl}
273
+ VITE_SUPABASE_ANON_KEY=${config.supabaseAnonKey}
274
+ `;
275
+ await writeFile(join(projectPath, ".env"), envContent);
276
+ }
277
+
278
+ // Create .gitignore
279
+ const gitignore = `# Dependencies
280
+ node_modules
281
+
282
+ # Build output
283
+ dist
284
+ dist-ssr
285
+ *.local
286
+
287
+ # Environment
288
+ .env
289
+ .env.local
290
+ .env.production.local
291
+
292
+ # Editor
293
+ .vscode/*
294
+ !.vscode/extensions.json
295
+ .idea
296
+ .DS_Store
297
+ *.suo
298
+ *.ntvs*
299
+ *.njsproj
300
+ *.sln
301
+ *.sw?
302
+ `;
303
+
304
+ await writeFile(join(projectPath, ".gitignore"), gitignore);
305
+
306
+ // Create README.md
307
+ const readme = `# ${projectName}
308
+
309
+ A CRM application built with RealTimeX CRM.
310
+
311
+ ## Getting Started
312
+
313
+ 1. Install dependencies:
314
+ \`\`\`bash
315
+ npm install
316
+ \`\`\`
317
+
318
+ 2. Configure Supabase:
319
+ - Create a \`.env\` file with your Supabase credentials:
320
+ \`\`\`
321
+ VITE_SUPABASE_URL=https://xxxxx.supabase.co
322
+ VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
323
+ \`\`\`
324
+ - Or configure via the UI after starting the app
325
+
326
+ 3. Start development server:
327
+ \`\`\`bash
328
+ npm run dev
329
+ \`\`\`
330
+
331
+ 4. Open [http://localhost:5173](http://localhost:5173)
332
+
333
+ ## Customization
334
+
335
+ Edit \`src/App.tsx\` to customize your CRM:
336
+
337
+ - \`title\`: App title
338
+ - \`darkModeLogo\` / \`lightModeLogo\`: Custom logos
339
+ - \`contactGender\`: Gender options
340
+ - \`companySectors\`: Industry sectors
341
+ - \`dealStages\`: Deal pipeline stages
342
+ - \`dealCategories\`: Deal categories
343
+ - \`noteStatuses\`: Note status options
344
+ - \`taskTypes\`: Task type options
345
+
346
+ See [RealTimeX CRM Documentation](https://github.com/therealtimex/realtimex-crm) for more options.
347
+
348
+ ## Build for Production
349
+
350
+ \`\`\`bash
351
+ npm run build
352
+ \`\`\`
353
+
354
+ The built files will be in the \`dist\` directory.
355
+ `;
356
+
357
+ await writeFile(join(projectPath, "README.md"), readme);
358
+ }
359
+
360
+ async function createLocalApp(projectPath, projectName, config) {
361
+ // Start with standalone setup
362
+ await createStandaloneApp(projectPath, projectName, config);
363
+
364
+ // Update package.json with RealTimeX App SDK
365
+ const packageJsonPath = join(projectPath, "package.json");
366
+ const packageJson = JSON.parse(
367
+ await import("fs").then((fs) => fs.promises.readFile(packageJsonPath, "utf8")),
368
+ );
369
+
370
+ packageJson.dependencies["@realtimex/app-sdk"] = "latest";
371
+
372
+ await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
373
+
374
+ // Create updated App.tsx with RealTimeX integration
375
+ const appTsx = `import { RealTimeXApp } from "@realtimex/app-sdk";
376
+ import { SupabaseProvider } from "@realtimex/app-sdk/providers/supabase";
377
+ import { CRM } from "realtimex-crm";
378
+ import "realtimex-crm/dist/style.css";
379
+
380
+ function App() {
381
+ return (
382
+ <RealTimeXApp
383
+ appId="${projectName}"
384
+ appName="CRM"
385
+ version="1.0.0"
386
+ description="Full-featured CRM for RealTimeX"
387
+ >
388
+ <SupabaseProvider
389
+ url={import.meta.env.VITE_SUPABASE_URL}
390
+ anonKey={import.meta.env.VITE_SUPABASE_ANON_KEY}
391
+ autoScope={{
392
+ enabled: true,
393
+ userIdField: "realtimex_user_id",
394
+ }}
395
+ >
396
+ <CRM
397
+ title="CRM"
398
+ // Customize your CRM here
399
+ />
400
+ </SupabaseProvider>
401
+ </RealTimeXApp>
402
+ );
403
+ }
404
+
405
+ export default App;
406
+ `;
407
+
408
+ await writeFile(join(projectPath, "src/App.tsx"), appTsx);
409
+
410
+ // Create LOCAL_APP.md documentation
411
+ const localAppDoc = `# RealTimeX Local App Integration
412
+
413
+ This CRM is configured as a RealTimeX Local App, which allows it to integrate with the RealTimeX.ai platform.
414
+
415
+ ## What is a RealTimeX Local App?
416
+
417
+ A Local App runs in the user's browser and communicates with RealTimeX.ai via the App SDK. It receives:
418
+ - User authentication context
419
+ - Parent-child user relationships
420
+ - Global state management
421
+ - Platform-level permissions
422
+
423
+ ## Architecture
424
+
425
+ \`\`\`
426
+ RealTimeX Platform (realtimex.ai)
427
+ ↓ (postMessage API)
428
+ Local App (localhost:5173 or embedded)
429
+ ↓ (HTTP + RLS)
430
+ Supabase Database
431
+ \`\`\`
432
+
433
+ ## Key Features
434
+
435
+ 1. **Authentication via Platform**: Users authenticate with RealTimeX, not Supabase directly
436
+ 2. **Auto-scoping**: Data is automatically filtered by \`realtimex_user_id\`
437
+ 3. **Parent-child support**: Parent users can see their children's data
438
+ 4. **No JWT tokens**: Uses platform-provided user headers instead
439
+
440
+ ## Database Schema Changes
441
+
442
+ To use this as a Local App, your Supabase schema needs \`realtimex_user_id\` instead of \`sales_id\`:
443
+
444
+ \`\`\`sql
445
+ -- Add RealTimeX user ID to all tables
446
+ ALTER TABLE contacts ADD COLUMN realtimex_user_id INTEGER;
447
+ ALTER TABLE companies ADD COLUMN realtimex_user_id INTEGER;
448
+ ALTER TABLE deals ADD COLUMN realtimex_user_id INTEGER;
449
+ ALTER TABLE tasks ADD COLUMN realtimex_user_id INTEGER;
450
+ -- etc.
451
+
452
+ -- Create indexes
453
+ CREATE INDEX idx_contacts_rtx_user ON contacts(realtimex_user_id);
454
+ CREATE INDEX idx_companies_rtx_user ON companies(realtimex_user_id);
455
+ -- etc.
456
+
457
+ -- Update RLS policies to use realtimex_user_id
458
+ CREATE POLICY "Users see own contacts" ON contacts
459
+ FOR SELECT USING (
460
+ realtimex_user_id = current_setting('request.headers')::json->>'x-realtimex-user-id'::INTEGER
461
+ );
462
+ \`\`\`
463
+
464
+ ## Running as Local App
465
+
466
+ ### Development Mode
467
+
468
+ \`\`\`bash
469
+ npm run dev
470
+ \`\`\`
471
+
472
+ Then register in RealTimeX.ai:
473
+ 1. Go to RealTimeX.ai → Settings → Local Apps
474
+ 2. Add new app:
475
+ - Name: CRM
476
+ - URL: http://localhost:5173
477
+ - App ID: ${projectName}
478
+
479
+ ### Production Mode
480
+
481
+ Build and deploy to any static host:
482
+
483
+ \`\`\`bash
484
+ npm run build
485
+ \`\`\`
486
+
487
+ Deploy \`dist/\` to:
488
+ - Vercel
489
+ - Netlify
490
+ - GitHub Pages
491
+ - Your own CDN
492
+
493
+ Then update the Local App URL in RealTimeX.ai settings.
494
+
495
+ ## Configuration
496
+
497
+ Environment variables:
498
+ \`\`\`
499
+ VITE_SUPABASE_URL=https://xxxxx.supabase.co
500
+ VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
501
+ \`\`\`
502
+
503
+ ## SDK Usage
504
+
505
+ The \`@realtimex/app-sdk\` provides:
506
+
507
+ ### Get Current User
508
+ \`\`\`typescript
509
+ import { useRealTimeXUser } from "@realtimex/app-sdk";
510
+
511
+ function MyComponent() {
512
+ const user = useRealTimeXUser();
513
+ // { id: 123, email: "user@example.com", role: "user" }
514
+ }
515
+ \`\`\`
516
+
517
+ ### Navigate to Platform
518
+ \`\`\`typescript
519
+ import { useRealTimeXNavigate } from "@realtimex/app-sdk";
520
+
521
+ function MyComponent() {
522
+ const navigate = useRealTimeXNavigate();
523
+ navigate("/contacts/123"); // Navigate within RealTimeX
524
+ }
525
+ \`\`\`
526
+
527
+ ### Access Global State
528
+ \`\`\`typescript
529
+ import { useRealTimeXState } from "@realtimex/app-sdk";
530
+
531
+ function MyComponent() {
532
+ const [value, setValue] = useRealTimeXState("my-key");
533
+ }
534
+ \`\`\`
535
+
536
+ ## Resources
537
+
538
+ - [RealTimeX App SDK Docs](https://realtimex.ai/docs/app-sdk)
539
+ - [RealTimeX CRM Docs](https://github.com/therealtimex/realtimex-crm)
540
+ - [Supabase RLS Guide](https://supabase.com/docs/guides/auth/row-level-security)
541
+ `;
542
+
543
+ await writeFile(join(projectPath, "LOCAL_APP.md"), localAppDoc);
544
+
545
+ // Create realtimex.config.json
546
+ const rtxConfig = {
547
+ appId: projectName,
548
+ name: "CRM",
549
+ version: "1.0.0",
550
+ description: "Full-featured CRM for RealTimeX",
551
+ permissions: ["database.read", "database.write", "user.read"],
552
+ settings: {
553
+ supabase: {
554
+ required: true,
555
+ fields: ["url", "anonKey"],
556
+ },
557
+ },
558
+ };
559
+
560
+ await writeFile(
561
+ join(projectPath, "realtimex.config.json"),
562
+ JSON.stringify(rtxConfig, null, 2),
563
+ );
564
+ }
565
+
566
+ async function createComponentSetup(projectPath, projectName, config) {
567
+ const packageJson = {
568
+ name: projectName,
569
+ version: "0.1.0",
570
+ private: true,
571
+ type: "module",
572
+ dependencies: {
573
+ "realtimex-crm": "latest",
574
+ },
575
+ };
576
+
577
+ await writeFile(
578
+ join(projectPath, "package.json"),
579
+ JSON.stringify(packageJson, null, 2),
580
+ );
581
+
582
+ // Create example integration files
583
+ await mkdir(join(projectPath, "examples"), { recursive: true });
584
+
585
+ const exampleReact = `import { CRM } from "realtimex-crm";
586
+ import "realtimex-crm/dist/style.css";
587
+
588
+ export function MyCRMPage() {
589
+ return (
590
+ <div className="h-screen">
591
+ <CRM
592
+ title="My CRM"
593
+ // Customize as needed
594
+ />
595
+ </div>
596
+ );
597
+ }
598
+ `;
599
+
600
+ await writeFile(join(projectPath, "examples/react-integration.tsx"), exampleReact);
601
+
602
+ const readme = `# ${projectName}
603
+
604
+ RealTimeX CRM component integration.
605
+
606
+ ## Installation
607
+
608
+ \`\`\`bash
609
+ npm install realtimex-crm
610
+ \`\`\`
611
+
612
+ ## Usage
613
+
614
+ \`\`\`tsx
615
+ import { CRM } from "realtimex-crm";
616
+ import "realtimex-crm/dist/style.css";
617
+
618
+ function App() {
619
+ return <CRM title="My CRM" />;
620
+ }
621
+ \`\`\`
622
+
623
+ ## Configuration
624
+
625
+ Configure Supabase via environment variables or UI:
626
+
627
+ \`\`\`
628
+ VITE_SUPABASE_URL=https://xxxxx.supabase.co
629
+ VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
630
+ \`\`\`
631
+
632
+ ## Customization
633
+
634
+ See \`examples/react-integration.tsx\` for a complete example.
635
+
636
+ Available props:
637
+ - \`title\`: string
638
+ - \`darkModeLogo\` / \`lightModeLogo\`: string
639
+ - \`contactGender\`: ContactGender[]
640
+ - \`companySectors\`: string[]
641
+ - \`dealStages\`: DealStage[]
642
+ - \`dealCategories\`: string[]
643
+ - \`noteStatuses\`: NoteStatus[]
644
+ - \`taskTypes\`: string[]
645
+
646
+ See [documentation](https://github.com/therealtimex/realtimex-crm) for details.
647
+ `;
648
+
649
+ await writeFile(join(projectPath, "README.md"), readme);
650
+ }
651
+
652
+ main().catch((error) => {
653
+ console.error("\n❌ Error:", error.message);
654
+ process.exit(1);
655
+ });