nothumanallowed 14.1.13 → 14.1.15

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "14.1.13",
3
+ "version": "14.1.15",
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 = '14.1.13';
8
+ export const VERSION = '14.1.15';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -334,6 +334,17 @@ export function register(router) {
334
334
  .replace(/<style[\s\S]*?<\/style>/gi, '').replace(/<script[\s\S]*?<\/script>/gi, '')
335
335
  .replace(/<[^>]+>/g, ' ').replace(/\s{3,}/g, '\n').trim().slice(0, 6000);
336
336
  }
337
+
338
+ // ── Canvas result — emit dedicated SSE event so the UI panel opens ──
339
+ if (resultStr.includes('[CANVAS_RENDER]')) {
340
+ const canvasMarker = resultStr.match(/\[CANVAS_RENDER\]([\s\S]*?)\[\/CANVAS_RENDER\]/);
341
+ if (canvasMarker) {
342
+ sse('canvas', { markers: canvasMarker[0] });
343
+ }
344
+ // Replace the raw HTML blob with a short placeholder so synthesis LLM doesn't see it
345
+ resultStr = 'Canvas rendered successfully in the panel.';
346
+ }
347
+
337
348
  toolResults.push({ action, result: resultStr });
338
349
  sse('tool', { action, status: 'done', result: resultStr.slice(0, 500) });
339
350
  } catch (e) {
@@ -526,13 +526,24 @@ RULES:
526
526
 
527
527
  // ── Generation pipeline (SSE) ─────────────────────────────────────────────────
528
528
 
529
- const FILE_PLAN_SYSTEM = `You are an expert full-stack web developer. Your job is to design the complete file structure for a web project.
530
- Output ONLY a JSON array of file objects: [{"name":"filename","purpose":"brief purpose"}]
531
- Rules:
532
- - Include ALL necessary files: HTML, CSS, JS, server, package.json, .env.example, README.md
533
- - Use relative paths (e.g. "public/styles.css", "routes/auth.js")
529
+ const FILE_PLAN_SYSTEM = `You are a senior full-stack architect. Design a COMPLETE, PRODUCTION-READY file structure for a web project.
530
+ Output ONLY a JSON array: [{"name":"path/to/file.ext","purpose":"what this file does","tokens":N}]
531
+ where "tokens" is your estimate of how many tokens the file content will need (200-800 for small files, 800-2000 for medium, 2000-4000 for large).
532
+
533
+ MANDATORY rules:
534
+ - Generate 20-40 files minimum for any real project — a complete site requires many files
535
+ - Split large concerns into separate files (separate route files, separate component files, separate util files)
536
+ - Always include: package.json, server.js (or index.js), .env.example, README.md
537
+ - For full-stack projects: routes/, middleware/, models/, controllers/ directories with individual files per resource
538
+ - For frontend: separate CSS files per section (hero, navbar, footer, components), separate JS modules
539
+ - Use relative paths only (e.g. "routes/auth.js", "public/js/app.js", "public/css/main.css")
534
540
  - No explanation, no markdown, ONLY the JSON array.`;
535
541
 
542
+ // Token counter — approximate based on character count (1 token ≈ 4 chars)
543
+ function countTokens(text) {
544
+ return Math.ceil((text || '').length / 4);
545
+ }
546
+
536
547
  async function runGenerate(config, projectName, description, blocks, authFields, emit) {
537
548
  const blocksDesc = Object.entries(blocks)
538
549
  .filter(([, enabled]) => enabled)
@@ -546,12 +557,16 @@ async function runGenerate(config, projectName, description, blocks, authFields,
546
557
  Description: ${description}
547
558
  ${blocksDesc ? `Required blocks: ${blocksDesc}` : ''}
548
559
  ${authDesc}
549
- Design a complete file structure for this project.`;
560
+
561
+ Design a COMPLETE production-ready file structure. Include ALL files needed for a fully working site: server, routes, middleware, models, public HTML/CSS/JS pages, config files, README. Minimum 20 files.`;
550
562
 
551
563
  // Round 1: plan files
552
564
  let filePlan = [];
565
+ let planTokensIn = countTokens(FILE_PLAN_SYSTEM) + countTokens(planPrompt);
566
+ let planTokensOut = 0;
553
567
  try {
554
- const planRaw = await callLLM(config, FILE_PLAN_SYSTEM, planPrompt, { max_tokens: 1500 });
568
+ const planRaw = await callLLM(config, FILE_PLAN_SYSTEM, planPrompt, { max_tokens: 4096 });
569
+ planTokensOut = countTokens(planRaw);
555
570
  const clean = planRaw.replace(/<think>[\s\S]*?<\/think>/g, '').trim()
556
571
  .replace(/^```[\w]*\n?/, '').replace(/\n?```$/, '').trim();
557
572
  const arr = JSON.parse(clean.match(/\[[\s\S]*\]/)?.[0] ?? clean);
@@ -559,13 +574,34 @@ Design a complete file structure for this project.`;
559
574
  } catch {}
