shotgun-sh 0.2.1.dev3__tar.gz → 0.2.1.dev5__tar.gz
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.
Potentially problematic release.
This version of shotgun-sh might be problematic. Click here for more details.
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/PKG-INFO +1 -1
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/pyproject.toml +1 -1
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/config/constants.py +2 -1
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/config/manager.py +90 -19
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/config/models.py +11 -2
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/config/provider.py +2 -1
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/cli/config.py +6 -6
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/cli/feedback.py +4 -2
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/codebase/core/ingestor.py +25 -5
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/posthog_telemetry.py +10 -8
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/sentry_telemetry.py +3 -3
- shotgun_sh-0.2.1.dev5/src/shotgun/shotgun_web/__init__.py +19 -0
- shotgun_sh-0.2.1.dev5/src/shotgun/shotgun_web/client.py +138 -0
- shotgun_sh-0.2.1.dev5/src/shotgun/shotgun_web/constants.py +17 -0
- shotgun_sh-0.2.1.dev5/src/shotgun/shotgun_web/models.py +47 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/telemetry.py +7 -4
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/app.py +24 -8
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/screens/chat_screen/command_providers.py +6 -4
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/screens/feedback.py +2 -2
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/screens/model_picker.py +92 -17
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/screens/provider_config.py +68 -2
- shotgun_sh-0.2.1.dev5/src/shotgun/tui/screens/shotgun_auth.py +295 -0
- shotgun_sh-0.2.1.dev5/src/shotgun/tui/screens/welcome.py +176 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/.gitignore +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/LICENSE +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/README.md +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/hatch_build.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/agent_manager.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/common.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/config/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/conversation_history.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/conversation_manager.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/export.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/history/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/history/compaction.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/history/constants.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/history/context_extraction.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/history/history_building.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/history/history_processors.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/history/message_utils.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/history/token_counting/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/history/token_counting/anthropic.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/history/token_counting/base.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/history/token_counting/openai.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/history/token_counting/sentencepiece_counter.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/history/token_counting/tokenizer_cache.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/history/token_counting/utils.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/history/token_estimation.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/llm.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/messages.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/models.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/plan.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/research.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/specify.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/tasks.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/tools/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/tools/codebase/models.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/tools/file_management.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/tools/user_interaction.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/tools/web_search/openai.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/tools/web_search/utils.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/agents/usage_manager.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/build_constants.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/cli/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/cli/codebase/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/cli/codebase/commands.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/cli/codebase/models.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/cli/export.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/cli/models.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/cli/plan.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/cli/research.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/cli/specify.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/cli/tasks.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/cli/update.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/cli/utils.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/codebase/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/codebase/core/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/codebase/core/change_detector.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/codebase/core/code_retrieval.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/codebase/core/cypher_models.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/codebase/core/language_config.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/codebase/core/manager.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/codebase/core/nl_query.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/codebase/core/parser_loader.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/codebase/models.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/codebase/service.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/llm_proxy/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/llm_proxy/clients.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/llm_proxy/constants.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/logging_config.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/main.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/agents/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/agents/export.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/agents/plan.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/agents/research.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/agents/specify.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/agents/tasks.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/codebase/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/history/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/history/summarization.j2 +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/prompts/loader.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/py.typed +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/sdk/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/sdk/codebase.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/sdk/exceptions.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/sdk/models.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/sdk/services.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/commands/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/components/prompt_input.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/components/spinner.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/components/splash.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/components/vertical_tail.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/filtered_codebase_service.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/screens/chat.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/screens/chat.tcss +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/screens/chat_screen/history.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/screens/directory_setup.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/screens/splash.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/styles.tcss +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/utils/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/tui/utils/mode_progress.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/utils/__init__.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/utils/env_utils.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/utils/file_system_utils.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/utils/source_detection.py +0 -0
- {shotgun_sh-0.2.1.dev3 → shotgun_sh-0.2.1.dev5}/src/shotgun/utils/update_checker.py +0 -0
|
@@ -12,6 +12,8 @@ from shotgun.utils import get_shotgun_home
|
|
|
12
12
|
|
|
13
13
|
from .constants import (
|
|
14
14
|
API_KEY_FIELD,
|
|
15
|
+
SHOTGUN_INSTANCE_ID_FIELD,
|
|
16
|
+
SUPABASE_JWT_FIELD,
|
|
15
17
|
ConfigSection,
|
|
16
18
|
)
|
|
17
19
|
from .models import (
|
|
@@ -46,21 +48,24 @@ class ConfigManager:
|
|
|
46
48
|
|
|
47
49
|
self._config: ShotgunConfig | None = None
|
|
48
50
|
|
|
49
|
-
def load(self) -> ShotgunConfig:
|
|
51
|
+
def load(self, force_reload: bool = True) -> ShotgunConfig:
|
|
50
52
|
"""Load configuration from file.
|
|
51
53
|
|
|
54
|
+
Args:
|
|
55
|
+
force_reload: If True, reload from disk even if cached (default: True)
|
|
56
|
+
|
|
52
57
|
Returns:
|
|
53
58
|
ShotgunConfig: Loaded configuration or default config if file doesn't exist
|
|
54
59
|
"""
|
|
55
|
-
if self._config is not None:
|
|
60
|
+
if self._config is not None and not force_reload:
|
|
56
61
|
return self._config
|
|
57
62
|
|
|
58
63
|
if not self.config_path.exists():
|
|
59
64
|
logger.info(
|
|
60
|
-
"Configuration file not found, creating new config
|
|
65
|
+
"Configuration file not found, creating new config at: %s",
|
|
61
66
|
self.config_path,
|
|
62
67
|
)
|
|
63
|
-
# Create new config with generated
|
|
68
|
+
# Create new config with generated shotgun_instance_id
|
|
64
69
|
self._config = self.initialize()
|
|
65
70
|
return self._config
|
|
66
71
|
|
|
@@ -68,6 +73,14 @@ class ConfigManager:
|
|
|
68
73
|
with open(self.config_path, encoding="utf-8") as f:
|
|
69
74
|
data = json.load(f)
|
|
70
75
|
|
|
76
|
+
# Migration: Rename user_id to shotgun_instance_id (config v2 -> v3)
|
|
77
|
+
if "user_id" in data and SHOTGUN_INSTANCE_ID_FIELD not in data:
|
|
78
|
+
data[SHOTGUN_INSTANCE_ID_FIELD] = data.pop("user_id")
|
|
79
|
+
data["config_version"] = 3
|
|
80
|
+
logger.info(
|
|
81
|
+
"Migrated config v2->v3: renamed user_id to shotgun_instance_id"
|
|
82
|
+
)
|
|
83
|
+
|
|
71
84
|
# Convert plain text secrets to SecretStr objects
|
|
72
85
|
self._convert_secrets_to_secretstr(data)
|
|
73
86
|
|
|
@@ -131,7 +144,7 @@ class ConfigManager:
|
|
|
131
144
|
logger.error(
|
|
132
145
|
"Failed to load configuration from %s: %s", self.config_path, e
|
|
133
146
|
)
|
|
134
|
-
logger.info("Creating new configuration with generated
|
|
147
|
+
logger.info("Creating new configuration with generated shotgun_instance_id")
|
|
135
148
|
self._config = self.initialize()
|
|
136
149
|
return self._config
|
|
137
150
|
|
|
@@ -145,9 +158,9 @@ class ConfigManager:
|
|
|
145
158
|
if self._config:
|
|
146
159
|
config = self._config
|
|
147
160
|
else:
|
|
148
|
-
# Create a new config with generated
|
|
161
|
+
# Create a new config with generated shotgun_instance_id
|
|
149
162
|
config = ShotgunConfig(
|
|
150
|
-
|
|
163
|
+
shotgun_instance_id=str(uuid.uuid4()),
|
|
151
164
|
)
|
|
152
165
|
|
|
153
166
|
# Ensure directory exists
|
|
@@ -243,7 +256,8 @@ class ConfigManager:
|
|
|
243
256
|
|
|
244
257
|
This checks only the configuration file.
|
|
245
258
|
"""
|
|
246
|
-
|
|
259
|
+
# Use force_reload=False to avoid infinite loop when called from load()
|
|
260
|
+
config = self.load(force_reload=False)
|
|
247
261
|
provider_enum = self._ensure_provider_enum(provider)
|
|
248
262
|
provider_config = self._get_provider_config(config, provider_enum)
|
|
249
263
|
|
|
@@ -251,7 +265,8 @@ class ConfigManager:
|
|
|
251
265
|
|
|
252
266
|
def has_any_provider_key(self) -> bool:
|
|
253
267
|
"""Determine whether any provider has a configured API key."""
|
|
254
|
-
|
|
268
|
+
# Use force_reload=False to avoid infinite loop when called from load()
|
|
269
|
+
config = self.load(force_reload=False)
|
|
255
270
|
# Check LLM provider keys (BYOK)
|
|
256
271
|
has_llm_key = any(
|
|
257
272
|
self._provider_has_api_key(self._get_provider_config(config, provider))
|
|
@@ -271,15 +286,15 @@ class ConfigManager:
|
|
|
271
286
|
Returns:
|
|
272
287
|
Default ShotgunConfig
|
|
273
288
|
"""
|
|
274
|
-
# Generate unique
|
|
289
|
+
# Generate unique shotgun instance ID for new config
|
|
275
290
|
config = ShotgunConfig(
|
|
276
|
-
|
|
291
|
+
shotgun_instance_id=str(uuid.uuid4()),
|
|
277
292
|
)
|
|
278
293
|
self.save(config)
|
|
279
294
|
logger.info(
|
|
280
|
-
"Configuration initialized at %s with
|
|
295
|
+
"Configuration initialized at %s with shotgun_instance_id: %s",
|
|
281
296
|
self.config_path,
|
|
282
|
-
config.
|
|
297
|
+
config.shotgun_instance_id,
|
|
283
298
|
)
|
|
284
299
|
return config
|
|
285
300
|
|
|
@@ -287,6 +302,7 @@ class ConfigManager:
|
|
|
287
302
|
"""Convert plain text secrets in data to SecretStr objects."""
|
|
288
303
|
for section in ConfigSection:
|
|
289
304
|
if section.value in data and isinstance(data[section.value], dict):
|
|
305
|
+
# Convert API key
|
|
290
306
|
if (
|
|
291
307
|
API_KEY_FIELD in data[section.value]
|
|
292
308
|
and data[section.value][API_KEY_FIELD] is not None
|
|
@@ -294,11 +310,21 @@ class ConfigManager:
|
|
|
294
310
|
data[section.value][API_KEY_FIELD] = SecretStr(
|
|
295
311
|
data[section.value][API_KEY_FIELD]
|
|
296
312
|
)
|
|
313
|
+
# Convert supabase JWT (shotgun section only)
|
|
314
|
+
if (
|
|
315
|
+
section == ConfigSection.SHOTGUN
|
|
316
|
+
and SUPABASE_JWT_FIELD in data[section.value]
|
|
317
|
+
and data[section.value][SUPABASE_JWT_FIELD] is not None
|
|
318
|
+
):
|
|
319
|
+
data[section.value][SUPABASE_JWT_FIELD] = SecretStr(
|
|
320
|
+
data[section.value][SUPABASE_JWT_FIELD]
|
|
321
|
+
)
|
|
297
322
|
|
|
298
323
|
def _convert_secretstr_to_plain(self, data: dict[str, Any]) -> None:
|
|
299
324
|
"""Convert SecretStr objects in data to plain text for JSON serialization."""
|
|
300
325
|
for section in ConfigSection:
|
|
301
326
|
if section.value in data and isinstance(data[section.value], dict):
|
|
327
|
+
# Convert API key
|
|
302
328
|
if (
|
|
303
329
|
API_KEY_FIELD in data[section.value]
|
|
304
330
|
and data[section.value][API_KEY_FIELD] is not None
|
|
@@ -307,6 +333,18 @@ class ConfigManager:
|
|
|
307
333
|
data[section.value][API_KEY_FIELD] = data[section.value][
|
|
308
334
|
API_KEY_FIELD
|
|
309
335
|
].get_secret_value()
|
|
336
|
+
# Convert supabase JWT (shotgun section only)
|
|
337
|
+
if (
|
|
338
|
+
section == ConfigSection.SHOTGUN
|
|
339
|
+
and SUPABASE_JWT_FIELD in data[section.value]
|
|
340
|
+
and data[section.value][SUPABASE_JWT_FIELD] is not None
|
|
341
|
+
):
|
|
342
|
+
if hasattr(
|
|
343
|
+
data[section.value][SUPABASE_JWT_FIELD], "get_secret_value"
|
|
344
|
+
):
|
|
345
|
+
data[section.value][SUPABASE_JWT_FIELD] = data[section.value][
|
|
346
|
+
SUPABASE_JWT_FIELD
|
|
347
|
+
].get_secret_value()
|
|
310
348
|
|
|
311
349
|
def _ensure_provider_enum(self, provider: ProviderType | str) -> ProviderType:
|
|
312
350
|
"""Normalize provider values to ProviderType enum."""
|
|
@@ -371,16 +409,49 @@ class ConfigManager:
|
|
|
371
409
|
provider_enum = self._ensure_provider_enum(provider)
|
|
372
410
|
return (self._get_provider_config(config, provider_enum), False)
|
|
373
411
|
|
|
374
|
-
def
|
|
375
|
-
"""Get the
|
|
412
|
+
def get_shotgun_instance_id(self) -> str:
|
|
413
|
+
"""Get the shotgun instance ID from configuration.
|
|
376
414
|
|
|
377
415
|
Returns:
|
|
378
|
-
The unique
|
|
416
|
+
The unique shotgun instance ID string
|
|
379
417
|
"""
|
|
380
418
|
config = self.load()
|
|
381
|
-
return config.
|
|
419
|
+
return config.shotgun_instance_id
|
|
420
|
+
|
|
421
|
+
def update_shotgun_account(
|
|
422
|
+
self, api_key: str | None = None, supabase_jwt: str | None = None
|
|
423
|
+
) -> None:
|
|
424
|
+
"""Update Shotgun Account configuration.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
api_key: LiteLLM proxy API key (optional)
|
|
428
|
+
supabase_jwt: Supabase authentication JWT (optional)
|
|
429
|
+
"""
|
|
430
|
+
config = self.load()
|
|
431
|
+
|
|
432
|
+
if api_key is not None:
|
|
433
|
+
config.shotgun.api_key = SecretStr(api_key) if api_key else None
|
|
434
|
+
|
|
435
|
+
if supabase_jwt is not None:
|
|
436
|
+
config.shotgun.supabase_jwt = (
|
|
437
|
+
SecretStr(supabase_jwt) if supabase_jwt else None
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
self.save(config)
|
|
441
|
+
logger.info("Updated Shotgun Account configuration")
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
# Global singleton instance
|
|
445
|
+
_config_manager_instance: ConfigManager | None = None
|
|
382
446
|
|
|
383
447
|
|
|
384
448
|
def get_config_manager() -> ConfigManager:
|
|
385
|
-
"""Get the global ConfigManager instance.
|
|
386
|
-
|
|
449
|
+
"""Get the global singleton ConfigManager instance.
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
The singleton ConfigManager instance
|
|
453
|
+
"""
|
|
454
|
+
global _config_manager_instance
|
|
455
|
+
if _config_manager_instance is None:
|
|
456
|
+
_config_manager_instance = ConfigManager()
|
|
457
|
+
return _config_manager_instance
|
|
@@ -149,6 +149,9 @@ class ShotgunAccountConfig(BaseModel):
|
|
|
149
149
|
"""Configuration for Shotgun Account (LiteLLM proxy)."""
|
|
150
150
|
|
|
151
151
|
api_key: SecretStr | None = None
|
|
152
|
+
supabase_jwt: SecretStr | None = Field(
|
|
153
|
+
default=None, description="Supabase authentication JWT"
|
|
154
|
+
)
|
|
152
155
|
|
|
153
156
|
|
|
154
157
|
class ShotgunConfig(BaseModel):
|
|
@@ -162,5 +165,11 @@ class ShotgunConfig(BaseModel):
|
|
|
162
165
|
default=None,
|
|
163
166
|
description="User-selected model",
|
|
164
167
|
)
|
|
165
|
-
|
|
166
|
-
|
|
168
|
+
shotgun_instance_id: str = Field(
|
|
169
|
+
description="Unique shotgun instance identifier (also used for anonymous telemetry)",
|
|
170
|
+
)
|
|
171
|
+
config_version: int = Field(default=3, description="Configuration schema version")
|
|
172
|
+
shown_welcome_screen: bool = Field(
|
|
173
|
+
default=False,
|
|
174
|
+
description="Whether the welcome screen has been shown to the user",
|
|
175
|
+
)
|
|
@@ -139,7 +139,8 @@ def get_provider_model(
|
|
|
139
139
|
ValueError: If provider is not configured properly or model not found
|
|
140
140
|
"""
|
|
141
141
|
config_manager = get_config_manager()
|
|
142
|
-
config
|
|
142
|
+
# Use cached config for read-only access (performance)
|
|
143
|
+
config = config_manager.load(force_reload=False)
|
|
143
144
|
|
|
144
145
|
# Priority 1: Check if Shotgun key exists - if so, use it for ANY model
|
|
145
146
|
shotgun_api_key = _get_api_key(config.shotgun.api_key)
|
|
@@ -233,14 +233,14 @@ def _mask_value(value: str) -> str:
|
|
|
233
233
|
|
|
234
234
|
|
|
235
235
|
@app.command()
|
|
236
|
-
def
|
|
237
|
-
"""Get the anonymous
|
|
236
|
+
def get_shotgun_instance_id() -> None:
|
|
237
|
+
"""Get the anonymous shotgun instance ID from configuration."""
|
|
238
238
|
config_manager = get_config_manager()
|
|
239
239
|
|
|
240
240
|
try:
|
|
241
|
-
|
|
242
|
-
console.print(f"[green]
|
|
241
|
+
shotgun_instance_id = config_manager.get_shotgun_instance_id()
|
|
242
|
+
console.print(f"[green]Shotgun Instance ID:[/green] {shotgun_instance_id}")
|
|
243
243
|
except Exception as e:
|
|
244
|
-
logger.error(f"Error getting
|
|
245
|
-
console.print(f"❌ Failed to get
|
|
244
|
+
logger.error(f"Error getting shotgun instance ID: {e}")
|
|
245
|
+
console.print(f"❌ Failed to get shotgun instance ID: {str(e)}", style="red")
|
|
246
246
|
raise typer.Exit(1) from e
|
|
@@ -30,7 +30,7 @@ def send_feedback(
|
|
|
30
30
|
"""Initialize Shotgun configuration."""
|
|
31
31
|
config_manager = get_config_manager()
|
|
32
32
|
config_manager.load()
|
|
33
|
-
|
|
33
|
+
shotgun_instance_id = config_manager.get_shotgun_instance_id()
|
|
34
34
|
|
|
35
35
|
if not description:
|
|
36
36
|
console.print(
|
|
@@ -39,7 +39,9 @@ def send_feedback(
|
|
|
39
39
|
)
|
|
40
40
|
raise typer.Exit(1)
|
|
41
41
|
|
|
42
|
-
feedback = Feedback(
|
|
42
|
+
feedback = Feedback(
|
|
43
|
+
kind=kind, description=description, shotgun_instance_id=shotgun_instance_id
|
|
44
|
+
)
|
|
43
45
|
|
|
44
46
|
submit_feedback_survey(feedback)
|
|
45
47
|
|
|
@@ -18,15 +18,12 @@ from shotgun.logging_config import get_logger
|
|
|
18
18
|
logger = get_logger(__name__)
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
#
|
|
22
|
-
|
|
21
|
+
# Directories that should never be traversed during indexing
|
|
22
|
+
BASE_IGNORE_DIRECTORIES = {
|
|
23
23
|
".git",
|
|
24
24
|
"venv",
|
|
25
25
|
".venv",
|
|
26
26
|
"__pycache__",
|
|
27
|
-
"node_modules",
|
|
28
|
-
"build",
|
|
29
|
-
"dist",
|
|
30
27
|
".eggs",
|
|
31
28
|
".pytest_cache",
|
|
32
29
|
".mypy_cache",
|
|
@@ -36,6 +33,29 @@ IGNORE_PATTERNS = {
|
|
|
36
33
|
".vscode",
|
|
37
34
|
}
|
|
38
35
|
|
|
36
|
+
# Well-known build output directories to skip when determining source files
|
|
37
|
+
BUILD_ARTIFACT_DIRECTORIES = {
|
|
38
|
+
"node_modules",
|
|
39
|
+
".next",
|
|
40
|
+
".nuxt",
|
|
41
|
+
".vite",
|
|
42
|
+
".yarn",
|
|
43
|
+
".svelte-kit",
|
|
44
|
+
".output",
|
|
45
|
+
".turbo",
|
|
46
|
+
".parcel-cache",
|
|
47
|
+
".vercel",
|
|
48
|
+
".serverless",
|
|
49
|
+
"build",
|
|
50
|
+
"dist",
|
|
51
|
+
"out",
|
|
52
|
+
"tmp",
|
|
53
|
+
"coverage",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Default ignore patterns combines base directories and build artifacts
|
|
57
|
+
IGNORE_PATTERNS = BASE_IGNORE_DIRECTORIES | BUILD_ARTIFACT_DIRECTORIES
|
|
58
|
+
|
|
39
59
|
|
|
40
60
|
class Ingestor:
|
|
41
61
|
"""Handles all communication and ingestion with the Kuzu database."""
|
|
@@ -51,14 +51,14 @@ def setup_posthog_observability() -> bool:
|
|
|
51
51
|
# Store the client for later use
|
|
52
52
|
_posthog_client = posthog
|
|
53
53
|
|
|
54
|
-
# Set user context with anonymous
|
|
54
|
+
# Set user context with anonymous shotgun instance ID from config
|
|
55
55
|
try:
|
|
56
56
|
config_manager = get_config_manager()
|
|
57
|
-
|
|
57
|
+
shotgun_instance_id = config_manager.get_shotgun_instance_id()
|
|
58
58
|
|
|
59
59
|
# Identify the user in PostHog
|
|
60
60
|
posthog.identify( # type: ignore[attr-defined]
|
|
61
|
-
distinct_id=
|
|
61
|
+
distinct_id=shotgun_instance_id,
|
|
62
62
|
properties={
|
|
63
63
|
"version": __version__,
|
|
64
64
|
"environment": environment,
|
|
@@ -69,7 +69,9 @@ def setup_posthog_observability() -> bool:
|
|
|
69
69
|
posthog.disabled = False
|
|
70
70
|
posthog.personal_api_key = None # Not needed for event tracking
|
|
71
71
|
|
|
72
|
-
logger.debug(
|
|
72
|
+
logger.debug(
|
|
73
|
+
"PostHog user identified with anonymous ID: %s", shotgun_instance_id
|
|
74
|
+
)
|
|
73
75
|
except Exception as e:
|
|
74
76
|
logger.warning("Failed to set user context: %s", e)
|
|
75
77
|
|
|
@@ -99,9 +101,9 @@ def track_event(event_name: str, properties: dict[str, Any] | None = None) -> No
|
|
|
99
101
|
return
|
|
100
102
|
|
|
101
103
|
try:
|
|
102
|
-
# Get
|
|
104
|
+
# Get shotgun instance ID for tracking
|
|
103
105
|
config_manager = get_config_manager()
|
|
104
|
-
|
|
106
|
+
shotgun_instance_id = config_manager.get_shotgun_instance_id()
|
|
105
107
|
|
|
106
108
|
# Add version and environment to properties
|
|
107
109
|
if properties is None:
|
|
@@ -116,7 +118,7 @@ def track_event(event_name: str, properties: dict[str, Any] | None = None) -> No
|
|
|
116
118
|
|
|
117
119
|
# Track the event using PostHog's capture method
|
|
118
120
|
_posthog_client.capture(
|
|
119
|
-
distinct_id=
|
|
121
|
+
distinct_id=shotgun_instance_id, event=event_name, properties=properties
|
|
120
122
|
)
|
|
121
123
|
logger.debug("Tracked PostHog event: %s", event_name)
|
|
122
124
|
except Exception as e:
|
|
@@ -146,7 +148,7 @@ class FeedbackKind(StrEnum):
|
|
|
146
148
|
class Feedback(BaseModel):
|
|
147
149
|
kind: FeedbackKind
|
|
148
150
|
description: str
|
|
149
|
-
|
|
151
|
+
shotgun_instance_id: str
|
|
150
152
|
|
|
151
153
|
|
|
152
154
|
SURVEY_ID = "01999f81-9486-0000-4fa6-9632959f92f3"
|
|
@@ -59,13 +59,13 @@ def setup_sentry_observability() -> bool:
|
|
|
59
59
|
profiles_sample_rate=0.1 if environment == "production" else 1.0,
|
|
60
60
|
)
|
|
61
61
|
|
|
62
|
-
# Set user context with anonymous
|
|
62
|
+
# Set user context with anonymous shotgun instance ID from config
|
|
63
63
|
try:
|
|
64
64
|
from shotgun.agents.config import get_config_manager
|
|
65
65
|
|
|
66
66
|
config_manager = get_config_manager()
|
|
67
|
-
|
|
68
|
-
sentry_sdk.set_user({"id":
|
|
67
|
+
shotgun_instance_id = config_manager.get_shotgun_instance_id()
|
|
68
|
+
sentry_sdk.set_user({"id": shotgun_instance_id})
|
|
69
69
|
logger.debug("Sentry user context set with anonymous ID")
|
|
70
70
|
except Exception as e:
|
|
71
71
|
logger.warning("Failed to set Sentry user context: %s", e)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Shotgun Web API client for subscription and authentication."""
|
|
2
|
+
|
|
3
|
+
from .client import ShotgunWebClient, check_token_status, create_unification_token
|
|
4
|
+
from .models import (
|
|
5
|
+
TokenCreateRequest,
|
|
6
|
+
TokenCreateResponse,
|
|
7
|
+
TokenStatus,
|
|
8
|
+
TokenStatusResponse,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"ShotgunWebClient",
|
|
13
|
+
"create_unification_token",
|
|
14
|
+
"check_token_status",
|
|
15
|
+
"TokenCreateRequest",
|
|
16
|
+
"TokenCreateResponse",
|
|
17
|
+
"TokenStatus",
|
|
18
|
+
"TokenStatusResponse",
|
|
19
|
+
]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""HTTP client for Shotgun Web API."""
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
from shotgun.logging_config import get_logger
|
|
6
|
+
|
|
7
|
+
from .constants import (
|
|
8
|
+
SHOTGUN_WEB_BASE_URL,
|
|
9
|
+
UNIFICATION_TOKEN_CREATE_PATH,
|
|
10
|
+
UNIFICATION_TOKEN_STATUS_PATH,
|
|
11
|
+
)
|
|
12
|
+
from .models import (
|
|
13
|
+
TokenCreateRequest,
|
|
14
|
+
TokenCreateResponse,
|
|
15
|
+
TokenStatusResponse,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
logger = get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ShotgunWebClient:
|
|
22
|
+
"""HTTP client for Shotgun Web API."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, base_url: str | None = None, timeout: float = 10.0):
|
|
25
|
+
"""Initialize Shotgun Web client.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
base_url: Base URL for Shotgun Web API. If None, uses SHOTGUN_WEB_BASE_URL
|
|
29
|
+
timeout: Request timeout in seconds
|
|
30
|
+
"""
|
|
31
|
+
self.base_url = base_url or SHOTGUN_WEB_BASE_URL
|
|
32
|
+
self.timeout = timeout
|
|
33
|
+
|
|
34
|
+
def create_unification_token(self, shotgun_instance_id: str) -> TokenCreateResponse:
|
|
35
|
+
"""Create a unification token for CLI authentication.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
shotgun_instance_id: UUID for this shotgun instance
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Token creation response with token and auth URL
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
httpx.HTTPError: If request fails
|
|
45
|
+
"""
|
|
46
|
+
url = f"{self.base_url}{UNIFICATION_TOKEN_CREATE_PATH}"
|
|
47
|
+
request_data = TokenCreateRequest(shotgun_instance_id=shotgun_instance_id)
|
|
48
|
+
|
|
49
|
+
logger.debug("Creating unification token for instance %s", shotgun_instance_id)
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
response = httpx.post(
|
|
53
|
+
url,
|
|
54
|
+
json=request_data.model_dump(),
|
|
55
|
+
timeout=self.timeout,
|
|
56
|
+
)
|
|
57
|
+
response.raise_for_status()
|
|
58
|
+
|
|
59
|
+
data = response.json()
|
|
60
|
+
result = TokenCreateResponse.model_validate(data)
|
|
61
|
+
|
|
62
|
+
logger.info(
|
|
63
|
+
"Successfully created unification token, expires in %d seconds",
|
|
64
|
+
result.expires_in_seconds,
|
|
65
|
+
)
|
|
66
|
+
return result
|
|
67
|
+
|
|
68
|
+
except httpx.HTTPError as e:
|
|
69
|
+
logger.error("Failed to create unification token: %s", e)
|
|
70
|
+
raise
|
|
71
|
+
|
|
72
|
+
def check_token_status(self, token: str) -> TokenStatusResponse:
|
|
73
|
+
"""Check token status and get keys if completed.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
token: Unification token to check
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Token status response with status and keys (if completed)
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
httpx.HTTPStatusError: If token not found (404) or expired (410)
|
|
83
|
+
httpx.HTTPError: For other request failures
|
|
84
|
+
"""
|
|
85
|
+
url = f"{self.base_url}{UNIFICATION_TOKEN_STATUS_PATH.format(token=token)}"
|
|
86
|
+
|
|
87
|
+
logger.debug("Checking status for token %s...", token[:8])
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
response = httpx.get(url, timeout=self.timeout)
|
|
91
|
+
response.raise_for_status()
|
|
92
|
+
|
|
93
|
+
data = response.json()
|
|
94
|
+
result = TokenStatusResponse.model_validate(data)
|
|
95
|
+
|
|
96
|
+
logger.debug("Token status: %s", result.status)
|
|
97
|
+
return result
|
|
98
|
+
|
|
99
|
+
except httpx.HTTPStatusError as e:
|
|
100
|
+
if e.response.status_code == 404:
|
|
101
|
+
logger.error("Token not found: %s", token[:8])
|
|
102
|
+
elif e.response.status_code == 410:
|
|
103
|
+
logger.error("Token expired: %s", token[:8])
|
|
104
|
+
raise
|
|
105
|
+
except httpx.HTTPError as e:
|
|
106
|
+
logger.error("Failed to check token status: %s", e)
|
|
107
|
+
raise
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# Convenience functions for standalone use
|
|
111
|
+
def create_unification_token(shotgun_instance_id: str) -> TokenCreateResponse:
|
|
112
|
+
"""Create a unification token.
|
|
113
|
+
|
|
114
|
+
Convenience function that creates a client and calls create_unification_token.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
shotgun_instance_id: UUID for this shotgun instance
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Token creation response
|
|
121
|
+
"""
|
|
122
|
+
client = ShotgunWebClient()
|
|
123
|
+
return client.create_unification_token(shotgun_instance_id)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def check_token_status(token: str) -> TokenStatusResponse:
|
|
127
|
+
"""Check token status.
|
|
128
|
+
|
|
129
|
+
Convenience function that creates a client and calls check_token_status.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
token: Unification token to check
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Token status response
|
|
136
|
+
"""
|
|
137
|
+
client = ShotgunWebClient()
|
|
138
|
+
return client.check_token_status(token)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Constants for Shotgun Web API."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
# Shotgun Web API base URL
|
|
6
|
+
# Default to production URL, can be overridden with environment variable
|
|
7
|
+
SHOTGUN_WEB_BASE_URL = os.environ.get(
|
|
8
|
+
"SHOTGUN_WEB_BASE_URL", "https://api-701197220809.us-east1.run.app"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
# API endpoints
|
|
12
|
+
UNIFICATION_TOKEN_CREATE_PATH = "/api/unification/token/create" # noqa: S105
|
|
13
|
+
UNIFICATION_TOKEN_STATUS_PATH = "/api/unification/token/{token}/status" # noqa: S105
|
|
14
|
+
|
|
15
|
+
# Polling configuration
|
|
16
|
+
DEFAULT_POLL_INTERVAL_SECONDS = 3
|
|
17
|
+
DEFAULT_TOKEN_TIMEOUT_SECONDS = 1800 # 30 minutes
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Pydantic models for Shotgun Web API."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TokenStatus(StrEnum):
|
|
9
|
+
"""Token status enum matching API specification."""
|
|
10
|
+
|
|
11
|
+
PENDING = "pending"
|
|
12
|
+
COMPLETED = "completed"
|
|
13
|
+
AWAITING_PAYMENT = "awaiting_payment"
|
|
14
|
+
EXPIRED = "expired"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TokenCreateRequest(BaseModel):
|
|
18
|
+
"""Request model for creating a unification token."""
|
|
19
|
+
|
|
20
|
+
shotgun_instance_id: str = Field(
|
|
21
|
+
description="CLI-provided UUID for shotgun instance"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TokenCreateResponse(BaseModel):
|
|
26
|
+
"""Response model for token creation."""
|
|
27
|
+
|
|
28
|
+
token: str = Field(description="Secure authentication token")
|
|
29
|
+
auth_url: str = Field(description="Web authentication URL for user to complete")
|
|
30
|
+
expires_in_seconds: int = Field(description="Token expiration time in seconds")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TokenStatusResponse(BaseModel):
|
|
34
|
+
"""Response model for token status check."""
|
|
35
|
+
|
|
36
|
+
status: TokenStatus = Field(description="Current token status")
|
|
37
|
+
supabase_key: str | None = Field(
|
|
38
|
+
default=None,
|
|
39
|
+
description="Supabase user JWT (only returned when status=completed)",
|
|
40
|
+
)
|
|
41
|
+
litellm_key: str | None = Field(
|
|
42
|
+
default=None,
|
|
43
|
+
description="LiteLLM virtual key (only returned when status=completed)",
|
|
44
|
+
)
|
|
45
|
+
message: str | None = Field(
|
|
46
|
+
default=None, description="Human-readable status message"
|
|
47
|
+
)
|