nothumanallowed 14.3.2 → 14.3.4

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.3.2",
3
+ "version": "14.3.4",
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.3.2';
8
+ export const VERSION = '14.3.4';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -488,31 +488,67 @@ async function runWebCraftAgent(config, projectName, message, attachments, emit)
488
488
  const toolSpec = `
489
489
  AVAILABLE TOOLS (use exactly ONE tool per <tool> tag):
490
490
 
491
- 1. Read a file (to see its current content before editing):
491
+ ── FILE OPERATIONS ──
492
+
493
+ 1. read — Read a file's content:
492
494
  <tool>{"op":"read","path":"relative/path.js"}</tool>
493
495
 
494
- 2. Edit a file (surgical replacement of exact code):
495
- <tool>{"op":"edit","path":"relative/path.js","old":"EXACT_EXISTING_CODE","new":"REPLACEMENT_CODE"}</tool>
496
+ 2. edit Surgical replacement (EXACT match required):
497
+ <tool>{"op":"edit","path":"relative/path.js","old":"EXACT_CODE_TO_REPLACE","new":"REPLACEMENT_CODE"}</tool>
496
498
 
497
- 3. Write/create a file (complete content):
499
+ 3. write — Write/create a file (full content):
498
500
  <tool>{"op":"write","path":"relative/path.js","content":"FULL_FILE_CONTENT"}</tool>
499
501
 
500
- 4. Check syntax of a JS file:
502
+ 4. rename Rename or move a file:
503
+ <tool>{"op":"rename","path":"old/path.js","newPath":"new/path.js"}</tool>
504
+
505
+ 5. delete — Delete a file:
506
+ <tool>{"op":"delete","path":"relative/path.js"}</tool>
507
+
508
+ ── VERIFICATION ──
509
+
510
+ 6. check — Syntax check (JS/JSON/CSS/HTML):
501
511
  <tool>{"op":"check","path":"relative/path.js"}</tool>
502
512
 
513
+ 7. lint — Full diagnostics with line numbers:
514
+ <tool>{"op":"lint","path":"relative/path.js"}</tool>
515
+
516
+ 8. search — Grep/find text in project files:
517
+ <tool>{"op":"search","query":"searchPattern","glob":"*.js"}</tool>
518
+
519
+ 9. list — List all project files with sizes:
520
+ <tool>{"op":"list"}</tool>
521
+
522
+ ── EXECUTION ──
523
+
524
+ 10. run — Execute a shell command in the project directory:
525
+ <tool>{"op":"run","cmd":"npm install express"}</tool>
526
+ <tool>{"op":"run","cmd":"npm test"}</tool>
527
+ <tool>{"op":"run","cmd":"node -e \\"console.log(1+1)\\""}</tool>
528
+
529
+ 11. sandbox — Restart the sandbox server to test changes:
530
+ <tool>{"op":"sandbox"}</tool>
531
+
532
+ ── DIFF ──
533
+
534
+ 12. diff — Show diff between current file and last snapshot:
535
+ <tool>{"op":"diff","path":"relative/path.js"}</tool>
536
+
503
537
  WORKFLOW — follow this for every change:
504
538
  1. Read the file(s) you need to modify
505
539
  2. Make your changes with edit or write
506
- 3. Use check to verify JS files have no syntax errors
507
- 4. If check fails, read the file again and fix
540
+ 3. Use check or lint to verify fix any errors
541
+ 4. Use sandbox to restart and verify the app works
508
542
  5. When ALL changes are complete and verified, output: <done/>
509
543
 
510
544
  RULES:
511
- - "old" must be an EXACT verbatim copy from the file — copy-paste, no paraphrasing
545
+ - "old" in edit must be EXACT verbatim code — copy-paste from read output
512
546
  - Use edit for targeted changes, write for new files or complete rewrites
513
- - ALWAYS read before edit if you haven't seen the file content yet
514
- - ALWAYS check JS files after modifications
515
- - Output <done/> when you are completely finished this is MANDATORY
547
+ - ALWAYS read before edit if you haven't seen the file content
548
+ - ALWAYS check/lint after modifications
549
+ - Use run for npm install, npm test, or any shell command
550
+ - Use search to find code patterns across the project
551
+ - Output <done/> when you are completely finished — MANDATORY
516
552
  `;
517
553
 
518
554
  // Build system prompt with fresh file list each step
@@ -611,19 +647,24 @@ RULES:
611
647
  continue;
612
648
  }
613
649
 
