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 +1 -1
- package/src/constants.mjs +1 -1
- package/src/server/routes/webcraft.mjs +115 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "16.0.
|
|
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.
|
|
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
|
-
|
|
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']);
|