@oh-story/dsh 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 (488) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -0
  3. package/cordis.patch.yml +3 -0
  4. package/lib/client.js +1201 -0
  5. package/lib/client.js.map +7 -0
  6. package/lib/drama/LICENSE.upstream +21 -0
  7. package/lib/drama/manifest.json +1012 -0
  8. package/lib/drama/skills/short-drama/SKILL.md +153 -0
  9. package/lib/drama/skills/short-drama/agents/openai.yaml +4 -0
  10. package/lib/drama/skills/short-drama/assets/creator-decision.example.jsonl +4 -0
  11. package/lib/drama/skills/short-drama/assets/production-observation.example.jsonl +2 -0
  12. package/lib/drama/skills/short-drama/assets/project-template/short-drama.json +36 -0
  13. package/lib/drama/skills/short-drama/assets/reference-observation.example.jsonl +2 -0
  14. package/lib/drama/skills/short-drama/references/audience-reveal.md +74 -0
  15. package/lib/drama/skills/short-drama/references/contract-and-ownership.md +94 -0
  16. package/lib/drama/skills/short-drama/references/creator-workflow.md +82 -0
  17. package/lib/drama/skills/short-drama/references/form-cards/Q/347/211/210/350/241/250/350/276/276.md +73 -0
  18. package/lib/drama/skills/short-drama/references/form-cards//344/272/214/347/273/264/345/212/250/346/200/201/346/274/253.md +74 -0
  19. package/lib/drama/skills/short-drama/references/form-cards//345/233/275/346/274/253/344/272/214/346/254/241/345/205/203.md +79 -0
  20. package/lib/drama/skills/short-drama/references/form-cards//345/256/236/346/213/215.md +75 -0
  21. package/lib/drama/skills/short-drama/references/form-cards//346/260/264/345/242/250/347/254/224/350/247/246.md +85 -0
  22. package/lib/drama/skills/short-drama/references/form-cards//351/243/216/346/240/274/345/214/226/344/270/211/347/273/264.md +72 -0
  23. package/lib/drama/skills/short-drama/references/knowhow-index.md +80 -0
  24. package/lib/drama/skills/short-drama/references/lifecycle-commands.md +130 -0
  25. package/lib/drama/skills/short-drama/references/look-development.md +52 -0
  26. package/lib/drama/skills/short-drama/references/pickup-and-alternate.md +58 -0
  27. package/lib/drama/skills/short-drama/references/production-form-profiles.md +126 -0
  28. package/lib/drama/skills/short-drama/references/reference-roles.md +142 -0
  29. package/lib/drama/skills/short-drama/references/runtime-preflight.md +27 -0
  30. package/lib/drama/skills/short-drama/scripts/project_tool.py +1963 -0
  31. package/lib/drama/skills/short-drama/scripts/selftest.py +287 -0
  32. package/lib/drama/skills/short-drama-assets/SKILL.md +187 -0
  33. package/lib/drama/skills/short-drama-assets/agents/openai.yaml +4 -0
  34. package/lib/drama/skills/short-drama-assets/assets/character-look.example.jsonl +3 -0
  35. package/lib/drama/skills/short-drama-assets/assets/continuity.example.jsonl +4 -0
  36. package/lib/drama/skills/short-drama-assets/assets/decisions.example.jsonl +5 -0
  37. package/lib/drama/skills/short-drama-assets/assets/location-view.example.jsonl +3 -0
  38. package/lib/drama/skills/short-drama-assets/assets/occurrences.example.jsonl +5 -0
  39. package/lib/drama/skills/short-drama-assets/assets/prop-state.example.jsonl +3 -0
  40. package/lib/drama/skills/short-drama-assets/examples/minimal/characters.jsonl +2 -0
  41. package/lib/drama/skills/short-drama-assets/examples/minimal/looks.jsonl +2 -0
  42. package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +89 -0
  43. package/lib/drama/skills/short-drama-assets/references/character-and-look.md +164 -0
  44. package/lib/drama/skills/short-drama-assets/references/continuity-delta.md +123 -0
  45. package/lib/drama/skills/short-drama-assets/references/identity-vs-variant.md +146 -0
  46. package/lib/drama/skills/short-drama-assets/references/location-and-view.md +84 -0
  47. package/lib/drama/skills/short-drama-assets/references/occurrence-extraction.md +111 -0
  48. package/lib/drama/skills/short-drama-assets/references/prop-and-state.md +107 -0
  49. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +136 -0
  50. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +171 -0
  51. package/lib/drama/skills/short-drama-assets/scripts/asset_check.py +267 -0
  52. package/lib/drama/skills/short-drama-assets/scripts/selftest.py +89 -0
  53. package/lib/drama/skills/short-drama-develop/SKILL.md +157 -0
  54. package/lib/drama/skills/short-drama-develop/agents/openai.yaml +4 -0
  55. package/lib/drama/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -0
  56. package/lib/drama/skills/short-drama-develop/assets/creative-brief.md +120 -0
  57. package/lib/drama/skills/short-drama-develop/assets/episode-map.jsonl +1 -0
  58. package/lib/drama/skills/short-drama-develop/assets/story-engine.md +181 -0
  59. package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +219 -0
  60. package/lib/drama/skills/short-drama-develop/references/creative-reference-intake.md +57 -0
  61. package/lib/drama/skills/short-drama-develop/references/director-brief-craft.md +113 -0
  62. package/lib/drama/skills/short-drama-develop/references/episode-design.md +509 -0
  63. package/lib/drama/skills/short-drama-develop/references/genre-and-hook-playbook.md +240 -0
  64. package/lib/drama/skills/short-drama-develop/references/genre-cards//344/272/262/345/255/220/351/232/220/347/247/230.md +112 -0
  65. package/lib/drama/skills/short-drama-develop/references/genre-cards//344/273/231/344/276/240/344/277/256/347/234/237.md +51 -0
  66. package/lib/drama/skills/short-drama-develop/references/genre-cards//345/212/250/344/275/234/344/273/273/345/212/241.md +51 -0
  67. package/lib/drama/skills/short-drama-develop/references/genre-cards//345/217/244/350/243/205/346/235/203/350/260/213.md +51 -0
  68. package/lib/drama/skills/short-drama-develop/references/genre-cards//345/244/215/344/273/207/346/211/223/350/204/270.md +51 -0
  69. package/lib/drama/skills/short-drama-develop/references/genre-cards//345/256/266/345/272/255/345/205/263/347/263/273.md +51 -0
  70. package/lib/drama/skills/short-drama-develop/references/genre-cards//346/202/254/347/226/221/350/247/204/345/210/231.md +51 -0
  71. package/lib/drama/skills/short-drama-develop/references/genre-cards//347/224/237/346/264/273/346/265/201.md +51 -0
  72. package/lib/drama/skills/short-drama-develop/references/genre-cards//347/240/264/351/225/234/351/207/215/345/234/206.md +109 -0
  73. package/lib/drama/skills/short-drama-develop/references/genre-cards//350/201/214/345/234/272/345/226/234/345/211/247.md +51 -0
  74. package/lib/drama/skills/short-drama-develop/references/genre-cards//350/261/252/351/227/250/345/251/232/346/201/213.md +51 -0
  75. package/lib/drama/skills/short-drama-develop/references/genre-cards//350/272/253/344/273/275/351/224/231/344/275/215.md +51 -0
  76. package/lib/drama/skills/short-drama-develop/references/genre-cards.md +92 -0
  77. package/lib/drama/skills/short-drama-develop/references/mechanism-loop.md +282 -0
  78. package/lib/drama/skills/short-drama-develop/references/multi-episode-intake.md +128 -0
  79. package/lib/drama/skills/short-drama-develop/references/premise-devices.md +211 -0
  80. package/lib/drama/skills/short-drama-develop/references/reveal-reversal-payoff.md +99 -0
  81. package/lib/drama/skills/short-drama-develop/references/serial-character-and-memory.md +293 -0
  82. package/lib/drama/skills/short-drama-develop/references/stage-contract.md +88 -0
  83. package/lib/drama/skills/short-drama-develop/references/story-craft.md +359 -0
  84. package/lib/drama/skills/short-drama-develop/scripts/episode_intake.py +699 -0
  85. package/lib/drama/skills/short-drama-develop/scripts/selftest.py +47 -0
  86. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +181 -0
  87. package/lib/drama/skills/short-drama-image-prompts/agents/openai.yaml +4 -0
  88. package/lib/drama/skills/short-drama-image-prompts/assets/image-prompt-spec.jsonl.md +187 -0
  89. package/lib/drama/skills/short-drama-image-prompts/assets/image-prompts.md +37 -0
  90. package/lib/drama/skills/short-drama-image-prompts/assets/lookdev-frame-spec.jsonl.md +128 -0
  91. package/lib/drama/skills/short-drama-image-prompts/assets/lookdev-prompts.md +31 -0
  92. package/lib/drama/skills/short-drama-image-prompts/examples/minimal-image-prompt-specs.jsonl +2 -0
  93. package/lib/drama/skills/short-drama-image-prompts/references/character-and-look.md +154 -0
  94. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +231 -0
  95. package/lib/drama/skills/short-drama-image-prompts/references/edit-and-revision.md +122 -0
  96. package/lib/drama/skills/short-drama-image-prompts/references/location-plate.md +152 -0
  97. package/lib/drama/skills/short-drama-image-prompts/references/look-and-state-variant.md +96 -0
  98. package/lib/drama/skills/short-drama-image-prompts/references/lookdev-frame.md +38 -0
  99. package/lib/drama/skills/short-drama-image-prompts/references/production-sheet-recipes.md +177 -0
  100. package/lib/drama/skills/short-drama-image-prompts/references/prop-plate.md +140 -0
  101. package/lib/drama/skills/short-drama-image-prompts/references/review-and-fixtures.md +92 -0
  102. package/lib/drama/skills/short-drama-image-prompts/references/stage-contract.md +89 -0
  103. package/lib/drama/skills/short-drama-image-prompts/scripts/image_prompt_check.py +443 -0
  104. package/lib/drama/skills/short-drama-image-prompts/scripts/selftest.py +93 -0
  105. package/lib/drama/skills/short-drama-novel-analyze/SKILL.md +245 -0
  106. package/lib/drama/skills/short-drama-novel-analyze/agents/openai.yaml +4 -0
  107. package/lib/drama/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +3 -0
  108. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-triage.md +116 -0
  109. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-value.md +124 -0
  110. package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +155 -0
  111. package/lib/drama/skills/short-drama-novel-analyze/references/chapter-extraction.md +141 -0
  112. package/lib/drama/skills/short-drama-novel-analyze/references/stage-contract.md +67 -0
  113. package/lib/drama/skills/short-drama-novel-analyze/scripts/novel_index.py +651 -0
  114. package/lib/drama/skills/short-drama-novel-analyze/scripts/selftest.py +51 -0
  115. package/lib/drama/skills/short-drama-produce/SKILL.md +103 -0
  116. package/lib/drama/skills/short-drama-produce/agents/openai.yaml +4 -0
  117. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +140 -0
  118. package/lib/drama/skills/short-drama-produce/references/providers/gpt-image-2.md +24 -0
  119. package/lib/drama/skills/short-drama-produce/references/providers/minimax-music.md +25 -0
  120. package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +40 -0
  121. package/lib/drama/skills/short-drama-produce/scripts/fixture_adapter.py +60 -0
  122. package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +1635 -0
  123. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +964 -0
  124. package/lib/drama/skills/short-drama-produce/scripts/selftest.py +134 -0
  125. package/lib/drama/skills/short-drama-review/SKILL.md +178 -0
  126. package/lib/drama/skills/short-drama-review/agents/openai.yaml +4 -0
  127. package/lib/drama/skills/short-drama-review/assets/finding-template.jsonl +2 -0
  128. package/lib/drama/skills/short-drama-review/assets/supersession-decision.example.json +42 -0
  129. package/lib/drama/skills/short-drama-review/assets/verdict-template.json +32 -0
  130. package/lib/drama/skills/short-drama-review/examples/minimal-findings.jsonl +2 -0
  131. package/lib/drama/skills/short-drama-review/examples/minimal-verdict.json +34 -0
  132. package/lib/drama/skills/short-drama-review/references/anti-template-repair.md +114 -0
  133. package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +270 -0
  134. package/lib/drama/skills/short-drama-review/references/project-calibration.md +85 -0
  135. package/lib/drama/skills/short-drama-review/references/review-method.md +82 -0
  136. package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +115 -0
  137. package/lib/drama/skills/short-drama-review/references/rubric-source-analysis.md +108 -0
  138. package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +164 -0
  139. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +166 -0
  140. package/lib/drama/skills/short-drama-review/references/stage-contract.md +87 -0
  141. package/lib/drama/skills/short-drama-review/scripts/review_check.py +325 -0
  142. package/lib/drama/skills/short-drama-review/scripts/selftest.py +87 -0
  143. package/lib/drama/skills/short-drama-storyboard/SKILL.md +264 -0
  144. package/lib/drama/skills/short-drama-storyboard/agents/openai.yaml +4 -0
  145. package/lib/drama/skills/short-drama-storyboard/assets/coverage-audition.example.jsonl +2 -0
  146. package/lib/drama/skills/short-drama-storyboard/assets/coverage-template.json +49 -0
  147. package/lib/drama/skills/short-drama-storyboard/assets/keyframe-prompts.md +32 -0
  148. package/lib/drama/skills/short-drama-storyboard/assets/keyframe-template.jsonl +2 -0
  149. package/lib/drama/skills/short-drama-storyboard/assets/revision-lineage.fragment.json +12 -0
  150. package/lib/drama/skills/short-drama-storyboard/assets/scene-visual-plan.example.jsonl +2 -0
  151. package/lib/drama/skills/short-drama-storyboard/assets/shot-template.jsonl +2 -0
  152. package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +241 -0
  153. package/lib/drama/skills/short-drama-storyboard/references/coverage-audition.md +39 -0
  154. package/lib/drama/skills/short-drama-storyboard/references/keyframe-craft.md +169 -0
  155. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +264 -0
  156. package/lib/drama/skills/short-drama-storyboard/references/review-and-fixtures.md +70 -0
  157. package/lib/drama/skills/short-drama-storyboard/references/scene-visual-plan.md +39 -0
  158. package/lib/drama/skills/short-drama-storyboard/references/screenplay-to-keyframe-example.md +157 -0
  159. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +192 -0
  160. package/lib/drama/skills/short-drama-storyboard/references/shot-revision-identity.md +29 -0
  161. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +110 -0
  162. package/lib/drama/skills/short-drama-storyboard/scripts/selftest.py +191 -0
  163. package/lib/drama/skills/short-drama-storyboard/scripts/storyboard_check.py +824 -0
  164. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +245 -0
  165. package/lib/drama/skills/short-drama-video-prompts/agents/openai.yaml +4 -0
  166. package/lib/drama/skills/short-drama-video-prompts/assets/coverage-scope.fragment.json +21 -0
  167. package/lib/drama/skills/short-drama-video-prompts/assets/delivery-container.jsonl.md +101 -0
  168. package/lib/drama/skills/short-drama-video-prompts/assets/motion-spec.jsonl.md +212 -0
  169. package/lib/drama/skills/short-drama-video-prompts/assets/motion-terminal.example.jsonl +2 -0
  170. package/lib/drama/skills/short-drama-video-prompts/assets/music-spec.jsonl.md +31 -0
  171. package/lib/drama/skills/short-drama-video-prompts/assets/performance.fragment.json +38 -0
  172. package/lib/drama/skills/short-drama-video-prompts/assets/video-prompts.md +46 -0
  173. package/lib/drama/skills/short-drama-video-prompts/examples/minimal-music-specs.jsonl +2 -0
  174. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +240 -0
  175. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +255 -0
  176. package/lib/drama/skills/short-drama-video-prompts/references/generability.md +93 -0
  177. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +304 -0
  178. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +191 -0
  179. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +312 -0
  180. package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +168 -0
  181. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +110 -0
  182. package/lib/drama/skills/short-drama-video-prompts/scripts/container_check.py +329 -0
  183. package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +450 -0
  184. package/lib/drama/skills/short-drama-video-prompts/scripts/music_spec_check.py +327 -0
  185. package/lib/drama/skills/short-drama-video-prompts/scripts/selftest.py +82 -0
  186. package/lib/drama/skills/short-drama-write/SKILL.md +284 -0
  187. package/lib/drama/skills/short-drama-write/agents/openai.yaml +4 -0
  188. package/lib/drama/skills/short-drama-write/assets/beats.jsonl +2 -0
  189. package/lib/drama/skills/short-drama-write/assets/episode-card-standalone.json +120 -0
  190. package/lib/drama/skills/short-drama-write/assets/episode-card.json +55 -0
  191. package/lib/drama/skills/short-drama-write/assets/screenplay.md +36 -0
  192. package/lib/drama/skills/short-drama-write/assets/voice-record-sheet.jsonl.md +89 -0
  193. package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +519 -0
  194. package/lib/drama/skills/short-drama-write/references/production-format-dialect.md +273 -0
  195. package/lib/drama/skills/short-drama-write/references/scene-handoff-capsule.md +51 -0
  196. package/lib/drama/skills/short-drama-write/references/scene-sound-dramaturgy.md +43 -0
  197. package/lib/drama/skills/short-drama-write/references/screenplay-format.md +260 -0
  198. package/lib/drama/skills/short-drama-write/references/script-craft.md +558 -0
  199. package/lib/drama/skills/short-drama-write/references/stage-contract.md +94 -0
  200. package/lib/drama/skills/short-drama-write/references/substitutable-realization.md +107 -0
  201. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +309 -0
  202. package/lib/drama/skills/short-drama-write/scripts/screenplay_index.py +1091 -0
  203. package/lib/drama/skills/short-drama-write/scripts/selftest.py +229 -0
  204. package/lib/drama/skills/short-drama-write/scripts/voice_sheet_check.py +416 -0
  205. package/lib/index.js +756 -0
  206. package/lib/index.js.map +7 -0
  207. package/lib/oh-story/LICENSE.upstream +21 -0
  208. package/lib/oh-story/manifest.json +1351 -0
  209. package/lib/oh-story/roles/chapter-extractor.md +314 -0
  210. package/lib/oh-story/roles/character-designer.md +169 -0
  211. package/lib/oh-story/roles/consistency-checker.md +190 -0
  212. package/lib/oh-story/roles/narrative-writer.md +294 -0
  213. package/lib/oh-story/roles/story-architect.md +195 -0
  214. package/lib/oh-story/roles/story-explorer.md +364 -0
  215. package/lib/oh-story/roles/story-researcher.md +304 -0
  216. package/lib/oh-story/skills/browser-cdp/SKILL.md +175 -0
  217. package/lib/oh-story/skills/story/SKILL.md +116 -0
  218. package/lib/oh-story/skills/story/VERSION +1 -0
  219. package/lib/oh-story/skills/story-cover/SKILL.md +327 -0
  220. package/lib/oh-story/skills/story-cover/references/cover-styles.md +210 -0
  221. package/lib/oh-story/skills/story-deslop/SKILL.md +445 -0
  222. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +582 -0
  223. package/lib/oh-story/skills/story-deslop/references/banned-words.md +147 -0
  224. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +1371 -0
  225. package/lib/oh-story/skills/story-deslop/scripts/check-degeneration.js +330 -0
  226. package/lib/oh-story/skills/story-deslop/scripts/check-outline-copy.js +241 -0
  227. package/lib/oh-story/skills/story-deslop/scripts/normalize-punctuation.js +410 -0
  228. package/lib/oh-story/skills/story-import/SKILL.md +634 -0
  229. package/lib/oh-story/skills/story-import/references/character-state-reverse.md +82 -0
  230. package/lib/oh-story/skills/story-import/references/format-and-structure.md +169 -0
  231. package/lib/oh-story/skills/story-import/references/length-routing.md +134 -0
  232. package/lib/oh-story/skills/story-import/references/state-tracking.md +108 -0
  233. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +442 -0
  234. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +175 -0
  235. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +161 -0
  236. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +1139 -0
  237. package/lib/oh-story/skills/story-long-analyze/SKILL.md +329 -0
  238. package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +270 -0
  239. package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +530 -0
  240. package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +559 -0
  241. package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +80 -0
  242. package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +149 -0
  243. package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +133 -0
  244. package/lib/oh-story/skills/story-long-scan/SKILL.md +342 -0
  245. package/lib/oh-story/skills/story-long-scan/references/genre-trends.md +197 -0
  246. package/lib/oh-story/skills/story-long-scan/references/publishing-guide.md +114 -0
  247. package/lib/oh-story/skills/story-long-scan/references/reader-profiling.md +105 -0
  248. package/lib/oh-story/skills/story-long-scan/references/scan-output-format.md +207 -0
  249. package/lib/oh-story/skills/story-long-scan/references/topic-decision.md +78 -0
  250. package/lib/oh-story/skills/story-long-write/SKILL.md +327 -0
  251. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +582 -0
  252. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +302 -0
  253. package/lib/oh-story/skills/story-long-write/references/banned-words.md +147 -0
  254. package/lib/oh-story/skills/story-long-write/references/character-basics.md +445 -0
  255. package/lib/oh-story/skills/story-long-write/references/character-design-methods.md +428 -0
  256. package/lib/oh-story/skills/story-long-write/references/character-relations.md +385 -0
  257. package/lib/oh-story/skills/story-long-write/references/commercial-core-methods.md +502 -0
  258. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +62 -0
  259. package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +282 -0
  260. package/lib/oh-story/skills/story-long-write/references/emotional-arc-design.md +426 -0
  261. package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +209 -0
  262. package/lib/oh-story/skills/story-long-write/references/female-audience-writing.md +188 -0
  263. package/lib/oh-story/skills/story-long-write/references/format-and-structure.md +169 -0
  264. package/lib/oh-story/skills/story-long-write/references/genre-catalog.md +480 -0
  265. package/lib/oh-story/skills/story-long-write/references/genre-core-mechanics.md +415 -0
  266. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//344/270/234/346/226/271/344/273/231/344/276/240.md +51 -0
  267. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//344/274/240/347/273/237/347/216/204/345/271/273.md +51 -0
  268. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/216/206/345/217/262/345/217/244/344/273/243.md +51 -0
  269. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/216/206/345/217/262/350/204/221/346/264/236.md +51 -0
  270. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/217/214/347/224/267/344/270/273.md +53 -0
  271. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/217/244/350/250/200/350/204/221/346/264/236.md +51 -0
  272. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/217/244/351/243/216/344/270/226/346/203/205.md +51 -0
  273. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/245/263/351/242/221/346/202/254/347/226/221.md +51 -0
  274. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/245/263/351/242/221/347/247/215/347/224/260.md +51 -0
  275. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/256/253/346/226/227/345/256/205/346/226/227.md +51 -0
  276. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/271/264/344/273/243.md +53 -0
  277. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//345/277/253/347/251/277.md +51 -0
  278. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//346/202/254/347/226/221/347/201/265/345/274/202.md +51 -0
  279. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//346/202/254/347/226/221/350/204/221/346/264/236.md +51 -0
  280. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//346/210/230/347/245/236/350/265/230/345/251/277.md +51 -0
  281. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//346/212/227/346/210/230/350/260/215/346/210/230.md +51 -0
  282. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//346/230/237/345/205/211/347/222/200/347/222/250.md +51 -0
  283. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//346/260/221/345/233/275/350/250/200/346/203/205.md +51 -0
  284. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//346/270/270/346/210/217/344/275/223/350/202/262.md +51 -0
  285. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//347/216/204/345/271/273/350/204/221/346/264/236.md +53 -0
  286. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//347/216/204/345/271/273/350/250/200/346/203/205.md +51 -0
  287. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//347/216/260/350/250/200/350/204/221/346/264/236.md +51 -0
  288. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//347/247/221/345/271/273/346/234/253/344/270/226.md +51 -0
  289. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//350/201/214/345/234/272/345/251/232/346/201/213.md +51 -0
  290. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//350/245/277/346/226/271/345/245/207/345/271/273.md +51 -0
  291. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//350/261/252/351/227/250/346/200/273/350/243/201.md +53 -0
  292. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//351/203/275/345/270/202/344/277/256/347/234/237.md +51 -0
  293. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//351/203/275/345/270/202/346/227/245/345/270/270.md +53 -0
  294. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//351/203/275/345/270/202/347/247/215/347/224/260.md +51 -0
  295. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//351/203/275/345/270/202/350/204/221/346/264/236.md +53 -0
  296. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//351/203/275/345/270/202/351/253/230/346/255/246.md +53 -0
  297. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards//351/235/222/346/230/245/347/224/234/345/256/240.md +51 -0
  298. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +91 -0
  299. package/lib/oh-story/skills/story-long-write/references/genre-readers.md +235 -0
  300. package/lib/oh-story/skills/story-long-write/references/genre-writing-formulas.md +444 -0
  301. package/lib/oh-story/skills/story-long-write/references/hooks-chapter.md +225 -0
  302. package/lib/oh-story/skills/story-long-write/references/hooks-paragraph.md +146 -0
  303. package/lib/oh-story/skills/story-long-write/references/hooks-suspense.md +352 -0
  304. package/lib/oh-story/skills/story-long-write/references/opening-design.md +349 -0
  305. package/lib/oh-story/skills/story-long-write/references/outline-conflict.md +457 -0
  306. package/lib/oh-story/skills/story-long-write/references/outline-methods.md +287 -0
  307. package/lib/oh-story/skills/story-long-write/references/outline-rhythm.md +389 -0
  308. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +326 -0
  309. package/lib/oh-story/skills/story-long-write/references/plot-core-methods.md +522 -0
  310. package/lib/oh-story/skills/story-long-write/references/plot-emotion-system.md +354 -0
  311. package/lib/oh-story/skills/story-long-write/references/plot-frameworks.md +711 -0
  312. package/lib/oh-story/skills/story-long-write/references/plot-special-topics.md +622 -0
  313. package/lib/oh-story/skills/story-long-write/references/quality-checklist.md +184 -0
  314. package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +109 -0
  315. package/lib/oh-story/skills/story-long-write/references/reversal-toolkit.md +349 -0
  316. package/lib/oh-story/skills/story-long-write/references/state-tracking.md +108 -0
  317. package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +409 -0
  318. package/lib/oh-story/skills/story-long-write/references/style-craft.md +347 -0
  319. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +510 -0
  320. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +161 -0
  321. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +140 -0
  322. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +171 -0
  323. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +99 -0
  324. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +284 -0
  325. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +421 -0
  326. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +1371 -0
  327. package/lib/oh-story/skills/story-long-write/scripts/check-degeneration.js +330 -0
  328. package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +241 -0
  329. package/lib/oh-story/skills/story-long-write/scripts/normalize-punctuation.js +410 -0
  330. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +1139 -0
  331. package/lib/oh-story/skills/story-review/SKILL.md +483 -0
  332. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +582 -0
  333. package/lib/oh-story/skills/story-review/references/banned-words.md +147 -0
  334. package/lib/oh-story/skills/story-review/references/character-relations.md +385 -0
  335. package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +282 -0
  336. package/lib/oh-story/skills/story-review/references/plot-core-methods.md +522 -0
  337. package/lib/oh-story/skills/story-review/references/quality-checklist.md +250 -0
  338. package/lib/oh-story/skills/story-review/references/quality-rubric.md +55 -0
  339. package/lib/oh-story/skills/story-review/references/rubrics/fanqie.md +18 -0
  340. package/lib/oh-story/skills/story-review/references/rubrics/qidian.md +19 -0
  341. package/lib/oh-story/skills/story-review/references/rubrics/zhihu.md +20 -0
  342. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +161 -0
  343. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +1371 -0
  344. package/lib/oh-story/skills/story-review/scripts/check-degeneration.js +330 -0
  345. package/lib/oh-story/skills/story-review/scripts/normalize-punctuation.js +410 -0
  346. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +1139 -0
  347. package/lib/oh-story/skills/story-setup/SKILL.md +495 -0
  348. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +582 -0
  349. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +147 -0
  350. package/lib/oh-story/skills/story-setup/references/agent-references/character-basics.md +445 -0
  351. package/lib/oh-story/skills/story-setup/references/agent-references/character-design-methods.md +428 -0
  352. package/lib/oh-story/skills/story-setup/references/agent-references/character-relations.md +385 -0
  353. package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +282 -0
  354. package/lib/oh-story/skills/story-setup/references/agent-references/emotional-arc-design.md +426 -0
  355. package/lib/oh-story/skills/story-setup/references/agent-references/emotional-methods.md +179 -0
  356. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +169 -0
  357. package/lib/oh-story/skills/story-setup/references/agent-references/genre-catalog.md +480 -0
  358. package/lib/oh-story/skills/story-setup/references/agent-references/genre-core-mechanics.md +415 -0
  359. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//344/270/234/346/226/271/344/273/231/344/276/240.md +51 -0
  360. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//344/274/240/347/273/237/347/216/204/345/271/273.md +51 -0
  361. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/216/206/345/217/262/345/217/244/344/273/243.md +51 -0
  362. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/216/206/345/217/262/350/204/221/346/264/236.md +51 -0
  363. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/217/214/347/224/267/344/270/273.md +53 -0
  364. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/217/244/350/250/200/350/204/221/346/264/236.md +51 -0
  365. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/217/244/351/243/216/344/270/226/346/203/205.md +51 -0
  366. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/245/263/351/242/221/346/202/254/347/226/221.md +51 -0
  367. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/245/263/351/242/221/347/247/215/347/224/260.md +51 -0
  368. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/256/253/346/226/227/345/256/205/346/226/227.md +51 -0
  369. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/271/264/344/273/243.md +53 -0
  370. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//345/277/253/347/251/277.md +51 -0
  371. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//346/202/254/347/226/221/347/201/265/345/274/202.md +51 -0
  372. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//346/202/254/347/226/221/350/204/221/346/264/236.md +51 -0
  373. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//346/210/230/347/245/236/350/265/230/345/251/277.md +51 -0
  374. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//346/212/227/346/210/230/350/260/215/346/210/230.md +51 -0
  375. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//346/230/237/345/205/211/347/222/200/347/222/250.md +51 -0
  376. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//346/260/221/345/233/275/350/250/200/346/203/205.md +51 -0
  377. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//346/270/270/346/210/217/344/275/223/350/202/262.md +51 -0
  378. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//347/216/204/345/271/273/350/204/221/346/264/236.md +53 -0
  379. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//347/216/204/345/271/273/350/250/200/346/203/205.md +51 -0
  380. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//347/216/260/350/250/200/350/204/221/346/264/236.md +51 -0
  381. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//347/247/221/345/271/273/346/234/253/344/270/226.md +51 -0
  382. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//350/201/214/345/234/272/345/251/232/346/201/213.md +51 -0
  383. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//350/245/277/346/226/271/345/245/207/345/271/273.md +51 -0
  384. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//350/261/252/351/227/250/346/200/273/350/243/201.md +53 -0
  385. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//351/203/275/345/270/202/344/277/256/347/234/237.md +51 -0
  386. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//351/203/275/345/270/202/346/227/245/345/270/270.md +53 -0
  387. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//351/203/275/345/270/202/347/247/215/347/224/260.md +51 -0
  388. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//351/203/275/345/270/202/350/204/221/346/264/236.md +53 -0
  389. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//351/203/275/345/270/202/351/253/230/346/255/246.md +53 -0
  390. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards//351/235/222/346/230/245/347/224/234/345/256/240.md +51 -0
  391. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +91 -0
  392. package/lib/oh-story/skills/story-setup/references/agent-references/genre-readers.md +235 -0
  393. package/lib/oh-story/skills/story-setup/references/agent-references/genre-writing-formulas.md +444 -0
  394. package/lib/oh-story/skills/story-setup/references/agent-references/hooks-chapter.md +225 -0
  395. package/lib/oh-story/skills/story-setup/references/agent-references/hooks-suspense.md +352 -0
  396. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +349 -0
  397. package/lib/oh-story/skills/story-setup/references/agent-references/outline-conflict.md +457 -0
  398. package/lib/oh-story/skills/story-setup/references/agent-references/outline-methods.md +287 -0
  399. package/lib/oh-story/skills/story-setup/references/agent-references/outline-rhythm.md +389 -0
  400. package/lib/oh-story/skills/story-setup/references/agent-references/plot-core-methods.md +522 -0
  401. package/lib/oh-story/skills/story-setup/references/agent-references/quality-checklist.md +247 -0
  402. package/lib/oh-story/skills/story-setup/references/agent-references/reversal-toolkit.md +349 -0
  403. package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +409 -0
  404. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +510 -0
  405. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +421 -0
  406. package/lib/oh-story/skills/story-short-analyze/SKILL.md +278 -0
  407. package/lib/oh-story/skills/story-short-analyze/references/anti-ai-writing.md +582 -0
  408. package/lib/oh-story/skills/story-short-analyze/references/banned-words.md +147 -0
  409. package/lib/oh-story/skills/story-short-analyze/references/character-basics.md +453 -0
  410. package/lib/oh-story/skills/story-short-analyze/references/character-design-methods.md +438 -0
  411. package/lib/oh-story/skills/story-short-analyze/references/character-relations.md +395 -0
  412. package/lib/oh-story/skills/story-short-analyze/references/deconstruction-examples.md +79 -0
  413. package/lib/oh-story/skills/story-short-analyze/references/genre-catalog.md +490 -0
  414. package/lib/oh-story/skills/story-short-analyze/references/genre-core-mechanics.md +425 -0
  415. package/lib/oh-story/skills/story-short-analyze/references/genre-readers.md +245 -0
  416. package/lib/oh-story/skills/story-short-analyze/references/genre-writing-formulas.md +453 -0
  417. package/lib/oh-story/skills/story-short-analyze/references/genre-writing-techniques.md +475 -0
  418. package/lib/oh-story/skills/story-short-analyze/references/hooks-chapter.md +225 -0
  419. package/lib/oh-story/skills/story-short-analyze/references/hooks-paragraph.md +146 -0
  420. package/lib/oh-story/skills/story-short-analyze/references/hooks-suspense.md +352 -0
  421. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +685 -0
  422. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +165 -0
  423. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +561 -0
  424. package/lib/oh-story/skills/story-short-analyze/references/quality-checklist.md +242 -0
  425. package/lib/oh-story/skills/story-short-analyze/references/real-market-data.md +90 -0
  426. package/lib/oh-story/skills/story-short-analyze/references/zhihu-style.md +54 -0
  427. package/lib/oh-story/skills/story-short-scan/SKILL.md +212 -0
  428. package/lib/oh-story/skills/story-short-scan/references/real-market-data.md +90 -0
  429. package/lib/oh-story/skills/story-short-write/SKILL.md +433 -0
  430. package/lib/oh-story/skills/story-short-write/references/banned-words.md +147 -0
  431. package/lib/oh-story/skills/story-short-write/references/cross-book-recall.md +62 -0
  432. package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +282 -0
  433. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +179 -0
  434. package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +159 -0
  435. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +156 -0
  436. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +163 -0
  437. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +167 -0
  438. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +178 -0
  439. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +166 -0
  440. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +159 -0
  441. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/262/231/351/233/225/350/204/221/346/264/236.md +151 -0
  442. package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +153 -0
  443. package/lib/oh-story/skills/story-short-write/references/genre-styles//350/277/275/345/246/273/347/201/253/350/221/254/345/234/272.md +210 -0
  444. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +444 -0
  445. package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +488 -0
  446. package/lib/oh-story/skills/story-short-write/references/hooks-chapter.md +225 -0
  447. package/lib/oh-story/skills/story-short-write/references/hooks-paragraph.md +146 -0
  448. package/lib/oh-story/skills/story-short-write/references/hooks-suspense.md +352 -0
  449. package/lib/oh-story/skills/story-short-write/references/output-contract.md +165 -0
  450. package/lib/oh-story/skills/story-short-write/references/quality-checklist.md +222 -0
  451. package/lib/oh-story/skills/story-short-write/references/reversal-toolkit.md +349 -0
  452. package/lib/oh-story/skills/story-short-write/references/short-craft.md +185 -0
  453. package/lib/oh-story/skills/story-short-write/references/short-deslop.md +133 -0
  454. package/lib/oh-story/skills/story-short-write/references/short-format.md +167 -0
  455. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +35 -0
  456. package/lib/oh-story/skills/story-short-write/references/villain-and-reveal.md +158 -0
  457. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +153 -0
  458. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +1371 -0
  459. package/lib/oh-story/skills/story-short-write/scripts/check-degeneration.js +330 -0
  460. package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +241 -0
  461. package/lib/oh-story/skills/story-short-write/scripts/normalize-punctuation.js +410 -0
  462. package/lib/types/client/file-activity.d.ts +30 -0
  463. package/lib/types/client/file-activity.d.ts.map +1 -0
  464. package/lib/types/client/file-tree.d.ts +20 -0
  465. package/lib/types/client/file-tree.d.ts.map +1 -0
  466. package/lib/types/client/index.d.ts +12 -0
  467. package/lib/types/client/index.d.ts.map +1 -0
  468. package/lib/types/client/jsonl-preview.d.ts +15 -0
  469. package/lib/types/client/jsonl-preview.d.ts.map +1 -0
  470. package/lib/types/client/markdown-preview.d.ts +38 -0
  471. package/lib/types/client/markdown-preview.d.ts.map +1 -0
  472. package/lib/types/index.d.ts +25 -0
  473. package/lib/types/index.d.ts.map +1 -0
  474. package/lib/types/native-hooks.d.ts +16 -0
  475. package/lib/types/native-hooks.d.ts.map +1 -0
  476. package/lib/types/role-provider.d.ts +6 -0
  477. package/lib/types/role-provider.d.ts.map +1 -0
  478. package/lib/types/role-tool.d.ts +10 -0
  479. package/lib/types/role-tool.d.ts.map +1 -0
  480. package/lib/types/skill-provider.d.ts +15 -0
  481. package/lib/types/skill-provider.d.ts.map +1 -0
  482. package/lib/types/workspace-request-trust.d.ts +13 -0
  483. package/lib/types/workspace-request-trust.d.ts.map +1 -0
  484. package/lib/types/workspace-route.d.ts +10 -0
  485. package/lib/types/workspace-route.d.ts.map +1 -0
  486. package/lib/types/workspace-security.d.ts +9 -0
  487. package/lib/types/workspace-security.d.ts.map +1 -0
  488. package/package.json +107 -0
