naider 1.21.0 → 1.21.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/bin/naide.js +50 -13
- package/mcp/server.js +1 -1
- package/package.json +1 -1
- package/src/gen.js +194 -0
- package/src/index.js +2 -2
package/bin/naide.js
CHANGED
|
@@ -8,7 +8,7 @@ import { spawn } from 'child_process';
|
|
|
8
8
|
import { createRequire } from 'module';
|
|
9
9
|
const _require = createRequire(import.meta.url);
|
|
10
10
|
|
|
11
|
-
const NAIDE_VERSION = '1.21.
|
|
11
|
+
const NAIDE_VERSION = '1.21.1';
|
|
12
12
|
|
|
13
13
|
function crashReport(err, context = {}) {
|
|
14
14
|
const info = [
|
|
@@ -171,7 +171,13 @@ if (files[0] === 'gen' || files[0] === '~~') {
|
|
|
171
171
|
Project Mode (--project):
|
|
172
172
|
naide ~~ "e-commerce app with auth" --project
|
|
173
173
|
# Creates: app.naide, package.json, .env, .gitignore,
|
|
174
|
-
# Dockerfile, .dockerignore, README.md
|
|
174
|
+
# Dockerfile, .dockerignore, README.md, openapi.json,
|
|
175
|
+
# admin.html, client.mjs
|
|
176
|
+
|
|
177
|
+
Update existing project (re-run --project in same dir):
|
|
178
|
+
naide ~~ "add users with auth" --project -o my-app
|
|
179
|
+
naide ~~ "add products" --project -o my-app
|
|
180
|
+
# Merges new entities, regenerates all supporting files
|
|
175
181
|
|
|
176
182
|
File Write Mode:
|
|
177
183
|
naide ~~ "add auth" app.naide # merge into existing file
|
|
@@ -213,7 +219,7 @@ if (files[0] === 'gen' || files[0] === '~~') {
|
|
|
213
219
|
process.exit(0);
|
|
214
220
|
}
|
|
215
221
|
|
|
216
|
-
const { generate, generateProject, writeToFile } = await import('../src/gen.js');
|
|
222
|
+
const { generate, generateProject, updateProject, writeToFile } = await import('../src/gen.js');
|
|
217
223
|
|
|
218
224
|
// Write mode: inject into existing .naide/.nx file
|
|
219
225
|
const targetFile = instruction.match(/\S+\.(?:naide|nx)$/)?.[0];
|
|
@@ -235,32 +241,63 @@ if (files[0] === 'gen' || files[0] === '~~') {
|
|
|
235
241
|
process.exit(0);
|
|
236
242
|
}
|
|
237
243
|
|
|
238
|
-
// Project mode: generate
|
|
244
|
+
// Project mode: generate or update project directory
|
|
239
245
|
if (flags.project) {
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
246
|
+
const isCurrentDir = instruction === '.' || instruction === './';
|
|
247
|
+
let dir, result;
|
|
248
|
+
|
|
249
|
+
if (isCurrentDir) {
|
|
250
|
+
// naide gen . --project → update current directory
|
|
251
|
+
dir = '.';
|
|
252
|
+
const outDir = resolve(dir);
|
|
253
|
+
if (!existsSync(resolve(outDir, 'app.naide'))) {
|
|
254
|
+
process.stderr.write(' Error: no app.naide found in current directory.\n');
|
|
255
|
+
process.stderr.write(' Use naide ~~ "instruction" --project to create a new project.\n');
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
process.stderr.write(' Error: use naide ~~ "instruction" --project in an existing project dir to update.\n');
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const primaryGuess = instruction.split(/\s+/)[0].toLowerCase().replace(/[^a-z0-9]/g, '') || 'app';
|
|
263
|
+
dir = flags.output || (primaryGuess + '-app');
|
|
243
264
|
const outDir = resolve(dir);
|
|
244
|
-
|
|
265
|
+
const isUpdate = existsSync(resolve(outDir, 'app.naide'));
|
|
266
|
+
|
|
267
|
+
if (isUpdate) {
|
|
268
|
+
result = updateProject(outDir, instruction);
|
|
269
|
+
} else {
|
|
270
|
+
result = generateProject(instruction);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const primaryName = (result.entities[0] || 'app').toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
274
|
+
if (!flags.output && !isUpdate) dir = primaryName + '-app';
|
|
275
|
+
const finalDir = resolve(dir);
|
|
276
|
+
if (!existsSync(finalDir)) mkdirSync(finalDir, { recursive: true });
|
|
245
277
|
for (const f of result.files) {
|
|
246
|
-
const fp = resolve(
|
|
278
|
+
const fp = resolve(finalDir, f.path);
|
|
247
279
|
writeFileSync(fp, f.content, 'utf-8');
|
|
248
280
|
}
|
|
249
281
|
const a = result.analysis;
|
|
250
|
-
|
|
282
|
+
const mode = isUpdate ? 'Updated' : 'Created';
|
|
283
|
+
process.stderr.write(`\n ── NAIDE Agent (${mode}) ────────────────────────────────\n`);
|
|
251
284
|
process.stderr.write(` Input: "${a.instruction}"\n`);
|
|
252
285
|
if (a.entities.length > 0) {
|
|
253
286
|
process.stderr.write(` Entities: ${a.entities.map(e => `${e.name} (${e.fieldCount} fields)`).join(', ')}\n`);
|
|
254
287
|
}
|
|
255
|
-
if (a.relationships.length > 0) {
|
|
288
|
+
if (a.relationships && a.relationships.length > 0) {
|
|
256
289
|
for (const r of a.relationships) process.stderr.write(` Relation: ${r.parent} 1→N ${r.child} (${r.fk})\n`);
|
|
257
290
|
}
|
|
291
|
+
if (a.added && a.added.length > 0) {
|
|
292
|
+
process.stderr.write(` Added: ${a.added.join(', ')}\n`);
|
|
293
|
+
}
|
|
258
294
|
process.stderr.write(` Plan:\n`);
|
|
259
295
|
for (const step of a.plan) process.stderr.write(` + ${step}\n`);
|
|
260
296
|
process.stderr.write(' ────────────────────────────────────────────────────────\n\n');
|
|
261
|
-
console.log(` Project created: ${dir}/`);
|
|
297
|
+
console.log(` Project ${isUpdate ? 'updated' : 'created'}: ${dir}/`);
|
|
262
298
|
for (const f of result.files) console.log(` ${f.path}`);
|
|
263
|
-
console.log(`\n cd ${dir} && npm install && npm run dev\n`);
|
|
299
|
+
if (!isUpdate) console.log(`\n cd ${dir} && npm install && npm run dev\n`);
|
|
300
|
+
else console.log(`\n Files regenerated. Run: npm run dev\n`);
|
|
264
301
|
process.exit(0);
|
|
265
302
|
}
|
|
266
303
|
|
package/mcp/server.js
CHANGED
|
@@ -192,7 +192,7 @@ async function handleMessage(msg) {
|
|
|
192
192
|
respond(msg.id, {
|
|
193
193
|
protocolVersion: '2024-11-05',
|
|
194
194
|
capabilities: { tools: {} },
|
|
195
|
-
serverInfo: { name: 'naide-mcp', version: '1.21.
|
|
195
|
+
serverInfo: { name: 'naide-mcp', version: '1.21.1' }
|
|
196
196
|
});
|
|
197
197
|
break;
|
|
198
198
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naider",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.1",
|
|
4
4
|
"description": "NAIDE - Simpler than Python, compiles to 15 targets. AI-specialized language with 35+ built-in functions, syntax sugar (unless/until/repeat/swap/is/isnt), and 47 features. Targets: Node.js, Python, TypeScript, C, C++, Java, Go, Rust, PHP, Ruby, Kotlin, Swift, Dart, C#, Bun.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"exports": {
|
package/src/gen.js
CHANGED
|
@@ -1479,6 +1479,200 @@ CMD ["node", "dist/app.mjs"]
|
|
|
1479
1479
|
return { ...result, files };
|
|
1480
1480
|
}
|
|
1481
1481
|
|
|
1482
|
+
// ── Project Update Mode ───────────────────────────────────
|
|
1483
|
+
|
|
1484
|
+
export function updateProject(projectDir, instruction, options = {}) {
|
|
1485
|
+
const appPath = projectDir + '/app.naide';
|
|
1486
|
+
const existing = _existsFS(appPath) ? _readFS(appPath, 'utf-8') : '';
|
|
1487
|
+
|
|
1488
|
+
const existingState = existing ? parseExistingProject(existing) : { entities: [], intents: [], port: 3000 };
|
|
1489
|
+
const newResult = generate(instruction, options);
|
|
1490
|
+
const newEntities = (newResult.entities || []).filter(e => !existingState.entities.includes(e));
|
|
1491
|
+
|
|
1492
|
+
const allEntities = [...existingState.entities, ...newEntities];
|
|
1493
|
+
const combinedIntents = [...new Set([...existingState.intents, ...newResult.intents])];
|
|
1494
|
+
const extras = [];
|
|
1495
|
+
if (combinedIntents.includes('auth')) extras.push('auth');
|
|
1496
|
+
if (combinedIntents.includes('websocket')) extras.push('websocket');
|
|
1497
|
+
if (combinedIntents.includes('database')) extras.push('database');
|
|
1498
|
+
const fullInstruction = 'REST API for ' + allEntities.join(' and ') + (extras.length ? ' with ' + extras.join(' and ') : '');
|
|
1499
|
+
|
|
1500
|
+
const combined = generate(fullInstruction, options);
|
|
1501
|
+
const finalCode = combined.code;
|
|
1502
|
+
|
|
1503
|
+
const files = [];
|
|
1504
|
+
files.push({ path: 'app.naide', content: finalCode });
|
|
1505
|
+
|
|
1506
|
+
const intents = new Set(combined.intents);
|
|
1507
|
+
const entities = combined.entities || allEntities;
|
|
1508
|
+
const relationships = combined.relationships || detectRelationships(entities);
|
|
1509
|
+
|
|
1510
|
+
const primaryName = (entities[0] || 'app').toLowerCase();
|
|
1511
|
+
const projectName = safeName(primaryName) + '-app';
|
|
1512
|
+
const port = existingState.port || combined.port || 3000;
|
|
1513
|
+
|
|
1514
|
+
const deps = {};
|
|
1515
|
+
if (intents.has('server')) deps.express = '^4.21.0';
|
|
1516
|
+
if (intents.has('websocket')) deps.ws = '^8.18.0';
|
|
1517
|
+
if (intents.has('auth')) {
|
|
1518
|
+
deps.jsonwebtoken = '^9.0.2';
|
|
1519
|
+
deps.bcryptjs = '^2.4.3';
|
|
1520
|
+
}
|
|
1521
|
+
if (intents.has('mail')) deps.nodemailer = '^6.9.0';
|
|
1522
|
+
if (entities.length > 0) deps.naider = '^1.21.0';
|
|
1523
|
+
|
|
1524
|
+
const existingPkgPath = projectDir + '/package.json';
|
|
1525
|
+
let pkg;
|
|
1526
|
+
if (_existsFS(existingPkgPath)) {
|
|
1527
|
+
pkg = JSON.parse(_readFS(existingPkgPath, 'utf-8'));
|
|
1528
|
+
pkg.dependencies = { ...pkg.dependencies, ...deps };
|
|
1529
|
+
} else {
|
|
1530
|
+
pkg = {
|
|
1531
|
+
name: projectName, version: '1.0.0', type: 'module',
|
|
1532
|
+
scripts: { dev: 'naide app.naide', build: 'naide app.naide --emit -o dist/app.mjs',
|
|
1533
|
+
start: 'node dist/app.mjs', test: 'naide test app.naide', seed: 'naide app.naide --run-seed' },
|
|
1534
|
+
dependencies: deps,
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
files.push({ path: 'package.json', content: JSON.stringify(pkg, null, 2) + '\n' });
|
|
1538
|
+
|
|
1539
|
+
const envL = [];
|
|
1540
|
+
envL.push(`PORT=${port}`);
|
|
1541
|
+
if (intents.has('auth')) envL.push('JWT_SECRET=change-me-in-production');
|
|
1542
|
+
if (intents.has('bot')) envL.push(`${(options.platform || 'DISCORD').toUpperCase()}_TOKEN=your-bot-token`);
|
|
1543
|
+
if (intents.has('mail')) { envL.push('SMTP_HOST=smtp.example.com'); envL.push('SMTP_USER=user@example.com'); envL.push('SMTP_PASS=password'); }
|
|
1544
|
+
if (intents.has('ai')) envL.push('OPENAI_API_KEY=your-api-key');
|
|
1545
|
+
const existingEnvPath = projectDir + '/.env';
|
|
1546
|
+
if (_existsFS(existingEnvPath)) {
|
|
1547
|
+
const existingEnv = _readFS(existingEnvPath, 'utf-8');
|
|
1548
|
+
const existingKeys = new Set(existingEnv.split('\n').map(l => l.split('=')[0]).filter(Boolean));
|
|
1549
|
+
const newEnvLines = envL.filter(l => !existingKeys.has(l.split('=')[0]));
|
|
1550
|
+
if (newEnvLines.length > 0) {
|
|
1551
|
+
files.push({ path: '.env', content: existingEnv.trimEnd() + '\n' + newEnvLines.join('\n') + '\n' });
|
|
1552
|
+
}
|
|
1553
|
+
} else {
|
|
1554
|
+
files.push({ path: '.env', content: envL.join('\n') + '\n' });
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
files.push({ path: '.gitignore', content: 'node_modules/\ndist/\ndata/\n.env\n*.mjs\n' });
|
|
1558
|
+
|
|
1559
|
+
if (intents.has('server')) {
|
|
1560
|
+
files.push({ path: 'Dockerfile', content: `FROM node:22-slim\nWORKDIR /app\nCOPY package*.json ./\nRUN npm ci --omit=dev\nRUN npm install -g naider\nCOPY . .\nRUN naide app.naide --emit -o dist/app.mjs\nEXPOSE ${port}\nCMD ["node", "dist/app.mjs"]\n` });
|
|
1561
|
+
files.push({ path: '.dockerignore', content: 'node_modules\ndist\ndata\n.env\n.git\n' });
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
const readmeLines = [`# ${projectName}\n`];
|
|
1565
|
+
readmeLines.push(`Generated by NAIDE Agent Coder (~~)\n`);
|
|
1566
|
+
readmeLines.push(`## Entities\n`);
|
|
1567
|
+
for (const e of entities) {
|
|
1568
|
+
const preset = SCHEMA_PRESETS[e.toLowerCase()];
|
|
1569
|
+
const fc = preset ? preset.length : 3;
|
|
1570
|
+
readmeLines.push(`- **${e}** (${fc} fields)`);
|
|
1571
|
+
}
|
|
1572
|
+
if (relationships.length > 0) {
|
|
1573
|
+
readmeLines.push(`\n## Relationships\n`);
|
|
1574
|
+
for (const r of relationships) readmeLines.push(`- ${r.parent} → ${r.child} (${r.fk})`);
|
|
1575
|
+
}
|
|
1576
|
+
readmeLines.push(`\n## Quick Start\n`);
|
|
1577
|
+
readmeLines.push('```bash\nnpm install\nnpm run dev\n```\n');
|
|
1578
|
+
if (intents.has('server')) {
|
|
1579
|
+
readmeLines.push(`## API Endpoints\n`);
|
|
1580
|
+
readmeLines.push(`- \`GET /api/health\` — Health check`);
|
|
1581
|
+
for (const e of entities) {
|
|
1582
|
+
const ep = pluralize(e.toLowerCase());
|
|
1583
|
+
readmeLines.push(`- \`GET /api/${ep}?page=1&limit=20&search=\` — List ${ep} (paginated)`);
|
|
1584
|
+
readmeLines.push(`- \`POST /api/${ep}\` — Create ${e.toLowerCase()}`);
|
|
1585
|
+
readmeLines.push(`- \`GET /api/${ep}/:id\` — Get ${e.toLowerCase()}`);
|
|
1586
|
+
readmeLines.push(`- \`PUT /api/${ep}/:id\` — Update ${e.toLowerCase()}`);
|
|
1587
|
+
readmeLines.push(`- \`DELETE /api/${ep}/:id\` — Delete ${e.toLowerCase()}`);
|
|
1588
|
+
readmeLines.push(`- \`DELETE /api/${ep}/:id/soft\` — Soft delete`);
|
|
1589
|
+
readmeLines.push(`- \`PUT /api/${ep}/:id/restore\` — Restore`);
|
|
1590
|
+
readmeLines.push(`- \`POST /api/${ep}/batch\` — Batch create`);
|
|
1591
|
+
readmeLines.push(`- \`DELETE /api/${ep}/batch\` — Batch delete`);
|
|
1592
|
+
}
|
|
1593
|
+
for (const r of relationships) {
|
|
1594
|
+
readmeLines.push(`- \`GET /api/${pluralize(r.parent.toLowerCase())}/:id/${pluralize(r.child.toLowerCase())}\` — ${r.child}s by ${r.parent}`);
|
|
1595
|
+
}
|
|
1596
|
+
readmeLines.push(`- \`POST /api/upload\` — File upload`);
|
|
1597
|
+
readmeLines.push(`- \`POST /api/webhooks\` — Register webhook`);
|
|
1598
|
+
if (intents.has('auth')) {
|
|
1599
|
+
readmeLines.push(`- \`POST /api/auth/register\` — Register`);
|
|
1600
|
+
readmeLines.push(`- \`POST /api/auth/login\` — Login`);
|
|
1601
|
+
readmeLines.push(`- \`GET /api/auth/me\` — Current user`);
|
|
1602
|
+
readmeLines.push(`- \`GET /api/admin/stats\` — Admin stats`);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
if (intents.has('server')) {
|
|
1606
|
+
readmeLines.push(`\n## Docker\n`);
|
|
1607
|
+
readmeLines.push('```bash');
|
|
1608
|
+
readmeLines.push(`docker build -t ${projectName} .`);
|
|
1609
|
+
readmeLines.push(`docker run -p ${port}:${port} --env-file .env ${projectName}`);
|
|
1610
|
+
readmeLines.push('```');
|
|
1611
|
+
}
|
|
1612
|
+
files.push({ path: 'README.md', content: readmeLines.join('\n') + '\n' });
|
|
1613
|
+
|
|
1614
|
+
const entityObjs = entities.map(name => {
|
|
1615
|
+
const preset = SCHEMA_PRESETS[name.toLowerCase()];
|
|
1616
|
+
const fields = preset ? preset.map(f => ({ name: f[0], type: f[1], modifiers: f.slice(2) })) : defaultFields(name);
|
|
1617
|
+
if (!fields.some(f => f.name === 'deleted')) fields.push({ name: 'deleted', type: 'bool', modifiers: [] });
|
|
1618
|
+
return { name, fields };
|
|
1619
|
+
});
|
|
1620
|
+
|
|
1621
|
+
if (intents.has('server') && entityObjs.length > 0) {
|
|
1622
|
+
files.push({ path: 'openapi.json', content: JSON.stringify(openApiSpec(entityObjs, intents, { port }, relationships), null, 2) + '\n' });
|
|
1623
|
+
files.push({ path: 'admin.html', content: adminPage(entityObjs, intents) });
|
|
1624
|
+
}
|
|
1625
|
+
if (intents.has('server')) {
|
|
1626
|
+
files.push({ path: 'client.mjs', content: apiClientCode(entityObjs, intents, port) });
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
const added = existing
|
|
1630
|
+
? entities.filter(e => !existing.includes(`schema ${e}`))
|
|
1631
|
+
: entities;
|
|
1632
|
+
const analysis = {
|
|
1633
|
+
instruction,
|
|
1634
|
+
entities: entityObjs.map(e => ({ name: e.name, fieldCount: e.fields.length })),
|
|
1635
|
+
relationships,
|
|
1636
|
+
plan: [],
|
|
1637
|
+
added,
|
|
1638
|
+
};
|
|
1639
|
+
if (added.length > 0) analysis.plan.push(`Added: ${added.join(', ')}`);
|
|
1640
|
+
if (existing) analysis.plan.push('Merged with existing app.naide');
|
|
1641
|
+
analysis.plan.push(`Total entities: ${entities.length}`);
|
|
1642
|
+
analysis.plan.push(`Supporting files regenerated`);
|
|
1643
|
+
|
|
1644
|
+
return {
|
|
1645
|
+
code: finalCode, valid: combined.valid, fixed: combined.fixed, repairs: combined.repairs,
|
|
1646
|
+
intents: [...intents], entities, relationships, port,
|
|
1647
|
+
files, analysis, action: existing ? 'updated' : 'created',
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
function parseExistingProject(code) {
|
|
1652
|
+
const lines = code.split('\n');
|
|
1653
|
+
const entities = [];
|
|
1654
|
+
const intents = new Set();
|
|
1655
|
+
let port = 3000;
|
|
1656
|
+
|
|
1657
|
+
for (const line of lines) {
|
|
1658
|
+
const sm = line.match(/^schema\s+(\w+)\s*:/);
|
|
1659
|
+
if (sm && sm[1] !== 'AuditLog') {
|
|
1660
|
+
entities.push(sm[1]);
|
|
1661
|
+
intents.add('schema');
|
|
1662
|
+
}
|
|
1663
|
+
if (line.match(/^server\s/)) intents.add('server');
|
|
1664
|
+
if (line.match(/\bcrud\b/)) intents.add('crud');
|
|
1665
|
+
if (line.match(/\bauth\b/)) intents.add('auth');
|
|
1666
|
+
if (line.match(/\bdb\b/)) intents.add('database');
|
|
1667
|
+
if (line.match(/\bws\b/)) intents.add('websocket');
|
|
1668
|
+
if (line.match(/\bbot\b/)) intents.add('bot');
|
|
1669
|
+
const pm = line.match(/port\s+(\d+)/);
|
|
1670
|
+
if (pm) port = parseInt(pm[1], 10);
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
return { entities, intents: [...intents], port };
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1482
1676
|
// ── File Write Mode ────────────────────────────────────────
|
|
1483
1677
|
|
|
1484
1678
|
export function writeToFile(filePath, instruction, options = {}) {
|
package/src/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { Parser } from './parser.js';
|
|
|
3
3
|
import { Generator } from './generator.js';
|
|
4
4
|
import { preprocess } from './preprocess.js';
|
|
5
5
|
import { TypeChecker } from './typechecker.js';
|
|
6
|
-
import { generate, expandDirectives } from './gen.js';
|
|
6
|
+
import { generate, generateProject, updateProject, expandDirectives } from './gen.js';
|
|
7
7
|
|
|
8
8
|
async function getGenerator(target, options) {
|
|
9
9
|
switch (target) {
|
|
@@ -162,4 +162,4 @@ export function transpile(source, opts) {
|
|
|
162
162
|
return compile(source, opts).js;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
export { preprocess, generate };
|
|
165
|
+
export { preprocess, generate, generateProject, updateProject };
|