nothumanallowed 13.5.55 → 13.5.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/commands/ui.mjs +109 -2
- package/src/constants.mjs +1 -1
- package/src/services/web-ui.mjs +273 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "13.5.
|
|
3
|
+
"version": "13.5.57",
|
|
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
|
@@ -3937,6 +3937,13 @@ ${completedHeadings ? `## SECTIONS ALREADY WRITTEN (headings only):\n${completed
|
|
|
3937
3937
|
'(aggiungi qui note per sessioni future — es. "usa Tailwind invece di BEM", "API key in .env.local")',
|
|
3938
3938
|
].join('\n');
|
|
3939
3939
|
fs.writeFileSync(path.join(projDir, 'webcraft-agent.md'), agentMd, 'utf8');
|
|
3940
|
+
// Create default context files (skills, memory, provider) if not present
|
|
3941
|
+
const ctxDir = path.join(projDir, 'skills');
|
|
3942
|
+
if (!fs.existsSync(ctxDir)) fs.mkdirSync(ctxDir, { recursive: true });
|
|
3943
|
+
for (const def of ['memory.md', 'liara.md', 'skills.md']) {
|
|
3944
|
+
const fp = path.join(ctxDir, def);
|
|
3945
|
+
if (!fs.existsSync(fp)) fs.writeFileSync(fp, '', 'utf8');
|
|
3946
|
+
}
|
|
3940
3947
|
sendJSON(res, 200, { ok: true, dir: projDir });
|
|
3941
3948
|
logRequest(method, pathname, 200, Date.now() - start);
|
|
3942
3949
|
return;
|
|
@@ -3966,6 +3973,81 @@ ${completedHeadings ? `## SECTIONS ALREADY WRITTEN (headings only):\n${completed
|
|
|
3966
3973
|
return;
|
|
3967
3974
|
}
|
|
3968
3975
|
|
|
3976
|
+
// GET /api/studio/webcraft/skills/:name → { skills: [{name, content, type}] }
|
|
3977
|
+
if (pathname.startsWith('/api/studio/webcraft/skills/') && method === 'GET') {
|
|
3978
|
+
const projName = decodeURIComponent(pathname.replace('/api/studio/webcraft/skills/', '')).replace(/[^a-zA-Z0-9_-]/g, '');
|
|
3979
|
+
const skillsDir = path.join(os.homedir(), '.nha', 'webcraft', projName, 'skills');
|
|
3980
|
+
// Ensure the 3 default files always exist on disk
|
|
3981
|
+
const WC_DEFAULTS = [
|
|
3982
|
+
{ name: 'memory.md', type: 'memory' },
|
|
3983
|
+
{ name: 'liara.md', type: 'provider' },
|
|
3984
|
+
{ name: 'skills.md', type: 'skill' },
|
|
3985
|
+
];
|
|
3986
|
+
if (!fs.existsSync(skillsDir)) fs.mkdirSync(skillsDir, { recursive: true });
|
|
3987
|
+
for (const def of WC_DEFAULTS) {
|
|
3988
|
+
const fp = path.join(skillsDir, def.name);
|
|
3989
|
+
if (!fs.existsSync(fp)) fs.writeFileSync(fp, '', 'utf8');
|
|
3990
|
+
}
|
|
3991
|
+
// Load type index
|
|
3992
|
+
const indexPath = path.join(skillsDir, '_index.json');
|
|
3993
|
+
let typeIndex = {};
|
|
3994
|
+
if (fs.existsSync(indexPath)) {
|
|
3995
|
+
try { typeIndex = JSON.parse(fs.readFileSync(indexPath, 'utf8')); } catch(_) {}
|
|
3996
|
+
}
|
|
3997
|
+
// Default types for well-known filenames
|
|
3998
|
+
const defaultType = (n) => n === 'memory.md' ? 'memory' : n === 'liara.md' ? 'provider' : 'skill';
|
|
3999
|
+
const skills = [];
|
|
4000
|
+
for (const fname of fs.readdirSync(skillsDir)) {
|
|
4001
|
+
if (!fname.endsWith('.md')) continue;
|
|
4002
|
+
try {
|
|
4003
|
+
const content = fs.readFileSync(path.join(skillsDir, fname), 'utf8');
|
|
4004
|
+
const type = typeIndex[fname] || defaultType(fname);
|
|
4005
|
+
skills.push({ name: fname, content, type });
|
|
4006
|
+
} catch(_) {}
|
|
4007
|
+
}
|
|
4008
|
+
sendJSON(res, 200, { skills });
|
|
4009
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
4010
|
+
return;
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
// POST /api/studio/webcraft/skills/:name { skills: [{name, content, type}] }
|
|
4014
|
+
if (pathname.startsWith('/api/studio/webcraft/skills/') && method === 'POST') {
|
|
4015
|
+
const projName = decodeURIComponent(pathname.replace('/api/studio/webcraft/skills/', '')).replace(/[^a-zA-Z0-9_-]/g, '');
|
|
4016
|
+
const body = await parseBody(req);
|
|
4017
|
+
const skills = body.skills || [];
|
|
4018
|
+
const skillsDir = path.join(os.homedir(), '.nha', 'webcraft', projName, 'skills');
|
|
4019
|
+
if (!fs.existsSync(skillsDir)) fs.mkdirSync(skillsDir, { recursive: true });
|
|
4020
|
+
// Build set of incoming names (normalised)
|
|
4021
|
+
const incoming = new Set(skills.map(s => {
|
|
4022
|
+
let n = (s.name || '').replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
4023
|
+
if (!n.endsWith('.md')) n += '.md';
|
|
4024
|
+
return n;
|
|
4025
|
+
}));
|
|
4026
|
+
// Remove .md files not in incoming set (but never remove the 3 defaults — only clear them)
|
|
4027
|
+
const WC_KEEP = new Set(['memory.md', 'liara.md', 'skills.md']);
|
|
4028
|
+
for (const fname of fs.readdirSync(skillsDir)) {
|
|
4029
|
+
if (!fname.endsWith('.md')) continue;
|
|
4030
|
+
if (!incoming.has(fname) && !WC_KEEP.has(fname)) fs.unlinkSync(path.join(skillsDir, fname));
|
|
4031
|
+
}
|
|
4032
|
+
// Write/update all skills; always keep defaults even if not in incoming (write empty)
|
|
4033
|
+
const typeIndex = {};
|
|
4034
|
+
for (const skill of skills) {
|
|
4035
|
+
if (!skill.name) continue;
|
|
4036
|
+
let safeName = skill.name.replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
4037
|
+
if (!safeName.endsWith('.md')) safeName += '.md';
|
|
4038
|
+
fs.writeFileSync(path.join(skillsDir, safeName), skill.content || '', 'utf8');
|
|
4039
|
+
typeIndex[safeName] = skill.type || 'skill';
|
|
4040
|
+
}
|
|
4041
|
+
// Ensure defaults exist
|
|
4042
|
+
for (const def of ['memory.md', 'liara.md', 'skills.md']) {
|
|
4043
|
+
if (!fs.existsSync(path.join(skillsDir, def))) fs.writeFileSync(path.join(skillsDir, def), '', 'utf8');
|
|
4044
|
+
}
|
|
4045
|
+
fs.writeFileSync(path.join(skillsDir, '_index.json'), JSON.stringify(typeIndex), 'utf8');
|
|
4046
|
+
sendJSON(res, 200, { ok: true });
|
|
4047
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
4048
|
+
return;
|
|
4049
|
+
}
|
|
4050
|
+
|
|
3969
4051
|
// GET /api/studio/webcraft/projects/load/:name → { projectName, description, files[] }
|
|
3970
4052
|
if (pathname.startsWith('/api/studio/webcraft/projects/load/') && method === 'GET') {
|
|
3971
4053
|
const projName = decodeURIComponent(pathname.replace('/api/studio/webcraft/projects/load/', '')).replace(/[^a-zA-Z0-9_-]/g, '');
|
|
@@ -4409,6 +4491,32 @@ module.exports = { get, set, del, exists };
|
|
|
4409
4491
|
const agentMemoryPath = path.join(sandboxDir, 'webcraft-agent.md');
|
|
4410
4492
|
const agentMemory = fs.existsSync(agentMemoryPath) ? fs.readFileSync(agentMemoryPath, 'utf8') : '';
|
|
4411
4493
|
|
|
4494
|
+
// Load context files from skills/ subfolder (skills, memory, provider)
|
|
4495
|
+
const skillsDir = path.join(sandboxDir, 'skills');
|
|
4496
|
+
let skillsContext = '';
|
|
4497
|
+
if (fs.existsSync(skillsDir)) {
|
|
4498
|
+
const indexPath = path.join(skillsDir, '_index.json');
|
|
4499
|
+
let typeIndex = {};
|
|
4500
|
+
if (fs.existsSync(indexPath)) { try { typeIndex = JSON.parse(fs.readFileSync(indexPath, 'utf8')); } catch(_) {} }
|
|
4501
|
+
const defaultType = (n) => n === 'memory.md' ? 'memory' : n === 'liara.md' ? 'provider' : 'skill';
|
|
4502
|
+
const sections = { memory: [], provider: [], skill: [] };
|
|
4503
|
+
for (const fname of fs.readdirSync(skillsDir)) {
|
|
4504
|
+
if (!fname.endsWith('.md')) continue;
|
|
4505
|
+
try {
|
|
4506
|
+
const content = fs.readFileSync(path.join(skillsDir, fname), 'utf8');
|
|
4507
|
+
if (!content.trim()) continue; // skip empty files
|
|
4508
|
+
const type = typeIndex[fname] || defaultType(fname);
|
|
4509
|
+
sections[type] = sections[type] || [];
|
|
4510
|
+
sections[type].push(`### ${fname}\n${content}`);
|
|
4511
|
+
} catch(_) {}
|
|
4512
|
+
}
|
|
4513
|
+
const parts = [];
|
|
4514
|
+
if (sections.memory.length) parts.push('MEMORIA PROGETTO:\n' + sections.memory.join('\n\n'));
|
|
4515
|
+
if (sections.provider.length) parts.push('ISTRUZIONI MODELLO AI:\n' + sections.provider.join('\n\n'));
|
|
4516
|
+
if (sections.skill.length) parts.push('SKILLS & PATTERN:\n' + sections.skill.join('\n\n'));
|
|
4517
|
+
if (parts.length) skillsContext = '\n' + parts.join('\n\n') + '\n';
|
|
4518
|
+
}
|
|
4519
|
+
|
|
4412
4520
|
// Read all project files to give agent full context
|
|
4413
4521
|
const allFiles = [];
|
|
4414
4522
|
if (fs.existsSync(sandboxDir)) {
|
|
@@ -4466,8 +4574,7 @@ Il tuo lavoro e di correggere, migliorare ed espandere il codice del progetto sa
|
|
|
4466
4574
|
|
|
4467
4575
|
PROGETTO ATTIVO: ${projectName}
|
|
4468
4576
|
PERCORSO: ${sandboxDir}
|
|
4469
|
-
${agentMemory ? '\nMEMORIA PROGETTO:\n' + agentMemory + '\n' : ''}
|
|
4470
|
-
FILE DISPONIBILI:
|
|
4577
|
+
${agentMemory ? '\nMEMORIA PROGETTO:\n' + agentMemory + '\n' : ''}${skillsContext}FILE DISPONIBILI:
|
|
4471
4578
|
${fileList}
|
|
4472
4579
|
|
|
4473
4580
|
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.57';
|
|
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,17 @@ 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, type}] type: 'skill'|'memory'|'provider'
|
|
6257
|
+
var wcSkillModal = null; // null | {mode:'edit'|'new', idx:number|null, name, content, type, generating}
|
|
6258
|
+
var _wcSkillsLoaded = false;
|
|
6259
|
+
|
|
6260
|
+
// Default 3 files always present in every project
|
|
6261
|
+
var WC_DEFAULT_FILES = [
|
|
6262
|
+
{ name: 'memory.md', type: 'memory', content: '' },
|
|
6263
|
+
{ name: 'liara.md', type: 'provider', content: '' },
|
|
6264
|
+
{ name: 'skills.md', type: 'skill', content: '' }
|
|
6265
|
+
];
|
|
6255
6266
|
|
|
6256
6267
|
function wcEsc(s){return s?String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'):''}
|
|
6257
6268
|
|
|
@@ -6336,6 +6347,7 @@ function renderWebCraft(el) {
|
|
|
6336
6347
|
'<div id="wcFieldsList">'+authFieldsHtml+'</div>' +
|
|
6337
6348
|
'<div style="font-size:9px;color:var(--dim);margin-top:4px">'+t('wc_required_hint')+'</div>' +
|
|
6338
6349
|
'</div>' +
|
|
6350
|
+
wcSkillsPanelHtml() +
|
|
6339
6351
|
(wcState.running ?
|
|
6340
6352
|
'<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
6353
|
: '') +
|
|
@@ -6361,7 +6373,8 @@ function renderWebCraft(el) {
|
|
|
6361
6373
|
(wcMainTab === 'projects' ? wcProjectsPanelHtml() : editorHtml) +
|
|
6362
6374
|
'</div>' +
|
|
6363
6375
|
wcChatPanelHtml() +
|
|
6364
|
-
'</div>'
|
|
6376
|
+
'</div>' +
|
|
6377
|
+
wcSkillModalHtml();
|
|
6365
6378
|
}
|
|
6366
6379
|
|
|
6367
6380
|
function wcPickExample(i) {
|
|
@@ -6383,6 +6396,258 @@ function wcTabFiles() { wcRightTab = 'files'; renderWebCraft(document.getElement
|
|
|
6383
6396
|
function wcTabPreview() { wcRightTab = 'preview'; renderWebCraft(document.getElementById('content')); }
|
|
6384
6397
|
function wcOpenSandbox() { if (wcState.sandbox.port) window.open('http://127.0.0.1:' + wcState.sandbox.port, '_blank'); }
|
|
6385
6398
|
|
|
6399
|
+
// ── WebCraft Context Files (Skills / Memory / Provider) ───────────────────────
|
|
6400
|
+
|
|
6401
|
+
function wcFileTypeIcon(type) {
|
|
6402
|
+
return type === 'memory' ? '🧠' : type === 'provider' ? '🤖' : '📋';
|
|
6403
|
+
}
|
|
6404
|
+
function wcFileTypeBadge(type) {
|
|
6405
|
+
var colors = { memory: '#7c5cbf', provider: '#2a7fff', skill: '#1a7a4a' };
|
|
6406
|
+
var labels = { memory: 'memory', provider: 'provider', skill: 'skill' };
|
|
6407
|
+
return '<span style="font-size:9px;padding:1px 5px;border-radius:3px;background:' + (colors[type]||'#444') + ';color:#fff;margin-left:4px;flex-shrink:0">' + (labels[type]||type) + '</span>';
|
|
6408
|
+
}
|
|
6409
|
+
|
|
6410
|
+
function wcSkillsPanelHtml() {
|
|
6411
|
+
var hasProj = wcState.projectName && wcState.generatedFiles.length > 0;
|
|
6412
|
+
// Load context files from server on first render if project active
|
|
6413
|
+
if (hasProj && !_wcSkillsLoaded) {
|
|
6414
|
+
_wcSkillsLoaded = true;
|
|
6415
|
+
fetch(API + '/api/studio/webcraft/skills/' + encodeURIComponent(wcState.projectName))
|
|
6416
|
+
.then(function(r){ return r.json(); })
|
|
6417
|
+
.then(function(d){
|
|
6418
|
+
wcSkills = d.skills || [];
|
|
6419
|
+
// Ensure the 3 default files always exist client-side
|
|
6420
|
+
WC_DEFAULT_FILES.forEach(function(def) {
|
|
6421
|
+
var exists = wcSkills.some(function(s){ return s.name === def.name; });
|
|
6422
|
+
if (!exists) wcSkills.unshift({ name: def.name, type: def.type, content: def.content });
|
|
6423
|
+
});
|
|
6424
|
+
renderWebCraft(document.getElementById('content'));
|
|
6425
|
+
})
|
|
6426
|
+
.catch(function(){});
|
|
6427
|
+
}
|
|
6428
|
+
// Can add new skill only (memory + provider are singletons already in defaults)
|
|
6429
|
+
var rows = wcSkills.map(function(s, si) {
|
|
6430
|
+
var isSingleton = s.type === 'memory' || s.type === 'provider';
|
|
6431
|
+
var isEmpty = !s.content || s.content.trim() === '';
|
|
6432
|
+
return '<div style="display:flex;align-items:center;gap:4px;padding:5px 0;border-bottom:1px solid var(--border)">' +
|
|
6433
|
+
'<span style="font-size:13px;flex-shrink:0">' + wcFileTypeIcon(s.type) + '</span>' +
|
|
6434
|
+
'<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>' +
|
|
6435
|
+
wcFileTypeBadge(s.type) +
|
|
6436
|
+
(isEmpty ? '<span title="Vuoto" style="font-size:9px;color:#e09020;flex-shrink:0">⚠</span>' : '') +
|
|
6437
|
+
'<button onclick="wcOpenSkill('+si+')" title="Modifica" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:12px;padding:2px 4px;flex-shrink:0">✎</button>' +
|
|
6438
|
+
(!isSingleton ? '<button onclick="wcClearSkill('+si+')" title="Svuota" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:11px;padding:2px 4px;flex-shrink:0">🗑</button>' : '') +
|
|
6439
|
+
'</div>';
|
|
6440
|
+
}).join('');
|
|
6441
|
+
return '<div style="background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:14px">' +
|
|
6442
|
+
'<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">' +
|
|
6443
|
+
'<div style="font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.8px">📚 Contesto AI</div>' +
|
|
6444
|
+
(hasProj ? '<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">+ Skill</button>' : '') +
|
|
6445
|
+
'</div>' +
|
|
6446
|
+
(wcSkills.length > 0
|
|
6447
|
+
? '<div style="max-height:160px;overflow-y:auto">' + rows + '</div>'
|
|
6448
|
+
: (hasProj
|
|
6449
|
+
? '<div style="font-size:10px;color:var(--dim);font-style:italic">Caricamento...</div>'
|
|
6450
|
+
: '<div style="font-size:10px;color:var(--dim);font-style:italic">Genera un progetto per attivare i file di contesto.</div>'
|
|
6451
|
+
)
|
|
6452
|
+
) +
|
|
6453
|
+
'</div>';
|
|
6454
|
+
}
|
|
6455
|
+
|
|
6456
|
+
function wcSkillModalHtml() {
|
|
6457
|
+
if (!wcSkillModal) return '';
|
|
6458
|
+
var m = wcSkillModal;
|
|
6459
|
+
var isNew = m.mode === 'new';
|
|
6460
|
+
var charCount = (m.content || '').length;
|
|
6461
|
+
// Length guidance per type
|
|
6462
|
+
var maxChars = m.type === 'skill' ? 6000 : 4000;
|
|
6463
|
+
var warnLen = charCount > maxChars;
|
|
6464
|
+
var typeHints = {
|
|
6465
|
+
skill: 'Istruzioni tecniche, snippet, pattern di codice specifici per una funzione (es. Stripe, email, auth). Puoi avere quante skill vuoi. Max consigliato: ~6000 caratteri.',
|
|
6466
|
+
memory: 'Note persistenti sul progetto: decisioni architetturali, preferenze, contesto generale. Solo UN file. Max consigliato: ~4000 caratteri.',
|
|
6467
|
+
provider: 'Istruzioni specifiche per il modello AI usato (Liara/Qwen3, Claude, GPT-4...). Es. tono, formato risposte, vincoli. Solo UN file. Max consigliato: ~4000 caratteri.'
|
|
6468
|
+
};
|
|
6469
|
+
var hint = typeHints[m.type] || '';
|
|
6470
|
+
var typeOptions = ['skill', 'memory', 'provider'].map(function(t) {
|
|
6471
|
+
var hasSingleton = (t === 'memory' || t === 'provider') && wcSkills.some(function(s){ return s.type === t && (m.mode !== 'edit' || wcSkills.indexOf(s) !== m.idx); });
|
|
6472
|
+
return '<option value="'+t+'"'+(m.type===t?' selected':'')+(hasSingleton?' disabled':'')+'>'+t+(hasSingleton?' (esiste già)':'')+'</option>';
|
|
6473
|
+
}).join('');
|
|
6474
|
+
// Suggested name based on type
|
|
6475
|
+
var namePlaceholder = m.type === 'memory' ? 'memory.md' : m.type === 'provider' ? 'liara.md' : 'nome-skill.md';
|
|
6476
|
+
return '<div onclick="wcCloseSkillModal(event)" style="position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:9999;display:flex;align-items:center;justify-content:center">' +
|
|
6477
|
+
'<div onclick="event.stopPropagation()" style="background:var(--bg2);border:1px solid var(--border);border-radius:14px;width:600px;max-width:96vw;max-height:90vh;display:flex;flex-direction:column;overflow:hidden">' +
|
|
6478
|
+
'<div style="padding:16px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px">' +
|
|
6479
|
+
'<span style="font-size:14px;font-weight:700;color:var(--text);flex:1">' + wcFileTypeIcon(m.type) + ' ' + (isNew ? 'Nuovo file di contesto' : 'Modifica ' + wcEsc(m.name)) + '</span>' +
|
|
6480
|
+
'<button onclick="wcCloseSkillModal()" style="background:none;border:none;color:var(--dim);font-size:18px;cursor:pointer;line-height:1">×</button>' +
|
|
6481
|
+
'</div>' +
|
|
6482
|
+
'<div style="padding:16px 20px;display:flex;flex-direction:column;gap:12px;flex:1;overflow-y:auto">' +
|
|
6483
|
+
(isNew ? (
|
|
6484
|
+
'<div style="display:flex;gap:10px">' +
|
|
6485
|
+
'<div style="flex:1">' +
|
|
6486
|
+
'<div style="font-size:10px;color:var(--dim);margin-bottom:4px">TIPO</div>' +
|
|
6487
|
+
'<select id="wcSkillType" onchange="wcSkillTypeChange(this.value)" style="width:100%;padding:7px 10px;font-size:12px;border-radius:6px;border:1px solid var(--border2);background:var(--bg3);color:var(--text)">' + typeOptions + '</select>' +
|
|
6488
|
+
'</div>' +
|
|
6489
|
+
'<div style="flex:2">' +
|
|
6490
|
+
'<div style="font-size:10px;color:var(--dim);margin-bottom:4px">NOME FILE</div>' +
|
|
6491
|
+
'<input id="wcSkillName" value="'+wcEsc(m.name||'')+'" placeholder="'+namePlaceholder+'" 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)">' +
|
|
6492
|
+
'</div>' +
|
|
6493
|
+
'</div>'
|
|
6494
|
+
) : (
|
|
6495
|
+
'<div style="font-size:11px;color:var(--dim);background:var(--bg3);padding:7px 10px;border-radius:6px">File: <code style="color:var(--text)">'+wcEsc(m.name)+'</code> '+wcFileTypeBadge(m.type)+'</div>'
|
|
6496
|
+
)) +
|
|
6497
|
+
'<div style="background:var(--bg3);border-radius:8px;padding:9px 11px;font-size:10px;color:var(--dim);line-height:1.5">' +
|
|
6498
|
+
'💡 ' + wcEsc(hint) +
|
|
6499
|
+
'</div>' +
|
|
6500
|
+
'<div style="background:var(--bg3);border:1px solid var(--border);border-radius:8px;padding:10px">' +
|
|
6501
|
+
'<div style="font-size:10px;color:var(--dim);margin-bottom:6px">🤖 GENERA CON AI</div>' +
|
|
6502
|
+
'<div style="display:flex;gap:8px">' +
|
|
6503
|
+
'<textarea id="wcSkillAiDesc" rows="2" placeholder="Descrivi cosa deve contenere questo file... (es. Istruzioni per integrare Stripe con Express, pattern webhook)" 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>' +
|
|
6504
|
+
'<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>' +
|
|
6505
|
+
'</div>' +
|
|
6506
|
+
'</div>' +
|
|
6507
|
+
'<div>' +
|
|
6508
|
+
'<div style="font-size:10px;color:var(--dim);margin-bottom:4px;display:flex;justify-content:space-between">' +
|
|
6509
|
+
'<span>CONTENUTO (markdown)</span>' +
|
|
6510
|
+
'<span style="color:'+(warnLen?'#e05050':'var(--dim)')+'">'+charCount+' car.'+(warnLen?' ⚠ Troppo lungo, potrebbe ridurre la qualita del contesto':'')+'</span>' +
|
|
6511
|
+
'</div>' +
|
|
6512
|
+
'<textarea id="wcSkillContent" rows="14" oninput="wcSkillContentChange(this.value)" placeholder="# Titolo'+String.fromCharCode(10)+'Scrivi le istruzioni in Markdown..." style="width:100%;padding:8px 10px;font-size:11px;border-radius:6px;border:1px solid '+(warnLen?'#e05050':'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>' +
|
|
6513
|
+
'</div>' +
|
|
6514
|
+
'</div>' +
|
|
6515
|
+
'<div style="padding:12px 20px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;gap:8px">' +
|
|
6516
|
+
'<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>' +
|
|
6517
|
+
'<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>' +
|
|
6518
|
+
'</div>' +
|
|
6519
|
+
'</div>' +
|
|
6520
|
+
'</div>';
|
|
6521
|
+
}
|
|
6522
|
+
|
|
6523
|
+
function wcSkillTypeChange(newType) {
|
|
6524
|
+
if (!wcSkillModal) return;
|
|
6525
|
+
wcSkillModal.type = newType;
|
|
6526
|
+
// Auto-fill name for singletons
|
|
6527
|
+
if (newType === 'memory') wcSkillModal.name = 'memory.md';
|
|
6528
|
+
else if (newType === 'provider') wcSkillModal.name = 'liara.md';
|
|
6529
|
+
else wcSkillModal.name = '';
|
|
6530
|
+
renderWebCraft(document.getElementById('content'));
|
|
6531
|
+
}
|
|
6532
|
+
|
|
6533
|
+
function wcSkillContentChange(val) {
|
|
6534
|
+
if (!wcSkillModal) return;
|
|
6535
|
+
wcSkillModal.content = val;
|
|
6536
|
+
// Re-render only the char counter without full re-render
|
|
6537
|
+
var maxChars = wcSkillModal.type === 'skill' ? 6000 : 4000;
|
|
6538
|
+
var warnLen = val.length > maxChars;
|
|
6539
|
+
var el = document.querySelector('#wcSkillContent');
|
|
6540
|
+
if (el) el.style.borderColor = warnLen ? '#e05050' : 'var(--border2)';
|
|
6541
|
+
}
|
|
6542
|
+
|
|
6543
|
+
function wcNewSkill() {
|
|
6544
|
+
wcSkillModal = { mode: 'new', idx: null, name: '', content: '', type: 'skill', generating: false };
|
|
6545
|
+
renderWebCraft(document.getElementById('content'));
|
|
6546
|
+
}
|
|
6547
|
+
|
|
6548
|
+
function wcOpenSkill(si) {
|
|
6549
|
+
var s = wcSkills[si];
|
|
6550
|
+
if (!s) return;
|
|
6551
|
+
wcSkillModal = { mode: 'edit', idx: si, name: s.name, content: s.content, type: s.type || 'skill', generating: false };
|
|
6552
|
+
renderWebCraft(document.getElementById('content'));
|
|
6553
|
+
}
|
|
6554
|
+
|
|
6555
|
+
function wcCloseSkillModal(e) {
|
|
6556
|
+
if (e && e.target !== e.currentTarget) return;
|
|
6557
|
+
wcSkillModal = null;
|
|
6558
|
+
renderWebCraft(document.getElementById('content'));
|
|
6559
|
+
}
|
|
6560
|
+
|
|
6561
|
+
async function wcClearSkill(si) {
|
|
6562
|
+
var s = wcSkills[si];
|
|
6563
|
+
if (!s) return;
|
|
6564
|
+
if (!confirm('Svuotare il file "' + s.name + '"? Il file rimane ma il contenuto viene cancellato.')) return;
|
|
6565
|
+
wcSkills[si].content = '';
|
|
6566
|
+
await wcPersistSkills();
|
|
6567
|
+
renderWebCraft(document.getElementById('content'));
|
|
6568
|
+
}
|
|
6569
|
+
|
|
6570
|
+
async function wcGenerateSkill() {
|
|
6571
|
+
var descEl = document.getElementById('wcSkillAiDesc');
|
|
6572
|
+
var nameEl = document.getElementById('wcSkillName');
|
|
6573
|
+
var typeEl = document.getElementById('wcSkillType');
|
|
6574
|
+
var desc = (descEl ? descEl.value : '').trim();
|
|
6575
|
+
if (!desc) { alert('Descrivi prima cosa deve contenere il file.'); return; }
|
|
6576
|
+
wcSkillModal.generating = true;
|
|
6577
|
+
if (nameEl) wcSkillModal.name = nameEl.value;
|
|
6578
|
+
if (typeEl) wcSkillModal.type = typeEl.value;
|
|
6579
|
+
renderWebCraft(document.getElementById('content'));
|
|
6580
|
+
var systemByType = {
|
|
6581
|
+
skill: 'Sei un esperto di sviluppo web fullstack. Genera un file Markdown "skill" per il WebCraft Agent di NotHumanAllowed. Deve contenere istruzioni, pattern di codice, best practice e snippet pronti all uso come contesto persistente. Scrivi SOLO il contenuto Markdown, niente altro.',
|
|
6582
|
+
memory: 'Sei un assistente tecnico. Genera un file Markdown "memory" per il WebCraft Agent. Deve riassumere decisioni architetturali, preferenze dello sviluppatore e contesto generale del progetto. Scrivi SOLO il Markdown.',
|
|
6583
|
+
provider: 'Sei un esperto di prompt engineering. Genera un file Markdown con istruzioni specifiche per calibrare il comportamento del modello AI (tono, formato risposte, vincoli, preferenze). Scrivi SOLO il Markdown.'
|
|
6584
|
+
};
|
|
6585
|
+
try {
|
|
6586
|
+
var r = await fetch(API + '/api/studio/webcraft', {
|
|
6587
|
+
method: 'POST',
|
|
6588
|
+
headers: {'Content-Type':'application/json'},
|
|
6589
|
+
body: JSON.stringify({
|
|
6590
|
+
system: systemByType[wcSkillModal.type] || systemByType.skill,
|
|
6591
|
+
user: 'Progetto: ' + wcState.projectName + String.fromCharCode(10) + 'Stack: Express.js, PostgreSQL, JWT auth' + String.fromCharCode(10) + String.fromCharCode(10) + desc,
|
|
6592
|
+
max_tokens: 2048
|
|
6593
|
+
})
|
|
6594
|
+
});
|
|
6595
|
+
if (r.ok) {
|
|
6596
|
+
var d = await r.json();
|
|
6597
|
+
wcSkillModal.content = d.text || '';
|
|
6598
|
+
wcSkillModal.generating = false;
|
|
6599
|
+
// Auto-suggest name if empty
|
|
6600
|
+
if (!wcSkillModal.name && desc.length > 0) {
|
|
6601
|
+
if (wcSkillModal.type === 'memory') wcSkillModal.name = 'memory.md';
|
|
6602
|
+
else if (wcSkillModal.type === 'provider') wcSkillModal.name = 'liara.md';
|
|
6603
|
+
else wcSkillModal.name = desc.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 30) + '.md';
|
|
6604
|
+
}
|
|
6605
|
+
}
|
|
6606
|
+
} catch(e) {}
|
|
6607
|
+
wcSkillModal.generating = false;
|
|
6608
|
+
renderWebCraft(document.getElementById('content'));
|
|
6609
|
+
}
|
|
6610
|
+
|
|
6611
|
+
async function wcSaveSkill() {
|
|
6612
|
+
var nameEl = document.getElementById('wcSkillName');
|
|
6613
|
+
var contentEl = document.getElementById('wcSkillContent');
|
|
6614
|
+
var typeEl = document.getElementById('wcSkillType');
|
|
6615
|
+
var name = (nameEl ? nameEl.value : wcSkillModal.name).trim();
|
|
6616
|
+
var content = contentEl ? contentEl.value : (wcSkillModal.content || '');
|
|
6617
|
+
var type = (typeEl ? typeEl.value : wcSkillModal.type) || 'skill';
|
|
6618
|
+
if (!name) { alert('Inserisci un nome per il file.'); return; }
|
|
6619
|
+
if (!name.endsWith('.md')) name = name + '.md';
|
|
6620
|
+
// Enforce singleton: only one memory, one provider
|
|
6621
|
+
if ((type === 'memory' || type === 'provider') && wcSkillModal.mode === 'new') {
|
|
6622
|
+
var existing = wcSkills.findIndex(function(s){ return s.type === type; });
|
|
6623
|
+
if (existing >= 0) { alert('Esiste gia un file di tipo "' + type + '". Modificalo direttamente.'); return; }
|
|
6624
|
+
}
|
|
6625
|
+
var skill = { name: name, content: content, type: type };
|
|
6626
|
+
if (wcSkillModal.mode === 'edit' && wcSkillModal.idx !== null) {
|
|
6627
|
+
wcSkills[wcSkillModal.idx] = skill;
|
|
6628
|
+
} else {
|
|
6629
|
+
wcSkills.push(skill);
|
|
6630
|
+
}
|
|
6631
|
+
wcSkillModal = null;
|
|
6632
|
+
await wcPersistSkills();
|
|
6633
|
+
renderWebCraft(document.getElementById('content'));
|
|
6634
|
+
}
|
|
6635
|
+
|
|
6636
|
+
async function wcDeleteSkill(si) {
|
|
6637
|
+
// No-op: kept for potential future use. Skills are only cleared, not deleted.
|
|
6638
|
+
}
|
|
6639
|
+
|
|
6640
|
+
async function wcPersistSkills() {
|
|
6641
|
+
if (!wcState.projectName) return;
|
|
6642
|
+
try {
|
|
6643
|
+
await fetch(API + '/api/studio/webcraft/skills/' + encodeURIComponent(wcState.projectName), {
|
|
6644
|
+
method: 'POST',
|
|
6645
|
+
headers: {'Content-Type':'application/json'},
|
|
6646
|
+
body: JSON.stringify({ skills: wcSkills })
|
|
6647
|
+
});
|
|
6648
|
+
} catch(_) {}
|
|
6649
|
+
}
|
|
6650
|
+
|
|
6386
6651
|
// ── WebCraft Agent Chat Panel ─────────────────────────────────────────────
|
|
6387
6652
|
function wcChatPanelHtml() {
|
|
6388
6653
|
var hasProject = wcState.projectName && wcState.generatedFiles.length > 0;
|
|
@@ -6739,6 +7004,13 @@ async function wcLoadProject(pi) {
|
|
|
6739
7004
|
var cr = await fetch(API + '/api/studio/webcraft/projects/chat/load/' + encodeURIComponent(wcState.projectName));
|
|
6740
7005
|
if (cr.ok) { var cd = await cr.json(); wcChat = cd.chat || []; }
|
|
6741
7006
|
} catch(_) { wcChat = []; }
|
|
7007
|
+
// Load skills for this project
|
|
7008
|
+
_wcSkillsLoaded = false;
|
|
7009
|
+
wcSkills = [];
|
|
7010
|
+
try {
|
|
7011
|
+
var sr = await fetch(API + '/api/studio/webcraft/skills/' + encodeURIComponent(wcState.projectName));
|
|
7012
|
+
if (sr.ok) { var sd = await sr.json(); wcSkills = sd.skills || []; _wcSkillsLoaded = true; }
|
|
7013
|
+
} catch(_) {}
|
|
6742
7014
|
renderWebCraft(document.getElementById('content'));
|
|
6743
7015
|
wcScrollChatToBottom();
|
|
6744
7016
|
}
|