nothumanallowed 13.5.76 → 13.5.77

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.76",
3
+ "version": "13.5.77",
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": {
@@ -4051,6 +4051,30 @@ ${completedHeadings ? `## SECTIONS ALREADY WRITTEN (headings only):\n${completed
4051
4051
  return;
4052
4052
  }
4053
4053
 
4054
+ // POST /api/studio/webcraft/skills/:name/delete { name } → delete a single skill/log file
4055
+ if (pathname.match(/^\/api\/studio\/webcraft\/skills\/[^/]+\/delete$/) && method === 'POST') {
4056
+ const projName = decodeURIComponent(pathname.replace('/api/studio/webcraft/skills/', '').replace('/delete', '')).replace(/[^a-zA-Z0-9_-]/g, '');
4057
+ const body = await parseBody(req);
4058
+ const fname = (body.name || '').replace(/[^a-zA-Z0-9_. -]/g, '_');
4059
+ if (!fname || !projName) { sendJSON(res, 400, { error: 'invalid' }); return; }
4060
+ // Protect the 3 default .md files from deletion
4061
+ const PROTECTED = new Set(['memory.md', 'liara.md', 'skills.md']);
4062
+ if (PROTECTED.has(fname)) { sendJSON(res, 400, { error: 'protected file' }); return; }
4063
+ const skillsDir = path.join(os.homedir(), '.nha', 'webcraft', projName, 'skills');
4064
+ const filePath = path.join(skillsDir, fname);
4065
+ try { if (fs.existsSync(filePath)) fs.unlinkSync(filePath); } catch(_) {}
4066
+ // Remove from _index.json too
4067
+ const idxPath = path.join(skillsDir, '_index.json');
4068
+ try {
4069
+ let idx = JSON.parse(fs.readFileSync(idxPath, 'utf8'));
4070
+ delete idx[fname];
4071
+ fs.writeFileSync(idxPath, JSON.stringify(idx), 'utf8');
4072
+ } catch(_) {}
4073
+ sendJSON(res, 200, { ok: true });
4074
+ logRequest(method, pathname, 200, Date.now() - start);
4075
+ return;
4076
+ }
4077
+
4054
4078
  // GET /api/studio/webcraft/projects/load/:name → { projectName, description, files[] }
4055
4079
  if (pathname.startsWith('/api/studio/webcraft/projects/load/') && method === 'GET') {
4056
4080
  const projName = decodeURIComponent(pathname.replace('/api/studio/webcraft/projects/load/', '')).replace(/[^a-zA-Z0-9_-]/g, '');
@@ -4584,7 +4608,9 @@ module.exports = { validateEmail, sanitizeText, validatePassword, validateUserna
4584
4608
  // Write sandbox log to skills/ so the agent can read it as context
4585
4609
  try {
4586
4610
  const _nl = '\n';
4587
- const logTs = new Date().toISOString().replace('T', ' ').slice(0, 19);
4611
+ const _now = new Date();
4612
+ const _pad = n => String(n).padStart(2,'0');
4613
+ const logTs = _now.getFullYear()+'-'+_pad(_now.getMonth()+1)+'-'+_pad(_now.getDate())+' '+_pad(_now.getHours())+':'+_pad(_now.getMinutes())+':'+_pad(_now.getSeconds());
4588
4614
  const logName = projName + '-' + logTs.replace(/[: ]/g, '-') + '.log';
4589
4615
  const logsDir = path.join(sandboxDir, 'skills');
4590
4616
  fs.mkdirSync(logsDir, { recursive: true });
@@ -4606,7 +4632,9 @@ module.exports = { validateEmail, sanitizeText, validatePassword, validateUserna
4606
4632
  // Write error log too
4607
4633
  try {
4608
4634
  const _nl = '\n';
4609
- const logTs = new Date().toISOString().replace('T', ' ').slice(0, 19);
4635
+ const _now2 = new Date();
4636
+ const _pad2 = n => String(n).padStart(2,'0');
4637
+ const logTs = _now2.getFullYear()+'-'+_pad2(_now2.getMonth()+1)+'-'+_pad2(_now2.getDate())+' '+_pad2(_now2.getHours())+':'+_pad2(_now2.getMinutes())+':'+_pad2(_now2.getSeconds());
4610
4638
  const logName = projName + '-' + logTs.replace(/[: ]/g, '-') + '-ERROR.log';
4611
4639
  const logsDir = path.join(sandboxDir, 'skills');
4612
4640
  fs.mkdirSync(logsDir, { recursive: true });
@@ -6786,6 +6786,22 @@ function wcSkillsPanelHtml() {
6786
6786
  function wcSkillModalHtml() {
6787
6787
  if (!wcSkillModal) return '';
6788
6788
  var m = wcSkillModal;
6789
+ // Log files: read-only viewer
6790
+ if (m.mode === 'view') {
6791
+ 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">' +
6792
+ '<div onclick="event.stopPropagation()" style="background:var(--bg2);border:1px solid var(--border);border-radius:14px;width:680px;max-width:96vw;max-height:88vh;display:flex;flex-direction:column;overflow:hidden">' +
6793
+ '<div style="padding:14px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px">' +
6794
+ '<span style="font-size:14px">&#128196;</span>' +
6795
+ '<span style="font-size:13px;font-weight:700;color:var(--text);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+wcEsc(m.name)+'</span>' +
6796
+ '<span style="font-size:10px;background:#333;color:#aaa;padding:2px 8px;border-radius:10px">log</span>' +
6797
+ '<button onclick="wcCloseSkillModal()" style="background:none;border:none;color:var(--dim);font-size:18px;cursor:pointer;line-height:1;margin-left:4px">&times;</button>' +
6798
+ '</div>' +
6799
+ '<div style="flex:1;overflow:auto;padding:14px 18px">' +
6800
+ '<pre style="margin:0;font-size:11px;line-height:1.7;color:var(--text);font-family:var(--mono);white-space:pre-wrap;word-break:break-all">'+wcEsc(m.content || '')+'</pre>' +
6801
+ '</div>' +
6802
+ '</div>' +
6803
+ '</div>';
6804
+ }
6789
6805
  var isNew = m.mode === 'new';
6790
6806
  var charCount = (m.content || '').length;
6791
6807
  // Length guidance per type
@@ -6878,7 +6894,8 @@ function wcNewSkill() {
6878
6894
  function wcOpenSkill(si) {
6879
6895
  var s = wcSkills[si];
6880
6896
  if (!s) return;
6881
- wcSkillModal = { mode: 'edit', idx: si, name: s.name, content: s.content, type: s.type || 'skill', generating: false };
6897
+ var mode = (s.type === 'log') ? 'view' : 'edit';
6898
+ wcSkillModal = { mode: mode, idx: si, name: s.name, content: s.content, type: s.type || 'skill', generating: false };
6882
6899
  renderWebCraft(document.getElementById('content'));
6883
6900
  }
6884
6901
 
@@ -6964,7 +6981,19 @@ async function wcSaveSkill() {
6964
6981
  }
6965
6982
 
6966
6983
  async function wcDeleteSkill(si) {
6967
- // No-op: kept for potential future use. Skills are only cleared, not deleted.
6984
+ var s = wcSkills[si];
6985
+ if (!s) return;
6986
+ if (!confirm('Eliminare "' + s.name + '"?')) return;
6987
+ // Delete the file on disk via the delete-skill endpoint
6988
+ try {
6989
+ await fetch(API + '/api/studio/webcraft/skills/' + encodeURIComponent(wcState.projectName) + '/delete', {
6990
+ method: 'POST',
6991
+ headers: {'Content-Type':'application/json'},
6992
+ body: JSON.stringify({ name: s.name })
6993
+ });
6994
+ } catch(_) {}
6995
+ wcSkills.splice(si, 1);
6996
+ renderWebCraft(document.getElementById('content'));
6968
6997
  }
6969
6998
 
6970
6999
  async function wcPersistSkills() {