shotgun-sh 0.1.6__tar.gz → 0.1.8__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.1.6 → shotgun_sh-0.1.8}/PKG-INFO +1 -1
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/pyproject.toml +1 -1
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/main.py +3 -42
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/app.py +3 -25
- shotgun_sh-0.1.8/src/shotgun/utils/update_checker.py +247 -0
- shotgun_sh-0.1.6/src/shotgun/utils/update_checker.py +0 -692
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/.gitignore +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/LICENSE +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/README.md +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/hatch_build.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/agent_manager.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/common.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/config/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/config/constants.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/config/manager.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/config/models.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/config/provider.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/conversation_history.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/conversation_manager.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/export.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/history/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/history/compaction.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/history/constants.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/history/context_extraction.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/history/history_building.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/history/history_processors.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/history/message_utils.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/history/token_counting.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/history/token_estimation.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/messages.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/models.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/plan.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/research.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/specify.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/tasks.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/codebase/models.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/file_management.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/user_interaction.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/web_search/openai.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/web_search/utils.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/build_constants.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/cli/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/cli/codebase/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/cli/codebase/commands.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/cli/codebase/models.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/cli/config.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/cli/export.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/cli/models.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/cli/plan.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/cli/research.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/cli/specify.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/cli/tasks.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/cli/update.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/cli/utils.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/codebase/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/codebase/core/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/codebase/core/change_detector.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/codebase/core/code_retrieval.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/codebase/core/cypher_models.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/codebase/core/ingestor.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/codebase/core/language_config.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/codebase/core/manager.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/codebase/core/nl_query.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/codebase/core/parser_loader.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/codebase/models.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/codebase/service.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/logging_config.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/posthog_telemetry.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/export.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/plan.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/research.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/specify.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/tasks.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/codebase/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/history/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/history/summarization.j2 +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/prompts/loader.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/py.typed +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/sdk/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/sdk/codebase.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/sdk/exceptions.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/sdk/models.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/sdk/services.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/sentry_telemetry.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/telemetry.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/commands/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/components/prompt_input.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/components/spinner.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/components/splash.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/components/vertical_tail.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/screens/chat.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/screens/chat.tcss +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/screens/chat_screen/command_providers.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/screens/chat_screen/history.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/screens/directory_setup.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/screens/provider_config.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/screens/splash.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/styles.tcss +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/utils/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/tui/utils/mode_progress.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/utils/__init__.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/utils/env_utils.py +0 -0
- {shotgun_sh-0.1.6 → shotgun_sh-0.1.8}/src/shotgun/utils/file_system_utils.py +0 -0
|
@@ -22,7 +22,7 @@ from shotgun.posthog_telemetry import setup_posthog_observability
|
|
|
22
22
|
from shotgun.sentry_telemetry import setup_sentry_observability
|
|
23
23
|
from shotgun.telemetry import setup_logfire_observability
|
|
24
24
|
from shotgun.tui import app as tui_app
|
|
25
|
-
from shotgun.utils.update_checker import
|
|
25
|
+
from shotgun.utils.update_checker import perform_auto_update_async
|
|
26
26
|
|
|
27
27
|
# Load environment variables from .env file
|
|
28
28
|
load_dotenv()
|
|
@@ -50,23 +50,6 @@ logger.debug("Sentry observability enabled: %s", _sentry_enabled)
|
|
|
50
50
|
_posthog_enabled = setup_posthog_observability()
|
|
51
51
|
logger.debug("PostHog analytics enabled: %s", _posthog_enabled)
|
|
52
52
|
|
|
53
|
-
# Global variable to store update notification
|
|
54
|
-
_update_notification: str | None = None
|
|
55
|
-
_update_progress: str | None = None
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def _update_callback(notification: str) -> None:
|
|
59
|
-
"""Callback to store update notification."""
|
|
60
|
-
global _update_notification
|
|
61
|
-
_update_notification = notification
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def _update_progress_callback(progress: str) -> None:
|
|
65
|
-
"""Callback to store update progress."""
|
|
66
|
-
global _update_progress
|
|
67
|
-
_update_progress = progress
|
|
68
|
-
logger.debug(f"Update progress: {progress}")
|
|
69
|
-
|
|
70
53
|
|
|
71
54
|
app = typer.Typer(
|
|
72
55
|
name="shotgun",
|
|
@@ -131,39 +114,17 @@ def main(
|
|
|
131
114
|
|
|
132
115
|
# Start async update check and install (non-blocking)
|
|
133
116
|
if not ctx.resilient_parsing:
|
|
134
|
-
|
|
135
|
-
callback=_update_callback,
|
|
136
|
-
no_update_check=no_update_check,
|
|
137
|
-
progress_callback=_update_progress_callback,
|
|
138
|
-
)
|
|
117
|
+
perform_auto_update_async(no_update_check=no_update_check)
|
|
139
118
|
|
|
140
119
|
if ctx.invoked_subcommand is None and not ctx.resilient_parsing:
|
|
141
120
|
logger.debug("Launching shotgun TUI application")
|
|
142
121
|
tui_app.run(no_update_check=no_update_check, continue_session=continue_session)
|
|
143
|
-
|
|
144
|
-
# Show update notification after TUI exits
|
|
145
|
-
if _update_notification:
|
|
146
|
-
from rich.console import Console
|
|
147
|
-
|
|
148
|
-
console = Console()
|
|
149
|
-
console.print(f"\n[cyan]{_update_notification}[/cyan]", style="bold")
|
|
150
|
-
|
|
151
122
|
raise typer.Exit()
|
|
152
123
|
|
|
153
|
-
# For CLI commands,
|
|
154
|
-
# This is handled by registering an atexit handler
|
|
124
|
+
# For CLI commands, register PostHog shutdown handler
|
|
155
125
|
if not ctx.resilient_parsing and ctx.invoked_subcommand is not None:
|
|
156
126
|
import atexit
|
|
157
127
|
|
|
158
|
-
def show_update_notification() -> None:
|
|
159
|
-
if _update_notification:
|
|
160
|
-
from rich.console import Console
|
|
161
|
-
|
|
162
|
-
console = Console()
|
|
163
|
-
console.print(f"\n[cyan]{_update_notification}[/cyan]", style="bold")
|
|
164
|
-
|
|
165
|
-
atexit.register(show_update_notification)
|
|
166
|
-
|
|
167
128
|
# Register PostHog shutdown handler
|
|
168
129
|
def shutdown_posthog() -> None:
|
|
169
130
|
from shotgun.posthog_telemetry import shutdown
|
|
@@ -9,7 +9,7 @@ from shotgun.agents.config import ConfigManager, get_config_manager
|
|
|
9
9
|
from shotgun.logging_config import get_logger
|
|
10
10
|
from shotgun.tui.screens.splash import SplashScreen
|
|
11
11
|
from shotgun.utils.file_system_utils import get_shotgun_base_path
|
|
12
|
-
from shotgun.utils.update_checker import
|
|
12
|
+
from shotgun.utils.update_checker import perform_auto_update_async
|
|
13
13
|
|
|
14
14
|
from .screens.chat import ChatScreen
|
|
15
15
|
from .screens.directory_setup import DirectorySetupScreen
|
|
@@ -36,26 +36,10 @@ class ShotgunApp(App[None]):
|
|
|
36
36
|
self.config_manager: ConfigManager = get_config_manager()
|
|
37
37
|
self.no_update_check = no_update_check
|
|
38
38
|
self.continue_session = continue_session
|
|
39
|
-
self.update_notification: str | None = None
|
|
40
|
-
self.update_progress: str | None = None
|
|
41
39
|
|
|
42
40
|
# Start async update check and install
|
|
43
41
|
if not no_update_check:
|
|
44
|
-
|
|
45
|
-
callback=self._update_callback,
|
|
46
|
-
no_update_check=no_update_check,
|
|
47
|
-
progress_callback=self._update_progress_callback,
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
def _update_callback(self, notification: str) -> None:
|
|
51
|
-
"""Store update notification to show later."""
|
|
52
|
-
self.update_notification = notification
|
|
53
|
-
logger.debug(f"Update notification received: {notification}")
|
|
54
|
-
|
|
55
|
-
def _update_progress_callback(self, progress: str) -> None:
|
|
56
|
-
"""Store update progress."""
|
|
57
|
-
self.update_progress = progress
|
|
58
|
-
logger.debug(f"Update progress: {progress}")
|
|
42
|
+
perform_auto_update_async(no_update_check=no_update_check)
|
|
59
43
|
|
|
60
44
|
def on_mount(self) -> None:
|
|
61
45
|
self.theme = "gruvbox"
|
|
@@ -98,13 +82,7 @@ class ShotgunApp(App[None]):
|
|
|
98
82
|
return shotgun_dir.exists() and shotgun_dir.is_dir()
|
|
99
83
|
|
|
100
84
|
async def action_quit(self) -> None:
|
|
101
|
-
"""
|
|
102
|
-
if self.update_notification:
|
|
103
|
-
# Show notification before quitting
|
|
104
|
-
from rich.console import Console
|
|
105
|
-
|
|
106
|
-
console = Console()
|
|
107
|
-
console.print(f"\n[cyan]{self.update_notification}[/cyan]", style="bold")
|
|
85
|
+
"""Quit the application."""
|
|
108
86
|
self.exit()
|
|
109
87
|
|
|
110
88
|
def get_system_commands(self, screen: Screen[Any]) -> Iterable[SystemCommand]:
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""Simple auto-update functionality for shotgun-sh CLI."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
import threading
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from shotgun.logging_config import get_logger
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def detect_installation_method() -> str:
|
|
14
|
+
"""Detect how shotgun-sh was installed.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Installation method: 'pipx', 'pip', 'venv', or 'unknown'.
|
|
18
|
+
"""
|
|
19
|
+
# Check for pipx installation
|
|
20
|
+
try:
|
|
21
|
+
result = subprocess.run(
|
|
22
|
+
["pipx", "list", "--short"], # noqa: S607
|
|
23
|
+
capture_output=True,
|
|
24
|
+
text=True,
|
|
25
|
+
timeout=5, # noqa: S603
|
|
26
|
+
)
|
|
27
|
+
if "shotgun-sh" in result.stdout:
|
|
28
|
+
logger.debug("Detected pipx installation")
|
|
29
|
+
return "pipx"
|
|
30
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
# Check if we're in a virtual environment
|
|
34
|
+
if hasattr(sys, "real_prefix") or (
|
|
35
|
+
hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix
|
|
36
|
+
):
|
|
37
|
+
logger.debug("Detected virtual environment installation")
|
|
38
|
+
return "venv"
|
|
39
|
+
|
|
40
|
+
# Check for user installation
|
|
41
|
+
import site
|
|
42
|
+
|
|
43
|
+
user_site = site.getusersitepackages()
|
|
44
|
+
if user_site and Path(user_site).exists():
|
|
45
|
+
shotgun_path = Path(user_site) / "shotgun"
|
|
46
|
+
if shotgun_path.exists() or any(
|
|
47
|
+
p.exists() for p in Path(user_site).glob("shotgun_sh*")
|
|
48
|
+
):
|
|
49
|
+
logger.debug("Detected pip --user installation")
|
|
50
|
+
return "pip"
|
|
51
|
+
|
|
52
|
+
# Default to pip if we can't determine
|
|
53
|
+
logger.debug("Could not detect installation method, defaulting to pip")
|
|
54
|
+
return "pip"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def perform_auto_update(no_update_check: bool = False) -> None:
|
|
58
|
+
"""Perform automatic update if installed via pipx.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
no_update_check: If True, skip the update.
|
|
62
|
+
"""
|
|
63
|
+
if no_update_check:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
# Only auto-update for pipx installations
|
|
68
|
+
if detect_installation_method() != "pipx":
|
|
69
|
+
logger.debug("Not a pipx installation, skipping auto-update")
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
# Run pipx upgrade quietly
|
|
73
|
+
logger.debug("Running pipx upgrade shotgun-sh --quiet")
|
|
74
|
+
result = subprocess.run(
|
|
75
|
+
["pipx", "upgrade", "shotgun-sh", "--quiet"], # noqa: S607, S603
|
|
76
|
+
capture_output=True,
|
|
77
|
+
text=True,
|
|
78
|
+
timeout=30,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if result.returncode == 0:
|
|
82
|
+
# Check if there was an actual update (pipx shows output even with --quiet for actual updates)
|
|
83
|
+
if result.stdout and "upgraded" in result.stdout.lower():
|
|
84
|
+
logger.info("Shotgun-sh has been updated to the latest version")
|
|
85
|
+
else:
|
|
86
|
+
# Only log errors at debug level to not annoy users
|
|
87
|
+
logger.debug(f"Auto-update check failed: {result.stderr or result.stdout}")
|
|
88
|
+
|
|
89
|
+
except subprocess.TimeoutExpired:
|
|
90
|
+
logger.debug("Auto-update timed out")
|
|
91
|
+
except Exception as e:
|
|
92
|
+
logger.debug(f"Auto-update error: {e}")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def perform_auto_update_async(no_update_check: bool = False) -> threading.Thread:
|
|
96
|
+
"""Run auto-update in a background thread.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
no_update_check: If True, skip the update.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
The thread object that was started.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def _run_update() -> None:
|
|
106
|
+
perform_auto_update(no_update_check)
|
|
107
|
+
|
|
108
|
+
thread = threading.Thread(target=_run_update, daemon=True)
|
|
109
|
+
thread.start()
|
|
110
|
+
return thread
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# Keep these for backward compatibility with the update CLI command
|
|
114
|
+
import httpx # noqa: E402
|
|
115
|
+
from packaging import version # noqa: E402
|
|
116
|
+
|
|
117
|
+
from shotgun import __version__ # noqa: E402
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def is_dev_version(version_str: str | None = None) -> bool:
|
|
121
|
+
"""Check if the current or given version is a development version.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
version_str: Version string to check. If None, uses current version.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
True if version contains 'dev', False otherwise.
|
|
128
|
+
"""
|
|
129
|
+
check_version = version_str or __version__
|
|
130
|
+
return "dev" in check_version.lower()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_latest_version() -> str | None:
|
|
134
|
+
"""Fetch the latest version from PyPI.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Latest version string if successful, None otherwise.
|
|
138
|
+
"""
|
|
139
|
+
try:
|
|
140
|
+
with httpx.Client(timeout=5.0) as client:
|
|
141
|
+
response = client.get("https://pypi.org/pypi/shotgun-sh/json")
|
|
142
|
+
response.raise_for_status()
|
|
143
|
+
data = response.json()
|
|
144
|
+
latest = data.get("info", {}).get("version")
|
|
145
|
+
if latest:
|
|
146
|
+
logger.debug(f"Latest version from PyPI: {latest}")
|
|
147
|
+
return str(latest)
|
|
148
|
+
except (httpx.RequestError, httpx.HTTPStatusError) as e:
|
|
149
|
+
logger.debug(f"Failed to fetch latest version: {e}")
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def compare_versions(current: str, latest: str) -> bool:
|
|
154
|
+
"""Compare version strings to determine if update is available.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
current: Current version string.
|
|
158
|
+
latest: Latest available version string.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
True if latest version is newer than current, False otherwise.
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
current_v = version.parse(current)
|
|
165
|
+
latest_v = version.parse(latest)
|
|
166
|
+
return latest_v > current_v
|
|
167
|
+
except Exception as e:
|
|
168
|
+
logger.debug(f"Error comparing versions: {e}")
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def get_update_command(method: str) -> list[str]:
|
|
173
|
+
"""Get the appropriate update command based on installation method.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
method: Installation method ('pipx', 'pip', 'venv', or 'unknown').
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Command list to execute for updating.
|
|
180
|
+
"""
|
|
181
|
+
commands = {
|
|
182
|
+
"pipx": ["pipx", "upgrade", "shotgun-sh"],
|
|
183
|
+
"pip": [sys.executable, "-m", "pip", "install", "--upgrade", "shotgun-sh"],
|
|
184
|
+
"venv": [sys.executable, "-m", "pip", "install", "--upgrade", "shotgun-sh"],
|
|
185
|
+
"unknown": [sys.executable, "-m", "pip", "install", "--upgrade", "shotgun-sh"],
|
|
186
|
+
}
|
|
187
|
+
return commands.get(method, commands["unknown"])
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def perform_update(force: bool = False) -> tuple[bool, str]:
|
|
191
|
+
"""Perform manual update of shotgun-sh (for CLI command).
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
force: If True, update even if it's a dev version.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Tuple of (success, message).
|
|
198
|
+
"""
|
|
199
|
+
# Check if dev version and not forced
|
|
200
|
+
if is_dev_version() and not force:
|
|
201
|
+
return False, "Cannot update development version. Use --force to override."
|
|
202
|
+
|
|
203
|
+
# Get latest version
|
|
204
|
+
latest = get_latest_version()
|
|
205
|
+
if not latest:
|
|
206
|
+
return False, "Failed to fetch latest version from PyPI"
|
|
207
|
+
|
|
208
|
+
# Check if update is needed
|
|
209
|
+
if not compare_versions(__version__, latest):
|
|
210
|
+
return False, f"Already at latest version ({__version__})"
|
|
211
|
+
|
|
212
|
+
# Detect installation method
|
|
213
|
+
method = detect_installation_method()
|
|
214
|
+
command = get_update_command(method)
|
|
215
|
+
|
|
216
|
+
# Perform update
|
|
217
|
+
try:
|
|
218
|
+
logger.info(f"Updating shotgun-sh using {method}...")
|
|
219
|
+
logger.debug(f"Running command: {' '.join(command)}")
|
|
220
|
+
|
|
221
|
+
result = subprocess.run(command, capture_output=True, text=True, timeout=60) # noqa: S603
|
|
222
|
+
|
|
223
|
+
if result.returncode == 0:
|
|
224
|
+
message = f"Successfully updated from {__version__} to {latest}"
|
|
225
|
+
logger.info(message)
|
|
226
|
+
return True, message
|
|
227
|
+
else:
|
|
228
|
+
error_msg = f"Update failed: {result.stderr or result.stdout}"
|
|
229
|
+
logger.error(error_msg)
|
|
230
|
+
return False, error_msg
|
|
231
|
+
|
|
232
|
+
except subprocess.TimeoutExpired:
|
|
233
|
+
return False, "Update command timed out"
|
|
234
|
+
except Exception as e:
|
|
235
|
+
return False, f"Update failed: {e}"
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
__all__ = [
|
|
239
|
+
"detect_installation_method",
|
|
240
|
+
"perform_auto_update",
|
|
241
|
+
"perform_auto_update_async",
|
|
242
|
+
"is_dev_version",
|
|
243
|
+
"get_latest_version",
|
|
244
|
+
"compare_versions",
|
|
245
|
+
"get_update_command",
|
|
246
|
+
"perform_update",
|
|
247
|
+
]
|