Просмотр исходного кода

✨ feat(tools): Support for Excel (.xlsx) files (#4668)

Christiaan Arnoldus 6 месяцев назад
Родитель
Сommit
bbbff7344b

+ 353 - 0
pnpm-lock.yaml

@@ -630,6 +630,9 @@ importers:
       diff-match-patch:
         specifier: ^1.0.5
         version: 1.0.5
+      exceljs:
+        specifier: ^4.4.0
+        version: 4.4.0
       fast-deep-equal:
         specifier: ^3.1.3
         version: 3.1.3
@@ -2088,6 +2091,12 @@ packages:
     resolution: {integrity: sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  '@fast-csv/[email protected]':
+    resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==}
+
+  '@fast-csv/[email protected]':
+    resolution: {integrity: sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==}
+
   '@fastify/[email protected]':
     resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
     engines: {node: '>=14'}
@@ -4225,6 +4234,9 @@ packages:
   '@types/[email protected]':
     resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==}
 
+  '@types/[email protected]':
+    resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==}
+
   '@types/[email protected]':
     resolution: {integrity: sha512-ojmMP8SZBKprc3qGrGk8Ujpo80AXkrP7G2tOT4VWr5jlr5DHjsJF+emXJz+Wm0glmy4Js62oKMdZZ6B9Y+tEcA==}
 
@@ -4573,6 +4585,18 @@ packages:
   [email protected]:
     resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
 
+  [email protected]:
+    resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==}
+    engines: {node: '>= 6'}
+
+  [email protected]:
+    resolution: {integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==}
+    engines: {node: '>= 10'}
+
+  [email protected]:
+    resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==}
+    engines: {node: '>= 10'}
+
   [email protected]:
     resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
 
@@ -4748,6 +4772,10 @@ packages:
   [email protected]:
     resolution: {integrity: sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==}
 
+  [email protected]:
+    resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==}
+    engines: {node: '>=0.6'}
+
   [email protected]:
     resolution: {integrity: sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==}
 
@@ -4755,9 +4783,15 @@ packages:
     resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
     engines: {node: '>=8'}
 
+  [email protected]:
+    resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==}
+
   [email protected]:
     resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
 
+  [email protected]:
+    resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
+
   [email protected]:
     resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==}
 
@@ -4805,9 +4839,17 @@ packages:
   [email protected]:
     resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
 
+  [email protected]:
+    resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==}
+    engines: {node: '>=0.10'}
+
   [email protected]:
     resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
 
+  [email protected]:
+    resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==}
+    engines: {node: '>=0.2.0'}
+
   [email protected]:
     resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
     engines: {node: '>=18'}
@@ -4876,6 +4918,9 @@ packages:
     resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==}
     engines: {node: '>=12'}
 
+  [email protected]:
+    resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==}
+
   [email protected]:
     resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
     engines: {node: '>=4'}
@@ -5092,6 +5137,10 @@ packages:
     resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
     engines: {node: '>= 12'}
 
+  [email protected]:
+    resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==}
+    engines: {node: '>= 10'}
+
   [email protected]:
     resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
 
@@ -5154,6 +5203,15 @@ packages:
     resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
     engines: {node: '>=10'}
 
+  [email protected]:
+    resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
+    engines: {node: '>=0.8'}
+    hasBin: true
+
+  [email protected]:
+    resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==}
+    engines: {node: '>= 10'}
+
   [email protected]:
     resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -5696,6 +5754,9 @@ packages:
     resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
     engines: {node: '>= 0.4'}
 
+  [email protected]:
+    resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==}
+
   [email protected]:
     resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
 
@@ -6018,6 +6079,10 @@ packages:
     resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
     engines: {node: '>=18.0.0'}
 
+  [email protected]:
+    resolution: {integrity: sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==}
+    engines: {node: '>=8.3.0'}
+
   [email protected]:
     resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
     engines: {node: '>=10'}
@@ -6081,6 +6146,10 @@ packages:
     engines: {node: '>= 10.17.0'}
     hasBin: true
 
+  [email protected]:
+    resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==}
+    engines: {node: '>=10.0.0'}
+
   [email protected]:
     resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
 
@@ -6267,6 +6336,9 @@ packages:
   [email protected]:
     resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==}
 
+  [email protected]:
+    resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
+
   [email protected]:
     resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==}
     engines: {node: '>=6 <7 || >=8'}
