shotgun-sh 0.2.17__py3-none-any.whl → 0.4.0.dev1__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.
Files changed (150) hide show
  1. shotgun/agents/agent_manager.py +219 -37
  2. shotgun/agents/common.py +79 -78
  3. shotgun/agents/config/README.md +89 -0
  4. shotgun/agents/config/__init__.py +10 -1
  5. shotgun/agents/config/manager.py +364 -53
  6. shotgun/agents/config/models.py +101 -21
  7. shotgun/agents/config/provider.py +51 -13
  8. shotgun/agents/config/streaming_test.py +119 -0
  9. shotgun/agents/context_analyzer/analyzer.py +6 -2
  10. shotgun/agents/conversation/__init__.py +18 -0
  11. shotgun/agents/conversation/filters.py +164 -0
  12. shotgun/agents/conversation/history/chunking.py +278 -0
  13. shotgun/agents/{history → conversation/history}/compaction.py +27 -1
  14. shotgun/agents/{history → conversation/history}/constants.py +5 -0
  15. shotgun/agents/conversation/history/file_content_deduplication.py +239 -0
  16. shotgun/agents/{history → conversation/history}/history_processors.py +267 -3
  17. shotgun/agents/{history → conversation/history}/token_counting/anthropic.py +8 -0
  18. shotgun/agents/{conversation_manager.py → conversation/manager.py} +1 -1
  19. shotgun/agents/{conversation_history.py → conversation/models.py} +8 -94
  20. shotgun/agents/error/__init__.py +11 -0
  21. shotgun/agents/error/models.py +19 -0
  22. shotgun/agents/export.py +12 -13
  23. shotgun/agents/models.py +66 -1
  24. shotgun/agents/plan.py +12 -13
  25. shotgun/agents/research.py +13 -10
  26. shotgun/agents/router/__init__.py +47 -0
  27. shotgun/agents/router/models.py +376 -0
  28. shotgun/agents/router/router.py +185 -0
  29. shotgun/agents/router/tools/__init__.py +18 -0
  30. shotgun/agents/router/tools/delegation_tools.py +503 -0
  31. shotgun/agents/router/tools/plan_tools.py +322 -0
  32. shotgun/agents/runner.py +230 -0
  33. shotgun/agents/specify.py +12 -13
  34. shotgun/agents/tasks.py +12 -13
  35. shotgun/agents/tools/file_management.py +49 -1
  36. shotgun/agents/tools/registry.py +2 -0
  37. shotgun/agents/tools/web_search/__init__.py +1 -2
  38. shotgun/agents/tools/web_search/gemini.py +1 -3
  39. shotgun/agents/tools/web_search/openai.py +1 -1
  40. shotgun/build_constants.py +2 -2
  41. shotgun/cli/clear.py +1 -1
  42. shotgun/cli/compact.py +5 -3
  43. shotgun/cli/context.py +44 -1
  44. shotgun/cli/error_handler.py +24 -0
  45. shotgun/cli/export.py +34 -34
  46. shotgun/cli/plan.py +34 -34
  47. shotgun/cli/research.py +17 -9
  48. shotgun/cli/spec/__init__.py +5 -0
  49. shotgun/cli/spec/backup.py +81 -0
  50. shotgun/cli/spec/commands.py +132 -0
  51. shotgun/cli/spec/models.py +48 -0
  52. shotgun/cli/spec/pull_service.py +219 -0
  53. shotgun/cli/specify.py +20 -19
  54. shotgun/cli/tasks.py +34 -34
  55. shotgun/codebase/core/change_detector.py +1 -1
  56. shotgun/codebase/core/ingestor.py +154 -8
  57. shotgun/codebase/core/manager.py +1 -1
  58. shotgun/codebase/models.py +2 -0
  59. shotgun/exceptions.py +325 -0
  60. shotgun/llm_proxy/__init__.py +17 -0
  61. shotgun/llm_proxy/client.py +215 -0
  62. shotgun/llm_proxy/models.py +137 -0
  63. shotgun/logging_config.py +42 -0
  64. shotgun/main.py +4 -0
  65. shotgun/posthog_telemetry.py +1 -1
  66. shotgun/prompts/agents/export.j2 +2 -0
  67. shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +23 -3
  68. shotgun/prompts/agents/partials/interactive_mode.j2 +3 -3
  69. shotgun/prompts/agents/partials/router_delegation_mode.j2 +36 -0
  70. shotgun/prompts/agents/plan.j2 +29 -1
  71. shotgun/prompts/agents/research.j2 +75 -23
  72. shotgun/prompts/agents/router.j2 +440 -0
  73. shotgun/prompts/agents/specify.j2 +80 -4
  74. shotgun/prompts/agents/state/system_state.j2 +15 -8
  75. shotgun/prompts/agents/tasks.j2 +63 -23
  76. shotgun/prompts/history/chunk_summarization.j2 +34 -0
  77. shotgun/prompts/history/combine_summaries.j2 +53 -0
  78. shotgun/sdk/codebase.py +14 -3
  79. shotgun/settings.py +5 -0
  80. shotgun/shotgun_web/__init__.py +67 -1
  81. shotgun/shotgun_web/client.py +42 -1
  82. shotgun/shotgun_web/constants.py +46 -0
  83. shotgun/shotgun_web/exceptions.py +29 -0
  84. shotgun/shotgun_web/models.py +390 -0
  85. shotgun/shotgun_web/shared_specs/__init__.py +32 -0
  86. shotgun/shotgun_web/shared_specs/file_scanner.py +175 -0
  87. shotgun/shotgun_web/shared_specs/hasher.py +83 -0
  88. shotgun/shotgun_web/shared_specs/models.py +71 -0
  89. shotgun/shotgun_web/shared_specs/upload_pipeline.py +329 -0
  90. shotgun/shotgun_web/shared_specs/utils.py +34 -0
  91. shotgun/shotgun_web/specs_client.py +703 -0
  92. shotgun/shotgun_web/supabase_client.py +31 -0
  93. shotgun/tui/app.py +78 -15
  94. shotgun/tui/components/mode_indicator.py +120 -25
  95. shotgun/tui/components/status_bar.py +2 -2
  96. shotgun/tui/containers.py +1 -1
  97. shotgun/tui/dependencies.py +64 -9
  98. shotgun/tui/layout.py +5 -0
  99. shotgun/tui/protocols.py +37 -0
  100. shotgun/tui/screens/chat/chat.tcss +9 -1
  101. shotgun/tui/screens/chat/chat_screen.py +1015 -106
  102. shotgun/tui/screens/chat/codebase_index_prompt_screen.py +196 -17
  103. shotgun/tui/screens/chat_screen/command_providers.py +13 -89
  104. shotgun/tui/screens/chat_screen/hint_message.py +76 -1
  105. shotgun/tui/screens/chat_screen/history/agent_response.py +7 -3
  106. shotgun/tui/screens/chat_screen/history/chat_history.py +12 -0
  107. shotgun/tui/screens/chat_screen/history/formatters.py +53 -15
  108. shotgun/tui/screens/chat_screen/history/partial_response.py +11 -1
  109. shotgun/tui/screens/chat_screen/messages.py +219 -0
  110. shotgun/tui/screens/confirmation_dialog.py +40 -0
  111. shotgun/tui/screens/directory_setup.py +45 -41
  112. shotgun/tui/screens/feedback.py +10 -3
  113. shotgun/tui/screens/github_issue.py +11 -2
  114. shotgun/tui/screens/model_picker.py +28 -8
  115. shotgun/tui/screens/onboarding.py +179 -26
  116. shotgun/tui/screens/pipx_migration.py +58 -6
  117. shotgun/tui/screens/provider_config.py +66 -8
  118. shotgun/tui/screens/shared_specs/__init__.py +21 -0
  119. shotgun/tui/screens/shared_specs/create_spec_dialog.py +273 -0
  120. shotgun/tui/screens/shared_specs/models.py +56 -0
  121. shotgun/tui/screens/shared_specs/share_specs_dialog.py +390 -0
  122. shotgun/tui/screens/shared_specs/upload_progress_screen.py +452 -0
  123. shotgun/tui/screens/shotgun_auth.py +110 -16
  124. shotgun/tui/screens/spec_pull.py +288 -0
  125. shotgun/tui/screens/welcome.py +123 -0
  126. shotgun/tui/services/conversation_service.py +5 -2
  127. shotgun/tui/utils/mode_progress.py +20 -86
  128. shotgun/tui/widgets/__init__.py +2 -1
  129. shotgun/tui/widgets/approval_widget.py +152 -0
  130. shotgun/tui/widgets/cascade_confirmation_widget.py +203 -0
  131. shotgun/tui/widgets/plan_panel.py +129 -0
  132. shotgun/tui/widgets/step_checkpoint_widget.py +180 -0
  133. shotgun/tui/widgets/widget_coordinator.py +1 -1
  134. {shotgun_sh-0.2.17.dist-info → shotgun_sh-0.4.0.dev1.dist-info}/METADATA +11 -4
  135. shotgun_sh-0.4.0.dev1.dist-info/RECORD +242 -0
  136. {shotgun_sh-0.2.17.dist-info → shotgun_sh-0.4.0.dev1.dist-info}/WHEEL +1 -1
  137. shotgun_sh-0.2.17.dist-info/RECORD +0 -194
  138. /shotgun/agents/{history → conversation/history}/__init__.py +0 -0
  139. /shotgun/agents/{history → conversation/history}/context_extraction.py +0 -0
  140. /shotgun/agents/{history → conversation/history}/history_building.py +0 -0
  141. /shotgun/agents/{history → conversation/history}/message_utils.py +0 -0
  142. /shotgun/agents/{history → conversation/history}/token_counting/__init__.py +0 -0
  143. /shotgun/agents/{history → conversation/history}/token_counting/base.py +0 -0
  144. /shotgun/agents/{history → conversation/history}/token_counting/openai.py +0 -0
  145. /shotgun/agents/{history → conversation/history}/token_counting/sentencepiece_counter.py +0 -0
  146. /shotgun/agents/{history → conversation/history}/token_counting/tokenizer_cache.py +0 -0
  147. /shotgun/agents/{history → conversation/history}/token_counting/utils.py +0 -0
  148. /shotgun/agents/{history → conversation/history}/token_estimation.py +0 -0
  149. {shotgun_sh-0.2.17.dist-info → shotgun_sh-0.4.0.dev1.dist-info}/entry_points.txt +0 -0
  150. {shotgun_sh-0.2.17.dist-info → shotgun_sh-0.4.0.dev1.dist-info}/licenses/LICENSE +0 -0
