@mounaji_npm/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.
Files changed (3) hide show
  1. package/bin/cli.js +42 -0
  2. package/bin/create.js +410 -0
  3. package/package.json +24 -0
package/bin/cli.js ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * mounaji CLI — entry point
4
+ *
5
+ * Commands:
6
+ * mounaji create <name> Scaffold a new SaaS app
7
+ * mounaji add <module> Add a module page to an existing app
8
+ * mounaji tokens export Export current tokens as CSS/JSON
9
+ */
10
+
11
+ const [,, command, ...args] = process.argv;
12
+
13
+ switch (command) {
14
+ case 'create':
15
+ process.argv = [...process.argv.slice(0, 2), ...args];
16
+ import('./create.js');
17
+ break;
18
+
19
+ case 'add':
20
+ console.log('\n@mounaji_npm/cli add — coming soon\n');
21
+ console.log('For now, manually add a module manifest to mn-config.js');
22
+ console.log('and create the page file in your app/ or src/pages/ directory.\n');
23
+ break;
24
+
25
+ case 'tokens':
26
+ console.log('\n@mounaji_npm/cli tokens — coming soon\n');
27
+ break;
28
+
29
+ default:
30
+ console.log(`
31
+ @mounaji_npm/cli v0.1.0
32
+
33
+ Usage:
34
+ npx @mounaji_npm/cli create <project-name> Scaffold a new SaaS app
35
+ npx @mounaji_npm/cli add <module-id> Add a module to an existing app (soon)
36
+ npx @mounaji_npm/cli tokens export Export design tokens (soon)
37
+
38
+ Examples:
39
+ npx @mounaji_npm/cli create my-saas-app
40
+ create-mounaji-app my-saas-app
41
+ `);
42
+ }
package/bin/create.js ADDED
@@ -0,0 +1,410 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * create-mounaji-app
4
+ *
5
+ * Usage:
6
+ * npx @mounaji_npm/cli create my-app
7
+ *
8
+ * Local linked binary:
9
+ * create-mounaji-app my-app
10
+ *
11
+ * Interactive prompts:
12
+ * 1. Project name
13
+ * 2. Framework (Next.js App Router | Vite + React)
14
+ * 3. Modules to include (multiselect)
15
+ * 4. Include admin controls / DevToolbar?
16
+ * 5. Auth provider (Firebase | Supabase | None)
17
+ */
18
+
19
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
20
+ import { join, resolve } from 'path';
21
+ import { execSync } from 'child_process';
22
+
23
+ // We avoid hard-coding a dependency on 'prompts' for the scaffold itself
24
+ // so it works immediately without install. Use readline-based prompts instead.
25
+ import { createInterface } from 'readline';
26
+
27
+ const CYAN = '\x1b[36m';
28
+ const GREEN = '\x1b[32m';
29
+ const YELLOW = '\x1b[33m';
30
+ const BOLD = '\x1b[1m';
31
+ const RESET = '\x1b[0m';
32
+
33
+ const ALL_MODULES = [
34
+ { id: 'home', label: 'Home', pkg: null },
35
+ { id: 'dashboard', label: 'Dashboard', pkg: '@mounaji_npm/dashboard' },
36
+ { id: 'chat', label: 'Chat', pkg: '@mounaji_npm/chat' },
37
+ { id: 'assistants', label: 'Assistants', pkg: '@mounaji_npm/assistant' },
38
+ { id: 'knowledge-base', label: 'Knowledge Base', pkg: '@mounaji_npm/knowledge-base' },
39
+ { id: 'tasks', label: 'Tasks', pkg: null },
40
+ { id: 'connections', label: 'Connections', pkg: null },
41
+ { id: 'settings', label: 'Settings', pkg: null },
42
+ { id: 'inventory', label: 'Inventory', pkg: null },
43
+ { id: 'contacts', label: 'Contacts', pkg: null },
44
+ { id: 'workflows', label: 'Workflows', pkg: null },
45
+ { id: 'platforms', label: 'Platforms', pkg: null },
46
+ { id: 'pricing', label: 'Pricing', pkg: null },
47
+ ];
48
+
49
+ async function prompt(question) {
50
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
51
+ return new Promise(resolve => {
52
+ rl.question(question, answer => { rl.close(); resolve(answer.trim()); });
53
+ });
54
+ }
55
+
56
+ async function multiSelect(items, message) {
57
+ console.log(`\n${CYAN}${message}${RESET}`);
58
+ items.forEach((item, i) => console.log(` ${YELLOW}${i + 1}${RESET}. ${item.label}`));
59
+ console.log(` ${YELLOW}a${RESET}. All modules`);
60
+ const answer = await prompt(`\nEnter numbers separated by commas (e.g. 1,2,3) or 'a' for all: `);
61
+ if (answer.toLowerCase() === 'a') return items;
62
+ const indices = answer.split(',').map(s => parseInt(s.trim(), 10) - 1).filter(i => i >= 0 && i < items.length);
63
+ return indices.map(i => items[i]);
64
+ }
65
+
66
+ async function main() {
67
+ const args = process.argv.slice(2);
68
+ let projectName = args[0];
69
+
70
+ console.log(`\n${BOLD}${CYAN}◆ create-mounaji-app${RESET}\n`);
71
+ console.log('Scaffolds a full SaaS app powered by @mounaji_npm/* components.\n');
72
+
73
+ if (!projectName) {
74
+ projectName = await prompt(`${CYAN}Project name${RESET} › `);
75
+ }
76
+ if (!projectName) { console.error('Project name is required.'); process.exit(1); }
77
+
78
+ const framework = await prompt(`${CYAN}Framework${RESET} [nextjs/vite] (default: nextjs) › `);
79
+ const useNextjs = !framework || framework.toLowerCase().startsWith('n');
80
+
81
+ const selectedModules = await multiSelect(
82
+ ALL_MODULES,
83
+ 'Select modules to include:'
84
+ );
85
+
86
+ const adminAnswer = await prompt(`\n${CYAN}Include DevToolbar + TokenEditor?${RESET} [y/n] (default: y) › `);
87
+ const includeAdmin = !adminAnswer || adminAnswer.toLowerCase() !== 'n';
88
+
89
+ // Derive output directory
90
+ const outDir = resolve(process.cwd(), projectName);
91
+ if (existsSync(outDir)) {
92
+ console.error(`\n${YELLOW}Directory "${projectName}" already exists.${RESET}`);
93
+ process.exit(1);
94
+ }
95
+
96
+ console.log(`\n${CYAN}Creating project...${RESET}`);
97
+ mkdirSync(outDir, { recursive: true });
98
+
99
+ // Derive package deps
100
+ const corePkgs = ['@mounaji_npm/tokens', '@mounaji_npm/ui', '@mounaji_npm/saas-template'];
101
+ const modulePkgs = [...new Set(selectedModules.map(m => m.pkg).filter(Boolean))];
102
+ if (includeAdmin) corePkgs.push('@mounaji_npm/admin-controls');
103
+
104
+ if (useNextjs) {
105
+ writeNextjsApp(outDir, projectName, selectedModules, includeAdmin, [...corePkgs, ...modulePkgs]);
106
+ } else {
107
+ writeViteApp(outDir, projectName, selectedModules, includeAdmin, [...corePkgs, ...modulePkgs]);
108
+ }
109
+
110
+ console.log(`\n${GREEN}✓ Project created at ${outDir}${RESET}`);
111
+ console.log(`\n${BOLD}Next steps:${RESET}`);
112
+ console.log(` cd ${projectName}`);
113
+ console.log(` npm install`);
114
+ console.log(` npm run dev\n`);
115
+ console.log(`${CYAN}Docs: https://github.com/mounaji-studio/mounaji-platform/tree/main/npm_components${RESET}\n`);
116
+ }
117
+
118
+ // ─── Next.js scaffolder ───────────────────────────────────────────────────────
119
+
120
+ function writeNextjsApp(outDir, name, modules, includeAdmin, deps) {
121
+ // package.json
122
+ writeJSON(join(outDir, 'package.json'), {
123
+ name,
124
+ version: '0.1.0',
125
+ private: true,
126
+ scripts: {
127
+ dev: 'next dev',
128
+ build: 'next build',
129
+ start: 'next start',
130
+ lint: 'next lint',
131
+ },
132
+ dependencies: Object.fromEntries([
133
+ ['next', '^15.0.0'],
134
+ ['react', '^19.0.0'],
135
+ ['react-dom', '^19.0.0'],
136
+ ...deps.map(d => [d, 'latest']),
137
+ ]),
138
+ devDependencies: {
139
+ eslint: '^9.0.0',
140
+ 'eslint-config-next': '^15.0.0',
141
+ },
142
+ });
143
+
144
+ // next.config.js
145
+ writeFile(join(outDir, 'next.config.js'), `/** @type {import('next').NextConfig} */\nconst nextConfig = {};\nexport default nextConfig;\n`);
146
+
147
+ // app/layout.js
148
+ mkdirSync(join(outDir, 'app'), { recursive: true });
149
+ writeFile(join(outDir, 'app', 'layout.js'), generateNextLayout(modules, includeAdmin, name));
150
+
151
+ // app/page.js (home)
152
+ writeFile(join(outDir, 'app', 'page.js'), generateHomePage(name));
153
+
154
+ // Selected module pages
155
+ modules.filter(m => m.id !== 'home').forEach(m => {
156
+ const dir = join(outDir, 'app', m.id);
157
+ mkdirSync(dir, { recursive: true });
158
+ writeFile(join(dir, 'page.js'), generateModulePage(m));
159
+ });
160
+
161
+ // mn-config.js
162
+ writeFile(join(outDir, 'mn-config.js'), generateMnConfig(modules, name));
163
+
164
+ // .env.local
165
+ writeFile(join(outDir, '.env.local'), [
166
+ '# Backend API',
167
+ 'NEXT_PUBLIC_BACKEND_URL=http://localhost:5000',
168
+ '',
169
+ '# Firebase (optional)',
170
+ '# NEXT_PUBLIC_FIREBASE_API_KEY=',
171
+ '# NEXT_PUBLIC_FIREBASE_PROJECT_ID=',
172
+ ].join('\n'));
173
+ }
174
+
175
+ // ─── Vite scaffolder ──────────────────────────────────────────────────────────
176
+
177
+ function writeViteApp(outDir, name, modules, includeAdmin, deps) {
178
+ writeJSON(join(outDir, 'package.json'), {
179
+ name,
180
+ version: '0.1.0',
181
+ private: true,
182
+ type: 'module',
183
+ scripts: { dev: 'vite', build: 'vite build', preview: 'vite preview' },
184
+ dependencies: Object.fromEntries([
185
+ ['react', '^19.0.0'],
186
+ ['react-dom', '^19.0.0'],
187
+ ['react-router-dom', '^6.0.0'],
188
+ ...deps.map(d => [d, 'latest']),
189
+ ]),
190
+ devDependencies: {
191
+ '@vitejs/plugin-react': '^4.3.4',
192
+ vite: '^6.0.0',
193
+ },
194
+ });
195
+
196
+ writeFile(join(outDir, 'vite.config.js'), `import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nexport default defineConfig({ plugins: [react()] });\n`);
197
+
198
+ mkdirSync(join(outDir, 'src'), { recursive: true });
199
+ writeFile(join(outDir, 'index.html'), generateViteHtml(name));
200
+ writeFile(join(outDir, 'src', 'main.jsx'), generateViteMain(modules, includeAdmin, name));
201
+ writeFile(join(outDir, 'src', 'App.jsx'), generateViteApp(modules, name));
202
+ writeFile(join(outDir, 'mn-config.js'), generateMnConfig(modules, name));
203
+ }
204
+
205
+ // ─── Template generators ──────────────────────────────────────────────────────
206
+
207
+ function generateNextLayout(modules, includeAdmin, name) {
208
+ const moduleImports = modules.map(m => ` ${toConstName(m.id)}_MODULE`).join(',\n');
209
+ return `'use client';
210
+ /**
211
+ * Root layout — generated by create-mounaji-app
212
+ * Edit mn-config.js to add/remove modules, change branding, or customize tokens.
213
+ */
214
+ import { AppShell } from '@mounaji_npm/saas-template';
215
+ import {
216
+ ${moduleImports},
217
+ } from '@mounaji_npm/saas-template/modules';
218
+ ${includeAdmin ? "import { DevToolbar } from '@mounaji_npm/admin-controls';" : ''}
219
+ import { usePathname } from 'next/navigation';
220
+ import Link from 'next/link';
221
+ import { MN_CONFIG } from '../mn-config.js';
222
+
223
+ const MODULES = [
224
+ ${modules.map(m => ` ${toConstName(m.id)}_MODULE,`).join('\n')}
225
+ ];
226
+
227
+ function RootLayoutInner({ children }) {
228
+ const pathname = usePathname();
229
+ return (
230
+ <AppShell
231
+ modules={MODULES}
232
+ activePath={pathname}
233
+ LinkComponent={Link}
234
+ logo={<span style={{ fontWeight: 700, fontSize: '0.9375rem', color: 'var(--mn-text-primary-dark, #F0F4FF)' }}>{MN_CONFIG.name}</span>}
235
+ tokens={MN_CONFIG.tokens}
236
+ >
237
+ {children}
238
+ ${includeAdmin ? '{process.env.NODE_ENV !== \'production\' && <DevToolbar />}' : ''}
239
+ </AppShell>
240
+ );
241
+ }
242
+
243
+ export default function RootLayout({ children }) {
244
+ return (
245
+ <html lang="en" suppressHydrationWarning>
246
+ <body style={{ margin: 0 }}>
247
+ <RootLayoutInner>{children}</RootLayoutInner>
248
+ </body>
249
+ </html>
250
+ );
251
+ }
252
+ `;
253
+ }
254
+
255
+ function generateHomePage(name) {
256
+ return `export default function HomePage() {
257
+ return (
258
+ <div style={{ padding: 32 }}>
259
+ <h1 style={{ margin: 0, fontSize: '1.5rem', fontWeight: 700, color: 'var(--mn-text-primary-dark, #F0F4FF)' }}>
260
+ Welcome to ${name}
261
+ </h1>
262
+ <p style={{ color: 'var(--mn-text-secondary-dark, #94A3B8)', marginTop: 8 }}>
263
+ Your SaaS platform is ready. Start building.
264
+ </p>
265
+ </div>
266
+ );
267
+ }
268
+ `;
269
+ }
270
+
271
+ function generateModulePage(m) {
272
+ const compName = toPascalCase(m.id) + 'Page';
273
+ return `// ${m.label} page — edit this file to build out the ${m.label} module
274
+ export default function ${compName}() {
275
+ return (
276
+ <div style={{ padding: 32, fontFamily: 'var(--mn-font-family, inherit)' }}>
277
+ <h1 style={{ margin: 0, fontSize: '1.5rem', fontWeight: 700, color: 'var(--mn-text-primary-dark, #F0F4FF)' }}>
278
+ ${m.label}
279
+ </h1>
280
+ <p style={{ color: 'var(--mn-text-secondary-dark, #94A3B8)', marginTop: 8 }}>
281
+ Build your ${m.label} page here.
282
+ </p>
283
+ </div>
284
+ );
285
+ }
286
+ `;
287
+ }
288
+
289
+ function generateMnConfig(modules, name) {
290
+ return `/**
291
+ * mn-config.js — Mounaji Platform Configuration
292
+ *
293
+ * Edit this file to:
294
+ * - Change branding (name, logo)
295
+ * - Add/remove nav modules
296
+ * - Override design tokens
297
+ * - Add custom page modules
298
+ *
299
+ * Adding a new page module:
300
+ * 1. Create app/my-page/page.js (Next.js) or src/pages/MyPage.jsx (Vite)
301
+ * 2. Add an entry to CUSTOM_MODULES below
302
+ * 3. Import and add it to AppShell's modules prop in layout.js
303
+ */
304
+
305
+ export const MN_CONFIG = {
306
+ // ── Branding ──────────────────────────────────────────────────────────────
307
+ name: '${name}',
308
+ logoUrl: null, // set to '/logo.png' or a URL
309
+
310
+ // ── Design Token Overrides ───────────────────────────────────────────────
311
+ // These override @mounaji_npm/tokens DEFAULT_TOKENS.
312
+ // All values are injected as CSS variables at runtime.
313
+ tokens: {
314
+ // colorPrimary: '#7C3AED', // change brand color
315
+ // radiusLg: '1rem', // rounder corners
316
+ // fontFamily: '"Outfit", sans-serif',
317
+ },
318
+
319
+ // ── Custom page modules ───────────────────────────────────────────────────
320
+ // Add entries here, then import and add to modules array in layout.js
321
+ customModules: [
322
+ // {
323
+ // id: 'analytics',
324
+ // label: 'Analytics',
325
+ // icon: '📊',
326
+ // path: '/analytics',
327
+ // section: 'Workspace',
328
+ // order: 10,
329
+ // },
330
+ ],
331
+ };
332
+ `;
333
+ }
334
+
335
+ function generateViteHtml(name) {
336
+ return `<!DOCTYPE html>
337
+ <html lang="en">
338
+ <head>
339
+ <meta charset="UTF-8" />
340
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
341
+ <title>${name}</title>
342
+ </head>
343
+ <body style="margin:0">
344
+ <div id="root"></div>
345
+ <script type="module" src="/src/main.jsx"></script>
346
+ </body>
347
+ </html>
348
+ `;
349
+ }
350
+
351
+ function generateViteMain(modules, includeAdmin, name) {
352
+ return `import { StrictMode } from 'react';
353
+ import { createRoot } from 'react-dom/client';
354
+ import App from './App.jsx';
355
+
356
+ createRoot(document.getElementById('root')).render(
357
+ <StrictMode>
358
+ <App />
359
+ </StrictMode>
360
+ );
361
+ `;
362
+ }
363
+
364
+ function generateViteApp(modules, name) {
365
+ const routes = modules.map(m => ` { path: '${m.id === 'home' ? '/' : '/' + m.id}', element: <div style={{padding:32}}><h1 style={{color:'var(--mn-text-primary-dark,#F0F4FF)'}}>${m.label}</h1></div> }`).join(',\n');
366
+ return `import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom';
367
+ import { AppShell } from '@mounaji_npm/saas-template';
368
+ import { ${modules.map(m => toConstName(m.id) + '_MODULE').join(', ')} } from '@mounaji_npm/saas-template/modules';
369
+ import { MN_CONFIG } from '../mn-config.js';
370
+
371
+ const MODULES = [${modules.map(m => toConstName(m.id) + '_MODULE').join(', ')}];
372
+
373
+ function AppInner() {
374
+ const { pathname } = useLocation();
375
+ return (
376
+ <AppShell modules={MODULES} activePath={pathname} tokens={MN_CONFIG.tokens}
377
+ logo={<span style={{fontWeight:700,color:'var(--mn-text-primary-dark,#F0F4FF)'}}>{MN_CONFIG.name}</span>}>
378
+ <Routes>
379
+ ${routes}
380
+ </Routes>
381
+ </AppShell>
382
+ );
383
+ }
384
+
385
+ export default function App() {
386
+ return <BrowserRouter><AppInner /></BrowserRouter>;
387
+ }
388
+ `;
389
+ }
390
+
391
+ // ─── Utils ────────────────────────────────────────────────────────────────────
392
+
393
+ function toConstName(id) {
394
+ return id.toUpperCase().replace(/-/g, '_');
395
+ }
396
+
397
+ function toPascalCase(id) {
398
+ return id.split('-').map(s => s[0].toUpperCase() + s.slice(1)).join('');
399
+ }
400
+
401
+ function writeFile(path, content) {
402
+ writeFileSync(path, content, 'utf8');
403
+ console.log(` ${GREEN}+${RESET} ${path.replace(process.cwd(), '.')}`);
404
+ }
405
+
406
+ function writeJSON(path, obj) {
407
+ writeFile(path, JSON.stringify(obj, null, 2) + '\n');
408
+ }
409
+
410
+ main().catch(err => { console.error(err); process.exit(1); });
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@mounaji_npm/cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI to scaffold a new Mounaji SaaS app — full Next.js template with selectable modules",
5
+ "keywords": ["cli", "scaffold", "saas", "nextjs", "mounaji", "create-app"],
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "publishConfig": {
9
+ "access": "public",
10
+ "registry": "https://registry.npmjs.org/"
11
+ },
12
+ "bin": {
13
+ "create-mounaji-app": "./bin/create.js",
14
+ "mounaji": "./bin/cli.js"
15
+ },
16
+ "files": ["bin", "templates"],
17
+ "scripts": {
18
+ "test": "node bin/create.js --help"
19
+ },
20
+ "dependencies": {
21
+ "prompts": "^2.4.2",
22
+ "kleur": "^4.1.5"
23
+ }
24
+ }