nothumanallowed 13.5.152 → 13.5.153

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": "13.5.152",
3
+ "version": "13.5.153",
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": {
@@ -4652,6 +4652,39 @@ ${completedHeadings ? `## SECTIONS ALREADY WRITTEN (headings only):\n${completed
4652
4652
  return;
4653
4653
  }
4654
4654
 
4655
+ // POST /api/studio/webcraft/stream { system, user, max_tokens } → SSE token stream
4656
+ // Used by WebCraft generation for live typewriter effect per file
4657
+ if (pathname === '/api/studio/webcraft/stream' && method === 'POST') {
4658
+ const body = await parseBody(req, 131072);
4659
+ if (!body.system || !body.user) {
4660
+ sendJSON(res, 400, { error: 'system and user required' });
4661
+ logRequest(method, pathname, 400, Date.now() - start);
4662
+ return;
4663
+ }
4664
+ res.writeHead(200, {
4665
+ 'Content-Type': 'text/event-stream',
4666
+ 'Cache-Control': 'no-cache',
4667
+ 'Connection': 'keep-alive',
4668
+ 'Access-Control-Allow-Origin': '*',
4669
+ });
4670
+ let fullText = '';
4671
+ let tokIn = 0, tokOut = 0;
4672
+ try {
4673
+ await callLLMStream(config, body.system, body.user, (token) => {
4674
+ fullText += token;
4675
+ tokOut++;
4676
+ res.write('data: ' + JSON.stringify({ type: 'token', token }) + '\n\n');
4677
+ }, { max_tokens: body.max_tokens || 16384, temperature: 0.3 });
4678
+ tokIn = Math.round((body.system.length + body.user.length) / 4);
4679
+ res.write('data: ' + JSON.stringify({ type: 'done', text: fullText, usage: { prompt_tokens: tokIn, completion_tokens: tokOut } }) + '\n\n');
4680
+ } catch (e) {
4681
+ res.write('data: ' + JSON.stringify({ type: 'error', message: e.message }) + '\n\n');
4682
+ }
4683
+ res.end();
4684
+ logRequest(method, pathname, 200, Date.now() - start);
4685
+ return;
4686
+ }
4687
+
4655
4688
  // ── WebCraft Projects — list and delete saved projects ──────────────────
4656
4689
  // GET /api/studio/webcraft/projects → { projects: [{name, description, fileCount, createdAt, dir}] }
4657
4690
  // DELETE /api/studio/webcraft/projects/:name → { ok }
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 = '13.5.152';
8
+ export const VERSION = '13.5.153';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -8864,19 +8864,20 @@ async function wcGenerate() {
8864
8864
  }
8865
8865
 
8866
8866
  // Helper: generate one file (with two-pass split for large CSS files)
8867
- async function wcGenOneFile(fp, signal) {
8867
+ // onLiveUpdate(partialContent) is called on each token for live display
8868
+ async function wcGenOneFile(fp, signal, onLiveUpdate) {
8868
8869
  var _nl2 = String.fromCharCode(10);
8869
8870
  var splitPrompts = WC_CSS_SPLIT[fp.name];
8870
8871
  if (splitPrompts) {
8871
- // Two-pass generation: call LLM twice and concatenate
8872
- var part1 = await wcCallLLM(sysPreamble, splitPrompts[0] + _nl2 + _nl2 + 'File: ' + fp.name, signal, fp.lang, 8192);
8872
+ // Two-pass generation: streaming on first pass only
8873
+ var part1 = await wcCallLLM(sysPreamble, splitPrompts[0] + _nl2 + _nl2 + 'File: ' + fp.name, signal, fp.lang, 8192, onLiveUpdate);
8873
8874
  part1 = wcStripFences(part1);
8874
8875
  if (signal && signal.aborted) return part1;
8875
- var part2 = await wcCallLLM(sysPreamble, splitPrompts[1] + _nl2 + _nl2 + 'File: ' + fp.name, signal, fp.lang, 8192);
8876
+ var part2 = await wcCallLLM(sysPreamble, splitPrompts[1] + _nl2 + _nl2 + 'File: ' + fp.name, signal, fp.lang, 8192, function(p2) { if (onLiveUpdate) onLiveUpdate(part1 + _nl2 + _nl2 + p2); });
8876
8877
  part2 = wcStripFences(part2);
8877
8878
  return part1 + _nl2 + _nl2 + part2;
8878
8879
  }
8879
- var content = await wcCallLLM(sysPreamble, fp.prompt + _nl2 + _nl2 + 'File to generate: ' + fp.name, signal, fp.lang);
8880
+ var content = await wcCallLLM(sysPreamble, fp.prompt + _nl2 + _nl2 + 'File to generate: ' + fp.name, signal, fp.lang, undefined, onLiveUpdate);
8880
8881
  return wcStripFences(content);
8881
8882
  }
8882
8883
 
@@ -8889,6 +8890,23 @@ async function wcGenerate() {
8889
8890
  wcState.activeFile = 0;
8890
8891
  renderWebCraft(document.getElementById('content'));
8891
8892
 
8893
+ // Live render throttle — update UI at most every 120ms per file during streaming
8894
+ var _wcLiveRenderTimers = {};
8895
+ function wcLiveUpdateFile(fpName, fpLang, partialContent) {
8896
+ for (var li = 0; li < wcState.generatedFiles.length; li++) {
8897
+ if (wcState.generatedFiles[li].name === fpName) {
8898
+ wcState.generatedFiles[li].content = partialContent;
8899
+ wcState.generatedFiles[li]._pending = false;
8900
+ break;
8901
+ }
8902
+ }
8903
+ if (_wcLiveRenderTimers[fpName]) return; // throttle
8904
+ _wcLiveRenderTimers[fpName] = setTimeout(function() {
8905
+ delete _wcLiveRenderTimers[fpName];
8906
+ renderWebCraft(document.getElementById('content'));
8907
+ }, 120);
8908
+ }
8909
+
8892
8910
  // Generate in parallel batches of 4 — each call is independent/fresh to Liara
8893
8911
  var BATCH = 4;
8894
8912
  var doneCount = 0;
@@ -8897,7 +8915,8 @@ async function wcGenerate() {
8897
8915
  var batch = filePlan.slice(bi, bi + BATCH);
8898
8916
  wcUpdateGenOverlay(doneCount, filePlan.length, batch.map(function(f){ return f.name; }).join(', '));
8899
8917
  var results = await Promise.allSettled(batch.map(function(fp) {
8900
- return wcGenOneFile(fp, _wcGenAbortCtrl ? _wcGenAbortCtrl.signal : null).then(function(c){ return { fp: fp, content: c }; });
8918
+ var liveCallback = function(partial) { wcLiveUpdateFile(fp.name, fp.lang, partial); };
8919
+ return wcGenOneFile(fp, _wcGenAbortCtrl ? _wcGenAbortCtrl.signal : null, liveCallback).then(function(c){ return { fp: fp, content: c }; });
8901
8920
  }));
8902
8921
  results.forEach(function(r) {
8903
8922
  if (r.status === 'fulfilled') {
@@ -9120,7 +9139,57 @@ function wcIsTruncated(content, lang) {
9120
9139
  return false;
9121
9140
  }
9122
9141
 
9123
- async function wcCallLLMRaw(sys, user, signal, maxTok) {
9142
+ async function wcCallLLMRaw(sys, user, signal, maxTok, onToken) {
9143
+ // Streaming path: use SSE endpoint so tokens appear live in the file editor
9144
+ if (onToken) {
9145
+ var streamOpts = {
9146
+ method: 'POST',
9147
+ headers: {'Content-Type':'application/json'},
9148
+ body: JSON.stringify({system: sys, user: user, max_tokens: maxTok || 16384})
9149
+ };
9150
+ if (signal) streamOpts.signal = signal;
9151
+ for (var sa = 0; sa < 3; sa++) {
9152
+ if (signal && signal.aborted) throw new DOMException('Aborted', 'AbortError');
9153
+ var sr = await fetch(API + '/api/studio/webcraft/stream', streamOpts);
9154
+ if (!sr.ok) {
9155
+ if (sr.status < 500 || sa === 2) throw new Error('LLM stream error ' + sr.status);
9156
+ await new Promise(function(resolve) { setTimeout(resolve, 2000); });
9157
+ continue;
9158
+ }
9159
+ var sreader = sr.body.getReader();
9160
+ var sdec = new TextDecoder();
9161
+ var sbuf = '';
9162
+ var fullText = '';
9163
+ while (true) {
9164
+ var sres = await sreader.read();
9165
+ if (sres.done) break;
9166
+ sbuf += sdec.decode(sres.value, {stream: true});
9167
+ var sparts = sbuf.split(String.fromCharCode(10) + String.fromCharCode(10));
9168
+ sbuf = sparts.pop();
9169
+ for (var si = 0; si < sparts.length; si++) {
9170
+ var sline = sparts[si].replace(/^data: /, '').trim();
9171
+ if (!sline) continue;
9172
+ try {
9173
+ var sev = JSON.parse(sline);
9174
+ if (sev.type === 'token') {
9175
+ fullText += sev.token;
9176
+ onToken(fullText);
9177
+ } else if (sev.type === 'done') {
9178
+ if (sev.usage) {
9179
+ _wcTokIn += (sev.usage.prompt_tokens || 0);
9180
+ _wcTokOut += (sev.usage.completion_tokens || 0);
9181
+ }
9182
+ } else if (sev.type === 'error') {
9183
+ throw new Error(sev.message || 'Stream error');
9184
+ }
9185
+ } catch(_) {}
9186
+ }
9187
+ }
9188
+ return fullText;
9189
+ }
9190
+ }
9191
+
9192
+ // Non-streaming fallback (used by repair and continuation passes)
9124
9193
  var fetchOpts = {
9125
9194
  method: 'POST',
9126
9195
  headers: {'Content-Type':'application/json'},
@@ -9132,12 +9201,10 @@ async function wcCallLLMRaw(sys, user, signal, maxTok) {
9132
9201
  var r = await fetch(API + '/api/studio/webcraft', fetchOpts);
9133
9202
  if (r.ok) {
9134
9203
  var d = await r.json();
9135
- // Accumulate token counts if the server returns usage data
9136
9204
  if (d && d.usage) {
9137
9205
  _wcTokIn += (d.usage.prompt_tokens || d.usage.input_tokens || 0);
9138
9206
  _wcTokOut += (d.usage.completion_tokens || d.usage.output_tokens || 0);
9139
9207
  } else if (d && d.text) {
9140
- // Estimate from char count (4 chars ≈ 1 token)
9141
9208
  _wcTokIn += Math.round((sys.length + user.length) / 4);
9142
9209
  _wcTokOut += Math.round((d.text || '').length / 4);
9143
9210
  }
@@ -9153,9 +9220,9 @@ async function wcCallLLMRaw(sys, user, signal, maxTok) {
9153
9220
  }
9154
9221
  }
9155
9222
 
9156
- async function wcCallLLM(sys, user, signal, lang, maxTok) {
9157
- var content = await wcCallLLMRaw(sys, user, signal, maxTok);
9158
- // Continuation loop: if response is truncated, ask model to continue
9223
+ async function wcCallLLM(sys, user, signal, lang, maxTok, onToken) {
9224
+ var content = await wcCallLLMRaw(sys, user, signal, maxTok, onToken);
9225
+ // Continuation loop: if response is truncated, ask model to continue (no streaming for continuations)
9159
9226
  var maxContinuations = 2;
9160
9227
  for (var ci = 0; ci < maxContinuations; ci++) {
9161
9228
  if (!wcIsTruncated(content, lang || 'text')) break;
@@ -9166,6 +9233,7 @@ async function wcCallLLM(sys, user, signal, lang, maxTok) {
9166
9233
  var continuation = await wcCallLLMRaw(sys, continuePrompt, signal, maxTok);
9167
9234
  if (!continuation || continuation.trim().length < 5) break;
9168
9235
  content = content + String.fromCharCode(10) + continuation;
9236
+ if (onToken) onToken(content);
9169
9237
  }
9170
9238
  return content;
9171
9239
  }