wicked-garden 12.27.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 (640) hide show
  1. package/.claude-plugin/archetypes.json +166 -0
  2. package/.claude-plugin/components.json +75 -0
  3. package/.claude-plugin/marketplace.json +15 -0
  4. package/.claude-plugin/plugin.json +174 -0
  5. package/.claude-plugin/specialist.json +48 -0
  6. package/.claude-plugin/stack-registry.json +96 -0
  7. package/AGENTS.md +13 -0
  8. package/CHANGELOG.md +712 -0
  9. package/CONTRIBUTING.md +99 -0
  10. package/ETHOS.md +83 -0
  11. package/LICENSE +21 -0
  12. package/README.md +115 -0
  13. package/WICKED_GARDEN_BUS_EVENTS.md +149 -0
  14. package/agents/agentic/architect.md +449 -0
  15. package/agents/agentic/performance-analyst.md +702 -0
  16. package/agents/agentic/safety-reviewer.md +662 -0
  17. package/agents/crew/implementer.md +163 -0
  18. package/agents/crew/researcher.md +94 -0
  19. package/agents/crew/reviewer.md +184 -0
  20. package/agents/data/data-engineer.md +227 -0
  21. package/agents/engineering/api-documentarian.md +440 -0
  22. package/agents/engineering/migration-engineer.md +301 -0
  23. package/agents/engineering/solution-architect.md +208 -0
  24. package/agents/jam/brainstorm-facilitator.md +297 -0
  25. package/agents/jam/council.md +322 -0
  26. package/agents/jam/quick-facilitator.md +85 -0
  27. package/agents/persona/persona-agent.md +121 -0
  28. package/agents/platform/compliance-officer.md +266 -0
  29. package/agents/platform/privacy-expert.md +496 -0
  30. package/agents/platform/security-engineer.md +287 -0
  31. package/agents/product/a11y-expert.md +323 -0
  32. package/agents/product/requirements-analyst.md +238 -0
  33. package/agents/product/ui-reviewer.md +180 -0
  34. package/agents/product/ux-designer.md +199 -0
  35. package/agents/product/value-strategist.md +269 -0
  36. package/agents/qe/semantic-reviewer.md +78 -0
  37. package/commands/agentic/audit.md +24 -0
  38. package/commands/agentic/design.md +24 -0
  39. package/commands/agentic/frameworks.md +25 -0
  40. package/commands/agentic/review.md +27 -0
  41. package/commands/archetype/build.md +12 -0
  42. package/commands/archetype/decide.md +12 -0
  43. package/commands/archetype/explore.md +12 -0
  44. package/commands/archetype/incident.md +12 -0
  45. package/commands/archetype/migrate.md +12 -0
  46. package/commands/archetype/modernize.md +12 -0
  47. package/commands/archetype/review.md +14 -0
  48. package/commands/archetype/ship.md +12 -0
  49. package/commands/archetype/specify.md +12 -0
  50. package/commands/archetype/triage.md +12 -0
  51. package/commands/compile.md +37 -0
  52. package/commands/data/analyze.md +18 -0
  53. package/commands/data/data.md +21 -0
  54. package/commands/data/ml.md +18 -0
  55. package/commands/data/ontology.md +22 -0
  56. package/commands/data/pipeline.md +17 -0
  57. package/commands/deliberate.md +181 -0
  58. package/commands/engineering/add-field.md +77 -0
  59. package/commands/engineering/apply.md +58 -0
  60. package/commands/engineering/arch.md +26 -0
  61. package/commands/engineering/debug.md +27 -0
  62. package/commands/engineering/docs.md +27 -0
  63. package/commands/engineering/new-generator.md +257 -0
  64. package/commands/engineering/patch-plan.md +59 -0
  65. package/commands/engineering/plan.md +29 -0
  66. package/commands/engineering/remove.md +48 -0
  67. package/commands/engineering/rename.md +44 -0
  68. package/commands/engineering/review.md +32 -0
  69. package/commands/help.md +128 -0
  70. package/commands/install.md +118 -0
  71. package/commands/intent.md +54 -0
  72. package/commands/jam/brainstorm.md +37 -0
  73. package/commands/jam/council.md +49 -0
  74. package/commands/jam/quick.md +21 -0
  75. package/commands/jam/revisit.md +79 -0
  76. package/commands/persona/as.md +136 -0
  77. package/commands/persona/define.md +24 -0
  78. package/commands/persona/list.md +118 -0
  79. package/commands/platform/actions.md +20 -0
  80. package/commands/platform/assert.md +18 -0
  81. package/commands/platform/audit.md +23 -0
  82. package/commands/platform/compliance.md +20 -0
  83. package/commands/platform/gh.md +22 -0
  84. package/commands/platform/health.md +22 -0
  85. package/commands/platform/incident.md +23 -0
  86. package/commands/platform/infra.md +24 -0
  87. package/commands/platform/peer-health.md +103 -0
  88. package/commands/platform/plugin-health.md +58 -0
  89. package/commands/platform/security.md +38 -0
  90. package/commands/platform/toolchain.md +96 -0
  91. package/commands/platform/traces.md +20 -0
  92. package/commands/product/a11y.md +19 -0
  93. package/commands/product/acceptance.md +19 -0
  94. package/commands/product/align.md +20 -0
  95. package/commands/product/analyze.md +23 -0
  96. package/commands/product/elicit.md +19 -0
  97. package/commands/product/listen.md +24 -0
  98. package/commands/product/mockup.md +20 -0
  99. package/commands/product/screenshot.md +19 -0
  100. package/commands/product/strategy.md +40 -0
  101. package/commands/product/synthesize.md +23 -0
  102. package/commands/product/ux-review.md +42 -0
  103. package/commands/product/ux.md +20 -0
  104. package/commands/product/visual-direction.md +34 -0
  105. package/commands/prove.md +47 -0
  106. package/commands/report-issue.md +243 -0
  107. package/commands/reset.md +144 -0
  108. package/commands/search/blast-radius.md +58 -0
  109. package/commands/search/hotspots.md +55 -0
  110. package/commands/search/index.md +38 -0
  111. package/commands/search/lineage.md +47 -0
  112. package/commands/search/service-map.md +79 -0
  113. package/commands/setup.md +363 -0
  114. package/commands/smaht/briefing.md +200 -0
  115. package/commands/smaht/events-import.md +95 -0
  116. package/commands/smaht/state.md +19 -0
  117. package/commands/where-am-i.md +58 -0
  118. package/docs/README.md +32 -0
  119. package/docs/adr/0001-code-relationship-graph-engine.md +116 -0
  120. package/docs/adr/0002-major-cleanup-keep-cut.md +91 -0
  121. package/docs/adr/0003-peers-gate-required-layers-optin.md +41 -0
  122. package/docs/adr/0004-code-graph-moves-to-wicked-brain.md +47 -0
  123. package/docs/brain-chunk-format.md +135 -0
  124. package/docs/compiler.md +95 -0
  125. package/docs/daemon-api.md +70 -0
  126. package/docs/domains.md +197 -0
  127. package/docs/getting-started.md +208 -0
  128. package/docs/plans/2026-06-08-wicked-loom-conduct.md +1337 -0
  129. package/docs/plans/2026-06-08-wicked-loom-contract.md +638 -0
  130. package/docs/plans/2026-06-08-wicked-loom-cutover.md +1300 -0
  131. package/docs/plans/2026-06-08-wicked-loom-expand-compose.md +843 -0
  132. package/docs/plans/2026-06-08-wicked-loom-flow-cutover.md +403 -0
  133. package/docs/plans/2026-06-10-codegraph-to-brain-phase1a-graph-core.md +838 -0
  134. package/docs/plans/2026-06-10-codegraph-to-brain-phase1b-injected-edges.md +44 -0
  135. package/docs/required-peers.md +60 -0
  136. package/docs/specs/2026-06-08-wicked-loom-extraction.md +225 -0
  137. package/docs/specs/2026-06-10-codegraph-to-brain-migration.md +168 -0
  138. package/docs/v11/archetypes.md +231 -0
  139. package/hooks/hooks.json +275 -0
  140. package/hooks/scripts/bootstrap.py +1797 -0
  141. package/hooks/scripts/invoke.py +62 -0
  142. package/hooks/scripts/notification.py +166 -0
  143. package/hooks/scripts/permission_request.py +223 -0
  144. package/hooks/scripts/post_tool.py +1374 -0
  145. package/hooks/scripts/pre_compact.py +272 -0
  146. package/hooks/scripts/pre_tool.py +1278 -0
  147. package/hooks/scripts/prompt_submit.py +1109 -0
  148. package/hooks/scripts/session_end.py +95 -0
  149. package/hooks/scripts/stop.py +624 -0
  150. package/hooks/scripts/subagent_lifecycle.py +464 -0
  151. package/hooks/scripts/subscribers/on_gate_decided.py +84 -0
  152. package/hooks/scripts/task_completed.py +322 -0
  153. package/hooks/scripts/worktree_guard.py +148 -0
  154. package/hooks/subscriptions/on_gate_decided.json +9 -0
  155. package/install.mjs +128 -0
  156. package/package.json +41 -0
  157. package/pyproject.toml +59 -0
  158. package/schemas/README.md +32 -0
  159. package/schemas/assert_contracts.json +37 -0
  160. package/schemas/health_probe.json +46 -0
  161. package/schemas/plugin_status.json +37 -0
  162. package/scripts/_adapters/__init__.py +166 -0
  163. package/scripts/_adapters/jira_adapter.py +18 -0
  164. package/scripts/_adapters/linear_adapter.py +18 -0
  165. package/scripts/_adapters/miro_adapter.py +18 -0
  166. package/scripts/_adapters/notion_adapter.py +18 -0
  167. package/scripts/_agents.py +357 -0
  168. package/scripts/_brain_ingest/__init__.py +2 -0
  169. package/scripts/_brain_ingest/session_fact_extractor.py +341 -0
  170. package/scripts/_brain_port.py +163 -0
  171. package/scripts/_bus.py +1081 -0
  172. package/scripts/_bus_catalog_gen.py +93 -0
  173. package/scripts/_bus_consumers.json +46 -0
  174. package/scripts/_bus_emit.py +43 -0
  175. package/scripts/_capability_registry.py +334 -0
  176. package/scripts/_capability_resolver.py +322 -0
  177. package/scripts/_domain_store.py +559 -0
  178. package/scripts/_event_log_reader.py +154 -0
  179. package/scripts/_event_schema.py +305 -0
  180. package/scripts/_event_store.py +397 -0
  181. package/scripts/_heavy_cadence.py +324 -0
  182. package/scripts/_integration_resolver.py +321 -0
  183. package/scripts/_logger.py +151 -0
  184. package/scripts/_loom.py +482 -0
  185. package/scripts/_paths.py +133 -0
  186. package/scripts/_python.sh +22 -0
  187. package/scripts/_run.py +137 -0
  188. package/scripts/_session.py +502 -0
  189. package/scripts/_validate_registry.py +1042 -0
  190. package/scripts/_wicked_testing_probe.py +277 -0
  191. package/scripts/agentic/analyze_agents.py +391 -0
  192. package/scripts/agentic/detect_framework.py +420 -0
  193. package/scripts/agentic/issue_taxonomy.py +357 -0
  194. package/scripts/agentic/pattern_scorer.py +434 -0
  195. package/scripts/classify/persist.py +169 -0
  196. package/scripts/compiler/compile.py +552 -0
  197. package/scripts/compiler/phase0/VERDICT.md +60 -0
  198. package/scripts/compiler/phase0/detect.py +292 -0
  199. package/scripts/compiler/phase0/emit.py +228 -0
  200. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.bindings.json +30 -0
  201. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.lint-output.txt +17 -0
  202. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/.wg/evidence/failing-tests.txt +4 -0
  203. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/.wg/evidence/honest-tests.txt +2 -0
  204. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/fab-fail.build.md +8 -0
  205. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/fab-missing.build.md +8 -0
  206. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/honest.build.md +8 -0
  207. package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/wg_check_claims.py +174 -0
  208. package/scripts/compiler/phase0/evidence/command_iq.bindings.json +45 -0
  209. package/scripts/compiler/phase0/evidence/command_iq.lint-output.txt +17 -0
  210. package/scripts/compiler/phase0/evidence/command_iq.stage/.projects/evidence-domain/phase-1-clarify/evidence/failing-tests.txt +4 -0
  211. package/scripts/compiler/phase0/evidence/command_iq.stage/.projects/evidence-domain/phase-1-clarify/evidence/honest-tests.txt +3 -0
  212. package/scripts/compiler/phase0/evidence/command_iq.stage/fab-fail.build.md +8 -0
  213. package/scripts/compiler/phase0/evidence/command_iq.stage/fab-missing.build.md +8 -0
  214. package/scripts/compiler/phase0/evidence/command_iq.stage/honest.build.md +8 -0
  215. package/scripts/compiler/phase0/evidence/command_iq.stage/wg_check_claims.py +174 -0
  216. package/scripts/compiler/phase0/evidence/memos.bindings.json +28 -0
  217. package/scripts/compiler/phase0/evidence/memos.lint-output.txt +17 -0
  218. package/scripts/compiler/phase0/evidence/memos.stage/.wg/evidence/failing-tests.txt +4 -0
  219. package/scripts/compiler/phase0/evidence/memos.stage/.wg/evidence/honest-tests.txt +2 -0
  220. package/scripts/compiler/phase0/evidence/memos.stage/fab-fail.build.md +8 -0
  221. package/scripts/compiler/phase0/evidence/memos.stage/fab-missing.build.md +8 -0
  222. package/scripts/compiler/phase0/evidence/memos.stage/honest.build.md +8 -0
  223. package/scripts/compiler/phase0/evidence/memos.stage/wg_check_claims.py +174 -0
  224. package/scripts/compiler/phase0/evidence/verdict.json +104 -0
  225. package/scripts/compiler/phase0/evidence/wicked-bus.bindings.json +29 -0
  226. package/scripts/compiler/phase0/evidence/wicked-bus.lint-output.txt +17 -0
  227. package/scripts/compiler/phase0/evidence/wicked-bus.stage/coverage/failing-tests.txt +4 -0
  228. package/scripts/compiler/phase0/evidence/wicked-bus.stage/coverage/honest-tests.txt +3 -0
  229. package/scripts/compiler/phase0/evidence/wicked-bus.stage/fab-fail.build.md +8 -0
  230. package/scripts/compiler/phase0/evidence/wicked-bus.stage/fab-missing.build.md +8 -0
  231. package/scripts/compiler/phase0/evidence/wicked-bus.stage/honest.build.md +8 -0
  232. package/scripts/compiler/phase0/evidence/wicked-bus.stage/wg_check_claims.py +174 -0
  233. package/scripts/compiler/phase0/evidence/wire-vault-transcript.txt +60 -0
  234. package/scripts/compiler/phase0/run_probe.py +234 -0
  235. package/scripts/compiler/phase0/wire_vault.py +310 -0
  236. package/scripts/crew/__init__.py +0 -0
  237. package/scripts/crew/_task_audit_writer.py +243 -0
  238. package/scripts/crew/_task_reader.py +389 -0
  239. package/scripts/crew/archetypes_v11.py +348 -0
  240. package/scripts/crew/detectors/__init__.py +12 -0
  241. package/scripts/crew/flow_compiler.py +121 -0
  242. package/scripts/crew/phase_manager.py +703 -0
  243. package/scripts/crew/scope_delta.py +264 -0
  244. package/scripts/crew/specialist_resolver.py +218 -0
  245. package/scripts/crew/stack_registry.py +189 -0
  246. package/scripts/data/data_profiler.py +271 -0
  247. package/scripts/data/ontology_recommender.py +231 -0
  248. package/scripts/data/schema_validator.py +267 -0
  249. package/scripts/delivery/drift.py +739 -0
  250. package/scripts/delivery/telemetry.py +614 -0
  251. package/scripts/engineering/patch/codegraph_db.py +125 -0
  252. package/scripts/engineering/patch/generators/__init__.py +56 -0
  253. package/scripts/engineering/patch/generators/base.py +373 -0
  254. package/scripts/engineering/patch/generators/csharp_generator.py +494 -0
  255. package/scripts/engineering/patch/generators/go_generator.py +430 -0
  256. package/scripts/engineering/patch/generators/java_generator.py +928 -0
  257. package/scripts/engineering/patch/generators/jsp_generator.py +454 -0
  258. package/scripts/engineering/patch/generators/kotlin_generator.py +561 -0
  259. package/scripts/engineering/patch/generators/perl_generator.py +510 -0
  260. package/scripts/engineering/patch/generators/php_generator.py +644 -0
  261. package/scripts/engineering/patch/generators/propagation_engine.py +929 -0
  262. package/scripts/engineering/patch/generators/python_generator.py +487 -0
  263. package/scripts/engineering/patch/generators/ruby_generator.py +463 -0
  264. package/scripts/engineering/patch/generators/rust_generator.py +400 -0
  265. package/scripts/engineering/patch/generators/sql_generator.py +594 -0
  266. package/scripts/engineering/patch/generators/typescript_generator.py +439 -0
  267. package/scripts/engineering/patch/patch.py +800 -0
  268. package/scripts/engineering/patch/safety.py +429 -0
  269. package/scripts/jam/__init__.py +0 -0
  270. package/scripts/jam/_bus_consumers.py +271 -0
  271. package/scripts/jam/agentic_cli_registry.py +703 -0
  272. package/scripts/jam/consensus.py +737 -0
  273. package/scripts/jam/detect_clis.py +409 -0
  274. package/scripts/jam/jam.py +283 -0
  275. package/scripts/jam/save_transcript.py +171 -0
  276. package/scripts/loom/__init__.py +22 -0
  277. package/scripts/loom/cli.py +170 -0
  278. package/scripts/loom/compose.py +180 -0
  279. package/scripts/loom/gate.py +120 -0
  280. package/scripts/loom/manifest.py +140 -0
  281. package/scripts/loom/resolve.py +59 -0
  282. package/scripts/persona/__init__.py +0 -0
  283. package/scripts/persona/registry.py +688 -0
  284. package/scripts/platform/guard_pipeline.py +1214 -0
  285. package/scripts/platform/guard_profiles.py +216 -0
  286. package/scripts/platform/observability/assert_contracts.py +434 -0
  287. package/scripts/platform/observability/health_probe.py +915 -0
  288. package/scripts/platform/observability/ops_log_viewer.py +209 -0
  289. package/scripts/platform/prereq_doctor.py +567 -0
  290. package/scripts/qe/conditions_manifest.py +260 -0
  291. package/scripts/qe/content_sanitizer.py +160 -0
  292. package/scripts/qe/coverage_tracker.py +535 -0
  293. package/scripts/qe/discover_scenarios.py +243 -0
  294. package/scripts/qe/evidence_tracker.py +232 -0
  295. package/scripts/qe/prove.py +196 -0
  296. package/scripts/qe/registry_lookup.py +80 -0
  297. package/scripts/qe/registry_store.py +176 -0
  298. package/scripts/qe/semantic_review.py +1005 -0
  299. package/scripts/qe/vault_gate.py +382 -0
  300. package/scripts/qe/verdict_audit.py +175 -0
  301. package/scripts/qe/verdict_schema.py +335 -0
  302. package/scripts/reset.py +220 -0
  303. package/scripts/resolve_path.py +22 -0
  304. package/scripts/sentinel/invariants.py +402 -0
  305. package/scripts/sentinel/pre_push.py +73 -0
  306. package/scripts/setup/__init__.py +1 -0
  307. package/scripts/setup/detect_state.py +114 -0
  308. package/scripts/setup/migrate_legacy.py +67 -0
  309. package/scripts/setup/migrate_v6_projects.py +232 -0
  310. package/scripts/setup/onboarding.py +114 -0
  311. package/scripts/smaht/_bus_consumers.py +98 -0
  312. package/scripts/smaht/adapters/__init__.py +83 -0
  313. package/scripts/smaht/adapters/brain_adapter.py +254 -0
  314. package/scripts/smaht/adapters/context7_adapter.py +438 -0
  315. package/scripts/smaht/adapters/delegation_adapter.py +104 -0
  316. package/scripts/smaht/adapters/domain_adapter.py +206 -0
  317. package/scripts/smaht/adapters/events_adapter.py +231 -0
  318. package/scripts/smaht/adapters/tools_adapter.py +49 -0
  319. package/scripts/smaht/cheatsheet_store.py +236 -0
  320. package/scripts/smaht/context_package.py +386 -0
  321. package/scripts/smaht/knowledge_graph.py +395 -0
  322. package/scripts/smaht/propose_skills.py +856 -0
  323. package/scripts/smaht/test_cheatsheet_store.py +428 -0
  324. package/scripts/statusline.py +151 -0
  325. package/scripts/where_am_i.py +236 -0
  326. package/skills/agentic/agentic-patterns/SKILL.md +91 -0
  327. package/skills/agentic/agentic-patterns/refs/anti-patterns-design.md +283 -0
  328. package/skills/agentic/agentic-patterns/refs/anti-patterns-operational.md +216 -0
  329. package/skills/agentic/agentic-patterns/refs/pattern-catalog.md +329 -0
  330. package/skills/agentic/context-engineering/SKILL.md +142 -0
  331. package/skills/agentic/context-engineering/refs/caching-and-optimization.md +251 -0
  332. package/skills/agentic/context-engineering/refs/compression-techniques.md +171 -0
  333. package/skills/agentic/context-engineering/refs/cost-calculation-budget.md +250 -0
  334. package/skills/agentic/context-engineering/refs/cost-optimization-reporting.md +300 -0
  335. package/skills/agentic/context-engineering/refs/selective-loading.md +179 -0
  336. package/skills/agentic/frameworks/SKILL.md +191 -0
  337. package/skills/agentic/frameworks/refs/framework-profiles-1.md +209 -0
  338. package/skills/agentic/frameworks/refs/framework-profiles-2.md +214 -0
  339. package/skills/agentic/frameworks/refs/migration-patterns-paths.md +267 -0
  340. package/skills/agentic/frameworks/refs/migration-patterns-testing.md +208 -0
  341. package/skills/agentic/refs/audit.md +134 -0
  342. package/skills/agentic/refs/design.md +137 -0
  343. package/skills/agentic/refs/frameworks.md +102 -0
  344. package/skills/agentic/refs/review.md +160 -0
  345. package/skills/agentic/review-methodology/SKILL.md +186 -0
  346. package/skills/agentic/review-methodology/refs/deliverable-templates.md +246 -0
  347. package/skills/agentic/review-methodology/refs/issue-taxonomy-quality-testing.md +277 -0
  348. package/skills/agentic/review-methodology/refs/issue-taxonomy-reliability-safety.md +253 -0
  349. package/skills/agentic/trust-and-safety/SKILL.md +177 -0
  350. package/skills/agentic/trust-and-safety/refs/guardrails-actions.md +246 -0
  351. package/skills/agentic/trust-and-safety/refs/guardrails-input-output.md +238 -0
  352. package/skills/agentic/trust-and-safety/refs/guardrails-resources.md +209 -0
  353. package/skills/agentic/trust-and-safety/refs/safety-checklist-advanced.md +209 -0
  354. package/skills/agentic/trust-and-safety/refs/safety-checklist-core.md +215 -0
  355. package/skills/archetype/SKILL.md +104 -0
  356. package/skills/archetype/refs/build.md +142 -0
  357. package/skills/archetype/refs/decide.md +117 -0
  358. package/skills/archetype/refs/explore.md +66 -0
  359. package/skills/archetype/refs/incident.md +149 -0
  360. package/skills/archetype/refs/migrate.md +170 -0
  361. package/skills/archetype/refs/modernize.md +187 -0
  362. package/skills/archetype/refs/review.md +145 -0
  363. package/skills/archetype/refs/ship.md +117 -0
  364. package/skills/archetype/refs/specify.md +108 -0
  365. package/skills/archetype/refs/triage.md +62 -0
  366. package/skills/classify/SKILL.md +157 -0
  367. package/skills/data/analysis/SKILL.md +125 -0
  368. package/skills/data/analysis/refs/templates.md +170 -0
  369. package/skills/data/data/SKILL.md +118 -0
  370. package/skills/data/data/refs/examples.md +235 -0
  371. package/skills/data/ml/SKILL.md +125 -0
  372. package/skills/data/ml/refs/templates.md +172 -0
  373. package/skills/data/pipeline/SKILL.md +136 -0
  374. package/skills/data/pipeline/refs/templates.md +175 -0
  375. package/skills/data/refs/analyze.md +123 -0
  376. package/skills/data/refs/data.md +95 -0
  377. package/skills/data/refs/ml.md +118 -0
  378. package/skills/data/refs/pipeline.md +92 -0
  379. package/skills/deliberate/SKILL.md +125 -0
  380. package/skills/deliberate/refs/architecture-lens.md +37 -0
  381. package/skills/deliberate/refs/assumptions-lens.md +43 -0
  382. package/skills/deliberate/refs/code-lens.md +38 -0
  383. package/skills/deliberate/refs/content-lens.md +37 -0
  384. package/skills/deliberate/refs/data-lens.md +43 -0
  385. package/skills/deliberate/refs/design-lens.md +37 -0
  386. package/skills/deliberate/refs/opportunity-patterns.md +107 -0
  387. package/skills/deliberate/refs/ops-lens.md +43 -0
  388. package/skills/deliberate/refs/product-lens.md +46 -0
  389. package/skills/deliberate/refs/rethink-framework.md +77 -0
  390. package/skills/deliberate/refs/security-lens.md +47 -0
  391. package/skills/deliberate/refs/testing-lens.md +45 -0
  392. package/skills/deliberate/refs/timing-lens.md +42 -0
  393. package/skills/engineering/architecture/SKILL.md +177 -0
  394. package/skills/engineering/architecture/refs/adr-template.md +268 -0
  395. package/skills/engineering/architecture/refs/architecture-template-deployment.md +176 -0
  396. package/skills/engineering/architecture/refs/architecture-template-design.md +300 -0
  397. package/skills/engineering/architecture/refs/examples-ecommerce-analytics.md +299 -0
  398. package/skills/engineering/architecture/refs/examples-saas-trading.md +291 -0
  399. package/skills/engineering/architecture/refs/patterns-advanced.md +238 -0
  400. package/skills/engineering/architecture/refs/patterns-structural.md +303 -0
  401. package/skills/engineering/backend/SKILL.md +102 -0
  402. package/skills/engineering/backend/refs/checklists.md +146 -0
  403. package/skills/engineering/backend/refs/patterns.md +181 -0
  404. package/skills/engineering/debugging/SKILL.md +99 -0
  405. package/skills/engineering/debugging/refs/patterns.md +248 -0
  406. package/skills/engineering/debugging/refs/process.md +203 -0
  407. package/skills/engineering/docs/SKILL.md +97 -0
  408. package/skills/engineering/engineering/SKILL.md +88 -0
  409. package/skills/engineering/engineering/refs/checklists.md +91 -0
  410. package/skills/engineering/engineering/refs/templates.md +71 -0
  411. package/skills/engineering/frontend/SKILL.md +92 -0
  412. package/skills/engineering/frontend/refs/checklists.md +96 -0
  413. package/skills/engineering/frontend/refs/patterns.md +167 -0
  414. package/skills/engineering/integration/SKILL.md +183 -0
  415. package/skills/engineering/integration/refs/error-handling-best-practices.md +139 -0
  416. package/skills/engineering/integration/refs/error-handling-error-classes-typescript.md +212 -0
  417. package/skills/engineering/integration/refs/error-handling-error-response-format.md +301 -0
  418. package/skills/engineering/integration/refs/error-handling-retry-strategies.md +190 -0
  419. package/skills/engineering/integration/refs/event-schemas-best-practices.md +225 -0
  420. package/skills/engineering/integration/refs/event-schemas-event-naming-conventions.md +303 -0
  421. package/skills/engineering/integration/refs/event-schemas-event-structure.md +288 -0
  422. package/skills/engineering/integration/refs/graphql-best-practices.md +138 -0
  423. package/skills/engineering/integration/refs/graphql-error-handling.md +198 -0
  424. package/skills/engineering/integration/refs/graphql-graphql-basics.md +197 -0
  425. package/skills/engineering/integration/refs/graphql-query-examples.md +226 -0
  426. package/skills/engineering/integration/refs/graphql-schema-design-patterns.md +280 -0
  427. package/skills/engineering/integration/refs/grpc-grpc-overview.md +246 -0
  428. package/skills/engineering/integration/refs/grpc-implementation-examples.md +269 -0
  429. package/skills/engineering/integration/refs/grpc-metadata-headers.md +304 -0
  430. package/skills/engineering/integration/refs/grpc-testing.md +49 -0
  431. package/skills/engineering/integration/refs/openapi-template.yaml +682 -0
  432. package/skills/engineering/integration/refs/resilience-best-practices.md +109 -0
  433. package/skills/engineering/integration/refs/resilience-circuit-breaker-pattern.md +302 -0
  434. package/skills/engineering/integration/refs/resilience-rate-limiting.md +245 -0
  435. package/skills/engineering/integration/refs/resilience-timeout-pattern.md +212 -0
  436. package/skills/engineering/integration/refs/rest-api-fundamentals.md +299 -0
  437. package/skills/engineering/integration/refs/rest-api-responses-security.md +302 -0
  438. package/skills/engineering/integration/refs/versioning-breaking-changes.md +237 -0
  439. package/skills/engineering/integration/refs/versioning-migration-strategies.md +257 -0
  440. package/skills/engineering/integration/refs/versioning-versioning-strategies.md +296 -0
  441. package/skills/engineering/large-scale-migration/SKILL.md +65 -0
  442. package/skills/engineering/patch/SKILL.md +184 -0
  443. package/skills/engineering/refs/arch.md +126 -0
  444. package/skills/engineering/refs/debug.md +142 -0
  445. package/skills/engineering/refs/docs.md +190 -0
  446. package/skills/engineering/refs/plan.md +109 -0
  447. package/skills/engineering/refs/review.md +138 -0
  448. package/skills/engineering/system-design/SKILL.md +154 -0
  449. package/skills/engineering/system-design/refs/anti-patterns-big-ball-of-mud.md +254 -0
  450. package/skills/engineering/system-design/refs/anti-patterns-circular-dependencies.md +241 -0
  451. package/skills/engineering/system-design/refs/anti-patterns-feature-envy.md +168 -0
  452. package/skills/engineering/system-design/refs/anti-patterns-god-object.md +247 -0
  453. package/skills/engineering/system-design/refs/component-template-lifecycle.md +210 -0
  454. package/skills/engineering/system-design/refs/component-template-maintenance.md +219 -0
  455. package/skills/engineering/system-design/refs/component-template-structure.md +276 -0
  456. package/skills/engineering/system-design/refs/dependencies-best-practices.md +260 -0
  457. package/skills/engineering/system-design/refs/dependencies-dependency-principles.md +295 -0
  458. package/skills/engineering/system-design/refs/dependencies-dependency-tracking.md +261 -0
  459. package/skills/engineering/system-design/refs/interface-template-lifecycle.md +211 -0
  460. package/skills/engineering/system-design/refs/interface-template-maintenance.md +212 -0
  461. package/skills/engineering/system-design/refs/interface-template-structure.md +269 -0
  462. package/skills/engineering/system-design/refs/patterns-dependency-injection.md +151 -0
  463. package/skills/engineering/system-design/refs/patterns-facade-pattern.md +257 -0
  464. package/skills/engineering/system-design/refs/patterns-layered-pattern.md +204 -0
  465. package/skills/engineering/system-design/refs/patterns-plugin-architecture.md +229 -0
  466. package/skills/engineering/unit-test-quality/SKILL.md +194 -0
  467. package/skills/ground/SKILL.md +130 -0
  468. package/skills/integration-discovery/SKILL.md +168 -0
  469. package/skills/integration-discovery/refs/capabilities.md +282 -0
  470. package/skills/integration-discovery/refs/cli-detection.md +295 -0
  471. package/skills/integration-discovery/refs/discovery-sources.md +233 -0
  472. package/skills/integration-discovery/refs/task-patterns.md +206 -0
  473. package/skills/jam/SKILL.md +45 -0
  474. package/skills/jam/refs/facilitation-patterns.md +125 -0
  475. package/skills/jam/refs/quick.md +64 -0
  476. package/skills/jam/refs/synthesis-patterns.md +114 -0
  477. package/skills/multi-model/SKILL.md +168 -0
  478. package/skills/multi-model/refs/aichat.md +119 -0
  479. package/skills/multi-model/refs/aider.md +100 -0
  480. package/skills/multi-model/refs/auditability.md +238 -0
  481. package/skills/multi-model/refs/codex.md +169 -0
  482. package/skills/multi-model/refs/context.md +306 -0
  483. package/skills/multi-model/refs/copilot.md +161 -0
  484. package/skills/multi-model/refs/examples.md +224 -0
  485. package/skills/multi-model/refs/gemini.md +185 -0
  486. package/skills/multi-model/refs/goose.md +112 -0
  487. package/skills/multi-model/refs/llm.md +124 -0
  488. package/skills/multi-model/refs/opencode.md +196 -0
  489. package/skills/multi-model/refs/orchestration.md +188 -0
  490. package/skills/multi-model/refs/pi.md +208 -0
  491. package/skills/persona/SKILL.md +144 -0
  492. package/skills/persona/refs/define.md +133 -0
  493. package/skills/platform/actions/refs/actions.md +114 -0
  494. package/skills/platform/audit/SKILL.md +174 -0
  495. package/skills/platform/audit/refs/audit.md +145 -0
  496. package/skills/platform/audit/refs/checklists-evidence-operations.md +144 -0
  497. package/skills/platform/audit/refs/checklists-gdpr-pci-evidence.md +213 -0
  498. package/skills/platform/audit/refs/checklists-soc2-hipaa.md +120 -0
  499. package/skills/platform/audit/refs/frameworks-gdpr-pci.md +225 -0
  500. package/skills/platform/audit/refs/frameworks-soc2-hipaa.md +228 -0
  501. package/skills/platform/compliance/SKILL.md +169 -0
  502. package/skills/platform/compliance/refs/checklists.md +303 -0
  503. package/skills/platform/compliance/refs/compliance.md +117 -0
  504. package/skills/platform/compliance/refs/frameworks.md +282 -0
  505. package/skills/platform/errors/SKILL.md +183 -0
  506. package/skills/platform/errors/refs/patterns.md +271 -0
  507. package/skills/platform/errors/refs/severity.md +264 -0
  508. package/skills/platform/gate-benchmark-rebaseline/SKILL.md +108 -0
  509. package/skills/platform/gh/refs/gh.md +105 -0
  510. package/skills/platform/gh-cli/SKILL.md +120 -0
  511. package/skills/platform/gh-cli/refs/automation.md +63 -0
  512. package/skills/platform/gh-cli/refs/gh-ops.md +103 -0
  513. package/skills/platform/gh-cli/refs/patterns.md +132 -0
  514. package/skills/platform/gh-cli/scripts/gh_ops.py +454 -0
  515. package/skills/platform/github-actions/SKILL.md +159 -0
  516. package/skills/platform/github-actions/refs/security.md +150 -0
  517. package/skills/platform/github-actions/refs/templates.md +230 -0
  518. package/skills/platform/github-actions/refs/troubleshooting.md +174 -0
  519. package/skills/platform/github-actions/scripts/workflow_gen.py +215 -0
  520. package/skills/platform/gitlab-ci/SKILL.md +140 -0
  521. package/skills/platform/gitlab-ci/refs/templates.md +223 -0
  522. package/skills/platform/gitlab-ci/refs/troubleshooting.md +213 -0
  523. package/skills/platform/glab-cli/SKILL.md +133 -0
  524. package/skills/platform/glab-cli/refs/ci-debugging.md +95 -0
  525. package/skills/platform/glab-cli/refs/glab-ops.md +78 -0
  526. package/skills/platform/glab-cli/refs/patterns.md +131 -0
  527. package/skills/platform/glab-cli/scripts/glab_ops.py +313 -0
  528. package/skills/platform/health/SKILL.md +177 -0
  529. package/skills/platform/health/refs/health.md +72 -0
  530. package/skills/platform/health/refs/sources.md +302 -0
  531. package/skills/platform/incident/refs/incident.md +136 -0
  532. package/skills/platform/infra/refs/infra.md +104 -0
  533. package/skills/platform/observability/SKILL.md +128 -0
  534. package/skills/platform/observability/refs/toolchain-discovery.md +243 -0
  535. package/skills/platform/prereq-doctor/SKILL.md +57 -0
  536. package/skills/platform/prereq-doctor/refs/tool-registry.md +58 -0
  537. package/skills/platform/traces/refs/traces.md +101 -0
  538. package/skills/product/acceptance-criteria/SKILL.md +193 -0
  539. package/skills/product/acceptance-criteria/refs/ac-anti-patterns.md +291 -0
  540. package/skills/product/acceptance-criteria/refs/ac-examples.md +277 -0
  541. package/skills/product/accessibility/SKILL.md +197 -0
  542. package/skills/product/accessibility/refs/aria-patterns-basics.md +250 -0
  543. package/skills/product/accessibility/refs/aria-patterns-dynamic.md +187 -0
  544. package/skills/product/accessibility/refs/aria-patterns-interactive.md +246 -0
  545. package/skills/product/accessibility/refs/audit-report-findings.md +248 -0
  546. package/skills/product/accessibility/refs/audit-report-results.md +276 -0
  547. package/skills/product/accessibility/refs/common-violations-html-css.md +270 -0
  548. package/skills/product/accessibility/refs/common-violations-interactive.md +303 -0
  549. package/skills/product/accessibility/refs/keyboard-testing-basics.md +222 -0
  550. package/skills/product/accessibility/refs/keyboard-testing-patterns.md +173 -0
  551. package/skills/product/accessibility/refs/screen-reader-testing-advanced.md +294 -0
  552. package/skills/product/accessibility/refs/screen-reader-testing-commands.md +255 -0
  553. package/skills/product/accessibility/refs/wcag-checklist.md +262 -0
  554. package/skills/product/analyze/SKILL.md +164 -0
  555. package/skills/product/analyze/refs/algorithms.md +238 -0
  556. package/skills/product/analyze/refs/sentiment-patterns.md +180 -0
  557. package/skills/product/imagery/SKILL.md +97 -0
  558. package/skills/product/imagery/alter/SKILL.md +105 -0
  559. package/skills/product/imagery/alter/refs/editing-reference.md +146 -0
  560. package/skills/product/imagery/create/SKILL.md +109 -0
  561. package/skills/product/imagery/create/refs/prompt-engineering.md +112 -0
  562. package/skills/product/imagery/create/refs/provider-reference.md +219 -0
  563. package/skills/product/imagery/review/SKILL.md +101 -0
  564. package/skills/product/imagery/review/refs/analysis-lenses.md +136 -0
  565. package/skills/product/imagery/review/refs/quality-gates.md +153 -0
  566. package/skills/product/imagery/scripts/analyze.sh +23 -0
  567. package/skills/product/imagery/scripts/edit.sh +14 -0
  568. package/skills/product/imagery/scripts/generate.sh +13 -0
  569. package/skills/product/imagery/scripts/provider.py +787 -0
  570. package/skills/product/listen/SKILL.md +175 -0
  571. package/skills/product/listen/refs/channels.md +220 -0
  572. package/skills/product/mockup/SKILL.md +106 -0
  573. package/skills/product/mockup/refs/templates.md +95 -0
  574. package/skills/product/refs/a11y.md +109 -0
  575. package/skills/product/refs/acceptance.md +79 -0
  576. package/skills/product/refs/align.md +67 -0
  577. package/skills/product/refs/analyze.md +77 -0
  578. package/skills/product/refs/elicit.md +67 -0
  579. package/skills/product/refs/listen.md +58 -0
  580. package/skills/product/refs/mockup.md +77 -0
  581. package/skills/product/refs/screenshot.md +59 -0
  582. package/skills/product/refs/strategy.md +77 -0
  583. package/skills/product/refs/synthesize.md +54 -0
  584. package/skills/product/refs/ux-review.md +68 -0
  585. package/skills/product/refs/ux.md +101 -0
  586. package/skills/product/requirements-analysis/SKILL.md +136 -0
  587. package/skills/product/requirements-analysis/refs/requirements-example-auth.md +264 -0
  588. package/skills/product/requirements-analysis/refs/requirements-example-export-integration.md +88 -0
  589. package/skills/product/requirements-analysis/refs/requirements-output-format-template.md +247 -0
  590. package/skills/product/requirements-analysis/refs/user-story-guide.md +296 -0
  591. package/skills/product/requirements-graph/SKILL.md +155 -0
  592. package/skills/product/requirements-graph/refs/examples.md +223 -0
  593. package/skills/product/requirements-graph/refs/schema.md +162 -0
  594. package/skills/product/requirements-migrate/SKILL.md +90 -0
  595. package/skills/product/requirements-navigate/SKILL.md +141 -0
  596. package/skills/product/requirements-navigate/refs/meta-templates.md +141 -0
  597. package/skills/product/screenshot/SKILL.md +147 -0
  598. package/skills/product/strategy/SKILL.md +198 -0
  599. package/skills/product/strategy/refs/frameworks.md +295 -0
  600. package/skills/product/strategy/refs/templates-competitive-market.md +299 -0
  601. package/skills/product/strategy/refs/templates-roi-value.md +207 -0
  602. package/skills/product/synthesize/SKILL.md +116 -0
  603. package/skills/product/synthesize/refs/journey-mapping.md +212 -0
  604. package/skills/product/synthesize/refs/prioritization.md +193 -0
  605. package/skills/product/ux-review/SKILL.md +164 -0
  606. package/skills/product/visual-direction/SKILL.md +137 -0
  607. package/skills/product/visual-review/SKILL.md +131 -0
  608. package/skills/runtime-exec/SKILL.md +138 -0
  609. package/skills/search/codebase-narrator/SKILL.md +131 -0
  610. package/skills/search/codebase-narrator/refs/output-template.md +100 -0
  611. package/skills/smaht/SKILL.md +72 -0
  612. package/skills/smaht/discovery/SKILL.md +65 -0
  613. package/skills/smaht/intent/SKILL.md +89 -0
  614. package/skills/smaht/propose-skills/SKILL.md +142 -0
  615. package/skills/smaht/refs/state.md +76 -0
  616. package/skills/swarm/SKILL.md +132 -0
  617. package/skills/swarm/refs/fan-out.md +104 -0
  618. package/skills/swarm/refs/independent-verification.md +86 -0
  619. package/skills/swarm/refs/receipts-and-evidence.md +69 -0
  620. package/skills/swarm/refs/recon-synthesis.md +87 -0
  621. package/skills/swarm/refs/ship-discipline.md +90 -0
  622. package/skills/wickedizer/SKILL.md +147 -0
  623. package/skills/wickedizer/refs/artifact-types.md +238 -0
  624. package/skills/wickedizer/refs/complexity.md +278 -0
  625. package/skills/wickedizer/refs/de-ai-pass.md +140 -0
  626. package/skills/wickedizer/refs/failure-modes.md +249 -0
  627. package/skills/wickedizer/refs/patterns.md +282 -0
  628. package/skills/wickedizer/refs/review.md +221 -0
  629. package/skills/wickedizer/refs/structure.md +282 -0
  630. package/skills/wickedizer/refs/voice-profile.md +125 -0
  631. package/skills/workflow/SKILL.md +132 -0
  632. package/skills/workflow/refs/archetype-adjustments.md +205 -0
  633. package/skills/workflow/refs/evidence.md +58 -0
  634. package/skills/workflow/refs/integration.md +130 -0
  635. package/skills/workflow/refs/risk-dimension-signals.md +260 -0
  636. package/skills/workflow/refs/scoring-rubric.md +49 -0
  637. package/skills/workflow/refs/signal-keywords.md +176 -0
  638. package/skills/workflow/refs/specialist-routing-rules.md +159 -0
  639. package/skills/worktrees/SKILL.md +87 -0
  640. package/skills/worktrees/refs/recipes.md +96 -0
