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,159 @@
1
+ """A service-free read of the root-option prefix, before Click parses anything.
2
+
3
+ `--format` and `--debug` decide how a *parse failure* is rendered, and Click's own syntax
4
+ errors leave through the same ErrorHandler boundary as everything else. So the root prefix is
5
+ scanned once here, cheaply, and the resulting policy is settled before Click runs. Click
6
+ remains the authoritative parser; this scan never renders and never decides an invocation.
7
+
8
+ Scanning only the prefix is the point: the scan stops at the first positional token, so no
9
+ command name or argument value can be mistaken for a root option. When syntax turns invalid
10
+ after a valid machine-output prefix, that output policy is kept for Click's structured error.
11
+
12
+ An unpassed option stays `None` rather than taking a default here. These values are the
13
+ top layer of configuration precedence, and a default invented at the parser would outrank
14
+ the profile the user actually stored.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from collections.abc import Mapping, Sequence
20
+ from dataclasses import dataclass
21
+ from typing import cast
22
+
23
+ from ..output.formats import ColorMode, OutputFormat
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class RootOptionValues:
28
+ """Typed root values, from either this scanner or Click's keyword callback."""
29
+
30
+ output_format: str | None
31
+ as_json: bool
32
+ profile: str | None
33
+ no_input: bool
34
+ color: str | None
35
+ debug: bool
36
+
37
+ @classmethod
38
+ def from_click(cls, values: Mapping[str, object]) -> RootOptionValues:
39
+ # Click validates primitive types before this typed reconstruction.
40
+ return cls(
41
+ output_format=cast(str | None, values["output_format"]),
42
+ as_json=cast(bool, values["as_json"]),
43
+ profile=cast(str | None, values["profile"]),
44
+ no_input=cast(bool, values["no_input"]),
45
+ color=cast(str | None, values["color"]),
46
+ debug=cast(bool, values["debug"]),
47
+ )
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class RootInspection:
52
+ """Validated root policy, and whether the invocation exits before any command runs."""
53
+
54
+ values: RootOptionValues
55
+ exits_before_command: bool = False
56
+
57
+
58
+ class RootOptionInspector:
59
+ """One scan of one argv, holding its own cursor and accumulated root values."""
60
+
61
+ _FLAGS = frozenset({"--json", "--no-input", "--debug"})
62
+ _VALUED = frozenset({"--format", "--profile", "--color"})
63
+
64
+ def __init__(self, argv: Sequence[str]) -> None:
65
+ self._tokens = list(argv[1:])
66
+ self._index = 0
67
+ self._output_format: str | None = None
68
+ self._as_json = False
69
+ self._profile: str | None = None
70
+ self._no_input = False
71
+ self._color: str | None = None
72
+ self._debug = False
73
+
74
+ def inspect(self) -> RootInspection | None:
75
+ # Root flags precede the command, so the first positional token ends the prefix.
76
+ exits_before_command = False
77
+ while self._index < len(self._tokens):
78
+ token = self._tokens[self._index]
79
+ if token == "--":
80
+ self._index += 1
81
+ break
82
+ if not token.startswith("-"):
83
+ break
84
+ name, separator, inline_value = token.partition("=")
85
+ if name in {"--help", "--version"}:
86
+ exits_before_command = True
87
+ break
88
+ if not self._consume(name, separator, inline_value):
89
+ return self._invalid()
90
+ self._index += 1
91
+ if not self._valid():
92
+ return None
93
+ return RootInspection(self._freeze(), exits_before_command)
94
+
95
+ def _consume(self, name: str, separator: str, inline_value: str) -> bool:
96
+ # False means "this is not a root option Click would accept" — never a guess.
97
+ if name in self._FLAGS:
98
+ # Click rejects `--flag=value` for boolean flags, so this scanner must too.
99
+ if separator:
100
+ return False
101
+ self._apply_flag(name)
102
+ return True
103
+ if name not in self._VALUED:
104
+ return False
105
+ value = self._read_value(separator, inline_value)
106
+ if value is None:
107
+ return False
108
+ self._apply_value(name, value)
109
+ return True
110
+
111
+ def _read_value(self, separator: str, inline_value: str) -> str | None:
112
+ # Both Click spellings are accepted: `--name=value` and `--name value`.
113
+ if separator:
114
+ return inline_value
115
+ if self._index + 1 >= len(self._tokens):
116
+ return None
117
+ self._index += 1
118
+ return self._tokens[self._index]
119
+
120
+ def _apply_flag(self, name: str) -> None:
121
+ if name == "--json":
122
+ self._as_json = True
123
+ elif name == "--no-input":
124
+ self._no_input = True
125
+ else:
126
+ self._debug = True
127
+
128
+ def _apply_value(self, name: str, value: str) -> None:
129
+ # Normalize choice values exactly as Click's case-insensitive choices do.
130
+ if name == "--format":
131
+ self._output_format = value.lower()
132
+ elif name == "--profile":
133
+ self._profile = value
134
+ else:
135
+ self._color = value.lower()
136
+
137
+ def _valid(self) -> bool:
138
+ # An invalid choice value stays service-free and is later explained by Click.
139
+ valid_formats = {None, *(item.value for item in OutputFormat)}
140
+ valid_colors = {None, *(item.value for item in ColorMode)}
141
+ return self._output_format in valid_formats and self._color in valid_colors
142
+
143
+ def _invalid(self) -> RootInspection | None:
144
+ # Returning the scanned policy rather than None is what keeps `--format json` in force
145
+ # for the machine error document Click's rejection is about to produce.
146
+ if not self._valid():
147
+ return None
148
+ return RootInspection(self._freeze())
149
+
150
+ def _freeze(self) -> RootOptionValues:
151
+ # Freeze before policy leaves the parser boundary.
152
+ return RootOptionValues(
153
+ self._output_format,
154
+ self._as_json,
155
+ self._profile,
156
+ self._no_input,
157
+ self._color,
158
+ self._debug,
159
+ )
@@ -0,0 +1,30 @@
1
+ """One source for the CLI's version: the installed distribution's own metadata.
2
+
3
+ pyproject.toml stays the release source of truth; nothing here duplicates the release
4
+ number. A raw source checkout has no distribution record, so it reports a dev marker rather
5
+ than impersonating a release.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from importlib.metadata import PackageNotFoundError, version
11
+
12
+ _DISTRIBUTION_NAME = "vidbyte-cli"
13
+ # Deliberately not "0.1.0": a source tree must not claim to be the released version.
14
+ _SOURCE_TREE_VERSION = "0.1.0.dev0"
15
+
16
+
17
+ class VersionProvider:
18
+ """Resolves the version of the installed distribution."""
19
+
20
+ def current(self) -> str:
21
+ # Editable/source execution has no metadata; fall back rather than raise.
22
+ try:
23
+ return version(_DISTRIBUTION_NAME)
24
+ except PackageNotFoundError:
25
+ return _SOURCE_TREE_VERSION
26
+
27
+
28
+ def current_version() -> str:
29
+ # Keeps callers independent of importlib.metadata and the distribution name.
30
+ return VersionProvider().current()
@@ -0,0 +1,28 @@
1
+ """Local planning boundaries for runtime primitives.
2
+
3
+ Exports discovery, planning, admission verification, and the guarded executor.
4
+ Each primitive's own algorithm lives in `services/`; only what all of them share is here.
5
+ """
6
+
7
+ from .executor import RuntimeExecutor
8
+ from .gate import RuntimeAdmissionGate
9
+ from .hosts import RuntimeHostRegistry
10
+ from .planner import RuntimeLaunchPlanner
11
+ from .stages import StagesCodexSession
12
+ from .task_board import TaskBoardCodexSession, TaskBoardSummarizer
13
+ from .task_board_checkpoints import TaskBoardCheckpointer
14
+ from .task_board_tasks import TaskBoardTaskFiles
15
+ from .verification import RuntimeGrantVerifier
16
+
17
+ __all__ = [
18
+ "RuntimeAdmissionGate",
19
+ "RuntimeExecutor",
20
+ "RuntimeGrantVerifier",
21
+ "RuntimeHostRegistry",
22
+ "RuntimeLaunchPlanner",
23
+ "StagesCodexSession",
24
+ "TaskBoardCheckpointer",
25
+ "TaskBoardCodexSession",
26
+ "TaskBoardSummarizer",
27
+ "TaskBoardTaskFiles",
28
+ ]
@@ -0,0 +1,54 @@
1
+ """Execution boundary for admitted local runtime primitives, and the one verdict policy.
2
+
3
+ Task-board and stages have implementations; adversarial-team retains its explicit scaffold.
4
+ Every process-launching path requires a matching deterministic admission verdict, which
5
+ `require_verdict` is the single source of. `persistence` and `same-host-ensemble` are
6
+ deliberately absent: they run in `services/persistence/` and `services/ensemble/`, because
7
+ a service may depend on `lib/` while nothing in `lib/` may depend on a service.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import NoReturn
13
+
14
+ from ...types.runtime import RuntimeAdmissionVerdict as Verdict
15
+ from ...types.runtime import RuntimeLaunchPlan as Plan
16
+ from ...types.runtime import StagesResult as Outcome
17
+ from ...types.runtime import StagesSettings as Tune
18
+ from ...types.runtime import TaskBoardResult as BoardResult
19
+ from ...types.runtime import TaskBoardSettings as BoardSettings
20
+ from ..errors.failures import RuntimeAdmissionNotVerified, RuntimeExecutionNotImplemented
21
+ from .stages import StagesCodexSession as StageHost
22
+ from .task_board import TaskBoardCodexSession as BoardSession
23
+
24
+
25
+ class RuntimeExecutor:
26
+ """Checks admission at the final boundary before an agent can start."""
27
+
28
+ def execute_adversarial_team(self, plan: Plan, verdict: Verdict | None = None) -> NoReturn:
29
+ # Keeps the existing primitive inert after validating its admission.
30
+ self.require_verdict(plan, verdict)
31
+ raise RuntimeExecutionNotImplemented()
32
+
33
+ def execute_task_board(
34
+ self, plan: Plan, settings: BoardSettings, host: BoardSession, proof: Verdict
35
+ ) -> BoardResult:
36
+ # Runs the ordered board only after its exact admission is verified.
37
+ self.require_verdict(plan, proof)
38
+ if plan.capability_id != "runtime.task-board@1" or plan.host.value != "codex":
39
+ raise RuntimeAdmissionNotVerified("task_board_plan_invalid")
40
+ return host.run(plan, settings, proof.admission_id)
41
+
42
+ def execute_stages(self, plan: Plan, tune: Tune, host: StageHost, proof: Verdict) -> Outcome:
43
+ # The stages cannot run until the one-cent stages admission is verified.
44
+ self.require_verdict(plan, proof)
45
+ if plan.capability_id != "runtime.stages@1" or plan.host.value != "codex":
46
+ raise RuntimeAdmissionNotVerified("stages_plan_invalid")
47
+ return host.run(plan, tune, proof.admission_id)
48
+
49
+ def require_verdict(self, plan: Plan, verdict: Verdict | None) -> None:
50
+ # Rejects absent, false, mismatched or empty receipts independently of command checks.
51
+ if verdict is None or not verdict.admitted:
52
+ raise RuntimeAdmissionNotVerified()
53
+ if verdict.capability_id != plan.capability_id or not verdict.admission_id.strip():
54
+ raise RuntimeAdmissionNotVerified()
@@ -0,0 +1,105 @@
1
+ """Deterministic receipt policy shared by offline and authenticated online verification."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ from datetime import UTC, datetime
7
+
8
+ from ...types.runtime import RuntimeAdmissionCheck as Check
9
+ from ...types.runtime import RuntimeAdmissionGrant as Grant
10
+ from ...types.runtime import RuntimeAdmissionVerdict as Verdict
11
+ from ...types.runtime import RuntimeLaunchPlan as Plan
12
+ from ..constants.runtime import AdmissionReason as Reason
13
+ from .verification import RuntimeGrantVerifier
14
+
15
+ _ALLOWED_PRICES = {
16
+ "runtime.review.adversarial-team@1": 25,
17
+ "runtime.adversarial-team@1": 25,
18
+ "runtime.same-host-ensemble@1": 2,
19
+ "runtime.persistence@1": 2,
20
+ "runtime.task-board@1": 2,
21
+ "runtime.stages@1": 1,
22
+ }
23
+ _MAX_TTL_SECONDS = 3600
24
+ Key = str | None
25
+
26
+
27
+ class RuntimeAdmissionGate:
28
+ """Checks local policy after a cryptographic verification boundary."""
29
+
30
+ def __init__(self, verifier: RuntimeGrantVerifier | None = None) -> None:
31
+ # Shares the strict payload decoder with offline verification.
32
+ self._verifier = verifier or RuntimeGrantVerifier()
33
+
34
+ def verify(self, plan: Plan, grant: Grant | None, now: datetime | None, key: Key) -> Verdict:
35
+ # Offline callers must supply a key; CLI execution uses backend verification.
36
+ current = now or datetime.now(UTC)
37
+ check = self._check_policy(plan, grant, current)
38
+ if check.passed and grant is not None:
39
+ check = self._check_signature(grant, current, key)
40
+ return self._verdict(plan, grant, check)
41
+
42
+ def verify_online(self, plan: Plan, grant: Grant, verified: Grant) -> Verdict:
43
+ # The backend confirms signature, ownership and Mongo payment evidence for either method.
44
+ check = self._check_policy(plan, grant, datetime.now(UTC))
45
+ if grant != verified:
46
+ check = Check(Reason.RECEIPT_MISMATCH)
47
+ return self._verdict(plan, grant, check)
48
+
49
+ def _check_policy(self, plan: Plan, grant: Grant | None, now: datetime) -> Check:
50
+ # Rejects unknown products, price drift and unbounded receipt lifetimes.
51
+ if grant is None:
52
+ return Check(Reason.MISSING)
53
+ if grant.capability_id != plan.capability_id:
54
+ return Check(Reason.CAPABILITY_MISMATCH)
55
+ if grant.charged_cents != _ALLOWED_PRICES.get(grant.capability_id):
56
+ return Check(Reason.PRICE_MISMATCH)
57
+ if not grant.admission_id.strip() or grant.execution_location != "local":
58
+ return Check(Reason.LOCATION_OR_ID_INVALID)
59
+ if not grant.grant_token:
60
+ return Check(Reason.TOKEN_MISSING)
61
+ return self._check_time(grant, now)
62
+
63
+ def _check_time(self, grant: Grant, now: datetime) -> Check:
64
+ # Both timestamps must be aware, current and bounded independently of token claims.
65
+ start, end = grant.admitted_at, grant.expires_at
66
+ if end is None or start.tzinfo is None or end.tzinfo is None or now.tzinfo is None:
67
+ return Check(Reason.TIME_INVALID)
68
+ if end <= now:
69
+ return Check(Reason.EXPIRED)
70
+ if start > now or end <= start:
71
+ return Check(Reason.TIME_INVALID)
72
+ if (end - start).total_seconds() > _MAX_TTL_SECONDS:
73
+ return Check(Reason.TTL_INVALID)
74
+ return Check(Reason.PASSED)
75
+
76
+ def _check_signature(self, grant: Grant, now: datetime, key: str | None) -> Check:
77
+ # A valid token must bind every public signed field to this receipt.
78
+ if not key or not key.strip():
79
+ return Check(Reason.VERIFICATION_KEY_MISSING)
80
+ try:
81
+ signed = self._verifier.verify(grant.grant_token or "", key, now)
82
+ except ValueError:
83
+ return Check(Reason.SIGNATURE_INVALID)
84
+ matches = (
85
+ signed.admission_id == grant.admission_id,
86
+ f"{signed.capability_id}@{signed.version}" == grant.capability_id,
87
+ signed.charged_cents == grant.charged_cents,
88
+ signed.admitted_at == grant.admitted_at,
89
+ signed.expires_at == grant.expires_at,
90
+ )
91
+ return Check(Reason.PASSED if all(matches) else Reason.SIGNED_CLAIMS_MISMATCH)
92
+
93
+ def _verdict(self, plan: Plan, grant: Grant | None, check: Check) -> Verdict:
94
+ # Exposes safe policy metadata without the token or caller identity.
95
+ return Verdict(
96
+ admitted=check.passed,
97
+ admission_id=grant.admission_id if grant else "rta_missing",
98
+ capability_id=plan.capability_id,
99
+ reason=None if check.passed else check.reason.value,
100
+ )
101
+
102
+ @staticmethod
103
+ def hash_idempotency_key(key: str) -> str:
104
+ # Binds the invocation without transmitting its raw key to verification.
105
+ return hashlib.sha256(key.encode("utf-8")).hexdigest()
@@ -0,0 +1,45 @@
1
+ """PATH-only discovery for supported native coding-agent executables.
2
+
3
+ Discovery never invokes a host, reads its configuration, or inspects environment values.
4
+ An explicit unavailable host is never silently replaced with a different agent.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import shutil
10
+ from collections.abc import Callable
11
+
12
+ from ...types.runtime import RuntimeHost, RuntimeHostStatus
13
+ from ..errors.failures import RuntimeHostUnavailable
14
+
15
+ _HOST_ORDER = (RuntimeHost.CODEX, RuntimeHost.CLAUDE, RuntimeHost.OPENCODE)
16
+
17
+
18
+ class RuntimeHostRegistry:
19
+ """Discovers and selects the user's installed native coding-agent hosts."""
20
+
21
+ def __init__(self, which: Callable[[str], str | None] = shutil.which) -> None:
22
+ # Makes PATH resolution injectable while using the operating system by default.
23
+ self._which = which
24
+
25
+ def inspect(self) -> tuple[RuntimeHostStatus, ...]:
26
+ # Returns stable host order so human and machine output remain predictable.
27
+ return tuple(self._status(host) for host in _HOST_ORDER)
28
+
29
+ def resolve(self, requested: RuntimeHost | None) -> RuntimeHostStatus:
30
+ # Honors explicit selection; auto mode chooses the first available reviewed host.
31
+ statuses = self.inspect()
32
+ if requested is not None:
33
+ selected = next(status for status in statuses if status.host is requested)
34
+ if not selected.available:
35
+ raise RuntimeHostUnavailable(requested.value)
36
+ return selected
37
+ automatic = next((status for status in statuses if status.available), None)
38
+ if automatic is None:
39
+ raise RuntimeHostUnavailable("codex, claude, or opencode")
40
+ return automatic
41
+
42
+ def _status(self, host: RuntimeHost) -> RuntimeHostStatus:
43
+ # Records only the resolved executable path, never host configuration or credentials.
44
+ executable = self._which(host.value)
45
+ return RuntimeHostStatus(host=host, available=executable is not None, executable=executable)
@@ -0,0 +1,79 @@
1
+ """Builds the local-only handoff for a future runtime executor.
2
+
3
+ The plan retains task and filesystem context in process memory. Nothing here contacts the
4
+ backend, serializes the environment, or launches an agent.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+ from typing import Literal
11
+
12
+ from ...types.runtime import RuntimeHost as Host
13
+ from ...types.runtime import RuntimeLaunchPlan as Plan
14
+ from ..constants.runtime import TaskBoardLimit
15
+ from ..errors.failures import (
16
+ RuntimeHostUnavailable,
17
+ RuntimeTaskInvalid,
18
+ RuntimeWorkingDirectoryInvalid,
19
+ )
20
+ from .hosts import RuntimeHostRegistry
21
+
22
+ Product = Literal[
23
+ "runtime.review.adversarial-team@1",
24
+ "runtime.adversarial-team@1",
25
+ "runtime.same-host-ensemble@1",
26
+ "runtime.persistence@1",
27
+ "runtime.task-board@1",
28
+ "runtime.stages@1",
29
+ ]
30
+
31
+
32
+ class RuntimeLaunchPlanner:
33
+ """Validates local prerequisites and creates an inert launch plan."""
34
+
35
+ def __init__(self, hosts: RuntimeHostRegistry) -> None:
36
+ # Uses one registry so doctor and execution selection share host semantics.
37
+ self._hosts = hosts
38
+
39
+ def build(self, task: str, host: Host | None, cwd: Path, capability_id: Product) -> Plan:
40
+ # Validates everything needed before a future paid admission can be requested.
41
+ normalized_task = task.strip()
42
+ if not normalized_task or len(task) > 20_000:
43
+ raise RuntimeTaskInvalid()
44
+ resolved_directory = cwd.resolve()
45
+ if not resolved_directory.is_dir():
46
+ raise RuntimeWorkingDirectoryInvalid()
47
+ selected = self._hosts.resolve(host)
48
+ if selected.executable is None:
49
+ raise RuntimeHostUnavailable(selected.host.value)
50
+ return Plan(
51
+ capability_id=capability_id,
52
+ host=selected.host,
53
+ executable=Path(selected.executable),
54
+ working_directory=resolved_directory,
55
+ task=task,
56
+ )
57
+
58
+ def build_task_board(self, tasks: tuple[str, ...], host: Host | None, cwd: Path) -> Plan:
59
+ # Validates board count and chars against the shared bounds, then builds a label-only
60
+ # plan; the bounds live in TaskBoardLimit so the settings model cannot drift from here.
61
+ if not tasks or len(tasks) > TaskBoardLimit.MAX_TASKS:
62
+ raise RuntimeTaskInvalid()
63
+ for task in tasks:
64
+ if not task.strip() or len(task) > TaskBoardLimit.MAX_TASK_CHARS:
65
+ raise RuntimeTaskInvalid()
66
+ resolved_directory = cwd.resolve()
67
+ if not resolved_directory.is_dir():
68
+ raise RuntimeWorkingDirectoryInvalid()
69
+ selected = self._hosts.resolve(host)
70
+ if selected.executable is None:
71
+ raise RuntimeHostUnavailable(selected.host.value)
72
+ label = tasks[0][:200] if len(tasks[0]) > 200 else tasks[0]
73
+ return Plan(
74
+ capability_id="runtime.task-board@1",
75
+ host=selected.host,
76
+ executable=Path(selected.executable),
77
+ working_directory=resolved_directory,
78
+ task=f"Task board with {len(tasks)} tasks starting with: {label}",
79
+ )