aiplang 2.6.1 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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.aiplang` → see it live.
11
+ Ask Claude to generate a page → paste into `pages/home.aip` → see it live.
12
12
 
13
13
  ---
14
14
 
@@ -16,9 +16,9 @@ Ask Claude to generate a page → paste into `pages/home.aiplang` → see it liv
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 `.aiplang` file describes a complete app: frontend, backend, database, auth, email, jobs.
19
+ A single `.aip` file describes a complete app: frontend, backend, database, auth, email, jobs.
20
20
 
21
- ```aiplang
21
+ ```aip
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.aiplang # full-stack server (Node.js)
86
+ npx aiplang start app.aip # full-stack server (Node.js)
87
87
  npx aiplang new dashboard # create new page template
88
88
  ```
89
89
 
@@ -117,7 +117,7 @@ aiplang/
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 ← .aiplang → AST parser
120
+ │ ├── compiler/compiler.go ← .aip → 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)
@@ -1,97 +1,139 @@
1
- # aiplang — syntax reference
2
- Generate ONLY aiplang code. No React, HTML, or other frameworks. No explanations unless asked.
1
+ # aiplang — LLM reference
2
+ Generate ONLY `.aip` syntax. Zero explanation. Zero React. Zero HTML frameworks.
3
3
 
4
- ## File structure
4
+ ## Config directives (before model/api/pages)
5
5
  ```
6
- ~env VAR required # env validation
7
- ~db sqlite ./app.db # or: postgres $DATABASE_URL
6
+ ~env VAR required # fail if missing
7
+ ~db sqlite ./app.db # or: postgres $DATABASE_URL
8
8
  ~auth jwt $JWT_SECRET expire=7d
9
- ~mail smtp host=x user=$U pass=$P
10
- ~s3 $KEY secret=$S bucket=$B region=us-east-1 prefix=uploads/ maxSize=10mb
9
+ ~mail smtp host=x user=$U pass=$P from=no-reply@x.com
10
+ ~s3 $KEY secret=$S bucket=$B region=us-east-1 prefix=up/ maxSize=10mb allow=image/jpeg,image/png
11
11
  ~stripe $KEY webhook=$WH success=/ok cancel=/pricing
12
12
  ~plan free=price_x pro=price_y
13
13
  ~admin /admin
14
14
  ~use cors origins=https://x.com
15
15
  ~use rate-limit max=100 window=60s
16
16
  ~use helmet | ~use logger | ~use compression
17
- ~plugin ./my-plugin.js
17
+ ~plugin ./plugins/custom.js
18
+ ```
18
19
 
20
+ ## Model
21
+ ```
19
22
  model Name {
20
- id : uuid : pk auto
21
- field : type : modifier
22
- ~soft-delete
23
- ~belongs OtherModel
23
+ id : uuid : pk auto
24
+ field : text : required
25
+ email : text : required unique
26
+ password : text : required hashed
27
+ count : int : default=0
28
+ price : float
29
+ active : bool : default=true
30
+ at : timestamp
31
+ data : json
32
+ kind : enum : a,b,c : default=a
33
+ ~soft-delete # adds deleted_at, filters from queries
34
+ ~belongs OtherModel # adds other_model_id FK
24
35
  }
25
- # types: uuid text int float bool timestamp json enum
26
- # modifiers: pk auto required unique hashed default=val index
36
+ ```
37
+ Types: `uuid text int float bool timestamp json enum`
38
+ Modifiers: `pk auto required unique hashed default=val index`
27
39
 
