shotgun-sh 0.1.14__py3-none-any.whl → 0.2.11__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 +715 -75
- shotgun/agents/common.py +80 -75
- shotgun/agents/config/constants.py +21 -10
- shotgun/agents/config/manager.py +322 -97
- shotgun/agents/config/models.py +114 -84
- shotgun/agents/config/provider.py +232 -88
- shotgun/agents/context_analyzer/__init__.py +28 -0
- shotgun/agents/context_analyzer/analyzer.py +471 -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_history.py +125 -2
- shotgun/agents/conversation_manager.py +57 -19
- shotgun/agents/export.py +6 -7
- shotgun/agents/history/compaction.py +10 -5
- shotgun/agents/history/context_extraction.py +93 -6
- shotgun/agents/history/history_processors.py +129 -12
- shotgun/agents/history/token_counting/__init__.py +31 -0
- shotgun/agents/history/token_counting/anthropic.py +127 -0
- shotgun/agents/history/token_counting/base.py +78 -0
- shotgun/agents/history/token_counting/openai.py +90 -0
- shotgun/agents/history/token_counting/sentencepiece_counter.py +127 -0
- shotgun/agents/history/token_counting/tokenizer_cache.py +92 -0
- shotgun/agents/history/token_counting/utils.py +144 -0
- shotgun/agents/history/token_estimation.py +12 -12
- shotgun/agents/llm.py +62 -0
- shotgun/agents/models.py +59 -4
- shotgun/agents/plan.py +6 -7
- shotgun/agents/research.py +7 -8
- shotgun/agents/specify.py +6 -7
- shotgun/agents/tasks.py +6 -7
- shotgun/agents/tools/__init__.py +0 -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 +82 -16
- shotgun/agents/tools/registry.py +217 -0
- shotgun/agents/tools/web_search/__init__.py +55 -16
- shotgun/agents/tools/web_search/anthropic.py +76 -51
- shotgun/agents/tools/web_search/gemini.py +50 -27
- shotgun/agents/tools/web_search/openai.py +26 -17
- shotgun/agents/tools/web_search/utils.py +2 -2
- shotgun/agents/usage_manager.py +164 -0
- shotgun/api_endpoints.py +15 -0
- shotgun/cli/clear.py +53 -0
- shotgun/cli/compact.py +186 -0
- shotgun/cli/config.py +41 -67
- shotgun/cli/context.py +111 -0
- shotgun/cli/export.py +1 -1
- shotgun/cli/feedback.py +50 -0
- shotgun/cli/models.py +3 -2
- 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/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 +57 -16
- shotgun/codebase/core/manager.py +20 -7
- shotgun/codebase/core/nl_query.py +1 -1
- shotgun/codebase/models.py +4 -4
- shotgun/exceptions.py +32 -0
- shotgun/llm_proxy/__init__.py +19 -0
- shotgun/llm_proxy/clients.py +44 -0
- shotgun/llm_proxy/constants.py +15 -0
- shotgun/logging_config.py +18 -27
- shotgun/main.py +91 -12
- shotgun/posthog_telemetry.py +81 -10
- shotgun/prompts/agents/export.j2 +18 -1
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +5 -1
- shotgun/prompts/agents/partials/interactive_mode.j2 +24 -7
- shotgun/prompts/agents/plan.j2 +1 -1
- shotgun/prompts/agents/research.j2 +1 -1
- shotgun/prompts/agents/specify.j2 +270 -3
- shotgun/prompts/agents/state/system_state.j2 +4 -0
- shotgun/prompts/agents/tasks.j2 +1 -1
- shotgun/prompts/loader.py +2 -2
- shotgun/prompts/tools/web_search.j2 +14 -0
- shotgun/sentry_telemetry.py +27 -18
- shotgun/settings.py +238 -0
- shotgun/shotgun_web/__init__.py +19 -0
- shotgun/shotgun_web/client.py +138 -0
- shotgun/shotgun_web/constants.py +21 -0
- shotgun/shotgun_web/models.py +47 -0
- shotgun/telemetry.py +24 -36
- shotgun/tui/app.py +251 -23
- 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/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 +1234 -0
- shotgun/tui/screens/chat/codebase_index_prompt_screen.py +64 -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 +226 -11
- 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 +116 -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 +151 -0
- shotgun/tui/screens/feedback.py +193 -0
- shotgun/tui/screens/github_issue.py +102 -0
- shotgun/tui/screens/model_picker.py +352 -0
- shotgun/tui/screens/onboarding.py +431 -0
- shotgun/tui/screens/pipx_migration.py +153 -0
- shotgun/tui/screens/provider_config.py +156 -39
- shotgun/tui/screens/shotgun_auth.py +295 -0
- shotgun/tui/screens/welcome.py +198 -0
- shotgun/tui/services/__init__.py +5 -0
- shotgun/tui/services/conversation_service.py +184 -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 +262 -0
- shotgun/utils/datetime_utils.py +77 -0
- shotgun/utils/env_utils.py +13 -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.2.11.dist-info/METADATA +130 -0
- shotgun_sh-0.2.11.dist-info/RECORD +194 -0
- {shotgun_sh-0.1.14.dist-info → shotgun_sh-0.2.11.dist-info}/entry_points.txt +1 -0
- {shotgun_sh-0.1.14.dist-info → shotgun_sh-0.2.11.dist-info}/licenses/LICENSE +1 -1
- shotgun/agents/history/token_counting.py +0 -429
- shotgun/agents/tools/user_interaction.py +0 -37
- shotgun/tui/screens/chat.py +0 -797
- shotgun/tui/screens/chat_screen/history.py +0 -350
- shotgun_sh-0.1.14.dist-info/METADATA +0 -466
- shotgun_sh-0.1.14.dist-info/RECORD +0 -133
- {shotgun_sh-0.1.14.dist-info → shotgun_sh-0.2.11.dist-info}/WHEEL +0 -0
shotgun/cli/config.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Configuration management CLI commands."""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import json
|
|
4
5
|
from typing import Annotated, Any
|
|
5
6
|
|
|
@@ -9,6 +10,7 @@ from rich.table import Table
|
|
|
9
10
|
|
|
10
11
|
from shotgun.agents.config import ProviderType, get_config_manager
|
|
11
12
|
from shotgun.logging_config import get_logger
|
|
13
|
+
from shotgun.utils.env_utils import is_shotgun_account_enabled
|
|
12
14
|
|
|
13
15
|
logger = get_logger(__name__)
|
|
14
16
|
console = Console()
|
|
@@ -43,11 +45,11 @@ def init(
|
|
|
43
45
|
console.print()
|
|
44
46
|
|
|
45
47
|
# Initialize with defaults
|
|
46
|
-
|
|
48
|
+
asyncio.run(config_manager.initialize())
|
|
47
49
|
|
|
48
|
-
# Ask for
|
|
50
|
+
# Ask for provider
|
|
49
51
|
provider_choices = ["openai", "anthropic", "google"]
|
|
50
|
-
console.print("Choose your
|
|
52
|
+
console.print("Choose your AI provider:")
|
|
51
53
|
for i, provider in enumerate(provider_choices, 1):
|
|
52
54
|
console.print(f" {i}. {provider}")
|
|
53
55
|
|
|
@@ -55,7 +57,7 @@ def init(
|
|
|
55
57
|
try:
|
|
56
58
|
choice = typer.prompt("Enter choice (1-3)", type=int)
|
|
57
59
|
if 1 <= choice <= 3:
|
|
58
|
-
|
|
60
|
+
provider = ProviderType(provider_choices[choice - 1])
|
|
59
61
|
break
|
|
60
62
|
else:
|
|
61
63
|
console.print(
|
|
@@ -65,7 +67,6 @@ def init(
|
|
|
65
67
|
console.print("❌ Please enter a valid number.", style="red")
|
|
66
68
|
|
|
67
69
|
# Ask for API key for the selected provider
|
|
68
|
-
provider = config.default_provider
|
|
69
70
|
console.print(f"\n🔑 Setting up {provider.upper()} API key...")
|
|
70
71
|
|
|
71
72
|
api_key = typer.prompt(
|
|
@@ -75,16 +76,16 @@ def init(
|
|
|
75
76
|
)
|
|
76
77
|
|
|
77
78
|
if api_key:
|
|
78
|
-
|
|
79
|
+
# update_provider will automatically set selected_model for first provider
|
|
80
|
+
asyncio.run(config_manager.update_provider(provider, api_key=api_key))
|
|
79
81
|
|
|
80
|
-
config_manager.save()
|
|
81
82
|
console.print(
|
|
82
83
|
f"\n✅ [bold green]Configuration saved to {config_manager.config_path}[/bold green]"
|
|
83
84
|
)
|
|
84
85
|
console.print("🎯 You can now use Shotgun with your configured provider!")
|
|
85
86
|
|
|
86
87
|
else:
|
|
87
|
-
config_manager.initialize()
|
|
88
|
+
asyncio.run(config_manager.initialize())
|
|
88
89
|
console.print(f"✅ Configuration initialized at {config_manager.config_path}")
|
|
89
90
|
|
|
90
91
|
|
|
@@ -98,16 +99,12 @@ def set(
|
|
|
98
99
|
str | None,
|
|
99
100
|
typer.Option("--api-key", "-k", help="API key for the provider"),
|
|
100
101
|
] = None,
|
|
101
|
-
default: Annotated[
|
|
102
|
-
bool,
|
|
103
|
-
typer.Option("--default", "-d", help="Set this provider as default"),
|
|
104
|
-
] = False,
|
|
105
102
|
) -> None:
|
|
106
103
|
"""Set configuration for a specific provider."""
|
|
107
104
|
config_manager = get_config_manager()
|
|
108
105
|
|
|
109
|
-
# If no API key provided via option
|
|
110
|
-
if api_key is None
|
|
106
|
+
# If no API key provided via option, prompt for it
|
|
107
|
+
if api_key is None:
|
|
111
108
|
api_key = typer.prompt(
|
|
112
109
|
f"Enter your {provider.upper()} API key",
|
|
113
110
|
hide_input=True,
|
|
@@ -116,12 +113,7 @@ def set(
|
|
|
116
113
|
|
|
117
114
|
try:
|
|
118
115
|
if api_key:
|
|
119
|
-
config_manager.update_provider(provider, api_key=api_key)
|
|
120
|
-
|
|
121
|
-
if default:
|
|
122
|
-
config = config_manager.load()
|
|
123
|
-
config.default_provider = provider
|
|
124
|
-
config_manager.save(config)
|
|
116
|
+
asyncio.run(config_manager.update_provider(provider, api_key=api_key))
|
|
125
117
|
|
|
126
118
|
console.print(f"✅ Configuration updated for {provider}")
|
|
127
119
|
|
|
@@ -130,41 +122,6 @@ def set(
|
|
|
130
122
|
raise typer.Exit(1) from e
|
|
131
123
|
|
|
132
124
|
|
|
133
|
-
@app.command()
|
|
134
|
-
def set_default(
|
|
135
|
-
provider: Annotated[
|
|
136
|
-
ProviderType,
|
|
137
|
-
typer.Argument(
|
|
138
|
-
help="AI provider to set as default (openai, anthropic, google)"
|
|
139
|
-
),
|
|
140
|
-
],
|
|
141
|
-
) -> None:
|
|
142
|
-
"""Set the default AI provider without modifying API keys."""
|
|
143
|
-
config_manager = get_config_manager()
|
|
144
|
-
|
|
145
|
-
try:
|
|
146
|
-
config = config_manager.load()
|
|
147
|
-
|
|
148
|
-
# Check if the provider has an API key configured
|
|
149
|
-
provider_config = getattr(config, provider.value)
|
|
150
|
-
if not provider_config.api_key:
|
|
151
|
-
console.print(
|
|
152
|
-
f"⚠️ Warning: {provider.upper()} does not have an API key configured.",
|
|
153
|
-
style="yellow",
|
|
154
|
-
)
|
|
155
|
-
console.print(f"Use 'shotgun config set {provider}' to configure it.")
|
|
156
|
-
|
|
157
|
-
# Set as default
|
|
158
|
-
config.default_provider = provider
|
|
159
|
-
config_manager.save(config)
|
|
160
|
-
|
|
161
|
-
console.print(f"✅ Default provider set to: {provider}")
|
|
162
|
-
|
|
163
|
-
except Exception as e:
|
|
164
|
-
console.print(f"❌ Failed to set default provider: {e}", style="red")
|
|
165
|
-
raise typer.Exit(1) from e
|
|
166
|
-
|
|
167
|
-
|
|
168
125
|
@app.command()
|
|
169
126
|
def get(
|
|
170
127
|
provider: Annotated[
|
|
@@ -177,8 +134,10 @@ def get(
|
|
|
177
134
|
] = False,
|
|
178
135
|
) -> None:
|
|
179
136
|
"""Display current configuration."""
|
|
137
|
+
import asyncio
|
|
138
|
+
|
|
180
139
|
config_manager = get_config_manager()
|
|
181
|
-
config = config_manager.load()
|
|
140
|
+
config = asyncio.run(config_manager.load())
|
|
182
141
|
|
|
183
142
|
if json_output:
|
|
184
143
|
# Convert to dict and mask secrets
|
|
@@ -201,16 +160,23 @@ def _show_full_config(config: Any) -> None:
|
|
|
201
160
|
table.add_column("Setting", style="cyan")
|
|
202
161
|
table.add_column("Value", style="white")
|
|
203
162
|
|
|
204
|
-
#
|
|
205
|
-
|
|
163
|
+
# Selected model
|
|
164
|
+
selected_model = config.selected_model or "None (will auto-detect)"
|
|
165
|
+
table.add_row("Selected Model", f"[bold]{selected_model}[/bold]")
|
|
206
166
|
table.add_row("", "") # Separator
|
|
207
167
|
|
|
208
168
|
# Provider configurations
|
|
209
|
-
|
|
169
|
+
providers_to_show = [
|
|
210
170
|
("OpenAI", config.openai),
|
|
211
171
|
("Anthropic", config.anthropic),
|
|
212
172
|
("Google", config.google),
|
|
213
|
-
]
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
# Only show Shotgun Account if feature flag is enabled
|
|
176
|
+
if is_shotgun_account_enabled():
|
|
177
|
+
providers_to_show.append(("Shotgun Account", config.shotgun))
|
|
178
|
+
|
|
179
|
+
for provider_name, provider_config in providers_to_show:
|
|
214
180
|
table.add_row(f"[bold]{provider_name}[/bold]", "")
|
|
215
181
|
|
|
216
182
|
# API Key
|
|
@@ -231,6 +197,8 @@ def _show_provider_config(provider: ProviderType, config: Any) -> None:
|
|
|
231
197
|
provider_config = config.anthropic
|
|
232
198
|
elif provider_str == "google":
|
|
233
199
|
provider_config = config.google
|
|
200
|
+
elif provider_str == "shotgun":
|
|
201
|
+
provider_config = config.shotgun
|
|
234
202
|
else:
|
|
235
203
|
console.print(f"❌ Unknown provider: {provider}", style="red")
|
|
236
204
|
return
|
|
@@ -248,7 +216,13 @@ def _show_provider_config(provider: ProviderType, config: Any) -> None:
|
|
|
248
216
|
|
|
249
217
|
def _mask_secrets(data: dict[str, Any]) -> None:
|
|
250
218
|
"""Mask secrets in configuration data."""
|
|
251
|
-
|
|
219
|
+
providers = ["openai", "anthropic", "google"]
|
|
220
|
+
|
|
221
|
+
# Only mask shotgun if feature flag is enabled
|
|
222
|
+
if is_shotgun_account_enabled():
|
|
223
|
+
providers.append("shotgun")
|
|
224
|
+
|
|
225
|
+
for provider in providers:
|
|
252
226
|
if provider in data and isinstance(data[provider], dict):
|
|
253
227
|
if "api_key" in data[provider] and data[provider]["api_key"]:
|
|
254
228
|
data[provider]["api_key"] = _mask_value(data[provider]["api_key"])
|
|
@@ -262,14 +236,14 @@ def _mask_value(value: str) -> str:
|
|
|
262
236
|
|
|
263
237
|
|
|
264
238
|
@app.command()
|
|
265
|
-
def
|
|
266
|
-
"""Get the anonymous
|
|
239
|
+
def get_shotgun_instance_id() -> None:
|
|
240
|
+
"""Get the anonymous shotgun instance ID from configuration."""
|
|
267
241
|
config_manager = get_config_manager()
|
|
268
242
|
|
|
269
243
|
try:
|
|
270
|
-
|
|
271
|
-
console.print(f"[green]
|
|
244
|
+
shotgun_instance_id = config_manager.get_shotgun_instance_id()
|
|
245
|
+
console.print(f"[green]Shotgun Instance ID:[/green] {shotgun_instance_id}")
|
|
272
246
|
except Exception as e:
|
|
273
|
-
logger.error(f"Error getting
|
|
274
|
-
console.print(f"❌ Failed to get
|
|
247
|
+
logger.error(f"Error getting shotgun instance ID: {e}")
|
|
248
|
+
console.print(f"❌ Failed to get shotgun instance ID: {str(e)}", style="red")
|
|
275
249
|
raise typer.Exit(1) from e
|
shotgun/cli/context.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Context command for shotgun CLI."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
from shotgun.agents.config import get_provider_model
|
|
12
|
+
from shotgun.agents.context_analyzer import (
|
|
13
|
+
ContextAnalysisOutput,
|
|
14
|
+
ContextAnalyzer,
|
|
15
|
+
ContextFormatter,
|
|
16
|
+
)
|
|
17
|
+
from shotgun.agents.conversation_manager import ConversationManager
|
|
18
|
+
from shotgun.cli.models import OutputFormat
|
|
19
|
+
from shotgun.logging_config import get_logger
|
|
20
|
+
|
|
21
|
+
app = typer.Typer(
|
|
22
|
+
name="context", help="Analyze conversation context usage", no_args_is_help=False
|
|
23
|
+
)
|
|
24
|
+
logger = get_logger(__name__)
|
|
25
|
+
console = Console()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@app.callback(invoke_without_command=True)
|
|
29
|
+
def context(
|
|
30
|
+
format: Annotated[
|
|
31
|
+
OutputFormat,
|
|
32
|
+
typer.Option(
|
|
33
|
+
"--format",
|
|
34
|
+
"-f",
|
|
35
|
+
help="Output format: markdown or json",
|
|
36
|
+
),
|
|
37
|
+
] = OutputFormat.MARKDOWN,
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Analyze the current conversation's context usage.
|
|
40
|
+
|
|
41
|
+
This command analyzes the agent's message history from ~/.shotgun-sh/conversation.json
|
|
42
|
+
and displays token usage breakdown by message type. Only agent context is counted
|
|
43
|
+
(UI elements like hints are excluded).
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
result = asyncio.run(analyze_context())
|
|
47
|
+
|
|
48
|
+
if format == OutputFormat.JSON:
|
|
49
|
+
# Output as JSON
|
|
50
|
+
console.print_json(json.dumps(result.json_data, indent=2))
|
|
51
|
+
else:
|
|
52
|
+
# Output as plain text (Markdown() reformats and makes categories inline)
|
|
53
|
+
console.print(result.markdown)
|
|
54
|
+
|
|
55
|
+
except FileNotFoundError as e:
|
|
56
|
+
console.print(
|
|
57
|
+
f"[red]Error:[/red] {e}\n\n"
|
|
58
|
+
"No conversation found. Start a TUI session first with: [cyan]shotgun[/cyan]",
|
|
59
|
+
style="bold",
|
|
60
|
+
)
|
|
61
|
+
raise typer.Exit(code=1) from e
|
|
62
|
+
except Exception as e:
|
|
63
|
+
console.print(f"[red]Error:[/red] Failed to analyze context: {e}", style="bold")
|
|
64
|
+
logger.debug("Full traceback:", exc_info=True)
|
|
65
|
+
raise typer.Exit(code=1) from e
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def analyze_context() -> ContextAnalysisOutput:
|
|
69
|
+
"""Analyze the conversation context and return structured data.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
ContextAnalysisOutput with both markdown and JSON representations of the analysis
|
|
73
|
+
"""
|
|
74
|
+
# Get conversation file path
|
|
75
|
+
conversation_file = Path.home() / ".shotgun-sh" / "conversation.json"
|
|
76
|
+
|
|
77
|
+
if not conversation_file.exists():
|
|
78
|
+
raise FileNotFoundError(f"Conversation file not found at {conversation_file}")
|
|
79
|
+
|
|
80
|
+
# Load conversation
|
|
81
|
+
manager = ConversationManager(conversation_file)
|
|
82
|
+
conversation = await manager.load()
|
|
83
|
+
|
|
84
|
+
if not conversation:
|
|
85
|
+
raise ValueError("Conversation file is empty or corrupted")
|
|
86
|
+
|
|
87
|
+
# Get agent messages only (not UI messages)
|
|
88
|
+
agent_messages = conversation.get_agent_messages()
|
|
89
|
+
|
|
90
|
+
if not agent_messages:
|
|
91
|
+
raise ValueError("No agent messages found in conversation")
|
|
92
|
+
|
|
93
|
+
# Get model config (use default provider settings)
|
|
94
|
+
model_config = await get_provider_model()
|
|
95
|
+
|
|
96
|
+
# Debug: Log the model being used
|
|
97
|
+
logger.debug(f"Using model: {model_config.name.value}")
|
|
98
|
+
logger.debug(f"Provider: {model_config.provider.value}")
|
|
99
|
+
logger.debug(f"Key provider: {model_config.key_provider.value}")
|
|
100
|
+
logger.debug(f"Max input tokens: {model_config.max_input_tokens}")
|
|
101
|
+
|
|
102
|
+
# Analyze with ContextAnalyzer
|
|
103
|
+
analyzer = ContextAnalyzer(model_config)
|
|
104
|
+
# For CLI, agent_messages and ui_message_history are the same (no hints in CLI mode)
|
|
105
|
+
analysis = await analyzer.analyze_conversation(agent_messages, list(agent_messages))
|
|
106
|
+
|
|
107
|
+
# Use formatter to generate markdown and JSON
|
|
108
|
+
markdown = ContextFormatter.format_markdown(analysis)
|
|
109
|
+
json_data = ContextFormatter.format_json(analysis)
|
|
110
|
+
|
|
111
|
+
return ContextAnalysisOutput(markdown=markdown, json_data=json_data)
|
shotgun/cli/export.py
CHANGED
|
@@ -63,7 +63,7 @@ def export(
|
|
|
63
63
|
)
|
|
64
64
|
|
|
65
65
|
# Create the export agent with deps and provider
|
|
66
|
-
agent, deps = create_export_agent(agent_runtime_options, provider)
|
|
66
|
+
agent, deps = asyncio.run(create_export_agent(agent_runtime_options, provider))
|
|
67
67
|
|
|
68
68
|
# Start export process
|
|
69
69
|
logger.info("🎯 Starting export...")
|
shotgun/cli/feedback.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Configuration management CLI commands."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from shotgun.agents.config import get_config_manager
|
|
9
|
+
from shotgun.logging_config import get_logger
|
|
10
|
+
from shotgun.posthog_telemetry import Feedback, FeedbackKind, submit_feedback_survey
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
app = typer.Typer(
|
|
16
|
+
name="feedback",
|
|
17
|
+
help="Send us feedback",
|
|
18
|
+
no_args_is_help=True,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@app.callback(invoke_without_command=True)
|
|
23
|
+
def send_feedback(
|
|
24
|
+
description: Annotated[str, typer.Argument(help="Description of the feedback")],
|
|
25
|
+
kind: Annotated[
|
|
26
|
+
FeedbackKind,
|
|
27
|
+
typer.Option("--type", "-t", help="Feedback type"),
|
|
28
|
+
],
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Initialize Shotgun configuration."""
|
|
31
|
+
import asyncio
|
|
32
|
+
|
|
33
|
+
config_manager = get_config_manager()
|
|
34
|
+
asyncio.run(config_manager.load())
|
|
35
|
+
shotgun_instance_id = asyncio.run(config_manager.get_shotgun_instance_id())
|
|
36
|
+
|
|
37
|
+
if not description:
|
|
38
|
+
console.print(
|
|
39
|
+
'❌ Please add your feedback (shotgun feedback "<your feedback>").',
|
|
40
|
+
style="red",
|
|
41
|
+
)
|
|
42
|
+
raise typer.Exit(1)
|
|
43
|
+
|
|
44
|
+
feedback = Feedback(
|
|
45
|
+
kind=kind, description=description, shotgun_instance_id=shotgun_instance_id
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
submit_feedback_survey(feedback)
|
|
49
|
+
|
|
50
|
+
console.print("Feedback sent. Thank you!")
|
shotgun/cli/models.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"""Common models for CLI commands."""
|
|
2
2
|
|
|
3
|
-
from enum import
|
|
3
|
+
from enum import StrEnum
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class OutputFormat(
|
|
6
|
+
class OutputFormat(StrEnum):
|
|
7
7
|
"""Output format options for CLI commands."""
|
|
8
8
|
|
|
9
9
|
TEXT = "text"
|
|
10
10
|
JSON = "json"
|
|
11
|
+
MARKDOWN = "markdown"
|
shotgun/cli/plan.py
CHANGED
|
@@ -55,7 +55,7 @@ def plan(
|
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
# Create the plan agent with deps and provider
|
|
58
|
-
agent, deps = create_plan_agent(agent_runtime_options, provider)
|
|
58
|
+
agent, deps = asyncio.run(create_plan_agent(agent_runtime_options, provider))
|
|
59
59
|
|
|
60
60
|
# Start planning process
|
|
61
61
|
logger.info("🎯 Starting planning...")
|
shotgun/cli/research.py
CHANGED
|
@@ -73,7 +73,7 @@ async def async_research(
|
|
|
73
73
|
agent_runtime_options = AgentRuntimeOptions(interactive_mode=not non_interactive)
|
|
74
74
|
|
|
75
75
|
# Create the research agent with deps and provider
|
|
76
|
-
agent, deps = create_research_agent(agent_runtime_options, provider)
|
|
76
|
+
agent, deps = await create_research_agent(agent_runtime_options, provider)
|
|
77
77
|
|
|
78
78
|
# Start research process
|
|
79
79
|
logger.info("🔬 Starting research...")
|
shotgun/cli/specify.py
CHANGED
|
@@ -51,7 +51,7 @@ def specify(
|
|
|
51
51
|
)
|
|
52
52
|
|
|
53
53
|
# Create the specify agent with deps and provider
|
|
54
|
-
agent, deps = create_specify_agent(agent_runtime_options, provider)
|
|
54
|
+
agent, deps = asyncio.run(create_specify_agent(agent_runtime_options, provider))
|
|
55
55
|
|
|
56
56
|
# Start specification process
|
|
57
57
|
logger.info("📋 Starting specification generation...")
|
shotgun/cli/tasks.py
CHANGED
|
@@ -60,7 +60,7 @@ def tasks(
|
|
|
60
60
|
)
|
|
61
61
|
|
|
62
62
|
# Create the tasks agent with deps and provider
|
|
63
|
-
agent, deps = create_tasks_agent(agent_runtime_options, provider)
|
|
63
|
+
agent, deps = asyncio.run(create_tasks_agent(agent_runtime_options, provider))
|
|
64
64
|
|
|
65
65
|
# Start task creation process
|
|
66
66
|
logger.info("🎯 Starting task creation...")
|
shotgun/cli/update.py
CHANGED
|
@@ -45,7 +45,7 @@ def update(
|
|
|
45
45
|
|
|
46
46
|
This command will:
|
|
47
47
|
- Check PyPI for the latest version
|
|
48
|
-
- Detect your installation method (pipx, pip, or venv)
|
|
48
|
+
- Detect your installation method (uvx, uv-tool, pipx, pip, or venv)
|
|
49
49
|
- Perform the appropriate upgrade command
|
|
50
50
|
|
|
51
51
|
Examples:
|
|
@@ -93,6 +93,8 @@ def update(
|
|
|
93
93
|
)
|
|
94
94
|
console.print(
|
|
95
95
|
"Use --force to update anyway, or install the stable version with:\n"
|
|
96
|
+
" uv tool install shotgun-sh\n"
|
|
97
|
+
" or\n"
|
|
96
98
|
" pipx install shotgun-sh\n"
|
|
97
99
|
" or\n"
|
|
98
100
|
" pip install shotgun-sh",
|
|
@@ -134,7 +136,19 @@ def update(
|
|
|
134
136
|
console.print(f"\n[red]✗[/red] {message}", style="bold red")
|
|
135
137
|
|
|
136
138
|
# Provide manual update instructions
|
|
137
|
-
if method == "
|
|
139
|
+
if method == "uvx":
|
|
140
|
+
console.print(
|
|
141
|
+
"\n[yellow]Run uvx again to use the latest version:[/yellow]\n"
|
|
142
|
+
" uvx shotgun-sh\n"
|
|
143
|
+
"\n[yellow]Or install permanently:[/yellow]\n"
|
|
144
|
+
" uv tool install shotgun-sh"
|
|
145
|
+
)
|
|
146
|
+
elif method == "uv-tool":
|
|
147
|
+
console.print(
|
|
148
|
+
"\n[yellow]Try updating manually:[/yellow]\n"
|
|
149
|
+
" uv tool upgrade shotgun-sh"
|
|
150
|
+
)
|
|
151
|
+
elif method == "pipx":
|
|
138
152
|
console.print(
|
|
139
153
|
"\n[yellow]Try updating manually:[/yellow]\n"
|
|
140
154
|
" pipx upgrade shotgun-sh"
|
|
@@ -6,6 +6,7 @@ from enum import Enum
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Any, cast
|
|
8
8
|
|
|
9
|
+
import aiofiles
|
|
9
10
|
import kuzu
|
|
10
11
|
|
|
11
12
|
from shotgun.logging_config import get_logger
|
|
@@ -301,7 +302,7 @@ class ChangeDetector:
|
|
|
301
302
|
# Direct substring match
|
|
302
303
|
return pattern in filepath
|
|
303
304
|
|
|
304
|
-
def _calculate_file_hash(self, filepath: Path) -> str:
|
|
305
|
+
async def _calculate_file_hash(self, filepath: Path) -> str:
|
|
305
306
|
"""Calculate hash of file contents.
|
|
306
307
|
|
|
307
308
|
Args:
|
|
@@ -311,8 +312,9 @@ class ChangeDetector:
|
|
|
311
312
|
SHA256 hash of file contents
|
|
312
313
|
"""
|
|
313
314
|
try:
|
|
314
|
-
with open(filepath, "rb") as f:
|
|
315
|
-
|
|
315
|
+
async with aiofiles.open(filepath, "rb") as f:
|
|
316
|
+
content = await f.read()
|
|
317
|
+
return hashlib.sha256(content).hexdigest()
|
|
316
318
|
except Exception as e:
|
|
317
319
|
logger.error(f"Failed to calculate hash for {filepath}: {e}")
|
|
318
320
|
return ""
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
|
+
import aiofiles
|
|
6
7
|
from pydantic import BaseModel
|
|
7
8
|
|
|
8
9
|
from shotgun.logging_config import get_logger
|
|
@@ -141,8 +142,9 @@ async def retrieve_code_by_qualified_name(
|
|
|
141
142
|
|
|
142
143
|
# Read the file and extract the snippet
|
|
143
144
|
try:
|
|
144
|
-
with
|
|
145
|
-
|
|
145
|
+
async with aiofiles.open(full_path, encoding="utf-8") as f:
|
|
146
|
+
content = await f.read()
|
|
147
|
+
all_lines = content.splitlines(keepends=True)
|
|
146
148
|
|
|
147
149
|
# Extract the relevant lines (1-indexed to 0-indexed)
|
|
148
150
|
snippet_lines = all_lines[start_line - 1 : end_line]
|