aiplang 2.2.0 → 2.4.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.
@@ -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.2.0'
8
+ const VERSION = '2.4.0'
9
9
  const RUNTIME_DIR = path.join(__dirname, '..', 'runtime')
10
10
  const cmd = process.argv[2]
11
11
  const args = process.argv.slice(3)
@@ -34,16 +34,31 @@ if (!cmd||cmd==='--help'||cmd==='-h') {
34
34
  AI-first web language — full apps in ~20 lines.
35
35
 
36
36
  Usage:
37
- npx aiplang init [name] create project
38
- npx aiplang init --template saas|landing|crud
39
- npx aiplang serve [dir] dev server + hot reload
40
- npx aiplang build [dir/file] compile static HTML
41
- npx aiplang new <page> new page template
37
+ npx aiplang init [name] create project (default template)
38
+ npx aiplang init [name] --template <t> use template: saas|landing|crud|dashboard|portfolio|blog
39
+ npx aiplang init [name] --template ./my.aiplang use a local .aiplang file as template
40
+ npx aiplang init [name] --template my-custom use a saved custom template
41
+ npx aiplang serve [dir] dev server + hot reload
42
+ npx aiplang build [dir/file] compile → static HTML
43
+ npx aiplang new <page> new page template
42
44
  npx aiplang --version
43
45
 
44
46
  Full-stack:
45
- npx aiplang start app.flux start full-stack server (API + DB + frontend)
46
- 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
+
50
+ Templates:
51
+ npx aiplang template list list all templates (built-in + custom)
52
+ npx aiplang template save <n> save current project as template
53
+ npx aiplang template save <n> --from <f> save a specific .aiplang file as template
54
+ npx aiplang template edit <n> open template in editor
55
+ npx aiplang template show <n> print template source
56
+ npx aiplang template export <n> export template to .aiplang file
57
+ npx aiplang template remove <n> delete a custom template
58
+
59
+ Custom template variables:
60
+ {{name}} project name
61
+ {{year}} current year
47
62
 
48
63
  Customization:
49
64
  ~theme accent=#7c3aed radius=1.5rem font=Syne bg=#000 text=#fff
@@ -59,66 +74,360 @@ if (!cmd||cmd==='--help'||cmd==='-h') {
59
74
  }
60
75
  if (cmd==='--version'||cmd==='-v') { console.log(`aiplang v${VERSION}`); process.exit(0) }
61
76
 
62
- // ── Templates ────────────────────────────────────────────────────
63
- const TEMPLATES = {
64
- saas: (n,y) => `# ${n}
77
+ // ─────────────────────────────────────────────────────────────────
78
+ // TEMPLATE SYSTEM
79
+ // Custom templates stored at ~/.aiplang/templates/<name>.aiplang
80
+ // ─────────────────────────────────────────────────────────────────
81
+
82
+ const TEMPLATES_DIR = path.join(require('os').homedir(), '.aiplang', 'templates')
83
+
84
+ function ensureTemplatesDir() {
85
+ if (!fs.existsSync(TEMPLATES_DIR)) fs.mkdirSync(TEMPLATES_DIR, { recursive: true })
86
+ }
87
+
88
+ // Built-in templates (interpolate {{name}} and {{year}})
89
+ const BUILTIN_TEMPLATES = {
90
+ saas: `# {{name}}
91
+ ~db sqlite ./app.db
92
+ ~auth jwt $JWT_SECRET expire=7d
93
+ ~admin /admin
94
+
95
+ model User {
96
+ id : uuid : pk auto
97
+ name : text : required
98
+ email : text : required unique
99
+ password : text : required hashed
100
+ plan : enum : free,starter,pro : default=free
101
+ role : enum : user,admin : default=user
102
+ ~soft-delete
103
+ }
104
+
105
+ api POST /api/auth/register {
106
+ ~validate name required | email required email | password min=8
107
+ ~unique User email $body.email | 409
108
+ ~hash password
109
+ insert User($body)
110
+ return jwt($inserted) 201
111
+ }
112
+
113
+ api POST /api/auth/login {
114
+ $user = User.findBy(email=$body.email)
115
+ ~check password $body.password $user.password | 401
116
+ return jwt($user) 200
117
+ }
118
+
119
+ api GET /api/me {
120
+ ~guard auth
121
+ return $auth.user
122
+ }
123
+
124
+ api GET /api/stats {
125
+ return User.count()
126
+ }
127
+
65
128
  %home dark /
66
129
  @stats = {}
67
130
  ~mount GET /api/stats => @stats
68
- nav{${n}>/features:Features>/pricing:Pricing>/login:Sign in}
69
- hero{Ship faster with AI|Zero config, infinite scale.>/signup:Start free>/demo:View demo} animate:fade-up
70
- stats{@stats.users:Users|@stats.mrr:MRR|@stats.uptime:Uptime}
71
- row3{rocket>Deploy instantly>Push to git, live in 3 seconds.|shield>Enterprise ready>SOC2, GDPR, SSO built-in.|chart>Full observability>Real-time errors and performance.} animate:stagger
72
- testimonial{Sarah Chen, CEO @ Acme|"Cut deployment time by 90%. Absolutely game-changing."|img:https://i.pravatar.cc/64?img=47}
73
- foot{© ${y} ${n}>/privacy:Privacy>/terms:Terms}`,
74
-
75
- landing: (n,y) => `# ${n}
76
- %home dark /
77
- nav{${n}>/about:About>/login:Sign in}
78
- hero{The future is now|${n} — built for the next generation.>/signup:Get started for free} animate:blur-in
79
- row3{rocket>Fast>Zero config, instant results.|bolt>Simple>One command to deploy.|globe>Global>CDN in 180+ countries.}
80
- foot{© ${y} ${n}}`,
131
+ nav{{{name}}>/pricing:Pricing>/login:Sign in}
132
+ hero{Ship faster with AI|Zero config, infinite scale.>/signup:Start free>/demo:View demo} animate:blur-in
133
+ stats{@stats:Users|99.9%:Uptime|$0:Start free}
134
+ row3{rocket>Deploy instantly>Push to git, live in seconds.|shield>Enterprise ready>SOC2, GDPR built-in.|chart>Full observability>Real-time errors.} animate:stagger
135
+ pricing{Free>$0/mo>3 projects>/signup:Get started|Pro>$29/mo>Unlimited>/signup:Start trial|Enterprise>Custom>SSO + SLA>/contact:Talk}
136
+ testimonial{Sarah Chen, CEO @ Acme|"Cut deployment time by 90%."|img:https://i.pravatar.cc/64?img=47} animate:fade-up
137
+ foot{© {{year}} {{name}}>/privacy:Privacy>/terms:Terms}
138
+
139
+ ---
140
+
141
+ %login dark /login
142
+ nav{{{name}}>/signup:Create account}
143
+ hero{Welcome back|Sign in to continue.}
144
+ form POST /api/auth/login => redirect /dashboard { Email:email | Password:password }
145
+ foot{© {{year}} {{name}}}
146
+
147
+ ---
81
148
 
82
- crud: (n,y) => `# ${n}
83
- %users dark /users
149
+ %signup dark /signup
150
+ nav{{{name}}>/login:Sign in}
151
+ hero{Start for free|No credit card required.}
152
+ form POST /api/auth/register => redirect /dashboard { Name:text | Email:email | Password:password }
153
+ foot{© {{year}} {{name}}}
154
+
155
+ ---
156
+
157
+ %dashboard dark /dashboard
84
158
  @users = []
159
+ @stats = {}
85
160
  ~mount GET /api/users => @users
86
- nav{${n}>/users:Users>/settings:Settings}
87
- sect{User Management}
88
- table @users { Name:name | Email:email | Plan:plan | Status:status | edit PUT /api/users/{id} | delete /api/users/{id} | empty: No users yet. }
89
- sect{Add User}
90
- form POST /api/users => @users.push($result) { Full name:text:Alice Johnson | Email:email:alice@company.com | Plan:select:starter,pro,enterprise }
91
- foot{© ${y} ${n}}`,
92
-
93
- default: (n,y) => `# ${n}
161
+ ~mount GET /api/stats => @stats
162
+ nav{{{name}}>/logout:Sign out}
163
+ stats{@stats:Total users}
164
+ sect{Users}
165
+ table @users { Name:name | Email:email | Plan:plan | edit PUT /api/users/{id} | delete /api/users/{id} | empty: No users yet. }
166
+ foot{{{name}} Dashboard}`,
167
+
168
+ landing: `# {{name}}
169
+ %home dark /
170
+ nav{{{name}}>/about:About>/contact:Contact}
171
+ hero{The future is now|{{name}} — built for the next generation.>/signup:Get started for free} animate:blur-in
172
+ row3{rocket>Fast>Zero config, instant results.|bolt>Simple>One command to deploy.|globe>Global>CDN in 180+ countries.}
173
+ foot{© {{year}} {{name}}}`,
174
+
175
+ crud: `# {{name}}
176
+ %items dark /
177
+ @items = []
178
+ ~mount GET /api/items => @items
179
+ nav{{{name}}>/items:Items>/settings:Settings}
180
+ sect{Manage Items}
181
+ table @items { Name:name | Status:status | edit PUT /api/items/{id} | delete /api/items/{id} | empty: No items yet. }
182
+ form POST /api/items => @items.push($result) { Name:text:Item name | Status:select:active,inactive }
183
+ foot{© {{year}} {{name}}}`,
184
+
185
+ blog: `# {{name}}
186
+ %home dark /
187
+ @posts = []
188
+ ~mount GET /api/posts => @posts
189
+ nav{{{name}}>/about:About}
190
+ hero{{{name}}|A blog about things that matter.} animate:fade-up
191
+ table @posts { Title:title | Date:created_at | empty: No posts yet. }
192
+ foot{© {{year}} {{name}}}`,
193
+
194
+ portfolio: `# {{name}}
195
+ ~theme accent=#f59e0b radius=2rem font=Syne bg=#0c0a09 text=#fafaf9
196
+
197
+ %home dark /
198
+ nav{{{name}}>/work:Work>/contact:Contact}
199
+ hero{Design & code.|Creative work for bold brands.>/work:See my work} animate:blur-in
200
+ row3{globe>10+ countries>Clients from 3 continents.|star>50+ projects>From startups to Fortune 500.|check>On time>98% on-schedule delivery.} animate:stagger
201
+ gallery{https://images.unsplash.com/photo-1518770660439?w=600|https://images.unsplash.com/photo-1561070791-2526d30994b5?w=600|https://images.unsplash.com/photo-1558655146?w=600}
202
+ testimonial{Marco Rossi, CEO|"Exceptional work from start to finish."|img:https://i.pravatar.cc/64?img=11}
203
+ foot{© {{year}} {{name}}>/github:GitHub>/linkedin:LinkedIn}`,
204
+
205
+ dashboard: `# {{name}}
206
+ %main dark /
207
+ @stats = {}
208
+ @items = []
209
+ ~mount GET /api/stats => @stats
210
+ ~mount GET /api/items => @items
211
+ ~interval 30000 GET /api/stats => @stats
212
+ nav{{{name}}>/logout:Sign out}
213
+ stats{@stats.total:Total|@stats.active:Active|@stats.revenue:Revenue}
214
+ sect{Recent Items}
215
+ table @items { Name:name | Status:status | Date:created_at | edit PUT /api/items/{id} | delete /api/items/{id} | empty: No data. }
216
+ sect{Add Item}
217
+ form POST /api/items => @items.push($result) { Name:text | Status:select:active,inactive }
218
+ foot{{{name}}}`,
219
+
220
+ default: `# {{name}}
94
221
  %home dark /
95
- nav{${n}>/login:Sign in}
96
- hero{Welcome to ${n}|Edit pages/home.flux to get started.>/signup:Get started} animate:fade-up
222
+ nav{{{name}}>/login:Sign in}
223
+ hero{Welcome to {{name}}|Edit pages/home.aiplang to get started.>/signup:Get started} animate:fade-up
97
224
  row3{rocket>Fast>Renders in under 1ms.|bolt>AI-native>Written by Claude in seconds.|globe>Deploy anywhere>Static files. Any host.}
98
- foot{© ${y} ${n}}`,
225
+ foot{© {{year}} {{name}}}`,
226
+ }
227
+
228
+ function applyTemplateVars(src, name, year) {
229
+ return src.replace(/\{\{name\}\}/g, name).replace(/\{\{year\}\}/g, year)
230
+ }
231
+
232
+ function getTemplate(tplName, name, year) {
233
+ ensureTemplatesDir()
234
+
235
+ // 1. Local file path: --template ./my-template.aiplang or --template /abs/path.aiplang
236
+ if (tplName.startsWith('./') || tplName.startsWith('../') || tplName.startsWith('/')) {
237
+ const full = path.resolve(tplName)
238
+ if (!fs.existsSync(full)) { console.error(`\n ✗ Template file not found: ${full}\n`); process.exit(1) }
239
+ return applyTemplateVars(fs.readFileSync(full, 'utf8'), name, year)
240
+ }
241
+
242
+ // 2. User custom template: ~/.aiplang/templates/<name>.aiplang
243
+ const customPath = path.join(TEMPLATES_DIR, tplName + '.aiplang')
244
+ if (fs.existsSync(customPath)) {
245
+ return applyTemplateVars(fs.readFileSync(customPath, 'utf8'), name, year)
246
+ }
247
+
248
+ // 3. Built-in template
249
+ const builtin = BUILTIN_TEMPLATES[tplName]
250
+ if (builtin) return applyTemplateVars(builtin, name, year)
251
+
252
+ // Not found — show what's available
253
+ const customs = fs.existsSync(TEMPLATES_DIR)
254
+ ? fs.readdirSync(TEMPLATES_DIR).filter(f=>f.endsWith('.aiplang')).map(f=>f.replace('.aiplang',''))
255
+ : []
256
+ const all = [...Object.keys(BUILTIN_TEMPLATES).filter(k=>k!=='default'), ...customs]
257
+ console.error(`\n ✗ Template "${tplName}" not found.\n Available: ${all.join(', ')}\n`)
258
+ process.exit(1)
259
+ }
260
+
261
+ function listTemplates() {
262
+ ensureTemplatesDir()
263
+ const builtins = Object.keys(BUILTIN_TEMPLATES).filter(k=>k!=='default')
264
+ const customs = fs.readdirSync(TEMPLATES_DIR).filter(f=>f.endsWith('.aiplang')).map(f=>f.replace('.aiplang',''))
265
+ console.log(`\n aiplang templates\n`)
266
+ console.log(` Built-in:`)
267
+ builtins.forEach(t => console.log(` ${t}`))
268
+ if (customs.length) {
269
+ console.log(`\n Custom (${TEMPLATES_DIR}):`)
270
+ customs.forEach(t => console.log(` ${t} ✓`))
271
+ } else {
272
+ console.log(`\n Custom: (none yet — use "aiplang template save <name>" to create one)`)
273
+ }
274
+ console.log()
275
+ }
276
+
277
+ // ── template subcommand ──────────────────────────────────────────
278
+ if (cmd === 'template') {
279
+ const sub = args[0]
280
+ ensureTemplatesDir()
281
+
282
+ // aiplang template list
283
+ if (!sub || sub === 'list' || sub === 'ls') {
284
+ listTemplates(); process.exit(0)
285
+ }
286
+
287
+ // aiplang template save <name> [--from <file>]
288
+ if (sub === 'save' || sub === 'add') {
289
+ const tname = args[1]
290
+ if (!tname) { console.error('\n ✗ Usage: aiplang template save <name> [--from <file>]\n'); process.exit(1) }
291
+ const fromIdx = args.indexOf('--from')
292
+ let src
293
+ if (fromIdx !== -1 && args[fromIdx+1]) {
294
+ const fp = path.resolve(args[fromIdx+1])
295
+ if (!fs.existsSync(fp)) { console.error(`\n ✗ File not found: ${fp}\n`); process.exit(1) }
296
+ src = fs.readFileSync(fp, 'utf8')
297
+ } else {
298
+ // Auto-detect: use pages/ directory or app.aiplang
299
+ const sources = ['pages', 'app.aiplang', 'index.aiplang']
300
+ const found = sources.find(s => fs.existsSync(s))
301
+ if (!found) { console.error('\n ✗ No .aiplang files found. Use --from <file> to specify source.\n'); process.exit(1) }
302
+ if (fs.statSync(found).isDirectory()) {
303
+ src = fs.readdirSync(found).filter(f=>f.endsWith('.aiplang'))
304
+ .map(f => fs.readFileSync(path.join(found,f),'utf8')).join('\n---\n')
305
+ } else {
306
+ src = fs.readFileSync(found, 'utf8')
307
+ }
308
+ }
309
+ const dest = path.join(TEMPLATES_DIR, tname + '.aiplang')
310
+ fs.writeFileSync(dest, src)
311
+ console.log(`\n ✓ Template saved: ${tname}\n ${dest}\n\n Use it: aiplang init my-app --template ${tname}\n`)
312
+ process.exit(0)
313
+ }
314
+
315
+ // aiplang template remove <name>
316
+ if (sub === 'remove' || sub === 'rm' || sub === 'delete') {
317
+ const tname = args[1]
318
+ if (!tname) { console.error('\n ✗ Usage: aiplang template remove <name>\n'); process.exit(1) }
319
+ const dest = path.join(TEMPLATES_DIR, tname + '.aiplang')
320
+ if (!fs.existsSync(dest)) { console.error(`\n ✗ Template "${tname}" not found.\n`); process.exit(1) }
321
+ fs.unlinkSync(dest)
322
+ console.log(`\n ✓ Removed template: ${tname}\n`); process.exit(0)
323
+ }
324
+
325
+ // aiplang template edit <name>
326
+ if (sub === 'edit' || sub === 'open') {
327
+ const tname = args[1]
328
+ if (!tname) { console.error('\n ✗ Usage: aiplang template edit <name>\n'); process.exit(1) }
329
+ let dest = path.join(TEMPLATES_DIR, tname + '.aiplang')
330
+ if (!fs.existsSync(dest)) {
331
+ // create from built-in if exists
332
+ const builtin = BUILTIN_TEMPLATES[tname]
333
+ if (builtin) { fs.writeFileSync(dest, builtin); console.log(`\n ✓ Copied built-in "${tname}" to custom templates.\n`) }
334
+ else { console.error(`\n ✗ Template "${tname}" not found.\n`); process.exit(1) }
335
+ }
336
+ const editor = process.env.EDITOR || process.env.VISUAL || 'code'
337
+ try { require('child_process').spawnSync(editor, [dest], { stdio: 'inherit' }) }
338
+ catch { console.log(`\n Template path: ${dest}\n Open it in your editor.\n`) }
339
+ process.exit(0)
340
+ }
341
+
342
+ // aiplang template show <name>
343
+ if (sub === 'show' || sub === 'cat') {
344
+ const tname = args[1] || 'default'
345
+ const customPath = path.join(TEMPLATES_DIR, tname + '.aiplang')
346
+ if (fs.existsSync(customPath)) { console.log(fs.readFileSync(customPath,'utf8')); process.exit(0) }
347
+ const builtin = BUILTIN_TEMPLATES[tname]
348
+ if (builtin) { console.log(builtin); process.exit(0) }
349
+ console.error(`\n ✗ Template "${tname}" not found.\n`); process.exit(1)
350
+ }
351
+
352
+ // aiplang template export <name> [--out <file>]
353
+ if (sub === 'export') {
354
+ const tname = args[1]
355
+ if (!tname) { console.error('\n ✗ Usage: aiplang template export <name>\n'); process.exit(1) }
356
+ const outIdx = args.indexOf('--out')
357
+ const outFile = outIdx !== -1 ? args[outIdx+1] : `./${tname}.aiplang`
358
+ const customPath = path.join(TEMPLATES_DIR, tname + '.aiplang')
359
+ const src = fs.existsSync(customPath) ? fs.readFileSync(customPath,'utf8') : BUILTIN_TEMPLATES[tname]
360
+ if (!src) { console.error(`\n ✗ Template "${tname}" not found.\n`); process.exit(1) }
361
+ fs.writeFileSync(outFile, src)
362
+ console.log(`\n ✓ Exported "${tname}" → ${outFile}\n`)
363
+ process.exit(0)
364
+ }
365
+
366
+ console.error(`\n ✗ Unknown template command: ${sub}\n Commands: list, save, remove, edit, show, export\n`)
367
+ process.exit(1)
99
368
  }
100
369
 
101
370
  // ── Init ─────────────────────────────────────────────────────────
102
371
  if (cmd==='init') {
103
372
  const tplIdx = args.indexOf('--template')
104
- const tplName = tplIdx!==-1 ? args[tplIdx+1] : 'default'
373
+ const tplName = tplIdx !== -1 ? args[tplIdx+1] : 'default'
105
374
  const name = args.find(a=>!a.startsWith('--')&&a!==tplName)||'aiplang-app'
106
375
  const dir = path.resolve(name), year = new Date().getFullYear()
376
+
107
377
  if (fs.existsSync(dir)) { console.error(`\n ✗ Directory "${name}" already exists.\n`); process.exit(1) }
108
- fs.mkdirSync(path.join(dir,'pages'), {recursive:true})
109
- fs.mkdirSync(path.join(dir,'public'), {recursive:true})
110
- for (const f of ['aiplang-runtime.js','aiplang-hydrate.js']) {
111
- const src=path.join(RUNTIME_DIR,f); if(fs.existsSync(src)) fs.copyFileSync(src,path.join(dir,'public',f))
378
+
379
+ // Get template source (built-in, custom, or file path)
380
+ const tplSrc = getTemplate(tplName, name, year)
381
+
382
+ // Check if template has full-stack backend (models/api blocks)
383
+ const isFullStack = tplSrc.includes('\nmodel ') || tplSrc.includes('\napi ')
384
+ const isMultiFile = tplSrc.includes('\n---\n')
385
+
386
+ if (isFullStack) {
387
+ // Full-stack project: single app.aiplang
388
+ fs.mkdirSync(dir, { recursive: true })
389
+ fs.writeFileSync(path.join(dir, 'app.aiplang'), tplSrc)
390
+ fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({
391
+ name, version:'0.1.0',
392
+ scripts: { dev: 'npx aiplang start app.aiplang', start: 'npx aiplang start app.aiplang' },
393
+ devDependencies: { 'aiplang': `^${VERSION}` }
394
+ }, null, 2))
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
+ 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.aiplang\n\`\`\`\n`)
398
+ const label = tplName !== 'default' ? ` (template: ${tplName})` : ''
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
+ } else if (isMultiFile) {
401
+ // Multi-page SSG project: pages/*.aiplang
402
+ fs.mkdirSync(path.join(dir,'pages'), {recursive:true})
403
+ fs.mkdirSync(path.join(dir,'public'), {recursive:true})
404
+ for (const f of ['aiplang-runtime.js','aiplang-hydrate.js']) {
405
+ const src=path.join(RUNTIME_DIR,f); if(fs.existsSync(src)) fs.copyFileSync(src,path.join(dir,'public',f))
406
+ }
407
+ const pageBlocks = tplSrc.split('\n---\n')
408
+ pageBlocks.forEach((block, i) => {
409
+ const m = block.match(/^%([a-zA-Z0-9_-]+)/m)
410
+ const pageName = m ? m[1] : (i === 0 ? 'home' : `page${i}`)
411
+ fs.writeFileSync(path.join(dir,'pages',`${pageName}.aiplang`), block.trim())
412
+ })
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
+ fs.writeFileSync(path.join(dir,'.gitignore'),'dist/\nnode_modules/\n')
415
+ const label = tplName !== 'default' ? ` (template: ${tplName})` : ''
416
+ const files = fs.readdirSync(path.join(dir,'pages')).map(f=>f).join(', ')
417
+ console.log(`\n ✓ Created ${name}/${label}\n\n pages/{${files}} ← edit these\n\n Next:\n cd ${name} && npx aiplang serve\n`)
418
+ } else {
419
+ // Single-page SSG project
420
+ fs.mkdirSync(path.join(dir,'pages'), {recursive:true})
421
+ fs.mkdirSync(path.join(dir,'public'), {recursive:true})
422
+ for (const f of ['aiplang-runtime.js','aiplang-hydrate.js']) {
423
+ const src=path.join(RUNTIME_DIR,f); if(fs.existsSync(src)) fs.copyFileSync(src,path.join(dir,'public',f))
424
+ }
425
+ fs.writeFileSync(path.join(dir,'pages','home.aiplang'), tplSrc)
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
+ fs.writeFileSync(path.join(dir,'.gitignore'),'dist/\nnode_modules/\n')
428
+ const label = tplName !== 'default' ? ` (template: ${tplName})` : ''
429
+ console.log(`\n ✓ Created ${name}/${label}\n\n pages/home.aiplang ← edit this\n\n Next:\n cd ${name} && npx aiplang serve\n`)
112
430
  }
113
- fs.writeFileSync(path.join(dir,'public','index.html'),`<!DOCTYPE html>
114
- <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${name}</title>
115
- <meta http-equiv="refresh" content="0; url=/">
116
- </head><body><p>Run <code>npx aiplang serve</code> to start the dev server.</p></body></html>`)
117
- fs.writeFileSync(path.join(dir,'pages','home.flux'), (TEMPLATES[tplName]||TEMPLATES.default)(name, year))
118
- 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))
119
- fs.writeFileSync(path.join(dir,'.gitignore'),'dist/\nnode_modules/\n')
120
- const label = tplName!=='default' ? ` (template: ${tplName})` : ''
121
- console.log(`\n ✓ Created ${name}/${label}\n\n pages/home.flux ← edit this\n\n Next:\n cd ${name} && npx aiplang serve\n`)
122
431
  process.exit(0)
123
432
  }
124
433
 
@@ -126,7 +435,7 @@ if (cmd==='init') {
126
435
  if (cmd==='new') {
127
436
  const name=args[0]; if(!name){console.error('\n ✗ Usage: aiplang new <page>\n');process.exit(1)}
128
437
  const dir=fs.existsSync('pages')?'pages':'.'
129
- const file=path.join(dir,`${name}.flux`)
438
+ const file=path.join(dir,`${name}.aiplang`)
130
439
  if(fs.existsSync(file)){console.error(`\n ✗ ${file} exists.\n`);process.exit(1)}
131
440
  const cap=name.charAt(0).toUpperCase()+name.slice(1)
132
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`)
@@ -141,9 +450,9 @@ if (cmd==='build') {
141
450
  const input=args.filter((a,i)=>!a.startsWith('--')&&i!==outIdx+1)[0]||'pages/'
142
451
  const files=[]
143
452
  if(fs.existsSync(input)&&fs.statSync(input).isDirectory()){
144
- fs.readdirSync(input).filter(f=>f.endsWith('.flux')).forEach(f=>files.push(path.join(input,f)))
145
- } else if(input.endsWith('.flux')&&fs.existsSync(input)){ files.push(input) }
146
- 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)}
147
456
  const src=files.map(f=>fs.readFileSync(f,'utf8')).join('\n---\n')
148
457
  const pages=parseFlux(src)
149
458
  if(!pages.length){console.error('\n ✗ No pages found.\n');process.exit(1)}
@@ -162,7 +471,7 @@ if (cmd==='build') {
162
471
  }
163
472
  const hf=path.join(RUNTIME_DIR,'aiplang-hydrate.js')
164
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)}`)}
165
- 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)))
166
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`)
167
476
  process.exit(0)
168
477
  }
@@ -171,13 +480,13 @@ if (cmd==='build') {
171
480
  if (cmd==='serve'||cmd==='dev') {
172
481
  const root=path.resolve(args[0]||'.')
173
482
  const port=parseInt(process.env.PORT||'3000')
174
- 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'}
175
484
  let clients=[]
176
485
  const mtimes={}
177
486
  setInterval(()=>{
178
487
  const pd=path.join(root,'pages')
179
488
  if(!fs.existsSync(pd))return
180
- fs.readdirSync(pd).filter(f=>f.endsWith('.flux')).forEach(f=>{
489
+ fs.readdirSync(pd).filter(f=>f.endsWith('.aiplang')).forEach(f=>{
181
490
  const fp=path.join(pd,f),mt=fs.statSync(fp).mtimeMs
182
491
  if(mtimes[fp]&&mtimes[fp]!==mt)clients.forEach(c=>{try{c.write('data: reload\n\n')}catch{}})
183
492
  mtimes[fp]=mt
@@ -192,7 +501,7 @@ if (cmd==='serve'||cmd==='dev') {
192
501
  let p=req.url.split('?')[0];if(p==='/') p='/index.html'
193
502
  let fp=null
194
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}}
195
- 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}
196
505
  if(!fp){res.writeHead(404);res.end('Not found');return}
197
506
  let content=fs.readFileSync(fp)
198
507
  if(path.extname(fp)==='.html'){
@@ -201,7 +510,7 @@ if (cmd==='serve'||cmd==='dev') {
201
510
  }
202
511
  res.writeHead(200,{'Content-Type':MIME[path.extname(fp)]||'application/octet-stream','Access-Control-Allow-Origin':'*'})
203
512
  res.end(content)
204
- }).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`))
205
514
  return
206
515
  }
207
516
 
@@ -209,7 +518,7 @@ if (cmd==='serve'||cmd==='dev') {
209
518
  if (cmd === 'start' || cmd === 'run') {
210
519
  const aipFile = args[0]
211
520
  if (!aipFile || !fs.existsSync(aipFile)) {
212
- console.error(`\n ✗ Usage: aiplang start <app.flux>\n`)
521
+ console.error(`\n ✗ Usage: aiplang start <app.aiplang>\n`)
213
522
  process.exit(1)
214
523
  }
215
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.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "AI-first full-stack language. Frontend + Backend + DB + Auth in one file. Competes with Laravel.",
5
5
  "keywords": [
6
6
  "aiplang",
@@ -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
  `
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