28
- api METHOD /path/:id {
29
- ~guard auth | admin | subscribed | owner
30
- ~validate field required | field email | field min=8 | field numeric
31
- ~query page=1 limit=20
40
+ ## API ALWAYS one blank line between ops, return is last
41
+ ```
42
+ api POST /path {
43
+ ~guard auth # auth | admin | subscribed | owner
44
+ ~validate field required | field email | field min=8 | field max=100 | field numeric | field in:a,b,c
32
45
  ~unique Model field $body.field | 409
33
- ~hash field
34
- ~check password $body.pw $user.pw | 401
46
+ ~hash field # bcrypt the field before insert
47
+ ~check password $body.pw $user.pw | 401 # bcrypt compare
48
+ ~query page=1 limit=20 # query params with defaults
35
49
  ~mail $user.email "Subject" "Body"
36
50
  ~dispatch jobName $body
37
51
  ~emit event.name $body
38
- $var = Model.findBy(field=$body.field)
39
- insert Model($body)
40
- update Model($id, $body)
41
- delete Model($id)
42
- restore Model($id)
43
- return $inserted | $updated | $auth.user | Model.all(order=created_at desc)
44
- return Model.paginate($page, $limit)
45
- return Model.count() | Model.sum(field) | Model.avg(field)
52
+ $var = Model.findBy(field=$body.field) # assign to $var
53
+ $var = Model.find($params.id)
54
+ insert Model($body) # sets $inserted
55
+ update Model($params.id, $body) # sets $updated
56
+ delete Model($params.id) # soft or hard delete
57
+ restore Model($params.id)
58
+ return $inserted 201 # status optional, default 200
59
+ return $updated
60
+ return $var
61
+ return $auth.user
62
+ return jwt($inserted) 201 # return JWT token
46
63
  return jwt($user) 200
64
+ return Model.all(order=created_at desc)
65
+ return Model.paginate($page, $limit)
66
+ return Model.count()
67
+ return Model.sum(field)
68
+ return Model.findBy(field=$body.field)
47
69
  }
70
+ ```
48
71
 
49
- %id theme /route # dark | light | acid | #bg,#text,#accent
50
- ~theme accent=#hex bg=#hex text=#hex font=Name radius=1rem surface=#hex navbg=#hex
51
- @var = [] # state: [] or {} or "string" or 0
52
- ~mount GET /api => @var
53
- ~interval 10000 GET /api => @var
54
-
55
- blocks...
56
- --- # page separator
72
+ ## Pages
73
+ ```
74
+ %id theme /route # id=any, theme=dark|light|acid|#bg,#text,#accent, route=/path
75
+ ~theme accent=#hex bg=#hex text=#hex font=Name radius=1rem surface=#hex navbg=#hex spacing=5rem
76
+ @list = [] # reactive state — [] for arrays, {} for objects, "str", 0
77
+ @obj = {}
78
+ ~mount GET /api/path => @list # fetch on page load → assign to @list
79
+ ~mount GET /api/path => @obj
80
+ ~interval 15000 GET /api/path => @obj # repeat every N ms (always pair with ~mount)
57
81
  ```
58
82
 
59
83
  ## All blocks
60
84
  ```
61
85
  nav{Brand>/path:Link>/path:Link}
62
- hero{Title|Sub>/path:CTA} | hero{Title|Sub>/path:CTA|img:https://url}
63
- stats{@val:Label|99%:Uptime|$0:Free}
86
+ hero{Title|Subtitle>/path:CTA}
87
+ hero{Title|Sub>/path:CTA|img:https://url} # split layout with image
88
+ stats{@obj.field:Label|99%:Uptime|$0:Free}
64
89
  row2{icon>Title>Body} | row3{...} | row4{...}
65
90
  sect{Title|Optional body}
66
- table @list { Col:field | edit PUT /api/{id} | delete /api/{id} | empty: msg }
67
- form POST /api => @list.push($result) { Label:type:placeholder | Label:select:a,b,c }
68
- form POST /api => redirect /path { Label:type | Label:password }
69
- pricing{Name>Price>Desc>/path:CTA|Name>Price>Desc>/path:CTA}
70
- faq{Question?>Answer.|Q2?>A2.}
71
- testimonial{Name, Role @ Co|"Quote."|img:https://url}
72
- gallery{https://img1|https://img2|https://img3}
73
- btn{Label > METHOD /api/path} | btn{Label > DELETE /api > confirm:Sure?}
91
+ table @list { Col:field | Col:field | edit PUT /api/path/{id} | delete /api/path/{id} | empty: msg }
92
+ form POST /api/path => @list.push($result) { Label:type:placeholder | Label:select:a,b,c }
93
+ form POST /api/path => redirect /dashboard { Label:type | Label:password }
94
+ pricing{Name>$0/mo>Desc>/path:CTA | Name>$19/mo>Desc>/path:CTA}
95
+ faq{Question?>Answer. | Q2?>A2.}
96
+ testimonial{Name, Role|"Quote."|img:https://url}
97
+ gallery{https://img1 | https://img2 | https://img3}
98
+ btn{Label > METHOD /api/path}
99
+ btn{Label > DELETE /api/path > confirm:Are you sure?}
74
100
  select @filterVar { All | Active | Inactive }
75
101
  if @var { blocks }
76
- raw{<div>any HTML</div>}
102
+ raw{<div>any HTML or embed</div>}
77
103
  foot{© 2025 Name>/path:Link}
78
104
  ```
79
105
 
80
- ## Block modifiers (any block)
81
- `animate:fade-up | fade-in | blur-in | slide-left | slide-right | zoom-in | stagger`
82
- `class:my-class`
106
+ ## Animate any block
107
+ `block{...} animate:fade-up` · `animate:fade-in` · `animate:blur-in` · `animate:stagger` · `animate:slide-left` · `animate:zoom-in`
83
108
 
84
- ## S3 auto-routes
85
- `POST /api/upload` · `DELETE /api/upload/:key` · `GET /api/upload/presign?key=x`
109
+ ## Multiple pages — separate with ---
110
+ ```
111
+ %home dark /
112
+ nav{...}
113
+ hero{...}
114
+ ---
115
+ %dashboard dark /dashboard
116
+ @data = []
117
+ ~mount GET /api/data => @data
118
+ table @data { ... }
119
+ ---
120
+ %login dark /login
121
+ form POST /api/auth/login => redirect /dashboard { Email:email | Password:password }
122
+ ```
86
123
 
87
- ## Stripe auto-routes
88
- `POST /api/stripe/checkout` · `POST /api/stripe/portal` · `GET /api/stripe/subscription`
124
+ ## Icons (use in row blocks)
125
+ `rocket bolt shield chart star check globe gear fire money bell mail user lock eye tag search home`
89
126
 
90
- ## Rules
91
- 1. Dark theme default
92
- 2. `@var = []` + `~mount` for all dynamic data
93
- 3. Tables always have `edit` + `delete` unless readonly
94
- 4. Forms: `=> @list.push($result)` or `=> redirect /path`
95
- 5. `~theme` before `%` declarations
96
- 6. Separate pages with `---`
97
- 7. Full-stack: `~db` + `~auth` + `model` + `api` before pages
127
+ ## S3 auto-routes (generated when ~s3 configured)
128
+ `POST /api/upload` · `DELETE /api/upload/:key` · `GET /api/upload/presign?key=x&expires=3600`
129
+
130
+ ## Stripe auto-routes (generated when ~stripe configured)
131
+ `POST /api/stripe/checkout {plan,email}` · `POST /api/stripe/portal` · `GET /api/stripe/subscription` · `POST /api/stripe/webhook`
132
+ Webhooks auto-handled: checkout.completed sets user.plan, subscription.deleted → resets to free
133
+
134
+ ## Run
135
+ ```bash
136
+ npx aiplang start app.aip # full-stack
137
+ npx aiplang serve # frontend dev
138
+ npx aiplang build pages/ # static build
139
+ ```
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.6.1'
8
+ const VERSION = '2.7.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)
@@ -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.aiplang use a local .aiplang file as template
39
+ npx aiplang init [name] --template ./my.aip use a local .aip 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.aiplang start full-stack server (API + DB + frontend)
48
- PORT=8080 aiplang start app.aiplang custom port
47
+ npx aiplang start app.aip start full-stack server (API + DB + frontend)
48
+ PORT=8080 aiplang start app.aip 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 .aiplang file as template
53
+ npx aiplang template save <n> --from <f> save a specific .aip 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 .aiplang file
56
+ npx aiplang template export <n> export template to .aip file
57
57
  npx aiplang template remove <n> delete a custom template
58
58
 
59
59
  Custom template variables:
@@ -76,10 +76,10 @@ 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>.aiplang
79
+ // Custom templates stored at ~/.aip/templates/<name>.aip
80
80
  // ─────────────────────────────────────────────────────────────────
81
81
 
82
- const TEMPLATES_DIR = path.join(require('os').homedir(), '.aiplang', 'templates')
82
+ const TEMPLATES_DIR = path.join(require('os').homedir(), '.aip', 'templates')
83
83
 
84
84
  function ensureTemplatesDir() {
85
85
  if (!fs.existsSync(TEMPLATES_DIR)) fs.mkdirSync(TEMPLATES_DIR, { recursive: true })
@@ -121,6 +121,23 @@ api GET /api/me {
121
121
  return $auth.user
122
122
  }
123
123
 
124
+ api GET /api/users {
125
+ ~guard admin
126
+ ~query page=1
127
+ return User.paginate($page, 20)
128
+ }
129
+
130
+ api PUT /api/users/:id {
131
+ ~guard admin
132
+ update User($id, $body)
133
+ return $updated
134
+ }
135
+
136
+ api DELETE /api/users/:id {
137
+ ~guard admin
138
+ delete User($id)
139
+ }
140
+
124
141
  api GET /api/stats {
125
142
  return User.count()
126
143
  }
@@ -220,7 +237,7 @@ foot{{{name}}}`,
220
237
  default: `# {{name}}
221
238
  %home dark /
222
239
  nav{{{name}}>/login:Sign in}
223
- hero{Welcome to {{name}}|Edit pages/home.aiplang to get started.>/signup:Get started} animate:fade-up
240
+ hero{Welcome to {{name}}|Edit pages/home.aip to get started.>/signup:Get started} animate:fade-up
224
241
  row3{rocket>Fast>Renders in under 1ms.|bolt>AI-native>Written by Claude in seconds.|globe>Deploy anywhere>Static files. Any host.}
225
242
  foot{© {{year}} {{name}}}`,
226
243
  }
@@ -232,15 +249,15 @@ function applyTemplateVars(src, name, year) {
232
249
  function getTemplate(tplName, name, year) {
233
250
  ensureTemplatesDir()
234
251
 
235
- // 1. Local file path: --template ./my-template.aiplang or --template /abs/path.aiplang
252
+ // 1. Local file path: --template ./my-template.aip or --template /abs/path.aip
236
253
  if (tplName.startsWith('./') || tplName.startsWith('../') || tplName.startsWith('/')) {
237
254
  const full = path.resolve(tplName)
238
255
  if (!fs.existsSync(full)) { console.error(`\n ✗ Template file not found: ${full}\n`); process.exit(1) }
239
256
  return applyTemplateVars(fs.readFileSync(full, 'utf8'), name, year)
240
257
  }
241
258
 
242
- // 2. User custom template: ~/.aiplang/templates/<name>.aiplang
243
- const customPath = path.join(TEMPLATES_DIR, tplName + '.aiplang')
259
+ // 2. User custom template: ~/.aip/templates/<name>.aip
260
+ const customPath = path.join(TEMPLATES_DIR, tplName + '.aip')
244
261
  if (fs.existsSync(customPath)) {
245
262
  return applyTemplateVars(fs.readFileSync(customPath, 'utf8'), name, year)
246
263
  }
@@ -251,7 +268,7 @@ function getTemplate(tplName, name, year) {
251
268
 
252
269
  // Not found — show what's available
253
270
  const customs = fs.existsSync(TEMPLATES_DIR)
254
- ? fs.readdirSync(TEMPLATES_DIR).filter(f=>f.endsWith('.aiplang')).map(f=>f.replace('.aiplang',''))
271
+ ? fs.readdirSync(TEMPLATES_DIR).filter(f=>f.endsWith('.aip')).map(f=>f.replace('.aip',''))
255
272
  : []
256
273
  const all = [...Object.keys(BUILTIN_TEMPLATES).filter(k=>k!=='default'), ...customs]
257
274
  console.error(`\n ✗ Template "${tplName}" not found.\n Available: ${all.join(', ')}\n`)
@@ -261,7 +278,7 @@ function getTemplate(tplName, name, year) {
261
278
  function listTemplates() {
262
279
  ensureTemplatesDir()
263
280
  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',''))
281
+ const customs = fs.readdirSync(TEMPLATES_DIR).filter(f=>f.endsWith('.aip')).map(f=>f.replace('.aip',''))
265
282
  console.log(`\n aiplang templates\n`)
266
283
  console.log(` Built-in:`)
267
284
  builtins.forEach(t => console.log(` ${t}`))
@@ -295,18 +312,18 @@ if (cmd === 'template') {
295
312
  if (!fs.existsSync(fp)) { console.error(`\n ✗ File not found: ${fp}\n`); process.exit(1) }
296
313
  src = fs.readFileSync(fp, 'utf8')
297
314
  } else {
298
- // Auto-detect: use pages/ directory or app.aiplang
299
- const sources = ['pages', 'app.aiplang', 'index.aiplang']
315
+ // Auto-detect: use pages/ directory or app.aip
316
+ const sources = ['pages', 'app.aip', 'index.aip']
300
317
  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) }
318
+ if (!found) { console.error('\n ✗ No .aip files found. Use --from <file> to specify source.\n'); process.exit(1) }
302
319
  if (fs.statSync(found).isDirectory()) {
303
- src = fs.readdirSync(found).filter(f=>f.endsWith('.aiplang'))
320
+ src = fs.readdirSync(found).filter(f=>f.endsWith('.aip'))
304
321
  .map(f => fs.readFileSync(path.join(found,f),'utf8')).join('\n---\n')
305
322
  } else {
306
323
  src = fs.readFileSync(found, 'utf8')
307
324
  }
308
325
  }
309
- const dest = path.join(TEMPLATES_DIR, tname + '.aiplang')
326
+ const dest = path.join(TEMPLATES_DIR, tname + '.aip')
310
327
  fs.writeFileSync(dest, src)
311
328
  console.log(`\n ✓ Template saved: ${tname}\n ${dest}\n\n Use it: aiplang init my-app --template ${tname}\n`)
312
329
  process.exit(0)
@@ -316,7 +333,7 @@ if (cmd === 'template') {
316
333
  if (sub === 'remove' || sub === 'rm' || sub === 'delete') {
317
334
  const tname = args[1]
318
335
  if (!tname) { console.error('\n ✗ Usage: aiplang template remove <name>\n'); process.exit(1) }
319
- const dest = path.join(TEMPLATES_DIR, tname + '.aiplang')
336
+ const dest = path.join(TEMPLATES_DIR, tname + '.aip')
320
337
  if (!fs.existsSync(dest)) { console.error(`\n ✗ Template "${tname}" not found.\n`); process.exit(1) }
321
338
  fs.unlinkSync(dest)
322
339
  console.log(`\n ✓ Removed template: ${tname}\n`); process.exit(0)
@@ -326,7 +343,7 @@ if (cmd === 'template') {
326
343
  if (sub === 'edit' || sub === 'open') {
327
344
  const tname = args[1]
328
345
  if (!tname) { console.error('\n ✗ Usage: aiplang template edit <name>\n'); process.exit(1) }
329
- let dest = path.join(TEMPLATES_DIR, tname + '.aiplang')
346
+ let dest = path.join(TEMPLATES_DIR, tname + '.aip')
330
347
  if (!fs.existsSync(dest)) {
331
348
  // create from built-in if exists
332
349
  const builtin = BUILTIN_TEMPLATES[tname]
@@ -342,7 +359,7 @@ if (cmd === 'template') {
342
359
  // aiplang template show <name>
343
360
  if (sub === 'show' || sub === 'cat') {
344
361
  const tname = args[1] || 'default'
345
- const customPath = path.join(TEMPLATES_DIR, tname + '.aiplang')
362
+ const customPath = path.join(TEMPLATES_DIR, tname + '.aip')
346
363
  if (fs.existsSync(customPath)) { console.log(fs.readFileSync(customPath,'utf8')); process.exit(0) }
347
364
  const builtin = BUILTIN_TEMPLATES[tname]
348
365
  if (builtin) { console.log(builtin); process.exit(0) }
@@ -354,8 +371,8 @@ if (cmd === 'template') {
354
371
  const tname = args[1]
355
372
  if (!tname) { console.error('\n ✗ Usage: aiplang template export <name>\n'); process.exit(1) }
356
373
  const outIdx = args.indexOf('--out')
357
- const outFile = outIdx !== -1 ? args[outIdx+1] : `./${tname}.aiplang`
358
- const customPath = path.join(TEMPLATES_DIR, tname + '.aiplang')
374
+ const outFile = outIdx !== -1 ? args[outIdx+1] : `./${tname}.aip`
375
+ const customPath = path.join(TEMPLATES_DIR, tname + '.aip')
359
376
  const src = fs.existsSync(customPath) ? fs.readFileSync(customPath,'utf8') : BUILTIN_TEMPLATES[tname]
360
377
  if (!src) { console.error(`\n ✗ Template "${tname}" not found.\n`); process.exit(1) }
361
378
  fs.writeFileSync(outFile, src)
@@ -384,21 +401,21 @@ if (cmd==='init') {
384
401
  const isMultiFile = tplSrc.includes('\n---\n')
385
402
 
386
403
  if (isFullStack) {
387
- // Full-stack project: single app.aiplang
404
+ // Full-stack project: single app.aip
388
405
  fs.mkdirSync(dir, { recursive: true })
389
- fs.writeFileSync(path.join(dir, 'app.aiplang'), tplSrc)
406
+ fs.writeFileSync(path.join(dir, 'app.aip'), tplSrc)
390
407
  fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({
391
408
  name, version:'0.1.0',
392
- scripts: { dev: 'npx aiplang start app.aiplang', start: 'npx aiplang start app.aiplang' },
409
+ scripts: { dev: 'npx aiplang start app.aip', start: 'npx aiplang start app.aip' },
393
410
  devDependencies: { 'aiplang': `^${VERSION}` }
394
411
  }, null, 2))
395
412
  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
413
  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`)
414
+ 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.aip\n\`\`\`\n`)
398
415
  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`)
416
+ console.log(`\n ✓ Created ${name}/${label}\n\n app.aip ← full-stack app (backend + frontend)\n\n Next:\n cd ${name} && npx aiplang start app.aip\n`)
400
417
  } else if (isMultiFile) {
401
- // Multi-page SSG project: pages/*.aiplang
418
+ // Multi-page SSG project: pages/*.aip
402
419
  fs.mkdirSync(path.join(dir,'pages'), {recursive:true})
403
420
  fs.mkdirSync(path.join(dir,'public'), {recursive:true})
404
421
  for (const f of ['aiplang-runtime.js','aiplang-hydrate.js']) {
@@ -408,7 +425,7 @@ if (cmd==='init') {
408
425
  pageBlocks.forEach((block, i) => {
409
426
  const m = block.match(/^%([a-zA-Z0-9_-]+)/m)
410
427
  const pageName = m ? m[1] : (i === 0 ? 'home' : `page${i}`)
411
- fs.writeFileSync(path.join(dir,'pages',`${pageName}.aiplang`), block.trim())
428
+ fs.writeFileSync(path.join(dir,'pages',`${pageName}.aip`), block.trim())
412
429
  })
413
430
  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
431
  fs.writeFileSync(path.join(dir,'.gitignore'),'dist/\nnode_modules/\n')
@@ -422,11 +439,11 @@ if (cmd==='init') {
422
439
  for (const f of ['aiplang-runtime.js','aiplang-hydrate.js']) {
423
440
  const src=path.join(RUNTIME_DIR,f); if(fs.existsSync(src)) fs.copyFileSync(src,path.join(dir,'public',f))
424
441
  }
425
- fs.writeFileSync(path.join(dir,'pages','home.aiplang'), tplSrc)
442
+ fs.writeFileSync(path.join(dir,'pages','home.aip'), tplSrc)
426
443
  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
444
  fs.writeFileSync(path.join(dir,'.gitignore'),'dist/\nnode_modules/\n')
428
445
  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`)
