nothumanallowed 14.5.2 → 14.5.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.5.2",
3
+ "version": "14.5.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.5.2';
8
+ export const VERSION = '14.5.4';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -2100,6 +2100,73 @@ export function register(router) {
2100
2100
  } catch (e) { sendError(res, 500, e.message); }
2101
2101
  });
2102
2102
 
2103
+ // ── Full project scan — lint ALL files at once ───────────────────────────
2104
+ router.post('/api/studio/webcraft/scan', async (req, res) => {
2105
+ try {
2106
+ const { projectName } = await parseBody(req);
2107
+ if (!projectName) return sendError(res, 400, 'projectName required');
2108
+ const dir = ProjectStore.dir(projectName);
2109
+ if (!fs.existsSync(dir)) return sendJSON(res, 200, { issues: [] });
2110
+
2111
+ const files = _listProjectFiles(dir);
2112
+ const issues = [];
2113
+ const allFileNames = new Set(files);
2114
+
2115
+ for (const relPath of files) {
2116
+ const content = ProjectStore.readFile(projectName, relPath);
2117
+ if (!content) continue;
2118
+ const ext = relPath.split('.').pop()?.toLowerCase();
2119
+
2120
+ // JS syntax check
2121
+ if (ext === 'js' || ext === 'mjs') {
2122
+ try { new Function(content); } catch (e) {
2123
+ issues.push({ file: relPath, severity: 'error', message: `Syntax: ${e.message.replace(/\n.*/s, '')}` });
2124
+ }
2125
+ }
2126
+
2127
+ // JSON parse check
2128
+ if (ext === 'json') {
2129
+ try { JSON.parse(content); } catch (e) {
2130
+ issues.push({ file: relPath, severity: 'error', message: `JSON: ${e.message}` });
2131
+ }
2132
+ }
2133
+
2134
+ // CSS brace balance
2135
+ if (ext === 'css') {
2136
+ const o = (content.match(/\{/g) || []).length, c = (content.match(/\}/g) || []).length;
2137
+ if (o !== c) issues.push({ file: relPath, severity: 'warning', message: `Unbalanced braces: ${o} open, ${c} close` });
2138
+ }
2139
+
2140
+ // HTML closing tag + reference check
2141
+ if (ext === 'html' || ext === 'htm') {
2142
+ if (!content.includes('</html>')) {
2143
+ issues.push({ file: relPath, severity: 'warning', message: 'Missing </html>' });
2144
+ }
2145
+ // Check file references
2146
+ const refRegex = /(?:src|href)=["']([^"']*?\.(?:js|css|mjs))["']/gi;
2147
+ let m;
2148
+ while ((m = refRegex.exec(content)) !== null) {
2149
+ const ref = m[1];
2150
+ if (ref.startsWith('http') || ref.startsWith('//') || ref.startsWith('data:')) continue;
2151
+ const htmlDir = path.dirname(relPath);
2152
+ const refPath = ref.startsWith('/') ? ref.slice(1) : path.join(htmlDir, ref).replace(/\\/g, '/');
2153
+ const publicRef = refPath.startsWith('public/') ? refPath : `public/${refPath}`;
2154
+ if (!allFileNames.has(refPath) && !allFileNames.has(publicRef) && !allFileNames.has(ref)) {
2155
+ issues.push({ file: relPath, severity: 'error', message: `Missing file: ${ref}` });
2156
+ }
2157
+ }
2158
+ }
2159
+
2160
+ // Truncation check
2161
+ if (isFileTruncated(content, relPath)) {
2162
+ issues.push({ file: relPath, severity: 'warning', message: 'File appears truncated' });
2163
+ }
2164
+ }
2165
+
2166
+ sendJSON(res, 200, { issues, scanned: files.length });
2167
+ } catch (e) { sendError(res, 500, e.message); }
2168
+ });
2169
+
2103
2170
  // ── Syntax check ──────────────────────────────────────────────────────────
2104
2171
  router.post('/api/studio/webcraft/syntax-check', async (req, res) => {
2105
2172
  try {
@@ -2177,13 +2244,17 @@ function _patchEntry(projectDir, entryFile, shimDir, port) {
2177
2244
  `const _nhaErrScript = require('fs').readFileSync('${path.join(shimDir, 'error-reporter.html').replace(/\\/g, '/')}', 'utf-8');`,
2178
2245
  `const _origWrite = require('http').ServerResponse.prototype.write;`,
2179
2246
  `const _origEnd = require('http').ServerResponse.prototype.end;`,
2180
- `function _inject(chunk) {`,
2181
- ` if (typeof chunk === 'string' && chunk.includes('</head>')) return chunk.replace('</head>', _nhaErrScript + '</head>');`,
2182
- ` if (Buffer.isBuffer(chunk)) { var s = chunk.toString(); if (s.includes('</head>')) return Buffer.from(s.replace('</head>', _nhaErrScript + '</head>')); }`,
2183
- ` return chunk;`,
2184
- `}`,
2185
2247
  `require('http').ServerResponse.prototype.end = function(chunk, enc, cb) {`,
2186
- ` if (this.getHeader && (this.getHeader('content-type')||'').includes('html')) { arguments[0] = _inject(chunk); delete this._headers['content-length']; }`,
2248
+ ` try {`,
2249
+ ` if (chunk && this.getHeader && (this.getHeader('content-type')||'').includes('html')) {`,
2250
+ ` var s = typeof chunk === 'string' ? chunk : Buffer.isBuffer(chunk) ? chunk.toString() : null;`,
2251
+ ` if (s && s.includes('</head>')) {`,
2252
+ ` var patched = s.replace('</head>', _nhaErrScript + '</head>');`,
2253
+ ` try { this.removeHeader('content-length'); } catch(e) {}`,
2254
+ ` return _origEnd.call(this, patched, enc, cb);`,
2255
+ ` }`,
2256
+ ` }`,
2257
+ ` } catch(e) {}`,
2187
2258
  ` return _origEnd.apply(this, arguments);`,
2188
2259
  `};`,
2189
2260
  `require('${entryAbs}');`,