shotgun-sh 0.1.9__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.

Files changed (150) hide show
  1. shotgun/agents/agent_manager.py +761 -52
  2. shotgun/agents/common.py +80 -75
  3. shotgun/agents/config/constants.py +21 -10
  4. shotgun/agents/config/manager.py +322 -97
  5. shotgun/agents/config/models.py +114 -84
  6. shotgun/agents/config/provider.py +232 -88
  7. shotgun/agents/context_analyzer/__init__.py +28 -0
  8. shotgun/agents/context_analyzer/analyzer.py +471 -0
  9. shotgun/agents/context_analyzer/constants.py +9 -0
  10. shotgun/agents/context_analyzer/formatter.py +115 -0
  11. shotgun/agents/context_analyzer/models.py +212 -0
  12. shotgun/agents/conversation_history.py +125 -2
  13. shotgun/agents/conversation_manager.py +57 -19
  14. shotgun/agents/export.py +6 -7
  15. shotgun/agents/history/compaction.py +23 -3
  16. shotgun/agents/history/context_extraction.py +93 -6
  17. shotgun/agents/history/history_processors.py +179 -11
  18. shotgun/agents/history/token_counting/__init__.py +31 -0
  19. shotgun/agents/history/token_counting/anthropic.py +127 -0
  20. shotgun/agents/history/token_counting/base.py +78 -0
  21. shotgun/agents/history/token_counting/openai.py +90 -0
  22. shotgun/agents/history/token_counting/sentencepiece_counter.py +127 -0
  23. shotgun/agents/history/token_counting/tokenizer_cache.py +92 -0
  24. shotgun/agents/history/token_counting/utils.py +144 -0
  25. shotgun/agents/history/token_estimation.py +12 -12
  26. shotgun/agents/llm.py +62 -0
  27. shotgun/agents/models.py +59 -4
  28. shotgun/agents/plan.py +6 -7
  29. shotgun/agents/research.py +7 -8
  30. shotgun/agents/specify.py +6 -7
  31. shotgun/agents/tasks.py +6 -7
  32. shotgun/agents/tools/__init__.py +0 -2
  33. shotgun/agents/tools/codebase/codebase_shell.py +6 -0
  34. shotgun/agents/tools/codebase/directory_lister.py +6 -0
  35. shotgun/agents/tools/codebase/file_read.py +11 -2
  36. shotgun/agents/tools/codebase/query_graph.py +6 -0
  37. shotgun/agents/tools/codebase/retrieve_code.py +6 -0
  38. shotgun/agents/tools/file_management.py +82 -16
  39. shotgun/agents/tools/registry.py +217 -0
  40. shotgun/agents/tools/web_search/__init__.py +55 -16
  41. shotgun/agents/tools/web_search/anthropic.py +76 -51
  42. shotgun/agents/tools/web_search/gemini.py +50 -27
  43. shotgun/agents/tools/web_search/openai.py +26 -17
  44. shotgun/agents/tools/web_search/utils.py +2 -2
  45. shotgun/agents/usage_manager.py +164 -0
  46. shotgun/api_endpoints.py +15 -0
  47. shotgun/cli/clear.py +53 -0
  48. shotgun/cli/codebase/commands.py +71 -2
  49. shotgun/cli/compact.py +186 -0
  50. shotgun/cli/config.py +41 -67
  51. shotgun/cli/context.py +111 -0
  52. shotgun/cli/export.py +1 -1
  53. shotgun/cli/feedback.py +50 -0
  54. shotgun/cli/models.py +3 -2
  55. shotgun/cli/plan.py +1 -1
  56. shotgun/cli/research.py +1 -1
  57. shotgun/cli/specify.py +1 -1
  58. shotgun/cli/tasks.py +1 -1
  59. shotgun/cli/update.py +18 -5
  60. shotgun/codebase/core/change_detector.py +5 -3
  61. shotgun/codebase/core/code_retrieval.py +4 -2
  62. shotgun/codebase/core/ingestor.py +169 -19
  63. shotgun/codebase/core/manager.py +177 -13
  64. shotgun/codebase/core/nl_query.py +1 -1
  65. shotgun/codebase/models.py +28 -3
  66. shotgun/codebase/service.py +14 -2
  67. shotgun/exceptions.py +32 -0
  68. shotgun/llm_proxy/__init__.py +19 -0
  69. shotgun/llm_proxy/clients.py +44 -0
  70. shotgun/llm_proxy/constants.py +15 -0
  71. shotgun/logging_config.py +18 -27
  72. shotgun/main.py +91 -4
  73. shotgun/posthog_telemetry.py +87 -40
  74. shotgun/prompts/agents/export.j2 +18 -1
  75. shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +5 -1
  76. shotgun/prompts/agents/partials/interactive_mode.j2 +24 -7
  77. shotgun/prompts/agents/plan.j2 +1 -1
  78. shotgun/prompts/agents/research.j2 +1 -1
  79. shotgun/prompts/agents/specify.j2 +270 -3
  80. shotgun/prompts/agents/state/system_state.j2 +4 -0
  81. shotgun/prompts/agents/tasks.j2 +1 -1
  82. shotgun/prompts/codebase/partials/cypher_rules.j2 +13 -0
  83. shotgun/prompts/loader.py +2 -2
  84. shotgun/prompts/tools/web_search.j2 +14 -0
  85. shotgun/sdk/codebase.py +60 -2
  86. shotgun/sentry_telemetry.py +28 -21
  87. shotgun/settings.py +238 -0
  88. shotgun/shotgun_web/__init__.py +19 -0
  89. shotgun/shotgun_web/client.py +138 -0
  90. shotgun/shotgun_web/constants.py +21 -0
  91. shotgun/shotgun_web/models.py +47 -0
  92. shotgun/telemetry.py +24 -36
  93. shotgun/tui/app.py +275 -23
  94. shotgun/tui/commands/__init__.py +1 -1
  95. shotgun/tui/components/context_indicator.py +179 -0
  96. shotgun/tui/components/mode_indicator.py +70 -0
  97. shotgun/tui/components/status_bar.py +48 -0
  98. shotgun/tui/components/vertical_tail.py +6 -0
  99. shotgun/tui/containers.py +91 -0
  100. shotgun/tui/dependencies.py +39 -0
  101. shotgun/tui/filtered_codebase_service.py +46 -0
  102. shotgun/tui/protocols.py +45 -0
  103. shotgun/tui/screens/chat/__init__.py +5 -0
  104. shotgun/tui/screens/chat/chat.tcss +54 -0
  105. shotgun/tui/screens/chat/chat_screen.py +1234 -0
  106. shotgun/tui/screens/chat/codebase_index_prompt_screen.py +64 -0
  107. shotgun/tui/screens/chat/codebase_index_selection.py +12 -0
  108. shotgun/tui/screens/chat/help_text.py +40 -0
  109. shotgun/tui/screens/chat/prompt_history.py +48 -0
  110. shotgun/tui/screens/chat.tcss +11 -0
  111. shotgun/tui/screens/chat_screen/command_providers.py +226 -11
  112. shotgun/tui/screens/chat_screen/history/__init__.py +22 -0
  113. shotgun/tui/screens/chat_screen/history/agent_response.py +66 -0
  114. shotgun/tui/screens/chat_screen/history/chat_history.py +116 -0
  115. shotgun/tui/screens/chat_screen/history/formatters.py +115 -0
  116. shotgun/tui/screens/chat_screen/history/partial_response.py +43 -0
  117. shotgun/tui/screens/chat_screen/history/user_question.py +42 -0
  118. shotgun/tui/screens/confirmation_dialog.py +151 -0
  119. shotgun/tui/screens/feedback.py +193 -0
  120. shotgun/tui/screens/github_issue.py +102 -0
  121. shotgun/tui/screens/model_picker.py +352 -0
  122. shotgun/tui/screens/onboarding.py +431 -0
  123. shotgun/tui/screens/pipx_migration.py +153 -0
  124. shotgun/tui/screens/provider_config.py +156 -39
  125. shotgun/tui/screens/shotgun_auth.py +295 -0
  126. shotgun/tui/screens/welcome.py +198 -0
  127. shotgun/tui/services/__init__.py +5 -0
  128. shotgun/tui/services/conversation_service.py +184 -0
  129. shotgun/tui/state/__init__.py +7 -0
  130. shotgun/tui/state/processing_state.py +185 -0
  131. shotgun/tui/utils/mode_progress.py +14 -7
  132. shotgun/tui/widgets/__init__.py +5 -0
  133. shotgun/tui/widgets/widget_coordinator.py +262 -0
  134. shotgun/utils/datetime_utils.py +77 -0
  135. shotgun/utils/env_utils.py +13 -0
  136. shotgun/utils/file_system_utils.py +22 -2
  137. shotgun/utils/marketing.py +110 -0
  138. shotgun/utils/source_detection.py +16 -0
  139. shotgun/utils/update_checker.py +73 -21
  140. shotgun_sh-0.2.11.dist-info/METADATA +130 -0
  141. shotgun_sh-0.2.11.dist-info/RECORD +194 -0
  142. {shotgun_sh-0.1.9.dist-info → shotgun_sh-0.2.11.dist-info}/entry_points.txt +1 -0
  143. {shotgun_sh-0.1.9.dist-info → shotgun_sh-0.2.11.dist-info}/licenses/LICENSE +1 -1
  144. shotgun/agents/history/token_counting.py +0 -429
  145. shotgun/agents/tools/user_interaction.py +0 -37
  146. shotgun/tui/screens/chat.py +0 -818
  147. shotgun/tui/screens/chat_screen/history.py +0 -222
  148. shotgun_sh-0.1.9.dist-info/METADATA +0 -466
  149. shotgun_sh-0.1.9.dist-info/RECORD +0 -131
  150. {shotgun_sh-0.1.9.dist-info → shotgun_sh-0.2.11.dist-info}/WHEEL +0 -0
