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.

Files changed (73) hide show
  1. shotgun/agents/agent_manager.py +28 -194
  2. shotgun/agents/common.py +8 -14
  3. shotgun/agents/config/manager.py +33 -64
  4. shotgun/agents/config/models.py +1 -25
  5. shotgun/agents/config/provider.py +2 -2
  6. shotgun/agents/context_analyzer/analyzer.py +24 -2
  7. shotgun/agents/conversation_manager.py +19 -35
  8. shotgun/agents/export.py +2 -2
  9. shotgun/agents/history/history_processors.py +3 -99
  10. shotgun/agents/history/token_counting/anthropic.py +1 -17
  11. shotgun/agents/history/token_counting/base.py +3 -14
  12. shotgun/agents/history/token_counting/openai.py +1 -11
  13. shotgun/agents/history/token_counting/sentencepiece_counter.py +0 -8
  14. shotgun/agents/history/token_counting/tokenizer_cache.py +1 -3
  15. shotgun/agents/history/token_counting/utils.py +3 -0
  16. shotgun/agents/plan.py +2 -2
  17. shotgun/agents/research.py +3 -3
  18. shotgun/agents/specify.py +2 -2
  19. shotgun/agents/tasks.py +2 -2
  20. shotgun/agents/tools/codebase/file_read.py +2 -5
  21. shotgun/agents/tools/file_management.py +7 -11
  22. shotgun/agents/tools/web_search/__init__.py +8 -8
  23. shotgun/agents/tools/web_search/anthropic.py +2 -2
  24. shotgun/agents/tools/web_search/gemini.py +1 -1
  25. shotgun/agents/tools/web_search/openai.py +1 -1
  26. shotgun/agents/tools/web_search/utils.py +2 -2
  27. shotgun/agents/usage_manager.py +11 -16
  28. shotgun/build_constants.py +2 -2
  29. shotgun/cli/clear.py +1 -2
  30. shotgun/cli/compact.py +3 -3
  31. shotgun/cli/config.py +5 -8
  32. shotgun/cli/context.py +2 -2
  33. shotgun/cli/export.py +1 -1
  34. shotgun/cli/feedback.py +2 -4
  35. shotgun/cli/plan.py +1 -1
  36. shotgun/cli/research.py +1 -1
  37. shotgun/cli/specify.py +1 -1
  38. shotgun/cli/tasks.py +1 -1
  39. shotgun/codebase/core/change_detector.py +3 -5
  40. shotgun/codebase/core/code_retrieval.py +2 -4
  41. shotgun/codebase/core/ingestor.py +8 -10
  42. shotgun/codebase/core/manager.py +3 -3
  43. shotgun/codebase/core/nl_query.py +1 -1
  44. shotgun/logging_config.py +17 -10
  45. shotgun/main.py +1 -3
  46. shotgun/posthog_telemetry.py +4 -14
  47. shotgun/sentry_telemetry.py +2 -22
  48. shotgun/telemetry.py +1 -3
  49. shotgun/tui/app.py +65 -71
  50. shotgun/tui/components/context_indicator.py +0 -43
  51. shotgun/tui/containers.py +17 -15
  52. shotgun/tui/dependencies.py +2 -2
  53. shotgun/tui/screens/chat/chat_screen.py +40 -164
  54. shotgun/tui/screens/chat/help_text.py +15 -16
  55. shotgun/tui/screens/chat_screen/command_providers.py +0 -10
  56. shotgun/tui/screens/feedback.py +4 -4
  57. shotgun/tui/screens/model_picker.py +20 -21
  58. shotgun/tui/screens/provider_config.py +27 -50
  59. shotgun/tui/screens/shotgun_auth.py +2 -2
  60. shotgun/tui/screens/welcome.py +11 -14
  61. shotgun/tui/services/conversation_service.py +14 -16
  62. shotgun/tui/utils/mode_progress.py +7 -14
  63. shotgun/tui/widgets/widget_coordinator.py +0 -15
  64. shotgun/utils/file_system_utils.py +0 -19
  65. {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/METADATA +1 -2
  66. {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/RECORD +69 -73
  67. shotgun/exceptions.py +0 -32
  68. shotgun/tui/screens/github_issue.py +0 -102
  69. shotgun/tui/screens/onboarding.py +0 -431
  70. shotgun/utils/marketing.py +0 -110
  71. {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/WHEEL +0 -0
  72. {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/entry_points.txt +0 -0
  73. {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/licenses/LICENSE +0 -0
@@ -1,431 +0,0 @@
1
- """Onboarding popup modal for first-time users."""
2
-
3
- import webbrowser
4
-
5
- from textual import on
6
- from textual.app import ComposeResult
7
- from textual.containers import Container, Horizontal, VerticalScroll
8
- from textual.screen import ModalScreen
9
- from textual.widgets import Button, Markdown, Static
10
-
11
-
12
- class OnboardingModal(ModalScreen[None]):
13
- """Multi-page onboarding modal for new users.
14
-
15
- This modal presents helpful resources and tips for using Shotgun across
16
- multiple pages. Users can navigate between pages using Next/Back buttons.
17
- """
18
-
19
- CSS = """
20
- OnboardingModal {
21
- align: center middle;
22
- }
23
-
24
- #onboarding-container {
25
- width: 95;
26
- max-width: 100;
27
- height: auto;
28
- max-height: 90%;
29
- border: thick $primary;
30
- background: $surface;
31
- padding: 2;
32
- }
33
-
34
- #progress-sidebar {
35
- width: 26;
36
- dock: left;
37
- border-right: solid $primary;
38
- padding: 1;
39
- height: 100%;
40
- }
41
-
42
- #main-content {
43
- width: 1fr;
44
- height: auto;
45
- }
46
-
47
- #progress-header {
48
- text-style: bold;
49
- padding-bottom: 1;
50
- color: $text-accent;
51
- }
52
-
53
- .progress-item {
54
- padding: 1 0;
55
- }
56
-
57
- .progress-item-current {
58
- color: $accent;
59
- text-style: bold;
60
- }
61
-
62
- .progress-item-visited {
63
- color: $success;
64
- }
65
-
66
- .progress-item-unvisited {
67
- color: $text-muted;
68
- }
69
-
70
- #onboarding-header {
71
- text-style: bold;
72
- color: $text-accent;
73
- padding-bottom: 1;
74
- text-align: center;
75
- }
76
-
77
- #onboarding-content {
78
- height: 1fr;
79
- padding: 1 0;
80
- }
81
-
82
- #page-indicator {
83
- text-align: center;
84
- color: $text-muted;
85
- padding: 1 0;
86
- }
87
-
88
- #buttons-container {
89
- height: auto;
90
- padding: 1 0 0 0;
91
- }
92
-
93
- #navigation-buttons {
94
- width: 100%;
95
- height: auto;
96
- align: center middle;
97
- }
98
-
99
- .nav-button {
100
- margin: 0 1;
101
- min-width: 12;
102
- }
103
-
104
- #resource-sections {
105
- padding: 1 0;
106
- height: auto;
107
- }
108
-
109
- #resource-sections Button {
110
- width: 100%;
111
- margin: 0 0 2 0;
112
- }
113
-
114
- #video-section {
115
- padding: 0;
116
- margin: 0 0 1 0;
117
- }
118
-
119
- #docs-section {
120
- padding: 0;
121
- margin: 2 0 1 0;
122
- }
123
- """
124
-
125
- BINDINGS = [
126
- ("escape", "dismiss", "Close"),
127
- ("ctrl+c", "app.quit", "Quit"),
128
- ]
129
-
130
- def __init__(self) -> None:
131
- """Initialize the onboarding modal."""
132
- super().__init__()
133
- self.current_page = 0
134
- self.total_pages = 4
135
- self.page_titles = [
136
- "Getting Started",
137
- "Discovering the 5 Modes",
138
- "Prompting Better",
139
- "Context Management!",
140
- ]
141
- # Track which pages have been visited (in memory only)
142
- self.visited_pages: set[int] = {0} # Start on page 0, so it's visited
143
-
144
- def compose(self) -> ComposeResult:
145
- """Compose the onboarding modal."""
146
- with Container(id="onboarding-container"):
147
- # Left sidebar for progress tracking
148
- with Container(id="progress-sidebar"):
149
- yield Static("Progress", id="progress-header")
150
- for i in range(self.total_pages):
151
- yield Static(
152
- f"{i + 1}. {self.page_titles[i]}",
153
- id=f"progress-item-{i}",
154
- classes="progress-item",
155
- )
156
-
157
- # Main content area
158
- with Container(id="main-content"):
159
- yield Static("Welcome to Shotgun!", id="onboarding-header")
160
- with VerticalScroll(id="onboarding-content"):
161
- yield Markdown(id="page-content")
162
- # Resource sections (only shown on page 1)
163
- with Container(id="resource-sections"):
164
- yield Markdown(
165
- "### 🎥 Video Demo\nWatch our demo video to see Shotgun in action",
166
- id="video-section",
167
- )
168
- yield Button(
169
- "▶️ Watch Demo Video",
170
- id="youtube-button",
171
- variant="success",
172
- )
173
- yield Markdown(
174
- "### 📖 Documentation\nRead the comprehensive usage guide for detailed instructions",
175
- id="docs-section",
176
- )
177
- yield Button(
178
- "📚 Read Usage Guide", id="usage-button", variant="primary"
179
- )
180
- yield Static(id="page-indicator")
181
- with Container(id="buttons-container"):
182
- with Horizontal(id="navigation-buttons"):
183
- yield Button("Back", id="back-button", classes="nav-button")
184
- yield Button(
185
- "Next",
186
- id="next-button",
187
- classes="nav-button",
188
- variant="primary",
189
- )
190
- yield Button("Close", id="close-button", classes="nav-button")
191
-
192
- def on_mount(self) -> None:
193
- """Set up the modal after mounting."""
194
- self.update_page()
195
-
196
- def update_page(self) -> None:
197
- """Update the displayed page content and navigation buttons."""
198
- # Mark current page as visited
199
- self.visited_pages.add(self.current_page)
200
-
201
- # Update page content
202
- content_widget = self.query_one("#page-content", Markdown)
203
- content_widget.update(self.get_page_content())
204
-
205
- # Update page indicator
206
- page_indicator = self.query_one("#page-indicator", Static)
207
- page_indicator.update(f"Page {self.current_page + 1} of {self.total_pages}")
208
-
209
- # Update progress sidebar
210
- for i in range(self.total_pages):
211
- progress_item = self.query_one(f"#progress-item-{i}", Static)
212
- # Remove all progress classes first
213
- progress_item.remove_class(
214
- "progress-item-current",
215
- "progress-item-visited",
216
- "progress-item-unvisited",
217
- )
218
- # Add appropriate class
219
- if i == self.current_page:
220
- progress_item.add_class("progress-item-current")
221
- progress_item.update(f"▶ {i + 1}. {self.page_titles[i]}")
222
- elif i in self.visited_pages:
223
- progress_item.add_class("progress-item-visited")
224
- progress_item.update(f"✓ {i + 1}. {self.page_titles[i]}")
225
- else:
226
- progress_item.add_class("progress-item-unvisited")
227
- progress_item.update(f" {i + 1}. {self.page_titles[i]}")
228
-
229
- # Show/hide resource sections (only on page 1)
230
- resource_sections = self.query_one("#resource-sections", Container)
231
- resource_sections.display = self.current_page == 0
232
-
233
- # Update button visibility and states
234
- back_button = self.query_one("#back-button", Button)
235
- next_button = self.query_one("#next-button", Button)
236
-
237
- # Update back button label and state
238
- if self.current_page == 0:
239
- back_button.disabled = True
240
- back_button.label = "Back"
241
- else:
242
- back_button.disabled = False
243
- prev_title = self.page_titles[self.current_page - 1]
244
- back_button.label = f"← {prev_title}"
245
-
246
- # Update next button label
247
- if self.current_page == self.total_pages - 1:
248
- next_button.label = "Finish"
249
- else:
250
- next_title = self.page_titles[self.current_page + 1]
251
- next_button.label = f"{next_title} (Next →)"
252
-
253
- # Focus the appropriate button
254
- if self.current_page == 0:
255
- next_button.focus()
256
- else:
257
- next_button.focus()
258
-
259
- # Scroll content to top
260
- self.query_one("#onboarding-content", VerticalScroll).scroll_home(animate=False)
261
-
262
- def get_page_content(self) -> str:
263
- """Get the content for the current page."""
264
- if self.current_page == 0:
265
- return self._page_1_resources()
266
- elif self.current_page == 1:
267
- return self._page_2_modes()
268
- elif self.current_page == 2:
269
- return self._page_3_prompts()
270
- else:
271
- return self._page_4_context_management()
272
-
273
- def _page_1_resources(self) -> str:
274
- """Page 1: Helpful resources."""
275
- return """
276
- ## Getting Started Resources
277
-
278
- Here are some helpful resources to get you up to speed with Shotgun:
279
- """
280
-
281
- def _page_2_modes(self) -> str:
282
- """Page 2: Explanation of the 5 modes."""
283
- return """
284
- ## Understanding Shotgun's 5 Modes
285
-
286
- Shotgun has 5 specialized modes, each designed for specific tasks. Each mode writes to its own dedicated file in `.shotgun/`:
287
-
288
- ### 🔬 Research Mode
289
- Research topics with web search and synthesize findings. Perfect for gathering information and exploring new concepts.
290
-
291
- **Writes to:** `.shotgun/research.md`
292
-
293
- ### 📝 Specify Mode
294
- Create detailed specifications and requirements documents. Great for planning features and documenting requirements.
295
-
296
- **Writes to:** `.shotgun/specification.md`
297
-
298
- ### 📋 Plan Mode
299
- Create comprehensive, actionable plans with milestones. Ideal for breaking down large projects into manageable steps.
300
-
301
- **Writes to:** `.shotgun/plan.md`
302
-
303
- ### ✅ Tasks Mode
304
- Generate specific, actionable tasks from research and plans. Best for getting concrete next steps and action items.
305
-
306
- **Writes to:** `.shotgun/tasks.md`
307
-
308
- ### 📤 Export Mode
309
- Export artifacts and findings to various formats. Creates documentation like Claude.md (AI instructions), Agent.md (agent specs), PRDs, and other deliverables. Can write to any file in `.shotgun/` except the mode-specific files above.
310
-
311
- **Writes to:** `.shotgun/Claude.md`, `.shotgun/Agent.md`, `.shotgun/PRD.md`, etc.
312
-
313
- ---
314
-
315
- **Tip:** You can switch between modes using `Shift+Tab` or `Ctrl+P` to open the command palette!
316
- """
317
-
318
- def _page_3_prompts(self) -> str:
319
- """Page 3: Tips for better prompts."""
320
- return """
321
- ## Writing Better Prompts
322
-
323
- Here are some tips to get the best results from Shotgun:
324
-
325
- ### 1. Ask for Research First
326
- Before jumping into a task, ask Shotgun to research the codebase or topic:
327
-
328
- > "Can you research how authentication works in this codebase?"
329
-
330
- ### 2. Request Clarifying Questions
331
- Let Shotgun ask you questions to better understand your needs:
332
-
333
- > "I want to add user profiles. Please ask me clarifying questions before starting."
334
-
335
- ### 3. Be Specific About Context
336
- Provide relevant context about what you're trying to accomplish:
337
-
338
- > "I'm working on the payment flow. I need to add support for refunds."
339
-
340
- ### 4. Use the Right Mode
341
- Switch to the appropriate mode for your task:
342
- - Use **Research** for exploration
343
- - Use **Specify** for requirements
344
- - Use **Plan** for implementation strategy
345
- - Use **Tasks** for actionable next steps
346
-
347
- ---
348
-
349
- **Remember:** Shotgun works best when you give it context and let it ask questions!
350
- """
351
-
352
- def _page_4_context_management(self) -> str:
353
- """Page 4: Context management and conversation controls."""
354
- return """
355
- ## Managing Conversation Context
356
-
357
- As conversations grow, you may need to manage the context sent to the AI model.
358
-
359
- ### Clear Conversation
360
- Completely start over with a fresh conversation.
361
-
362
- **How to use:**
363
- - Open Command Palette: `Ctrl+P`
364
- - Type: "Clear Conversation"
365
- - Confirm the action
366
-
367
- **When to use:**
368
- - Starting a completely new task or project
369
- - When you want a clean slate
370
- - Context has become too cluttered
371
-
372
- ---
373
-
374
- ### Compact Conversation
375
- Intelligently compress the conversation history while preserving important context.
376
-
377
- **How to use:**
378
- - Open Command Palette: `Ctrl+P`
379
- - Type: "Compact Conversation"
380
- - Shotgun will compress older messages automatically
381
-
382
- **When to use:**
383
- - Conversation is getting long but you want to keep context
384
- - Running into token limits
385
- - Want to reduce costs while maintaining continuity
386
-
387
- **What it does:**
388
- - Summarizes older messages
389
- - Keeps recent messages intact
390
- - Preserves key information and decisions
391
-
392
- ---
393
-
394
- **Tip:** Use `Ctrl+U` to view your current usage and see how much context you're using!
395
- """
396
-
397
- @on(Button.Pressed, "#back-button")
398
- def handle_back(self) -> None:
399
- """Handle back button press."""
400
- if self.current_page > 0:
401
- self.current_page -= 1
402
- self.update_page()
403
-
404
- @on(Button.Pressed, "#next-button")
405
- def handle_next(self) -> None:
406
- """Handle next/finish button press."""
407
- if self.current_page < self.total_pages - 1:
408
- self.current_page += 1
409
- self.update_page()
410
- else:
411
- # On last page, finish closes the modal
412
- self.dismiss()
413
-
414
- @on(Button.Pressed, "#close-button")
415
- def handle_close(self) -> None:
416
- """Handle close button press."""
417
- self.dismiss()
418
-
419
- @on(Button.Pressed, "#youtube-button")
420
- def handle_youtube(self) -> None:
421
- """Open demo section in README."""
422
- webbrowser.open(
423
- "https://github.com/shotgun-sh/shotgun?tab=readme-ov-file#-demo"
424
- )
425
-
426
- @on(Button.Pressed, "#usage-button")
427
- def handle_usage_guide(self) -> None:
428
- """Open usage guide in browser."""
429
- webbrowser.open(
430
- "https://github.com/shotgun-sh/shotgun?tab=readme-ov-file#-usage"
431
- )
@@ -1,110 +0,0 @@
1
- """Marketing message management for Shotgun CLI."""
2
-
3
- from collections.abc import Callable
4
- from datetime import datetime, timezone
5
- from pathlib import Path
6
- from typing import TYPE_CHECKING
7
-
8
- from shotgun.agents.config.models import MarketingConfig, MarketingMessageRecord
9
- from shotgun.agents.models import FileOperation
10
-
11
- if TYPE_CHECKING:
12
- from shotgun.agents.config.manager import ConfigManager
13
-
14
- # Marketing message IDs
15
- GITHUB_STAR_MESSAGE_ID = "github_star_v1"
16
-
17
- # Spec files that trigger the GitHub star message
18
- SPEC_FILES = {"research.md", "specification.md", "plan.md", "tasks.md"}
19
-
20
-
21
- class MarketingManager:
22
- """Manages marketing messages shown to users."""
23
-
24
- @staticmethod
25
- def should_show_github_star_message(
26
- marketing_config: MarketingConfig, file_operations: list[FileOperation]
27
- ) -> bool:
28
- """
29
- Check if the GitHub star message should be shown.
30
-
31
- Args:
32
- marketing_config: Current marketing configuration
33
- file_operations: List of file operations from the current agent run
34
-
35
- Returns:
36
- True if message should be shown, False otherwise
37
- """
38
- # Check if message has already been shown
39
- if GITHUB_STAR_MESSAGE_ID in marketing_config.messages:
40
- return False
41
-
42
- # Check if any spec file was written
43
- for operation in file_operations:
44
- # operation.file_path is a string, so we convert to Path to get the filename
45
- file_name = Path(operation.file_path).name
46
- if file_name in SPEC_FILES:
47
- return True
48
-
49
- return False
50
-
51
- @staticmethod
52
- def mark_message_shown(
53
- marketing_config: MarketingConfig, message_id: str
54
- ) -> MarketingConfig:
55
- """
56
- Mark a marketing message as shown.
57
-
58
- Args:
59
- marketing_config: Current marketing configuration
60
- message_id: ID of the message to mark as shown
61
-
62
- Returns:
63
- Updated marketing configuration
64
- """
65
- # Create a new record with current timestamp
66
- record = MarketingMessageRecord(shown_at=datetime.now(timezone.utc))
67
-
68
- # Update the messages dict
69
- marketing_config.messages[message_id] = record
70
-
71
- return marketing_config
72
-
73
- @staticmethod
74
- def get_github_star_message() -> str:
75
- """Get the GitHub star marketing message text."""
76
- return "⭐ Enjoying Shotgun? Star us on GitHub: https://github.com/shotgun-sh/shotgun"
77
-
78
- @staticmethod
79
- async def check_and_display_messages(
80
- config_manager: "ConfigManager",
81
- file_operations: list[FileOperation],
82
- display_callback: Callable[[str], None],
83
- ) -> None:
84
- """
85
- Check if any marketing messages should be shown and display them.
86
-
87
- This is the main entry point for marketing message handling. It checks
88
- all configured messages, displays them if appropriate, and updates the
89
- config to mark them as shown.
90
-
91
- Args:
92
- config_manager: Config manager to load/save configuration
93
- file_operations: List of file operations from the current agent run
94
- display_callback: Callback function to display messages to the user
95
- """
96
- config = await config_manager.load()
97
-
98
- # Check GitHub star message
99
- if MarketingManager.should_show_github_star_message(
100
- config.marketing, file_operations
101
- ):
102
- # Display the message
103
- message = MarketingManager.get_github_star_message()
104
- display_callback(message)
105
-
106
- # Mark as shown and save
107
- MarketingManager.mark_message_shown(
108
- config.marketing, GITHUB_STAR_MESSAGE_ID
109
- )
110
- await config_manager.save(config)