@@ -5,9 +5,12 @@ import webbrowser
5
5
  from textual import on
6
6
  from textual.app import ComposeResult
7
7
  from textual.containers import Container, Horizontal, VerticalScroll
8
+ from textual.events import Resize
8
9
  from textual.screen import ModalScreen
9
10
  from textual.widgets import Button, Markdown, Static
10
11
 
12
+ from shotgun.tui.layout import COMPACT_HEIGHT_THRESHOLD, TINY_HEIGHT_THRESHOLD
13
+
11
14
 
12
15
  class OnboardingModal(ModalScreen[None]):
13
16
  """Multi-page onboarding modal for new users.
@@ -120,6 +123,80 @@ class OnboardingModal(ModalScreen[None]):
120
123
  padding: 0;
121
124
  margin: 2 0 1 0;
122
125
  }
126
+
127
+ /* Tiny screen fallback */
128
+ #tiny-screen-container {
129
+ display: none;
130
+ width: auto;
131
+ height: auto;
132
+ padding: 1 2;
133
+ background: $surface;
134
+ text-align: center;
135
+ }
136
+
137
+ #tiny-screen-message {
138
+ padding: 1 0;
139
+ }
140
+
141
+ #tiny-screen-link {
142
+ padding: 1 0;
143
+ color: $accent;
144
+ }
145
+
146
+ /* Compact styles for short terminals */
147
+ #onboarding-container.compact {
148
+ padding: 1;
149
+ max-height: 98%;
150
+ }
151
+
152
+ #progress-sidebar.compact {
153
+ padding: 0;
154
+ }
155
+
156
+ .progress-item.compact {
157
+ padding: 0;
158
+ }
159
+
160
+ #onboarding-header.compact {
161
+ padding-bottom: 0;
162
+ }
163
+
164
+ #onboarding-content.compact {
165
+ padding: 0;
166
+ }
167
+
168
+ #page-indicator.compact {
169
+ padding: 0;
170
+ }
171
+
172
+ #buttons-container.compact {
173
+ padding: 0;
174
+ }
175
+
176
+ #resource-sections.compact {
177
+ padding: 0;
178
+ }
179
+
180
+ #resource-sections.compact Button {
181
+ margin: 0 0 1 0;
182
+ }
183
+
184
+ #video-section.compact {
185
+ margin: 0;
186
+ }
187
+
188
+ #docs-section.compact {
189
+ margin: 1 0 0 0;
190
+ }
191
+
192
+ /* Tiny mode - hide full onboarding, show minimal message */
193
+ OnboardingModal.tiny #onboarding-container {
194
+ display: none;
195
+ }
196
+
197
+ OnboardingModal.tiny #tiny-screen-container {
198
+ display: block;
199
+ }
123
200
  """
