nothumanallowed 13.5.35 → 13.5.37
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/package.json +1 -1
- package/src/constants.mjs +1 -1
- package/src/services/web-ui.mjs +244 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "13.5.
|
|
3
|
+
"version": "13.5.37",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/constants.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
|
|
8
|
-
export const VERSION = '13.5.
|
|
8
|
+
export const VERSION = '13.5.37';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
package/src/services/web-ui.mjs
CHANGED
|
@@ -326,6 +326,7 @@ function render(){
|
|
|
326
326
|
case 'birthdays':renderBirthdays(el);break;
|
|
327
327
|
case 'agents':renderAgents(el);break;
|
|
328
328
|
case 'studio':renderStudio(el);break;
|
|
329
|
+
case 'webcraft':renderWebCraft(el);break;
|
|
329
330
|
case 'collab':renderCollab(el);break;
|
|
330
331
|
case 'settings':renderSettings(el);break;
|
|
331
332
|
}
|
|
@@ -3307,6 +3308,10 @@ function renderSidebar() {
|
|
|
3307
3308
|
\x27<span class="nav-item__icon">⚙</span> \x27+t(\x27nav_studio\x27)+
|
|
3308
3309
|
\x27<span style="font-size:8px;padding:1px 5px;border-radius:4px;background:rgba(99,102,241,.25);color:var(--green);margin-left:4px;font-weight:700">NEW</span>\x27+
|
|
3309
3310
|
\x27</div>\x27+
|
|
3311
|
+
\x27<div class="nav-item\x27+(activeView===\x27webcraft\x27?\x27 nav-item--active\x27:\x27\x27)+\x27" data-view="webcraft" onclick="switchView(\\\x27webcraft\\\x27)">\x27+
|
|
3312
|
+
\x27<span class="nav-item__icon">🛠</span> WebCraft\x27+
|
|
3313
|
+
\x27<span style="font-size:8px;padding:1px 5px;border-radius:4px;background:rgba(99,102,241,.25);color:var(--green);margin-left:4px;font-weight:700">NEW</span>\x27+
|
|
3314
|
+
\x27</div>\x27+
|
|
3310
3315
|
\x27<div class="nav-item\x27+(activeView===\x27collab\x27?\x27 nav-item--active\x27:\x27\x27)+\x27" data-view="collab" onclick="switchView(\\\x27collab\\\x27)">\x27+
|
|
3311
3316
|
\x27<span class="nav-item__icon">🔒</span> \x27+t(\x27nav_collab\x27)+
|
|
3312
3317
|
\x27<span id="collabBadge" style="display:none;background:var(--red);color:#fff;font-size:9px;padding:1px 5px;border-radius:8px;margin-left:4px;font-family:var(--mono)">0</span>\x27+
|
|
@@ -3340,7 +3345,7 @@ var studioAbortController = null;
|
|
|
3340
3345
|
var parlActiveAgent = null; // active agent label during parliament streaming
|
|
3341
3346
|
var parlDoneAgents = {}; // set of completed agent labels during parliament
|
|
3342
3347
|
var _parlPersistHtml = null; // persists parliament block HTML across tab navigations
|
|
3343
|
-
var _PARL_STAMP = '<!--nha-parl-v13.5.
|
|
3348
|
+
var _PARL_STAMP = '<!--nha-parl-v13.5.37-->';
|
|
3344
3349
|
|
|
3345
3350
|
function stopStudio() {
|
|
3346
3351
|
if (!studioState.running) return;
|
|
@@ -6150,6 +6155,244 @@ function init(){
|
|
|
6150
6155
|
},3000);
|
|
6151
6156
|
}
|
|
6152
6157
|
init();
|
|
6158
|
+
|
|
6159
|
+
// ---- WEBCRAFT ----
|
|
6160
|
+
var wcState = {
|
|
6161
|
+
description: '',
|
|
6162
|
+
authFields: [{name:'firstName',label:'First name',type:'text',required:true},{name:'lastName',label:'Last name',type:'text',required:true},{name:'email',label:'Email',type:'email',required:true},{name:'password',label:'Password',type:'password',required:true}],
|
|
6163
|
+
blocks: {auth:true, cookieBanner:true, securityMiddleware:true, emailVerification:true},
|
|
6164
|
+
generatedFiles: [], // [{name, content, lang}]
|
|
6165
|
+
activeFile: 0,
|
|
6166
|
+
running: false,
|
|
6167
|
+
projectName: ''
|
|
6168
|
+
};
|
|
6169
|
+
|
|
6170
|
+
function wcEsc(s){return s?String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'):''}
|
|
6171
|
+
|
|
6172
|
+
function renderWebCraft(el) {
|
|
6173
|
+
var fileTabsHtml = wcState.generatedFiles.length > 0
|
|
6174
|
+
? '<div style="display:flex;gap:0;overflow-x:auto;border-bottom:1px solid var(--border);margin-bottom:0;flex-shrink:0">' +
|
|
6175
|
+
wcState.generatedFiles.map(function(f,i){
|
|
6176
|
+
var active = i === wcState.activeFile;
|
|
6177
|
+
return '<button onclick="wcSetFile('+i+')" style="padding:6px 14px;font-size:11px;font-family:var(--mono);font-weight:'+(active?'700':'400')+';background:'+(active?'var(--bg3)':'transparent')+';border:none;border-bottom:2px solid '+(active?'var(--green3)':'transparent')+';color:'+(active?'var(--green)':'var(--dim)')+';cursor:pointer;white-space:nowrap;flex-shrink:0">'+wcEsc(f.name)+'</button>';
|
|
6178
|
+
}).join('') +
|
|
6179
|
+
'</div>'
|
|
6180
|
+
: '';
|
|
6181
|
+
|
|
6182
|
+
var codeHtml = wcState.generatedFiles.length > 0 && wcState.generatedFiles[wcState.activeFile]
|
|
6183
|
+
? '<div id="wcCodeWrap" style="flex:1;overflow:auto;background:var(--bg3);border-radius:0 0 8px 8px">' +
|
|
6184
|
+
'<pre style="margin:0;padding:14px 16px;font-size:11px;line-height:1.6;color:var(--text);font-family:var(--mono);white-space:pre-wrap;word-break:break-all">'+wcEsc(wcState.generatedFiles[wcState.activeFile].content)+'</pre>' +
|
|
6185
|
+
'</div>'
|
|
6186
|
+
: '<div style="flex:1;display:flex;align-items:center;justify-content:center;color:var(--dim);font-size:12px;flex-direction:column;gap:8px">' +
|
|
6187
|
+
'<span style="font-size:36px;opacity:.25">🛠</span>' +
|
|
6188
|
+
'<span>Describe your project and click Generate</span>' +
|
|
6189
|
+
'</div>';
|
|
6190
|
+
|
|
6191
|
+
var authFieldsHtml = wcState.authFields.map(function(f,i){
|
|
6192
|
+
return '<div style="display:flex;align-items:center;gap:6px;padding:5px 8px;background:var(--bg3);border-radius:6px;margin-bottom:4px">' +
|
|
6193
|
+
'<input value="'+wcEsc(f.label)+'" onchange="wcUpdateField('+i+',this.value)" style="flex:1;background:transparent;border:none;color:var(--text);font-size:11px;font-family:var(--mono)" />' +
|
|
6194
|
+
'<select onchange="wcUpdateFieldType('+i+',this.value)" style="background:var(--bg2);border:1px solid var(--border);border-radius:4px;color:var(--dim);font-size:10px;padding:2px 4px">' +
|
|
6195
|
+
['text','email','password','tel','date','number'].map(function(t){return '<option value="'+t+'"'+(f.type===t?' selected':'')+'>'+t+'</option>'}).join('') +
|
|
6196
|
+
'</select>' +
|
|
6197
|
+
'<input type="checkbox"'+(f.required?' checked':'')+' onchange="wcToggleRequired('+i+',this.checked)" title="Required" style="accent-color:var(--green3)">' +
|
|
6198
|
+
'<button onclick="wcRemoveField('+i+')" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:13px;line-height:1;padding:0 2px">×</button>' +
|
|
6199
|
+
'</div>';
|
|
6200
|
+
}).join('');
|
|
6201
|
+
|
|
6202
|
+
el.innerHTML =
|
|
6203
|
+
'<div style="max-width:1100px;margin:0 auto;padding:0 8px">' +
|
|
6204
|
+
'<div style="margin-bottom:18px">' +
|
|
6205
|
+
'<h2 style="font-size:15px;color:var(--green);margin-bottom:4px">🛠 WebCraft</h2>' +
|
|
6206
|
+
'<p style="font-size:11px;color:var(--dim);line-height:1.5">Generate enterprise-grade web projects — security headers A+, BEM CSS, PostgreSQL pool, Auth, GDPR cookie banner.</p>' +
|
|
6207
|
+
'</div>' +
|
|
6208
|
+
|
|
6209
|
+
'<div style="display:flex;gap:16px;align-items:flex-start;flex-wrap:wrap">' +
|
|
6210
|
+
|
|
6211
|
+
// LEFT: config panel
|
|
6212
|
+
'<div style="width:300px;flex-shrink:0;display:flex;flex-direction:column;gap:12px">' +
|
|
6213
|
+
|
|
6214
|
+
// Project name + description
|
|
6215
|
+
'<div style="background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:14px">' +
|
|
6216
|
+
'<div style="font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.8px;margin-bottom:8px">Project</div>' +
|
|
6217
|
+
'<input id="wcProjectName" placeholder="Project name (e.g. MySaaS)" value="'+wcEsc(wcState.projectName)+'" oninput="wcState.projectName=this.value" style="width:100%;padding:8px 10px;font-size:12px;border-radius:6px;border:1px solid var(--border2);background:var(--bg3);color:var(--text);margin-bottom:8px;box-sizing:border-box">' +
|
|
6218
|
+
'<textarea id="wcDesc" placeholder="Describe your project... e.g. SaaS landing page with user registration, dashboard, Stripe-ready pricing section" rows="4" oninput="wcState.description=this.value" style="width:100%;padding:8px 10px;font-size:12px;border-radius:6px;border:1px solid var(--border2);background:var(--bg3);color:var(--text);resize:vertical;box-sizing:border-box;line-height:1.5">'+wcEsc(wcState.description)+'</textarea>' +
|
|
6219
|
+
'</div>' +
|
|
6220
|
+
|
|
6221
|
+
// Blocks
|
|
6222
|
+
'<div style="background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:14px">' +
|
|
6223
|
+
'<div style="font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.8px;margin-bottom:10px">Included Blocks</div>' +
|
|
6224
|
+
['auth','cookieBanner','securityMiddleware','emailVerification'].map(function(b){
|
|
6225
|
+
var labels = {auth:'Auth (register/login/JWT)',cookieBanner:'GDPR Cookie Banner',securityMiddleware:'Security Middleware',emailVerification:'Email Verification'};
|
|
6226
|
+
var icons = {auth:'🔒',cookieBanner:'🍪',securityMiddleware:'🛡',emailVerification:'✉'};
|
|
6227
|
+
return '<label style="display:flex;align-items:center;gap:8px;padding:6px 0;cursor:pointer;font-size:11px;color:var(--text)">' +
|
|
6228
|
+
'<input type="checkbox"'+(wcState.blocks[b]?' checked':'')+' onchange="wcState.blocks['+JSON.stringify(b)+']=this.checked" style="accent-color:var(--green3);width:14px;height:14px">' +
|
|
6229
|
+
'<span>'+icons[b]+'</span><span>'+labels[b]+'</span>' +
|
|
6230
|
+
'</label>';
|
|
6231
|
+
}).join('') +
|
|
6232
|
+
'</div>' +
|
|
6233
|
+
|
|
6234
|
+
// Auth fields configurator
|
|
6235
|
+
'<div id="wcAuthFieldsPanel" style="background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:14px;'+(wcState.blocks.auth?'':'display:none')+'">' +
|
|
6236
|
+
'<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">' +
|
|
6237
|
+
'<div style="font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.8px">Auth Fields</div>' +
|
|
6238
|
+
'<button onclick="wcAddField()" style="font-size:10px;padding:3px 8px;background:var(--bg3);border:1px solid var(--border2);border-radius:5px;color:var(--green);cursor:pointer">+ Add field</button>' +
|
|
6239
|
+
'</div>' +
|
|
6240
|
+
'<div id="wcFieldsList">'+authFieldsHtml+'</div>' +
|
|
6241
|
+
'<div style="font-size:9px;color:var(--dim);margin-top:4px">☑ = Required | Edit label & type inline</div>' +
|
|
6242
|
+
'</div>' +
|
|
6243
|
+
|
|
6244
|
+
// Generate button
|
|
6245
|
+
'<button id="wcRunBtn" onclick="wcGenerate()" style="width:100%;padding:12px;background:var(--green3);border:none;border-radius:8px;color:var(--bg);font-size:13px;font-weight:700;cursor:pointer;letter-spacing:.2px"'+(wcState.running?' disabled':'')+'>'+
|
|
6246
|
+
(wcState.running ? '⏳ Generating...' : '▶ Generate Project') +
|
|
6247
|
+
'</button>' +
|
|
6248
|
+
|
|
6249
|
+
(wcState.generatedFiles.length > 0 ?
|
|
6250
|
+
'<button onclick="wcDownloadZip()" style="width:100%;padding:10px;background:var(--bg3);border:1px solid var(--border2);border-radius:8px;color:var(--text);font-size:12px;font-weight:600;cursor:pointer">⇩ Download ZIP</button>' : '') +
|
|
6251
|
+
|
|
6252
|
+
'</div>' +
|
|
6253
|
+
|
|
6254
|
+
// RIGHT: file viewer
|
|
6255
|
+
'<div style="flex:1;min-width:0;background:var(--bg2);border:1px solid var(--border);border-radius:10px;display:flex;flex-direction:column;min-height:520px;overflow:hidden">' +
|
|
6256
|
+
fileTabsHtml +
|
|
6257
|
+
codeHtml +
|
|
6258
|
+
'</div>' +
|
|
6259
|
+
|
|
6260
|
+
'</div>' +
|
|
6261
|
+
'</div>';
|
|
6262
|
+
}
|
|
6263
|
+
|
|
6264
|
+
function wcUpdateField(i, val) { wcState.authFields[i].label = val; }
|
|
6265
|
+
function wcUpdateFieldType(i, t) { wcState.authFields[i].type = t; }
|
|
6266
|
+
function wcToggleRequired(i, v) { wcState.authFields[i].required = v; }
|
|
6267
|
+
function wcRemoveField(i) { wcState.authFields.splice(i,1); renderWebCraft(document.getElementById('content')); }
|
|
6268
|
+
function wcAddField() {
|
|
6269
|
+
wcState.authFields.push({name:'field'+wcState.authFields.length,label:'New field',type:'text',required:false});
|
|
6270
|
+
renderWebCraft(document.getElementById('content'));
|
|
6271
|
+
}
|
|
6272
|
+
function wcSetFile(i) { wcState.activeFile = i; renderWebCraft(document.getElementById('content')); }
|
|
6273
|
+
|
|
6274
|
+
async function wcGenerate() {
|
|
6275
|
+
if (wcState.running) return;
|
|
6276
|
+
var desc = (document.getElementById('wcDesc')||{}).value || wcState.description;
|
|
6277
|
+
var projName = (document.getElementById('wcProjectName')||{}).value || wcState.projectName || 'myproject';
|
|
6278
|
+
if (!desc || desc.length < 10) { alert('Please describe your project first.'); return; }
|
|
6279
|
+
wcState.description = desc;
|
|
6280
|
+
wcState.projectName = projName;
|
|
6281
|
+
wcState.running = true;
|
|
6282
|
+
wcState.generatedFiles = [];
|
|
6283
|
+
wcState.activeFile = 0;
|
|
6284
|
+
renderWebCraft(document.getElementById('content'));
|
|
6285
|
+
|
|
6286
|
+
// Security rules always injected
|
|
6287
|
+
var SECURITY_RULES = [
|
|
6288
|
+
'ALWAYS use security headers: X-Content-Type-Options, X-Frame-Options: DENY, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy: geolocation=(), microphone=(), camera=(), Strict-Transport-Security: max-age=31536000; includeSubDomains; preload.',
|
|
6289
|
+
'NEVER put secrets, API keys, or DB credentials in frontend code. Only in .env server-side.',
|
|
6290
|
+
'ALWAYS use prepared statements / parameterized queries. NEVER string-concatenate SQL.',
|
|
6291
|
+
'ALWAYS hash passwords with bcrypt (cost factor 12+). NEVER store plain passwords.',
|
|
6292
|
+
'ALWAYS validate and sanitize all user inputs server-side.',
|
|
6293
|
+
'ALWAYS use httpOnly, secure, sameSite=Strict cookies for session tokens.',
|
|
6294
|
+
'ALWAYS rate-limit auth endpoints (max 5 attempts / 15min per IP).',
|
|
6295
|
+
'CSS MUST follow BEM naming: block__element--modifier. No inline styles except dynamic values.',
|
|
6296
|
+
'PostgreSQL: use pg.Pool (max:10, idleTimeoutMillis:30000). Export singleton. Always use parameterized queries.',
|
|
6297
|
+
'JWT: access token 15min, refresh token 7 days with rotation. Store refresh in httpOnly cookie.',
|
|
6298
|
+
].join(String.fromCharCode(10));
|
|
6299
|
+
|
|
6300
|
+
var authFieldsDef = wcState.authFields.map(function(f){ return f.label+' ('+f.type+(f.required?', required':'')+')'; }).join(', ');
|
|
6301
|
+
var blocksEnabled = Object.keys(wcState.blocks).filter(function(b){ return wcState.blocks[b]; }).join(', ');
|
|
6302
|
+
|
|
6303
|
+
// File plan — always this structure
|
|
6304
|
+
var filePlan = [
|
|
6305
|
+
{ name: 'package.json', lang: 'json', prompt: 'Generate package.json for an Express/PostgreSQL project named "'+projName+'". Dependencies: express, pg, bcryptjs, jsonwebtoken, nodemailer, helmet, express-rate-limit, cors, dotenv, express-validator, ioredis. DevDependencies: nodemon. Scripts: start, dev.' },
|
|
6306
|
+
{ name: '.env.example', lang: 'bash', prompt: 'Generate .env.example with all required env vars: DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASS, JWT_SECRET, JWT_REFRESH_SECRET, SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_FROM, SENDGRID_API_KEY (commented as optional fallback), REDIS_URL (default redis://localhost:6379, works with Dragonfly too), PORT, NODE_ENV, CORS_ORIGIN, BASE_URL. Add helpful comments for each. Note that REDIS_URL is optional — app falls back to in-memory LRU if Redis unavailable.' },
|
|
6307
|
+
{ name: 'server/db.js', lang: 'javascript', prompt: 'Generate server/db.js: pg.Pool singleton (max:10, idleTimeoutMillis:30000, connectionTimeoutMillis:2000). Circuit breaker: if DB fails 5+ times in 60s, open circuit (throw immediately) for 30s, then half-open (try one query). Export pool, query(text, params) with circuit breaker, transaction(callback) helper (BEGIN/COMMIT/ROLLBACK). Graceful shutdown on SIGTERM/SIGINT.' },
|
|
6308
|
+
{ name: 'server/middleware/security.js', lang: 'javascript', prompt: 'Generate server/middleware/security.js: helmet with full CSP (defaultSrc self, scriptSrc self, styleSrc self unsafe-inline, imgSrc self data:, connectSrc self, frameSrc none, objectSrc none), HSTS, X-Frame-Options DENY, Referrer-Policy. Add express-rate-limit for general routes (100/15min) and strict limiter for auth (5/15min). Export { applySecurityMiddleware, authLimiter }.' },
|
|
6309
|
+
{ name: 'server/middleware/validate.js', lang: 'javascript', prompt: 'Generate server/middleware/validate.js using express-validator. Export handleValidationErrors middleware. Export auth field validators: registerValidator (fields: '+authFieldsDef+'), loginValidator (email + password).' },
|
|
6310
|
+
{ name: 'server/services/email.js', lang: 'javascript', prompt: 'Generate server/services/email.js: Nodemailer transporter using SMTP from env. Function sendVerificationEmail(to, token, baseUrl): sends HTML email with verification link. Function sendPasswordResetEmail(to, token, baseUrl). Add SendGrid fallback (commented out, predisposed with transporter swap). Never expose credentials.' },
|
|
6311
|
+
{ name: 'server/routes/auth.js', lang: 'javascript', prompt: 'Generate server/routes/auth.js: POST /register (validate fields: '+authFieldsDef+', check duplicate email, bcrypt hash password cost 12, insert user, send verification email, return 201), POST /login (validate, check email verified, compare bcrypt, issue JWT access 15min + refresh 7d httpOnly cookie), POST /logout (clear refresh cookie), POST /refresh-token (validate refresh from httpOnly cookie, rotate token), GET /verify-email/:token (mark email verified). Use parameterized queries only. Apply authLimiter to register and login.' },
|
|
6312
|
+
{ name: 'server/routes/api.js', lang: 'javascript', prompt: 'Generate server/routes/api.js: Express router with a verifyToken middleware (validates JWT Bearer). GET /api/me returns authenticated user profile (no password hash). GET /api/health returns {status: ok, timestamp}. Structure ready for adding more routes.' },
|
|
6313
|
+
{ name: 'server/index.js', lang: 'javascript', prompt: 'Generate server/index.js: Express app entry point. Apply applySecurityMiddleware first. Then apply sentinelMiddleware (import from ./middleware/sentinel.js). Use CORS with env CORS_ORIGIN. Parse JSON body (limit 10kb). Mount /api/auth → auth.js, /api → api.js. Serve public/ as static. 404 handler and global error handler (never leak stack traces in production). Start on PORT from env.' },
|
|
6314
|
+
{ name: 'db/migrations/001_init.sql', lang: 'sql', prompt: 'Generate PostgreSQL migration 001_init.sql: CREATE TABLE users with id UUID default gen_random_uuid(), fields for '+authFieldsDef+', email_verified BOOLEAN default false, verification_token VARCHAR, reset_token VARCHAR, reset_token_expires TIMESTAMPTZ, refresh_token_hash VARCHAR, created_at TIMESTAMPTZ default now(), updated_at TIMESTAMPTZ default now(). CREATE INDEX on email. CREATE TABLE refresh_tokens (id, user_id FK, token_hash, expires_at, created_at). Add updated_at trigger function.' },
|
|
6315
|
+
{ name: 'public/css/base.css', lang: 'css', prompt: 'Generate public/css/base.css: CSS custom properties (color palette, spacing scale, font scale, border-radius, shadows, transitions). CSS reset (*, box-sizing). Base typography (Inter or system-ui). Utility classes using BEM where applicable. Dark/light mode via prefers-color-scheme.' },
|
|
6316
|
+
{ name: 'public/css/components.css', lang: 'css', prompt: 'Generate public/css/components.css following STRICT BEM (block__element--modifier). Components: .btn (--primary, --secondary, --danger, --ghost), .form (form__field, form__label, form__input, form__error, form__hint), .card (card__header, card__body, card__footer), .nav (nav__brand, nav__links, nav__link--active), .alert (--success, --error, --warning, --info), .spinner, .badge, .modal (modal__overlay, modal__content, modal__header, modal__body, modal__footer). Fully accessible (focus states, aria).' },
|
|
6317
|
+
{ name: 'public/css/pages.css', lang: 'css', prompt: 'Generate public/css/pages.css: page-level layout classes using BEM. .page-auth (centered card layout for login/register), .page-dashboard (sidebar + content grid), .page-landing (hero section, features grid, pricing cards). Responsive at 768px and 480px breakpoints.' },
|
|
6318
|
+
{ name: 'public/js/main.js', lang: 'javascript', prompt: 'Generate public/js/main.js: vanilla JS, no dependencies. authAPI object with methods register(data), login(data), logout(), refreshToken(), getMe(). Cookie banner controller: reads localStorage consent, shows banner if not set, sets consent by category (necessary/analytics/marketing). Form handlers for register and login pages. Global error display utility. Export nothing (IIFE).' },
|
|
6319
|
+
{ name: 'public/index.html', lang: 'html', prompt: 'Generate public/index.html for "'+projName+'": '+desc+'. Full HTML5. Security meta tags (CSP via meta as fallback, X-UA-Compatible). Include base.css, components.css, pages.css. GDPR cookie banner HTML (class .cookie-banner, .cookie-banner__text, .cookie-banner__actions, .cookie-banner__btn--accept, .cookie-banner__btn--reject). Navigation. Hero section. Include main.js at end of body. Semantic HTML, ARIA roles, lang attribute.' },
|
|
6320
|
+
{ name: 'public/login.html', lang: 'html', prompt: 'Generate public/login.html: login page for "'+projName+'". Form with email + password fields using .form BEM classes. Link to register.html. Error display area. Include same CSS files. ARIA labels, autocomplete attributes.' },
|
|
6321
|
+
{ name: 'public/register.html', lang: 'html', prompt: 'Generate public/register.html: registration page for "'+projName+'". Form fields: '+authFieldsDef+'. Use .form BEM classes. Client-side validation hints. Link to login.html. Error/success display. Include same CSS files. ARIA labels, autocomplete attributes.' },
|
|
6322
|
+
{ name: 'server/middleware/sentinel.js', lang: 'javascript', prompt: 'Generate server/middleware/sentinel.js: a lightweight WAF middleware for Express. Check request for: SQL injection patterns (UNION SELECT, DROP TABLE, etc.), XSS patterns (<script, javascript:, onerror=), path traversal (../), oversized payloads (>100KB body). Rate limit by IP using an in-memory sliding window (fallback when Redis unavailable). Log blocked requests with IP, method, path, reason to stderr. Export sentinelMiddleware(req, res, next).' },
|
|
6323
|
+
{ name: 'server/services/cache.js', lang: 'javascript', prompt: 'Generate server/services/cache.js: Redis/Dragonfly client using ioredis. Connect to REDIS_URL from env. Export: get(key), set(key, value, ttlSeconds), del(key), exists(key). Add circuit breaker pattern: if Redis fails 3+ times in 30s, switch to in-memory LRU fallback (Map with max 1000 entries, LRU eviction). Reconnect Redis in background every 60s. Log circuit state changes. This makes the app resilient when Redis is down.' },
|
|
6324
|
+
{ name: 'README.md', lang: 'markdown', prompt: 'Generate README.md for "'+projName+'": project description, tech stack (Express, PostgreSQL with circuit breaker, Redis/Dragonfly with LRU fallback, JWT auth, Nodemailer SMTP + SendGrid fallback, Sentinel WAF, BEM CSS), folder structure, setup instructions (clone, npm install, copy .env.example to .env, run migrations with psql, optional: start Redis/Dragonfly, npm run dev), environment variables table (including REDIS_URL), API endpoints table, security notes, email configuration guide.' },
|
|
6325
|
+
];
|
|
6326
|
+
|
|
6327
|
+
// Filter by selected blocks
|
|
6328
|
+
if (!wcState.blocks.auth) {
|
|
6329
|
+
filePlan = filePlan.filter(function(f){ return !['server/routes/auth.js','public/login.html','public/register.html','server/middleware/validate.js'].includes(f.name); });
|
|
6330
|
+
}
|
|
6331
|
+
if (!wcState.blocks.cookieBanner) {
|
|
6332
|
+
// keep index.html but note no cookie banner
|
|
6333
|
+
}
|
|
6334
|
+
if (!wcState.blocks.emailVerification) {
|
|
6335
|
+
// mark in prompt
|
|
6336
|
+
filePlan = filePlan.map(function(f){ return f.name === 'server/services/email.js' ? Object.assign({},f,{prompt:f.prompt+' (Skip email verification — not enabled)'}) : f; });
|
|
6337
|
+
}
|
|
6338
|
+
|
|
6339
|
+
var _nl = String.fromCharCode(10);
|
|
6340
|
+
var sysPreamble = 'You are an expert full-stack engineer generating production-quality code.' + _nl + _nl + 'SECURITY RULES (non-negotiable):' + _nl + SECURITY_RULES + _nl + _nl + 'Project: ' + projName + _nl + 'Description: ' + desc + _nl + 'Enabled blocks: ' + blocksEnabled + _nl + _nl + 'Generate ONLY the file content requested. No explanations, no markdown code fences, no comments like "here is the file". Output raw file content only.';
|
|
6341
|
+
|
|
6342
|
+
for (var fi = 0; fi < filePlan.length; fi++) {
|
|
6343
|
+
var fp = filePlan[fi];
|
|
6344
|
+
var runBtn = document.getElementById('wcRunBtn');
|
|
6345
|
+
if (runBtn) runBtn.textContent = '⏳ Generating ' + fp.name + ' (' + (fi+1) + '/' + filePlan.length + ')...';
|
|
6346
|
+
try {
|
|
6347
|
+
var _nl2 = String.fromCharCode(10);
|
|
6348
|
+
var content = await wcCallLLM(sysPreamble, fp.prompt + _nl2 + _nl2 + 'File to generate: ' + fp.name);
|
|
6349
|
+
// Strip markdown code fences if model added them anyway
|
|
6350
|
+
var _fence = String.fromCharCode(96,96,96);
|
|
6351
|
+
var wcLines = content.split(_nl2);
|
|
6352
|
+
if (wcLines.length > 0 && wcLines[0].indexOf(_fence) === 0) wcLines.shift();
|
|
6353
|
+
if (wcLines.length > 0 && wcLines[wcLines.length-1].trim() === _fence) wcLines.pop();
|
|
6354
|
+
content = wcLines.join(_nl2).trim();
|
|
6355
|
+
wcState.generatedFiles.push({ name: fp.name, content: content, lang: fp.lang });
|
|
6356
|
+
if (fi === 0) wcState.activeFile = 0;
|
|
6357
|
+
renderWebCraft(document.getElementById('content'));
|
|
6358
|
+
} catch(e) {
|
|
6359
|
+
wcState.generatedFiles.push({ name: fp.name, content: '// Error generating this file: ' + e.message, lang: fp.lang });
|
|
6360
|
+
}
|
|
6361
|
+
}
|
|
6362
|
+
|
|
6363
|
+
wcState.running = false;
|
|
6364
|
+
renderWebCraft(document.getElementById('content'));
|
|
6365
|
+
}
|
|
6366
|
+
|
|
6367
|
+
async function wcCallLLM(sys, user) {
|
|
6368
|
+
var r = await fetch(API + '/studio/step', {
|
|
6369
|
+
method: 'POST',
|
|
6370
|
+
headers: {'Content-Type':'application/json'},
|
|
6371
|
+
body: JSON.stringify({system: sys, user: user, max_tokens: 4096, stream: false})
|
|
6372
|
+
});
|
|
6373
|
+
if (!r.ok) throw new Error('LLM error ' + r.status);
|
|
6374
|
+
var d = await r.json();
|
|
6375
|
+
return (d && (d.text || d.content || d.result)) || '';
|
|
6376
|
+
}
|
|
6377
|
+
|
|
6378
|
+
function wcDownloadZip() {
|
|
6379
|
+
if (!wcState.generatedFiles.length) return;
|
|
6380
|
+
// Build a simple ZIP using JSZip-free approach: store as individual downloads or use blob trick
|
|
6381
|
+
// Since we have no JSZip, we offer each file as a combined text archive (tar-like) with clear separators
|
|
6382
|
+
var lines = ['# ' + (wcState.projectName || 'project') + ' — Generated by NHA WebCraft', '# Extract manually or use: csplit or awk to split by === FILE: markers', ''];
|
|
6383
|
+
wcState.generatedFiles.forEach(function(f) {
|
|
6384
|
+
lines.push('=== FILE: ' + f.name + ' ===');
|
|
6385
|
+
lines.push(f.content);
|
|
6386
|
+
lines.push('=== END: ' + f.name + ' ===');
|
|
6387
|
+
lines.push('');
|
|
6388
|
+
});
|
|
6389
|
+
var blob = new Blob([lines.join(String.fromCharCode(10))], {type:'text/plain'});
|
|
6390
|
+
var a = document.createElement('a');
|
|
6391
|
+
a.href = URL.createObjectURL(blob);
|
|
6392
|
+
a.download = (wcState.projectName || 'project') + '-webcraft.txt';
|
|
6393
|
+
a.click();
|
|
6394
|
+
URL.revokeObjectURL(a.href);
|
|
6395
|
+
}
|
|
6153
6396
|
`;
|
|
6154
6397
|
|
|
6155
6398
|
export function getHTML(port) {
|