cognova 0.2.11 → 0.2.13

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.
Files changed (290) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/{DwY7rCd_.js → -HePz7lp.js} +2 -2
  3. package/.output/public/_nuxt/{CY-cjAwJ.js → 4ALIM-zZ.js} +1 -1
  4. package/.output/public/_nuxt/{DXdwpJ-I.js → 5sUbkh_6.js} +2 -2
  5. package/.output/public/_nuxt/{_cy8R3nk.js → 6bB8Ev7a.js} +1 -1
  6. package/.output/public/_nuxt/{1zUTf4AP.js → 7rT1DCe2.js} +1 -1
  7. package/.output/public/_nuxt/{vIOxcXKR.js → 9O1MXkck.js} +1 -1
  8. package/.output/public/_nuxt/{7oCGSglN.js → 9kAKMaPC.js} +1 -1
  9. package/.output/public/_nuxt/{CVqlefTY.js → B-DMcEU9.js} +1 -1
  10. package/.output/public/_nuxt/{BS2ZNXI1.js → B0nzD8Sk.js} +1 -1
  11. package/.output/public/_nuxt/B2PqR2Vu.js +1 -0
  12. package/.output/public/_nuxt/{Db4KMnt8.js → B2wI_pYg.js} +1 -1
  13. package/.output/public/_nuxt/{zq-a1TeT.js → B5FuOh7E.js} +1 -1
  14. package/.output/public/_nuxt/B6om4MW2.js +1 -0
  15. package/.output/public/_nuxt/{Dya5oK8u.js → B7ikW4eF.js} +1 -1
  16. package/.output/public/_nuxt/{BYHCP8x7.js → B8DFEjxA.js} +1 -1
  17. package/.output/public/_nuxt/{6boMs_nF.js → B9kWNcU7.js} +1 -1
  18. package/.output/public/_nuxt/BAZ8sewA.js +1 -0
  19. package/.output/public/_nuxt/{BXuWCWsJ.js → BCPwL4ma.js} +1 -1
  20. package/.output/public/_nuxt/{DY3uK7wM.js → BCrlmHh_.js} +1 -1
  21. package/.output/public/_nuxt/{BR8bKz8h.js → BD-AFJWW.js} +1 -1
  22. package/.output/public/_nuxt/{BtHQ1T0f.js → BGcQMReV.js} +1 -1
  23. package/.output/public/_nuxt/{BDyn4ApB.js → BL1mkqgd.js} +3 -3
  24. package/.output/public/_nuxt/BNJJSRKh.js +1 -0
  25. package/.output/public/_nuxt/{D8lwrAYS.js → BNeeERBt.js} +1 -1
  26. package/.output/public/_nuxt/{CVFwOzbl.js → BSJb5amZ.js} +1 -1
  27. package/.output/public/_nuxt/{CEnSeCqn.js → BSsaE9Db.js} +1 -1
  28. package/.output/public/_nuxt/{DzA58_Lm.js → BVO3XpHT.js} +1 -1
  29. package/.output/public/_nuxt/{CuxrHsu-.js → BWuL2CzO.js} +1 -1
  30. package/.output/public/_nuxt/{0yk-pS3R.js → B_9zJTqe.js} +1 -1
  31. package/.output/public/_nuxt/{DcJbYLTp.js → B_Lr1vlV.js} +1 -1
  32. package/.output/public/_nuxt/{CZoEPC_Q.js → Bc8bTnkd.js} +3 -3
  33. package/.output/public/_nuxt/{BKXg-alD.js → BghuwIGa.js} +1 -1
  34. package/.output/public/_nuxt/{PP_4ebzl.js → BjsKx597.js} +1 -1
  35. package/.output/public/_nuxt/{DuvzM-P1.js → Bl0qBhCG.js} +1 -1
  36. package/.output/public/_nuxt/{B_wilgcd.js → Bl43dkDq.js} +1 -1
  37. package/.output/public/_nuxt/{JbHa4oXq.js → BnMTFF5R.js} +1 -1
  38. package/.output/public/_nuxt/{CY-QVcA5.js → BrX-PnSK.js} +2 -2
  39. package/.output/public/_nuxt/{DYbZBZet.js → BtV7oNn3.js} +1 -1
  40. package/.output/public/_nuxt/{apYB9dr5.js → BvFYnRMR.js} +1 -1
  41. package/.output/public/_nuxt/{x6FRJ5ac.js → BvTUBUNN.js} +1 -1
  42. package/.output/public/_nuxt/{wO6z2ugJ.js → ByLsRRoQ.js} +1 -1
  43. package/.output/public/_nuxt/{DC4idAGt.js → C1LcEMwB.js} +1 -1
  44. package/.output/public/_nuxt/{YX8avsvq.js → C2IiXp9N.js} +2 -2
  45. package/.output/public/_nuxt/{fbyIeNkc.js → C6OSRpRd.js} +1 -1
  46. package/.output/public/_nuxt/{3jQMk_5H.js → C796kFT9.js} +1 -1
  47. package/.output/public/_nuxt/{DEd2xVbS.js → C7m9zGMG.js} +1 -1
  48. package/.output/public/_nuxt/C8AtdrMH.js +1 -0
  49. package/.output/public/_nuxt/CAWbHTgy.js +1 -0
  50. package/.output/public/_nuxt/{ixlNW2So.js → CFmvNggn.js} +1 -1
  51. package/.output/public/_nuxt/{gTrVszwd.js → CGplHfmR.js} +1 -1
  52. package/.output/public/_nuxt/CJPrV25_.js +1 -0
  53. package/.output/public/_nuxt/{U1MWjQMi.js → CKKEWapF.js} +1 -1
  54. package/.output/public/_nuxt/{C6qKcgOY.js → CLkkdThv.js} +22 -22
  55. package/.output/public/_nuxt/CNjv28J2.js +1 -0
  56. package/.output/public/_nuxt/{BLnYhy_t.js → CRRw0Thu.js} +1 -1
  57. package/.output/public/_nuxt/{10_wwHSz.js → CT8s4rqa.js} +1 -1
  58. package/.output/public/_nuxt/{C27WoGNZ.js → CZSOrxUy.js} +1 -1
  59. package/.output/public/_nuxt/{DNP5E1bC.js → CaNEL9JC.js} +5 -5
  60. package/.output/public/_nuxt/{DhuOKJda.js → CbTLg9oX.js} +1 -1
  61. package/.output/public/_nuxt/CfCXpVn5.js +1 -0
  62. package/.output/public/_nuxt/{C9WIgRRL.js → ChmNlvWR.js} +1 -1
  63. package/.output/public/_nuxt/{t8aDAkZ5.js → CiNxIhaq.js} +1 -1
  64. package/.output/public/_nuxt/{E3rXPwU8.js → Ck0QYdxe.js} +1 -1
  65. package/.output/public/_nuxt/{DUVzIl3o.js → Ckb0pu4-.js} +1 -1
  66. package/.output/public/_nuxt/{vScW1Zgm.js → CkwPRs1D.js} +1 -1
  67. package/.output/public/_nuxt/CmEJg8C6.js +2 -0
  68. package/.output/public/_nuxt/{CTCcEJU3.js → CmL_vMJD.js} +1 -1
  69. package/.output/public/_nuxt/{Bciqk4dX.js → Cq8zPxkT.js} +1 -1
  70. package/.output/public/_nuxt/{D8M722pn.js → CseYuM6E.js} +1 -1
  71. package/.output/public/_nuxt/{D0ifH682.js → CxRnB4NC.js} +1 -1
  72. package/.output/public/_nuxt/{CVdCqaby.js → D0zMyG8n.js} +1 -1
  73. package/.output/public/_nuxt/{DhI5cA_n.js → D12qPplu.js} +1 -1
  74. package/.output/public/_nuxt/{Beom-INj.js → D1XYB283.js} +1 -1
  75. package/.output/public/_nuxt/{BUghTae1.js → D2NxqyzW.js} +1 -1
  76. package/.output/public/_nuxt/{CZRnNmU8.js → D5xyrvFu.js} +1 -1
  77. package/.output/public/_nuxt/{2dNDtTiv.js → D6tVZcRs.js} +3 -3
  78. package/.output/public/_nuxt/{EgKnQnf-.js → D7N3EZ-I.js} +1 -1
  79. package/.output/public/_nuxt/{C5R5QaCA.js → D8iBB0Py.js} +1 -1
  80. package/.output/public/_nuxt/{XCjS70z4.js → D8oIH8m1.js} +1 -1
  81. package/.output/public/_nuxt/{BA_pRWwX.js → DB2zZev7.js} +1 -1
  82. package/.output/public/_nuxt/{Cl-LOSDV.js → DBrOAqkm.js} +1 -1
  83. package/.output/public/_nuxt/{DO9SFIh1.js → DDXDFweK.js} +1 -1
  84. package/.output/public/_nuxt/{B93pdGAW.js → DFZkCmLQ.js} +1 -1
  85. package/.output/public/_nuxt/{CLfF6dSn.js → DFpmIy6J.js} +1 -1
  86. package/.output/public/_nuxt/{BfcLZ_fC.js → DKZmgHNU.js} +1 -1
  87. package/.output/public/_nuxt/{CPutXj8l.js → DNcOC5zj.js} +1 -1
  88. package/.output/public/_nuxt/{BHtY0l0l.js → DQhm-UPG.js} +1 -1
  89. package/.output/public/_nuxt/{C2FxZbO_.js → DR8tGJhL.js} +1 -1
  90. package/.output/public/_nuxt/{D4UJwDQJ.js → DVAEVTjy.js} +1 -1
  91. package/.output/public/_nuxt/{2nOqGUPr.js → DVqrPkCJ.js} +1 -1
  92. package/.output/public/_nuxt/DWZ9ieYa.js +1 -0
  93. package/.output/public/_nuxt/{nnQqD5pb.js → DWj6VsZv.js} +1 -1
  94. package/.output/public/_nuxt/{C2vq6Te8.js → DXJ9PxCv.js} +1 -1
  95. package/.output/public/_nuxt/{CobqYwkp.js → D_ziPROx.js} +1 -1
  96. package/.output/public/_nuxt/{GtEM7xVU.js → Da2vwoYG.js} +1 -1
  97. package/.output/public/_nuxt/{Cc-2ziaZ.js → Ddz3HcBB.js} +1 -1
  98. package/.output/public/_nuxt/{Di-Nc75e.js → DgYei0bK.js} +1 -1
  99. package/.output/public/_nuxt/{DQh6I9z9.js → Di_3GV_R.js} +1 -1
  100. package/.output/public/_nuxt/{ByYh0uRg.js → Djog1f_e.js} +1 -1
  101. package/.output/public/_nuxt/{C4mwL7SE.js → Dkap2-Pj.js} +2 -2
  102. package/.output/public/_nuxt/{D5q8SnqE.js → DlJe_sHk.js} +1 -1
  103. package/.output/public/_nuxt/DlPOQpH_.js +1 -0
  104. package/.output/public/_nuxt/{DTGenhcA.js → DmYWMtVA.js} +1 -1
  105. package/.output/public/_nuxt/DmxKU7sH.js +1 -0
  106. package/.output/public/_nuxt/{DtjjnHnt.js → DowkFZ2V.js} +1 -1
  107. package/.output/public/_nuxt/{0NJ3PaRM.js → DsodHRrQ.js} +1 -1
  108. package/.output/public/_nuxt/{Bk6JUtIo.js → DuzpliIL.js} +1 -1
  109. package/.output/public/_nuxt/DvHS7_h2.js +1 -0
  110. package/.output/public/_nuxt/DwSY6_2U.js +1 -0
  111. package/.output/public/_nuxt/{Djs0Tlpa.js → DypJaSm-.js} +1 -1
  112. package/.output/public/_nuxt/{C71_a1IX.js → DzKaXH6P.js} +1 -1
  113. package/.output/public/_nuxt/{CBTkrk2M.js → FK-jSuOT.js} +1 -1
  114. package/.output/public/_nuxt/{ILEvizzp.js → Fc2JsQt4.js} +1 -1
  115. package/.output/public/_nuxt/{BIXrSYwR.js → IYCBGuRS.js} +1 -1
  116. package/.output/public/_nuxt/{JX1oqJI9.js → LRO7YHoH.js} +1 -1
  117. package/.output/public/_nuxt/{Kw0zy3FG.js → NrRzUJNY.js} +1 -1
  118. package/.output/public/_nuxt/{DJTCT0bl.js → OeVS0Rsb.js} +1 -1
  119. package/.output/public/_nuxt/{BmOtR1wX.js → OlF0QnKF.js} +1 -1
  120. package/.output/public/_nuxt/{DTAStixR.js → QuAoqY7t.js} +1 -1
  121. package/.output/public/_nuxt/{8q5NepGW.js → RlXHkmOd.js} +2 -2
  122. package/.output/public/_nuxt/{rfGRTJJW.js → TxMdabnL.js} +1 -1
  123. package/.output/public/_nuxt/{CZVzFlpI.js → WZnvL9Dh.js} +1 -1
  124. package/.output/public/_nuxt/ZvG7a1W3.js +1 -0
  125. package/.output/public/_nuxt/{N5XtbYVD.js → aPNZBVrG.js} +1 -1
  126. package/.output/public/_nuxt/builds/latest.json +1 -1
  127. package/.output/public/_nuxt/builds/meta/495aca73-bfa5-456e-b96a-f02e009c72d3.json +1 -0
  128. package/.output/public/_nuxt/entry.C_udkZt9.css +1 -0
  129. package/.output/public/_nuxt/{D_4LPm1U.js → evY81hM3.js} +1 -1
  130. package/.output/public/_nuxt/{DzGy77Vr.js → h_bXa-f2.js} +1 -1
  131. package/.output/public/_nuxt/{D9nmzBAw.js → i6QBzaEi.js} +1 -1
  132. package/.output/public/_nuxt/{BGgwYWjH.js → iTVKJ1kw.js} +1 -1
  133. package/.output/public/_nuxt/{Um1vPiAz.js → j92uR3uR.js} +1 -1
  134. package/.output/public/_nuxt/jdUkeAj_.js +1 -0
  135. package/.output/public/_nuxt/{BmVLxnDU.js → jeRvcMHU.js} +1 -1
  136. package/.output/public/_nuxt/{SrncdpaW.js → kuv0zpKq.js} +1 -1
  137. package/.output/public/_nuxt/{DfF81NlA.js → l3lKQs7b.js} +1 -1
  138. package/.output/public/_nuxt/{4K03TkGA.js → pdYvpiud.js} +3 -3
  139. package/.output/public/_nuxt/{C39dQ88F.js → qIgZ7OXa.js} +1 -1
  140. package/.output/public/_nuxt/{DS2wStH1.js → qcVwZv8e.js} +1 -1
  141. package/.output/public/_nuxt/{g5MjDvm5.js → ru8fqqWs.js} +1 -1
  142. package/.output/public/_nuxt/{CQqCBrXj.js → sBzyvWMJ.js} +1 -1
  143. package/.output/public/_nuxt/{D3AZaldL.js → sgmhgpCS.js} +1 -1
  144. package/.output/public/_nuxt/{ChcO9s3o.js → y2jv0da-.js} +1 -1
  145. package/.output/public/_nuxt/{Dn5a-guE.js → yyS4-kAX.js} +1 -1
  146. package/.output/public/_nuxt/zIoBLBHF.js +1 -0
  147. package/.output/public/_nuxt/zpazwPgx.js +1 -0
  148. package/.output/server/chunks/build/CodeIcon-CWD5HcV7.mjs +1 -1
  149. package/.output/server/chunks/build/DropdownMenu-BBrV9nXz.mjs +1 -1
  150. package/.output/server/chunks/build/EditorToolbar-DIfb5arC.mjs +1 -1
  151. package/.output/server/chunks/build/Img-CWLmvN1t.mjs +1 -1
  152. package/.output/server/chunks/build/MDC-Dx0YPDhe.mjs +1 -1
  153. package/.output/server/chunks/build/Select-BB1oLrCD.mjs +1 -1
  154. package/.output/server/chunks/build/SelectMenu-DPssg6zD.mjs +1 -1
  155. package/.output/server/chunks/build/Table-DCwTlhCj.mjs +1 -1
  156. package/.output/server/chunks/build/Tooltip-TRyl6dje.mjs +1 -1
  157. package/.output/server/chunks/build/Tree-DUhXKd8y.mjs +1 -1
  158. package/.output/server/chunks/build/_uuid_-DfJaumTE.mjs +1 -1
  159. package/.output/server/chunks/build/{chat-CR3JIVEq.mjs → chat-m4-n9vC6.mjs} +516 -14
  160. package/.output/server/chunks/build/chat-m4-n9vC6.mjs.map +1 -0
  161. package/.output/server/chunks/build/client.precomputed.mjs +1 -1
  162. package/.output/server/chunks/build/cookie-C_iulBi6.mjs +1 -1
  163. package/.output/server/chunks/build/dashboard-CiVTAZuF.mjs +1 -1
  164. package/.output/server/chunks/build/docs-ChGwOPg5.mjs +1 -1
  165. package/.output/server/chunks/build/fetch-BB7Qzkwe.mjs +1 -1
  166. package/.output/server/chunks/build/index-CxDxc9fm.mjs +1 -1
  167. package/.output/server/chunks/build/server.mjs +2 -2
  168. package/.output/server/chunks/build/settings-B2KXoGcz.mjs +1 -1
  169. package/.output/server/chunks/build/useNotificationBus-BG5JNQf1.mjs +1 -1
  170. package/.output/server/chunks/build/view-n2sYa4Zh.mjs +1 -1
  171. package/.output/server/chunks/nitro/nitro.mjs +882 -866
  172. package/.output/server/chunks/routes/_ws/chat.mjs +40 -6
  173. package/.output/server/chunks/routes/_ws/chat.mjs.map +1 -1
  174. package/.output/server/chunks/routes/api/bridges/_id/contacts.get.mjs +84 -0
  175. package/.output/server/chunks/routes/api/bridges/_id/contacts.get.mjs.map +1 -0
  176. package/.output/server/chunks/routes/api/bridges/_id/send.post.mjs +70 -0
  177. package/.output/server/chunks/routes/api/bridges/_id/send.post.mjs.map +1 -0
  178. package/.output/server/chunks/routes/api/bridges/context.get.mjs +19 -18
  179. package/.output/server/chunks/routes/api/bridges/context.get.mjs.map +1 -1
  180. package/.output/server/chunks/routes/api/conversations/_id_.delete.mjs +1 -1
  181. package/.output/server/chunks/routes/api/conversations/_id_.get.mjs +1 -1
  182. package/.output/server/chunks/routes/api/dashboard/overview.get.mjs +1 -1
  183. package/.output/server/chunks/routes/api/documents/_id/public.get.mjs +1 -1
  184. package/.output/server/chunks/routes/api/documents/_id/restore.post.mjs +1 -1
  185. package/.output/server/chunks/routes/api/documents/by-path.post.mjs +1 -1
  186. package/.output/server/chunks/routes/api/documents/index.delete.mjs +1 -1
  187. package/.output/server/chunks/routes/api/documents/index.put.mjs +1 -1
  188. package/.output/server/chunks/routes/api/fs/delete.post.mjs +1 -1
  189. package/.output/server/chunks/routes/api/fs/list.get.mjs +1 -1
  190. package/.output/server/chunks/routes/api/fs/mkdir.post.mjs +1 -1
  191. package/.output/server/chunks/routes/api/fs/move.post.mjs +1 -1
  192. package/.output/server/chunks/routes/api/fs/read.post.mjs +1 -1
  193. package/.output/server/chunks/routes/api/fs/rename.post.mjs +1 -1
  194. package/.output/server/chunks/routes/api/fs/write.post.mjs +1 -1
  195. package/.output/server/chunks/routes/api/health.get.mjs +1 -1
  196. package/.output/server/chunks/routes/api/home.get.mjs +1 -1
  197. package/.output/server/chunks/routes/api/hooks/index.get.mjs +1 -1
  198. package/.output/server/chunks/routes/api/hooks/index.post.mjs +1 -1
  199. package/.output/server/chunks/routes/api/hooks/stats.get.mjs +1 -1
  200. package/.output/server/chunks/routes/api/index.get10.mjs +1 -1
  201. package/.output/server/chunks/routes/api/index.get3.mjs +1 -1
  202. package/.output/server/chunks/routes/api/index.get4.mjs +1 -1
  203. package/.output/server/chunks/routes/api/index.get5.mjs +1 -1
  204. package/.output/server/chunks/routes/api/index.get6.mjs +1 -1
  205. package/.output/server/chunks/routes/api/index.get7.mjs +1 -1
  206. package/.output/server/chunks/routes/api/index.get8.mjs +1 -1
  207. package/.output/server/chunks/routes/api/index.get9.mjs +1 -1
  208. package/.output/server/chunks/routes/api/index.post3.mjs +1 -1
  209. package/.output/server/chunks/routes/api/index.post4.mjs +1 -1
  210. package/.output/server/chunks/routes/api/index.post5.mjs +1 -1
  211. package/.output/server/chunks/routes/api/index.put.mjs +1 -1
  212. package/.output/server/chunks/routes/api/memory/_id_.delete.mjs +1 -1
  213. package/.output/server/chunks/routes/api/memory/context.get.mjs +1 -1
  214. package/.output/server/chunks/routes/api/memory/extract.post.mjs +1 -1
  215. package/.output/server/chunks/routes/api/memory/search.get.mjs +1 -1
  216. package/.output/server/chunks/routes/api/memory/store.post.mjs +1 -1
  217. package/.output/server/chunks/routes/api/projects/index.delete.mjs +1 -1
  218. package/.output/server/chunks/routes/api/projects/index.get.mjs +1 -1
  219. package/.output/server/chunks/routes/api/projects/index.put.mjs +1 -1
  220. package/.output/server/chunks/routes/api/secrets/_key_.delete.mjs +1 -1
  221. package/.output/server/chunks/routes/api/secrets/_key_.get.mjs +1 -1
  222. package/.output/server/chunks/routes/api/secrets/_key_.put.mjs +1 -1
  223. package/.output/server/chunks/routes/api/skills/_name/delete.post.mjs +1 -1
  224. package/.output/server/chunks/routes/api/skills/_name/export.get.mjs +1 -1
  225. package/.output/server/chunks/routes/api/skills/_name/files/create.post.mjs +1 -1
  226. package/.output/server/chunks/routes/api/skills/_name/files/delete.post.mjs +1 -1
  227. package/.output/server/chunks/routes/api/skills/_name/files/read.post.mjs +1 -1
  228. package/.output/server/chunks/routes/api/skills/_name/files/write.post.mjs +1 -1
  229. package/.output/server/chunks/routes/api/skills/_name/index.get.mjs +1 -1
  230. package/.output/server/chunks/routes/api/skills/_name/rename.post.mjs +1 -1
  231. package/.output/server/chunks/routes/api/skills/_name/toggle.post.mjs +1 -1
  232. package/.output/server/chunks/routes/api/skills/create.post.mjs +1 -1
  233. package/.output/server/chunks/routes/api/skills/generate.post.mjs +1 -1
  234. package/.output/server/chunks/routes/api/skills/import.post.mjs +1 -1
  235. package/.output/server/chunks/routes/api/skills/index.get.mjs +1 -1
  236. package/.output/server/chunks/routes/api/skills/index.get2.mjs +1 -1
  237. package/.output/server/chunks/routes/api/skills/library/check-updates.get.mjs +1 -1
  238. package/.output/server/chunks/routes/api/skills/library/install.post.mjs +1 -1
  239. package/.output/server/chunks/routes/api/tasks/_id/restore.post.mjs +1 -1
  240. package/.output/server/chunks/routes/api/tasks/index.delete.mjs +1 -1
  241. package/.output/server/chunks/routes/api/tasks/index.put.mjs +1 -1
  242. package/.output/server/chunks/routes/api/tasks/tags.get.mjs +1 -1
  243. package/.output/server/chunks/routes/api/usage/stats.get.mjs +1 -1
  244. package/.output/server/chunks/routes/api/user/email.patch.mjs +1 -1
  245. package/.output/server/chunks/routes/api/webhooks/bluebubbles.post.mjs +1 -1
  246. package/.output/server/chunks/routes/api/webhooks/telegram.post.mjs +1 -1
  247. package/.output/server/chunks/routes/notifications.mjs +1 -1
  248. package/.output/server/chunks/routes/renderer.mjs +1 -1
  249. package/.output/server/chunks/routes/terminal.mjs +1 -1
  250. package/.output/server/index.mjs +1 -1
  251. package/.output/server/package.json +1 -1
  252. package/Claude/skills/_lib/api.py +3 -2
  253. package/Claude/skills/bridge/SKILL.md +25 -1
  254. package/Claude/skills/bridge/bridge.py +64 -0
  255. package/app/components/chat/ChatInput.vue +133 -5
  256. package/app/components/chat/MessageBubble.vue +188 -6
  257. package/app/composables/useAttachments.ts +196 -0
  258. package/app/composables/useChat.ts +14 -2
  259. package/app/pages/chat.vue +4 -2
  260. package/package.json +1 -1
  261. package/server/api/bridges/[id]/contacts.get.ts +57 -0
  262. package/server/api/bridges/[id]/send.post.ts +40 -0
  263. package/server/api/bridges/context.get.ts +19 -18
  264. package/server/bridge/adapters/imessage.ts +9 -2
  265. package/server/bridge/responder.ts +3 -3
  266. package/server/routes/_ws/chat.ts +61 -7
  267. package/server/utils/chat-session-manager.ts +20 -2
  268. package/shared/types/index.ts +27 -1
  269. package/.output/public/_nuxt/BH0QDWEm.js +0 -1
  270. package/.output/public/_nuxt/BK8S2ony.js +0 -1
  271. package/.output/public/_nuxt/BZrSPX0S.js +0 -1
  272. package/.output/public/_nuxt/BaZwjF8o.js +0 -1
  273. package/.output/public/_nuxt/BkfI94R8.js +0 -1
  274. package/.output/public/_nuxt/C3NST0BU.js +0 -1
  275. package/.output/public/_nuxt/CKxSHyww.js +0 -1
  276. package/.output/public/_nuxt/CSKJ-Ahh.js +0 -1
  277. package/.output/public/_nuxt/CWyMCJQH.js +0 -1
  278. package/.output/public/_nuxt/CYPO5o_C.js +0 -1
  279. package/.output/public/_nuxt/CqNwHCpo.js +0 -1
  280. package/.output/public/_nuxt/CwPdIZ9J.js +0 -1
  281. package/.output/public/_nuxt/D5jZq8b3.js +0 -1
  282. package/.output/public/_nuxt/DH4YkDAi.js +0 -1
  283. package/.output/public/_nuxt/DPklr_tJ.js +0 -2
  284. package/.output/public/_nuxt/DQM4eBdt.js +0 -1
  285. package/.output/public/_nuxt/DUAAXJ6Q.js +0 -1
  286. package/.output/public/_nuxt/DmmdPt_5.js +0 -1
  287. package/.output/public/_nuxt/H6JbrRBU.js +0 -1
  288. package/.output/public/_nuxt/builds/meta/515be38f-7222-4e4b-80cb-cef7594b1834.json +0 -1
  289. package/.output/public/_nuxt/entry.NKPfH2kE.css +0 -1
  290. package/.output/server/chunks/build/chat-CR3JIVEq.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import type { ChatSessionStatus, ChatConnectionStatus } from '~~/shared/types'
