shotgun-sh 0.1.7__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.

Files changed (133) hide show
  1. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/PKG-INFO +1 -1
  2. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/pyproject.toml +1 -1
  3. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/main.py +3 -42
  4. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/app.py +3 -25
  5. shotgun_sh-0.1.8/src/shotgun/utils/update_checker.py +247 -0
  6. shotgun_sh-0.1.7/src/shotgun/utils/update_checker.py +0 -692
  7. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/.gitignore +0 -0
  8. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/LICENSE +0 -0
  9. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/README.md +0 -0
  10. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/hatch_build.py +0 -0
  11. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/__init__.py +0 -0
  12. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/__init__.py +0 -0
  13. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/agent_manager.py +0 -0
  14. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/common.py +0 -0
  15. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/config/__init__.py +0 -0
  16. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/config/constants.py +0 -0
  17. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/config/manager.py +0 -0
  18. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/config/models.py +0 -0
  19. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/config/provider.py +0 -0
  20. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/conversation_history.py +0 -0
  21. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/conversation_manager.py +0 -0
  22. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/export.py +0 -0
  23. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/history/__init__.py +0 -0
  24. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/history/compaction.py +0 -0
  25. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/history/constants.py +0 -0
  26. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/history/context_extraction.py +0 -0
  27. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/history/history_building.py +0 -0
  28. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/history/history_processors.py +0 -0
  29. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/history/message_utils.py +0 -0
  30. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/history/token_counting.py +0 -0
  31. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/history/token_estimation.py +0 -0
  32. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/messages.py +0 -0
  33. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/models.py +0 -0
  34. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/plan.py +0 -0
  35. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/research.py +0 -0
  36. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/specify.py +0 -0
  37. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/tasks.py +0 -0
  38. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/__init__.py +0 -0
  39. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
  40. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
  41. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
  42. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
  43. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/codebase/models.py +0 -0
  44. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
  45. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
  46. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/file_management.py +0 -0
  47. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/user_interaction.py +0 -0
  48. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
  49. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
  50. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
  51. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/web_search/openai.py +0 -0
  52. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/agents/tools/web_search/utils.py +0 -0
  53. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/build_constants.py +0 -0
  54. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/cli/__init__.py +0 -0
  55. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/cli/codebase/__init__.py +0 -0
  56. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/cli/codebase/commands.py +0 -0
  57. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/cli/codebase/models.py +0 -0
  58. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/cli/config.py +0 -0
  59. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/cli/export.py +0 -0
  60. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/cli/models.py +0 -0
  61. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/cli/plan.py +0 -0
  62. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/cli/research.py +0 -0
  63. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/cli/specify.py +0 -0
  64. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/cli/tasks.py +0 -0
  65. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/cli/update.py +0 -0
  66. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/cli/utils.py +0 -0
  67. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/codebase/__init__.py +0 -0
  68. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/codebase/core/__init__.py +0 -0
  69. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/codebase/core/change_detector.py +0 -0
  70. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/codebase/core/code_retrieval.py +0 -0
  71. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/codebase/core/cypher_models.py +0 -0
  72. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/codebase/core/ingestor.py +0 -0
  73. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/codebase/core/language_config.py +0 -0
  74. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/codebase/core/manager.py +0 -0
  75. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/codebase/core/nl_query.py +0 -0
  76. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/codebase/core/parser_loader.py +0 -0
  77. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/codebase/models.py +0 -0
  78. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/codebase/service.py +0 -0
  79. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/logging_config.py +0 -0
  80. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/posthog_telemetry.py +0 -0
  81. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/__init__.py +0 -0
  82. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/__init__.py +0 -0
  83. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/export.j2 +0 -0
  84. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
  85. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
  86. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
  87. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
  88. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/plan.j2 +0 -0
  89. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/research.j2 +0 -0
  90. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/specify.j2 +0 -0
  91. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
  92. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
  93. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/agents/tasks.j2 +0 -0
  94. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/codebase/__init__.py +0 -0
  95. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
  96. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
  97. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
  98. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
  99. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
  100. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
  101. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/history/__init__.py +0 -0
  102. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
  103. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/history/summarization.j2 +0 -0
  104. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/prompts/loader.py +0 -0
  105. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/py.typed +0 -0
  106. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/sdk/__init__.py +0 -0
  107. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/sdk/codebase.py +0 -0
  108. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/sdk/exceptions.py +0 -0
  109. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/sdk/models.py +0 -0
  110. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/sdk/services.py +0 -0
  111. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/sentry_telemetry.py +0 -0
  112. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/telemetry.py +0 -0
  113. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/__init__.py +0 -0
  114. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/commands/__init__.py +0 -0
  115. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/components/prompt_input.py +0 -0
  116. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/components/spinner.py +0 -0
  117. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/components/splash.py +0 -0
  118. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/components/vertical_tail.py +0 -0
  119. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/screens/chat.py +0 -0
  120. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/screens/chat.tcss +0 -0
  121. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
  122. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/screens/chat_screen/command_providers.py +0 -0
  123. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
  124. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/screens/chat_screen/history.py +0 -0
  125. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/screens/directory_setup.py +0 -0
  126. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/screens/provider_config.py +0 -0
  127. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/screens/splash.py +0 -0
  128. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/styles.tcss +0 -0
  129. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/utils/__init__.py +0 -0
  130. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/tui/utils/mode_progress.py +0 -0
  131. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/utils/__init__.py +0 -0
  132. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/utils/env_utils.py +0 -0
  133. {shotgun_sh-0.1.7 → shotgun_sh-0.1.8}/src/shotgun/utils/file_system_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shotgun-sh
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: AI-powered research, planning, and task management CLI tool
5
5
  Project-URL: Homepage, https://shotgun.sh/
6
6
  Project-URL: Repository, https://github.com/shotgun-sh/shotgun
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "shotgun-sh"
3
- version = "0.1.7"
3
+ version = "0.1.8"
4
4
  description = "AI-powered research, planning, and task management CLI tool"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -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 check_and_install_updates_async
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
- check_and_install_updates_async(
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, we'll show notification at the end
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 check_and_install_updates_async
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
- check_and_install_updates_async(
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
- """Override quit action to show update notification."""
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
+ ]