minimal-agent 0.8.2 → 0.10.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 (484) hide show
  1. package/README.md +14 -12
  2. package/package.json +34 -1
  3. package/plugins/cron-runner/.claude-plugin/plugin.json +5 -0
  4. package/plugins/cron-runner/commands/cron.md +26 -0
  5. package/plugins/cron-runner/commands/crons.md +7 -0
  6. package/plugins/cron-runner/plugin.js +34 -0
  7. package/plugins/cron-runner/src/commands.js +348 -0
  8. package/plugins/cron-runner/src/cronTime.js +148 -0
  9. package/plugins/cron-runner/src/lock.js +68 -0
  10. package/plugins/cron-runner/src/runs.js +89 -0
  11. package/plugins/cron-runner/src/scheduleEngine.js +72 -0
  12. package/plugins/cron-runner/src/scheduler.js +355 -0
  13. package/plugins/cron-runner/src/schema.js +140 -0
  14. package/plugins/cron-runner/src/service.js +167 -0
  15. package/plugins/cron-runner/src/store.js +298 -0
  16. package/plugins/cron-runner/src/types.js +1 -0
  17. package/plugins/workflow-runner/commands/workflow.md +4 -4
  18. package/plugins/workflow-runner/commands/workflows.md +1 -1
  19. package/plugins/workflow-runner/plugin.js +3 -1
  20. package/plugins/workflow-runner/src/agentCommon.js +249 -51
  21. package/plugins/workflow-runner/src/dslManifest.js +72 -0
  22. package/plugins/workflow-runner/src/expressions.js +198 -15
  23. package/plugins/workflow-runner/src/index.js +40 -8
  24. package/plugins/workflow-runner/src/jsonPayload.js +1 -1
  25. package/plugins/workflow-runner/src/loader.js +286 -72
  26. package/plugins/workflow-runner/src/multimodal.js +37 -0
  27. package/plugins/workflow-runner/src/recovery.js +49 -39
  28. package/plugins/workflow-runner/src/runner.js +802 -287
  29. package/plugins/workflow-runner/src/stepExecutors/agent.js +157 -39
  30. package/plugins/workflow-runner/src/stepExecutors/assert.js +16 -3
  31. package/plugins/workflow-runner/src/stepExecutors/llm.js +60 -111
  32. package/plugins/workflow-runner/src/stepExecutors/skill.js +47 -47
  33. package/plugins/workflow-runner/src/stepExecutors/tool.js +76 -10
  34. package/plugins/workflow-runner/src/stepExecutors/verify.js +27 -10
  35. package/plugins/workflow-runner/src/taskIdentity.js +296 -0
  36. package/plugins/workflow-runner/src/types.js +19 -9
  37. package/plugins/workflow-runner/src/workflowState.js +516 -33
  38. package/skills/adversarial-review/SKILL.md +0 -1
  39. package/skills/ai-image/SKILL.md +89 -0
  40. package/skills/ai-image/scripts/generate.mjs +497 -0
  41. package/skills/ai-image/scripts/generate_from_plan.mjs +469 -0
  42. package/skills/ai-image/scripts/generate_from_plan_test.mjs +223 -0
  43. package/skills/ai-image/scripts/generate_test.mjs +74 -0
  44. package/skills/browser-automation/SKILL.md +47 -0
  45. package/skills/docx/SKILL.md +25 -3
  46. package/skills/docx/scripts/office/helpers/__pycache__/__init__.cpython-314.pyc +0 -0
  47. package/skills/docx/scripts/office/helpers/__pycache__/merge_runs.cpython-314.pyc +0 -0
  48. package/skills/docx/scripts/office/helpers/__pycache__/simplify_redlines.cpython-314.pyc +0 -0
  49. package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-314.pyc +0 -0
  50. package/skills/docx/scripts/office/validators/__pycache__/base.cpython-314.pyc +0 -0
  51. package/skills/docx/scripts/office/validators/__pycache__/docx.cpython-314.pyc +0 -0
  52. package/skills/docx/scripts/office/validators/__pycache__/pptx.cpython-314.pyc +0 -0
  53. package/skills/docx/scripts/office/validators/__pycache__/redlining.cpython-314.pyc +0 -0
  54. package/skills/docx/scripts/opc_check.py +253 -0
  55. package/skills/image-gen-openrouter/SKILL.md +10 -2
  56. package/skills/image-stats/SKILL.md +65 -0
  57. package/skills/image-stats/scripts/__pycache__/image_stats.cpython-314.pyc +0 -0
  58. package/skills/image-stats/scripts/__pycache__/image_stats_test.cpython-314.pyc +0 -0
  59. package/skills/image-stats/scripts/image_stats.py +368 -0
  60. package/skills/image-stats/scripts/image_stats_test.py +181 -0
  61. package/skills/pptx/SKILL.md +123 -95
  62. package/skills/pptx/design.md +82 -0
  63. package/skills/pptx/examples/README.md +29 -0
  64. package/skills/pptx/examples/hybrid_example.mjs +107 -0
  65. package/skills/pptx/examples/hybrid_example.pptx +0 -0
  66. package/skills/pptx/pptxgenjs.md +41 -0
  67. package/skills/pptx/scripts/deck_helpers.mjs +224 -0
  68. package/skills/pptx/scripts/layout_lint.py +293 -0
  69. package/skills/pptx/scripts/layout_lint_test.py +160 -0
  70. package/skills/pptx/scripts/opc_check.py +339 -0
  71. package/skills/pptx/scripts/svg_fallback.mjs +145 -0
  72. package/skills/pptx/svg.md +67 -0
  73. package/skills/tts-zh/SKILL.md +19 -2
  74. package/skills/ui-design/SKILL.md +158 -0
  75. package/skills/ui-design/claude_design.md +309 -0
  76. package/skills/ui-design/evals/evals.json +254 -0
  77. package/skills/ui-design/references/aesthetic-direction.md +83 -0
  78. package/skills/ui-design/references/claude-design-protocol.md +36 -0
  79. package/skills/ui-design/references/design-context.md +53 -0
  80. package/skills/ui-design/references/design-execution-playbook.md +82 -0
  81. package/skills/ui-design/references/export-pptx.md +124 -0
  82. package/skills/ui-design/references/layout-composition.md +654 -0
  83. package/skills/ui-design/references/preview-feedback.md +37 -0
  84. package/skills/ui-design/references/source-image-layout.md +189 -0
  85. package/skills/ui-design/references/starter-components.md +78 -0
  86. package/skills/ui-design/references/vi-aegis-vector.md +144 -0
  87. package/skills/ui-design/references/vi-amber-prism.md +133 -0
  88. package/skills/ui-design/references/vi-aqua-glass.md +200 -0
  89. package/skills/ui-design/references/vi-comfort-vision.md +147 -0
  90. package/skills/ui-design/references/vi-ember-prism.md +140 -0
  91. package/skills/ui-design/references/vi-nebula-console.md +151 -0
  92. package/skills/ui-design/references/vi-obsidian-optics.md +139 -0
  93. package/skills/ui-design/references/vi-obsidian-xui.md +143 -0
  94. package/skills/ui-design/references/vi-precision-horizon-dark.md +117 -0
  95. package/skills/ui-design/references/vi-precision-horizon.md +215 -0
  96. package/skills/ui-design/references/vi-solar-relay.md +130 -0
  97. package/skills/ui-design/references/vi-template-workflow.md +48 -0
  98. package/skills/ui-design/references/vi-verdant-grid.md +141 -0
  99. package/skills/ui-design/references/vi-violet-meridian.md +172 -0
  100. package/skills/ui-design/scripts/__pycache__/assemble_material.cpython-314.pyc +0 -0
  101. package/skills/ui-design/scripts/__pycache__/assemble_material_test.cpython-314.pyc +0 -0
  102. package/skills/ui-design/scripts/__pycache__/chart_sanity_check.cpython-314.pyc +0 -0
  103. package/skills/ui-design/scripts/__pycache__/check_language_policy.cpython-314.pyc +0 -0
  104. package/skills/ui-design/scripts/__pycache__/check_ui_artifact.cpython-314.pyc +0 -0
  105. package/skills/ui-design/scripts/__pycache__/check_ui_artifact_pptx_test.cpython-314.pyc +0 -0
  106. package/skills/ui-design/scripts/__pycache__/deck_pages_check.cpython-314.pyc +0 -0
  107. package/skills/ui-design/scripts/__pycache__/deck_pages_check_test.cpython-314.pyc +0 -0
  108. package/skills/ui-design/scripts/__pycache__/deck_visual_style_check.cpython-314.pyc +0 -0
  109. package/skills/ui-design/scripts/__pycache__/deck_visual_style_check_test.cpython-314.pyc +0 -0
  110. package/skills/ui-design/scripts/__pycache__/design_budget_check.cpython-314.pyc +0 -0
  111. package/skills/ui-design/scripts/__pycache__/design_budget_check_test.cpython-314.pyc +0 -0
  112. package/skills/ui-design/scripts/__pycache__/design_repair_hints.cpython-314.pyc +0 -0
  113. package/skills/ui-design/scripts/__pycache__/design_repair_hints_test.cpython-314.pyc +0 -0
  114. package/skills/ui-design/scripts/__pycache__/design_style_check.cpython-314.pyc +0 -0
  115. package/skills/ui-design/scripts/__pycache__/design_style_check_test.cpython-314.pyc +0 -0
  116. package/skills/ui-design/scripts/__pycache__/draft_coverage_check.cpython-314.pyc +0 -0
  117. package/skills/ui-design/scripts/__pycache__/extract_source_assets.cpython-314.pyc +0 -0
  118. package/skills/ui-design/scripts/__pycache__/extract_source_assets_test.cpython-314.pyc +0 -0
  119. package/skills/ui-design/scripts/__pycache__/generated_image_review_collect.cpython-314.pyc +0 -0
  120. package/skills/ui-design/scripts/__pycache__/generated_image_review_collect_test.cpython-314.pyc +0 -0
  121. package/skills/ui-design/scripts/__pycache__/image_plan_lint.cpython-314.pyc +0 -0
  122. package/skills/ui-design/scripts/__pycache__/image_plan_lint_test.cpython-314.pyc +0 -0
  123. package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite.cpython-314.pyc +0 -0
  124. package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite_test.cpython-314.pyc +0 -0
  125. package/skills/ui-design/scripts/__pycache__/layout_balance.cpython-314.pyc +0 -0
  126. package/skills/ui-design/scripts/__pycache__/layout_balance_test.cpython-314.pyc +0 -0
  127. package/skills/ui-design/scripts/__pycache__/normalize_design_json.cpython-314.pyc +0 -0
  128. package/skills/ui-design/scripts/__pycache__/normalize_design_json_test.cpython-314.pyc +0 -0
  129. package/skills/ui-design/scripts/__pycache__/plan_source_layout.cpython-314.pyc +0 -0
  130. package/skills/ui-design/scripts/__pycache__/plan_source_layout_test.cpython-314.pyc +0 -0
  131. package/skills/ui-design/scripts/__pycache__/resolve_source_hint.cpython-314.pyc +0 -0
  132. package/skills/ui-design/scripts/__pycache__/resolve_source_hint_test.cpython-314.pyc +0 -0
  133. package/skills/ui-design/scripts/__pycache__/resolve_source_input.cpython-314.pyc +0 -0
  134. package/skills/ui-design/scripts/__pycache__/resolve_source_input_test.cpython-314.pyc +0 -0
  135. package/skills/ui-design/scripts/__pycache__/scaffold_ui_artifact.cpython-314.pyc +0 -0
  136. package/skills/ui-design/scripts/__pycache__/source_usage_check.cpython-314.pyc +0 -0
  137. package/skills/ui-design/scripts/__pycache__/source_usage_check_test.cpython-314.pyc +0 -0
  138. package/skills/ui-design/scripts/__pycache__/text_readability_pixel.cpython-314.pyc +0 -0
  139. package/skills/ui-design/scripts/__pycache__/visual_quality_gate.cpython-314.pyc +0 -0
  140. package/skills/ui-design/scripts/__pycache__/visual_quality_gate_test.cpython-314.pyc +0 -0
  141. package/skills/ui-design/scripts/assemble_material.py +217 -0
  142. package/skills/ui-design/scripts/assemble_material_test.py +149 -0
  143. package/skills/ui-design/scripts/browser_check_ui_artifact.mjs +221 -0
  144. package/skills/ui-design/scripts/chart_sanity_check.py +261 -0
  145. package/skills/ui-design/scripts/chart_sanity_check_test.py +165 -0
  146. package/skills/ui-design/scripts/check_language_policy.py +351 -0
  147. package/skills/ui-design/scripts/check_ui_artifact.py +435 -0
  148. package/skills/ui-design/scripts/check_ui_artifact_pptx_test.py +251 -0
  149. package/skills/ui-design/scripts/deck_pages_check.py +158 -0
  150. package/skills/ui-design/scripts/deck_pages_check_test.py +134 -0
  151. package/skills/ui-design/scripts/deck_visual_style_check.py +767 -0
  152. package/skills/ui-design/scripts/deck_visual_style_check_test.py +713 -0
  153. package/skills/ui-design/scripts/design_budget_check.py +271 -0
  154. package/skills/ui-design/scripts/design_budget_check_test.py +183 -0
  155. package/skills/ui-design/scripts/design_repair_hints.py +162 -0
  156. package/skills/ui-design/scripts/design_repair_hints_test.py +54 -0
  157. package/skills/ui-design/scripts/design_style_check.py +397 -0
  158. package/skills/ui-design/scripts/design_style_check_test.py +233 -0
  159. package/skills/ui-design/scripts/draft_coverage_check.py +184 -0
  160. package/skills/ui-design/scripts/draft_coverage_check_test.py +131 -0
  161. package/skills/ui-design/scripts/export_pptx_from_html.mjs +2367 -0
  162. package/skills/ui-design/scripts/extract_source_assets.py +607 -0
  163. package/skills/ui-design/scripts/extract_source_assets_test.py +256 -0
  164. package/skills/ui-design/scripts/generated_image_review_collect.py +206 -0
  165. package/skills/ui-design/scripts/generated_image_review_collect_test.py +91 -0
  166. package/skills/ui-design/scripts/image_plan_lint.py +571 -0
  167. package/skills/ui-design/scripts/image_plan_lint_test.py +515 -0
  168. package/skills/ui-design/scripts/image_prompt_rewrite.py +713 -0
  169. package/skills/ui-design/scripts/image_prompt_rewrite_test.py +966 -0
  170. package/skills/ui-design/scripts/layout_balance.py +881 -0
  171. package/skills/ui-design/scripts/layout_balance_test.py +927 -0
  172. package/skills/ui-design/scripts/normalize_design_json.py +272 -0
  173. package/skills/ui-design/scripts/normalize_design_json_test.py +119 -0
  174. package/skills/ui-design/scripts/plan_source_layout.py +1450 -0
  175. package/skills/ui-design/scripts/plan_source_layout_test.py +1136 -0
  176. package/skills/ui-design/scripts/resolve_source_input.py +270 -0
  177. package/skills/ui-design/scripts/resolve_source_input_test.py +98 -0
  178. package/skills/ui-design/scripts/scaffold_ui_artifact.py +377 -0
  179. package/skills/ui-design/scripts/source_usage_check.py +581 -0
  180. package/skills/ui-design/scripts/source_usage_check_test.py +427 -0
  181. package/skills/ui-design/scripts/text_readability_pixel.py +340 -0
  182. package/skills/ui-design/scripts/text_readability_pixel_test.py +219 -0
  183. package/skills/ui-design/scripts/visual_quality_gate.py +301 -0
  184. package/skills/ui-design/scripts/visual_quality_gate_test.py +233 -0
  185. package/skills/ui-design/tests/e2e/prompts/deck.md +19 -0
  186. package/skills/ui-design/tests/e2e/prompts/webpage.md +17 -0
  187. package/skills/ui-design/tests/e2e/render_check.mjs +101 -0
  188. package/skills/ui-design/tests/e2e/rubric.json +54 -0
  189. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/browser-check.json +49 -0
  190. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/language-check.txt +12 -0
  191. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/static-check.txt +15 -0
  192. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/outputs/index.html +837 -0
  193. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/prompt.md +12 -0
  194. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/review.md +21 -0
  195. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/desktop.png +0 -0
  196. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/mobile.png +0 -0
  197. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/homepage.html +373 -0
  198. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/platform.html +378 -0
  199. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/prompt.md +13 -0
  200. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/review.md +26 -0
  201. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
  202. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
  203. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
  204. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
  205. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
  206. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
  207. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/checks/static-check.txt +2 -0
  208. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/outputs/index.html +656 -0
  209. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/prompt.md +12 -0
  210. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/render_check.mjs +67 -0
  211. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/checks/static-check.txt +2 -0
  212. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/outputs/index.html +587 -0
  213. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/prompt.md +12 -0
  214. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/review.md +36 -0
  215. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/browser-check.json +49 -0
  216. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/language-check.txt +8 -0
  217. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/static-check.txt +16 -0
  218. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/outputs/index.html +739 -0
  219. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/prompt.md +11 -0
  220. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/review.md +22 -0
  221. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/desktop.png +0 -0
  222. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/mobile.png +0 -0
  223. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/checks/browser-check.json +52 -0
  224. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/outputs/index.html +641 -0
  225. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/desktop.png +0 -0
  226. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/mobile.png +0 -0
  227. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/outputs/index.html +467 -0
  228. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/browser-check.json +49 -0
  229. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/desktop.png +0 -0
  230. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/mobile.png +0 -0
  231. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/homepage.html +373 -0
  232. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/platform.html +378 -0
  233. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/prompt.md +13 -0
  234. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/review.md +26 -0
  235. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
  236. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
  237. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
  238. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
  239. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
  240. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
  241. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/browser-check.json +49 -0
  242. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/language-check.txt +4 -0
  243. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/static-check.txt +2 -0
  244. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/execution_notes.md +10 -0
  245. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/outputs/index.html +587 -0
  246. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/prompt.md +14 -0
  247. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/review.md +13 -0
  248. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/desktop.png +0 -0
  249. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/mobile.png +0 -0
  250. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/browser-check.json +24 -0
  251. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/static-check.txt +2 -0
  252. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/execution_notes.md +21 -0
  253. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/outputs/deck.html +451 -0
  254. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-01.png +0 -0
  255. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-02.png +0 -0
  256. package/skills/ui-design/tests/e2e/runs/iteration-1/review.md +79 -0
  257. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/browser-check.json +26 -0
  258. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/static-check.txt +2 -0
  259. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/execution_notes.md +20 -0
  260. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/outputs/index.html +534 -0
  261. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/desktop.png +0 -0
  262. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/mobile.png +0 -0
  263. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/browser-check.json +64 -0
  264. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/language-check.txt +7 -0
  265. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/quick-validate.txt +1 -0
  266. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-console.txt +2 -0
  267. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-homepage.txt +2 -0
  268. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/console.html +600 -0
  269. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/homepage.html +556 -0
  270. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/prompt.md +29 -0
  271. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/review.md +57 -0
  272. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-desktop.png +0 -0
  273. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-mobile.png +0 -0
  274. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-desktop.png +0 -0
  275. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-mobile.png +0 -0
  276. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/browser-check.json +33 -0
  277. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/language-check.txt +6 -0
  278. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/quick-validate.txt +1 -0
  279. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/static-check.txt +2 -0
  280. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/outputs/index.html +682 -0
  281. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/prompt.md +13 -0
  282. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/review.md +59 -0
  283. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/desktop.png +0 -0
  284. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/mobile.png +0 -0
  285. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-01.html +466 -0
  286. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-02.html +433 -0
  287. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/prompt.md +13 -0
  288. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/browser-check.json +49 -0
  289. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/desktop.png +0 -0
  290. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/mobile.png +0 -0
  291. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/browser-check.json +49 -0
  292. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/desktop.png +0 -0
  293. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/mobile.png +0 -0
  294. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/browser-check.json +49 -0
  295. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/language-check.txt +3 -0
  296. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/static-check.txt +2 -0
  297. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/execution_notes.md +24 -0
  298. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/outputs/index.html +723 -0
  299. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/prompt.md +15 -0
  300. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/review.md +26 -0
  301. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/desktop.png +0 -0
  302. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/mobile.png +0 -0
  303. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-dark.json +49 -0
  304. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-light.json +49 -0
  305. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/language-check.txt +11 -0
  306. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-dark.txt +2 -0
  307. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-light.txt +3 -0
  308. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/execution_notes.md +23 -0
  309. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/dark.html +392 -0
  310. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/light.html +476 -0
  311. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/prompt.md +23 -0
  312. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/review.md +41 -0
  313. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/desktop.png +0 -0
  314. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/mobile.png +0 -0
  315. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/desktop.png +0 -0
  316. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/mobile.png +0 -0
  317. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/browser-check.json +54 -0
  318. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/language-check.txt +5 -0
  319. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/quick-validate.txt +1 -0
  320. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/static-check.txt +2 -0
  321. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/outputs/index.html +765 -0
  322. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/prompt.md +15 -0
  323. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/review.md +59 -0
  324. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/desktop.png +0 -0
  325. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/mobile.png +0 -0
  326. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/outputs/index.html +508 -0
  327. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/browser-check.json +49 -0
  328. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/desktop.png +0 -0
  329. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/mobile.png +0 -0
  330. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/browser-check.json +54 -0
  331. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/language-check.txt +6 -0
  332. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/quick-validate.txt +1 -0
  333. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/static-check.txt +2 -0
  334. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/outputs/index.html +788 -0
  335. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/prompt.md +14 -0
  336. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/review.md +60 -0
  337. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/desktop.png +0 -0
  338. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/mobile.png +0 -0
  339. package/skills/video-compose/SKILL.md +8 -1
  340. package/skills/web-crawler/SKILL.md +70 -0
  341. package/skills/web-crawler/scripts/crawl.mjs +538 -0
  342. package/skills/web-crawler/scripts/crawl.test.mjs +204 -0
  343. package/skills/xlsx/SKILL.md +12 -0
  344. package/skills/xlsx/scripts/opc_check.py +253 -0
  345. package/src/advisorState.js +22 -0
  346. package/src/askBridge.js +97 -3
  347. package/src/bootstrap/workingDir.js +12 -1
  348. package/src/cli/args.js +17 -1
  349. package/src/cli/configMenu.js +236 -0
  350. package/src/cli/configWizard.js +7 -5
  351. package/src/cli/print.js +37 -4
  352. package/src/cli/streamJson.js +77 -24
  353. package/src/cli/wizards/advisorWizard.js +147 -0
  354. package/src/cli/wizards/fieldForm.js +149 -0
  355. package/src/cli/wizards/imageWizard.js +60 -0
  356. package/src/cli/wizards/openRouterWizard.js +23 -0
  357. package/src/cli/wizards/providerPresets.js +95 -0
  358. package/src/cli/wizards/providerWizard.js +219 -0
  359. package/src/cli/wizards/searchWizard.js +23 -0
  360. package/src/cli/wizards/ttsWizard.js +34 -0
  361. package/src/config/configFile.js +441 -83
  362. package/src/config/configService.js +446 -0
  363. package/src/config.js +24 -64
  364. package/src/context/agentHome.js +9 -0
  365. package/src/context/archive.js +7 -2
  366. package/src/context/compact.js +8 -5
  367. package/src/context/contextLock.js +81 -0
  368. package/src/context/persistContext.js +30 -11
  369. package/src/context/reactiveCompact.js +4 -2
  370. package/src/context/recentDirs.js +21 -61
  371. package/src/context/sessionPath.js +97 -23
  372. package/src/context/sessionRegistry.js +303 -24
  373. package/src/context/tokenCounter.js +18 -0
  374. package/src/context/workspaceIndex.js +731 -0
  375. package/src/infra/verification.js +28 -15
  376. package/src/llm/client.js +394 -159
  377. package/src/llm/providers/anthropicFormat.js +241 -0
  378. package/src/llm/providers/openaiFormat.js +190 -0
  379. package/src/loop.js +219 -32
  380. package/src/main.js +61 -11
  381. package/src/messageQueue.js +97 -0
  382. package/src/multimodal.js +203 -0
  383. package/src/plugin-sdk.js +15 -0
  384. package/src/plugins/commandRouter.js +3 -0
  385. package/src/plugins/hookEngine.js +5 -1
  386. package/src/plugins/pluginLifecycle.js +28 -0
  387. package/src/plugins/pluginLoader.js +2 -1
  388. package/src/plugins/pluginRunner.js +49 -21
  389. package/src/plugins/transcript.js +4 -3
  390. package/src/prompts/pluginList.js +5 -5
  391. package/src/prompts/projectInstructions.js +3 -3
  392. package/src/prompts/promptAssembly.js +133 -144
  393. package/src/prompts/skillList.js +12 -22
  394. package/src/prompts/system.js +0 -1
  395. package/src/session/runAgentTurn.js +70 -0
  396. package/src/session/runTurn.js +41 -6
  397. package/src/toolGate.js +17 -0
  398. package/src/tools/advisor/advisor.js +379 -0
  399. package/src/tools/agent/agent.js +555 -0
  400. package/src/tools/agent/agentTypes.js +59 -0
  401. package/src/tools/agent/coordinator.js +93 -0
  402. package/src/tools/agent/worktree.js +270 -0
  403. package/src/tools/ask/ask.js +7 -2
  404. package/src/tools/bash/bash.js +112 -18
  405. package/src/tools/download/download.js +144 -0
  406. package/src/tools/edit/edit.js +1 -0
  407. package/src/tools/edit/multi-edit.js +1 -0
  408. package/src/tools/genimage/genimage.js +242 -0
  409. package/src/tools/genspeech/genspeech.js +67 -0
  410. package/src/tools/glob/glob.js +1 -0
  411. package/src/tools/grep/grep.js +3 -0
  412. package/src/tools/grep/rgPath.js +2 -0
  413. package/src/tools/index.js +25 -2
  414. package/src/tools/read/read.js +1 -0
  415. package/src/tools/shared/killTree.js +52 -0
  416. package/src/tools/toolProgressBridge.js +48 -0
  417. package/src/tools/webbrowser/agentBrowserPath.js +258 -0
  418. package/src/tools/webbrowser/browser.js +59 -42
  419. package/src/tools/webbrowser/browserControl.js +118 -0
  420. package/src/tools/webbrowser/browserDaemon.js +97 -0
  421. package/src/tools/webbrowser/browserService.js +27 -0
  422. package/src/tools/webbrowser/browserStream.js +119 -0
  423. package/src/tools/webbrowser/webbrowser.js +699 -130
  424. package/src/tools/webfetch/webfetch.js +1 -0
  425. package/src/tools/websearch/websearch.js +134 -51
  426. package/src/tools/write/write.js +1 -0
  427. package/src/ui/App.js +249 -22
  428. package/src/ui/AskQuestion.js +29 -10
  429. package/src/ui/InputBox.js +104 -12
  430. package/src/ui/LiveArea.js +112 -19
  431. package/src/ui/MessageList.js +100 -14
  432. package/src/ui/ModelPicker.js +56 -0
  433. package/src/ui/QueuedMessagesBanner.js +14 -0
  434. package/src/ui/Root.js +18 -10
  435. package/src/ui/SessionPicker.js +102 -0
  436. package/src/ui/StatusLine.js +13 -4
  437. package/src/ui/WorkspacePicker.js +55 -0
  438. package/src/ui/figures.js +44 -0
  439. package/src/ui/hooks/askSlot.js +82 -0
  440. package/src/ui/hooks/useChat.js +358 -64
  441. package/src/ui/hooks/usePasteHandler.js +11 -1
  442. package/src/ui/hooks/useTextBuffer.js +1 -3
  443. package/src/ui/liveViewport.js +5 -1
  444. package/src/ui/markdown.js +206 -0
  445. package/src/ui/textBuffer.js +0 -7
  446. package/src/ui/theme.js +32 -0
  447. package/src/utils/debugLog.js +0 -4
  448. package/src/utils/resourcePaths.js +2 -2
  449. package/src/utils/stripThink.js +50 -0
  450. package/src/web/browserBridge.js +447 -0
  451. package/src/web/protocol.js +19 -0
  452. package/workflows/adaptive-research.yaml +13 -5
  453. package/workflows/book-review-short.yaml +36 -10
  454. package/workflows/e2e-write-greet.yaml +6 -2
  455. package/workflows/illustrate.yaml +227 -0
  456. package/workflows/plan.yaml +460 -179
  457. package/workflows/ppt.yaml +1659 -0
  458. package/workflows/ralph-loop.yaml +199 -0
  459. package/workflows/schema.json +81 -23
  460. package/workflows/webppt.yaml +496 -0
  461. package/workflows/word.yaml +710 -0
  462. package/workflows/youtube-shorts.yaml +46 -15
  463. package/MINIMAL-TRACE.md +0 -77
  464. package/plugins/HOW-TO-WRITE-A-PLUGIN.md +0 -186
  465. package/plugins/ralph-wiggum/.claude-plugin/plugin.json +0 -9
  466. package/plugins/ralph-wiggum/README.md +0 -197
  467. package/plugins/ralph-wiggum/commands/cancel-ralph.md +0 -18
  468. package/plugins/ralph-wiggum/commands/help.md +0 -137
  469. package/plugins/ralph-wiggum/commands/ralph-loop.md +0 -85
  470. package/plugins/ralph-wiggum/hooks/hooks.json +0 -15
  471. package/plugins/ralph-wiggum/hooks/stop-hook.sh +0 -191
  472. package/plugins/ralph-wiggum/plugin.js +0 -280
  473. package/plugins/ralph-wiggum/src/goalState.js +0 -258
  474. package/plugins/ralph-wiggum/src/sentinels.js +0 -76
  475. package/plugins/ralph-wiggum/src/stopHookRunner.js +0 -104
  476. package/plugins/ralph-wiggum/src/verificationGate.js +0 -202
  477. package/plugins/workflow-runner/src/miniReAct.js +0 -223
  478. package/plugins/workflow-runner/src/voteHelpers.js +0 -110
  479. package/skills/config/SKILL.md +0 -123
  480. package/skills/frontend-design/LICENSE.txt +0 -177
  481. package/skills/frontend-design/SKILL.md +0 -42
  482. package/src/ui/ProgressPanel.js +0 -54
  483. package/src/ui/StreamingBlock.js +0 -11
  484. package/src/ui/ToolStatus.js +0 -11
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: ai-image
3
+ description: 当用户说"生成图片"、"画一张图"、"配图"、"插画/插图"、"AI 生图"、"图生图"、"参考这张图改图/扩图/增强"或者你判断出客户实际上的要求等价于用AI生成或编辑图片时使用(OPENAI_IMAGE_* 配置那一路)。支持文生图和带输入图的图生图/参考图生成,支持指定宽高比。与 image-gen-openrouter(OPENROUTER_API_KEY)是两套独立机制,按用户配置选用,别混用。
4
+ ---
5
+
6
+ # ai-image —— 文生图 / 图生图
7
+
8
+ > 生图能力住在本技能的脚本 `scripts/generate.mjs`(零依赖、裸 fetch、自己落盘),
9
+ > 走 `OPENAI_IMAGE_*` 配置。工作流批量生图优先用内核 `GenerateImage` 工具(无 Bash 120s 帽),
10
+ > 手动调试或单张生成时用 Bash 调脚本即可。
11
+ > 如果用户明确点名 OpenRouter / nano banana 且用 `OPENROUTER_API_KEY`,那是另一个技能
12
+ > `image-gen-openrouter`(curl 版),别混用。
13
+
14
+ ## 怎么干
15
+
16
+ 用 **Bash 工具**跑本技能目录下的脚本(**用 node 跑,不是 bun**):
17
+
18
+ ```bash
19
+ node "<本技能目录>/scripts/generate.mjs" \
20
+ --prompt "A serene mountain lake at sunrise, soft mist, photorealistic, wide cinematic shot" \
21
+ --out ./assets/lake.png \
22
+ --aspect 16:9
23
+ ```
24
+
25
+ 图生图 / 参考图增强:
26
+
27
+ ```bash
28
+ node "<本技能目录>/scripts/generate.mjs" \
29
+ --prompt "Extend this product photo into a clean 16:9 hero background, preserve the main product, premium soft light, no text, no logos" \
30
+ --input-image ./assets/source.png \
31
+ --out ./assets/source_enhanced.png \
32
+ --aspect 16:9
33
+ ```
34
+
35
+ 批量 PPT 配图不要让 agent 循环手写 Bash;在 workflow 中优先调用 `GenerateImage` 工具。手动调试时,
36
+ 让计划脚本读取 `img_plan.json` 确定性执行:
37
+
38
+ ```bash
39
+ node "<本技能目录>/scripts/generate_from_plan.mjs" \
40
+ --plan ./build/img_plan.json \
41
+ --assets ./build/assets \
42
+ --timeout-ms 600000 \
43
+ --size 1K
44
+ ```
45
+
46
+ - `<本技能目录>` = 系统提示「可用技能」列表里本技能(ai-image)后面给出的**绝对目录**,直接拼 `/scripts/generate.mjs` 或 `/scripts/generate_from_plan.mjs`。
47
+ - 路径含空格时给整条路径加引号。
48
+ - **调 Bash 时务必把 `timeout` 设成 `600000`(10 分钟)**:图像模型很慢(一张常要 1 分钟、重模型几分钟),
49
+ Bash 默认只给 120s,会把还没出图的进程当超时掐断。脚本运行时会往 stderr 打 `[ai-image] attempt k/N …` 进度,
50
+ 看到它就说明在正常等待、不是卡死。
51
+
52
+ ### 参数
53
+
54
+ | 参数 | 必填 | 说明 |
55
+ |------|------|------|
56
+ | `--prompt` | ✅ | 图片提示词。**英文通常效果更好**;用户给的是中文就保留,不要主动翻译。越具体(主体/风格/光线/构图/负空间)越好。 |
57
+ | `--out` | ✅ | 落盘路径。绝对或相对(相对当前工作目录)。**建议 `.png` 后缀**。父目录自动创建。 |
58
+ | `--aspect` | ⬜ | 宽高比,如 `16:9` / `9:16` / `4:5` / `1:1`。脚本会同时①发 provider 原生 `image_config.aspect_ratio`、②在提示词里追加构图提示,双保险。 |
59
+ | `--size` | ⬜ | 分辨率档,如 `1K` / `2K` / `4K`(部分模型/端点支持;不支持则忽略)。 |
60
+ | `--model` | ⬜ | 临时覆盖 `OPENAI_IMAGE_MODEL`(想换更快/更便宜的模型试一张时用)。 |
61
+ | `--input-image` | ⬜ | 输入参考图,本地路径 / URL / data URL 均可。本地图片会转成 `data:image/...;base64,...`。用于图生图、扩图、换背景、比例修复、质感增强。 |
62
+
63
+ 成功后脚本在 stdout 打**一行**:`saved: <path> (model=<m>, bytes=<n>, aspect=<a>)`;失败信息走 stderr,退出码非 0。
64
+
65
+ ## 注意事项
66
+
67
+ - **先确认配置**:脚本需要 `.env`/环境里有 `OPENAI_IMAGE_BASE_URL` / `OPENAI_IMAGE_API_KEY` / `OPENAI_IMAGE_MODEL`。
68
+ 缺了脚本以**退出码 3** + 明确文案报错——如实转告用户补好配置再试,别瞎重试。
69
+ - **协议自动选(无需关心)**:脚本支持两套生图协议并按 provider 自动切——OpenRouter 的多模态图模型走
70
+ `chat/completions`(图在 `message.images[]`,输入图作为 `image_url` 内容块);OpenAI / Azure / **火山方舟 Seedream** 走官方 Images API
71
+ `images/generations`(图在 `data[0].b64_json`,输入图通过 `image` 扩展参数传入)。`OPENAI_IMAGE_PROVIDER ∈ {volces,volces-plan,ark,doubao,seedream,openai,azure}`
72
+ → images,其余 → chat;可用 `OPENAI_IMAGE_PROTOCOL=chat|images` 显式覆盖。用户只换 `.env` 即可切 provider,
73
+ 调脚本的命令行参数完全不变。
74
+ - **火山 Seedream 注意 base URL**:图片生成/图生图用 `https://ark.cn-beijing.volces.com/api/v3`;不要用聊天/识图的
75
+ `https://ark.cn-beijing.volces.com/api/coding/v3`。若开通了「流量包」套餐(`OPENAI_IMAGE_PROVIDER=volces-plan`),
76
+ base URL 改用 `https://ark.cn-beijing.volces.com/api/plan/v3`(按套餐计费,模型 ID 需与套餐支持列表一致,别混用 v3 端点否则产生套餐外费用)。
77
+ - **图生图用途**:客户图比例不适合、主体太小、背景杂乱、需要扩成 16:9 hero、需要统一光影或增强清晰度时使用。不要把图生图当作结构图生成器;流程、架构、表格、模块图仍用 HTML/SVG/图表。
78
+ - **速度取决于模型**:flash 类(如 `google/gemini-3.1-flash-image-preview`)约数十秒/张;重模型(如 `openai/gpt-5.4-image-2`)可达 ~3 分钟/张。生成期间耐心等其返回再发下一张;批量会成倍变慢,按需克制数量。
79
+ - **鉴权失败**(401/403,退出码 1):脚本返回「API key 可能失效/无权」诊断;转告用户检查 key,别重试烧钱。
80
+ - **不要回显 API key**:脚本本身绝不打印 key;你转述错误时也别把 `OPENAI_IMAGE_API_KEY` 带出来。
81
+ - **绿色版**:脚本随 `skills/` 分发到 exe 同级,但执行需机器装有 **Node**(与 ui-design 的 PPTX 导出脚本同样要求)。
82
+ - **校验产物**:生成后可用 `Read`(offset=0 limit=1)触发二进制嗅探,或 `Bash` 看文件 size > 10KB 确认是真图。
83
+ - 一次一张;多张就多次调用、每次换 `--out` 路径。
84
+
85
+ ## 关于宽高比(重要)
86
+
87
+ `--aspect` 决定**生成图的构图**(主体摆放、负空间留给谁)。当生图是为了放进 PPT/网页的固定槽位时,
88
+ **aspect 应贴合槽位的宽高比**——这样 CSS `object-fit:cover` 裁切最少、构图意图(如给文字留的净空)能保住。
89
+ 怎么按版面选 aspect 见 ui-design 的 `references/layout-composition.md`(图文混排方法论)。
@@ -0,0 +1,497 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ============================================================
4
+ * skills/ai-image/scripts/generate.mjs
5
+ * —— OpenAI 兼容「AI 文生图」CLI(零依赖、裸 fetch、落盘)
6
+ * ------------------------------------------------------------
7
+ * 做一件事:把一句提示词(可选带一张参考图)发给 OpenAI 兼容的图像模型,拿回 base64
8
+ * 图片解码成二进制写到磁盘。供 ai-image 技能 / ppt 工作流用 Bash 调用:
9
+ *
10
+ * node <技能目录>/scripts/generate.mjs \
11
+ * --prompt "a serene mountain lake at dawn, cinematic" \
12
+ * --out ./build/assets/img_1.png \
13
+ * --aspect 16:9
14
+ *
15
+ * node <技能目录>/scripts/generate.mjs \
16
+ * --prompt "extend this product photo into a clean 16:9 hero background, no text" \
17
+ * --input-image ./build/assets/src_1.png \
18
+ * --out ./build/assets/enhanced_src_1.png \
19
+ * --aspect 16:9
20
+ *
21
+ * 为什么是脚本不是内置工具?
22
+ * - 内置工具(Write)只能写文本,写不了二进制;而把生图做成「基础工具」
23
+ * 和文件读写不是一个层级、且与本技能职责重叠。统一收进技能脚本 =
24
+ * 单一真相源(删掉了原 src/llm/imageClient.ts + GenerateImage 工具)。
25
+ * - 技能脚本随 skills/ 在 dev / npm / 绿色版三形态都被分发、用 node 跑,
26
+ * 与 ui-design 的 export_pptx_from_html.mjs 同款(PPT 流程本就依赖 Node)。
27
+ *
28
+ * 两套协议(按 provider / OPENAI_IMAGE_PROTOCOL 自动选,见 resolveProtocol):
29
+ * A) chat —— OpenRouter 的多模态模型(gemini / gpt-image 系)走 chat 协议:
30
+ * POST /chat/completions,请求加 `modalities:["image","text"]`,模型把图片塞进
31
+ * `choices[0].message.images[].image_url.url`(base64 data URL)返回。
32
+ * B) images —— OpenAI 官方「Images API」标准格式(OpenAI / Azure / 火山方舟 Seedream):
33
+ * POST /images/generations,请求是 `{model,prompt,size,response_format,watermark,image?}`,
34
+ * 图片在 `data[0].b64_json`(base64)或 `data[0].url`(需再下载)返回。
35
+ * 为什么两套都要:二者覆盖不同生态——只留 images 会丢掉 OpenRouter(它的图模型只走 chat);
36
+ * 只留 chat 又用不了火山 Seedream 这类标准 Images API。按 provider 自动选 = 换 .env 即可切。
37
+ *
38
+ * 比例(aspect)三层保险:
39
+ * 1. image_config.aspect_ratio / image_size —— OpenRouter 原生参数(端点支持就生效);
40
+ * 2. 提示词里追加一句构图提示(端点忽略 image_config 时的软引导);
41
+ * 3. 真正的 PPT 几何由 ui-design 的 HTML 槽位 + CSS object-fit 决定(兜底)。
42
+ *
43
+ * 零依赖:fetch / Buffer / AbortSignal 都是 Node 20+ 内置。
44
+ * 退出码:0 成功;1 生成失败;2 参数错;3 缺环境变量。全程不回显 API key。
45
+ * ============================================================
46
+ */
47
+
48
+ import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
49
+ import { dirname, extname } from 'node:path';
50
+
51
+ // ============================================================
52
+ // 1. 解析命令行参数(支持 `--flag value` 与 `--flag=value` 两种写法)
53
+ // ============================================================
54
+
55
+ /** 把 process.argv 解析成 { prompt, out, aspect, size, model, input-image }。 */
56
+ function parseArgs(argv) {
57
+ const out = {};
58
+ const keys = new Set(['prompt', 'out', 'aspect', 'size', 'model', 'input-image', 'dry-run-request']);
59
+ for (let i = 0; i < argv.length; i++) {
60
+ const tok = argv[i];
61
+ if (!tok.startsWith('--')) continue;
62
+ const body = tok.slice(2);
63
+ const eq = body.indexOf('=');
64
+ let key;
65
+ let val;
66
+ if (eq !== -1) {
67
+ key = body.slice(0, eq);
68
+ val = body.slice(eq + 1);
69
+ } else {
70
+ key = body;
71
+ val = argv[i + 1] && !argv[i + 1].startsWith('--') ? argv[++i] : '';
72
+ }
73
+ if (keys.has(key)) out[key] = val;
74
+ }
75
+ return out;
76
+ }
77
+
78
+ /** stderr 打印(诊断信息不污染 stdout 的机器友好 summary)。 */
79
+ function err(msg) {
80
+ process.stderr.write(`${msg}\n`);
81
+ }
82
+
83
+ function mimeFromPath(path) {
84
+ const ext = extname(path || '').toLowerCase();
85
+ if (ext === '.jpg' || ext === '.jpeg') return 'image/jpeg';
86
+ if (ext === '.png') return 'image/png';
87
+ if (ext === '.gif') return 'image/gif';
88
+ if (ext === '.webp') return 'image/webp';
89
+ return 'application/octet-stream';
90
+ }
91
+
92
+ async function imageInputToUrl(input) {
93
+ if (!input) return undefined;
94
+ if (/^https?:\/\//i.test(input) || input.startsWith('data:image/')) return input;
95
+ const st = await stat(input);
96
+ const maxBytes = 10 * 1024 * 1024;
97
+ if (st.size > maxBytes) {
98
+ throw new Error(`输入图片超过 10MB:${input} (${st.size} bytes)`);
99
+ }
100
+ const buf = await readFile(input);
101
+ return `data:${mimeFromPath(input)};base64,${buf.toString('base64')}`;
102
+ }
103
+
104
+ // ============================================================
105
+ // 2. 配置(OPENAI_IMAGE_* 命名空间,独立于聊天 LLM)
106
+ // ============================================================
107
+
108
+ /**
109
+ * 读环境变量得到 provider 配置;缺任一必需项返回 null(调用方据此优雅报错)。
110
+ * OPENAI_IMAGE_BASE_URL / OPENAI_IMAGE_API_KEY / OPENAI_IMAGE_MODEL —— 必需
111
+ * OPENAI_IMAGE_TIMEOUT_MS(默认 240s)/ OPENAI_IMAGE_MAX_RETRIES(默认 3)—— 可选
112
+ */
113
+ function loadProvider(modelOverride) {
114
+ const baseURL = process.env.OPENAI_IMAGE_BASE_URL;
115
+ const apiKey = process.env.OPENAI_IMAGE_API_KEY;
116
+ const model = modelOverride || process.env.OPENAI_IMAGE_MODEL;
117
+ if (!baseURL || !apiKey || !model) return null;
118
+ return { baseURL, apiKey, model };
119
+ }
120
+
121
+ // 单次尝试超时:默认 240s(覆盖「1 分钟甚至更久」的慢渲染)。注意上游若用 Bash 工具调本脚本,
122
+ // Bash 默认只给 120s——必须显式把 Bash 的 timeout 设到 ~600s,否则本脚本的超时/重试来不及生效
123
+ // 进程就被 OS 杀掉。240s × 2 次尝试 ≈ 480s,落在 Bash 10 分钟上限内,能在被杀前自己返回干净错误。
124
+ const TIMEOUT_MS = (() => {
125
+ const v = Number(process.env.OPENAI_IMAGE_TIMEOUT_MS);
126
+ // 成本不计、最大努力生图:单次默认放宽到 600s(原 240s 偏紧,大分辨率慢渲染易超时降级);env 可覆盖。
127
+ return Number.isFinite(v) && v > 0 ? v : 600_000;
128
+ })();
129
+
130
+ const MAX_ATTEMPTS = (() => {
131
+ const v = Number(process.env.OPENAI_IMAGE_MAX_RETRIES);
132
+ return Number.isFinite(v) && v >= 1 ? Math.floor(v) : 3;
133
+ })();
134
+
135
+ // ============================================================
136
+ // 3. 重试 / 退避 / data URL 解码(精简自原 imageClient.ts)
137
+ // ============================================================
138
+
139
+ /** 可重试状态码:408/425/429/5xx(其余 4xx 重试无意义)。 */
140
+ function isRetryable(status) {
141
+ return status === 408 || status === 425 || status === 429 || (status >= 500 && status < 600);
142
+ }
143
+
144
+ /** 鉴权错误:明确告知 key 失效/无权,不浪费重试。 */
145
+ function isAuthError(status) {
146
+ return status === 401 || status === 403;
147
+ }
148
+
149
+ /** 指数退避:0.5s → 1s → 2s …,封顶 8s,±15% 抖动。 */
150
+ function backoffMs(attempt) {
151
+ const base = Math.min(500 * 2 ** (attempt - 1), 8_000);
152
+ return Math.round(base * (0.85 + Math.random() * 0.3));
153
+ }
154
+
155
+ /** Retry-After 头(秒),封顶 60s;无效返回 undefined。 */
156
+ function parseRetryAfter(resp) {
157
+ const h = resp.headers.get('retry-after');
158
+ if (!h) return undefined;
159
+ const sec = Number.parseFloat(h);
160
+ if (!Number.isFinite(sec) || sec < 0) return undefined;
161
+ return Math.min(sec * 1000, 60_000);
162
+ }
163
+
164
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
165
+
166
+ /** 解析 `data:<mime>;base64,<payload>` → Buffer(兼容无 mime / 非 base64)。 */
167
+ function dataUrlToBuffer(dataUrl) {
168
+ const comma = dataUrl.indexOf(',');
169
+ if (!dataUrl.startsWith('data:') || comma === -1) {
170
+ throw new Error(`不是合法的 data URL(应以 "data:" 开头并含 ","):${dataUrl.slice(0, 64)}…`);
171
+ }
172
+ const header = dataUrl.slice(5, comma);
173
+ const payload = dataUrl.slice(comma + 1);
174
+ const isB64 = /;base64/i.test(header);
175
+ return isB64
176
+ ? Buffer.from(payload, 'base64')
177
+ : Buffer.from(decodeURIComponent(payload), 'utf8');
178
+ }
179
+
180
+ /** 从 message.images[] 抽第一个可用 data URL(兼容 {image_url:{url}} 与 {url})。 */
181
+ function firstImageUrl(message) {
182
+ const images = message?.images;
183
+ if (!Array.isArray(images)) return undefined;
184
+ for (const item of images) {
185
+ const url = item?.image_url?.url ?? item?.url;
186
+ if (typeof url === 'string' && url.length > 0) return url;
187
+ }
188
+ return undefined;
189
+ }
190
+
191
+ /** 从 Images API 的 `data[0]` 抽图:优先 b64_json(base64),否则 url(需再下载)。 */
192
+ function firstImageFromData(data) {
193
+ const item = Array.isArray(data?.data) ? data.data[0] : undefined;
194
+ if (!item) return undefined;
195
+ if (typeof item.b64_json === 'string' && item.b64_json.length > 0) {
196
+ return { kind: 'b64', value: item.b64_json };
197
+ }
198
+ if (typeof item.url === 'string' && item.url.length > 0) {
199
+ return { kind: 'url', value: item.url };
200
+ }
201
+ return undefined;
202
+ }
203
+
204
+ // ============================================================
205
+ // 3.5 协议选择 + 按协议组装请求 / 取图 / 降级(chat vs images 双栈)
206
+ // ============================================================
207
+
208
+ /**
209
+ * 选协议:OPENAI_IMAGE_PROTOCOL 显式优先(chat|images),否则按 OPENAI_IMAGE_PROVIDER 静态查表推断。
210
+ * 原生 Images API 那一类(OpenAI/Azure/火山方舟 Seedream)→ images;其余(含 OpenRouter、未设)→ chat。
211
+ * 纯确定性映射 + 可显式覆盖,不是 LLM 猜测。
212
+ */
213
+ function resolveProtocol() {
214
+ const explicit = (process.env.OPENAI_IMAGE_PROTOCOL || '').trim().toLowerCase();
215
+ if (explicit === 'chat' || explicit === 'images') return explicit;
216
+ const provider = (process.env.OPENAI_IMAGE_PROVIDER || '').trim().toLowerCase();
217
+ const IMAGES_PROVIDERS = new Set(['volces', 'volces-plan', 'ark', 'doubao', 'seedream', 'openai', 'azure']);
218
+ return IMAGES_PROVIDERS.has(provider) ? 'images' : 'chat';
219
+ }
220
+
221
+ /**
222
+ * Images API 的 size 归一化:火山 Seedream 只认 `2k/3k/4k`(小写)或 `WIDTHxHEIGHT`,**没有 1k 档**。
223
+ * - 显式 `WxH` 原样透传;
224
+ * - 档位统一小写;`1k`(如 ppt.yaml 写死的 `--size 1K`)抬到 `2k`(PPT 清晰度足够、是该模型的下限);
225
+ * - 其余非法值交给 400 降级(去掉 size 走默认 2048×2048)兜底。
226
+ */
227
+ function normalizeImagesSize(size) {
228
+ if (!size) return '2k';
229
+ if (/^\d+x\d+$/i.test(size)) return size;
230
+ const lower = size.toLowerCase();
231
+ return lower === '1k' ? '2k' : lower;
232
+ }
233
+
234
+ /** 按协议组装 { url, body, kind }。kind 用于后续取图/降级分派。 */
235
+ function buildRequest(protocol, provider, args, aspectHint, inputImageUrl) {
236
+ const base = provider.baseURL.replace(/\/$/, '');
237
+ if (protocol === 'images') {
238
+ const body = {
239
+ model: provider.model,
240
+ prompt: args.prompt + aspectHint, // aspect 在 images 路径只能靠 prompt 软引导
241
+ size: normalizeImagesSize(args.size),
242
+ response_format: 'b64_json', // 直接拿 base64,省一次 url 下载;端点不支持时回退 url
243
+ watermark: false, // PPT 配图不要水印
244
+ };
245
+ if (inputImageUrl) {
246
+ // 火山/BytePlus Seedream 的统一生成/编辑接口在 /images/generations 上通过 image 传参考图。
247
+ // 多数 OpenAI 兼容网关也接受同名扩展参数;若端点不支持,会走 400 显性失败,避免静默变成纯文生图。
248
+ body.image = inputImageUrl;
249
+ }
250
+ return { url: `${base}/images/generations`, body, kind: 'images' };
251
+ }
252
+ // chat(默认,向后兼容 OpenRouter 多模态)
253
+ const body = {
254
+ model: provider.model,
255
+ messages: [{ role: 'user', content: args.prompt + aspectHint }],
256
+ modalities: ['image', 'text'],
257
+ };
258
+ if (inputImageUrl) {
259
+ body.messages[0].content = [
260
+ { type: 'text', text: args.prompt + aspectHint },
261
+ { type: 'image_url', image_url: { url: inputImageUrl } },
262
+ ];
263
+ }
264
+ if (args.aspect || args.size) {
265
+ body.image_config = {};
266
+ if (args.aspect) body.image_config.aspect_ratio = args.aspect;
267
+ if (args.size) body.image_config.image_size = args.size;
268
+ }
269
+ return { url: `${base}/chat/completions`, body, kind: 'chat' };
270
+ }
271
+
272
+ /** 从 200 响应里按协议取图,返回 {kind,value} 或 undefined(undefined → 当「200 无图」可重试)。 */
273
+ function extractImage(kind, data) {
274
+ return kind === 'images'
275
+ ? firstImageFromData(data)
276
+ : (() => {
277
+ const url = firstImageUrl(data?.choices?.[0]?.message);
278
+ return url ? { kind: 'dataurl', value: url } : undefined;
279
+ })();
280
+ }
281
+
282
+ /**
283
+ * 400 优雅降级:去掉端点可能不认的可选参数,原地 mutate body。
284
+ * 返回降级动作文案(用于日志);无可去参数返回 null(调用方据此判定 400 不可救)。
285
+ */
286
+ function degradeBody(kind, body) {
287
+ if (kind === 'images') {
288
+ if (body.size === undefined) return null;
289
+ delete body.size; // 走默认 2048×2048
290
+ return '去掉 size 参数走默认尺寸重发';
291
+ }
292
+ if (!body.image_config) return null;
293
+ delete body.image_config;
294
+ return '去掉比例参数重发(aspect 改由提示词软引导)';
295
+ }
296
+
297
+ /** 把取到的图片源解码/下载成 Buffer。dataurl/b64 本地解码;url 再发一次 GET。 */
298
+ async function srcToBuffer(src, timeoutMs) {
299
+ if (src.kind === 'dataurl') return dataUrlToBuffer(src.value);
300
+ if (src.kind === 'b64') return Buffer.from(src.value, 'base64');
301
+ // src.kind === 'url':response_format=url 时图片是个外链,下载回来。
302
+ const resp = await fetch(src.value, { signal: AbortSignal.timeout(timeoutMs) });
303
+ if (!resp.ok) throw new Error(`下载图片 URL 失败 [${resp.status} ${resp.statusText}]`);
304
+ return Buffer.from(await resp.arrayBuffer());
305
+ }
306
+
307
+ // ============================================================
308
+ // 4. 主流程
309
+ // ============================================================
310
+
311
+ async function main() {
312
+ const args = parseArgs(process.argv.slice(2));
313
+
314
+ // —— 参数校验 ——
315
+ if (!args.prompt || !args.out) {
316
+ err('用法:node generate.mjs --prompt "<英文提示词>" --out <路径> [--aspect 16:9] [--size 2K] [--input-image <路径或URL>] [--model <id>]');
317
+ err('错误:--prompt 和 --out 均为必填。');
318
+ process.exit(2);
319
+ }
320
+
321
+ // —— provider ——
322
+ const provider = loadProvider(args.model);
323
+ if (!provider) {
324
+ err('错误:未配置图片生成(缺少环境变量 OPENAI_IMAGE_BASE_URL / OPENAI_IMAGE_API_KEY / OPENAI_IMAGE_MODEL)。');
325
+ process.exit(3);
326
+ }
327
+
328
+ // —— 组装请求(按协议分派 chat / images)——
329
+ // aspect 软引导:拼一句构图提示(端点忽略原生比例参数时仍有作用,两套协议共用)。
330
+ const protocol = resolveProtocol();
331
+ const aspectHint = args.aspect ? `\n\n(Compose for a ${args.aspect} aspect ratio.)` : '';
332
+ let inputImageUrl;
333
+ try {
334
+ inputImageUrl = await imageInputToUrl(args['input-image']);
335
+ } catch (e) {
336
+ err(`错误:读取输入图片失败:${e?.message ?? e}`);
337
+ process.exit(2);
338
+ }
339
+ const { url, body, kind } = buildRequest(protocol, provider, args, aspectHint, inputImageUrl);
340
+
341
+ if (args['dry-run-request'] !== undefined) {
342
+ process.stdout.write(JSON.stringify({
343
+ url,
344
+ kind,
345
+ body,
346
+ hasInputImage: Boolean(inputImageUrl),
347
+ }, null, 2) + '\n');
348
+ process.exit(0);
349
+ }
350
+
351
+ const headers = {
352
+ 'Content-Type': 'application/json',
353
+ Authorization: `Bearer ${provider.apiKey}`,
354
+ };
355
+ let payload = JSON.stringify(body);
356
+
357
+ // —— 开跑诊断(stderr,绝不打 key / 不打 baseURL 全文,只打 host)——
358
+ const host = (() => {
359
+ try { return new URL(url).host; } catch { return '(unknown host)'; }
360
+ })();
361
+ err(
362
+ `[ai-image] protocol=${kind} model=${provider.model} aspect=${args.aspect ?? 'n/a'} size=${args.size ?? 'n/a'} ` +
363
+ `input=${inputImageUrl ? 'yes' : 'no'} → POST ${host}(单次超时 ${Math.round(TIMEOUT_MS / 1000)}s,最多 ${MAX_ATTEMPTS} 次)`,
364
+ );
365
+
366
+ // —— 带重试的 POST ——
367
+ let resp;
368
+ let data; // 解析后的 JSON(成功路径在循环内填)
369
+ let imgSrc; // 取到的图片源 {kind,value}(循环内填)
370
+ let lastErr;
371
+ let degraded = false; // 是否已为「端点拒绝可选参数」做过去参降级
372
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
373
+ const t0 = Date.now();
374
+ err(`[ai-image] attempt ${attempt}/${MAX_ATTEMPTS}:发送请求…`);
375
+ try {
376
+ resp = await fetch(url, {
377
+ method: 'POST',
378
+ headers,
379
+ body: payload,
380
+ signal: AbortSignal.timeout(TIMEOUT_MS),
381
+ });
382
+ } catch (e) {
383
+ // 超时 / 网络错 → 可重试
384
+ const secs = ((Date.now() - t0) / 1000).toFixed(1);
385
+ lastErr = new Error(`网络错误 [${e?.name ?? 'NetworkError'}]:${e?.message ?? e}`);
386
+ err(`[ai-image] attempt ${attempt} 网络错/超时(${secs}s):${e?.name ?? 'NetworkError'} ${e?.message ?? e}`);
387
+ if (attempt >= MAX_ATTEMPTS) break;
388
+ await sleep(backoffMs(attempt));
389
+ continue;
390
+ }
391
+
392
+ const secs = ((Date.now() - t0) / 1000).toFixed(1);
393
+ if (resp.ok) {
394
+ // 200 后**就地解析取图**:解析失败 / 「200 但无图」都视为可重试——模型偶发只回文本
395
+ // (message.images 为空),此前这种情况硬退出 1,逼上游 agent 自己重发;现在脚本内重试。
396
+ try {
397
+ data = await resp.json();
398
+ } catch (e) {
399
+ lastErr = new Error(`响应不是合法 JSON:${e?.message ?? e}`);
400
+ err(`[ai-image] attempt ${attempt} 收到 200(${secs}s)但 JSON 解析失败,将退避重试:${e?.message ?? e}`);
401
+ if (attempt >= MAX_ATTEMPTS) break;
402
+ await sleep(backoffMs(attempt));
403
+ continue;
404
+ }
405
+ imgSrc = extractImage(kind, data);
406
+ if (imgSrc) {
407
+ err(`[ai-image] attempt ${attempt} 收到 200(${secs}s),已取到图片。`);
408
+ break;
409
+ }
410
+ lastErr = new Error('返回 200 但未包含任何图片(message.images / data[] 为空)');
411
+ err(
412
+ `[ai-image] attempt ${attempt} 收到 200(${secs}s)但响应无图片,将退避重试。` +
413
+ `响应片段:${JSON.stringify(data).slice(0, 200)}`,
414
+ );
415
+ if (attempt >= MAX_ATTEMPTS) break;
416
+ await sleep(backoffMs(attempt));
417
+ continue;
418
+ }
419
+
420
+ const errText = await resp.text().catch(() => '');
421
+ if (isAuthError(resp.status)) {
422
+ // 鉴权失败:响亮失败,绝不回显 key 本身
423
+ err(`错误:图片生成鉴权失败 [${resp.status} ${resp.statusText}]——API key 可能失效/过期/无权访问该模型。`);
424
+ err(`请检查 .env 的 OPENAI_IMAGE_API_KEY / OPENAI_IMAGE_BASE_URL / OPENAI_IMAGE_MODEL。`);
425
+ err(`原始错误:${errText.slice(0, 300)}`);
426
+ process.exit(1);
427
+ }
428
+ // 400 优雅降级:端点可能不认可选参数(chat 的 image_config / images 的 size)、直接回 400。
429
+ // 去掉该参数重发一次(aspect 仍靠 prompt 软引导 + 导出端 object-fit 兜底几何)。
430
+ // 镜像聊天 client.ts「provider 拒绝大 max_tokens 就去掉重发」的既有降级范式。
431
+ if (resp.status === 400 && !degraded) {
432
+ const action = degradeBody(kind, body);
433
+ if (action) {
434
+ degraded = true;
435
+ payload = JSON.stringify(body);
436
+ err(
437
+ `[ai-image] attempt ${attempt} 端点拒绝参数 [400](${secs}s),${action}。` +
438
+ `原始错误:${errText.slice(0, 200)}`,
439
+ );
440
+ continue; // 直接用新 payload 重发,不退避
441
+ }
442
+ }
443
+ if (!isRetryable(resp.status)) {
444
+ err(`错误:图片生成请求失败 [${resp.status} ${resp.statusText}]:${errText.slice(0, 300)}`);
445
+ process.exit(1);
446
+ }
447
+ lastErr = new Error(`[${resp.status} ${resp.statusText}]:${errText.slice(0, 300)}`);
448
+ err(`[ai-image] attempt ${attempt} 失败 [${resp.status} ${resp.statusText}](${secs}s),将退避重试。`);
449
+ if (attempt >= MAX_ATTEMPTS) break;
450
+ await sleep(parseRetryAfter(resp) ?? backoffMs(attempt));
451
+ }
452
+
453
+ // 重试用尽仍无图:响亮失败。区分「始终 200 但无图」与「网络/HTTP 失败」两类诊断。
454
+ if (!imgSrc) {
455
+ if (resp && resp.ok) {
456
+ err(`错误:返回成功但始终未包含图片(重试 ${MAX_ATTEMPTS} 次,images/data[] 一直为空)。`);
457
+ err(
458
+ kind === 'images'
459
+ ? `请确认模型 "${provider.model}" 支持 /images/generations 且能返回 b64_json/url。`
460
+ : `请确认模型 "${provider.model}" 支持图像输出、且端点接受 modalities=["image","text"]。`,
461
+ );
462
+ if (data) err(`最后响应片段:${JSON.stringify(data).slice(0, 400)}`);
463
+ } else {
464
+ err(`错误:图片生成失败(重试 ${MAX_ATTEMPTS} 次后仍未成功):${lastErr?.message ?? '未知错误'}`);
465
+ }
466
+ process.exit(1);
467
+ }
468
+
469
+ // —— 解码(dataurl/b64 本地解码;url 再下载)落盘 ——
470
+ let buffer;
471
+ try {
472
+ buffer = await srcToBuffer(imgSrc, TIMEOUT_MS);
473
+ } catch (e) {
474
+ err(`错误:图片解码/下载失败:${e?.message ?? e}`);
475
+ process.exit(1);
476
+ }
477
+
478
+ try {
479
+ await mkdir(dirname(args.out), { recursive: true });
480
+ await writeFile(args.out, buffer);
481
+ } catch (e) {
482
+ err(`错误:写入文件失败(${args.out}):${e?.message ?? e}`);
483
+ process.exit(1);
484
+ }
485
+
486
+ // —— 机器友好 summary(仅这一行进 stdout,便于上游 capture/regex)——
487
+ process.stdout.write(
488
+ `saved: ${args.out} (model=${provider.model}, bytes=${buffer.length}, aspect=${args.aspect ?? 'n/a'})\n`,
489
+ );
490
+ process.exit(0);
491
+ }
492
+
493
+ main().catch((e) => {
494
+ // 兜底:任何未预期异常都响亮失败,不回显 key
495
+ err(`错误:未预期异常:${e?.message ?? e}`);
496
+ process.exit(1);
497
+ });