aiplang 2.3.0 → 2.4.1

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
@@ -8,7 +8,7 @@ cd my-app
8
8
  npx aiplang serve
9
9
  ```
10
10
 
11
- Ask Claude to generate a page → paste into `pages/home.flux` → see it live.
11
+ Ask Claude to generate a page → paste into `pages/home.aiplang` → see it live.
12
12
 
13
13
  ---
14
14
 
@@ -16,9 +16,9 @@ Ask Claude to generate a page → paste into `pages/home.flux` → see it live.
16
16
 
17
17
  **aiplang** is a web language designed to be generated by AI (Claude), not written by humans.
18
18
 
19
- A single `.flux` file describes a complete app: frontend, backend, database, auth, email, jobs.
19
+ A single `.aiplang` file describes a complete app: frontend, backend, database, auth, email, jobs.
20
20
 
21
- ```flux
21
+ ```aiplang
22
22
  ~db sqlite ./app.db
23
23
  ~auth jwt $JWT_SECRET expire=7d
24
24
  ~admin /admin
@@ -83,7 +83,7 @@ npx aiplang init --template landing # landing page template
83
83
  npx aiplang init --template crud # CRUD app template
84
84
  npx aiplang serve # dev server + hot reload → localhost:3000
85
85
  npx aiplang build pages/ --out dist/ # compile → static HTML
86
- npx aiplang start app.flux # full-stack server (Node.js)
86
+ npx aiplang start app.aiplang # full-stack server (Node.js)
87
87
  npx aiplang new dashboard # create new page template
88
88
  ```
89
89
 
