vidbyte-cli 0.1.0__py3-none-any.whl

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 (269) hide show
  1. vidbyte_cli/__init__.py +11 -0
  2. vidbyte_cli/__main__.py +6 -0
  3. vidbyte_cli/cli.py +18 -0
  4. vidbyte_cli/commands/__init__.py +86 -0
  5. vidbyte_cli/commands/agent_options.py +53 -0
  6. vidbyte_cli/commands/agents/__init__.py +31 -0
  7. vidbyte_cli/commands/agents/suggestion/__init__.py +38 -0
  8. vidbyte_cli/commands/agents/suggestion/prompts/__init__.py +3 -0
  9. vidbyte_cli/commands/agents/suggestion/prompts/all_categories.md +5 -0
  10. vidbyte_cli/commands/agents/suggestion/prompts/approaches.md +100 -0
  11. vidbyte_cli/commands/agents/suggestion/prompts/avoid.md +100 -0
  12. vidbyte_cli/commands/agents/suggestion/prompts/blockers.md +100 -0
  13. vidbyte_cli/commands/agents/suggestion/prompts/capability.md +100 -0
  14. vidbyte_cli/commands/agents/suggestion/prompts/categories.md +5 -0
  15. vidbyte_cli/commands/agents/suggestion/prompts/category.md +5 -0
  16. vidbyte_cli/commands/agents/suggestion/prompts/completed.md +100 -0
  17. vidbyte_cli/commands/agents/suggestion/prompts/constraint.md +100 -0
  18. vidbyte_cli/commands/agents/suggestion/prompts/context.md +100 -0
  19. vidbyte_cli/commands/agents/suggestion/prompts/count.md +5 -0
  20. vidbyte_cli/commands/agents/suggestion/prompts/critic_model.md +100 -0
  21. vidbyte_cli/commands/agents/suggestion/prompts/decision.md +100 -0
  22. vidbyte_cli/commands/agents/suggestion/prompts/dry_run.md +5 -0
  23. vidbyte_cli/commands/agents/suggestion/prompts/extra_compute.md +5 -0
  24. vidbyte_cli/commands/agents/suggestion/prompts/feedback_accept.md +7 -0
  25. vidbyte_cli/commands/agents/suggestion/prompts/feedback_group.md +7 -0
  26. vidbyte_cli/commands/agents/suggestion/prompts/feedback_project.md +100 -0
  27. vidbyte_cli/commands/agents/suggestion/prompts/feedback_reason.md +98 -0
  28. vidbyte_cli/commands/agents/suggestion/prompts/feedback_reject.md +7 -0
  29. vidbyte_cli/commands/agents/suggestion/prompts/feedback_suggestion.md +101 -0
  30. vidbyte_cli/commands/agents/suggestion/prompts/files.md +100 -0
  31. vidbyte_cli/commands/agents/suggestion/prompts/forbidden.md +100 -0
  32. vidbyte_cli/commands/agents/suggestion/prompts/goal.md +100 -0
  33. vidbyte_cli/commands/agents/suggestion/prompts/handoff.md +7 -0
  34. vidbyte_cli/commands/agents/suggestion/prompts/horizon.md +5 -0
  35. vidbyte_cli/commands/agents/suggestion/prompts/hypotheses.md +100 -0
  36. vidbyte_cli/commands/agents/suggestion/prompts/idea.md +100 -0
  37. vidbyte_cli/commands/agents/suggestion/prompts/in_progress.md +100 -0
  38. vidbyte_cli/commands/agents/suggestion/prompts/input.md +100 -0
  39. vidbyte_cli/commands/agents/suggestion/prompts/library.py +29 -0
  40. vidbyte_cli/commands/agents/suggestion/prompts/max_messages.md +5 -0
  41. vidbyte_cli/commands/agents/suggestion/prompts/max_output_tokens.md +5 -0
  42. vidbyte_cli/commands/agents/suggestion/prompts/max_total_tokens.md +5 -0
  43. vidbyte_cli/commands/agents/suggestion/prompts/mistakes.md +100 -0
  44. vidbyte_cli/commands/agents/suggestion/prompts/outcomes.md +100 -0
  45. vidbyte_cli/commands/agents/suggestion/prompts/project.md +100 -0
  46. vidbyte_cli/commands/agents/suggestion/prompts/project_create.md +7 -0
  47. vidbyte_cli/commands/agents/suggestion/prompts/project_description.md +98 -0
  48. vidbyte_cli/commands/agents/suggestion/prompts/project_group.md +7 -0
  49. vidbyte_cli/commands/agents/suggestion/prompts/project_key.md +96 -0
  50. vidbyte_cli/commands/agents/suggestion/prompts/project_list.md +7 -0
  51. vidbyte_cli/commands/agents/suggestion/prompts/project_title.md +99 -0
  52. vidbyte_cli/commands/agents/suggestion/prompts/provider.md +5 -0
  53. vidbyte_cli/commands/agents/suggestion/prompts/question.md +100 -0
  54. vidbyte_cli/commands/agents/suggestion/prompts/risks.md +100 -0
  55. vidbyte_cli/commands/agents/suggestion/prompts/rounds.md +5 -0
  56. vidbyte_cli/commands/agents/suggestion/prompts/run.md +5 -0
  57. vidbyte_cli/commands/agents/suggestion/prompts/success.md +100 -0
  58. vidbyte_cli/commands/agents/suggestion/prompts/timeout.md +5 -0
  59. vidbyte_cli/commands/agents/suggestion/prompts/trajectory.md +100 -0
  60. vidbyte_cli/commands/agents/suggestion/prompts/view.md +5 -0
  61. vidbyte_cli/commands/agents/suggestion/prompts/view_all.md +5 -0
  62. vidbyte_cli/commands/agents/suggestion/render.py +77 -0
  63. vidbyte_cli/commands/agents/suggestion/request_builder.py +240 -0
  64. vidbyte_cli/commands/agents/suggestion/suggest.py +154 -0
  65. vidbyte_cli/commands/agents/suggestion/suggestion_categories.py +77 -0
  66. vidbyte_cli/commands/agents/suggestion/suggestion_feedback.py +85 -0
  67. vidbyte_cli/commands/agents/suggestion/suggestion_handoff.py +67 -0
  68. vidbyte_cli/commands/agents/suggestion/suggestion_projects.py +95 -0
  69. vidbyte_cli/commands/auth/__init__.py +0 -0
  70. vidbyte_cli/commands/auth/login.py +100 -0
  71. vidbyte_cli/commands/auth/logout.py +41 -0
  72. vidbyte_cli/commands/auth/whoami.py +51 -0
  73. vidbyte_cli/commands/billing/__init__.py +20 -0
  74. vidbyte_cli/commands/billing/prompts/__init__.py +1 -0
  75. vidbyte_cli/commands/billing/prompts/confirm.md +16 -0
  76. vidbyte_cli/commands/billing/prompts/group.md +13 -0
  77. vidbyte_cli/commands/billing/prompts/idempotency_key.md +16 -0
  78. vidbyte_cli/commands/billing/prompts/library.py +18 -0
  79. vidbyte_cli/commands/billing/prompts/top_up.md +16 -0
  80. vidbyte_cli/commands/billing/top_up.py +65 -0
  81. vidbyte_cli/commands/config/__init__.py +0 -0
  82. vidbyte_cli/commands/config/get.py +45 -0
  83. vidbyte_cli/commands/config/set.py +39 -0
  84. vidbyte_cli/commands/provider/__init__.py +9 -0
  85. vidbyte_cli/commands/provider/login.py +102 -0
  86. vidbyte_cli/commands/provider/logout.py +45 -0
  87. vidbyte_cli/commands/provider/whoami.py +49 -0
  88. vidbyte_cli/commands/research/__init__.py +5 -0
  89. vidbyte_cli/commands/research/add.py +41 -0
  90. vidbyte_cli/commands/research/options.py +134 -0
  91. vidbyte_cli/commands/research/render.py +157 -0
  92. vidbyte_cli/commands/research/resume.py +37 -0
  93. vidbyte_cli/commands/research/start.py +37 -0
  94. vidbyte_cli/commands/research/status.py +32 -0
  95. vidbyte_cli/commands/research/thread.py +30 -0
  96. vidbyte_cli/commands/research/threads.py +35 -0
  97. vidbyte_cli/commands/research/watch.py +93 -0
  98. vidbyte_cli/commands/runtime/__init__.py +23 -0
  99. vidbyte_cli/commands/runtime/adversarial_team.py +41 -0
  100. vidbyte_cli/commands/runtime/doctor.py +44 -0
  101. vidbyte_cli/commands/runtime/list.py +47 -0
  102. vidbyte_cli/commands/runtime/persistence.py +141 -0
  103. vidbyte_cli/commands/runtime/same_host_ensemble.py +221 -0
  104. vidbyte_cli/commands/runtime/stages.py +553 -0
  105. vidbyte_cli/commands/runtime/task_board.py +1342 -0
  106. vidbyte_cli/commands/setup/__init__.py +0 -0
  107. vidbyte_cli/commands/setup/doctor.py +64 -0
  108. vidbyte_cli/commands/setup/prompts/__init__.py +1 -0
  109. vidbyte_cli/commands/setup/prompts/browser.md +7 -0
  110. vidbyte_cli/commands/setup/prompts/library.py +18 -0
  111. vidbyte_cli/commands/setup/prompts/star.md +13 -0
  112. vidbyte_cli/commands/setup/star.py +132 -0
  113. vidbyte_cli/lib/__init__.py +0 -0
  114. vidbyte_cli/lib/api/__init__.py +0 -0
  115. vidbyte_cli/lib/api/client.py +236 -0
  116. vidbyte_cli/lib/api/endpoints/__init__.py +0 -0
  117. vidbyte_cli/lib/api/endpoints/auth.py +24 -0
  118. vidbyte_cli/lib/api/endpoints/billing.py +36 -0
  119. vidbyte_cli/lib/api/endpoints/research.py +103 -0
  120. vidbyte_cli/lib/api/endpoints/runtime.py +113 -0
  121. vidbyte_cli/lib/api/problem.py +86 -0
  122. vidbyte_cli/lib/api/response.py +101 -0
  123. vidbyte_cli/lib/api/retry.py +116 -0
  124. vidbyte_cli/lib/api/runtime_payment.py +89 -0
  125. vidbyte_cli/lib/auth/__init__.py +16 -0
  126. vidbyte_cli/lib/auth/credentials.py +223 -0
  127. vidbyte_cli/lib/auth/input.py +59 -0
  128. vidbyte_cli/lib/auth/keyring_store.py +126 -0
  129. vidbyte_cli/lib/auth/provider_credentials.py +157 -0
  130. vidbyte_cli/lib/auth/provider_input.py +54 -0
  131. vidbyte_cli/lib/auth/provider_resolver.py +89 -0
  132. vidbyte_cli/lib/auth/provider_store.py +149 -0
  133. vidbyte_cli/lib/auth/provider_verifier.py +219 -0
  134. vidbyte_cli/lib/auth/resolver.py +63 -0
  135. vidbyte_cli/lib/auth/verifier.py +38 -0
  136. vidbyte_cli/lib/config/__init__.py +25 -0
  137. vidbyte_cli/lib/config/atomic.py +47 -0
  138. vidbyte_cli/lib/config/config.py +141 -0
  139. vidbyte_cli/lib/config/migration.py +81 -0
  140. vidbyte_cli/lib/config/models.py +145 -0
  141. vidbyte_cli/lib/config/paths.py +66 -0
  142. vidbyte_cli/lib/config/resolver.py +137 -0
  143. vidbyte_cli/lib/constants/__init__.py +1 -0
  144. vidbyte_cli/lib/constants/runtime.py +186 -0
  145. vidbyte_cli/lib/errors/__init__.py +12 -0
  146. vidbyte_cli/lib/errors/cli_error.py +86 -0
  147. vidbyte_cli/lib/errors/codes.py +43 -0
  148. vidbyte_cli/lib/errors/failures.py +2910 -0
  149. vidbyte_cli/lib/errors/handler.py +72 -0
  150. vidbyte_cli/lib/files/__init__.py +6 -0
  151. vidbyte_cli/lib/files/documents.py +94 -0
  152. vidbyte_cli/lib/files/store.py +156 -0
  153. vidbyte_cli/lib/io/__init__.py +16 -0
  154. vidbyte_cli/lib/io/attachments.py +129 -0
  155. vidbyte_cli/lib/io/codex_attachments.py +47 -0
  156. vidbyte_cli/lib/io/prompt.py +71 -0
  157. vidbyte_cli/lib/io/streams.py +37 -0
  158. vidbyte_cli/lib/io/terminal.py +66 -0
  159. vidbyte_cli/lib/output/__init__.py +7 -0
  160. vidbyte_cli/lib/output/formats.py +29 -0
  161. vidbyte_cli/lib/output/manager.py +95 -0
  162. vidbyte_cli/lib/output/models.py +49 -0
  163. vidbyte_cli/lib/runtime/__init__.py +9 -0
  164. vidbyte_cli/lib/runtime/application.py +134 -0
  165. vidbyte_cli/lib/runtime/context.py +250 -0
  166. vidbyte_cli/lib/runtime/options.py +159 -0
  167. vidbyte_cli/lib/runtime/version.py +30 -0
  168. vidbyte_cli/lib/runtime_primitives/__init__.py +28 -0
  169. vidbyte_cli/lib/runtime_primitives/executor.py +54 -0
  170. vidbyte_cli/lib/runtime_primitives/gate.py +105 -0
  171. vidbyte_cli/lib/runtime_primitives/hosts.py +45 -0
  172. vidbyte_cli/lib/runtime_primitives/planner.py +79 -0
  173. vidbyte_cli/lib/runtime_primitives/stages.py +338 -0
  174. vidbyte_cli/lib/runtime_primitives/task_board.py +982 -0
  175. vidbyte_cli/lib/runtime_primitives/task_board_checkpoints.py +473 -0
  176. vidbyte_cli/lib/runtime_primitives/task_board_system.md +33 -0
  177. vidbyte_cli/lib/runtime_primitives/task_board_tasks.py +111 -0
  178. vidbyte_cli/lib/runtime_primitives/verification.py +68 -0
  179. vidbyte_cli/services/__init__.py +5 -0
  180. vidbyte_cli/services/ensemble/__init__.py +7 -0
  181. vidbyte_cli/services/ensemble/prompts/__init__.py +6 -0
  182. vidbyte_cli/services/ensemble/prompts/implementer_system.md +546 -0
  183. vidbyte_cli/services/ensemble/prompts/implementer_turn.md +49 -0
  184. vidbyte_cli/services/ensemble/prompts/library.py +188 -0
  185. vidbyte_cli/services/ensemble/prompts/planner_system.md +728 -0
  186. vidbyte_cli/services/ensemble/prompts/planner_turn.md +6 -0
  187. vidbyte_cli/services/ensemble/prompts/role_system.md +104 -0
  188. vidbyte_cli/services/ensemble/prompts/role_turn.md +19 -0
  189. vidbyte_cli/services/ensemble/prompts/selector_final_turn.md +19 -0
  190. vidbyte_cli/services/ensemble/prompts/selector_round_turn.md +17 -0
  191. vidbyte_cli/services/ensemble/prompts/selector_system.md +89 -0
  192. vidbyte_cli/services/ensemble/runner.py +30 -0
  193. vidbyte_cli/services/ensemble/sdk.py +134 -0
  194. vidbyte_cli/services/ensemble/service.py +233 -0
  195. vidbyte_cli/services/ensemble/settings.py +60 -0
  196. vidbyte_cli/services/persistence/__init__.py +6 -0
  197. vidbyte_cli/services/persistence/prompts/__init__.py +6 -0
  198. vidbyte_cli/services/persistence/prompts/library.py +46 -0
  199. vidbyte_cli/services/persistence/prompts/persistence_system.md +1 -0
  200. vidbyte_cli/services/persistence/prompts/persistence_turn.md +3 -0
  201. vidbyte_cli/services/persistence/runner.py +35 -0
  202. vidbyte_cli/services/persistence/session.py +116 -0
  203. vidbyte_cli/services/suggestions/__init__.py +7 -0
  204. vidbyte_cli/services/suggestions/categories.py +270 -0
  205. vidbyte_cli/services/suggestions/context.py +222 -0
  206. vidbyte_cli/services/suggestions/extra_compute.py +53 -0
  207. vidbyte_cli/services/suggestions/handoff.py +173 -0
  208. vidbyte_cli/services/suggestions/message_tools.py +47 -0
  209. vidbyte_cli/services/suggestions/project.py +253 -0
  210. vidbyte_cli/services/suggestions/prompts/__init__.py +1 -0
  211. vidbyte_cli/services/suggestions/prompts/categories/__init__.py +3 -0
  212. vidbyte_cli/services/suggestions/prompts/categories/adjacent_opportunity.md +37 -0
  213. vidbyte_cli/services/suggestions/prompts/categories/alternative.md +38 -0
  214. vidbyte_cli/services/suggestions/prompts/categories/big_bets.md +37 -0
  215. vidbyte_cli/services/suggestions/prompts/categories/bottleneck.md +38 -0
  216. vidbyte_cli/services/suggestions/prompts/categories/business_growth.md +38 -0
  217. vidbyte_cli/services/suggestions/prompts/categories/completion.md +37 -0
  218. vidbyte_cli/services/suggestions/prompts/categories/continuation.md +37 -0
  219. vidbyte_cli/services/suggestions/prompts/categories/coordination.md +37 -0
  220. vidbyte_cli/services/suggestions/prompts/categories/creative_exploration.md +37 -0
  221. vidbyte_cli/services/suggestions/prompts/categories/cross_domain.md +37 -0
  222. vidbyte_cli/services/suggestions/prompts/categories/delegation.md +37 -0
  223. vidbyte_cli/services/suggestions/prompts/categories/experiment.md +37 -0
  224. vidbyte_cli/services/suggestions/prompts/categories/feedback.md +37 -0
  225. vidbyte_cli/services/suggestions/prompts/categories/goal_clarification.md +37 -0
  226. vidbyte_cli/services/suggestions/prompts/categories/immediate_next_steps.md +37 -0
  227. vidbyte_cli/services/suggestions/prompts/categories/investigation.md +37 -0
  228. vidbyte_cli/services/suggestions/prompts/categories/learning.md +37 -0
  229. vidbyte_cli/services/suggestions/prompts/categories/leverage.md +37 -0
  230. vidbyte_cli/services/suggestions/prompts/categories/long_term_directions.md +37 -0
  231. vidbyte_cli/services/suggestions/prompts/categories/preparation.md +37 -0
  232. vidbyte_cli/services/suggestions/prompts/categories/prerequisite.md +37 -0
  233. vidbyte_cli/services/suggestions/prompts/categories/prioritization.md +37 -0
  234. vidbyte_cli/services/suggestions/prompts/categories/product_experience.md +37 -0
  235. vidbyte_cli/services/suggestions/prompts/categories/quick_wins.md +37 -0
  236. vidbyte_cli/services/suggestions/prompts/categories/reframing.md +37 -0
  237. vidbyte_cli/services/suggestions/prompts/categories/risk_prevention.md +37 -0
  238. vidbyte_cli/services/suggestions/prompts/categories/simplification.md +37 -0
  239. vidbyte_cli/services/suggestions/prompts/categories/stop_or_defer.md +37 -0
  240. vidbyte_cli/services/suggestions/prompts/categories/strategy.md +37 -0
  241. vidbyte_cli/services/suggestions/prompts/categories/technical_possibilities.md +37 -0
  242. vidbyte_cli/services/suggestions/prompts/categories/verification.md +37 -0
  243. vidbyte_cli/services/suggestions/prompts/critic.md +209 -0
  244. vidbyte_cli/services/suggestions/prompts/critic_message.md +8 -0
  245. vidbyte_cli/services/suggestions/prompts/generator.md +64 -0
  246. vidbyte_cli/services/suggestions/prompts/library.py +72 -0
  247. vidbyte_cli/services/suggestions/prompts/message_generator_argument.md +1 -0
  248. vidbyte_cli/services/suggestions/prompts/message_generator_tool.md +1 -0
  249. vidbyte_cli/services/suggestions/prompts/message_parent_argument.md +1 -0
  250. vidbyte_cli/services/suggestions/prompts/message_parent_tool.md +1 -0
  251. vidbyte_cli/services/suggestions/prompts/message_receipt.md +1 -0
  252. vidbyte_cli/services/suggestions/prompts/revision.md +8 -0
  253. vidbyte_cli/services/suggestions/sdk.py +228 -0
  254. vidbyte_cli/services/suggestions/selection.py +78 -0
  255. vidbyte_cli/services/suggestions/service.py +540 -0
  256. vidbyte_cli/types/__init__.py +0 -0
  257. vidbyte_cli/types/api.py +101 -0
  258. vidbyte_cli/types/attachments.py +79 -0
  259. vidbyte_cli/types/ensemble.py +189 -0
  260. vidbyte_cli/types/provider.py +62 -0
  261. vidbyte_cli/types/research.py +260 -0
  262. vidbyte_cli/types/runtime.py +1098 -0
  263. vidbyte_cli/types/suggestions.py +518 -0
  264. vidbyte_cli-0.1.0.dist-info/METADATA +335 -0
  265. vidbyte_cli-0.1.0.dist-info/RECORD +269 -0
  266. vidbyte_cli-0.1.0.dist-info/WHEEL +5 -0
  267. vidbyte_cli-0.1.0.dist-info/entry_points.txt +2 -0
  268. vidbyte_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  269. vidbyte_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,553 @@
