nothumanallowed 16.0.38 → 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 +105 -0
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
|
|
@@ -3611,6 +3622,93 @@ function _placeholderContent(kind, refPath) {
|
|
|
3611
3622
|
* Creates missing directories + empty JSON placeholders so the app can boot
|
|
3612
3623
|
* instead of crashing with ENOENT on first write.
|
|
3613
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
|
+
|
|
3614
3712
|
export function _detectMissingDataFiles(projectDir) {
|
|
3615
3713
|
const created = [];
|
|
3616
3714
|
const exts = new Set(['.js', '.mjs', '.cjs', '.ts']);
|
|
@@ -3674,6 +3772,13 @@ export function autoRepairProject(projectName) {
|
|
|
3674
3772
|
repairs.push({ file: f, kind: 'missing-data-file', source: 'code-reference' });
|
|
3675
3773
|
}
|
|
3676
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
|
+
|
|
3677
3782
|
// Walk every HTML file in the project
|
|
3678
3783
|
const stack = [dir];
|
|
3679
3784
|
const skipDirs = new Set(['node_modules', '.git', '.nha-shims', 'dist', 'build', '.next']);
|