@@ -6283,6 +6355,11 @@ packages:
     engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
     os: [darwin]
 
+  [email protected]:
+    resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==}
+    engines: {node: '>=0.6'}
+    deprecated: This package is no longer supported.
+
   [email protected]:
     resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
 
@@ -7274,6 +7351,10 @@ packages:
   [email protected]:
     resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==}
 
+  [email protected]:
+    resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==}
+    engines: {node: '>= 0.6.3'}
+
   [email protected]:
     resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
     engines: {node: '>=6'}
@@ -7433,6 +7514,9 @@ packages:
     engines: {node: '>=20.17'}
     hasBin: true
 
+  [email protected]:
+    resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==}
+
   [email protected]:
     resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==}
     engines: {node: '>=18.0.0'}
@@ -7462,15 +7546,40 @@ packages:
   [email protected]:
     resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
 
+  [email protected]:
+    resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
+
+  [email protected]:
+    resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==}
+
+  [email protected]:
+    resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==}
+
+  [email protected]:
+    resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==}
+
+  [email protected]:
+    resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==}
+
   [email protected]:
     resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
 
   [email protected]:
     resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
 
+  [email protected]:
+    resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
+    deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
+
+  [email protected]:
+    resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==}
+
   [email protected]:
     resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==}
 
+  [email protected]:
+    resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==}
+
   [email protected]:
     resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==}
 
@@ -7480,6 +7589,9 @@ packages:
   [email protected]:
     resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
 
+  [email protected]:
+    resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==}
+
   [email protected]:
     resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
 
@@ -7495,6 +7607,12 @@ packages:
   [email protected]:
     resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==}
 
+  [email protected]:
+    resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==}
+
+  [email protected]:
+    resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==}
+
   [email protected]:
     resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
 
@@ -7846,6 +7964,10 @@ packages:
   [email protected]:
     resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
 
+  [email protected]:
+    resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
+    hasBin: true
+
   [email protected]:
     resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
     engines: {node: '>=10'}
@@ -8749,6 +8871,13 @@ packages:
   [email protected]:
     resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
 
+  [email protected]:
+    resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
+    engines: {node: '>= 6'}
+
+  [email protected]:
+    resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==}
+
   [email protected]:
     resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
     engines: {node: '>=8.10.0'}
@@ -8872,6 +9001,11 @@ packages:
   [email protected]:
     resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
 
+  [email protected]:
+    resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
+    deprecated: Rimraf versions prior to v4 are no longer supported
+    hasBin: true
+
   [email protected]:
     resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==}
     engines: {node: 20 || >=22}
@@ -8932,6 +9066,10 @@ packages:
   [email protected]:
     resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
 
+  [email protected]:
+    resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==}
+    engines: {node: '>=10'}
+
   [email protected]:
     resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
     engines: {node: '>=v12.22.7'}
@@ -9411,6 +9549,10 @@ packages:
   [email protected]:
     resolution: {integrity: sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==}
 
+  [email protected]:
+    resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
+    engines: {node: '>=6'}
+
   [email protected]:
     resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==}
 
@@ -9517,6 +9659,9 @@ packages:
     resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==}
     engines: {node: '>=12'}
 
+  [email protected]:
+    resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==}
+
   [email protected]:
     resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
     hasBin: true
@@ -9818,6 +9963,9 @@ packages:
     resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
     engines: {node: '>=8'}
 
+  [email protected]:
+    resolution: {integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==}
+
   [email protected]:
     resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
     hasBin: true
@@ -10278,6 +10426,10 @@ packages:
     resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==}
     engines: {node: '>=18'}
 
+  [email protected]:
+    resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==}
+    engines: {node: '>= 10'}
+
   [email protected]:
     resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==}
     peerDependencies:
@@ -11600,6 +11752,25 @@ snapshots:
       '@eslint/core': 0.14.0
       levn: 0.4.1
 
+  '@fast-csv/[email protected]':
+    dependencies:
+      '@types/node': 14.18.63
+      lodash.escaperegexp: 4.1.2
+      lodash.isboolean: 3.0.3
+      lodash.isequal: 4.5.0
+      lodash.isfunction: 3.0.9
+      lodash.isnil: 4.0.0
+
+  '@fast-csv/[email protected]':
+    dependencies:
+      '@types/node': 14.18.63
+      lodash.escaperegexp: 4.1.2
+      lodash.groupby: 4.6.0
+      lodash.isfunction: 3.0.9
+      lodash.isnil: 4.0.0
+      lodash.isundefined: 3.0.1
+      lodash.uniq: 4.5.0
+
   '@fastify/[email protected]': {}
 
   '@floating-ui/[email protected]':