124
201
 
125
202
  BINDINGS = [
@@ -143,6 +220,20 @@ class OnboardingModal(ModalScreen[None]):
143
220
 
144
221
  def compose(self) -> ComposeResult:
145
222
  """Compose the onboarding modal."""
223
+ # Tiny screen fallback - shown when terminal is too small
224
+ with Container(id="tiny-screen-container"):
225
+ yield Static(
226
+ "Your screen is too small for the onboarding wizard.",
227
+ id="tiny-screen-message",
228
+ )
229
+ yield Static(
230
+ "[@click=screen.open_usage_guide]View usage instructions[/]",
231
+ id="tiny-screen-link",
232
+ markup=True,
233
+ )
234
+ yield Button("Start Shotgunning", id="tiny-close-button")
235
+
236
+ # Full onboarding container
146
237
  with Container(id="onboarding-container"):
147
238
  # Left sidebar for progress tracking
148
239
  with Container(id="progress-sidebar"):
@@ -192,6 +283,63 @@ class OnboardingModal(ModalScreen[None]):
192
283
  def on_mount(self) -> None:
193
284
  """Set up the modal after mounting."""
194
285
  self.update_page()
286
+ # Apply layout based on terminal height
287
+ self._apply_layout_for_height(self.app.size.height)
288
+
289
+ @on(Resize)
290
+ def handle_resize(self, event: Resize) -> None:
291
+ """Adjust layout based on terminal height."""
292
+ self._apply_layout_for_height(event.size.height)
293
+
294
+ def _apply_layout_for_height(self, height: int) -> None:
295
+ """Apply appropriate layout based on terminal height."""
296
+ if height < TINY_HEIGHT_THRESHOLD:
297
+ self.add_class("tiny")
298
+ self.remove_class("compact")
299
+ elif height < COMPACT_HEIGHT_THRESHOLD:
300
+ self.remove_class("tiny")
301
+ self._apply_compact_classes(True)
302
+ else:
303
+ self.remove_class("tiny")
304
+ self._apply_compact_classes(False)
305
+
306
+ def _apply_compact_classes(self, compact: bool) -> None:
307
+ """Apply or remove compact layout classes."""
308
+ container = self.query_one("#onboarding-container")
309
+ sidebar = self.query_one("#progress-sidebar")
310
+ header = self.query_one("#onboarding-header")
311
+ content = self.query_one("#onboarding-content")
312
+ page_indicator = self.query_one("#page-indicator")
313
+ buttons_container = self.query_one("#buttons-container")
314
+ resource_sections = self.query_one("#resource-sections")
315
+ progress_items = self.query(".progress-item")
316
+
317
+ if compact:
318
+ container.add_class("compact")
319
+ sidebar.add_class("compact")
320
+ header.add_class("compact")
321
+ content.add_class("compact")
322
+ page_indicator.add_class("compact")
323
+ buttons_container.add_class("compact")
324
+ resource_sections.add_class("compact")
325
+ for item in progress_items:
326
+ item.add_class("compact")
327
+ else:
328
+ container.remove_class("compact")
329
+ sidebar.remove_class("compact")
330
+ header.remove_class("compact")
331
+ content.remove_class("compact")
332
+ page_indicator.remove_class("compact")
333
+ buttons_container.remove_class("compact")
334
+ resource_sections.remove_class("compact")
335
+ for item in progress_items:
336
+ item.remove_class("compact")
337
+
338
+ def action_open_usage_guide(self) -> None:
339
+ """Open the usage guide in browser."""
340
+ webbrowser.open(
341
+ "https://github.com/shotgun-sh/shotgun?tab=readme-ov-file#-usage"
342
+ )
195
343
 
196
344
  def update_page(self) -> None:
197
345
  """Update the displayed page content and navigation buttons."""
@@ -279,40 +427,45 @@ Here are some helpful resources to get you up to speed with Shotgun:
279
427
  """
280
428
 
281
429
  def _page_2_modes(self) -> str:
282
- """Page 2: Explanation of the 5 modes."""
430
+ """Page 2: Explanation of the Router and its modes."""
283
431
  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/`:
432
+ ## Understanding Shotgun's Router
287
433
 
288
- ### 🔬 Research Mode
289
- Research topics with web search and synthesize findings. Perfect for gathering information and exploring new concepts.
434
+ Shotgun uses an intelligent **Router** that orchestrates your workflow automatically. Just describe what you need, and the Router will coordinate research, specifications, planning, and tasks for you.
290
435
 
291
- **Writes to:** `.shotgun/research.md`
436
+ ### Two Operating Modes
292
437
 
293
- ### 📝 Specify Mode
294
- Create detailed specifications and requirements documents. Great for planning features and documenting requirements.
438
+ The Router operates in two modes, which you can toggle with `Shift+Tab`:
295
439
 
296
- **Writes to:** `.shotgun/specification.md`
440
+ ### 📋 Planning Mode (Default)
441
+ - **Incremental execution**: Does one step at a time
442
+ - **Asks clarifying questions** before complex tasks
443
+ - **Shows plan for approval** before executing multi-step work
444
+ - **Confirms before cascading** changes to dependent files
297
445
 
298
- ### 📋 Plan Mode
299
- Create comprehensive, actionable plans with milestones. Ideal for breaking down large projects into manageable steps.
446
+ Best for: Complex tasks, learning the workflow, staying in control
300
447
 
301
- **Writes to:** `.shotgun/plan.md`
448
+ ### ✍️ Drafting Mode
449
+ - **Auto-executes** plans without stopping for approval
450
+ - **Makes reasonable assumptions** instead of asking questions
451
+ - **Updates all dependent files** automatically
302
452
 
303
- ### Tasks Mode
304
- Generate specific, actionable tasks from research and plans. Best for getting concrete next steps and action items.
453
+ Best for: Routine tasks, experienced users, speed-focused work
305
454
 
306
- **Writes to:** `.shotgun/tasks.md`
455
+ ---
307
456
 
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.
457
+ ### Files Created
310
458
 
311
- **Writes to:** `.shotgun/Claude.md`, `.shotgun/Agent.md`, `.shotgun/PRD.md`, etc.
459
+ The Router manages these files in `.shotgun/`:
460
+ - `research.md` - Research findings
461
+ - `specification.md` - Detailed specifications
462
+ - `plan.md` - Implementation plans
463
+ - `tasks.md` - Actionable task lists
464
+ - `exports/` - Documentation exports
312
465
 
313
466
  ---
314
467
 
315
- **Tip:** You can switch between modes using `Shift+Tab` or `Ctrl+P` to open the command palette!
468
+ **Tip:** Press `Shift+Tab` to toggle between Planning and Drafting modes!
316
469
  """
317
470
 
318
471
  def _page_3_prompts(self) -> str:
@@ -337,12 +490,11 @@ Provide relevant context about what you're trying to accomplish:
337
490
 
338
491
  > "I'm working on the payment flow. I need to add support for refunds."
339
492
 
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
493
+ ### 4. Let the Router Guide You
494
+ The Router will automatically coordinate the right workflow:
495
+ - Describe what you want to accomplish
496
+ - In Planning mode, it will ask clarifying questions first
497
+ - In Drafting mode, it will execute immediately
346
498
 
347
499
  ---
348
500
 
@@ -412,6 +564,7 @@ Intelligently compress the conversation history while preserving important conte
412
564
  self.dismiss()
413
565
 
414
566
  @on(Button.Pressed, "#close-button")
567
+ @on(Button.Pressed, "#tiny-close-button")
415
568
  def handle_close(self) -> None:
416
569
  """Handle close button press."""
417
570
  self.dismiss()
@@ -7,8 +7,11 @@ from typing import TYPE_CHECKING
7
7
  from textual import on
8
8
  from textual.app import ComposeResult
9
9
  from textual.containers import Container, Horizontal, VerticalScroll
10
+ from textual.events import Resize
10
11
  from textual.screen import ModalScreen
11
- from textual.widgets import Button, Markdown
12
+ from textual.widgets import Button, Label, Markdown
13
+
14
+ from shotgun.tui.layout import COMPACT_HEIGHT_THRESHOLD
12
15
 
13
16
  if TYPE_CHECKING:
14
17
  pass
@@ -51,6 +54,31 @@ class PipxMigrationScreen(ModalScreen[None]):
51
54
  margin: 0 1;
52
55
  min-width: 20;
53
56
  }