@@ -111,13 +111,13 @@ All blocks accept: animate:fade-up class:my-class | raw{<html>} | foot{text>/pat
111
111
 
112
112
  ```
113
113
  aiplang/
114
- ├── packages/flux-lang/ ← npm package (aiplang CLI + runtime)
114
+ ├── packages/aiplang-pkg/ ← npm package (aiplang CLI + runtime)
115
115
  │ ├── bin/aiplang.js ← CLI: init, serve, build, new, start
116
116
  │ ├── runtime/aiplang-hydrate.js← 10KB reactive runtime
117
117
  │ ├── server/server.js ← full-stack Node.js server
118
118
  │ └── aiplang-knowledge.md ← Claude Project knowledge file
119
119
  ├── aiplang-go/ ← Go compiler + server (v2)
120
- │ ├── compiler/compiler.go ← .flux → AST parser
120
+ │ ├── compiler/compiler.go ← .aiplang → AST parser
121
121
  │ ├── server/server.go ← Go HTTP server
122
122
  │ └── cmd/aiplangd/main.go ← binary entrypoint
123
123
  ├── docs/ ← GitHub Pages (aiplang.io)
@@ -330,9 +330,9 @@ aiplang serve # dev → localhost:3000
330
330
  aiplang build pages/ # compile → dist/
331
331
 
332
332
  # Full-stack (Node.js backend)
333
- aiplang start app.flux
333
+ aiplang start app.aiplang
334
334
 
335
335
  # Go binary (production, v2)
336
- aiplangd dev app.flux
337
- aiplangd build app.flux
336
+ aiplangd dev app.aiplang
337
+ aiplangd build app.aiplang
338
338
  ```
package/bin/aiplang.js CHANGED
@@ -5,7 +5,7 @@ const fs = require('fs')
5
5
  const path = require('path')
6
6
  const http = require('http')
7
7
 
8
- const VERSION = '2.3.0'
8
+ const VERSION = '2.4.1'
9
9
  const RUNTIME_DIR = path.join(__dirname, '..', 'runtime')
10
10
  const cmd = process.argv[2]
11
11
  const args = process.argv.slice(3)
@@ -36,7 +36,7 @@ if (!cmd||cmd==='--help'||cmd==='-h') {
36
36
  Usage:
37
37
  npx aiplang init [name] create project (default template)
38
38
  npx aiplang init [name] --template <t> use template: saas|landing|crud|dashboard|portfolio|blog
39
- npx aiplang init [name] --template ./my.flux use a local .flux file as template
39
+ npx aiplang init [name] --template ./my.aiplang use a local .aiplang file as template
40
40
  npx aiplang init [name] --template my-custom use a saved custom template
41
41
  npx aiplang serve [dir] dev server + hot reload
42
42
  npx aiplang build [dir/file] compile → static HTML
@@ -44,16 +44,16 @@ if (!cmd||cmd==='--help'||cmd==='-h') {
44
44
  npx aiplang --version
45
45
 
46
46
  Full-stack:
47
- npx aiplang start app.flux start full-stack server (API + DB + frontend)
48
- PORT=8080 aiplang start app.flux custom port
47
+ npx aiplang start app.aiplang start full-stack server (API + DB + frontend)
48
+ PORT=8080 aiplang start app.aiplang custom port
49
49
 
50
50
  Templates:
51
51
  npx aiplang template list list all templates (built-in + custom)
52
52
  npx aiplang template save <n> save current project as template
53
- npx aiplang template save <n> --from <f> save a specific .flux file as template
53
+ npx aiplang template save <n> --from <f> save a specific .aiplang file as template
54
54
  npx aiplang template edit <n> open template in editor
55
55
  npx aiplang template show <n> print template source
56
- npx aiplang template export <n> export template to .flux file
56
+ npx aiplang template export <n> export template to .aiplang file
57
57
  npx aiplang template remove <n> delete a custom template
58
58
 
59
59
  Custom template variables:
@@ -76,7 +76,7 @@ if (cmd==='--version'||cmd==='-v') { console.log(`aiplang v${VERSION}`); process
76
76
 
77
77
  // ─────────────────────────────────────────────────────────────────
78
78
  // TEMPLATE SYSTEM
79
- // Custom templates stored at ~/.aiplang/templates/<name>.flux
79
+ // Custom templates stored at ~/.aiplang/templates/<name>.aiplang
80
80
  // ─────────────────────────────────────────────────────────────────
81
81
 
82
82
  const TEMPLATES_DIR = path.join(require('os').homedir(), '.aiplang', 'templates')
@@ -220,7 +220,7 @@ foot{{{name}}}`,
220
220
  default: `# {{name}}
221
221
  %home dark /
222
222
  nav{{{name}}>/login:Sign in}
223
- hero{Welcome to {{name}}|Edit pages/home.flux to get started.>/signup:Get started} animate:fade-up
223
+ hero{Welcome to {{name}}|Edit pages/home.aiplang to get started.>/signup:Get started} animate:fade-up
224
224
  row3{rocket>Fast>Renders in under 1ms.|bolt>AI-native>Written by Claude in seconds.|globe>Deploy anywhere>Static files. Any host.}
225
225
  foot{© {{year}} {{name}}}`,
226
226
  }
@@ -232,15 +232,15 @@ function applyTemplateVars(src, name, year) {
232
232
  function getTemplate(tplName, name, year) {
233
233
  ensureTemplatesDir()
234
234
 
235
- // 1. Local file path: --template ./my-template.flux or --template /abs/path.flux
235
+ // 1. Local file path: --template ./my-template.aiplang or --template /abs/path.aiplang
236
236
  if (tplName.startsWith('./') || tplName.startsWith('../') || tplName.startsWith('/')) {
237
237
  const full = path.resolve(tplName)
238
238
  if (!fs.existsSync(full)) { console.error(`\n ✗ Template file not found: ${full}\n`); process.exit(1) }
239
239
  return applyTemplateVars(fs.readFileSync(full, 'utf8'), name, year)
240
240
  }
241
241
 
242
- // 2. User custom template: ~/.aiplang/templates/<name>.flux
243
- const customPath = path.join(TEMPLATES_DIR, tplName + '.flux')
242
+ // 2. User custom template: ~/.aiplang/templates/<name>.aiplang
243
+ const customPath = path.join(TEMPLATES_DIR, tplName + '.aiplang')
244
244
  if (fs.existsSync(customPath)) {
245
245
  return applyTemplateVars(fs.readFileSync(customPath, 'utf8'), name, year)
246
246
  }
@@ -251,7 +251,7 @@ function getTemplate(tplName, name, year) {
251
251
 
252
252
  // Not found — show what's available
253
253
  const customs = fs.existsSync(TEMPLATES_DIR)
254
- ? fs.readdirSync(TEMPLATES_DIR).filter(f=>f.endsWith('.flux')).map(f=>f.replace('.flux',''))
254
+ ? fs.readdirSync(TEMPLATES_DIR).filter(f=>f.endsWith('.aiplang')).map(f=>f.replace('.aiplang',''))
255
255
  : []
256
256
  const all = [...Object.keys(BUILTIN_TEMPLATES).filter(k=>k!=='default'), ...customs]
257
257
  console.error(`\n ✗ Template "${tplName}" not found.\n Available: ${all.join(', ')}\n`)
@@ -261,7 +261,7 @@ function getTemplate(tplName, name, year) {
261
261
  function listTemplates() {
262
262
  ensureTemplatesDir()
263
263
  const builtins = Object.keys(BUILTIN_TEMPLATES).filter(k=>k!=='default')
264
- const customs = fs.readdirSync(TEMPLATES_DIR).filter(f=>f.endsWith('.flux')).map(f=>f.replace('.flux',''))
264
+ const customs = fs.readdirSync(TEMPLATES_DIR).filter(f=>f.endsWith('.aiplang')).map(f=>f.replace('.aiplang',''))
265
265
  console.log(`\n aiplang templates\n`)
266
266
  console.log(` Built-in:`)
267
267
  builtins.forEach(t => console.log(` ${t}`))
@@ -295,18 +295,18 @@ if (cmd === 'template') {
295
295
  if (!fs.existsSync(fp)) { console.error(`\n ✗ File not found: ${fp}\n`); process.exit(1) }
296
296
  src = fs.readFileSync(fp, 'utf8')
297
297
  } else {
298
- // Auto-detect: use pages/ directory or app.flux
299
- const sources = ['pages', 'app.flux', 'index.flux']
298
+ // Auto-detect: use pages/ directory or app.aiplang
299
+ const sources = ['pages', 'app.aiplang', 'index.aiplang']
300
300
  const found = sources.find(s => fs.existsSync(s))
301
- if (!found) { console.error('\n ✗ No .flux files found. Use --from <file> to specify source.\n'); process.exit(1) }
301
+ if (!found) { console.error('\n ✗ No .aiplang files found. Use --from <file> to specify source.\n'); process.exit(1) }
302
302
  if (fs.statSync(found).isDirectory()) {
303
- src = fs.readdirSync(found).filter(f=>f.endsWith('.flux'))
303
+ src = fs.readdirSync(found).filter(f=>f.endsWith('.aiplang'))
304
304
  .map(f => fs.readFileSync(path.join(found,f),'utf8')).join('\n---\n')
305
305
  } else {
306
306
  src = fs.readFileSync(found, 'utf8')
307
307
  }
308
308
  }
309
- const dest = path.join(TEMPLATES_DIR, tname + '.flux')
309
+ const dest = path.join(TEMPLATES_DIR, tname + '.aiplang')
310
310
  fs.writeFileSync(dest, src)
311
311
  console.log(`\n ✓ Template saved: ${tname}\n ${dest}\n\n Use it: aiplang init my-app --template ${tname}\n`)
312
312
  process.exit(0)
@@ -316,7 +316,7 @@ if (cmd === 'template') {
316
316
  if (sub === 'remove' || sub === 'rm' || sub === 'delete') {
317
317
  const tname = args[1]
318
318
  if (!tname) { console.error('\n ✗ Usage: aiplang template remove <name>\n'); process.exit(1) }
319
- const dest = path.join(TEMPLATES_DIR, tname + '.flux')
319
+ const dest = path.join(TEMPLATES_DIR, tname + '.aiplang')
320
320
  if (!fs.existsSync(dest)) { console.error(`\n ✗ Template "${tname}" not found.\n`); process.exit(1) }
321
321
  fs.unlinkSync(dest)
322
322
  console.log(`\n ✓ Removed template: ${tname}\n`); process.exit(0)
@@ -326,7 +326,7 @@ if (cmd === 'template') {
326
326
  if (sub === 'edit' || sub === 'open') {
327
327
  const tname = args[1]
328
328
  if (!tname) { console.error('\n ✗ Usage: aiplang template edit <name>\n'); process.exit(1) }
329
- let dest = path.join(TEMPLATES_DIR, tname + '.flux')
329
+ let dest = path.join(TEMPLATES_DIR, tname + '.aiplang')
330
330
  if (!fs.existsSync(dest)) {
331
331
  // create from built-in if exists
332
332
  const builtin = BUILTIN_TEMPLATES[tname]
@@ -342,7 +342,7 @@ if (cmd === 'template') {
342
342
  // aiplang template show <name>
343
343
  if (sub === 'show' || sub === 'cat') {
344
344
  const tname = args[1] || 'default'
345
- const customPath = path.join(TEMPLATES_DIR, tname + '.flux')
345
+ const customPath = path.join(TEMPLATES_DIR, tname + '.aiplang')
346
346
  if (fs.existsSync(customPath)) { console.log(fs.readFileSync(customPath,'utf8')); process.exit(0) }
347
347
  const builtin = BUILTIN_TEMPLATES[tname]
348
348
  if (builtin) { console.log(builtin); process.exit(0) }
@@ -354,8 +354,8 @@ if (cmd === 'template') {
354
354
  const tname = args[1]
355
355
  if (!tname) { console.error('\n ✗ Usage: aiplang template export <name>\n'); process.exit(1) }
356
356
  const outIdx = args.indexOf('--out')
357
- const outFile = outIdx !== -1 ? args[outIdx+1] : `./${tname}.flux`
358
- const customPath = path.join(TEMPLATES_DIR, tname + '.flux')
357
+ const outFile = outIdx !== -1 ? args[outIdx+1] : `./${tname}.aiplang`
358
+ const customPath = path.join(TEMPLATES_DIR, tname + '.aiplang')
359
359
  const src = fs.existsSync(customPath) ? fs.readFileSync(customPath,'utf8') : BUILTIN_TEMPLATES[tname]
360
360
  if (!src) { console.error(`\n ✗ Template "${tname}" not found.\n`); process.exit(1) }
361
361
  fs.writeFileSync(outFile, src)
@@ -384,21 +384,21 @@ if (cmd==='init') {
384
384
  const isMultiFile = tplSrc.includes('\n---\n')
385
385
 
386
386
  if (isFullStack) {
387
- // Full-stack project: single app.flux
387
+ // Full-stack project: single app.aiplang
388
388
  fs.mkdirSync(dir, { recursive: true })
389
- fs.writeFileSync(path.join(dir, 'app.flux'), tplSrc)
389
+ fs.writeFileSync(path.join(dir, 'app.aiplang'), tplSrc)
390
390
  fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({
391
391
  name, version:'0.1.0',
392
- scripts: { dev: 'npx aiplang start app.flux', start: 'npx aiplang start app.flux' },
392
+ scripts: { dev: 'npx aiplang start app.aiplang', start: 'npx aiplang start app.aiplang' },
393
393
  devDependencies: { 'aiplang': `^${VERSION}` }
394
394
  }, null, 2))
395
395
  fs.writeFileSync(path.join(dir, '.env.example'), 'JWT_SECRET=change-me-in-production\n# STRIPE_SECRET_KEY=sk_test_...\n# AWS_ACCESS_KEY_ID=...\n# AWS_SECRET_ACCESS_KEY=...\n# S3_BUCKET=...\n')
396
396
  fs.writeFileSync(path.join(dir, '.gitignore'), '*.db\nnode_modules/\ndist/\n.env\nuploads/\n')
397
- fs.writeFileSync(path.join(dir, 'README.md'), `# ${name}\n\nGenerated with [aiplang](https://npmjs.com/package/aiplang) v${VERSION}\n\n## Run\n\n\`\`\`bash\nnpx aiplang start app.flux\n\`\`\`\n`)
397
+ fs.writeFileSync(path.join(dir, 'README.md'), `# ${name}\n\nGenerated with [aiplang](https://npmjs.com/package/aiplang) v${VERSION}\n\n## Run\n\n\`\`\`bash\nnpx aiplang start app.aiplang\n\`\`\`\n`)
398
398
  const label = tplName !== 'default' ? ` (template: ${tplName})` : ''
399
- console.log(`\n ✓ Created ${name}/${label}\n\n app.flux ← full-stack app (backend + frontend)\n\n Next:\n cd ${name} && npx aiplang start app.flux\n`)
399
+ console.log(`\n ✓ Created ${name}/${label}\n\n app.aiplang ← full-stack app (backend + frontend)\n\n Next:\n cd ${name} && npx aiplang start app.aiplang\n`)
400
400
  } else if (isMultiFile) {
401
- // Multi-page SSG project: pages/*.flux
401
+ // Multi-page SSG project: pages/*.aiplang
402
402
  fs.mkdirSync(path.join(dir,'pages'), {recursive:true})
403
403
  fs.mkdirSync(path.join(dir,'public'), {recursive:true})
404
404
  for (const f of ['aiplang-runtime.js','aiplang-hydrate.js']) {
@@ -408,7 +408,7 @@ if (cmd==='init') {
408
408
  pageBlocks.forEach((block, i) => {
409
409
  const m = block.match(/^%([a-zA-Z0-9_-]+)/m)
410
410
  const pageName = m ? m[1] : (i === 0 ? 'home' : `page${i}`)
411
- fs.writeFileSync(path.join(dir,'pages',`${pageName}.flux`), block.trim())
411
+ fs.writeFileSync(path.join(dir,'pages',`${pageName}.aiplang`), block.trim())
412
412
  })
413
413
  fs.writeFileSync(path.join(dir,'package.json'), JSON.stringify({name,version:'0.1.0',scripts:{dev:'npx aiplang serve',build:'npx aiplang build pages/ --out dist/'},devDependencies:{'aiplang':`^${VERSION}`}},null,2))
414
414
  fs.writeFileSync(path.join(dir,'.gitignore'),'dist/\nnode_modules/\n')
@@ -422,11 +422,11 @@ if (cmd==='init') {
422
422
  for (const f of ['aiplang-runtime.js','aiplang-hydrate.js']) {
423
423
  const src=path.join(RUNTIME_DIR,f); if(fs.existsSync(src)) fs.copyFileSync(src,path.join(dir,'public',f))
424
424
  }
425
- fs.writeFileSync(path.join(dir,'pages','home.flux'), tplSrc)
425
+ fs.writeFileSync(path.join(dir,'pages','home.aiplang'), tplSrc)
426
426
  fs.writeFileSync(path.join(dir,'package.json'), JSON.stringify({name,version:'0.1.0',scripts:{dev:'npx aiplang serve',build:'npx aiplang build pages/ --out dist/'},devDependencies:{'aiplang':`^${VERSION}`}},null,2))
427
427
  fs.writeFileSync(path.join(dir,'.gitignore'),'dist/\nnode_modules/\n')
428
428
  const label = tplName !== 'default' ? ` (template: ${tplName})` : ''
429
- console.log(`\n ✓ Created ${name}/${label}\n\n pages/home.flux ← edit this\n\n Next:\n cd ${name} && npx aiplang serve\n`)
429
+ console.log(`\n ✓ Created ${name}/${label}\n\n pages/home.aiplang ← edit this\n\n Next:\n cd ${name} && npx aiplang serve\n`)
430
430
  }
431
431
  process.exit(0)
432
432
  }
@@ -435,7 +435,7 @@ if (cmd==='init') {
435
435
  if (cmd==='new') {
436
436
  const name=args[0]; if(!name){console.error('\n ✗ Usage: aiplang new <page>\n');process.exit(1)}
437
437
  const dir=fs.existsSync('pages')?'pages':'.'
438
- const file=path.join(dir,`${name}.flux`)
438
+ const file=path.join(dir,`${name}.aiplang`)
439
439
  if(fs.existsSync(file)){console.error(`\n ✗ ${file} exists.\n`);process.exit(1)}
440
440
  const cap=name.charAt(0).toUpperCase()+name.slice(1)
441
441
  fs.writeFileSync(file,`# ${name}\n%${name} dark /${name}\n\nnav{AppName>/home:Home}\nhero{${cap}|Description.>/action:Get started}\nfoot{© ${new Date().getFullYear()} AppName}\n`)
@@ -450,9 +450,9 @@ if (cmd==='build') {
450
450
  const input=args.filter((a,i)=>!a.startsWith('--')&&i!==outIdx+1)[0]||'pages/'
451
451
  const files=[]
452
452
  if(fs.existsSync(input)&&fs.statSync(input).isDirectory()){
453
- fs.readdirSync(input).filter(f=>f.endsWith('.flux')).forEach(f=>files.push(path.join(input,f)))
454
- } else if(input.endsWith('.flux')&&fs.existsSync(input)){ files.push(input) }
455
- if(!files.length){console.error(`\n ✗ No .flux files in: ${input}\n`);process.exit(1)}
453
+ fs.readdirSync(input).filter(f=>f.endsWith('.aiplang')).forEach(f=>files.push(path.join(input,f)))
454
+ } else if(input.endsWith('.aiplang')&&fs.existsSync(input)){ files.push(input) }
455
+ if(!files.length){console.error(`\n ✗ No .aiplang files in: ${input}\n`);process.exit(1)}
456
456
  const src=files.map(f=>fs.readFileSync(f,'utf8')).join('\n---\n')
457
457
  const pages=parseFlux(src)
458
458
  if(!pages.length){console.error('\n ✗ No pages found.\n');process.exit(1)}
@@ -471,7 +471,7 @@ if (cmd==='build') {
471
471
  }
472
472
  const hf=path.join(RUNTIME_DIR,'aiplang-hydrate.js')
473
473
  if(fs.existsSync(hf)){const dst=path.join(outDir,'aiplang-hydrate.js');fs.copyFileSync(hf,dst);total+=fs.statSync(dst).size;console.log(` ✓ ${dst.padEnd(40)} ${hSize(fs.statSync(dst).size)}`)}
474
- if(fs.existsSync('public'))fs.readdirSync('public').filter(f=>!f.endsWith('.flux')).forEach(f=>fs.copyFileSync(path.join('public',f),path.join(outDir,f)))
474
+ if(fs.existsSync('public'))fs.readdirSync('public').filter(f=>!f.endsWith('.aiplang')).forEach(f=>fs.copyFileSync(path.join('public',f),path.join(outDir,f)))
475
475
  console.log(`\n ${pages.length} page(s) — ${hSize(total)} total\n\n Preview: npx serve ${outDir}\n Deploy: Vercel, Netlify, S3, any static host\n`)
476
476
  process.exit(0)
477
477
  }
@@ -480,13 +480,13 @@ if (cmd==='build') {
480
480
  if (cmd==='serve'||cmd==='dev') {
481
481
  const root=path.resolve(args[0]||'.')
482
482
  const port=parseInt(process.env.PORT||'3000')
483
- const MIME={'.html':'text/html;charset=utf-8','.js':'application/javascript','.css':'text/css','.flux':'text/plain','.json':'application/json','.wasm':'application/wasm','.svg':'image/svg+xml','.png':'image/png','.jpg':'image/jpeg','.ico':'image/x-icon'}
483
+ const MIME={'.html':'text/html;charset=utf-8','.js':'application/javascript','.css':'text/css','.aiplang':'text/plain','.json':'application/json','.wasm':'application/wasm','.svg':'image/svg+xml','.png':'image/png','.jpg':'image/jpeg','.ico':'image/x-icon'}
484
484
  let clients=[]
485
485
  const mtimes={}
486
486
  setInterval(()=>{
487
487
  const pd=path.join(root,'pages')
488
488
  if(!fs.existsSync(pd))return
489
- fs.readdirSync(pd).filter(f=>f.endsWith('.flux')).forEach(f=>{
489
+ fs.readdirSync(pd).filter(f=>f.endsWith('.aiplang')).forEach(f=>{
490
490
  const fp=path.join(pd,f),mt=fs.statSync(fp).mtimeMs
491
491
  if(mtimes[fp]&&mtimes[fp]!==mt)clients.forEach(c=>{try{c.write('data: reload\n\n')}catch{}})
492
492
  mtimes[fp]=mt
@@ -501,7 +501,7 @@ if (cmd==='serve'||cmd==='dev') {
501
501
  let p=req.url.split('?')[0];if(p==='/') p='/index.html'
502
502
  let fp=null
503
503
  for(const c of [path.join(root,'public',p),path.join(root,p)]){if(fs.existsSync(c)&&fs.statSync(c).isFile()){fp=c;break}}
504
- if(!fp&&p.endsWith('.flux')){const c=path.join(root,'pages',path.basename(p));if(fs.existsSync(c))fp=c}
504
+ if(!fp&&p.endsWith('.aiplang')){const c=path.join(root,'pages',path.basename(p));if(fs.existsSync(c))fp=c}
505
505
  if(!fp){res.writeHead(404);res.end('Not found');return}
506
506
  let content=fs.readFileSync(fp)
507
507
  if(path.extname(fp)==='.html'){
@@ -510,7 +510,7 @@ if (cmd==='serve'||cmd==='dev') {
510
510
  }
511
511
  res.writeHead(200,{'Content-Type':MIME[path.extname(fp)]||'application/octet-stream','Access-Control-Allow-Origin':'*'})
512
512
  res.end(content)
513
- }).listen(port,()=>console.log(`\n ✓ aiplang dev server\n\n → http://localhost:${port}\n\n Hot reload ON — edit .flux files and browser refreshes.\n Ctrl+C to stop.\n`))
513
+ }).listen(port,()=>console.log(`\n ✓ aiplang dev server\n\n → http://localhost:${port}\n\n Hot reload ON — edit .aiplang files and browser refreshes.\n Ctrl+C to stop.\n`))
514
514
  return
515
515
  }
516
516
 
@@ -518,7 +518,7 @@ if (cmd==='serve'||cmd==='dev') {
518
518
  if (cmd === 'start' || cmd === 'run') {
519
519
  const aipFile = args[0]
520
520
  if (!aipFile || !fs.existsSync(aipFile)) {
521
- console.error(`\n ✗ Usage: aiplang start <app.flux>\n`)
521
+ console.error(`\n ✗ Usage: aiplang start <app.aiplang>\n`)
522
522
  process.exit(1)
523
523
  }
524
524
  const port = parseInt(process.env.PORT || args[1] || '3000')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiplang",
3
- "version": "2.3.0",
3
+ "version": "2.4.1",
4
4
  "description": "AI-first full-stack language. Frontend + Backend + DB + Auth in one file. Competes with Laravel.",
5
5
  "keywords": [
6
6
  "aiplang",
@@ -456,7 +456,7 @@ class Renderer {
456
456
 
457
457
  render(page) {
458
458
  this.container.innerHTML = ''
459
- this.container.className = `flux-root flux-theme-${page.theme}`
459
+ this.container.className = `aiplang-root aiplang-theme-${page.theme}`
460
460
  for (const block of page.blocks) {
461
461
  const el = this.renderBlock(block)
462
462
  if (el) this.container.appendChild(el)
@@ -957,108 +957,108 @@ html{scroll-behavior:smooth}
957
957
  body{font-family:-apple-system,'Segoe UI',system-ui,sans-serif;-webkit-font-smoothing:antialiased}
958
958
  a{text-decoration:none;color:inherit}
959
959
  input,button,select{font-family:inherit}
960
- .flux-root{min-height:100vh}
961
- .flux-theme-dark{background:#030712;color:#f1f5f9}
962
- .flux-theme-light{background:#fff;color:#0f172a}
963
- .flux-theme-acid{background:#000;color:#a3e635}
960
+ .aiplang-root{min-height:100vh}
961
+ .aiplang-theme-dark{background:#030712;color:#f1f5f9}
962
+ .aiplang-theme-light{background:#fff;color:#0f172a}
963
+ .aiplang-theme-acid{background:#000;color:#a3e635}
964
964
  .fx-nav{display:flex;align-items:center;justify-content:space-between;padding:1rem 2.5rem;position:sticky;top:0;z-index:50;backdrop-filter:blur(12px)}
965
- .flux-theme-dark .fx-nav{border-bottom:1px solid #1e293b;background:rgba(3,7,18,.85)}
966
- .flux-theme-light .fx-nav{border-bottom:1px solid #e2e8f0;background:rgba(255,255,255,.85)}
967
- .flux-theme-acid .fx-nav{border-bottom:1px solid #1a2e05;background:rgba(0,0,0,.9)}
965
+ .aiplang-theme-dark .fx-nav{border-bottom:1px solid #1e293b;background:rgba(3,7,18,.85)}
966
+ .aiplang-theme-light .fx-nav{border-bottom:1px solid #e2e8f0;background:rgba(255,255,255,.85)}
967
+ .aiplang-theme-acid .fx-nav{border-bottom:1px solid #1a2e05;background:rgba(0,0,0,.9)}
968
968
  .fx-brand{font-size:1.25rem;font-weight:800;letter-spacing:-.03em}
969
969
  .fx-nav-links{display:flex;align-items:center;gap:1.75rem}
970
970
  .fx-nav-link{font-size:.875rem;font-weight:500;opacity:.65;transition:opacity .15s;cursor:pointer}
971
971
  .fx-nav-link:hover{opacity:1}
972
- .flux-theme-dark .fx-nav-link{color:#cbd5e1}
973
- .flux-theme-light .fx-nav-link{color:#475569}
974
- .flux-theme-acid .fx-nav-link{color:#86efac}
972
+ .aiplang-theme-dark .fx-nav-link{color:#cbd5e1}
973
+ .aiplang-theme-light .fx-nav-link{color:#475569}
974
+ .aiplang-theme-acid .fx-nav-link{color:#86efac}
975
975
  .fx-hero{display:flex;align-items:center;justify-content:center;min-height:92vh;padding:4rem 1.5rem}
976
976
  .fx-hero-inner{max-width:56rem;text-align:center;display:flex;flex-direction:column;align-items:center;gap:1.5rem}
977
977
  .fx-title{font-size:clamp(2.5rem,8vw,5.5rem);font-weight:900;letter-spacing:-.04em;line-height:1}
978
978
  .fx-sub{font-size:clamp(1rem,2vw,1.25rem);line-height:1.75;max-width:40rem}
979
- .flux-theme-dark .fx-sub{color:#94a3b8}
980
- .flux-theme-light .fx-sub{color:#475569}
979
+ .aiplang-theme-dark .fx-sub{color:#94a3b8}
980
+ .aiplang-theme-light .fx-sub{color:#475569}
981
981
  .fx-cta{display:inline-flex;align-items:center;padding:.875rem 2.5rem;border-radius:.75rem;font-weight:700;font-size:1rem;letter-spacing:-.01em;transition:transform .15s,box-shadow .15s;margin:.25rem;cursor:pointer}
982
982
  .fx-cta:hover{transform:translateY(-1px)}
983
- .flux-theme-dark .fx-cta{background:#2563eb;color:#fff;box-shadow:0 8px 24px rgba(37,99,235,.35)}
984
- .flux-theme-light .fx-cta{background:#2563eb;color:#fff}
985
- .flux-theme-acid .fx-cta{background:#a3e635;color:#000;font-weight:800}
983
+ .aiplang-theme-dark .fx-cta{background:#2563eb;color:#fff;box-shadow:0 8px 24px rgba(37,99,235,.35)}
984
+ .aiplang-theme-light .fx-cta{background:#2563eb;color:#fff}
985
+ .aiplang-theme-acid .fx-cta{background:#a3e635;color:#000;font-weight:800}
986
986
  .fx-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:3rem;padding:5rem 2.5rem;text-align:center}
987
987
  .fx-stat-val{font-size:clamp(2.5rem,5vw,4rem);font-weight:900;letter-spacing:-.04em;line-height:1}
988
988
  .fx-stat-lbl{font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.1em;margin-top:.5rem}
989
- .flux-theme-dark .fx-stat-lbl{color:#64748b}
990
- .flux-theme-light .fx-stat-lbl{color:#94a3b8}
989
+ .aiplang-theme-dark .fx-stat-lbl{color:#64748b}
990
+ .aiplang-theme-light .fx-stat-lbl{color:#94a3b8}
991
991
  .fx-grid{display:grid;gap:1.25rem;padding:1rem 2.5rem 5rem}
992
992
  .fx-grid-2{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
993
993
  .fx-grid-3{grid-template-columns:repeat(auto-fit,minmax(240px,1fr))}
994
994
  .fx-grid-4{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}
995
995
  .fx-card{border-radius:1rem;padding:1.75rem;transition:transform .2s,box-shadow .2s}
996
996
  .fx-card:hover{transform:translateY(-2px)}
997
- .flux-theme-dark .fx-card{background:#0f172a;border:1px solid #1e293b}
998
- .flux-theme-light .fx-card{background:#f8fafc;border:1px solid #e2e8f0}
999
- .flux-theme-acid .fx-card{background:#0a0f00;border:1px solid #1a2e05}
1000
- .flux-theme-dark .fx-card:hover{box-shadow:0 20px 40px rgba(0,0,0,.5)}
1001
- .flux-theme-light .fx-card:hover{box-shadow:0 20px 40px rgba(0,0,0,.08)}
997
+ .aiplang-theme-dark .fx-card{background:#0f172a;border:1px solid #1e293b}
998
+ .aiplang-theme-light .fx-card{background:#f8fafc;border:1px solid #e2e8f0}
999
+ .aiplang-theme-acid .fx-card{background:#0a0f00;border:1px solid #1a2e05}
1000
+ .aiplang-theme-dark .fx-card:hover{box-shadow:0 20px 40px rgba(0,0,0,.5)}
1001
+ .aiplang-theme-light .fx-card:hover{box-shadow:0 20px 40px rgba(0,0,0,.08)}
1002
1002
  .fx-icon{font-size:2rem;margin-bottom:1rem}
1003
1003
  .fx-card-title{font-size:1.0625rem;font-weight:700;letter-spacing:-.02em;margin-bottom:.5rem}
1004
1004
  .fx-card-body{font-size:.875rem;line-height:1.65}
1005
- .flux-theme-dark .fx-card-body{color:#64748b}
1006
- .flux-theme-light .fx-card-body{color:#475569}
1005
+ .aiplang-theme-dark .fx-card-body{color:#64748b}
1006
+ .aiplang-theme-light .fx-card-body{color:#475569}
1007
1007
  .fx-card-link{font-size:.8125rem;font-weight:600;display:inline-block;margin-top:1rem;opacity:.6;transition:opacity .15s}
1008
1008
  .fx-card-link:hover{opacity:1}
1009
1009
  .fx-sect{padding:5rem 2.5rem}
1010
1010
  .fx-sect-title{font-size:clamp(1.75rem,4vw,3rem);font-weight:800;letter-spacing:-.04em;margin-bottom:1.5rem;text-align:center}
1011
1011
  .fx-sect-body{font-size:1rem;line-height:1.75;text-align:center;max-width:48rem;margin:0 auto}
1012
- .flux-theme-dark .fx-sect-body{color:#64748b}
1012
+ .aiplang-theme-dark .fx-sect-body{color:#64748b}
1013
1013
  .fx-form-wrap{padding:3rem 2.5rem;display:flex;justify-content:center}
1014
1014
  .fx-form{width:100%;max-width:28rem;border-radius:1.25rem;padding:2.5rem}
1015
- .flux-theme-dark .fx-form{background:#0f172a;border:1px solid #1e293b}
1016
- .flux-theme-light .fx-form{background:#f8fafc;border:1px solid #e2e8f0}
1015
+ .aiplang-theme-dark .fx-form{background:#0f172a;border:1px solid #1e293b}
1016
+ .aiplang-theme-light .fx-form{background:#f8fafc;border:1px solid #e2e8f0}
1017
1017
  .fx-field{margin-bottom:1.25rem}
1018
1018
  .fx-label{display:block;font-size:.8125rem;font-weight:600;margin-bottom:.5rem}
1019
- .flux-theme-dark .fx-label{color:#94a3b8}
1020
- .flux-theme-light .fx-label{color:#475569}
1019
+ .aiplang-theme-dark .fx-label{color:#94a3b8}
1020
+ .aiplang-theme-light .fx-label{color:#475569}
1021
1021
  .fx-input{width:100%;padding:.75rem 1rem;border-radius:.625rem;font-size:.9375rem;outline:none;transition:box-shadow .15s;background:transparent}
1022
1022
  .fx-input:focus{box-shadow:0 0 0 3px rgba(37,99,235,.35)}
1023
- .flux-theme-dark .fx-input{background:#020617;border:1px solid #1e293b;color:#f1f5f9}
1024
- .flux-theme-dark .fx-input::placeholder{color:#334155}
1025
- .flux-theme-light .fx-input{background:#fff;border:1px solid #cbd5e1;color:#0f172a}
1026
- .flux-theme-acid .fx-input{background:#000;border:1px solid #1a2e05;color:#a3e635}
1023
+ .aiplang-theme-dark .fx-input{background:#020617;border:1px solid #1e293b;color:#f1f5f9}
1024
+ .aiplang-theme-dark .fx-input::placeholder{color:#334155}
1025
+ .aiplang-theme-light .fx-input{background:#fff;border:1px solid #cbd5e1;color:#0f172a}
1026
+ .aiplang-theme-acid .fx-input{background:#000;border:1px solid #1a2e05;color:#a3e635}
1027
1027
  .fx-btn{width:100%;padding:.875rem 1.5rem;border:none;border-radius:.625rem;font-size:.9375rem;font-weight:700;cursor:pointer;margin-top:.5rem;transition:transform .15s,opacity .15s;letter-spacing:-.01em}
1028
1028
  .fx-btn:hover{transform:translateY(-1px)}
1029
1029
  .fx-btn:disabled{opacity:.5;cursor:not-allowed;transform:none}
1030
- .flux-theme-dark .fx-btn{background:#2563eb;color:#fff;box-shadow:0 4px 14px rgba(37,99,235,.4)}
1031
- .flux-theme-light .fx-btn{background:#2563eb;color:#fff}
1032
- .flux-theme-acid .fx-btn{background:#a3e635;color:#000;font-weight:800}
1030
+ .aiplang-theme-dark .fx-btn{background:#2563eb;color:#fff;box-shadow:0 4px 14px rgba(37,99,235,.4)}
1031
+ .aiplang-theme-light .fx-btn{background:#2563eb;color:#fff}
1032
+ .aiplang-theme-acid .fx-btn{background:#a3e635;color:#000;font-weight:800}
1033
1033
  .fx-form-msg{font-size:.8125rem;padding:.5rem 0;min-height:1.5rem;text-align:center}
1034
1034
  .fx-form-err{color:#f87171}
1035
1035
  .fx-form-ok{color:#4ade80}
1036
1036
  .fx-table-wrap{overflow-x:auto;padding:0 2.5rem 4rem}
1037
1037
  .fx-table{width:100%;border-collapse:collapse;font-size:.875rem}
1038
1038
  .fx-th{text-align:left;padding:.875rem 1.25rem;font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em}
1039
- .flux-theme-dark .fx-th{color:#475569;border-bottom:1px solid #1e293b}
1040
- .flux-theme-light .fx-th{color:#94a3b8;border-bottom:1px solid #e2e8f0}
1039
+ .aiplang-theme-dark .fx-th{color:#475569;border-bottom:1px solid #1e293b}
1040
+ .aiplang-theme-light .fx-th{color:#94a3b8;border-bottom:1px solid #e2e8f0}
1041
1041
  .fx-tr{transition:background .1s}
1042
1042
  .fx-td{padding:.875rem 1.25rem}
1043
- .flux-theme-dark .fx-tr:hover{background:#0f172a}
1044
- .flux-theme-light .fx-tr:hover{background:#f8fafc}
1045
- .flux-theme-dark .fx-td{border-bottom:1px solid #0f172a}
1046
- .flux-theme-light .fx-td{border-bottom:1px solid #f1f5f9}
1043
+ .aiplang-theme-dark .fx-tr:hover{background:#0f172a}
1044
+ .aiplang-theme-light .fx-tr:hover{background:#f8fafc}
1045
+ .aiplang-theme-dark .fx-td{border-bottom:1px solid #0f172a}
1046
+ .aiplang-theme-light .fx-td{border-bottom:1px solid #f1f5f9}
1047
1047
  .fx-td-empty{padding:2rem 1.25rem;text-align:center;opacity:.4}
1048
1048
  .fx-list-wrap{padding:1rem 2.5rem 4rem;display:flex;flex-direction:column;gap:.75rem}
1049
1049
  .fx-list-item{border-radius:.75rem;padding:1.25rem 1.5rem}
1050
- .flux-theme-dark .fx-list-item{background:#0f172a;border:1px solid #1e293b}
1050
+ .aiplang-theme-dark .fx-list-item{background:#0f172a;border:1px solid #1e293b}
1051
1051
  .fx-list-field{font-size:.9375rem;line-height:1.5}
1052
1052
  .fx-list-link{font-size:.8125rem;font-weight:600;opacity:.6;transition:opacity .15s}
1053
1053
  .fx-list-link:hover{opacity:1}
1054
1054
  .fx-alert{padding:1rem 2.5rem;font-size:.9375rem;font-weight:500;border-radius:.75rem;margin:1rem 2.5rem}
1055
- .flux-theme-dark .fx-alert{background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.3);color:#fca5a5}
1055
+ .aiplang-theme-dark .fx-alert{background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.3);color:#fca5a5}
1056
1056
  .fx-if-wrap{display:contents}
1057
1057
  .fx-footer{padding:3rem 2.5rem;text-align:center}
1058
- .flux-theme-dark .fx-footer{border-top:1px solid #1e293b}
1059
- .flux-theme-light .fx-footer{border-top:1px solid #e2e8f0}
1058
+ .aiplang-theme-dark .fx-footer{border-top:1px solid #1e293b}
1059
+ .aiplang-theme-light .fx-footer{border-top:1px solid #e2e8f0}
1060
1060
  .fx-footer-text{font-size:.8125rem}
1061
- .flux-theme-dark .fx-footer-text{color:#334155}
1061
+ .aiplang-theme-dark .fx-footer-text{color:#334155}
1062
1062
  .fx-footer-link{font-size:.8125rem;margin:0 .75rem;opacity:.5;transition:opacity .15s}
1063
1063
  .fx-footer-link:hover{opacity:1}
1064
1064
  `
@@ -1069,9 +1069,9 @@ input,button,select{font-family:inherit}
1069
1069
 
1070
1070
  function boot(src, container) {
1071
1071
  // Inject CSS once
1072
- if (!document.getElementById('flux-css')) {
1072
+ if (!document.getElementById('aiplang-css')) {
1073
1073
  const style = document.createElement('style')
1074
- style.id = 'flux-css'
1074
+ style.id = 'aiplang-css'
1075
1075
  style.textContent = CSS
1076
1076
  document.head.appendChild(style)
1077
1077
  }
@@ -1089,9 +1089,9 @@ return { boot, parseFlux, State, Renderer, Router, QueryEngine }
1089
1089
 
1090
1090
  })()
1091
1091
 
1092
- // Auto-boot from <script type="text/flux">
1092
+ // Auto-boot from <script type="text/aiplang">
1093
1093
  document.addEventListener('DOMContentLoaded', () => {
1094
- const script = document.querySelector('script[type="text/flux"]')
1094
+ const script = document.querySelector('script[type="text/aiplang"]')
1095
1095
  if (script) {
1096
1096
  const targetSel = script.getAttribute('target') || '#app'
1097
1097
  const container = document.querySelector(targetSel)
package/server/server.js CHANGED
@@ -1137,7 +1137,7 @@ function getMime(filename) {
1137
1137
  // name: 'my-plugin',
1138
1138
  // setup(server, app, utils) {
1139
1139
  // // server = AiplangServer instance (.addRoute, .models)
1140
- // // app = parsed .flux app definition
1140
+ // // app = parsed .aiplang app definition
1141
1141
  // // utils = { uuid, now, emit, on, dispatch, resolveEnv, dbRun, dbAll, dbGet }
1142
1142
  // }
1143
1143
  // }
@@ -1364,7 +1364,7 @@ async function startServer(aipFile, port = 3000) {
1364
1364
  module.exports = { startServer, parseApp, Model, getDB, dispatch, on, emit, sendMail, setupStripe, registerStripeRoutes, setupS3, registerS3Routes, s3Upload, s3Delete, s3PresignedUrl, PLUGIN_UTILS }
1365
1365
  if (require.main === module) {
1366
1366
  const f=process.argv[2], p=parseInt(process.argv[3]||process.env.PORT||'3000')
1367
- if (!f) { console.error('Usage: node server.js <app.flux> [port]'); process.exit(1) }
1367
+ if (!f) { console.error('Usage: node server.js <app.aiplang> [port]'); process.exit(1) }
1368
1368
  startServer(f, p).catch(e=>{console.error(e);process.exit(1)})
1369
1369
  }
1370
1370