2
+ import type { ChatSessionStatus, ChatConnectionStatus, ChatImageBlock, ChatDocumentBlock } from '~~/shared/types'
3
3
 
4
4
  const props = defineProps<{
5
5
  sessionStatus: ChatSessionStatus
@@ -7,16 +7,24 @@ const props = defineProps<{
7
7
  }>()
8
8
 
9
9
  const emit = defineEmits<{
10
- send: [message: string]
10
+ send: [message: string, attachments?: ChatImageBlock[], documents?: ChatDocumentBlock[]]
11
11
  interrupt: []
12
12
  }>()
13
13
 
14
+ const { attachments, addFiles, removeAttachment, clearAttachments, toImageBlocks, toDocumentBlocks } = useAttachments()
15
+
14
16
  const inputText = ref('')
15
17
  const textareaRef = ref<HTMLTextAreaElement | null>(null)
18
+ const fileInputRef = ref<HTMLInputElement | null>(null)
19
+ const isDragging = ref(false)
16
20
 
17
21
  const isStreaming = computed(() => props.sessionStatus === 'streaming')
18
22
  const isConnected = computed(() => props.connectionStatus === 'connected')
19
- const canSend = computed(() => isConnected.value && !isStreaming.value && inputText.value.trim().length > 0)
23
+ const canSend = computed(() =>
24
+ isConnected.value
25
+ && !isStreaming.value
26
+ && (inputText.value.trim().length > 0 || attachments.value.length > 0)
27
+ )
20
28
 
21
29
  function handleKeydown(e: KeyboardEvent) {
22
30
  if (e.key === 'Enter' && !e.shiftKey) {
@@ -27,8 +35,16 @@ function handleKeydown(e: KeyboardEvent) {
27
35
 
28
36
  function handleSend() {
29
37
  if (!canSend.value) return
30
- emit('send', inputText.value.trim())
38
+ const images = toImageBlocks()
39
+ const docs = toDocumentBlocks()
40
+ emit(
41
+ 'send',
42
+ inputText.value.trim(),
43
+ images.length > 0 ? images : undefined,
44
+ docs.length > 0 ? docs : undefined
45
+ )
31
46
  inputText.value = ''
47
+ clearAttachments()
32
48
  nextTick(() => {
33
49
  if (textareaRef.value) textareaRef.value.style.height = 'auto'
34
50
  })
@@ -39,10 +55,64 @@ function autoResize(e: Event) {
39
55
  target.style.height = 'auto'
40
56
  target.style.height = Math.min(target.scrollHeight, 200) + 'px'
41
57
  }
58
+
59
+ function handlePaste(e: ClipboardEvent) {
60
+ const files = Array.from(e.clipboardData?.files || [])
61
+ if (files.length > 0) {
62
+ e.preventDefault()
63
+ addFiles(files)
64
+ }
65
+ }
66
+
67
+ function handleDragOver(e: DragEvent) {
68
+ e.preventDefault()
69
+ isDragging.value = true
70
+ }
71
+
72
+ function handleDragLeave() {
73
+ isDragging.value = false
74
+ }
75
+
76
+ function handleDrop(e: DragEvent) {
77
+ e.preventDefault()
78
+ isDragging.value = false
79
+ const files = Array.from(e.dataTransfer?.files || [])
80
+ if (files.length > 0) addFiles(files)
81
+ }
82
+
83
+ function handleFileSelect(e: Event) {
84
+ const input = e.target as HTMLInputElement
85
+ if (input.files?.length) addFiles(input.files)
86
+ input.value = ''
87
+ }
88
+
89
+ const FILE_ACCEPT = [
90
+ 'image/jpeg', 'image/png', 'image/gif', 'image/webp',
91
+ 'application/pdf',
92
+ '.txt', '.md', '.js', '.ts', '.jsx', '.tsx', '.py', '.rb', '.rs', '.go',
93
+ '.java', '.c', '.cpp', '.h', '.cs', '.swift', '.kt',
94
+ '.json', '.yaml', '.yml', '.toml', '.ini',
95
+ '.xml', '.html', '.css', '.scss',
96
+ '.sh', '.sql', '.graphql', '.csv', '.log',
97
+ '.vue', '.svelte', '.prisma', '.lua', '.dart'
98
+ ].join(',')
42
99
  </script>
43
100
 
44
101
  <template>
45
- <div class="border-t border-default p-4">
102
+ <div
103
+ class="border-t border-default p-4 relative"
104
+ @dragover="handleDragOver"
105
+ @dragleave="handleDragLeave"
106
+ @drop="handleDrop"
107
+ >
108
+ <!-- Drag overlay -->
109
+ <div
110
+ v-if="isDragging"
111
+ class="absolute inset-0 bg-primary/10 border-2 border-dashed border-primary rounded-lg flex items-center justify-center z-10"
112
+ >
113
+ <span class="text-sm text-primary font-medium">Drop files here</span>
114
+ </div>
115
+
46
116
  <!-- Connection status -->
47
117
  <div
48
118
  v-if="!isConnected"
@@ -52,6 +122,45 @@ function autoResize(e: Event) {
52
122
  <span>{{ connectionStatus === 'connecting' ? 'Connecting...' : 'Disconnected' }}</span>
53
123
  </div>
54
124
 
125
+ <!-- Attachment preview strip -->
126
+ <div
127
+ v-if="attachments.length"
128
+ class="flex gap-2 mb-2 flex-wrap"
129
+ >
130
+ <div
131
+ v-for="att in attachments"
132
+ :key="att.id"
133
+ class="relative group rounded-lg overflow-hidden border border-default"
134
+ :class="att.kind === 'image' ? 'size-16' : 'h-10 px-3 flex items-center gap-1.5 bg-elevated/50'"
135
+ >
136
+ <!-- Image thumbnail -->
137
+ <img
138
+ v-if="att.kind === 'image'"
139
+ :src="att.previewUrl"
140
+ :alt="att.name"
141
+ class="size-full object-cover"
142
+ >
143
+ <!-- Document chip -->
144
+ <template v-else>
145
+ <UIcon
146
+ :name="att.name.endsWith('.pdf') ? 'i-lucide-file-text' : 'i-lucide-file-code'"
147
+ class="size-4 text-dimmed shrink-0"
148
+ />
149
+ <span class="text-xs truncate max-w-24">{{ att.name }}</span>
150
+ </template>
151
+
152
+ <button
153
+ class="absolute top-0 right-0 p-0.5 bg-error/80 rounded-bl-lg opacity-0 group-hover:opacity-100 transition-opacity"
154
+ @click="removeAttachment(att.id)"
155
+ >
156
+ <UIcon
157
+ name="i-lucide-x"
158
+ class="size-3 text-white"
159
+ />
160
+ </button>
161
+ </div>
162
+ </div>
163
+
55
164
  <div class="flex items-start gap-2">
56
165
  <textarea
57
166
  ref="textareaRef"
@@ -62,6 +171,25 @@ function autoResize(e: Event) {
62
171
  class="flex-1 resize-none bg-elevated/50 border border-default rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary/50 disabled:opacity-50"
63
172
  @keydown="handleKeydown"
64
173
  @input="autoResize"
174
+ @paste="handlePaste"
175
+ />
176
+
177
+ <input
178
+ ref="fileInputRef"
179
+ type="file"
180
+ :accept="FILE_ACCEPT"
181
+ multiple
182
+ class="hidden"
183
+ @change="handleFileSelect"
184
+ >
185
+
186
+ <UButton
187
+ icon="i-lucide-paperclip"
188
+ variant="ghost"
189
+ color="neutral"
190
+ size="md"
191
+ :disabled="!isConnected || isStreaming"
192
+ @click="fileInputRef?.click()"
65
193
  />
66
194
 
67
195
  <UButton
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import type { ChatMessage, ChatContentBlock, MessageSource } from '~~/shared/types'
2
+ import type { ChatMessage, ChatContentBlock, ChatImageBlock, ChatDocumentBlock, MessageSource, CodeLanguage } from '~~/shared/types'
3
3
 
4
4
  defineProps<{
5
5
  message: ChatMessage
@@ -12,6 +12,14 @@ function getTextContent(blocks: ChatContentBlock[]): string {
12
12
  .join('\n')
13
13
  }
14
14
 
15
+ function getImageBlocks(blocks: ChatContentBlock[]): ChatImageBlock[] {
16
+ return blocks.filter((b): b is ChatImageBlock => b.type === 'image')
17
+ }
18
+
19
+ function getDocumentBlocks(blocks: ChatContentBlock[]): ChatDocumentBlock[] {
20
+ return blocks.filter((b): b is ChatDocumentBlock => b.type === 'document')
21
+ }
22
+
15
23
  function getToolPairs(blocks: ChatContentBlock[]) {
16
24
  const tools: { name: string, id: string, result?: string, isError?: boolean }[] = []
17
25
  for (const block of blocks) {
@@ -28,6 +36,99 @@ function getToolPairs(blocks: ChatContentBlock[]) {
28
36
  return tools
29
37
  }
30
38
 
39
+ function getDocIcon(doc: ChatDocumentBlock): string {
40
+ if (doc.source.media_type === 'application/pdf') return 'i-lucide-file-text'
41
+ return 'i-lucide-file-code'
42
+ }
43
+
44
+ // Image preview
45
+ const previewImage = ref<ChatImageBlock | null>(null)
46
+ const previewOpen = computed({
47
+ get: () => previewImage.value !== null,
48
+ set: (v) => { if (!v) previewImage.value = null }
49
+ })
50
+
51
+ // Code/document preview
52
+ const previewDoc = ref<ChatDocumentBlock | null>(null)
53
+ const codePreviewOpen = computed({
54
+ get: () => previewDoc.value !== null,
55
+ set: (v) => { if (!v) previewDoc.value = null }
56
+ })
57
+
58
+ const codePreviewContent = computed(() => previewDoc.value?.source.data || '')
59
+
60
+ const extToLang: Record<string, CodeLanguage> = {
61
+ md: 'markdown',
62
+ js: 'javascript',
63
+ mjs: 'javascript',
64
+ cjs: 'javascript',
65
+ jsx: 'javascript',
66
+ ts: 'typescript',
67
+ tsx: 'typescript',
68
+ mts: 'typescript',
69
+ json: 'json',
70
+ html: 'html',
71
+ htm: 'html',
72
+ css: 'css',
73
+ scss: 'css',
74
+ sass: 'css',
75
+ less: 'css',
76
+ vue: 'vue',
77
+ svelte: 'html',
78
+ py: 'python',
79
+ sql: 'sql',
80
+ yaml: 'yaml',
81
+ yml: 'yaml',
82
+ sh: 'bash',
83
+ bash: 'bash',
84
+ zsh: 'bash',
85
+ fish: 'bash',
86
+ go: 'go',
87
+ rs: 'rust',
88
+ dockerfile: 'dockerfile',
89
+ java: 'java',
90
+ kt: 'java',
91
+ c: 'cpp',
92
+ cpp: 'cpp',
93
+ h: 'cpp',
94
+ hpp: 'cpp',
95
+ cs: 'cpp',
96
+ xml: 'xml',
97
+ graphql: 'plaintext',
98
+ gql: 'plaintext',
99
+ toml: 'plaintext',
100
+ ini: 'plaintext',
101
+ csv: 'plaintext',
102
+ txt: 'plaintext',
103
+ log: 'plaintext',
104
+ env: 'plaintext',
105
+ prisma: 'plaintext',
106
+ proto: 'plaintext'
107
+ }
108
+
109
+ const codePreviewLang = computed<CodeLanguage>(() => {
110
+ if (!previewDoc.value?.title) return 'plaintext'
111
+ const ext = previewDoc.value.title.split('.').pop()?.toLowerCase() || ''
112
+ return extToLang[ext] || 'plaintext'
113
+ })
114
+
115
+ function handleDocClick(doc: ChatDocumentBlock) {
116
+ if (doc.source.media_type === 'application/pdf') {
117
+ // Open PDF in new tab
118
+ const byteChars = atob(doc.source.data)
119
+ const bytes = new Uint8Array(byteChars.length)
120
+ for (let i = 0; i < byteChars.length; i++)
121
+ bytes[i] = byteChars.charCodeAt(i)
122
+ const blob = new Blob([bytes], { type: 'application/pdf' })
123
+ const url = URL.createObjectURL(blob)
124
+ window.open(url, '_blank')
125
+ setTimeout(() => URL.revokeObjectURL(url), 10000)
126
+ } else {
127
+ // Open text file in code viewer modal
128
+ previewDoc.value = doc
129
+ }
130
+ }
131
+
31
132
  const sourceIconMap: Record<string, string> = {
32
133
  telegram: 'i-simple-icons-telegram',
33
134
  discord: 'i-simple-icons-discord',
@@ -76,11 +177,52 @@ function formatTime(date: Date | string | undefined): string {
76
177
  : 'bg-muted'"
77
178
  >
78
179
  <!-- User message -->
79
- <div
80
- v-if="message.role === 'user'"
81
- class="text-sm whitespace-pre-wrap"
82
- >
83
- {{ getTextContent(message.content) }}
180
+ <div v-if="message.role === 'user'">
181
+ <!-- Image attachments -->
182
+ <div
183
+ v-if="getImageBlocks(message.content).length"
184
+ class="flex flex-wrap gap-2 mb-2"
185
+ >
186
+ <img
187
+ v-for="(img, i) in getImageBlocks(message.content)"
188
+ :key="i"
189
+ :src="`data:${img.source.media_type};base64,${img.source.data}`"
190
+ class="max-w-48 max-h-48 rounded-lg object-contain cursor-pointer hover:opacity-80 transition-opacity"
191
+ @click="previewImage = img"
192
+ >
193
+ </div>
194
+
195
+ <!-- Document attachments -->
196
+ <div
197
+ v-if="getDocumentBlocks(message.content).length"
198
+ class="flex flex-wrap gap-2 mb-2"
199
+ >
200
+ <button
201
+ v-for="(doc, i) in getDocumentBlocks(message.content)"
202
+ :key="i"
203
+ class="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg bg-elevated/50 border border-default text-xs hover:bg-elevated transition-colors cursor-pointer"
204
+ @click="handleDocClick(doc)"
205
+ >
206
+ <UIcon
207
+ :name="getDocIcon(doc)"
208
+ class="size-3.5 text-dimmed shrink-0"
209
+ />
210
+ <span class="truncate max-w-32">{{ doc.title || 'Document' }}</span>
211
+ <UIcon
212
+ v-if="doc.source.media_type === 'application/pdf'"
213
+ name="i-lucide-external-link"
214
+ class="size-3 text-dimmed shrink-0"
215
+ />
216
+ </button>
217
+ </div>
218
+
219
+ <!-- Text content -->
220
+ <div
221
+ v-if="getTextContent(message.content)"
222
+ class="text-sm whitespace-pre-wrap"
223
+ >
224
+ {{ getTextContent(message.content) }}
225
+ </div>
84
226
  </div>
85
227
 
86
228
  <!-- Assistant message -->
@@ -121,5 +263,45 @@ function formatTime(date: Date | string | undefined): string {
121
263
  {{ formatTime(message.createdAt) }}
122
264
  </div>
123
265
  </div>
266
+
267
+ <!-- Image preview modal -->
268
+ <UModal
269
+ v-model:open="previewOpen"
270
+ >
271
+ <template #content>
272
+ <div class="flex items-center justify-center">
273
+ <img
274
+ v-if="previewImage"
275
+ :src="`data:${previewImage.source.media_type};base64,${previewImage.source.data}`"
276
+ class="max-w-full max-h-[80vh] object-contain rounded-lg"
277
+ >
278
+ </div>
279
+ </template>
280
+ </UModal>
281
+
282
+ <!-- Code preview modal -->
283
+ <UModal
284
+ v-model:open="codePreviewOpen"
285
+ >
286
+ <template #header>
287
+ <div class="flex items-center gap-2">
288
+ <UIcon
289
+ name="i-lucide-file-code"
290
+ class="size-4 text-dimmed"
291
+ />
292
+ <span class="text-sm font-medium truncate">{{ previewDoc?.title || 'File' }}</span>
293
+ </div>
294
+ </template>
295
+ <template #body>
296
+ <div class="h-[60vh] overflow-hidden rounded-lg border border-default">
297
+ <EditorCodeEditor
298
+ v-if="previewDoc"
299
+ :model-value="codePreviewContent"
300
+ :language="codePreviewLang"
301
+ read-only
302
+ />
303
+ </div>
304
+ </template>
305
+ </UModal>
124
306
  </div>
125
307
  </template>
@@ -0,0 +1,196 @@
1
+ import type { ChatImageBlock, ChatImageMediaType, ChatDocumentBlock } from '~~/shared/types'
2
+
3
+ const IMAGE_TYPES: ChatImageMediaType[] = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
4
+
5
+ const TEXT_MIME_TYPES = new Set([
6
+ 'application/json',
7
+ 'application/javascript',
8
+ 'application/typescript',
9
+ 'application/xml',
10
+ 'application/x-yaml',
11
+ 'application/x-sh',
12
+ 'application/sql',
13
+ 'application/toml',
14
+ 'application/x-httpd-php'
15
+ ])
16
+
17
+ const MAX_FILE_SIZE = 20 * 1024 * 1024 // 20MB
18
+
19
+ export interface PendingImageAttachment {
20
+ id: string
21
+ kind: 'image'
22
+ previewUrl: string
23
+ base64: string
24
+ mediaType: ChatImageMediaType
25
+ name: string
26
+ }
27
+
28
+ export interface PendingDocumentAttachment {
29
+ id: string
30
+ kind: 'document'
31
+ data: string
32
+ sourceType: 'base64' | 'text'
33
+ name: string
34
+ }
35
+
36
+ export type PendingAttachment = PendingImageAttachment | PendingDocumentAttachment
37
+
38
+ function generateId(): string {
39
+ if (typeof crypto !== 'undefined' && crypto.randomUUID)
40
+ return crypto.randomUUID()
41
+ return Math.random().toString(36).slice(2)
42
+ }
43
+
44
+ function readFileAsBase64(file: File): Promise<string> {
45
+ return new Promise((resolve, reject) => {
46
+ const reader = new FileReader()
47
+ reader.onload = () => {
48
+ const result = reader.result as string
49
+ const base64 = result.split(',')[1]
50
+ if (base64) resolve(base64)
51
+ else reject(new Error('Failed to read file as base64'))
52
+ }
53
+ reader.onerror = () => reject(reader.error)
54
+ reader.readAsDataURL(file)
55
+ })
56
+ }
57
+
58
+ function readFileAsText(file: File): Promise<string> {
59
+ return new Promise((resolve, reject) => {
60
+ const reader = new FileReader()
61
+ reader.onload = () => resolve(reader.result as string)
62
+ reader.onerror = () => reject(reader.error)
63
+ reader.readAsText(file)
64
+ })
65
+ }
66
+
67
+ function isImageType(mime: string): mime is ChatImageMediaType {
68
+ return IMAGE_TYPES.includes(mime as ChatImageMediaType)
69
+ }
70
+
71
+ function isTextType(mime: string): boolean {
72
+ return mime.startsWith('text/') || TEXT_MIME_TYPES.has(mime)
73
+ }
74
+
75
+ function isPdfType(mime: string): boolean {
76
+ return mime === 'application/pdf'
77
+ }
78
+
79
+ export function useAttachments() {
80
+ const attachments = ref<PendingAttachment[]>([])
81
+ const toast = useToast()
82
+
83
+ async function addFiles(files: FileList | File[]) {
84
+ for (const file of Array.from(files)) {
85
+ if (file.size > MAX_FILE_SIZE) {
86
+ toast.add({ title: 'File too large', description: `${file.name} exceeds 20MB limit`, color: 'error' })
87
+ continue
88
+ }
89
+
90
+ const mime = file.type || ''
91
+
92
+ try {
93
+ if (isImageType(mime)) {
94
+ const base64 = await readFileAsBase64(file)
95
+ const previewUrl = URL.createObjectURL(file)
96
+ attachments.value.push({
97
+ id: generateId(),
98
+ kind: 'image',
99
+ previewUrl,
100
+ base64,
101
+ mediaType: mime,
102
+ name: file.name
103
+ })
104
+ } else if (isPdfType(mime)) {
105
+ const base64 = await readFileAsBase64(file)
106
+ attachments.value.push({
107
+ id: generateId(),
108
+ kind: 'document',
109
+ data: base64,
110
+ sourceType: 'base64',
111
+ name: file.name
112
+ })
113
+ } else if (isTextType(mime) || isLikelyTextFile(file.name)) {
114
+ const text = await readFileAsText(file)
115
+ attachments.value.push({
116
+ id: generateId(),
117
+ kind: 'document',
118
+ data: text,
119
+ sourceType: 'text',
120
+ name: file.name
121
+ })
122
+ } else {
123
+ toast.add({ title: 'Unsupported file type', description: `${file.name} (${mime || 'unknown type'})`, color: 'error' })
124
+ }
125
+ } catch {
126
+ toast.add({ title: 'Failed to read file', description: file.name, color: 'error' })
127
+ }
128
+ }
129
+ }
130
+
131
+ function removeAttachment(id: string) {
132
+ const idx = attachments.value.findIndex(a => a.id === id)
133
+ if (idx !== -1) {
134
+ const att = attachments.value[idx]!
135
+ if (att.kind === 'image') URL.revokeObjectURL(att.previewUrl)
136
+ attachments.value.splice(idx, 1)
137
+ }
138
+ }
139
+
140
+ function clearAttachments() {
141
+ for (const att of attachments.value)
142
+ if (att.kind === 'image') URL.revokeObjectURL(att.previewUrl)
143
+ attachments.value = []
144
+ }
145
+
146
+ function toImageBlocks(): ChatImageBlock[] {
147
+ return attachments.value
148
+ .filter((a): a is PendingImageAttachment => a.kind === 'image')
149
+ .map(att => ({
150
+ type: 'image' as const,
151
+ source: {
152
+ type: 'base64' as const,
153
+ media_type: att.mediaType,
154
+ data: att.base64
155
+ }
156
+ }))
157
+ }
158
+
159
+ function toDocumentBlocks(): ChatDocumentBlock[] {
160
+ return attachments.value
161
+ .filter((a): a is PendingDocumentAttachment => a.kind === 'document')
162
+ .map(att => ({
163
+ type: 'document' as const,
164
+ source: att.sourceType === 'base64'
165
+ ? { type: 'base64' as const, media_type: 'application/pdf' as const, data: att.data }
166
+ : { type: 'text' as const, media_type: 'text/plain' as const, data: att.data },
167
+ title: att.name
168
+ }))
169
+ }
170
+
171
+ return {
172
+ attachments: readonly(attachments),
173
+ addFiles,
174
+ removeAttachment,
175
+ clearAttachments,
176
+ toImageBlocks,
177
+ toDocumentBlocks
178
+ }
179
+ }
180
+
181
+ /** Fallback for files with empty MIME but recognizable extensions */
182
+ function isLikelyTextFile(name: string): boolean {
183
+ const ext = name.split('.').pop()?.toLowerCase()
184
+ if (!ext) return false
185
+ const textExtensions = new Set([
186
+ 'txt', 'md', 'js', 'ts', 'jsx', 'tsx', 'py', 'rb', 'rs', 'go',
187
+ 'java', 'c', 'cpp', 'h', 'hpp', 'cs', 'swift', 'kt', 'scala',
188
+ 'json', 'yaml', 'yml', 'toml', 'ini', 'cfg', 'conf',
189
+ 'xml', 'html', 'htm', 'css', 'scss', 'sass', 'less',
190
+ 'sh', 'bash', 'zsh', 'fish', 'ps1', 'bat', 'cmd',
191
+ 'sql', 'graphql', 'gql', 'proto',
192
+ 'csv', 'tsv', 'log', 'env', 'gitignore', 'dockerignore',
193
+ 'vue', 'svelte', 'astro', 'prisma', 'tf', 'r', 'lua', 'dart'
194
+ ])
195
+ return textExtensions.has(ext)
196
+ }
@@ -2,6 +2,8 @@ import type {
2
2
  ChatConversation,
3
3
  ChatMessage,
4
4
  ChatContentBlock,
5
+ ChatImageBlock,
6
+ ChatDocumentBlock,
5
7
  ChatServerMessage,
6
8
  ChatSessionStatus,
7
9
  ChatConnectionStatus
@@ -200,21 +202,31 @@ export function useChat() {
200
202
  }
201
203
  }
202
204
 
203
- function sendMessage(message: string) {
205
+ function sendMessage(message: string, attachments?: ChatImageBlock[], documents?: ChatDocumentBlock[]) {
204
206
  if (!ws.value || ws.value.readyState !== WebSocket.OPEN) return
205
207
 
208
+ // Build content blocks for local display
209
+ const content: ChatContentBlock[] = []
210
+ if (attachments?.length)
211
+ for (const img of attachments) content.push(img)
212
+ if (documents?.length)
213
+ for (const doc of documents) content.push(doc)
214
+ if (message) content.push({ type: 'text', text: message })
215
+
206
216
  // Add user message locally
207
217
  messages.value.push({
208
218
  id: generateId(),
209
219
  conversationId: activeConversationId.value || '',
210
220
  role: 'user',
211
- content: [{ type: 'text', text: message }],
221
+ content,
212
222
  createdAt: new Date()
213
223
  })
214
224
 
215
225
  ws.value.send(JSON.stringify({
216
226
  type: 'chat:send',
217
227
  message,
228
+ attachments: attachments?.length ? attachments : undefined,
229
+ documents: documents?.length ? documents : undefined,
218
230
  conversationId: activeConversationId.value
219
231
  }))
220
232
  }
@@ -1,4 +1,6 @@
1
1
  <script setup lang="ts">
2
+ import type { ChatImageBlock, ChatDocumentBlock } from '~~/shared/types'
3
+
2
4
  definePageMeta({
3
5
  layout: 'dashboard',
4
6
  middleware: 'auth'
@@ -53,8 +55,8 @@ onMounted(() => {
53
55
  }
54
56
  })
55
57
 
56
- function handleSend(message: string) {
57
- sendMessage(message)
58
+ function handleSend(message: string, attachments?: ChatImageBlock[], documents?: ChatDocumentBlock[]) {
59
+ sendMessage(message, attachments, documents)
58
60
  nextTick(scrollToBottom)
59
61
  }
60
62
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cognova",
3
3
  "type": "module",
4
- "version": "0.2.11",
4
+ "version": "0.2.13",
5
5
  "description": "Personal knowledge management system with Claude Code integration",
6
6
  "repository": {
7
7
  "type": "git",