57
+
58
+ #migration-status {
59
+ height: auto;
60
+ padding: 1;
61
+ min-height: 1;
62
+ text-align: center;
63
+ }
64
+
65
+ /* Compact styles for short terminals */
66
+ #migration-container.compact {
67
+ padding: 1;
68
+ max-height: 98%;
69
+ }
70
+
71
+ #migration-content.compact {
72
+ padding: 0;
73
+ }
74
+
75
+ #buttons-container.compact {
76
+ padding: 1 0 0 0;
77
+ }
78
+
79
+ #migration-status.compact {
80
+ padding: 0;
81
+ }
54
82
  """
55
83
 
56
84
  BINDINGS = [
@@ -106,6 +134,7 @@ Or install permanently: `uv tool install shotgun-sh`
106
134
  )
107
135
 
108
136
  with Container(id="buttons-container"):
137
+ yield Label("", id="migration-status")
109
138
  with Horizontal(id="action-buttons"):
110
139
  yield Button(
111
140
  "Copy Instructions to Clipboard",
@@ -124,6 +153,31 @@ Or install permanently: `uv tool install shotgun-sh`
124
153
  """Focus the continue button and ensure scroll starts at top."""
125
154
  self.query_one("#continue", Button).focus()
126
155
  self.query_one("#migration-content", VerticalScroll).scroll_home(animate=False)
156
+ # Apply compact layout if starting in a short terminal
157
+ self._apply_compact_layout(self.app.size.height < COMPACT_HEIGHT_THRESHOLD)
158
+
159
+ @on(Resize)
160
+ def handle_resize(self, event: Resize) -> None:
161
+ """Adjust layout based on terminal height."""
162
+ self._apply_compact_layout(event.size.height < COMPACT_HEIGHT_THRESHOLD)
163
+
164
+ def _apply_compact_layout(self, compact: bool) -> None:
165
+ """Apply or remove compact layout classes for short terminals."""
166
+ container = self.query_one("#migration-container")
167
+ content = self.query_one("#migration-content")
168
+ buttons_container = self.query_one("#buttons-container")
169
+ status = self.query_one("#migration-status")
170
+
171
+ if compact:
172
+ container.add_class("compact")
173
+ content.add_class("compact")
174
+ buttons_container.add_class("compact")
175
+ status.add_class("compact")
176
+ else:
177
+ container.remove_class("compact")
178
+ content.remove_class("compact")
179
+ buttons_container.remove_class("compact")
180
+ status.remove_class("compact")
127
181
 
128
182
  @on(Button.Pressed, "#copy-instructions")
129
183
  def _copy_instructions(self) -> None:
@@ -136,16 +190,14 @@ curl -LsSf https://astral.sh/uv/install.sh | sh
136
190
 
137
191
  # Step 3: Run shotgun with uvx
138
192
  uvx shotgun-sh"""
