nothumanallowed 14.2.5 → 14.2.7

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.2.5",
3
+ "version": "14.2.7",
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/cli.mjs CHANGED
@@ -200,14 +200,34 @@ export async function main(argv) {
200
200
  return cmdHelp();
201
201
 
202
202
  default: {
203
- // Check if a plugin handles this command before falling through to Legion
203
+ // Check if a plugin handles this command before falling through
204
204
  const pluginMatch = await findPluginForCommand(cmd);
205
205
  if (pluginMatch && pluginMatch.plugin.run) {
206
206
  const { cmdPlugin: runPlugin } = await import('./commands/plugin.mjs');
207
207
  return runPlugin(['run', cmd, ...args]);
208
208
  }
209
- // Try as Legion command passthrough
210
- return spawnCore('legion', [cmd, ...args]);
209
+ // Try as Legion command passthrough (only if legion is installed)
210
+ try {
211
+ const { LEGION_FILE } = await import('./constants.mjs');
212
+ const { existsSync } = await import('fs');
213
+ if (existsSync(LEGION_FILE)) {
214
+ return spawnCore('legion', [cmd, ...args]);
215
+ }
216
+ } catch {}
217
+ // Unknown command — show helpful error
218
+ fail(`Unknown command: ${cmd}`);
219
+ console.log('');
220
+ info('Common commands:');
221
+ console.log(' nha chat Chat with AI');
222
+ console.log(' nha ui Open web UI');
223
+ console.log(' nha start Start ops daemon (Telegram/Discord)');
224
+ console.log(' nha stop Stop ops daemon');
225
+ console.log(' nha restart Restart ops daemon');
226
+ console.log(' nha status Check daemon status');
227
+ console.log(' nha update Update agents + npm package');
228
+ console.log(' nha config set <k> <v> Set configuration');
229
+ console.log(' nha help Full command list');
230
+ return;
211
231
  }
212
232
  }