614
- const { op, path: relPath, old: oldStr, new: newStr, content } = toolCall;
615
- if (!relPath || !_isSafePath(relPath)) {
650
+ const { op, path: relPath, old: oldStr, new: newStr, content, newPath, query, glob: globPat, cmd } = toolCall;
651
+
652
+ // Path validation (skip for path-less ops)
653
+ const needsPath = !['list', 'search', 'sandbox', 'run'].includes(op);
654
+ if (needsPath && (!relPath || !_isSafePath(relPath))) {
616
655
  toolResults.push({ op, path: relPath, result: 'unsafe_path' });
617
656
  emit({ type: 'tool', op, path: relPath ?? '', result: 'unsafe_path' });
618
657
  continue;
619
658
  }
620
659
 
660
+ // ── read ──
621
661
  if (op === 'read') {
622
662
  const src = ProjectStore.readFile(projectName, relPath);
623
663
  const result = src !== null ? 'ok' : 'not_found';
624
- toolResults.push({ op: 'read', path: relPath, result, content: src?.slice(0, 12000) });
664
+ toolResults.push({ op: 'read', path: relPath, result, content: src?.slice(0, 16000) });
625
665
  emit({ type: 'tool', op: 'read', path: relPath, result });
626
666
 
667
+ // ── check ──
627
668
  } else if (op === 'check') {
628
669
  const src = ProjectStore.readFile(projectName, relPath);
629
670
  let checkResult = 'ok';
@@ -633,7 +674,6 @@ RULES:
633
674
  } else if (relPath.endsWith('.json')) {
634
675
  try { JSON.parse(src); } catch (e) { checkResult = `json_error: ${e.message}`; }
635
676
  } else if (relPath.endsWith('.css')) {
636
- // Basic CSS validation: balanced braces
637
677
  const opens = (src.match(/\{/g) || []).length;
638
678
  const closes = (src.match(/\}/g) || []).length;
639
679
  if (opens !== closes) checkResult = `css_error: unbalanced braces (${opens} open, ${closes} close)`;
@@ -643,6 +683,38 @@ RULES:
643
683
  toolResults.push({ op: 'check', path: relPath, result: checkResult });
644
684
  emit({ type: 'tool', op: 'check', path: relPath, result: checkResult });
645
685
 
686
+ // ── lint (full diagnostics with line numbers) ──
687
+ } else if (op === 'lint') {
688
+ const src = ProjectStore.readFile(projectName, relPath);
689
+ if (!src) {
690
+ toolResults.push({ op: 'lint', path: relPath, result: 'file_not_found' });
691
+ emit({ type: 'tool', op: 'lint', path: relPath, result: 'file_not_found' });
692
+ } else {
693
+ const diags = [];
694
+ const ext = relPath.split('.').pop()?.toLowerCase();
695
+ if (ext === 'js' || ext === 'mjs') {
696
+ try { new Function(src); } catch (e) {
697
+ diags.push({ line: 1, message: e.message.replace(/\n.*/s, ''), severity: 'error' });
698
+ }
699
+ }
700
+ if (ext === 'json') {
701
+ try { JSON.parse(src); } catch (e) {
702
+ diags.push({ line: 1, message: e.message, severity: 'error' });
703
+ }
704
+ }
705
+ if (ext === 'css') {
706
+ const o = (src.match(/\{/g) || []).length, c = (src.match(/\}/g) || []).length;
707
+ if (o !== c) diags.push({ line: src.split('\n').length, message: `Unbalanced braces: ${o} open, ${c} close`, severity: 'warning' });
708
+ }
709
+ if (ext === 'html' && !src.includes('</html>')) {
710
+ diags.push({ line: src.split('\n').length, message: 'Missing </html>', severity: 'warning' });
711
+ }
712
+ const result = diags.length === 0 ? 'ok' : diags.map((d) => `L${d.line}: [${d.severity}] ${d.message}`).join('\n');
713
+ toolResults.push({ op: 'lint', path: relPath, result });
714
+ emit({ type: 'tool', op: 'lint', path: relPath, result });
715
+ }
716
+
717
+ // ── edit ──
646
718
  } else if (op === 'edit') {
647
719
  const src = ProjectStore.readFile(projectName, relPath);
648
720
  if (src === null) {
@@ -669,6 +741,7 @@ RULES:
669
741
  emit({ type: 'tool', op: 'edit', path: relPath, result: 'ok', oldSnippet: oldStr.slice(0, 300), newSnippet: newStr?.slice(0, 300) ?? '' });
670
742
  }
671
743
 
744
+ // ── write ──
672
745
  } else if (op === 'write') {
673
746
  if (content === undefined) {
674
747
  toolResults.push({ op: 'write', path: relPath, result: 'missing_content' });
@@ -680,6 +753,179 @@ RULES:
680
753
  toolResults.push({ op: 'write', path: relPath, result: 'ok' });
681
754
  emit({ type: 'tool', op: 'write', path: relPath, result: 'ok' });
682
755
  }
756
+
757
+ // ── rename ──
758
+ } else if (op === 'rename') {
759
+ if (!newPath || !_isSafePath(newPath)) {
760
+ toolResults.push({ op: 'rename', path: relPath, result: 'invalid_newPath' });
761
+ emit({ type: 'tool', op: 'rename', path: relPath, result: 'invalid_newPath' });
762
+ } else {
763
+ const oldAbs = path.join(dir, relPath);
764
+ const newAbs = path.join(dir, newPath);
765
+ if (!fs.existsSync(oldAbs)) {
766
+ toolResults.push({ op: 'rename', path: relPath, result: 'file_not_found' });
767
+ emit({ type: 'tool', op: 'rename', path: relPath, result: 'file_not_found' });
768
+ } else {
769
+ ensureDir(path.dirname(newAbs));
770
+ fs.renameSync(oldAbs, newAbs);
771
+ hasChanges = true;
772
+ modifiedFiles.add(newPath);
773
+ toolResults.push({ op: 'rename', path: relPath, result: `ok → ${newPath}` });
774
+ emit({ type: 'tool', op: 'rename', path: relPath, result: 'ok' });
775
+ }
776
+ }
777
+
778
+ // ── delete ──
779
+ } else if (op === 'delete') {
780
+ const abs = path.join(dir, relPath);
781
+ if (!fs.existsSync(abs)) {
782
+ toolResults.push({ op: 'delete', path: relPath, result: 'file_not_found' });
783
+ emit({ type: 'tool', op: 'delete', path: relPath, result: 'file_not_found' });
784
+ } else {
785
+ fs.unlinkSync(abs);
786
+ hasChanges = true;
787
+ toolResults.push({ op: 'delete', path: relPath, result: 'ok' });
788
+ emit({ type: 'tool', op: 'delete', path: relPath, result: 'ok' });
789
+ }
790
+
791
+ // ── search (grep) ──
792
+ } else if (op === 'search') {
793
+ const matches = ProjectStore.grep(projectName, query || '', globPat);
794
+ const resultText = matches.length === 0 ? 'no_matches'
795
+ : matches.slice(0, 30).map((m) => `${m.file}:${m.lineNum}: ${m.line}`).join('\n');
796
+ toolResults.push({ op: 'search', result: resultText, content: resultText });
797
+ emit({ type: 'tool', op: 'search', path: query || '', result: `${matches.length} matches` });
798
+
799
+ // ── list ──
800
+ } else if (op === 'list') {
801
+ const allFiles = _listProjectFiles(dir);
802
+ const listing = allFiles.map((f) => {
803
+ try {
804
+ const stat = fs.statSync(path.join(dir, f));
805
+ return `${f} (${stat.size} B)`;
806
+ } catch { return f; }
807
+ }).join('\n');
808
+ toolResults.push({ op: 'list', result: listing, content: listing });
809
+ emit({ type: 'tool', op: 'list', path: '', result: `${allFiles.length} files` });
810
+
811
+ // ── run (shell command) ──
812
+ } else if (op === 'run') {
813
+ if (!cmd) {
814
+ toolResults.push({ op: 'run', result: 'missing cmd' });
815
+ emit({ type: 'tool', op: 'run', path: '', result: 'missing_cmd' });
816
+ } else {
817
+ // Security: block dangerous commands
818
+ const blocked = /rm\s+-rf|rmdir|format|mkfs|dd\s+if|shutdown|reboot|kill\s+-9\s+1\b/i;
819
+ if (blocked.test(cmd)) {
820
+ toolResults.push({ op: 'run', result: 'blocked: dangerous command' });
821
+ emit({ type: 'tool', op: 'run', path: cmd, result: 'blocked' });
822
+ } else {
823
+ try {
824
+ const { stdout, stderr } = await execAsync(cmd, {
825
+ cwd: dir,
826
+ timeout: 30_000,
827
+ env: { ...process.env, NODE_ENV: 'development' },
828
+ });
829
+ const output = (stdout + (stderr ? `\n[stderr] ${stderr}` : '')).slice(0, 8000);
830
+ toolResults.push({ op: 'run', result: output || '(no output)', content: output });
831
+ emit({ type: 'tool', op: 'run', path: cmd, result: 'ok' });
832
+ } catch (e) {
833
+ const errMsg = (e.stderr || e.message || '').slice(0, 2000);
834
+ toolResults.push({ op: 'run', result: `error: ${errMsg}`, content: errMsg });
835
+ emit({ type: 'tool', op: 'run', path: cmd, result: 'error' });
836
+ }
837
+ }
838
+ }
839
+
840
+ // ── sandbox (restart) ──
841
+ } else if (op === 'sandbox') {
842
+ if (sandbox.isRunning()) {
843
+ await sandbox.stop();
844
+ }
845
+ try {
846
+ const projectDir = ProjectStore.dir(projectName);
847
+ const port = await _findFreePort(4000, 4999);
848
+ if (port) {
849
+ const shimDir = path.join(projectDir, '.nha-shims');
850
+ ensureDir(shimDir);
851
+ _writeShims(shimDir);
852
+ const entryFile = _detectEntry(projectDir);
853
+ if (entryFile) {
854
+ const patchedEntry = _patchEntry(projectDir, entryFile, shimDir, port);
855
+ const proc = spawn('node', [patchedEntry], {
856
+ cwd: projectDir,
857
+ env: { ...process.env, PORT: String(port), NODE_ENV: 'development', NHA_SANDBOX: '1' },
858
+ detached: false, stdio: ['ignore', 'pipe', 'pipe'],
859
+ });
860
+ sandbox._sandbox = { proc, port, projectName, startedAt: new Date(), healthy: false };
861
+ let sandboxStderr = '';
862
+ proc.stderr.on('data', (d) => { sandboxStderr += d.toString(); });
863
+ proc.stdout.on('data', (d) => {
864
+ if (/listen|running|started|ready|port/i.test(d.toString())) sandbox._sandbox.healthy = true;
865
+ });
866
+ const healthy = await _waitForPort(port, 10_000);
867
+ if (healthy) {
868
+ sandbox._sandbox.healthy = true;
869
+ toolResults.push({ op: 'sandbox', result: `ok: running on port ${port}` });
870
+ emit({ type: 'tool', op: 'sandbox', path: '', result: `port:${port}` });
871
+ emit({ type: 'sandbox_ready', port });
872
+ } else {
873
+ const errLine = sandboxStderr.split('\n').find((l) => l.includes('Error')) || 'startup timeout';
874
+ toolResults.push({ op: 'sandbox', result: `error: ${errLine.slice(0, 500)}`, content: errLine });
875
+ emit({ type: 'tool', op: 'sandbox', path: '', result: 'error' });
876
+ }
877
+ } else {
878
+ toolResults.push({ op: 'sandbox', result: 'no entry point found' });
879
+ emit({ type: 'tool', op: 'sandbox', path: '', result: 'no_entry' });
880
+ }
881
+ }
882
+ } catch (e) {
883
+ toolResults.push({ op: 'sandbox', result: `error: ${e.message?.slice(0, 200)}` });
884
+ emit({ type: 'tool', op: 'sandbox', path: '', result: 'error' });
885
+ }
886
+
887
+ // ── diff ──
888
+ } else if (op === 'diff') {
889
+ const current = ProjectStore.readFile(projectName, relPath);
890
+ // Load last snapshot
891
+ const snapshots = SnapshotStore.list(projectName);
892
+ if (!current) {
893
+ toolResults.push({ op: 'diff', path: relPath, result: 'file_not_found' });
894
+ emit({ type: 'tool', op: 'diff', path: relPath, result: 'file_not_found' });
895
+ } else if (snapshots.length === 0) {
896
+ toolResults.push({ op: 'diff', path: relPath, result: 'no snapshots available' });
897
+ emit({ type: 'tool', op: 'diff', path: relPath, result: 'no_snapshots' });
898
+ } else {
899
+ const lastSnap = snapshots[0];
900
+ const snapDir = SnapshotStore.dir(projectName);
901
+ try {
902
+ const snapData = JSON.parse(fs.readFileSync(path.join(snapDir, `${lastSnap.ts}.json`), 'utf-8'));
903
+ const oldFile = snapData.files?.find((f) => f.name === relPath);
904
+ const oldContent = oldFile?.content || '';
905
+ // Simple line diff
906
+ const oldLines = oldContent.split('\n');
907
+ const newLines = current.split('\n');
908
+ const diffs = [];
909
+ const maxLen = Math.max(oldLines.length, newLines.length);
910
+ for (let i = 0; i < maxLen; i++) {
911
+ if (oldLines[i] !== newLines[i]) {
912
+ if (oldLines[i] !== undefined) diffs.push(`- L${i + 1}: ${oldLines[i]}`);
913
+ if (newLines[i] !== undefined) diffs.push(`+ L${i + 1}: ${newLines[i]}`);
914
+ }
915
+ }
916
+ const diffText = diffs.length === 0 ? 'no changes' : diffs.slice(0, 100).join('\n');
917
+ toolResults.push({ op: 'diff', path: relPath, result: diffText, content: diffText });
918
+ emit({ type: 'tool', op: 'diff', path: relPath, result: `${diffs.length} changes` });
919
+ } catch {
920
+ toolResults.push({ op: 'diff', path: relPath, result: 'snapshot read error' });
921
+ emit({ type: 'tool', op: 'diff', path: relPath, result: 'error' });
922
+ }
923
+ }
924
+
925
+ // ── unknown op ──
926
+ } else {
927
+ toolResults.push({ op, path: relPath, result: `unknown_op: ${op}` });
928
+ emit({ type: 'tool', op, path: relPath ?? '', result: 'unknown_op' });
683
929
  }
684
930
  }
