nothumanallowed 13.5.75 → 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.75",
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": {
@@ -3997,12 +3997,15 @@ ${completedHeadings ? `## SECTIONS ALREADY WRITTEN (headings only):\n${completed
3997
3997
  // Default types for well-known filenames
3998
3998
  const defaultType = (n) => n === 'memory.md' ? 'memory' : n === 'liara.md' ? 'provider' : 'skill';
3999
3999
  const skills = [];
4000
- for (const fname of fs.readdirSync(skillsDir)) {
4001
- if (!fname.endsWith('.md')) continue;
4000
+ // .md first (sorted), then .log files (sorted newest-first by filename)
4001
+ const allFiles = fs.readdirSync(skillsDir);
4002
+ const mdFiles = allFiles.filter(f => f.endsWith('.md')).sort();
4003
+ const logFiles = allFiles.filter(f => f.endsWith('.log')).sort().reverse();
4004
+ for (const fname of [...mdFiles, ...logFiles]) {
4002
4005
  try {
4003
4006
  const content = fs.readFileSync(path.join(skillsDir, fname), 'utf8');
4004
4007
  const type = typeIndex[fname] || defaultType(fname);
4005
- skills.push({ name: fname, content, type });
4008
+ skills.push({ name: fname, content, type: fname.endsWith('.log') ? 'log' : type });
4006
4009
  } catch(_) {}
4007
4010
  }
4008
4011
  sendJSON(res, 200, { skills });
@@ -4048,6 +4051,30 @@ ${completedHeadings ? `## SECTIONS ALREADY WRITTEN (headings only):\n${completed
4048
4051
  return;
4049
4052
  }
4050
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
+
4051
4078
  // GET /api/studio/webcraft/projects/load/:name → { projectName, description, files[] }
4052
4079
  if (pathname.startsWith('/api/studio/webcraft/projects/load/') && method === 'GET') {
4053
4080
  const projName = decodeURIComponent(pathname.replace('/api/studio/webcraft/projects/load/', '')).replace(/[^a-zA-Z0-9_-]/g, '');
@@ -4581,7 +4608,9 @@ module.exports = { validateEmail, sanitizeText, validatePassword, validateUserna
4581
4608
  // Write sandbox log to skills/ so the agent can read it as context
4582
4609
  try {
4583
4610
  const _nl = '\n';
4584
- 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());
4585
4614
  const logName = projName + '-' + logTs.replace(/[: ]/g, '-') + '.log';
4586
4615
  const logsDir = path.join(sandboxDir, 'skills');
4587
4616
  fs.mkdirSync(logsDir, { recursive: true });
@@ -4603,7 +4632,9 @@ module.exports = { validateEmail, sanitizeText, validatePassword, validateUserna
4603
4632
  // Write error log too
4604
4633
  try {
4605
4634
  const _nl = '\n';
4606
- 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());
4607
4638
  const logName = projName + '-' + logTs.replace(/[: ]/g, '-') + '-ERROR.log';
4608
4639
  const logsDir = path.join(sandboxDir, 'skills');
4609
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() {
@@ -7901,10 +7930,22 @@ async function wcCallLLM(sys, user, signal) {
7901
7930
  body: JSON.stringify({system: sys, user: user, max_tokens: 4096})
7902
7931
  };
7903
7932
  if (signal) fetchOpts.signal = signal;
7904
- var r = await fetch(API + '/api/studio/webcraft', fetchOpts);
7905
- if (!r.ok) throw new Error('LLM error ' + r.status);
7906
- var d = await r.json();
7907
- return (d && (d.text || d.content || d.result)) || '';
7933
+ // Retry up to 2 times on 5xx errors (Liara may be temporarily overloaded)
7934
+ for (var attempt = 0; attempt < 3; attempt++) {
7935
+ if (signal && signal.aborted) throw new DOMException('Aborted', 'AbortError');
7936
+ var r = await fetch(API + '/api/studio/webcraft', fetchOpts);
7937
+ if (r.ok) {
7938
+ var d = await r.json();
7939
+ return (d && (d.text || d.content || d.result)) || '';
7940
+ }
7941
+ if (r.status < 500 || attempt === 2) {
7942
+ var errBody = '';
7943
+ try { var eb = await r.json(); errBody = eb.error || ''; } catch(_) {}
7944
+ throw new Error('LLM error ' + r.status + (errBody ? ': ' + errBody : ''));
7945
+ }
7946
+ // 5xx — wait 2s then retry
7947
+ await new Promise(function(resolve) { setTimeout(resolve, 2000); });
7948
+ }
7908
7949
  }
7909
7950
 
7910
7951
  function wcSandboxPanelHtml() {
@@ -8091,10 +8132,14 @@ async function wcStartSandbox() {
8091
8132
  wcState.sandbox.dir = evt.dir;
8092
8133
  _wcAutoFixAttempts = 0;
8093
8134
  wcStartAutoFixPoller();
8135
+ // Reload skills so newly written log file appears in the panel
8136
+ _wcSkillsLoaded = false;
8094
8137
  renderWebCraft(document.getElementById('content'));
8095
8138
  } else if (evt.type === 'error') {
8096
8139
  wcState.sandbox.running = false;
8097
8140
  wcState.sandbox.error = evt.msg;
8141
+ // Reload skills so error log appears in the panel
8142
+ _wcSkillsLoaded = false;
8098
8143
  renderWebCraft(document.getElementById('content'));
8099
8144
  // Auto-fix: try to detect MODULE_NOT_FOUND in crash message and fix it
8100
8145
  var errMsg = evt.msg || '';