@@ -13960,6 +14131,8 @@ snapshots:
 
   '@types/[email protected]': {}
 
+  '@types/[email protected]': {}
+
   '@types/[email protected]':
     dependencies:
       undici-types: 5.26.5
@@ -14393,6 +14566,42 @@ snapshots:
 
   [email protected]: {}
 
+  [email protected]:
+    dependencies:
+      glob: 7.2.3
+      graceful-fs: 4.2.11
+      lazystream: 1.0.1
+      lodash.defaults: 4.2.0
+      lodash.difference: 4.5.0
+      lodash.flatten: 4.4.0
+      lodash.isplainobject: 4.0.6
+      lodash.union: 4.6.0
+      normalize-path: 3.0.0
+      readable-stream: 2.3.8
+
+  [email protected]:
+    dependencies:
+      glob: 7.2.3
+      graceful-fs: 4.2.11
+      lazystream: 1.0.1
+      lodash.defaults: 4.2.0
+      lodash.difference: 4.5.0
+      lodash.flatten: 4.4.0
+      lodash.isplainobject: 4.0.6
+      lodash.union: 4.6.0
+      normalize-path: 3.0.0
+      readable-stream: 3.6.2
+
+  [email protected]:
+    dependencies:
+      archiver-utils: 2.1.0
+      async: 3.2.6
+      buffer-crc32: 0.2.13
+      readable-stream: 3.6.2
+      readdir-glob: 1.1.3
+      tar-stream: 2.2.0
+      zip-stream: 4.1.1
+
   [email protected]: {}
 
   [email protected]:
@@ -14622,15 +14831,28 @@ snapshots:
       - bare-buffer
     optional: true
 
+  [email protected]: {}
+
   [email protected]: {}
 
   [email protected]: {}
 
+  [email protected]:
+    dependencies:
+      buffers: 0.1.1
+      chainsaw: 0.1.0
+
   [email protected]:
     dependencies:
       file-uri-to-path: 1.0.0
     optional: true
 
+  [email protected]:
+    dependencies:
+      buffer: 5.7.1
+      inherits: 2.0.4
+      readable-stream: 3.6.2
+
   [email protected]: {}
 
   [email protected]:
@@ -14687,11 +14909,15 @@ snapshots:
 
   [email protected]: {}
 
+  [email protected]: {}
+
   [email protected]:
     dependencies:
       base64-js: 1.5.1
       ieee754: 1.2.1
 
+  [email protected]: {}
+
   [email protected]:
     dependencies:
       run-applescript: 7.0.0
@@ -14762,6 +14988,10 @@ snapshots:
       loupe: 3.1.3
       pathval: 2.0.0
 
+  [email protected]:
+    dependencies:
+      traverse: 0.3.9
+
   [email protected]:
     dependencies:
       ansi-styles: 3.2.1
@@ -14985,6 +15215,13 @@ snapshots:
 
   [email protected]: {}
 
+  [email protected]:
+    dependencies:
+      buffer-crc32: 0.2.13
+      crc32-stream: 4.0.3
+      normalize-path: 3.0.0
+      readable-stream: 3.6.2
+
   [email protected]: {}
 
   [email protected]: {}
@@ -15047,6 +15284,13 @@ snapshots:
       yaml: 1.10.2
     optional: true
 
+  [email protected]: {}
+
+  [email protected]:
+    dependencies:
+      crc-32: 1.2.2
+      readable-stream: 3.6.2
+
   [email protected](@types/[email protected])([email protected]):
     dependencies:
       '@jest/types': 29.6.3
@@ -15515,6 +15759,10 @@ snapshots:
       es-errors: 1.3.0
       gopd: 1.2.0
 
+  [email protected]:
+    dependencies:
+      readable-stream: 2.3.8
+
   [email protected]: {}
 
   [email protected]: {}
@@ -16015,6 +16263,18 @@ snapshots:
     dependencies:
       eventsource-parser: 3.0.2
 
+  [email protected]:
+    dependencies:
+      archiver: 5.3.2
+      dayjs: 1.11.13
+      fast-csv: 4.3.6
+      jszip: 3.10.1
+      readable-stream: 3.6.2
+      saxes: 5.0.1
+      tmp: 0.2.3
+      unzipper: 0.10.14
+      uuid: 8.3.2
+
   [email protected]:
     dependencies:
       cross-spawn: 7.0.6