193
+ status_label = self.query_one("#migration-status", Label)
139
194
  try:
140
195
  import pyperclip # type: ignore[import-untyped] # noqa: PGH003
141
196
 
142
197
  pyperclip.copy(instructions)
143
- self.notify("Copied migration instructions to clipboard!")
198
+ status_label.update("Copied migration instructions to clipboard!")
144
199
  except ImportError:
145
- self.notify(
146
- "Clipboard not available. See instructions above.",
147
- severity="warning",
148
- )
200
+ status_label.update("⚠️ Clipboard not available. See instructions above.")
149
201
 
150
202
  @on(Button.Pressed, "#continue")
151
203
  def _continue(self) -> None:
@@ -7,11 +7,13 @@ from typing import TYPE_CHECKING, cast
7
7
  from textual import on
8
8
  from textual.app import ComposeResult
9
9
  from textual.containers import Horizontal, Vertical
10
+ from textual.events import Resize
10
11
  from textual.reactive import reactive
11
12
  from textual.screen import Screen
12
13
  from textual.widgets import Button, Input, Label, ListItem, ListView, Markdown, Static
13
14
 
14
15
  from shotgun.agents.config import ConfigManager, ProviderType
16
+ from shotgun.tui.layout import COMPACT_HEIGHT_THRESHOLD
15
17
 
