nothumanallowed 16.0.37 → 16.0.39

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": "16.0.37",
3
+ "version": "16.0.39",
4
4
  "description": "Local AI assistant: 80 tools (Gmail, Calendar, Drive, GitHub, Slack, browser, code, files), 38 agents, visual workflows (Studio, AWF, WebCraft). Install with `npm i -g nothumanallowed`, run with `nha ui`. Free tier built-in (Liara), no API key required. Your data stays on your PC — OAuth tokens local, no cloud. Open-source MIT.",
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 = '16.0.37';
8
+ export const VERSION = '16.0.39';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -229,6 +229,17 @@ class SandboxManager {
229
229
  }
230
230
  } catch {}
231
231
 
232
+ // Pre-flight: repair unsafe err.X / error.X access in route handlers.
233
+ // LLMs write `res.send(err.stack)` without null-checking err → 500 on
234
+ // every request when error middleware signature is wrong (3 args not 4).
235
+ try {
236
+ const errRepaired = _repairUnsafeErrAccess(projectDir);
237
+ if (errRepaired.length > 0) {
238
+ const fileList = errRepaired.slice(0, 5).map(r => `${r.file} (${r.edits} fixes)`).join(', ');
239
+ emit({ type: 'status', msg: `Pre-flight: null-checked ${errRepaired.reduce((s, r) => s + r.edits, 0)} unsafe err/error accesses in ${errRepaired.length} file${errRepaired.length === 1 ? '' : 's'} → ${fileList}${errRepaired.length > 5 ? '...' : ''}` });
240
+ }
241
+ } catch {}
242
+
232
243
  // ── Phase 2: Dependencies (pre-scan + batch install) ──────────────────
233
244
  // Pre-scan the project source files for require()/import statements and
234
245
  // diff against package.json + node_modules. Install everything missing in
