research-copilot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (395) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -0
  3. package/app/build/icon.icns +0 -0
  4. package/app/build/icon.ico +0 -0
  5. package/app/build/icon.png +0 -0
  6. package/app/out/main/index.mjs +6719 -0
  7. package/app/out/preload/index.js +141 -0
  8. package/app/out/renderer/assets/Inter-Variable-Latin-8kRkwJBP.woff2 +0 -0
  9. package/app/out/renderer/assets/Inter-Variable-LatinExt-B_-bZUTo.woff2 +0 -0
  10. package/app/out/renderer/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  11. package/app/out/renderer/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  12. package/app/out/renderer/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  13. package/app/out/renderer/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  14. package/app/out/renderer/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  15. package/app/out/renderer/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  16. package/app/out/renderer/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  17. package/app/out/renderer/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  18. package/app/out/renderer/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  19. package/app/out/renderer/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  20. package/app/out/renderer/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  21. package/app/out/renderer/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  22. package/app/out/renderer/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  23. package/app/out/renderer/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  24. package/app/out/renderer/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  25. package/app/out/renderer/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  26. package/app/out/renderer/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  27. package/app/out/renderer/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  28. package/app/out/renderer/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  29. package/app/out/renderer/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  30. package/app/out/renderer/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  31. package/app/out/renderer/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  32. package/app/out/renderer/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  33. package/app/out/renderer/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  34. package/app/out/renderer/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  35. package/app/out/renderer/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  36. package/app/out/renderer/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  37. package/app/out/renderer/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  38. package/app/out/renderer/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  39. package/app/out/renderer/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  40. package/app/out/renderer/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  41. package/app/out/renderer/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  42. package/app/out/renderer/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  43. package/app/out/renderer/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  44. package/app/out/renderer/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  45. package/app/out/renderer/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  46. package/app/out/renderer/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  47. package/app/out/renderer/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  48. package/app/out/renderer/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  49. package/app/out/renderer/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  50. package/app/out/renderer/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  51. package/app/out/renderer/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  52. package/app/out/renderer/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  53. package/app/out/renderer/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  54. package/app/out/renderer/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  55. package/app/out/renderer/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  56. package/app/out/renderer/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  57. package/app/out/renderer/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  58. package/app/out/renderer/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  59. package/app/out/renderer/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  60. package/app/out/renderer/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  61. package/app/out/renderer/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  62. package/app/out/renderer/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  63. package/app/out/renderer/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  64. package/app/out/renderer/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  65. package/app/out/renderer/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  66. package/app/out/renderer/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  67. package/app/out/renderer/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  68. package/app/out/renderer/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  69. package/app/out/renderer/assets/MilkdownMarkdownEditor-bLPxrCVb.js +89821 -0
  70. package/app/out/renderer/assets/MilkdownMarkdownEditor-tTNRIB2K.css +2555 -0
  71. package/app/out/renderer/assets/Tableau10-BqnYsPR6.js +9 -0
  72. package/app/out/renderer/assets/apl-fqmucPXA.js +140 -0
  73. package/app/out/renderer/assets/arc-J47ePHZ2.js +132 -0
  74. package/app/out/renderer/assets/array-DgktLKBx.js +6 -0
  75. package/app/out/renderer/assets/asciiarmor-DucZyvP0.js +56 -0
  76. package/app/out/renderer/assets/asn1-BnOEsgAm.js +144 -0
  77. package/app/out/renderer/assets/asterisk-QAlztEwS.js +345 -0
  78. package/app/out/renderer/assets/blockDiagram-c4efeb88-5uRQXgQJ.js +1817 -0
  79. package/app/out/renderer/assets/brainfuck-DZVCuF_t.js +53 -0
  80. package/app/out/renderer/assets/c4Diagram-c83219d4-C4iCTPEL.js +2464 -0
  81. package/app/out/renderer/assets/channel-ZAmhHE3g.js +7 -0
  82. package/app/out/renderer/assets/classDiagram-beda092f-7NOZxq_W.js +357 -0
  83. package/app/out/renderer/assets/classDiagram-v2-2358418a-5fPT-cUH.js +291 -0
  84. package/app/out/renderer/assets/clike-xqXYL6ge.js +805 -0
  85. package/app/out/renderer/assets/clojure-BhXMqnxz.js +849 -0
  86. package/app/out/renderer/assets/clone-CsnzsYXQ.js +8 -0
  87. package/app/out/renderer/assets/cmake-BGaNd9E7.js +71 -0
  88. package/app/out/renderer/assets/cobol-4yqQntpt.js +120 -0
  89. package/app/out/renderer/assets/coffeescript-D2dXvhEc.js +308 -0
  90. package/app/out/renderer/assets/commonlisp-CF_VNHQR.js +130 -0
  91. package/app/out/renderer/assets/createText-1719965b-Cji7KN4K.js +4904 -0
  92. package/app/out/renderer/assets/crystal-DyuLTqLs.js +398 -0
  93. package/app/out/renderer/assets/css-c-jst79C.js +1783 -0
  94. package/app/out/renderer/assets/cypher-Dlu_3r4V.js +121 -0
  95. package/app/out/renderer/assets/d-UURgV0Ux.js +179 -0
  96. package/app/out/renderer/assets/diff-B_Bi2Crb.js +25 -0
  97. package/app/out/renderer/assets/dockerfile-Bvk733Ga.js +201 -0
  98. package/app/out/renderer/assets/dtd-Dy74G54E.js +114 -0
  99. package/app/out/renderer/assets/dylan-TSb-Nfix.js +314 -0
  100. package/app/out/renderer/assets/ebnf-DAomQUbD.js +139 -0
  101. package/app/out/renderer/assets/ecl-B59qGGVg.js +178 -0
  102. package/app/out/renderer/assets/edges-96097737-CD0EvAZQ.js +1844 -0
  103. package/app/out/renderer/assets/eiffel-Dze7nlu3.js +134 -0
  104. package/app/out/renderer/assets/elm-DG7jkhNZ.js +176 -0
  105. package/app/out/renderer/assets/erDiagram-0228fc6a-DRYXBpi7.js +1321 -0
  106. package/app/out/renderer/assets/erlang-BO6gOnGA.js +674 -0
  107. package/app/out/renderer/assets/factor-CMxFHDqz.js +65 -0
  108. package/app/out/renderer/assets/fcl-CDDUNjTj.js +141 -0
  109. package/app/out/renderer/assets/flowDb-c6c81e3f-CuoIN-Cy.js +1713 -0
  110. package/app/out/renderer/assets/flowDiagram-50d868cf-CPWPLOml.js +1272 -0
  111. package/app/out/renderer/assets/flowDiagram-v2-4f6560a1-C_R12s4S.js +33 -0
  112. package/app/out/renderer/assets/flowchart-elk-definition-6af322e1-BdKUSFpi.js +92921 -0
  113. package/app/out/renderer/assets/forth-B9D2JCeE.js +116 -0
  114. package/app/out/renderer/assets/fortran-CAG2BFbe.js +467 -0
  115. package/app/out/renderer/assets/ganttDiagram-a2739b55-ygqT5HlG.js +3399 -0
  116. package/app/out/renderer/assets/gas-d3KEcW3x.js +294 -0
  117. package/app/out/renderer/assets/gherkin-DhZlEZiy.js +115 -0
  118. package/app/out/renderer/assets/gitGraphDiagram-82fe8481-D97GT4iA.js +1791 -0
  119. package/app/out/renderer/assets/graph-DpC13d95.js +1237 -0
  120. package/app/out/renderer/assets/groovy-CpwJiBl7.js +223 -0
  121. package/app/out/renderer/assets/haskell-BlGBCCe3.js +459 -0
  122. package/app/out/renderer/assets/haxe-7MlzfeYV.js +514 -0
  123. package/app/out/renderer/assets/http-BqypyemW.js +79 -0
  124. package/app/out/renderer/assets/idl-4HIGJlDI.js +985 -0
  125. package/app/out/renderer/assets/index-4-ziknCv.js +292 -0
  126. package/app/out/renderer/assets/index-5325376f-Bbs7Fbqr.js +663 -0
  127. package/app/out/renderer/assets/index-B2jip-rk.js +2489 -0
  128. package/app/out/renderer/assets/index-BKTVfokE.js +312 -0
  129. package/app/out/renderer/assets/index-BiJbFgVG.js +118 -0
  130. package/app/out/renderer/assets/index-Bn433Fat.js +83 -0
  131. package/app/out/renderer/assets/index-BqDyyRCx.js +39679 -0
  132. package/app/out/renderer/assets/index-BzFMeMPn.js +158 -0
  133. package/app/out/renderer/assets/index-C-_uCjZJ.css +2701 -0
  134. package/app/out/renderer/assets/index-C1ithNW1.js +1765 -0
  135. package/app/out/renderer/assets/index-CAJkRYkO.js +407 -0
  136. package/app/out/renderer/assets/index-CleO0-yj.js +690 -0
  137. package/app/out/renderer/assets/index-Cq4MH3sY.js +1020 -0
  138. package/app/out/renderer/assets/index-CtA0Xj22.js +705 -0
  139. package/app/out/renderer/assets/index-CvAZkqBZ.js +83 -0
  140. package/app/out/renderer/assets/index-D3UDN-5c.js +152 -0
  141. package/app/out/renderer/assets/index-D4F9R5ao.js +179 -0
  142. package/app/out/renderer/assets/index-D6RguhZ5.js +328 -0
  143. package/app/out/renderer/assets/index-DnEowqXv.js +386 -0
  144. package/app/out/renderer/assets/index-K8c8Mqdy.js +98 -0
  145. package/app/out/renderer/assets/index-Kh14gO6K.js +62 -0
  146. package/app/out/renderer/assets/index-WFd2jRnA.js +333 -0
  147. package/app/out/renderer/assets/index-WgMfkRFp.js +313 -0
  148. package/app/out/renderer/assets/index-Y4lKyF6t.js +1042 -0
  149. package/app/out/renderer/assets/index-fx307_f1.js +643 -0
  150. package/app/out/renderer/assets/infoDiagram-8eee0895-ptaVSwzq.js +511 -0
  151. package/app/out/renderer/assets/init-ZxktEp_H.js +16 -0
  152. package/app/out/renderer/assets/javascript-C3MnDRiU.js +994 -0
  153. package/app/out/renderer/assets/journeyDiagram-c64418c1-aloEGOQp.js +1184 -0
  154. package/app/out/renderer/assets/julia-Bs6JJhYG.js +407 -0
  155. package/app/out/renderer/assets/layout-ZeuHE_aY.js +2217 -0
  156. package/app/out/renderer/assets/line-CAgaGl-S.js +45 -0
  157. package/app/out/renderer/assets/linear-DIg7lTe1.js +539 -0
  158. package/app/out/renderer/assets/livescript-DmzgM3Yt.js +296 -0
  159. package/app/out/renderer/assets/lua-8cJgIlqe.js +256 -0
  160. package/app/out/renderer/assets/mathematica-DNLOL9PQ.js +110 -0
  161. package/app/out/renderer/assets/mbox-Ga7d4MMN.js +117 -0
  162. package/app/out/renderer/assets/mindmap-definition-8da855dc-B8XVoUxz.js +36054 -0
  163. package/app/out/renderer/assets/mirc-Dma3B8rS.js +107 -0
  164. package/app/out/renderer/assets/mllike-DHn7xckP.js +334 -0
  165. package/app/out/renderer/assets/modelica-0d55jYY0.js +147 -0
  166. package/app/out/renderer/assets/mscgen-DdqZYINH.js +135 -0
  167. package/app/out/renderer/assets/mumps-Btr8VblO.js +93 -0
  168. package/app/out/renderer/assets/nginx-DTDtBDVN.js +141 -0
  169. package/app/out/renderer/assets/nsis-3zG7tgur.js +62 -0
  170. package/app/out/renderer/assets/ntriples-CvgOYMpL.js +153 -0
  171. package/app/out/renderer/assets/octave-DYBj3-tl.js +200 -0
  172. package/app/out/renderer/assets/ordinal-DSZU4PqD.js +76 -0
  173. package/app/out/renderer/assets/oz-R_e8WMIi.js +231 -0
  174. package/app/out/renderer/assets/pascal-GD8iposT.js +105 -0
  175. package/app/out/renderer/assets/path-Cp2qmpkd.js +109 -0
  176. package/app/out/renderer/assets/perl-DL9mHpoi.js +1105 -0
  177. package/app/out/renderer/assets/pieDiagram-a8764435-DlwoeBU2.js +770 -0
  178. package/app/out/renderer/assets/pig-C_4T4YIV.js +101 -0
  179. package/app/out/renderer/assets/powershell-B0suO7Vd.js +328 -0
  180. package/app/out/renderer/assets/properties-BR-vP1aU.js +58 -0
  181. package/app/out/renderer/assets/protobuf-BxgpyhoW.js +77 -0
  182. package/app/out/renderer/assets/pug-By0kVCfm.js +405 -0
  183. package/app/out/renderer/assets/puppet-Bdao66PW.js +137 -0
  184. package/app/out/renderer/assets/python-CvWbmiX4.js +427 -0
  185. package/app/out/renderer/assets/q-CrbCVq4a.js +131 -0
  186. package/app/out/renderer/assets/quadrantDiagram-1e28029f-BaSi1XB4.js +1200 -0
  187. package/app/out/renderer/assets/r-V7nswm59.js +170 -0
  188. package/app/out/renderer/assets/requirementDiagram-08caed73-D3EFyegZ.js +1092 -0
  189. package/app/out/renderer/assets/rpm-C-DLY-If.js +109 -0
  190. package/app/out/renderer/assets/ruby-JDKLJNK0.js +330 -0
  191. package/app/out/renderer/assets/sankeyDiagram-a04cb91d-Cv44AsnM.js +1174 -0
  192. package/app/out/renderer/assets/sas-D2UG-yhZ.js +207 -0
  193. package/app/out/renderer/assets/scheme-BKzrkGJD.js +222 -0
  194. package/app/out/renderer/assets/sequenceDiagram-c5b8d532-CuUBu-x4.js +3337 -0
  195. package/app/out/renderer/assets/shell-BlsXDxCn.js +222 -0
  196. package/app/out/renderer/assets/sieve-CjwBwOY5.js +135 -0
  197. package/app/out/renderer/assets/simple-mode-DMneyfDu.js +130 -0
  198. package/app/out/renderer/assets/smalltalk-BOIGQuhN.js +121 -0
  199. package/app/out/renderer/assets/solr-CwD7U71z.js +69 -0
  200. package/app/out/renderer/assets/sparql-DYskk2vE.js +249 -0
  201. package/app/out/renderer/assets/spreadsheet-Bgtt3oLP.js +87 -0
  202. package/app/out/renderer/assets/sql-BSrOzCRI.js +354 -0
  203. package/app/out/renderer/assets/stateDiagram-1ecb1508-BOU34Zp4.js +454 -0
  204. package/app/out/renderer/assets/stateDiagram-v2-c2b004d7-BgRoffou.js +326 -0
  205. package/app/out/renderer/assets/stex-B6LNC55o.js +231 -0
  206. package/app/out/renderer/assets/styles-b4e223ce-BMr9TPuj.js +1483 -0
  207. package/app/out/renderer/assets/styles-ca3715f6-DgbNw99p.js +1363 -0
  208. package/app/out/renderer/assets/styles-d45a18b0-DtRYKYKf.js +574 -0
  209. package/app/out/renderer/assets/stylus-BkS-boTH.js +565 -0
  210. package/app/out/renderer/assets/svgDrawCommon-b86b1483-Bein03PD.js +100 -0
  211. package/app/out/renderer/assets/swift-FRZi1uvB.js +291 -0
  212. package/app/out/renderer/assets/tcl-CUcaCdmq.js +114 -0
  213. package/app/out/renderer/assets/textile-BnFpjsrl.js +414 -0
  214. package/app/out/renderer/assets/tiddlywiki-CjprD-Qp.js +218 -0
  215. package/app/out/renderer/assets/tiki-B4EPSQ1G.js +265 -0
  216. package/app/out/renderer/assets/timeline-definition-faaaa080-BlWpLE_4.js +1212 -0
  217. package/app/out/renderer/assets/toml-BOuWGMcf.js +76 -0
  218. package/app/out/renderer/assets/troff-E1bJ0PPL.js +61 -0
  219. package/app/out/renderer/assets/ttcn-cfg-Dc39-fIP.js +133 -0
  220. package/app/out/renderer/assets/ttcn-tKd4HLu4.js +192 -0
  221. package/app/out/renderer/assets/turtle-Dq7-1WAf.js +124 -0
  222. package/app/out/renderer/assets/vb-Dp90gtsv.js +196 -0
  223. package/app/out/renderer/assets/vbscript-CI6_mxxU.js +479 -0
  224. package/app/out/renderer/assets/velocity-BwIZK1TH.js +149 -0
  225. package/app/out/renderer/assets/verilog-DDCYnHN8.js +430 -0
  226. package/app/out/renderer/assets/vhdl-DCkMIyT9.js +158 -0
  227. package/app/out/renderer/assets/webidl-BTLTThCm.js +204 -0
  228. package/app/out/renderer/assets/xquery-BgiOC5Ce.js +525 -0
  229. package/app/out/renderer/assets/xychartDiagram-f5964ef8-Bhga-YXm.js +1799 -0
  230. package/app/out/renderer/assets/yacas-b5lAVEIl.js +130 -0
  231. package/app/out/renderer/assets/z80-BZV19vqv.js +93 -0
  232. package/app/out/renderer/index.html +13 -0
  233. package/app/out/skills/community-builtin/README.md +29 -0
  234. package/app/out/skills/community-builtin/document-docx/SKILL.md +44 -0
  235. package/app/out/skills/community-builtin/document-docx/scripts/docx-to-markdown.sh +20 -0
  236. package/app/out/skills/community-builtin/document-docx/scripts/extract-docx-text.sh +28 -0
  237. package/app/out/skills/community-builtin/document-docx/scripts/init-docx-template.sh +32 -0
  238. package/app/out/skills/community-builtin/document-docx/scripts/setup-docx-tools.sh +10 -0
  239. package/app/out/skills/community-builtin/markitdown/SKILL.md +105 -0
  240. package/app/out/skills/community-builtin/markitdown/scripts/batch-convert.sh +40 -0
  241. package/app/out/skills/community-builtin/markitdown/scripts/convert-file.sh +24 -0
  242. package/app/out/skills/community-builtin/markitdown/scripts/setup-markitdown.sh +10 -0
  243. package/app/out/skills/community-builtin/repo-quick-audit/SKILL.md +27 -0
  244. package/app/out/skills/community-builtin/repo-quick-audit/scripts/audit-basics.sh +19 -0
  245. package/app/out/skills/research-pilot-default-project-skills/README.md +23 -0
  246. package/app/out/skills/research-pilot-default-project-skills/citation-management/SKILL.md +39 -0
  247. package/app/out/skills/research-pilot-default-project-skills/citation-management/scripts/doi-to-bibtex.sh +25 -0
  248. package/app/out/skills/research-pilot-default-project-skills/citation-management/scripts/normalize-bibtex-keys.sh +51 -0
  249. package/app/out/skills/research-pilot-default-project-skills/citation-management/scripts/setup-citation-tools.sh +10 -0
  250. package/app/out/skills/research-pilot-default-project-skills/citation-management/scripts/validate-bib.sh +31 -0
  251. package/app/out/skills/research-pilot-default-project-skills/matplotlib/SKILL.md +34 -0
  252. package/app/out/skills/research-pilot-default-project-skills/matplotlib/references/api_reference.md +412 -0
  253. package/app/out/skills/research-pilot-default-project-skills/matplotlib/references/common_issues.md +563 -0
  254. package/app/out/skills/research-pilot-default-project-skills/matplotlib/references/plot_types.md +476 -0
  255. package/app/out/skills/research-pilot-default-project-skills/matplotlib/references/styling_guide.md +589 -0
  256. package/app/out/skills/research-pilot-default-project-skills/matplotlib/references/subagent_quickstart.md +30 -0
  257. package/app/out/skills/research-pilot-default-project-skills/matplotlib/scripts/plot_template.py +401 -0
  258. package/app/out/skills/research-pilot-default-project-skills/matplotlib/scripts/style_configurator.py +409 -0
  259. package/app/out/skills/research-pilot-default-project-skills/research-grants/SKILL.md +38 -0
  260. package/app/out/skills/research-pilot-default-project-skills/research-grants/scripts/check-grant-compliance.sh +40 -0
  261. package/app/out/skills/research-pilot-default-project-skills/research-grants/scripts/grant-summary-card.sh +32 -0
  262. package/app/out/skills/research-pilot-default-project-skills/research-grants/scripts/init-grant-structure.sh +70 -0
  263. package/app/package.json +77 -0
  264. package/bin/cli.mjs +56 -0
  265. package/lib/README.md +145 -0
  266. package/lib/skills/_generated.ts +13 -0
  267. package/lib/skills/builtin/brainstorming-research-ideas/SKILL.md +280 -0
  268. package/lib/skills/builtin/coding/SKILL.md +114 -0
  269. package/lib/skills/builtin/creative-thinking-for-research/SKILL.md +273 -0
  270. package/lib/skills/builtin/matplotlib/SKILL.md +361 -0
  271. package/lib/skills/builtin/matplotlib/references/api_reference.md +412 -0
  272. package/lib/skills/builtin/matplotlib/references/common_issues.md +563 -0
  273. package/lib/skills/builtin/matplotlib/references/plot_types.md +476 -0
  274. package/lib/skills/builtin/matplotlib/references/styling_guide.md +589 -0
  275. package/lib/skills/builtin/matplotlib/scripts/plot_template.py +401 -0
  276. package/lib/skills/builtin/matplotlib/scripts/style_configurator.py +409 -0
  277. package/lib/skills/builtin/paper-writing/SKILL.md +554 -0
  278. package/lib/skills/builtin/paper-writing/references/checklists.md +524 -0
  279. package/lib/skills/builtin/paper-writing/references/citation-workflow.md +562 -0
  280. package/lib/skills/builtin/paper-writing/references/reviewer-guidelines.md +462 -0
  281. package/lib/skills/builtin/paper-writing/references/sources.md +189 -0
  282. package/lib/skills/builtin/paper-writing/references/systems-conferences.md +260 -0
  283. package/lib/skills/builtin/paper-writing/references/writing-guide.md +476 -0
  284. package/lib/skills/builtin/paper-writing/templates/README.md +408 -0
  285. package/lib/skills/builtin/paper-writing/templates/aaai2026/README.md +534 -0
  286. package/lib/skills/builtin/paper-writing/templates/aaai2026/aaai2026-unified-supp.tex +144 -0
  287. package/lib/skills/builtin/paper-writing/templates/aaai2026/aaai2026-unified-template.tex +952 -0
  288. package/lib/skills/builtin/paper-writing/templates/aaai2026/aaai2026.bib +111 -0
  289. package/lib/skills/builtin/paper-writing/templates/aaai2026/aaai2026.bst +1493 -0
  290. package/lib/skills/builtin/paper-writing/templates/aaai2026/aaai2026.sty +315 -0
  291. package/lib/skills/builtin/paper-writing/templates/acl/README.md +50 -0
  292. package/lib/skills/builtin/paper-writing/templates/acl/acl.sty +312 -0
  293. package/lib/skills/builtin/paper-writing/templates/acl/acl_latex.tex +377 -0
  294. package/lib/skills/builtin/paper-writing/templates/acl/acl_lualatex.tex +101 -0
  295. package/lib/skills/builtin/paper-writing/templates/acl/acl_natbib.bst +1940 -0
  296. package/lib/skills/builtin/paper-writing/templates/acl/anthology.bib.txt +26 -0
  297. package/lib/skills/builtin/paper-writing/templates/acl/custom.bib +70 -0
  298. package/lib/skills/builtin/paper-writing/templates/acl/formatting.md +326 -0
  299. package/lib/skills/builtin/paper-writing/templates/asplos2027/main.tex +459 -0
  300. package/lib/skills/builtin/paper-writing/templates/asplos2027/references.bib +135 -0
  301. package/lib/skills/builtin/paper-writing/templates/colm2025/README.md +3 -0
  302. package/lib/skills/builtin/paper-writing/templates/colm2025/colm2025_conference.bib +11 -0
  303. package/lib/skills/builtin/paper-writing/templates/colm2025/colm2025_conference.bst +1440 -0
  304. package/lib/skills/builtin/paper-writing/templates/colm2025/colm2025_conference.pdf +0 -0
  305. package/lib/skills/builtin/paper-writing/templates/colm2025/colm2025_conference.sty +218 -0
  306. package/lib/skills/builtin/paper-writing/templates/colm2025/colm2025_conference.tex +305 -0
  307. package/lib/skills/builtin/paper-writing/templates/colm2025/fancyhdr.sty +485 -0
  308. package/lib/skills/builtin/paper-writing/templates/colm2025/math_commands.tex +508 -0
  309. package/lib/skills/builtin/paper-writing/templates/colm2025/natbib.sty +1246 -0
  310. package/lib/skills/builtin/paper-writing/templates/iclr2026/fancyhdr.sty +485 -0
  311. package/lib/skills/builtin/paper-writing/templates/iclr2026/iclr2026_conference.bib +24 -0
  312. package/lib/skills/builtin/paper-writing/templates/iclr2026/iclr2026_conference.bst +1440 -0
  313. package/lib/skills/builtin/paper-writing/templates/iclr2026/iclr2026_conference.pdf +0 -0
  314. package/lib/skills/builtin/paper-writing/templates/iclr2026/iclr2026_conference.sty +246 -0
  315. package/lib/skills/builtin/paper-writing/templates/iclr2026/iclr2026_conference.tex +414 -0
  316. package/lib/skills/builtin/paper-writing/templates/iclr2026/math_commands.tex +508 -0
  317. package/lib/skills/builtin/paper-writing/templates/iclr2026/natbib.sty +1246 -0
  318. package/lib/skills/builtin/paper-writing/templates/icml2026/algorithm.sty +79 -0
  319. package/lib/skills/builtin/paper-writing/templates/icml2026/algorithmic.sty +201 -0
  320. package/lib/skills/builtin/paper-writing/templates/icml2026/example_paper.bib +75 -0
  321. package/lib/skills/builtin/paper-writing/templates/icml2026/example_paper.pdf +0 -0
  322. package/lib/skills/builtin/paper-writing/templates/icml2026/example_paper.tex +662 -0
  323. package/lib/skills/builtin/paper-writing/templates/icml2026/fancyhdr.sty +864 -0
  324. package/lib/skills/builtin/paper-writing/templates/icml2026/icml2026.bst +1443 -0
  325. package/lib/skills/builtin/paper-writing/templates/icml2026/icml2026.sty +767 -0
  326. package/lib/skills/builtin/paper-writing/templates/icml2026/icml_numpapers.pdf +0 -0
  327. package/lib/skills/builtin/paper-writing/templates/neurips2025/Makefile +36 -0
  328. package/lib/skills/builtin/paper-writing/templates/neurips2025/extra_pkgs.tex +53 -0
  329. package/lib/skills/builtin/paper-writing/templates/neurips2025/main.tex +38 -0
  330. package/lib/skills/builtin/paper-writing/templates/neurips2025/neurips.sty +382 -0
  331. package/lib/skills/builtin/paper-writing/templates/nsdi2027/main.tex +426 -0
  332. package/lib/skills/builtin/paper-writing/templates/nsdi2027/references.bib +151 -0
  333. package/lib/skills/builtin/paper-writing/templates/nsdi2027/usenix-2020-09.sty +83 -0
  334. package/lib/skills/builtin/paper-writing/templates/osdi2026/main.tex +429 -0
  335. package/lib/skills/builtin/paper-writing/templates/osdi2026/references.bib +150 -0
  336. package/lib/skills/builtin/paper-writing/templates/osdi2026/usenix-2020-09.sty +83 -0
  337. package/lib/skills/builtin/paper-writing/templates/sosp2026/main.tex +532 -0
  338. package/lib/skills/builtin/paper-writing/templates/sosp2026/references.bib +148 -0
  339. package/lib/skills/builtin/research-grants/SKILL.md +958 -0
  340. package/lib/skills/builtin/research-grants/assets/budget_justification_template.md +453 -0
  341. package/lib/skills/builtin/research-grants/assets/nih_specific_aims_template.md +166 -0
  342. package/lib/skills/builtin/research-grants/assets/nsf_project_summary_template.md +92 -0
  343. package/lib/skills/builtin/research-grants/references/README.md +285 -0
  344. package/lib/skills/builtin/research-grants/references/broader_impacts.md +392 -0
  345. package/lib/skills/builtin/research-grants/references/darpa_guidelines.md +636 -0
  346. package/lib/skills/builtin/research-grants/references/doe_guidelines.md +586 -0
  347. package/lib/skills/builtin/research-grants/references/nih_guidelines.md +851 -0
  348. package/lib/skills/builtin/research-grants/references/nsf_guidelines.md +570 -0
  349. package/lib/skills/builtin/research-grants/references/nstc_guidelines.md +733 -0
  350. package/lib/skills/builtin/research-grants/references/specific_aims_guide.md +458 -0
  351. package/lib/skills/builtin/rewrite-humanize/SKILL.md +116 -0
  352. package/lib/skills/builtin/rewrite-humanize/references/cs-venue-tone.md +57 -0
  353. package/lib/skills/builtin/rewrite-humanize/references/lexicon.md +50 -0
  354. package/lib/skills/builtin/scholar-evaluation/SKILL.md +300 -0
  355. package/lib/skills/builtin/scholar-evaluation/references/evaluation_framework.md +663 -0
  356. package/lib/skills/builtin/scholar-evaluation/scripts/calculate_scores.py +379 -0
  357. package/lib/skills/builtin/scientific-schematics/SKILL.md +603 -0
  358. package/lib/skills/builtin/scientific-schematics/references/QUICK_REFERENCE.md +182 -0
  359. package/lib/skills/builtin/scientific-schematics/references/README.md +292 -0
  360. package/lib/skills/builtin/scientific-schematics/references/best_practices.md +560 -0
  361. package/lib/skills/builtin/scientific-schematics/scripts/__pycache__/generate_schematic.cpython-312.pyc +0 -0
  362. package/lib/skills/builtin/scientific-schematics/scripts/__pycache__/generate_schematic_ai.cpython-312.pyc +0 -0
  363. package/lib/skills/builtin/scientific-schematics/scripts/example_usage.sh +85 -0
  364. package/lib/skills/builtin/scientific-schematics/scripts/generate_schematic.py +141 -0
  365. package/lib/skills/builtin/scientific-schematics/scripts/generate_schematic_ai.py +910 -0
  366. package/lib/skills/builtin/scientific-visualization/SKILL.md +749 -0
  367. package/lib/skills/builtin/scientific-visualization/assets/color_palettes.py +197 -0
  368. package/lib/skills/builtin/scientific-visualization/assets/nature.mplstyle +63 -0
  369. package/lib/skills/builtin/scientific-visualization/assets/presentation.mplstyle +61 -0
  370. package/lib/skills/builtin/scientific-visualization/assets/publication.mplstyle +68 -0
  371. package/lib/skills/builtin/scientific-visualization/references/color_palettes.md +348 -0
  372. package/lib/skills/builtin/scientific-visualization/references/journal_requirements.md +320 -0
  373. package/lib/skills/builtin/scientific-visualization/references/matplotlib_examples.md +620 -0
  374. package/lib/skills/builtin/scientific-visualization/references/publication_guidelines.md +205 -0
  375. package/lib/skills/builtin/scientific-visualization/scripts/figure_export.py +343 -0
  376. package/lib/skills/builtin/scientific-visualization/scripts/style_presets.py +416 -0
  377. package/lib/skills/builtin/scientific-writing/SKILL.md +745 -0
  378. package/lib/skills/builtin/scientific-writing/assets/REPORT_FORMATTING_GUIDE.md +574 -0
  379. package/lib/skills/builtin/scientific-writing/assets/scientific_report.sty +606 -0
  380. package/lib/skills/builtin/scientific-writing/assets/scientific_report_template.tex +449 -0
  381. package/lib/skills/builtin/scientific-writing/references/citation_styles.md +720 -0
  382. package/lib/skills/builtin/scientific-writing/references/figures_tables.md +806 -0
  383. package/lib/skills/builtin/scientific-writing/references/imrad_structure.md +686 -0
  384. package/lib/skills/builtin/scientific-writing/references/professional_report_formatting.md +664 -0
  385. package/lib/skills/builtin/scientific-writing/references/reporting_guidelines.md +748 -0
  386. package/lib/skills/builtin/scientific-writing/references/writing_principles.md +824 -0
  387. package/lib/skills/builtin/seaborn/SKILL.md +674 -0
  388. package/lib/skills/builtin/seaborn/references/examples.md +822 -0
  389. package/lib/skills/builtin/seaborn/references/function_reference.md +770 -0
  390. package/lib/skills/builtin/seaborn/references/objects_interface.md +964 -0
  391. package/lib/skills/data-analysis/SKILL.md +285 -0
  392. package/lib/skills/generate-skill-content.mjs +58 -0
  393. package/lib/skills/index.ts +34 -0
  394. package/lib/skills/loader.ts +452 -0
  395. package/package.json +62 -0
