shotgun-sh 0.2.11__py3-none-any.whl → 0.2.11.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of shotgun-sh might be problematic. Click here for more details.
- shotgun/agents/agent_manager.py +28 -194
- shotgun/agents/common.py +8 -14
- shotgun/agents/config/manager.py +33 -64
- shotgun/agents/config/models.py +1 -25
- shotgun/agents/config/provider.py +2 -2
- shotgun/agents/context_analyzer/analyzer.py +24 -2
- shotgun/agents/conversation_manager.py +19 -35
- shotgun/agents/export.py +2 -2
- shotgun/agents/history/history_processors.py +3 -99
- shotgun/agents/history/token_counting/anthropic.py +1 -17
- shotgun/agents/history/token_counting/base.py +3 -14
- shotgun/agents/history/token_counting/openai.py +1 -11
- shotgun/agents/history/token_counting/sentencepiece_counter.py +0 -8
- shotgun/agents/history/token_counting/tokenizer_cache.py +1 -3
- shotgun/agents/history/token_counting/utils.py +3 -0
- shotgun/agents/plan.py +2 -2
- shotgun/agents/research.py +3 -3
- shotgun/agents/specify.py +2 -2
- shotgun/agents/tasks.py +2 -2
- shotgun/agents/tools/codebase/file_read.py +2 -5
- shotgun/agents/tools/file_management.py +7 -11
- shotgun/agents/tools/web_search/__init__.py +8 -8
- shotgun/agents/tools/web_search/anthropic.py +2 -2
- shotgun/agents/tools/web_search/gemini.py +1 -1
- shotgun/agents/tools/web_search/openai.py +1 -1
- shotgun/agents/tools/web_search/utils.py +2 -2
- shotgun/agents/usage_manager.py +11 -16
- shotgun/build_constants.py +2 -2
- shotgun/cli/clear.py +1 -2
- shotgun/cli/compact.py +3 -3
- shotgun/cli/config.py +5 -8
- shotgun/cli/context.py +2 -2
- shotgun/cli/export.py +1 -1
- shotgun/cli/feedback.py +2 -4
- shotgun/cli/plan.py +1 -1
- shotgun/cli/research.py +1 -1
- shotgun/cli/specify.py +1 -1
- shotgun/cli/tasks.py +1 -1
- shotgun/codebase/core/change_detector.py +3 -5
- shotgun/codebase/core/code_retrieval.py +2 -4
- shotgun/codebase/core/ingestor.py +8 -10
- shotgun/codebase/core/manager.py +3 -3
- shotgun/codebase/core/nl_query.py +1 -1
- shotgun/logging_config.py +17 -10
- shotgun/main.py +1 -3
- shotgun/posthog_telemetry.py +4 -14
- shotgun/sentry_telemetry.py +2 -22
- shotgun/telemetry.py +1 -3
- shotgun/tui/app.py +65 -71
- shotgun/tui/components/context_indicator.py +0 -43
- shotgun/tui/containers.py +17 -15
- shotgun/tui/dependencies.py +2 -2
- shotgun/tui/screens/chat/chat_screen.py +40 -164
- shotgun/tui/screens/chat/help_text.py +15 -16
- shotgun/tui/screens/chat_screen/command_providers.py +0 -10
- shotgun/tui/screens/feedback.py +4 -4
- shotgun/tui/screens/model_picker.py +20 -21
- shotgun/tui/screens/provider_config.py +27 -50
- shotgun/tui/screens/shotgun_auth.py +2 -2
- shotgun/tui/screens/welcome.py +11 -14
- shotgun/tui/services/conversation_service.py +14 -16
- shotgun/tui/utils/mode_progress.py +7 -14
- shotgun/tui/widgets/widget_coordinator.py +0 -15
- shotgun/utils/file_system_utils.py +0 -19
- {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/METADATA +1 -2
- {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/RECORD +69 -73
- shotgun/exceptions.py +0 -32
- shotgun/tui/screens/github_issue.py +0 -102
- shotgun/tui/screens/onboarding.py +0 -431
- shotgun/utils/marketing.py +0 -110
- {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/WHEEL +0 -0
- {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/entry_points.txt +0 -0
- {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/licenses/LICENSE +0 -0
|
@@ -97,7 +97,7 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
97
97
|
"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
98
|
id="provider-links",
|
|
99
99
|
)
|
|
100
|
-
yield ListView(*self.
|
|
100
|
+
yield ListView(*self._build_provider_items(), id="provider-list")
|
|
101
101
|
yield Input(
|
|
102
102
|
placeholder=self._input_placeholder(self.selected_provider),
|
|
103
103
|
password=True,
|
|
@@ -110,6 +110,8 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
110
110
|
yield Button("Done \\[ESC]", id="done")
|
|
111
111
|
|
|
112
112
|
def on_mount(self) -> None:
|
|
113
|
+
self.refresh_provider_status()
|
|
114
|
+
self._update_done_button_visibility()
|
|
113
115
|
list_view = self.query_one(ListView)
|
|
114
116
|
if list_view.children:
|
|
115
117
|
list_view.index = 0
|
|
@@ -119,20 +121,13 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
119
121
|
self.query_one("#authenticate", Button).display = False
|
|
120
122
|
self.set_focus(self.query_one("#api-key", Input))
|
|
121
123
|
|
|
122
|
-
# Refresh UI asynchronously
|
|
123
|
-
self.run_worker(self._refresh_ui(), exclusive=False)
|
|
124
|
-
|
|
125
124
|
def on_screenresume(self) -> None:
|
|
126
125
|
"""Refresh provider status when screen is resumed.
|
|
127
126
|
|
|
128
127
|
This ensures the UI reflects any provider changes made elsewhere.
|
|
129
128
|
"""
|
|
130
|
-
self.
|
|
131
|
-
|
|
132
|
-
async def _refresh_ui(self) -> None:
|
|
133
|
-
"""Refresh provider status and button visibility."""
|
|
134
|
-
await self.refresh_provider_status()
|
|
135
|
-
await self._update_done_button_visibility()
|
|
129
|
+
self.refresh_provider_status()
|
|
130
|
+
self._update_done_button_visibility()
|
|
136
131
|
|
|
137
132
|
def action_done(self) -> None:
|
|
138
133
|
self.dismiss()
|
|
@@ -175,11 +170,7 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
175
170
|
if not self.is_mounted:
|
|
176
171
|
return
|
|
177
172
|
|
|
178
|
-
# Show/hide UI elements based on provider type
|
|
179
|
-
self.run_worker(self._update_provider_ui(provider), exclusive=False)
|
|
180
|
-
|
|
181
|
-
async def _update_provider_ui(self, provider: ProviderType) -> None:
|
|
182
|
-
"""Update UI elements based on selected provider."""
|
|
173
|
+
# Show/hide UI elements based on provider type
|
|
183
174
|
is_shotgun = provider == "shotgun"
|
|
184
175
|
|
|
185
176
|
input_widget = self.query_one("#api-key", Input)
|
|
@@ -192,7 +183,7 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
192
183
|
save_button.display = False
|
|
193
184
|
|
|
194
185
|
# Only show Authenticate button if shotgun is NOT already configured
|
|
195
|
-
if
|
|
186
|
+
if self._has_provider_key("shotgun"):
|
|
196
187
|
auth_button.display = False
|
|
197
188
|
else:
|
|
198
189
|
auth_button.display = True
|
|
@@ -209,29 +200,22 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
209
200
|
app = cast("ShotgunApp", self.app)
|
|
210
201
|
return app.config_manager
|
|
211
202
|
|
|
212
|
-
|
|
203
|
+
def refresh_provider_status(self) -> None:
|
|
213
204
|
"""Update the list view entries to reflect configured providers."""
|
|
214
205
|
for provider_id in get_configurable_providers():
|
|
215
206
|
label = self.query_one(f"#label-{provider_id}", Label)
|
|
216
|
-
label.update(
|
|
207
|
+
label.update(self._provider_label(provider_id))
|
|
217
208
|
|
|
218
|
-
|
|
209
|
+
def _update_done_button_visibility(self) -> None:
|
|
219
210
|
"""Show/hide Done button based on whether any provider keys are configured."""
|
|
220
211
|
done_button = self.query_one("#done", Button)
|
|
221
|
-
has_keys =
|
|
212
|
+
has_keys = self.config_manager.has_any_provider_key()
|
|
222
213
|
done_button.display = has_keys
|
|
223
214
|
|
|
224
|
-
def
|
|
225
|
-
"""Build provider items synchronously for compose().
|
|
226
|
-
|
|
227
|
-
Labels will be populated with status asynchronously in on_mount().
|
|
228
|
-
"""
|
|
215
|
+
def _build_provider_items(self) -> list[ListItem]:
|
|
229
216
|
items: list[ListItem] = []
|
|
230
217
|
for provider_id in get_configurable_providers():
|
|
231
|
-
|
|
232
|
-
label = Label(
|
|
233
|
-
self._provider_display_name(provider_id), id=f"label-{provider_id}"
|
|
234
|
-
)
|
|
218
|
+
label = Label(self._provider_label(provider_id), id=f"label-{provider_id}")
|
|
235
219
|
items.append(ListItem(label, id=f"provider-{provider_id}"))
|
|
236
220
|
return items
|
|
237
221
|
|
|
@@ -241,10 +225,11 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
241
225
|
provider_id = item.id.removeprefix("provider-")
|
|
242
226
|
return provider_id if provider_id in get_configurable_providers() else None
|
|
243
227
|
|
|
244
|
-
|
|
228
|
+
def _provider_label(self, provider_id: str) -> str:
|
|
245
229
|
display = self._provider_display_name(provider_id)
|
|
246
|
-
|
|
247
|
-
|
|
230
|
+
status = (
|
|
231
|
+
"Configured" if self._has_provider_key(provider_id) else "Not configured"
|
|
232
|
+
)
|
|
248
233
|
return f"{display} · {status}"
|
|
249
234
|
|
|
250
235
|
def _provider_display_name(self, provider_id: str) -> str:
|
|
@@ -259,25 +244,21 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
259
244
|
def _input_placeholder(self, provider_id: str) -> str:
|
|
260
245
|
return f"{self._provider_display_name(provider_id)} API key"
|
|
261
246
|
|
|
262
|
-
|
|
247
|
+
def _has_provider_key(self, provider_id: str) -> bool:
|
|
263
248
|
"""Check if provider has a configured API key."""
|
|
264
249
|
if provider_id == "shotgun":
|
|
265
250
|
# Check shotgun key directly
|
|
266
|
-
config =
|
|
251
|
+
config = self.config_manager.load()
|
|
267
252
|
return self.config_manager._provider_has_api_key(config.shotgun)
|
|
268
253
|
else:
|
|
269
254
|
# Check LLM provider key
|
|
270
255
|
try:
|
|
271
256
|
provider = ProviderType(provider_id)
|
|
272
|
-
return
|
|
257
|
+
return self.config_manager.has_provider_key(provider)
|
|
273
258
|
except ValueError:
|
|
274
259
|
return False
|
|
275
260
|
|
|
276
261
|
def _save_api_key(self) -> None:
|
|
277
|
-
self.run_worker(self._do_save_api_key(), exclusive=True)
|
|
278
|
-
|
|
279
|
-
async def _do_save_api_key(self) -> None:
|
|
280
|
-
"""Async implementation of API key saving."""
|
|
281
262
|
input_widget = self.query_one("#api-key", Input)
|
|
282
263
|
api_key = input_widget.value.strip()
|
|
283
264
|
|
|
@@ -286,7 +267,7 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
286
267
|
return
|
|
287
268
|
|
|
288
269
|
try:
|
|
289
|
-
|
|
270
|
+
self.config_manager.update_provider(
|
|
290
271
|
self.selected_provider,
|
|
291
272
|
api_key=api_key,
|
|
292
273
|
)
|
|
@@ -295,25 +276,21 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
295
276
|
return
|
|
296
277
|
|
|
297
278
|
input_widget.value = ""
|
|
298
|
-
|
|
299
|
-
|
|
279
|
+
self.refresh_provider_status()
|
|
280
|
+
self._update_done_button_visibility()
|
|
300
281
|
self.notify(
|
|
301
282
|
f"Saved API key for {self._provider_display_name(self.selected_provider)}."
|
|
302
283
|
)
|
|
303
284
|
|
|
304
285
|
def _clear_api_key(self) -> None:
|
|
305
|
-
self.run_worker(self._do_clear_api_key(), exclusive=True)
|
|
306
|
-
|
|
307
|
-
async def _do_clear_api_key(self) -> None:
|
|
308
|
-
"""Async implementation of API key clearing."""
|
|
309
286
|
try:
|
|
310
|
-
|
|
287
|
+
self.config_manager.clear_provider_key(self.selected_provider)
|
|
311
288
|
except Exception as exc: # pragma: no cover - defensive; textual path
|
|
312
289
|
self.notify(f"Failed to clear key: {exc}", severity="error")
|
|
313
290
|
return
|
|
314
291
|
|
|
315
|
-
|
|
316
|
-
|
|
292
|
+
self.refresh_provider_status()
|
|
293
|
+
self._update_done_button_visibility()
|
|
317
294
|
self.query_one("#api-key", Input).value = ""
|
|
318
295
|
|
|
319
296
|
# If we just cleared shotgun, show the Authenticate button
|
|
@@ -334,5 +311,5 @@ class ProviderConfigScreen(Screen[None]):
|
|
|
334
311
|
|
|
335
312
|
# Refresh provider status after auth completes
|
|
336
313
|
if result:
|
|
337
|
-
|
|
314
|
+
self.refresh_provider_status()
|
|
338
315
|
# Notify handled by auth screen
|
|
@@ -135,7 +135,7 @@ class ShotgunAuthScreen(Screen[bool]):
|
|
|
135
135
|
"""Start the authentication flow."""
|
|
136
136
|
try:
|
|
137
137
|
# Get shotgun instance ID from config
|
|
138
|
-
shotgun_instance_id =
|
|
138
|
+
shotgun_instance_id = self.config_manager.get_shotgun_instance_id()
|
|
139
139
|
logger.info("Starting auth flow with instance ID: %s", shotgun_instance_id)
|
|
140
140
|
|
|
141
141
|
# Update status
|
|
@@ -215,7 +215,7 @@ class ShotgunAuthScreen(Screen[bool]):
|
|
|
215
215
|
logger.info("Authentication completed successfully")
|
|
216
216
|
|
|
217
217
|
if status_response.litellm_key and status_response.supabase_key:
|
|
218
|
-
|
|
218
|
+
self.config_manager.update_shotgun_account(
|
|
219
219
|
api_key=status_response.litellm_key,
|
|
220
220
|
supabase_jwt=status_response.supabase_key,
|
|
221
221
|
)
|
shotgun/tui/screens/welcome.py
CHANGED
|
@@ -136,17 +136,14 @@ class WelcomeScreen(Screen[None]):
|
|
|
136
136
|
|
|
137
137
|
def on_mount(self) -> None:
|
|
138
138
|
"""Focus the first button on mount."""
|
|
139
|
-
|
|
140
|
-
# Update BYOK button text asynchronously
|
|
141
|
-
self.run_worker(self._update_byok_button_text(), exclusive=False)
|
|
142
|
-
|
|
143
|
-
async def _update_byok_button_text(self) -> None:
|
|
144
|
-
"""Update BYOK button text based on whether user has existing providers."""
|
|
139
|
+
# Update BYOK button text based on whether user has existing providers
|
|
145
140
|
byok_button = self.query_one("#byok-button", Button)
|
|
146
141
|
app = cast("ShotgunApp", self.app)
|
|
147
|
-
if
|
|
142
|
+
if app.config_manager.has_any_provider_key():
|
|
148
143
|
byok_button.label = "I'll stick with my BYOK setup"
|
|
149
144
|
|
|
145
|
+
self.query_one("#shotgun-button", Button).focus()
|
|
146
|
+
|
|
150
147
|
@on(Button.Pressed, "#shotgun-button")
|
|
151
148
|
def _on_shotgun_pressed(self) -> None:
|
|
152
149
|
"""Handle Shotgun Account button press."""
|
|
@@ -159,12 +156,12 @@ class WelcomeScreen(Screen[None]):
|
|
|
159
156
|
|
|
160
157
|
async def _start_byok_config(self) -> None:
|
|
161
158
|
"""Launch BYOK provider configuration flow."""
|
|
162
|
-
|
|
159
|
+
self._mark_welcome_shown()
|
|
163
160
|
|
|
164
161
|
app = cast("ShotgunApp", self.app)
|
|
165
162
|
|
|
166
163
|
# If user already has providers, just dismiss and continue to chat
|
|
167
|
-
if
|
|
164
|
+
if app.config_manager.has_any_provider_key():
|
|
168
165
|
self.dismiss()
|
|
169
166
|
return
|
|
170
167
|
|
|
@@ -174,7 +171,7 @@ class WelcomeScreen(Screen[None]):
|
|
|
174
171
|
await self.app.push_screen_wait(ProviderConfigScreen())
|
|
175
172
|
|
|
176
173
|
# Dismiss welcome screen after config if providers are now configured
|
|
177
|
-
if
|
|
174
|
+
if app.config_manager.has_any_provider_key():
|
|
178
175
|
self.dismiss()
|
|
179
176
|
|
|
180
177
|
async def _start_shotgun_auth(self) -> None:
|
|
@@ -182,7 +179,7 @@ class WelcomeScreen(Screen[None]):
|
|
|
182
179
|
from .shotgun_auth import ShotgunAuthScreen
|
|
183
180
|
|
|
184
181
|
# Mark welcome screen as shown before auth
|
|
185
|
-
|
|
182
|
+
self._mark_welcome_shown()
|
|
186
183
|
|
|
187
184
|
# Push the auth screen and wait for result
|
|
188
185
|
await self.app.push_screen_wait(ShotgunAuthScreen())
|
|
@@ -190,9 +187,9 @@ class WelcomeScreen(Screen[None]):
|
|
|
190
187
|
# Dismiss welcome screen after auth
|
|
191
188
|
self.dismiss()
|
|
192
189
|
|
|
193
|
-
|
|
190
|
+
def _mark_welcome_shown(self) -> None:
|
|
194
191
|
"""Mark the welcome screen as shown in config."""
|
|
195
192
|
app = cast("ShotgunApp", self.app)
|
|
196
|
-
config =
|
|
193
|
+
config = app.config_manager.load()
|
|
197
194
|
config.shown_welcome_screen = True
|
|
198
|
-
|
|
195
|
+
app.config_manager.save(config)
|
|
@@ -8,8 +8,6 @@ import logging
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import TYPE_CHECKING
|
|
10
10
|
|
|
11
|
-
import aiofiles.os
|
|
12
|
-
|
|
13
11
|
from shotgun.agents.conversation_history import ConversationHistory, ConversationState
|
|
14
12
|
from shotgun.agents.conversation_manager import ConversationManager
|
|
15
13
|
from shotgun.agents.models import AgentType
|
|
@@ -50,7 +48,7 @@ class ConversationService:
|
|
|
50
48
|
else:
|
|
51
49
|
self.conversation_manager = ConversationManager()
|
|
52
50
|
|
|
53
|
-
|
|
51
|
+
def save_conversation(self, agent_manager: "AgentManager") -> bool:
|
|
54
52
|
"""Save the current conversation to persistent storage.
|
|
55
53
|
|
|
56
54
|
Args:
|
|
@@ -70,15 +68,15 @@ class ConversationService:
|
|
|
70
68
|
conversation.set_agent_messages(state.agent_messages)
|
|
71
69
|
conversation.set_ui_messages(state.ui_messages)
|
|
72
70
|
|
|
73
|
-
# Save to file
|
|
74
|
-
|
|
71
|
+
# Save to file
|
|
72
|
+
self.conversation_manager.save(conversation)
|
|
75
73
|
logger.debug("Conversation saved successfully")
|
|
76
74
|
return True
|
|
77
75
|
except Exception as e:
|
|
78
76
|
logger.exception(f"Failed to save conversation: {e}")
|
|
79
77
|
return False
|
|
80
78
|
|
|
81
|
-
|
|
79
|
+
def load_conversation(self) -> ConversationHistory | None:
|
|
82
80
|
"""Load conversation from persistent storage.
|
|
83
81
|
|
|
84
82
|
Returns:
|
|
@@ -86,7 +84,7 @@ class ConversationService:
|
|
|
86
84
|
or if loading failed.
|
|
87
85
|
"""
|
|
88
86
|
try:
|
|
89
|
-
conversation =
|
|
87
|
+
conversation = self.conversation_manager.load()
|
|
90
88
|
if conversation is None:
|
|
91
89
|
logger.debug("No conversation file found")
|
|
92
90
|
return None
|
|
@@ -97,7 +95,7 @@ class ConversationService:
|
|
|
97
95
|
logger.exception(f"Failed to load conversation: {e}")
|
|
98
96
|
return None
|
|
99
97
|
|
|
100
|
-
|
|
98
|
+
def check_for_corrupted_conversation(self) -> bool:
|
|
101
99
|
"""Check if a conversation backup exists (indicating corruption).
|
|
102
100
|
|
|
103
101
|
Returns:
|
|
@@ -106,9 +104,9 @@ class ConversationService:
|
|
|
106
104
|
backup_path = self.conversation_manager.conversation_path.with_suffix(
|
|
107
105
|
".json.backup"
|
|
108
106
|
)
|
|
109
|
-
return
|
|
107
|
+
return backup_path.exists()
|
|
110
108
|
|
|
111
|
-
|
|
109
|
+
def restore_conversation(
|
|
112
110
|
self,
|
|
113
111
|
agent_manager: "AgentManager",
|
|
114
112
|
usage_manager: "SessionUsageManager | None" = None,
|
|
@@ -125,11 +123,11 @@ class ConversationService:
|
|
|
125
123
|
- error_message: Error message if restoration failed, None otherwise
|
|
126
124
|
- restored_agent_type: The agent type from restored conversation
|
|
127
125
|
"""
|
|
128
|
-
conversation =
|
|
126
|
+
conversation = self.load_conversation()
|
|
129
127
|
|
|
130
128
|
if conversation is None:
|
|
131
129
|
# Check for corruption
|
|
132
|
-
if
|
|
130
|
+
if self.check_for_corrupted_conversation():
|
|
133
131
|
return (
|
|
134
132
|
False,
|
|
135
133
|
"⚠️ Previous session was corrupted and has been backed up. Starting fresh conversation.",
|
|
@@ -153,7 +151,7 @@ class ConversationService:
|
|
|
153
151
|
|
|
154
152
|
# Restore usage state if manager provided
|
|
155
153
|
if usage_manager:
|
|
156
|
-
|
|
154
|
+
usage_manager.restore_usage_state()
|
|
157
155
|
|
|
158
156
|
restored_type = AgentType(conversation.last_agent_model)
|
|
159
157
|
logger.info(f"Conversation restored successfully (mode: {restored_type})")
|
|
@@ -167,7 +165,7 @@ class ConversationService:
|
|
|
167
165
|
None,
|
|
168
166
|
)
|
|
169
167
|
|
|
170
|
-
|
|
168
|
+
def clear_conversation(self) -> bool:
|
|
171
169
|
"""Clear the saved conversation file.
|
|
172
170
|
|
|
173
171
|
Returns:
|
|
@@ -175,8 +173,8 @@ class ConversationService:
|
|
|
175
173
|
"""
|
|
176
174
|
try:
|
|
177
175
|
conversation_path = self.conversation_manager.conversation_path
|
|
178
|
-
if
|
|
179
|
-
|
|
176
|
+
if conversation_path.exists():
|
|
177
|
+
conversation_path.unlink()
|
|
180
178
|
logger.info("Conversation file cleared")
|
|
181
179
|
return True
|
|
182
180
|
except Exception as e:
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
import random
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
import aiofiles
|
|
7
|
-
|
|
8
6
|
from shotgun.agents.models import AgentType
|
|
9
7
|
from shotgun.utils.file_system_utils import get_shotgun_base_path
|
|
10
8
|
|
|
@@ -32,7 +30,7 @@ class ModeProgressChecker:
|
|
|
32
30
|
"""
|
|
33
31
|
self.base_path = base_path or get_shotgun_base_path()
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
def has_mode_content(self, mode: AgentType) -> bool:
|
|
36
34
|
"""Check if a mode has meaningful content.
|
|
37
35
|
|
|
38
36
|
Args:
|
|
@@ -54,8 +52,7 @@ class ModeProgressChecker:
|
|
|
54
52
|
for item in export_path.glob("*"):
|
|
55
53
|
if item.is_file() and not item.name.startswith("."):
|
|
56
54
|
try:
|
|
57
|
-
|
|
58
|
-
content = await f.read()
|
|
55
|
+
content = item.read_text(encoding="utf-8")
|
|
59
56
|
if len(content.strip()) > self.MIN_CONTENT_SIZE:
|
|
60
57
|
return True
|
|
61
58
|
except (OSError, UnicodeDecodeError):
|
|
@@ -68,16 +65,13 @@ class ModeProgressChecker:
|
|
|
68
65
|
return False
|
|
69
66
|
|
|
70
67
|
try:
|
|
71
|
-
|
|
72
|
-
content = await f.read()
|
|
68
|
+
content = file_path.read_text(encoding="utf-8")
|
|
73
69
|
# Check if file has meaningful content
|
|
74
70
|
return len(content.strip()) > self.MIN_CONTENT_SIZE
|
|
75
71
|
except (OSError, UnicodeDecodeError):
|
|
76
72
|
return False
|
|
77
73
|
|
|
78
|
-
|
|
79
|
-
self, current_mode: AgentType
|
|
80
|
-
) -> AgentType | None:
|
|
74
|
+
def get_next_suggested_mode(self, current_mode: AgentType) -> AgentType | None:
|
|
81
75
|
"""Get the next suggested mode based on current progress.
|
|
82
76
|
|
|
83
77
|
Args:
|
|
@@ -100,7 +94,7 @@ class ModeProgressChecker:
|
|
|
100
94
|
return None
|
|
101
95
|
|
|
102
96
|
# Check if current mode has content
|
|
103
|
-
if not
|
|
97
|
+
if not self.has_mode_content(current_mode):
|
|
104
98
|
# Current mode is empty, no suggestion for next mode
|
|
105
99
|
return None
|
|
106
100
|
|
|
@@ -228,9 +222,8 @@ class PlaceholderHints:
|
|
|
228
222
|
if current_mode not in self.HINTS:
|
|
229
223
|
return f"Enter your {current_mode.value} mode prompt (SHIFT+TAB to switch modes)"
|
|
230
224
|
|
|
231
|
-
#
|
|
232
|
-
|
|
233
|
-
has_content = False
|
|
225
|
+
# Determine if mode has content
|
|
226
|
+
has_content = self.progress_checker.has_mode_content(current_mode)
|
|
234
227
|
|
|
235
228
|
# Get hint variations for this mode and state
|
|
236
229
|
hints_list = self.HINTS[current_mode][has_content]
|
|
@@ -245,18 +245,3 @@ class WidgetCoordinator:
|
|
|
245
245
|
spinner.text = text
|
|
246
246
|
except Exception as e:
|
|
247
247
|
logger.exception(f"Failed to update spinner text: {e}")
|
|
248
|
-
|
|
249
|
-
def set_context_streaming(self, streaming: bool) -> None:
|
|
250
|
-
"""Enable or disable context indicator streaming animation.
|
|
251
|
-
|
|
252
|
-
Args:
|
|
253
|
-
streaming: Whether to show streaming animation.
|
|
254
|
-
"""
|
|
255
|
-
if not self.screen.is_mounted:
|
|
256
|
-
return
|
|
257
|
-
|
|
258
|
-
try:
|
|
259
|
-
context_indicator = self.screen.query_one(ContextIndicator)
|
|
260
|
-
context_indicator.set_streaming(streaming)
|
|
261
|
-
except Exception as e:
|
|
262
|
-
logger.exception(f"Failed to set context streaming: {e}")
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
-
import aiofiles
|
|
6
|
-
|
|
7
5
|
from shotgun.settings import settings
|
|
8
6
|
|
|
9
7
|
|
|
@@ -37,20 +35,3 @@ def ensure_shotgun_directory_exists() -> Path:
|
|
|
37
35
|
shotgun_dir.mkdir(exist_ok=True)
|
|
38
36
|
# Note: Removed logger to avoid circular dependency with logging_config
|
|
39
37
|
return shotgun_dir
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
async def async_copy_file(src: Path, dst: Path) -> None:
|
|
43
|
-
"""Asynchronously copy a file from src to dst.
|
|
44
|
-
|
|
45
|
-
Args:
|
|
46
|
-
src: Source file path
|
|
47
|
-
dst: Destination file path
|
|
48
|
-
|
|
49
|
-
Raises:
|
|
50
|
-
FileNotFoundError: If source file doesn't exist
|
|
51
|
-
OSError: If copy operation fails
|
|
52
|
-
"""
|
|
53
|
-
async with aiofiles.open(src, "rb") as src_file:
|
|
54
|
-
content = await src_file.read()
|
|
55
|
-
async with aiofiles.open(dst, "wb") as dst_file:
|
|
56
|
-
await dst_file.write(content)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shotgun-sh
|
|
3
|
-
Version: 0.2.11
|
|
3
|
+
Version: 0.2.11.dev2
|
|
4
4
|
Summary: AI-powered research, planning, and task management CLI tool
|
|
5
5
|
Project-URL: Homepage, https://shotgun.sh/
|
|
6
6
|
Project-URL: Repository, https://github.com/shotgun-sh/shotgun
|
|
@@ -21,7 +21,6 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
22
|
Classifier: Topic :: Utilities
|
|
23
23
|
Requires-Python: >=3.11
|
|
24
|
-
Requires-Dist: aiofiles>=24.0.0
|
|
25
24
|
Requires-Dist: anthropic>=0.39.0
|
|
26
25
|
Requires-Dist: dependency-injector>=4.41.0
|
|
27
26
|
Requires-Dist: genai-prices>=0.0.27
|