nothumanallowed 13.2.81 → 13.2.83

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.2.81",
3
+ "version": "13.2.83",
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": {
@@ -2958,12 +2958,30 @@ ${rawText.slice(0, 18000)}`;
2958
2958
  // If there is document context from a previous step, ask the LLM to derive
2959
2959
  // the optimal search queries. This is generic and works for any document/task.
2960
2960
  let searchQueries = [stepPrompt.slice(0, 120)];
2961
- if (context && context.length > 50) {
2961
+ // Only use LLM query generation when context is a PDF/document (not previous agent text output).
2962
+ // When context is email/github output from a prior step, ignore it — use task + stepPrompt directly.
2963
+ const contextIsPdf = context && context.length > 50 && context.startsWith('## ATTACHED PDF');
2964
+ if (contextIsPdf) {
2962
2965
  sendToken('[Building search queries from document...] ');
2963
2966
  try {
2964
- // Document context goes in system prompt — SENTINEL only scans user message
2965
2967
  const queryPlanSys = `You are a search query generator. Given a document summary and a user task, output a JSON array of 1-3 concise web search queries (strings, max 80 chars each) that will find the best results. Output ONLY the JSON array, no explanation.\n\nDocument content:\n${context.slice(0, 3000)}`;
2966
- const queryPlanUser = `User task: "${task.slice(0, 200)}". Generate search queries. If task asks for similar/alternative products use technical specs. If it asks where to buy include vendor queries. Output: ["query1","query2",...]`;
2968
+ const queryPlanUser = `User task: "${task.slice(0, 200)}". Generate search queries. If task asks for similar/alternative products use technical specs. Output: ["query1","query2",...]`;
2969
+ const planConfig2 = Object.assign({}, config, { thinking: 'off' });
2970
+ const queryRaw = await withTimeout(callLLM(planConfig2, queryPlanSys, queryPlanUser, { max_tokens: 200 }), 15000);
2971
+ const jsonMatch = queryRaw.match(/\[[\s\S]*?\]/);
2972
+ if (jsonMatch) {
2973
+ const parsed = JSON.parse(jsonMatch[0]);
2974
+ if (Array.isArray(parsed) && parsed.length > 0) {
2975
+ searchQueries = parsed.filter(q => typeof q === 'string' && q.length > 2).slice(0, 3);
2976
+ }
2977
+ }
2978
+ } catch {}
2979
+ } else {
2980
+ // No PDF — derive queries from task + stepPrompt using LLM for better queries
2981
+ sendToken('[Building search queries...] ');
2982
+ try {
2983
+ const queryPlanSys = `You are a search query generator. Given a user task and a search instruction, output a JSON array of 2-3 concise web search queries (strings, max 80 chars each). Focus on the specific topics in the task. Output ONLY the JSON array, no explanation.`;
2984
+ const queryPlanUser = `Task: "${task.slice(0, 300)}"\nSearch instruction: "${stepPrompt.slice(0, 200)}"\nOutput: ["query1","query2","query3"]`;
2967
2985
  const planConfig2 = Object.assign({}, config, { thinking: 'off' });
2968
2986
  const queryRaw = await withTimeout(callLLM(planConfig2, queryPlanSys, queryPlanUser, { max_tokens: 200 }), 15000);
2969
2987
  const jsonMatch = queryRaw.match(/\[[\s\S]*?\]/);
@@ -2974,8 +2992,8 @@ ${rawText.slice(0, 18000)}`;
2974
2992
  }
2975
2993
  }
2976
2994
  } catch {}
2977
- sendToken(`[Queries: ${searchQueries.map(q => '"' + q + '"').join(', ')}] `);
2978
2995
  }
2996
+ sendToken(`[Queries: ${searchQueries.map(q => '"' + q + '"').join(', ')}] `);
2979
2997
 
2980
2998
  // Run all queries sequentially, accumulate results
