nothumanallowed 13.5.55 → 13.5.56

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.55",
3
+ "version": "13.5.56",
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": {
@@ -3966,6 +3966,46 @@ ${completedHeadings ? `## SECTIONS ALREADY WRITTEN (headings only):\n${completed
3966
3966
  return;
3967
3967
  }
3968
3968
 
3969
+ // GET /api/studio/webcraft/skills/:name → { skills: [{name, content}] }
3970
+ if (pathname.startsWith('/api/studio/webcraft/skills/') && method === 'GET') {
3971
+ const projName = decodeURIComponent(pathname.replace('/api/studio/webcraft/skills/', '')).replace(/[^a-zA-Z0-9_-]/g, '');
3972
+ const skillsDir = path.join(os.homedir(), '.nha', 'webcraft', projName, 'skills');
3973
+ const skills = [];
3974
+ if (fs.existsSync(skillsDir)) {
3975
+ for (const fname of fs.readdirSync(skillsDir)) {
3976
+ if (!fname.endsWith('.md')) continue;
3977
+ try {
3978
+ const content = fs.readFileSync(path.join(skillsDir, fname), 'utf8');
3979
+ skills.push({ name: fname.replace(/\.md$/, ''), content });
3980
+ } catch(_) {}
3981
+ }
3982
+ }
3983
+ sendJSON(res, 200, { skills });
3984
+ logRequest(method, pathname, 200, Date.now() - start);
3985
+ return;
3986
+ }
3987
+
3988
+ // POST /api/studio/webcraft/skills/:name { skills: [{name, content}] }
3989
+ if (pathname.startsWith('/api/studio/webcraft/skills/') && method === 'POST') {
3990
+ const projName = decodeURIComponent(pathname.replace('/api/studio/webcraft/skills/', '')).replace(/[^a-zA-Z0-9_-]/g, '');
3991
+ const body = await parseBody(req);
3992
+ const skills = body.skills || [];
3993
+ const skillsDir = path.join(os.homedir(), '.nha', 'webcraft', projName, 'skills');
3994
+ if (!fs.existsSync(skillsDir)) fs.mkdirSync(skillsDir, { recursive: true });
3995
+ // Remove all existing .md files, then write current set
3996
+ for (const fname of fs.readdirSync(skillsDir)) {
3997
+ if (fname.endsWith('.md')) fs.unlinkSync(path.join(skillsDir, fname));
3998
+ }
3999
+ for (const skill of skills) {
4000
+ if (!skill.name) continue;
4001
+ const safeName = skill.name.replace(/[^a-zA-Z0-9_-]/g, '_');
4002
+ fs.writeFileSync(path.join(skillsDir, safeName + '.md'), skill.content || '', 'utf8');
4003
+ }
4004
+ sendJSON(res, 200, { ok: true });
4005
+ logRequest(method, pathname, 200, Date.now() - start);
4006
+ return;
4007
+ }
4008
+
3969
4009
  // GET /api/studio/webcraft/projects/load/:name → { projectName, description, files[] }
3970
4010
  if (pathname.startsWith('/api/studio/webcraft/projects/load/') && method === 'GET') {
3971
4011
  const projName = decodeURIComponent(pathname.replace('/api/studio/webcraft/projects/load/', '')).replace(/[^a-zA-Z0-9_-]/g, '');
@@ -4409,6 +4449,20 @@ module.exports = { get, set, del, exists };
4409
4449
  const agentMemoryPath = path.join(sandboxDir, 'webcraft-agent.md');
4410
4450
  const agentMemory = fs.existsSync(agentMemoryPath) ? fs.readFileSync(agentMemoryPath, 'utf8') : '';
4411
4451
 
4452
+ // Load skills files from skills/ subfolder
4453
+ const skillsDir = path.join(sandboxDir, 'skills');
4454
+ let skillsContext = '';
4455
+ if (fs.existsSync(skillsDir)) {
4456
+ const skillFiles = fs.readdirSync(skillsDir).filter(f => f.endsWith('.md'));
4457
+ if (skillFiles.length > 0) {
4458
+ skillsContext = '\nSKILLS DEL PROGETTO:\n' + skillFiles.map(fname => {
4459
+ const skillName = fname.replace(/\.md$/, '');
4460
+ const content = fs.readFileSync(path.join(skillsDir, fname), 'utf8');
4461
+ return `--- SKILL: ${skillName} ---\n${content}`;
4462
+ }).join('\n\n') + '\n';
4463
+ }
4464
+ }
4465
+
4412
4466
  // Read all project files to give agent full context
4413
4467
  const allFiles = [];
4414
4468
  if (fs.existsSync(sandboxDir)) {
@@ -4466,8 +4520,7 @@ Il tuo lavoro e di correggere, migliorare ed espandere il codice del progetto sa
4466
4520
 
4467
4521
  PROGETTO ATTIVO: ${projectName}
4468
4522
  PERCORSO: ${sandboxDir}
4469
- ${agentMemory ? '\nMEMORIA PROGETTO:\n' + agentMemory + '\n' : ''}
4470
- FILE DISPONIBILI:
4523
+ ${agentMemory ? '\nMEMORIA PROGETTO:\n' + agentMemory + '\n' : ''}${skillsContext}FILE DISPONIBILI:
4471
4524
  ${fileList}
4472
4525
 
4473
4526
  STRUMENTI A TUA DISPOSIZIONE (rispondi SOLO con JSON valido per le operazioni):
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.55';
8
+ export const VERSION = '13.5.56';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -6252,6 +6252,10 @@ var wcChatRunning = false;
6252
6252
  var wcChatAttachments = []; // [{name, mimeType, base64, size}]
6253
6253
  var _wcAutoFixAttempts = 0;
6254
6254
  var _wcAutoFixTimer = null;
6255
+ // Skills state
6256
+ var wcSkills = []; // [{name, content}] loaded from disk
6257
+ var wcSkillModal = null; // null | {mode:'edit'|'new', idx:number|null, name, content, generating}
6258
+ var _wcSkillsLoaded = false;
6255
6259
 
6256
6260
  function wcEsc(s){return s?String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'):''}
6257
6261
 
@@ -6336,6 +6340,7 @@ function renderWebCraft(el) {
6336
6340
  '<div id="wcFieldsList">'+authFieldsHtml+'</div>' +
6337
6341
  '<div style="font-size:9px;color:var(--dim);margin-top:4px">'+t('wc_required_hint')+'</div>' +
6338
6342
  '</div>' +
6343
+ wcSkillsPanelHtml() +
6339
6344
  (wcState.running ?
6340
6345
  '<div style="width:100%;padding:11px;background:var(--bg3);border:1px solid var(--border);border-radius:8px;color:var(--dim);font-size:12px;text-align:center">&#9203; '+t('wc_generating')+'...</div>'
6341
6346
  : '') +
@@ -6361,7 +6366,8 @@ function renderWebCraft(el) {
6361
6366
  (wcMainTab === 'projects' ? wcProjectsPanelHtml() : editorHtml) +
6362
6367
  '</div>' +
6363
6368
  wcChatPanelHtml() +
6364
- '</div>';
6369
+ '</div>' +
6370
+ wcSkillModalHtml();
6365
6371
  }
6366
6372
 
6367
6373
  function wcPickExample(i) {
@@ -6383,6 +6389,159 @@ function wcTabFiles() { wcRightTab = 'files'; renderWebCraft(document.getElement
6383
6389
  function wcTabPreview() { wcRightTab = 'preview'; renderWebCraft(document.getElementById('content')); }
6384
6390
  function wcOpenSandbox() { if (wcState.sandbox.port) window.open('http://127.0.0.1:' + wcState.sandbox.port, '_blank'); }
6385
6391
 
6392
+ // ── WebCraft Skills ───────────────────────────────────────────────────────────
6393
+ function wcSkillsPanelHtml() {
6394
+ var hasProj = wcState.projectName && wcState.generatedFiles.length > 0;
6395
+ // Load skills from server on first render if project active
6396
+ if (hasProj && !_wcSkillsLoaded) {
6397
+ _wcSkillsLoaded = true;
6398
+ fetch(API + '/api/studio/webcraft/skills/' + encodeURIComponent(wcState.projectName))
6399
+ .then(function(r){ return r.json(); })
6400
+ .then(function(d){ wcSkills = d.skills || []; renderWebCraft(document.getElementById('content')); })
6401
+ .catch(function(){});
6402
+ }
6403
+ var rows = wcSkills.map(function(s, si) {
6404
+ return '<div style="display:flex;align-items:center;gap:6px;padding:5px 0;border-bottom:1px solid var(--border)">' +
6405
+ '<span style="font-size:11px;color:var(--text);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">&#128203; '+wcEsc(s.name)+'</span>' +
6406
+ '<button onclick="wcOpenSkill('+si+')" title="Modifica" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:12px;padding:2px 4px">&#9998;</button>' +
6407
+ '<button onclick="wcDeleteSkill('+si+')" title="Elimina" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:12px;padding:2px 4px">&times;</button>' +
6408
+ '</div>';
6409
+ }).join('');
6410
+ return '<div style="background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:14px">' +
6411
+ '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">' +
6412
+ '<div style="font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.8px">&#128218; Skills & Memoria</div>' +
6413
+ '<button onclick="wcNewSkill()" style="font-size:10px;padding:3px 8px;background:var(--bg3);border:1px solid var(--border2);border-radius:5px;color:var(--green);cursor:pointer">+ Aggiungi</button>' +
6414
+ '</div>' +
6415
+ (wcSkills.length > 0
6416
+ ? '<div style="max-height:120px;overflow-y:auto">' + rows + '</div>'
6417
+ : '<div style="font-size:10px;color:var(--dim);font-style:italic">Nessuna skill. Aggiungine una per dare istruzioni persistenti.</div>'
6418
+ ) +
6419
+ '</div>';
6420
+ }
6421
+
6422
+ function wcSkillModalHtml() {
6423
+ if (!wcSkillModal) return '';
6424
+ var m = wcSkillModal;
6425
+ var isNew = m.mode === 'new';
6426
+ return '<div onclick="wcCloseSkillModal(event)" style="position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:9999;display:flex;align-items:center;justify-content:center">' +
6427
+ '<div onclick="event.stopPropagation()" style="background:var(--bg2);border:1px solid var(--border);border-radius:14px;width:560px;max-width:95vw;max-height:85vh;display:flex;flex-direction:column;overflow:hidden">' +
6428
+ '<div style="padding:16px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px">' +
6429
+ '<span style="font-size:14px;font-weight:700;color:var(--text);flex:1">'+(isNew?'&#128218; Nuova Skill':'&#128218; Modifica Skill')+'</span>' +
6430
+ '<button onclick="wcCloseSkillModal()" style="background:none;border:none;color:var(--dim);font-size:18px;cursor:pointer;line-height:1">&times;</button>' +
6431
+ '</div>' +
6432
+ '<div style="padding:16px 20px;display:flex;flex-direction:column;gap:10px;flex:1;overflow-y:auto">' +
6433
+ '<div>' +
6434
+ '<div style="font-size:10px;color:var(--dim);margin-bottom:4px">NOME FILE (es. stripe.md, email-templates.md)</div>' +
6435
+ '<input id="wcSkillName" value="'+wcEsc(m.name||'')+'" placeholder="nome-skill.md" style="width:100%;padding:7px 10px;font-size:12px;border-radius:6px;border:1px solid var(--border2);background:var(--bg3);color:var(--text);box-sizing:border-box;font-family:var(--mono)">' +
6436
+ '</div>' +
6437
+ '<div style="background:var(--bg3);border:1px solid var(--border);border-radius:8px;padding:10px">' +
6438
+ '<div style="font-size:10px;color:var(--dim);margin-bottom:6px">&#129302; GENERA CON AI — descrivi cosa deve contenere la skill</div>' +
6439
+ '<div style="display:flex;gap:8px">' +
6440
+ '<textarea id="wcSkillAiDesc" rows="2" placeholder="es. Istruzioni per integrare Stripe Checkout con Express. Includi pattern per webhook, error handling e test mode." style="flex:1;padding:7px 10px;font-size:11px;border-radius:6px;border:1px solid var(--border2);background:var(--bg2);color:var(--text);resize:none;font-family:inherit"></textarea>' +
6441
+ '<button onclick="wcGenerateSkill()" '+(m.generating?'disabled':'')+' style="padding:8px 12px;background:var(--green3);border:none;border-radius:6px;color:var(--bg);font-size:11px;font-weight:700;cursor:pointer;white-space:nowrap;align-self:flex-end">'+(m.generating?'&#9203;':'&#9654; Genera')+'</button>' +
6442
+ '</div>' +
6443
+ '</div>' +
6444
+ '<div>' +
6445
+ '<div style="font-size:10px;color:var(--dim);margin-bottom:4px">CONTENUTO (markdown)</div>' +
6446
+ '<textarea id="wcSkillContent" rows="12" placeholder="# Nome Skill'+String.fromCharCode(10)+String.fromCharCode(10)+'Scrivi qui le istruzioni, pattern o snippet..." style="width:100%;padding:8px 10px;font-size:11px;border-radius:6px;border:1px solid var(--border2);background:var(--bg3);color:var(--text);resize:vertical;box-sizing:border-box;font-family:var(--mono);line-height:1.6">'+wcEsc(m.content||'')+'</textarea>' +
6447
+ '</div>' +
6448
+ '</div>' +
6449
+ '<div style="padding:12px 20px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;gap:8px">' +
6450
+ '<button onclick="wcCloseSkillModal()" style="padding:8px 16px;background:var(--bg3);border:1px solid var(--border2);border-radius:7px;color:var(--dim);font-size:12px;cursor:pointer">Annulla</button>' +
6451
+ '<button onclick="wcSaveSkill()" style="padding:8px 18px;background:var(--green3);border:none;border-radius:7px;color:var(--bg);font-size:12px;font-weight:700;cursor:pointer">&#10003; Salva</button>' +
6452
+ '</div>' +
6453
+ '</div>' +
6454
+ '</div>';
6455
+ }
6456
+
6457
+ function wcNewSkill() {
6458
+ wcSkillModal = { mode: 'new', idx: null, name: '', content: '', generating: false };
6459
+ renderWebCraft(document.getElementById('content'));
6460
+ }
6461
+
6462
+ function wcOpenSkill(si) {
6463
+ var s = wcSkills[si];
6464
+ if (!s) return;
6465
+ wcSkillModal = { mode: 'edit', idx: si, name: s.name, content: s.content, generating: false };
6466
+ renderWebCraft(document.getElementById('content'));
6467
+ }
6468
+
6469
+ function wcCloseSkillModal(e) {
6470
+ if (e && e.target !== e.currentTarget) return;
6471
+ wcSkillModal = null;
6472
+ renderWebCraft(document.getElementById('content'));
6473
+ }
6474
+
6475
+ async function wcGenerateSkill() {
6476
+ var descEl = document.getElementById('wcSkillAiDesc');
6477
+ var nameEl = document.getElementById('wcSkillName');
6478
+ var desc = (descEl ? descEl.value : '').trim();
6479
+ if (!desc) { alert('Descrivi prima cosa deve contenere la skill.'); return; }
6480
+ wcSkillModal.generating = true;
6481
+ wcSkillModal.name = nameEl ? nameEl.value : wcSkillModal.name;
6482
+ renderWebCraft(document.getElementById('content'));
6483
+ try {
6484
+ var r = await fetch(API + '/api/studio/webcraft', {
6485
+ method: 'POST',
6486
+ headers: {'Content-Type':'application/json'},
6487
+ body: JSON.stringify({
6488
+ system: 'Sei un esperto di sviluppo web fullstack. Genera un file Markdown di "skill" per il WebCraft Agent di NotHumanAllowed. Il file deve contenere istruzioni, pattern di codice, best practice e snippet pronti per essere usati dal modello AI come contesto persistente. Scrivi SOLO il contenuto Markdown, senza spiegazioni esterne.',
6489
+ user: 'Progetto: ' + wcState.projectName + String.fromCharCode(10) + 'Stack: Express.js, PostgreSQL, JWT auth, BEM CSS' + String.fromCharCode(10) + String.fromCharCode(10) + 'Genera la skill: ' + desc,
6490
+ max_tokens: 2048
6491
+ })
6492
+ });
6493
+ if (r.ok) {
6494
+ var d = await r.json();
6495
+ wcSkillModal.content = d.text || '';
6496
+ wcSkillModal.generating = false;
6497
+ // Auto-suggest name if empty
6498
+ if (!wcSkillModal.name && desc.length > 0) {
6499
+ wcSkillModal.name = desc.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 30) + '.md';
6500
+ }
6501
+ }
6502
+ } catch(e) {
6503
+ wcSkillModal.generating = false;
6504
+ }
6505
+ renderWebCraft(document.getElementById('content'));
6506
+ }
6507
+
6508
+ async function wcSaveSkill() {
6509
+ var nameEl = document.getElementById('wcSkillName');
6510
+ var contentEl = document.getElementById('wcSkillContent');
6511
+ var name = (nameEl ? nameEl.value : '').trim();
6512
+ var content = contentEl ? contentEl.value : '';
6513
+ if (!name) { alert('Inserisci un nome per la skill.'); return; }
6514
+ if (!name.endsWith('.md')) name = name + '.md';
6515
+ var skill = { name: name, content: content };
6516
+ if (wcSkillModal.mode === 'edit' && wcSkillModal.idx !== null) {
6517
+ wcSkills[wcSkillModal.idx] = skill;
6518
+ } else {
6519
+ wcSkills.push(skill);
6520
+ }
6521
+ wcSkillModal = null;
6522
+ // Persist to server
6523
+ await wcPersistSkills();
6524
+ renderWebCraft(document.getElementById('content'));
6525
+ }
6526
+
6527
+ async function wcDeleteSkill(si) {
6528
+ if (!confirm('Eliminare la skill "' + wcSkills[si].name + '"?')) return;
6529
+ wcSkills.splice(si, 1);
6530
+ await wcPersistSkills();
6531
+ renderWebCraft(document.getElementById('content'));
6532
+ }
6533
+
6534
+ async function wcPersistSkills() {
6535
+ if (!wcState.projectName) return;
6536
+ try {
6537
+ await fetch(API + '/api/studio/webcraft/skills/' + encodeURIComponent(wcState.projectName), {
6538
+ method: 'POST',
6539
+ headers: {'Content-Type':'application/json'},
6540
+ body: JSON.stringify({ skills: wcSkills })
6541
+ });
6542
+ } catch(_) {}
6543
+ }
6544
+
6386
6545
  // ── WebCraft Agent Chat Panel ─────────────────────────────────────────────
6387
6546
  function wcChatPanelHtml() {
6388
6547
  var hasProject = wcState.projectName && wcState.generatedFiles.length > 0;
@@ -6739,6 +6898,13 @@ async function wcLoadProject(pi) {
6739
6898
  var cr = await fetch(API + '/api/studio/webcraft/projects/chat/load/' + encodeURIComponent(wcState.projectName));
6740
6899
  if (cr.ok) { var cd = await cr.json(); wcChat = cd.chat || []; }
6741
6900
  } catch(_) { wcChat = []; }
6901
+ // Load skills for this project
6902
+ _wcSkillsLoaded = false;
6903
+ wcSkills = [];
6904
+ try {
6905
+ var sr = await fetch(API + '/api/studio/webcraft/skills/' + encodeURIComponent(wcState.projectName));
6906
+ if (sr.ok) { var sd = await sr.json(); wcSkills = sd.skills || []; _wcSkillsLoaded = true; }
6907
+ } catch(_) {}
6742
6908
  renderWebCraft(document.getElementById('content'));
6743
6909
  wcScrollChatToBottom();
6744
6910
  }