446
+ console.log(`\n ✓ Created ${name}/${label}\n\n pages/home.aip ← edit this\n\n Next:\n cd ${name} && npx aiplang serve\n`)
430
447
  }
431
448
  process.exit(0)
432
449
  }
@@ -435,7 +452,7 @@ if (cmd==='init') {
435
452
  if (cmd==='new') {
436
453
  const name=args[0]; if(!name){console.error('\n ✗ Usage: aiplang new <page>\n');process.exit(1)}
437
454
  const dir=fs.existsSync('pages')?'pages':'.'
438
- const file=path.join(dir,`${name}.aiplang`)
455
+ const file=path.join(dir,`${name}.aip`)
439
456
  if(fs.existsSync(file)){console.error(`\n ✗ ${file} exists.\n`);process.exit(1)}
440
457
  const cap=name.charAt(0).toUpperCase()+name.slice(1)
441
458
  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 +467,9 @@ if (cmd==='build') {
450
467
  const input=args.filter((a,i)=>!a.startsWith('--')&&i!==outIdx+1)[0]||'pages/'
451
468
  const files=[]
452
469
  if(fs.existsSync(input)&&fs.statSync(input).isDirectory()){
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)}
470
+ fs.readdirSync(input).filter(f=>f.endsWith('.aip')).forEach(f=>files.push(path.join(input,f)))
471
+ } else if(input.endsWith('.aip')&&fs.existsSync(input)){ files.push(input) }
472
+ if(!files.length){console.error(`\n ✗ No .aip files in: ${input}\n`);process.exit(1)}
456
473
  const src=files.map(f=>fs.readFileSync(f,'utf8')).join('\n---\n')
457
474
  const pages=parsePages(src)
458
475
  if(!pages.length){console.error('\n ✗ No pages found.\n');process.exit(1)}
@@ -471,7 +488,7 @@ if (cmd==='build') {
471
488
  }
472
489
  const hf=path.join(RUNTIME_DIR,'aiplang-hydrate.js')
473
490
  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('.aiplang')).forEach(f=>fs.copyFileSync(path.join('public',f),path.join(outDir,f)))