560
575
 
561
576
  if (filePlan.length === 0) {
562
- // Minimal fallback structure
577
+ // Comprehensive fallback structure for a full-stack web project
563
578
  filePlan = [
564
- { name: 'index.html', purpose: 'Main HTML page' },
565
- { name: 'styles.css', purpose: 'CSS styles' },
566
- { name: 'app.js', purpose: 'Application logic' },
567
- { name: 'server.js', purpose: 'Express server' },
568
- { name: 'package.json', purpose: 'Node.js package manifest' },
579
+ { name: 'package.json', purpose: 'Node.js dependencies and scripts', tokens: 300 },
580
+ { name: '.env.example', purpose: 'Environment variables template', tokens: 200 },
581
+ { name: 'README.md', purpose: 'Project documentation', tokens: 400 },
582
+ { name: 'server.js', purpose: 'Express server entry point with middleware setup', tokens: 1500 },
583
+ { name: 'routes/index.js', purpose: 'Main router — mounts all sub-routers', tokens: 300 },
584
+ { name: 'routes/auth.js', purpose: 'Auth routes: register, login, logout, refresh', tokens: 1200 },
585
+ { name: 'routes/api.js', purpose: 'REST API routes', tokens: 800 },
586
+ { name: 'middleware/auth.js', purpose: 'JWT authentication middleware', tokens: 600 },
587
+ { name: 'middleware/error.js', purpose: 'Global error handler', tokens: 400 },
588
+ { name: 'middleware/validate.js', purpose: 'Request validation middleware', tokens: 500 },
589
+ { name: 'models/user.js', purpose: 'User model (SQLite/JSON storage)', tokens: 600 },
590
+ { name: 'controllers/authController.js', purpose: 'Auth business logic', tokens: 1200 },
591
+ { name: 'utils/jwt.js', purpose: 'JWT sign/verify helpers', tokens: 400 },
592
+ { name: 'utils/hash.js', purpose: 'Password hashing utilities (bcrypt)', tokens: 300 },
593
+ { name: 'config/database.js', purpose: 'Database connection and setup', tokens: 500 },
594
+ { name: 'public/index.html', purpose: 'Main landing page', tokens: 2000 },
595
+ { name: 'public/dashboard.html', purpose: 'User dashboard page', tokens: 1500 },
596
+ { name: 'public/login.html', purpose: 'Login/register page', tokens: 1200 },
597
+ { name: 'public/css/main.css', purpose: 'Global styles, variables, reset', tokens: 1500 },
598
+ { name: 'public/css/components.css', purpose: 'Reusable component styles (cards, buttons, forms)', tokens: 1200 },
599
+ { name: 'public/css/layout.css', purpose: 'Layout styles: nav, hero, sections, footer', tokens: 1000 },
600
+ { name: 'public/css/animations.css', purpose: 'Animations and transitions', tokens: 600 },
601
+ { name: 'public/js/app.js', purpose: 'Main frontend JS — router, init', tokens: 800 },
602
+ { name: 'public/js/auth.js', purpose: 'Frontend auth logic — login, register, token storage', tokens: 800 },
603
+ { name: 'public/js/api.js', purpose: 'API client wrapper with fetch', tokens: 500 },
604
+ { name: 'public/js/ui.js', purpose: 'UI helpers — toasts, modals, loaders', tokens: 600 },
569
605
  ];
570
606
  }
