nothumanallowed 16.0.45 → 16.0.47

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.45",
3
+ "version": "16.0.47",
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.45';
8
+ export const VERSION = '16.0.47';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -229,16 +229,42 @@ class SandboxManager {
229
229
  }
230
230
  } catch {}
231
231
 
232
- // Pre-flight: auto-extend CSS if coverage is below threshold.
233
- // Detects "incomplete styling" (LLM truncated CSS generation) and calls
234
- // the LLM automatically to extend the CSS file with rules for the missing
235
- // HTML selectors. NO user intervention required.
232
+ // Pre-flight: auto-extend CSS until 100% coverage (or LLM stops making
233
+ // progress). Target = 100%; max 5 passes as safety; early-exit if two
234
+ // consecutive passes don't reduce missing count (LLM stuck) or LLM fails.
235
+ // Goal: ZERO uncovered selectors when the LLM is capable of producing them.
236
236
  try {
237
237
  const projectName = path.basename(projectDir);
238
238
  const cfg = loadConfig();
239
- const styleResult = await _autoExtendStylesIfNeeded(projectName, cfg, emit, { minCoverage: 0.6 });
240
- if (styleResult.extended) {
241
- emit({ type: 'status', msg: `Auto-extended styles: ${styleResult.file} (${styleResult.missingBefore - styleResult.missingAfter} new selectors covered).` });
239
+ const maxPasses = 5;
240
+ let totalCovered = 0;
241
+ let prevMissing = Infinity;
242
+ let stuckPasses = 0;
243
+ for (let pass = 1; pass <= maxPasses; pass++) {
244
+ const styleResult = await _autoExtendStylesIfNeeded(projectName, cfg, emit, { minCoverage: 1.0 });
245
+ if (!styleResult.extended) {
246
+ if (styleResult.reason === 'coverage acceptable') break; // already 100%
247
+ // LLM failed or skipped — stop loop
248
+ break;
249
+ }
250
+ const newlyCovered = (styleResult.missingBefore || 0) - (styleResult.missingAfter || 0);
251
+ totalCovered += newlyCovered;
252
+ const missingNow = styleResult.missingAfter || 0;
253
+ emit({ type: 'status', msg: `Pass ${pass}/${maxPasses}: ${styleResult.file} extended — ${newlyCovered} new selectors covered (${missingNow} still missing, ${((styleResult.coverageAfter || 0) * 100).toFixed(0)}% coverage).` });
254
+ if (missingNow === 0) {
255
+ emit({ type: 'status', msg: `100% CSS coverage reached after ${pass} pass${pass === 1 ? '' : 'es'}. Total ${totalCovered} selectors covered.` });
256
+ break;
257
+ }
258
+ if (missingNow >= prevMissing) {
259
+ stuckPasses++;
260
+ if (stuckPasses >= 2) {
261
+ emit({ type: 'warn', msg: `LLM stopped making progress on extension (${missingNow} selectors still missing — likely pseudo-classes or JS-state classes the model can't infer). Stopping at ${((styleResult.coverageAfter || 0) * 100).toFixed(0)}%.` });
262
+ break;
263
+ }
264
+ } else {
265
+ stuckPasses = 0;
266
+ }
267
+ prevMissing = missingNow;
242
268
  }
243
269
  } catch (e) {
244
270
  emit({ type: 'warn', msg: `Auto-extend styles failed: ${(e.message || e).slice(0, 200)}` });
@@ -4254,27 +4280,57 @@ export async function _autoExtendStylesIfNeeded(projectName, config, emit, opts)
4254
4280
  } catch {}
4255
4281
  }
4256
4282
 
4257
- const sys = 'You are an expert frontend designer. Extend the existing CSS to cover ALL classes/ids found in the HTML, maintaining the existing design language (colors, gradients, typography, spacing). Add responsive mobile-first rules. Output ONLY the complete extended CSS file content — no markdown fences, no explanations.';
4283
+ const sys = `You are an expert frontend designer producing PRODUCTION-QUALITY CSS.
4284
+
4285
+ DESIGN REQUIREMENTS (NON-NEGOTIABLE):
4286
+ - WCAG AA contrast: text-on-background ratio >= 4.5:1. NO washed-out pastels for text. Body text must be near-black on light bg, or near-white on dark bg.
4287
+ - Use VIBRANT accent colors with sufficient saturation (HSL S >= 60%, L between 35-65% for accents).
4288
+ - Cover EVERY selector listed — including footer, header, nav, hero, sections, cards, buttons, forms, modals, tooltips.
4289
+ - Use modern CSS: flex/grid layouts, custom properties for colors, smooth transitions (200-300ms), subtle shadows.
4290
+
4291
+ OUTPUT: ONLY the complete extended CSS file content. No markdown fences, no explanations, no preamble. The output will be written directly to disk.`;
4258
4292
  const user =
