nothumanallowed 13.5.114 → 13.5.115

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.5.114",
3
+ "version": "13.5.115",
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": {
@@ -57,6 +57,9 @@
57
57
  "check-bundle": "node check-bundle.mjs"
58
58
  },
59
59
  "dependencies": {
60
+ "better-sqlite3": "^12.9.0",
61
+ "imapflow": "^1.3.3",
62
+ "mailparser": "^3.9.8",
60
63
  "ws": "^8.18.0"
61
64
  }
62
65
  }
@@ -707,6 +707,409 @@ export async function cmdUI(args) {
707
707
  return;
708
708
  }
709
709
 
710
+ // ═══════════════════════════════════════════════════════════════════
711
+ // IMAP EMAIL CLIENT ROUTES — READ-ONLY IMAP, local SQLite DB
712
+ // ═══════════════════════════════════════════════════════════════════
713
+
714
+ // GET /api/imap/accounts
715
+ if (method === 'GET' && pathname === '/api/imap/accounts') {
716
+ try {
717
+ const { listAccounts } = await import('../services/email-db.mjs');
718
+ sendJSON(res, 200, { accounts: listAccounts() });
719
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
720
+ logRequest(method, pathname, 200, Date.now() - start); return;
721
+ }
722
+
723
+ // POST /api/imap/accounts — create account
724
+ if (method === 'POST' && pathname === '/api/imap/accounts') {
725
+ try {
726
+ const body = await parseBody(req);
727
+ const { createAccount, listAccounts } = await import('../services/email-db.mjs');
728
+ if (!body.email_address || !body.imap_host || !body.smtp_host || !body.password) {
729
+ sendJSON(res, 400, { error: 'email_address, imap_host, smtp_host, password required' }); return;
730
+ }
731
+ const id = createAccount(body);
732
+ sendJSON(res, 200, { ok: true, id });
733
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
734
+ logRequest(method, pathname, 200, Date.now() - start); return;
735
+ }
736
+
737
+ // POST /api/imap/accounts/update
738
+ if (method === 'POST' && pathname === '/api/imap/accounts/update') {
739
+ try {
740
+ const body = await parseBody(req);
741
+ const { updateAccount } = await import('../services/email-db.mjs');
742
+ if (!body.id) { sendJSON(res, 400, { error: 'id required' }); return; }
743
+ updateAccount(body.id, body);
744
+ sendJSON(res, 200, { ok: true });
745
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
746
+ logRequest(method, pathname, 200, Date.now() - start); return;
747
+ }
748
+
749
+ // POST /api/imap/accounts/delete
750
+ if (method === 'POST' && pathname === '/api/imap/accounts/delete') {
751
+ try {
752
+ const body = await parseBody(req);
753
+ const { deleteAccount } = await import('../services/email-db.mjs');
754
+ if (!body.id) { sendJSON(res, 400, { error: 'id required' }); return; }
755
+ deleteAccount(body.id);
756
+ sendJSON(res, 200, { ok: true });
757
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
758
+ logRequest(method, pathname, 200, Date.now() - start); return;
759
+ }
760
+
761
+ // POST /api/imap/sync — trigger incremental sync for an account
762
+ if (method === 'POST' && pathname === '/api/imap/sync') {
763
+ try {
764
+ const body = await parseBody(req);
765
+ if (!body.accountId) { sendJSON(res, 400, { error: 'accountId required' }); return; }
766
+ const { syncAccount } = await import('../services/email-imap.mjs');
767
+ // Run async — respond immediately
768
+ sendJSON(res, 200, { ok: true, status: 'syncing' });
769
+ syncAccount(body.accountId).catch(e => console.error('[email:sync]', e.message));
770
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
771
+ logRequest(method, pathname, 200, Date.now() - start); return;
772
+ }
773
+
774
+ // GET /api/imap/messages?accountId=&labelId=&limit=&offset=&search=
775
+ if (method === 'GET' && pathname === '/api/imap/messages') {
776
+ try {
777
+ const accountId = url.searchParams.get('accountId');
778
+ const labelId = url.searchParams.get('labelId') || null;
779
+ const limit = parseInt(url.searchParams.get('limit') || '50', 10);
780
+ const offset = parseInt(url.searchParams.get('offset') || '0', 10);
781
+ const search = url.searchParams.get('search') || null;
782
+ if (!accountId) { sendJSON(res, 400, { error: 'accountId required' }); return; }
783
+ const { listMessages } = await import('../services/email-db.mjs');
784
+ const result = listMessages(accountId, labelId, limit, offset, search);
785
+ sendJSON(res, 200, result);
786
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
787
+ logRequest(method, pathname, 200, Date.now() - start); return;
788
+ }
789
+
790
+ // GET /api/imap/message?id=
791
+ if (method === 'GET' && pathname === '/api/imap/message') {
792
+ try {
793
+ const id = url.searchParams.get('id');
794
+ if (!id) { sendJSON(res, 400, { error: 'id required' }); return; }
795
+ const { getMessage, markRead } = await import('../services/email-db.mjs');
796
+ const msg = getMessage(id);
797
+ if (!msg) { sendJSON(res, 404, { error: 'not found' }); return; }
798
+ markRead(id, true);
799
+ sendJSON(res, 200, { message: msg });
800
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
801
+ logRequest(method, pathname, 200, Date.now() - start); return;
802
+ }
803
+
804
+ // GET /api/imap/thread?threadId=&accountId=
805
+ if (method === 'GET' && pathname === '/api/imap/thread') {
806
+ try {
807
+ const threadId = url.searchParams.get('threadId');
808
+ const accountId = url.searchParams.get('accountId');
809
+ if (!threadId || !accountId) { sendJSON(res, 400, { error: 'threadId and accountId required' }); return; }
810
+ const { getThread } = await import('../services/email-db.mjs');
811
+ sendJSON(res, 200, { messages: getThread(threadId, accountId) });
812
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
813
+ logRequest(method, pathname, 200, Date.now() - start); return;
814
+ }
815
+
816
+ // GET /api/imap/labels?accountId=
817
+ if (method === 'GET' && pathname === '/api/imap/labels') {
818
+ try {
819
+ const accountId = url.searchParams.get('accountId');
820
+ if (!accountId) { sendJSON(res, 400, { error: 'accountId required' }); return; }
821
+ const { listLabels } = await import('../services/email-db.mjs');
822
+ sendJSON(res, 200, { labels: listLabels(accountId) });
823
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
824
+ logRequest(method, pathname, 200, Date.now() - start); return;
825
+ }
826
+
827
+ // POST /api/imap/labels/create
828
+ if (method === 'POST' && pathname === '/api/imap/labels/create') {
829
+ try {
830
+ const body = await parseBody(req);
831
+ const { createLabel } = await import('../services/email-db.mjs');
832
+ if (!body.accountId || !body.name) { sendJSON(res, 400, { error: 'accountId, name required' }); return; }
833
+ const id = createLabel(body.accountId, body.name, body.color, body.parentId);
834
+ sendJSON(res, 200, { ok: true, id });
835
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
836
+ logRequest(method, pathname, 200, Date.now() - start); return;
837
+ }
838
+
839
+ // POST /api/imap/labels/update
840
+ if (method === 'POST' && pathname === '/api/imap/labels/update') {
841
+ try {
842
+ const body = await parseBody(req);
843
+ const { updateLabel } = await import('../services/email-db.mjs');
844
+ if (!body.id) { sendJSON(res, 400, { error: 'id required' }); return; }
845
+ updateLabel(body.id, body);
846
+ sendJSON(res, 200, { ok: true });
847
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
848
+ logRequest(method, pathname, 200, Date.now() - start); return;
849
+ }
850
+
851
+ // POST /api/imap/labels/delete
852
+ if (method === 'POST' && pathname === '/api/imap/labels/delete') {
853
+ try {
854
+ const body = await parseBody(req);
855
+ const { deleteLabel } = await import('../services/email-db.mjs');
856
+ if (!body.id) { sendJSON(res, 400, { error: 'id required' }); return; }
857
+ deleteLabel(body.id);
858
+ sendJSON(res, 200, { ok: true });
859
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
860
+ logRequest(method, pathname, 200, Date.now() - start); return;
861
+ }
862
+
863
+ // POST /api/imap/labels/assign — add label to message
864
+ if (method === 'POST' && pathname === '/api/imap/labels/assign') {
865
+ try {
866
+ const body = await parseBody(req);
867
+ const { addMessageToLabel, removeMessageFromLabel } = await import('../services/email-db.mjs');
868
+ if (!body.messageId || !body.labelId) { sendJSON(res, 400, { error: 'messageId, labelId required' }); return; }
869
+ if (body.remove) removeMessageFromLabel(body.messageId, body.labelId);
870
+ else addMessageToLabel(body.messageId, body.labelId);
871
+ sendJSON(res, 200, { ok: true });
872
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
873
+ logRequest(method, pathname, 200, Date.now() - start); return;
874
+ }
875
+
876
+ // POST /api/imap/mark-read
877
+ if (method === 'POST' && pathname === '/api/imap/mark-read') {
878
+ try {
879
+ const body = await parseBody(req);
880
+ const { markRead } = await import('../services/email-db.mjs');
881
+ if (!body.messageId) { sendJSON(res, 400, { error: 'messageId required' }); return; }
882
+ markRead(body.messageId, body.isRead !== false);
883
+ sendJSON(res, 200, { ok: true });
884
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
885
+ logRequest(method, pathname, 200, Date.now() - start); return;
886
+ }
887
+
888
+ // POST /api/imap/mark-starred
889
+ if (method === 'POST' && pathname === '/api/imap/mark-starred') {
890
+ try {
891
+ const body = await parseBody(req);
892
+ const { markStarred } = await import('../services/email-db.mjs');
893
+ if (!body.messageId) { sendJSON(res, 400, { error: 'messageId required' }); return; }
894
+ markStarred(body.messageId, body.isStarred !== false);
895
+ sendJSON(res, 200, { ok: true });
896
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
897
+ logRequest(method, pathname, 200, Date.now() - start); return;
898
+ }
899
+
900
+ // POST /api/imap/mark-all-read
901
+ if (method === 'POST' && pathname === '/api/imap/mark-all-read') {
902
+ try {
903
+ const body = await parseBody(req);
904
+ const { markAllRead } = await import('../services/email-db.mjs');
905
+ if (!body.accountId) { sendJSON(res, 400, { error: 'accountId required' }); return; }
906
+ const count = markAllRead(body.accountId, body.labelId || null);
907
+ sendJSON(res, 200, { ok: true, count });
908
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
909
+ logRequest(method, pathname, 200, Date.now() - start); return;
910
+ }
911
+
912
+ // POST /api/imap/trash — soft delete (moves to trash label, NEVER touches IMAP)
913
+ if (method === 'POST' && pathname === '/api/imap/trash') {
914
+ try {
915
+ const body = await parseBody(req);
916
+ const { softDelete } = await import('../services/email-db.mjs');
917
+ if (!body.messageId) { sendJSON(res, 400, { error: 'messageId required' }); return; }
918
+ softDelete(body.messageId);
919
+ sendJSON(res, 200, { ok: true });
920
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
921
+ logRequest(method, pathname, 200, Date.now() - start); return;
922
+ }
923
+
924
+ // POST /api/imap/send — send email via SMTP
925
+ if (method === 'POST' && pathname === '/api/imap/send') {
926
+ try {
927
+ const body = await parseBody(req);
928
+ const { sendEmail } = await import('../services/email-smtp.mjs');
929
+ if (!body.accountId || !body.to || !body.subject) {
930
+ sendJSON(res, 400, { error: 'accountId, to, subject required' }); return;
931
+ }
932
+ const result = await sendEmail(body.accountId, body);
933
+ sendJSON(res, 200, { ok: true, messageId: result.messageId });
934
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
935
+ logRequest(method, pathname, 200, Date.now() - start); return;
936
+ }
937
+
938
+ // POST /api/imap/drafts/save
939
+ if (method === 'POST' && pathname === '/api/imap/drafts/save') {
940
+ try {
941
+ const body = await parseBody(req);
942
+ const { saveDraft } = await import('../services/email-db.mjs');
943
+ if (!body.accountId) { sendJSON(res, 400, { error: 'accountId required' }); return; }
944
+ const id = saveDraft(body.accountId, body);
945
+ sendJSON(res, 200, { ok: true, id });
946
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
947
+ logRequest(method, pathname, 200, Date.now() - start); return;
948
+ }
949
+
950
+ // GET /api/imap/drafts?accountId=
951
+ if (method === 'GET' && pathname === '/api/imap/drafts') {
952
+ try {
953
+ const accountId = url.searchParams.get('accountId');
954
+ if (!accountId) { sendJSON(res, 400, { error: 'accountId required' }); return; }
955
+ const { listDrafts } = await import('../services/email-db.mjs');
956
+ sendJSON(res, 200, { drafts: listDrafts(accountId) });
957
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
958
+ logRequest(method, pathname, 200, Date.now() - start); return;
959
+ }
960
+
961
+ // POST /api/imap/drafts/delete
962
+ if (method === 'POST' && pathname === '/api/imap/drafts/delete') {
963
+ try {
964
+ const body = await parseBody(req);
965
+ const { deleteDraft } = await import('../services/email-db.mjs');
966
+ if (!body.id) { sendJSON(res, 400, { error: 'id required' }); return; }
967
+ deleteDraft(body.id);
968
+ sendJSON(res, 200, { ok: true });
969
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
970
+ logRequest(method, pathname, 200, Date.now() - start); return;
971
+ }
972
+
973
+ // GET /api/imap/blocked?accountId=
974
+ if (method === 'GET' && pathname === '/api/imap/blocked') {
975
+ try {
976
+ const accountId = url.searchParams.get('accountId');
977
+ if (!accountId) { sendJSON(res, 400, { error: 'accountId required' }); return; }
978
+ const { listBlockedSenders } = await import('../services/email-db.mjs');
979
+ sendJSON(res, 200, { blocked: listBlockedSenders(accountId) });
980
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
981
+ logRequest(method, pathname, 200, Date.now() - start); return;
982
+ }
983
+
984
+ // POST /api/imap/blocked/add
985
+ if (method === 'POST' && pathname === '/api/imap/blocked/add') {
986
+ try {
987
+ const body = await parseBody(req);
988
+ const { blockSender } = await import('../services/email-db.mjs');
989
+ if (!body.accountId || !body.email) { sendJSON(res, 400, { error: 'accountId, email required' }); return; }
990
+ blockSender(body.accountId, body.email);
991
+ sendJSON(res, 200, { ok: true });
992
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
993
+ logRequest(method, pathname, 200, Date.now() - start); return;
994
+ }
995
+
996
+ // POST /api/imap/blocked/remove
997
+ if (method === 'POST' && pathname === '/api/imap/blocked/remove') {
998
+ try {
999
+ const body = await parseBody(req);
1000
+ const { unblockSender } = await import('../services/email-db.mjs');
1001
+ if (!body.id) { sendJSON(res, 400, { error: 'id required' }); return; }
1002
+ unblockSender(body.id);
1003
+ sendJSON(res, 200, { ok: true });
1004
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
1005
+ logRequest(method, pathname, 200, Date.now() - start); return;
1006
+ }
1007
+
1008
+ // GET /api/imap/rules?accountId=
1009
+ if (method === 'GET' && pathname === '/api/imap/rules') {
1010
+ try {
1011
+ const accountId = url.searchParams.get('accountId');
1012
+ if (!accountId) { sendJSON(res, 400, { error: 'accountId required' }); return; }
1013
+ const { listArchivingRules } = await import('../services/email-db.mjs');
1014
+ sendJSON(res, 200, { rules: listArchivingRules(accountId) });
1015
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
1016
+ logRequest(method, pathname, 200, Date.now() - start); return;
1017
+ }
1018
+
1019
+ // POST /api/imap/rules/create
1020
+ if (method === 'POST' && pathname === '/api/imap/rules/create') {
1021
+ try {
1022
+ const body = await parseBody(req);
1023
+ const { createArchivingRule } = await import('../services/email-db.mjs');
1024
+ if (!body.accountId || !body.matchType || !body.matchValue) {
1025
+ sendJSON(res, 400, { error: 'accountId, matchType, matchValue required' }); return;
1026
+ }
1027
+ const id = createArchivingRule(body.accountId, body.matchType, body.matchValue, body.targetLabelId);
1028
+ sendJSON(res, 200, { ok: true, id });
1029
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
1030
+ logRequest(method, pathname, 200, Date.now() - start); return;
1031
+ }
1032
+
1033
+ // POST /api/imap/rules/delete
1034
+ if (method === 'POST' && pathname === '/api/imap/rules/delete') {
1035
+ try {
1036
+ const body = await parseBody(req);
1037
+ const { deleteArchivingRule } = await import('../services/email-db.mjs');
1038
+ if (!body.id) { sendJSON(res, 400, { error: 'id required' }); return; }
1039
+ deleteArchivingRule(body.id);
1040
+ sendJSON(res, 200, { ok: true });
1041
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
1042
+ logRequest(method, pathname, 200, Date.now() - start); return;
1043
+ }
1044
+
1045
+ // GET /api/imap/signatures?accountId=
1046
+ if (method === 'GET' && pathname === '/api/imap/signatures') {
1047
+ try {
1048
+ const accountId = url.searchParams.get('accountId');
1049
+ if (!accountId) { sendJSON(res, 400, { error: 'accountId required' }); return; }
1050
+ const { listSignatures } = await import('../services/email-db.mjs');
1051
+ sendJSON(res, 200, { signatures: listSignatures(accountId) });
1052
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
1053
+ logRequest(method, pathname, 200, Date.now() - start); return;
1054
+ }
1055
+
1056
+ // POST /api/imap/signatures/create
1057
+ if (method === 'POST' && pathname === '/api/imap/signatures/create') {
1058
+ try {
1059
+ const body = await parseBody(req);
1060
+ const { createSignature } = await import('../services/email-db.mjs');
1061
+ if (!body.accountId || !body.name || !body.content) {
1062
+ sendJSON(res, 400, { error: 'accountId, name, content required' }); return;
1063
+ }
1064
+ const id = createSignature(body.accountId, body.name, body.content, body.isDefault || false);
1065
+ sendJSON(res, 200, { ok: true, id });
1066
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
1067
+ logRequest(method, pathname, 200, Date.now() - start); return;
1068
+ }
1069
+
1070
+ // POST /api/imap/signatures/delete
1071
+ if (method === 'POST' && pathname === '/api/imap/signatures/delete') {
1072
+ try {
1073
+ const body = await parseBody(req);
1074
+ const { deleteSignature } = await import('../services/email-db.mjs');
1075
+ if (!body.id) { sendJSON(res, 400, { error: 'id required' }); return; }
1076
+ deleteSignature(body.id);
1077
+ sendJSON(res, 200, { ok: true });
1078
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
1079
+ logRequest(method, pathname, 200, Date.now() - start); return;
1080
+ }
1081
+
1082
+ // GET /api/imap/attachment?messageId=&partId=&accountId=
1083
+ if (method === 'GET' && pathname === '/api/imap/attachment') {
1084
+ try {
1085
+ const messageId = url.searchParams.get('messageId');
1086
+ const partId = url.searchParams.get('partId');
1087
+ const accountId = url.searchParams.get('accountId');
1088
+ if (!messageId || !accountId) { sendJSON(res, 400, { error: 'messageId, accountId required' }); return; }
1089
+ const { getMessage } = await import('../services/email-db.mjs');
1090
+ const { fetchAttachmentContent } = await import('../services/email-imap.mjs');
1091
+ const msg = getMessage(messageId);
1092
+ if (!msg) { sendJSON(res, 404, { error: 'message not found' }); return; }
1093
+ const att = msg.attachments?.find(a => a.part_id === partId);
1094
+ if (!att) { sendJSON(res, 404, { error: 'attachment not found' }); return; }
1095
+ // Check local cache first
1096
+ const db = (await import('../services/email-db.mjs')).getDb();
1097
+ const cached = db.prepare('SELECT content_blob, content_type FROM email_attachments WHERE id = ?').get(att.id);
1098
+ if (cached?.content_blob) {
1099
+ res.writeHead(200, { 'Content-Type': cached.content_type || 'application/octet-stream', 'Content-Disposition': `attachment; filename="${att.filename || 'attachment'}"` });
1100
+ res.end(cached.content_blob);
1101
+ } else {
1102
+ const result = await fetchAttachmentContent(accountId, msg.imap_folder_path, msg.uid, partId);
1103
+ if (!result) { sendJSON(res, 404, { error: 'could not fetch attachment' }); return; }
1104
+ res.writeHead(200, { 'Content-Type': result.contentType || att.content_type || 'application/octet-stream', 'Content-Disposition': `attachment; filename="${att.filename || 'attachment'}"` });
1105
+ res.end(result.buffer);
1106
+ }
1107
+ } catch (e) { sendJSON(res, 500, { error: e.message }); }
1108
+ logRequest(method, pathname, 200, Date.now() - start); return;
1109
+ }
1110
+
1111
+ // ═══════════════════════════════════════════════════════════════════
1112
+
710
1113
  // POST /api/contacts — create contact
711
1114
  if (method === 'POST' && pathname === '/api/contacts') {
712
1115
  try {
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.5.114';
8
+ export const VERSION = '13.5.115';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11