571
607
 
@@ -573,43 +609,70 @@ Design a complete file structure for this project.`;
573
609
 
574
610
  const projectDir = ensureDir(ProjectStore.dir(projectName));
575
611
  const generatedFiles = [];
612
+ let totalTokensIn = planTokensIn;
613
+ let totalTokensOut = planTokensOut;
576
614
 
577
- // Round 2: generate each file
615
+ const allFileNames = filePlan.map((f) => f.name).join(', ');
616
+
617
+ // Round 2: generate each file with streaming
578
618
  for (let fi = 0; fi < filePlan.length; fi++) {
579
619
  const fileSpec = filePlan[fi];
580
620
  emit({ type: 'file_start', name: fileSpec.name, fi: fi + 1, total: filePlan.length });
581
621
 
582
- const allFileNames = filePlan.map((f) => f.name).join(', ');
583
- const prevContext = generatedFiles.slice(-3)
584
- .map((f) => `### ${f.name} (excerpt)\n${f.content.slice(0, 800)}`)
622
+ // Include last 4 generated files as context (truncated to avoid token overflow)
623
+ const prevContext = generatedFiles.slice(-4)
624
+ .map((f) => {
625
+ const ext = f.name.split('.').pop();
626
+ const snippet = f.content.slice(0, ext === 'json' ? 600 : 1200);
627
+ return `### ${f.name}\n\`\`\`\n${snippet}${f.content.length > 1200 ? '\n... (truncated)' : ''}\n\`\`\``;
628
+ })
585
629
  .join('\n\n');
586
630
 
587
- const fileSys = `You are an expert full-stack web developer generating production-quality code.
588
- Output ONLY the complete file content — no explanation, no markdown fences, no comments about what you're doing.
589
- Write real, working, modern code. Do NOT use placeholder text like "TODO" or "// add code here".`;
631
+ // Estimate appropriate max_tokens for this file
632
+ const estimatedTokens = fileSpec.tokens || 2000;
633
+ const maxTokens = Math.min(Math.max(estimatedTokens * 2, 2000), 8192);
634
+
635
+ const fileSys = `You are a senior full-stack developer generating a COMPLETE, PRODUCTION-READY file.
636
+ CRITICAL RULES:
637
+ - Output ONLY the raw file content — zero explanations, zero markdown fences, zero "here is the file:" preamble
638
+ - Write COMPLETE, WORKING code — no TODOs, no placeholders, no "add your code here" comments
639
+ - Every function must be fully implemented with real logic
640
+ - Use modern patterns: async/await, ES6+, proper error handling
641
+ - CSS must include responsive design (mobile-first), dark/light variables, smooth animations
642
+ - HTML must be complete with proper meta tags, semantic structure, accessible markup
643
+ - JS must handle all edge cases, show loading states, handle errors gracefully`;
590
644
 