@@ -16144,6 +16404,11 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  [email protected]:
+    dependencies:
+      '@fast-csv/format': 4.3.5
+      '@fast-csv/parse': 4.3.6
+
   [email protected]: {}
 
   [email protected]: {}
@@ -16323,6 +16588,8 @@ snapshots:
 
   [email protected]: {}
 
+  [email protected]: {}
+
   [email protected]:
     dependencies:
       graceful-fs: 4.2.11
@@ -16340,6 +16607,13 @@ snapshots:
   [email protected]:
     optional: true
 
+  [email protected]:
+    dependencies:
+      graceful-fs: 4.2.11
+      inherits: 2.0.4
+      mkdirp: 0.5.6
+      rimraf: 2.7.1
+
   [email protected]: {}
 
   [email protected]:
@@ -17610,6 +17884,10 @@ snapshots:
 
   [email protected]: {}
 
+  [email protected]:
+    dependencies:
+      readable-stream: 2.3.8
+
   [email protected]: {}
 
   [email protected]:
@@ -17750,6 +18028,8 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  [email protected]: {}
+
   [email protected]:
     dependencies:
       cli-truncate: 4.0.0
@@ -17781,18 +18061,36 @@ snapshots:
 
   [email protected]: {}
 
+  [email protected]: {}
+
+  [email protected]: {}
+
+  [email protected]: {}
+
+  [email protected]: {}
+
+  [email protected]: {}
+
   [email protected]: {}
 
   [email protected]: {}
 
+  [email protected]: {}
+
+  [email protected]: {}
+
   [email protected]: {}
 
+  [email protected]: {}
+
   [email protected]: {}
 
   [email protected]: {}
 
   [email protected]: {}
 
+  [email protected]: {}
+
   [email protected]: {}
 
   [email protected]: {}
@@ -17803,6 +18101,10 @@ snapshots:
 
   [email protected]: {}
 
+  [email protected]: {}
+
+  [email protected]: {}
+
   [email protected]: {}
 
   [email protected]:
@@ -18387,6 +18689,10 @@ snapshots:
   [email protected]:
     optional: true
 
+  [email protected]:
+    dependencies:
+      minimist: 1.2.8
+
   [email protected]: {}
 
   [email protected]: {}
@@ -19399,6 +19705,16 @@ snapshots:
       string_decoder: 1.1.1
       util-deprecate: 1.0.2
 
+  [email protected]:
+    dependencies:
+      inherits: 2.0.4
+      string_decoder: 1.1.1
+      util-deprecate: 1.0.2
+
+  [email protected]:
+    dependencies:
+      minimatch: 5.1.6
+
   [email protected]:
     dependencies:
       picomatch: 2.3.1
@@ -19565,6 +19881,10 @@ snapshots:
 
   [email protected]: {}
 
+  [email protected]:
+    dependencies:
+      glob: 7.2.3
+
   [email protected]:
     dependencies:
       glob: 11.0.2
@@ -19658,6 +19978,10 @@ snapshots:
 
   [email protected]: {}
 
+  [email protected]:
+    dependencies:
+      xmlchars: 2.2.0
+
   [email protected]:
     dependencies:
       xmlchars: 2.2.0
@@ -20221,6 +20545,14 @@ snapshots:
     transitivePeerDependencies:
       - bare-buffer
 
+  [email protected]:
+    dependencies:
+      bl: 4.1.0
+      end-of-stream: 1.4.4
+      fs-constants: 1.0.0
+      inherits: 2.0.4
+      readable-stream: 3.6.2
+
   [email protected]:
     dependencies:
       b4a: 1.6.7
@@ -20324,6 +20656,8 @@ snapshots:
     dependencies:
       punycode: 2.3.1
 
+  [email protected]: {}
+
   [email protected]: {}
 
   [email protected]: {}
@@ -20640,6 +20974,19 @@ snapshots:
 
   [email protected]: {}
 
+  [email protected]:
+    dependencies:
+      big-integer: 1.6.52
+      binary: 0.3.0
+      bluebird: 3.4.7
+      buffer-indexof-polyfill: 1.0.2
+      duplexer2: 0.1.4
+      fstream: 1.0.12
+      graceful-fs: 4.2.11
+      listenercount: 1.0.1
+      readable-stream: 2.3.8
+      setimmediate: 1.0.5
+
   [email protected]([email protected]):
     dependencies:
       browserslist: 4.24.5