@@ -0,0 +1,964 @@
1
+ #!/usr/bin/env python3
2
+ """Stdlib-only production adapters for supported media providers.
3
+
4
+ The public ``compile_*`` functions are deterministic and perform no I/O. The
5
+ CLI reads the confirmed production job from stdin and writes only the adapter
6
+ contract JSON to stdout after a provider result has been saved to a temporary
7
+ regular file.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import base64
14
+ import binascii
15
+ import json
16
+ import mimetypes
17
+ import os
18
+ import re
19
+ import secrets
20
+ import stat
21
+ import sys
22
+ import time
23
+ import urllib.error
24
+ import urllib.parse
25
+ import urllib.request
26
+ from collections.abc import Collection, Mapping, Sequence
27
+ from pathlib import Path
28
+ from typing import Any
29
+
30
+ OPENAI_MODEL = "gpt-image-2"
31
+ MINIMAX_MUSIC_MODEL = "music-3.0"
32
+ OPENAI_BASE_URL = "https://api.openai.com/v1"
33
+ SEEDANCE_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3"
34
+ MINIMAX_BASE_URL = "https://api.minimax.io/v1"
35
+ MAX_JSON_RESPONSE = 128 * 1024 * 1024
36
+ MAX_OUTPUT_BYTES = 512 * 1024 * 1024
37
+ MAX_REFERENCE_BYTES = 50 * 1024 * 1024
38
+ MAX_MULTIPART_BYTES = 200 * 1024 * 1024
39
+ MAX_ERROR_BODY_BYTES = 64 * 1024
40
+ TERMINAL_FAILURES = {"failed", "cancelled", "canceled", "timeout", "expired"}
41
+ SEEDANCE_RATIOS = {"adaptive", "1:1", "3:4", "4:3", "9:16", "16:9", "21:9"}
42
+ GPT_IMAGE_MIN_PIXELS = 655_360
43
+ GPT_IMAGE_MAX_PIXELS = 8_294_400
44
+ MINIMUM_PYTHON = (3, 9)
45
+ if sys.version_info < MINIMUM_PYTHON:
46
+ raise SystemExit("provider_adapters.py requires Python 3.9 or newer")
47
+
48
+
49
+ class AdapterFailure(RuntimeError):
50
+ """A safe-to-report adapter failure without provider response contents."""
51
+
52
+ def __init__(
53
+ self,
54
+ message: str,
55
+ *,
56
+ category: str = "provider_response",
57
+ code: str = "adapter_failure",
58
+ http_status: int | None = None,
59
+ request_id: str | None = None,
60
+ retryable: bool = False,
61
+ ) -> None:
62
+ super().__init__(message)
63
+ self.category = category
64
+ self.code = _safe_token(code) or "adapter_failure"
65
+ self.http_status = http_status
66
+ self.request_id = _safe_token(request_id)
67
+ self.retryable = retryable
68
+
69
+ def public(self, provider: str) -> dict[str, Any]:
70
+ result: dict[str, Any] = {
71
+ "provider": provider,
72
+ "category": self.category,
73
+ "code": self.code,
74
+ "retryable": self.retryable,
75
+ }
76
+ if self.http_status is not None:
77
+ result["http_status"] = self.http_status
78
+ if self.request_id is not None:
79
+ result["request_id"] = self.request_id
80
+ return result
81
+
82
+
83
+ def _safe_token(value: object) -> str | None:
84
+ if isinstance(value, str) and re.fullmatch(
85
+ r"[A-Za-z0-9][A-Za-z0-9._:-]{0,199}", value
86
+ ):
87
+ return value
88
+ if isinstance(value, int) and not isinstance(value, bool):
89
+ return str(value)
90
+ return None
91
+
92
+
93
+ def _request_id(headers: Mapping[str, Any]) -> str | None:
94
+ folded = {str(key).casefold(): value for key, value in headers.items()}
95
+ for name in ("x-request-id", "x-tt-logid", "x-trace-id", "trace-id"):
96
+ value = _safe_token(folded.get(name))
97
+ if value is not None:
98
+ return value
99
+ return None
100
+
101
+
102
+ def _provider_code(document: object) -> str | None:
103
+ if not isinstance(document, Mapping):
104
+ return None
105
+ error = document.get("error")
106
+ if isinstance(error, Mapping):
107
+ value = _safe_token(error.get("code"))
108
+ if value is not None:
109
+ return value
110
+ base_resp = document.get("base_resp")
111
+ if isinstance(base_resp, Mapping):
112
+ value = _safe_token(base_resp.get("status_code"))
113
+ if value not in {None, "0"}:
114
+ return value
115
+ return _safe_token(document.get("code"))
116
+
117
+
118
+ def _http_failure(provider: str, error: urllib.error.HTTPError) -> AdapterFailure:
119
+ status = error.code
120
+ try:
121
+ raw = error.read(MAX_ERROR_BODY_BYTES + 1)
122
+ document = json.loads(raw) if len(raw) <= MAX_ERROR_BODY_BYTES else None
123
+ except (OSError, UnicodeError, json.JSONDecodeError):
124
+ document = None
125
+ if status == 401:
126
+ category = "authentication"
127
+ elif status == 403:
128
+ category = "permission"
129
+ elif status == 429:
130
+ category = "rate_limit"
131
+ elif 500 <= status <= 599:
132
+ category = "server"
133
+ elif 400 <= status <= 499:
134
+ category = "invalid_request"
135
+ else:
136
+ category = "provider_response"
137
+ return AdapterFailure(
138
+ f"{provider} HTTP request failed",
139
+ category=category,
140
+ code=_provider_code(document) or f"http_{status}",
141
+ http_status=status,
142
+ request_id=_request_id(
143
+ dict(error.headers.items()) if error.headers is not None else {}
144
+ ),
145
+ retryable=status == 429 or 500 <= status <= 599,
146
+ )
147
+
148
+
149
+ def _require_job(job: Mapping[str, Any], modality: str) -> tuple[str, dict[str, Any]]:
150
+ if not isinstance(job, Mapping) or job.get("modality") != modality:
151
+ raise ValueError(f"adapter requires a {modality} job")
152
+ prompt = job.get("prompt")
153
+ if not isinstance(prompt, str) or not prompt.strip():
154
+ raise ValueError("job prompt must be non-empty")
155
+ parameters = job.get("parameters", {})
156
+ if not isinstance(parameters, Mapping):
157
+ raise ValueError("job parameters must be an object")
158
+ outputs = job.get("outputs")
159
+ if not isinstance(outputs, list) or len(outputs) != 1 or not isinstance(outputs[0], str):
160
+ raise ValueError("provider adapter requires exactly one output")
161
+ return prompt, dict(parameters)
162
+
163
+
164
+ def _take(parameters: dict[str, Any], allowed: set[str]) -> dict[str, Any]:
165
+ unknown = set(parameters) - allowed
166
+ if unknown:
167
+ raise ValueError("unsupported provider parameters: " + ", ".join(sorted(unknown)))
168
+ return parameters
169
+
170
+
171
+ def compile_seedance_payload(
172
+ job: Mapping[str, Any],
173
+ *,
174
+ model: str,
175
+ reference_urls: Sequence[str] = (),
176
+ allowed_ratios: Collection[str] | None = None,
177
+ duration_range: tuple[int, int] | None = None,
178
+ ) -> dict[str, Any]:
179
+ """Compile a video production job into the official Seedance task body.
180
+
181
+ ``model`` is deliberately mandatory: callers must obtain it from explicit
182
+ runtime configuration rather than assuming any Seedance release.
183
+ """
184
+ prompt, parameters = _require_job(job, "video")
185
+ if not isinstance(model, str) or not model.strip():
186
+ raise ValueError("Seedance model must be explicitly configured")
187
+ references = job.get("references", [])
188
+ if not isinstance(references, list) or len(reference_urls) != len(references):
189
+ raise ValueError("Seedance reference URLs must match job references")
190
+ if Path(job["outputs"][0]).suffix.casefold() != ".mp4":
191
+ raise ValueError("Seedance adapter requires an MP4 target")
192
+ parameters = _take(
193
+ parameters,
194
+ {"duration", "ratio"},
195
+ )
196
+ duration = parameters.get("duration")
197
+ if duration is not None and (
198
+ not isinstance(duration, int)
199
+ or isinstance(duration, bool)
200
+ or not 1 <= duration <= 15
201
+ ):
202
+ raise ValueError("Seedance duration must be an integer from 1 to 15")
203
+ if duration is not None:
204
+ if duration_range is None:
205
+ raise ValueError("Seedance duration needs an explicit model profile")
206
+ minimum, maximum = duration_range
207
+ if not 1 <= minimum <= maximum <= 15 or not minimum <= duration <= maximum:
208
+ raise ValueError("Seedance duration is outside the configured model profile")
209
+ ratio = parameters.get("ratio")
210
+ if ratio is not None and (
211
+ not isinstance(ratio, str) or ratio.strip() not in SEEDANCE_RATIOS
212
+ ):
213
+ raise ValueError("Seedance ratio is outside the supported profile")
214
+ if ratio is not None:
215
+ configured_ratios = set(allowed_ratios or ())
216
+ if not configured_ratios:
217
+ raise ValueError("Seedance ratio needs an explicit model profile")
218
+ if not configured_ratios <= SEEDANCE_RATIOS or ratio.strip() not in configured_ratios:
219
+ raise ValueError("Seedance ratio is outside the configured model profile")
220
+ text = prompt
221
+ if ratio is not None:
222
+ text += f" --ratio {ratio.strip()}"
223
+ if duration is not None:
224
+ text += f" --dur {duration}"
225
+ content: list[dict[str, Any]] = [{"type": "text", "text": text}]
226
+ for index, url in enumerate(reference_urls):
227
+ if not isinstance(url, str) or not url:
228
+ raise ValueError("Seedance reference URL must be non-empty")
229
+ suffix = Path(str(references[index])).suffix.casefold()
230
+ if suffix in {".png", ".jpg", ".jpeg", ".webp"}:
231
+ parsed = urllib.parse.urlparse(url)
232
+ if (
233
+ parsed.scheme == "https" and parsed.netloc
234
+ ) or (
235
+ parsed.scheme == "asset"
236
+ and parsed.netloc.startswith("asset-")
237
+ and not parsed.path
238
+ ):
239
+ pass
240
+ else:
241
+ raise ValueError("Seedance reference URL must be HTTPS or asset://")
242
+ content.append(
243
+ {
244
+ "type": "image_url",
245
+ "image_url": {"url": url},
246
+ "role": "reference_image",
247
+ }
248
+ )
249
+ else:
250
+ raise ValueError("Seedance adapter accepts image references only")
251
+ return {"model": model.strip(), "content": content}
252
+
253
+
254
+ def _seedance_runtime_profile(
255
+ ) -> tuple[frozenset[str] | None, tuple[int, int] | None]:
256
+ raw_ratios = os.environ.get("SEEDANCE_ALLOWED_RATIOS")
257
+ allowed_ratios = (
258
+ frozenset(item.strip() for item in raw_ratios.split(",") if item.strip())
259
+ if raw_ratios is not None
260
+ else None
261
+ )
262
+ minimum_raw = os.environ.get("SEEDANCE_MIN_DURATION")
263
+ maximum_raw = os.environ.get("SEEDANCE_MAX_DURATION")
264
+ if (minimum_raw is None) != (maximum_raw is None):
265
+ raise AdapterFailure(
266
+ "Seedance duration profile is incomplete",
267
+ category="configuration",
268
+ code="invalid_model_profile",
269
+ )
270
+ try:
271
+ duration_range = (
272
+ (int(minimum_raw), int(maximum_raw))
273
+ if minimum_raw is not None and maximum_raw is not None
274
+ else None
275
+ )
276
+ except ValueError as exc:
277
+ raise AdapterFailure(
278
+ "Seedance duration profile is invalid",
279
+ category="configuration",
280
+ code="invalid_model_profile",
281
+ ) from exc
282
+ if allowed_ratios is not None and (
283
+ not allowed_ratios or not allowed_ratios <= SEEDANCE_RATIOS
284
+ ):
285
+ raise AdapterFailure(
286
+ "Seedance ratio profile is invalid",
287
+ category="configuration",
288
+ code="invalid_model_profile",
289
+ )
290
+ if duration_range is not None and not (
291
+ 1 <= duration_range[0] <= duration_range[1] <= 15
292
+ ):
293
+ raise AdapterFailure(
294
+ "Seedance duration profile is invalid",
295
+ category="configuration",
296
+ code="invalid_model_profile",
297
+ )
298
+ return allowed_ratios, duration_range
299
+
300
+
301
+ def compile_gpt_image_2_payload(job: Mapping[str, Any]) -> dict[str, Any]:
302
+ """Compile an image job into GPT Image 2 generation/edit fields."""
303
+ prompt, parameters = _require_job(job, "image")
304
+ parameters = _take(
305
+ parameters,
306
+ {"width", "height", "size", "quality", "background", "moderation"},
307
+ )
308
+ if len(prompt) > 32000:
309
+ raise ValueError("GPT Image prompt exceeds the provider limit")
310
+ width = parameters.pop("width", None)
311
+ height = parameters.pop("height", None)
312
+ if (width is None) != (height is None) or (width is not None and "size" in parameters):
313
+ raise ValueError("use either both width/height or size")
314
+ if width is not None:
315
+ if not isinstance(width, int) or not isinstance(height, int) or width <= 0 or height <= 0:
316
+ raise ValueError("image dimensions must be positive integers")
317
+ if width % 16 or height % 16 or not (1 / 3 <= width / height <= 3):
318
+ raise ValueError("GPT Image dimensions must be divisible by 16 with ratio between 1:3 and 3:1")
319
+ parameters["size"] = f"{width}x{height}"
320
+ size = parameters.get("size")
321
+ if size != "auto" and size is not None:
322
+ if not isinstance(size, str) or "x" not in size:
323
+ raise ValueError("GPT Image size is invalid")
324
+ try:
325
+ parsed_width, parsed_height = (int(part) for part in size.split("x", 1))
326
+ except ValueError as exc:
327
+ raise ValueError("GPT Image size is invalid") from exc
328
+ if (
329
+ parsed_width <= 0
330
+ or parsed_height <= 0
331
+ or parsed_width % 16
332
+ or parsed_height % 16
333
+ or not (1 / 3 <= parsed_width / parsed_height <= 3)
334
+ or parsed_width > 3840
335
+ or parsed_height > 3840
336
+ or not (
337
+ GPT_IMAGE_MIN_PIXELS
338
+ <= parsed_width * parsed_height
339
+ <= GPT_IMAGE_MAX_PIXELS
340
+ )
341
+ ):
342
+ raise ValueError("GPT Image size violates official dimension constraints")
343
+ if parameters.get("background") == "transparent":
344
+ raise ValueError("GPT Image 2 transparent background is unsupported")
345
+ if "background" in parameters and parameters["background"] not in {"auto", "opaque"}:
346
+ raise ValueError("GPT Image background is invalid")
347
+ if "quality" in parameters and parameters["quality"] not in {"auto", "low", "medium", "high"}:
348
+ raise ValueError("GPT Image quality is invalid")
349
+ if "moderation" in parameters and parameters["moderation"] not in {"auto", "low"}:
350
+ raise ValueError("GPT Image moderation is invalid")
351
+ suffix = Path(job["outputs"][0]).suffix.casefold()
352
+ formats = {".png": "png", ".jpg": "jpeg", ".jpeg": "jpeg", ".webp": "webp"}
353
+ if suffix not in formats:
354
+ raise ValueError("unsupported GPT Image output extension")
355
+ references = job.get("references", [])
356
+ if not isinstance(references, list) or len(references) > 16:
357
+ raise ValueError("GPT Image accepts at most sixteen references")
358
+ if any(
359
+ not isinstance(reference, str)
360
+ or Path(reference).suffix.casefold() not in {".png", ".jpg", ".jpeg", ".webp"}
361
+ for reference in references
362
+ ):
363
+ raise ValueError("GPT Image references must be supported image files")
364
+ return {
365
+ "model": OPENAI_MODEL,
366
+ "prompt": prompt,
367
+ "n": 1,
368
+ "output_format": formats[suffix],
369
+ **parameters,
370
+ }
371
+
372
+
373
+ def compile_minimax_music_payload(job: Mapping[str, Any]) -> dict[str, Any]:
374
+ """Compile an audio job into the official MiniMax Music 3.0 JSON body."""
375
+ prompt, parameters = _require_job(job, "music")
376
+ parameters = _take(
377
+ parameters,
378
+ {"lyrics", "sample_rate", "bitrate", "format", "lyrics_optimizer", "is_instrumental"},
379
+ )
380
+ target_format = Path(job["outputs"][0]).suffix.casefold().lstrip(".")
381
+ requested_format = parameters.pop("format", target_format)
382
+ if requested_format != target_format or requested_format not in {"mp3", "wav"}:
383
+ raise ValueError("MiniMax output format must match a supported target extension")
384
+ lyrics = parameters.pop("lyrics", None)
385
+ instrumental = parameters.pop("is_instrumental", False)
386
+ optimizer = parameters.pop("lyrics_optimizer", False)
387
+ if not isinstance(instrumental, bool) or not isinstance(optimizer, bool):
388
+ raise ValueError("MiniMax boolean parameters must be booleans")
389
+ if optimizer:
390
+ raise ValueError("MiniMax lyrics_optimizer is forbidden for confirmed production")
391
+ if instrumental and lyrics not in {None, ""}:
392
+ raise ValueError("MiniMax instrumental music must not carry lyrics")
393
+ if not instrumental and (not isinstance(lyrics, str) or not lyrics.strip()):
394
+ raise ValueError("MiniMax vocal music requires confirmed lyrics")
395
+ if len(prompt) > 2000 or (isinstance(lyrics, str) and len(lyrics) > 3500):
396
+ raise ValueError("MiniMax prompt or lyrics exceeds the provider limit")
397
+ sample_rate = parameters.pop("sample_rate", 44100)
398
+ bitrate = parameters.pop("bitrate", 256000)
399
+ if sample_rate not in {16000, 24000, 32000, 44100}:
400
+ raise ValueError("unsupported MiniMax sample rate")
401
+ if bitrate not in {32000, 64000, 128000, 256000}:
402
+ raise ValueError("unsupported MiniMax bitrate")
403
+ body: dict[str, Any] = {
404
+ "model": MINIMAX_MUSIC_MODEL,
405
+ "prompt": prompt,
406
+ "stream": False,
407
+ "output_format": "hex",
408
+ "audio_setting": {
409
+ "sample_rate": sample_rate,
410
+ "bitrate": bitrate,
411
+ "format": requested_format,
412
+ },
413
+ "lyrics_optimizer": False,
414
+ "is_instrumental": instrumental,
415
+ }
416
+ if lyrics is not None:
417
+ if not isinstance(lyrics, str):
418
+ raise ValueError("MiniMax lyrics must be a string")
419
+ body["lyrics"] = lyrics
420
+ return body
421
+
422
+
423
+ def _base_url(env_name: str, default: str) -> str:
424
+ value = os.environ.get(env_name, default).rstrip("/")
425
+ parsed = urllib.parse.urlparse(value)
426
+ if parsed.scheme != "https" or not parsed.netloc or parsed.username or parsed.password:
427
+ raise AdapterFailure(
428
+ "provider base URL is invalid",
429
+ category="configuration",
430
+ code="invalid_base_url",
431
+ )
432
+ return value
433
+
434
+
435
+ def _credential(name: str) -> str:
436
+ value = os.environ.get(name)
437
+ if not value:
438
+ raise AdapterFailure(
439
+ "provider credential is not configured",
440
+ category="configuration",
441
+ code="missing_credential",
442
+ )
443
+ return value
444
+
445
+
446
+ def _request_json(
447
+ url: str,
448
+ *,
449
+ provider: str,
450
+ method: str = "POST",
451
+ body: Mapping[str, Any] | None = None,
452
+ token: str,
453
+ ) -> tuple[dict[str, Any], Mapping[str, str]]:
454
+ data = None if body is None else json.dumps(body, ensure_ascii=False).encode("utf-8")
455
+ request = urllib.request.Request(url, data=data, method=method)
456
+ request.add_header("Authorization", f"Bearer {token}")
457
+ if body is not None:
458
+ request.add_header("Content-Type", "application/json")
459
+ try:
460
+ with urllib.request.urlopen(request, timeout=180) as response:
461
+ raw = response.read(MAX_JSON_RESPONSE + 1)
462
+ headers = dict(response.headers.items())
463
+ except urllib.error.HTTPError as exc:
464
+ raise _http_failure(provider, exc) from exc
465
+ except TimeoutError as exc:
466
+ raise AdapterFailure(
467
+ f"{provider} HTTP request timed out",
468
+ category="timeout",
469
+ code="request_timeout",
470
+ retryable=True,
471
+ ) from exc
472
+ except (urllib.error.URLError, OSError) as exc:
473
+ raise AdapterFailure(
474
+ f"{provider} HTTP request failed",
475
+ category="network",
476
+ code="network_error",
477
+ retryable=True,
478
+ ) from exc
479
+ if len(raw) > MAX_JSON_RESPONSE:
480
+ raise AdapterFailure(
481
+ f"{provider} response is too large", code="response_too_large"
482
+ )
483
+ try:
484
+ document = json.loads(raw)
485
+ except (UnicodeError, json.JSONDecodeError) as exc:
486
+ raise AdapterFailure(
487
+ f"{provider} returned invalid JSON", code="invalid_json"
488
+ ) from exc
489
+ if not isinstance(document, dict):
490
+ raise AdapterFailure(
491
+ f"{provider} returned an invalid response", code="invalid_response"
492
+ )
493
+ return document, headers
494
+
495
+
496
+ def _read_reference(path: Path) -> bytes:
497
+ before = path.lstat()
498
+ if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode):
499
+ raise ValueError("reference is not a regular file")
500
+ if before.st_size > MAX_REFERENCE_BYTES:
501
+ raise ValueError("reference exceeds the provider input size limit")
502
+ flags = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)
503
+ descriptor = os.open(path, flags)
504
+ try:
505
+ opened = os.fstat(descriptor)
506
+ if (
507
+ not stat.S_ISREG(opened.st_mode)
508
+ or (opened.st_dev, opened.st_ino) != (before.st_dev, before.st_ino)
509
+ ):
510
+ raise ValueError("reference changed while opening")
511
+ chunks: list[bytes] = []
512
+ size = 0
513
+ while chunk := os.read(descriptor, 1024 * 1024):
514
+ size += len(chunk)
515
+ if size > MAX_REFERENCE_BYTES:
516
+ raise ValueError("reference exceeds the provider input size limit")
517
+ chunks.append(chunk)
518
+ return b"".join(chunks)
519
+ finally:
520
+ os.close(descriptor)
521
+
522
+
523
+ def _multipart(fields: Mapping[str, Any], paths: Sequence[Path]) -> tuple[bytes, str]:
524
+ boundary = "short-drama-" + secrets.token_hex(16)
525
+ chunks: list[bytes] = []
526
+ for name, value in fields.items():
527
+ chunks.extend(
528
+ [
529
+ f"--{boundary}\r\n".encode(),
530
+ f'Content-Disposition: form-data; name="{name}"\r\n\r\n'.encode(),
531
+ str(value).lower().encode() if isinstance(value, bool) else str(value).encode("utf-8"),
532
+ b"\r\n",
533
+ ]
534
+ )
535
+ for path in paths:
536
+ content = _read_reference(path)
537
+ media_type = mimetypes.guess_type(path.name)[0] or "application/octet-stream"
538
+ chunks.extend(
539
+ [
540
+ f"--{boundary}\r\n".encode(),
541
+ f'Content-Disposition: form-data; name="image[]"; filename="{path.name}"\r\n'.encode(),
542
+ f"Content-Type: {media_type}\r\n\r\n".encode(),
543
+ content,
544
+ b"\r\n",
545
+ ]
546
+ )
547
+ chunks.append(f"--{boundary}--\r\n".encode())
548
+ encoded = b"".join(chunks)
549
+ if len(encoded) > MAX_MULTIPART_BYTES:
550
+ raise ValueError("multipart provider input exceeds the total size limit")
551
+ return encoded, f"multipart/form-data; boundary={boundary}"
552
+
553
+
554
+ def _reference_paths(job: Mapping[str, Any]) -> list[Path]:
555
+ raw_root = job.get("project_root")
556
+ if not isinstance(raw_root, str) or not Path(raw_root).is_absolute():
557
+ raise ValueError("project_root is invalid")
558
+ root = Path(raw_root).resolve()
559
+ if not root.is_dir():
560
+ raise ValueError("project_root is invalid")
561
+ result: list[Path] = []
562
+ for reference in job.get("references", []):
563
+ if not isinstance(reference, str):
564
+ raise ValueError("reference path is invalid")
565
+ path = (root / reference).resolve()
566
+ try:
567
+ path.relative_to(root)
568
+ except ValueError as exc:
569
+ raise ValueError("reference escapes project root") from exc
570
+ if not path.is_file() or path.is_symlink():
571
+ raise ValueError("reference is not a regular file")
572
+ result.append(path)
573
+ return result
574
+
575
+
576
+ def _validate_media_content(target: str, content: bytes) -> None:
577
+ suffix = Path(target).suffix.casefold()
578
+ signatures = {
579
+ ".png": content.startswith(b"\x89PNG\r\n\x1a\n"),
580
+ ".jpg": content.startswith(b"\xff\xd8\xff"),
581
+ ".jpeg": content.startswith(b"\xff\xd8\xff"),
582
+ ".webp": len(content) >= 12
583
+ and content.startswith(b"RIFF")
584
+ and content[8:12] == b"WEBP",
585
+ ".mp3": content.startswith(b"ID3")
586
+ or (
587
+ len(content) >= 2
588
+ and content[0] == 0xFF
589
+ and content[1] & 0xE0 == 0xE0
590
+ ),
591
+ ".wav": len(content) >= 12
592
+ and content.startswith(b"RIFF")
593
+ and content[8:12] == b"WAVE",
594
+ ".mp4": len(content) >= 12 and content[4:8] == b"ftyp",
595
+ }
596
+ if not signatures.get(suffix, False):
597
+ raise AdapterFailure("provider output does not match the target media type")
598
+
599
+
600
+ def _output_root(job: Mapping[str, Any]) -> Path:
601
+ raw = job.get("output_root")
602
+ if not isinstance(raw, str):
603
+ raise ValueError("output_root is invalid")
604
+ root = Path(raw)
605
+ if not root.is_absolute():
606
+ raise ValueError("output_root is invalid")
607
+ try:
608
+ details = root.lstat()
609
+ except OSError as exc:
610
+ raise ValueError("output_root is missing") from exc
611
+ if stat.S_ISLNK(details.st_mode) or not stat.S_ISDIR(details.st_mode):
612
+ raise ValueError("output_root is unsafe")
613
+ return root
614
+
615
+
616
+ def _temporary_output(
617
+ job: Mapping[str, Any], target: str, content: bytes
618
+ ) -> Path:
619
+ if not content or len(content) > MAX_OUTPUT_BYTES:
620
+ raise AdapterFailure("provider output is too large")
621
+ _validate_media_content(target, content)
622
+ path = _output_root(job) / ("result" + Path(target).suffix.casefold())
623
+ with path.open("xb") as handle:
624
+ handle.write(content)
625
+ return path
626
+
627
+
628
+ def _download(
629
+ job: Mapping[str, Any],
630
+ url: str,
631
+ target: str,
632
+ *,
633
+ provider: str = "seedance",
634
+ ) -> Path:
635
+ parsed = urllib.parse.urlparse(url)
636
+ if parsed.scheme != "https" or not parsed.netloc:
637
+ raise AdapterFailure("provider output URL is invalid")
638
+ path = _output_root(job) / ("result" + Path(target).suffix.casefold())
639
+ size = 0
640
+ try:
641
+ with urllib.request.urlopen(url, timeout=180) as response, path.open("xb") as handle:
642
+ while chunk := response.read(1024 * 1024):
643
+ size += len(chunk)
644
+ if size > MAX_OUTPUT_BYTES:
645
+ raise AdapterFailure("provider output is too large")
646
+ handle.write(chunk)
647
+ except AdapterFailure:
648
+ path.unlink(missing_ok=True)
649
+ raise
650
+ except urllib.error.HTTPError as exc:
651
+ path.unlink(missing_ok=True)
652
+ raise _http_failure(provider, exc) from exc
653
+ except TimeoutError as exc:
654
+ path.unlink(missing_ok=True)
655
+ raise AdapterFailure(
656
+ "provider output download timed out",
657
+ category="timeout",
658
+ code="download_timeout",
659
+ retryable=True,
660
+ ) from exc
661
+ except (urllib.error.URLError, OSError) as exc:
662
+ path.unlink(missing_ok=True)
663
+ raise AdapterFailure(
664
+ "provider output download failed",
665
+ category="network",
666
+ code="download_failed",
667
+ retryable=True,
668
+ ) from exc
669
+ if size == 0:
670
+ raise AdapterFailure("provider output is empty")
671
+ with path.open("rb") as handle:
672
+ _validate_media_content(target, handle.read(16))
673
+ return path
674
+
675
+
676
+ def _run_seedance(job: Mapping[str, Any]) -> tuple[Path, str]:
677
+ token = _credential("ARK_API_KEY")
678
+ model = os.environ.get("SEEDANCE_MODEL", "")
679
+ references = _reference_paths(job)
680
+ if references:
681
+ raise AdapterFailure(
682
+ "Seedance local references require an external trusted HTTPS upload adapter"
683
+ )
684
+ allowed_ratios, duration_range = _seedance_runtime_profile()
685
+ body = compile_seedance_payload(
686
+ job,
687
+ model=model,
688
+ allowed_ratios=allowed_ratios,
689
+ duration_range=duration_range,
690
+ )
691
+ base = _base_url("SEEDANCE_BASE_URL", SEEDANCE_BASE_URL)
692
+ created, _ = _request_json(
693
+ f"{base}/contents/generations/tasks",
694
+ provider="seedance",
695
+ body=body,
696
+ token=token,
697
+ )
698
+ task_id = created.get("id")
699
+ if not isinstance(task_id, str) or not task_id:
700
+ raise AdapterFailure(
701
+ "Seedance did not return a task id", code="missing_task_id"
702
+ )
703
+ try:
704
+ interval = float(os.environ.get("SEEDANCE_POLL_INTERVAL", "5"))
705
+ deadline = time.monotonic() + float(os.environ.get("SEEDANCE_TIMEOUT_SECONDS", "1800"))
706
+ except ValueError as exc:
707
+ raise AdapterFailure("Seedance polling configuration is invalid") from exc
708
+ if interval <= 0 or deadline <= time.monotonic():
709
+ raise AdapterFailure("Seedance polling configuration is invalid")
710
+ while time.monotonic() < deadline:
711
+ status_doc, _ = _request_json(
712
+ f"{base}/contents/generations/tasks/{urllib.parse.quote(task_id, safe='')}",
713
+ provider="seedance",
714
+ method="GET",
715
+ token=token,
716
+ )
717
+ status = status_doc.get("status")
718
+ if status == "succeeded":
719
+ content = status_doc.get("content")
720
+ url = content.get("video_url") if isinstance(content, Mapping) else None
721
+ if not isinstance(url, str):
722
+ raise AdapterFailure(
723
+ "Seedance succeeded without a video URL",
724
+ code="missing_video_url",
725
+ request_id=task_id,
726
+ )
727
+ return _download(
728
+ job,
729
+ url,
730
+ job["outputs"][0],
731
+ provider="seedance",
732
+ ), task_id
733
+ if isinstance(status, str) and status.casefold() in TERMINAL_FAILURES:
734
+ raise AdapterFailure(
735
+ "Seedance task failed",
736
+ code="task_" + status.casefold(),
737
+ request_id=task_id,
738
+ )
739
+ if status not in {"queued", "in_progress", "running", "processing", "pending"}:
740
+ raise AdapterFailure(
741
+ "Seedance returned an unknown task status",
742
+ code="unknown_task_status",
743
+ request_id=task_id,
744
+ )
745
+ time.sleep(interval)
746
+ raise AdapterFailure(
747
+ "Seedance task polling timed out",
748
+ category="timeout",
749
+ code="task_poll_timeout",
750
+ request_id=task_id,
751
+ retryable=True,
752
+ )
753
+
754
+
755
+ def _run_openai(job: Mapping[str, Any]) -> tuple[Path, str | None]:
756
+ token = _credential("OPENAI_API_KEY")
757
+ body = compile_gpt_image_2_payload(job)
758
+ base = _base_url("OPENAI_BASE_URL", OPENAI_BASE_URL)
759
+ references = _reference_paths(job)
760
+ if references:
761
+ encoded, content_type = _multipart(body, references)
762
+ request = urllib.request.Request(f"{base}/images/edits", data=encoded, method="POST")
763
+ request.add_header("Authorization", f"Bearer {token}")
764
+ request.add_header("Content-Type", content_type)
765
+ try:
766
+ with urllib.request.urlopen(request, timeout=300) as response:
767
+ raw = response.read(MAX_JSON_RESPONSE + 1)
768
+ request_id = response.headers.get("x-request-id")
769
+ except urllib.error.HTTPError as exc:
770
+ raise _http_failure("gpt-image-2", exc) from exc
771
+ except TimeoutError as exc:
772
+ raise AdapterFailure(
773
+ "OpenAI HTTP request timed out",
774
+ category="timeout",
775
+ code="request_timeout",
776
+ retryable=True,
777
+ ) from exc
778
+ except (urllib.error.URLError, OSError) as exc:
779
+ raise AdapterFailure(
780
+ "OpenAI HTTP request failed",
781
+ category="network",
782
+ code="network_error",
783
+ retryable=True,
784
+ ) from exc
785
+ if len(raw) > MAX_JSON_RESPONSE:
786
+ raise AdapterFailure(
787
+ "OpenAI response is too large",
788
+ code="response_too_large",
789
+ request_id=request_id,
790
+ )
791
+ try:
792
+ result = json.loads(raw)
793
+ except (UnicodeError, json.JSONDecodeError) as exc:
794
+ raise AdapterFailure(
795
+ "OpenAI returned invalid JSON",
796
+ code="invalid_json",
797
+ request_id=request_id,
798
+ ) from exc
799
+ else:
800
+ result, headers = _request_json(
801
+ f"{base}/images/generations",
802
+ provider="gpt-image-2",
803
+ body=body,
804
+ token=token,
805
+ )
806
+ request_id = headers.get("x-request-id")
807
+ data = result.get("data") if isinstance(result, Mapping) else None
808
+ image = data[0].get("b64_json") if isinstance(data, list) and len(data) == 1 and isinstance(data[0], Mapping) else None
809
+ if not isinstance(image, str):
810
+ raise AdapterFailure(
811
+ "OpenAI did not return exactly one image",
812
+ code="missing_image",
813
+ request_id=request_id,
814
+ )
815
+ try:
816
+ content = base64.b64decode(image, validate=True)
817
+ except (ValueError, binascii.Error) as exc:
818
+ raise AdapterFailure(
819
+ "OpenAI returned invalid image data",
820
+ code="invalid_image_data",
821
+ request_id=request_id,
822
+ ) from exc
823
+ return _temporary_output(job, job["outputs"][0], content), request_id
824
+
825
+
826
+ def _run_minimax(job: Mapping[str, Any]) -> tuple[Path, str | None]:
827
+ token = _credential("MINIMAX_API_KEY")
828
+ body = compile_minimax_music_payload(job)
829
+ base = _base_url("MINIMAX_BASE_URL", MINIMAX_BASE_URL)
830
+ result, _ = _request_json(
831
+ f"{base}/music_generation",
832
+ provider="minimax-music",
833
+ body=body,
834
+ token=token,
835
+ )
836
+ base_resp = result.get("base_resp")
837
+ if not isinstance(base_resp, Mapping) or base_resp.get("status_code") != 0:
838
+ raise AdapterFailure(
839
+ "MiniMax music generation failed",
840
+ code=_provider_code(result) or "generation_failed",
841
+ request_id=_safe_token(result.get("trace_id")),
842
+ )
843
+ data = result.get("data")
844
+ audio = data.get("audio") if isinstance(data, Mapping) else None
845
+ if not isinstance(data, Mapping) or data.get("status") != 2 or not isinstance(audio, str):
846
+ raise AdapterFailure(
847
+ "MiniMax did not return audio data",
848
+ code="missing_audio",
849
+ request_id=_safe_token(result.get("trace_id")),
850
+ )
851
+ try:
852
+ content = bytes.fromhex(audio)
853
+ except ValueError as exc:
854
+ raise AdapterFailure("MiniMax returned invalid audio data") from exc
855
+ if not content:
856
+ raise AdapterFailure("MiniMax returned empty audio data")
857
+ trace_id = result.get("trace_id")
858
+ return (
859
+ _temporary_output(job, job["outputs"][0], content),
860
+ trace_id if isinstance(trace_id, str) else None,
861
+ )
862
+
863
+
864
+ def _selftest() -> None:
865
+ image = {
866
+ "modality": "image", "prompt": "portrait", "references": [],
867
+ "outputs": ["制作成果/a.png"], "parameters": {"width": 1024, "height": 1536},
868
+ }
869
+ video = {
870
+ "modality": "video", "prompt": "slow push in", "references": [],
871
+ "outputs": ["制作成果/a.mp4"], "parameters": {"duration": 5, "ratio": "9:16"},
872
+ }
873
+ music = {
874
+ "modality": "music", "prompt": "cinematic", "references": [],
875
+ "outputs": ["制作成果/a.mp3"], "parameters": {"lyrics": "[Verse]\nHello"},
876
+ }
877
+ if compile_gpt_image_2_payload(image)["model"] != OPENAI_MODEL:
878
+ raise RuntimeError("GPT Image 2 self-test failed")
879
+ seedance = compile_seedance_payload(
880
+ video,
881
+ model="configured-model",
882
+ allowed_ratios={"9:16"},
883
+ duration_range=(5, 10),
884
+ )
885
+ if seedance["model"] != "configured-model":
886
+ raise RuntimeError("Seedance model self-test failed")
887
+ if not seedance["content"][0]["text"].endswith("--ratio 9:16 --dur 5"):
888
+ raise RuntimeError("Seedance prompt switch self-test failed")
889
+ compiled_music = compile_minimax_music_payload(music)
890
+ if compiled_music["model"] != MINIMAX_MUSIC_MODEL:
891
+ raise RuntimeError("MiniMax model self-test failed")
892
+ if compiled_music["output_format"] != "hex":
893
+ raise RuntimeError("MiniMax output self-test failed")
894
+ for invalid_image in (
895
+ {**image, "parameters": {"background": "transparent"}},
896
+ {**image, "parameters": {"input_fidelity": "high"}},
897
+ {**image, "parameters": {"size": "1023x1024"}},
898
+ ):
899
+ try:
900
+ compile_gpt_image_2_payload(invalid_image)
901
+ except ValueError:
902
+ pass
903
+ else:
904
+ raise AssertionError("invalid GPT Image payload was accepted")
905
+ invalid_reference = {**video, "references": ["clip.mp4"]}
906
+ try:
907
+ compile_seedance_payload(
908
+ invalid_reference,
909
+ model="configured-model",
910
+ reference_urls=["asset://asset-example-clip"],
911
+ )
912
+ except ValueError:
913
+ pass
914
+ else:
915
+ raise AssertionError("non-image Seedance reference was accepted")
916
+
917
+
918
+ def main() -> int:
919
+ parser = argparse.ArgumentParser(description=__doc__)
920
+ parser.add_argument("provider", nargs="?", choices=("seedance", "gpt-image-2", "minimax-music"))
921
+ parser.add_argument("--selftest", action="store_true")
922
+ args = parser.parse_args()
923
+ if args.selftest:
924
+ _selftest()
925
+ return 0
926
+ if args.provider is None:
927
+ parser.error("provider is required unless --selftest is used")
928
+ try:
929
+ job = json.load(sys.stdin.buffer)
930
+ if not isinstance(job, Mapping):
931
+ raise ValueError("adapter input must be an object")
932
+ runners = {"seedance": _run_seedance, "gpt-image-2": _run_openai, "minimax-music": _run_minimax}
933
+ path, provider_job_id = runners[args.provider](job)
934
+ response: dict[str, Any] = {
935
+ "outputs": [{"target": job["outputs"][0], "source": str(path)}]
936
+ }
937
+ if provider_job_id:
938
+ response["provider_job_id"] = provider_job_id
939
+ json.dump(response, sys.stdout, ensure_ascii=True)
940
+ return 0
941
+ except AdapterFailure as exc:
942
+ # Provider bodies and credentials are intentionally never reflected.
943
+ json.dump({"error": exc.public(args.provider)}, sys.stdout, ensure_ascii=True)
944
+ print("provider adapter failed safely", file=sys.stderr)
945
+ return 1
946
+ except (ValueError, KeyError, TypeError, json.JSONDecodeError):
947
+ json.dump(
948
+ {
949
+ "error": {
950
+ "provider": args.provider,
951
+ "category": "invalid_request",
952
+ "code": "invalid_job",
953
+ "retryable": False,
954
+ }
955
+ },
956
+ sys.stdout,
957
+ ensure_ascii=True,
958
+ )
959
+ print("provider adapter failed safely", file=sys.stderr)
960
+ return 1
961
+
962
+
963
+ if __name__ == "__main__":
964
+ raise SystemExit(main())