@@ -380,7 +391,16 @@ class SandboxManager {
380
391
  const preview = body.slice(0, 200).replace(/\s+/g, ' ').trim();
381
392
  emit({ type: 'log', msg: `[probe] GET / → ${status} (${ct}, ${len} bytes)` });
382
393
  if (status >= 400) {
383
- emit({ type: 'warn', msg: `Probe: GET / returned ${status}. The sandbox is running but has no route for "/". Add app.get('/', ...) in your code or generate an index.html.` });
394
+ // Show the actual error body so the user knows WHAT went wrong,
395
+ // not just the status code. 500s from Express handlers often
396
+ // include the stack trace or error message in the body.
397
+ const bodyPreview = body.slice(0, 800).trim();
398
+ emit({ type: 'error', msg: `Probe: GET / returned ${status}. Response body:\n${bodyPreview}` });
399
+ if (status === 500) {
400
+ emit({ type: 'warn', msg: `500 Internal Server Error usually means the route handler threw. Common causes: missing await on async function, JSON.parse on empty file, undefined variable, DB connection failure. Check stderr logs above for the actual stack trace.` });
401
+ } else if (status === 404) {
402
+ emit({ type: 'warn', msg: `404 means no route matches "/". Add app.get('/', ...) returning HTML, or app.use(express.static('public')) if you have an index.html in public/.` });
403
+ }
384
404
  } else if (len === 0) {
385
405
  emit({ type: 'warn', msg: `Probe: GET / returned empty body (${status}). The route exists but sends no response — check that you call res.send() / res.json() / res.end().` });
386
406
  } else if (isHtml && !/<\w+/.test(body)) {
@@ -3602,6 +3622,93 @@ function _placeholderContent(kind, refPath) {
3602
3622
  * Creates missing directories + empty JSON placeholders so the app can boot
3603
3623
  * instead of crashing with ENOENT on first write.
3604
3624
  */
3625
+ /**
3626
+ * Auto-repair unsafe `err.X` and similar null-deref patterns in route handlers.
3627
+ * The most common LLM-generated bug: error middleware that does `res.send(err.stack)`
3628
+ * but `err` is undefined because the middleware signature is wrong (only 3 args
3629
+ * instead of 4). Result: 500 on EVERY request including static files.
3630
+ *
3631
+ * Pattern fixed:
3632
+ * err.stack → (err && err.stack) if not already null-checked
3633
+ * err.message → (err && err.message) same
3634
+ * error.stack → (error && error.stack) same
3635
+ * error.message → (error && error.message) same
3636
+ *
3637
+ * Skips matches already guarded (`err && err.stack`, `err?.stack`, `err?.message`).
3638
+ */
3639
+ export function _repairUnsafeErrAccess(projectDir) {
3640
+ const repaired = [];
3641
+ const exts = new Set(['.js', '.mjs', '.cjs']);
3642
+ const skipDirs = new Set(['node_modules', '.git', '.nha-shims', 'dist', 'build', '.next', 'public']);
3643
+ // Match `IDENT.PROP` where PROP is stack|message and IDENT is err|error|e.
3644
+ // Negative lookbehind: skip if preceded by `&&`, `||`, `?.`, `(`, `:`, `,`.
3645
+ // Use a global regex and inspect context manually.
3646
+ const targetIdents = ['err', 'error', 'e'];
3647
+ const targetProps = ['stack', 'message', 'code', 'statusCode', 'name'];
3648
+
3649
+ const stack = [projectDir];
3650
+ while (stack.length) {
3651
+ const cur = stack.pop();
3652
+ let entries;
3653
+ try { entries = fs.readdirSync(cur, { withFileTypes: true }); } catch { continue; }
3654
+ for (const ent of entries) {
3655
+ if (skipDirs.has(ent.name) || ent.name.startsWith('.')) continue;
3656
+ const abs = path.join(cur, ent.name);
3657
+ if (ent.isDirectory()) { stack.push(abs); continue; }
3658
+ if (!exts.has(path.extname(ent.name))) continue;
3659
+ let content;
3660
+ try { content = fs.readFileSync(abs, 'utf-8'); } catch { continue; }
3661
+
3662
+ let changed = content;
3663
+ const edits = [];
3664
+
3665
+ for (const ident of targetIdents) {
3666
+ for (const prop of targetProps) {
3667
+ // Match unsafe access: `ident.prop` not preceded by `&&`, `?`, `||`
3668
+ // Use a simpler regex + manual filter for surrounding context
3669
+ const re = new RegExp(`\\b${ident}\\.${prop}\\b`, 'g');
3670
+ changed = changed.replace(re, (match, offset, str) => {
3671
+ // Check context preceding this match
3672
+ const before = str.slice(Math.max(0, offset - 40), offset);
3673
+ // Already guarded patterns — skip
3674
+ if (/&&\s*$/.test(before)) return match;
3675
+ if (/\?\.$/.test(before.slice(-2))) return match;
3676
+ if (/\?\s*$/.test(before)) return match;
3677
+ if (/\|\|\s*$/.test(before)) return match;
3678
+ // Inside object literal key (e.g., `{ stack: err.stack }`) is harmless
3679
+ // when it's the value. We still wrap because the assignment crashes too.
3680
+ // Inside a string literal — skip
3681
+ const lineStart = str.lastIndexOf('\n', offset) + 1;
3682
+ const lineUpTo = str.slice(lineStart, offset);
3683
+ const singleQuotes = (lineUpTo.match(/(?<!\\)'/g) || []).length;
3684
+ const doubleQuotes = (lineUpTo.match(/(?<!\\)"/g) || []).length;
3685
+ const backticks = (lineUpTo.match(/(?<!\\)`/g) || []).length;
3686
+ if (singleQuotes % 2 !== 0 || doubleQuotes % 2 !== 0 || backticks % 2 !== 0) return match;
3687
+ // Inside a comment — skip
3688
+ if (/\/\/[^\n]*$/.test(lineUpTo)) return match;
3689
+ // Already inside an existing parens guard like `(err && err.stack)` — skip
3690
+ const wider = str.slice(Math.max(0, offset - 60), offset);
3691
+ if (new RegExp(`\\b${ident}\\s*&&\\s*$`).test(wider)) return match;
3692
+ // Wrap in null-check
3693
+ edits.push(`${ident}.${prop} → (${ident} && ${ident}.${prop})`);
3694
+ return `(${ident} && ${ident}.${prop})`;
3695
+ });
3696
+ }
3697
+ }
3698
+
3699
+ if (changed !== content && edits.length > 0) {
3700
+ try {
3701
+ fs.writeFileSync(abs + '.before-err-repair-' + Date.now(), content, 'utf-8');
3702
+ fs.writeFileSync(abs, changed, 'utf-8');
3703
+ const rel = path.relative(projectDir, abs).replace(/\\/g, '/');
3704
+ repaired.push({ file: rel, edits: edits.length, samples: edits.slice(0, 3) });
3705
+ } catch {}
3706
+ }
3707
+ }
3708
+ }
3709
+ return repaired;
3710
+ }
3711
+
3605
3712
  export function _detectMissingDataFiles(projectDir) {
3606
3713
  const created = [];
3607
3714
  const exts = new Set(['.js', '.mjs', '.cjs', '.ts']);
@@ -3665,6 +3772,13 @@ export function autoRepairProject(projectName) {
3665
3772
  repairs.push({ file: f, kind: 'missing-data-file', source: 'code-reference' });
3666
3773
  }
3667
3774
 
3775
+ // Phase 0.5: null-check unsafe err.X access in route handlers
3776
+ const errFixed = _repairUnsafeErrAccess(dir);
3777
+ for (const r of errFixed) {
3778
+ filesRepaired.add(r.file);
3779
+ repairs.push({ file: r.file, kind: 'unsafe-err-access', edits: r.edits });
3780
+ }
3781
+
3668
3782
  // Walk every HTML file in the project
3669
3783
  const stack = [dir];
3670
3784
  const skipDirs = new Set(['node_modules', '.git', '.nha-shims', 'dist', 'build', '.next']);