luxaura 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/luxaura.js CHANGED
@@ -2,8 +2,34 @@
2
2
  'use strict';
3
3
 
4
4
  /**
5
- * Luxaura CLI
6
- * Commands: new, run, build, install, generate, format, secure
5
+ * Luxaura CLI v2.0
6
+ *
7
+ * Commands (ALL renamed — nothing similar to React/Vue/Angular/Next CLI):
8
+ *
9
+ * luxaura release <name> → Create a new project (was: new)
10
+ * luxaura ignite → Start dev server + live reload (was: run)
11
+ * luxaura forge → Build for production (was: build)
12
+ * luxaura bring <lib> → Add external library (was: install)
13
+ * luxaura component <name> → Scaffold a UI component (was: generate component)
14
+ * luxaura service <name> → Scaffold a vault service (was: generate api)
15
+ * luxaura module <name> → Scaffold a shared module (was: generate page)
16
+ * luxaura help_me → Interactive CLI guide
17
+ * luxaura polish → Auto-format .lux files (was: format)
18
+ * luxaura shield → Security scan (was: secure)
19
+ *
20
+ * Project folder hierarchy (ALL renamed):
21
+ *
22
+ * /scenes → route entry points (was: /pages — Next.js-like)
23
+ * /craft → root components live here (was: /modules — Vue-like)
24
+ * /craft/<ComponentName>/
25
+ * <ComponentName>.lux
26
+ * <ComponentName>.paint.css ← per-component styles
27
+ * index.js ← optional JS bridge
28
+ * /services → vault/server logic (was: /server)
29
+ * /media → static assets (was: /assets)
30
+ * /depot → build output (was: /dist)
31
+ * /depot/public → client assets (was: dist/client)
32
+ * /depot/vault → server modules (was: dist/server)
7
33
  */
8
34
 
9
35
  const { program } = require('commander');
@@ -17,451 +43,866 @@ const { LuxParser } = require('../src/parser');
17
43
  const { LuxCompiler } = require('../src/compiler');
18
44
  const { VaultServer } = require('../src/vault/server');
19
45
 
20
- const VERSION = '1.0.0';
46
+ const VERSION = '2.0.0';
21
47
 
22
48
  // ─── Banner ──────────────────────────────────────────────────────────────────
23
49
 
24
50
  function banner() {
25
- console.log(chalk.cyan(`
51
+ console.log(chalk.hex('#6c63ff')(`
26
52
  ██╗ ██╗ ██╗██╗ ██╗ █████╗ ██╗ ██╗██████╗ █████╗
27
53
  ██║ ██║ ██║╚██╗██╔╝██╔══██╗██║ ██║██╔══██╗██╔══██╗
28
54
  ██║ ██║ ██║ ╚███╔╝ ███████║██║ ██║██████╔╝███████║
29
55
  ██║ ██║ ██║ ██╔██╗ ██╔══██║██║ ██║██╔══██╗██╔══██║
30
56
  ███████╗╚██████╔╝██╔╝ ██╗██║ ██║╚██████╔╝██║ ██║██║ ██║
31
- ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
32
- `));
33
- console.log(chalk.gray(` Intent-Based Web Framework v${VERSION}\n`));
57
+ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝`));
58
+ console.log(chalk.hex('#a8a3ff')(` The Intent-Driven Full-Stack Web Framework`));
59
+ console.log(chalk.gray(` v${VERSION} · Not React. Not Vue. Not Angular. Just Luxaura.\n`));
34
60
  }
35
61
 
36
- // ─── Helpers ─────────────────────────────────────────────────────────────────
62
+ // ─── Logging helpers ─────────────────────────────────────────────────────────
37
63
 
38
- function log(msg) { console.log(chalk.cyan(' '), msg); }
39
- function success(msg) { console.log(chalk.green(' '), msg); }
40
- function warn(msg) { console.log(chalk.yellow(' ⚠'), msg); }
41
- function error(msg) { console.error(chalk.red(' ✖'), msg); }
42
- function info(msg) { console.log(chalk.gray(' ·'), msg); }
64
+ const log = (m) => console.log(chalk.hex('#6c63ff')(' '), m);
65
+ const success = (m) => console.log(chalk.greenBright(' '), m);
66
+ const warn = (m) => console.log(chalk.yellow(' ⚠'), m);
67
+ const error = (m) => console.error(chalk.redBright(' ✖'), m);
68
+ const info = (m) => console.log(chalk.gray(' ·'), m);
69
+ const dim = (m) => console.log(chalk.gray(m));
43
70
 
44
- function compileLuxFile(filePath, options = {}) {
45
- const source = fs.readFileSync(filePath, 'utf8');
46
- const filename = path.basename(filePath);
47
- const parser = new LuxParser(source, filename);
48
- const ast = parser.parse();
49
- const compiler = new LuxCompiler(ast, options);
50
- return compiler.compile();
51
- }
71
+ // ─── .lux file traversal ─────────────────────────────────────────────────────
52
72
 