16
18
  if TYPE_CHECKING:
17
19
  from ..app import ShotgunApp
@@ -77,6 +79,38 @@ class ProviderConfigScreen(Screen[None]):
77
79
  #provider-list {
78
80
  padding: 1;
79
81
  }
82
+ #provider-status {
83
+ height: auto;
84
+ padding: 0 1;
85
+ min-height: 1;
86
+ }
87
+ #provider-status.error {
88
+ color: $error;
89
+ }
90
+
91
+ /* Compact styles for short terminals */
92
+ ProviderConfigScreen.compact #titlebox {
93
+ margin: 0;
94
+ padding: 0;
95
+ border: none;
96
+ }
97
+
98
+ ProviderConfigScreen.compact #provider-config-summary {
99
+ display: none;
100
+ }
101
+
102
+ ProviderConfigScreen.compact #provider-links {
103
+ display: none;
104
+ }
105
+
106
+ ProviderConfigScreen.compact #provider-list {
107
+ margin: 0;
108
+ padding: 0;
109
+ }
110
+
111
+ ProviderConfigScreen.compact #provider-actions {
112
+ padding: 0;
113
+ }
80
114
  """
81
115
 
82
116
  BINDINGS = [
@@ -103,6 +137,7 @@ class ProviderConfigScreen(Screen[None]):
103
137
  password=True,
104
138
  id="api-key",
105
139
  )
140
+ yield Label("", id="provider-status")
106
141
  with Horizontal(id="provider-actions"):
107
142
  yield Button("Save key \\[ENTER]", variant="primary", id="save")
108
143
  yield Button("Authenticate", variant="success", id="authenticate")
@@ -122,6 +157,21 @@ class ProviderConfigScreen(Screen[None]):
122
157
  # Refresh UI asynchronously
123
158
  self.run_worker(self._refresh_ui(), exclusive=False)
124
159
 
160
+ # Apply layout based on terminal height
161
+ self._apply_layout_for_height(self.app.size.height)
162
+
163
+ @on(Resize)
164
+ def handle_resize(self, event: Resize) -> None:
165
+ """Adjust layout based on terminal height."""
166
+ self._apply_layout_for_height(event.size.height)
167
+
168
+ def _apply_layout_for_height(self, height: int) -> None:
169
+ """Apply appropriate layout based on terminal height."""
170
+ if height < COMPACT_HEIGHT_THRESHOLD:
171
+ self.add_class("compact")
172
+ else:
173
+ self.remove_class("compact")
174
+
125
175
  def on_screenresume(self) -> None:
126
176
  """Refresh provider status when screen is resumed.
