nothumanallowed 14.4.11 → 14.4.13

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": "14.4.11",
3
+ "version": "14.4.13",
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/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 = '14.4.11';
8
+ export const VERSION = '14.4.13';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -381,23 +381,95 @@ const SkillStore = {
381
381
  if (fs.existsSync(abs)) fs.unlinkSync(abs);
382
382
  },
383
383
 
384
- /** Ensure memory.md, skills.md, and provider.md always exist for a project. */
384
+ /** Ensure memory.md, skills.md, and provider.md always exist with useful content. */
385
385
  ensureDefaults(projectName, config) {
386
386
  const dir = ensureDir(this.dir(projectName));
387
387
  const provider = config?.llm?.provider || 'nha';
388
388
  const model = config?.llm?.model || '';
389
+ const projectDir = path.join(NHA_DIR, 'webcraft', projectName);
390
+
391
+ // Scan project files for auto-generating context
392
+ let fileList = '';
393
+ try {
394
+ const files = fs.readdirSync(projectDir, { recursive: true })
395
+ .filter((f) => !String(f).includes('node_modules') && !String(f).startsWith('.nha-') && !String(f).startsWith('.'));
396
+ fileList = files.slice(0, 30).join(', ');
397
+ } catch {}
398
+
399
+ const needsWrite = (filePath, minLen = 150) => {
400
+ if (!fs.existsSync(filePath)) return true;
401
+ return fs.readFileSync(filePath, 'utf-8').length < minLen;
402
+ };
389
403
 
390
404
  const memFile = path.join(dir, 'memory.md');
391
- if (!fs.existsSync(memFile)) {
392
- fs.writeFileSync(memFile, `# ${projectName} — Project Memory\n\n_Architectural decisions, preferences, and notes._\n`, 'utf-8');
405
+ if (needsWrite(memFile)) {
406
+ fs.writeFileSync(memFile, `# ${projectName} — Project Memory
407
+
408
+ ## Project Files
409
+ ${fileList || '_No files generated yet_'}
410
+
411
+ ## Architecture
412
+ - Express.js server with middleware stack
413
+ - Vanilla HTML/CSS/JS frontend
414
+ - JSON file storage (no external DB)
415
+
416
+ ## Development Notes
417
+ _The AI agent updates this file as you build. Ask it to add features one by one._
418
+ _Example: "Add user authentication" or "Add a contact form"_
419
+
420
+ ## Completed Features
421
+ _None yet — start building!_
422
+ `, 'utf-8');
393
423
  }
424
+
394
425
  const skillsFile = path.join(dir, 'skills.md');
395
- if (!fs.existsSync(skillsFile)) {
396
- fs.writeFileSync(skillsFile, `# ${projectName} — Skills\n\n_Coding patterns, best practices, and conventions for this project._\n`, 'utf-8');
426
+ if (needsWrite(skillsFile)) {
427
+ fs.writeFileSync(skillsFile, `# ${projectName} — Skills & Conventions
428
+
429
+ ## Code Style
430
+ - Modern ES6+: const/let, arrow functions, async/await, template literals
431
+ - Express routes: router.get/post/put/delete with error handling
432
+ - CSS: custom properties (--primary, --bg), mobile-first, flexbox/grid
433
+ - HTML: semantic tags, accessible (aria-labels, focus styles)
434
+
435
+ ## File Structure
436
+ - server.js — main entry, middleware, routes
437
+ - public/ — static HTML/CSS/JS
438
+ - routes/ — Express route handlers
439
+ - middleware/ — auth, validation, error handling
440
+ - models/ — data models with JSON storage
441
+
442
+ ## Patterns
443
+ - Always validate input on server side
444
+ - Use try/catch for async operations
445
+ - Return proper HTTP status codes (200, 201, 400, 401, 404, 500)
446
+ - CSS variables for theming, transitions for hover states
447
+ `, 'utf-8');
397
448
  }
449
+
398
450
  const providerFile = path.join(dir, `${provider}.md`);
399
- if (!fs.existsSync(providerFile)) {
400
- fs.writeFileSync(providerFile, `# ${provider.toUpperCase()} — ${model || 'Default'}\n\n_Model-specific notes, prompt tips, and configuration._\n`, 'utf-8');
451
+ if (needsWrite(providerFile)) {
452
+ fs.writeFileSync(providerFile, `# ${provider.toUpperCase()} — ${model || 'Default'}
453
+
454
+ ## Project: ${projectName}
455
+
456
+ ## Instructions for Code Generation
457
+ - Generate COMPLETE files — never truncate
458
+ - Use external CSS/JS via link/script tags
459
+ - Every function must be fully implemented — no TODOs or placeholders
460
+ - Handle errors gracefully with try/catch
461
+ - Mobile-first responsive design
462
+ - Dark mode support via CSS custom properties
463
+
464
+ ## Available Tools
465
+ read, edit, write, rename, delete, check, lint, search, list, run, sandbox, diff
466
+
467
+ ## Workflow
468
+ 1. Read files before editing
469
+ 2. Make surgical edits (not full rewrites unless needed)
470
+ 3. Check/lint after modifications
471
+ 4. Restart sandbox to verify
472
+ `, 'utf-8');
401
473
  }
402
474
  },
403
475
 
package/src/server/ws.mjs CHANGED
@@ -55,63 +55,112 @@ export function setupWebSocket(server) {
55
55
  });
56
56
 
57
57
  wssTerminal.on('connection', (ws, req) => {
58
- // Parse project from query: /api/terminal?cwd=ProjectName
59
58
  const url = new URL(req.url || '', 'http://localhost');
60
59
  const cwdParam = url.searchParams.get('cwd') || '';
61
60
  let cwd;
62
61
  if (cwdParam && !cwdParam.includes('/') && !cwdParam.includes('\\')) {
63
- // Project name → resolve to webcraft dir
64
62
  cwd = path.join(NHA_DIR, 'webcraft', cwdParam);
65
63
  } else if (cwdParam) {
66
64
  cwd = cwdParam;
67
65
  } else {
68
66
  cwd = path.join(NHA_DIR, 'webcraft');
69
67
  }
70
- // Security: ensure cwd is under NHA_DIR or home
71
68
  const home = os.homedir();
72
69
  if (!cwd.startsWith(NHA_DIR) && !cwd.startsWith(home)) cwd = home;
73
70
  if (!fs.existsSync(cwd)) cwd = home;
74
71
 
75
- const shell = process.platform === 'win32' ? 'cmd.exe' : (process.env.SHELL || '/bin/sh');
76
- const shellArgs = process.platform === 'win32' ? [] : ['-i']; // interactive
72
+ // Command runner mode user sends commands, we exec them and return output
73
+ let currentCwd = cwd;
74
+ let cmdBuffer = '';
77
75
 
78
- const proc = spawn(shell, shellArgs, {
79
- cwd,
80
- env: { ...process.env, TERM: 'xterm-256color', LANG: 'en_US.UTF-8' },
81
- stdio: ['pipe', 'pipe', 'pipe'],
82
- });
83
-
84
- // Shell stdout → WS
85
- proc.stdout.on('data', (data) => {
86
- if (ws.readyState === 1) ws.send(data);
87
- });
76
+ const send = (text) => { if (ws.readyState === 1) ws.send(text); };
88
77
 
89
- // Shell stderr → WS
90
- proc.stderr.on('data', (data) => {
91
- if (ws.readyState === 1) ws.send(data);
92
- });
78
+ send(`\x1b[32mNHA Terminal\x1b[0m\r\n`);
79
+ send(`\x1b[90m${currentCwd}\x1b[0m\r\n`);
80
+ send(`\x1b[36m$ \x1b[0m`);
93
81
 
94
- // WS → Shell stdin
95
82
  ws.on('message', (data) => {
96
- if (proc.stdin.writable) proc.stdin.write(data);
97
- });
98
-
99
- // Cleanup
100
- proc.on('exit', () => {
101
- if (ws.readyState === 1) ws.send('\r\n[shell exited]\r\n');
102
- ws.close();
83
+ const char = data.toString();
84
+
85
+ // Handle special keys
86
+ if (char === '\r' || char === '\n') {
87
+ send('\r\n');
88
+ const cmd = cmdBuffer.trim();
89
+ cmdBuffer = '';
90
+
91
+ if (!cmd) {
92
+ send(`\x1b[36m$ \x1b[0m`);
93
+ return;
94
+ }
95
+
96
+ // Built-in: cd
97
+ if (cmd.startsWith('cd ')) {
98
+ const target = cmd.slice(3).trim().replace('~', home);
99
+ const newCwd = path.resolve(currentCwd, target);
100
+ if (fs.existsSync(newCwd) && fs.statSync(newCwd).isDirectory()) {
101
+ currentCwd = newCwd;
102
+ send(`\x1b[90m${currentCwd}\x1b[0m\r\n`);
103
+ } else {
104
+ send(`\x1b[31mcd: no such directory: ${target}\x1b[0m\r\n`);
105
+ }
106
+ send(`\x1b[36m$ \x1b[0m`);
107
+ return;
108
+ }
109
+
110
+ // Built-in: clear
111
+ if (cmd === 'clear' || cmd === 'cls') {
112
+ send('\x1b[2J\x1b[H');
113
+ send(`\x1b[90m${currentCwd}\x1b[0m\r\n`);
114
+ send(`\x1b[36m$ \x1b[0m`);
115
+ return;
116
+ }
117
+
118
+ // Execute command
119
+ const { exec } = require('child_process');
120
+ const child = exec(cmd, {
121
+ cwd: currentCwd,
122
+ timeout: 30_000,
123
+ env: { ...process.env, TERM: 'xterm-256color', NODE_ENV: 'development' },
124
+ maxBuffer: 1024 * 1024,
125
+ });
126
+
127
+ child.stdout?.on('data', (d) => {
128
+ send(d.toString().replace(/\n/g, '\r\n'));
129
+ });
130
+ child.stderr?.on('data', (d) => {
131
+ send(`\x1b[31m${d.toString().replace(/\n/g, '\r\n')}\x1b[0m`);
132
+ });
133
+ child.on('exit', (code) => {
134
+ if (code !== 0 && code !== null) {
135
+ send(`\x1b[90m[exit code: ${code}]\x1b[0m\r\n`);
136
+ }
137
+ send(`\x1b[90m${currentCwd}\x1b[0m\r\n`);
138
+ send(`\x1b[36m$ \x1b[0m`);
139
+ });
140
+ child.on('error', (err) => {
141
+ send(`\x1b[31m${err.message}\x1b[0m\r\n`);
142
+ send(`\x1b[36m$ \x1b[0m`);
143
+ });
144
+ } else if (char === '\x7f' || char === '\b') {
145
+ // Backspace
146
+ if (cmdBuffer.length > 0) {
147
+ cmdBuffer = cmdBuffer.slice(0, -1);
148
+ send('\b \b');
149
+ }
150
+ } else if (char === '\x03') {
151
+ // Ctrl+C
152
+ cmdBuffer = '';
153
+ send('^C\r\n');
154
+ send(`\x1b[36m$ \x1b[0m`);
155
+ } else if (char.charCodeAt(0) >= 32) {
156
+ // Normal character
157
+ cmdBuffer += char;
158
+ send(char);
159
+ }
103
160
  });
104
161
 
105
- ws.on('close', () => {
106
- try { proc.kill(); } catch {}
107
- });
108
-
109
- ws.on('error', () => {
110
- try { proc.kill(); } catch {}
111
- });
112
-
113
- // Welcome message
114
- ws.send(`\x1b[32mNHA Terminal\x1b[0m — ${cwd}\r\n`);
162
+ ws.on('close', () => {});
163
+ ws.on('error', () => {});
115
164
  });
116
165
  }
117
166