shotgun-sh 0.2.9__py3-none-any.whl → 0.2.10__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.

Potentially problematic release.


This version of shotgun-sh might be problematic. Click here for more details.

@@ -142,7 +142,7 @@ class ConfigManager:
142
142
  # Find default model for this provider
143
143
  provider_models = {
144
144
  ProviderType.OPENAI: ModelName.GPT_5,
145
- ProviderType.ANTHROPIC: ModelName.CLAUDE_SONNET_4_5,
145
+ ProviderType.ANTHROPIC: ModelName.CLAUDE_HAIKU_4_5,
146
146
  ProviderType.GOOGLE: ModelName.GEMINI_2_5_PRO,
147
147
  }
148
148
 
@@ -243,7 +243,7 @@ class ConfigManager:
243
243
 
244
244
  provider_models = {
245
245
  ProviderType.OPENAI: ModelName.GPT_5,
246
- ProviderType.ANTHROPIC: ModelName.CLAUDE_SONNET_4_5,
246
+ ProviderType.ANTHROPIC: ModelName.CLAUDE_HAIKU_4_5,
247
247
  ProviderType.GOOGLE: ModelName.GEMINI_2_5_PRO,
248
248
  }
249
249
  if provider_enum in provider_models:
@@ -28,6 +28,7 @@ class ModelName(StrEnum):
28
28
  GPT_5_MINI = "gpt-5-mini"
29
29
  CLAUDE_OPUS_4_1 = "claude-opus-4-1"
30
30
  CLAUDE_SONNET_4_5 = "claude-sonnet-4-5"
31
+ CLAUDE_HAIKU_4_5 = "claude-haiku-4-5"
31
32
  GEMINI_2_5_PRO = "gemini-2.5-pro"
32
33
  GEMINI_2_5_FLASH = "gemini-2.5-flash"
33
34
 
@@ -110,6 +111,13 @@ MODEL_SPECS: dict[ModelName, ModelSpec] = {
110
111
  max_output_tokens=16_000,
111
112
  litellm_proxy_model_name="anthropic/claude-sonnet-4-5",
112
113
  ),
