nothumanallowed 13.5.54 → 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 +1 -1
- package/src/commands/ui.mjs +114 -2
- package/src/constants.mjs +1 -1
- package/src/services/web-ui.mjs +180 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "13.5.
|
|
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": {
|
package/src/commands/ui.mjs
CHANGED
|
@@ -3913,11 +3913,99 @@ ${completedHeadings ? `## SECTIONS ALREADY WRITTEN (headings only):\n${completed
|
|
|
3913
3913
|
// Write meta
|
|
3914
3914
|
const meta = { projectName: projName, description: body.description || '', files: (body.files || []).map(f => f.name), createdAt: new Date().toISOString() };
|
|
3915
3915
|
fs.writeFileSync(path.join(projDir, 'webcraft-meta.json'), JSON.stringify(meta, null, 2), 'utf8');
|
|
3916
|
+
// Write agent memory file — always included as first context for WebCraft Agent
|
|
3917
|
+
const agentMd = [
|
|
3918
|
+
'# WebCraft Agent Memory — ' + projName,
|
|
3919
|
+
'> Auto-generated. Edit to give the agent persistent context about this project.',
|
|
3920
|
+
'',
|
|
3921
|
+
'## Progetto',
|
|
3922
|
+
'- **Nome**: ' + projName,
|
|
3923
|
+
'- **Descrizione**: ' + (body.description || ''),
|
|
3924
|
+
'- **Creato**: ' + new Date().toISOString(),
|
|
3925
|
+
'',
|
|
3926
|
+
'## Stack',
|
|
3927
|
+
'- Server: Express.js (Node.js)',
|
|
3928
|
+
'- Auth: JWT (access 15min, refresh 7d httpOnly cookie) + bcryptjs cost 12',
|
|
3929
|
+
'- DB: PostgreSQL (sandbox: in-memory shim in server/db.js)',
|
|
3930
|
+
'- CSS: BEM methodology',
|
|
3931
|
+
'- Security: helmet, express-rate-limit, custom sentinel WAF middleware',
|
|
3932
|
+
'',
|
|
3933
|
+
'## File principali',
|
|
3934
|
+
(body.files || []).map(f => '- ' + f.name).join('\n'),
|
|
3935
|
+
'',
|
|
3936
|
+
'## Note agente',
|
|
3937
|
+
'(aggiungi qui note per sessioni future — es. "usa Tailwind invece di BEM", "API key in .env.local")',
|
|
3938
|
+
].join('\n');
|
|
3939
|
+
fs.writeFileSync(path.join(projDir, 'webcraft-agent.md'), agentMd, 'utf8');
|
|
3916
3940
|
sendJSON(res, 200, { ok: true, dir: projDir });
|
|
3917
3941
|
logRequest(method, pathname, 200, Date.now() - start);
|
|
3918
3942
|
return;
|
|
3919
3943
|
}
|
|
3920
3944
|
|
|
3945
|
+
// POST /api/studio/webcraft/projects/chat/save { projectName, chat[] }
|
|
3946
|
+
if (pathname === '/api/studio/webcraft/projects/chat/save' && method === 'POST') {
|
|
3947
|
+
const body = await parseBody(req, 2 * 1024 * 1024);
|
|
3948
|
+
const projName = (body.projectName || '').replace(/[^a-zA-Z0-9_-]/g, '-');
|
|
3949
|
+
if (!projName) { sendJSON(res, 400, { error: 'projectName required' }); return; }
|
|
3950
|
+
const projDir = path.join(os.homedir(), '.nha', 'webcraft', projName);
|
|
3951
|
+
fs.mkdirSync(projDir, { recursive: true });
|
|
3952
|
+
fs.writeFileSync(path.join(projDir, 'webcraft-chat.json'), JSON.stringify(body.chat || [], null, 2), 'utf8');
|
|
3953
|
+
sendJSON(res, 200, { ok: true });
|
|
3954
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
3955
|
+
return;
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3958
|
+
// GET /api/studio/webcraft/projects/chat/load/:name
|
|
3959
|
+
if (pathname.startsWith('/api/studio/webcraft/projects/chat/load/') && method === 'GET') {
|
|
3960
|
+
const projName = decodeURIComponent(pathname.replace('/api/studio/webcraft/projects/chat/load/', '')).replace(/[^a-zA-Z0-9_-]/g, '');
|
|
3961
|
+
const chatPath = path.join(os.homedir(), '.nha', 'webcraft', projName, 'webcraft-chat.json');
|
|
3962
|
+
if (!fs.existsSync(chatPath)) { sendJSON(res, 200, { chat: [] }); return; }
|
|
3963
|
+
const chat = JSON.parse(fs.readFileSync(chatPath, 'utf8'));
|
|
3964
|
+
sendJSON(res, 200, { chat });
|
|
3965
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
3966
|
+
return;
|
|
3967
|
+
}
|
|
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
|
+
|
|
3921
4009
|
// GET /api/studio/webcraft/projects/load/:name → { projectName, description, files[] }
|
|
3922
4010
|
if (pathname.startsWith('/api/studio/webcraft/projects/load/') && method === 'GET') {
|
|
3923
4011
|
const projName = decodeURIComponent(pathname.replace('/api/studio/webcraft/projects/load/', '')).replace(/[^a-zA-Z0-9_-]/g, '');
|
|
@@ -4147,6 +4235,13 @@ module.exports = { get, set, del, exists };
|
|
|
4147
4235
|
[/require\(['"]\.\/config\/database['"]\)/g, "require('./db')"],
|
|
4148
4236
|
[/require\(['"]\.\.\/config\/db['"]\)/g, "require('../db')"],
|
|
4149
4237
|
[/require\(['"]\.\/config\/db['"]\)/g, "require('./db')"],
|
|
4238
|
+
// email utils: LLM puts utils/email but file is in services/email
|
|
4239
|
+
[/require\(['"]\.\.\/utils\/email['"]\)/g, "require('../services/email')"],
|
|
4240
|
+
[/require\(['"]\.\/utils\/email['"]\)/g, "require('./services/email')"],
|
|
4241
|
+
// config module: LLM generates require('../../config') or require('../config')
|
|
4242
|
+
[/require\(['"]\.\.\/\.\.\/config['"]\)/g, "{env:process.env}"],
|
|
4243
|
+
[/require\(['"]\.\.\/config['"]\)/g, "{env:process.env}"],
|
|
4244
|
+
[/require\(['"]\.\/config['"]\)/g, "{env:process.env}"],
|
|
4150
4245
|
];
|
|
4151
4246
|
function patchJsFiles(dir) {
|
|
4152
4247
|
if (!fs.existsSync(dir)) return;
|
|
@@ -4350,6 +4445,24 @@ module.exports = { get, set, del, exists };
|
|
|
4350
4445
|
const sendEv = (data) => { try { res.write(`data: ${JSON.stringify(data)}\n\n`); } catch {} };
|
|
4351
4446
|
|
|
4352
4447
|
try {
|
|
4448
|
+
// Always read agent memory file first if present
|
|
4449
|
+
const agentMemoryPath = path.join(sandboxDir, 'webcraft-agent.md');
|
|
4450
|
+
const agentMemory = fs.existsSync(agentMemoryPath) ? fs.readFileSync(agentMemoryPath, 'utf8') : '';
|
|
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
|
+
|
|
4353
4466
|
// Read all project files to give agent full context
|
|
4354
4467
|
const allFiles = [];
|
|
4355
4468
|
if (fs.existsSync(sandboxDir)) {
|
|
@@ -4407,8 +4520,7 @@ Il tuo lavoro e di correggere, migliorare ed espandere il codice del progetto sa
|
|
|
4407
4520
|
|
|
4408
4521
|
PROGETTO ATTIVO: ${projectName}
|
|
4409
4522
|
PERCORSO: ${sandboxDir}
|
|
4410
|
-
|
|
4411
|
-
FILE DISPONIBILI:
|
|
4523
|
+
${agentMemory ? '\nMEMORIA PROGETTO:\n' + agentMemory + '\n' : ''}${skillsContext}FILE DISPONIBILI:
|
|
4412
4524
|
${fileList}
|
|
4413
4525
|
|
|
4414
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.
|
|
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
|
|
package/src/services/web-ui.mjs
CHANGED
|
@@ -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,'&').replace(/</g,'<').replace(/>/g,'>'):''}
|
|
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">⏳ '+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">📋 '+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">✎</button>' +
|
|
6407
|
+
'<button onclick="wcDeleteSkill('+si+')" title="Elimina" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:12px;padding:2px 4px">×</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">📚 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?'📚 Nuova Skill':'📚 Modifica Skill')+'</span>' +
|
|
6430
|
+
'<button onclick="wcCloseSkillModal()" style="background:none;border:none;color:var(--dim);font-size:18px;cursor:pointer;line-height:1">×</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">🤖 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?'⏳':'▶ 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">✓ 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;
|
|
@@ -6572,10 +6731,13 @@ async function wcChatSend() {
|
|
|
6572
6731
|
}
|
|
6573
6732
|
} else if (ev.type === 'done') {
|
|
6574
6733
|
wcChatRunning = false;
|
|
6575
|
-
if (ev.changed) {
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6734
|
+
if (ev.changed) { wcReloadProjectFiles(); }
|
|
6735
|
+
// Persist chat to disk
|
|
6736
|
+
fetch(API + '/api/studio/webcraft/projects/chat/save', {
|
|
6737
|
+
method: 'POST',
|
|
6738
|
+
headers: {'Content-Type':'application/json'},
|
|
6739
|
+
body: JSON.stringify({ projectName: wcState.projectName, chat: wcChat })
|
|
6740
|
+
}).catch(function(){});
|
|
6579
6741
|
} else if (ev.type === 'restart_sandbox') {
|
|
6580
6742
|
wcStartSandbox();
|
|
6581
6743
|
} else if (ev.type === 'error') {
|
|
@@ -6731,7 +6893,20 @@ async function wcLoadProject(pi) {
|
|
|
6731
6893
|
wcState.activeFile = 0;
|
|
6732
6894
|
wcMainTab = 'new';
|
|
6733
6895
|
wcRightTab = 'files';
|
|
6896
|
+
// Load persisted chat history
|
|
6897
|
+
try {
|
|
6898
|
+
var cr = await fetch(API + '/api/studio/webcraft/projects/chat/load/' + encodeURIComponent(wcState.projectName));
|
|
6899
|
+
if (cr.ok) { var cd = await cr.json(); wcChat = cd.chat || []; }
|
|
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(_) {}
|
|
6734
6908
|
renderWebCraft(document.getElementById('content'));
|
|
6909
|
+
wcScrollChatToBottom();
|
|
6735
6910
|
}
|
|
6736
6911
|
|
|
6737
6912
|
async function wcDeleteProject(pi) {
|