1
+ """`vidbyte-cli runtime stages` runs caller-defined stages on fresh Codex agents.
2
+
3
+ Every stage is described entirely on the command line: each `--stage-*` option repeats once
4
+ per stage, and occurrence i of every option describes the agent that runs stage i. There is
5
+ no stage file, because review of PR #36 asked for a surface an agent can drive from argv
6
+ alone without first writing a document to disk. Sequential mode is the default and threads
7
+ the previous reply through `{{previous}}`; parallel mode fans every stage out at once. Only
8
+ `run` charges (one cent); `describe` stays offline so an agent can read the tunables first.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+ from pathlib import Path
15
+ from uuid import uuid4
16
+
17
+ import click
18
+
19
+ from ...lib.constants.runtime import StagesLimit
20
+ from ...lib.constants.runtime import StagesProgress as Progress
21
+ from ...lib.errors.failures import (
22
+ RuntimeAdmissionNotVerified,
23
+ StagesOptionCountMismatch,
24
+ StagesSettingsInvalid,
25
+ )
26
+ from ...lib.output import OutputDocument
27
+ from ...lib.runtime.context import ApplicationContext as Ctx
28
+ from ...lib.runtime_primitives.gate import RuntimeAdmissionGate
29
+ from ...lib.runtime_primitives.stages import StagesCodexSession
30
+ from ...types.provider import PROVIDER_ENV_VARS, Provider
31
+ from ...types.runtime import RuntimeAdmissionRequest as AdmitRequest
32
+ from ...types.runtime import RuntimeGrantVerificationRequest as VerifyRequest
33
+ from ...types.runtime import RuntimeHost as Host
34
+ from ...types.runtime import (
35
+ StageApproval,
36
+ StageEffort,
37
+ StagePersonality,
38
+ StageSandbox,
39
+ StageSpec,
40
+ StagesSettings,
41
+ StageSummary,
42
+ )
43
+
44
+ _GROUP_HELP = (
45
+ "Run one task through several ordered stages, each on its own brand-new Codex agent. A "
46
+ "stage is a complete agent configuration - its own system prompt, turn prompt, model, "
47
+ "effort, and sandbox - so an auditing stage and an implementing stage no longer have to "
48
+ "share one set of standing instructions. Stages are described entirely with repeated "
49
+ "--stage-* options on the run subcommand, never from a file, so a caller can build a "
50
+ "whole staged flow in a single invocation. Run charges a flat one-cent Vidbyte admission "
51
+ "however many stages the run holds, while every model call is billed to your own OpenAI "
52
+ "account. Describe is offline and free, and prints every option a stage accepts."
53
+ )
54
+ _RUN_HELP = (
55
+ "Admit and run every stage of TASK, one fresh Codex agent per stage, on this machine. "
56
+ "TASK is the single top-level statement of what the whole run is for; it is what the "
57
+ "first stage sees in place of {{previous}}, and in parallel mode what every stage sees. "
58
+ "Stages are built by position from the repeated --stage-* options: the first "
59
+ "--stage-prompt and --stage-system-prompt describe stage 1, the second pair describes "
60
+ "stage 2, and so on, so --stage-prompt is what decides how many stages run. Each agent "
61
+ "is discarded when its stage ends, which is the whole point of staging - no thread "
62
+ "history, tool state, or context ever crosses a stage boundary. Vidbyte charges one cent "
63
+ "to admit the run and refuses to start any stage until that admission is verified."
64
+ )
65
+ _DESCRIBE_HELP = (
66
+ "Print every option a stage accepts, with the legal values for each closed-value setting, "
67
+ "and exit. This subcommand touches no network, buys no admission, starts no agent, and "
68
+ "costs nothing, so it is safe to call before deciding how to shape a run. It is written "
69
+ "for a caller assembling a stages invocation programmatically: it names each --stage-* "
70
+ "option, says what one occurrence of it configures, and lists the exact words the closed "
71
+ "sets accept. Read it once and you have the whole surface without needing this "
72
+ "repository. The same text is what --help expands on for each individual option."
73
+ )
74
+ _STAGE_PROMPT_HELP = (
75
+ "The turn prompt for one stage, repeated once per stage in the order the stages run. This "
76
+ "option is what defines the run: give it three times and three stages run, in that order. "
77
+ "The token {{previous}} inside a prompt is replaced before the turn starts - in sequential "
78
+ "mode with the previous stage's entire output, and for stage 1, or for every stage in "
79
+ "parallel mode, with TASK itself. Write each prompt as a complete standalone instruction, "
80
+ "because the agent running it has no thread history and has never seen the other stages. A "
81
+ "run accepts between 1 and 25 prompts, and a blank one is rejected before payment."
82
+ )
83
+ _STAGE_SYSTEM_PROMPT_HELP = (
84
+ "The system prompt for one stage, repeated once per stage in the same order as "
85
+ "--stage-prompt. It must be given exactly as many times as --stage-prompt, because a "
86
+ "stage without standing instructions of its own would silently inherit nothing. This is "
87
+ "where a stage's role lives: what it is for, what it must not do, and what shape its "
88
+ "output should take for whichever stage reads it next. It is applied when the stage's "
89
+ "agent is built and is discarded with that agent, so it never leaks into another stage. A "
90
+ "blank system prompt is rejected before credentials are read or any admission is bought."
91
+ )
92
+ _STAGE_NAME_HELP = (
93
+ "A short label for one stage, repeated once per stage in the same order as "
94
+ "--stage-prompt. Omit it entirely and the stages are named stage-1, stage-2, and so on, "
95
+ "which is fine for a run you are not going to read progress output from. Give it and the "
96
+ "name is used as the agent's name and in the per-stage progress lines, which is what makes "
97
+ "a failure in a long run readable. Names need not be unique, are never sent to the backend "
98
+ "with the admission request, and have no effect on ordering. Either give one per stage or "
99
+ "give none at all; a partial list is rejected as a count mismatch."
100
+ )
101
+ _STAGE_MODEL_HELP = (
102
+ "The model id one stage's agent runs on, repeated once per stage in the same order as "
103
+ "--stage-prompt. Omit the option and every stage uses whatever model the installed Codex "
104
+ "is already configured with, which is usually what you want. Pass an empty string for a "
105
+ "single stage to leave that one stage on the configured default while pinning the others. "
106
+ "This is the only stage setting with an open value set, so an unrecognized id is not "
107
+ "caught here and fails inside Codex after the admission has already been charged. Use it "
108
+ "to put a cheap model on early stages and a strong one on the stage that does the work."
109
+ )
110
+ _STAGE_EFFORT_HELP = (
111
+ "How much reasoning one stage's agent spends before answering, repeated once per stage in "
112
+ "the same order as --stage-prompt. Accepted values are none, minimal, low, medium, high, "
113
+ "and xhigh, and the default for every stage is medium. Higher effort costs more tokens on "
114
+ "your own OpenAI account and takes longer, and it is the single setting that most changes "
115
+ "what a stage is capable of. Analysis, planning, and review stages usually justify high, "
116
+ "while mechanical or formatting stages rarely do. Because the value set is closed, a "
117
+ "misspelling is rejected by the parser instead of quietly falling back to a default."
118
+ )
119
+ _STAGE_SUMMARY_HELP = (
120
+ "How much of its own reasoning one stage's agent reports back, repeated once per stage in "
121
+ "the same order as --stage-prompt. Accepted values are none, auto, concise, and detailed, "
122
+ "and the default for every stage is auto, which lets Codex decide. This changes what you "
123
+ "read, not what the agent does, so it affects neither the result nor the one-cent "
124
+ "admission. Choose detailed when a stage exists to show its reasoning to the stage that "
125
+ "reads it next, and none when the summary is noise. Like the other closed-value settings, "
126
+ "an unrecognized word fails at parse time, before any credential or wallet is touched."
127
+ )
128
+ _STAGE_SANDBOX_HELP = (
129
+ "How far one stage's agent may reach into the filesystem, repeated once per stage in the "
130
+ "same order as --stage-prompt. Accepted values are read-only, workspace-write, and "
131
+ "full-access, and the default for every stage is workspace-write. Codex itself enforces "
132
+ "the sandbox rather than this CLI, so it is a real boundary and not a hint to the model. "
133
+ "Give read-only to stages that only inspect, audit, or plan, and reserve workspace-write "
134
+ "for the stages that actually have to change files. full-access exists for the rare stage "
135
+ "that must reach outside the working directory and is never right for an unattended run."
136
+ )
137
+ _STAGE_APPROVAL_HELP = (
138
+ "What one stage's agent does when an action needs approval, repeated once per stage in the "
139
+ "same order as --stage-prompt. Accepted values are auto_review and deny_all, and the "
140
+ "default for every stage is auto_review. A staged run is not interactive, so neither value "
141
+ "ever stops to prompt you: auto_review lets Codex review the action and proceed on its "
142
+ "own, which is what keeps the run moving, while deny_all refuses it outright. Pair "
143
+ "deny_all with a read-only sandbox for a stage that must not act under any circumstances. "
144
+ "Both words come from a closed set that is validated before the wallet is touched."
145
+ )
146
+ _STAGE_PERSONALITY_HELP = (
147
+ "The response personality one stage's agent writes in, repeated once per stage in the same "
148
+ "order as --stage-prompt. Accepted values are none, friendly, and pragmatic, and the "
149
+ "default for every stage is none. It changes tone and framing only - never capability, "
150
+ "tool use, sandboxing, or cost. It is worth setting on a stage whose output a person will "
151
+ "read and worth leaving alone on a stage whose output is consumed by the next stage. Each "
152
+ "stage carries its own, so the stages of one run need not agree. An unrecognized value is "
153
+ "rejected at parse time along with the rest of the closed-value options."
154
+ )
155
+ _STAGE_CONTEXT_HELP = (
156
+ "Extra turn-scoped context for one stage, repeated once per stage in the same order as "
157
+ "--stage-prompt. Use it for reference material rather than instruction: the file excerpt, "
158
+ "the schema, or the prior decision a stage needs in front of it but that does not belong "
159
+ "in the prompt itself. It is applied to that one stage only and never reaches another, so "
160
+ "context two stages both need has to be given to both. Omit the option and every stage "
161
+ "runs with none, which is the right choice when the prompt already says everything. It "
162
+ "counts against the same token budget the prompt does, billed to your own OpenAI account."
163
+ )
164
+ _STAGE_IMAGE_HELP = (
165
+ "One image input for one stage, repeated once per stage in the same order as "
166
+ "--stage-prompt. Give an https:// URL, an http:// URL, or a data: URL to send a remote "
167
+ "image, and give anything else to send a local image file path, which is the right choice "
168
+ "for a screenshot a review stage must look at. The value travels with that stage's turn "
169
+ "only and never reaches another stage, so two stages that need the same image each need "
170
+ "their own occurrence. Omit the option and every stage runs on text alone, which is the "
171
+ "common case. A blank value is rejected before credentials are read or any admission is "
172
+ "bought."
173
+ )
174
+ _STAGE_SKILL_HELP = (
175
+ "One native Codex skill for one stage, repeated once per stage in the same order as "
176
+ "--stage-prompt. Write each value as NAME=PATH, where NAME is the skill name and PATH is "
177
+ "the skill directory the agent should load, splitting on the first equals sign so PATH "
178
+ "itself may contain further equals signs. The skill is handed to that stage's agent only "
179
+ "and never reaches another stage, so two stages that need the same skill each need their "
180
+ "own occurrence. Omit the option and no stage loads a skill, which is the right default "
181
+ "for stages that only need their prompts. A value without the separator, or with a blank "
182
+ "name or path, is rejected before credentials are read or any admission is bought."
183
+ )
184
+ _STAGE_MENTION_HELP = (
185
+ "One named resource mention for one stage, repeated once per stage in the same order as "
186
+ "--stage-prompt. Write each value as NAME=PATH, where NAME is the resource name and PATH "
187
+ "is the resource path the agent should read, splitting on the first equals sign so PATH "
188
+ "itself may contain further equals signs. The mention is handed to that stage's agent only "
189
+ "and never reaches another stage, so two stages that need the same resource each need "
190
+ "their own occurrence. Omit the option and no stage receives a mention, which is the "
191
+ "right default when the prompt already names everything. A value without the separator, "
192
+ "or with a blank name or path, is rejected before credentials are read."
193
+ )
194
+ _STAGE_OUTPUT_SCHEMA_HELP = (
195
+ "A JSON object schema string forcing one stage's agent to answer in a fixed shape, "
196
+ "repeated once per stage in the same order as --stage-prompt. Use it when the next "
197
+ 'consumer is another stage: a stage forced to return {"files": [...], "summary": '
198
+ '"..."} hands the next stage a handoff it can check mechanically instead of prose it '
199
+ "may misread. The value must parse as a JSON object when given, and an empty omission "
200
+ "means the stage answers in free prose, which stays the right choice for a final stage a "
201
+ "person reads. A value that is not a JSON object is rejected before credentials are read "
202
+ "or any admission is bought."
203
+ )
204
+ _STOP_ON_ERROR_HELP = (
205
+ "What the run does the first time a stage exhausts its retries. With --stop-on-error, "
206
+ "the default, the run halts immediately, keeps every result completed before the "
207
+ "failure, and returns that prefix rather than running work that depends on a stage which "
208
+ "never finished. With --no-stop-on-error the failed stage is recorded with a failed "
209
+ "status, a short placeholder stands in for its text so stage indices stay aligned, and "
210
+ "the next stage starts anyway. Keep the default for stages that build on each other, and "
211
+ "turn it off for independent stages where one failure should not cancel the rest."
212
+ )
213
+ _RETRIES_PER_STAGE_HELP = (
214
+ "How many extra attempts one stage receives after its first attempt fails, before the "
215
+ "run treats that stage as failed. Every retry sends the identical prompt to a brand-new "
216
+ "agent, so a retry recovers from a crashed or timed-out host rather than from a stage the "
217
+ "agent understood but could not do. Each attempt is billed to your own OpenAI account, so "
218
+ "a high retry count on a long run multiplies model cost even though the Vidbyte admission "
219
+ "is charged once. Accepted values run from 0 to 3 with a default of 1, and a stage that "
220
+ "succeeds on a retry produces exactly one result entry, never a duplicate."
221
+ )
222
+ _PARALLEL_HELP = (
223
+ "Whether the stages all run at once instead of one after another. Sequential, the default, "
224
+ "awaits each stage before starting the next and substitutes the finished output into the "
225
+ "next stage's {{previous}} token, which is what lets a later stage build on an earlier "
226
+ "one. Parallel starts every stage together and gives each one TASK as {{previous}}, so "
227
+ "stages that depend on each other's results must not be run this way. Parallel finishes "
228
+ "sooner in wall-clock time but runs every stage's model calls concurrently against your "
229
+ "own OpenAI account. The one-cent Vidbyte admission is the same under either topology."
230
+ )
231
+ _IDEMPOTENCY_KEY_HELP = (
232
+ "A caller-supplied key that makes the paid admission for this run replay-safe. Leave it "
233
+ "unset and the CLI generates a fresh key, which is correct for every new run. Reuse a key "
234
+ "from an earlier invocation only to recover an admission whose response you never saw, so "
235
+ "the wallet is not charged twice for the same run. Reusing a key does not resume or "
236
+ "deduplicate the stages themselves: every stage runs again from the first, and every model "
237
+ "call is billed to your own OpenAI account again. The key must be 8 to 128 characters of "
238
+ "letters, digits, and the punctuation . _ - and :, and is rejected before anything runs."
239
+ )
240
+ _KEY_PATTERN = r"[A-Za-z0-9._:-]{8,128}"
241
+ _SECRET_NAMES = (
242
+ "GOOGLE_API_KEY",
243
+ "VIDBYTE_API_KEY",
244
+ "RUNTIME_ADMISSION_SIGNING_KEY",
245
+ "CODEX_API_KEY",
246
+ )
247
+ _REFERENCE = (
248
+ "Stages are built from repeated --stage-* options; occurrence i of every option describes "
249
+ "the agent that runs stage i, and --stage-prompt decides how many stages there are.\n"
250
+ " --stage-prompt required, 1-25 times; the turn prompt, where {{previous}} "
251
+ "becomes the prior stage output (or TASK for stage 1 and for parallel runs)\n"
252
+ " --stage-system-prompt required, once per stage; that stage's standing instructions\n"
253
+ " --stage-name optional; defaults to stage-1, stage-2, ...\n"
254
+ " --stage-model optional; open value set, empty means the configured default\n"
255
+ " --stage-effort optional; none|minimal|low|medium|high|xhigh (default medium)\n"
256
+ " --stage-summary optional; none|auto|concise|detailed (default auto)\n"
257
+ " --stage-sandbox optional; read-only|workspace-write|full-access "
258
+ "(default workspace-write)\n"
259
+ " --stage-approval optional; auto_review|deny_all (default auto_review)\n"
260
+ " --stage-personality optional; none|friendly|pragmatic (default none)\n"
261
+ " --stage-context optional; turn-scoped reference material for that stage\n"
262
+ " --stage-image optional; one image URL or local path for that stage\n"
263
+ " --stage-skill optional; one NAME=PATH native skill for that stage\n"
264
+ " --stage-mention optional; one NAME=PATH resource mention for that stage\n"
265
+ " --stage-output-schema optional; JSON object schema forcing that stage's shape\n"
266
+ "Each optional option must be given once per stage or omitted entirely.\n"
267
+ "--parallel runs every stage at once; --sequential (the default) runs them in order.\n"
268
+ "--stop-on-error halts at the first exhausted stage; --max-retries-per-stage 0-3 "
269
+ "(default 1) retries each stage on a fresh agent with backoff."
270
+ )
271
+
272
+
273
+ class StagesCommand:
274
+ """Owns the stages group: one priced run plus the offline option reference."""
275
+
276
+ def register(self, parent: click.Group) -> None:
277
+ # Attaches a subgroup so every stage tunable surfaces in nested --help.
278
+ group = click.Group(name="stages", help=_GROUP_HELP)
279
+ self._register_run(group)
280
+ self._register_describe(group)
281
+ parent.add_command(group)
282
+
283
+ def _register_run(self, group: click.Group) -> None:
284
+ # Declares the only priced path in this group, with one repeated option per knob.
285
+ @group.command(name="run", help=_RUN_HELP)
286
+ @click.argument("task")
287
+ @click.option("--stage-prompt", "prompts", multiple=True, help=_STAGE_PROMPT_HELP)
288
+ @click.option(
289
+ "--stage-system-prompt", "systems", multiple=True, help=_STAGE_SYSTEM_PROMPT_HELP
290
+ )
291
+ @click.option("--stage-name", "names", multiple=True, help=_STAGE_NAME_HELP)
292
+ @click.option("--stage-model", "models", multiple=True, help=_STAGE_MODEL_HELP)
293
+ @click.option(
294
+ "--stage-effort",
295
+ "efforts",
296
+ multiple=True,
297
+ type=click.Choice(tuple(item.value for item in StageEffort)),
298
+ help=_STAGE_EFFORT_HELP,
299
+ )
300
+ @click.option(
301
+ "--stage-summary",
302
+ "summaries",
303
+ multiple=True,
304
+ type=click.Choice(tuple(item.value for item in StageSummary)),
305
+ help=_STAGE_SUMMARY_HELP,
306
+ )
307
+ @click.option(
308
+ "--stage-sandbox",
309
+ "sandboxes",
310
+ multiple=True,
311
+ type=click.Choice(tuple(item.value for item in StageSandbox)),
312
+ help=_STAGE_SANDBOX_HELP,
313
+ )
314
+ @click.option(
315
+ "--stage-approval",
316
+ "approvals",
317
+ multiple=True,
318
+ type=click.Choice(tuple(item.value for item in StageApproval)),
319
+ help=_STAGE_APPROVAL_HELP,
320
+ )
321
+ @click.option(
322
+ "--stage-personality",
323
+ "personalities",
324
+ multiple=True,
325
+ type=click.Choice(tuple(item.value for item in StagePersonality)),
326
+ help=_STAGE_PERSONALITY_HELP,
327
+ )
328
+ @click.option("--stage-context", "contexts", multiple=True, help=_STAGE_CONTEXT_HELP)
329
+ @click.option("--stage-image", "images", multiple=True, help=_STAGE_IMAGE_HELP)
330
+ @click.option("--stage-skill", "skills", multiple=True, help=_STAGE_SKILL_HELP)
331
+ @click.option("--stage-mention", "mentions", multiple=True, help=_STAGE_MENTION_HELP)
332
+ @click.option(
333
+ "--stage-output-schema", "schemas", multiple=True, help=_STAGE_OUTPUT_SCHEMA_HELP
334
+ )
335
+ @click.option("--parallel/--sequential", default=False, help=_PARALLEL_HELP)
336
+ @click.option(
337
+ "--stop-on-error/--no-stop-on-error",
338
+ default=True,
339
+ show_default=True,
340
+ help=_STOP_ON_ERROR_HELP,
341
+ )
342
+ @click.option(
343
+ "--max-retries-per-stage",
344
+ "retries_per_stage",
345
+ type=click.IntRange(0, 3),
346
+ default=1,
347
+ show_default=True,
348
+ help=_RETRIES_PER_STAGE_HELP,
349
+ )
350
+ @click.option("--idempotency-key", "key", default=None, help=_IDEMPOTENCY_KEY_HELP)
351
+ @click.pass_obj
352
+ def _run(
353
+ ctx: Ctx,
354
+ /,
355
+ task: str,
356
+ parallel: bool,
357
+ key: str | None,
358
+ stop_on_error: bool,
359
+ retries_per_stage: int,
360
+ **stages: object,
361
+ ) -> None:
362
+ # Delegates without normalizing the task text.
363
+ self.execute_run(ctx, task, parallel, key, stop_on_error, retries_per_stage, **stages)
364
+
365
+ def _register_describe(self, group: click.Group) -> None:
366
+ # Declares the offline tunable reference for stage authors.
367
+ @group.command(name="describe", help=_DESCRIBE_HELP)
368
+ @click.pass_obj
369
+ def _describe(ctx: Ctx) -> None:
370
+ # Delegates reference rendering to the owned method.
371
+ self.execute_describe(ctx)
372
+
373
+ def execute_run(
374
+ self,
375
+ ctx: Ctx,
376
+ task: str,
377
+ parallel: bool,
378
+ key: str | None,
379
+ stop_on_error: bool,
380
+ retries_per_stage: int,
381
+ **stages: object,
382
+ ) -> None:
383
+ # Everything before ADMISSION is free, so every rejection a caller can cause happens
384
+ # before the wallet is touched, and no stage starts until the grant is verified.
385
+ progress = ctx.output().diagnostic
386
+ progress(Progress.PREPARING)
387
+ # The key is validated first because an invalid one would otherwise surface only after
388
+ # the stages have been assembled and a host has been resolved on PATH.
389
+ resolved_key = self._resolve_key(key)
390
+ # Assembling the stages is pure argv work: it aligns the repeated options by position
391
+ # and fails closed on a count mismatch, a blank prompt, or more than the stage ceiling.
392
+ settings = self._settings(stages, parallel, stop_on_error, retries_per_stage)
393
+ planner = ctx.runtime_launch_planner()
394
+ # The plan carries only the task and the resolved host; stage prompts are local and
395
+ # never travel to the backend with the admission request.
396
+ plan = planner.build(task, Host.CODEX, Path.cwd(), "runtime.stages@1")
397
+ progress(Progress.CREDENTIALS)
398
+ # Building the session imports the SDK and filters the child environment, so a missing
399
+ # SDK or a missing provider key also fails before payment rather than after it.
400
+ session = self._session(ctx)
401
+ # prepare() records the launch paths and proves the SDK imports while the run is
402
+ # still free; agents are built lazily per attempt so a failure never reuses one.
403
+ session.prepare(plan, settings)
404
+ endpoints = ctx.runtime_endpoints()
405
+ progress(Progress.ADMISSION)
406
+ # One flat one-cent admission covers the whole run, however many stages it holds.
407
+ grant = endpoints.admit_stages(AdmitRequest(host=plan.host), resolved_key)
408
+ if grant.grant_token is None:
409
+ raise RuntimeAdmissionNotVerified("grant_token_missing")
410
+ progress(Progress.VERIFYING)
411
+ # The grant is re-read from the backend rather than trusted as returned, and the key
412
+ # hash ties that verification to this exact invocation.
413
+ hashed = RuntimeAdmissionGate.hash_idempotency_key(resolved_key)
414
+ proof = VerifyRequest(grant_token=grant.grant_token, idempotency_key_hash=hashed)
415
+ verified = endpoints.verify_grant(proof)
416
+ verdict = RuntimeAdmissionGate().verify_online(plan, grant, verified)
417
+ if not verdict.admitted:
418
+ raise RuntimeAdmissionNotVerified(verdict.reason)
419
+ progress(Progress.ADMITTED)
420
+ # The executor receives the verified verdict and no endpoints at all, which is what
421
+ # keeps the network out of stage execution.
422
+ result = ctx.runtime_executor().execute_stages(plan, settings, session, verdict)
423
+ # Only the run result reaches stdout; every phase line above went to stderr.
424
+ ctx.output().result(
425
+ OutputDocument(kind="runtime.stages", data=result.model_dump(mode="json")),
426
+ result.text,
427
+ )
428
+
429
+ def execute_describe(self, ctx: Ctx) -> None:
430
+ # Prints the option reference agents read before assembling a run.
431
+ ctx.output().result(
432
+ OutputDocument(kind="runtime.stages.reference", data={"reference": _REFERENCE}),
433
+ _REFERENCE,
434
+ )
435
+
436
+ def _settings(
437
+ self, stages: dict[str, object], parallel: bool, stop_on_error: bool, retries: int
438
+ ) -> StagesSettings:
439
+ # Builds the frozen settings by position, so occurrence i of every option is stage i.
440
+ prompts = self._values(stages, "prompts")
441
+ systems = self._aligned(stages, "systems", len(prompts))
442
+ names = self._aligned(stages, "names", len(prompts))
443
+ models = self._aligned(stages, "models", len(prompts))
444
+ efforts = self._aligned(stages, "efforts", len(prompts))
445
+ summaries = self._aligned(stages, "summaries", len(prompts))
446
+ sandboxes = self._aligned(stages, "sandboxes", len(prompts))
447
+ approvals = self._aligned(stages, "approvals", len(prompts))
448
+ personalities = self._aligned(stages, "personalities", len(prompts))
449
+ contexts = self._aligned(stages, "contexts", len(prompts))
450
+ images = self._aligned(stages, "images", len(prompts))
451
+ skills = self._aligned(stages, "skills", len(prompts))
452
+ mentions = self._aligned(stages, "mentions", len(prompts))
453
+ schemas = self._aligned(stages, "schemas", len(prompts))
454
+ if not prompts or len(prompts) > StagesLimit.MAX_STAGES or not systems:
455
+ raise StagesSettingsInvalid()
456
+ specs = [
457
+ self._spec(
458
+ index,
459
+ prompts[index],
460
+ systems[index],
461
+ names[index] if names else "",
462
+ models[index] if models else "",
463
+ efforts[index] if efforts else StageEffort.MEDIUM.value,
464
+ summaries[index] if summaries else StageSummary.AUTO.value,
465
+ sandboxes[index] if sandboxes else StageSandbox.WORKSPACE_WRITE.value,
466
+ approvals[index] if approvals else StageApproval.AUTO_REVIEW.value,
467
+ personalities[index] if personalities else StagePersonality.NONE.value,
468
+ contexts[index] if contexts else "",
469
+ images[index] if images else "",
470
+ skills[index] if skills else "",
471
+ mentions[index] if mentions else "",
472
+ schemas[index] if schemas else "",
473
+ )
474
+ for index in range(len(prompts))
475
+ ]
476
+ try:
477
+ return StagesSettings(
478
+ stages=tuple(specs),
479
+ parallel=parallel,
480
+ stop_on_error=stop_on_error,
481
+ max_retries_per_stage=retries,
482
+ )
483
+ except ValueError as error:
484
+ raise StagesSettingsInvalid() from error
485
+
486
+ def _spec(
487
+ self,
488
+ index: int,
489
+ prompt: str,
490
+ system_prompt: str,
491
+ name: str,
492
+ model: str,
493
+ effort: str,
494
+ summary: str,
495
+ sandbox: str,
496
+ approval: str,
497
+ personality: str,
498
+ context: str,
499
+ image: str,
500
+ skill: str,
501
+ mention: str,
502
+ schema: str,
503
+ ) -> StageSpec:
504
+ # Names an unnamed stage by position so progress output stays readable either way.
505
+ try:
506
+ return StageSpec(
507
+ name=name or f"stage-{index + 1}",
508
+ prompt=prompt,
509
+ system_prompt=system_prompt,
510
+ model=model,
511
+ effort=StageEffort(effort),
512
+ summary=StageSummary(summary),
513
+ sandbox=StageSandbox(sandbox),
514
+ approval=StageApproval(approval),
515
+ personality=StagePersonality(personality),
516
+ additional_context=context,
517
+ image=image,
518
+ skill=skill,
519
+ mention=mention,
520
+ output_schema=schema,
521
+ )
522
+ except ValueError as error:
523
+ raise StagesSettingsInvalid() from error
524
+
525
+ def _values(self, stages: dict[str, object], key: str) -> tuple[str, ...]:
526
+ # Reads one repeated option as the tuple of strings Click collected for it.
527
+ collected = stages.get(key, ())
528
+ if not isinstance(collected, tuple):
529
+ raise StagesSettingsInvalid()
530
+ return tuple(str(item) for item in collected)
531
+
532
+ def _aligned(self, stages: dict[str, object], key: str, count: int) -> tuple[str, ...]:
533
+ # An optional option is legal only when omitted entirely or given once per stage.
534
+ values = self._values(stages, key)
535
+ if values and len(values) != count:
536
+ raise StagesOptionCountMismatch()
537
+ return values
538
+
539
+ def _resolve_key(self, key: str | None) -> str:
540
+ # Generates or validates the replay-safe admission key.
541
+ resolved = key or str(uuid4())
542
+ if re.fullmatch(_KEY_PATTERN, resolved) is None:
543
+ raise click.BadParameter("Use 8-128 key chars: letters, digits, ._-:.")
544
+ return resolved
545
+
546
+ def _session(self, ctx: Ctx) -> StagesCodexSession:
547
+ # Sanitizes the child environment so only the selected key is inherited.
548
+ credentials = ctx.require_provider_credentials(Provider.OPENAI)
549
+ environment = dict(ctx.environment)
550
+ for name in (*PROVIDER_ENV_VARS.values(), *_SECRET_NAMES):
551
+ environment[name] = ""
552
+ environment["OPENAI_API_KEY"] = credentials.secret_value()
553
+ return StagesCodexSession(environment, ctx.output().diagnostic)