213
233
  }
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.2.5';
8
+ export const VERSION = '14.2.7';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -848,20 +848,17 @@ function isFileTruncated(content, filename) {
848
848
  const trimmed = content.trimEnd();
849
849
  const ext = filename.split('.').pop()?.toLowerCase();
850
850
 
851
- // Common truncation signs: ends mid-statement without closing bracket/brace
852
851
  if (ext === 'js' || ext === 'mjs' || ext === 'ts') {
853
- // Balanced braces check (fast approximation)
854
852
  const open = (trimmed.match(/\{/g) || []).length;
855
853
  const close = (trimmed.match(/\}/g) || []).length;
856
- if (open > close + 2) return true;
857
- // Last meaningful line should not end with an operator or comma
854
+ if (open > close) return true; // ANY unbalanced brace = truncated
858
855
  const lastLine = trimmed.split('\n').pop()?.trim() ?? '';
859
856
  if (/[,({=+\-*/<>|&]$/.test(lastLine)) return true;
860
857
  }
861
858
  if (ext === 'css') {
862
859
  const open = (trimmed.match(/\{/g) || []).length;
863
860
  const close = (trimmed.match(/\}/g) || []).length;
864
- if (open > close + 1) return true;
861
+ if (open > close) return true;
865
862
  }
866
863
  if (ext === 'html') {
867
864
  if (!trimmed.includes('</html>') && !trimmed.includes('</body>')) return true;
@@ -872,6 +869,69 @@ function isFileTruncated(content, filename) {
872
869
  return false;
873
870
  }
874
871
 
872
+ /**
873
+ * Deterministic repair of common truncation artifacts.
874
+ * Adds missing closing braces, tags, etc. WITHOUT calling the LLM.
875
+ */
876
+ function repairTruncation(content, filename) {
877
+ if (!content) return content;
878
+ const ext = filename.split('.').pop()?.toLowerCase();
879
+ let result = content.trimEnd();
880
+
881
+ if (ext === 'js' || ext === 'mjs' || ext === 'ts') {
882
+ // Balance curly braces
883
+ const open = (result.match(/\{/g) || []).length;
884
+ const close = (result.match(/\}/g) || []).length;
885
+ const missing = open - close;
886
+ if (missing > 0 && missing <= 10) {
887
+ result += '\n' + '}\n'.repeat(missing);
888
+ }
889
+ // Balance parentheses
890
+ const openP = (result.match(/\(/g) || []).length;
891
+ const closeP = (result.match(/\)/g) || []).length;
892
+ const missingP = openP - closeP;
893
+ if (missingP > 0 && missingP <= 5) {
894
+ // Find last line and append
895
+ const lines = result.split('\n');
896
+ const lastIdx = lines.length - 1;
897
+ lines[lastIdx] = lines[lastIdx] + ')'.repeat(missingP) + ';';
898
+ result = lines.join('\n');
899
+ }
900
+ }
901
+
902
+ if (ext === 'css') {
903
+ const open = (result.match(/\{/g) || []).length;
904
+ const close = (result.match(/\}/g) || []).length;
905
+ const missing = open - close;
906
+ if (missing > 0 && missing <= 10) {
907
+ result += '\n' + '}\n'.repeat(missing);
908
+ }
909
+ }
910
+
911
+ if (ext === 'html' || ext === 'htm') {
912
+ if (!result.includes('</body>')) result += '\n</body>';
913
+ if (!result.includes('</html>')) result += '\n</html>';
914
+ }
915
+
916
+ if (ext === 'json') {
917
+ // Try to fix unclosed JSON
918
+ try { JSON.parse(result); } catch {
919
+ // Count brackets
920
+ const openB = (result.match(/\[/g) || []).length;
921
+ const closeB = (result.match(/\]/g) || []).length;
922
+ const openC = (result.match(/\{/g) || []).length;
923
+ const closeC = (result.match(/\}/g) || []).length;
924
+ // Remove trailing comma if present
925
+ result = result.replace(/,\s*$/, '');
926
+ // Add missing closers
927
+ if (openB > closeB) result += '\n' + ']'.repeat(openB - closeB);
928
+ if (openC > closeC) result += '\n' + '}'.repeat(openC - closeC);
929
+ }
930
+ }
931
+
932
+ return result;
933
+ }
934
+
875
935
  async function runGenerate(config, projectName, description, blocks, authFields, emit) {
876
936
  const blocksDesc = Object.entries(blocks)
877
937
  .filter(([, enabled]) => enabled)
@@ -1058,7 +1118,8 @@ Continue from here:`;
1058
1118
  }
1059
1119
  }
1060
1120
 
1061
- fileContent = rawOutput;
1121
+ // Deterministic repair of truncation artifacts (missing braces, tags)
1122
+ fileContent = repairTruncation(rawOutput, fileSpec.name);
1062
1123
  syntaxError = null;
1063
1124
 
1064
1125
  const fileTokensOut = countTokens(fileContent);
@@ -1109,10 +1170,27 @@ Continue from here:`;
1109
1170
  return isFileTruncated(f.content, f.name);
1110
1171
  });
1111
1172
 
1112
- if (brokenFiles.length > 0 && brokenFiles.length <= 10) {
1173
+ if (brokenFiles.length > 0) {
1113
1174
  emit({ type: 'phase', phase: 'autofix', msg: `Post-generation fix: ${brokenFiles.length} file(s) need repair...` });
1114
1175
  for (const broken of brokenFiles) {
1115
1176
  try {
1177
+ // Step 1: Try deterministic repair first (fast, no LLM call)
1178
+ const deterministicFix = repairTruncation(broken.content, broken.name);
1179
+ let isFixed = false;
1180
+ if (deterministicFix !== broken.content) {
1181
+ // Verify the fix actually resolved the issue
1182
+ if (!isFileTruncated(deterministicFix, broken.name)) {
1183
+ emit({ type: 'status', msg: `Auto-fixed ${broken.name} (deterministic repair)` });
1184
+ broken.content = deterministicFix;
1185
+ const abs = path.join(projectDir, broken.name);
1186
+ fs.writeFileSync(abs, deterministicFix, 'utf-8');
1187
+ isFixed = true;
1188
+ }
1189
+ }
1190
+ if (isFixed) continue;
1191
+
1192
+ // Step 2: If deterministic repair wasn't enough, regenerate with LLM
1193
+ if (brokenFiles.length > 15) continue; // don't LLM-regenerate too many files
1116
1194
  emit({ type: 'status', msg: `Regenerating ${broken.name}...` });
1117
1195
  let fixedContent = '';
1118
1196
  const fixPrompt = `Regenerate this file COMPLETELY. It was truncated or has errors.\n\nFile: ${broken.name}\nProject: ${projectName}\nDescription: ${description}\nFull file list: ${allFileNames}\n\nOutput the COMPLETE file content only, no explanation.`;
@@ -1121,7 +1199,8 @@ Continue from here:`;
1121
1199
  }, { max_tokens: 16384 });
1122
1200
  fixedContent = fixedContent
1123
1201
  .replace(/^```[\w]*\n/, '').replace(/\n```$/, '').trim();
1124
- if (fixedContent.length > broken.content.length) {
1202
+ fixedContent = repairTruncation(fixedContent, broken.name); // repair the regenerated content too
1203
+ if (fixedContent.length > 50) {
1125
1204
  broken.content = fixedContent;
1126
1205
  const abs = path.join(projectDir, broken.name);
1127
1206
  fs.writeFileSync(abs, fixedContent, 'utf-8');
@@ -161,8 +161,10 @@ TOOLS:
161
161
  NEVER use calendar_find with month names — use calendar_month instead.
162
162
 
163
163
  14. calendar_create(summary: string, start: string, end: string, attendees?: string[], description?: string)
164
- Create a calendar event. start/end are ISO 8601 datetime strings.
165
- ALWAYS confirm with the user before creating.
164
+ Create a NEW calendar event. start/end are ISO 8601 datetime strings.
165
+ Use this when the user says: "inserisci", "aggiungi", "crea", "metti", "fissa", "prenota", "add", "create", "schedule", "book".
166
+ IMPORTANT: When user says "inserisci appuntamento" or "crea evento" → use calendar_create, NOT calendar_find.
167
+ Extract the summary, date, and time from the user message. If end time is not specified, default to 1 hour after start.
166
168
 
167
169
  15. calendar_move(eventId: string, newStart: string, newEnd: string)
168
170
  Reschedule an event. ALWAYS confirm before moving.
@@ -625,7 +627,8 @@ RULES:
625
627
  - For write/send/delete operations (gmail_send, gmail_reply, gmail_delete, calendar_create, calendar_move, calendar_update, contact_delete, task_done, notify_remind, file_write), DESCRIBE what you're about to do and include the JSON block so the system can ask the user for confirmation.
626
628
  - For schedule_meeting and schedule_draft_email, execute immediately — these are read operations that suggest slots.
627
629
  - When presenting email results, show From, Subject, Date, and a brief snippet. Never dump raw JSON.
628
- - When presenting calendar events, show Time, Title, Location/Link. Format times in a human-readable way.
630
+ - When presenting calendar events, show Time, Title, Location/Link. Format times in a human-readable way. NEVER show raw eventId to the user — it's internal.
631
+ - When confirming a created event, say something like "Ho creato l'appuntamento 'X' per il giorno Y alle ore Z." — natural, human, no IDs.
629
632
  - When presenting tasks, show ID, Description, Priority, Status.
630
633
  - When presenting slot proposals, show day, date, time range, and travel info clearly.
631
634
  - If you need multiple actions in sequence (e.g., read an email then reply), do them ONE AT A TIME — wait for the result of each before proceeding.