@@ -0,0 +1,787 @@
1
+ #!/usr/bin/env python3
2
+ """Multi-provider image generation abstraction.
3
+
4
+ Provides a unified CLI for image generation across providers:
5
+ - cstudio: Creative Studio CLI binary
6
+ - vertex-curl: Google Vertex AI Imagen via gcloud + urllib
7
+ - openai: OpenAI Images API (DALL-E 3 / gpt-image-1)
8
+ - stability: Stability AI (Stable Diffusion 3.5)
9
+ - replicate: Replicate (Flux models)
10
+
11
+ Usage:
12
+ python3 provider.py generate --prompt "..." --output ./out.png [--provider auto]
13
+ python3 provider.py edit --image ./in.png --prompt "..." --output ./out.png
14
+ python3 provider.py inpaint --image ./in.png --mask ./mask.png --prompt "..." --output ./out.png
15
+ python3 provider.py detect
16
+ python3 provider.py list-providers
17
+ """
18
+
19
+ import argparse
20
+ import base64
21
+ import json
22
+ import os
23
+ import shutil
24
+ import subprocess
25
+ import sys
26
+ import urllib.request
27
+ import urllib.error
28
+ from pathlib import Path
29
+
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Provider base
33
+ # ---------------------------------------------------------------------------
34
+
35
+ class BaseProvider:
36
+ """Interface that every provider must implement."""
37
+
38
+ name: str = ""
39
+
40
+ def detect(self) -> bool:
41
+ """Return True if this provider is available in the current env."""
42
+ raise NotImplementedError
43
+
44
+ def generate(self, prompt: str, output: str, **opts) -> dict:
45
+ raise NotImplementedError
46
+
47
+ def edit(self, image: str, prompt: str, output: str, **opts) -> dict:
48
+ raise NotImplementedError
49
+
50
+ def inpaint(self, image: str, mask: str, prompt: str, output: str, **opts) -> dict:
51
+ raise NotImplementedError
52
+
53
+ # helpers ---------------------------------------------------------------
54
+
55
+ def _ok(self, operation: str, output_path: str, **metadata) -> dict:
56
+ return {
57
+ "ok": True,
58
+ "provider": self.name,
59
+ "operation": operation,
60
+ "output_path": output_path,
61
+ "metadata": metadata,
62
+ }
63
+
64
+ def _fail(self, error: str) -> dict:
65
+ return {"ok": False, "provider": self.name, "error": error}
66
+
67
+ @staticmethod
68
+ def _ensure_parent(path: str) -> None:
69
+ Path(path).parent.mkdir(parents=True, exist_ok=True)
70
+
71
+ @staticmethod
72
+ def _read_image_b64(path: str) -> str:
73
+ with open(path, "rb") as fh:
74
+ return base64.b64encode(fh.read()).decode()
75
+
76
+ @staticmethod
77
+ def _write_image_b64(b64data: str, path: str) -> None:
78
+ BaseProvider._ensure_parent(path)
79
+ with open(path, "wb") as fh:
80
+ fh.write(base64.b64decode(b64data))
81
+
82
+ @staticmethod
83
+ def _write_image_bytes(data: bytes, path: str) -> None:
84
+ BaseProvider._ensure_parent(path)
85
+ with open(path, "wb") as fh:
86
+ fh.write(data)
87
+
88
+ @staticmethod
89
+ def _http_json(url: str, body: dict, headers: dict, timeout: int = 120) -> dict:
90
+ """POST JSON and return parsed response."""
91
+ data = json.dumps(body).encode()
92
+ hdrs = {"Content-Type": "application/json", **headers}
93
+ req = urllib.request.Request(url, data=data, method="POST", headers=hdrs)
94
+ try:
95
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
96
+ return json.loads(resp.read().decode())
97
+ except urllib.error.HTTPError as exc:
98
+ body_text = exc.read().decode() if exc.fp else ""
99
+ raise RuntimeError(f"API returned {exc.code}: {body_text}") from exc
100
+
101
+ @staticmethod
102
+ def _http_download(url: str, path: str, timeout: int = 60) -> None:
103
+ """Download a URL to a file path."""
104
+ BaseProvider._ensure_parent(path)
105
+ req = urllib.request.Request(url)
106
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
107
+ with open(path, "wb") as fh:
108
+ fh.write(resp.read())
109
+
110
+
111
+ # ---------------------------------------------------------------------------
112
+ # cstudio provider
113
+ # ---------------------------------------------------------------------------
114
+
115
+ class CStudioProvider(BaseProvider):
116
+ """Creative Studio CLI wrapper."""
117
+
118
+ name = "cstudio"
119
+
120
+ def _bin(self) -> str:
121
+ return os.environ.get("CSTUDIO_PATH", "cstudio")
122
+
123
+ def detect(self) -> bool:
124
+ custom = os.environ.get("CSTUDIO_PATH")
125
+ if custom:
126
+ return os.path.isfile(custom) and os.access(custom, os.X_OK)
127
+ return shutil.which("cstudio") is not None
128
+
129
+ def _run(self, args: list[str]) -> subprocess.CompletedProcess:
130
+ return subprocess.run(
131
+ args, capture_output=True, text=True, timeout=120
132
+ )
133
+
134
+ def generate(self, prompt: str, output: str, **opts) -> dict:
135
+ self._ensure_parent(output)
136
+ cmd = [self._bin(), "generate", "image", "--prompt", prompt, "--output", output]
137
+ try:
138
+ result = self._run(cmd)
139
+ if result.returncode != 0:
140
+ return self._fail(f"cstudio exited {result.returncode}: {result.stderr.strip()}")
141
+ return self._ok("generate", output, prompt=prompt)
142
+ except FileNotFoundError:
143
+ return self._fail("cstudio binary not found")
144
+ except subprocess.TimeoutExpired:
145
+ return self._fail("cstudio timed out after 120s")
146
+ except Exception as exc:
147
+ return self._fail(str(exc))
148
+
149
+ def edit(self, image: str, prompt: str, output: str, **opts) -> dict:
150
+ self._ensure_parent(output)
151
+ cmd = [self._bin(), "edit", "image", "--image", image, "--prompt", prompt, "--output", output]
152
+ try:
153
+ result = self._run(cmd)
154
+ if result.returncode != 0:
155
+ return self._fail(f"cstudio exited {result.returncode}: {result.stderr.strip()}")
156
+ return self._ok("edit", output, prompt=prompt, source_image=image)
157
+ except FileNotFoundError:
158
+ return self._fail("cstudio binary not found")
159
+ except subprocess.TimeoutExpired:
160
+ return self._fail("cstudio timed out after 120s")
161
+ except Exception as exc:
162
+ return self._fail(str(exc))
163
+
164
+ def inpaint(self, image: str, mask: str, prompt: str, output: str, **opts) -> dict:
165
+ self._ensure_parent(output)
166
+ cmd = [
167
+ self._bin(), "inpaint", "image",
168
+ "--image", image, "--mask", mask,
169
+ "--prompt", prompt, "--output", output,
170
+ ]
171
+ try:
172
+ result = self._run(cmd)
173
+ if result.returncode != 0:
174
+ return self._fail(f"cstudio exited {result.returncode}: {result.stderr.strip()}")
175
+ return self._ok("inpaint", output, prompt=prompt, source_image=image, mask=mask)
176
+ except FileNotFoundError:
177
+ return self._fail("cstudio binary not found")
178
+ except subprocess.TimeoutExpired:
179
+ return self._fail("cstudio timed out after 120s")
180
+ except Exception as exc:
181
+ return self._fail(str(exc))
182
+
183
+
184
+ # ---------------------------------------------------------------------------
185
+ # vertex-curl provider
186
+ # ---------------------------------------------------------------------------
187
+
188
+ class VertexCurlProvider(BaseProvider):
189
+ """Google Vertex AI Imagen via gcloud auth + urllib.request."""
190
+
191
+ name = "vertex-curl"
192
+
193
+ GENERATE_MODEL = "imagen-3.0-generate-001"
194
+ EDIT_MODEL = "imagen-3.0-capability-001"
195
+
196
+ def detect(self) -> bool:
197
+ if not os.environ.get("GOOGLE_CLOUD_PROJECT"):
198
+ return False
199
+ return shutil.which("gcloud") is not None
200
+
201
+ # internals -------------------------------------------------------------
202
+
203
+ def _project(self) -> str:
204
+ return os.environ["GOOGLE_CLOUD_PROJECT"]
205
+
206
+ def _location(self) -> str:
207
+ return os.environ.get("GOOGLE_CLOUD_LOCATION", "us-central1")
208
+
209
+ def _token(self) -> str:
210
+ result = subprocess.run(
211
+ ["gcloud", "auth", "print-access-token"],
212
+ capture_output=True, text=True, timeout=30,
213
+ )
214
+ if result.returncode != 0:
215
+ raise RuntimeError(f"gcloud auth failed: {result.stderr.strip()}")
216
+ return result.stdout.strip()
217
+
218
+ def _endpoint(self, model: str) -> str:
219
+ loc = self._location()
220
+ proj = self._project()
221
+ return (
222
+ f"https://{loc}-aiplatform.googleapis.com/v1/"
223
+ f"projects/{proj}/locations/{loc}/"
224
+ f"publishers/google/models/{model}:predict"
225
+ )
226
+
227
+ def _vertex_post(self, url: str, body: dict, token: str) -> dict:
228
+ return self._http_json(url, body, {"Authorization": f"Bearer {token}"})
229
+
230
+ # operations ------------------------------------------------------------
231
+
232
+ def generate(self, prompt: str, output: str, **opts) -> dict:
233
+ try:
234
+ token = self._token()
235
+ url = self._endpoint(self.GENERATE_MODEL)
236
+ body = {
237
+ "instances": [{"prompt": prompt}],
238
+ "parameters": {"sampleCount": 1},
239
+ }
240
+ resp = self._vertex_post(url, body, token)
241
+ preds = resp.get("predictions") or []
242
+ if not preds or "bytesBase64Encoded" not in preds[0]:
243
+ return self._fail("API returned no predictions — check quota, project ID, or prompt safety filters")
244
+ self._write_image_b64(preds[0]["bytesBase64Encoded"], output)
245
+ return self._ok(
246
+ "generate", output,
247
+ model=self.GENERATE_MODEL, prompt=prompt,
248
+ )
249
+ except Exception as exc:
250
+ return self._fail(str(exc))
251
+
252
+ def edit(self, image: str, prompt: str, output: str, **opts) -> dict:
253
+ try:
254
+ token = self._token()
255
+ url = self._endpoint(self.EDIT_MODEL)
256
+ b64src = self._read_image_b64(image)
257
+ body = {
258
+ "instances": [{
259
+ "prompt": prompt,
260
+ "image": {"bytesBase64Encoded": b64src},
261
+ }],
262
+ "parameters": {"sampleCount": 1},
263
+ }
264
+ resp = self._vertex_post(url, body, token)
265
+ preds = resp.get("predictions") or []
266
+ if not preds or "bytesBase64Encoded" not in preds[0]:
267
+ return self._fail("API returned no predictions — check quota, project ID, or prompt safety filters")
268
+ self._write_image_b64(preds[0]["bytesBase64Encoded"], output)
269
+ return self._ok(
270
+ "edit", output,
271
+ model=self.EDIT_MODEL, prompt=prompt, source_image=image,
272
+ )
273
+ except Exception as exc:
274
+ return self._fail(str(exc))
275
+
276
+ def inpaint(self, image: str, mask: str, prompt: str, output: str, **opts) -> dict:
277
+ try:
278
+ token = self._token()
279
+ url = self._endpoint(self.EDIT_MODEL)
280
+ b64src = self._read_image_b64(image)
281
+ b64mask = self._read_image_b64(mask)
282
+ body = {
283
+ "instances": [{
284
+ "prompt": prompt,
285
+ "image": {"bytesBase64Encoded": b64src},
286
+ "mask": {"image": {"bytesBase64Encoded": b64mask}},
287
+ }],
288
+ "parameters": {"sampleCount": 1},
289
+ }
290
+ resp = self._vertex_post(url, body, token)
291
+ preds = resp.get("predictions") or []
292
+ if not preds or "bytesBase64Encoded" not in preds[0]:
293
+ return self._fail("API returned no predictions — check quota, project ID, or prompt safety filters")
294
+ self._write_image_b64(preds[0]["bytesBase64Encoded"], output)
295
+ return self._ok(
296
+ "inpaint", output,
297
+ model=self.EDIT_MODEL, prompt=prompt,
298
+ source_image=image, mask=mask,
299
+ )
300
+ except Exception as exc:
301
+ return self._fail(str(exc))
302
+
303
+
304
+ # ---------------------------------------------------------------------------
305
+ # OpenAI provider
306
+ # ---------------------------------------------------------------------------
307
+
308
+ class OpenAIProvider(BaseProvider):
309
+ """OpenAI Images API (gpt-image-1 / DALL-E 3)."""
310
+
311
+ name = "openai"
312
+
313
+ GENERATE_MODEL = "gpt-image-1"
314
+ EDIT_MODEL = "gpt-image-1"
315
+ API_BASE = "https://api.openai.com/v1"
316
+
317
+ def detect(self) -> bool:
318
+ return bool(os.environ.get("OPENAI_API_KEY"))
319
+
320
+ def _headers(self) -> dict:
321
+ return {"Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}"}
322
+
323
+ def generate(self, prompt: str, output: str, **opts) -> dict:
324
+ try:
325
+ url = f"{self.API_BASE}/images/generations"
326
+ body = {
327
+ "model": self.GENERATE_MODEL,
328
+ "prompt": prompt,
329
+ "n": 1,
330
+ "size": opts.get("size", "1024x1024"),
331
+ "response_format": "b64_json",
332
+ }
333
+ resp = self._http_json(url, body, self._headers())
334
+ data = resp.get("data") or []
335
+ if not data:
336
+ return self._fail("API returned no images — check prompt or billing")
337
+ b64 = data[0].get("b64_json")
338
+ if not b64:
339
+ # Fallback: download from URL
340
+ img_url = data[0].get("url")
341
+ if img_url:
342
+ self._http_download(img_url, output)
343
+ else:
344
+ return self._fail("API returned no image data")
345
+ else:
346
+ self._write_image_b64(b64, output)
347
+ return self._ok("generate", output, model=self.GENERATE_MODEL, prompt=prompt)
348
+ except Exception as exc:
349
+ return self._fail(str(exc))
350
+
351
+ def edit(self, image: str, prompt: str, output: str, **opts) -> dict:
352
+ try:
353
+ url = f"{self.API_BASE}/images/edits"
354
+ boundary = "----WickedGardenBoundary"
355
+ img_data = Path(image).read_bytes()
356
+ img_name = Path(image).name
357
+ import io
358
+ buf = io.BytesIO()
359
+ for part_text in [("model", self.EDIT_MODEL), ("prompt", prompt), ("response_format", "b64_json")]:
360
+ buf.write(f"--{boundary}\r\n".encode())
361
+ buf.write(f'Content-Disposition: form-data; name="{part_text[0]}"\r\n\r\n'.encode())
362
+ buf.write(f"{part_text[1]}\r\n".encode())
363
+ # image file part
364
+ buf.write(f"--{boundary}\r\n".encode())
365
+ buf.write(f'Content-Disposition: form-data; name="image"; filename="{img_name}"\r\n'.encode())
366
+ buf.write(b"Content-Type: image/png\r\n\r\n")
367
+ buf.write(img_data)
368
+ buf.write(b"\r\n")
369
+ buf.write(f"--{boundary}--\r\n".encode())
370
+
371
+ req = urllib.request.Request(
372
+ url, data=buf.getvalue(), method="POST",
373
+ headers={
374
+ "Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}",
375
+ "Content-Type": f"multipart/form-data; boundary={boundary}",
376
+ },
377
+ )
378
+ with urllib.request.urlopen(req, timeout=120) as resp:
379
+ result = json.loads(resp.read().decode())
380
+ data = result.get("data") or []
381
+ if not data:
382
+ return self._fail("API returned no images from edit")
383
+ b64 = data[0].get("b64_json")
384
+ if b64:
385
+ self._write_image_b64(b64, output)
386
+ else:
387
+ img_url = data[0].get("url")
388
+ if img_url:
389
+ self._http_download(img_url, output)
390
+ else:
391
+ return self._fail("API returned no image data from edit")
392
+ return self._ok("edit", output, model=self.EDIT_MODEL, prompt=prompt, source_image=image)
393
+ except urllib.error.HTTPError as exc:
394
+ body_text = exc.read().decode() if exc.fp else ""
395
+ return self._fail(f"API returned {exc.code}: {body_text}")
396
+ except Exception as exc:
397
+ return self._fail(str(exc))
398
+
399
+ def inpaint(self, image: str, mask: str, prompt: str, output: str, **opts) -> dict:
400
+ try:
401
+ url = f"{self.API_BASE}/images/edits"
402
+ boundary = "----WickedGardenBoundary"
403
+ img_data = Path(image).read_bytes()
404
+ mask_data = Path(mask).read_bytes()
405
+ import io
406
+ buf = io.BytesIO()
407
+ for field, value in [("model", self.EDIT_MODEL), ("prompt", prompt), ("response_format", "b64_json")]:
408
+ buf.write(f"--{boundary}\r\n".encode())
409
+ buf.write(f'Content-Disposition: form-data; name="{field}"\r\n\r\n'.encode())
410
+ buf.write(f"{value}\r\n".encode())
411
+ for field, data, fname in [("image", img_data, Path(image).name), ("mask", mask_data, Path(mask).name)]:
412
+ buf.write(f"--{boundary}\r\n".encode())
413
+ buf.write(f'Content-Disposition: form-data; name="{field}"; filename="{fname}"\r\n'.encode())
414
+ buf.write(b"Content-Type: image/png\r\n\r\n")
415
+ buf.write(data)
416
+ buf.write(b"\r\n")
417
+ buf.write(f"--{boundary}--\r\n".encode())
418
+
419
+ req = urllib.request.Request(
420
+ url, data=buf.getvalue(), method="POST",
421
+ headers={
422
+ "Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}",
423
+ "Content-Type": f"multipart/form-data; boundary={boundary}",
424
+ },
425
+ )
426
+ with urllib.request.urlopen(req, timeout=120) as resp:
427
+ result = json.loads(resp.read().decode())
428
+ data = result.get("data") or []
429
+ if not data:
430
+ return self._fail("API returned no images from inpaint")
431
+ b64 = data[0].get("b64_json")
432
+ if b64:
433
+ self._write_image_b64(b64, output)
434
+ else:
435
+ img_url = data[0].get("url")
436
+ if img_url:
437
+ self._http_download(img_url, output)
438
+ else:
439
+ return self._fail("API returned no image data from inpaint")
440
+ return self._ok("inpaint", output, model=self.EDIT_MODEL, prompt=prompt, source_image=image, mask=mask)
441
+ except urllib.error.HTTPError as exc:
442
+ body_text = exc.read().decode() if exc.fp else ""
443
+ return self._fail(f"API returned {exc.code}: {body_text}")
444
+ except Exception as exc:
445
+ return self._fail(str(exc))
446
+
447
+
448
+ # ---------------------------------------------------------------------------
449
+ # Stability AI provider
450
+ # ---------------------------------------------------------------------------
451
+
452
+ class StabilityProvider(BaseProvider):
453
+ """Stability AI (Stable Diffusion 3.5) via REST API."""
454
+
455
+ name = "stability"
456
+ API_BASE = "https://api.stability.ai/v2beta/stable-image"
457
+
458
+ def detect(self) -> bool:
459
+ return bool(os.environ.get("STABILITY_API_KEY"))
460
+
461
+ def _headers(self) -> dict:
462
+ return {
463
+ "Authorization": f"Bearer {os.environ['STABILITY_API_KEY']}",
464
+ "Accept": "application/json",
465
+ }
466
+
467
+ def _multipart_post(self, url: str, fields: list, files: list) -> dict:
468
+ """POST multipart/form-data with text fields and file uploads."""
469
+ boundary = "----WickedGardenBoundary"
470
+ import io
471
+ buf = io.BytesIO()
472
+ for name, value in fields:
473
+ buf.write(f"--{boundary}\r\n".encode())
474
+ buf.write(f'Content-Disposition: form-data; name="{name}"\r\n\r\n'.encode())
475
+ buf.write(f"{value}\r\n".encode())
476
+ for name, data, filename, content_type in files:
477
+ buf.write(f"--{boundary}\r\n".encode())
478
+ buf.write(f'Content-Disposition: form-data; name="{name}"; filename="{filename}"\r\n'.encode())
479
+ buf.write(f"Content-Type: {content_type}\r\n\r\n".encode())
480
+ buf.write(data)
481
+ buf.write(b"\r\n")
482
+ buf.write(f"--{boundary}--\r\n".encode())
483
+
484
+ req = urllib.request.Request(
485
+ url, data=buf.getvalue(), method="POST",
486
+ headers={
487
+ "Authorization": f"Bearer {os.environ['STABILITY_API_KEY']}",
488
+ "Accept": "application/json",
489
+ "Content-Type": f"multipart/form-data; boundary={boundary}",
490
+ },
491
+ )
492
+ try:
493
+ with urllib.request.urlopen(req, timeout=120) as resp:
494
+ return json.loads(resp.read().decode())
495
+ except urllib.error.HTTPError as exc:
496
+ body_text = exc.read().decode() if exc.fp else ""
497
+ raise RuntimeError(f"API returned {exc.code}: {body_text}") from exc
498
+
499
+ def generate(self, prompt: str, output: str, **opts) -> dict:
500
+ try:
501
+ url = f"{self.API_BASE}/generate/sd3"
502
+ fields = [
503
+ ("prompt", prompt),
504
+ ("output_format", "png"),
505
+ ("model", opts.get("model", "sd3.5-large")),
506
+ ]
507
+ if opts.get("negative_prompt"):
508
+ fields.append(("negative_prompt", opts["negative_prompt"]))
509
+ resp = self._multipart_post(url, fields, [])
510
+ b64 = resp.get("image")
511
+ if not b64:
512
+ return self._fail("API returned no image data")
513
+ self._write_image_b64(b64, output)
514
+ return self._ok("generate", output, model="sd3.5-large", prompt=prompt)
515
+ except Exception as exc:
516
+ return self._fail(str(exc))
517
+
518
+ def edit(self, image: str, prompt: str, output: str, **opts) -> dict:
519
+ try:
520
+ url = f"{self.API_BASE}/generate/sd3"
521
+ img_data = Path(image).read_bytes()
522
+ fields = [
523
+ ("prompt", prompt),
524
+ ("output_format", "png"),
525
+ ("model", opts.get("model", "sd3.5-large")),
526
+ ("mode", "image-to-image"),
527
+ ("strength", str(opts.get("strength", 0.7))),
528
+ ]
529
+ files = [("image", img_data, Path(image).name, "image/png")]
530
+ resp = self._multipart_post(url, fields, files)
531
+ b64 = resp.get("image")
532
+ if not b64:
533
+ return self._fail("API returned no image data from edit")
534
+ self._write_image_b64(b64, output)
535
+ return self._ok("edit", output, model="sd3.5-large", prompt=prompt, source_image=image)
536
+ except Exception as exc:
537
+ return self._fail(str(exc))
538
+
539
+ def inpaint(self, image: str, mask: str, prompt: str, output: str, **opts) -> dict:
540
+ try:
541
+ url = f"{self.API_BASE}/edit/inpaint"
542
+ img_data = Path(image).read_bytes()
543
+ mask_data = Path(mask).read_bytes()
544
+ fields = [
545
+ ("prompt", prompt),
546
+ ("output_format", "png"),
547
+ ]
548
+ files = [
549
+ ("image", img_data, Path(image).name, "image/png"),
550
+ ("mask", mask_data, Path(mask).name, "image/png"),
551
+ ]
552
+ resp = self._multipart_post(url, fields, files)
553
+ b64 = resp.get("image")
554
+ if not b64:
555
+ return self._fail("API returned no image data from inpaint")
556
+ self._write_image_b64(b64, output)
557
+ return self._ok("inpaint", output, model="sd3.5-large", prompt=prompt, source_image=image, mask=mask)
558
+ except Exception as exc:
559
+ return self._fail(str(exc))
560
+
561
+
562
+ # ---------------------------------------------------------------------------
563
+ # Replicate provider
564
+ # ---------------------------------------------------------------------------
565
+
566
+ class ReplicateProvider(BaseProvider):
567
+ """Replicate API (Flux models)."""
568
+
569
+ name = "replicate"
570
+
571
+ GENERATE_MODEL = "black-forest-labs/flux-1.1-pro"
572
+ EDIT_MODEL = "black-forest-labs/flux-fill-pro"
573
+ API_BASE = "https://api.replicate.com/v1"
574
+
575
+ def detect(self) -> bool:
576
+ return bool(os.environ.get("REPLICATE_API_TOKEN"))
577
+
578
+ def _headers(self) -> dict:
579
+ return {
580
+ "Authorization": f"Bearer {os.environ['REPLICATE_API_TOKEN']}",
581
+ "Prefer": "wait",
582
+ }
583
+
584
+ def _predict(self, model: str, input_data: dict) -> dict:
585
+ """Create a prediction and wait for result (sync mode)."""
586
+ url = f"{self.API_BASE}/models/{model}/predictions"
587
+ body = {"input": input_data}
588
+ hdrs = {**self._headers(), "Content-Type": "application/json"}
589
+ req = urllib.request.Request(
590
+ url, data=json.dumps(body).encode(), method="POST", headers=hdrs,
591
+ )
592
+ try:
593
+ with urllib.request.urlopen(req, timeout=120) as resp:
594
+ return json.loads(resp.read().decode())
595
+ except urllib.error.HTTPError as exc:
596
+ body_text = exc.read().decode() if exc.fp else ""
597
+ raise RuntimeError(f"API returned {exc.code}: {body_text}") from exc
598
+
599
+ def _download_output(self, result: dict, output: str) -> str | None:
600
+ """Extract image URL from prediction output and download it."""
601
+ out = result.get("output")
602
+ if not out:
603
+ return "API returned no output — check model and input"
604
+ # Output can be a URL string or a list of URLs
605
+ img_url = out[0] if isinstance(out, list) else out
606
+ if not isinstance(img_url, str) or not img_url.startswith("http"):
607
+ return f"Unexpected output format: {type(out)}"
608
+ self._http_download(img_url, output)
609
+ return None
610
+
611
+ def generate(self, prompt: str, output: str, **opts) -> dict:
612
+ try:
613
+ input_data = {"prompt": prompt}
614
+ if opts.get("aspect_ratio"):
615
+ input_data["aspect_ratio"] = opts["aspect_ratio"]
616
+ result = self._predict(self.GENERATE_MODEL, input_data)
617
+ if result.get("status") == "failed":
618
+ return self._fail(f"Prediction failed: {result.get('error', 'unknown')}")
619
+ err = self._download_output(result, output)
620
+ if err:
621
+ return self._fail(err)
622
+ return self._ok("generate", output, model=self.GENERATE_MODEL, prompt=prompt)
623
+ except Exception as exc:
624
+ return self._fail(str(exc))
625
+
626
+ def edit(self, image: str, prompt: str, output: str, **opts) -> dict:
627
+ try:
628
+ b64src = self._read_image_b64(image)
629
+ input_data = {
630
+ "prompt": prompt,
631
+ "image": f"data:image/png;base64,{b64src}",
632
+ }
633
+ result = self._predict(self.EDIT_MODEL, input_data)
634
+ if result.get("status") == "failed":
635
+ return self._fail(f"Prediction failed: {result.get('error', 'unknown')}")
636
+ err = self._download_output(result, output)
637
+ if err:
638
+ return self._fail(err)
639
+ return self._ok("edit", output, model=self.EDIT_MODEL, prompt=prompt, source_image=image)
640
+ except Exception as exc:
641
+ return self._fail(str(exc))
642
+
643
+ def inpaint(self, image: str, mask: str, prompt: str, output: str, **opts) -> dict:
644
+ try:
645
+ b64src = self._read_image_b64(image)
646
+ b64mask = self._read_image_b64(mask)
647
+ input_data = {
648
+ "prompt": prompt,
649
+ "image": f"data:image/png;base64,{b64src}",
650
+ "mask": f"data:image/png;base64,{b64mask}",
651
+ }
652
+ result = self._predict(self.EDIT_MODEL, input_data)
653
+ if result.get("status") == "failed":
654
+ return self._fail(f"Prediction failed: {result.get('error', 'unknown')}")
655
+ err = self._download_output(result, output)
656
+ if err:
657
+ return self._fail(err)
658
+ return self._ok("inpaint", output, model=self.EDIT_MODEL, prompt=prompt, source_image=image, mask=mask)
659
+ except Exception as exc:
660
+ return self._fail(str(exc))
661
+
662
+
663
+ # ---------------------------------------------------------------------------
664
+ # Registry & detection
665
+ # ---------------------------------------------------------------------------
666
+
667
+ PROVIDERS: dict[str, BaseProvider] = {
668
+ "cstudio": CStudioProvider(),
669
+ "vertex-curl": VertexCurlProvider(),
670
+ "openai": OpenAIProvider(),
671
+ "stability": StabilityProvider(),
672
+ "replicate": ReplicateProvider(),
673
+ }
674
+
675
+ PRIORITY_ORDER = ["cstudio", "vertex-curl", "openai", "stability", "replicate"]
676
+
677
+
678
+ def detect_providers() -> list[str]:
679
+ """Return names of available providers in priority order."""
680
+ return [name for name in PRIORITY_ORDER if PROVIDERS[name].detect()]
681
+
682
+
683
+ def select_provider(requested: str) -> BaseProvider:
684
+ """Resolve a provider by name or auto-detect the best available one."""
685
+ if requested and requested != "auto":
686
+ if requested not in PROVIDERS:
687
+ raise ValueError(f"Unknown provider: {requested}")
688
+ prov = PROVIDERS[requested]
689
+ if not prov.detect():
690
+ raise RuntimeError(f"Provider '{requested}' is not available in this environment")
691
+ return prov
692
+
693
+ available = detect_providers()
694
+ if not available:
695
+ raise RuntimeError(
696
+ "No image providers detected. Options: "
697
+ "cstudio CLI, gcloud + GOOGLE_CLOUD_PROJECT, "
698
+ "OPENAI_API_KEY, STABILITY_API_KEY, or REPLICATE_API_TOKEN."
699
+ )
700
+ return PROVIDERS[available[0]]
701
+
702
+
703
+ # ---------------------------------------------------------------------------
704
+ # CLI
705
+ # ---------------------------------------------------------------------------
706
+
707
+ def build_parser() -> argparse.ArgumentParser:
708
+ parser = argparse.ArgumentParser(
709
+ description="Multi-provider image generation CLI",
710
+ )
711
+ sub = parser.add_subparsers(dest="command", required=True)
712
+
713
+ # generate
714
+ gen = sub.add_parser("generate", help="Generate an image from a text prompt")
715
+ gen.add_argument("--prompt", required=True)
716
+ gen.add_argument("--output", required=True)
717
+ gen.add_argument("--provider", default="auto")
718
+
719
+ # edit
720
+ ed = sub.add_parser("edit", help="Edit an existing image with a prompt")
721
+ ed.add_argument("--image", required=True)
722
+ ed.add_argument("--prompt", required=True)
723
+ ed.add_argument("--output", required=True)
724
+ ed.add_argument("--provider", default="auto")
725
+
726
+ # inpaint
727
+ inp = sub.add_parser("inpaint", help="Inpaint a masked region of an image")
728
+ inp.add_argument("--image", required=True)
729
+ inp.add_argument("--mask", required=True)
730
+ inp.add_argument("--prompt", required=True)
731
+ inp.add_argument("--output", required=True)
732
+ inp.add_argument("--provider", default="auto")
733
+
734
+ # detect
735
+ sub.add_parser("detect", help="List available providers in this environment")
736
+
737
+ # list-providers
738
+ sub.add_parser("list-providers", help="List all supported providers")
739
+
740
+ return parser
741
+
742
+
743
+ def main() -> None:
744
+ parser = build_parser()
745
+ args = parser.parse_args()
746
+
747
+ try:
748
+ if args.command == "detect":
749
+ available = detect_providers()
750
+ print(json.dumps({
751
+ "ok": True,
752
+ "available": available,
753
+ "default": available[0] if available else None,
754
+ }))
755
+ return
756
+
757
+ if args.command == "list-providers":
758
+ print(json.dumps({
759
+ "ok": True,
760
+ "providers": [
761
+ {"name": name, "available": PROVIDERS[name].detect()}
762
+ for name in PRIORITY_ORDER
763
+ ],
764
+ }))
765
+ return
766
+
767
+ provider = select_provider(args.provider)
768
+
769
+ if args.command == "generate":
770
+ result = provider.generate(args.prompt, args.output)
771
+ elif args.command == "edit":
772
+ result = provider.edit(args.image, args.prompt, args.output)
773
+ elif args.command == "inpaint":
774
+ result = provider.inpaint(args.image, args.mask, args.prompt, args.output)
775
+ else:
776
+ result = {"ok": False, "provider": "", "error": f"Unknown command: {args.command}"}
777
+
778
+ print(json.dumps(result))
779
+ sys.exit(0 if result.get("ok") else 1)
780
+
781
+ except Exception as exc:
782
+ print(json.dumps({"ok": False, "provider": "", "error": str(exc)}))
783
+ sys.exit(1)
784
+
785
+
786
+ if __name__ == "__main__":
787
+ main()