114
+ ModelName.CLAUDE_HAIKU_4_5: ModelSpec(
115
+ name=ModelName.CLAUDE_HAIKU_4_5,
116
+ provider=ProviderType.ANTHROPIC,
117
+ max_input_tokens=200_000,
118
+ max_output_tokens=64_000,
119
+ litellm_proxy_model_name="anthropic/claude-haiku-4-5",
120
+ ),
113
121
  ModelName.GEMINI_2_5_PRO: ModelSpec(
114
122
  name=ModelName.GEMINI_2_5_PRO,
115
123
  provider=ProviderType.GOOGLE,
@@ -32,6 +32,34 @@ logger = get_logger(__name__)
32
32
  _model_cache: dict[tuple[ProviderType, KeyProvider, ModelName, str], Model] = {}
33
33
 
34
34
 
35
+ def get_default_model_for_provider(config: ShotgunConfig) -> ModelName:
36
+ """Get the default model based on which provider/account is configured.
37
+
38
+ Checks API keys in priority order and returns appropriate default model.
39
+ Treats Shotgun Account as a provider context.
40
+
41
+ Args:
42
+ config: Shotgun configuration containing API keys
43
+
44
+ Returns:
45
+ Default ModelName for the configured provider/account
46
+ """
47
+ # Priority 1: Shotgun Account
48
+ if _get_api_key(config.shotgun.api_key):
49
+ return ModelName.CLAUDE_HAIKU_4_5
50
+
51
+ # Priority 2: Individual provider keys
52
+ if _get_api_key(config.anthropic.api_key):
53
+ return ModelName.CLAUDE_HAIKU_4_5
54
+ if _get_api_key(config.openai.api_key):
55
+ return ModelName.GPT_5
56
+ if _get_api_key(config.google.api_key):
57
+ return ModelName.GEMINI_2_5_PRO
58
+
59
+ # Fallback: system-wide default
60
+ return ModelName.CLAUDE_HAIKU_4_5
61
+
62
+
35
63
  def get_or_create_model(
36
64
  provider: ProviderType,
37
65
  key_provider: "KeyProvider",
@@ -172,7 +200,7 @@ def get_provider_model(
172
200
  model_name = provider_or_model
173
201
  else:
174
202
  # No specific model requested - use selected or default
175
- model_name = config.selected_model or ModelName.CLAUDE_SONNET_4_5
203
+ model_name = config.selected_model or ModelName.CLAUDE_HAIKU_4_5
176
204
 
177
205
  if model_name not in MODEL_SPECS:
178
206
  raise ValueError(f"Model '{model_name.value}' not found")
@@ -247,8 +275,8 @@ def get_provider_model(
247
275
  if not api_key:
248
276
  raise ValueError("Anthropic API key not configured. Set via config.")
249
277
 
250
- # Use requested model or default to claude-sonnet-4-5
251
- model_name = requested_model if requested_model else ModelName.CLAUDE_SONNET_4_5
278
+ # Use requested model or default to claude-haiku-4-5
279
+ model_name = requested_model if requested_model else ModelName.CLAUDE_HAIKU_4_5
252
280
  if model_name not in MODEL_SPECS:
253
281
  raise ValueError(f"Model '{model_name.value}' not found")
254
282
  spec = MODEL_SPECS[model_name]
shotgun/api_endpoints.py CHANGED
@@ -1,10 +1,16 @@
1
1
  """Shotgun backend service API endpoints and URLs."""
2
2
 
3
+ import os
4
+
3
5
  # Shotgun Web API base URL (for authentication/subscription)
4
6
  # Can be overridden with environment variable
5
- SHOTGUN_WEB_BASE_URL = "https://api-219702594231.us-east4.run.app"
7
+ SHOTGUN_WEB_BASE_URL = os.getenv(
8
+ "SHOTGUN_WEB_BASE_URL", "https://api-219702594231.us-east4.run.app"
9
+ )
6
10
  # Shotgun's LiteLLM proxy base URL (for AI model requests)
7
- LITELLM_PROXY_BASE_URL = "https://litellm-219702594231.us-east4.run.app"
11
+ LITELLM_PROXY_BASE_URL = os.getenv(
12
+ "SHOTGUN_ACCOUNT_LLM_BASE_URL", "https://litellm-219702594231.us-east4.run.app"
13
+ )
8
14
 
9
15
  # Provider-specific LiteLLM proxy endpoints
10
16
  LITELLM_PROXY_ANTHROPIC_BASE = f"{LITELLM_PROXY_BASE_URL}/anthropic"
shotgun/cli/update.py CHANGED
@@ -45,7 +45,7 @@ def update(
45
45
 
46
46
  This command will:
47
47
  - Check PyPI for the latest version
48
- - Detect your installation method (pipx, pip, or venv)
48
+ - Detect your installation method (uvx, uv-tool, pipx, pip, or venv)
49
49
  - Perform the appropriate upgrade command
50
50
 
51
51
  Examples:
@@ -93,6 +93,8 @@ def update(
93
93
  )
94
94
  console.print(
95
95
  "Use --force to update anyway, or install the stable version with:\n"
96
+ " uv tool install shotgun-sh\n"
97
+ " or\n"
96
98
  " pipx install shotgun-sh\n"
97
99
  " or\n"
98
100
  " pip install shotgun-sh",
@@ -134,7 +136,19 @@ def update(
134
136
  console.print(f"\n[red]✗[/red] {message}", style="bold red")
135
137
 
136
138
  # Provide manual update instructions
137
- if method == "pipx":
139
+ if method == "uvx":
140
+ console.print(
141
+ "\n[yellow]Run uvx again to use the latest version:[/yellow]\n"
142
+ " uvx shotgun-sh\n"
143
+ "\n[yellow]Or install permanently:[/yellow]\n"
144
+ " uv tool install shotgun-sh"
145
+ )
146
+ elif method == "uv-tool":
147
+ console.print(
148
+ "\n[yellow]Try updating manually:[/yellow]\n"
149
+ " uv tool upgrade shotgun-sh"
150
+ )
151
+ elif method == "pipx":
138
152
  console.print(
139
153
  "\n[yellow]Try updating manually:[/yellow]\n"
140
154
  " pipx upgrade shotgun-sh"
@@ -371,7 +371,16 @@ class CodebaseGraphManager:
371
371
  )
372
372
  import shutil
373
373
 
374
- shutil.rmtree(graph_path)
374
+ # Handle both files and directories (kuzu v0.11.2+ uses files)
375
+ if graph_path.is_file():
376
+ graph_path.unlink() # Delete file
377
+ # Also delete WAL file if it exists
378
+ wal_path = graph_path.with_suffix(graph_path.suffix + ".wal")
379
+ if wal_path.exists():
380
+ wal_path.unlink()
381
+ logger.debug(f"Deleted WAL file: {wal_path}")
382
+ else:
383
+ shutil.rmtree(graph_path) # Delete directory
375
384
 
376
385
  # Import the builder from local core module
377
386
  from shotgun.codebase.core import CodebaseIngestor
shotgun/main.py CHANGED
@@ -125,6 +125,41 @@ def main(
125
125
  help="Continue previous TUI conversation",
126
126
  ),
127
127
  ] = False,
128
+ web: Annotated[
129
+ bool,
130
+ typer.Option(
131
+ "--web",
132
+ help="Serve TUI as web application",
133
+ ),
134
+ ] = False,
135
+ port: Annotated[
136
+ int,
137
+ typer.Option(
138
+ "--port",
139
+ help="Port for web server (only used with --web)",
140
+ ),
141
+ ] = 8000,
142
+ host: Annotated[
143
+ str,
144
+ typer.Option(
145
+ "--host",
146
+ help="Host address for web server (only used with --web)",
147
+ ),
148
+ ] = "localhost",
149
+ public_url: Annotated[
150
+ str | None,
151
+ typer.Option(
152
+ "--public-url",
153
+ help="Public URL if behind proxy (only used with --web)",
154
+ ),
155
+ ] = None,
156
+ force_reindex: Annotated[
157
+ bool,
158
+ typer.Option(
159
+ "--force-reindex",
160
+ help="Force re-indexing of codebase (ignores existing index)",
161
+ ),
162
+ ] = False,
128
163
  ) -> None:
129
164
  """Shotgun - AI-powered CLI tool."""
130
165
  logger.debug("Starting shotgun CLI application")
@@ -134,16 +169,35 @@ def main(
134
169
  perform_auto_update_async(no_update_check=no_update_check)
135
170
 
136
171
  if ctx.invoked_subcommand is None and not ctx.resilient_parsing:
137
- logger.debug("Launching shotgun TUI application")
138
- try:
139
- tui_app.run(
140
- no_update_check=no_update_check, continue_session=continue_session
141
- )
142
- finally:
143
- # Ensure PostHog is shut down cleanly even if TUI exits unexpectedly
144
- from shotgun.posthog_telemetry import shutdown
145
-
146
- shutdown()
172
+ if web:
173
+ logger.debug("Launching shotgun TUI as web application")
174
+ try:
175
+ tui_app.serve(
176
+ host=host,
177
+ port=port,
178
+ public_url=public_url,
179
+ no_update_check=no_update_check,
180
+ continue_session=continue_session,
181
+ force_reindex=force_reindex,
182
+ )
183
+ finally:
184
+ # Ensure PostHog is shut down cleanly even if server exits unexpectedly
185
+ from shotgun.posthog_telemetry import shutdown
186
+
187
+ shutdown()
188
+ else:
189
+ logger.debug("Launching shotgun TUI application")
190
+ try:
191
+ tui_app.run(
192
+ no_update_check=no_update_check,
193
+ continue_session=continue_session,
194
+ force_reindex=force_reindex,
195
+ )
196
+ finally:
197
+ # Ensure PostHog is shut down cleanly even if TUI exits unexpectedly
198
+ from shotgun.posthog_telemetry import shutdown
199
+
200
+ shutdown()
147
201
  raise typer.Exit()
148
202
 
149
203
  # For CLI commands, register PostHog shutdown handler
shotgun/tui/app.py CHANGED
@@ -9,12 +9,16 @@ 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 perform_auto_update_async
12
+ from shotgun.utils.update_checker import (
13
+ detect_installation_method,
14
+ perform_auto_update_async,
15
+ )
13
16
 
14
17
  from .screens.chat import ChatScreen
15
18
  from .screens.directory_setup import DirectorySetupScreen
16
19
  from .screens.feedback import FeedbackScreen
17
20
  from .screens.model_picker import ModelPickerScreen
21
+ from .screens.pipx_migration import PipxMigrationScreen
18
22
  from .screens.provider_config import ProviderConfigScreen
19
23
  from .screens.welcome import WelcomeScreen
20
24
 
@@ -36,12 +40,16 @@ class ShotgunApp(App[None]):
36
40
  CSS_PATH = "styles.tcss"
37
41
 
38
42
  def __init__(
39
- self, no_update_check: bool = False, continue_session: bool = False
43
+ self,
44
+ no_update_check: bool = False,
45
+ continue_session: bool = False,
46
+ force_reindex: bool = False,
40
47
  ) -> None:
41
48
  super().__init__()
42
49
  self.config_manager: ConfigManager = get_config_manager()
43
50
  self.no_update_check = no_update_check
44
51
  self.continue_session = continue_session
52
+ self.force_reindex = force_reindex
45
53
 
46
54
  # Start async update check and install
47
55
  if not no_update_check:
@@ -52,14 +60,35 @@ class ShotgunApp(App[None]):
52
60
  # Track TUI startup
53
61
  from shotgun.posthog_telemetry import track_event
54
62
 
55
- track_event("tui_started", {})
63
+ track_event(
64
+ "tui_started",
65
+ {
66
+ "installation_method": detect_installation_method(),
67
+ },
68
+ )
56
69
 
57
70
  self.push_screen(
58
71
  SplashScreen(), callback=lambda _arg: self.refresh_startup_screen()
59
72
  )
60
73
 
61
- def refresh_startup_screen(self) -> None:
74
+ def refresh_startup_screen(self, skip_pipx_check: bool = False) -> None:
62
75
  """Push the appropriate screen based on configured providers."""
76
+ # Check for pipx installation and show migration modal first
77
+ if not skip_pipx_check:
78
+ installation_method = detect_installation_method()
79
+ if installation_method == "pipx":
80
+ if isinstance(self.screen, PipxMigrationScreen):
81
+ return
82
+
83
+ # Show pipx migration modal as a blocking modal screen
84
+ self.push_screen(
85
+ PipxMigrationScreen(),
86
+ callback=lambda _arg: self.refresh_startup_screen(
87
+ skip_pipx_check=True
88
+ ),
89
+ )
90
+ return
91
+
63
92
  # Show welcome screen if no providers are configured OR if user hasn't seen it yet
64
93
  config = self.config_manager.load()
65
94
  if (
@@ -87,8 +116,12 @@ class ShotgunApp(App[None]):
87
116
 
88
117
  if isinstance(self.screen, ChatScreen):
89
118
  return
90
- # Pass continue_session flag to ChatScreen
91
- self.push_screen(ChatScreen(continue_session=self.continue_session))
119
+ # Pass continue_session and force_reindex flags to ChatScreen
120
+ self.push_screen(
121
+ ChatScreen(
122
+ continue_session=self.continue_session, force_reindex=self.force_reindex
123
+ )
124
+ )
92
125
 
93
126
  def check_local_shotgun_directory_exists(self) -> bool:
94
127
  shotgun_dir = get_shotgun_base_path()
@@ -121,12 +154,17 @@ class ShotgunApp(App[None]):
121
154
  self.push_screen(FeedbackScreen(), callback=handle_feedback)
122
155
 
123
156
 
124
- def run(no_update_check: bool = False, continue_session: bool = False) -> None:
157
+ def run(
158
+ no_update_check: bool = False,
159
+ continue_session: bool = False,
160
+ force_reindex: bool = False,
161
+ ) -> None:
125
162
  """Run the TUI application.
126
163
 
127
164
  Args:
128
165
  no_update_check: If True, disable automatic update checks.
129
166
  continue_session: If True, continue from previous conversation.
167
+ force_reindex: If True, force re-indexing of codebase (ignores existing index).
130
168
  """
131
169
  # Clean up any corrupted databases BEFORE starting the TUI
132
170
  # This prevents crashes from corrupted databases during initialization
@@ -148,9 +186,133 @@ def run(no_update_check: bool = False, continue_session: bool = False) -> None:
148
186
  logger.error(f"Failed to cleanup corrupted databases: {e}")
149
187
  # Continue anyway - the TUI can still function
150
188
 
151
- app = ShotgunApp(no_update_check=no_update_check, continue_session=continue_session)
189
+ app = ShotgunApp(
190
+ no_update_check=no_update_check,
191
+ continue_session=continue_session,
192
+ force_reindex=force_reindex,
193
+ )
152
194
  app.run(inline_no_clear=True)
153
195
 
154
196
 
197
+ def serve(
198
+ host: str = "localhost",
199
+ port: int = 8000,
200
+ public_url: str | None = None,
201
+ no_update_check: bool = False,
202
+ continue_session: bool = False,
203
+ force_reindex: bool = False,
204
+ ) -> None:
205
+ """Serve the TUI application as a web application.
206
+
207
+ Args:
208
+ host: Host address for the web server.
209
+ port: Port number for the web server.
210
+ public_url: Public URL if behind a proxy.
211
+ no_update_check: If True, disable automatic update checks.
212
+ continue_session: If True, continue from previous conversation.
213
+ force_reindex: If True, force re-indexing of codebase (ignores existing index).
214
+ """
215
+ # Clean up any corrupted databases BEFORE starting the TUI
216
+ # This prevents crashes from corrupted databases during initialization
217
+ import asyncio
218
+
219
+ from textual_serve.server import Server
220
+
221
+ from shotgun.codebase.core.manager import CodebaseGraphManager
222
+ from shotgun.utils import get_shotgun_home
223
+
224
+ storage_dir = get_shotgun_home() / "codebases"
225
+ manager = CodebaseGraphManager(storage_dir)
226
+
227
+ try:
228
+ removed = asyncio.run(manager.cleanup_corrupted_databases())
229
+ if removed:
230
+ logger.info(
231
+ f"Cleaned up {len(removed)} corrupted database(s) before TUI startup"
232
+ )
233
+ except Exception as e:
234
+ logger.error(f"Failed to cleanup corrupted databases: {e}")
235
+ # Continue anyway - the TUI can still function
236
+
237
+ # Create a new event loop after asyncio.run() closes the previous one
238
+ # This is needed for the Server.serve() method
239
+ loop = asyncio.new_event_loop()
240
+ asyncio.set_event_loop(loop)
241
+
242
+ # Build the command string based on flags
243
+ command = "shotgun"
244
+ if no_update_check:
245
+ command += " --no-update-check"
246
+ if continue_session:
247
+ command += " --continue"
248
+ if force_reindex:
249
+ command += " --force-reindex"
250
+
251
+ # Create and start the server with hardcoded title and debug=False
252
+ server = Server(
253
+ command=command,
254
+ host=host,
255
+ port=port,
256
+ title="The Shotgun",
257
+ public_url=public_url,
258
+ )
259
+
260
+ # Set up graceful shutdown on SIGTERM/SIGINT
261
+ import signal
262
+ import sys
263
+
264
+ def signal_handler(_signum: int, _frame: Any) -> None:
265
+ """Handle shutdown signals gracefully."""
266
+ from shotgun.posthog_telemetry import shutdown
267
+
268
+ logger.info("Received shutdown signal, cleaning up...")
269
+ # Restore stdout/stderr before shutting down
270
+ sys.stdout = original_stdout
271
+ sys.stderr = original_stderr
272
+ shutdown()
273
+ sys.exit(0)
274
+
275
+ signal.signal(signal.SIGTERM, signal_handler)
276
+ signal.signal(signal.SIGINT, signal_handler)
277
+
278
+ # Suppress the textual-serve banner by redirecting stdout/stderr
279
+ import io
280
+
281
+ # Capture and suppress the banner, but show the actual serving URL
282
+ original_stdout = sys.stdout
283
+ original_stderr = sys.stderr
284
+
285
+ captured_output = io.StringIO()
286
+ sys.stdout = captured_output
287
+ sys.stderr = captured_output
288
+
289
+ try:
290
+ # This will print the banner to our captured output
291
+ import logging
292
+
293
+ # Temporarily set logging to ERROR level to suppress INFO messages
294
+ textual_serve_logger = logging.getLogger("textual_serve")
295
+ original_level = textual_serve_logger.level
296
+ textual_serve_logger.setLevel(logging.ERROR)
297
+
298
+ # Print our own message to the original stdout
299
+ sys.stdout = original_stdout
300
+ sys.stderr = original_stderr
301
+ print(f"Serving Shotgun TUI at http://{host}:{port}")
302
+ print("Press Ctrl+C to quit")
303
+
304
+ # Now suppress output again for the serve call
305
+ sys.stdout = captured_output
306
+ sys.stderr = captured_output
307
+
308
+ server.serve(debug=False)
309
+ finally:
310
+ # Restore original stdout/stderr
311
+ sys.stdout = original_stdout
312
+ sys.stderr = original_stderr
313
+ if "textual_serve_logger" in locals():
314
+ textual_serve_logger.setLevel(original_level)
315
+
316
+
155
317
  if __name__ == "__main__":
156
318
  run()
@@ -39,7 +39,10 @@ from shotgun.agents.models import (
39
39
  AgentType,
40
40
  FileOperationTracker,
41
41
  )
42
- from shotgun.codebase.core.manager import CodebaseAlreadyIndexedError
42
+ from shotgun.codebase.core.manager import (
43
+ CodebaseAlreadyIndexedError,
44
+ CodebaseGraphManager,
45
+ )
43
46
  from shotgun.codebase.models import IndexProgress, ProgressPhase
44
47
  from shotgun.posthog_telemetry import track_event
45
48
  from shotgun.sdk.codebase import CodebaseSDK
@@ -291,7 +294,9 @@ class ChatScreen(Screen[None]):
291
294
  qa_current_index = reactive(0)
292
295
  qa_answers: list[str] = []
293
296
 
294
- def __init__(self, continue_session: bool = False) -> None:
297
+ def __init__(
298
+ self, continue_session: bool = False, force_reindex: bool = False
299
+ ) -> None:
295
300
  super().__init__()
296
301
  # Get the model configuration and services
297
302
  model_config = get_provider_model()
@@ -319,6 +324,7 @@ class ChatScreen(Screen[None]):
319
324
  self.placeholder_hints = PlaceholderHints()
320
325
  self.conversation_manager = ConversationManager()
321
326
  self.continue_session = continue_session
327
+ self.force_reindex = force_reindex
322
328
 
323
329
  def on_mount(self) -> None:
324
330
  self.query_one(PromptInput).focus(scroll_visible=True)
@@ -378,6 +384,22 @@ class ChatScreen(Screen[None]):
378
384
  if is_empty or self.continue_session:
379
385
  return
380
386
 
387
+ # If force_reindex is True, delete any existing graphs for this directory
388
+ if self.force_reindex:
389
+ accessible_graphs = (
390
+ await self.codebase_sdk.list_codebases_for_directory()
391
+ ).graphs
392
+ for graph in accessible_graphs:
393
+ try:
394
+ await self.codebase_sdk.delete_codebase(graph.graph_id)
395
+ logger.info(
396
+ f"Deleted existing graph {graph.graph_id} due to --force-reindex"
397
+ )
398
+ except Exception as e:
399
+ logger.warning(
400
+ f"Failed to delete graph {graph.graph_id} during force reindex: {e}"
401
+ )
402
+
381
403
  # Check if the current directory has any accessible codebases
382
404
  accessible_graphs = (
383
405
  await self.codebase_sdk.list_codebases_for_directory()
@@ -766,6 +788,28 @@ class ChatScreen(Screen[None]):
766
788
  except Exception as exc: # pragma: no cover - defensive UI path
767
789
  self.notify(f"Failed to delete codebase: {exc}", severity="error")
768
790
 
791
+ def _is_kuzu_corruption_error(self, exception: Exception) -> bool:
792
+ """Check if error is related to kuzu database corruption.
793
+
794
+ Args:
795
+ exception: The exception to check
796
+
797
+ Returns:
798
+ True if the error indicates kuzu database corruption
799
+ """
800
+ error_str = str(exception).lower()
801
+ error_indicators = [
802
+ "not a directory",
803
+ "errno 20",
804
+ "corrupted",
805
+ ".kuzu",
806
+ "ioexception",
807
+ "unordered_map", # C++ STL map errors from kuzu
808
+ "key not found", # unordered_map::at errors
809
+ "std::exception", # Generic C++ exceptions from kuzu
810
+ ]
811
+ return any(indicator in error_str for indicator in error_indicators)
812
+
769
813
  @work
770
814
  async def index_codebase(self, selection: CodebaseIndexSelection) -> None:
771
815
  label = self.query_one("#indexing-job-display", Static)
@@ -834,58 +878,96 @@ class ChatScreen(Screen[None]):
834
878
  # Start progress animation timer (10 fps = 100ms interval)
835
879
  progress_timer = self.set_interval(0.1, update_progress_display)
836
880
 
837
- try:
838
- # Pass the current working directory as the indexed_from_cwd
839
- logger.debug(
840
- f"Starting indexing - repo_path: {selection.repo_path}, "
841
- f"name: {selection.name}, cwd: {Path.cwd().resolve()}"
842
- )
843
- result = await self.codebase_sdk.index_codebase(
844
- selection.repo_path,
845
- selection.name,
846
- indexed_from_cwd=str(Path.cwd().resolve()),
847
- progress_callback=progress_callback,
848
- )
881
+ # Retry logic for handling kuzu corruption
882
+ max_retries = 3
883
+
884
+ for attempt in range(max_retries):
885
+ try:
886
+ # Clean up corrupted DBs before retry (skip on first attempt)
887
+ if attempt > 0:
888
+ logger.info(
889
+ f"Retry attempt {attempt + 1}/{max_retries} - cleaning up corrupted databases"
890
+ )
891
+ manager = CodebaseGraphManager(
892
+ self.codebase_sdk.service.storage_dir
893
+ )
894
+ cleaned = await manager.cleanup_corrupted_databases()
895
+ logger.info(f"Cleaned up {len(cleaned)} corrupted database(s)")
896
+ self.notify(
897
+ f"Retrying indexing after cleanup (attempt {attempt + 1}/{max_retries})...",
898
+ severity="information",
899
+ )
849
900
 
850
- # Stop progress animation
851
- progress_timer.stop()
901
+ # Pass the current working directory as the indexed_from_cwd
902
+ logger.debug(
903
+ f"Starting indexing - repo_path: {selection.repo_path}, "
904
+ f"name: {selection.name}, cwd: {Path.cwd().resolve()}"
905
+ )
906
+ result = await self.codebase_sdk.index_codebase(
907
+ selection.repo_path,
908
+ selection.name,
909
+ indexed_from_cwd=str(Path.cwd().resolve()),
910
+ progress_callback=progress_callback,
911
+ )
852
912
 
853
- # Show 100% completion after indexing finishes
854
- final_bar = create_progress_bar(100.0)
855
- label.update(f"[$foreground-muted]Indexing codebase: {final_bar} 100%[/]")
856
- label.refresh()
913
+ # Success! Stop progress animation
914
+ progress_timer.stop()
857
915
 
858
- logger.info(
859
- f"Successfully indexed codebase '{result.name}' (ID: {result.graph_id})"
860
- )
861
- self.notify(
862
- f"Indexed codebase '{result.name}' (ID: {result.graph_id})",
863
- severity="information",
864
- timeout=8,
865
- )
916
+ # Show 100% completion after indexing finishes
917
+ final_bar = create_progress_bar(100.0)
918
+ label.update(
919
+ f"[$foreground-muted]Indexing codebase: {final_bar} 100%[/]"
920
+ )
921
+ label.refresh()
866
922
 
867
- except CodebaseAlreadyIndexedError as exc:
868
- progress_timer.stop()
869
- logger.warning(f"Codebase already indexed: {exc}")
870
- self.notify(str(exc), severity="warning")
871
- return
872
- except InvalidPathError as exc:
873
- progress_timer.stop()
874
- logger.error(f"Invalid path error: {exc}")
875
- self.notify(str(exc), severity="error")
923
+ logger.info(
924
+ f"Successfully indexed codebase '{result.name}' (ID: {result.graph_id})"
925
+ )
926
+ self.notify(
927
+ f"Indexed codebase '{result.name}' (ID: {result.graph_id})",
928
+ severity="information",
929
+ timeout=8,
930
+ )
931
+ break # Success - exit retry loop
932
+
933
+ except CodebaseAlreadyIndexedError as exc:
934
+ progress_timer.stop()
935
+ logger.warning(f"Codebase already indexed: {exc}")
936
+ self.notify(str(exc), severity="warning")
937
+ return
938
+ except InvalidPathError as exc:
939
+ progress_timer.stop()
940
+ logger.error(f"Invalid path error: {exc}")
941
+ self.notify(str(exc), severity="error")
942
+ return
943
+
944
+ except Exception as exc: # pragma: no cover - defensive UI path
945
+ # Check if this is a kuzu corruption error and we have retries left
946
+ if attempt < max_retries - 1 and self._is_kuzu_corruption_error(exc):
947
+ logger.warning(
948
+ f"Kuzu corruption detected on attempt {attempt + 1}/{max_retries}: {exc}. "
949
+ f"Will retry after cleanup..."
950
+ )
951
+ # Exponential backoff: 1s, 2s
952
+ await asyncio.sleep(2**attempt)
953
+ continue
954
+
955
+ # Either final retry failed OR not a corruption error - show error
956
+ logger.exception(
957
+ f"Failed to index codebase after {attempt + 1} attempts - "
958
+ f"repo_path: {selection.repo_path}, name: {selection.name}, error: {exc}"
959
+ )
960
+ self.notify(
961
+ f"Failed to index codebase after {attempt + 1} attempts: {exc}",
962
+ severity="error",
963
+ timeout=30, # Keep error visible for 30 seconds
964
+ )
965
+ break
876
966
 
877
- except Exception as exc: # pragma: no cover - defensive UI path
878
- # Log full exception details with stack trace
879
- logger.exception(
880
- f"Failed to index codebase - repo_path: {selection.repo_path}, "
881
- f"name: {selection.name}, error: {exc}"
882
- )
883
- self.notify(f"Failed to index codebase: {exc}", severity="error")
884
- finally:
885
- # Always stop the progress timer
886
- progress_timer.stop()
887
- label.update("")
888
- label.refresh()
967
+ # Always stop the progress timer and clean up label
968
+ progress_timer.stop()
969
+ label.update("")
970
+ label.refresh()
889
971
 
890
972
  @work
891
973
  async def run_agent(self, message: str) -> None:
@@ -13,6 +13,7 @@ from textual.widgets import Button, Label, ListItem, ListView, Static
13
13
 
14
14
  from shotgun.agents.config import ConfigManager
15
15
  from shotgun.agents.config.models import MODEL_SPECS, ModelName, ShotgunConfig
16
+ from shotgun.agents.config.provider import get_default_model_for_provider
16
17
  from shotgun.logging_config import get_logger
17
18
 
18
19
  if TYPE_CHECKING:
@@ -111,7 +112,7 @@ class ModelPickerScreen(Screen[None]):
111
112
  config_manager._provider_has_api_key(config.shotgun),
112
113
  )
113
114
 
114
- current_model = config.selected_model or ModelName.CLAUDE_SONNET_4_5
115
+ current_model = config.selected_model or get_default_model_for_provider(config)
115
116
  self.selected_model = current_model
116
117
  logger.debug("Current selected model: %s", current_model)
117
118
 
@@ -193,7 +194,7 @@ class ModelPickerScreen(Screen[None]):
193
194
  """
194
195
  # Load config once with force_reload
195
196
  config = self.config_manager.load(force_reload=True)
196
- current_model = config.selected_model or ModelName.CLAUDE_SONNET_4_5
197
+ current_model = config.selected_model or get_default_model_for_provider(config)
197
198
 
198
199
  # Update labels for available models only
199
200
  for model_name in AVAILABLE_MODELS:
@@ -0,0 +1,153 @@
1
+ """Migration notice screen for pipx users."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from textual import on
8
+ from textual.app import ComposeResult
9
+ from textual.containers import Container, Horizontal, VerticalScroll
10
+ from textual.screen import ModalScreen
11
+ from textual.widgets import Button, Markdown
12
+
13
+ if TYPE_CHECKING:
14
+ pass
15
+
16
+
17
+ class PipxMigrationScreen(ModalScreen[None]):
18
+ """Modal screen warning pipx users about migration to uvx."""
19
+
20
+ CSS = """
21
+ PipxMigrationScreen {
22
+ align: center middle;
23
+ }
24
+
25
+ #migration-container {
26
+ width: 90;
27
+ height: auto;
28
+ max-height: 90%;
29
+ border: thick $error;
30
+ background: $surface;
31
+ padding: 2;
32
+ }
33
+
34
+ #migration-content {
35
+ height: 1fr;
36
+ padding: 1 0;
37
+ }
38
+
39
+ #buttons-container {
40
+ height: auto;
41
+ padding: 2 0 1 0;
42
+ }
43
+
44
+ #action-buttons {
45
+ width: 100%;
46
+ height: auto;
47
+ align: center middle;
48
+ }
49
+
50
+ .action-button {
51
+ margin: 0 1;
52
+ min-width: 20;
53
+ }
54
+ """
55
+
56
+ BINDINGS = [
57
+ ("escape", "dismiss", "Continue Anyway"),
58
+ ("ctrl+c", "app.quit", "Quit"),
59
+ ]
60
+
61
+ def compose(self) -> ComposeResult:
62
+ """Compose the migration notice modal."""
63
+ with Container(id="migration-container"):
64
+ with VerticalScroll(id="migration-content"):
65
+ yield Markdown(
66
+ """
67
+ ## We've Switched to uvx
68
+
69
+ We've switched from `pipx` to `uvx` as the primary installation method due to critical build issues with our `kuzu` dependency.
70
+
71
+ ### The Problem
72
+ Users with pipx encounter cmake build errors during installation because pip falls back to building from source instead of using pre-built binary wheels.
73
+
74
+ ### The Solution: uvx
75
+ - ✅ **No build tools required** - Binary wheels enforced
76
+ - ✅ **10-100x faster** - Much faster than pipx
77
+ - ✅ **Better reliability** - No cmake/build errors
78
+
79
+ ### How to Migrate
80
+
81
+ **1. Uninstall shotgun-sh from pipx:**
82
+ ```bash
83
+ pipx uninstall shotgun-sh
84
+ ```
85
+
86
+ **2. Install uv:**
87
+ ```bash
88
+ curl -LsSf https://astral.sh/uv/install.sh | sh
89
+ ```
90
+ Or with Homebrew: `brew install uv`
91
+
92
+ **3. Run shotgun-sh with uvx:**
93
+ ```bash
94
+ uvx shotgun-sh
95
+ ```
96
+ Or install permanently: `uv tool install shotgun-sh`
97
+
98
+ ---
99
+
100
+ ### Need Help?
101
+
102
+ **Discord:** https://discord.gg/5RmY6J2N7s
103
+
104
+ **Full Migration Guide:** https://github.com/shotgun-sh/shotgun/blob/main/PIPX_MIGRATION.md
105
+ """
106
+ )
107
+
108
+ with Container(id="buttons-container"):
109
+ with Horizontal(id="action-buttons"):
110
+ yield Button(
111
+ "Copy Instructions to Clipboard",
112
+ variant="default",
113
+ id="copy-instructions",
114
+ classes="action-button",
115
+ )
116
+ yield Button(
117
+ "Continue Anyway",
118
+ variant="primary",
119
+ id="continue",
120
+ classes="action-button",
121
+ )
122
+
123
+ def on_mount(self) -> None:
124
+ """Focus the continue button and ensure scroll starts at top."""
125
+ self.query_one("#continue", Button).focus()
126
+ self.query_one("#migration-content", VerticalScroll).scroll_home(animate=False)
127
+
128
+ @on(Button.Pressed, "#copy-instructions")
129
+ def _copy_instructions(self) -> None:
130
+ """Copy all migration instructions to clipboard."""
131
+ instructions = """# Step 1: Uninstall from pipx
132
+ pipx uninstall shotgun-sh
133
+
134
+ # Step 2: Install uv
135
+ curl -LsSf https://astral.sh/uv/install.sh | sh
136
+
137
+ # Step 3: Run shotgun with uvx
138
+ uvx shotgun-sh"""
139
+ try:
140
+ import pyperclip # type: ignore[import-untyped] # noqa: PGH003
141
+
142
+ pyperclip.copy(instructions)
143
+ self.notify("Copied migration instructions to clipboard!")
144
+ except ImportError:
145
+ self.notify(
146
+ "Clipboard not available. See instructions above.",
147
+ severity="warning",
148
+ )
149
+
150
+ @on(Button.Pressed, "#continue")
151
+ def _continue(self) -> None:
152
+ """Dismiss the modal and continue."""
153
+ self.dismiss()
@@ -1,5 +1,6 @@
1
1
  """Simple auto-update functionality for shotgun-sh CLI."""
2
2
 
3
+ import os
3
4
  import subprocess
4
5
  import sys
5
6
  import threading
@@ -18,8 +19,34 @@ def detect_installation_method() -> str:
18
19
  """Detect how shotgun-sh was installed.
19
20
 
20
21
  Returns:
21
- Installation method: 'pipx', 'pip', 'venv', or 'unknown'.
22
+ Installation method: 'uvx', 'uv-tool', 'pipx', 'pip', 'venv', or 'unknown'.
22
23
  """
24
+ # Check for simulation environment variable (for testing)
25
+ if os.getenv("PIPX_SIMULATE", "").lower() in ("true", "1"):
26
+ logger.debug("PIPX_SIMULATE enabled, simulating pipx installation")
27
+ return "pipx"
28
+
29
+ # Check for uvx (ephemeral execution) by looking at executable path
30
+ # uvx runs from a temporary cache directory
31
+ executable = Path(sys.executable)
32
+ if ".cache/uv" in str(executable) or "uv/cache" in str(executable):
33
+ logger.debug("Detected uvx (ephemeral) execution")
34
+ return "uvx"
35
+
36
+ # Check for uv tool installation
37
+ try:
38
+ result = subprocess.run(
39
+ ["uv", "tool", "list"], # noqa: S607, S603
40
+ capture_output=True,
41
+ text=True,
42
+ timeout=5,
43
+ )
44
+ if result.returncode == 0 and "shotgun-sh" in result.stdout:
45
+ logger.debug("Detected uv tool installation")
46
+ return "uv-tool"
47
+ except (subprocess.SubprocessError, FileNotFoundError):
48
+ pass
49
+
23
50
  # Check for pipx installation
24
51
  try:
25
52
  result = subprocess.run(
@@ -59,7 +86,7 @@ def detect_installation_method() -> str:
59
86
 
60
87
 
61
88
  def perform_auto_update(no_update_check: bool = False) -> None:
62
- """Perform automatic update if installed via pipx.
89
+ """Perform automatic update if installed via pipx or uv tool.
63
90
 
64
91
  Args:
65
92
  no_update_check: If True, skip the update.
@@ -68,23 +95,40 @@ def perform_auto_update(no_update_check: bool = False) -> None:
68
95
  return
69
96
 
70
97
  try:
71
- # Only auto-update for pipx installations
72
- if detect_installation_method() != "pipx":
73
- logger.debug("Not a pipx installation, skipping auto-update")
98
+ method = detect_installation_method()
99
+
100
+ # Skip auto-update for ephemeral uvx executions
101
+ if method == "uvx":
102
+ logger.debug("uvx (ephemeral) execution, skipping auto-update")
74
103
  return
75
104
 
76
- # Run pipx upgrade quietly
77
- logger.debug("Running pipx upgrade shotgun-sh --quiet")
78
- result = subprocess.run(
79
- ["pipx", "upgrade", "shotgun-sh", "--quiet"], # noqa: S607, S603
105
+ # Only auto-update for pipx and uv-tool installations
106
+ if method not in ["pipx", "uv-tool"]:
107
+ logger.debug(f"Installation method '{method}', skipping auto-update")
108
+ return
109
+
110
+ # Determine the appropriate upgrade command
111
+ if method == "pipx":
112
+ command = ["pipx", "upgrade", "shotgun-sh", "--quiet"]
113
+ logger.debug("Running pipx upgrade shotgun-sh --quiet")
114
+ elif method == "uv-tool":
115
+ command = ["uv", "tool", "upgrade", "shotgun-sh"]
116
+ logger.debug("Running uv tool upgrade shotgun-sh")
117
+ else:
118
+ return
119
+
120
+ # Run upgrade command
121
+ result = subprocess.run( # noqa: S603, S607
122
+ command,
80
123
  capture_output=True,
81
124
  text=True,
82
125
  timeout=30,
83
126
  )
84
127
 
85
128
  if result.returncode == 0:
86
- # Check if there was an actual update (pipx shows output even with --quiet for actual updates)
87
- if result.stdout and "upgraded" in result.stdout.lower():
129
+ # Check if there was an actual update
130
+ output = result.stdout.lower()
131
+ if "upgraded" in output or "updated" in output:
88
132
  logger.info("Shotgun-sh has been updated to the latest version")
89
133
  else:
90
134
  # Only log errors at debug level to not annoy users
@@ -166,16 +210,18 @@ def compare_versions(current: str, latest: str) -> bool:
166
210
  return False
167
211
 
168
212
 
169
- def get_update_command(method: str) -> list[str]:
213
+ def get_update_command(method: str) -> list[str] | None:
170
214
  """Get the appropriate update command based on installation method.
171
215
 
172
216
  Args:
173
- method: Installation method ('pipx', 'pip', 'venv', or 'unknown').
217
+ method: Installation method ('uvx', 'uv-tool', 'pipx', 'pip', 'venv', or 'unknown').
174
218
 
175
219
  Returns:
176
- Command list to execute for updating.
220
+ Command list to execute for updating, or None for uvx (ephemeral).
177
221
  """
178
222
  commands = {
223
+ "uvx": None, # uvx is ephemeral, no update command
224
+ "uv-tool": ["uv", "tool", "upgrade", "shotgun-sh"],
179
225
  "pipx": ["pipx", "upgrade", "shotgun-sh"],
180
226
  "pip": [sys.executable, "-m", "pip", "install", "--upgrade", "shotgun-sh"],
181
227
  "venv": [sys.executable, "-m", "pip", "install", "--upgrade", "shotgun-sh"],
@@ -210,6 +256,15 @@ def perform_update(force: bool = False) -> tuple[bool, str]:
210
256
  method = detect_installation_method()
211
257
  command = get_update_command(method)
212
258
 
259
+ # Handle uvx (ephemeral) installations
260
+ if method == "uvx" or command is None:
261
+ return (
262
+ False,
263
+ "You're running shotgun-sh via uvx (ephemeral mode). "
264
+ "To get the latest version, simply run 'uvx shotgun-sh' again, "
265
+ "or install permanently with 'uv tool install shotgun-sh'.",
266
+ )
267
+
213
268
  # Perform update
214
269
  try:
215
270
  logger.info(f"Updating shotgun-sh using {method}...")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shotgun-sh
3
- Version: 0.2.9
3
+ Version: 0.2.10
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
@@ -26,7 +26,7 @@ Requires-Dist: genai-prices>=0.0.27
26
26
  Requires-Dist: httpx>=0.27.0
27
27
  Requires-Dist: jinja2>=3.1.0
28
28
  Requires-Dist: kuzu>=0.7.0
29
- Requires-Dist: logfire[pydantic-ai]>=2.0.0
29
+ Requires-Dist: logfire>=2.0.0
30
30
  Requires-Dist: openai>=1.0.0
31
31
  Requires-Dist: packaging>=23.0
32
32
  Requires-Dist: posthog>=3.0.0
@@ -36,6 +36,7 @@ Requires-Dist: sentencepiece>=0.2.0
36
36
  Requires-Dist: sentry-sdk[pure-eval]>=2.0.0
37
37
  Requires-Dist: tenacity>=8.0.0
38
38
  Requires-Dist: textual-dev>=1.7.0
39
+ Requires-Dist: textual-serve>=0.1.0
39
40
  Requires-Dist: textual>=6.1.0
40
41
  Requires-Dist: tiktoken>=0.7.0
41
42
  Requires-Dist: tree-sitter-go>=0.23.0
@@ -83,13 +84,30 @@ Every research finding, every architectural decision, every "here's why we didn'
83
84
 
84
85
  ## Installation
85
86
 
86
- ### Using pipx (Recommended)
87
+ ### Using uvx (Recommended)
88
+
89
+ **Quick start (ephemeral):**
90
+ ```bash
91
+ uvx shotgun-sh
92
+ ```
93
+
94
+ **Install permanently:**
95
+ ```bash
96
+ uv tool install shotgun-sh
97
+ ```
98
+
99
+ **Why uvx?** It's 10-100x faster than pipx and handles binary wheels more reliably. If you don't have `uv` installed, get it at [astral.sh/uv](https://astral.sh/uv) or `curl -LsSf https://astral.sh/uv/install.sh | sh`
100
+
101
+ ### Using pipx
87
102
 
88
103
  ```bash
89
104
  pipx install shotgun-sh
90
105
  ```
91
106
 
92
- **Why pipx?** It installs Shotgun in an isolated environment, preventing dependency conflicts with your other Python projects.
107
+ If you encounter build errors with kuzu on macOS:
108
+ ```bash
109
+ pipx install --pip-args="--only-binary kuzu" shotgun-sh
110
+ ```
93
111
 
94
112
  ### Using pip
95
113
 
@@ -1,8 +1,8 @@
1
1
  shotgun/__init__.py,sha256=P40K0fnIsb7SKcQrFnXZ4aREjpWchVDhvM1HxI4cyIQ,104
2
- shotgun/api_endpoints.py,sha256=TvxuJyMrZLy6KZTrR6lrdkG8OBtb3TJ48qaw3pWitO0,526
2
+ shotgun/api_endpoints.py,sha256=fBMTAQDyDXFY622MSrya5i_0ur_KYrCNg6wMf7QJVb4,627
3
3
  shotgun/build_constants.py,sha256=hDFr6eO0lwN0iCqHQ1A5s0D68txR8sYrTJLGa7tSi0o,654
4
4
  shotgun/logging_config.py,sha256=UKenihvgH8OA3W0b8ZFcItYaFJVe9MlsMYlcevyW1HY,7440
5
- shotgun/main.py,sha256=RA3q1xPfqxCu43UmgI2ryZpA-IxPhJb_MJrbLqp9c_g,5140
5
+ shotgun/main.py,sha256=4HLlnSmkVdm97yFCsXpY7d598xkOL7jtJLTq5sHPzrs,6762
6
6
  shotgun/posthog_telemetry.py,sha256=TOiyBtLg21SttHGWKc4-e-PQgpbq6Uz_4OzlvlxMcZ0,6099
7
7
  shotgun/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  shotgun/sentry_telemetry.py,sha256=VD8es-tREfgtRKhDsEVvqpo0_kM_ab6iVm2lkOEmTlI,2950
@@ -23,9 +23,9 @@ shotgun/agents/tasks.py,sha256=AuriwtDn6uZz2G0eKfqBHYQrxYfJlbiAd-fcsw9lU3I,2949
23
23
  shotgun/agents/usage_manager.py,sha256=5d9JC4_cthXwhTSytMfMExMDAUYp8_nkPepTJZXk13w,5017
24
24
  shotgun/agents/config/__init__.py,sha256=Fl8K_81zBpm-OfOW27M_WWLSFdaHHek6lWz95iDREjQ,318
25
25
  shotgun/agents/config/constants.py,sha256=JNuLpeBUKikEsxGSjwX3RVWUQpbCKnDKstF2NczuDqk,932
26
- shotgun/agents/config/manager.py,sha256=WO3TOwXCSOa7e3s_rmvH74jGw7xTqA_OaGhNaUsZ0a4,18922
27
- shotgun/agents/config/models.py,sha256=ohLXt9niCy4uFfFP1E6WSBZtxh7aZ16gTA2S3pHYkmc,5431
28
- shotgun/agents/config/provider.py,sha256=K2uW7DkdAhZfoDJcWV7NxOrSoEq1rQzArtZnxIgEe3E,12496
26
+ shotgun/agents/config/manager.py,sha256=Z8EQoWPQM7uj3MyklmxHQ1GR6va1uv9BjT5DDvAv3pY,18920
27
+ shotgun/agents/config/models.py,sha256=tM0JnMf-hWWzJCmOsJBA6tIiwrdFTQI_4B0zYFNg6CU,5736
28
+ shotgun/agents/config/provider.py,sha256=8TLiyTowkjT5p0zjocv9zGjem5hgQKnNuiN1nESguok,13412
29
29
  shotgun/agents/history/__init__.py,sha256=XFQj2a6fxDqVg0Q3juvN9RjV_RJbgvFZtQOCOjVJyp4,147
30
30
  shotgun/agents/history/compaction.py,sha256=9RMpG0aY_7L4TecbgwHSOkGtbd9W5XZTg-MbzZmNl00,3515
31
31
  shotgun/agents/history/constants.py,sha256=yWY8rrTZarLA3flCCMB_hS2NMvUDRDTwP4D4j7MIh1w,446
@@ -64,7 +64,7 @@ shotgun/cli/plan.py,sha256=T-eu-I9z-dSoKqJ-KI8X5i5Mm0VL1BfornxRiUjTgnk,2324
64
64
  shotgun/cli/research.py,sha256=qvBBtX3Wyn6pDZlJpcEvbeK-0iTOXegi71tm8HKVYaE,2490
65
65
  shotgun/cli/specify.py,sha256=ErRQ72Zc75fmxopZbKy0vvnLPuYBLsGynpjj1X6-BwI,2166
66
66
  shotgun/cli/tasks.py,sha256=17qWoGCVYpNIxa2vaoIH1P-xz2RcGLaK8SF4JlPsOWI,2420
67
- shotgun/cli/update.py,sha256=Dn_No7jPmdZ-7qYlhzI0BtmlufetVdw1BN-xRi_UE5A,4718
67
+ shotgun/cli/update.py,sha256=sc3uuw3AXFF0kpskWah1JEoTwrKv67fCnqp9BjeND3o,5328
68
68
  shotgun/cli/utils.py,sha256=umVWXDx8pelovMk-nT8B7m0c39AKY9hHsuAMnbw_Hcg,732
69
69
  shotgun/cli/codebase/__init__.py,sha256=rKdvx33p0i_BYbNkz5_4DCFgEMwzOOqLi9f5p7XTLKM,73
70
70
  shotgun/cli/codebase/commands.py,sha256=1N2yOGmok0ZarqXPIpWGcsQrwm_ZJcyWiMxy6tm0j70,8711
@@ -78,7 +78,7 @@ shotgun/codebase/core/code_retrieval.py,sha256=_JVyyQKHDFm3dxOOua1mw9eIIOHIVz3-I
78
78
  shotgun/codebase/core/cypher_models.py,sha256=Yfysfa9lLguILftkmtuJCN3kLBFIo7WW7NigM-Zr-W4,1735
79
79
  shotgun/codebase/core/ingestor.py,sha256=CNYbdoJycnbA2psYCD9uKcUwIe3Ao7I7T6NrPhTQE9k,64613
80
80
  shotgun/codebase/core/language_config.py,sha256=vsqHyuFnumRPRBV1lMOxWKNOIiClO6FyfKQR0fGrtl4,8934
81
- shotgun/codebase/core/manager.py,sha256=kjxQ9eCs5vVCVDproCN1eYSKuGiqtcxF01reQ18JfOw,66184
81
+ shotgun/codebase/core/manager.py,sha256=1ykkGSrR4ooEKiluGp-17q3n9hd2LSjonvZL10Xb0pk,66697
82
82
  shotgun/codebase/core/nl_query.py,sha256=kPoSJXBlm5rLhzOofZhqPVMJ_Lj3rV2H6sld6BwtMdg,16115
83
83
  shotgun/codebase/core/parser_loader.py,sha256=LZRrDS8Sp518jIu3tQW-BxdwJ86lnsTteI478ER9Td8,4278
84
84
  shotgun/llm_proxy/__init__.py,sha256=3ST3ygtf2sXXSOjIFHxVZ5xqRbT3TF7jpNHwuZAtIwA,452
@@ -119,7 +119,7 @@ shotgun/shotgun_web/client.py,sha256=n5DDuVfSa6VPZjhSsfSxQlSFOnhgDHyidRnB8Hv9XF4
119
119
  shotgun/shotgun_web/constants.py,sha256=eNvtjlu81bAVQaCwZXOVjSpDopUm9pf34XuZEvuMiko,661
120
120
  shotgun/shotgun_web/models.py,sha256=Ie9VfqKZM2tIJhIjentU9qLoNaMZvnUJaIu-xg9kQsA,1391
121
121
  shotgun/tui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
122
- shotgun/tui/app.py,sha256=B2tKbXeGhWBIVec1jJHGAuBcP1SMbO_6xol2OaBpw2Y,5374
122
+ shotgun/tui/app.py,sha256=I4rPtHIp_XiO4Vy3TQe3Rjfp20xHkxDvijdlY7x6wi8,10636
123
123
  shotgun/tui/filtered_codebase_service.py,sha256=lJ8gTMhIveTatmvmGLP299msWWTkVYKwvY_2FhuL2s4,1687
124
124
  shotgun/tui/styles.tcss,sha256=ETyyw1bpMBOqTi5RLcAJUScdPWTvAWEqE9YcT0kVs_E,121
125
125
  shotgun/tui/commands/__init__.py,sha256=8D5lvtpqMW5-fF7Bg3oJtUzU75cKOv6aUaHYYszydU8,2518
@@ -127,11 +127,12 @@ shotgun/tui/components/prompt_input.py,sha256=Ss-htqraHZAPaehGE4x86ij0veMjc4Ugad
127
127
  shotgun/tui/components/spinner.py,sha256=ovTDeaJ6FD6chZx_Aepia6R3UkPOVJ77EKHfRmn39MY,2427
128
128
  shotgun/tui/components/splash.py,sha256=vppy9vEIEvywuUKRXn2y11HwXSRkQZHLYoVjhDVdJeU,1267
129
129
  shotgun/tui/components/vertical_tail.py,sha256=kROwTaRjUwVB7H35dtmNcUVPQqNYvvfq7K2tXBKEb6c,638
130
- shotgun/tui/screens/chat.py,sha256=D_HKVSOjT1ex3AHRtE41hTmDKiiUmmHY7BIbHvTyj-w,38747
130
+ shotgun/tui/screens/chat.py,sha256=-MPbvdeWSWLK2VTgPYicMXger2vMHA9t_YrEzaux-1Q,42255
131
131
  shotgun/tui/screens/chat.tcss,sha256=2Yq3E23jxsySYsgZf4G1AYrYVcpX0UDW6kNNI0tDmtM,437
132
132
  shotgun/tui/screens/directory_setup.py,sha256=lIZ1J4A6g5Q2ZBX8epW7BhR96Dmdcg22CyiM5S-I5WU,3237
133
133
  shotgun/tui/screens/feedback.py,sha256=VxpW0PVxMp22ZvSfQkTtgixNrpEOlfWtekjqlVfYEjA,5708
134
- shotgun/tui/screens/model_picker.py,sha256=G-EvalpxgHKk0W3FgHMcxIr817VwZyEgh_ZadSQiRwo,11831
134
+ shotgun/tui/screens/model_picker.py,sha256=kPvBnMK20SJrAGAC0HHM4Yi8n7biHraz58eiE8jiPxg,11927
135
+ shotgun/tui/screens/pipx_migration.py,sha256=BY6R1Z__htCLjWwffXbHUpxfAk1nnLQnzGRUCmXfwiY,4321
135
136
  shotgun/tui/screens/provider_config.py,sha256=UCnAzjXPoP7Y73gsXxZF2PNA4LdSgpgoGYwiOd6fERA,10902
136
137
  shotgun/tui/screens/shotgun_auth.py,sha256=Y--7LZewV6gfDkucxymfAO7BCd7eI2C3H1ClDMztVio,10663
137
138
  shotgun/tui/screens/splash.py,sha256=E2MsJihi3c9NY1L28o_MstDxGwrCnnV7zdq00MrGAsw,706
@@ -147,9 +148,9 @@ shotgun/utils/datetime_utils.py,sha256=x_uYmG1n9rkhSO2oR2uV9ttiuPL0nKa9os8YYaPfd
147
148
  shotgun/utils/env_utils.py,sha256=ulM3BRi9ZhS7uC-zorGeDQm4SHvsyFuuU9BtVPqdrHY,1418
148
149
  shotgun/utils/file_system_utils.py,sha256=l-0p1bEHF34OU19MahnRFdClHufThfGAjQ431teAIp0,1004
149
150
  shotgun/utils/source_detection.py,sha256=Co6Q03R3fT771TF3RzB-70stfjNP2S4F_ArZKibwzm8,454
150
- shotgun/utils/update_checker.py,sha256=IgzPHRhS1ETH7PnJR_dIx6lxgr1qHpCkMTgzUxvGjhI,7586
151
- shotgun_sh-0.2.9.dist-info/METADATA,sha256=Oa7hEsFj4nzgRGje9A2ScEOndeRTHh0TcrzIaSVUzdQ,4294
152
- shotgun_sh-0.2.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
153
- shotgun_sh-0.2.9.dist-info/entry_points.txt,sha256=asZxLU4QILneq0MWW10saVCZc4VWhZfb0wFZvERnzfA,45
154
- shotgun_sh-0.2.9.dist-info/licenses/LICENSE,sha256=YebsZl590zCHrF_acCU5pmNt0pnAfD2DmAnevJPB1tY,1065
155
- shotgun_sh-0.2.9.dist-info/RECORD,,
151
+ shotgun/utils/update_checker.py,sha256=5pK9ZXEjgnE-BQLvibm9Fj6SJHVYeG0U-WspRf0bJec,9660
152
+ shotgun_sh-0.2.10.dist-info/METADATA,sha256=57UVH3orQmmh8ahq3pJYX6NpHiGZCxPvPJV0x1MWr2k,4665
153
+ shotgun_sh-0.2.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
154
+ shotgun_sh-0.2.10.dist-info/entry_points.txt,sha256=GQmtjKaPtviqYOuB3C0SMGlG5RZS9-VDDIKxV_IVHmY,75
155
+ shotgun_sh-0.2.10.dist-info/licenses/LICENSE,sha256=YebsZl590zCHrF_acCU5pmNt0pnAfD2DmAnevJPB1tY,1065
156
+ shotgun_sh-0.2.10.dist-info/RECORD,,
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
2
  shotgun = shotgun.main:app
3
+ shotgun-sh = shotgun.main:app