nothumanallowed 13.5.56 → 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 +75 -21
- package/src/constants.mjs +1 -1
- package/src/services/web-ui.mjs +150 -44
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,41 +3973,76 @@ ${completedHeadings ? `## SECTIONS ALREADY WRITTEN (headings only):\n${completed
|
|
|
3966
3973
|
return;
|
|
3967
3974
|
}
|
|
3968
3975
|
|
|
3969
|
-
// GET /api/studio/webcraft/skills/:name → { skills: [{name, content}] }
|
|
3976
|
+
// GET /api/studio/webcraft/skills/:name → { skills: [{name, content, type}] }
|
|
3970
3977
|
if (pathname.startsWith('/api/studio/webcraft/skills/') && method === 'GET') {
|
|
3971
3978
|
const projName = decodeURIComponent(pathname.replace('/api/studio/webcraft/skills/', '')).replace(/[^a-zA-Z0-9_-]/g, '');
|
|
3972
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';
|
|
3973
3999
|
const skills = [];
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
}
|
|
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(_) {}
|
|
3982
4007
|
}
|
|
3983
4008
|
sendJSON(res, 200, { skills });
|
|
3984
4009
|
logRequest(method, pathname, 200, Date.now() - start);
|
|
3985
4010
|
return;
|
|
3986
4011
|
}
|
|
3987
4012
|
|
|
3988
|
-
// POST /api/studio/webcraft/skills/:name { skills: [{name, content}] }
|
|
4013
|
+
// POST /api/studio/webcraft/skills/:name { skills: [{name, content, type}] }
|
|
3989
4014
|
if (pathname.startsWith('/api/studio/webcraft/skills/') && method === 'POST') {
|
|
3990
4015
|
const projName = decodeURIComponent(pathname.replace('/api/studio/webcraft/skills/', '')).replace(/[^a-zA-Z0-9_-]/g, '');
|
|
3991
4016
|
const body = await parseBody(req);
|
|
3992
4017
|
const skills = body.skills || [];
|
|
3993
4018
|
const skillsDir = path.join(os.homedir(), '.nha', 'webcraft', projName, 'skills');
|
|
3994
4019
|
if (!fs.existsSync(skillsDir)) fs.mkdirSync(skillsDir, { recursive: true });
|
|
3995
|
-
//
|
|
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']);
|
|
3996
4028
|
for (const fname of fs.readdirSync(skillsDir)) {
|
|
3997
|
-
if (fname.endsWith('.md'))
|
|
4029
|
+
if (!fname.endsWith('.md')) continue;
|
|
4030
|
+
if (!incoming.has(fname) && !WC_KEEP.has(fname)) fs.unlinkSync(path.join(skillsDir, fname));
|
|
3998
4031
|
}
|
|
4032
|
+
// Write/update all skills; always keep defaults even if not in incoming (write empty)
|
|
4033
|
+
const typeIndex = {};
|
|
3999
4034
|
for (const skill of skills) {
|
|
4000
4035
|
if (!skill.name) continue;
|
|
4001
|
-
|
|
4002
|
-
|
|
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');
|
|
4003
4044
|
}
|
|
4045
|
+
fs.writeFileSync(path.join(skillsDir, '_index.json'), JSON.stringify(typeIndex), 'utf8');
|
|
4004
4046
|
sendJSON(res, 200, { ok: true });
|
|
4005
4047
|
logRequest(method, pathname, 200, Date.now() - start);
|
|
4006
4048
|
return;
|
|
@@ -4449,18 +4491,30 @@ module.exports = { get, set, del, exists };
|
|
|
4449
4491
|
const agentMemoryPath = path.join(sandboxDir, 'webcraft-agent.md');
|
|
4450
4492
|
const agentMemory = fs.existsSync(agentMemoryPath) ? fs.readFileSync(agentMemoryPath, 'utf8') : '';
|
|
4451
4493
|
|
|
4452
|
-
// Load
|
|
4494
|
+
// Load context files from skills/ subfolder (skills, memory, provider)
|
|
4453
4495
|
const skillsDir = path.join(sandboxDir, 'skills');
|
|
4454
4496
|
let skillsContext = '';
|
|
4455
4497
|
if (fs.existsSync(skillsDir)) {
|
|
4456
|
-
const
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
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 {
|
|
4460
4506
|
const content = fs.readFileSync(path.join(skillsDir, fname), 'utf8');
|
|
4461
|
-
|
|
4462
|
-
|
|
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(_) {}
|
|
4463
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';
|
|
4464
4518
|
}
|
|
4465
4519
|
|
|
4466
4520
|
// Read all project files to give agent full context
|
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
|
@@ -6253,10 +6253,17 @@ var wcChatAttachments = []; // [{name, mimeType, base64, size}]
|
|
|
6253
6253
|
var _wcAutoFixAttempts = 0;
|
|
6254
6254
|
var _wcAutoFixTimer = null;
|
|
6255
6255
|
// Skills state
|
|
6256
|
-
var wcSkills = []; // [{name, content}]
|
|
6257
|
-
var wcSkillModal = null; // null | {mode:'edit'|'new', idx:number|null, name, content, generating}
|
|
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
6258
|
var _wcSkillsLoaded = false;
|
|
6259
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
|
+
];
|
|
6266
|
+
|
|
6260
6267
|
function wcEsc(s){return s?String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'):''}
|
|
6261
6268
|
|
|
6262
6269
|
function renderWebCraft(el) {
|
|
@@ -6389,32 +6396,59 @@ function wcTabFiles() { wcRightTab = 'files'; renderWebCraft(document.getElement
|
|
|
6389
6396
|
function wcTabPreview() { wcRightTab = 'preview'; renderWebCraft(document.getElementById('content')); }
|
|
6390
6397
|
function wcOpenSandbox() { if (wcState.sandbox.port) window.open('http://127.0.0.1:' + wcState.sandbox.port, '_blank'); }
|
|
6391
6398
|
|
|
6392
|
-
// ── WebCraft Skills
|
|
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
|
+
|
|
6393
6410
|
function wcSkillsPanelHtml() {
|
|
6394
6411
|
var hasProj = wcState.projectName && wcState.generatedFiles.length > 0;
|
|
6395
|
-
// Load
|
|
6412
|
+
// Load context files from server on first render if project active
|
|
6396
6413
|
if (hasProj && !_wcSkillsLoaded) {
|
|
6397
6414
|
_wcSkillsLoaded = true;
|
|
6398
6415
|
fetch(API + '/api/studio/webcraft/skills/' + encodeURIComponent(wcState.projectName))
|
|
6399
6416
|
.then(function(r){ return r.json(); })
|
|
6400
|
-
.then(function(d){
|
|
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
|
+
})
|
|
6401
6426
|
.catch(function(){});
|
|
6402
6427
|
}
|
|
6428
|
+
// Can add new skill only (memory + provider are singletons already in defaults)
|
|
6403
6429
|
var rows = wcSkills.map(function(s, si) {
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
'<
|
|
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>' : '') +
|
|
6408
6439
|
'</div>';
|
|
6409
6440
|
}).join('');
|
|
6410
6441
|
return '<div style="background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:14px">' +
|
|
6411
6442
|
'<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">📚
|
|
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">+
|
|
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>' : '') +
|
|
6414
6445
|
'</div>' +
|
|
6415
6446
|
(wcSkills.length > 0
|
|
6416
|
-
? '<div style="max-height:
|
|
6417
|
-
:
|
|
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
|
+
)
|
|
6418
6452
|
) +
|
|
6419
6453
|
'</div>';
|
|
6420
6454
|
}
|
|
@@ -6423,27 +6457,59 @@ function wcSkillModalHtml() {
|
|
|
6423
6457
|
if (!wcSkillModal) return '';
|
|
6424
6458
|
var m = wcSkillModal;
|
|
6425
6459
|
var isNew = m.mode === 'new';
|
|
6426
|
-
|
|
6427
|
-
|
|
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">' +
|
|
6428
6478
|
'<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?'
|
|
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>' +
|
|
6430
6480
|
'<button onclick="wcCloseSkillModal()" style="background:none;border:none;color:var(--dim);font-size:18px;cursor:pointer;line-height:1">×</button>' +
|
|
6431
6481
|
'</div>' +
|
|
6432
|
-
'<div style="padding:16px 20px;display:flex;flex-direction:column;gap:
|
|
6433
|
-
|
|
6434
|
-
'<div style="
|
|
6435
|
-
|
|
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) +
|
|
6436
6499
|
'</div>' +
|
|
6437
6500
|
'<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
|
|
6501
|
+
'<div style="font-size:10px;color:var(--dim);margin-bottom:6px">🤖 GENERA CON AI</div>' +
|
|
6439
6502
|
'<div style="display:flex;gap:8px">' +
|
|
6440
|
-
'<textarea id="wcSkillAiDesc" rows="2" placeholder="es. Istruzioni per integrare Stripe
|
|
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>' +
|
|
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>' +
|
|
6442
6505
|
'</div>' +
|
|
6443
6506
|
'</div>' +
|
|
6444
6507
|
'<div>' +
|
|
6445
|
-
'<div style="font-size:10px;color:var(--dim);margin-bottom:4px">
|
|
6446
|
-
|
|
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>' +
|
|
6447
6513
|
'</div>' +
|
|
6448
6514
|
'</div>' +
|
|
6449
6515
|
'<div style="padding:12px 20px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;gap:8px">' +
|
|
@@ -6454,15 +6520,35 @@ function wcSkillModalHtml() {
|
|
|
6454
6520
|
'</div>';
|
|
6455
6521
|
}
|
|
6456
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
|
+
|
|
6457
6543
|
function wcNewSkill() {
|
|
6458
|
-
wcSkillModal = { mode: 'new', idx: null, name: '', content: '', generating: false };
|
|
6544
|
+
wcSkillModal = { mode: 'new', idx: null, name: '', content: '', type: 'skill', generating: false };
|
|
6459
6545
|
renderWebCraft(document.getElementById('content'));
|
|
6460
6546
|
}
|
|
6461
6547
|
|
|
6462
6548
|
function wcOpenSkill(si) {
|
|
6463
6549
|
var s = wcSkills[si];
|
|
6464
6550
|
if (!s) return;
|
|
6465
|
-
wcSkillModal = { mode: 'edit', idx: si, name: s.name, content: s.content, generating: false };
|
|
6551
|
+
wcSkillModal = { mode: 'edit', idx: si, name: s.name, content: s.content, type: s.type || 'skill', generating: false };
|
|
6466
6552
|
renderWebCraft(document.getElementById('content'));
|
|
6467
6553
|
}
|
|
6468
6554
|
|
|
@@ -6472,21 +6558,37 @@ function wcCloseSkillModal(e) {
|
|
|
6472
6558
|
renderWebCraft(document.getElementById('content'));
|
|
6473
6559
|
}
|
|
6474
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
|
+
|
|
6475
6570
|
async function wcGenerateSkill() {
|
|
6476
6571
|
var descEl = document.getElementById('wcSkillAiDesc');
|
|
6477
6572
|
var nameEl = document.getElementById('wcSkillName');
|
|
6573
|
+
var typeEl = document.getElementById('wcSkillType');
|
|
6478
6574
|
var desc = (descEl ? descEl.value : '').trim();
|
|
6479
|
-
if (!desc) { alert('Descrivi prima cosa deve contenere
|
|
6575
|
+
if (!desc) { alert('Descrivi prima cosa deve contenere il file.'); return; }
|
|
6480
6576
|
wcSkillModal.generating = true;
|
|
6481
|
-
wcSkillModal.name = nameEl
|
|
6577
|
+
if (nameEl) wcSkillModal.name = nameEl.value;
|
|
6578
|
+
if (typeEl) wcSkillModal.type = typeEl.value;
|
|
6482
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
|
+
};
|
|
6483
6585
|
try {
|
|
6484
6586
|
var r = await fetch(API + '/api/studio/webcraft', {
|
|
6485
6587
|
method: 'POST',
|
|
6486
6588
|
headers: {'Content-Type':'application/json'},
|
|
6487
6589
|
body: JSON.stringify({
|
|
6488
|
-
system:
|
|
6489
|
-
user: 'Progetto: ' + wcState.projectName + String.fromCharCode(10) + 'Stack: Express.js, PostgreSQL, JWT auth
|
|
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,
|
|
6490
6592
|
max_tokens: 2048
|
|
6491
6593
|
})
|
|
6492
6594
|
});
|
|
@@ -6496,39 +6598,43 @@ async function wcGenerateSkill() {
|
|
|
6496
6598
|
wcSkillModal.generating = false;
|
|
6497
6599
|
// Auto-suggest name if empty
|
|
6498
6600
|
if (!wcSkillModal.name && desc.length > 0) {
|
|
6499
|
-
wcSkillModal.
|
|
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';
|
|
6500
6604
|
}
|
|
6501
6605
|
}
|
|
6502
|
-
} catch(e) {
|
|
6503
|
-
|
|
6504
|
-
}
|
|
6606
|
+
} catch(e) {}
|
|
6607
|
+
wcSkillModal.generating = false;
|
|
6505
6608
|
renderWebCraft(document.getElementById('content'));
|
|
6506
6609
|
}
|
|
6507
6610
|
|
|
6508
6611
|
async function wcSaveSkill() {
|
|
6509
6612
|
var nameEl = document.getElementById('wcSkillName');
|
|
6510
6613
|
var contentEl = document.getElementById('wcSkillContent');
|
|
6511
|
-
var
|
|
6512
|
-
var
|
|
6513
|
-
|
|
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; }
|
|
6514
6619
|
if (!name.endsWith('.md')) name = name + '.md';
|
|
6515
|
-
|
|
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 };
|
|
6516
6626
|
if (wcSkillModal.mode === 'edit' && wcSkillModal.idx !== null) {
|
|
6517
6627
|
wcSkills[wcSkillModal.idx] = skill;
|
|
6518
6628
|
} else {
|
|
6519
6629
|
wcSkills.push(skill);
|
|
6520
6630
|
}
|
|
6521
6631
|
wcSkillModal = null;
|
|
6522
|
-
// Persist to server
|
|
6523
6632
|
await wcPersistSkills();
|
|
6524
6633
|
renderWebCraft(document.getElementById('content'));
|
|
6525
6634
|
}
|
|
6526
6635
|
|
|
6527
6636
|
async function wcDeleteSkill(si) {
|
|
6528
|
-
|
|
6529
|
-
wcSkills.splice(si, 1);
|
|
6530
|
-
await wcPersistSkills();
|
|
6531
|
-
renderWebCraft(document.getElementById('content'));
|
|
6637
|
+
// No-op: kept for potential future use. Skills are only cleared, not deleted.
|
|
6532
6638
|
}
|
|
6533
6639
|
|
|
6534
6640
|
async function wcPersistSkills() {
|