685
931
 
@@ -1438,6 +1684,68 @@ export function register(router) {
1438
1684
  } catch (e) { sendError(res, 500, e.message); }
1439
1685
  });
1440
1686
 
1687
+ // ── Diagnostics (lint) — returns errors/warnings for a file ───────────────
1688
+ router.post('/api/studio/webcraft/lint', async (req, res) => {
1689
+ try {
1690
+ const { projectName, path: relPath } = await parseBody(req);
1691
+ if (!projectName || !relPath) return sendError(res, 400, 'projectName and path required');
1692
+ const content = ProjectStore.readFile(projectName, relPath);
1693
+ if (content === null) return sendJSON(res, 200, { diagnostics: [] });
1694
+
1695
+ const diagnostics = [];
1696
+ const ext = relPath.split('.').pop()?.toLowerCase();
1697
+
1698
+ if (ext === 'js' || ext === 'mjs' || ext === 'jsx') {
1699
+ try { new Function(content); } catch (e) {
1700
+ const match = e.message.match(/^(.*?)$/m);
1701
+ const lineMatch = e.message.match(/:(\d+):(\d+)/);
1702
+ diagnostics.push({
1703
+ from: lineMatch ? { line: parseInt(lineMatch[1]), col: parseInt(lineMatch[2]) } : { line: 1, col: 0 },
1704
+ severity: 'error',
1705
+ message: match?.[1] || e.message,
1706
+ });
1707
+ }
1708
+ }
1709
+
1710
+ if (ext === 'json') {
1711
+ try { JSON.parse(content); } catch (e) {
1712
+ const posMatch = e.message.match(/position (\d+)/);
1713
+ const pos = posMatch ? parseInt(posMatch[1]) : 0;
1714
+ const lines = content.slice(0, pos).split('\n');
1715
+ diagnostics.push({
1716
+ from: { line: lines.length, col: (lines[lines.length - 1] || '').length },
1717
+ severity: 'error',
1718
+ message: e.message,
1719
+ });
1720
+ }
1721
+ }
1722
+
1723
+ if (ext === 'css') {
1724
+ const opens = (content.match(/\{/g) || []).length;
1725
+ const closes = (content.match(/\}/g) || []).length;
1726
+ if (opens !== closes) {
1727
+ diagnostics.push({
1728
+ from: { line: content.split('\n').length, col: 0 },
1729
+ severity: 'warning',
1730
+ message: `Unbalanced braces: ${opens} open, ${closes} close`,
1731
+ });
1732
+ }
1733
+ }
1734
+
1735
+ if (ext === 'html' || ext === 'htm') {
1736
+ if (!content.includes('</html>')) {
1737
+ diagnostics.push({
1738
+ from: { line: content.split('\n').length, col: 0 },
1739
+ severity: 'warning',
1740
+ message: 'Missing </html> closing tag',
1741
+ });
1742
+ }
1743
+ }
1744
+
1745
+ sendJSON(res, 200, { diagnostics });
1746
+ } catch (e) { sendError(res, 500, e.message); }
1747
+ });
1748
+
1441
1749
  // ── File write (from IDE editor) ──────────────────────────────────────────
1442
1750
  router.post('/api/studio/webcraft/file/write', async (req, res) => {
1443
1751
  try {