dsh-aris-panel 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 (442) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/README_CN.md +87 -0
  4. package/dsh/checkout.patch.yml +38 -0
  5. package/dsh/client.js +634 -0
  6. package/dsh/cordis.patch.yml +44 -0
  7. package/dsh/index.mjs +76 -0
  8. package/dsh/run-status.mjs +182 -0
  9. package/dsh/scope-limits.mjs +50 -0
  10. package/dsh/workbench.mjs +291 -0
  11. package/mcp-servers/claude-review/README.md +93 -0
  12. package/mcp-servers/claude-review/run_with_claude_aws.sh +49 -0
  13. package/mcp-servers/claude-review/server.py +718 -0
  14. package/mcp-servers/codex-image2/README.md +65 -0
  15. package/mcp-servers/codex-image2/server.py +893 -0
  16. package/mcp-servers/feishu-bridge/requirements.txt +1 -0
  17. package/mcp-servers/feishu-bridge/server.py +240 -0
  18. package/mcp-servers/gemini-review/README.md +171 -0
  19. package/mcp-servers/gemini-review/server.py +1856 -0
  20. package/mcp-servers/llm-chat/requirements.txt +1 -0
  21. package/mcp-servers/llm-chat/server.py +664 -0
  22. package/mcp-servers/manual-review/README.md +133 -0
  23. package/mcp-servers/manual-review/server.py +910 -0
  24. package/mcp-servers/manual-review/ui.html +279 -0
  25. package/mcp-servers/minimax-chat/requirements.txt +1 -0
  26. package/mcp-servers/minimax-chat/server.py +381 -0
  27. package/package.json +51 -0
  28. package/skills/ablation-planner/SKILL.md +123 -0
  29. package/skills/alphaxiv/SKILL.md +196 -0
  30. package/skills/analyze-results/SKILL.md +46 -0
  31. package/skills/arxiv/SKILL.md +248 -0
  32. package/skills/auto-paper-improvement-loop/SKILL.md +651 -0
  33. package/skills/auto-review-loop/SKILL.md +1137 -0
  34. package/skills/auto-review-loop-llm/SKILL.md +259 -0
  35. package/skills/auto-review-loop-minimax/SKILL.md +302 -0
  36. package/skills/citation-audit/SKILL.md +502 -0
  37. package/skills/claims-drafting/SKILL.md +227 -0
  38. package/skills/comm-lit-review/SKILL.md +297 -0
  39. package/skills/deepxiv/SKILL.md +263 -0
  40. package/skills/dse-loop/SKILL.md +296 -0
  41. package/skills/embodiment-description/SKILL.md +129 -0
  42. package/skills/exa-search/SKILL.md +205 -0
  43. package/skills/experiment-audit/SKILL.md +311 -0
  44. package/skills/experiment-bridge/SKILL.md +376 -0
  45. package/skills/experiment-plan/SKILL.md +249 -0
  46. package/skills/experiment-queue/SKILL.md +431 -0
  47. package/skills/experiment-queue/scripts/build_manifest.py +142 -0
  48. package/skills/experiment-queue/scripts/queue_manager.py +433 -0
  49. package/skills/feishu-notify/SKILL.md +156 -0
  50. package/skills/figure-description/SKILL.md +138 -0
  51. package/skills/figure-spec/SKILL.md +262 -0
  52. package/skills/figure-spec/scripts/figure_renderer.py +799 -0
  53. package/skills/formula-derivation/SKILL.md +280 -0
  54. package/skills/gemini-search/SKILL.md +231 -0
  55. package/skills/grant-proposal/SKILL.md +698 -0
  56. package/skills/idea-creator/SKILL.md +542 -0
  57. package/skills/idea-discovery/SKILL.md +521 -0
  58. package/skills/idea-discovery-robot/SKILL.md +363 -0
  59. package/skills/integrity-forensics/SKILL.md +284 -0
  60. package/skills/interview-cheatsheet/SKILL.md +245 -0
  61. package/skills/invention-structuring/SKILL.md +188 -0
  62. package/skills/jurisdiction-format/SKILL.md +192 -0
  63. package/skills/kill-argument/SKILL.md +437 -0
  64. package/skills/mermaid-diagram/SKILL.md +419 -0
  65. package/skills/meta-apply/SKILL.md +141 -0
  66. package/skills/meta-optimize/SKILL.md +437 -0
  67. package/skills/monitor-experiment/SKILL.md +140 -0
  68. package/skills/novelty-check/SKILL.md +101 -0
  69. package/skills/openalex/SKILL.md +237 -0
  70. package/skills/overleaf-sync/SKILL.md +220 -0
  71. package/skills/paper-claim-audit/SKILL.md +348 -0
  72. package/skills/paper-compile/SKILL.md +266 -0
  73. package/skills/paper-figure/SKILL.md +312 -0
  74. package/skills/paper-illustration/SKILL.md +736 -0
  75. package/skills/paper-illustration-image2/SKILL.md +391 -0
  76. package/skills/paper-illustration-image2/scripts/paper_illustration_image2.py +255 -0
  77. package/skills/paper-plan/SKILL.md +386 -0
  78. package/skills/paper-poster/SKILL.md +19 -0
  79. package/skills/paper-poster-html/DESIGN_FINAL.md +176 -0
  80. package/skills/paper-poster-html/IMPLEMENTATION_CONVENTIONS.md +161 -0
  81. package/skills/paper-poster-html/LICENSES/posterly-MIT.txt +21 -0
  82. package/skills/paper-poster-html/NOTICE.md +57 -0
  83. package/skills/paper-poster-html/SKILL.md +323 -0
  84. package/skills/paper-poster-html/scripts/_posterly/__init__.py +0 -0
  85. package/skills/paper-poster-html/scripts/_posterly/canvas.py +200 -0
  86. package/skills/paper-poster-html/scripts/_posterly/measure.py +588 -0
  87. package/skills/paper-poster-html/scripts/_posterly/polish.py +498 -0
  88. package/skills/paper-poster-html/scripts/_posterly/preflight.py +489 -0
  89. package/skills/paper-poster-html/scripts/_posterly/render.py +215 -0
  90. package/skills/paper-poster-html/scripts/_posterly/textutil.py +16 -0
  91. package/skills/paper-poster-html/scripts/_posterly/verify_final.py +171 -0
  92. package/skills/paper-poster-html/scripts/asset_check.py +897 -0
  93. package/skills/paper-poster-html/scripts/extract_pdf_figures.py +666 -0
  94. package/skills/paper-poster-html/scripts/poster_check.py +251 -0
  95. package/skills/paper-poster-html/scripts/preprocess_figures.py +238 -0
  96. package/skills/paper-poster-html/scripts/render_preview.py +217 -0
  97. package/skills/paper-poster-html/scripts/run_gates.py +556 -0
  98. package/skills/paper-poster-html/scripts/style_check.py +1324 -0
  99. package/skills/paper-poster-html/templates/COMPONENTS.md +462 -0
  100. package/skills/paper-poster-html/templates/README.md +170 -0
  101. package/skills/paper-poster-html/templates/landscape_4col.html +1032 -0
  102. package/skills/paper-poster-html/templates/landscape_hero.html +1046 -0
  103. package/skills/paper-poster-html/templates/portrait_2col.html +947 -0
  104. package/skills/paper-poster-html/templates/tokens/acl.json +9 -0
  105. package/skills/paper-poster-html/templates/tokens/cvpr.json +9 -0
  106. package/skills/paper-poster-html/templates/tokens/generic.json +9 -0
  107. package/skills/paper-poster-html/templates/tokens/iclr.json +9 -0
  108. package/skills/paper-poster-html/templates/tokens/icml.json +9 -0
  109. package/skills/paper-poster-html/templates/tokens/neurips.json +9 -0
  110. package/skills/paper-slides/SKILL.md +635 -0
  111. package/skills/paper-talk/SKILL.md +381 -0
  112. package/skills/paper-write/SKILL.md +604 -0
  113. package/skills/paper-write/templates/IEEEtran.bst +2409 -0
  114. package/skills/paper-write/templates/IEEEtran.cls +6347 -0
  115. package/skills/paper-write/templates/iclr2026.tex +84 -0
  116. package/skills/paper-write/templates/icml2025.tex +87 -0
  117. package/skills/paper-write/templates/ieee_conference.tex +89 -0
  118. package/skills/paper-write/templates/ieee_journal.tex +93 -0
  119. package/skills/paper-write/templates/math_commands.tex +48 -0
  120. package/skills/paper-write/templates/neurips2025.tex +80 -0
  121. package/skills/paper-writing/SKILL.md +916 -0
  122. package/skills/patent-novelty-check/SKILL.md +153 -0
  123. package/skills/patent-pipeline/SKILL.md +344 -0
  124. package/skills/patent-review/SKILL.md +203 -0
  125. package/skills/pixel-art/SKILL.md +137 -0
  126. package/skills/prior-art-search/SKILL.md +146 -0
  127. package/skills/proof-checker/SKILL.md +866 -0
  128. package/skills/proof-orchestrator/NOTICE.md +24 -0
  129. package/skills/proof-orchestrator/SKILL.md +254 -0
  130. package/skills/proof-orchestrator/references/audit-output-contract.md +126 -0
  131. package/skills/proof-orchestrator/references/deepseek-routing.md +74 -0
  132. package/skills/proof-orchestrator/references/dispatch-prompts.md +227 -0
  133. package/skills/proof-orchestrator/references/notation-audit.md +135 -0
  134. package/skills/proof-orchestrator/references/proof-audit-rubric.md +70 -0
  135. package/skills/proof-orchestrator/references/stress-tests.md +38 -0
  136. package/skills/proof-writer/SKILL.md +223 -0
  137. package/skills/qzcli/SKILL.md +324 -0
  138. package/skills/rebuttal/SKILL.md +376 -0
  139. package/skills/render-html/SKILL.md +316 -0
  140. package/skills/render-html/scripts/render_html.py +1006 -0
  141. package/skills/render-html/scripts/templates/academic.html +703 -0
  142. package/skills/render-html/scripts/templates/dashboard.html +333 -0
  143. package/skills/research-lit/SKILL.md +756 -0
  144. package/skills/research-pipeline/SKILL.md +384 -0
  145. package/skills/research-refine/SKILL.md +770 -0
  146. package/skills/research-refine-pipeline/SKILL.md +186 -0
  147. package/skills/research-review/SKILL.md +198 -0
  148. package/skills/research-wiki/SKILL.md +461 -0
  149. package/skills/resubmit-pipeline/SKILL.md +447 -0
  150. package/skills/result-to-claim/SKILL.md +311 -0
  151. package/skills/run-experiment/SKILL.md +313 -0
  152. package/skills/semantic-scholar/SKILL.md +236 -0
  153. package/skills/serverless-modal/SKILL.md +335 -0
  154. package/skills/shared-references/acceptance-gate.md +324 -0
  155. package/skills/shared-references/assurance-contract.md +248 -0
  156. package/skills/shared-references/capture-antipatterns.md +78 -0
  157. package/skills/shared-references/citation-discipline.md +583 -0
  158. package/skills/shared-references/compute-env-contract.md +163 -0
  159. package/skills/shared-references/effort-contract.md +183 -0
  160. package/skills/shared-references/evidence-precheck.md +65 -0
  161. package/skills/shared-references/experiment-integrity.md +49 -0
  162. package/skills/shared-references/external-cadence.md +326 -0
  163. package/skills/shared-references/fan-out-pattern.md +366 -0
  164. package/skills/shared-references/injection-hygiene.md +127 -0
  165. package/skills/shared-references/integration-contract.md +461 -0
  166. package/skills/shared-references/output-composition.md +93 -0
  167. package/skills/shared-references/output-language.md +45 -0
  168. package/skills/shared-references/output-manifest.md +49 -0
  169. package/skills/shared-references/output-versioning.md +111 -0
  170. package/skills/shared-references/patent-format-cn.md +199 -0
  171. package/skills/shared-references/patent-format-ep.md +173 -0
  172. package/skills/shared-references/patent-format-us.md +161 -0
  173. package/skills/shared-references/patent-writing-principles.md +197 -0
  174. package/skills/shared-references/prior-art-databases.md +141 -0
  175. package/skills/shared-references/resumable-runs.md +109 -0
  176. package/skills/shared-references/review-scope-limits.md +81 -0
  177. package/skills/shared-references/review-tracing.md +391 -0
  178. package/skills/shared-references/reviewer-independence.md +79 -0
  179. package/skills/shared-references/reviewer-routing.md +852 -0
  180. package/skills/shared-references/skill-governance.md +104 -0
  181. package/skills/shared-references/taste-calibration.md +85 -0
  182. package/skills/shared-references/venue-checklists.md +114 -0
  183. package/skills/shared-references/wiki-helper-resolution.md +134 -0
  184. package/skills/shared-references/writing-principles.md +525 -0
  185. package/skills/skills-codex/README.md +102 -0
  186. package/skills/skills-codex/README_CN.md +100 -0
  187. package/skills/skills-codex/ablation-planner/SKILL.md +126 -0
  188. package/skills/skills-codex/alphaxiv/SKILL.md +186 -0
  189. package/skills/skills-codex/analyze-results/SKILL.md +45 -0
  190. package/skills/skills-codex/arxiv/SKILL.md +210 -0
  191. package/skills/skills-codex/auto-paper-improvement-loop/SKILL.md +574 -0
  192. package/skills/skills-codex/auto-review-loop/SKILL.md +500 -0
  193. package/skills/skills-codex/auto-review-loop-llm/SKILL.md +247 -0
  194. package/skills/skills-codex/auto-review-loop-minimax/SKILL.md +290 -0
  195. package/skills/skills-codex/citation-audit/SKILL.md +504 -0
  196. package/skills/skills-codex/claims-drafting/SKILL.md +239 -0
  197. package/skills/skills-codex/comm-lit-review/SKILL.md +299 -0
  198. package/skills/skills-codex/comm-lit-review/references/domain-taxonomy.md +57 -0
  199. package/skills/skills-codex/comm-lit-review/references/output-template.md +37 -0
  200. package/skills/skills-codex/comm-lit-review/references/source-policy.md +99 -0
  201. package/skills/skills-codex/comm-lit-review/references/venue-tiering.md +112 -0
  202. package/skills/skills-codex/deepxiv/SKILL.md +142 -0
  203. package/skills/skills-codex/dse-loop/SKILL.md +285 -0
  204. package/skills/skills-codex/embodiment-description/SKILL.md +129 -0
  205. package/skills/skills-codex/exa-search/SKILL.md +192 -0
  206. package/skills/skills-codex/experiment-audit/SKILL.md +286 -0
  207. package/skills/skills-codex/experiment-bridge/SKILL.md +356 -0
  208. package/skills/skills-codex/experiment-plan/SKILL.md +249 -0
  209. package/skills/skills-codex/experiment-queue/SKILL.md +401 -0
  210. package/skills/skills-codex/feishu-notify/SKILL.md +155 -0
  211. package/skills/skills-codex/figure-description/SKILL.md +138 -0
  212. package/skills/skills-codex/figure-spec/SKILL.md +252 -0
  213. package/skills/skills-codex/formula-derivation/SKILL.md +280 -0
  214. package/skills/skills-codex/gemini-search/SKILL.md +205 -0
  215. package/skills/skills-codex/grant-proposal/SKILL.md +626 -0
  216. package/skills/skills-codex/idea-creator/SKILL.md +405 -0
  217. package/skills/skills-codex/idea-discovery/SKILL.md +475 -0
  218. package/skills/skills-codex/idea-discovery-robot/SKILL.md +362 -0
  219. package/skills/skills-codex/integrity-forensics/SKILL.md +106 -0
  220. package/skills/skills-codex/interview-cheatsheet/SKILL.md +245 -0
  221. package/skills/skills-codex/invention-structuring/SKILL.md +188 -0
  222. package/skills/skills-codex/jurisdiction-format/SKILL.md +192 -0
  223. package/skills/skills-codex/kill-argument/SKILL.md +403 -0
  224. package/skills/skills-codex/mermaid-diagram/SKILL.md +379 -0
  225. package/skills/skills-codex/meta-apply/SKILL.md +154 -0
  226. package/skills/skills-codex/meta-optimize/SKILL.md +348 -0
  227. package/skills/skills-codex/monitor-experiment/SKILL.md +98 -0
  228. package/skills/skills-codex/novelty-check/SKILL.md +89 -0
  229. package/skills/skills-codex/openalex/SKILL.md +228 -0
  230. package/skills/skills-codex/overleaf-sync/SKILL.md +220 -0
  231. package/skills/skills-codex/paper-claim-audit/SKILL.md +350 -0
  232. package/skills/skills-codex/paper-compile/SKILL.md +253 -0
  233. package/skills/skills-codex/paper-figure/SKILL.md +311 -0
  234. package/skills/skills-codex/paper-illustration/SKILL.md +690 -0
  235. package/skills/skills-codex/paper-illustration-image2/SKILL.md +383 -0
  236. package/skills/skills-codex/paper-illustration-image2/scripts/paper_illustration_image2.py +255 -0
  237. package/skills/skills-codex/paper-plan/SKILL.md +278 -0
  238. package/skills/skills-codex/paper-poster/SKILL.md +19 -0
  239. package/skills/skills-codex/paper-poster-html/SKILL.md +377 -0
  240. package/skills/skills-codex/paper-slides/SKILL.md +571 -0
  241. package/skills/skills-codex/paper-talk/SKILL.md +381 -0
  242. package/skills/skills-codex/paper-write/SKILL.md +411 -0
  243. package/skills/skills-codex/paper-write/templates/IEEEtran.bst +2409 -0
  244. package/skills/skills-codex/paper-write/templates/IEEEtran.cls +6347 -0
  245. package/skills/skills-codex/paper-write/templates/aaai2026.bst +1493 -0
  246. package/skills/skills-codex/paper-write/templates/aaai2026.sty +315 -0
  247. package/skills/skills-codex/paper-write/templates/aaai2026.tex +952 -0
  248. package/skills/skills-codex/paper-write/templates/acl.sty +312 -0
  249. package/skills/skills-codex/paper-write/templates/acl2026.tex +377 -0
  250. package/skills/skills-codex/paper-write/templates/acl_natbib.bst +1940 -0
  251. package/skills/skills-codex/paper-write/templates/acm.bst +3081 -0
  252. package/skills/skills-codex/paper-write/templates/acm_mm2026.tex +204 -0
  253. package/skills/skills-codex/paper-write/templates/acmart.cls +3520 -0
  254. package/skills/skills-codex/paper-write/templates/cvpr.bst +1448 -0
  255. package/skills/skills-codex/paper-write/templates/cvpr.sty +508 -0
  256. package/skills/skills-codex/paper-write/templates/cvpr2026.tex +63 -0
  257. package/skills/skills-codex/paper-write/templates/iclr2026.tex +84 -0
  258. package/skills/skills-codex/paper-write/templates/iclr2026_conference.bst +1440 -0
  259. package/skills/skills-codex/paper-write/templates/iclr2026_conference.sty +246 -0
  260. package/skills/skills-codex/paper-write/templates/icml2026.sty +767 -0
  261. package/skills/skills-codex/paper-write/templates/icml2026.tex +662 -0
  262. package/skills/skills-codex/paper-write/templates/ieee_conference.tex +89 -0
  263. package/skills/skills-codex/paper-write/templates/ieee_journal.tex +93 -0
  264. package/skills/skills-codex/paper-write/templates/math_commands.tex +48 -0
  265. package/skills/skills-codex/paper-write/templates/neurips2026.tex +493 -0
  266. package/skills/skills-codex/paper-write/templates/neurips_2026.sty +437 -0
  267. package/skills/skills-codex/paper-writing/SKILL.md +731 -0
  268. package/skills/skills-codex/patent-novelty-check/SKILL.md +153 -0
  269. package/skills/skills-codex/patent-pipeline/SKILL.md +344 -0
  270. package/skills/skills-codex/patent-review/SKILL.md +202 -0
  271. package/skills/skills-codex/pixel-art/SKILL.md +139 -0
  272. package/skills/skills-codex/prior-art-search/SKILL.md +146 -0
  273. package/skills/skills-codex/proof-checker/SKILL.md +554 -0
  274. package/skills/skills-codex/proof-orchestrator/SKILL.md +260 -0
  275. package/skills/skills-codex/proof-orchestrator/references/audit-output-contract.md +126 -0
  276. package/skills/skills-codex/proof-orchestrator/references/deepseek-routing.md +76 -0
  277. package/skills/skills-codex/proof-orchestrator/references/dispatch-prompts.md +227 -0
  278. package/skills/skills-codex/proof-orchestrator/references/notation-audit.md +135 -0
  279. package/skills/skills-codex/proof-orchestrator/references/proof-audit-rubric.md +70 -0
  280. package/skills/skills-codex/proof-orchestrator/references/stress-tests.md +38 -0
  281. package/skills/skills-codex/proof-writer/SKILL.md +222 -0
  282. package/skills/skills-codex/qzcli/SKILL.md +324 -0
  283. package/skills/skills-codex/rebuttal/SKILL.md +305 -0
  284. package/skills/skills-codex/render-html/SKILL.md +305 -0
  285. package/skills/skills-codex/render-html/scripts/__pycache__/render_html.cpython-314.pyc +0 -0
  286. package/skills/skills-codex/render-html/scripts/render_html.py +909 -0
  287. package/skills/skills-codex/render-html/scripts/templates/academic.html +342 -0
  288. package/skills/skills-codex/render-html/scripts/templates/dashboard.html +333 -0
  289. package/skills/skills-codex/research-lit/SKILL.md +464 -0
  290. package/skills/skills-codex/research-pipeline/SKILL.md +340 -0
  291. package/skills/skills-codex/research-refine/SKILL.md +721 -0
  292. package/skills/skills-codex/research-refine-pipeline/SKILL.md +186 -0
  293. package/skills/skills-codex/research-review/SKILL.md +135 -0
  294. package/skills/skills-codex/research-wiki/SKILL.md +421 -0
  295. package/skills/skills-codex/resubmit-pipeline/SKILL.md +444 -0
  296. package/skills/skills-codex/result-to-claim/SKILL.md +246 -0
  297. package/skills/skills-codex/run-experiment/SKILL.md +236 -0
  298. package/skills/skills-codex/semantic-scholar/SKILL.md +219 -0
  299. package/skills/skills-codex/serverless-modal/SKILL.md +335 -0
  300. package/skills/skills-codex/shared-references/acceptance-gate.md +336 -0
  301. package/skills/skills-codex/shared-references/assurance-contract.md +139 -0
  302. package/skills/skills-codex/shared-references/capture-antipatterns.md +84 -0
  303. package/skills/skills-codex/shared-references/citation-discipline.md +452 -0
  304. package/skills/skills-codex/shared-references/compute-env-contract.md +163 -0
  305. package/skills/skills-codex/shared-references/effort-contract.md +143 -0
  306. package/skills/skills-codex/shared-references/evidence-precheck.md +73 -0
  307. package/skills/skills-codex/shared-references/experiment-integrity.md +49 -0
  308. package/skills/skills-codex/shared-references/external-cadence.md +334 -0
  309. package/skills/skills-codex/shared-references/fan-out-pattern.md +375 -0
  310. package/skills/skills-codex/shared-references/injection-hygiene.md +132 -0
  311. package/skills/skills-codex/shared-references/integration-contract.md +372 -0
  312. package/skills/skills-codex/shared-references/output-composition.md +98 -0
  313. package/skills/skills-codex/shared-references/output-language.md +45 -0
  314. package/skills/skills-codex/shared-references/output-manifest.md +40 -0
  315. package/skills/skills-codex/shared-references/output-versioning.md +111 -0
  316. package/skills/skills-codex/shared-references/patent-format-cn.md +199 -0
  317. package/skills/skills-codex/shared-references/patent-format-ep.md +173 -0
  318. package/skills/skills-codex/shared-references/patent-format-us.md +161 -0
  319. package/skills/skills-codex/shared-references/patent-writing-principles.md +197 -0
  320. package/skills/skills-codex/shared-references/prior-art-databases.md +141 -0
  321. package/skills/skills-codex/shared-references/resumable-runs.md +125 -0
  322. package/skills/skills-codex/shared-references/review-scope-limits.md +81 -0
  323. package/skills/skills-codex/shared-references/review-tracing.md +144 -0
  324. package/skills/skills-codex/shared-references/reviewer-independence.md +66 -0
  325. package/skills/skills-codex/shared-references/reviewer-routing.md +128 -0
  326. package/skills/skills-codex/shared-references/skill-governance.md +119 -0
  327. package/skills/skills-codex/shared-references/taste-calibration.md +90 -0
  328. package/skills/skills-codex/shared-references/venue-checklists.md +73 -0
  329. package/skills/skills-codex/shared-references/wiki-helper-resolution.md +69 -0
  330. package/skills/skills-codex/shared-references/writing-principles.md +525 -0
  331. package/skills/skills-codex/slides-polish/SKILL.md +563 -0
  332. package/skills/skills-codex/specification-writing/SKILL.md +211 -0
  333. package/skills/skills-codex/system-profile/SKILL.md +103 -0
  334. package/skills/skills-codex/training-check/SKILL.md +83 -0
  335. package/skills/skills-codex/vast-gpu/SKILL.md +394 -0
  336. package/skills/skills-codex/web-debug-search/SKILL.md +334 -0
  337. package/skills/skills-codex/wiki-enrich/SKILL.md +255 -0
  338. package/skills/skills-codex/writing-systems-papers/SKILL.md +184 -0
  339. package/skills/skills-codex-claude-review/README.md +79 -0
  340. package/skills/skills-codex-claude-review/README_CN.md +78 -0
  341. package/skills/skills-codex-claude-review/auto-paper-improvement-loop/SKILL.md +581 -0
  342. package/skills/skills-codex-claude-review/auto-review-loop/SKILL.md +510 -0
  343. package/skills/skills-codex-claude-review/novelty-check/SKILL.md +102 -0
  344. package/skills/skills-codex-claude-review/paper-figure/SKILL.md +319 -0
  345. package/skills/skills-codex-claude-review/paper-plan/SKILL.md +287 -0
  346. package/skills/skills-codex-claude-review/paper-write/SKILL.md +420 -0
  347. package/skills/skills-codex-claude-review/research-refine/SKILL.md +732 -0
  348. package/skills/skills-codex-claude-review/research-review/SKILL.md +149 -0
  349. package/skills/skills-codex-gemini-review/README.md +176 -0
  350. package/skills/skills-codex-gemini-review/README_CN.md +175 -0
  351. package/skills/skills-codex-gemini-review/auto-paper-improvement-loop/SKILL.md +331 -0
  352. package/skills/skills-codex-gemini-review/auto-review-loop/SKILL.md +304 -0
  353. package/skills/skills-codex-gemini-review/grant-proposal/SKILL.md +630 -0
  354. package/skills/skills-codex-gemini-review/idea-creator/SKILL.md +263 -0
  355. package/skills/skills-codex-gemini-review/idea-discovery/SKILL.md +275 -0
  356. package/skills/skills-codex-gemini-review/idea-discovery-robot/SKILL.md +365 -0
  357. package/skills/skills-codex-gemini-review/novelty-check/SKILL.md +92 -0
  358. package/skills/skills-codex-gemini-review/paper-figure/SKILL.md +289 -0
  359. package/skills/skills-codex-gemini-review/paper-plan/SKILL.md +265 -0
  360. package/skills/skills-codex-gemini-review/paper-poster-html/SKILL.md +102 -0
  361. package/skills/skills-codex-gemini-review/paper-slides/SKILL.md +582 -0
  362. package/skills/skills-codex-gemini-review/paper-write/SKILL.md +346 -0
  363. package/skills/skills-codex-gemini-review/paper-writing/SKILL.md +312 -0
  364. package/skills/skills-codex-gemini-review/research-refine/SKILL.md +674 -0
  365. package/skills/skills-codex-gemini-review/research-review/SKILL.md +112 -0
  366. package/skills/slides-polish/SKILL.md +565 -0
  367. package/skills/specification-writing/SKILL.md +211 -0
  368. package/skills/system-profile/SKILL.md +103 -0
  369. package/skills/training-check/SKILL.md +132 -0
  370. package/skills/vast-gpu/SKILL.md +394 -0
  371. package/skills/web-debug-search/SKILL.md +334 -0
  372. package/skills/wiki-enrich/SKILL.md +257 -0
  373. package/skills/writing-systems-papers/SKILL.md +184 -0
  374. package/templates/CLAUDE_MD_TEMPLATE.md +29 -0
  375. package/templates/EXPERIMENT_LOG_TEMPLATE.md +47 -0
  376. package/templates/EXPERIMENT_PLAN_TEMPLATE.md +51 -0
  377. package/templates/EXPERIMENT_PLAN_TEMPLATE_CN.md +53 -0
  378. package/templates/FINDINGS_TEMPLATE.md +52 -0
  379. package/templates/IDEA_CANDIDATES_TEMPLATE.md +47 -0
  380. package/templates/IDEA_CANDIDATES_TEMPLATE_CN.md +47 -0
  381. package/templates/INVENTION_BRIEF_TEMPLATE.md +87 -0
  382. package/templates/MANIFEST_TEMPLATE.md +7 -0
  383. package/templates/NARRATIVE_REPORT_TEMPLATE.md +49 -0
  384. package/templates/PAPER_PLAN_TEMPLATE.md +47 -0
  385. package/templates/PATENT_CLAIMS_TEMPLATE.md +78 -0
  386. package/templates/PATENT_SPECIFICATION_TEMPLATE.md +68 -0
  387. package/templates/README.md +57 -0
  388. package/templates/RESEARCH_BRIEF_TEMPLATE.md +35 -0
  389. package/templates/RESEARCH_BRIEF_TEMPLATE_CN.md +41 -0
  390. package/templates/RESEARCH_CONTRACT_TEMPLATE.md +60 -0
  391. package/templates/claude-hooks/corpus_write_guard.json +16 -0
  392. package/templates/claude-hooks/corpus_write_guard.py +85 -0
  393. package/templates/claude-hooks/meta_logging.json +74 -0
  394. package/templates/gitignore-trace.txt +3 -0
  395. package/tools/__pycache__/check_skills_inventory.cpython-314.pyc +0 -0
  396. package/tools/arxiv_fetch.py +311 -0
  397. package/tools/capture_filter.py +126 -0
  398. package/tools/check_skills_inventory.py +273 -0
  399. package/tools/convert_skills_to_llm_chat.py +282 -0
  400. package/tools/copilot_native_evidence.py +818 -0
  401. package/tools/deepxiv_fetch.py +213 -0
  402. package/tools/evidence_check.py +212 -0
  403. package/tools/exa_search.py +425 -0
  404. package/tools/experiment_queue/README.md +118 -0
  405. package/tools/experiment_queue/build_manifest.py +44 -0
  406. package/tools/experiment_queue/queue_manager.py +44 -0
  407. package/tools/extract_paper_style.py +560 -0
  408. package/tools/figure_renderer.py +69 -0
  409. package/tools/forensics_gate.py +669 -0
  410. package/tools/generate_codex_claude_review_overrides.py +299 -0
  411. package/tools/idea_discovery_gate.py +256 -0
  412. package/tools/install_aris.ps1 +1372 -0
  413. package/tools/install_aris.sh +1370 -0
  414. package/tools/install_aris_codex.sh +1023 -0
  415. package/tools/install_aris_copilot.sh +1052 -0
  416. package/tools/iteration_log.py +143 -0
  417. package/tools/lint_skills_helpers.sh +84 -0
  418. package/tools/meta_opt/check_ready.sh +80 -0
  419. package/tools/meta_opt/log_event.sh +91 -0
  420. package/tools/meta_opt/trigger_eval.py +280 -0
  421. package/tools/meta_opt/trigger_evals.sample.json +28 -0
  422. package/tools/openalex_fetch.py +326 -0
  423. package/tools/overleaf_audit.sh +104 -0
  424. package/tools/overleaf_setup.sh +150 -0
  425. package/tools/paper_illustration_image2.py +62 -0
  426. package/tools/provenance.py +294 -0
  427. package/tools/research_wiki.py +1720 -0
  428. package/tools/review_gate.py +502 -0
  429. package/tools/run_state.py +399 -0
  430. package/tools/save_trace.sh +477 -0
  431. package/tools/semantic_scholar_fetch.py +438 -0
  432. package/tools/skill-groups.tsv +116 -0
  433. package/tools/skill_picker.py +238 -0
  434. package/tools/smart_update.ps1 +521 -0
  435. package/tools/smart_update.sh +591 -0
  436. package/tools/smart_update_codex.sh +419 -0
  437. package/tools/smart_update_copilot.sh +605 -0
  438. package/tools/threat_scan.py +222 -0
  439. package/tools/verify_paper_audits.sh +487 -0
  440. package/tools/verify_papers.py +613 -0
  441. package/tools/verify_wiki_coverage.sh +176 -0
  442. package/tools/watchdog.py +485 -0
