shotgun-sh 0.2.11__py3-none-any.whl → 0.2.11.dev2__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.
- shotgun/agents/agent_manager.py +28 -194
- shotgun/agents/common.py +8 -14
- shotgun/agents/config/manager.py +33 -64
- shotgun/agents/config/models.py +1 -25
- shotgun/agents/config/provider.py +2 -2
- shotgun/agents/context_analyzer/analyzer.py +24 -2
- shotgun/agents/conversation_manager.py +19 -35
- shotgun/agents/export.py +2 -2
- shotgun/agents/history/history_processors.py +3 -99
- shotgun/agents/history/token_counting/anthropic.py +1 -17
- shotgun/agents/history/token_counting/base.py +3 -14
- shotgun/agents/history/token_counting/openai.py +1 -11
- shotgun/agents/history/token_counting/sentencepiece_counter.py +0 -8
- shotgun/agents/history/token_counting/tokenizer_cache.py +1 -3
- shotgun/agents/history/token_counting/utils.py +3 -0
- shotgun/agents/plan.py +2 -2
- shotgun/agents/research.py +3 -3
- shotgun/agents/specify.py +2 -2
- shotgun/agents/tasks.py +2 -2
- shotgun/agents/tools/codebase/file_read.py +2 -5
- shotgun/agents/tools/file_management.py +7 -11
- shotgun/agents/tools/web_search/__init__.py +8 -8
- shotgun/agents/tools/web_search/anthropic.py +2 -2
- shotgun/agents/tools/web_search/gemini.py +1 -1
- shotgun/agents/tools/web_search/openai.py +1 -1
- shotgun/agents/tools/web_search/utils.py +2 -2
- shotgun/agents/usage_manager.py +11 -16
- shotgun/build_constants.py +2 -2
- shotgun/cli/clear.py +1 -2
- shotgun/cli/compact.py +3 -3
- shotgun/cli/config.py +5 -8
- shotgun/cli/context.py +2 -2
- shotgun/cli/export.py +1 -1
- shotgun/cli/feedback.py +2 -4
- shotgun/cli/plan.py +1 -1
- shotgun/cli/research.py +1 -1
- shotgun/cli/specify.py +1 -1
- shotgun/cli/tasks.py +1 -1
- shotgun/codebase/core/change_detector.py +3 -5
- shotgun/codebase/core/code_retrieval.py +2 -4
- shotgun/codebase/core/ingestor.py +8 -10
- shotgun/codebase/core/manager.py +3 -3
- shotgun/codebase/core/nl_query.py +1 -1
- shotgun/logging_config.py +17 -10
- shotgun/main.py +1 -3
- shotgun/posthog_telemetry.py +4 -14
- shotgun/sentry_telemetry.py +2 -22
- shotgun/telemetry.py +1 -3
- shotgun/tui/app.py +65 -71
- shotgun/tui/components/context_indicator.py +0 -43
- shotgun/tui/containers.py +17 -15
- shotgun/tui/dependencies.py +2 -2
- shotgun/tui/screens/chat/chat_screen.py +40 -164
- shotgun/tui/screens/chat/help_text.py +15 -16
- shotgun/tui/screens/chat_screen/command_providers.py +0 -10
- shotgun/tui/screens/feedback.py +4 -4
- shotgun/tui/screens/model_picker.py +20 -21
- shotgun/tui/screens/provider_config.py +27 -50
- shotgun/tui/screens/shotgun_auth.py +2 -2
- shotgun/tui/screens/welcome.py +11 -14
- shotgun/tui/services/conversation_service.py +14 -16
- shotgun/tui/utils/mode_progress.py +7 -14
- shotgun/tui/widgets/widget_coordinator.py +0 -15
- shotgun/utils/file_system_utils.py +0 -19
- {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/METADATA +1 -2
- {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/RECORD +69 -73
- shotgun/exceptions.py +0 -32
- shotgun/tui/screens/github_issue.py +0 -102
- shotgun/tui/screens/onboarding.py +0 -431
- shotgun/utils/marketing.py +0 -110
- {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/WHEEL +0 -0
- {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/entry_points.txt +0 -0
- {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
-
from datetime import datetime, timezone
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
from typing import cast
|
|
8
7
|
|
|
@@ -32,7 +31,6 @@ from shotgun.agents.agent_manager import (
|
|
|
32
31
|
ModelConfigUpdated,
|
|
33
32
|
PartialResponseMessage,
|
|
34
33
|
)
|
|
35
|
-
from shotgun.agents.config import get_config_manager
|
|
36
34
|
from shotgun.agents.config.models import MODEL_SPECS
|
|
37
35
|
from shotgun.agents.conversation_manager import ConversationManager
|
|
38
36
|
from shotgun.agents.history.compaction import apply_persistent_compaction
|
|
@@ -47,7 +45,6 @@ from shotgun.codebase.core.manager import (
|
|
|
47
45
|
CodebaseGraphManager,
|
|
48
46
|
)
|
|
49
47
|
from shotgun.codebase.models import IndexProgress, ProgressPhase
|
|
50
|
-
from shotgun.exceptions import ContextSizeLimitExceeded
|
|
51
48
|
from shotgun.posthog_telemetry import track_event
|
|
52
49
|
from shotgun.sdk.codebase import CodebaseSDK
|
|
53
50
|
from shotgun.sdk.exceptions import CodebaseNotFoundError, InvalidPathError
|
|
@@ -73,13 +70,11 @@ from shotgun.tui.screens.chat_screen.command_providers import (
|
|
|
73
70
|
from shotgun.tui.screens.chat_screen.hint_message import HintMessage
|
|
74
71
|
from shotgun.tui.screens.chat_screen.history import ChatHistory
|
|
75
72
|
from shotgun.tui.screens.confirmation_dialog import ConfirmationDialog
|
|
76
|
-
from shotgun.tui.screens.onboarding import OnboardingModal
|
|
77
73
|
from shotgun.tui.services.conversation_service import ConversationService
|
|
78
74
|
from shotgun.tui.state.processing_state import ProcessingStateManager
|
|
79
75
|
from shotgun.tui.utils.mode_progress import PlaceholderHints
|
|
80
76
|
from shotgun.tui.widgets.widget_coordinator import WidgetCoordinator
|
|
81
77
|
from shotgun.utils import get_shotgun_home
|
|
82
|
-
from shotgun.utils.marketing import MarketingManager
|
|
83
78
|
|
|
84
79
|
logger = logging.getLogger(__name__)
|
|
85
80
|
|
|
@@ -170,17 +165,13 @@ class ChatScreen(Screen[None]):
|
|
|
170
165
|
self.processing_state.bind_spinner(self.query_one("#spinner", Spinner))
|
|
171
166
|
|
|
172
167
|
# Load conversation history if --continue flag was provided
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
self.call_later(self._check_and_load_conversation)
|
|
168
|
+
if self.continue_session and self.conversation_manager.exists():
|
|
169
|
+
self._load_conversation()
|
|
176
170
|
|
|
177
171
|
self.call_later(self.check_if_codebase_is_indexed)
|
|
178
172
|
# Initial update of context indicator
|
|
179
173
|
self.update_context_indicator()
|
|
180
174
|
|
|
181
|
-
# Show onboarding popup if not shown before
|
|
182
|
-
self.call_later(self._check_and_show_onboarding)
|
|
183
|
-
|
|
184
175
|
async def on_key(self, event: events.Key) -> None:
|
|
185
176
|
"""Handle key presses for cancellation."""
|
|
186
177
|
# If escape is pressed during Q&A mode, exit Q&A
|
|
@@ -313,10 +304,6 @@ class ChatScreen(Screen[None]):
|
|
|
313
304
|
else:
|
|
314
305
|
self.notify("No context analysis available", severity="error")
|
|
315
306
|
|
|
316
|
-
def action_view_onboarding(self) -> None:
|
|
317
|
-
"""Show the onboarding modal."""
|
|
318
|
-
self.app.push_screen(OnboardingModal())
|
|
319
|
-
|
|
320
307
|
@work
|
|
321
308
|
async def action_compact_conversation(self) -> None:
|
|
322
309
|
"""Compact the conversation history to reduce size."""
|
|
@@ -399,11 +386,11 @@ class ChatScreen(Screen[None]):
|
|
|
399
386
|
# Save to conversation file
|
|
400
387
|
conversation_file = get_shotgun_home() / "conversation.json"
|
|
401
388
|
manager = ConversationManager(conversation_file)
|
|
402
|
-
conversation =
|
|
389
|
+
conversation = manager.load()
|
|
403
390
|
|
|
404
391
|
if conversation:
|
|
405
392
|
conversation.set_agent_messages(compacted_messages)
|
|
406
|
-
|
|
393
|
+
manager.save(conversation)
|
|
407
394
|
|
|
408
395
|
# Post compaction completed event
|
|
409
396
|
self.agent_manager.post_message(CompactionCompletedMessage())
|
|
@@ -468,7 +455,7 @@ class ChatScreen(Screen[None]):
|
|
|
468
455
|
self.agent_manager.ui_message_history = []
|
|
469
456
|
|
|
470
457
|
# Use conversation service to clear conversation
|
|
471
|
-
|
|
458
|
+
self.conversation_service.clear_conversation()
|
|
472
459
|
|
|
473
460
|
# Post message history updated event to refresh UI
|
|
474
461
|
self.agent_manager.post_message(
|
|
@@ -515,34 +502,6 @@ class ChatScreen(Screen[None]):
|
|
|
515
502
|
f"[CONTEXT] Failed to update context indicator: {e}", exc_info=True
|
|
516
503
|
)
|
|
517
504
|
|
|
518
|
-
@work(exclusive=False)
|
|
519
|
-
async def update_context_indicator_with_messages(
|
|
520
|
-
self,
|
|
521
|
-
agent_messages: list[ModelMessage],
|
|
522
|
-
ui_messages: list[ModelMessage | HintMessage],
|
|
523
|
-
) -> None:
|
|
524
|
-
"""Update the context indicator with specific message sets (for streaming updates).
|
|
525
|
-
|
|
526
|
-
Args:
|
|
527
|
-
agent_messages: Agent message history including streaming messages (for token counting)
|
|
528
|
-
ui_messages: UI message history including hints and streaming messages
|
|
529
|
-
"""
|
|
530
|
-
try:
|
|
531
|
-
from shotgun.agents.context_analyzer.analyzer import ContextAnalyzer
|
|
532
|
-
|
|
533
|
-
analyzer = ContextAnalyzer(self.deps.llm_model)
|
|
534
|
-
# Analyze the combined message histories for accurate progressive token counts
|
|
535
|
-
analysis = await analyzer.analyze_conversation(agent_messages, ui_messages)
|
|
536
|
-
|
|
537
|
-
if analysis:
|
|
538
|
-
model_name = self.deps.llm_model.name
|
|
539
|
-
self.widget_coordinator.update_context_indicator(analysis, model_name)
|
|
540
|
-
except Exception as e:
|
|
541
|
-
logger.error(
|
|
542
|
-
f"Failed to update context indicator with streaming messages: {e}",
|
|
543
|
-
exc_info=True,
|
|
544
|
-
)
|
|
545
|
-
|
|
546
505
|
def compose(self) -> ComposeResult:
|
|
547
506
|
"""Create child widgets for the app."""
|
|
548
507
|
with Container(id="window"):
|
|
@@ -592,7 +551,7 @@ class ChatScreen(Screen[None]):
|
|
|
592
551
|
# Keep all ModelResponse and other message types
|
|
593
552
|
filtered_event_messages.append(msg)
|
|
594
553
|
|
|
595
|
-
# Build new message list
|
|
554
|
+
# Build new message list
|
|
596
555
|
new_message_list = self.messages + cast(
|
|
597
556
|
list[ModelMessage | HintMessage], filtered_event_messages
|
|
598
557
|
)
|
|
@@ -602,13 +561,6 @@ class ChatScreen(Screen[None]):
|
|
|
602
561
|
self.partial_message, new_message_list
|
|
603
562
|
)
|
|
604
563
|
|
|
605
|
-
# Update context indicator with full message history including streaming messages
|
|
606
|
-
# Combine existing agent history with new streaming messages for accurate token count
|
|
607
|
-
combined_agent_history = self.agent_manager.message_history + event.messages
|
|
608
|
-
self.update_context_indicator_with_messages(
|
|
609
|
-
combined_agent_history, new_message_list
|
|
610
|
-
)
|
|
611
|
-
|
|
612
564
|
def _clear_partial_response(self) -> None:
|
|
613
565
|
# Use widget coordinator to clear partial response
|
|
614
566
|
self.widget_coordinator.set_partial_response(None, self.messages)
|
|
@@ -650,9 +602,7 @@ class ChatScreen(Screen[None]):
|
|
|
650
602
|
self.qa_answers = []
|
|
651
603
|
|
|
652
604
|
@on(MessageHistoryUpdated)
|
|
653
|
-
|
|
654
|
-
self, event: MessageHistoryUpdated
|
|
655
|
-
) -> None:
|
|
605
|
+
def handle_message_history_updated(self, event: MessageHistoryUpdated) -> None:
|
|
656
606
|
"""Handle message history updates from the agent manager."""
|
|
657
607
|
self._clear_partial_response()
|
|
658
608
|
self.messages = event.messages
|
|
@@ -667,50 +617,32 @@ class ChatScreen(Screen[None]):
|
|
|
667
617
|
self.update_context_indicator()
|
|
668
618
|
|
|
669
619
|
# If there are file operations, add a message showing the modified files
|
|
670
|
-
# Skip if hint was already added by agent_manager (e.g., in QA mode)
|
|
671
620
|
if event.file_operations:
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
# The terminal emulator will make this clickable automatically
|
|
691
|
-
path_obj = Path(display_path)
|
|
692
|
-
|
|
693
|
-
if len(event.file_operations) == 1:
|
|
694
|
-
message = f"📝 Modified: `{display_path}`"
|
|
621
|
+
chat_history = self.query_one(ChatHistory)
|
|
622
|
+
if chat_history.vertical_tail:
|
|
623
|
+
tracker = FileOperationTracker(operations=event.file_operations)
|
|
624
|
+
display_path = tracker.get_display_path()
|
|
625
|
+
|
|
626
|
+
if display_path:
|
|
627
|
+
# Create a simple markdown message with the file path
|
|
628
|
+
# The terminal emulator will make this clickable automatically
|
|
629
|
+
path_obj = Path(display_path)
|
|
630
|
+
|
|
631
|
+
if len(event.file_operations) == 1:
|
|
632
|
+
message = f"📝 Modified: `{display_path}`"
|
|
633
|
+
else:
|
|
634
|
+
num_files = len({op.file_path for op in event.file_operations})
|
|
635
|
+
if path_obj.is_dir():
|
|
636
|
+
message = (
|
|
637
|
+
f"📁 Modified {num_files} files in: `{display_path}`"
|
|
638
|
+
)
|
|
695
639
|
else:
|
|
696
|
-
|
|
697
|
-
|
|
640
|
+
# Common path is a file, show parent directory
|
|
641
|
+
message = (
|
|
642
|
+
f"📁 Modified {num_files} files in: `{path_obj.parent}`"
|
|
698
643
|
)
|
|
699
|
-
if path_obj.is_dir():
|
|
700
|
-
message = f"📁 Modified {num_files} files in: `{display_path}`"
|
|
701
|
-
else:
|
|
702
|
-
# Common path is a file, show parent directory
|
|
703
|
-
message = f"📁 Modified {num_files} files in: `{path_obj.parent}`"
|
|
704
644
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
# Check and display any marketing messages
|
|
708
|
-
from shotgun.tui.app import ShotgunApp
|
|
709
|
-
|
|
710
|
-
app = cast(ShotgunApp, self.app)
|
|
711
|
-
await MarketingManager.check_and_display_messages(
|
|
712
|
-
app.config_manager, event.file_operations, self.mount_hint
|
|
713
|
-
)
|
|
645
|
+
self.mount_hint(message)
|
|
714
646
|
|
|
715
647
|
@on(CompactionStartedMessage)
|
|
716
648
|
def handle_compaction_started(self, event: CompactionStartedMessage) -> None:
|
|
@@ -1116,9 +1048,6 @@ class ChatScreen(Screen[None]):
|
|
|
1116
1048
|
self.processing_state.start_processing("Processing...")
|
|
1117
1049
|
self.processing_state.bind_worker(get_current_worker())
|
|
1118
1050
|
|
|
1119
|
-
# Start context indicator animation immediately
|
|
1120
|
-
self.widget_coordinator.set_context_streaming(True)
|
|
1121
|
-
|
|
1122
1051
|
prompt = message
|
|
1123
1052
|
|
|
1124
1053
|
try:
|
|
@@ -1128,27 +1057,6 @@ class ChatScreen(Screen[None]):
|
|
|
1128
1057
|
except asyncio.CancelledError:
|
|
1129
1058
|
# Handle cancellation gracefully - DO NOT re-raise
|
|
1130
1059
|
self.mount_hint("⚠️ Operation cancelled by user")
|
|
1131
|
-
except ContextSizeLimitExceeded as e:
|
|
1132
|
-
# User-friendly error with actionable options
|
|
1133
|
-
hint = (
|
|
1134
|
-
f"⚠️ **Context too large for {e.model_name}**\n\n"
|
|
1135
|
-
f"Your conversation history exceeds this model's limit ({e.max_tokens:,} tokens).\n\n"
|
|
1136
|
-
f"**Choose an action:**\n\n"
|
|
1137
|
-
f"1. Switch to a larger model (`Ctrl+P` → Change Model)\n"
|
|
1138
|
-
f"2. Switch to a larger model, compact (`/compact`), then switch back to {e.model_name}\n"
|
|
1139
|
-
f"3. Clear conversation (`/clear`)\n"
|
|
1140
|
-
)
|
|
1141
|
-
|
|
1142
|
-
self.mount_hint(hint)
|
|
1143
|
-
|
|
1144
|
-
# Log for debugging (won't send to Sentry due to ErrorNotPickedUpBySentry)
|
|
1145
|
-
logger.info(
|
|
1146
|
-
"Context size limit exceeded",
|
|
1147
|
-
extra={
|
|
1148
|
-
"max_tokens": e.max_tokens,
|
|
1149
|
-
"model_name": e.model_name,
|
|
1150
|
-
},
|
|
1151
|
-
)
|
|
1152
1060
|
except Exception as e:
|
|
1153
1061
|
# Log with full stack trace to shotgun.log
|
|
1154
1062
|
logger.exception(
|
|
@@ -1175,8 +1083,6 @@ class ChatScreen(Screen[None]):
|
|
|
1175
1083
|
self.mount_hint(hint)
|
|
1176
1084
|
finally:
|
|
1177
1085
|
self.processing_state.stop_processing()
|
|
1178
|
-
# Stop context indicator animation
|
|
1179
|
-
self.widget_coordinator.set_context_streaming(False)
|
|
1180
1086
|
|
|
1181
1087
|
# Save conversation after each interaction
|
|
1182
1088
|
self._save_conversation()
|
|
@@ -1185,50 +1091,20 @@ class ChatScreen(Screen[None]):
|
|
|
1185
1091
|
|
|
1186
1092
|
def _save_conversation(self) -> None:
|
|
1187
1093
|
"""Save the current conversation to persistent storage."""
|
|
1188
|
-
# Use conversation service for saving
|
|
1189
|
-
|
|
1190
|
-
self.run_worker(
|
|
1191
|
-
self.conversation_service.save_conversation(self.agent_manager),
|
|
1192
|
-
exclusive=True,
|
|
1193
|
-
)
|
|
1194
|
-
|
|
1195
|
-
async def _check_and_load_conversation(self) -> None:
|
|
1196
|
-
"""Check if conversation exists and load it if it does."""
|
|
1197
|
-
if await self.conversation_manager.exists():
|
|
1198
|
-
self._load_conversation()
|
|
1094
|
+
# Use conversation service for saving
|
|
1095
|
+
self.conversation_service.save_conversation(self.agent_manager)
|
|
1199
1096
|
|
|
1200
1097
|
def _load_conversation(self) -> None:
|
|
1201
1098
|
"""Load conversation from persistent storage."""
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
(
|
|
1206
|
-
success,
|
|
1207
|
-
error_msg,
|
|
1208
|
-
restored_type,
|
|
1209
|
-
) = await self.conversation_service.restore_conversation(
|
|
1099
|
+
# Use conversation service for restoration
|
|
1100
|
+
success, error_msg, restored_type = (
|
|
1101
|
+
self.conversation_service.restore_conversation(
|
|
1210
1102
|
self.agent_manager, self.deps.usage_manager
|
|
1211
1103
|
)
|
|
1104
|
+
)
|
|
1212
1105
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
self.run_worker(_do_load(), exclusive=False)
|
|
1220
|
-
|
|
1221
|
-
@work
|
|
1222
|
-
async def _check_and_show_onboarding(self) -> None:
|
|
1223
|
-
"""Check if onboarding should be shown and display modal if needed."""
|
|
1224
|
-
config_manager = get_config_manager()
|
|
1225
|
-
config = await config_manager.load()
|
|
1226
|
-
|
|
1227
|
-
# Only show onboarding if it hasn't been shown before
|
|
1228
|
-
if config.shown_onboarding_popup is None:
|
|
1229
|
-
# Show the onboarding modal
|
|
1230
|
-
await self.app.push_screen_wait(OnboardingModal())
|
|
1231
|
-
|
|
1232
|
-
# Mark as shown in config with current timestamp
|
|
1233
|
-
config.shown_onboarding_popup = datetime.now(timezone.utc)
|
|
1234
|
-
await config_manager.save(config)
|
|
1106
|
+
if not success and error_msg:
|
|
1107
|
+
self.mount_hint(error_msg)
|
|
1108
|
+
elif success and restored_type:
|
|
1109
|
+
# Update the current mode to match restored conversation
|
|
1110
|
+
self.mode = restored_type
|
|
@@ -11,14 +11,14 @@ def help_text_with_codebase(already_indexed: bool = False) -> str:
|
|
|
11
11
|
Formatted help text string.
|
|
12
12
|
"""
|
|
13
13
|
return (
|
|
14
|
-
"Howdy! Welcome to Shotgun -
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
"-
|
|
19
|
-
"-
|
|
20
|
-
"-
|
|
21
|
-
"
|
|
14
|
+
"Howdy! Welcome to Shotgun - the context tool for software engineering. \n\n"
|
|
15
|
+
"You can research, build specs, plan, create tasks, and export context to your "
|
|
16
|
+
"favorite code-gen agents.\n\n"
|
|
17
|
+
f"{'' if already_indexed else 'Once your codebase is indexed, '}I can help with:\n\n"
|
|
18
|
+
"- Speccing out a new feature\n"
|
|
19
|
+
"- Onboarding you onto this project\n"
|
|
20
|
+
"- Helping with a refactor spec\n"
|
|
21
|
+
"- Creating AGENTS.md file for this project\n"
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
|
|
@@ -29,12 +29,11 @@ def help_text_empty_dir() -> str:
|
|
|
29
29
|
Formatted help text string.
|
|
30
30
|
"""
|
|
31
31
|
return (
|
|
32
|
-
"Howdy! Welcome to Shotgun -
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"-
|
|
37
|
-
"-
|
|
38
|
-
"-
|
|
39
|
-
"Ready to build something? Let's go.\n"
|
|
32
|
+
"Howdy! Welcome to Shotgun - the context tool for software engineering.\n\n"
|
|
33
|
+
"You can research, build specs, plan, create tasks, and export context to your "
|
|
34
|
+
"favorite code-gen agents.\n\n"
|
|
35
|
+
"What would you like to build? Here are some examples:\n\n"
|
|
36
|
+
"- Research FastAPI vs Django\n"
|
|
37
|
+
"- Plan my new web app using React\n"
|
|
38
|
+
"- Create PRD for my planned product\n"
|
|
40
39
|
)
|
|
@@ -369,11 +369,6 @@ class UnifiedCommandProvider(Provider):
|
|
|
369
369
|
self.chat_screen.action_show_usage,
|
|
370
370
|
help="Display usage information for the current session",
|
|
371
371
|
)
|
|
372
|
-
yield DiscoveryHit(
|
|
373
|
-
"View Onboarding",
|
|
374
|
-
self.chat_screen.action_view_onboarding,
|
|
375
|
-
help="View the onboarding tutorial and helpful resources",
|
|
376
|
-
)
|
|
377
372
|
|
|
378
373
|
async def search(self, query: str) -> AsyncGenerator[Hit, None]:
|
|
379
374
|
"""Search for commands in alphabetical order."""
|
|
@@ -421,11 +416,6 @@ class UnifiedCommandProvider(Provider):
|
|
|
421
416
|
self.chat_screen.action_show_usage,
|
|
422
417
|
"Display usage information for the current session",
|
|
423
418
|
),
|
|
424
|
-
(
|
|
425
|
-
"View Onboarding",
|
|
426
|
-
self.chat_screen.action_view_onboarding,
|
|
427
|
-
"View the onboarding tutorial and helpful resources",
|
|
428
|
-
),
|
|
429
419
|
]
|
|
430
420
|
|
|
431
421
|
for title, callback, help_text in commands:
|
shotgun/tui/screens/feedback.py
CHANGED
|
@@ -125,8 +125,8 @@ class FeedbackScreen(Screen[Feedback | None]):
|
|
|
125
125
|
self.set_focus(self.query_one("#feedback-description", TextArea))
|
|
126
126
|
|
|
127
127
|
@on(Button.Pressed, "#submit")
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
def _on_submit_pressed(self) -> None:
|
|
129
|
+
self._submit_feedback()
|
|
130
130
|
|
|
131
131
|
@on(Button.Pressed, "#cancel")
|
|
132
132
|
def _on_cancel_pressed(self) -> None:
|
|
@@ -171,7 +171,7 @@ class FeedbackScreen(Screen[Feedback | None]):
|
|
|
171
171
|
}
|
|
172
172
|
return placeholders.get(kind, "Enter your feedback...")
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
def _submit_feedback(self) -> None:
|
|
175
175
|
text_area = self.query_one("#feedback-description", TextArea)
|
|
176
176
|
description = text_area.text.strip()
|
|
177
177
|
|
|
@@ -182,7 +182,7 @@ class FeedbackScreen(Screen[Feedback | None]):
|
|
|
182
182
|
return
|
|
183
183
|
|
|
184
184
|
app = cast("ShotgunApp", self.app)
|
|
185
|
-
shotgun_instance_id =
|
|
185
|
+
shotgun_instance_id = app.config_manager.get_shotgun_instance_id()
|
|
186
186
|
|
|
187
187
|
feedback = Feedback(
|
|
188
188
|
kind=self.selected_kind,
|
|
@@ -98,7 +98,7 @@ class ModelPickerScreen(Screen[ModelConfigUpdated | None]):
|
|
|
98
98
|
yield Button("Select \\[ENTER]", variant="primary", id="select")
|
|
99
99
|
yield Button("Done \\[ESC]", id="done")
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
def _rebuild_model_list(self) -> None:
|
|
102
102
|
"""Rebuild the model list from current config.
|
|
103
103
|
|
|
104
104
|
This method is called both on first show and when screen is resumed
|
|
@@ -108,7 +108,7 @@ class ModelPickerScreen(Screen[ModelConfigUpdated | None]):
|
|
|
108
108
|
|
|
109
109
|
# Load current config with force_reload to get latest API keys
|
|
110
110
|
config_manager = self.config_manager
|
|
111
|
-
config =
|
|
111
|
+
config = config_manager.load(force_reload=True)
|
|
112
112
|
|
|
113
113
|
# Log provider key status
|
|
114
114
|
logger.debug(
|
|
@@ -133,7 +133,7 @@ class ModelPickerScreen(Screen[ModelConfigUpdated | None]):
|
|
|
133
133
|
logger.debug("Removed %d existing model items from list", old_count)
|
|
134
134
|
|
|
135
135
|
# Add new items (labels already have correct text including current indicator)
|
|
136
|
-
new_items =
|
|
136
|
+
new_items = self._build_model_items(config)
|
|
137
137
|
for item in new_items:
|
|
138
138
|
list_view.append(item)
|
|
139
139
|
logger.debug("Added %d available model items to list", len(new_items))
|
|
@@ -153,7 +153,7 @@ class ModelPickerScreen(Screen[ModelConfigUpdated | None]):
|
|
|
153
153
|
def on_show(self) -> None:
|
|
154
154
|
"""Rebuild model list when screen is first shown."""
|
|
155
155
|
logger.debug("ModelPickerScreen.on_show() called")
|
|
156
|
-
self.
|
|
156
|
+
self._rebuild_model_list()
|
|
157
157
|
|
|
158
158
|
def on_screenresume(self) -> None:
|
|
159
159
|
"""Rebuild model list when screen is resumed (subsequent visits).
|
|
@@ -162,7 +162,7 @@ class ModelPickerScreen(Screen[ModelConfigUpdated | None]):
|
|
|
162
162
|
ensuring the model list reflects any config changes made while away.
|
|
163
163
|
"""
|
|
164
164
|
logger.debug("ModelPickerScreen.on_screenresume() called")
|
|
165
|
-
self.
|
|
165
|
+
self._rebuild_model_list()
|
|
166
166
|
|
|
167
167
|
def action_done(self) -> None:
|
|
168
168
|
self.dismiss()
|
|
@@ -193,14 +193,14 @@ class ModelPickerScreen(Screen[ModelConfigUpdated | None]):
|
|
|
193
193
|
app = cast("ShotgunApp", self.app)
|
|
194
194
|
return app.config_manager
|
|
195
195
|
|
|
196
|
-
|
|
196
|
+
def refresh_model_labels(self) -> None:
|
|
197
197
|
"""Update the list view entries to reflect current selection.
|
|
198
198
|
|
|
199
199
|
Note: This method only updates labels for currently displayed models.
|
|
200
200
|
To rebuild the entire list after provider changes, on_show() should be used.
|
|
201
201
|
"""
|
|
202
202
|
# Load config once with force_reload
|
|
203
|
-
config =
|
|
203
|
+
config = self.config_manager.load(force_reload=True)
|
|
204
204
|
current_model = config.selected_model or get_default_model_for_provider(config)
|
|
205
205
|
|
|
206
206
|
# Update labels for available models only
|
|
@@ -215,11 +215,9 @@ class ModelPickerScreen(Screen[ModelConfigUpdated | None]):
|
|
|
215
215
|
self._model_label(model_name, is_current=model_name == current_model)
|
|
216
216
|
)
|
|
217
217
|
|
|
218
|
-
|
|
219
|
-
self, config: ShotgunConfig | None = None
|
|
220
|
-
) -> list[ListItem]:
|
|
218
|
+
def _build_model_items(self, config: ShotgunConfig | None = None) -> list[ListItem]:
|
|
221
219
|
if config is None:
|
|
222
|
-
config =
|
|
220
|
+
config = self.config_manager.load(force_reload=True)
|
|
223
221
|
|
|
224
222
|
items: list[ListItem] = []
|
|
225
223
|
current_model = self.selected_model
|
|
@@ -248,7 +246,9 @@ class ModelPickerScreen(Screen[ModelConfigUpdated | None]):
|
|
|
248
246
|
return model_name
|
|
249
247
|
return None
|
|
250
248
|
|
|
251
|
-
def _is_model_available(
|
|
249
|
+
def _is_model_available(
|
|
250
|
+
self, model_name: ModelName, config: ShotgunConfig | None = None
|
|
251
|
+
) -> bool:
|
|
252
252
|
"""Check if a model is available based on provider key configuration.
|
|
253
253
|
|
|
254
254
|
A model is available if:
|
|
@@ -257,11 +257,14 @@ class ModelPickerScreen(Screen[ModelConfigUpdated | None]):
|
|
|
257
257
|
|
|
258
258
|
Args:
|
|
259
259
|
model_name: The model to check availability for
|
|
260
|
-
config:
|
|
260
|
+
config: Optional pre-loaded config to avoid multiple reloads
|
|
261
261
|
|
|
262
262
|
Returns:
|
|
263
263
|
True if the model can be used, False otherwise
|
|
264
264
|
"""
|
|
265
|
+
if config is None:
|
|
266
|
+
config = self.config_manager.load(force_reload=True)
|
|
267
|
+
|
|
265
268
|
# If Shotgun Account is configured, all models are available
|
|
266
269
|
if self.config_manager._provider_has_api_key(config.shotgun):
|
|
267
270
|
logger.debug("Model %s available (Shotgun Account configured)", model_name)
|
|
@@ -322,21 +325,17 @@ class ModelPickerScreen(Screen[ModelConfigUpdated | None]):
|
|
|
322
325
|
|
|
323
326
|
def _select_model(self) -> None:
|
|
324
327
|
"""Save the selected model."""
|
|
325
|
-
self.run_worker(self._do_select_model(), exclusive=True)
|
|
326
|
-
|
|
327
|
-
async def _do_select_model(self) -> None:
|
|
328
|
-
"""Async implementation of model selection."""
|
|
329
328
|
try:
|
|
330
329
|
# Get old model before updating
|
|
331
|
-
config =
|
|
330
|
+
config = self.config_manager.load()
|
|
332
331
|
old_model = config.selected_model
|
|
333
332
|
|
|
334
333
|
# Update the selected model in config
|
|
335
|
-
|
|
336
|
-
|
|
334
|
+
self.config_manager.update_selected_model(self.selected_model)
|
|
335
|
+
self.refresh_model_labels()
|
|
337
336
|
|
|
338
337
|
# Get the full model config with provider information
|
|
339
|
-
model_config =
|
|
338
|
+
model_config = get_provider_model(self.selected_model)
|
|
340
339
|
|
|
341
340
|
# Dismiss the screen and return the model config update to the caller
|
|
342
341
|
self.dismiss(
|