127
177
 
@@ -280,9 +330,11 @@ class ProviderConfigScreen(Screen[None]):
280
330
  """Async implementation of API key saving."""
281
331
  input_widget = self.query_one("#api-key", Input)
282
332
  api_key = input_widget.value.strip()
333
+ status_label = self.query_one("#provider-status", Label)
283
334
 
284
335
  if not api_key:
285
- self.notify("Enter an API key before saving.", severity="error")
336
+ status_label.update("Enter an API key before saving.")
337
+ status_label.add_class("error")
286
338
  return
287
339
 
288
340
  try:
@@ -291,25 +343,29 @@ class ProviderConfigScreen(Screen[None]):
291
343
  api_key=api_key,
292
344
  )
293
345
  except Exception as exc: # pragma: no cover - defensive; textual path
294
- self.notify(f"Failed to save key: {exc}", severity="error")
346
+ status_label.update(f"Failed to save key: {exc}")
347
+ status_label.add_class("error")
295
348
  return
296
349
 
297
350
  input_widget.value = ""
298
351
  await self.refresh_provider_status()
299
352
  await self._update_done_button_visibility()
300
- self.notify(
301
- f"Saved API key for {self._provider_display_name(self.selected_provider)}."
353
+ status_label.update(
354
+ f"Saved API key for {self._provider_display_name(self.selected_provider)}."
302
355
  )
356
+ status_label.remove_class("error")
303
357
 
304
358
  def _clear_api_key(self) -> None:
305
359
  self.run_worker(self._do_clear_api_key(), exclusive=True)
306
360
 
307
361
  async def _do_clear_api_key(self) -> None:
308
362
  """Async implementation of API key clearing."""
363
+ status_label = self.query_one("#provider-status", Label)
309
364
  try:
310
365
  await self.config_manager.clear_provider_key(self.selected_provider)
311
366
  except Exception as exc: # pragma: no cover - defensive; textual path
312
- self.notify(f"Failed to clear key: {exc}", severity="error")
367
+ status_label.update(f"Failed to clear key: {exc}")
368
+ status_label.add_class("error")
313
369
  return
314
370
 
315
371
  await self.refresh_provider_status()
@@ -321,9 +377,10 @@ class ProviderConfigScreen(Screen[None]):
321
377
  auth_button = self.query_one("#authenticate", Button)
322
378
  auth_button.display = True
323
379
 
324
- self.notify(
325
- f"Cleared API key for {self._provider_display_name(self.selected_provider)}."
380
+ status_label.update(
381
+ f"Cleared API key for {self._provider_display_name(self.selected_provider)}."
326
382
  )
383
+ status_label.remove_class("error")
327
384
 
328
385
  async def _start_shotgun_auth(self) -> None:
329
386
  """Launch Shotgun Account authentication flow."""
@@ -335,4 +392,5 @@ class ProviderConfigScreen(Screen[None]):
335
392
  # Refresh provider status after auth completes
336
393
  if result:
337
394
  await self.refresh_provider_status()
338
- # Notify handled by auth screen
395
+ # Auto-dismiss provider config screen after successful auth
396
+ self.dismiss()
@@ -0,0 +1,21 @@
1
+ """Shared specs TUI screens and dialogs."""
2
+
3
+ from shotgun.tui.screens.shared_specs.create_spec_dialog import CreateSpecDialog
4
+ from shotgun.tui.screens.shared_specs.models import (
5
+ CreateSpecResult,
6
+ ShareSpecsAction,
7
+ ShareSpecsResult,
8
+ UploadScreenResult,
9
+ )
10
+ from shotgun.tui.screens.shared_specs.share_specs_dialog import ShareSpecsDialog
11
+ from shotgun.tui.screens.shared_specs.upload_progress_screen import UploadProgressScreen
12
+
13
+ __all__ = [
14
+ "CreateSpecDialog",
15
+ "CreateSpecResult",
16
+ "ShareSpecsAction",
17
+ "ShareSpecsDialog",
18
+ "ShareSpecsResult",
19
+ "UploadProgressScreen",
20
+ "UploadScreenResult",
21
+ ]