nothumanallowed 14.2.0 → 14.2.2

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.2.0",
3
+ "version": "14.2.2",
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.2.0';
8
+ export const VERSION = '14.2.2';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -632,6 +632,11 @@ RULES:
632
632
  try { new Function(src); } catch (e) { checkResult = `syntax_error: ${e.message.replace(/\n.*/s, '')}`; }
633
633
  } else if (relPath.endsWith('.json')) {
634
634
  try { JSON.parse(src); } catch (e) { checkResult = `json_error: ${e.message}`; }
635
+ } else if (relPath.endsWith('.css')) {
636
+ // Basic CSS validation: balanced braces
637
+ const opens = (src.match(/\{/g) || []).length;
638
+ const closes = (src.match(/\}/g) || []).length;
639
+ if (opens !== closes) checkResult = `css_error: unbalanced braces (${opens} open, ${closes} close)`;
635
640
  } else if (relPath.endsWith('.html')) {
636
641
  checkResult = src.includes('</html>') ? 'ok' : 'missing_closing_html_tag';
637
642
  }
@@ -727,6 +732,54 @@ RULES:
727
732
 
728
733
  // Notify about modified files so frontend can reload them
729
734
  emit({ type: 'files_changed', files: [...modifiedFiles] });
735
+
736
+ // ── Auto-restart sandbox if running and files changed ──────────────────────
737
+ if (hasChanges && sandbox.isRunning()) {
738
+ emit({ type: 'sandbox_restart', msg: 'Restarting sandbox to verify changes...' });
739
+ try {
740
+ await sandbox.stop();
741
+ const projectDir = ProjectStore.dir(projectName);
742
+ // Quick restart — capture stderr for 5s to detect crash
743
+ const port = await _findFreePort(4000, 4999);
744
+ if (port) {
745
+ const shimDir = path.join(projectDir, '.nha-shims');
746
+ ensureDir(shimDir);
747
+ _writeShims(shimDir);
748
+ const entryFile = _detectEntry(projectDir);
749
+ if (entryFile) {
750
+ const patchedEntry = _patchEntry(projectDir, entryFile, shimDir, port);
751
+ const proc = spawn('node', [patchedEntry], {
752
+ cwd: projectDir,
753
+ env: { ...process.env, PORT: String(port), NODE_ENV: 'development', NHA_SANDBOX: '1' },
754
+ detached: false,
755
+ stdio: ['ignore', 'pipe', 'pipe'],
756
+ });
757
+ sandbox._sandbox = { proc, port, projectName, startedAt: new Date(), healthy: false };
758
+
759
+ let crashErr = '';
760
+ proc.stderr.on('data', (d) => { crashErr += d.toString(); });
761
+ proc.stdout.on('data', (d) => {
762
+ if (/listen|running|started|ready|port/i.test(d.toString())) {
763
+ sandbox._sandbox.healthy = true;
764
+ }
765
+ });
766
+
767
+ const healthy = await _waitForPort(port, 8000);
768
+ if (healthy) {
769
+ sandbox._sandbox.healthy = true;
770
+ emit({ type: 'sandbox_ready', port });
771
+ } else if (crashErr) {
772
+ // Extract error for the user
773
+ const errLine = crashErr.split('\n').find((l) => l.includes('Error')) || crashErr.slice(0, 200);
774
+ emit({ type: 'sandbox_error', msg: errLine });
775
+ }
776
+ }
777
+ }
778
+ } catch (e) {
779
+ emit({ type: 'sandbox_error', msg: e.message?.slice(0, 200) });
780
+ }
781
+ }
782
+
730
783
  emit({ type: 'done', changed: hasChanges, syntaxErrors: syntaxErrors.length });
731
784
  }
732
785
 
@@ -902,11 +955,12 @@ Design a COMPLETE production-ready file structure. Include ALL files needed for
902
955
  const fileSpec = filePlan[fi];
903
956
  emit({ type: 'file_start', name: fileSpec.name, fi: fi + 1, total: filePlan.length });
904
957
 
905
- // Include last 6 generated files as context for consistency
906
- const prevContext = generatedFiles.slice(-6)
958
+ // Include last 8 generated files key files get full content
959
+ const prevContext = generatedFiles.slice(-8)
907
960
  .map((f) => {
908
961
  const ext = f.name.split('.').pop();
909
- const maxSnippet = ext === 'json' ? 800 : ext === 'css' ? 2000 : 1600;
962
+ const isKey = /^(server|app|index)\.(js|mjs)$/.test(f.name) || f.name === 'package.json';
963
+ const maxSnippet = isKey ? 16000 : ext === 'json' ? 1200 : ext === 'css' ? 4000 : ext === 'html' ? 4000 : 3000;
910
964
  const snippet = f.content.slice(0, maxSnippet);
911
965
  return `### ${f.name}\n\`\`\`\n${snippet}${f.content.length > maxSnippet ? '\n... (truncated)' : ''}\n\`\`\``;
912
966
  })
@@ -1047,6 +1101,11 @@ Continue from here:`;
1047
1101
  if (f.name.endsWith('.json')) {
1048
1102
  try { JSON.parse(f.content); } catch { return true; }
1049
1103
  }
1104
+ if (f.name.endsWith('.css')) {
1105
+ const opens = (f.content.match(/\{/g) || []).length;
1106
+ const closes = (f.content.match(/\}/g) || []).length;
1107
+ if (opens !== closes) return true;
1108
+ }
1050
1109
  return isFileTruncated(f.content, f.name);
1051
1110
  });
1052
1111
 
Binary file
Binary file