nothumanallowed 13.2.78 → 13.2.79

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.2.78",
3
+ "version": "13.2.79",
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 = '13.2.78';
8
+ export const VERSION = '13.2.79';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -81,6 +81,34 @@ function renderMd(raw) {
81
81
  // Close lists on blank or non-list line
82
82
  if (inUl) { out.push('</ul>'); inUl = false; }
83
83
  if (inOl) { out.push('</ol>'); inOl = false; }
84
+ // Markdown table: line starting with | and containing at least two |
85
+ if (l.charAt(0) === '|' && l.lastIndexOf('|') > 0) {
86
+ // Separator row (---|---) — skip, handled via <thead>
87
+ if (/^\|[\s\-|:]+\|$/.test(l.trim())) continue;
88
+ var cells = l.split('|').slice(1,-1).map(function(c){ return c.trim(); });
89
+ // Check if next line is a separator → this is a header row
90
+ var nextL = lines[i+1] ? lines[i+1].trim() : '';
91
+ var isHeader = /^\|[\s\-|:]+\|$/.test(nextL);
92
+ if (isHeader) {
93
+ out.push('<table class="md-table"><thead><tr>' + cells.map(function(c){ return '<th>'+c+'</th>'; }).join('') + '</tr></thead><tbody>');
94
+ } else {
95
+ // Check if we need to open tbody (no header case)
96
+ var prevOut = out[out.length-1] || '';
97
+ if (prevOut.indexOf('<tbody>') === -1 && prevOut.indexOf('<tr>') === -1) {
98
+ out.push('<table class="md-table"><tbody>');
99
+ }
100
+ out.push('<tr>' + cells.map(function(c){ return '<td>'+c+'</td>'; }).join('') + '</tr>');
101
+ // Close table if next line is not a table row
102
+ var nextL2 = lines[i+1] ? lines[i+1].trim() : '';
103
+ if (!nextL2 || nextL2.charAt(0) !== '|') { out.push('</tbody></table>'); }
104
+ }
105
+ continue;
106
+ }
107
+ // Close open table if we hit a non-table line
108
+ var lastOut = out[out.length-1] || '';
109
+ if (lastOut.indexOf('<tr>') !== -1 && lastOut.indexOf('</table>') === -1) {
110
+ out.push('</tbody></table>');
111
+ }
84
112
  // Blockquote > text
85
113
  var bqm = l.match(/^&gt; (.+)/);
86
114
  if (bqm) { out.push('<blockquote class="md-bq">'+bqm[1]+'</blockquote>'); continue; }
@@ -3388,68 +3416,221 @@ function downloadStudioPDF() {
3388
3416
  return;
3389
3417
  }
3390
3418
 
3391
- // Build sections for each agent
3419
+ // ── Markdown → HTML for PDF (full support: tables, lists, headers, inline) ──
3392
3420
  function mdToPdfHtml(raw) {
3393
- var lines = raw.split(String.fromCharCode(10));
3421
+ var NL2 = String.fromCharCode(10);
3422
+ var lines = raw.split(NL2);
3394
3423
  var out = '';
3395
- var inList = false;
3424
+ var inUl = false, inOl = false, inTable = false, inTbody = false;
3425
+ function closeAll() {
3426
+ if (inUl) { out += '</ul>'; inUl = false; }
3427
+ if (inOl) { out += '</ol>'; inOl = false; }
3428
+ if (inTable) { if (inTbody) { out += '</tbody>'; inTbody = false; } out += '</table>'; inTable = false; }
3429
+ }
3430
+ function inlineFormat(t) {
3431
+ t = t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
3432
+ t = t.replace(new RegExp('[*][*]([^*]+)[*][*]','g'),'<strong>$1</strong>');
3433
+ t = t.replace(new RegExp('[*]([^*]+)[*]','g'),'<em>$1</em>');
3434
+ t = t.replace(/~~([^~]+)~~/g,'<del>$1</del>');
3435
+ return t;
3436
+ }
3396
3437
  for (var li = 0; li < lines.length; li++) {
3397
- var line = lines[li]
3398
- .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
3399
- // Inline bold: **text**
3400
- line = line.replace(new RegExp('[*][*]([^*]+)[*][*]','g'),'<strong>$1</strong>');
3401
- // Inline italic: *text*
3402
- line = line.replace(new RegExp('[*]([^*]+)[*]','g'),'<em>$1</em>');
3403
- if (line.slice(0,4) === '### ') { if(inList){out+='</ul>';inList=false;} out += '<h3>' + line.slice(4) + '</h3>'; continue; }
3404
- if (line.slice(0,3) === '## ') { if(inList){out+='</ul>';inList=false;} out += '<h2>' + line.slice(3) + '</h2>'; continue; }
3405
- if (line.slice(0,2) === '# ') { if(inList){out+='</ul>';inList=false;} out += '<h2>' + line.slice(2) + '</h2>'; continue; }
3406
- if (line.slice(0,2) === '- ' || line.slice(0,2) === '* ') {
3407
- if (!inList) { out += '<ul>'; inList = true; }
3408
- out += '<li>' + line.slice(2) + '</li>';
3438
+ var line = lines[li];
3439
+ var trimmed = line.trim();
3440
+ // Headers
3441
+ if (trimmed.slice(0,4) === '### ') { closeAll(); out += '<h3>' + inlineFormat(trimmed.slice(4)) + '</h3>'; continue; }
3442
+ if (trimmed.slice(0,3) === '## ') { closeAll(); out += '<h2>' + inlineFormat(trimmed.slice(3)) + '</h2>'; continue; }
3443
+ if (trimmed.slice(0,2) === '# ') { closeAll(); out += '<h1>' + inlineFormat(trimmed.slice(2)) + '</h1>'; continue; }
3444
+ // Horizontal rule
3445
+ if (/^---+$/.test(trimmed)) { closeAll(); out += '<hr>'; continue; }
3446
+ // Markdown table
3447
+ if (trimmed.charAt(0) === '|' && trimmed.lastIndexOf('|') > 0) {
3448
+ // Separator row signals end of header
3449
+ if (/^\|[\s\-|:]+\|$/.test(trimmed)) {
3450
+ if (inTable) { out += '</thead><tbody>'; inTbody = true; }
3451
+ continue;
3452
+ }
3453
+ var cells = trimmed.split('|').slice(1,-1).map(function(c){ return inlineFormat(c.trim()); });
3454
+ var nextTrimmed = lines[li+1] ? lines[li+1].trim() : '';
3455
+ var nextIsSep = /^\|[\s\-|:]+\|$/.test(nextTrimmed);
3456
+ if (!inTable) {
3457
+ out += '<table>';
3458
+ inTable = true;
3459
+ if (nextIsSep) { out += '<thead>'; inTbody = false; }
3460
+ else { out += '<tbody>'; inTbody = true; }
3461
+ }
3462
+ var tag = (!inTbody) ? 'th' : 'td';
3463
+ out += '<tr>' + cells.map(function(c){ return '<'+tag+'>'+c+'</'+tag+'>'; }).join('') + '</tr>';
3409
3464
  continue;
3410
3465
  }
3411
- if (inList) { out += '</ul>'; inList = false; }
3412
- if (line.trim() === '') { out += '</p><p>'; } else { out += line + '<br>'; }
3466
+ // Close table if not a table row
3467
+ if (inTable) { if (inTbody) { out += '</tbody>'; inTbody = false; } out += '</table>'; inTable = false; }
3468
+ // Unordered list
3469
+ if (/^[\-\*] /.test(trimmed)) {
3470
+ if (inOl) { out += '</ol>'; inOl = false; }
3471
+ if (!inUl) { out += '<ul>'; inUl = true; }
3472
+ out += '<li>' + inlineFormat(trimmed.slice(2)) + '</li>';
3473
+ continue;
3474
+ }
3475
+ // Ordered list
3476
+ var olMatch = trimmed.match(/^\d+\. (.+)/);
3477
+ if (olMatch) {
3478
+ if (inUl) { out += '</ul>'; inUl = false; }
3479
+ if (!inOl) { out += '<ol>'; inOl = true; }
3480
+ out += '<li>' + inlineFormat(olMatch[1]) + '</li>';
3481
+ continue;
3482
+ }
3483
+ // Close lists
3484
+ if (inUl) { out += '</ul>'; inUl = false; }
3485
+ if (inOl) { out += '</ol>'; inOl = false; }
3486
+ // Blank line
3487
+ if (trimmed === '') { out += '<div style="height:6px"></div>'; continue; }
3488
+ // Paragraph
3489
+ out += '<p>' + inlineFormat(trimmed) + '</p>';
3413
3490
  }
3414
- if (inList) out += '</ul>';
3415
- return '<p>' + out + '</p>';
3491
+ closeAll();
3492
+ return out;
3416
3493
  }
3417
- var sectionsHtml = nodes.map(function(n) {
3418
- if (!n.output || n.output === '(no output)' || n.agent === 'CanvasAgent') return '';
3419
- return '<div class="section"><div class="agent-label">' + (n.icon||'') + ' ' + esc(n.label||n.agent) + '</div><div class="section-body">' + mdToPdfHtml(n.output) + '</div></div>';
3494
+
3495
+ // ── Collect workflow metadata ─────────────────────────────────────────────
3496
+ var activeNodes = nodes.filter(function(n){ return n.output && n.output !== '(no output)' && n.agent !== 'CanvasAgent'; });
3497
+ var totalTokensIn = studioTokens ? (studioTokens.in || 0) : 0;
3498
+ var totalTokensOut = studioTokens ? (studioTokens.out || 0) : 0;
3499
+ var agentNames = activeNodes.map(function(n){ return (n.icon||'') + ' ' + esc(n.label||n.agent); });
3500
+ var nowTime = new Date().toLocaleTimeString('it-IT', {hour:'2-digit',minute:'2-digit'});
3501
+
3502
+ // ── Section HTML ──────────────────────────────────────────────────────────
3503
+ var sectionsHtml = activeNodes.map(function(n, idx) {
3504
+ var agentColor = ['#4f46e5','#0891b2','#059669','#d97706','#dc2626','#7c3aed','#0284c7'][idx % 7];
3505
+ return '<div class="section">' +
3506
+ '<div class="agent-header" style="border-left-color:' + agentColor + '">' +
3507
+ '<span class="agent-icon">' + (n.icon||'&#9632;') + '</span>' +
3508
+ '<div><div class="agent-name">' + esc(n.label||n.agent) + '</div>' +
3509
+ '<div class="agent-sub">' + esc(n.agent) + ' &nbsp;&#183;&nbsp; Step ' + (idx+1) + ' di ' + activeNodes.length + '</div></div>' +
3510
+ '</div>' +
3511
+ '<div class="section-body">' + mdToPdfHtml(n.output) + '</div>' +
3512
+ '</div>';
3420
3513
  }).join('');
3421
3514
 
3422
- // Include canvas if present (as an embedded iframe screenshot fallback note)
3423
- var canvasNote = studioState.canvas ? ["<div class='section canvas-note'><div class='agent-label'>&#9632; Canvas Report</div><div class='section-body'><p><em>Il Canvas HTML e disponibile in Studio. Aprire il pannello Canvas e usare la funzione stampa del browser per includerlo.</em></p></div></div>"].join("") : "";
3424
-
3425
- var html = '<!DOCTYPE html><html lang="it"><head><meta charset="UTF-8"><title>' + esc(task) + '</title><style>' +
3426
- 'body{font-family:"Helvetica Neue",Arial,sans-serif;color:#111;background:#fff;margin:0;padding:0}' +
3427
- '.cover{background:#0d0d0d;color:#fff;padding:60px 50px;page-break-after:always}' +
3428
- '.cover h1{font-size:28px;font-weight:700;margin:0 0 12px;color:#00ff41}' +
3429
- '.cover .meta{font-size:13px;color:#aaa;margin-top:8px}' +
3430
- '.cover .task{font-size:15px;color:#e0e0e0;margin-top:20px;line-height:1.6;max-width:700px}' +
3431
- '.cover .brand{font-size:11px;color:#555;margin-top:40px;letter-spacing:2px;text-transform:uppercase}' +
3432
- '.toc{padding:40px 50px;border-bottom:1px solid #e0e0e0;page-break-after:always}' +
3433
- '.toc h2{font-size:14px;text-transform:uppercase;letter-spacing:2px;color:#555;margin-bottom:16px}' +
3434
- '.toc ol{margin:0;padding-left:20px;font-size:13px;line-height:2}' +
3435
- '.section{padding:36px 50px;border-bottom:1px solid #f0f0f0;page-break-inside:avoid}' +
3515
+ // ── Full HTML document ────────────────────────────────────────────────────
3516
+ var html = '<!DOCTYPE html><html lang="it"><head><meta charset="UTF-8"><title>' + esc(task) + '</title>' +
3517
+ '<link rel="preconnect" href="https://fonts.googleapis.com">' +
3518
+ '<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">' +
3519
+ '<style>' +
3520
+ '*{box-sizing:border-box;margin:0;padding:0}' +
3521
+ 'body{font-family:"Inter",system-ui,sans-serif;color:#1e1e2e;background:#fff;font-size:13px;line-height:1.7}' +
3522
+
3523
+ // Cover
3524
+ '.cover{background:linear-gradient(135deg,#1e1b4b 0%,#312e81 40%,#1e3a5f 100%);color:#fff;padding:64px 60px 56px;page-break-after:always;position:relative;overflow:hidden}' +
3525
+ '.cover::before{content:"";position:absolute;top:-80px;right:-80px;width:360px;height:360px;background:radial-gradient(circle,rgba(99,102,241,.25) 0%,transparent 70%);pointer-events:none}' +
3526
+ '.cover-brand{font-size:10px;font-weight:600;letter-spacing:3px;text-transform:uppercase;color:rgba(255,255,255,.5);margin-bottom:32px;display:flex;align-items:center;gap:8px}' +
3527
+ '.cover-brand::before{content:"";display:inline-block;width:24px;height:2px;background:#6366f1}' +
3528
+ '.cover h1{font-size:30px;font-weight:800;line-height:1.25;color:#fff;margin-bottom:20px;max-width:680px}' +
3529
+ '.cover-task-label{font-size:10px;font-weight:600;letter-spacing:2px;text-transform:uppercase;color:rgba(255,255,255,.4);margin-bottom:8px}' +
3530
+ '.cover-task{font-size:14px;color:rgba(255,255,255,.8);line-height:1.65;max-width:660px;font-style:italic;padding:14px 18px;background:rgba(255,255,255,.07);border-radius:8px;border-left:3px solid #6366f1}' +
3531
+
3532
+ // Stats bar
3533
+ '.cover-stats{display:flex;gap:0;margin-top:40px;border-top:1px solid rgba(255,255,255,.12);padding-top:28px}' +
3534
+ '.stat{flex:1;padding-right:28px;border-right:1px solid rgba(255,255,255,.1)}' +
3535
+ '.stat:last-child{border-right:none;padding-right:0;padding-left:28px}' +
3536
+ '.stat:not(:first-child){padding-left:28px}' +
3537
+ '.stat-value{font-size:22px;font-weight:800;color:#fff;line-height:1}' +
3538
+ '.stat-label{font-size:10px;font-weight:500;letter-spacing:1.5px;text-transform:uppercase;color:rgba(255,255,255,.45);margin-top:5px}' +
3539
+
3540
+ // Workflow bar
3541
+ '.workflow-bar{padding:28px 60px;background:#f8f7ff;border-bottom:1px solid #e8e5ff;display:flex;align-items:center;gap:0;flex-wrap:wrap}' +
3542
+ '.wf-step{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:600;color:#4f46e5;white-space:nowrap}' +
3543
+ '.wf-arrow{color:#c7c2f0;margin:0 6px;font-size:14px}' +
3544
+ '.wf-label{font-size:9px;font-weight:500;letter-spacing:1.5px;text-transform:uppercase;color:#9c97c7;margin-right:16px}' +
3545
+
3546
+ // TOC
3547
+ '.toc{padding:36px 60px;border-bottom:1px solid #eee;page-break-after:always}' +
3548
+ '.toc-title{font-size:10px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:#9ca3af;margin-bottom:18px}' +
3549
+ '.toc-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:10px}' +
3550
+ '.toc-item{display:flex;align-items:center;gap:10px;padding:10px 14px;background:#f9f9fc;border-radius:8px;border:1px solid #ede9fe}' +
3551
+ '.toc-num{width:22px;height:22px;border-radius:50%;background:#4f46e5;color:#fff;font-size:10px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0}' +
3552
+ '.toc-name{font-size:12px;font-weight:600;color:#1e1e2e}' +
3553
+
3554
+ // Sections
3555
+ '.section{padding:36px 60px;border-bottom:1px solid #f0f0f5;page-break-inside:avoid}' +
3436
3556
  '.section:last-child{border-bottom:none}' +
3437
- '.agent-label{font-size:11px;text-transform:uppercase;letter-spacing:1.5px;color:#888;font-weight:700;margin-bottom:14px}' +
3438
- '.section-body{font-size:13px;line-height:1.8;color:#222}' +
3439
- '.section-body h2{font-size:16px;font-weight:700;color:#111;margin:20px 0 8px}' +
3440
- '.section-body h3{font-size:14px;font-weight:600;color:#333;margin:16px 0 6px}' +
3441
- '.section-body ul{margin:8px 0;padding-left:20px}' +
3557
+ '.agent-header{display:flex;align-items:center;gap:14px;margin-bottom:20px;padding-left:14px;border-left:3px solid #4f46e5}' +
3558
+ '.agent-icon{font-size:22px;line-height:1}' +
3559
+ '.agent-name{font-size:14px;font-weight:700;color:#1e1e2e}' +
3560
+ '.agent-sub{font-size:10px;font-weight:500;color:#9ca3af;letter-spacing:.5px;margin-top:2px}' +
3561
+ '.section-body{font-size:13px;line-height:1.75;color:#374151}' +
3562
+ '.section-body h1{font-size:18px;font-weight:700;color:#1e1e2e;margin:20px 0 10px;border-bottom:1px solid #e5e7eb;padding-bottom:6px}' +
3563
+ '.section-body h2{font-size:15px;font-weight:700;color:#1e1e2e;margin:18px 0 8px}' +
3564
+ '.section-body h3{font-size:13px;font-weight:600;color:#4f46e5;margin:14px 0 6px}' +
3565
+ '.section-body p{margin:0 0 10px}' +
3566
+ '.section-body ul{margin:8px 0 10px 18px;list-style:disc}' +
3567
+ '.section-body ol{margin:8px 0 10px 18px}' +
3442
3568
  '.section-body li{margin-bottom:4px}' +
3443
- '.section-body strong{font-weight:700}' +
3444
- '.section-body p{margin:0 0 12px}' +
3445
- '.canvas-note{background:#f9f9f9}' +
3446
- '.footer-bar{padding:20px 50px;background:#f9f9f9;font-size:10px;color:#aaa;text-align:center;letter-spacing:1px}' +
3447
- '@media print{body{-webkit-print-color-adjust:exact;print-color-adjust:exact}.cover{page-break-after:always}.toc{page-break-after:always}}' +
3569
+ '.section-body strong{font-weight:700;color:#1e1e2e}' +
3570
+ '.section-body em{color:#6366f1;font-style:italic}' +
3571
+ '.section-body table{width:100%;border-collapse:collapse;margin:14px 0;font-size:12px}' +
3572
+ '.section-body th{background:#f0eeff;color:#4f46e5;font-weight:700;text-align:left;padding:8px 12px;border:1px solid #e0d9ff;font-size:11px;letter-spacing:.3px}' +
3573
+ '.section-body td{padding:7px 12px;border:1px solid #ede9fe;color:#374151}' +
3574
+ '.section-body tr:nth-child(even) td{background:#f9f8ff}' +
3575
+ '.section-body hr{border:none;border-top:1px solid #e5e7eb;margin:16px 0}' +
3576
+ '.section-body blockquote{border-left:3px solid #6366f1;padding:8px 14px;background:#f5f3ff;border-radius:0 6px 6px 0;color:#4f46e5;font-style:italic;margin:10px 0}' +
3577
+
3578
+ // Footer
3579
+ '.footer-bar{padding:18px 60px;background:#f8f7ff;border-top:2px solid #e8e5ff;display:flex;justify-content:space-between;align-items:center}' +
3580
+ '.footer-left{font-size:10px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:#9c97c7}' +
3581
+ '.footer-right{font-size:10px;color:#b8b4d4}' +
3582
+
3583
+ '@media print{' +
3584
+ 'body{-webkit-print-color-adjust:exact;print-color-adjust:exact}' +
3585
+ '.cover{page-break-after:always}' +
3586
+ '.toc{page-break-after:always}' +
3587
+ '.section{page-break-inside:avoid}' +
3588
+ '}' +
3448
3589
  '</style></head><body>' +
3449
- '<div class="cover"><div class="brand">NotHumanAllowed — NHA Studio</div><h1>' + esc(task.length > 80 ? task.slice(0,80)+'...' : task) + '</h1><div class="meta">Generato il ' + today + ' &nbsp;·&nbsp; ' + nodes.filter(function(n){return n.agent!=='CanvasAgent'}).length + ' agenti</div><div class="task">' + esc(task) + '</div></div>' +
3450
- '<div class="toc"><h2>Indice</h2><ol>' + nodes.filter(function(n){return n.output&&n.output!=='(no output)'&&n.agent!=='CanvasAgent'}).map(function(n){return '<li>' + esc(n.label||n.agent) + '</li>';}).join('') + '</ol></div>' +
3451
- sectionsHtml + canvasNote +
3452
- '<div class="footer-bar">NHA Studio &nbsp;·&nbsp; nothumanallowed.com &nbsp;·&nbsp; ' + today + '</div>' +
3590
+
3591
+ // ── Cover ────────────────────────────────────────────────────────────────
3592
+ '<div class="cover">' +
3593
+ '<div class="cover-brand">NotHumanAllowed &nbsp; NHA Studio</div>' +
3594
+ '<h1>' + esc(task.length > 90 ? task.slice(0,90)+'...' : task) + '</h1>' +
3595
+ '<div class="cover-task-label">Workflow richiesto</div>' +
3596
+ '<div class="cover-task">' + esc(task) + '</div>' +
3597
+ '<div class="cover-stats">' +
3598
+ '<div class="stat"><div class="stat-value">' + activeNodes.length + '</div><div class="stat-label">Agenti eseguiti</div></div>' +
3599
+ '<div class="stat"><div class="stat-value">' + today + '</div><div class="stat-label">Data generazione</div></div>' +
3600
+ '<div class="stat"><div class="stat-value">' + nowTime + '</div><div class="stat-label">Ora</div></div>' +
3601
+ (totalTokensIn > 0 ? '<div class="stat"><div class="stat-value">' + (totalTokensIn + totalTokensOut).toLocaleString() + '</div><div class="stat-label">Token totali</div></div>' : '') +
3602
+ '</div>' +
3603
+ '</div>' +
3604
+
3605
+ // ── Workflow bar ─────────────────────────────────────────────────────────
3606
+ '<div class="workflow-bar">' +
3607
+ '<span class="wf-label">Workflow:</span>' +
3608
+ activeNodes.map(function(n, idx){
3609
+ return '<span class="wf-step">' + (n.icon||'') + ' ' + esc(n.label||n.agent) + '</span>' +
3610
+ (idx < activeNodes.length-1 ? '<span class="wf-arrow">&#8594;</span>' : '');
3611
+ }).join('') +
3612
+ '</div>' +
3613
+
3614
+ // ── TOC ──────────────────────────────────────────────────────────────────
3615
+ '<div class="toc">' +
3616
+ '<div class="toc-title">Indice dei contenuti</div>' +
3617
+ '<div class="toc-grid">' +
3618
+ activeNodes.map(function(n, idx){
3619
+ return '<div class="toc-item"><div class="toc-num">' + (idx+1) + '</div><div class="toc-name">' + esc(n.label||n.agent) + '</div></div>';
3620
+ }).join('') +
3621
+ '</div>' +
3622
+ '</div>' +
3623
+
3624
+ // ── Sections ─────────────────────────────────────────────────────────────
3625
+ sectionsHtml +
3626
+
3627
+ // ── Footer ───────────────────────────────────────────────────────────────
3628
+ '<div class="footer-bar">' +
3629
+ '<span class="footer-left">NHA Studio &nbsp;&#183;&nbsp; nothumanallowed.com</span>' +
3630
+ '<span class="footer-right">' + today + ' ' + nowTime +
3631
+ (totalTokensIn > 0 ? ' &nbsp;&#183;&nbsp; ' + totalTokensIn.toLocaleString() + ' token in / ' + totalTokensOut.toLocaleString() + ' out' : '') +
3632
+ '</span>' +
3633
+ '</div>' +
3453
3634
  '</body></html>';
3454
3635
 
3455
3636
  // Use Blob URL to avoid popup blockers — opens in new tab, user can Cmd+P to print as PDF
@@ -4619,6 +4800,11 @@ input:focus,textarea:focus{border-color:var(--green3)}
4619
4800
  .md-body em{font-style:italic;color:var(--dim)}
4620
4801
  .md-body del{text-decoration:line-through;color:var(--dim)}
4621
4802
  .md-body a{color:var(--cyan);text-decoration:underline}
4803
+ .md-body .md-table{width:100%;border-collapse:collapse;margin:10px 0 14px;font-size:13px}
4804
+ .md-body .md-table th{background:var(--bg3);color:var(--bright);font-weight:600;padding:7px 12px;text-align:left;border:1px solid var(--border2);white-space:nowrap}
4805
+ .md-body .md-table td{padding:6px 12px;border:1px solid var(--border);color:var(--text);vertical-align:top}
4806
+ .md-body .md-table tbody tr:nth-child(odd){background:rgba(0,0,0,0.15)}
4807
+ .md-body .md-table tbody tr:hover{background:rgba(0,255,65,0.04)}
4622
4808
 
4623
4809
  /* ---- CHAT bubble markdown tweaks ---- */
4624
4810
  .msg--assistant .msg__bubble{white-space:normal}