nothumanallowed 16.0.56 → 16.0.57
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 +65 -32
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "16.0.
|
|
3
|
+
"version": "16.0.57",
|
|
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.57';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -4334,27 +4334,36 @@ export async function _autoExtendStylesIfNeeded(projectName, config, emit, opts)
|
|
|
4334
4334
|
} catch {}
|
|
4335
4335
|
}
|
|
4336
4336
|
|
|
4337
|
-
|
|
4337
|
+
// APPEND mode (16.0.57): output ONLY the new rules — server appends to
|
|
4338
|
+
// existing file. Prevents monotonic regression (LLM truncating + losing
|
|
4339
|
+
// existing rules) and dramatically reduces output token cost.
|
|
4340
|
+
const sys = `You are an expert frontend designer. Generate ONLY new CSS rules to ADD to an existing stylesheet.
|
|
4338
4341
|
|
|
4339
|
-
|
|
4340
|
-
-
|
|
4341
|
-
-
|
|
4342
|
-
-
|
|
4343
|
-
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4342
|
+
CRITICAL RULES:
|
|
4343
|
+
- Output ONLY the new CSS rules — do NOT repeat any existing rules.
|
|
4344
|
+
- Do NOT output markdown fences, explanations, or comments about what you're doing.
|
|
4345
|
+
- The output will be APPENDED to an existing CSS file. Do not include @import, @charset, or any preamble.
|
|
4346
|
+
- Generate at least one rule for EVERY listed missing selector.
|
|
4347
|
+
|
|
4348
|
+
DESIGN REQUIREMENTS:
|
|
4349
|
+
- WCAG AA contrast: text-on-bg ratio >= 4.5:1. NO washed-out pastels for text.
|
|
4350
|
+
- Vibrant accent colors (HSL S >= 60%, L 35-65%).
|
|
4351
|
+
- Match the existing CSS's design language (look at the colors/spacing in the existing rules).
|
|
4352
|
+
- Include responsive breakpoints (768px, 480px) where layout matters.
|
|
4353
|
+
- Hover/focus/transition states for interactive elements.`;
|
|
4354
|
+
// Pick a sample of missing selectors that fits in token budget. We cap at
|
|
4355
|
+
// 200 to keep one pass reasonable; remaining are picked up by next pass.
|
|
4356
|
+
const passSelectors = analysis.missing.slice(0, 200);
|
|
4346
4357
|
const user =
|
|
4347
|
-
`
|
|
4348
|
-
`
|
|
4349
|
-
`
|
|
4350
|
-
`
|
|
4351
|
-
`Output the
|
|
4358
|
+
`Existing CSS file: \`${targetRel}\` (${target.size} bytes, ${analysis.cssRuleCount} rules).\n\n` +
|
|
4359
|
+
`Sample of existing rules (for design-language reference — DO NOT repeat in output):\n\`\`\`css\n${target.content.slice(0, 3000)}\n\`\`\`\n\n` +
|
|
4360
|
+
`HTML context (snippet, for layout reference):\n${htmlSample.slice(0, 2500)}\n\n` +
|
|
4361
|
+
`Generate NEW CSS rules that cover these ${passSelectors.length} currently-uncovered selectors (out of ${analysis.missing.length} total):\n${passSelectors.join(', ')}\n\n` +
|
|
4362
|
+
`Output ONLY the new rules. Required additions if not already in existing CSS:\n` +
|
|
4352
4363
|
`- img { max-width: 100%; height: auto; object-fit: cover; display: block; }\n` +
|
|
4353
|
-
`-
|
|
4354
|
-
`-
|
|
4355
|
-
|
|
4356
|
-
`- Hover/focus/transition states for buttons, links, cards\n` +
|
|
4357
|
-
`- Color contrast must pass WCAG AA: text-on-bg >= 4.5:1`;
|
|
4364
|
+
`- footer { padding: 32px 16px; background: ...; color: ...; } (or similar — with visible contrast)\n` +
|
|
4365
|
+
`- Responsive @media queries at 768px and 480px for grid/flex sections.\n` +
|
|
4366
|
+
`Begin your output directly with the first CSS rule. No preamble.`;
|
|
4358
4367
|
|
|
4359
4368
|
const provider = config?.llm?.provider || 'unknown';
|
|
4360
4369
|
const model = config?.llm?.model || config?.llm?.[provider]?.model || 'default';
|
|
@@ -4374,23 +4383,30 @@ OUTPUT: ONLY the complete extended CSS file content. No markdown fences, no expl
|
|
|
4374
4383
|
let aborted = false;
|
|
4375
4384
|
const abortController = new AbortController();
|
|
4376
4385
|
|
|
4386
|
+
// Track byte velocity for an informative heartbeat: show b/s instead of
|
|
4387
|
+
// misleading "0s since last chunk" (which is almost always 0 when streaming).
|
|
4388
|
+
let prevBytes = 0;
|
|
4389
|
+
let prevHeartbeatAt = startedAt;
|
|
4377
4390
|
const heartbeatInterval = setInterval(() => {
|
|
4378
4391
|
if (!emit) return;
|
|
4379
|
-
const
|
|
4380
|
-
const
|
|
4381
|
-
|
|
4382
|
-
|
|
4392
|
+
const now = Date.now();
|
|
4393
|
+
const elapsed = ((now - startedAt) / 1000).toFixed(0);
|
|
4394
|
+
const bytesPerSec = Math.round((body.length - prevBytes) / Math.max(1, (now - prevHeartbeatAt) / 1000));
|
|
4395
|
+
prevBytes = body.length;
|
|
4396
|
+
prevHeartbeatAt = now;
|
|
4397
|
+
emit({ type: 'status', msg: `LLM extend: ${elapsed}s elapsed, ${body.length} bytes received (${bytesPerSec} b/s)` });
|
|
4398
|
+
if (!earlyWarningEmitted && body.length === 0 && now - startedAt > 15_000) {
|
|
4383
4399
|
earlyWarningEmitted = true;
|
|
4384
4400
|
emit({ type: 'warn', msg: `Provider ${provider} hasn't sent any data in 15s. If this is Liara, the free tier may be under load — try switching to Anthropic/OpenAI in Settings.` });
|
|
4385
4401
|
}
|
|
4386
4402
|
// No-progress timeout: only if STUCK (zero chunks for 30s)
|
|
4387
|
-
if (
|
|
4403
|
+
if (now - lastChunkAt > noProgressTimeoutMs && body.length > 0) {
|
|
4388
4404
|
timedOut = true;
|
|
4389
4405
|
aborted = true;
|
|
4390
4406
|
abortController.abort();
|
|
4391
4407
|
}
|
|
4392
4408
|
// Absolute timeout
|
|
4393
|
-
if (
|
|
4409
|
+
if (now - startedAt > absoluteTimeoutMs) {
|
|
4394
4410
|
timedOut = true;
|
|
4395
4411
|
aborted = true;
|
|
4396
4412
|
abortController.abort();
|
|
@@ -4418,30 +4434,47 @@ OUTPUT: ONLY the complete extended CSS file content. No markdown fences, no expl
|
|
|
4418
4434
|
}
|
|
4419
4435
|
clearInterval(heartbeatInterval);
|
|
4420
4436
|
|
|
4421
|
-
// Strip markdown fences
|
|
4437
|
+
// Strip markdown fences from the appended rules (LLM sometimes adds them)
|
|
4422
4438
|
body = body.replace(/^```[a-zA-Z]*\n?/m, '').replace(/\n?```\s*$/m, '').trim();
|
|
4423
|
-
if (_looksLikeLLMError(body) || body.length <
|
|
4424
|
-
if (emit) emit({ type: 'warn', msg: `CSS extend produced suspicious output (${body.length} bytes
|
|
4439
|
+
if (_looksLikeLLMError(body) || body.length < 200) {
|
|
4440
|
+
if (emit) emit({ type: 'warn', msg: `CSS extend produced suspicious output (${body.length} bytes of new rules) — keeping original.` });
|
|
4425
4441
|
return { extended: false, reason: 'output_too_short_or_error' };
|
|
4426
4442
|
}
|
|
4427
4443
|
|
|
4428
|
-
//
|
|
4444
|
+
// APPEND mode (16.0.57): keep ALL existing rules intact, add new ones at end.
|
|
4445
|
+
// Prevents monotonic regression where pass N replaces pass N-1's work with
|
|
4446
|
+
// a smaller file. Combined content = original + delimiter comment + new rules.
|
|
4447
|
+
const combined = target.content
|
|
4448
|
+
+ '\n\n/* === nha-webcraft: auto-extended rules (' + new Date().toISOString() + ') === */\n'
|
|
4449
|
+
+ body
|
|
4450
|
+
+ '\n';
|
|
4451
|
+
|
|
4429
4452
|
try {
|
|
4430
4453
|
fs.writeFileSync(target.abs + '.before-extend-' + Date.now(), target.content, 'utf-8');
|
|
4431
|
-
fs.writeFileSync(target.abs,
|
|
4454
|
+
fs.writeFileSync(target.abs, combined, 'utf-8');
|
|
4432
4455
|
} catch (e) {
|
|
4433
4456
|
return { extended: false, reason: 'write_failed', error: e.message };
|
|
4434
4457
|
}
|
|
4435
4458
|
|
|
4436
|
-
// Re-analyze to confirm improvement
|
|
4459
|
+
// Re-analyze to confirm improvement. APPEND mode guarantees coverage
|
|
4460
|
+
// monotonically increases (or stays equal), never decreases.
|
|
4437
4461
|
const after = _analyzeCssCoverage(dir);
|
|
4438
|
-
if (
|
|
4462
|
+
if (after.missing.length >= analysis.missing.length) {
|
|
4463
|
+
// No progress despite append — LLM produced rules that don't match selectors.
|
|
4464
|
+
// Roll back so the file doesn't bloat with useless rules.
|
|
4465
|
+
try { fs.writeFileSync(target.abs, target.content, 'utf-8'); } catch {}
|
|
4466
|
+
if (emit) emit({ type: 'warn', msg: `CSS extend rolled back: ${body.length} bytes of new rules added but no selectors covered (model output didn't match needed selectors).` });
|
|
4467
|
+
return { extended: false, reason: 'no_coverage_gain', missingBefore: analysis.missing.length, missingAfter: after.missing.length };
|
|
4468
|
+
}
|
|
4469
|
+
|
|
4470
|
+
if (emit) emit({ type: 'status', msg: `CSS extended: ${targetRel} ${target.size} → ${combined.length} bytes (appended ${body.length} bytes). Coverage ${(analysis.coverage * 100).toFixed(0)}% → ${(after.coverage * 100).toFixed(0)}%, ${analysis.missing.length} → ${after.missing.length} selectors missing.` });
|
|
4439
4471
|
|
|
4440
4472
|
return {
|
|
4441
4473
|
extended: true,
|
|
4442
4474
|
file: targetRel,
|
|
4443
4475
|
sizeBefore: target.size,
|
|
4444
|
-
sizeAfter:
|
|
4476
|
+
sizeAfter: combined.length,
|
|
4477
|
+
appendedBytes: body.length,
|
|
4445
4478
|
coverageBefore: analysis.coverage,
|
|
4446
4479
|
coverageAfter: after.coverage,
|
|
4447
4480
|
missingBefore: analysis.missing.length,
|