591
645
  const filePrompt = `Project: ${projectName}
592
646
  Description: ${description}
593
- ${blocksDesc ? `Required blocks: ${blocksDesc}` : ''}
647
+ ${blocksDesc ? `Enabled blocks: ${blocksDesc}` : ''}
594
648
  ${authDesc}
595
- All files in project: ${allFileNames}
649
+ Full project file list: ${allFileNames}
596
650
 
597
- Generate COMPLETE content for: ${fileSpec.name}
651
+ NOW GENERATE: ${fileSpec.name}
598
652
  Purpose: ${fileSpec.purpose}
599
- ${prevContext ? `\n--- Recent files for context ---\n${prevContext}` : ''}
600
653
 
601
- Output ONLY the complete file content.`;
654
+ ${prevContext ? `Recent files generated (for consistency):\n${prevContext}\n\n` : ''}Output ONLY the complete file content, starting immediately with the first line of the file.`;
602
655
 
603
656
  let fileContent = '';
604
657
  let syntaxError = null;
658
+ const fileTokensIn = countTokens(fileSys) + countTokens(filePrompt);
605
659
 
606
660
  try {
607
661
  await callLLMStream(config, fileSys, filePrompt, (chunk) => {
608
662
  fileContent += chunk;
609
663
  emit({ type: 'file_chunk', name: fileSpec.name, chunk, fi: fi + 1, total: filePlan.length });
610
- }, { max_tokens: 4096 });
664
+ }, { max_tokens: maxTokens });
665
+
666
+ const fileTokensOut = countTokens(fileContent);
667
+ totalTokensIn += fileTokensIn;
668
+ totalTokensOut += fileTokensOut;
669
+
670
+ // Strip markdown fences if LLM wrapped the output anyway
671
+ fileContent = fileContent
672
+ .replace(/^```[\w]*\n/, '').replace(/\n```$/, '')
673
+ .replace(/^```[\w]*\r\n/, '').replace(/\r\n```$/, '').trim();
611
674
 
612
- // Quick syntax check for JS files
675
+ // Quick syntax check for JS/TS files
613
676
  if (fileSpec.name.endsWith('.js') || fileSpec.name.endsWith('.mjs')) {
614
677
  try { new Function(fileContent); } catch (e) { syntaxError = e.message.replace(/\n.*/s, ''); }
615
678
  }
@@ -618,7 +681,7 @@ Output ONLY the complete file content.`;
618
681
  ensureDir(path.dirname(abs));
619
682
  fs.writeFileSync(abs, fileContent, 'utf-8');
620
683
  generatedFiles.push({ name: fileSpec.name, content: fileContent });
621
- emit({ type: 'file_done', name: fileSpec.name, fi: fi + 1, total: filePlan.length, syntaxError });
684
+ emit({ type: 'file_done', name: fileSpec.name, fi: fi + 1, total: filePlan.length, syntaxError, tokOut: fileTokensOut });
622
685
  } catch (e) {
623
686
  emit({ type: 'file_error', name: fileSpec.name, error: e.message });
624
687
  }
@@ -641,12 +704,10 @@ Output ONLY the complete file content.`;
641
704
  fs.writeFileSync(memFile, `# ${projectName} — Project Memory\n\n_Add architectural decisions, preferences, and notes here._\n`, 'utf-8');
642
705
  }
643
706
  const logFile = path.join(ctxDir, 'changes.log.md');
644
- const logEntry = `## ${new Date().toISOString().slice(0, 10)} — Initial generation\n- Generated ${generatedFiles.length} files\n- Description: ${description}\n`;
707
+ const logEntry = `## ${new Date().toISOString().slice(0, 10)} — Initial generation\n- Generated ${generatedFiles.length} files\n- Tokens in: ${totalTokensIn} / out: ${totalTokensOut}\n- Description: ${description}\n`;
645
708
  fs.writeFileSync(logFile, (fs.existsSync(logFile) ? fs.readFileSync(logFile, 'utf-8') : '') + logEntry, 'utf-8');
646
709
 
647
- const inputTokensEst = Math.round(filePlan.length * 800);
648
- const outputTokensEst = generatedFiles.reduce((sum, f) => sum + Math.ceil(f.content.length / 4), 0);
649
- emit({ type: 'done', tokIn: inputTokensEst, tokOut: outputTokensEst });
710
+ emit({ type: 'done', tokIn: totalTokensIn, tokOut: totalTokensOut });
650
711
  }
651
712
 
652
713
  // ── ZIP download ──────────────────────────────────────────────────────────────