@@ -1,222 +0,0 @@
1
- import json
2
- from collections.abc import Sequence
3
-
4
- from pydantic_ai.messages import (
5
- BuiltinToolCallPart,
6
- BuiltinToolReturnPart,
7
- ModelMessage,
8
- ModelRequest,
9
- ModelRequestPart,
10
- ModelResponse,
11
- TextPart,
12
- ThinkingPart,
13
- ToolCallPart,
14
- ToolReturnPart,
15
- UserPromptPart,
16
- )
17
- from textual.app import ComposeResult
18
- from textual.reactive import reactive
19
- from textual.widget import Widget
20
- from textual.widgets import Markdown
21
-
22
- from shotgun.tui.components.vertical_tail import VerticalTail
23
- from shotgun.tui.screens.chat_screen.hint_message import HintMessage, HintMessageWidget
24
-
25
-
26
- class PartialResponseWidget(Widget): # TODO: doesn't work lol
27
- DEFAULT_CSS = """
28
- PartialResponseWidget {
29
- height: auto;
30
- }
31
- Markdown, AgentResponseWidget, UserQuestionWidget {
32
- height: auto;
33
- }
34
- """
35
-
36
- item: reactive[ModelMessage | None] = reactive(None, recompose=True)
37
-
38
- def __init__(self, item: ModelMessage | None) -> None:
39
- super().__init__()
40
- self.item = item
41
-
42
- def compose(self) -> ComposeResult:
43
- yield Markdown(markdown="**partial response**")
44
- if self.item is None:
45
- pass
46
- elif self.item.kind == "response":
47
- yield AgentResponseWidget(self.item)
48
- elif self.item.kind == "request":
49
- yield UserQuestionWidget(self.item)
50
-
51
- def watch_item(self, item: ModelMessage | None) -> None:
52
- if item is None:
53
- self.display = False
54
- else:
55
- self.display = True
56
-
57
-
58
- class ChatHistory(Widget):
59
- DEFAULT_CSS = """
60
- VerticalTail {
61
- align: left bottom;
62
-
63
- }
64
- VerticalTail > * {
65
- height: auto;
66
- }
67
-
68
- Horizontal {
69
- height: auto;
70
- background: $secondary-muted;
71
- }
72
-
73
- Markdown {
74
- height: auto;
75
- }
76
- """
77
- partial_response: reactive[ModelMessage | None] = reactive(None)
78
-
79
- def __init__(self) -> None:
80
- super().__init__()
81
- self.items: Sequence[ModelMessage | HintMessage] = []
82
- self.vertical_tail: VerticalTail | None = None
83
- self.partial_response = None
84
-
85
- def compose(self) -> ComposeResult:
86
- self.vertical_tail = VerticalTail()
87
-
88
- with self.vertical_tail:
89
- for item in self.items:
90
- if isinstance(item, ModelRequest):
91
- yield UserQuestionWidget(item)
92
- elif isinstance(item, HintMessage):
93
- yield HintMessageWidget(item)
94
- elif isinstance(item, ModelResponse):
95
- yield AgentResponseWidget(item)
96
- yield PartialResponseWidget(self.partial_response).data_bind(
97
- item=ChatHistory.partial_response
98
- )
99
- self.call_later(self.autoscroll)
100
-
101
- def update_messages(self, messages: list[ModelMessage | HintMessage]) -> None:
102
- """Update the displayed messages without recomposing."""
103
- if not self.vertical_tail:
104
- return
105
-
106
- self.items = messages
107
- self.refresh(recompose=True)
108
- self.call_later(self.autoscroll)
109
-
110
- def autoscroll(self) -> None:
111
- if self.vertical_tail:
112
- self.vertical_tail.scroll_end(animate=False, immediate=False, force=True)
113
-
114
-
115
- class UserQuestionWidget(Widget):
116
- def __init__(self, item: ModelRequest | None) -> None:
117
- super().__init__()
118
- self.item = item
119
-
120
- def compose(self) -> ComposeResult:
121
- self.display = self.item is not None
122
- if self.item is None:
123
- yield Markdown(markdown="")
124
- else:
125
- prompt = self.format_prompt_parts(self.item.parts)
126
- yield Markdown(markdown=prompt)
127
-
128
- def format_prompt_parts(self, parts: Sequence[ModelRequestPart]) -> str:
129
- acc = ""
130
- for part in parts:
131
- if isinstance(part, UserPromptPart):
132
- acc += (
133
- f"**>** {part.content if isinstance(part.content, str) else ''}\n\n"
134
- )
135
- elif isinstance(part, ToolReturnPart):
136
- if part.tool_name == "ask_user" and isinstance(part.content, dict):
137
- acc += f"**>** {part.content['answer']}\n\n"
138
- else:
139
- # acc += " ∟ finished\n\n" # let's not show anything yet
140
- pass
141
- elif isinstance(part, UserPromptPart):
142
- acc += f"**>** {part.content}\n\n"
143
- return acc
144
-
145
-
146
- class AgentResponseWidget(Widget):
147
- def __init__(self, item: ModelResponse | None) -> None:
148
- super().__init__()
149
- self.item = item
150
-
151
- def compose(self) -> ComposeResult:
152
- self.display = self.item is not None
153
- if self.item is None:
154
- yield Markdown(markdown="")
155
- else:
156
- yield Markdown(markdown=self.compute_output())
157
-
158
- def compute_output(self) -> str:
159
- acc = ""
160
- if self.item is None:
161
- return ""
162
- for idx, part in enumerate(self.item.parts):
163
- if isinstance(part, TextPart):
164
- acc += f"**⏺** {part.content}\n\n"
165
- elif isinstance(part, ToolCallPart):
166
- parts_str = self._format_tool_call_part(part)
167
- acc += parts_str + "\n\n"
168
- elif isinstance(part, BuiltinToolCallPart):
169
- acc += f"{part.tool_name}({part.args})\n\n"
170
- elif isinstance(part, BuiltinToolReturnPart):
171
- acc += f"builtin tool ({part.tool_name}) return: {part.content}\n\n"
172
- elif isinstance(part, ThinkingPart):
173
- if (
174
- idx == len(self.item.parts) - 1
175
- ): # show the thinking part only if it's the last part
176
- acc += (
177
- f"thinking: {part.content}\n\n"
178
- if part.content
179
- else "Thinking..."
180
- )
181
- else:
182
- continue
183
- return acc.strip()
184
-
185
- def _format_tool_call_part(self, part: ToolCallPart) -> str:
186
- if part.tool_name == "ask_user":
187
- return self._format_ask_user_part(part)
188
- # write_file
189
- if part.tool_name == "write_file" or part.tool_name == "append_file":
190
- if isinstance(part.args, dict) and "filename" in part.args:
191
- return f"{part.tool_name}({part.args['filename']})"
192
- else:
193
- return f"{part.tool_name}()"
194
- if part.tool_name == "write_artifact_section":
195
- if isinstance(part.args, dict) and "section_title" in part.args:
196
- return f"{part.tool_name}({part.args['section_title']})"
197
- else:
198
- return f"{part.tool_name}()"
199
- if part.tool_name == "create_artifact":
200
- if isinstance(part.args, dict) and "name" in part.args:
201
- return f"{part.tool_name}({part.args['name']})"
202
- else:
203
- return f"▪ {part.tool_name}()"
204
-
205
- return f"{part.tool_name}({part.args})"
206
-
207
- def _format_ask_user_part(
208
- self,
209
- part: ToolCallPart,
210
- ) -> str:
211
- if isinstance(part.args, str):
212
- try:
213
- _args = json.loads(part.args) if part.args.strip() else {}
214
- except json.JSONDecodeError:
215
- _args = {}
216
- else:
217
- _args = part.args
218
-
219
- if isinstance(_args, dict) and "question" in _args:
220
- return f"{_args['question']}"
221
- else:
222
- return "❓ "
@@ -1,466 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: shotgun-sh
3
- Version: 0.1.9
4
- Summary: AI-powered research, planning, and task management CLI tool
5
- Project-URL: Homepage, https://shotgun.sh/
6
- Project-URL: Repository, https://github.com/shotgun-sh/shotgun
7
- Project-URL: Issues, https://github.com/shotgun-sh/shotgun-alpha/issues
8
- Project-URL: Discord, https://discord.gg/5RmY6J2N7s
9
- Author-email: "Proofs.io" <hello@proofs.io>
10
- License: MIT
11
- License-File: LICENSE
12
- Keywords: agent,ai,cli,llm,planning,productivity,pydantic-ai,research,task-management
13
- Classifier: Development Status :: 3 - Alpha
14
- Classifier: Environment :: Console
15
- Classifier: Intended Audience :: Developers
16
- Classifier: License :: OSI Approved :: MIT License
17
- Classifier: Operating System :: OS Independent
18
- Classifier: Programming Language :: Python :: 3
19
- Classifier: Programming Language :: Python :: 3.11
20
- Classifier: Programming Language :: Python :: 3.12
21
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
- Classifier: Topic :: Utilities
23
- Requires-Python: >=3.11
24
- Requires-Dist: anthropic>=0.39.0
25
- Requires-Dist: google-generativeai>=0.8.5
26
- Requires-Dist: httpx>=0.27.0
27
- Requires-Dist: jinja2>=3.1.0
28
- Requires-Dist: kuzu>=0.7.0
29
- Requires-Dist: logfire[pydantic-ai]>=2.0.0
30
- Requires-Dist: openai>=1.0.0
31
- Requires-Dist: packaging>=23.0
32
- Requires-Dist: posthog>=3.0.0
33
- Requires-Dist: pydantic-ai>=0.0.14
34
- Requires-Dist: rich>=13.0.0
35
- Requires-Dist: sentry-sdk[pure-eval]>=2.0.0
36
- Requires-Dist: textual-dev>=1.7.0
37
- Requires-Dist: textual>=6.1.0
38
- Requires-Dist: tiktoken>=0.7.0
39
- Requires-Dist: tree-sitter-go>=0.23.0
40
- Requires-Dist: tree-sitter-javascript>=0.23.0
41
- Requires-Dist: tree-sitter-python>=0.23.0
42
- Requires-Dist: tree-sitter-rust>=0.23.0
43
- Requires-Dist: tree-sitter-typescript>=0.23.0
44
- Requires-Dist: tree-sitter>=0.21.0
45
- Requires-Dist: typer>=0.12.0
46
- Requires-Dist: watchdog>=4.0.0
47
- Provides-Extra: dev
48
- Requires-Dist: commitizen>=3.13.0; extra == 'dev'
49
- Requires-Dist: lefthook>=1.12.0; extra == 'dev'
50
- Requires-Dist: mypy>=1.11.0; extra == 'dev'
51
- Requires-Dist: ruff>=0.6.0; extra == 'dev'
52
- Description-Content-Type: text/markdown
53
-
54
- # Shotgun
55
-
56
- A Python CLI tool for research, planning, and task management powered by AI agents.
57
-
58
- ## Features
59
-
60
- - **Research**: Perform research with agentic loops
61
- - **Planning**: Generate structured plans for achieving goals
62
- - **Tasks**: Generate prioritized task lists with agentic approaches
63
-
64
- ## Installation
65
-
66
- ### From PyPI (Recommended)
67
-
68
- ```bash
69
- pip install shotgun-sh
70
- ```
71
-
72
- ### From Source
73
-
74
- ```bash
75
- git clone https://github.com/shotgun-sh/shotgun.git
76
- cd shotgun
77
- uv sync --all-extras
78
- ```
79
-
80
- After installation from source, you can use either method:
81
-
82
- **Method 1: Direct command (after uv sync)**
83
- ```bash
84
- shotgun --help
85
- ```
86
-
87
- **Method 2: Via uv run**
88
- ```bash
89
- uv run shotgun --help
90
- ```
91
-
92
- If installed from PyPI, simply use:
93
- ```bash
94
- shotgun --help
95
- ```
96
-
97
- ### Virtual Environment Setup (Optional)
98
-
99
- If you prefer using a local virtual environment:
100
-
101
- ```bash
102
- uv venv
103
- source .venv/bin/activate # On Windows: .venv\Scripts\activate
104
- uv sync --all-extras
105
- shotgun --help
106
- ```
107
-
108
- ## Usage
109
-
110
- ### Using Direct Commands (after uv sync)
111
-
112
- ```bash
113
- # Research a topic
114
- shotgun research "What is quantum computing?"
115
-
116
- # Generate a plan
117
- shotgun plan "Build a web application"
118
- shotgun plan "build me a house"
119
-
120
- # Generate tasks for a project
121
- shotgun tasks "Create a machine learning model"
122
- ```
123
-
124
- ### Using uv run
125
-
126
- ```bash
127
- # Research a topic
128
- uv run shotgun research "What is quantum computing?"
129
-
130
- # Generate a plan
131
- uv run shotgun plan "Build a web application"
132
-
133
- # Generate tasks for a project
134
- uv run shotgun tasks "Create a machine learning model"
135
- ```
136
-
137
- ## Auto-Updates
138
-
139
- Shotgun automatically checks for updates to keep you on the latest version.
140
-
141
- ### How it works
142
-
143
- - Checks for updates on startup (runs in background, non-blocking)
144
- - Caches results for 24 hours to minimize API calls
145
- - Shows notification after command execution if an update is available
146
- - Never auto-updates development versions
147
-
148
- ### Update Commands
149
-
150
- ```bash
151
- # Check for available updates
152
- shotgun update --check
153
-
154
- # Install available updates
155
- shotgun update
156
-
157
- # Force update (even for dev versions with confirmation)
158
- shotgun update --force
159
- ```
160
-
161
- ### Disable Update Checks
162
-
163
- ```bash
164
- # Disable for a single command
165
- shotgun --no-update-check research "topic"
166
- ```
167
-
168
- ### Installation Methods
169
-
170
- The update command automatically detects and uses the appropriate method:
171
- - **pipx**: `pipx upgrade shotgun-sh`
172
- - **pip**: `pip install --upgrade shotgun-sh`
173
- - **venv**: Updates within the virtual environment
174
-
175
- ## Development Setup
176
-
177
- ### Requirements
178
-
179
- - **Python 3.11+** (3.13 recommended)
180
- - **uv** - Fast Python package installer and resolver
181
- - **actionlint** (optional) - For GitHub Actions workflow validation
182
-
183
- ### Quick Start
184
-
185
- 1. **Clone and setup**:
186
- ```bash
187
- git clone https://github.com/shotgun-sh/shotgun.git
188
- cd shotgun
189
- ```
190
-
191
- 2. **Install uv** (if not already installed):
192
- ```bash
193
- # macOS/Linux
194
- curl -LsSf https://astral.sh/uv/install.sh | sh
195
-
196
- # Or via brew
197
- brew install uv
198
- ```
199
-
200
- 3. **Install dependencies**:
201
- ```bash
202
- uv sync --all-extras
203
- ```
204
-
205
- 4. **Install git hooks**:
206
- ```bash
207
- uv run lefthook install
208
- ```
209
-
210
- 5. **Verify setup**:
211
- ```bash
212
- uv run shotgun --version
213
- ```
214
-
215
- ### Development Commands
216
-
217
- ```bash
218
- # Run the CLI
219
- uv run shotgun --help
220
-
221
- # Run the TUI
222
- uv run tui
223
-
224
- # Run tests
225
- uv run pytest
226
-
227
- # Run tests with coverage
228
- uv run pytest --cov=src --cov-report=term-missing --cov-report=html
229
-
230
- # Run linting
231
- uv run ruff check .
232
-
233
- # Run formatting
234
- uv run ruff format .
235
-
236
- # Run type checking
237
- uv run mypy src/
238
-
239
- # Run all pre-commit hooks manually
240
- uv run lefthook run pre-commit
241
- ```
242
-
243
- ### Code Coverage
244
-
245
- To analyze test coverage and identify areas that need testing:
246
-
247
- ```bash
248
- # Run tests with coverage analysis
249
- uv run pytest --cov=src --cov-report=term-missing --cov-report=html
250
- ```
251
-
252
- This will:
253
- - Display coverage summary in the terminal
254
- - Generate a detailed HTML coverage report
255
-
256
- **Viewing the coverage report:**
257
- Open `htmlcov/index.html` in your browser to see:
258
- - Overall coverage percentage
259
- - File-by-file coverage breakdown
260
- - Line-by-line coverage highlighting
261
- - Missing coverage areas
262
-
263
- The coverage configuration is in `pyproject.toml` and will automatically run when you use `uv run pytest`.
264
-
265
- ### Git Hooks (Lefthook)
266
-
267
- This project uses [lefthook](https://github.com/evilmartians/lefthook) for git hooks. The hooks automatically run:
268
-
269
- - **ruff** - Python linting with auto-fix
270
- - **ruff-format** - Code formatting
271
- - **mypy** - Type checking
272
- - **commitizen** - Commit message validation
273
- - **actionlint** - GitHub Actions workflow validation (if installed)
274
-
275
- #### Installing actionlint (recommended)
276
-
277
- ```bash
278
- # macOS
279
- brew install actionlint
280
-
281
- # Linux/macOS (direct download)
282
- curl -sSfL https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash | bash
283
-
284
- # Go install
285
- go install github.com/rhysd/actionlint/cmd/actionlint@latest
286
- ```
287
-
288
-
289
- ### Python Version Management
290
-
291
- The project supports **Python 3.11+**. The `.python-version` file specifies Python 3.11 to ensure development against the minimum supported version.
292
-
293
- If using **pyenv**:
294
- ```bash
295
- pyenv install 3.11
296
- ```
297
-
298
- If using **uv** (recommended):
299
- ```bash
300
- uv python install 3.11
301
- uv sync --python 3.11
302
- ```
303
-
304
- ### Commit Message Convention
305
-
306
- This project enforces **Conventional Commits** specification. All commit messages must follow this format:
307
-
308
- ```
309
- <type>[optional scope]: <description>
310
- ```
311
-
312
- **Required commit types:**
313
- - `feat`: New feature
314
- - `fix`: Bug fix
315
- - `docs`: Documentation changes
316
- - `style`: Code formatting changes
317
- - `refactor`: Code restructuring without feature changes
318
- - `perf`: Performance improvements
319
- - `test`: Adding or updating tests
320
- - `build`: Build system changes
321
- - `ci`: CI/CD changes
322
- - `chore`: Maintenance tasks
323
- - `revert`: Reverting previous commits
324
-
325
- **Examples:**
326
- ```bash
327
- feat: add user authentication system
328
- fix: resolve memory leak in data processing
329
- docs: update API documentation
330
- refactor: simplify user validation logic
331
- ```
332
-
333
- **For interactive commit creation:**
334
- ```bash
335
- uv run cz commit
336
- ```
337
-
338
- ### Contributing
339
-
340
- 1. Fork the repository
341
- 2. Create a feature branch: `git checkout -b feat/feature-name`
342
- 3. Make your changes
343
- 4. Run the pre-commit hooks: `uv run lefthook run pre-commit`
344
- 5. Commit with conventional format: `git commit -m "feat: add new feature"`
345
- 6. Push to your fork: `git push origin feat/feature-name`
346
- 7. Create a Pull Request with conventional title format
347
-
348
- ### CI/CD
349
-
350
- GitHub Actions automatically:
351
- - Runs on pull requests and pushes to main
352
- - Tests with Python 3.11
353
- - Validates code with ruff, ruff-format, and mypy
354
- - Ensures all checks pass before merge
355
-
356
- ## Observability & Telemetry
357
-
358
- Shotgun includes built-in observability with Sentry for error tracking and Logfire for logging and tracing. Both services track users anonymously using a UUID generated on first run.
359
-
360
- ### Anonymous User Tracking
361
-
362
- Each user gets a unique anonymous ID stored in their config:
363
- ```bash
364
- # Get your anonymous user ID
365
- shotgun config get-user-id
366
- ```
367
-
368
- This ID is automatically included in:
369
- - **Sentry**: Error reports and exceptions
370
- - **Logfire**: All logs, traces, and spans
371
-
372
- ### Logfire Queries
373
-
374
- Logfire uses SQL for querying logs. Here are helpful queries for debugging and analysis:
375
-
376
- #### Find all logs for a specific user
377
- ```sql
378
- SELECT * FROM records
379
- WHERE attributes->>'user_id' = 'your-user-id-here'
380
- ORDER BY timestamp DESC;
381
- ```
382
-
383
- #### Track user actions
384
- ```sql
385
- SELECT
386
- timestamp,
387
- span_name,
388
- message,
389
- attributes
390
- FROM records
391
- WHERE attributes->>'user_id' = 'your-user-id-here'
392
- AND span_name LIKE '%research%'
393
- ORDER BY timestamp DESC;
394
- ```
395
-
396
- #### Find slow operations for a user
397
- ```sql
398
- SELECT
399
- span_name,
400
- duration_ms,
401
- attributes
402
- FROM records
403
- WHERE attributes->>'user_id' = 'your-user-id-here'
404
- AND duration_ms > 1000
405
- ORDER BY duration_ms DESC;
406
- ```
407
-
408
- #### Find errors for a user
409
- ```sql
410
- SELECT * FROM records
411
- WHERE attributes->>'user_id' = 'your-user-id-here'
412
- AND level = 'error'
413
- ORDER BY timestamp DESC;
414
- ```
415
-
416
- #### Analyze user's AI provider usage
417
- ```sql
418
- SELECT
419
- attributes->>'provider' as provider,
420
- COUNT(*) as usage_count,
421
- AVG(duration_ms) as avg_duration
422
- FROM records
423
- WHERE attributes->>'user_id' = 'your-user-id-here'
424
- AND attributes->>'provider' IS NOT NULL
425
- GROUP BY provider;
426
- ```
427
-
428
- #### Track feature usage by user
429
- ```sql
430
- SELECT
431
- span_name,
432
- COUNT(*) as usage_count
433
- FROM records
434
- WHERE attributes->>'user_id' = 'your-user-id-here'
435
- AND span_name IN ('research', 'plan', 'tasks')
436
- GROUP BY span_name
437
- ORDER BY usage_count DESC;
438
- ```
439
-
440
- ### Setting Up Observability (Optional)
441
-
442
- For local development with Logfire:
443
- ```bash
444
- # Set environment variables
445
- export LOGFIRE_ENABLED=true
446
- export LOGFIRE_TOKEN=your-logfire-token
447
-
448
- # Run shotgun - will now send logs to Logfire
449
- shotgun research "topic"
450
- ```
451
-
452
- For Sentry (automatically configured in production builds):
453
- ```bash
454
- # Set for local development
455
- export SENTRY_DSN=your-sentry-dsn
456
- ```
457
-
458
- ### Privacy
459
-
460
- - **No PII collected**: Only anonymous UUIDs are used for identification
461
- - **Opt-in for development**: Telemetry requires explicit environment variables
462
- - **Automatic in production**: Production builds include telemetry for error tracking
463
-
464
- ## Support
465
-
466
- Join our discord https://discord.gg/5RmY6J2N7s