@@ -0,0 +1,893 @@
1
+ #!/usr/bin/env python3
2
+ """Codex app-server image bridge for Claude-first ARIS workflows.
3
+
4
+ This server exposes a narrow MCP interface that asks the local Codex desktop
5
+ app to generate an image through the app-server path. It is intentionally kept
6
+ small and dependency-free so users can copy it into `~/.claude/mcp-servers/`
7
+ and register it with Claude Code.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import base64
13
+ import json
14
+ import os
15
+ import shutil
16
+ import subprocess
17
+ import sys
18
+ import time
19
+ import traceback
20
+ import uuid
21
+ from datetime import datetime, timedelta, timezone
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+
26
+ _stdio_initialized = False
27
+
28
+
29
+ def _init_stdio() -> None:
30
+ """Rebind stdio to raw unbuffered binary streams for MCP framing.
31
+
32
+ Deferred into a function (called at the top of main()) so that merely
33
+ IMPORTING this module has no stdio side effects. os.fdopen(fileno) defaults
34
+ to closefd=True and thus seizes ownership of the fd; doing that at import
35
+ time under a test harness that captures stdio (pytest fd-capture) closes the
36
+ harness's capture fd and corrupts capture for every subsequent test. Real
37
+ server launch (python server.py) still calls this first via main(), so
38
+ runtime behavior is unchanged. Idempotent."""
39
+ global _stdio_initialized
40
+ if _stdio_initialized:
41
+ return
42
+ sys.stdout = os.fdopen(sys.stdout.fileno(), "wb", buffering=0)
43
+ sys.stdin = os.fdopen(sys.stdin.fileno(), "rb", buffering=0)
44
+ _stdio_initialized = True
45
+
46
+
47
+ SERVER_NAME = os.environ.get("CODEX_IMAGE2_SERVER_NAME", "codex-image2")
48
+ CODEX_BIN = os.environ.get("CODEX_IMAGE2_CODEX_BIN", "codex")
49
+ DEFAULT_TIMEOUT_SEC = int(os.environ.get("CODEX_IMAGE2_TIMEOUT_SEC", "600"))
50
+ DEFAULT_JOB_EXPIRY_GRACE_SEC = int(
51
+ os.environ.get("CODEX_IMAGE2_JOB_EXPIRY_GRACE_SEC", "60")
52
+ )
53
+ MAX_STATUS_WAIT_SEC = int(os.environ.get("CODEX_IMAGE2_MAX_STATUS_WAIT_SEC", "30"))
54
+ DEFAULT_MODEL = os.environ.get("CODEX_IMAGE2_MODEL", "")
55
+ DEBUG_LOG_RAW = os.environ.get("CODEX_IMAGE2_DEBUG_LOG", "").strip()
56
+ DEBUG_LOG = Path(DEBUG_LOG_RAW).expanduser() if DEBUG_LOG_RAW else None
57
+ SAVE_RUN_LOGS = os.environ.get("CODEX_IMAGE2_SAVE_RUN_LOGS", "").strip().lower() in {
58
+ "1",
59
+ "true",
60
+ "yes",
61
+ "on",
62
+ }
63
+ STATE_DIR = Path(
64
+ os.environ.get(
65
+ "CODEX_IMAGE2_STATE_DIR",
66
+ str(Path.home() / ".claude" / "state" / SERVER_NAME),
67
+ )
68
+ )
69
+ JOBS_DIR = STATE_DIR / "jobs"
70
+ RUNS_DIR = STATE_DIR / "runs"
71
+
72
+ TERMINAL_JOB_STATES = {"completed", "failed"}
73
+ PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
74
+ _use_ndjson = False
75
+
76
+
77
+ def debug_log(message: str) -> None:
78
+ if DEBUG_LOG is None:
79
+ return
80
+ try:
81
+ DEBUG_LOG.parent.mkdir(parents=True, exist_ok=True)
82
+ with DEBUG_LOG.open("a", encoding="utf-8") as fh:
83
+ fh.write(f"{message}\n")
84
+ except OSError:
85
+ pass
86
+
87
+
88
+ def send_response(response: dict[str, Any]) -> None:
89
+ global _use_ndjson
90
+
91
+ payload = json.dumps(response, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
92
+ debug_log(f"SEND {payload.decode('utf-8', errors='replace')}")
93
+ if _use_ndjson:
94
+ sys.stdout.write(payload + b"\n")
95
+ else:
96
+ header = f"Content-Length: {len(payload)}\r\n\r\n".encode("utf-8")
97
+ sys.stdout.write(header + payload)
98
+ sys.stdout.flush()
99
+
100
+
101
+ def read_message() -> dict[str, Any] | None:
102
+ global _use_ndjson
103
+
104
+ line = sys.stdin.readline()
105
+ if not line:
106
+ return None
107
+
108
+ line_text = line.decode("utf-8").rstrip("\r\n")
109
+ if line_text.lower().startswith("content-length:"):
110
+ try:
111
+ content_length = int(line_text.split(":", 1)[1].strip())
112
+ except ValueError:
113
+ return None
114
+
115
+ while True:
116
+ header_line = sys.stdin.readline()
117
+ if not header_line:
118
+ return None
119
+ if header_line in {b"\r\n", b"\n"}:
120
+ break
121
+
122
+ body = sys.stdin.read(content_length)
123
+ try:
124
+ return json.loads(body.decode("utf-8"))
125
+ except json.JSONDecodeError:
126
+ return None
127
+
128
+ if line_text.startswith("{") or line_text.startswith("["):
129
+ _use_ndjson = True
130
+ try:
131
+ return json.loads(line_text)
132
+ except json.JSONDecodeError:
133
+ return None
134
+
135
+ return None
136
+
137
+
138
+ def utc_now() -> str:
139
+ return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
140
+
141
+
142
+ def parse_utc_timestamp(raw_value: Any) -> datetime | None:
143
+ if not isinstance(raw_value, str) or not raw_value:
144
+ return None
145
+ try:
146
+ return datetime.fromisoformat(raw_value.replace("Z", "+00:00"))
147
+ except ValueError:
148
+ return None
149
+
150
+
151
+ def utc_after_seconds(seconds: int) -> str:
152
+ return (datetime.now(timezone.utc) + timedelta(seconds=seconds)).replace(
153
+ microsecond=0
154
+ ).isoformat().replace("+00:00", "Z")
155
+
156
+
157
+ def write_json(path: Path, payload: dict[str, Any]) -> None:
158
+ path.parent.mkdir(parents=True, exist_ok=True)
159
+ temp_path = path.with_suffix(path.suffix + ".tmp")
160
+ temp_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
161
+ temp_path.replace(path)
162
+
163
+
164
+ def read_json(path: Path) -> dict[str, Any]:
165
+ return json.loads(path.read_text(encoding="utf-8"))
166
+
167
+
168
+ def job_state_path(job_id: str) -> Path:
169
+ return JOBS_DIR / f"{job_id}.json"
170
+
171
+
172
+ def classify_worker_state(pid: int | None) -> str:
173
+ if not pid or pid <= 0:
174
+ return "missing"
175
+ try:
176
+ waited_pid, _ = os.waitpid(pid, os.WNOHANG)
177
+ except ChildProcessError:
178
+ try:
179
+ os.kill(pid, 0)
180
+ except OSError:
181
+ return "exited"
182
+ return "running"
183
+ except OSError:
184
+ return "exited"
185
+ if waited_pid == 0:
186
+ return "running"
187
+ return "exited"
188
+
189
+
190
+ def find_codex_bin() -> str | None:
191
+ if Path(CODEX_BIN).is_file():
192
+ return CODEX_BIN
193
+ return shutil.which(CODEX_BIN)
194
+
195
+
196
+ def normalize_string_list(raw_value: Any) -> tuple[list[str], str | None]:
197
+ if raw_value is None:
198
+ return [], None
199
+ if isinstance(raw_value, str):
200
+ candidate = raw_value.strip()
201
+ return ([candidate] if candidate else []), None
202
+ if not isinstance(raw_value, list):
203
+ return [], "referenceImagePaths must be a string or an array of strings"
204
+
205
+ values: list[str] = []
206
+ for item in raw_value:
207
+ if not isinstance(item, str):
208
+ return [], "referenceImagePaths entries must be strings"
209
+ candidate = item.strip()
210
+ if candidate:
211
+ values.append(candidate)
212
+ return values, None
213
+
214
+
215
+ def resolve_cwd(raw_cwd: str | None) -> tuple[Path, str | None]:
216
+ if raw_cwd:
217
+ cwd = Path(raw_cwd).expanduser()
218
+ else:
219
+ cwd = Path.cwd()
220
+ try:
221
+ cwd = cwd.resolve()
222
+ except OSError as exc:
223
+ return cwd, f"failed to resolve cwd {raw_cwd!r}: {exc}"
224
+ if not cwd.exists():
225
+ return cwd, f"working directory does not exist: {cwd}"
226
+ if not cwd.is_dir():
227
+ return cwd, f"working directory is not a directory: {cwd}"
228
+ return cwd, None
229
+
230
+
231
+ def resolve_output_path(raw_output_path: str | None, *, cwd: Path, job_id: str) -> Path:
232
+ if raw_output_path:
233
+ path = Path(raw_output_path).expanduser()
234
+ if not path.is_absolute():
235
+ path = cwd / path
236
+ else:
237
+ path = cwd / "figures" / "ai_generated" / f"codex-image2-{job_id}.png"
238
+ return path.resolve()
239
+
240
+
241
+ def allowed_output_root(*, cwd: Path) -> Path:
242
+ return (cwd / "figures" / "ai_generated").resolve()
243
+
244
+
245
+ def validate_output_path(output_path: Path, *, cwd: Path) -> str | None:
246
+ root = allowed_output_root(cwd=cwd)
247
+ try:
248
+ output_path.relative_to(root)
249
+ except ValueError:
250
+ return f"outputPath must stay under {root}"
251
+ if output_path == root:
252
+ return f"outputPath must be a file under {root}, not the directory itself"
253
+ return None
254
+
255
+
256
+ def parse_timeout_seconds(raw_value: Any) -> tuple[int | None, str | None]:
257
+ if raw_value is None:
258
+ return DEFAULT_TIMEOUT_SEC, None
259
+ try:
260
+ timeout_sec = int(raw_value)
261
+ except (TypeError, ValueError):
262
+ return None, "timeoutSeconds must be an integer"
263
+ if timeout_sec <= 0:
264
+ return None, "timeoutSeconds must be positive"
265
+ return timeout_sec, None
266
+
267
+
268
+ def is_png_bytes(raw_bytes: bytes) -> bool:
269
+ return raw_bytes.startswith(PNG_SIGNATURE)
270
+
271
+
272
+ def maybe_run_log_path(run_id: str) -> Path | None:
273
+ if not SAVE_RUN_LOGS:
274
+ return None
275
+ return RUNS_DIR / f"{run_id}.log"
276
+
277
+
278
+ def scrub_job_request(job: dict[str, Any]) -> None:
279
+ request = job.get("request")
280
+ if not isinstance(request, dict):
281
+ return
282
+ job["request"] = {
283
+ "cwd": request.get("cwd"),
284
+ "outputPath": request.get("outputPath"),
285
+ "timeoutSec": request.get("timeoutSec"),
286
+ }
287
+
288
+
289
+ def fail_job(job_path: Path, job: dict[str, Any], message: str) -> dict[str, Any]:
290
+ finished_at = utc_now()
291
+ job["status"] = "failed"
292
+ job["error"] = message
293
+ job["completedAt"] = finished_at
294
+ job["updatedAt"] = finished_at
295
+ job["result"] = None
296
+ scrub_job_request(job)
297
+ write_json(job_path, job)
298
+ return job
299
+
300
+
301
+ def build_bridge_prompt(
302
+ prompt: str,
303
+ *,
304
+ system: str | None,
305
+ reference_image_paths: list[str],
306
+ ) -> str:
307
+ sections: list[str] = [
308
+ "You are operating behind a Codex native image-generation MCP bridge.",
309
+ "Use native image generation through the Codex app-server.",
310
+ "Do not use shell commands, Python, SVG, HTML, Canvas, or manual bitmap encoding.",
311
+ "Do not fabricate success. If native image generation is unavailable, reply exactly NATIVE_IMAGE_UNAVAILABLE.",
312
+ "Generate exactly one publication-quality raster image unless the user explicitly requests multiple variants.",
313
+ "",
314
+ ]
315
+ selected_system = (system or "").strip()
316
+ if selected_system:
317
+ sections.extend(["## System Instructions", selected_system, ""])
318
+ if reference_image_paths:
319
+ sections.append("## Reference Images")
320
+ sections.append(
321
+ "These local image files are available. If image viewing tools are available in this Codex session, inspect them before generating."
322
+ )
323
+ for path in reference_image_paths:
324
+ sections.append(f"- {path}")
325
+ sections.append("")
326
+ sections.extend(["## User Request", prompt.strip()])
327
+ return "\n".join(sections).strip()
328
+
329
+
330
+ def parse_debug_json_messages(raw_stdout: str) -> list[dict[str, Any]]:
331
+ messages: list[dict[str, Any]] = []
332
+ buffer: list[str] = []
333
+ collecting = False
334
+
335
+ for raw_line in raw_stdout.splitlines():
336
+ if not collecting:
337
+ if raw_line.startswith("< {"):
338
+ collecting = True
339
+ buffer = [raw_line[2:]]
340
+ continue
341
+
342
+ if not raw_line.startswith("< "):
343
+ collecting = False
344
+ buffer = []
345
+ continue
346
+
347
+ buffer.append(raw_line[2:])
348
+ candidate = "\n".join(buffer)
349
+ try:
350
+ payload = json.loads(candidate)
351
+ except json.JSONDecodeError:
352
+ continue
353
+ messages.append(payload)
354
+ collecting = False
355
+ buffer = []
356
+
357
+ return messages
358
+
359
+
360
+ def extract_run_summary(messages: list[dict[str, Any]]) -> dict[str, Any]:
361
+ thread_id: str | None = None
362
+ agent_messages: list[str] = []
363
+ image_items: list[dict[str, Any]] = []
364
+ command_items: list[dict[str, Any]] = []
365
+
366
+ for message in messages:
367
+ if not isinstance(message, dict):
368
+ continue
369
+ params = message.get("params")
370
+ if isinstance(params, dict):
371
+ candidate_thread_id = params.get("threadId")
372
+ if isinstance(candidate_thread_id, str) and candidate_thread_id:
373
+ thread_id = candidate_thread_id
374
+ item = params.get("item")
375
+ if isinstance(item, dict):
376
+ item_type = item.get("type")
377
+ if item_type == "agentMessage":
378
+ text = item.get("text")
379
+ if isinstance(text, str):
380
+ agent_messages.append(text)
381
+ elif item_type == "imageGeneration":
382
+ image_items.append(item)
383
+ elif item_type == "commandExecution":
384
+ command_items.append(item)
385
+
386
+ result = message.get("result")
387
+ if isinstance(result, dict):
388
+ thread = result.get("thread")
389
+ if isinstance(thread, dict):
390
+ candidate_thread_id = thread.get("id")
391
+ if isinstance(candidate_thread_id, str) and candidate_thread_id:
392
+ thread_id = candidate_thread_id
393
+
394
+ return {
395
+ "threadId": thread_id,
396
+ "agentMessages": agent_messages,
397
+ "imageItems": image_items,
398
+ "commandItems": command_items,
399
+ }
400
+
401
+
402
+ def materialize_generated_image(
403
+ image_item: dict[str, Any],
404
+ output_path: Path,
405
+ ) -> tuple[Path | None, str | None, str | None, str | None]:
406
+ saved_path_value = image_item.get("savedPath")
407
+ revised_prompt = image_item.get("revisedPrompt")
408
+ revised_prompt_text = revised_prompt if isinstance(revised_prompt, str) else None
409
+
410
+ if isinstance(saved_path_value, str) and saved_path_value:
411
+ source_path = Path(saved_path_value).expanduser()
412
+ if source_path.is_file():
413
+ source_bytes = source_path.read_bytes()
414
+ if not is_png_bytes(source_bytes):
415
+ return None, None, None, "imageGeneration savedPath did not contain a PNG image"
416
+ output_path.parent.mkdir(parents=True, exist_ok=True)
417
+ shutil.copy2(source_path, output_path)
418
+ return output_path, str(source_path), revised_prompt_text, None
419
+
420
+ raw_result = image_item.get("result")
421
+ if isinstance(raw_result, str) and raw_result.strip():
422
+ try:
423
+ decoded = base64.b64decode(raw_result)
424
+ except ValueError as exc:
425
+ return None, None, None, f"imageGeneration result was not valid base64: {exc}"
426
+ if not is_png_bytes(decoded):
427
+ return None, None, None, "imageGeneration result did not decode to a PNG image"
428
+ output_path.parent.mkdir(parents=True, exist_ok=True)
429
+ output_path.write_bytes(decoded)
430
+ return output_path, None, revised_prompt_text, None
431
+
432
+ return None, None, None, "imageGeneration item did not contain a savedPath or decodable result"
433
+
434
+
435
+ def run_codex_image(
436
+ prompt: str,
437
+ *,
438
+ cwd: Path,
439
+ output_path: Path,
440
+ system: str | None = None,
441
+ model: str | None = None,
442
+ reference_image_paths: list[str] | None = None,
443
+ timeout_sec: int | None = None,
444
+ run_log_path: Path | None = None,
445
+ ) -> tuple[dict[str, Any] | None, str | None]:
446
+ bin_path = find_codex_bin()
447
+ if not bin_path:
448
+ return None, f"Codex CLI not found: {CODEX_BIN}"
449
+
450
+ output_path = output_path.resolve()
451
+ output_path_error = validate_output_path(output_path, cwd=cwd)
452
+ if output_path_error:
453
+ return None, output_path_error
454
+
455
+ normalized_refs = reference_image_paths or []
456
+ prompt_text = build_bridge_prompt(
457
+ prompt,
458
+ system=system,
459
+ reference_image_paths=normalized_refs,
460
+ )
461
+ cmd = [bin_path]
462
+ selected_model = model or DEFAULT_MODEL
463
+ if selected_model:
464
+ cmd.extend(["-c", f'model="{selected_model}"'])
465
+ cmd.extend(["debug", "app-server", "send-message-v2", prompt_text])
466
+
467
+ effective_timeout = timeout_sec or DEFAULT_TIMEOUT_SEC
468
+ debug_log(f"RUN {' '.join(cmd)} cwd={cwd}")
469
+ try:
470
+ started = time.monotonic()
471
+ result = subprocess.run(
472
+ cmd,
473
+ capture_output=True,
474
+ text=True,
475
+ stdin=subprocess.DEVNULL,
476
+ cwd=str(cwd),
477
+ timeout=effective_timeout,
478
+ check=False,
479
+ )
480
+ duration_ms = int((time.monotonic() - started) * 1000)
481
+ except subprocess.TimeoutExpired:
482
+ return None, f"Codex image generation timed out after {effective_timeout} seconds"
483
+
484
+ if run_log_path is not None:
485
+ run_log_path.parent.mkdir(parents=True, exist_ok=True)
486
+ run_log_path.write_text(
487
+ result.stdout + ("\n[stderr]\n" + result.stderr if result.stderr else ""),
488
+ encoding="utf-8",
489
+ )
490
+
491
+ messages = parse_debug_json_messages(result.stdout)
492
+ summary = extract_run_summary(messages)
493
+
494
+ if summary["commandItems"]:
495
+ return None, (
496
+ "Codex attempted shell-based image creation instead of native image generation. "
497
+ "This bridge only accepts native imageGeneration events."
498
+ )
499
+
500
+ image_items = summary["imageItems"]
501
+ if not image_items:
502
+ final_message = ""
503
+ for candidate in reversed(summary["agentMessages"]):
504
+ if candidate and candidate.strip():
505
+ final_message = candidate.strip()
506
+ break
507
+ stderr_text = result.stderr.strip()
508
+ if final_message == "NATIVE_IMAGE_UNAVAILABLE":
509
+ return None, "Codex app-server reported that native image generation is unavailable in this session."
510
+ if final_message:
511
+ return None, f"Codex did not emit an imageGeneration item. Final message: {final_message}"
512
+ if stderr_text:
513
+ return None, f"Codex did not emit an imageGeneration item. stderr: {stderr_text}"
514
+ return None, "Codex did not emit an imageGeneration item."
515
+
516
+ image_item = image_items[-1]
517
+ generated_output, source_saved_path, revised_prompt, materialize_error = materialize_generated_image(
518
+ image_item,
519
+ output_path,
520
+ )
521
+ if materialize_error:
522
+ return None, materialize_error
523
+ if generated_output is None:
524
+ return None, "Codex image bridge failed to materialize the generated image"
525
+
526
+ response_text = ""
527
+ for candidate in reversed(summary["agentMessages"]):
528
+ if candidate is not None:
529
+ response_text = candidate
530
+ break
531
+
532
+ return {
533
+ "threadId": summary["threadId"],
534
+ "response": response_text,
535
+ "model": selected_model or None,
536
+ "duration_ms": duration_ms,
537
+ "nativeToolConfirmed": True,
538
+ "imageCount": len(image_items),
539
+ "outputPath": str(generated_output),
540
+ "sourceSavedPath": source_saved_path,
541
+ "revisedPrompt": revised_prompt,
542
+ "runLogPath": str(run_log_path) if run_log_path is not None else None,
543
+ }, None
544
+
545
+
546
+ def serialize_job(job: dict[str, Any]) -> dict[str, Any]:
547
+ result = job.get("result") or {}
548
+ return {
549
+ "jobId": job.get("jobId"),
550
+ "status": job.get("status"),
551
+ "done": job.get("status") in TERMINAL_JOB_STATES,
552
+ "threadId": result.get("threadId"),
553
+ "response": result.get("response"),
554
+ "model": result.get("model"),
555
+ "duration_ms": result.get("duration_ms"),
556
+ "nativeToolConfirmed": result.get("nativeToolConfirmed"),
557
+ "imageCount": result.get("imageCount"),
558
+ "outputPath": result.get("outputPath"),
559
+ "sourceSavedPath": result.get("sourceSavedPath"),
560
+ "revisedPrompt": result.get("revisedPrompt"),
561
+ "runLogPath": result.get("runLogPath"),
562
+ "error": job.get("error"),
563
+ "createdAt": job.get("createdAt"),
564
+ "startedAt": job.get("startedAt"),
565
+ "completedAt": job.get("completedAt"),
566
+ "updatedAt": job.get("updatedAt"),
567
+ "expiresAt": job.get("expiresAt"),
568
+ "resumeHint": "Call generate_status with this jobId until done=true.",
569
+ }
570
+
571
+
572
+ def start_async_generate(
573
+ prompt: str,
574
+ *,
575
+ cwd: str | None = None,
576
+ output_path: str | None = None,
577
+ system: str | None = None,
578
+ model: str | None = None,
579
+ reference_image_paths: Any = None,
580
+ timeout_seconds: Any = None,
581
+ ) -> tuple[dict[str, Any] | None, str | None]:
582
+ resolved_cwd, cwd_error = resolve_cwd(cwd)
583
+ if cwd_error:
584
+ return None, cwd_error
585
+
586
+ refs, refs_error = normalize_string_list(reference_image_paths)
587
+ if refs_error:
588
+ return None, refs_error
589
+
590
+ timeout_sec, timeout_error = parse_timeout_seconds(timeout_seconds)
591
+ if timeout_error:
592
+ return None, timeout_error
593
+
594
+ job_id = uuid.uuid4().hex
595
+ resolved_output_path = resolve_output_path(output_path, cwd=resolved_cwd, job_id=job_id)
596
+ output_path_error = validate_output_path(resolved_output_path, cwd=resolved_cwd)
597
+ if output_path_error:
598
+ return None, output_path_error
599
+ created_at = utc_now()
600
+ job = {
601
+ "jobId": job_id,
602
+ "status": "queued",
603
+ "createdAt": created_at,
604
+ "startedAt": None,
605
+ "completedAt": None,
606
+ "updatedAt": created_at,
607
+ "expiresAt": utc_after_seconds(timeout_sec + DEFAULT_JOB_EXPIRY_GRACE_SEC),
608
+ "error": None,
609
+ "result": None,
610
+ "workerPid": None,
611
+ "request": {
612
+ "prompt": prompt,
613
+ "cwd": str(resolved_cwd),
614
+ "outputPath": str(resolved_output_path),
615
+ "system": system,
616
+ "model": model,
617
+ "referenceImagePaths": refs,
618
+ "timeoutSec": timeout_sec,
619
+ },
620
+ }
621
+
622
+ job_path = job_state_path(job_id)
623
+ write_json(job_path, job)
624
+
625
+ try:
626
+ worker = subprocess.Popen(
627
+ [sys.executable, str(Path(__file__).resolve()), "--run-job", job_id],
628
+ stdin=subprocess.DEVNULL,
629
+ stdout=subprocess.DEVNULL,
630
+ stderr=subprocess.DEVNULL,
631
+ close_fds=True,
632
+ start_new_session=True,
633
+ )
634
+ except OSError as exc:
635
+ job["status"] = "failed"
636
+ job["completedAt"] = utc_now()
637
+ job["updatedAt"] = job["completedAt"]
638
+ job["error"] = f"Failed to launch background image worker: {exc}"
639
+ write_json(job_path, job)
640
+ return None, job["error"]
641
+
642
+ job["workerPid"] = worker.pid
643
+ job["updatedAt"] = utc_now()
644
+ write_json(job_path, job)
645
+ debug_log(f"JOB_START job_id={job_id} worker_pid={worker.pid}")
646
+ return serialize_job(job), None
647
+
648
+
649
+ def get_generate_status(job_id: str, *, wait_seconds: int = 0) -> tuple[dict[str, Any] | None, str | None]:
650
+ job_path = job_state_path(job_id)
651
+ if not job_path.exists():
652
+ return None, f"Unknown jobId: {job_id}"
653
+
654
+ deadline = time.monotonic() + max(wait_seconds, 0)
655
+ while True:
656
+ job = read_json(job_path)
657
+ if job.get("status") in {"queued", "running"}:
658
+ expires_at = parse_utc_timestamp(job.get("expiresAt"))
659
+ if expires_at is not None and datetime.now(timezone.utc) > expires_at:
660
+ job = fail_job(job_path, job, "Background image worker exceeded its deadline before writing a final result")
661
+ else:
662
+ worker_state = classify_worker_state(job.get("workerPid"))
663
+ if worker_state in {"missing", "exited"}:
664
+ job = fail_job(job_path, job, "Background image worker exited before writing a final result")
665
+ if job.get("status") in TERMINAL_JOB_STATES:
666
+ return serialize_job(job), None
667
+ if time.monotonic() >= deadline:
668
+ return serialize_job(job), None
669
+ time.sleep(min(0.5, max(deadline - time.monotonic(), 0.0)))
670
+
671
+
672
+ def run_async_job(job_id: str) -> int:
673
+ job_path = job_state_path(job_id)
674
+ if not job_path.exists():
675
+ debug_log(f"JOB_MISSING job_id={job_id}")
676
+ return 1
677
+
678
+ job = read_json(job_path)
679
+ job["status"] = "running"
680
+ job["startedAt"] = utc_now()
681
+ job["updatedAt"] = job["startedAt"]
682
+ job["workerPid"] = os.getpid()
683
+ write_json(job_path, job)
684
+ debug_log(f"JOB_RUNNING job_id={job_id} worker_pid={os.getpid()}")
685
+
686
+ request = job.get("request") or {}
687
+ run_log_path = maybe_run_log_path(job_id)
688
+ try:
689
+ payload, error = run_codex_image(
690
+ str(request.get("prompt", "")),
691
+ cwd=Path(str(request.get("cwd"))),
692
+ output_path=Path(str(request.get("outputPath"))),
693
+ system=request.get("system"),
694
+ model=request.get("model"),
695
+ reference_image_paths=request.get("referenceImagePaths") or [],
696
+ timeout_sec=request.get("timeoutSec"),
697
+ run_log_path=run_log_path,
698
+ )
699
+ except Exception as exc:
700
+ payload = None
701
+ error = f"Background image generation crashed: {exc}"
702
+ debug_log(traceback.format_exc())
703
+
704
+ finished_at = utc_now()
705
+ job = read_json(job_path)
706
+ job["updatedAt"] = finished_at
707
+ job["completedAt"] = finished_at
708
+ if error:
709
+ fail_job(job_path, job, error)
710
+ debug_log(f"JOB_FAILED job_id={job_id} error={error}")
711
+ return 1
712
+
713
+ job["status"] = "completed"
714
+ job["error"] = None
715
+ job["result"] = payload
716
+ scrub_job_request(job)
717
+ write_json(job_path, job)
718
+ debug_log(f"JOB_COMPLETED job_id={job_id} output={(payload or {}).get('outputPath')}")
719
+ return 0
720
+
721
+
722
+ def tool_success(request_id: Any, payload: dict[str, Any]) -> dict[str, Any]:
723
+ return {
724
+ "jsonrpc": "2.0",
725
+ "id": request_id,
726
+ "result": {
727
+ "content": [{"type": "text", "text": json.dumps(payload, ensure_ascii=False)}],
728
+ },
729
+ }
730
+
731
+
732
+ def tool_error(request_id: Any, message: str) -> dict[str, Any]:
733
+ return {
734
+ "jsonrpc": "2.0",
735
+ "id": request_id,
736
+ "result": {
737
+ "content": [{"type": "text", "text": json.dumps({"error": message}, ensure_ascii=False)}],
738
+ "isError": True,
739
+ },
740
+ }
741
+
742
+
743
+ def handle_request(request: dict[str, Any]) -> dict[str, Any] | None:
744
+ request_id = request.get("id")
745
+ method = request.get("method", "")
746
+ params = request.get("params", {})
747
+ if DEBUG_LOG is not None:
748
+ param_keys = sorted(params.keys()) if isinstance(params, dict) else []
749
+ debug_log(f"REQUEST id={request_id!r} method={method} param_keys={param_keys}")
750
+
751
+ if request_id is None:
752
+ if method in {"notifications/initialized", "initialized"}:
753
+ return None
754
+ return None
755
+
756
+ if method == "initialize":
757
+ return {
758
+ "jsonrpc": "2.0",
759
+ "id": request_id,
760
+ "result": {
761
+ "protocolVersion": "2024-11-05",
762
+ "capabilities": {"tools": {}},
763
+ "serverInfo": {"name": SERVER_NAME, "version": "0.1.0"},
764
+ },
765
+ }
766
+
767
+ if method == "ping":
768
+ return {"jsonrpc": "2.0", "id": request_id, "result": {}}
769
+
770
+ if method == "resources/list":
771
+ return {"jsonrpc": "2.0", "id": request_id, "result": {"resources": []}}
772
+
773
+ if method == "resources/templates/list":
774
+ return {"jsonrpc": "2.0", "id": request_id, "result": {"resourceTemplates": []}}
775
+
776
+ if method in {"notifications/initialized", "initialized"}:
777
+ return {"jsonrpc": "2.0", "id": request_id, "result": {}}
778
+
779
+ if method == "tools/list":
780
+ return {
781
+ "jsonrpc": "2.0",
782
+ "id": request_id,
783
+ "result": {
784
+ "tools": [
785
+ {
786
+ "name": "generate_start",
787
+ "description": "Start a background native image generation job through the Codex app-server.",
788
+ "inputSchema": {
789
+ "type": "object",
790
+ "properties": {
791
+ "prompt": {"type": "string", "description": "Image generation prompt"},
792
+ "cwd": {"type": "string", "description": "Optional working directory"},
793
+ "outputPath": {"type": "string", "description": "Optional output file path; defaults to figures/ai_generated"},
794
+ "system": {"type": "string", "description": "Optional extra bridge instructions"},
795
+ "model": {"type": "string", "description": "Optional Codex text model override"},
796
+ "referenceImagePaths": {
797
+ "oneOf": [
798
+ {"type": "string"},
799
+ {"type": "array", "items": {"type": "string"}},
800
+ ],
801
+ "description": "Optional local reference image paths that Codex may inspect before generating",
802
+ },
803
+ "timeoutSeconds": {
804
+ "type": "integer",
805
+ "description": "Optional positive timeout for the underlying Codex image call",
806
+ },
807
+ },
808
+ "required": ["prompt"],
809
+ },
810
+ },
811
+ {
812
+ "name": "generate_status",
813
+ "description": "Check whether a background image generation job has finished and fetch the output path when available.",
814
+ "inputSchema": {
815
+ "type": "object",
816
+ "properties": {
817
+ "jobId": {"type": "string", "description": "Background image job id"},
818
+ "job_id": {"type": "string", "description": "Alias of jobId"},
819
+ "waitSeconds": {
820
+ "type": "integer",
821
+ "description": "Optional bounded wait before returning status; capped to keep the MCP server responsive",
822
+ },
823
+ },
824
+ "required": ["jobId"],
825
+ },
826
+ },
827
+ ]
828
+ },
829
+ }
830
+
831
+ if method == "tools/call":
832
+ name = params.get("name", "")
833
+ args = params.get("arguments", {})
834
+ if not isinstance(args, dict):
835
+ return tool_error(request_id, "tool arguments must be an object")
836
+
837
+ if name == "generate_start":
838
+ prompt = str(args.get("prompt", "")).strip()
839
+ if not prompt:
840
+ return tool_error(request_id, "prompt is required")
841
+ payload, error = start_async_generate(
842
+ prompt,
843
+ cwd=args.get("cwd"),
844
+ output_path=args.get("outputPath"),
845
+ system=args.get("system"),
846
+ model=args.get("model"),
847
+ reference_image_paths=args.get("referenceImagePaths"),
848
+ timeout_seconds=args.get("timeoutSeconds"),
849
+ )
850
+ if error:
851
+ return tool_error(request_id, error)
852
+ return tool_success(request_id, payload or {})
853
+
854
+ if name == "generate_status":
855
+ job_id = args.get("jobId") or args.get("job_id")
856
+ if not job_id:
857
+ return tool_error(request_id, "jobId or job_id is required")
858
+ wait_seconds = args.get("waitSeconds", 0)
859
+ try:
860
+ wait_seconds = int(wait_seconds)
861
+ except (TypeError, ValueError):
862
+ wait_seconds = 0
863
+ wait_seconds = min(max(wait_seconds, 0), MAX_STATUS_WAIT_SEC)
864
+ payload, error = get_generate_status(str(job_id), wait_seconds=wait_seconds)
865
+ if error:
866
+ return tool_error(request_id, error)
867
+ return tool_success(request_id, payload or {})
868
+
869
+ return tool_error(request_id, f"unknown tool: {name}")
870
+
871
+ return {
872
+ "jsonrpc": "2.0",
873
+ "id": request_id,
874
+ "error": {"code": -32601, "message": f"Unknown method: {method}"},
875
+ }
876
+
877
+
878
+ def main() -> int:
879
+ _init_stdio()
880
+ if len(sys.argv) == 3 and sys.argv[1] == "--run-job":
881
+ return run_async_job(sys.argv[2])
882
+
883
+ while True:
884
+ request = read_message()
885
+ if request is None:
886
+ return 0
887
+ response = handle_request(request)
888
+ if response is not None:
889
+ send_response(response)
890
+
891
+
892
+ if __name__ == "__main__":
893
+ raise SystemExit(main())