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.
- shotgun/agents/agent_manager.py +219 -37
- shotgun/agents/common.py +79 -78
- shotgun/agents/config/README.md +89 -0
- shotgun/agents/config/__init__.py +10 -1
- shotgun/agents/config/manager.py +364 -53
- shotgun/agents/config/models.py +101 -21
- shotgun/agents/config/provider.py +51 -13
- shotgun/agents/config/streaming_test.py +119 -0
- shotgun/agents/context_analyzer/analyzer.py +6 -2
- shotgun/agents/conversation/__init__.py +18 -0
- shotgun/agents/conversation/filters.py +164 -0
- shotgun/agents/conversation/history/chunking.py +278 -0
- shotgun/agents/{history → conversation/history}/compaction.py +27 -1
- shotgun/agents/{history → conversation/history}/constants.py +5 -0
- shotgun/agents/conversation/history/file_content_deduplication.py +239 -0
- shotgun/agents/{history → conversation/history}/history_processors.py +267 -3
- shotgun/agents/{history → conversation/history}/token_counting/anthropic.py +8 -0
- shotgun/agents/{conversation_manager.py → conversation/manager.py} +1 -1
- shotgun/agents/{conversation_history.py → conversation/models.py} +8 -94
- shotgun/agents/error/__init__.py +11 -0
- shotgun/agents/error/models.py +19 -0
- shotgun/agents/export.py +12 -13
- shotgun/agents/models.py +66 -1
- shotgun/agents/plan.py +12 -13
- shotgun/agents/research.py +13 -10
- shotgun/agents/router/__init__.py +47 -0
- shotgun/agents/router/models.py +376 -0
- shotgun/agents/router/router.py +185 -0
- shotgun/agents/router/tools/__init__.py +18 -0
- shotgun/agents/router/tools/delegation_tools.py +503 -0
- shotgun/agents/router/tools/plan_tools.py +322 -0
- shotgun/agents/runner.py +230 -0
- shotgun/agents/specify.py +12 -13
- shotgun/agents/tasks.py +12 -13
- shotgun/agents/tools/file_management.py +49 -1
- shotgun/agents/tools/registry.py +2 -0
- shotgun/agents/tools/web_search/__init__.py +1 -2
- shotgun/agents/tools/web_search/gemini.py +1 -3
- shotgun/agents/tools/web_search/openai.py +1 -1
- shotgun/build_constants.py +2 -2
- shotgun/cli/clear.py +1 -1
- shotgun/cli/compact.py +5 -3
- shotgun/cli/context.py +44 -1
- shotgun/cli/error_handler.py +24 -0
- shotgun/cli/export.py +34 -34
- shotgun/cli/plan.py +34 -34
- shotgun/cli/research.py +17 -9
- shotgun/cli/spec/__init__.py +5 -0
- shotgun/cli/spec/backup.py +81 -0
- shotgun/cli/spec/commands.py +132 -0
- shotgun/cli/spec/models.py +48 -0
- shotgun/cli/spec/pull_service.py +219 -0
- shotgun/cli/specify.py +20 -19
- shotgun/cli/tasks.py +34 -34
- shotgun/codebase/core/change_detector.py +1 -1
- shotgun/codebase/core/ingestor.py +154 -8
- shotgun/codebase/core/manager.py +1 -1
- shotgun/codebase/models.py +2 -0
- shotgun/exceptions.py +325 -0
- shotgun/llm_proxy/__init__.py +17 -0
- shotgun/llm_proxy/client.py +215 -0
- shotgun/llm_proxy/models.py +137 -0
- shotgun/logging_config.py +42 -0
- shotgun/main.py +4 -0
- shotgun/posthog_telemetry.py +1 -1
- shotgun/prompts/agents/export.j2 +2 -0
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +23 -3
- shotgun/prompts/agents/partials/interactive_mode.j2 +3 -3
- shotgun/prompts/agents/partials/router_delegation_mode.j2 +36 -0
- shotgun/prompts/agents/plan.j2 +29 -1
- shotgun/prompts/agents/research.j2 +75 -23
- shotgun/prompts/agents/router.j2 +440 -0
- shotgun/prompts/agents/specify.j2 +80 -4
- shotgun/prompts/agents/state/system_state.j2 +15 -8
- shotgun/prompts/agents/tasks.j2 +63 -23
- shotgun/prompts/history/chunk_summarization.j2 +34 -0
- shotgun/prompts/history/combine_summaries.j2 +53 -0
- shotgun/sdk/codebase.py +14 -3
- shotgun/settings.py +5 -0
- shotgun/shotgun_web/__init__.py +67 -1
- shotgun/shotgun_web/client.py +42 -1
- shotgun/shotgun_web/constants.py +46 -0
- shotgun/shotgun_web/exceptions.py +29 -0
- shotgun/shotgun_web/models.py +390 -0
- shotgun/shotgun_web/shared_specs/__init__.py +32 -0
- shotgun/shotgun_web/shared_specs/file_scanner.py +175 -0
- shotgun/shotgun_web/shared_specs/hasher.py +83 -0
- shotgun/shotgun_web/shared_specs/models.py +71 -0
- shotgun/shotgun_web/shared_specs/upload_pipeline.py +329 -0
- shotgun/shotgun_web/shared_specs/utils.py +34 -0
- shotgun/shotgun_web/specs_client.py +703 -0
- shotgun/shotgun_web/supabase_client.py +31 -0
- shotgun/tui/app.py +78 -15
- shotgun/tui/components/mode_indicator.py +120 -25
- shotgun/tui/components/status_bar.py +2 -2
- shotgun/tui/containers.py +1 -1
- shotgun/tui/dependencies.py +64 -9
- shotgun/tui/layout.py +5 -0
- shotgun/tui/protocols.py +37 -0
- shotgun/tui/screens/chat/chat.tcss +9 -1
- shotgun/tui/screens/chat/chat_screen.py +1015 -106
- shotgun/tui/screens/chat/codebase_index_prompt_screen.py +196 -17
- shotgun/tui/screens/chat_screen/command_providers.py +13 -89
- shotgun/tui/screens/chat_screen/hint_message.py +76 -1
- shotgun/tui/screens/chat_screen/history/agent_response.py +7 -3
- shotgun/tui/screens/chat_screen/history/chat_history.py +12 -0
- shotgun/tui/screens/chat_screen/history/formatters.py +53 -15
- shotgun/tui/screens/chat_screen/history/partial_response.py +11 -1
- shotgun/tui/screens/chat_screen/messages.py +219 -0
- shotgun/tui/screens/confirmation_dialog.py +40 -0
- shotgun/tui/screens/directory_setup.py +45 -41
- shotgun/tui/screens/feedback.py +10 -3
- shotgun/tui/screens/github_issue.py +11 -2
- shotgun/tui/screens/model_picker.py +28 -8
- shotgun/tui/screens/onboarding.py +179 -26
- shotgun/tui/screens/pipx_migration.py +58 -6
- shotgun/tui/screens/provider_config.py +66 -8
- shotgun/tui/screens/shared_specs/__init__.py +21 -0
- shotgun/tui/screens/shared_specs/create_spec_dialog.py +273 -0
- shotgun/tui/screens/shared_specs/models.py +56 -0
- shotgun/tui/screens/shared_specs/share_specs_dialog.py +390 -0
- shotgun/tui/screens/shared_specs/upload_progress_screen.py +452 -0
- shotgun/tui/screens/shotgun_auth.py +110 -16
- shotgun/tui/screens/spec_pull.py +288 -0
- shotgun/tui/screens/welcome.py +123 -0
- shotgun/tui/services/conversation_service.py +5 -2
- shotgun/tui/utils/mode_progress.py +20 -86
- shotgun/tui/widgets/__init__.py +2 -1
- shotgun/tui/widgets/approval_widget.py +152 -0
- shotgun/tui/widgets/cascade_confirmation_widget.py +203 -0
- shotgun/tui/widgets/plan_panel.py +129 -0
- shotgun/tui/widgets/step_checkpoint_widget.py +180 -0
- shotgun/tui/widgets/widget_coordinator.py +1 -1
- {shotgun_sh-0.2.17.dist-info → shotgun_sh-0.4.0.dev1.dist-info}/METADATA +11 -4
- shotgun_sh-0.4.0.dev1.dist-info/RECORD +242 -0
- {shotgun_sh-0.2.17.dist-info → shotgun_sh-0.4.0.dev1.dist-info}/WHEEL +1 -1
- shotgun_sh-0.2.17.dist-info/RECORD +0 -194
- /shotgun/agents/{history → conversation/history}/__init__.py +0 -0
- /shotgun/agents/{history → conversation/history}/context_extraction.py +0 -0
- /shotgun/agents/{history → conversation/history}/history_building.py +0 -0
- /shotgun/agents/{history → conversation/history}/message_utils.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_counting/__init__.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_counting/base.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_counting/openai.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_counting/sentencepiece_counter.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_counting/tokenizer_cache.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_counting/utils.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_estimation.py +0 -0
- {shotgun_sh-0.2.17.dist-info → shotgun_sh-0.4.0.dev1.dist-info}/entry_points.txt +0 -0
- {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
|
|
430
|
+
"""Page 2: Explanation of the Router and its modes."""
|
|
283
431
|
return """
|
|
284
|
-
## Understanding Shotgun's
|
|
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
|
-
|
|
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
|
-
|
|
436
|
+
### Two Operating Modes
|
|
292
437
|
|
|
293
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
455
|
+
---
|
|
307
456
|
|
|
308
|
-
###
|
|
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
|
-
|
|
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:**
|
|
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.
|
|
341
|
-
|
|
342
|
-
-
|
|
343
|
-
-
|
|
344
|
-
-
|
|
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
|
-
|
|
198
|
+
status_label.update("✓ Copied migration instructions to clipboard!")
|
|
144
199
|
except ImportError:
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
+
]
|