@@ -21264,6 +21611,12 @@ snapshots:
 
   [email protected]: {}
 
+  [email protected]:
+    dependencies:
+      archiver-utils: 3.0.4
+      compress-commons: 4.1.2
+      readable-stream: 3.6.2
+
   [email protected]([email protected]):
     dependencies:
       zod: 3.25.61

+ 221 - 0
src/integrations/misc/__tests__/extract-text-from-xlsx.test.ts

@@ -0,0 +1,221 @@
+import ExcelJS from "exceljs"
+import { extractTextFromXLSX } from "../extract-text-from-xlsx"
+
+describe("extractTextFromXLSX", () => {
+	describe("basic functionality", () => {
+		it("should extract text with proper formatting", async () => {
+			const workbook = new ExcelJS.Workbook()
+			const worksheet = workbook.addWorksheet("Sheet1")
+
+			worksheet.getCell("A1").value = "Hello"
+			worksheet.getCell("B1").value = "World"
+			worksheet.getCell("A2").value = "Test"
+			worksheet.getCell("B2").value = 123
+
+			const result = await extractTextFromXLSX(workbook)
+
+			expect(result).toContain("--- Sheet: Sheet1 ---")
+			expect(result).toContain("Hello\tWorld")
+			expect(result).toContain("Test\t123")
+		})
+
+		it("should skip rows with no content", async () => {
+			const workbook = new ExcelJS.Workbook()
+			const worksheet = workbook.addWorksheet("Sheet1")
+
+			worksheet.getCell("A1").value = "Row 1"
+			// Row 2 is completely empty
+			worksheet.getCell("A3").value = "Row 3"
+
+			const result = await extractTextFromXLSX(workbook)
+
+			expect(result).toContain("Row 1")
+			expect(result).toContain("Row 3")
+			// Should not contain empty rows
+			expect(result).not.toMatch(/\n\t*\n/)
+		})
+	})
+
+	describe("sheet handling", () => {
+		it("should process multiple sheets", async () => {
+			const workbook = new ExcelJS.Workbook()
+
+			const sheet1 = workbook.addWorksheet("First Sheet")
+			sheet1.getCell("A1").value = "Sheet 1 Data"
+
+			const sheet2 = workbook.addWorksheet("Second Sheet")
+			sheet2.getCell("A1").value = "Sheet 2 Data"
+
+			const result = await extractTextFromXLSX(workbook)
+
+			expect(result).toContain("--- Sheet: First Sheet ---")
+			expect(result).toContain("Sheet 1 Data")
+			expect(result).toContain("--- Sheet: Second Sheet ---")
+			expect(result).toContain("Sheet 2 Data")
+		})
+
+		it("should skip hidden sheets", async () => {
+			const workbook = new ExcelJS.Workbook()
+
+			const visibleSheet = workbook.addWorksheet("Visible Sheet")
+			visibleSheet.getCell("A1").value = "Visible Data"
+
+			const hiddenSheet = workbook.addWorksheet("Hidden Sheet")
+			hiddenSheet.getCell("A1").value = "Hidden Data"
+			hiddenSheet.state = "hidden"
+
+			const result = await extractTextFromXLSX(workbook)
+
+			expect(result).toContain("--- Sheet: Visible Sheet ---")
+			expect(result).toContain("Visible Data")
+			expect(result).not.toContain("--- Sheet: Hidden Sheet ---")
+			expect(result).not.toContain("Hidden Data")
+		})
+
+		it("should skip very hidden sheets", async () => {
+			const workbook = new ExcelJS.Workbook()
+
+			const visibleSheet = workbook.addWorksheet("Visible Sheet")
+			visibleSheet.getCell("A1").value = "Visible Data"
+
+			const veryHiddenSheet = workbook.addWorksheet("Very Hidden Sheet")
+			veryHiddenSheet.getCell("A1").value = "Very Hidden Data"
+			veryHiddenSheet.state = "veryHidden"
+
+			const result = await extractTextFromXLSX(workbook)
+
+			expect(result).toContain("--- Sheet: Visible Sheet ---")
+			expect(result).toContain("Visible Data")
+			expect(result).not.toContain("--- Sheet: Very Hidden Sheet ---")
+			expect(result).not.toContain("Very Hidden Data")
+		})
+	})
+
+	describe("formatCellValue logic", () => {
+		it("should handle null and undefined values", async () => {
+			const workbook = new ExcelJS.Workbook()
+			const worksheet = workbook.addWorksheet("Sheet1")
+
+			worksheet.getCell("A1").value = "Before"
+			worksheet.getCell("A2").value = null
+			worksheet.getCell("A3").value = undefined
+			worksheet.getCell("A4").value = "After"
+
+			const result = await extractTextFromXLSX(workbook)
+
+			expect(result).toContain("Before")
+			expect(result).toContain("After")
+			// Should handle null/undefined as empty strings
+			const lines = result.split("\n")
+			const dataLines = lines.filter((line) => !line.startsWith("---") && line.trim())
+			expect(dataLines).toHaveLength(2) // Only 'Before' and 'After' should create content
+		})
+
+		it("should format dates correctly", async () => {
+			const workbook = new ExcelJS.Workbook()
+			const worksheet = workbook.addWorksheet("Sheet1")
+
+			const testDate = new Date("2023-12-25")
+			worksheet.getCell("A1").value = testDate
+
+			const result = await extractTextFromXLSX(workbook)
+
+			expect(result).toContain("2023-12-25")
+		})
+
+		it("should handle error values", async () => {
+			const workbook = new ExcelJS.Workbook()
+			const worksheet = workbook.addWorksheet("Sheet1")
+
+			worksheet.getCell("A1").value = { error: "#DIV/0!" }
+
+			const result = await extractTextFromXLSX(workbook)
+
+			expect(result).toContain("[Error: #DIV/0!]")
+		})
+
+		it("should handle rich text", async () => {
+			const workbook = new ExcelJS.Workbook()
+			const worksheet = workbook.addWorksheet("Sheet1")
+
+			worksheet.getCell("A1").value = {
+				richText: [{ text: "Hello " }, { text: "World", font: { bold: true } }],
+			}
+
+			const result = await extractTextFromXLSX(workbook)
+
+			expect(result).toContain("Hello World")
+		})
+
+		it("should handle hyperlinks", async () => {
+			const workbook = new ExcelJS.Workbook()
+			const worksheet = workbook.addWorksheet("Sheet1")
+
+			worksheet.getCell("A1").value = {
+				text: "Roo Code",
+				hyperlink: "https://roocode.com/",
+			}
+
+			const result = await extractTextFromXLSX(workbook)
+
+			expect(result).toContain("Roo Code (https://roocode.com/)")
+		})
+
+		it("should handle formulas with and without results", async () => {
+			const workbook = new ExcelJS.Workbook()
+			const worksheet = workbook.addWorksheet("Sheet1")
+
+			worksheet.getCell("A1").value = { formula: "A2+A3", result: 30 }
+			worksheet.getCell("A2").value = { formula: "SUM(B1:B10)" }
+
+			const result = await extractTextFromXLSX(workbook)
+
+			expect(result).toContain("30") // Formula with result
+			expect(result).toContain("[Formula: SUM(B1:B10)]") // Formula without result
+		})
+	})
+
+	describe("edge cases", () => {
+		it("should handle empty workbook", async () => {
+			const workbook = new ExcelJS.Workbook()
+			workbook.addWorksheet("Empty Sheet")
+
+			const result = await extractTextFromXLSX(workbook)
+
+			expect(result).toContain("--- Sheet: Empty Sheet ---")
+			expect(result.trim()).toBe("--- Sheet: Empty Sheet ---")
+		})
+
+		it("should handle workbook with only empty cells", async () => {
+			const workbook = new ExcelJS.Workbook()
+			const worksheet = workbook.addWorksheet("Sheet1")
+
+			// Set cells but leave them empty
+			worksheet.getCell("A1").value = ""
+			worksheet.getCell("B1").value = ""
+
+			const result = await extractTextFromXLSX(workbook)
+
+			expect(result).toContain("--- Sheet: Sheet1 ---")
+			// Should not contain any data rows since empty strings don't count as content
+			const lines = result.split("\n").filter((line) => line.trim() && !line.startsWith("---"))
+			expect(lines).toHaveLength(0)
+		})
+	})
+
+	describe("function overloads", () => {
+		it("should work with workbook objects", async () => {
+			const workbook = new ExcelJS.Workbook()
+			const worksheet = workbook.addWorksheet("Test")
+			worksheet.getCell("A1").value = "Test Data"
+
+			const result = await extractTextFromXLSX(workbook)
+
+			expect(result).toContain("Test Data")
+		})
+
+		it("should reject invalid file paths", async () => {
+			await expect(extractTextFromXLSX("/non/existent/file.xlsx")).rejects.toThrow()
+		})
+	})
+})