491
+ if(fs.existsSync('public'))fs.readdirSync('public').filter(f=>!f.endsWith('.aip')).forEach(f=>fs.copyFileSync(path.join('public',f),path.join(outDir,f)))
475
492
  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
493
  process.exit(0)
477
494
  }
@@ -480,13 +497,13 @@ if (cmd==='build') {
480
497
  if (cmd==='serve'||cmd==='dev') {
481
498
  const root=path.resolve(args[0]||'.')
482
499
  const port=parseInt(process.env.PORT||'3000')
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'}
500
+ const MIME={'.html':'text/html;charset=utf-8','.js':'application/javascript','.css':'text/css','.aip':'text/plain','.json':'application/json','.wasm':'application/wasm','.svg':'image/svg+xml','.png':'image/png','.jpg':'image/jpeg','.ico':'image/x-icon'}
484
501
  let clients=[]
485
502
  const mtimes={}
486
503
  setInterval(()=>{
487
504
  const pd=path.join(root,'pages')
488
505
  if(!fs.existsSync(pd))return
489
- fs.readdirSync(pd).filter(f=>f.endsWith('.aiplang')).forEach(f=>{
506
+ fs.readdirSync(pd).filter(f=>f.endsWith('.aip')).forEach(f=>{
490
507
  const fp=path.join(pd,f),mt=fs.statSync(fp).mtimeMs
491
508
  if(mtimes[fp]&&mtimes[fp]!==mt)clients.forEach(c=>{try{c.write('data: reload\n\n')}catch{}})
492
509
  mtimes[fp]=mt
@@ -501,7 +518,7 @@ if (cmd==='serve'||cmd==='dev') {
501
518
  let p=req.url.split('?')[0];if(p==='/') p='/index.html'
502
519
  let fp=null
503
520
  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('.aiplang')){const c=path.join(root,'pages',path.basename(p));if(fs.existsSync(c))fp=c}
521
+ if(!fp&&p.endsWith('.aip')){const c=path.join(root,'pages',path.basename(p));if(fs.existsSync(c))fp=c}
505
522
  if(!fp){res.writeHead(404);res.end('Not found');return}
506
523
  let content=fs.readFileSync(fp)
507
524
  if(path.extname(fp)==='.html'){
@@ -510,7 +527,7 @@ if (cmd==='serve'||cmd==='dev') {
510
527
  }
511
528
  res.writeHead(200,{'Content-Type':MIME[path.extname(fp)]||'application/octet-stream','Access-Control-Allow-Origin':'*'})
512
529
  res.end(content)
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`))
530
+ }).listen(port,()=>console.log(`\n ✓ aiplang dev server\n\n → http://localhost:${port}\n\n Hot reload ON — edit .aip files and browser refreshes.\n Ctrl+C to stop.\n`))
514
531
  return
515
532
  }
516
533
 
@@ -518,7 +535,7 @@ if (cmd==='serve'||cmd==='dev') {
518
535
  if (cmd === 'start' || cmd === 'run') {
519
536
  const aipFile = args[0]
520
537
  if (!aipFile || !fs.existsSync(aipFile)) {
521
- console.error(`\n ✗ Usage: aiplang start <app.aiplang>\n`)
538
+ console.error(`\n ✗ Usage: aiplang start <app.aip>\n`)
522
539
  process.exit(1)
523
540
  }
524
541
  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.6.1",
3
+ "version": "2.7.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",
@@ -954,108 +954,108 @@ html{scroll-behavior:smooth}
954
954
  body{font-family:-apple-system,'Segoe UI',system-ui,sans-serif;-webkit-font-smoothing:antialiased}
955
955
  a{text-decoration:none;color:inherit}
956
956
  input,button,select{font-family:inherit}
957
- .aiplang-root{min-height:100vh}
958
- .aiplang-theme-dark{background:#030712;color:#f1f5f9}
959
- .aiplang-theme-light{background:#fff;color:#0f172a}
960
- .aiplang-theme-acid{background:#000;color:#a3e635}
957
+ .aip-root{min-height:100vh}
958
+ .aip-theme-dark{background:#030712;color:#f1f5f9}
959
+ .aip-theme-light{background:#fff;color:#0f172a}
960
+ .aip-theme-acid{background:#000;color:#a3e635}
961
961
  .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)}
962
- .aiplang-theme-dark .fx-nav{border-bottom:1px solid #1e293b;background:rgba(3,7,18,.85)}
963
- .aiplang-theme-light .fx-nav{border-bottom:1px solid #e2e8f0;background:rgba(255,255,255,.85)}
964
- .aiplang-theme-acid .fx-nav{border-bottom:1px solid #1a2e05;background:rgba(0,0,0,.9)}
962
+ .aip-theme-dark .fx-nav{border-bottom:1px solid #1e293b;background:rgba(3,7,18,.85)}
963
+ .aip-theme-light .fx-nav{border-bottom:1px solid #e2e8f0;background:rgba(255,255,255,.85)}
964
+ .aip-theme-acid .fx-nav{border-bottom:1px solid #1a2e05;background:rgba(0,0,0,.9)}
965
965
  .fx-brand{font-size:1.25rem;font-weight:800;letter-spacing:-.03em}
966
966
  .fx-nav-links{display:flex;align-items:center;gap:1.75rem}
967
967
  .fx-nav-link{font-size:.875rem;font-weight:500;opacity:.65;transition:opacity .15s;cursor:pointer}
968
968
  .fx-nav-link:hover{opacity:1}
969
- .aiplang-theme-dark .fx-nav-link{color:#cbd5e1}
970
- .aiplang-theme-light .fx-nav-link{color:#475569}
971
- .aiplang-theme-acid .fx-nav-link{color:#86efac}
969
+ .aip-theme-dark .fx-nav-link{color:#cbd5e1}
970
+ .aip-theme-light .fx-nav-link{color:#475569}
971
+ .aip-theme-acid .fx-nav-link{color:#86efac}
972
972
  .fx-hero{display:flex;align-items:center;justify-content:center;min-height:92vh;padding:4rem 1.5rem}
973
973
  .fx-hero-inner{max-width:56rem;text-align:center;display:flex;flex-direction:column;align-items:center;gap:1.5rem}
974
974
  .fx-title{font-size:clamp(2.5rem,8vw,5.5rem);font-weight:900;letter-spacing:-.04em;line-height:1}
975
975
  .fx-sub{font-size:clamp(1rem,2vw,1.25rem);line-height:1.75;max-width:40rem}
976
- .aiplang-theme-dark .fx-sub{color:#94a3b8}
977
- .aiplang-theme-light .fx-sub{color:#475569}
976
+ .aip-theme-dark .fx-sub{color:#94a3b8}
977
+ .aip-theme-light .fx-sub{color:#475569}
978
978
  .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}
979
979
  .fx-cta:hover{transform:translateY(-1px)}
980
- .aiplang-theme-dark .fx-cta{background:#2563eb;color:#fff;box-shadow:0 8px 24px rgba(37,99,235,.35)}
981
- .aiplang-theme-light .fx-cta{background:#2563eb;color:#fff}
982
- .aiplang-theme-acid .fx-cta{background:#a3e635;color:#000;font-weight:800}
980
+ .aip-theme-dark .fx-cta{background:#2563eb;color:#fff;box-shadow:0 8px 24px rgba(37,99,235,.35)}
981
+ .aip-theme-light .fx-cta{background:#2563eb;color:#fff}
982
+ .aip-theme-acid .fx-cta{background:#a3e635;color:#000;font-weight:800}
983
983
  .fx-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:3rem;padding:5rem 2.5rem;text-align:center}
984
984
  .fx-stat-val{font-size:clamp(2.5rem,5vw,4rem);font-weight:900;letter-spacing:-.04em;line-height:1}
985
985
  .fx-stat-lbl{font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.1em;margin-top:.5rem}
986
- .aiplang-theme-dark .fx-stat-lbl{color:#64748b}
987
- .aiplang-theme-light .fx-stat-lbl{color:#94a3b8}
986
+ .aip-theme-dark .fx-stat-lbl{color:#64748b}
987
+ .aip-theme-light .fx-stat-lbl{color:#94a3b8}
988
988
  .fx-grid{display:grid;gap:1.25rem;padding:1rem 2.5rem 5rem}
989
989
  .fx-grid-2{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
990
990
  .fx-grid-3{grid-template-columns:repeat(auto-fit,minmax(240px,1fr))}
991
991
  .fx-grid-4{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}
992
992
  .fx-card{border-radius:1rem;padding:1.75rem;transition:transform .2s,box-shadow .2s}
993
993
  .fx-card:hover{transform:translateY(-2px)}
994
- .aiplang-theme-dark .fx-card{background:#0f172a;border:1px solid #1e293b}
995
- .aiplang-theme-light .fx-card{background:#f8fafc;border:1px solid #e2e8f0}
996
- .aiplang-theme-acid .fx-card{background:#0a0f00;border:1px solid #1a2e05}
997
- .aiplang-theme-dark .fx-card:hover{box-shadow:0 20px 40px rgba(0,0,0,.5)}
998
- .aiplang-theme-light .fx-card:hover{box-shadow:0 20px 40px rgba(0,0,0,.08)}
994
+ .aip-theme-dark .fx-card{background:#0f172a;border:1px solid #1e293b}
995
+ .aip-theme-light .fx-card{background:#f8fafc;border:1px solid #e2e8f0}
996
+ .aip-theme-acid .fx-card{background:#0a0f00;border:1px solid #1a2e05}
997
+ .aip-theme-dark .fx-card:hover{box-shadow:0 20px 40px rgba(0,0,0,.5)}
998
+ .aip-theme-light .fx-card:hover{box-shadow:0 20px 40px rgba(0,0,0,.08)}
999
999
  .fx-icon{font-size:2rem;margin-bottom:1rem}
1000
1000
  .fx-card-title{font-size:1.0625rem;font-weight:700;letter-spacing:-.02em;margin-bottom:.5rem}
1001
1001
  .fx-card-body{font-size:.875rem;line-height:1.65}
1002
- .aiplang-theme-dark .fx-card-body{color:#64748b}
1003
- .aiplang-theme-light .fx-card-body{color:#475569}
1002
+ .aip-theme-dark .fx-card-body{color:#64748b}
1003
+ .aip-theme-light .fx-card-body{color:#475569}
1004
1004
  .fx-card-link{font-size:.8125rem;font-weight:600;display:inline-block;margin-top:1rem;opacity:.6;transition:opacity .15s}
1005
1005
  .fx-card-link:hover{opacity:1}
1006
1006
  .fx-sect{padding:5rem 2.5rem}
1007
1007
  .fx-sect-title{font-size:clamp(1.75rem,4vw,3rem);font-weight:800;letter-spacing:-.04em;margin-bottom:1.5rem;text-align:center}
1008
1008
  .fx-sect-body{font-size:1rem;line-height:1.75;text-align:center;max-width:48rem;margin:0 auto}
1009
- .aiplang-theme-dark .fx-sect-body{color:#64748b}
1009
+ .aip-theme-dark .fx-sect-body{color:#64748b}
1010
1010
  .fx-form-wrap{padding:3rem 2.5rem;display:flex;justify-content:center}
1011
1011
  .fx-form{width:100%;max-width:28rem;border-radius:1.25rem;padding:2.5rem}
1012
- .aiplang-theme-dark .fx-form{background:#0f172a;border:1px solid #1e293b}
1013
- .aiplang-theme-light .fx-form{background:#f8fafc;border:1px solid #e2e8f0}
1012
+ .aip-theme-dark .fx-form{background:#0f172a;border:1px solid #1e293b}
1013
+ .aip-theme-light .fx-form{background:#f8fafc;border:1px solid #e2e8f0}
1014
1014
  .fx-field{margin-bottom:1.25rem}
1015
1015
  .fx-label{display:block;font-size:.8125rem;font-weight:600;margin-bottom:.5rem}
1016
- .aiplang-theme-dark .fx-label{color:#94a3b8}
1017
- .aiplang-theme-light .fx-label{color:#475569}
1016
+ .aip-theme-dark .fx-label{color:#94a3b8}
1017
+ .aip-theme-light .fx-label{color:#475569}
1018
1018
  .fx-input{width:100%;padding:.75rem 1rem;border-radius:.625rem;font-size:.9375rem;outline:none;transition:box-shadow .15s;background:transparent}
1019
1019
  .fx-input:focus{box-shadow:0 0 0 3px rgba(37,99,235,.35)}
1020
- .aiplang-theme-dark .fx-input{background:#020617;border:1px solid #1e293b;color:#f1f5f9}
1021
- .aiplang-theme-dark .fx-input::placeholder{color:#334155}
1022
- .aiplang-theme-light .fx-input{background:#fff;border:1px solid #cbd5e1;color:#0f172a}
1023
- .aiplang-theme-acid .fx-input{background:#000;border:1px solid #1a2e05;color:#a3e635}
1020
+ .aip-theme-dark .fx-input{background:#020617;border:1px solid #1e293b;color:#f1f5f9}
1021
+ .aip-theme-dark .fx-input::placeholder{color:#334155}
1022
+ .aip-theme-light .fx-input{background:#fff;border:1px solid #cbd5e1;color:#0f172a}
1023
+ .aip-theme-acid .fx-input{background:#000;border:1px solid #1a2e05;color:#a3e635}
1024
1024
  .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}
1025
1025
  .fx-btn:hover{transform:translateY(-1px)}
1026
1026
  .fx-btn:disabled{opacity:.5;cursor:not-allowed;transform:none}
1027
- .aiplang-theme-dark .fx-btn{background:#2563eb;color:#fff;box-shadow:0 4px 14px rgba(37,99,235,.4)}
1028
- .aiplang-theme-light .fx-btn{background:#2563eb;color:#fff}
1029
- .aiplang-theme-acid .fx-btn{background:#a3e635;color:#000;font-weight:800}
1027
+ .aip-theme-dark .fx-btn{background:#2563eb;color:#fff;box-shadow:0 4px 14px rgba(37,99,235,.4)}
1028
+ .aip-theme-light .fx-btn{background:#2563eb;color:#fff}
1029
+ .aip-theme-acid .fx-btn{background:#a3e635;color:#000;font-weight:800}
1030
1030
  .fx-form-msg{font-size:.8125rem;padding:.5rem 0;min-height:1.5rem;text-align:center}
1031
1031
  .fx-form-err{color:#f87171}
1032
1032
  .fx-form-ok{color:#4ade80}
1033
1033
  .fx-table-wrap{overflow-x:auto;padding:0 2.5rem 4rem}
1034
1034
  .fx-table{width:100%;border-collapse:collapse;font-size:.875rem}
1035
1035
  .fx-th{text-align:left;padding:.875rem 1.25rem;font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em}
1036
- .aiplang-theme-dark .fx-th{color:#475569;border-bottom:1px solid #1e293b}
1037
- .aiplang-theme-light .fx-th{color:#94a3b8;border-bottom:1px solid #e2e8f0}
1036
+ .aip-theme-dark .fx-th{color:#475569;border-bottom:1px solid #1e293b}
1037
+ .aip-theme-light .fx-th{color:#94a3b8;border-bottom:1px solid #e2e8f0}
1038
1038
  .fx-tr{transition:background .1s}
1039
1039
  .fx-td{padding:.875rem 1.25rem}
1040
- .aiplang-theme-dark .fx-tr:hover{background:#0f172a}
1041
- .aiplang-theme-light .fx-tr:hover{background:#f8fafc}
1042
- .aiplang-theme-dark .fx-td{border-bottom:1px solid #0f172a}
1043
- .aiplang-theme-light .fx-td{border-bottom:1px solid #f1f5f9}
1040
+ .aip-theme-dark .fx-tr:hover{background:#0f172a}
1041
+ .aip-theme-light .fx-tr:hover{background:#f8fafc}
1042
+ .aip-theme-dark .fx-td{border-bottom:1px solid #0f172a}
1043
+ .aip-theme-light .fx-td{border-bottom:1px solid #f1f5f9}
1044
1044
  .fx-td-empty{padding:2rem 1.25rem;text-align:center;opacity:.4}
1045
1045
  .fx-list-wrap{padding:1rem 2.5rem 4rem;display:flex;flex-direction:column;gap:.75rem}
1046
1046
  .fx-list-item{border-radius:.75rem;padding:1.25rem 1.5rem}
1047
- .aiplang-theme-dark .fx-list-item{background:#0f172a;border:1px solid #1e293b}
1047
+ .aip-theme-dark .fx-list-item{background:#0f172a;border:1px solid #1e293b}
1048
1048
  .fx-list-field{font-size:.9375rem;line-height:1.5}
1049
1049
  .fx-list-link{font-size:.8125rem;font-weight:600;opacity:.6;transition:opacity .15s}
1050
1050
  .fx-list-link:hover{opacity:1}
1051
1051
  .fx-alert{padding:1rem 2.5rem;font-size:.9375rem;font-weight:500;border-radius:.75rem;margin:1rem 2.5rem}
1052
- .aiplang-theme-dark .fx-alert{background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.3);color:#fca5a5}
1052
+ .aip-theme-dark .fx-alert{background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.3);color:#fca5a5}
1053
1053
  .fx-if-wrap{display:contents}
1054
1054
  .fx-footer{padding:3rem 2.5rem;text-align:center}
1055
- .aiplang-theme-dark .fx-footer{border-top:1px solid #1e293b}
1056
- .aiplang-theme-light .fx-footer{border-top:1px solid #e2e8f0}
1055
+ .aip-theme-dark .fx-footer{border-top:1px solid #1e293b}
1056
+ .aip-theme-light .fx-footer{border-top:1px solid #e2e8f0}
1057
1057
  .fx-footer-text{font-size:.8125rem}
1058
- .aiplang-theme-dark .fx-footer-text{color:#334155}
1058
+ .aip-theme-dark .fx-footer-text{color:#334155}
1059
1059
  .fx-footer-link{font-size:.8125rem;margin:0 .75rem;opacity:.5;transition:opacity .15s}
1060
1060
  .fx-footer-link:hover{opacity:1}
1061
1061
  `
package/server/server.js CHANGED
@@ -1212,7 +1212,7 @@ function getMime(filename) {
1212
1212
  // name: 'my-plugin',
1213
1213
  // setup(server, app, utils) {
1214
1214
  // // server = AiplangServer instance (.addRoute, .models)
1215
- // // app = parsed .aiplang app definition
1215
+ // // app = parsed .aip app definition
1216
1216
  // // utils = { uuid, now, emit, on, dispatch, resolveEnv, dbRun, dbAll, dbGet }
1217
1217
  // }
1218
1218
  // }
@@ -1435,7 +1435,7 @@ async function startServer(aipFile, port = 3000) {
1435
1435
 
1436
1436
  // Health
1437
1437
  srv.addRoute('GET', '/health', (req, res) => res.json(200, {
1438
- status:'ok', version:'2.6.1',
1438
+ status:'ok', version:'2.7.0',
1439
1439
  models: app.models.map(m=>m.name),
1440
1440
  routes: app.apis.length, pages: app.pages.length,
1441
1441
  admin: app.admin?.prefix || null,
@@ -1462,7 +1462,7 @@ async function startServer(aipFile, port = 3000) {
1462
1462
  module.exports = { startServer, parseApp, Model, getDB, dispatch, on, emit, sendMail, setupStripe, registerStripeRoutes, setupS3, registerS3Routes, s3Upload, s3Delete, s3PresignedUrl, PLUGIN_UTILS }
1463
1463
  if (require.main === module) {
1464
1464
  const f=process.argv[2], p=parseInt(process.argv[3]||process.env.PORT||'3000')
1465
- if (!f) { console.error('Usage: node server.js <app.aiplang> [port]'); process.exit(1) }
1465
+ if (!f) { console.error('Usage: node server.js <app.aip> [port]'); process.exit(1) }
1466
1466
  startServer(f, p).catch(e=>{console.error(e);process.exit(1)})
1467
1467
  }
1468
1468