nothumanallowed 8.8.2 → 8.9.0

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": "8.8.2",
3
+ "version": "8.9.0",
4
4
  "description": "NotHumanAllowed — 38 AI agents + unified productivity suite. Gmail, Calendar, Drive, Contacts, Tasks, GitHub, Notion, Slack, voice chat, smart scheduler. Zero-dependency CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -764,6 +764,87 @@ export async function cmdUI(args) {
764
764
  if (memCtx) enrichedSystemPrompt = basePrompt + memCtx;
765
765
  } catch { /* memory unavailable */ }
766
766
 
767
+ // Handle image attachment — vision API
768
+ if (body.imageBase64 && body.imageMimeType) {
769
+ try {
770
+ const provider = config.llm.provider || 'anthropic';
771
+ const apiKey = config.llm.apiKey;
772
+ const model = config.llm.model;
773
+ const imagePrompt = body.message || 'Describe this image in detail. Extract any text or important information.';
774
+ let visionResponse = '';
775
+
776
+ if (provider === 'anthropic') {
777
+ const r = await fetch('https://api.anthropic.com/v1/messages', {
778
+ method: 'POST',
779
+ headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' },
780
+ body: JSON.stringify({
781
+ model: model || 'claude-sonnet-4-20250514', max_tokens: 4096, system: enrichedSystemPrompt,
782
+ messages: [{ role: 'user', content: [
783
+ { type: 'image', source: { type: 'base64', media_type: body.imageMimeType, data: body.imageBase64 } },
784
+ { type: 'text', text: imagePrompt },
785
+ ]}],
786
+ }),
787
+ });
788
+ if (!r.ok) throw new Error(`Anthropic ${r.status}`);
789
+ const d = await r.json();
790
+ visionResponse = d.content?.[0]?.text || '';
791
+ } else if (provider === 'openai') {
792
+ const r = await fetch('https://api.openai.com/v1/chat/completions', {
793
+ method: 'POST',
794
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
795
+ body: JSON.stringify({
796
+ model: model || 'gpt-4o-mini', max_tokens: 4096,
797
+ messages: [
798
+ { role: 'system', content: enrichedSystemPrompt },
799
+ { role: 'user', content: [
800
+ { type: 'image_url', image_url: { url: `data:${body.imageMimeType};base64,${body.imageBase64}` } },
801
+ { type: 'text', text: imagePrompt },
802
+ ]},
803
+ ],
804
+ }),
805
+ });
806
+ if (!r.ok) throw new Error(`OpenAI ${r.status}`);
807
+ const d = await r.json();
808
+ visionResponse = d.choices?.[0]?.message?.content || '';
809
+ } else if (provider === 'gemini') {
810
+ const m = model || 'gemini-2.0-flash';
811
+ const r = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${m}:generateContent?key=${apiKey}`, {
812
+ method: 'POST',
813
+ headers: { 'Content-Type': 'application/json' },
814
+ body: JSON.stringify({
815
+ system_instruction: { parts: [{ text: enrichedSystemPrompt }] },
816
+ contents: [{ parts: [
817
+ { inline_data: { mime_type: body.imageMimeType, data: body.imageBase64 } },
818
+ { text: imagePrompt },
819
+ ]}],
820
+ generationConfig: { maxOutputTokens: 4096 },
821
+ }),
822
+ });
823
+ if (!r.ok) throw new Error(`Gemini ${r.status}`);
824
+ const d = await r.json();
825
+ visionResponse = d.candidates?.[0]?.content?.parts?.[0]?.text || '';
826
+ } else {
827
+ visionResponse = `Vision not supported for provider "${provider}". Use anthropic, openai, or gemini.`;
828
+ }
829
+
830
+ sendJSON(res, 200, { response: visionResponse });
831
+ logRequest(method, pathname, 200, Date.now() - start);
832
+ return;
833
+ } catch (e) {
834
+ sendJSON(res, 200, { response: null, error: e.message });
835
+ logRequest(method, pathname, 200, Date.now() - start);
836
+ return;
837
+ }
838
+ }
839
+
840
+ // Handle file attachment
841
+ if (body.fileContent && body.fileName) {
842
+ const filePrompt = body.message
843
+ ? `User asks about file "${body.fileName}": ${body.message}\n\nFile content:\n${body.fileContent.slice(0, 8000)}`
844
+ : `Analyze this file "${body.fileName}":\n\n${body.fileContent.slice(0, 8000)}`;
845
+ userMessage = filePrompt;
846
+ }
847
+
767
848
  try {
768
849
  const response = await callLLM(config, enrichedSystemPrompt, userMessage);
769
850
  const { textParts, actions } = parseActions(response);
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 = '8.8.2';
8
+ export const VERSION = '8.9.0';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -316,7 +316,7 @@ function renderDash(el){
316
316
  var chatReady=false;
317
317
  function renderChat(el){
318
318
  if(!chatReady||!document.getElementById('chatMessages')){
319
- el.innerHTML='<div class="chat"><div class="chat__messages" id="chatMessages"></div><div class="chat__bar"><button class="chat__mic" id="chatMic" onclick="toggleVoiceInput()" title="Voice input">&#127908;</button><textarea class="chat__input" id="chatInput" placeholder="Ask anything..." rows="1"></textarea><button class="chat__send" id="chatSend">Send</button><button onclick="clearChatHistory()" style="background:none;color:var(--dim);font-size:10px;padding:4px 8px" title="Clear chat history">Clear</button></div></div>';
319
+ el.innerHTML='<div class="chat"><div class="chat__messages" id="chatMessages"></div><div id="chatAttachInfo" style="display:none;padding:4px 12px;font-size:11px;color:var(--cyan);background:var(--bg2);border-top:1px solid var(--border)"><span id="chatAttachName"></span> <button onclick="clearChatAttach()" style="background:none;border:none;color:#f44;cursor:pointer;font-size:14px;font-weight:700">&times;</button></div><div class="chat__bar"><button class="chat__mic" id="chatMic" onclick="toggleVoiceInput()" title="Voice input">&#127908;</button><button onclick="document.getElementById(\\x27chatFileInput\\x27).click()" style="background:none;border:none;cursor:pointer;font-size:16px;padding:4px" title="Attach file">&#128206;</button><button onclick="document.getElementById(\\x27chatImageInput\\x27).click()" style="background:none;border:none;cursor:pointer;font-size:16px;padding:4px" title="Attach image">&#128247;</button><input type="file" id="chatFileInput" style="display:none" onchange="handleChatFile(this)"><input type="file" id="chatImageInput" accept="image/*" style="display:none" onchange="handleChatImage(this)"><textarea class="chat__input" id="chatInput" placeholder="Ask anything... (or attach file/image first)" rows="1"></textarea><button class="chat__send" id="chatSend">Send</button><button onclick="clearChatHistory()" style="background:none;color:var(--dim);font-size:10px;padding:4px 8px" title="Clear chat history">Clear</button></div></div>';
320
320
  chatReady=true;
321
321
  document.getElementById('chatSend').onclick=sendChat;
322
322
  document.getElementById('chatInput').onkeydown=function(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendChat()}};
@@ -335,26 +335,68 @@ function renderMessages(){
335
335
  });
336
336
  el.innerHTML=h;el.scrollTop=el.scrollHeight;
337
337
  }
338
+ var chatAttachedFile=null;
339
+ var chatAttachedImage=null;
340
+
341
+ function handleChatFile(input){
342
+ var file=input.files&&input.files[0];if(!file)return;
343
+ var reader=new FileReader();
344
+ reader.onload=function(e){
345
+ chatAttachedFile={name:file.name,size:file.size,content:e.target.result};
346
+ chatAttachedImage=null;
347
+ document.getElementById('chatAttachInfo').style.display='';
348
+ document.getElementById('chatAttachName').textContent='📎 '+file.name+' ('+Math.round(file.size/1024)+' KB)';
349
+ };
350
+ reader.readAsText(file);
351
+ }
352
+
353
+ function handleChatImage(input){
354
+ var file=input.files&&input.files[0];if(!file)return;
355
+ var reader=new FileReader();
356
+ reader.onload=function(e){
357
+ var base64=e.target.result.split(',')[1];
358
+ chatAttachedImage={name:file.name,size:file.size,base64:base64,mimeType:file.type||'image/jpeg'};
359
+ chatAttachedFile=null;
360
+ document.getElementById('chatAttachInfo').style.display='';
361
+ document.getElementById('chatAttachName').textContent='📷 '+file.name+' ('+Math.round(file.size/1024)+' KB)';
362
+ };
363
+ reader.readAsDataURL(file);
364
+ }
365
+
366
+ function clearChatAttach(){
367
+ chatAttachedFile=null;chatAttachedImage=null;
368
+ document.getElementById('chatAttachInfo').style.display='none';
369
+ document.getElementById('chatFileInput').value='';
370
+ document.getElementById('chatImageInput').value='';
371
+ }
372
+
338
373
  function sendChat(){
339
374
  var inp=document.getElementById('chatInput');if(!inp)return;
340
- var msg=inp.value.trim();if(!msg)return;
341
- chatHistory.push({role:'user',content:msg});
375
+ var msg=inp.value.trim();
376
+ var hasAttach=!!chatAttachedFile||!!chatAttachedImage;
377
+ if(!msg&&!hasAttach)return;
378
+
379
+ var displayMsg=msg;
380
+ if(chatAttachedFile)displayMsg=(msg?msg+' ':'')+'[File: '+chatAttachedFile.name+']';
381
+ if(chatAttachedImage)displayMsg=(msg?msg+' ':'')+'[Image: '+chatAttachedImage.name+']';
382
+
383
+ chatHistory.push({role:'user',content:displayMsg});
342
384
  inp.value='';saveChatToStorage();renderMessages();
343
385
  chatHistory.push({role:'assistant',content:'Thinking...'});renderMessages();
344
- apiPost('/api/chat',{message:msg,history:chatHistory.slice(0,-1)}).then(function(r){
386
+
387
+ var payload={message:msg||'Analyze this attachment',history:chatHistory.slice(0,-1)};
388
+ if(chatAttachedFile){payload.fileContent=chatAttachedFile.content;payload.fileName=chatAttachedFile.name;}
389
+ if(chatAttachedImage){payload.imageBase64=chatAttachedImage.base64;payload.imageMimeType=chatAttachedImage.mimeType;payload.imageName=chatAttachedImage.name;}
390
+ clearChatAttach();
391
+
392
+ apiPost('/api/chat',payload).then(function(r){
345
393
  chatHistory.pop();
346
394
  if(r&&r.response){chatHistory.push({role:'assistant',content:r.response})}
347
395
  else if(r&&r.error){chatHistory.push({role:'assistant',content:'Error: '+r.error})}
348
396
  else{chatHistory.push({role:'assistant',content:'Error: no response from server'})}
349
397
  saveChatToStorage();renderMessages();
350
- // Refresh ALL data after any tool execution
351
398
  if(r&&((r.actions&&r.actions.length>0)||(r.toolResults&&r.toolResults.length>0))){
352
- calEventsCache={};
353
- contactsData=null;
354
- notesData=null;
355
- driveData=null;
356
- onedriveData=null;
357
- mstodoData=null;
399
+ calEventsCache={};contactsData=null;notesData=null;driveData=null;onedriveData=null;mstodoData=null;
358
400
  loadDash().then(function(){render()}).catch(function(){});
359
401
  }
360
402
  });