53
- function getAllLuxFiles(dir) {
73
+ function allLuxFiles(dir) {
54
74
  const results = [];
55
75
  function walk(d) {
56
76
  if (!fs.existsSync(d)) return;
57
- for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
58
- const full = path.join(d, entry.name);
59
- if (entry.isDirectory() && !['node_modules', 'dist', '.git'].includes(entry.name)) {
60
- walk(full);
61
- } else if (entry.isFile() && entry.name.endsWith('.lux')) {
62
- results.push(full);
63
- }
77
+ for (const e of fs.readdirSync(d, { withFileTypes: true })) {
78
+ const full = path.join(d, e.name);
79
+ if (e.isDirectory() && !['node_modules', 'depot', '.git'].includes(e.name)) walk(full);
80
+ else if (e.isFile() && e.name.endsWith('.lux')) results.push(full);
64
81
  }
65
82
  }
66
83
  walk(dir);
67
84
  return results;
68
85
  }
69
86
 
70
- // ─── Command: new ─────────────────────────────────────────────────────────────
87
+ // ─── Command: release ─────────────────────────────────────────────────────────
88
+ // Creates a new Luxaura project with unique folder structure + start page
71
89
 
72
90
  program
73
- .command('new <name>')
74
- .description('Create a new Luxaura project')
91
+ .command('release <name>')
92
+ .description('Bootstrap a new Luxaura project')
75
93
  .action(async (name) => {
76
94
  banner();
77
95
  const projectDir = path.resolve(process.cwd(), name);
78
96
 
79
97
  if (fs.existsSync(projectDir)) {
80
- error(`Directory "${name}" already exists.`);
81
- process.exit(1);
98
+ error(`"${name}" already exists.`); process.exit(1);
82
99
  }
83
100
 
84
- log(`Creating project: ${chalk.white(name)}`);
101
+ log(`Releasing project: ${chalk.white(name)}`);
85
102
 
86
- // Directory structure
87
- const dirs = [
103
+ // ── Unique folder structure ──────────────────────────────────────────────
104
+ [
88
105
  projectDir,
89
- path.join(projectDir, 'pages'),
90
- path.join(projectDir, 'modules'),
91
- path.join(projectDir, 'assets'),
92
- path.join(projectDir, 'server'),
93
- path.join(projectDir, 'dist', 'client'),
94
- path.join(projectDir, 'dist', 'server'),
95
- ];
96
- dirs.forEach(d => fs.mkdirpSync(d));
97
-
98
- // luxaura.config
106
+ path.join(projectDir, 'scenes'), // route entry points (not "pages")
107
+ path.join(projectDir, 'craft'), // components root (not "modules")
108
+ path.join(projectDir, 'craft', 'Ribbon'), // starter Ribbon nav component
109
+ path.join(projectDir, 'services'), // vault/server logic (not "server")
110
+ path.join(projectDir, 'media'), // static assets (not "assets")
111
+ path.join(projectDir, 'depot', 'public'), // build output client (not "dist/client")
112
+ path.join(projectDir, 'depot', 'vault'), // build output server (not "dist/server")
113
+ ].forEach(d => fs.mkdirpSync(d));
114
+
115
+ // ── luxaura.config ───────────────────────────────────────────────────────
99
116
  fs.writeFileSync(path.join(projectDir, 'luxaura.config'), `
100
- # Luxaura Configuration
101
- app.name: ${name}
102
- app.version: 1.0.0
117
+ # ┌─────────────────────────────────────────┐
118
+ # │ Luxaura Configuration │
119
+ # │ Edit this file to configure your app
120
+ # └─────────────────────────────────────────┘
103
121
 
104
- # theme: light | dark
122
+ # App identity
123
+ identity.name: ${name}
124
+ identity.version: 1.0.0
125
+ identity.author: You
126
+
127
+ # Visual theme: light | dark | auto
105
128
  theme: light
106
129
 
107
- # mode: full | headless
130
+ # Runtime mode: full | headless
131
+ # full → Luxaura handles frontend + vault backend
132
+ # headless → frontend only, vault proxies to external API
108
133
  mode: full
109
134
 
110
- # Database (optional)
111
- # db.type: postgres
112
- # db.url: postgresql://localhost:5432/${name}
135
+ # Database connection (optional — uncomment to activate)
136
+ # db.engine: postgres
137
+ # db.link: postgresql://localhost:5432/${name}
113
138
 
114
- # Headless proxy (only used when mode: headless)
115
- # proxy: http://localhost:8080
116
- `.trim() + '\n');
139
+ # Headless proxy target (only when mode: headless)
140
+ # proxy.target: http://localhost:8080
117
141
 
118
- // pages/index.lux
119
- fs.writeFileSync(path.join(projectDir, 'pages', 'index.lux'), `
120
- # ${name} — Home Page
121
-
122
- props
123
- title: String = "Welcome to ${name}"
124
-
125
- state
126
- count: 0
127
-
128
- style
129
- self
130
- padding: 8
131
- background: #ffffff
132
-
133
- Title
134
- fontSize: 2xl
135
- fontWeight: bold
136
- color: #1a1a2e
137
-
138
- Action
139
- padding: 4
140
- radius: medium
141
- background: #6c63ff
142
- color: #ffffff
143
- cursor: pointer
144
-
145
- view
146
- Container
147
- Title "{title}"
148
- Text "You have clicked the button {count} times."
149
- Action "Click Me"
150
- on click:
151
- await server.logClick(count)
142
+ # Port for luxaura ignite (dev server)
143
+ port: 3000
152
144
  `.trim() + '\n');
153
145
 
154
- // modules/Navbar.lux
155
- fs.writeFileSync(path.join(projectDir, 'modules', 'Navbar.lux'), `
156
- # Reusable Navbar Component
146
+ // ── scenes/index.lux — The start page (rich how-to content) ─────────────
147
+ fs.writeFileSync(path.join(projectDir, 'scenes', 'index.lux'), buildStartPage(name));
157
148
 
158
- props
149
+ // ── craft/Ribbon/Ribbon.lux — starter nav component ─────────────────────
150
+ fs.writeFileSync(path.join(projectDir, 'craft', 'Ribbon', 'Ribbon.lux'), `
151
+ # Ribbon Navigation Component
152
+ # Modify this component to build your own navigation bar.
153
+
154
+ receive
159
155
  brand: String = "${name}"
160
156
 
161
- style
157
+ paint
162
158
  self
163
- padding: 4
164
- background: #1a1a2e
165
- shadow: soft
166
-
167
- Title
168
- color: #ffffff
169
- fontSize: lg
170
- fontWeight: bold
171
-
172
- view
173
- Nav
174
- Row
175
- Title "{brand}"
176
- Row
177
- Action "Home"
178
- Action "About"
179
- Action "Contact"
159
+ breathe: 4
160
+ fill: #0d0d1a
161
+ glow: whisper
162
+
163
+ Headline
164
+ ink: #ffffff
165
+ size: large
166
+ weight: strong
167
+
168
+ Trigger
169
+ curve: pill
170
+ breathe: 3
171
+ fill: #6c63ff
172
+ ink: #ffffff
173
+ pointer: true
174
+
175
+ canvas
176
+ Ribbon
177
+ Strip
178
+ Headline "<<brand>>"
179
+ Strip
180
+ Trigger "Home"
181
+ Trigger "Docs"
182
+ Trigger "About"
180
183
  `.trim() + '\n');
181
184
 
182
- // server/index.js
183
- fs.writeFileSync(path.join(projectDir, 'server', 'index.js'), `
185
+ // ── craft/Ribbon/Ribbon.paint.css — component-scoped styles ─────────────
186
+ fs.writeFileSync(path.join(projectDir, 'craft', 'Ribbon', 'Ribbon.paint.css'),
187
+ `/* Ribbon component — scoped paint styles */\n/* Add CSS here for fine-grained control */\n`);
188
+
189
+ // ── services/index.js — starter vault service ────────────────────────────
190
+ fs.writeFileSync(path.join(projectDir, 'services', 'index.js'), `
184
191
  'use strict';
185
- // Shared server utilities (imported by compiled server modules)
192
+ /**
193
+ * Luxaura Vault Service
194
+ * Functions defined here run ONLY on the server.
195
+ * They are called from .lux files via: await vault.functionName(args)
196
+ */
186
197
 
187
- async function logClick(count) {
188
- console.log('[Server] Button clicked, count:', count);
189
- return { ok: true, count };
198
+ async function ping() {
199
+ return { ok: true, message: 'Vault is alive', time: new Date().toISOString() };
190
200
  }
191
201
 
192
- module.exports = { logClick };
202
+ module.exports = { ping };
193
203
  `);
194
204
 
195
- // package.json for project
205
+ // ── package.json ─────────────────────────────────────────────────────────
196
206
  fs.writeFileSync(path.join(projectDir, 'package.json'), JSON.stringify({
197
207
  name,
198
208
  version: '1.0.0',
199
- description: `${name} — built with Luxaura`,
209
+ description: `${name} — crafted with Luxaura`,
200
210
  scripts: {
201
- dev: 'luxaura run',
202
- build: 'luxaura build',
203
- },
204
- dependencies: {
205
- luxaura: `^${VERSION}`,
211
+ ignite: 'luxaura ignite',
212
+ forge: 'luxaura forge',
206
213
  },
214
+ dependencies: { luxaura: `^${VERSION}` },
207
215
  }, null, 2));
208
216
 
209
- // .gitignore
210
- fs.writeFileSync(path.join(projectDir, '.gitignore'), `node_modules/\ndist/\n.DS_Store\n`);
217
+ // ── .gitignore ───────────────────────────────────────────────────────────
218
+ fs.writeFileSync(path.join(projectDir, '.gitignore'), `node_modules/\ndepot/\n.DS_Store\n*.log\n`);
211
219
 
212
- // Copy UI kit files if they exist (they'll be generated later)
213
- const uiKitSrc = path.join(__dirname, '..', 'ui-kit');
214
- const uiKitDst = path.join(projectDir, 'dist', 'client');
215
- if (fs.existsSync(path.join(uiKitSrc, 'luxaura.min.css'))) {
216
- fs.copySync(path.join(uiKitSrc, 'luxaura.min.css'), path.join(uiKitDst, 'luxaura.min.css'));
217
- fs.copySync(path.join(uiKitSrc, 'luxaura.min.js'), path.join(uiKitDst, 'luxaura.min.js'));
220
+ // ── Copy UI kit to depot/public ──────────────────────────────────────────
221
+ const kitSrc = path.join(__dirname, '..', 'ui-kit');
222
+ const kitDst = path.join(projectDir, 'depot', 'public');
223
+ if (fs.existsSync(path.join(kitSrc, 'luxaura.min.css'))) {
224
+ fs.copySync(path.join(kitSrc, 'luxaura.min.css'), path.join(kitDst, 'luxaura.min.css'));
225
+ fs.copySync(path.join(kitSrc, 'luxaura.min.js'), path.join(kitDst, 'luxaura.min.js'));
218
226
  }
219
227
 
220
- success(`Project "${name}" created!`);
221
- info(`\n cd ${name}`);
222
- info(` luxaura run\n`);
228
+ success(`Project "${chalk.white(name)}" released!`);
229
+ dim('');
230
+ info(` ${chalk.white('cd ' + name)}`);
231
+ info(` ${chalk.white('luxaura ignite')} ${chalk.gray('→ start dev server')}`);
232
+ info(` ${chalk.white('luxaura help_me')} ${chalk.gray('→ see all commands')}`);
233
+ dim('');
223
234
  });
224
235
 
225
- // ─── Command: run ─────────────────────────────────────────────────────────────
236
+ // ─── Start page builder ───────────────────────────────────────────────────────
237
+
238
+ function buildStartPage(name) {
239
+ return `
240
+ # ${name} — Start Scene
241
+ # This is your project's home. Edit it freely.
242
+ # Run: luxaura help_me to see all commands.
243
+
244
+ receive
245
+ appName: String = "${name}"
246
+
247
+ signal
248
+ activeSection: "welcome"
249
+
250
+ paint
251
+ self
252
+ fill: #0d0d1a
253
+ breathe: 0
254
+
255
+ Headline
256
+ ink: #ffffff
257
+ weight: heavy
258
+
259
+ Body
260
+ ink: #b0afd0
261
+
262
+ Trigger
263
+ curve: pill
264
+ glow: soft
265
+ pointer: true
266
+
267
+ Plate
268
+ fill: #1a1a2e
269
+ curve: round
270
+ glow: whisper
271
+ breathe: 6
272
+
273
+ Snippet
274
+ fill: #0a0920
275
+ ink: #a8a3ff
276
+ curve: gentle
277
+
278
+ canvas
279
+ Frame
280
+ Ribbon
281
+ Strip
282
+ Headline "<<appName>>"
283
+ Mark "v1.0"
284
+ Strip
285
+ Trigger "Docs"
286
+ Trigger "GitHub"
287
+
288
+ Scene
289
+ Stack
290
+
291
+ Frame
292
+ Headline "Welcome to <<appName>>"
293
+ Body "Your Luxaura project is alive. This is your start scene — edit scenes/index.lux to make it yours."
294
+ Strip
295
+ Trigger "Read the Docs"
296
+ when click:
297
+ Trigger "View on GitHub"
298
+ when click:
299
+
300
+ Splitter
301
+
302
+ Plate
303
+ Headline "Luxaura Quick Reference"
304
+ Body "Everything is different by design. Here is what you need to know."
305
+
306
+ Mosaic
307
+
308
+ Plate
309
+ Headline "Project structure"
310
+ Roster
311
+ Entry
312
+ Snippet "/scenes"
313
+ Body "Your routes live here. index.lux is this page."
314
+ Entry
315
+ Snippet "/craft"
316
+ Body "Your components. Each gets its own folder."
317
+ Entry
318
+ Snippet "/services"
319
+ Body "Vault logic — server-only, never exposed."
320
+ Entry
321
+ Snippet "/media"
322
+ Body "Images, fonts, static files."
323
+ Entry
324
+ Snippet "/depot"
325
+ Body "Build output. Never commit this."
326
+
327
+ Plate
328
+ Headline "CLI Commands"
329
+ Roster
330
+ Entry
331
+ Snippet "luxaura ignite"
332
+ Body "Start the dev server with live reload."
333
+ Entry
334
+ Snippet "luxaura forge"
335
+ Body "Build for production."
336
+ Entry
337
+ Snippet "luxaura component MyCard"
338
+ Body "Create a new component in /craft/MyCard/."
339
+ Entry
340
+ Snippet "luxaura service UserService"
341
+ Body "Create a vault service in /services/."
342
+ Entry
343
+ Snippet "luxaura module SharedLogic"
344
+ Body "Create a shared module."
345
+ Entry
346
+ Snippet "luxaura bring tailwindcss"
347
+ Body "Pull in an external library."
348
+ Entry
349
+ Snippet "luxaura polish"
350
+ Body "Auto-format all .lux files."
351
+ Entry
352
+ Snippet "luxaura shield"
353
+ Body "Scan for security issues."
354
+ Entry
355
+ Snippet "luxaura help_me"
356
+ Body "Full interactive CLI guide."
357
+
358
+ Plate
359
+ Headline ".lux Syntax"
360
+ Roster
361
+ Entry
362
+ Snippet "receive"
363
+ Body "Inputs passed in from outside."
364
+ Entry
365
+ Snippet "signal"
366
+ Body "Reactive data inside this scene."
367
+ Entry
368
+ Snippet "vault"
369
+ Body "Server-only logic. Never reaches browser."
370
+ Entry
371
+ Snippet "paint"
372
+ Body "Styles using smart token names."
373
+ Entry
374
+ Snippet "canvas"
375
+ Body "Your UI tree using Luxaura nodes."
376
+ Entry
377
+ Snippet "<<varName>>"
378
+ Body "Bind a signal or receive value."
379
+ Entry
380
+ Snippet "when click:"
381
+ Body "Event handler on any node."
382
+ Entry
383
+ Snippet "await vault.myAction()"
384
+ Body "Call a server vault action."
385
+
386
+ Plate
387
+ Headline "Paint Tokens"
388
+ Roster
389
+ Entry
390
+ Snippet "breathe: 4"
391
+ Body "Padding — 4 × 4px = 16px."
392
+ Entry
393
+ Snippet "push: 4"
394
+ Body "Margin."
395
+ Entry
396
+ Snippet "glow: soft"
397
+ Body "Box shadow."
398
+ Entry
399
+ Snippet "curve: smooth"
400
+ Body "Border radius — 8px."
401
+ Entry
402
+ Snippet "ink: #fff"
403
+ Body "Text color."
404
+ Entry
405
+ Snippet "fill: #000"
406
+ Body "Background color."
407
+ Entry
408
+ Snippet "size: titan"
409
+ Body "Font size — 3rem."
410
+ Entry
411
+ Snippet "weight: heavy"
412
+ Body "Font weight — 900."
413
+
414
+ Splitter
415
+
416
+ Anchor
417
+ Body "Built with Luxaura — the framework that does things differently."
418
+ `.trim() + '\n';
419
+ }
420
+
421
+ // ─── Command: ignite (was: run) ───────────────────────────────────────────────
226
422
 
227
423
  program
228
- .command('run')
229
- .description('Start development server with HMR')
230
- .option('-p, --port <port>', 'Port number', '3000')
424
+ .command('ignite')
425
+ .description('Start the Luxaura dev server with live reload')
426
+ .option('-p, --port <port>', 'Port to listen on', '3000')
231
427
  .action(async (opts) => {
232
428
  banner();
233
429
  const rootDir = process.cwd();
234
- const port = parseInt(opts.port, 10);
430
+ const port = parseInt(opts.port, 10);
235
431
 
236
- log('Compiling .lux files...');
432
+ log('Forging scenes and craft...');
237
433
  await buildAll(rootDir, { dev: true });
238
434
 
239
- const vault = new VaultServer({ port, rootDir });
240
- const actualPort = await vault.start();
241
- success(`Dev server running at ${chalk.white(`http://localhost:${actualPort}`)}`);
242
- info('Hot Module Replacement enabled. Watching for changes...\n');
435
+ const server = new VaultServer({ port, rootDir });
436
+ const live = await server.start();
437
+ success(`Dev server ignited at ${chalk.white(`http://localhost:${live}`)}`);
438
+ info('Live reload active. Watching /scenes, /craft, /services...\n');
243
439
 
244
- // Inject HMR client script into index.html
245
- _injectHMR(rootDir, actualPort);
440
+ _injectLiveReload(rootDir, live);
246
441
 
247
442
  const watcher = chokidar.watch(
248
- [path.join(rootDir, 'pages'), path.join(rootDir, 'modules')],
443
+ [
444
+ path.join(rootDir, 'scenes'),
445
+ path.join(rootDir, 'craft'),
446
+ path.join(rootDir, 'services'),
447
+ ],
249
448
  { ignoreInitial: true }
250
449
  );
251
450
 
252
- watcher.on('change', async (filePath) => {
253
- log(`Changed: ${chalk.yellow(path.relative(rootDir, filePath))}`);
451
+ watcher.on('change', async (fp) => {
452
+ log(`Changed: ${chalk.yellow(path.relative(rootDir, fp))}`);
254
453
  try {
255
- await buildFile(filePath, rootDir, { dev: true });
256
- vault.triggerHMR(path.relative(rootDir, filePath));
257
- success('Rebuilt and reloaded.');
258
- } catch (e) {
259
- error(`Compile error: ${e.message}`);
260
- }
454
+ if (fp.endsWith('.lux')) await buildFile(fp, rootDir, { dev: true });
455
+ server.triggerHMR(path.relative(rootDir, fp));
456
+ success('Rebuilt.');
457
+ } catch (e) { error(`Build error: ${e.message}`); }
261
458
  });
262
459
 
263
- watcher.on('add', async (filePath) => {
264
- log(`New file: ${chalk.yellow(path.relative(rootDir, filePath))}`);
265
- await buildFile(filePath, rootDir, { dev: true });
266
- vault.triggerHMR('new-file');
460
+ watcher.on('add', async (fp) => {
461
+ if (!fp.endsWith('.lux')) return;
462
+ log(`New: ${chalk.yellow(path.relative(rootDir, fp))}`);
463
+ await buildFile(fp, rootDir, { dev: true });
464
+ server.triggerHMR('new-file');
267
465
  });
268
466
  });
269
467
 
270
- function _injectHMR(rootDir, port) {
271
- const indexPath = path.join(rootDir, 'dist', 'client', 'index.html');
272
- if (!fs.existsSync(indexPath)) return;
273
- let html = fs.readFileSync(indexPath, 'utf8');
274
- if (html.includes('__lux_hmr__')) return;
275
- const hmrScript = `
276
- <script id="__lux_hmr__">
468
+ function _injectLiveReload(rootDir, port) {
469
+ const htmlPath = path.join(rootDir, 'depot', 'public', 'index.html');
470
+ if (!fs.existsSync(htmlPath)) return;
471
+ let html = fs.readFileSync(htmlPath, 'utf8');
472
+ if (html.includes('__lux_live__')) return;
473
+ const script = `\n<script id="__lux_live__">
277
474
  (function(){
278
- const ws = new WebSocket('ws://localhost:${port}');
279
- ws.onmessage = function(e) {
280
- const msg = JSON.parse(e.data);
281
- if (msg.type === 'hmr') {
282
- console.log('[Luxaura HMR] Reloading:', msg.file);
283
- location.reload();
284
- }
285
- };
286
- ws.onclose = function() {
287
- console.log('[Luxaura HMR] Connection lost. Reconnecting...');
288
- setTimeout(() => location.reload(), 2000);
475
+ var ws = new WebSocket('ws://localhost:${port}');
476
+ ws.onmessage = function(e){
477
+ var m = JSON.parse(e.data);
478
+ if (m.type === 'hmr') { console.log('[Luxaura Live]', m.file); location.reload(); }
289
479
  };
480
+ ws.onclose = function(){ setTimeout(function(){ location.reload(); }, 2000); };
290
481
  })();
291
482
  </script>`;
292
- html = html.replace('</body>', hmrScript + '</body>');
293
- fs.writeFileSync(indexPath, html);
483
+ html = html.replace('</body>', script + '</body>');
484
+ fs.writeFileSync(htmlPath, html);
294
485
  }
295
486
 
296
- // ─── Command: build ───────────────────────────────────────────────────────────
487
+ // ─── Command: forge (was: build) ─────────────────────────────────────────────
297
488
 
298
489
  program
299
- .command('build')
300
- .description('Compile for production (splits client/server)')
490
+ .command('forge')
491
+ .description('Forge the production build (splits public + vault)')
301
492
  .action(async () => {
302
493
  banner();
303
494
  const rootDir = process.cwd();
304
- log('Building for production...');
305
-
495
+ log('Forging production build...');
306
496
  const start = Date.now();
307
497
  const stats = await buildAll(rootDir, { dev: false });
308
-
309
- success(`Build complete in ${Date.now() - start}ms`);
310
- info(` Client assets → ${chalk.white('dist/client/')}`);
311
- info(` Server modules ${chalk.white('dist/server/')}`);
312
- info(` Files compiled: ${stats.count}`);
313
- if (stats.errors.length) {
314
- stats.errors.forEach(e => error(e));
315
- }
498
+ success(`Forged in ${Date.now() - start}ms — ${stats.count} file(s)`);
499
+ info(` Public assets → ${chalk.white('depot/public/')}`);
500
+ info(` Vault modules → ${chalk.white('depot/vault/')}`);
501
+ if (stats.errors.length) stats.errors.forEach(e => error(e));
316
502
  });
317
503
 
318
- // ─── Command: install ─────────────────────────────────────────────────────────
504
+ // ─── Command: bring (was: install) ───────────────────────────────────────────
319
505
 
320
506
  program
321
- .command('install <lib>')
322
- .description('Install and integrate a frontend library')
323
- .action(async (lib) => {
507
+ .command('bring <lib>')
508
+ .description('Bring in an external library and wire it into the build')
509
+ .action((lib) => {
324
510
  banner();
325
- log(`Installing ${chalk.yellow(lib)}...`);
511
+ log(`Bringing in: ${chalk.yellow(lib)}`);
326
512
  try {
327
513
  execSync(`npm install ${lib}`, { stdio: 'inherit', cwd: process.cwd() });
328
- success(`${lib} installed.`);
329
- info('Classes are now available in your .lux files via the class: attribute.');
330
- } catch (e) {
331
- error(`npm install failed: ${e.message}`);
332
- }
514
+ success(`${lib} is now available.`);
515
+ info('Use its classes directly in your canvas via the class: attribute.');
516
+ } catch (e) { error(`npm install failed: ${e.message}`); }
333
517
  });
334
518
 
335
- // ─── Command: generate ────────────────────────────────────────────────────────
519
+ // ─── Command: component ───────────────────────────────────────────────────────
520
+ // Creates /craft/<Name>/<Name>.lux + <Name>.paint.css + index.js
521
+
522
+ program
523
+ .command('component <name>')
524
+ .description('Craft a new UI component in /craft/<Name>/')
525
+ .action((name) => {
526
+ banner();
527
+ const rootDir = process.cwd();
528
+ const compDir = path.join(rootDir, 'craft', name);
529
+
530
+ if (fs.existsSync(compDir)) {
531
+ warn(`Component "${name}" already exists at craft/${name}/`);
532
+ process.exit(0);
533
+ }
534
+
535
+ fs.mkdirpSync(compDir);
336
536
 
337
- const TEMPLATES = {
338
- component: (name) => `
537
+ // ── <Name>.lux — component with a styled big title ───────────────────────
538
+ fs.writeFileSync(path.join(compDir, `${name}.lux`), `
339
539
  # ${name} Component
540
+ # Edit this file to build your component.
340
541
 
341
- props
342
- label: String = "${name}"
542
+ receive
543
+ label: String = "I'm ${name} component !!"
343
544
 
344
- state
545
+ signal
345
546
  active: false
346
547
 
347
- style
548
+ paint
348
549
  self
349
- padding: 4
350
- radius: medium
351
- shadow: soft
550
+ breathe: 8
551
+ fill: #0d0d1a
552
+ glow: soft
553
+ curve: round
554
+
555
+ Headline
556
+ ink: #ffffff
557
+ size: titan
558
+ weight: heavy
559
+ align: center
560
+
561
+ Body
562
+ ink: #a8a3ff
563
+ size: large
564
+ align: center
565
+ push: 4
566
+
567
+ Mark
568
+ fill: #6c63ff
569
+ ink: #ffffff
570
+ curve: pill
571
+ push: 2
572
+
573
+ canvas
574
+ Frame
575
+ Stack
576
+ Headline "<<label>>"
577
+ Body "I live in craft/${name}/${name}.lux — edit me!"
578
+ Strip
579
+ Mark "Luxaura Component"
580
+ Mark "<<active>>"
581
+ `.trim() + '\n');
352
582
 
353
- view
354
- Box
355
- Title "{label}"
356
- Text "Edit ${name}.lux to get started."
357
- `.trim() + '\n',
583
+ // ── <Name>.paint.css — scoped paint styles ───────────────────────────────
584
+ fs.writeFileSync(path.join(compDir, `${name}.paint.css`), `
585
+ /* ${name} — Component-scoped paint styles */
586
+ /* These styles apply only to this component. */
587
+ /* Use .lux-* classes from luxaura.min.css or write your own. */
358
588
 
359
- page: (name) => `
360
- # ${name} Page
589
+ .lux-scene-${name.toLowerCase()} {
590
+ /* Component root styles */
591
+ }
592
+ `.trim() + '\n');
361
593
 
362
- props
363
- title: String = "${name}"
594
+ // ── index.js — optional JS bridge ───────────────────────────────────────
595
+ fs.writeFileSync(path.join(compDir, 'index.js'), `
596
+ /**
597
+ * ${name} — Optional JS bridge
598
+ * Use this file to export helpers, constants, or sub-components.
599
+ * The .lux file handles the UI — this is for shared logic only.
600
+ */
364
601
 
365
- state
366
- loading: false
602
+ module.exports = {
603
+ name: '${name}',
604
+ version: '1.0.0',
605
+ };
606
+ `);
367
607
 
368
- style
369
- self
370
- padding: 8
608
+ success(`Component "${chalk.white(name)}" crafted at ${chalk.white(`craft/${name}/`)}`);
609
+ info(` ${chalk.white(`craft/${name}/${name}.lux`)} ← the component canvas`);
610
+ info(` ${chalk.white(`craft/${name}/${name}.paint.css`)} ← scoped styles`);
611
+ info(` ${chalk.white(`craft/${name}/index.js`)} ← optional JS bridge`);
612
+ });
371
613
 
372
- view
373
- Container
374
- Title "{title}"
375
- Text "This is the ${name} page."
376
- `.trim() + '\n',
614
+ // ─── Command: service ────────────────────────────────────────────────────────
615
+ // Creates /services/<Name>.service.js — vault-only, never reaches browser
377
616
 
378
- api: (name) => `
379
- # ${name} API Module (server-side only)
380
- # This file is compiled into dist/server and NEVER sent to the client.
617
+ program
618
+ .command('service <name>')
619
+ .description('Create a vault service in /services/ (server-only)')
620
+ .action((name) => {
621
+ banner();
622
+ const rootDir = process.cwd();
623
+ const servicesDir = path.join(rootDir, 'services');
624
+ fs.mkdirpSync(servicesDir);
381
625
 
382
- server
383
- import db from "luxaura/db"
626
+ const filePath = path.join(servicesDir, `${name}.service.js`);
627
+ if (fs.existsSync(filePath)) {
628
+ warn(`Service "${name}" already exists.`); process.exit(0);
629
+ }
384
630
 
385
- def get${name}(id):
386
- return db.query("SELECT * FROM ${name.toLowerCase()}s WHERE id = ?", [id])
631
+ fs.writeFileSync(filePath, `
632
+ /**
633
+ * ${name} — Luxaura Vault Service
634
+ *
635
+ * ┌─────────────────────────────────────────────────────┐
636
+ * │ VAULT — This file NEVER reaches the browser. │
637
+ * │ All functions here run server-side only. │
638
+ * │ Call them from .lux files: await vault.action() │
639
+ * └─────────────────────────────────────────────────────┘
640
+ *
641
+ * Syntax in .lux files:
642
+ *
643
+ * vault
644
+ * pull db from "luxaura/db"
645
+ * action get${name}(id):
646
+ * return db.query("SELECT * FROM ${name.toLowerCase()}s WHERE id = ?", [id])
647
+ */
648
+ 'use strict';
387
649
 
388
- def create${name}(data):
389
- return db.insert("${name.toLowerCase()}s", data)
650
+ /**
651
+ * get${name} — Fetch a ${name} by id
652
+ */
653
+ async function get${name}(id) {
654
+ // TODO: implement
655
+ return { id, name: '${name}', fetched: new Date().toISOString() };
656
+ }
390
657
 
391
- def update${name}(id, data):
392
- return db.update("${name.toLowerCase()}s", data, { id })
393
- `.trim() + '\n',
394
- };
658
+ /**
659
+ * create${name} Create a new ${name}
660
+ */
661
+ async function create${name}(data) {
662
+ // TODO: implement
663
+ return { ok: true, data };
664
+ }
665
+
666
+ /**
667
+ * update${name} — Update a ${name}
668
+ */
669
+ async function update${name}(id, data) {
670
+ // TODO: implement
671
+ return { ok: true, id, data };
672
+ }
673
+
674
+ /**
675
+ * delete${name} — Delete a ${name}
676
+ */
677
+ async function delete${name}(id) {
678
+ // TODO: implement
679
+ return { ok: true, id };
680
+ }
681
+
682
+ module.exports = { get${name}, create${name}, update${name}, delete${name} };
683
+ `);
684
+
685
+ success(`Service "${chalk.white(name)}" created at ${chalk.white(`services/${name}.service.js`)}`);
686
+ info('Call its actions from .lux vault blocks:');
687
+ info(` ${chalk.white(`await vault.get${name}(id)`)}`);
688
+ });
689
+
690
+ // ─── Command: module ─────────────────────────────────────────────────────────
691
+ // Creates a shared logic module — used by multiple scenes or components
395
692
 
396
693
  program
397
- .command('generate <type> <name>')
398
- .alias('g')
399
- .description('Scaffold component, page, or api file')
400
- .action((type, name) => {
694
+ .command('module <name>')
695
+ .description('Create a shared module in /craft/<Name>/')
696
+ .action((name) => {
401
697
  banner();
402
- const rootDir = process.cwd();
403
- const template = TEMPLATES[type];
404
- if (!template) {
405
- error(`Unknown type "${type}". Use: component, page, api`);
406
- process.exit(1);
698
+ const rootDir = process.cwd();
699
+ const modDir = path.join(rootDir, 'craft', name);
700
+
701
+ if (fs.existsSync(modDir)) {
702
+ warn(`Module "${name}" already exists.`); process.exit(0);
407
703
  }
408
704
 
409
- const dirs = { component: 'modules', page: 'pages', api: 'pages' };
410
- const dir = path.join(rootDir, dirs[type]);
411
- fs.mkdirpSync(dir);
705
+ fs.mkdirpSync(modDir);
412
706
 
413
- const filePath = path.join(dir, `${name}.lux`);
414
- if (fs.existsSync(filePath)) {
415
- warn(`File already exists: ${path.relative(rootDir, filePath)}`);
416
- process.exit(0);
417
- }
707
+ // A module is a .lux scene + a JS utility file — shared, not a route
708
+ fs.writeFileSync(path.join(modDir, `${name}.lux`), `
709
+ # ${name} — Shared Module
710
+ # This module can be composed into any scene or component.
711
+
712
+ receive
713
+ title: String = "${name} Module"
714
+
715
+ signal
716
+ ready: true
717
+
718
+ paint
719
+ self
720
+ breathe: 6
721
+ fill: #1a1a2e
722
+ curve: smooth
723
+
724
+ Headline
725
+ ink: #6c63ff
726
+ size: xlarge
727
+ weight: strong
728
+
729
+ canvas
730
+ Frame
731
+ Stack
732
+ Headline "<<title>>"
733
+ Body "I'm the ${name} module — compose me anywhere."
734
+ `.trim() + '\n');
735
+
736
+ fs.writeFileSync(path.join(modDir, `${name}.utils.js`), `
737
+ /**
738
+ * ${name} Module — Shared utilities
739
+ * Import these helpers into any service or scene.
740
+ */
741
+ 'use strict';
742
+
743
+ function format${name}(data) {
744
+ return { ...data, formatted: true, module: '${name}' };
745
+ }
746
+
747
+ module.exports = { format${name} };
748
+ `);
749
+
750
+ success(`Module "${chalk.white(name)}" created at ${chalk.white(`craft/${name}/`)}`);
751
+ info(` ${chalk.white(`craft/${name}/${name}.lux`)} ← composable scene`);
752
+ info(` ${chalk.white(`craft/${name}/${name}.utils.js`)} ← shared utilities`);
753
+ });
754
+
755
+ // ─── Command: help_me ────────────────────────────────────────────────────────
756
+
757
+ program
758
+ .command('help_me')
759
+ .description('Interactive CLI guide — everything Luxaura')
760
+ .action(() => {
761
+ banner();
418
762
 
419
- fs.writeFileSync(filePath, template(name));
420
- success(`Generated: ${chalk.white(path.relative(rootDir, filePath))}`);
763
+ console.log(chalk.hex('#6c63ff').bold(' ══════════════════════════════════════════════'));
764
+ console.log(chalk.hex('#a8a3ff').bold(' LUXAURA CLI — Complete Reference'));
765
+ console.log(chalk.hex('#6c63ff').bold(' ══════════════════════════════════════════════\n'));
766
+
767
+ const section = (title) => {
768
+ console.log(chalk.hex('#6c63ff').bold(` ▸ ${title}`));
769
+ };
770
+ const cmd = (c, d) => {
771
+ console.log(` ${chalk.white(c.padEnd(38))} ${chalk.gray(d)}`);
772
+ };
773
+ const note = (t) => console.log(chalk.gray(` ${t}`));
774
+ const br = () => console.log('');
775
+
776
+ section('PROJECT');
777
+ cmd('luxaura release <name>', 'Bootstrap a new project');
778
+ note('Creates /scenes /craft /services /media /depot');
779
+ note('Generates a rich start scene with full docs at scenes/index.lux');
780
+ br();
781
+
782
+ section('DEVELOPMENT');
783
+ cmd('luxaura ignite', 'Start dev server + live reload (port 3000)');
784
+ cmd('luxaura ignite --port 8080', 'Start on a custom port');
785
+ cmd('luxaura forge', 'Build for production → depot/public + depot/vault');
786
+ br();
787
+
788
+ section('SCAFFOLDING');
789
+ cmd('luxaura component <Name>', 'Create a component → craft/<Name>/');
790
+ note('Generates: <Name>.lux + <Name>.paint.css + index.js');
791
+ cmd('luxaura service <Name>', 'Create a vault service → services/<Name>.service.js');
792
+ note('Vault services run server-side ONLY — never exposed to browser');
793
+ cmd('luxaura module <Name>', 'Create a shared module → craft/<Name>/');
794
+ note('Modules are composable — use them across scenes and components');
795
+ br();
796
+
797
+ section('LIBRARIES');
798
+ cmd('luxaura bring <lib>', 'Pull in an npm library (e.g. luxaura bring gsap)');
799
+ note('Wires the library into your build automatically');
800
+ br();
801
+
802
+ section('QUALITY');
803
+ cmd('luxaura polish', 'Auto-format all .lux files');
804
+ cmd('luxaura shield', 'Scan for XSS, SQL injection, vault leaks');
805
+ br();
806
+
807
+ section('HELP');
808
+ cmd('luxaura help_me', 'Show this guide');
809
+ cmd('luxaura --version', 'Show framework version');
810
+ br();
811
+
812
+ console.log(chalk.hex('#6c63ff').bold(' ══════════════════════════════════════════════'));
813
+ console.log(chalk.hex('#a8a3ff')(' .lux Block Reference'));
814
+ console.log(chalk.hex('#6c63ff').bold(' ══════════════════════════════════════════════\n'));
815
+
816
+ const block = (b, d) => console.log(` ${chalk.white(b.padEnd(12))} ${chalk.gray(d)}`);
817
+ block('receive', 'Inputs passed in from outside this scene/component');
818
+ block('signal', 'Reactive data — changes trigger canvas re-render');
819
+ block('vault', 'Server-only logic — NEVER sent to browser');
820
+ block('paint', 'Styling using Luxaura smart tokens (breathe/glow/curve/ink...)');
821
+ block('canvas', 'UI composition tree using Luxaura nodes');
822
+ br();
823
+
824
+ console.log(chalk.hex('#a8a3ff')(' Node Vocabulary (instead of HTML tags):\n'));
825
+ const node = (n, d) => console.log(` ${chalk.hex('#6c63ff')(n.padEnd(14))} ${chalk.gray(d)}`);
826
+ node('Frame', 'Generic block wrapper');
827
+ node('Scene', 'Max-width centered wrapper (like <main>)');
828
+ node('Strip', 'Horizontal flex row');
829
+ node('Stack', 'Vertical flex column');
830
+ node('Mosaic', 'Auto-responsive grid');
831
+ node('Ribbon', 'Top navigation bar');
832
+ node('Drawer', 'Side panel');
833
+ node('Anchor', 'Footer');
834
+ node('Crown', 'Page header');
835
+ node('Headline', 'Auto-selects h1–h6 by nesting depth');
836
+ node('Body', 'Paragraph text');
837
+ node('Picture', 'Image (auto-lazy loaded)');
838
+ node('Trigger', 'Button / clickable action');
839
+ node('Field', 'Text input');
840
+ node('Vessel', 'Form wrapper');
841
+ node('Toggle', 'Checkbox / switch');
842
+ node('Plate', 'Card surface');
843
+ node('Mark', 'Badge / tag');
844
+ node('Roster', 'List (ul)');
845
+ node('Entry', 'List item (li)');
846
+ node('Splitter', 'Horizontal divider');
847
+ node('Snippet', 'Inline code');
848
+ node('Block', 'Code block');
849
+ br();
850
+
851
+ console.log(chalk.hex('#a8a3ff')(' Paint Smart Tokens:\n'));
852
+ const token = (t, d) => console.log(` ${chalk.hex('#6c63ff')(t.padEnd(24))} ${chalk.gray(d)}`);
853
+ token('breathe: 4', 'padding (4 × 4px = 16px)');
854
+ token('push: 4', 'margin');
855
+ token('gap: 4', 'gap between children');
856
+ token('glow: soft', 'box-shadow (whisper/soft/loud/neon)');
857
+ token('curve: smooth', 'border-radius (gentle/smooth/round/pill)');
858
+ token('ink: #color', 'text color');
859
+ token('fill: #color', 'background color');
860
+ token('size: titan', 'font-size (tiny/small/base/large/xlarge/huge/titan/mega)');
861
+ token('weight: heavy', 'font-weight (feather/light/normal/firm/strong/heavy)');
862
+ token('pointer: true', 'cursor: pointer');
863
+ token('wide: 200', 'width in px');
864
+ token('tall: 100', 'height in px');
865
+ br();
866
+
867
+ console.log(chalk.hex('#6c63ff').bold(' ══════════════════════════════════════════════\n'));
421
868
  });
422
869
 
423
- // ─── Command: format ──────────────────────────────────────────────────────────
870
+ // ─── Command: polish (was: format) ───────────────────────────────────────────
424
871
 
425
872
  program
426
- .command('format')
427
- .description('Auto-fix indentation and style in .lux files')
873
+ .command('polish')
874
+ .description('Auto-format all .lux files')
428
875
  .action(() => {
429
876
  banner();
430
877
  const rootDir = process.cwd();
431
- const files = getAllLuxFiles(rootDir);
432
- let fixed = 0;
878
+ const files = allLuxFiles(rootDir);
879
+ let fixed = 0;
433
880
 
434
- files.forEach(filePath => {
881
+ files.forEach(fp => {
435
882
  try {
436
- const source = fs.readFileSync(filePath, 'utf8');
437
- const formatted = formatLux(source);
438
- if (formatted !== source) {
439
- fs.writeFileSync(filePath, formatted);
440
- log(`Formatted: ${path.relative(rootDir, filePath)}`);
441
- fixed++;
442
- }
443
- } catch (e) {
444
- warn(`Could not format ${filePath}: ${e.message}`);
445
- }
883
+ const src = fs.readFileSync(fp, 'utf8');
884
+ const fmtd = formatLux(src);
885
+ if (fmtd !== src) { fs.writeFileSync(fp, fmtd); log(`Polished: ${path.relative(rootDir, fp)}`); fixed++; }
886
+ } catch (e) { warn(`Could not polish ${fp}: ${e.message}`); }
446
887
  });
447
888
 
448
- success(`Formatted ${fixed} file(s).`);
889
+ success(`Polished ${fixed} file(s) out of ${files.length} scanned.`);
449
890
  });
450
891
 
451
892
  function formatLux(source) {
452
- const BLOCK_NAMES = ['props', 'state', 'server', 'style', 'view'];
453
- const lines = source.split('\n');
454
- const out = [];
455
- let inBlock = false;
893
+ const BLOCKS = ['receive', 'signal', 'vault', 'paint', 'canvas'];
894
+ const lines = source.split('\n');
895
+ const out = [];
896
+ let inBlock = false;
456
897
 
457
898
  for (let i = 0; i < lines.length; i++) {
458
- const raw = lines[i];
899
+ const raw = lines[i];
459
900
  const trimmed = raw.trim();
460
901
 
461
902
  if (!trimmed) { out.push(''); continue; }
462
903
  if (trimmed.startsWith('#')) { out.push(trimmed); continue; }
463
904
 
464
- if (BLOCK_NAMES.includes(trimmed)) {
905
+ if (BLOCKS.includes(trimmed)) {
465
906
  if (i > 0) out.push('');
466
907
  out.push(trimmed);
467
908
  inBlock = true;
@@ -469,9 +910,7 @@ function formatLux(source) {
469
910
  }
470
911
 
471
912
  if (inBlock) {
472
- // Normalize to 4-space indent per level
473
- const indent = raw.match(/^(\s*)/)[1];
474
- const spaces = indent.replace(/\t/g, ' ');
913
+ const spaces = raw.match(/^(\s*)/)[1].replace(/\t/g, ' ');
475
914
  const level = Math.round(spaces.length / 4);
476
915
  out.push(' '.repeat(Math.max(1, level)) + trimmed);
477
916
  } else {
@@ -482,151 +921,131 @@ function formatLux(source) {
482
921
  return out.join('\n').trimEnd() + '\n';
483
922
  }
484
923
 
485
- // ─── Command: secure ──────────────────────────────────────────────────────────
924
+ // ─── Command: shield (was: secure) ───────────────────────────────────────────
486
925
 
487
926
  program
488
- .command('secure')
927
+ .command('shield')
489
928
  .description('Scan codebase for security vulnerabilities')
490
929
  .action(() => {
491
930
  banner();
492
931
  const rootDir = process.cwd();
493
- const files = getAllLuxFiles(rootDir);
494
- let issues = 0;
495
-
496
- const XSS_PATTERNS = [
497
- { re: /innerHTML\s*=/g, msg: 'Unsafe innerHTML assignment (XSS risk)' },
498
- { re: /document\.write\s*\(/g, msg: 'document.write() usage (XSS risk)' },
499
- { re: /eval\s*\(/g, msg: 'eval() usage (code injection risk)' },
500
- ];
501
-
502
- const SQL_PATTERNS = [
503
- { re: /db\.query\s*\(\s*`[^`]*\$\{/g, msg: 'Possible SQL injection: template literal in query. Use parameterized queries.' },
504
- { re: /db\.query\s*\(\s*"[^"]*"\s*\+/g, msg: 'Possible SQL injection: string concatenation in query. Use parameterized queries.' },
932
+ const files = allLuxFiles(rootDir);
933
+ let issues = 0;
934
+
935
+ const CHECKS = [
936
+ { re: /innerHTML\s*=/g, msg: 'Unsafe innerHTML (XSS risk)' },
937
+ { re: /document\.write\s*\(/g, msg: 'document.write() (XSS risk)' },
938
+ { re: /eval\s*\(/g, msg: 'eval() usage (injection risk)' },
939
+ { re: /db\.query\s*\(`[^`]*\$\{/g, msg: 'SQL injection: template literal in query — use parameterized queries' },
940
+ { re: /db\.query\s*\("[^"]*"\s*\+/g, msg: 'SQL injection: string concat in query — use parameterized queries' },
941
+ { re: /process\.env\.(DB_PASSWORD|SECRET|API_KEY|TOKEN)/gi, msg: 'Sensitive env var — confirm it stays in vault block' },
505
942
  ];
506
943
 
507
- const SERVER_LEAK = [
508
- { re: /process\.env\.(DB_PASSWORD|SECRET|API_KEY)/gi, msg: 'Sensitive env var referenced — verify it is in server block only' },
509
- ];
510
-
511
- files.forEach(filePath => {
512
- const source = fs.readFileSync(filePath, 'utf8');
513
- const relPath = path.relative(rootDir, filePath);
514
-
515
- const allPatterns = [...XSS_PATTERNS, ...SQL_PATTERNS, ...SERVER_LEAK];
516
- allPatterns.forEach(({ re, msg }) => {
517
- const matches = source.match(re);
518
- if (matches) {
519
- warn(`${relPath}: ${msg}`);
520
- issues++;
521
- }
944
+ files.forEach(fp => {
945
+ const src = fs.readFileSync(fp, 'utf8');
946
+ const relPath = path.relative(rootDir, fp);
947
+ CHECKS.forEach(({ re, msg }) => {
948
+ if (re.test(src)) { warn(`${relPath}: ${msg}`); issues++; }
522
949
  });
523
950
 
524
- // Check for server code outside server block
525
- const serverBlockMatch = source.match(/^server$([\s\S]*?)(?=^(?:props|state|style|view)$|\Z)/m);
526
- if (!serverBlockMatch) {
527
- const dbOutsideServer = /db\.(query|insert|update)/.test(source);
528
- if (dbOutsideServer) {
529
- error(`${relPath}: db access detected outside server block — this may leak to client`);
530
- issues++;
531
- }
951
+ // Detect vault/db code outside vault block
952
+ if (!src.match(/^vault$/m) && /db\.(query|insert|update)/.test(src)) {
953
+ error(`${relPath}: db access outside vault block — may leak to browser!`);
954
+ issues++;
532
955
  }
533
956
  });
534
957
 
535
- if (issues === 0) {
536
- success(`No issues found. ${files.length} files scanned.`);
537
- } else {
538
- warn(`Found ${issues} potential issue(s) across ${files.length} files.`);
539
- }
958
+ if (issues === 0) success(`Shield clear. ${files.length} file(s) scanned, 0 issues.`);
959
+ else warn(`Found ${issues} issue(s) in ${files.length} file(s). Review and fix before deploying.`);
540
960
  });
541
961
 
542
- // ─── Build Helpers ─────────────────────────────────────────────────────────────
962
+ // ─── Build helpers ────────────────────────────────────────────────────────────
543
963
 
544
- async function buildAll(rootDir, opts = {}) {
545
- const files = getAllLuxFiles(rootDir);
546
- const errors = [];
547
- let count = 0;
964
+ async function buildAll(rootDir, opts) {
965
+ opts = opts || {};
966
+ const files = allLuxFiles(rootDir);
967
+ const errors = [];
968
+ let count = 0;
548
969
 
549
- // Copy UI kit
550
- const uiKitSrc = path.join(__dirname, '..', 'ui-kit');
551
- const clientDir = path.join(rootDir, 'dist', 'client');
552
- fs.mkdirpSync(clientDir);
553
- fs.mkdirpSync(path.join(rootDir, 'dist', 'server'));
970
+ // Copy UI kit to depot/public
971
+ const kitSrc = path.join(__dirname, '..', 'ui-kit');
972
+ const publicDir = path.join(rootDir, 'depot', 'public');
973
+ const vaultDir = path.join(rootDir, 'depot', 'vault');
974
+ fs.mkdirpSync(publicDir);
975
+ fs.mkdirpSync(vaultDir);
554
976
 
555
- if (fs.existsSync(path.join(uiKitSrc, 'luxaura.min.css'))) {
556
- fs.copySync(path.join(uiKitSrc, 'luxaura.min.css'), path.join(clientDir, 'luxaura.min.css'));
557
- fs.copySync(path.join(uiKitSrc, 'luxaura.min.js'), path.join(clientDir, 'luxaura.min.js'));
977
+ if (fs.existsSync(path.join(kitSrc, 'luxaura.min.css'))) {
978
+ fs.copySync(path.join(kitSrc, 'luxaura.min.css'), path.join(publicDir, 'luxaura.min.css'));
979
+ fs.copySync(path.join(kitSrc, 'luxaura.min.js'), path.join(publicDir, 'luxaura.min.js'));
558
980
  }
559
981
 
560
- // Copy static assets
561
- const assetsDir = path.join(rootDir, 'assets');
562
- if (fs.existsSync(assetsDir)) {
563
- fs.copySync(assetsDir, path.join(clientDir, 'assets'));
564
- }
982
+ // Copy media assets
983
+ const mediaDir = path.join(rootDir, 'media');
984
+ if (fs.existsSync(mediaDir)) fs.copySync(mediaDir, path.join(publicDir, 'media'));
565
985
 
566
986
  for (const f of files) {
567
- try {
568
- await buildFile(f, rootDir, opts);
569
- count++;
570
- } catch (e) {
571
- errors.push(`${path.relative(rootDir, f)}: ${e.message}`);
572
- }
987
+ try { await buildFile(f, rootDir, opts); count++; }
988
+ catch (e) { errors.push(`${path.relative(rootDir, f)}: ${e.message}`); }
573
989
  }
574
990
 
575
991
  return { count, errors };
576
992
  }
577
993
 
578
- async function buildFile(filePath, rootDir, opts = {}) {
579
- const source = fs.readFileSync(filePath, 'utf8');
994
+ async function buildFile(filePath, rootDir, opts) {
995
+ opts = opts || {};
996
+ const source = fs.readFileSync(filePath, 'utf8');
580
997
  const filename = path.basename(filePath);
581
- const parser = new LuxParser(source, filename);
582
- const ast = parser.parse();
583
- const compiler = new LuxCompiler(ast);
584
- const output = compiler.compile();
998
+ const ast = new LuxParser(source, filename).parse();
999
+ const output = new LuxCompiler(ast).compile();
585
1000
 
586
- const clientDir = path.join(rootDir, 'dist', 'client');
587
- const serverDir = path.join(rootDir, 'dist', 'server');
588
- const name = filename.replace('.lux', '');
1001
+ const publicDir = path.join(rootDir, 'depot', 'public');
1002
+ const vaultDir = path.join(rootDir, 'depot', 'vault');
1003
+ const name = filename.replace('.lux', '');
589
1004
 
590
- fs.mkdirpSync(clientDir);
591
- fs.mkdirpSync(serverDir);
1005
+ fs.mkdirpSync(publicDir);
1006
+ fs.mkdirpSync(vaultDir);
592
1007
 
593
- // Client JS
594
- fs.writeFileSync(path.join(clientDir, `${name}.component.js`), output.clientJS);
1008
+ // Client component JS
1009
+ fs.writeFileSync(path.join(publicDir, `${name}.component.js`), output.clientJS);
595
1010
 
596
- // Server JS (vault)
597
- if (output.serverJS && output.serverJS !== `/* Luxaura Vault — ${name} | no server block */\nmodule.exports = {};`) {
598
- fs.writeFileSync(path.join(serverDir, `${name}.js`), output.serverJS);
1011
+ // Vault JS (server-only — never served publicly)
1012
+ const emptyVault = `/* Luxaura Vault — ${name} | no vault block */\nmodule.exports = {};`;
1013
+ if (output.vaultJS && output.vaultJS !== emptyVault) {
1014
+ fs.writeFileSync(path.join(vaultDir, `${name}.js`), output.vaultJS);
599
1015
  }
600
1016
 
601
- // Component CSS
602
- if (output.css) {
603
- const cssPath = path.join(clientDir, 'styles.css');
604
- const existing = fs.existsSync(cssPath) ? fs.readFileSync(cssPath, 'utf8') : '';
605
- if (!existing.includes(`/* Luxaura — ${name} Styles */`)) {
606
- fs.appendFileSync(cssPath, '\n' + output.css);
607
- }
1017
+ // Paint CSS — append per-component styles to global paint.css
1018
+ if (output.paintCSS) {
1019
+ const cssPath = path.join(publicDir, 'paint.css');
1020
+ const exists = fs.existsSync(cssPath) ? fs.readFileSync(cssPath, 'utf8') : '';
1021
+ const marker = `/* Luxaura Paint — ${name} */`;
1022
+ if (!exists.includes(marker)) fs.appendFileSync(cssPath, '\n' + output.paintCSS);
608
1023
  }
609
1024
 
610
- // HTML shell (only for pages)
611
- if (filePath.includes('/pages/')) {
612
- const htmlPath = path.join(clientDir, name === 'index' ? 'index.html' : `${name}.html`);
1025
+ // HTML shell only for scenes (route entry points)
1026
+ const isScene = filePath.includes(`${path.sep}scenes${path.sep}`) ||
1027
+ filePath.replace(/\\/g, '/').includes('/scenes/');
1028
+
1029
+ if (isScene) {
1030
+ const htmlName = name === 'index' ? 'index.html' : `${name}.html`;
1031
+ const htmlPath = path.join(publicDir, htmlName);
613
1032
  fs.writeFileSync(htmlPath, output.html);
614
1033
 
615
- // Bundle app.js
616
- const appJsPath = path.join(clientDir, 'app.js');
617
- const componentFiles = fs.readdirSync(clientDir)
1034
+ // Bundle app.js from all component JS files
1035
+ const appJsPath = path.join(publicDir, 'app.js');
1036
+ const bundle = fs.readdirSync(publicDir)
618
1037
  .filter(f => f.endsWith('.component.js'))
619
- .map(f => `// Component: ${f}\n` + fs.readFileSync(path.join(clientDir, f), 'utf8'))
1038
+ .map(f => `/* ── ${f} ── */\n` + fs.readFileSync(path.join(publicDir, f), 'utf8'))
620
1039
  .join('\n\n');
621
- fs.writeFileSync(appJsPath, componentFiles);
1040
+ fs.writeFileSync(appJsPath, bundle);
622
1041
  }
623
1042
  }
624
1043
 
625
- // ─── Run ─────────────────────────────────────────────────────────────────────
1044
+ // ─── Boot ─────────────────────────────────────────────────────────────────────
626
1045
 
627
1046
  program
628
1047
  .name('luxaura')
629
- .version(VERSION)
630
- .description('Luxaura Framework CLI');
1048
+ .version(VERSION, '-v, --version', 'Show Luxaura version')
1049
+ .description('Luxaura — The Intent-Driven Full-Stack Web Framework');
631
1050
 
632
1051
  program.parse(process.argv);