nothumanallowed 14.4.12 → 14.4.14
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/constants.mjs +1 -1
- package/src/server/routes/webcraft.mjs +79 -7
- package/src/server/ws.mjs +78 -38
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "14.4.
|
|
3
|
+
"version": "14.4.14",
|
|
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.
|
|
8
|
+
export const VERSION = '14.4.14';
|
|
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
|
|
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 (
|
|
392
|
-
fs.writeFileSync(memFile, `# ${projectName} — Project Memory
|
|
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 (
|
|
396
|
-
fs.writeFileSync(skillsFile, `# ${projectName} — Skills
|
|
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 (
|
|
400
|
-
fs.writeFileSync(providerFile, `# ${provider.toUpperCase()} — ${model || 'Default'}
|
|
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
|
@@ -8,7 +8,7 @@ import fs from 'fs';
|
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import os from 'os';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
|
-
import {
|
|
11
|
+
import { execFile } from 'child_process';
|
|
12
12
|
import { NHA_DIR } from '../constants.mjs';
|
|
13
13
|
|
|
14
14
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -55,63 +55,103 @@ 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
|
-
|
|
76
|
-
|
|
72
|
+
// Command runner mode — user sends commands, we exec them and return output
|
|
73
|
+
let currentCwd = cwd;
|
|
74
|
+
let cmdBuffer = '';
|
|
77
75
|
|
|
78
|
-
const
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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 — fully isolated from parent process stdio
|
|
119
|
+
const shell = process.platform === 'win32' ? 'cmd.exe' : '/bin/sh';
|
|
120
|
+
const shellArg = process.platform === 'win32' ? '/c' : '-c';
|
|
121
|
+
const child = execFile(shell, [shellArg, cmd], {
|
|
122
|
+
cwd: currentCwd,
|
|
123
|
+
timeout: 30_000,
|
|
124
|
+
env: { ...process.env, TERM: 'dumb', NODE_ENV: 'development' },
|
|
125
|
+
maxBuffer: 1024 * 1024,
|
|
126
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
127
|
+
}, (err, stdout, stderr) => {
|
|
128
|
+
if (stdout) send(stdout.replace(/\n/g, '\r\n'));
|
|
129
|
+
if (stderr) send(`\x1b[31m${stderr.replace(/\n/g, '\r\n')}\x1b[0m`);
|
|
130
|
+
if (err && err.killed) send(`\x1b[31m[timeout]\x1b[0m\r\n`);
|
|
131
|
+
else if (err && !stdout && !stderr) send(`\x1b[31m${err.message}\x1b[0m\r\n`);
|
|
132
|
+
send(`\x1b[90m${currentCwd}\x1b[0m\r\n`);
|
|
133
|
+
send(`\x1b[36m$ \x1b[0m`);
|
|
134
|
+
});
|
|
135
|
+
} else if (char === '\x7f' || char === '\b') {
|
|
136
|
+
// Backspace
|
|
137
|
+
if (cmdBuffer.length > 0) {
|
|
138
|
+
cmdBuffer = cmdBuffer.slice(0, -1);
|
|
139
|
+
send('\b \b');
|
|
140
|
+
}
|
|
141
|
+
} else if (char === '\x03') {
|
|
142
|
+
// Ctrl+C
|
|
143
|
+
cmdBuffer = '';
|
|
144
|
+
send('^C\r\n');
|
|
145
|
+
send(`\x1b[36m$ \x1b[0m`);
|
|
146
|
+
} else if (char.charCodeAt(0) >= 32) {
|
|
147
|
+
// Normal character
|
|
148
|
+
cmdBuffer += char;
|
|
149
|
+
send(char);
|
|
150
|
+
}
|
|
103
151
|
});
|
|
104
152
|
|
|
105
|
-
ws.on('close', () => {
|
|
106
|
-
|
|
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`);
|
|
153
|
+
ws.on('close', () => {});
|
|
154
|
+
ws.on('error', () => {});
|
|
115
155
|
});
|
|
116
156
|
}
|
|
117
157
|
|