4259
4293
  `Extend this CSS file: \`${targetRel}\`\n\n` +
4260
4294
  `Current CSS (${target.size} bytes, ${analysis.cssRuleCount} rules):\n\`\`\`css\n${target.content.slice(0, 6000)}\n\`\`\`\n\n` +
4261
- `Missing selectors (${analysis.missing.length}):\n${analysis.missing.slice(0, 50).join(', ')}\n\n` +
4295
+ `ALL missing selectors (${analysis.missing.length} — cover every single one):\n${analysis.missing.join(', ')}\n\n` +
4262
4296
  `HTML files for context:\n${htmlSample}\n\n` +
4263
- `Output the COMPLETE extended CSS file (existing rules + new rules for all missing selectors). Add:\n` +
4264
- `- img { max-width: 100%; height: auto; object-fit: cover; }\n` +
4265
- `- Layout for each missing class/id, coherent with the existing design language\n` +
4266
- `- Responsive breakpoints at 768px and 480px\n` +
4267
- `- Hover/transition states for interactive elements`;
4268
-
4269
- if (emit) emit({ type: 'status', msg: `CSS coverage ${(analysis.coverage * 100).toFixed(0)}% (${analysis.missing.length} selectors missing). Auto-extending ${targetRel} via LLM...` });
4270
-
4297
+ `Output the COMPLETE extended CSS file. Required additions:\n` +
4298
+ `- img { max-width: 100%; height: auto; object-fit: cover; display: block; }\n` +
4299
+ `- Footer, header, nav with proper layout and visible styling (NOT transparent backgrounds with low-contrast text)\n` +
4300
+ `- Rules for EVERY one of the ${analysis.missing.length} missing selectors above\n` +
4301
+ `- Responsive breakpoints at 1024px, 768px, 480px (mobile-first)\n` +
4302
+ `- Hover/focus/transition states for buttons, links, cards\n` +
4303
+ `- Color contrast must pass WCAG AA: text-on-bg >= 4.5:1`;
4304
+
4305
+ if (emit) emit({ type: 'status', msg: `CSS coverage ${(analysis.coverage * 100).toFixed(0)}% (${analysis.missing.length} selectors missing). Auto-extending ${targetRel} via LLM (timeout 60s)...` });
4306
+
4307
+ // Call LLM with timeout + progress heartbeat. Otherwise a slow/stuck Liara
4308
+ // leaves the user staring at "Auto-extending..." forever with no feedback.
4271
4309
  let body = '';
4310
+ let lastChunkAt = Date.now();
4311
+ const startedAt = Date.now();
4312
+ const timeoutMs = 60_000;
4313
+ const heartbeatInterval = setInterval(() => {
4314
+ if (!emit) return;
4315
+ const elapsed = ((Date.now() - startedAt) / 1000).toFixed(0);
4316
+ const sinceLast = ((Date.now() - lastChunkAt) / 1000).toFixed(0);
4317
+ emit({ type: 'status', msg: `LLM extend: ${elapsed}s elapsed, ${body.length} bytes received (${sinceLast}s since last chunk)` });
4318
+ }, 5_000);
4319
+
4272
4320
  try {
4273
- await callLLMStream(config, sys, user, (chunk) => { body += chunk; }, { max_tokens: 8192 });
4321
+ await Promise.race([
4322
+ callLLMStream(config, sys, user, (chunk) => {
4323
+ body += chunk;
4324
+ lastChunkAt = Date.now();
4325
+ }, { max_tokens: 8192 }),
4326
+ new Promise((_, reject) => setTimeout(() => reject(new Error('LLM timeout after ' + (timeoutMs / 1000) + 's')), timeoutMs)),
4327
+ ]);
4274
4328
  } catch (e) {
4275
- if (emit) emit({ type: 'warn', msg: `CSS extend failed: ${(e.message || e).slice(0, 200)}` });
4276
- return { extended: false, reason: 'llm_failed', error: e.message };
4329
+ clearInterval(heartbeatInterval);
4330
+ if (emit) emit({ type: 'warn', msg: `CSS extend failed: ${(e.message || e).slice(0, 200)}. Sandbox continues with current CSS — open chat to extend manually.` });
4331
+ return { extended: false, reason: 'llm_failed', error: e.message, partialBytes: body.length };
4277
4332
  }
4333
+ clearInterval(heartbeatInterval);
4278
4334
 
4279
4335
  // Strip markdown fences
4280
4336
  body = body.replace(/^```[a-zA-Z]*\n?/m, '').replace(/\n?```\s*$/m, '').trim();