2981
2999
  for (let qi = 0; qi < searchQueries.length; qi++) {
@@ -3012,24 +3030,64 @@ ${rawText.slice(0, 18000)}`;
3012
3030
  toolData = 'GitHub token not configured. Run: nha config set github-token YOUR_PAT';
3013
3031
  } else {
3014
3032
  const parts = [];
3015
- // Notifications (always available)
3016
- try {
3017
- const notifs = await withTimeout(gh.listNotifications(config, 15), 'GitHubAgent-notifs');
3018
- if (notifs) parts.push('## GitHub Notifications\n' + notifs);
3019
- } catch (e) { /* skip */ }
3020
- // Issues/PRs on configured repo if available
3021
- const repo = config.github?.defaultRepo || '';
3022
- if (repo) {
3033
+ // Extract repo from prompt or task (e.g. "owner/repo" pattern)
3034
+ const repoMatch = (stepPrompt + ' ' + task).match(/([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)/);
3035
+ const targetRepo = repoMatch ? repoMatch[1].replace(/[`'"]/g, '') : (config.github?.defaultRepo || '');
3036
+
3037
+ if (targetRepo) {
3038
+ sendToken(`[Analyzing ${targetRepo}...] `);
3039
+ // Repo metadata
3040
+ try {
3041
+ const info = await withTimeout(gh.getRepoInfo(config, targetRepo), 20000);
3042
+ parts.push(`## Repository: ${info.full_name}\n` +
3043
+ `- Description: ${info.description || 'none'}\n` +
3044
+ `- Stars: ${info.stars} | Forks: ${info.forks} | Watchers: ${info.watchers}\n` +
3045
+ `- Open issues: ${info.open_issues}\n` +
3046
+ `- Primary language: ${info.language}\n` +
3047
+ `- Topics: ${info.topics}\n` +
3048
+ `- License: ${info.license}\n` +
3049
+ `- Last push: ${info.pushed_at} | Created: ${info.created_at}\n` +
3050
+ `- Homepage: ${info.homepage || 'none'}\n` +
3051
+ `- Archived: ${info.archived}`);
3052
+ } catch (e) { parts.push(`## Repository ${targetRepo}\nCould not fetch repo info: ${e.message}`); }
3053
+ // Languages
3054
+ try {
3055
+ const langs = await withTimeout(gh.getRepoLanguages(config, targetRepo), 10000);
3056
+ if (langs) parts.push('## Languages\n' + langs);
3057
+ } catch {}
3058
+ // README
3059
+ try {
3060
+ const readme = await withTimeout(gh.getReadme(config, targetRepo), 15000);
3061
+ if (readme) parts.push('## README\n' + readme.slice(0, 3000));
3062
+ } catch {}
3063
+ // Recent commits
3064
+ try {
3065
+ const commits = await withTimeout(gh.getRecentCommits(config, targetRepo, 10), 15000);
3066
+ if (commits) parts.push('## Recent Commits\n' + commits);
3067
+ } catch {}
3068
+ // Open issues
3023
3069
  try {
3024
- const issues = await withTimeout(gh.listIssues(config, repo, 'open', 10), 'GitHubAgent-issues');
3025
- if (issues) parts.push('## Open Issues (' + repo + ')\n' + issues);
3026
- } catch (e) { /* skip */ }
3070
+ const issues = await withTimeout(gh.listIssues(config, targetRepo, 'open', 10), 15000);
3071
+ if (issues) parts.push('## Open Issues\n' + issues);
3072
+ } catch {}
3073
+ // Open PRs
3027
3074
  try {
3028
- const prs = await withTimeout(gh.listPRs(config, repo, 'open', 10), 'GitHubAgent-prs');
3029
- if (prs) parts.push('## Open PRs (' + repo + ')\n' + prs);
3030
- } catch (e) { /* skip */ }
3075
+ const prs = await withTimeout(gh.listPRs(config, targetRepo, 'open', 10), 15000);
3076
+ if (prs) parts.push('## Open Pull Requests\n' + prs);
3077
+ } catch {}
3078
+ // Contributors
3079
+ try {
3080
+ const contributors = await withTimeout(gh.getContributors(config, targetRepo, 10), 10000);
3081
+ if (contributors) parts.push('## Contributors\n' + contributors);
3082
+ } catch {}
3083
+ } else {
3084
+ // No specific repo — read notifications + user repos
3085
+ try {
3086
+ const notifs = await withTimeout(gh.listNotifications(config, 15), 15000);
3087
+ if (notifs) parts.push('## GitHub Notifications\n' + notifs);
3088
+ } catch {}
3031
3089
  }
3032
- toolData = parts.length > 0 ? parts.join('\n\n') : 'No GitHub data available.';
3090
+ toolData = parts.length > 0 ? parts.join('\n\n') : 'No GitHub data could be retrieved.';
3033
3091
  }
3034
3092
  } catch (e) { toolData = `GitHub read failed: ${e.message}`; }
3035
3093
 
@@ -3188,14 +3246,15 @@ ${task}
3188
3246
  CRITICAL RULES:
3189
3247
  - Do NOT output JSON, tool calls, function calls, or code blocks
3190
3248
  - NEVER invent, fabricate, or hallucinate data, events, emails, meetings, or news