+ 89 - 0
src/integrations/misc/extract-text-from-xlsx.ts

@@ -0,0 +1,89 @@
+import ExcelJS from "exceljs"
+
+const ROW_LIMIT = 50000
+
+function formatCellValue(cell: ExcelJS.Cell): string {
+	const value = cell.value
+	if (value === null || value === undefined) {
+		return ""
+	}
+
+	// Handle error values (#DIV/0!, #N/A, etc.)
+	if (typeof value === "object" && "error" in value) {
+		return `[Error: ${value.error}]`
+	}
+
+	// Handle dates - ExcelJS can parse them as Date objects
+	if (value instanceof Date) {
+		return value.toISOString().split("T")[0]
+	}
+
+	// Handle rich text
+	if (typeof value === "object" && "richText" in value) {
+		return value.richText.map((rt) => rt.text).join("")
+	}
+
+	// Handle hyperlinks
+	if (typeof value === "object" && "text" in value && "hyperlink" in value) {
+		return `${value.text} (${value.hyperlink})`
+	}
+
+	// Handle formulas - get the calculated result
+	if (typeof value === "object" && "formula" in value) {
+		if ("result" in value && value.result !== undefined && value.result !== null) {
+			return value.result.toString()
+		} else {
+			return `[Formula: ${value.formula}]`
+		}
+	}
+
+	return value.toString()
+}
+
+export async function extractTextFromXLSX(filePathOrWorkbook: string | ExcelJS.Workbook): Promise<string> {
+	let workbook: ExcelJS.Workbook
+	let excelText = ""
+
+	if (typeof filePathOrWorkbook === "string") {
+		workbook = new ExcelJS.Workbook()
+		await workbook.xlsx.readFile(filePathOrWorkbook)
+	} else {
+		workbook = filePathOrWorkbook
+	}
+
+	workbook.eachSheet((worksheet, sheetId) => {
+		if (worksheet.state === "hidden" || worksheet.state === "veryHidden") {
+			return
+		}
+
+		excelText += `--- Sheet: ${worksheet.name} ---\n`
+
+		worksheet.eachRow({ includeEmpty: false }, (row, rowNumber) => {
+			if (rowNumber > ROW_LIMIT) {
+				excelText += `[... truncated at row ${rowNumber} ...]\n`
+				return false
+			}
+
+			const rowTexts: string[] = []
+			let hasContent = false
+
+			row.eachCell({ includeEmpty: true }, (cell, colNumber) => {
+				const cellText = formatCellValue(cell)
+				if (cellText.trim()) {
+					hasContent = true
+				}
+				rowTexts.push(cellText)
+			})
+
+			if (hasContent) {
+				excelText += rowTexts.join("\t") + "\n"
+			}
+
+			return true
+		})
+
+		excelText += "\n"
+	})
+
+	return excelText.trim()
+}

+ 2 - 0
src/integrations/misc/extract-text.ts

@@ -4,6 +4,7 @@ import pdf from "pdf-parse/lib/pdf-parse"
 import mammoth from "mammoth"
 import fs from "fs/promises"
 import { isBinaryFile } from "isbinaryfile"
+import { extractTextFromXLSX } from "./extract-text-from-xlsx"
 
 async function extractTextFromPDF(filePath: string): Promise<string> {
 	const dataBuffer = await fs.readFile(filePath)
@@ -37,6 +38,7 @@ const SUPPORTED_BINARY_FORMATS = {
 	".pdf": extractTextFromPDF,
 	".docx": extractTextFromDOCX,
 	".ipynb": extractTextFromIPYNB,
+	".xlsx": extractTextFromXLSX,
 } as const
 
 /**

+ 1 - 0
src/package.json

@@ -387,6 +387,7 @@
 		"delay": "^6.0.0",
 		"diff": "^5.2.0",
 		"diff-match-patch": "^1.0.5",
+		"exceljs": "^4.4.0",
 		"fast-deep-equal": "^3.1.3",
 		"fast-xml-parser": "^5.0.0",
 		"fastest-levenshtein": "^1.0.16",