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.
- vidbyte_cli/__init__.py +11 -0
- vidbyte_cli/__main__.py +6 -0
- vidbyte_cli/cli.py +18 -0
- vidbyte_cli/commands/__init__.py +86 -0
- vidbyte_cli/commands/agent_options.py +53 -0
- vidbyte_cli/commands/agents/__init__.py +31 -0
- vidbyte_cli/commands/agents/suggestion/__init__.py +38 -0
- vidbyte_cli/commands/agents/suggestion/prompts/__init__.py +3 -0
- vidbyte_cli/commands/agents/suggestion/prompts/all_categories.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/approaches.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/avoid.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/blockers.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/capability.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/categories.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/category.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/completed.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/constraint.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/context.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/count.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/critic_model.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/decision.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/dry_run.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/extra_compute.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/feedback_accept.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/feedback_group.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/feedback_project.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/feedback_reason.md +98 -0
- vidbyte_cli/commands/agents/suggestion/prompts/feedback_reject.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/feedback_suggestion.md +101 -0
- vidbyte_cli/commands/agents/suggestion/prompts/files.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/forbidden.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/goal.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/handoff.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/horizon.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/hypotheses.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/idea.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/in_progress.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/input.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/library.py +29 -0
- vidbyte_cli/commands/agents/suggestion/prompts/max_messages.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/max_output_tokens.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/max_total_tokens.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/mistakes.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/outcomes.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project_create.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project_description.md +98 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project_group.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project_key.md +96 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project_list.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project_title.md +99 -0
- vidbyte_cli/commands/agents/suggestion/prompts/provider.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/question.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/risks.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/rounds.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/run.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/success.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/timeout.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/trajectory.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/view.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/view_all.md +5 -0
- vidbyte_cli/commands/agents/suggestion/render.py +77 -0
- vidbyte_cli/commands/agents/suggestion/request_builder.py +240 -0
- vidbyte_cli/commands/agents/suggestion/suggest.py +154 -0
- vidbyte_cli/commands/agents/suggestion/suggestion_categories.py +77 -0
- vidbyte_cli/commands/agents/suggestion/suggestion_feedback.py +85 -0
- vidbyte_cli/commands/agents/suggestion/suggestion_handoff.py +67 -0
- vidbyte_cli/commands/agents/suggestion/suggestion_projects.py +95 -0
- vidbyte_cli/commands/auth/__init__.py +0 -0
- vidbyte_cli/commands/auth/login.py +100 -0
- vidbyte_cli/commands/auth/logout.py +41 -0
- vidbyte_cli/commands/auth/whoami.py +51 -0
- vidbyte_cli/commands/billing/__init__.py +20 -0
- vidbyte_cli/commands/billing/prompts/__init__.py +1 -0
- vidbyte_cli/commands/billing/prompts/confirm.md +16 -0
- vidbyte_cli/commands/billing/prompts/group.md +13 -0
- vidbyte_cli/commands/billing/prompts/idempotency_key.md +16 -0
- vidbyte_cli/commands/billing/prompts/library.py +18 -0
- vidbyte_cli/commands/billing/prompts/top_up.md +16 -0
- vidbyte_cli/commands/billing/top_up.py +65 -0
- vidbyte_cli/commands/config/__init__.py +0 -0
- vidbyte_cli/commands/config/get.py +45 -0
- vidbyte_cli/commands/config/set.py +39 -0
- vidbyte_cli/commands/provider/__init__.py +9 -0
- vidbyte_cli/commands/provider/login.py +102 -0
- vidbyte_cli/commands/provider/logout.py +45 -0
- vidbyte_cli/commands/provider/whoami.py +49 -0
- vidbyte_cli/commands/research/__init__.py +5 -0
- vidbyte_cli/commands/research/add.py +41 -0
- vidbyte_cli/commands/research/options.py +134 -0
- vidbyte_cli/commands/research/render.py +157 -0
- vidbyte_cli/commands/research/resume.py +37 -0
- vidbyte_cli/commands/research/start.py +37 -0
- vidbyte_cli/commands/research/status.py +32 -0
- vidbyte_cli/commands/research/thread.py +30 -0
- vidbyte_cli/commands/research/threads.py +35 -0
- vidbyte_cli/commands/research/watch.py +93 -0
- vidbyte_cli/commands/runtime/__init__.py +23 -0
- vidbyte_cli/commands/runtime/adversarial_team.py +41 -0
- vidbyte_cli/commands/runtime/doctor.py +44 -0
- vidbyte_cli/commands/runtime/list.py +47 -0
- vidbyte_cli/commands/runtime/persistence.py +141 -0
- vidbyte_cli/commands/runtime/same_host_ensemble.py +221 -0
- vidbyte_cli/commands/runtime/stages.py +553 -0
- vidbyte_cli/commands/runtime/task_board.py +1342 -0
- vidbyte_cli/commands/setup/__init__.py +0 -0
- vidbyte_cli/commands/setup/doctor.py +64 -0
- vidbyte_cli/commands/setup/prompts/__init__.py +1 -0
- vidbyte_cli/commands/setup/prompts/browser.md +7 -0
- vidbyte_cli/commands/setup/prompts/library.py +18 -0
- vidbyte_cli/commands/setup/prompts/star.md +13 -0
- vidbyte_cli/commands/setup/star.py +132 -0
- vidbyte_cli/lib/__init__.py +0 -0
- vidbyte_cli/lib/api/__init__.py +0 -0
- vidbyte_cli/lib/api/client.py +236 -0
- vidbyte_cli/lib/api/endpoints/__init__.py +0 -0
- vidbyte_cli/lib/api/endpoints/auth.py +24 -0
- vidbyte_cli/lib/api/endpoints/billing.py +36 -0
- vidbyte_cli/lib/api/endpoints/research.py +103 -0
- vidbyte_cli/lib/api/endpoints/runtime.py +113 -0
- vidbyte_cli/lib/api/problem.py +86 -0
- vidbyte_cli/lib/api/response.py +101 -0
- vidbyte_cli/lib/api/retry.py +116 -0
- vidbyte_cli/lib/api/runtime_payment.py +89 -0
- vidbyte_cli/lib/auth/__init__.py +16 -0
- vidbyte_cli/lib/auth/credentials.py +223 -0
- vidbyte_cli/lib/auth/input.py +59 -0
- vidbyte_cli/lib/auth/keyring_store.py +126 -0
- vidbyte_cli/lib/auth/provider_credentials.py +157 -0
- vidbyte_cli/lib/auth/provider_input.py +54 -0
- vidbyte_cli/lib/auth/provider_resolver.py +89 -0
- vidbyte_cli/lib/auth/provider_store.py +149 -0
- vidbyte_cli/lib/auth/provider_verifier.py +219 -0
- vidbyte_cli/lib/auth/resolver.py +63 -0
- vidbyte_cli/lib/auth/verifier.py +38 -0
- vidbyte_cli/lib/config/__init__.py +25 -0
- vidbyte_cli/lib/config/atomic.py +47 -0
- vidbyte_cli/lib/config/config.py +141 -0
- vidbyte_cli/lib/config/migration.py +81 -0
- vidbyte_cli/lib/config/models.py +145 -0
- vidbyte_cli/lib/config/paths.py +66 -0
- vidbyte_cli/lib/config/resolver.py +137 -0
- vidbyte_cli/lib/constants/__init__.py +1 -0
- vidbyte_cli/lib/constants/runtime.py +186 -0
- vidbyte_cli/lib/errors/__init__.py +12 -0
- vidbyte_cli/lib/errors/cli_error.py +86 -0
- vidbyte_cli/lib/errors/codes.py +43 -0
- vidbyte_cli/lib/errors/failures.py +2910 -0
- vidbyte_cli/lib/errors/handler.py +72 -0
- vidbyte_cli/lib/files/__init__.py +6 -0
- vidbyte_cli/lib/files/documents.py +94 -0
- vidbyte_cli/lib/files/store.py +156 -0
- vidbyte_cli/lib/io/__init__.py +16 -0
- vidbyte_cli/lib/io/attachments.py +129 -0
- vidbyte_cli/lib/io/codex_attachments.py +47 -0
- vidbyte_cli/lib/io/prompt.py +71 -0
- vidbyte_cli/lib/io/streams.py +37 -0
- vidbyte_cli/lib/io/terminal.py +66 -0
- vidbyte_cli/lib/output/__init__.py +7 -0
- vidbyte_cli/lib/output/formats.py +29 -0
- vidbyte_cli/lib/output/manager.py +95 -0
- vidbyte_cli/lib/output/models.py +49 -0
- vidbyte_cli/lib/runtime/__init__.py +9 -0
- vidbyte_cli/lib/runtime/application.py +134 -0
- vidbyte_cli/lib/runtime/context.py +250 -0
- vidbyte_cli/lib/runtime/options.py +159 -0
- vidbyte_cli/lib/runtime/version.py +30 -0
- vidbyte_cli/lib/runtime_primitives/__init__.py +28 -0
- vidbyte_cli/lib/runtime_primitives/executor.py +54 -0
- vidbyte_cli/lib/runtime_primitives/gate.py +105 -0
- vidbyte_cli/lib/runtime_primitives/hosts.py +45 -0
- vidbyte_cli/lib/runtime_primitives/planner.py +79 -0
- vidbyte_cli/lib/runtime_primitives/stages.py +338 -0
- vidbyte_cli/lib/runtime_primitives/task_board.py +982 -0
- vidbyte_cli/lib/runtime_primitives/task_board_checkpoints.py +473 -0
- vidbyte_cli/lib/runtime_primitives/task_board_system.md +33 -0
- vidbyte_cli/lib/runtime_primitives/task_board_tasks.py +111 -0
- vidbyte_cli/lib/runtime_primitives/verification.py +68 -0
- vidbyte_cli/services/__init__.py +5 -0
- vidbyte_cli/services/ensemble/__init__.py +7 -0
- vidbyte_cli/services/ensemble/prompts/__init__.py +6 -0
- vidbyte_cli/services/ensemble/prompts/implementer_system.md +546 -0
- vidbyte_cli/services/ensemble/prompts/implementer_turn.md +49 -0
- vidbyte_cli/services/ensemble/prompts/library.py +188 -0
- vidbyte_cli/services/ensemble/prompts/planner_system.md +728 -0
- vidbyte_cli/services/ensemble/prompts/planner_turn.md +6 -0
- vidbyte_cli/services/ensemble/prompts/role_system.md +104 -0
- vidbyte_cli/services/ensemble/prompts/role_turn.md +19 -0
- vidbyte_cli/services/ensemble/prompts/selector_final_turn.md +19 -0
- vidbyte_cli/services/ensemble/prompts/selector_round_turn.md +17 -0
- vidbyte_cli/services/ensemble/prompts/selector_system.md +89 -0
- vidbyte_cli/services/ensemble/runner.py +30 -0
- vidbyte_cli/services/ensemble/sdk.py +134 -0
- vidbyte_cli/services/ensemble/service.py +233 -0
- vidbyte_cli/services/ensemble/settings.py +60 -0
- vidbyte_cli/services/persistence/__init__.py +6 -0
- vidbyte_cli/services/persistence/prompts/__init__.py +6 -0
- vidbyte_cli/services/persistence/prompts/library.py +46 -0
- vidbyte_cli/services/persistence/prompts/persistence_system.md +1 -0
- vidbyte_cli/services/persistence/prompts/persistence_turn.md +3 -0
- vidbyte_cli/services/persistence/runner.py +35 -0
- vidbyte_cli/services/persistence/session.py +116 -0
- vidbyte_cli/services/suggestions/__init__.py +7 -0
- vidbyte_cli/services/suggestions/categories.py +270 -0
- vidbyte_cli/services/suggestions/context.py +222 -0
- vidbyte_cli/services/suggestions/extra_compute.py +53 -0
- vidbyte_cli/services/suggestions/handoff.py +173 -0
- vidbyte_cli/services/suggestions/message_tools.py +47 -0
- vidbyte_cli/services/suggestions/project.py +253 -0
- vidbyte_cli/services/suggestions/prompts/__init__.py +1 -0
- vidbyte_cli/services/suggestions/prompts/categories/__init__.py +3 -0
- vidbyte_cli/services/suggestions/prompts/categories/adjacent_opportunity.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/alternative.md +38 -0
- vidbyte_cli/services/suggestions/prompts/categories/big_bets.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/bottleneck.md +38 -0
- vidbyte_cli/services/suggestions/prompts/categories/business_growth.md +38 -0
- vidbyte_cli/services/suggestions/prompts/categories/completion.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/continuation.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/coordination.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/creative_exploration.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/cross_domain.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/delegation.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/experiment.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/feedback.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/goal_clarification.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/immediate_next_steps.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/investigation.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/learning.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/leverage.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/long_term_directions.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/preparation.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/prerequisite.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/prioritization.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/product_experience.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/quick_wins.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/reframing.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/risk_prevention.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/simplification.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/stop_or_defer.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/strategy.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/technical_possibilities.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/verification.md +37 -0
- vidbyte_cli/services/suggestions/prompts/critic.md +209 -0
- vidbyte_cli/services/suggestions/prompts/critic_message.md +8 -0
- vidbyte_cli/services/suggestions/prompts/generator.md +64 -0
- vidbyte_cli/services/suggestions/prompts/library.py +72 -0
- vidbyte_cli/services/suggestions/prompts/message_generator_argument.md +1 -0
- vidbyte_cli/services/suggestions/prompts/message_generator_tool.md +1 -0
- vidbyte_cli/services/suggestions/prompts/message_parent_argument.md +1 -0
- vidbyte_cli/services/suggestions/prompts/message_parent_tool.md +1 -0
- vidbyte_cli/services/suggestions/prompts/message_receipt.md +1 -0
- vidbyte_cli/services/suggestions/prompts/revision.md +8 -0
- vidbyte_cli/services/suggestions/sdk.py +228 -0
- vidbyte_cli/services/suggestions/selection.py +78 -0
- vidbyte_cli/services/suggestions/service.py +540 -0
- vidbyte_cli/types/__init__.py +0 -0
- vidbyte_cli/types/api.py +101 -0
- vidbyte_cli/types/attachments.py +79 -0
- vidbyte_cli/types/ensemble.py +189 -0
- vidbyte_cli/types/provider.py +62 -0
- vidbyte_cli/types/research.py +260 -0
- vidbyte_cli/types/runtime.py +1098 -0
- vidbyte_cli/types/suggestions.py +518 -0
- vidbyte_cli-0.1.0.dist-info/METADATA +335 -0
- vidbyte_cli-0.1.0.dist-info/RECORD +269 -0
- vidbyte_cli-0.1.0.dist-info/WHEEL +5 -0
- vidbyte_cli-0.1.0.dist-info/entry_points.txt +2 -0
- vidbyte_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- vidbyte_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Registers explicit accepted and rejected suggestion feedback commands.
|
|
2
|
+
|
|
3
|
+
Feedback is appended to one project's local JSON memory and never inferred from silence or
|
|
4
|
+
implementation. This module owns Click parsing and result rendering; validation and
|
|
5
|
+
persistence belong to `SuggestionFeedbackInput` and `SuggestionProject`.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
from ....lib.errors.failures import SuggestionFeedbackInvalid, SuggestionProjectInvalid
|
|
13
|
+
from ....lib.output import OutputDocument
|
|
14
|
+
from ....lib.runtime.context import ApplicationContext as Context
|
|
15
|
+
from ....services.suggestions.project import (
|
|
16
|
+
SuggestionFeedbackInput,
|
|
17
|
+
SuggestionProject,
|
|
18
|
+
SuggestionProjectKey,
|
|
19
|
+
)
|
|
20
|
+
from ....types.suggestions import FeedbackType
|
|
21
|
+
from .prompts.library import SuggestionHelpLibrary
|
|
22
|
+
|
|
23
|
+
_HELP = SuggestionHelpLibrary()
|
|
24
|
+
_GROUP_HELP = _HELP.load("feedback_group")
|
|
25
|
+
_ACCEPT_HELP = _HELP.load("feedback_accept")
|
|
26
|
+
_REJECT_HELP = _HELP.load("feedback_reject")
|
|
27
|
+
_PROJECT_HELP = _HELP.load("feedback_project")
|
|
28
|
+
_SUGGESTION_HELP = _HELP.load("feedback_suggestion")
|
|
29
|
+
_REASON_HELP = _HELP.load("feedback_reason")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SuggestionFeedbackGroup:
|
|
33
|
+
"""Attaches accepted and rejected feedback verbs below the suggest group."""
|
|
34
|
+
|
|
35
|
+
def register(self, parent: click.Group) -> None:
|
|
36
|
+
# Keeps both dispositions on one command family with one persistence implementation.
|
|
37
|
+
feedback = click.Group(name="feedback", help=_GROUP_HELP)
|
|
38
|
+
SuggestionFeedbackCommand(FeedbackType.ACCEPTED).register(feedback)
|
|
39
|
+
SuggestionFeedbackCommand(FeedbackType.REJECTED).register(feedback)
|
|
40
|
+
parent.add_command(feedback)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SuggestionFeedbackCommand:
|
|
44
|
+
"""Validates and appends one explicit accepted or rejected reaction."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, feedback_type: FeedbackType) -> None:
|
|
47
|
+
# The disposition is fixed by registration so callers cannot pass arbitrary types.
|
|
48
|
+
self._feedback_type = feedback_type
|
|
49
|
+
|
|
50
|
+
def register(self, parent: click.Group) -> None:
|
|
51
|
+
# Adds one verb whose name and help match its fixed disposition.
|
|
52
|
+
accepted = self._feedback_type is FeedbackType.ACCEPTED
|
|
53
|
+
|
|
54
|
+
@parent.command(
|
|
55
|
+
name="accept" if accepted else "reject",
|
|
56
|
+
help=_ACCEPT_HELP if accepted else _REJECT_HELP,
|
|
57
|
+
)
|
|
58
|
+
@click.option("--project", required=True, help=_PROJECT_HELP)
|
|
59
|
+
@click.option("--suggestion", required=True, help=_SUGGESTION_HELP)
|
|
60
|
+
@click.option("--reason", default=None, help=_REASON_HELP)
|
|
61
|
+
@click.pass_obj
|
|
62
|
+
def _feedback(ctx: Context, project: str, suggestion: str, reason: str | None) -> None:
|
|
63
|
+
self.execute(ctx, project, suggestion, reason)
|
|
64
|
+
|
|
65
|
+
def execute(self, context: Context, project: str, suggestion: str, reason: str | None) -> None:
|
|
66
|
+
# A malformed key and malformed feedback name different options, so they fail apart.
|
|
67
|
+
try:
|
|
68
|
+
key = SuggestionProjectKey(project)
|
|
69
|
+
except (TypeError, ValueError) as error:
|
|
70
|
+
raise SuggestionProjectInvalid(str(error)) from error
|
|
71
|
+
try:
|
|
72
|
+
request = SuggestionFeedbackInput(key, self._feedback_type, suggestion, reason)
|
|
73
|
+
except (TypeError, ValueError) as error:
|
|
74
|
+
raise SuggestionFeedbackInvalid(str(error)) from error
|
|
75
|
+
record = SuggestionProject(context.paths()).record_feedback(request)
|
|
76
|
+
context.output().result(
|
|
77
|
+
OutputDocument(
|
|
78
|
+
kind="suggestions.feedback.recorded",
|
|
79
|
+
data={"project": key.value, "feedback": record.model_dump(mode="json")},
|
|
80
|
+
),
|
|
81
|
+
f"Recorded {record.type.value} feedback for '{key.value}'.",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
__all__ = ["SuggestionFeedbackGroup"]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Extracts one handoff from a saved suggestion result without a model.
|
|
2
|
+
|
|
3
|
+
The command reads a `suggestions.result` document, selects one idea by id,
|
|
4
|
+
and emits its handoff as `suggestions.handoff`. This is a pure local read, so
|
|
5
|
+
it never needs credentials and never calls a provider on any code path.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
|
|
15
|
+
from ....lib.errors.failures import SuggestionContextUnreadable, SuggestionInputInvalid
|
|
16
|
+
from ....lib.runtime.context import ApplicationContext as Context
|
|
17
|
+
from ....types.suggestions import SuggestionHandoff, SuggestionResult
|
|
18
|
+
from .prompts.library import SuggestionHelpLibrary
|
|
19
|
+
from .render import SuggestionRenderer
|
|
20
|
+
|
|
21
|
+
_HELP = SuggestionHelpLibrary()
|
|
22
|
+
_COMMAND_HELP = _HELP.load("handoff")
|
|
23
|
+
_INPUT_HELP = _HELP.load("input")
|
|
24
|
+
_IDEA_HELP = _HELP.load("idea")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SuggestionHandoffCommand:
|
|
28
|
+
"""Reads a saved batch and renders one deterministic handoff packet."""
|
|
29
|
+
|
|
30
|
+
def register(self, parent: click.Group) -> None:
|
|
31
|
+
# Attaches handoff with input and idea selectors as its only options.
|
|
32
|
+
@parent.command(name="handoff", help=_COMMAND_HELP)
|
|
33
|
+
@click.option(
|
|
34
|
+
"--input",
|
|
35
|
+
"input_path",
|
|
36
|
+
required=True,
|
|
37
|
+
type=click.Path(path_type=Path),
|
|
38
|
+
help=_INPUT_HELP,
|
|
39
|
+
)
|
|
40
|
+
@click.option("--idea", required=True, help=_IDEA_HELP)
|
|
41
|
+
@click.pass_obj
|
|
42
|
+
def _handoff(ctx: Context, input_path: Path, idea: str) -> None:
|
|
43
|
+
# Delegates to the execution method for direct testing.
|
|
44
|
+
self.execute(ctx, input_path, idea)
|
|
45
|
+
|
|
46
|
+
def execute(self, context: Context, input_path: Path, idea: str) -> None:
|
|
47
|
+
# Validates the document, selects the idea, and emits its handoff.
|
|
48
|
+
result = self._load(input_path)
|
|
49
|
+
selected = next((item for item in result.ideas if item.id == idea.strip()), None)
|
|
50
|
+
if selected is None:
|
|
51
|
+
raise SuggestionInputInvalid()
|
|
52
|
+
handoff = SuggestionHandoff.model_validate(selected.handoff.model_dump(mode="json"))
|
|
53
|
+
SuggestionRenderer().render_handoff(context, handoff)
|
|
54
|
+
|
|
55
|
+
def _load(self, input_path: Path) -> SuggestionResult:
|
|
56
|
+
# Rejects missing, malformed, and wrong-kind documents before selecting.
|
|
57
|
+
try:
|
|
58
|
+
text = Path(input_path).read_text(encoding="utf-8-sig")
|
|
59
|
+
document = json.loads(text)
|
|
60
|
+
except (OSError, json.JSONDecodeError) as error:
|
|
61
|
+
raise SuggestionContextUnreadable(str(error)) from error
|
|
62
|
+
if not isinstance(document, dict) or document.get("kind") != "suggestions.result":
|
|
63
|
+
raise SuggestionInputInvalid()
|
|
64
|
+
try:
|
|
65
|
+
return SuggestionResult.model_validate(document.get("data", document))
|
|
66
|
+
except Exception as error:
|
|
67
|
+
raise SuggestionContextUnreadable(str(error)) from error
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Registers local suggestion project creation and catalog listing commands.
|
|
2
|
+
|
|
3
|
+
Projects are small, credential-free local records that give later suggestion runs a stable
|
|
4
|
+
memory scope. This module owns Click parsing and output only; what a project is and how it is
|
|
5
|
+
stored belong to `SuggestionProject`.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import cast
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
from pydantic import JsonValue
|
|
14
|
+
|
|
15
|
+
from ....lib.errors.failures import SuggestionProjectInvalid
|
|
16
|
+
from ....lib.output import OutputDocument
|
|
17
|
+
from ....lib.runtime.context import ApplicationContext as Context
|
|
18
|
+
from ....services.suggestions.project import (
|
|
19
|
+
SuggestionProject,
|
|
20
|
+
SuggestionProjectCreateInput,
|
|
21
|
+
SuggestionProjectKey,
|
|
22
|
+
)
|
|
23
|
+
from .prompts.library import SuggestionHelpLibrary
|
|
24
|
+
|
|
25
|
+
_HELP = SuggestionHelpLibrary()
|
|
26
|
+
_GROUP_HELP = _HELP.load("project_group")
|
|
27
|
+
_CREATE_HELP = _HELP.load("project_create")
|
|
28
|
+
_LIST_HELP = _HELP.load("project_list")
|
|
29
|
+
_KEY_HELP = _HELP.load("project_key")
|
|
30
|
+
_TITLE_HELP = _HELP.load("project_title")
|
|
31
|
+
_DESCRIPTION_HELP = _HELP.load("project_description")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SuggestionProjectGroup:
|
|
35
|
+
"""Attaches project create and list commands below the suggest group."""
|
|
36
|
+
|
|
37
|
+
def register(self, parent: click.Group) -> None:
|
|
38
|
+
# Keeps project verbs together without adding persistence logic to registration.
|
|
39
|
+
project = click.Group(name="project", help=_GROUP_HELP)
|
|
40
|
+
SuggestionProjectCreateCommand().register(project)
|
|
41
|
+
SuggestionProjectListCommand().register(project)
|
|
42
|
+
parent.add_command(project)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SuggestionProjectCreateCommand:
|
|
46
|
+
"""Validates and creates one local suggestion project."""
|
|
47
|
+
|
|
48
|
+
def register(self, parent: click.Group) -> None:
|
|
49
|
+
# Click owns required-field errors while the input dataclass owns domain validation.
|
|
50
|
+
@parent.command(name="create", help=_CREATE_HELP)
|
|
51
|
+
@click.option("--key", required=True, help=_KEY_HELP)
|
|
52
|
+
@click.option("--title", required=True, help=_TITLE_HELP)
|
|
53
|
+
@click.option("--description", required=True, help=_DESCRIPTION_HELP)
|
|
54
|
+
@click.pass_obj
|
|
55
|
+
def _create(ctx: Context, key: str, title: str, description: str) -> None:
|
|
56
|
+
self.execute(ctx, key, title, description)
|
|
57
|
+
|
|
58
|
+
def execute(self, context: Context, key: str, title: str, description: str) -> None:
|
|
59
|
+
# Every field is validated before the catalog is read, so a bad value writes nothing.
|
|
60
|
+
try:
|
|
61
|
+
request = SuggestionProjectCreateInput(SuggestionProjectKey(key), title, description)
|
|
62
|
+
except (TypeError, ValueError) as error:
|
|
63
|
+
raise SuggestionProjectInvalid(str(error)) from error
|
|
64
|
+
project = SuggestionProject(context.paths()).create(request)
|
|
65
|
+
context.output().result(
|
|
66
|
+
OutputDocument(
|
|
67
|
+
kind="suggestions.project.created", data=project.model_dump(mode="json")
|
|
68
|
+
),
|
|
69
|
+
f"Created suggestion project '{project.key}'.",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class SuggestionProjectListCommand:
|
|
74
|
+
"""Reads and renders the local suggestion project catalog."""
|
|
75
|
+
|
|
76
|
+
def register(self, parent: click.Group) -> None:
|
|
77
|
+
# Catalog listing is read-only and remains available without credentials.
|
|
78
|
+
@parent.command(name="list", help=_LIST_HELP)
|
|
79
|
+
@click.pass_obj
|
|
80
|
+
def _list(ctx: Context) -> None:
|
|
81
|
+
self.execute(ctx)
|
|
82
|
+
|
|
83
|
+
def execute(self, context: Context) -> None:
|
|
84
|
+
# Renders project summaries only; feedback bodies stay in their own files.
|
|
85
|
+
projects = SuggestionProject(context.paths()).list_projects()
|
|
86
|
+
data: dict[str, JsonValue] = {
|
|
87
|
+
"projects": [cast(JsonValue, project.model_dump(mode="json")) for project in projects]
|
|
88
|
+
}
|
|
89
|
+
human = "\n\n".join(
|
|
90
|
+
f"{project.key} {project.title}\n {project.description}" for project in projects
|
|
91
|
+
)
|
|
92
|
+
context.output().result(OutputDocument(kind="suggestions.projects", data=data), human)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
__all__ = ["SuggestionProjectGroup"]
|
|
File without changes
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""`vidbyte-cli login` — stores the user's Vidbyte API key for later commands.
|
|
2
|
+
|
|
3
|
+
There is no `--api-key` option: a secret in argv leaks into `ps`, shell history, and CI
|
|
4
|
+
logs. `--with-token` reads bounded stdin; anything else uses a hidden prompt.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
from ...lib.auth import CredentialStorage
|
|
12
|
+
from ...lib.auth.input import CredentialInput
|
|
13
|
+
from ...lib.errors.failures import FileFallbackNotApproved
|
|
14
|
+
from ...lib.output import OutputDocument
|
|
15
|
+
from ...lib.runtime.context import ApplicationContext
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LoginCommand:
|
|
19
|
+
def register(self, parent: click.Group) -> None:
|
|
20
|
+
# Attaches `login` to the root program.
|
|
21
|
+
@parent.command(name="login", help="Authenticate with a Vidbyte API key")
|
|
22
|
+
@click.option(
|
|
23
|
+
"--with-token",
|
|
24
|
+
is_flag=True,
|
|
25
|
+
help="Read the API key from stdin instead of a hidden prompt.",
|
|
26
|
+
)
|
|
27
|
+
@click.option(
|
|
28
|
+
"--allow-file-fallback",
|
|
29
|
+
is_flag=True,
|
|
30
|
+
help="Approve restricted-file storage when no OS keyring is available.",
|
|
31
|
+
)
|
|
32
|
+
@click.pass_obj
|
|
33
|
+
def _run(
|
|
34
|
+
context: ApplicationContext,
|
|
35
|
+
with_token: bool,
|
|
36
|
+
allow_file_fallback: bool,
|
|
37
|
+
) -> None:
|
|
38
|
+
self.execute(context, with_token, allow_file_fallback)
|
|
39
|
+
|
|
40
|
+
def execute(
|
|
41
|
+
self,
|
|
42
|
+
context: ApplicationContext,
|
|
43
|
+
with_token: bool,
|
|
44
|
+
allow_file_fallback: bool,
|
|
45
|
+
) -> None:
|
|
46
|
+
config = context.resolved_config()
|
|
47
|
+
credentials = CredentialInput(
|
|
48
|
+
context.streams,
|
|
49
|
+
context.output().terminal,
|
|
50
|
+
no_input=context.options.no_input,
|
|
51
|
+
).read(from_stdin=with_token)
|
|
52
|
+
# Verify-before-persist. Syntactic checks cannot prove a key authenticates, and this
|
|
53
|
+
# call sits immediately before the only write so a reordering is obvious in review.
|
|
54
|
+
context.credential_verifier().verify(credentials, config)
|
|
55
|
+
fallback_allowed = self._fallback_consent(context, allow_file_fallback)
|
|
56
|
+
# Legacy state moves only on an explicit mutation, never on a read path.
|
|
57
|
+
context.migration().migrate_if_needed()
|
|
58
|
+
storage = context.credential_store().write(
|
|
59
|
+
credentials,
|
|
60
|
+
config.profile,
|
|
61
|
+
config.api_url,
|
|
62
|
+
allow_file_fallback=fallback_allowed,
|
|
63
|
+
)
|
|
64
|
+
if storage is CredentialStorage.RESTRICTED_FILE:
|
|
65
|
+
context.output().warning(
|
|
66
|
+
"The API key is stored in a permission-restricted file because no OS "
|
|
67
|
+
"keyring is available."
|
|
68
|
+
)
|
|
69
|
+
context.output().result(
|
|
70
|
+
OutputDocument(
|
|
71
|
+
kind="auth.login",
|
|
72
|
+
data={
|
|
73
|
+
"profile": config.profile,
|
|
74
|
+
"api_url": config.api_url,
|
|
75
|
+
"storage": storage.value,
|
|
76
|
+
},
|
|
77
|
+
),
|
|
78
|
+
f"Authenticated profile '{config.profile}' using {storage.value}.",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def _fallback_consent(
|
|
82
|
+
self,
|
|
83
|
+
context: ApplicationContext,
|
|
84
|
+
explicitly_allowed: bool,
|
|
85
|
+
) -> bool:
|
|
86
|
+
# The fallback file is readable by anything running as this user, so consent is the
|
|
87
|
+
# caller's to give — asked for here only when there is a terminal to ask on.
|
|
88
|
+
if context.credential_store().keyring.available():
|
|
89
|
+
return False
|
|
90
|
+
if explicitly_allowed:
|
|
91
|
+
return True
|
|
92
|
+
if context.options.no_input or not context.output().terminal.interactive:
|
|
93
|
+
raise FileFallbackNotApproved()
|
|
94
|
+
return bool(
|
|
95
|
+
click.confirm(
|
|
96
|
+
"No OS keyring is available. Store the key in a restricted local file?",
|
|
97
|
+
default=False,
|
|
98
|
+
err=True,
|
|
99
|
+
)
|
|
100
|
+
)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""`vidbyte-cli logout` — removes stored credentials from this machine.
|
|
2
|
+
|
|
3
|
+
Logout is a desired state, not an action that must have had something to undo: an
|
|
4
|
+
already-logged-out profile succeeds, so a cleanup script can run it unconditionally.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
from ...lib.output import OutputDocument
|
|
12
|
+
from ...lib.runtime.context import ApplicationContext
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LogoutCommand:
|
|
16
|
+
def register(self, parent: click.Group) -> None:
|
|
17
|
+
@parent.command(name="logout", help="Remove stored Vidbyte credentials from this machine")
|
|
18
|
+
@click.pass_obj
|
|
19
|
+
def _run(context: ApplicationContext) -> None:
|
|
20
|
+
self.execute(context)
|
|
21
|
+
|
|
22
|
+
def execute(self, context: ApplicationContext) -> None:
|
|
23
|
+
# Scoped to the selected profile and API host: logging out of staging must leave the
|
|
24
|
+
# production credential alone.
|
|
25
|
+
config = context.resolved_config()
|
|
26
|
+
removed = context.credential_store().clear(config.profile, config.api_url)
|
|
27
|
+
context.output().result(
|
|
28
|
+
OutputDocument(
|
|
29
|
+
kind="auth.logout",
|
|
30
|
+
data={
|
|
31
|
+
"profile": config.profile,
|
|
32
|
+
"api_url": config.api_url,
|
|
33
|
+
"removed": removed,
|
|
34
|
+
},
|
|
35
|
+
),
|
|
36
|
+
(
|
|
37
|
+
f"Removed credentials for profile '{config.profile}'."
|
|
38
|
+
if removed
|
|
39
|
+
else f"Profile '{config.profile}' was already logged out."
|
|
40
|
+
),
|
|
41
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""`vidbyte-cli whoami` — shows which account the stored credentials belong to.
|
|
2
|
+
|
|
3
|
+
The identity comes from the same backend check login uses, so the two commands can never
|
|
4
|
+
disagree about whether a key is good. Reading is done through `CredentialResolver`, which puts
|
|
5
|
+
`VIDBYTE_API_KEY` ahead of the keyring — correct here, because that is the key later commands
|
|
6
|
+
would actually send.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
from pydantic import JsonValue
|
|
13
|
+
|
|
14
|
+
from ...lib.errors.failures import AuthenticationRequired
|
|
15
|
+
from ...lib.output import OutputDocument
|
|
16
|
+
from ...lib.runtime.context import ApplicationContext
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class WhoamiCommand:
|
|
20
|
+
def register(self, parent: click.Group) -> None:
|
|
21
|
+
# Attaches `whoami` to the root program.
|
|
22
|
+
@parent.command(name="whoami", help="Show the account behind the stored credentials")
|
|
23
|
+
@click.pass_obj
|
|
24
|
+
def _run(context: ApplicationContext) -> None:
|
|
25
|
+
self.execute(context)
|
|
26
|
+
|
|
27
|
+
def execute(self, context: ApplicationContext) -> None:
|
|
28
|
+
# Resolves the stored credential, proves it against the backend, and prints the identity.
|
|
29
|
+
config = context.resolved_config()
|
|
30
|
+
credential = context.credential_resolver().resolve(config.profile, config.api_url)
|
|
31
|
+
if credential is None:
|
|
32
|
+
# Raised before any request, so an unauthenticated machine never reaches the network.
|
|
33
|
+
raise AuthenticationRequired()
|
|
34
|
+
identity = context.credential_verifier().verify(credential.credentials, config)
|
|
35
|
+
data: dict[str, JsonValue] = {
|
|
36
|
+
"profile": config.profile,
|
|
37
|
+
"api_url": config.api_url,
|
|
38
|
+
"username": identity.username,
|
|
39
|
+
"account_tier": identity.account_tier,
|
|
40
|
+
"credential_source": credential.source.value,
|
|
41
|
+
}
|
|
42
|
+
human = "\n".join(
|
|
43
|
+
(
|
|
44
|
+
f"Authenticated as: {identity.username}",
|
|
45
|
+
f"Account tier: {identity.account_tier}",
|
|
46
|
+
f"Profile: {config.profile}",
|
|
47
|
+
f"API URL: {config.api_url}",
|
|
48
|
+
f"Credential source: {credential.source.value}",
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
context.output().result(OutputDocument(kind="auth.whoami", data=data), human)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Commands for explicitly funding the authenticated Vidbyte API balance."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from .prompts.library import BillingHelpLibrary
|
|
6
|
+
from .top_up import BillingTopUpCommand
|
|
7
|
+
|
|
8
|
+
_GROUP_HELP = BillingHelpLibrary().load("group")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BillingCommandGroup:
|
|
12
|
+
"""Registers billing commands beneath the CLI's top-level billing group."""
|
|
13
|
+
|
|
14
|
+
def register(self, parent: click.Group) -> None:
|
|
15
|
+
billing = click.Group(name="billing", help=_GROUP_HELP)
|
|
16
|
+
BillingTopUpCommand().register(billing)
|
|
17
|
+
parent.add_command(billing)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = ["BillingCommandGroup", "BillingTopUpCommand"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Markdown assets used by billing command help."""
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# `--confirm`
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
This required opt-in authorizes the billing command to proceed toward a paid Vidbyte API-balance top-up. Supplying it means the caller has obtained the user's explicit approval for a minimum five-dollar purchase. Without it, the command exits before resolving credentials, constructing a signer, or sending an HTTP request. It does not approve unrelated charges or authorize a later top-up. Error handling in other commands never adds this flag automatically.
|
|
5
|
+
|
|
6
|
+
## Default and when to use
|
|
7
|
+
The default is off, so omitting the flag cannot create a payment authorization. Add it only when the user has reviewed the amount and asked to fund the Vidbyte API balance. Do not infer approval from an earlier request to run research or from a prior top-up. If the user has not approved spending, leave the flag out and stop at the recovery instructions. The command does not prompt interactively in place of this explicit choice.
|
|
8
|
+
|
|
9
|
+
## Payment boundary
|
|
10
|
+
With confirmation present, the command still pays only the exact challenge returned by `POST /agent/topup`. The endpoint controls the accepted amount, network, asset, and recipient. The flag does not expose or print the private key and does not authorize payment to a different endpoint. If payment configuration is missing or the challenge is invalid, the command fails without launching an agent. Provider-model charges are outside this confirmation and remain governed by the provider account.
|
|
11
|
+
|
|
12
|
+
## Examples
|
|
13
|
+
After user approval, run `vidbyte-cli billing top-up --confirm` to create a new top-up request. To recover the same uncertain request, pass the original key as `--idempotency-key` along with `--confirm`. Do not combine confirmation with a new key when recovering an uncertain payment, because a new key can represent a second charge. After success, verify the available balance before retrying the original operation. If approval is absent, run no payment command.
|
|
14
|
+
|
|
15
|
+
## Related commands
|
|
16
|
+
The parent command is `vidbyte-cli billing top-up`. Its help explains account setup, payment behavior, output, and recovery. The idempotency option's help explains how to reuse a key after an uncertain response. Neither option changes the requirement for explicit user approval. The browser fallback is <https://vidbyte.pro/settings/api>.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Billing commands
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
The billing group contains explicit operations for managing the Vidbyte API balance used by API-key requests. Its commands interact with Vidbyte billing endpoints and do not manage model-provider accounts. Billing actions that spend money require a separate explicit command invocation and user approval. A failed research or runtime command never calls a billing command automatically. Read the child command help before making a purchase.
|
|
5
|
+
|
|
6
|
+
## Available command
|
|
7
|
+
Use `billing top-up` to add the backend's minimum five-dollar credit to the API balance associated with the configured Vidbyte key. The top-up command pays the exact x402 challenge returned by the billing endpoint. It reports the credited amount and the balance available afterward. It does not start or resume any prior operation. After a top-up, confirm the balance and then decide whether the original operation should be retried or resumed.
|
|
8
|
+
|
|
9
|
+
## Approval and credentials
|
|
10
|
+
Obtain explicit approval from the user before invoking a paid billing action. Configure the same Vidbyte API key that made the request requiring more balance. Configure any payment signer through the supported environment variable and never pass its secret as a command argument. The command requires `--confirm` before it resolves credentials or sends HTTP. If the user has not approved a purchase, stop and use the account settings page instead.
|
|
11
|
+
|
|
12
|
+
## Recovery and related commands
|
|
13
|
+
When a top-up response is uncertain, repeat the same top-up with its original idempotency key so the backend can recover that purchase. A new key represents a distinct operation and can create an additional charge. The idempotency key from the failed research operation is separate and should remain unchanged when retrying that operation. If payment cannot be completed in the CLI, visit <https://vidbyte.pro/settings/api> to manage API balance in a browser. Provider charges for model usage remain separate from Vidbyte API balance.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# `--idempotency-key`
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
This option identifies one top-up operation to the backend so a repeated request can recover the same result instead of creating a second purchase. For a new top-up, leave it unset and the CLI creates a fresh key. For a retry after a timeout or lost response, provide exactly the key used by the original attempt. The key applies only to this top-up and does not replace the key from the failed research or runtime operation. Reusing a key does not itself prove that payment succeeded; inspect the returned balance.
|
|
5
|
+
|
|
6
|
+
## Defaults and recovery
|
|
7
|
+
The default is no caller-provided key, which causes the CLI to generate one for a new purchase. Supply a key only when recovering the same top-up whose outcome is uncertain. Keep the value identical across retries and preserve it somewhere safe until the outcome is known. Do not reuse a completed top-up's key for a separate purchase. Do not generate a replacement key just because the first response was missing, since that can create another charge.
|
|
8
|
+
|
|
9
|
+
## Accepted value
|
|
10
|
+
Pass one stable string of 8 to 128 letters, digits, dots, underscores, colons, or hyphens. Avoid including account data, credentials, or other secrets in the value. The key is sent to the Vidbyte API as the idempotency identifier for the top-up request. The same key must accompany both the unpaid challenge request and the paid retry. The CLI does not print the payment authorization as part of its result.
|
|
11
|
+
|
|
12
|
+
## Examples
|
|
13
|
+
Start a new approved purchase with `vidbyte-cli billing top-up --confirm` and let the CLI generate a key. If that request times out, repeat it with `vidbyte-cli billing top-up --confirm --idempotency-key <same-key>`. Replace the placeholder with the exact key from the original request rather than the literal angle-bracket text. Confirm the balance returned by the API before retrying the original operation. Use a different fresh key only for a distinct top-up after a separate user approval.
|
|
14
|
+
|
|
15
|
+
## Related commands
|
|
16
|
+
The parent `vidbyte-cli billing top-up` help describes the charge and the post-payment recovery sequence. `--confirm` remains mandatory for both new and recovered attempts. The original failed operation's idempotency key is separate and must be retained for retrying that operation. Use <https://vidbyte.pro/settings/api> when the caller cannot safely make the payment through this command.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Loads caller-facing billing help from packaged Markdown assets."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib import resources
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BillingHelpLibrary:
|
|
9
|
+
"""Reads each billing help description from its named Markdown asset."""
|
|
10
|
+
|
|
11
|
+
def __init__(self) -> None:
|
|
12
|
+
self._cache: dict[str, str] = {}
|
|
13
|
+
|
|
14
|
+
def load(self, name: str) -> str:
|
|
15
|
+
if name not in self._cache:
|
|
16
|
+
asset = resources.files(__package__).joinpath(f"{name}.md")
|
|
17
|
+
self._cache[name] = asset.read_text(encoding="utf-8").strip()
|
|
18
|
+
return self._cache[name]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Billing top-up
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
Use this command to add the backend's minimum five-dollar credit to the Vidbyte API balance associated with the configured API key. The command does not top up a model-provider account or change the provider credentials used by a research run. It sends `POST /agent/topup` and pays the exact x402 challenge returned by that endpoint. The backend response reports the amount credited and the remaining available balance. Call this command only after the account owner has approved the purchase.
|
|
5
|
+
|
|
6
|
+
## When to use
|
|
7
|
+
Use this command after a Vidbyte API-balance error when the user has approved a charge and wants to continue the original operation. Before running it, configure the API key for the same Vidbyte account that received the error. Configure the x402 private key through the supported environment variable without pasting it into a command, prompt, or log. If a payment result is uncertain, reuse the idempotency key from that same top-up instead of creating a new purchase. If payment setup is unavailable, the user can add balance at <https://vidbyte.pro/settings/api>.
|
|
8
|
+
|
|
9
|
+
## Approval and cost
|
|
10
|
+
This command creates a real payment authorization and must never be invoked as an automatic error-recovery side effect. Obtain explicit user approval before adding `--confirm`. The API determines the exact payment challenge; this CLI does not let the caller select a different amount, rail, or recipient. The minimum top-up is five US dollars, and any additional charge must be established by the challenge itself rather than inferred from this help text. Model-provider usage remains separately billed by that provider.
|
|
11
|
+
|
|
12
|
+
## Output and next step
|
|
13
|
+
On success, human output names the credited amount, payment rail, and available Vidbyte API balance. JSON and JSONL output contain the corresponding typed result fields. The command does not start or resume the failed agent operation. Verify that the available balance covers the original or remaining operation, then retry or resume it explicitly and preserve its original idempotency key when one was supplied.
|
|
14
|
+
|
|
15
|
+
## Related commands
|
|
16
|
+
Use `vidbyte-cli agents` or the relevant research command only after the balance is confirmed. The original operation may have made progress before a mid-run exhaustion error, so inspect its status before deciding whether to resume or repeat it. For browser-based account management, use the API settings URL above. Do not rerun a top-up with a new key merely because the first response was lost.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Explicitly pay the Vidbyte API-balance top-up challenge."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
from uuid import uuid4
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from ...lib.api.endpoints.billing import TOP_UP_CENTS
|
|
11
|
+
from ...lib.errors.failures import BillingTopUpApprovalRequired
|
|
12
|
+
from ...lib.output import OutputDocument
|
|
13
|
+
from .prompts.library import BillingHelpLibrary
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from ...lib.runtime.context import ApplicationContext
|
|
17
|
+
|
|
18
|
+
_TOP_UP_HELP = BillingHelpLibrary().load("top_up")
|
|
19
|
+
_CONFIRM_HELP = BillingHelpLibrary().load("confirm")
|
|
20
|
+
_IDEMPOTENCY_KEY_HELP = BillingHelpLibrary().load("idempotency_key")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BillingTopUpCommand:
|
|
24
|
+
"""Registers the one explicit command that can purchase API balance."""
|
|
25
|
+
|
|
26
|
+
def register(self, parent: click.Group) -> None:
|
|
27
|
+
# Keeps payment behind an explicit confirmation flag and never runs from error handling.
|
|
28
|
+
@parent.command(name="top-up", help=_TOP_UP_HELP)
|
|
29
|
+
@click.option(
|
|
30
|
+
"--confirm",
|
|
31
|
+
is_flag=True,
|
|
32
|
+
help=_CONFIRM_HELP,
|
|
33
|
+
)
|
|
34
|
+
@click.option(
|
|
35
|
+
"--idempotency-key",
|
|
36
|
+
default=None,
|
|
37
|
+
help=_IDEMPOTENCY_KEY_HELP,
|
|
38
|
+
)
|
|
39
|
+
@click.pass_obj
|
|
40
|
+
def _run(context: ApplicationContext, confirm: bool, idempotency_key: str | None) -> None:
|
|
41
|
+
# Routes parsed Click options through the typed command boundary.
|
|
42
|
+
self.execute(context, confirm, idempotency_key)
|
|
43
|
+
|
|
44
|
+
def execute(
|
|
45
|
+
self, context: ApplicationContext, confirm: bool, idempotency_key: str | None
|
|
46
|
+
) -> None:
|
|
47
|
+
# Validates approval before resolving credentials or creating a payment authorization.
|
|
48
|
+
if not confirm:
|
|
49
|
+
raise BillingTopUpApprovalRequired()
|
|
50
|
+
from ...lib.api.runtime_payment import RuntimePayment
|
|
51
|
+
|
|
52
|
+
payer = RuntimePayment(context.environment, TOP_UP_CENTS)
|
|
53
|
+
result = context.billing_endpoints().top_up(idempotency_key or str(uuid4()), payer)
|
|
54
|
+
context.output().result(
|
|
55
|
+
OutputDocument(kind="billing.top_up", data=result.model_dump(mode="json")),
|
|
56
|
+
self._summary(result.credited_cents, result.available_balance_cents, result.rail),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def _summary(credited_cents: int, available_balance_cents: int, rail: str) -> str:
|
|
61
|
+
# Reports only non-secret financial outcome fields returned by the backend.
|
|
62
|
+
return (
|
|
63
|
+
f"Added ${credited_cents / 100:.2f} to Vidbyte API balance via {rail}. "
|
|
64
|
+
f"Available balance: ${available_balance_cents / 100:.2f}."
|
|
65
|
+
)
|
|
File without changes
|