3191
- - ONLY use the EXACT data provided in the DATA sections below if no data is provided, say so clearly
3192
- - Do NOT add fictional examples, placeholder content, or generic suggestions not grounded in the data
3249
+ - ONLY use data from the DATA sections that is RELEVANT to your specific domain and the WORKFLOW GOAL
3250
+ - If the previous agents' output contains irrelevant personal data (e.g. unrelated emails, purchases, subscriptions) — IGNORE it entirely
3251
+ - ONLY reference data that directly relates to the subject of the WORKFLOW GOAL
3252
+ - If genuinely no relevant data exists for your domain, say so clearly — do NOT invent analysis
3193
3253
  - Write in plain prose, structured with markdown headers (##) and bullet points (-)
3194
3254
  - Be thorough and specific — this is for an executive briefing based on REAL data only
3195
- - Always keep the OVERALL WORKFLOW GOAL in mind — apply your analysis specifically to the subject mentioned
3196
3255
 
3197
3256
  ${attachmentText ? `## ATTACHED FILE CONTENT:\n${attachmentText}\n` : ''}${toolData ? `## LIVE DATA FROM TOOLS:\n${toolData}\n` : '## LIVE DATA: No tool data was fetched for this step.\n'}
3198
- ${context ? `## OUTPUT FROM PREVIOUS AGENTS:\n${context}\n` : ''}`;
3257
+ ${context ? `## OUTPUT FROM PREVIOUS AGENTS (use only what is RELEVANT to the workflow goal):\n${context}\n` : ''}`;
3199
3258
  userMsg = hasRealData
3200
3259
  ? `Based ONLY on the real data above, complete this task specifically for the subject in the WORKFLOW GOAL: ${stepPrompt}`
3201
3260
  : `No real data is available for "${task}". State this clearly and explain what data would be needed to complete: ${stepPrompt}`;
@@ -3299,16 +3358,117 @@ ${context ? `## OUTPUT FROM PREVIOUS AGENTS:\n${context}\n` : ''}`;
3299
3358
  const stopWords = new Set(['di','la','il','lo','le','gli','un','una','dei','del','della','per','che','con','su','in','e','a','da','è','come','analizza','analisi','ricerca','crea','genera','fai','fammi','dammi','the','of','for','and','a','an','in','with','on','about','analyze','analysis','research','create','generate','make','find','search']);
3300
3359
  const titleWords = task.replace(/[.,;:!?]/g,'').split(/\s+/).filter(w => w.length > 2 && !stopWords.has(w.toLowerCase())).slice(0, 6);
3301
3360
  const reportTitle = titleWords.length > 0 ? titleWords.map(w => w.charAt(0).toUpperCase()+w.slice(1)).join(' ') : 'Studio Report';
3302
- // Fallback: if LLM output is empty or has no HTML tags, build body from context using markdown→HTML conversion
3361
+ // Convert markdown to NHA-classed HTML handles the case where Liara/Qwen3
3362
+ // returns markdown instead of HTML despite instructions.
3363
+ const mdToNhaHtml = (md) => {
3364
+ const esc = s => s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
3365
+ // inline: bold, italic, inline-code, links
3366
+ const inl = s => s
3367
+ .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
3368
+ .replace(/\*([^*]+)\*/g, '<em>$1</em>')
3369
+ .replace(/`([^`]+)`/g, '<code style="background:#1c1c28;padding:1px 5px;border-radius:3px;font-size:12px">$1</code>')
3370
+ .replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
3371
+
3372
+ const lines = md.split('\n');
3373
+ let out = [];
3374
+ let i = 0;
3375
+ let currentSection = null; // accumulates section content
3376
+
3377
+ const flushSection = () => {
3378
+ if (currentSection) { out.push(currentSection + '</div>'); currentSection = null; }
3379
+ };
3380
+
3381
+ while (i < lines.length) {
3382
+ const l = lines[i];
3383
+ // H1 — treat as sub-header inside a new section
3384
+ if (/^# /.test(l)) {
3385
+ flushSection();
3386
+ const title = esc(l.replace(/^# /, '').replace(/\*\*/g,'').replace(/\*/g,''));
3387
+ currentSection = `<div class="section"><div class="section-title">${title}</div>`;
3388
+ i++; continue;
3389
+ }
3390
+ // H2 / H3 — new section
3391
+ if (/^#{2,3} /.test(l)) {
3392
+ flushSection();
3393
+ const title = esc(l.replace(/^#{2,3} /, '').replace(/\*\*/g,'').replace(/\*/g,''));
3394
+ currentSection = `<div class="section"><div class="section-title">${title}</div>`;
3395
+ i++; continue;
3396
+ }
3397
+ // H4 — sub-heading inside current section
3398
+ if (/^#### /.test(l)) {
3399
+ const h = esc(l.replace(/^#### /, '').replace(/\*\*/g,'').replace(/\*/g,''));
3400
+ const frag = `<h3>${h}</h3>`;
3401
+ if (currentSection) currentSection += frag; else out.push(frag);
3402
+ i++; continue;
3403
+ }
3404
+ // Horizontal rule — divider
3405
+ if (/^---+$/.test(l.trim())) {
3406
+ const frag = '<div class="divider"></div>';
3407
+ if (currentSection) currentSection += frag; else out.push(frag);
3408
+ i++; continue;
3409
+ }
3410
+ // Table row
3411
+ if (l.trim().startsWith('|') && l.includes('|', 1)) {
3412
+ // Collect all table lines
3413
+ const tableLines = [];
3414
+ while (i < lines.length && lines[i].trim().startsWith('|')) {
3415
+ if (!/^\|[\s:|-]+\|$/.test(lines[i].trim())) tableLines.push(lines[i]);
3416
+ i++;
3417
+ }
3418
+ if (tableLines.length > 0) {
3419
+ const isHeader = tableLines.length > 1;
3420
+ let tHtml = '<table style="width:100%;border-collapse:collapse;margin:10px 0;font-size:12px">';
3421
+ tableLines.forEach((tl, ti) => {
3422
+ const cells = tl.split('|').slice(1,-1).map(c => c.trim());
3423
+ const tag = (ti === 0 && isHeader) ? 'th' : 'td';
3424
+ const bg = ti === 0 && isHeader ? 'background:#1c1c28;color:#a5b4fc;font-weight:700' : (ti % 2 === 0 ? 'background:#15151f' : 'background:#1a1a28');
3425
+ tHtml += '<tr>' + cells.map(c => `<${tag} style="${bg};padding:6px 10px;border:1px solid #2a2a38;text-align:left">${inl(esc(c))}</${tag}>`).join('') + '</tr>';
3426
+ });
3427
+ tHtml += '</table>';
3428
+ if (currentSection) currentSection += tHtml; else out.push(tHtml);
3429
+ }
3430
+ continue;
3431
+ }
3432
+ // Unordered list block
3433
+ if (/^(\s*[-*+] )/.test(l)) {
3434
+ const items = [];
3435
+ while (i < lines.length && /^(\s*[-*+] )/.test(lines[i])) {
3436
+ items.push(inl(esc(lines[i].replace(/^\s*[-*+] /, ''))));
3437
+ i++;
3438
+ }
3439
+ const frag = '<ul>' + items.map(it => `<li>${it}</li>`).join('') + '</ul>';
3440
+ if (currentSection) currentSection += frag; else out.push(frag);
3441
+ continue;
3442
+ }
3443
+ // Ordered list block
3444
+ if (/^\d+\. /.test(l)) {
3445
+ const items = [];
3446
+ while (i < lines.length && /^\d+\. /.test(lines[i])) {
3447
+ items.push(inl(esc(lines[i].replace(/^\d+\. /, ''))));
3448
+ i++;
3449
+ }
3450
+ const frag = '<ol>' + items.map(it => `<li>${it}</li>`).join('') + '</ol>';
3451
+ if (currentSection) currentSection += frag; else out.push(frag);
3452
+ continue;
3453
+ }
3454
+ // Blank line — skip
3455
+ if (!l.trim()) { i++; continue; }
3456
+ // Regular paragraph
3457
+ const frag = `<p>${inl(esc(l))}</p>`;
3458
+ if (currentSection) currentSection += frag; else out.push(frag);
3459
+ i++;
3460
+ }
3461
+ flushSection();
3462
+ return out.join('');
3463
+ };
3464
+
3465
+ // If LLM output has no HTML tags → it's markdown → convert
3303
3466
  if (!bodyHtml || !bodyHtml.includes('<')) {
3304
- const sections = context.split(/\n#{1,3} |(?=\n\n)/).filter(s => s.trim()).slice(0, 12);
3467
+ const source = bodyHtml || context;
3468
+ const converted = mdToNhaHtml(source);
3469
+ const agentNames = (stepDef && Array.isArray(stepDef)) ? '' : '';
3305
3470
  bodyHtml = `<div class="header"><h1>${reportTitle.replace(/</g,'&lt;')}</h1><p>NHA Studio Report \u00b7 ${today}</p><div class="meta"><span>${today}</span></div></div>` +
3306
- sections.map(s => {
3307
- const lines = s.replace(/\*\*/g,'').replace(/\*/g,'').trim().split('\n').filter(Boolean);
3308
- const stitle = lines[0] || '';
3309
- const body = lines.slice(1).map(l => `<p>${l.replace(/</g,'&lt;')}</p>`).join('');
3310
- return `<div class="section"><div class="section-title">${stitle.replace(/</g,'&lt;')}</div>${body}</div>`;
3311
- }).join('') +
3471
+ converted +
3312
3472
  `<div class="footer">NHA Studio \u00b7 ${today}</div>`;
3313
3473
  } else {
3314
3474
  // Replace the h1 inside existing header div if the model included the full prompt as title
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 = '13.2.81';
8
+ export const VERSION = '13.2.83';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -185,6 +185,85 @@ export async function listUserRepos(config, maxResults = 30) {
185
185
  };
186
186
  }
187
187
 
188
+ /**
189
+ * Get repository metadata: description, stars, forks, language, topics, license, last push.
190
+ */
191
+ export async function getRepoInfo(config, repo) {
192
+ const data = await ghFetch(config, `/repos/${repo}`);
193
+ return {
194
+ full_name: data.full_name,
195
+ description: data.description || '',
196
+ stars: data.stargazers_count || 0,
197
+ forks: data.forks_count || 0,
198
+ watchers: data.watchers_count || 0,
199
+ open_issues: data.open_issues_count || 0,
200
+ language: data.language || 'N/A',
201
+ topics: (data.topics || []).join(', ') || 'none',
202
+ license: data.license?.name || 'none',
203
+ default_branch: data.default_branch || 'main',
204
+ pushed_at: data.pushed_at ? data.pushed_at.slice(0, 10) : 'unknown',
205
+ created_at: data.created_at ? data.created_at.slice(0, 10) : 'unknown',
206
+ homepage: data.homepage || '',
207
+ private: data.private,
208
+ archived: data.archived,
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Get programming languages breakdown for a repo.
214
+ */
215
+ export async function getRepoLanguages(config, repo) {
216
+ const data = await ghFetch(config, `/repos/${repo}/languages`);
217
+ const total = Object.values(data).reduce((a, b) => a + b, 0) || 1;
218
+ return Object.entries(data)
219
+ .map(([lang, bytes]) => `${lang}: ${((bytes / total) * 100).toFixed(1)}%`)
220
+ .join(', ');
221
+ }
222
+
223
+ /**
224
+ * Get recent commits for a repo (last N).
225
+ */
226
+ export async function getRecentCommits(config, repo, maxResults = 10) {
227
+ const data = await ghFetch(config, `/repos/${repo}/commits?per_page=${maxResults}`);
228
+ if (!Array.isArray(data) || data.length === 0) return `No commits found in ${repo}.`;
229
+ return data.map((c, i) => {
230
+ const sha = c.sha ? c.sha.slice(0, 7) : '?';
231
+ const msg = (c.commit?.message || '').split('\n')[0].slice(0, 100);
232
+ const author = c.commit?.author?.name || c.author?.login || 'unknown';
233
+ const date = c.commit?.author?.date ? c.commit.author.date.slice(0, 10) : '';
234
+ return `${i + 1}. [${sha}] ${date} — ${author}: ${msg}`;
235
+ }).join('\n');
236
+ }
237
+
238
+ /**
239
+ * Get README content for a repo.
240
+ */
241
+ export async function getReadme(config, repo) {
242
+ try {
243
+ const data = await ghFetch(config, `/repos/${repo}/readme`);
244
+ if (data.content) {
245
+ const text = Buffer.from(data.content, 'base64').toString('utf-8');
246
+ return text.slice(0, 6000); // cap at 6KB
247
+ }
248
+ return '';
249
+ } catch {
250
+ return '';
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Get contributors for a repo.
256
+ */
257
+ export async function getContributors(config, repo, maxResults = 10) {
258
+ try {
259
+ const data = await ghFetch(config, `/repos/${repo}/contributors?per_page=${maxResults}`);
260
+ if (!Array.isArray(data) || data.length === 0) return 'No contributors data.';
261
+ return data.map((c, i) => `${i + 1}. @${c.login} — ${c.contributions} commits`).join('\n');
262
+ } catch {
263
+ return 'Contributors data unavailable.';
264
+ }
265
+ }
266
+
188
267
  export async function markNotificationsRead(config) {
189
268
  await ghFetch(config, '/notifications', {
190
269
  method: 'PUT',