@@ -0,0 +1,910 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ AI-powered scientific schematic generation using OpenRouter API.
4
+
5
+ This script uses a smart iterative refinement approach:
6
+ 1. Generate initial image with Gemini image model via OpenRouter
7
+ 2. AI quality review using Gemini for scientific critique
8
+ 3. Only regenerate if quality is below threshold for document type
9
+ 4. Repeat until quality meets standards (max iterations)
10
+
11
+ Requirements:
12
+ - OPENROUTER_API_KEY environment variable
13
+ - requests library
14
+
15
+ Usage:
16
+ python generate_schematic_ai.py "Create a flowchart showing CONSORT participant flow" -o flowchart.png
17
+ python generate_schematic_ai.py "Neural network architecture diagram" -o architecture.png --iterations 2
18
+ python generate_schematic_ai.py "Simple block diagram" -o diagram.png --doc-type poster
19
+ """
20
+
21
+ import argparse
22
+ import base64
23
+ import json
24
+ import os
25
+ import re
26
+ import sys
27
+ import time
28
+ from pathlib import Path
29
+ from typing import Any, Dict, List, Optional, Tuple
30
+
31
+ try:
32
+ import requests
33
+ except ImportError:
34
+ print("Error: requests library not found. Install with: pip install requests")
35
+ sys.exit(1)
36
+
37
+
38
+ # Try to load .env file from multiple potential locations
39
+ def _load_env_file():
40
+ """Load .env file from current directory, parent directories, or package directory.
41
+
42
+ Returns True if a .env file was found and loaded, False otherwise.
43
+ Note: This does NOT override existing environment variables.
44
+ """
45
+ try:
46
+ from dotenv import load_dotenv
47
+ except ImportError:
48
+ return False # python-dotenv not installed
49
+
50
+ # Try current working directory first
51
+ env_path = Path.cwd() / ".env"
52
+ if env_path.exists():
53
+ load_dotenv(dotenv_path=env_path, override=False)
54
+ return True
55
+
56
+ # Try parent directories (up to 5 levels)
57
+ cwd = Path.cwd()
58
+ for _ in range(5):
59
+ env_path = cwd / ".env"
60
+ if env_path.exists():
61
+ load_dotenv(dotenv_path=env_path, override=False)
62
+ return True
63
+ cwd = cwd.parent
64
+ if cwd == cwd.parent: # Reached root
65
+ break
66
+
67
+ # Try the package's parent directory (project root)
68
+ script_dir = Path(__file__).resolve().parent
69
+ for _ in range(5):
70
+ env_path = script_dir / ".env"
71
+ if env_path.exists():
72
+ load_dotenv(dotenv_path=env_path, override=False)
73
+ return True
74
+ script_dir = script_dir.parent
75
+ if script_dir == script_dir.parent:
76
+ break
77
+
78
+ return False
79
+
80
+
81
+ OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions"
82
+
83
+
84
+ class ScientificSchematicGenerator:
85
+ """Generate scientific schematics using AI with smart iterative refinement.
86
+
87
+ Uses Gemini review to determine if regeneration is needed.
88
+ Multiple passes only occur if the generated schematic doesn't meet the
89
+ quality threshold for the target document type.
90
+ """
91
+
92
+ # Quality thresholds by document type (score out of 10)
93
+ QUALITY_THRESHOLDS = {
94
+ "journal": 8.5,
95
+ "conference": 8.0,
96
+ "poster": 7.0,
97
+ "presentation": 6.5,
98
+ "report": 7.5,
99
+ "grant": 8.0,
100
+ "thesis": 8.0,
101
+ "preprint": 7.5,
102
+ "default": 7.5,
103
+ }
104
+
105
+ DEFAULT_IMAGE_MODEL = "google/gemini-3-pro-image-preview"
106
+ DEFAULT_REVIEW_MODEL = "google/gemini-3-pro-preview"
107
+ DEFAULT_REQUEST_TIMEOUT_SECONDS = 300
108
+
109
+ # Scientific diagram best practices prompt template
110
+ SCIENTIFIC_DIAGRAM_GUIDELINES = """
111
+ Create a high-quality scientific diagram with these requirements:
112
+
113
+ VISUAL QUALITY:
114
+ - Clean white or light background (no textures or gradients). Professional pastel tones.
115
+ - Flat vector illustration, academic aesthetic. Similar to figures in DeepMind or OpenAI papers.
116
+ - High contrast for readability and printing
117
+ - Professional, publication-ready appearance
118
+ - Sharp, clear lines and text
119
+ - Adequate spacing between elements to prevent crowding
120
+
121
+ TYPOGRAPHY:
122
+ - Clear, readable sans-serif fonts (Arial, Helvetica style)
123
+ - Minimum 10pt font size for all labels
124
+ - Consistent font sizes throughout
125
+ - All text horizontal or clearly readable
126
+ - No overlapping text
127
+
128
+ SCIENTIFIC STANDARDS:
129
+ - Accurate representation of concepts
130
+ - Clear labels for all components
131
+ - Include scale bars, legends, or axes where appropriate
132
+ - Use standard scientific notation and symbols
133
+ - Include units where applicable
134
+
135
+ ACCESSIBILITY:
136
+ - Colorblind-friendly color palette (use Okabe-Ito colors if using color)
137
+ - High contrast between elements
138
+ - Redundant encoding (shapes + colors, not just colors)
139
+ - Works well in grayscale
140
+
141
+ LAYOUT:
142
+ - Logical flow (left-to-right, top-to-bottom, circular and other shapes). Group related components logically.
143
+ - Clear visual hierarchy
144
+ - Balanced composition
145
+ - Appropriate use of whitespace
146
+ - No clutter or unnecessary decorative elements
147
+
148
+ IMPORTANT - NO FIGURE NUMBERS:
149
+ - Do NOT include "Figure 1:", "Fig. 1", or any figure numbering in the image
150
+ - Do NOT add captions or titles like "Figure: ..." at the top or bottom
151
+ - Figure numbers and captions are added separately in the document/LaTeX
152
+ - The diagram should contain only the visual content itself
153
+
154
+ Negative Constraints:
155
+ - NO photorealistic photos, NO messy sketches, NO unreadable text, NO 3D shading artifacts.
156
+ """
157
+
158
+ def __init__(
159
+ self,
160
+ api_key: Optional[str] = None,
161
+ image_model: Optional[str] = None,
162
+ review_model: Optional[str] = None,
163
+ request_timeout_seconds: Optional[int] = None,
164
+ verbose: bool = False,
165
+ ):
166
+ """
167
+ Initialize the generator.
168
+
169
+ Args:
170
+ api_key: OpenRouter API key
171
+ image_model: Model ID for image generation (OpenRouter format)
172
+ review_model: Model ID for quality review (OpenRouter format)
173
+ request_timeout_seconds: Request timeout for API calls
174
+ verbose: Print detailed progress information
175
+ """
176
+ _load_env_file()
177
+ self.verbose = verbose
178
+ self._last_error = None
179
+
180
+ self.api_key = api_key or os.getenv("OPENROUTER_API_KEY")
181
+ if not self.api_key:
182
+ raise ValueError(
183
+ "OPENROUTER_API_KEY is not set.\n"
184
+ "Set OPENROUTER_API_KEY environment variable or pass --api-key."
185
+ )
186
+
187
+ self.image_model = (
188
+ image_model
189
+ or os.getenv("SCHEMATIC_IMAGE_MODEL")
190
+ or self.DEFAULT_IMAGE_MODEL
191
+ )
192
+ self.review_model = (
193
+ review_model
194
+ or os.getenv("SCHEMATIC_REVIEW_MODEL")
195
+ or self.DEFAULT_REVIEW_MODEL
196
+ )
197
+ self.request_timeout_seconds = self._parse_timeout_seconds(
198
+ request_timeout_seconds
199
+ or os.getenv("SCHEMATIC_REQUEST_TIMEOUT_SECONDS")
200
+ )
201
+
202
+ def _parse_timeout_seconds(self, value: Optional[Any]) -> int:
203
+ try:
204
+ parsed = int(str(value).strip()) if value is not None else 0
205
+ except (TypeError, ValueError):
206
+ parsed = 0
207
+ return max(parsed, 30) if parsed else self.DEFAULT_REQUEST_TIMEOUT_SECONDS
208
+
209
+ def _log(self, message: str):
210
+ """Log message if verbose mode is enabled."""
211
+ if self.verbose:
212
+ print(f"[{time.strftime('%H:%M:%S')}] {message}")
213
+
214
+ def _post_openrouter(
215
+ self,
216
+ model: str,
217
+ messages: List[Dict[str, Any]],
218
+ timeout_seconds: Optional[int] = None,
219
+ ) -> Dict[str, Any]:
220
+ """
221
+ Make a request to OpenRouter chat completions API.
222
+
223
+ Args:
224
+ model: Model identifier (OpenRouter format, e.g. google/gemini-3-pro-image-preview)
225
+ messages: List of message dictionaries in OpenAI format
226
+ timeout_seconds: Optional timeout override
227
+
228
+ Returns:
229
+ API response as dictionary
230
+ """
231
+ timeout = timeout_seconds or self.request_timeout_seconds
232
+ headers = {
233
+ "Authorization": f"Bearer {self.api_key}",
234
+ "Content-Type": "application/json",
235
+ "HTTP-Referer": "https://github.com/research-copilot",
236
+ "X-Title": "Research Copilot Scientific Schematics",
237
+ }
238
+ payload: Dict[str, Any] = {
239
+ "model": model,
240
+ "messages": messages,
241
+ }
242
+
243
+ self._log(f"Making request to OpenRouter with model {model}...")
244
+
245
+ try:
246
+ response = requests.post(
247
+ OPENROUTER_API_URL,
248
+ headers=headers,
249
+ json=payload,
250
+ timeout=timeout,
251
+ )
252
+
253
+ try:
254
+ response_json = response.json()
255
+ except json.JSONDecodeError:
256
+ response_json = {"raw_text": response.text[:500]}
257
+
258
+ if response.status_code != 200:
259
+ error_detail = response_json.get("error", response_json)
260
+ self._log(f"HTTP {response.status_code}: {error_detail}")
261
+ raise RuntimeError(
262
+ f"API request failed (HTTP {response.status_code}): {error_detail}"
263
+ )
264
+
265
+ return response_json
266
+ except requests.exceptions.Timeout:
267
+ raise RuntimeError(f"API request timed out after {timeout} seconds")
268
+ except requests.exceptions.RequestException as e:
269
+ raise RuntimeError(f"API request failed: {str(e)}")
270
+
271
+ def _extract_image_from_response(self, response: Dict[str, Any]) -> Optional[bytes]:
272
+ """
273
+ Extract image bytes from an OpenRouter response.
274
+
275
+ Handles multiple response formats:
276
+ 1. message.images array with image_url data URIs (Gemini via OpenRouter)
277
+ 2. Multipart content array with inline_data (Gemini native)
278
+ 3. Content array with image_url data URIs
279
+ 4. Base64 image data in content string
280
+
281
+ Returns:
282
+ Image bytes or None if not found
283
+ """
284
+ try:
285
+ choices = response.get("choices", [])
286
+ if not choices:
287
+ self._log("No choices in response")
288
+ return None
289
+
290
+ message = choices[0].get("message", {})
291
+
292
+ # Case 0: message.images array (OpenRouter Gemini image generation format)
293
+ # The API returns content=null but images=[{type: "image_url", image_url: {url: "data:..."}}]
294
+ images = message.get("images")
295
+ if isinstance(images, list):
296
+ for index, img in enumerate(images):
297
+ if not isinstance(img, dict):
298
+ continue
299
+ # Handle {type: "image_url", image_url: {url: "data:image/png;base64,..."}}
300
+ if img.get("type") == "image_url":
301
+ image_url = img.get("image_url", {})
302
+ url = image_url.get("url", "") if isinstance(image_url, dict) else str(image_url)
303
+ if url.startswith("data:") and "," in url:
304
+ _, b64data = url.split(",", 1)
305
+ self._log(f"Found image in message.images[{index}]")
306
+ return base64.b64decode(b64data)
307
+ # Handle direct {url: "data:..."} format
308
+ url = img.get("url", "")
309
+ if url.startswith("data:") and "," in url:
310
+ _, b64data = url.split(",", 1)
311
+ self._log(f"Found image in message.images[{index}] (direct url)")
312
+ return base64.b64decode(b64data)
313
+
314
+ content = message.get("content", "")
315
+
316
+ # Case 1: content is a list of parts (multimodal response)
317
+ if isinstance(content, list):
318
+ for index, part in enumerate(content):
319
+ if not isinstance(part, dict):
320
+ continue
321
+
322
+ # Check for inline_data (Gemini native format passed through)
323
+ inline_data = part.get("inline_data", {})
324
+ if isinstance(inline_data, dict) and inline_data.get("data"):
325
+ data = str(inline_data["data"]).replace("\n", "").replace("\r", "").replace(" ", "")
326
+ self._log(f"Found image in inline_data part {index}")
327
+ return base64.b64decode(data)
328
+
329
+ # Check for image_url with data URI
330
+ if part.get("type") == "image_url":
331
+ image_url = part.get("image_url", {})
332
+ url = image_url.get("url", "") if isinstance(image_url, dict) else str(image_url)
333
+ if url.startswith("data:") and "," in url:
334
+ _, b64data = url.split(",", 1)
335
+ self._log(f"Found image in image_url part {index}")
336
+ return base64.b64decode(b64data)
337
+
338
+ self._log("No image data found in content parts")
339
+ return None
340
+
341
+ # Case 2: content is a string — might contain base64 image in markdown
342
+ if isinstance(content, str) and content:
343
+ # Look for markdown image with base64
344
+ match = re.search(r'!\[.*?\]\(data:image/[^;]+;base64,([A-Za-z0-9+/=\s]+)\)', content)
345
+ if match:
346
+ b64data = match.group(1).replace("\n", "").replace("\r", "").replace(" ", "")
347
+ self._log("Found base64 image in markdown content")
348
+ return base64.b64decode(b64data)
349
+
350
+ # Look for raw base64 block (some models return just the data)
351
+ stripped = content.strip()
352
+ if len(stripped) > 1000 and re.match(r'^[A-Za-z0-9+/=\n\r]+$', stripped):
353
+ try:
354
+ data = base64.b64decode(stripped.replace("\n", "").replace("\r", ""))
355
+ # Verify it's actually an image (PNG or JPEG magic bytes)
356
+ if data[:4] == b'\x89PNG' or data[:2] == b'\xff\xd8':
357
+ self._log("Found raw base64 image in content string")
358
+ return data
359
+ except Exception:
360
+ pass
361
+
362
+ self._log("No image data found in response")
363
+ return None
364
+
365
+ except Exception as e:
366
+ self._log(f"Error extracting image: {str(e)}")
367
+ if self.verbose:
368
+ import traceback
369
+ traceback.print_exc()
370
+ return None
371
+
372
+ def _image_to_base64_url(self, image_path: str) -> str:
373
+ """
374
+ Convert image file to a data URI for use in OpenAI-compatible messages.
375
+
376
+ Args:
377
+ image_path: Path to image file
378
+
379
+ Returns:
380
+ Data URI string (data:image/png;base64,...)
381
+ """
382
+ with open(image_path, "rb") as f:
383
+ image_data = f.read()
384
+
385
+ ext = Path(image_path).suffix.lower()
386
+ mime_type = {
387
+ ".png": "image/png",
388
+ ".jpg": "image/jpeg",
389
+ ".jpeg": "image/jpeg",
390
+ ".gif": "image/gif",
391
+ ".webp": "image/webp",
392
+ }.get(ext, "image/png")
393
+
394
+ b64 = base64.b64encode(image_data).decode("utf-8")
395
+ return f"data:{mime_type};base64,{b64}"
396
+
397
+ def generate_image(self, prompt: str) -> Optional[bytes]:
398
+ """
399
+ Generate an image using the image model via OpenRouter.
400
+
401
+ Args:
402
+ prompt: Description of the diagram to generate
403
+
404
+ Returns:
405
+ Image bytes or None if generation failed
406
+ """
407
+ self._last_error = None
408
+
409
+ try:
410
+ messages = [{"role": "user", "content": prompt}]
411
+ response = self._post_openrouter(
412
+ model=self.image_model,
413
+ messages=messages,
414
+ )
415
+ image_data = self._extract_image_from_response(response)
416
+
417
+ if self.verbose:
418
+ self._log(f"Response keys: {response.keys()}")
419
+ if "error" in response:
420
+ self._log(f"API Error: {response['error']}")
421
+ choices = response.get("choices", [])
422
+ if choices:
423
+ msg = choices[0].get("message", {})
424
+ content = msg.get("content", "")
425
+ if isinstance(content, list):
426
+ self._log(f"Content part count: {len(content)}")
427
+ for i, part in enumerate(content[:3]):
428
+ if isinstance(part, dict):
429
+ self._log(f" Part {i}: keys={list(part.keys())}")
430
+ elif isinstance(content, str):
431
+ self._log(f"Content is string, length={len(content)}")
432
+
433
+ if "error" in response:
434
+ error_msg = response["error"]
435
+ if isinstance(error_msg, dict):
436
+ error_msg = error_msg.get("message", str(error_msg))
437
+ self._last_error = f"API Error: {error_msg}"
438
+ print(f"✗ {self._last_error}")
439
+ return None
440
+
441
+ if image_data:
442
+ self._log(f"✓ Generated image ({len(image_data)} bytes)")
443
+ else:
444
+ self._last_error = (
445
+ f"No image data in response for model {self.image_model}"
446
+ )
447
+ self._log(f"✗ {self._last_error}")
448
+
449
+ return image_data
450
+ except RuntimeError as e:
451
+ self._last_error = str(e)
452
+ self._log(f"✗ Generation failed: {self._last_error}")
453
+ return None
454
+ except Exception as e:
455
+ self._last_error = f"Unexpected error: {str(e)}"
456
+ self._log(f"✗ Generation failed: {self._last_error}")
457
+ if self.verbose:
458
+ import traceback
459
+ traceback.print_exc()
460
+ return None
461
+
462
+ def review_image(
463
+ self,
464
+ image_path: str,
465
+ original_prompt: str,
466
+ iteration: int,
467
+ doc_type: str = "default",
468
+ max_iterations: int = 2,
469
+ ) -> Tuple[str, float, bool]:
470
+ """
471
+ Review generated image using a multimodal model for quality analysis.
472
+
473
+ Args:
474
+ image_path: Path to the generated image
475
+ original_prompt: Original user prompt
476
+ iteration: Current iteration number
477
+ doc_type: Document type (journal, poster, presentation, etc.)
478
+ max_iterations: Maximum iterations allowed
479
+
480
+ Returns:
481
+ Tuple of (critique text, quality score 0-10, needs_improvement bool)
482
+ """
483
+ image_data_url = self._image_to_base64_url(image_path)
484
+
485
+ threshold = self.QUALITY_THRESHOLDS.get(
486
+ doc_type.lower(), self.QUALITY_THRESHOLDS["default"]
487
+ )
488
+
489
+ review_prompt = f"""You are an expert reviewer evaluating a scientific diagram for publication quality.
490
+
491
+ ORIGINAL REQUEST: {original_prompt}
492
+
493
+ DOCUMENT TYPE: {doc_type} (quality threshold: {threshold}/10)
494
+ ITERATION: {iteration}/{max_iterations}
495
+
496
+ Carefully evaluate this diagram on these criteria:
497
+
498
+ 1. **Scientific Accuracy** (0-2 points)
499
+ - Correct representation of concepts
500
+ - Proper notation and symbols
501
+ - Accurate relationships shown
502
+
503
+ 2. **Clarity and Readability** (0-2 points)
504
+ - Easy to understand at a glance
505
+ - Clear visual hierarchy
506
+ - No ambiguous elements
507
+
508
+ 3. **Label Quality** (0-2 points)
509
+ - All important elements labeled
510
+ - Labels are readable (appropriate font size)
511
+ - Consistent labeling style
512
+
513
+ 4. **Layout and Composition** (0-2 points)
514
+ - Logical flow (top-to-bottom or left-to-right)
515
+ - Balanced use of space
516
+ - No overlapping elements
517
+
518
+ 5. **Professional Appearance** (0-2 points)
519
+ - Publication-ready quality
520
+ - Clean, crisp lines and shapes
521
+ - Appropriate colors/contrast
522
+
523
+ RESPOND IN THIS EXACT FORMAT:
524
+ SCORE: [total score 0-10]
525
+
526
+ STRENGTHS:
527
+ - [strength 1]
528
+ - [strength 2]
529
+
530
+ ISSUES:
531
+ - [issue 1 if any]
532
+ - [issue 2 if any]
533
+
534
+ VERDICT: [ACCEPTABLE or NEEDS_IMPROVEMENT]
535
+
536
+ If score >= {threshold}, the diagram is ACCEPTABLE for {doc_type} publication.
537
+ If score < {threshold}, mark as NEEDS_IMPROVEMENT with specific suggestions."""
538
+
539
+ messages = [
540
+ {
541
+ "role": "user",
542
+ "content": [
543
+ {"type": "text", "text": review_prompt},
544
+ {
545
+ "type": "image_url",
546
+ "image_url": {"url": image_data_url},
547
+ },
548
+ ],
549
+ }
550
+ ]
551
+
552
+ try:
553
+ response = self._post_openrouter(model=self.review_model, messages=messages)
554
+
555
+ choices = response.get("choices", [])
556
+ if not choices:
557
+ return "Image generated successfully", 8.0, False
558
+
559
+ message = choices[0].get("message", {})
560
+ content = message.get("content", "")
561
+
562
+ # Handle content as string or list of parts
563
+ if isinstance(content, list):
564
+ text_parts = []
565
+ for block in content:
566
+ if isinstance(block, dict) and block.get("type") == "text":
567
+ text_parts.append(str(block.get("text", "")))
568
+ elif isinstance(block, str):
569
+ text_parts.append(block)
570
+ content = "\n".join(text_parts)
571
+
572
+ if not isinstance(content, str):
573
+ content = str(content)
574
+
575
+ # Extract score
576
+ score = 7.5 # Default
577
+
578
+ score_match = re.search(r"SCORE:\s*(\d+(?:\.\d+)?)", content, re.IGNORECASE)
579
+ if score_match:
580
+ score = float(score_match.group(1))
581
+ else:
582
+ score_match = re.search(
583
+ r"(?:score|rating|quality)[:\s]+(\d+(?:\.\d+)?)\s*(?:/\s*10)?",
584
+ content,
585
+ re.IGNORECASE,
586
+ )
587
+ if score_match:
588
+ score = float(score_match.group(1))
589
+
590
+ # Determine if improvement is needed
591
+ needs_improvement = False
592
+ if "NEEDS_IMPROVEMENT" in content.upper():
593
+ needs_improvement = True
594
+ elif score < threshold:
595
+ needs_improvement = True
596
+
597
+ self._log(
598
+ f"✓ Review complete (Score: {score}/10, Threshold: {threshold}/10)"
599
+ )
600
+ self._log(
601
+ f" Verdict: {'Needs improvement' if needs_improvement else 'Acceptable'}"
602
+ )
603
+
604
+ return (
605
+ content if content else "Image generated successfully",
606
+ score,
607
+ needs_improvement,
608
+ )
609
+ except Exception as e:
610
+ self._log(f"Review skipped: {str(e)}")
611
+ return "Image generated successfully (review skipped)", 7.5, False
612
+
613
+ def improve_prompt(
614
+ self, original_prompt: str, critique: str, iteration: int
615
+ ) -> str:
616
+ """
617
+ Improve the generation prompt based on critique.
618
+ """
619
+ improved_prompt = f"""{self.SCIENTIFIC_DIAGRAM_GUIDELINES}
620
+
621
+ USER REQUEST: {original_prompt}
622
+
623
+ ITERATION {iteration}: Based on previous feedback, address these specific improvements:
624
+ {critique}
625
+
626
+ Generate an improved version that addresses all the critique points while maintaining scientific accuracy and professional quality."""
627
+
628
+ return improved_prompt
629
+
630
+ def generate_iterative(
631
+ self,
632
+ user_prompt: str,
633
+ output_path: str,
634
+ iterations: int = 2,
635
+ doc_type: str = "default",
636
+ ) -> Dict[str, Any]:
637
+ """
638
+ Generate scientific schematic with smart iterative refinement.
639
+
640
+ Only regenerates if the quality score is below the threshold for the
641
+ specified document type.
642
+
643
+ Args:
644
+ user_prompt: User's description of desired diagram
645
+ output_path: Path to save final image
646
+ iterations: Maximum refinement iterations (default: 2, max: 2)
647
+ doc_type: Document type for quality threshold
648
+
649
+ Returns:
650
+ Dictionary with generation results and metadata
651
+ """
652
+ output_path = Path(output_path)
653
+ output_dir = output_path.parent
654
+ output_dir.mkdir(parents=True, exist_ok=True)
655
+
656
+ base_name = output_path.stem
657
+ extension = output_path.suffix or ".png"
658
+
659
+ threshold = self.QUALITY_THRESHOLDS.get(
660
+ doc_type.lower(), self.QUALITY_THRESHOLDS["default"]
661
+ )
662
+
663
+ results = {
664
+ "user_prompt": user_prompt,
665
+ "doc_type": doc_type,
666
+ "quality_threshold": threshold,
667
+ "iterations": [],
668
+ "final_image": None,
669
+ "final_score": 0.0,
670
+ "success": False,
671
+ "early_stop": False,
672
+ "early_stop_reason": None,
673
+ }
674
+
675
+ current_prompt = f"""{self.SCIENTIFIC_DIAGRAM_GUIDELINES}
676
+
677
+ USER REQUEST: {user_prompt}
678
+
679
+ Generate a publication-quality scientific diagram that meets all the guidelines above."""
680
+
681
+ print(f"\n{'=' * 60}")
682
+ print(f"Generating Scientific Schematic")
683
+ print(f"{'=' * 60}")
684
+ print(f"Description: {user_prompt}")
685
+ print(f"Document Type: {doc_type}")
686
+ print(f"Quality Threshold: {threshold}/10")
687
+ print(f"Max Iterations: {iterations}")
688
+ print(f"Image Model: {self.image_model}")
689
+ print(f"Review Model: {self.review_model}")
690
+ print(f"Output: {output_path}")
691
+ print(f"{'=' * 60}\n")
692
+
693
+ for i in range(1, iterations + 1):
694
+ print(f"\n[Iteration {i}/{iterations}]")
695
+ print("-" * 40)
696
+
697
+ # Generate image
698
+ print(f"Generating image...")
699
+ image_data = self.generate_image(current_prompt)
700
+
701
+ if not image_data:
702
+ error_msg = self._last_error or "Image generation failed - no image data returned"
703
+ print(f"✗ Generation failed: {error_msg}")
704
+ results["iterations"].append(
705
+ {"iteration": i, "success": False, "error": error_msg}
706
+ )
707
+ continue
708
+
709
+ # Save iteration image
710
+ iter_path = output_dir / f"{base_name}_v{i}{extension}"
711
+ with open(iter_path, "wb") as f:
712
+ f.write(image_data)
713
+ print(f"✓ Saved: {iter_path}")
714
+
715
+ # Review image
716
+ print(f"Reviewing image...")
717
+ critique, score, needs_improvement = self.review_image(
718
+ str(iter_path), user_prompt, i, doc_type, iterations
719
+ )
720
+ print(f"✓ Score: {score}/10 (threshold: {threshold}/10)")
721
+
722
+ iteration_result = {
723
+ "iteration": i,
724
+ "image_path": str(iter_path),
725
+ "prompt": current_prompt,
726
+ "critique": critique,
727
+ "score": score,
728
+ "needs_improvement": needs_improvement,
729
+ "success": True,
730
+ }
731
+ results["iterations"].append(iteration_result)
732
+
733
+ # Check if quality is acceptable
734
+ if not needs_improvement:
735
+ print(
736
+ f"\n✓ Quality meets {doc_type} threshold ({score} >= {threshold})"
737
+ )
738
+ print(f" No further iterations needed!")
739
+ results["final_image"] = str(iter_path)
740
+ results["final_score"] = score
741
+ results["success"] = True
742
+ results["early_stop"] = True
743
+ results["early_stop_reason"] = (
744
+ f"Quality score {score} meets threshold {threshold} for {doc_type}"
745
+ )
746
+ break
747
+
748
+ if i == iterations:
749
+ print(f"\n⚠ Maximum iterations reached")
750
+ results["final_image"] = str(iter_path)
751
+ results["final_score"] = score
752
+ results["success"] = True
753
+ break
754
+
755
+ # Quality below threshold — improve prompt
756
+ print(f"\n⚠ Quality below threshold ({score} < {threshold})")
757
+ print(f"Improving prompt based on feedback...")
758
+ current_prompt = self.improve_prompt(user_prompt, critique, i + 1)
759
+
760
+ # Copy final version to output path
761
+ if results["success"] and results["final_image"]:
762
+ final_iter_path = Path(results["final_image"])
763
+ if final_iter_path != output_path:
764
+ import shutil
765
+ shutil.copy(final_iter_path, output_path)
766
+ print(f"\n✓ Final image: {output_path}")
767
+
768
+ # Save review log
769
+ log_path = output_dir / f"{base_name}_review_log.json"
770
+ with open(log_path, "w") as f:
771
+ json.dump(results, f, indent=2)
772
+ print(f"✓ Review log: {log_path}")
773
+
774
+ print(f"\n{'=' * 60}")
775
+ print(f"Generation Complete!")
776
+ print(f"Final Score: {results['final_score']}/10")
777
+ if results["early_stop"]:
778
+ print(
779
+ f"Iterations Used: {len([r for r in results['iterations'] if r.get('success')])}/{iterations} (early stop)"
780
+ )
781
+ print(f"{'=' * 60}\n")
782
+
783
+ return results
784
+
785
+
786
+ def main():
787
+ """Command-line interface."""
788
+ parser = argparse.ArgumentParser(
789
+ description="Generate scientific schematics using AI with smart iterative refinement",
790
+ formatter_class=argparse.RawDescriptionHelpFormatter,
791
+ epilog="""
792
+ Examples:
793
+ # Generate a flowchart for a journal paper
794
+ python generate_schematic_ai.py "CONSORT participant flow diagram" -o flowchart.png --doc-type journal
795
+
796
+ # Generate neural network architecture for presentation (lower threshold)
797
+ python generate_schematic_ai.py "Transformer encoder-decoder architecture" -o transformer.png --doc-type presentation
798
+
799
+ # Generate with custom max iterations for poster
800
+ python generate_schematic_ai.py "Biological signaling pathway" -o pathway.png --iterations 2 --doc-type poster
801
+
802
+ # Verbose output
803
+ python generate_schematic_ai.py "Circuit diagram" -o circuit.png -v
804
+
805
+ Document Types (quality thresholds):
806
+ journal 8.5/10 - Nature, Science, peer-reviewed journals
807
+ conference 8.0/10 - Conference papers
808
+ thesis 8.0/10 - Dissertations, theses
809
+ grant 8.0/10 - Grant proposals
810
+ preprint 7.5/10 - arXiv, bioRxiv, etc.
811
+ report 7.5/10 - Technical reports
812
+ poster 7.0/10 - Academic posters
813
+ presentation 6.5/10 - Slides, talks
814
+ default 7.5/10 - General purpose
815
+
816
+ Note: Multiple iterations only occur if quality is BELOW the threshold.
817
+ If the first generation meets the threshold, no extra API calls are made.
818
+
819
+ Environment:
820
+ OPENROUTER_API_KEY Required. Your OpenRouter API key.
821
+ SCHEMATIC_IMAGE_MODEL Optional. Override image generation model (default: google/gemini-3-pro-image-preview)
822
+ SCHEMATIC_REVIEW_MODEL Optional. Override review model (default: google/gemini-3-pro-preview)
823
+ SCHEMATIC_REQUEST_TIMEOUT_SECONDS Optional. Request timeout in seconds.
824
+ """,
825
+ )
826
+
827
+ parser.add_argument("prompt", help="Description of the diagram to generate")
828
+ parser.add_argument(
829
+ "-o", "--output", required=True, help="Output image path (e.g., diagram.png)"
830
+ )
831
+ parser.add_argument(
832
+ "--iterations",
833
+ type=int,
834
+ default=2,
835
+ help="Maximum refinement iterations (default: 2, max: 2)",
836
+ )
837
+ parser.add_argument(
838
+ "--doc-type",
839
+ default="default",
840
+ choices=[
841
+ "journal",
842
+ "conference",
843
+ "poster",
844
+ "presentation",
845
+ "report",
846
+ "grant",
847
+ "thesis",
848
+ "preprint",
849
+ "default",
850
+ ],
851
+ help="Document type for quality threshold (default: default)",
852
+ )
853
+ parser.add_argument("--api-key", help="OpenRouter API key (or use OPENROUTER_API_KEY)")
854
+ parser.add_argument(
855
+ "--image-model",
856
+ default=None,
857
+ help="Image generation model (default: google/gemini-3-pro-image-preview)",
858
+ )
859
+ parser.add_argument(
860
+ "--review-model",
861
+ default=None,
862
+ help="Review model (default: google/gemini-3-pro-preview)",
863
+ )
864
+ parser.add_argument(
865
+ "--timeout-seconds",
866
+ type=int,
867
+ default=None,
868
+ help=argparse.SUPPRESS,
869
+ )
870
+ parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
871
+
872
+ args = parser.parse_args()
873
+
874
+ # Validate iterations
875
+ if args.iterations < 1 or args.iterations > 2:
876
+ print("Error: Iterations must be between 1 and 2")
877
+ sys.exit(1)
878
+
879
+ try:
880
+ generator = ScientificSchematicGenerator(
881
+ api_key=args.api_key,
882
+ image_model=args.image_model,
883
+ review_model=args.review_model,
884
+ request_timeout_seconds=args.timeout_seconds,
885
+ verbose=args.verbose,
886
+ )
887
+ results = generator.generate_iterative(
888
+ user_prompt=args.prompt,
889
+ output_path=args.output,
890
+ iterations=args.iterations,
891
+ doc_type=args.doc_type,
892
+ )
893
+
894
+ if results["success"]:
895
+ print(f"\n✓ Success! Image saved to: {args.output}")
896
+ if results.get("early_stop"):
897
+ print(
898
+ f" (Completed in {len([r for r in results['iterations'] if r.get('success')])} iteration(s) - quality threshold met)"
899
+ )
900
+ sys.exit(0)
901
+ else:
902
+ print(f"\n✗ Generation failed. Check review log for details.")
903
+ sys.exit(1)
904
+ except Exception as e:
905
+ print(f"\n✗ Error: {str(e)}")
906
+ sys.exit(1)
907
+
908
+
909
+ if __name__ == "__main__":
910
+ main()