shotgun-sh 0.2.8.dev2__py3-none-any.whl → 0.3.3.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 +382 -60
- shotgun/agents/common.py +15 -9
- shotgun/agents/config/README.md +89 -0
- shotgun/agents/config/__init__.py +10 -1
- shotgun/agents/config/constants.py +0 -6
- shotgun/agents/config/manager.py +383 -82
- shotgun/agents/config/models.py +122 -18
- shotgun/agents/config/provider.py +81 -15
- shotgun/agents/config/streaming_test.py +119 -0
- shotgun/agents/context_analyzer/__init__.py +28 -0
- shotgun/agents/context_analyzer/analyzer.py +475 -0
- shotgun/agents/context_analyzer/constants.py +9 -0
- shotgun/agents/context_analyzer/formatter.py +115 -0
- shotgun/agents/context_analyzer/models.py +212 -0
- 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 +36 -5
- shotgun/agents/{history → conversation/history}/constants.py +5 -0
- shotgun/agents/conversation/history/file_content_deduplication.py +216 -0
- shotgun/agents/{history → conversation/history}/history_processors.py +380 -8
- shotgun/agents/{history → conversation/history}/token_counting/anthropic.py +25 -1
- shotgun/agents/{history → conversation/history}/token_counting/base.py +14 -3
- shotgun/agents/{history → conversation/history}/token_counting/openai.py +11 -1
- shotgun/agents/{history → conversation/history}/token_counting/sentencepiece_counter.py +8 -0
- shotgun/agents/{history → conversation/history}/token_counting/tokenizer_cache.py +3 -1
- shotgun/agents/{history → conversation/history}/token_counting/utils.py +0 -3
- shotgun/agents/{conversation_manager.py → conversation/manager.py} +36 -20
- shotgun/agents/{conversation_history.py → conversation/models.py} +8 -92
- shotgun/agents/error/__init__.py +11 -0
- shotgun/agents/error/models.py +19 -0
- shotgun/agents/export.py +2 -2
- shotgun/agents/plan.py +2 -2
- shotgun/agents/research.py +3 -3
- shotgun/agents/runner.py +230 -0
- shotgun/agents/specify.py +2 -2
- shotgun/agents/tasks.py +2 -2
- shotgun/agents/tools/codebase/codebase_shell.py +6 -0
- shotgun/agents/tools/codebase/directory_lister.py +6 -0
- shotgun/agents/tools/codebase/file_read.py +11 -2
- shotgun/agents/tools/codebase/query_graph.py +6 -0
- shotgun/agents/tools/codebase/retrieve_code.py +6 -0
- shotgun/agents/tools/file_management.py +27 -7
- shotgun/agents/tools/registry.py +217 -0
- shotgun/agents/tools/web_search/__init__.py +8 -8
- shotgun/agents/tools/web_search/anthropic.py +8 -2
- shotgun/agents/tools/web_search/gemini.py +7 -1
- shotgun/agents/tools/web_search/openai.py +8 -2
- shotgun/agents/tools/web_search/utils.py +2 -2
- shotgun/agents/usage_manager.py +16 -11
- shotgun/api_endpoints.py +7 -3
- shotgun/build_constants.py +2 -2
- shotgun/cli/clear.py +53 -0
- shotgun/cli/compact.py +188 -0
- shotgun/cli/config.py +8 -5
- shotgun/cli/context.py +154 -0
- shotgun/cli/error_handler.py +24 -0
- shotgun/cli/export.py +34 -34
- shotgun/cli/feedback.py +4 -2
- shotgun/cli/models.py +1 -0
- shotgun/cli/plan.py +34 -34
- shotgun/cli/research.py +18 -10
- 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/cli/update.py +16 -2
- shotgun/codebase/core/change_detector.py +5 -3
- shotgun/codebase/core/code_retrieval.py +4 -2
- shotgun/codebase/core/ingestor.py +163 -15
- shotgun/codebase/core/manager.py +13 -4
- shotgun/codebase/core/nl_query.py +1 -1
- shotgun/codebase/models.py +2 -0
- shotgun/exceptions.py +357 -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 +60 -27
- shotgun/main.py +77 -11
- shotgun/posthog_telemetry.py +38 -29
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +28 -2
- shotgun/prompts/agents/partials/interactive_mode.j2 +3 -3
- shotgun/prompts/agents/plan.j2 +16 -0
- shotgun/prompts/agents/research.j2 +16 -3
- shotgun/prompts/agents/specify.j2 +54 -1
- shotgun/prompts/agents/state/system_state.j2 +0 -2
- shotgun/prompts/agents/tasks.j2 +16 -0
- shotgun/prompts/history/chunk_summarization.j2 +34 -0
- shotgun/prompts/history/combine_summaries.j2 +53 -0
- shotgun/sdk/codebase.py +14 -3
- shotgun/sentry_telemetry.py +163 -16
- shotgun/settings.py +243 -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/telemetry.py +10 -33
- shotgun/tui/app.py +310 -46
- shotgun/tui/commands/__init__.py +1 -1
- shotgun/tui/components/context_indicator.py +179 -0
- shotgun/tui/components/mode_indicator.py +70 -0
- shotgun/tui/components/status_bar.py +48 -0
- shotgun/tui/containers.py +91 -0
- shotgun/tui/dependencies.py +39 -0
- shotgun/tui/layout.py +5 -0
- shotgun/tui/protocols.py +45 -0
- shotgun/tui/screens/chat/__init__.py +5 -0
- shotgun/tui/screens/chat/chat.tcss +54 -0
- shotgun/tui/screens/chat/chat_screen.py +1531 -0
- shotgun/tui/screens/chat/codebase_index_prompt_screen.py +243 -0
- shotgun/tui/screens/chat/codebase_index_selection.py +12 -0
- shotgun/tui/screens/chat/help_text.py +40 -0
- shotgun/tui/screens/chat/prompt_history.py +48 -0
- shotgun/tui/screens/chat.tcss +11 -0
- shotgun/tui/screens/chat_screen/command_providers.py +91 -4
- shotgun/tui/screens/chat_screen/hint_message.py +76 -1
- shotgun/tui/screens/chat_screen/history/__init__.py +22 -0
- shotgun/tui/screens/chat_screen/history/agent_response.py +66 -0
- shotgun/tui/screens/chat_screen/history/chat_history.py +115 -0
- shotgun/tui/screens/chat_screen/history/formatters.py +115 -0
- shotgun/tui/screens/chat_screen/history/partial_response.py +43 -0
- shotgun/tui/screens/chat_screen/history/user_question.py +42 -0
- shotgun/tui/screens/confirmation_dialog.py +191 -0
- shotgun/tui/screens/directory_setup.py +45 -41
- shotgun/tui/screens/feedback.py +14 -7
- shotgun/tui/screens/github_issue.py +111 -0
- shotgun/tui/screens/model_picker.py +77 -32
- shotgun/tui/screens/onboarding.py +580 -0
- shotgun/tui/screens/pipx_migration.py +205 -0
- shotgun/tui/screens/provider_config.py +116 -35
- 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 +112 -18
- shotgun/tui/screens/spec_pull.py +288 -0
- shotgun/tui/screens/welcome.py +137 -11
- shotgun/tui/services/__init__.py +5 -0
- shotgun/tui/services/conversation_service.py +187 -0
- shotgun/tui/state/__init__.py +7 -0
- shotgun/tui/state/processing_state.py +185 -0
- shotgun/tui/utils/mode_progress.py +14 -7
- shotgun/tui/widgets/__init__.py +5 -0
- shotgun/tui/widgets/widget_coordinator.py +263 -0
- shotgun/utils/file_system_utils.py +22 -2
- shotgun/utils/marketing.py +110 -0
- shotgun/utils/update_checker.py +69 -14
- shotgun_sh-0.3.3.dev1.dist-info/METADATA +472 -0
- shotgun_sh-0.3.3.dev1.dist-info/RECORD +229 -0
- {shotgun_sh-0.2.8.dev2.dist-info → shotgun_sh-0.3.3.dev1.dist-info}/WHEEL +1 -1
- {shotgun_sh-0.2.8.dev2.dist-info → shotgun_sh-0.3.3.dev1.dist-info}/entry_points.txt +1 -0
- {shotgun_sh-0.2.8.dev2.dist-info → shotgun_sh-0.3.3.dev1.dist-info}/licenses/LICENSE +1 -1
- shotgun/tui/screens/chat.py +0 -996
- shotgun/tui/screens/chat_screen/history.py +0 -335
- shotgun_sh-0.2.8.dev2.dist-info/METADATA +0 -126
- shotgun_sh-0.2.8.dev2.dist-info/RECORD +0 -155
- /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_estimation.py +0 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""Migration notice screen for pipx users."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from textual import on
|
|
8
|
+
from textual.app import ComposeResult
|
|
9
|
+
from textual.containers import Container, Horizontal, VerticalScroll
|
|
10
|
+
from textual.events import Resize
|
|
11
|
+
from textual.screen import ModalScreen
|
|
12
|
+
from textual.widgets import Button, Label, Markdown
|
|
13
|
+
|
|
14
|
+
from shotgun.tui.layout import COMPACT_HEIGHT_THRESHOLD
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PipxMigrationScreen(ModalScreen[None]):
|
|
21
|
+
"""Modal screen warning pipx users about migration to uvx."""
|
|
22
|
+
|
|
23
|
+
CSS = """
|
|
24
|
+
PipxMigrationScreen {
|
|
25
|
+
align: center middle;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#migration-container {
|
|
29
|
+
width: 90;
|
|
30
|
+
height: auto;
|
|
31
|
+
max-height: 90%;
|
|
32
|
+
border: thick $error;
|
|
33
|
+
background: $surface;
|
|
34
|
+
padding: 2;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#migration-content {
|
|
38
|
+
height: 1fr;
|
|
39
|
+
padding: 1 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#buttons-container {
|
|
43
|
+
height: auto;
|
|
44
|
+
padding: 2 0 1 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
#action-buttons {
|
|
48
|
+
width: 100%;
|
|
49
|
+
height: auto;
|
|
50
|
+
align: center middle;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.action-button {
|
|
54
|
+
margin: 0 1;
|
|
55
|
+
min-width: 20;
|
|
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
|
+
}
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
BINDINGS = [
|
|
85
|
+
("escape", "dismiss", "Continue Anyway"),
|
|
86
|
+
("ctrl+c", "app.quit", "Quit"),
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
def compose(self) -> ComposeResult:
|
|
90
|
+
"""Compose the migration notice modal."""
|
|
91
|
+
with Container(id="migration-container"):
|
|
92
|
+
with VerticalScroll(id="migration-content"):
|
|
93
|
+
yield Markdown(
|
|
94
|
+
"""
|
|
95
|
+
## We've Switched to uvx
|
|
96
|
+
|
|
97
|
+
We've switched from `pipx` to `uvx` as the primary installation method due to critical build issues with our `kuzu` dependency.
|
|
98
|
+
|
|
99
|
+
### The Problem
|
|
100
|
+
Users with pipx encounter cmake build errors during installation because pip falls back to building from source instead of using pre-built binary wheels.
|
|
101
|
+
|
|
102
|
+
### The Solution: uvx
|
|
103
|
+
- ✅ **No build tools required** - Binary wheels enforced
|
|
104
|
+
- ✅ **10-100x faster** - Much faster than pipx
|
|
105
|
+
- ✅ **Better reliability** - No cmake/build errors
|
|
106
|
+
|
|
107
|
+
### How to Migrate
|
|
108
|
+
|
|
109
|
+
**1. Uninstall shotgun-sh from pipx:**
|
|
110
|
+
```bash
|
|
111
|
+
pipx uninstall shotgun-sh
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**2. Install uv:**
|
|
115
|
+
```bash
|
|
116
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
117
|
+
```
|
|
118
|
+
Or with Homebrew: `brew install uv`
|
|
119
|
+
|
|
120
|
+
**3. Run shotgun-sh with uvx:**
|
|
121
|
+
```bash
|
|
122
|
+
uvx shotgun-sh
|
|
123
|
+
```
|
|
124
|
+
Or install permanently: `uv tool install shotgun-sh`
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
### Need Help?
|
|
129
|
+
|
|
130
|
+
**Discord:** https://discord.gg/5RmY6J2N7s
|
|
131
|
+
|
|
132
|
+
**Full Migration Guide:** https://github.com/shotgun-sh/shotgun/blob/main/docs/PIPX_MIGRATION.md
|
|
133
|
+
"""
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
with Container(id="buttons-container"):
|
|
137
|
+
yield Label("", id="migration-status")
|
|
138
|
+
with Horizontal(id="action-buttons"):
|
|
139
|
+
yield Button(
|
|
140
|
+
"Copy Instructions to Clipboard",
|
|
141
|
+
variant="default",
|
|
142
|
+
id="copy-instructions",
|
|
143
|
+
classes="action-button",
|
|
144
|
+
)
|
|
145
|
+
yield Button(
|
|
146
|
+
"Continue Anyway",
|
|
147
|
+
variant="primary",
|
|
148
|
+
id="continue",
|
|
149
|
+
classes="action-button",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def on_mount(self) -> None:
|
|
153
|
+
"""Focus the continue button and ensure scroll starts at top."""
|
|
154
|
+
self.query_one("#continue", Button).focus()
|
|
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")
|
|
181
|
+
|
|
182
|
+
@on(Button.Pressed, "#copy-instructions")
|
|
183
|
+
def _copy_instructions(self) -> None:
|
|
184
|
+
"""Copy all migration instructions to clipboard."""
|
|
185
|
+
instructions = """# Step 1: Uninstall from pipx
|
|
186
|
+
pipx uninstall shotgun-sh
|
|
187
|
+
|
|
188
|
+
# Step 2: Install uv
|
|
189
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
190
|
+
|
|
191
|
+
# Step 3: Run shotgun with uvx
|
|
192
|
+
uvx shotgun-sh"""
|
|
193
|
+
status_label = self.query_one("#migration-status", Label)
|
|
194
|
+
try:
|
|
195
|
+
import pyperclip # type: ignore[import-untyped] # noqa: PGH003
|
|
196
|
+
|
|
197
|
+
pyperclip.copy(instructions)
|
|
198
|
+
status_label.update("✓ Copied migration instructions to clipboard!")
|
|
199
|
+
except ImportError:
|
|
200
|
+
status_label.update("⚠️ Clipboard not available. See instructions above.")
|
|
201
|
+
|
|
202
|
+
@on(Button.Pressed, "#continue")
|
|
203
|
+
def _continue(self) -> None:
|
|
204
|
+
"""Dismiss the modal and continue."""
|
|
205
|
+
self.dismiss()
|
|
@@ -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 = [
|
|
@@ -97,12 +131,13 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
97
131
|
"Don't have an API Key? Use these links to get one: [OpenAI](https://platform.openai.com/api-keys) | [Anthropic](https://console.anthropic.com) | [Google Gemini](https://aistudio.google.com)",
|
|
98
132
|
id="provider-links",
|
|
99
133
|
)
|
|
100
|
-
yield ListView(*self.
|
|
134
|
+
yield ListView(*self._build_provider_items_sync(), id="provider-list")
|
|
101
135
|
yield Input(
|
|
102
136
|
placeholder=self._input_placeholder(self.selected_provider),
|
|
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")
|
|
@@ -110,8 +145,6 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
110
145
|
yield Button("Done \\[ESC]", id="done")
|
|
111
146
|
|
|
112
147
|
def on_mount(self) -> None:
|
|
113
|
-
self.refresh_provider_status()
|
|
114
|
-
self._update_done_button_visibility()
|
|
115
148
|
list_view = self.query_one(ListView)
|
|
116
149
|
if list_view.children:
|
|
117
150
|
list_view.index = 0
|
|
@@ -121,13 +154,35 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
121
154
|
self.query_one("#authenticate", Button).display = False
|
|
122
155
|
self.set_focus(self.query_one("#api-key", Input))
|
|
123
156
|
|
|
157
|
+
# Refresh UI asynchronously
|
|
158
|
+
self.run_worker(self._refresh_ui(), exclusive=False)
|
|
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
|
+
|
|
124
175
|
def on_screenresume(self) -> None:
|
|
125
176
|
"""Refresh provider status when screen is resumed.
|
|
126
177
|
|
|
127
178
|
This ensures the UI reflects any provider changes made elsewhere.
|
|
128
179
|
"""
|
|
129
|
-
self.
|
|
130
|
-
|
|
180
|
+
self.run_worker(self._refresh_ui(), exclusive=False)
|
|
181
|
+
|
|
182
|
+
async def _refresh_ui(self) -> None:
|
|
183
|
+
"""Refresh provider status and button visibility."""
|
|
184
|
+
await self.refresh_provider_status()
|
|
185
|
+
await self._update_done_button_visibility()
|
|
131
186
|
|
|
132
187
|
def action_done(self) -> None:
|
|
133
188
|
self.dismiss()
|
|
@@ -170,7 +225,11 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
170
225
|
if not self.is_mounted:
|
|
171
226
|
return
|
|
172
227
|
|
|
173
|
-
# Show/hide UI elements based on provider type
|
|
228
|
+
# Show/hide UI elements based on provider type asynchronously
|
|
229
|
+
self.run_worker(self._update_provider_ui(provider), exclusive=False)
|
|
230
|
+
|
|
231
|
+
async def _update_provider_ui(self, provider: ProviderType) -> None:
|
|
232
|
+
"""Update UI elements based on selected provider."""
|
|
174
233
|
is_shotgun = provider == "shotgun"
|
|
175
234
|
|
|
176
235
|
input_widget = self.query_one("#api-key", Input)
|
|
@@ -183,7 +242,7 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
183
242
|
save_button.display = False
|
|
184
243
|
|
|
185
244
|
# Only show Authenticate button if shotgun is NOT already configured
|
|
186
|
-
if self._has_provider_key("shotgun"):
|
|
245
|
+
if await self._has_provider_key("shotgun"):
|
|
187
246
|
auth_button.display = False
|
|
188
247
|
else:
|
|
189
248
|
auth_button.display = True
|
|
@@ -200,22 +259,29 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
200
259
|
app = cast("ShotgunApp", self.app)
|
|
201
260
|
return app.config_manager
|
|
202
261
|
|
|
203
|
-
def refresh_provider_status(self) -> None:
|
|
262
|
+
async def refresh_provider_status(self) -> None:
|
|
204
263
|
"""Update the list view entries to reflect configured providers."""
|
|
205
264
|
for provider_id in get_configurable_providers():
|
|
206
265
|
label = self.query_one(f"#label-{provider_id}", Label)
|
|
207
|
-
label.update(self._provider_label(provider_id))
|
|
266
|
+
label.update(await self._provider_label(provider_id))
|
|
208
267
|
|
|
209
|
-
def _update_done_button_visibility(self) -> None:
|
|
268
|
+
async def _update_done_button_visibility(self) -> None:
|
|
210
269
|
"""Show/hide Done button based on whether any provider keys are configured."""
|
|
211
270
|
done_button = self.query_one("#done", Button)
|
|
212
|
-
has_keys = self.config_manager.has_any_provider_key()
|
|
271
|
+
has_keys = await self.config_manager.has_any_provider_key()
|
|
213
272
|
done_button.display = has_keys
|
|
214
273
|
|
|
215
|
-
def
|
|
274
|
+
def _build_provider_items_sync(self) -> list[ListItem]:
|
|
275
|
+
"""Build provider items synchronously for compose().
|
|
276
|
+
|
|
277
|
+
Labels will be populated with status asynchronously in on_mount().
|
|
278
|
+
"""
|
|
216
279
|
items: list[ListItem] = []
|
|
217
280
|
for provider_id in get_configurable_providers():
|
|
218
|
-
|
|
281
|
+
# Create labels with placeholder text - will be updated in on_mount()
|
|
282
|
+
label = Label(
|
|
283
|
+
self._provider_display_name(provider_id), id=f"label-{provider_id}"
|
|
284
|
+
)
|
|
219
285
|
items.append(ListItem(label, id=f"provider-{provider_id}"))
|
|
220
286
|
return items
|
|
221
287
|
|
|
@@ -225,11 +291,10 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
225
291
|
provider_id = item.id.removeprefix("provider-")
|
|
226
292
|
return provider_id if provider_id in get_configurable_providers() else None
|
|
227
293
|
|
|
228
|
-
def _provider_label(self, provider_id: str) -> str:
|
|
294
|
+
async def _provider_label(self, provider_id: str) -> str:
|
|
229
295
|
display = self._provider_display_name(provider_id)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
)
|
|
296
|
+
has_key = await self._has_provider_key(provider_id)
|
|
297
|
+
status = "Configured" if has_key else "Not configured"
|
|
233
298
|
return f"{display} · {status}"
|
|
234
299
|
|
|
235
300
|
def _provider_display_name(self, provider_id: str) -> str:
|
|
@@ -244,53 +309,67 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
244
309
|
def _input_placeholder(self, provider_id: str) -> str:
|
|
245
310
|
return f"{self._provider_display_name(provider_id)} API key"
|
|
246
311
|
|
|
247
|
-
def _has_provider_key(self, provider_id: str) -> bool:
|
|
312
|
+
async def _has_provider_key(self, provider_id: str) -> bool:
|
|
248
313
|
"""Check if provider has a configured API key."""
|
|
249
314
|
if provider_id == "shotgun":
|
|
250
315
|
# Check shotgun key directly
|
|
251
|
-
config = self.config_manager.load()
|
|
316
|
+
config = await self.config_manager.load()
|
|
252
317
|
return self.config_manager._provider_has_api_key(config.shotgun)
|
|
253
318
|
else:
|
|
254
319
|
# Check LLM provider key
|
|
255
320
|
try:
|
|
256
321
|
provider = ProviderType(provider_id)
|
|
257
|
-
return self.config_manager.has_provider_key(provider)
|
|
322
|
+
return await self.config_manager.has_provider_key(provider)
|
|
258
323
|
except ValueError:
|
|
259
324
|
return False
|
|
260
325
|
|
|
261
326
|
def _save_api_key(self) -> None:
|
|
327
|
+
self.run_worker(self._do_save_api_key(), exclusive=True)
|
|
328
|
+
|
|
329
|
+
async def _do_save_api_key(self) -> None:
|
|
330
|
+
"""Async implementation of API key saving."""
|
|
262
331
|
input_widget = self.query_one("#api-key", Input)
|
|
263
332
|
api_key = input_widget.value.strip()
|
|
333
|
+
status_label = self.query_one("#provider-status", Label)
|
|
264
334
|
|
|
265
335
|
if not api_key:
|
|
266
|
-
|
|
336
|
+
status_label.update("❌ Enter an API key before saving.")
|
|
337
|
+
status_label.add_class("error")
|
|
267
338
|
return
|
|
268
339
|
|
|
269
340
|
try:
|
|
270
|
-
self.config_manager.update_provider(
|
|
341
|
+
await self.config_manager.update_provider(
|
|
271
342
|
self.selected_provider,
|
|
272
343
|
api_key=api_key,
|
|
273
344
|
)
|
|
274
345
|
except Exception as exc: # pragma: no cover - defensive; textual path
|
|
275
|
-
|
|
346
|
+
status_label.update(f"❌ Failed to save key: {exc}")
|
|
347
|
+
status_label.add_class("error")
|
|
276
348
|
return
|
|
277
349
|
|
|
278
350
|
input_widget.value = ""
|
|
279
|
-
self.refresh_provider_status()
|
|
280
|
-
self._update_done_button_visibility()
|
|
281
|
-
|
|
282
|
-
f"Saved API key for {self._provider_display_name(self.selected_provider)}."
|
|
351
|
+
await self.refresh_provider_status()
|
|
352
|
+
await self._update_done_button_visibility()
|
|
353
|
+
status_label.update(
|
|
354
|
+
f"✓ Saved API key for {self._provider_display_name(self.selected_provider)}."
|
|
283
355
|
)
|
|
356
|
+
status_label.remove_class("error")
|
|
284
357
|
|
|
285
358
|
def _clear_api_key(self) -> None:
|
|
359
|
+
self.run_worker(self._do_clear_api_key(), exclusive=True)
|
|
360
|
+
|
|
361
|
+
async def _do_clear_api_key(self) -> None:
|
|
362
|
+
"""Async implementation of API key clearing."""
|
|
363
|
+
status_label = self.query_one("#provider-status", Label)
|
|
286
364
|
try:
|
|
287
|
-
self.config_manager.clear_provider_key(self.selected_provider)
|
|
365
|
+
await self.config_manager.clear_provider_key(self.selected_provider)
|
|
288
366
|
except Exception as exc: # pragma: no cover - defensive; textual path
|
|
289
|
-
|
|
367
|
+
status_label.update(f"❌ Failed to clear key: {exc}")
|
|
368
|
+
status_label.add_class("error")
|
|
290
369
|
return
|
|
291
370
|
|
|
292
|
-
self.refresh_provider_status()
|
|
293
|
-
self._update_done_button_visibility()
|
|
371
|
+
await self.refresh_provider_status()
|
|
372
|
+
await self._update_done_button_visibility()
|
|
294
373
|
self.query_one("#api-key", Input).value = ""
|
|
295
374
|
|
|
296
375
|
# If we just cleared shotgun, show the Authenticate button
|
|
@@ -298,9 +377,10 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
298
377
|
auth_button = self.query_one("#authenticate", Button)
|
|
299
378
|
auth_button.display = True
|
|
300
379
|
|
|
301
|
-
|
|
302
|
-
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)}."
|
|
303
382
|
)
|
|
383
|
+
status_label.remove_class("error")
|
|
304
384
|
|
|
305
385
|
async def _start_shotgun_auth(self) -> None:
|
|
306
386
|
"""Launch Shotgun Account authentication flow."""
|
|
@@ -311,5 +391,6 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
311
391
|
|
|
312
392
|
# Refresh provider status after auth completes
|
|
313
393
|
if result:
|
|
314
|
-
self.refresh_provider_status()
|
|
315
|
-
#
|
|
394
|
+
await self.refresh_provider_status()
|
|
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
|
+
]
|