shotgun-sh 0.1.0.dev7__tar.gz → 0.1.0.dev9__tar.gz

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 (108) hide show
  1. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/.gitignore +3 -0
  2. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/PKG-INFO +150 -1
  3. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/README.md +146 -0
  4. shotgun_sh-0.1.0.dev9/hatch_build.py +84 -0
  5. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/pyproject.toml +15 -4
  6. shotgun_sh-0.1.0.dev9/src/shotgun/__init__.py +5 -0
  7. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/config/manager.py +34 -7
  8. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/config/models.py +2 -0
  9. shotgun_sh-0.1.0.dev9/src/shotgun/build_constants.py +20 -0
  10. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/cli/config.py +14 -0
  11. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/cli/plan.py +11 -0
  12. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/cli/research.py +11 -0
  13. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/cli/tasks.py +11 -0
  14. shotgun_sh-0.1.0.dev9/src/shotgun/cli/update.py +152 -0
  15. shotgun_sh-0.1.0.dev9/src/shotgun/main.py +148 -0
  16. shotgun_sh-0.1.0.dev9/src/shotgun/posthog_telemetry.py +156 -0
  17. shotgun_sh-0.1.0.dev9/src/shotgun/sentry_telemetry.py +85 -0
  18. shotgun_sh-0.1.0.dev9/src/shotgun/telemetry.py +86 -0
  19. shotgun_sh-0.1.0.dev9/src/shotgun/tui/app.py +84 -0
  20. shotgun_sh-0.1.0.dev9/src/shotgun/utils/update_checker.py +375 -0
  21. shotgun_sh-0.1.0.dev7/src/shotgun/__init__.py +0 -3
  22. shotgun_sh-0.1.0.dev7/src/shotgun/main.py +0 -78
  23. shotgun_sh-0.1.0.dev7/src/shotgun/telemetry.py +0 -44
  24. shotgun_sh-0.1.0.dev7/src/shotgun/tui/app.py +0 -49
  25. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/LICENSE +0 -0
  26. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/__init__.py +0 -0
  27. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/agent_manager.py +0 -0
  28. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/common.py +0 -0
  29. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/config/__init__.py +0 -0
  30. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/config/provider.py +0 -0
  31. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/history/__init__.py +0 -0
  32. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/history/history_processors.py +0 -0
  33. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/models.py +0 -0
  34. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/plan.py +0 -0
  35. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/research.py +0 -0
  36. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/tasks.py +0 -0
  37. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/tools/__init__.py +0 -0
  38. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
  39. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
  40. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
  41. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
  42. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/tools/codebase/models.py +0 -0
  43. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
  44. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
  45. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/tools/file_management.py +0 -0
  46. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/tools/user_interaction.py +0 -0
  47. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
  48. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
  49. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
  50. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/tools/web_search/openai.py +0 -0
  51. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/agents/tools/web_search/utils.py +0 -0
  52. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/cli/__init__.py +0 -0
  53. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/cli/codebase/__init__.py +0 -0
  54. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/cli/codebase/commands.py +0 -0
  55. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/cli/codebase/models.py +0 -0
  56. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/cli/models.py +0 -0
  57. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/cli/utils.py +0 -0
  58. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/codebase/__init__.py +0 -0
  59. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/codebase/core/__init__.py +0 -0
  60. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/codebase/core/change_detector.py +0 -0
  61. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/codebase/core/code_retrieval.py +0 -0
  62. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/codebase/core/ingestor.py +0 -0
  63. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/codebase/core/language_config.py +0 -0
  64. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/codebase/core/manager.py +0 -0
  65. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/codebase/core/nl_query.py +0 -0
  66. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/codebase/core/parser_loader.py +0 -0
  67. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/codebase/models.py +0 -0
  68. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/codebase/service.py +0 -0
  69. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/logging_config.py +0 -0
  70. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/__init__.py +0 -0
  71. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/agents/__init__.py +0 -0
  72. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
  73. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
  74. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
  75. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/agents/plan.j2 +0 -0
  76. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/agents/research.j2 +0 -0
  77. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
  78. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
  79. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/agents/tasks.j2 +0 -0
  80. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/codebase/__init__.py +0 -0
  81. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
  82. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
  83. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
  84. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
  85. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
  86. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
  87. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/history/__init__.py +0 -0
  88. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/history/summarization.j2 +0 -0
  89. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/loader.py +0 -0
  90. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/prompts/user/research.j2 +0 -0
  91. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/py.typed +0 -0
  92. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/sdk/__init__.py +0 -0
  93. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/sdk/codebase.py +0 -0
  94. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/sdk/exceptions.py +0 -0
  95. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/sdk/models.py +0 -0
  96. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/sdk/services.py +0 -0
  97. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/tui/__init__.py +0 -0
  98. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/tui/components/prompt_input.py +0 -0
  99. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/tui/components/spinner.py +0 -0
  100. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/tui/components/splash.py +0 -0
  101. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/tui/components/vertical_tail.py +0 -0
  102. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/tui/screens/chat.py +0 -0
  103. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/tui/screens/chat.tcss +0 -0
  104. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/tui/screens/provider_config.py +0 -0
  105. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/tui/screens/splash.py +0 -0
  106. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/tui/styles.tcss +0 -0
  107. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/utils/__init__.py +0 -0
  108. {shotgun_sh-0.1.0.dev7 → shotgun_sh-0.1.0.dev9}/src/shotgun/utils/file_system_utils.py +0 -0
@@ -7,6 +7,9 @@ playground/
7
7
  # Output directory
8
8
  .shotgun/
9
9
 
10
+ # Generated build files
11
+ src/shotgun/build_constants.py
12
+
10
13
  # Claude code
11
14
  ./claude/.claude/settings.local.json
12
15
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shotgun-sh
3
- Version: 0.1.0.dev7
3
+ Version: 0.1.0.dev9
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
@@ -29,8 +29,11 @@ Requires-Dist: jinja2>=3.1.0
29
29
  Requires-Dist: kuzu>=0.7.0
30
30
  Requires-Dist: logfire[pydantic-ai]>=2.0.0
31
31
  Requires-Dist: openai>=1.0.0
32
+ Requires-Dist: packaging>=23.0
33
+ Requires-Dist: posthog>=3.0.0
32
34
  Requires-Dist: pydantic-ai>=0.0.14
33
35
  Requires-Dist: rich>=13.0.0
36
+ Requires-Dist: sentry-sdk[pure-eval]>=2.0.0
34
37
  Requires-Dist: textual-dev>=1.7.0
35
38
  Requires-Dist: textual>=6.1.0
36
39
  Requires-Dist: tree-sitter-go>=0.23.0
@@ -131,6 +134,44 @@ uv run shotgun plan "Build a web application"
131
134
  uv run shotgun tasks "Create a machine learning model"
132
135
  ```
133
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
+
134
175
  ## Development Setup
135
176
 
136
177
  ### Requirements
@@ -312,6 +353,114 @@ GitHub Actions automatically:
312
353
  - Validates code with ruff, ruff-format, and mypy
313
354
  - Ensures all checks pass before merge
314
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
+
315
464
  ## Support
316
465
 
317
466
  Join our discord https://discord.gg/5RmY6J2N7s
@@ -81,6 +81,44 @@ uv run shotgun plan "Build a web application"
81
81
  uv run shotgun tasks "Create a machine learning model"
82
82
  ```
83
83
 
84
+ ## Auto-Updates
85
+
86
+ Shotgun automatically checks for updates to keep you on the latest version.
87
+
88
+ ### How it works
89
+
90
+ - Checks for updates on startup (runs in background, non-blocking)
91
+ - Caches results for 24 hours to minimize API calls
92
+ - Shows notification after command execution if an update is available
93
+ - Never auto-updates development versions
94
+
95
+ ### Update Commands
96
+
97
+ ```bash
98
+ # Check for available updates
99
+ shotgun update --check
100
+
101
+ # Install available updates
102
+ shotgun update
103
+
104
+ # Force update (even for dev versions with confirmation)
105
+ shotgun update --force
106
+ ```
107
+
108
+ ### Disable Update Checks
109
+
110
+ ```bash
111
+ # Disable for a single command
112
+ shotgun --no-update-check research "topic"
113
+ ```
114
+
115
+ ### Installation Methods
116
+
117
+ The update command automatically detects and uses the appropriate method:
118
+ - **pipx**: `pipx upgrade shotgun-sh`
119
+ - **pip**: `pip install --upgrade shotgun-sh`
120
+ - **venv**: Updates within the virtual environment
121
+
84
122
  ## Development Setup
85
123
 
86
124
  ### Requirements
@@ -262,6 +300,114 @@ GitHub Actions automatically:
262
300
  - Validates code with ruff, ruff-format, and mypy
263
301
  - Ensures all checks pass before merge
264
302
 
303
+ ## Observability & Telemetry
304
+
305
+ 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.
306
+
307
+ ### Anonymous User Tracking
308
+
309
+ Each user gets a unique anonymous ID stored in their config:
310
+ ```bash
311
+ # Get your anonymous user ID
312
+ shotgun config get-user-id
313
+ ```
314
+
315
+ This ID is automatically included in:
316
+ - **Sentry**: Error reports and exceptions
317
+ - **Logfire**: All logs, traces, and spans
318
+
319
+ ### Logfire Queries
320
+
321
+ Logfire uses SQL for querying logs. Here are helpful queries for debugging and analysis:
322
+
323
+ #### Find all logs for a specific user
324
+ ```sql
325
+ SELECT * FROM records
326
+ WHERE attributes->>'user_id' = 'your-user-id-here'
327
+ ORDER BY timestamp DESC;
328
+ ```
329
+
330
+ #### Track user actions
331
+ ```sql
332
+ SELECT
333
+ timestamp,
334
+ span_name,
335
+ message,
336
+ attributes
337
+ FROM records
338
+ WHERE attributes->>'user_id' = 'your-user-id-here'
339
+ AND span_name LIKE '%research%'
340
+ ORDER BY timestamp DESC;
341
+ ```
342
+
343
+ #### Find slow operations for a user
344
+ ```sql
345
+ SELECT
346
+ span_name,
347
+ duration_ms,
348
+ attributes
349
+ FROM records
350
+ WHERE attributes->>'user_id' = 'your-user-id-here'
351
+ AND duration_ms > 1000
352
+ ORDER BY duration_ms DESC;
353
+ ```
354
+
355
+ #### Find errors for a user
356
+ ```sql
357
+ SELECT * FROM records
358
+ WHERE attributes->>'user_id' = 'your-user-id-here'
359
+ AND level = 'error'
360
+ ORDER BY timestamp DESC;
361
+ ```
362
+
363
+ #### Analyze user's AI provider usage
364
+ ```sql
365
+ SELECT
366
+ attributes->>'provider' as provider,
367
+ COUNT(*) as usage_count,
368
+ AVG(duration_ms) as avg_duration
369
+ FROM records
370
+ WHERE attributes->>'user_id' = 'your-user-id-here'
371
+ AND attributes->>'provider' IS NOT NULL
372
+ GROUP BY provider;
373
+ ```
374
+
375
+ #### Track feature usage by user
376
+ ```sql
377
+ SELECT
378
+ span_name,
379
+ COUNT(*) as usage_count
380
+ FROM records
381
+ WHERE attributes->>'user_id' = 'your-user-id-here'
382
+ AND span_name IN ('research', 'plan', 'tasks')
383
+ GROUP BY span_name
384
+ ORDER BY usage_count DESC;
385
+ ```
386
+
387
+ ### Setting Up Observability (Optional)
388
+
389
+ For local development with Logfire:
390
+ ```bash
391
+ # Set environment variables
392
+ export LOGFIRE_ENABLED=true
393
+ export LOGFIRE_TOKEN=your-logfire-token
394
+
395
+ # Run shotgun - will now send logs to Logfire
396
+ shotgun research "topic"
397
+ ```
398
+
399
+ For Sentry (automatically configured in production builds):
400
+ ```bash
401
+ # Set for local development
402
+ export SENTRY_DSN=your-sentry-dsn
403
+ ```
404
+
405
+ ### Privacy
406
+
407
+ - **No PII collected**: Only anonymous UUIDs are used for identification
408
+ - **Opt-in for development**: Telemetry requires explicit environment variables
409
+ - **Automatic in production**: Production builds include telemetry for error tracking
410
+
265
411
  ## Support
266
412
 
267
413
  Join our discord https://discord.gg/5RmY6J2N7s
@@ -0,0 +1,84 @@
1
+ """Hatchling build hook for generating build constants."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+
6
+ from hatchling.builders.hooks.plugin.interface import BuildHookInterface
7
+
8
+
9
+ class CustomBuildHook(BuildHookInterface): # type: ignore[type-arg]
10
+ """Custom build hook to generate build constants from environment variables."""
11
+
12
+ def initialize(self, version, build_data): # type: ignore[no-untyped-def]
13
+ """Generate build constants file from environment variables.
14
+
15
+ This runs immediately before each build.
16
+ """
17
+ # Check if this is a development build based on version
18
+ is_dev_build = any(
19
+ marker in str(version)
20
+ for marker in ["dev", "rc", "alpha", "beta", "a", "b"]
21
+ )
22
+
23
+ # Only generate constants if SENTRY_DSN is provided (production builds)
24
+ sentry_dsn = os.environ.get("SENTRY_DSN", "")
25
+
26
+ # Get PostHog configuration from environment
27
+ posthog_api_key = os.environ.get("POSTHOG_API_KEY", "")
28
+ posthog_project_id = os.environ.get("POSTHOG_PROJECT_ID", "")
29
+
30
+ # Get Logfire configuration (only for dev builds)
31
+ logfire_enabled = ""
32
+ logfire_token = ""
33
+ if is_dev_build:
34
+ logfire_enabled = os.environ.get("LOGFIRE_ENABLED", "")
35
+ logfire_token = os.environ.get("LOGFIRE_TOKEN", "")
36
+
37
+ # Generate Python configuration file with build-time constants
38
+ constants_content = f'''"""Build-time constants generated during packaging.
39
+
40
+ This file is auto-generated during the build process.
41
+ DO NOT EDIT MANUALLY.
42
+ """
43
+
44
+ # Sentry DSN embedded at build time (empty string if not provided)
45
+ SENTRY_DSN = {repr(sentry_dsn)}
46
+
47
+ # PostHog configuration embedded at build time (empty strings if not provided)
48
+ POSTHOG_API_KEY = {repr(posthog_api_key)}
49
+ POSTHOG_PROJECT_ID = {repr(posthog_project_id)}
50
+
51
+ # Logfire configuration embedded at build time (only for dev builds)
52
+ LOGFIRE_ENABLED = {repr(logfire_enabled)}
53
+ LOGFIRE_TOKEN = {repr(logfire_token)}
54
+
55
+ # Build metadata
56
+ BUILD_TIME_ENV = "production" if SENTRY_DSN else "development"
57
+ IS_DEV_BUILD = {repr(is_dev_build)}
58
+ '''
59
+
60
+ # Write to build_constants.py in the source directory
61
+ output_path = Path(self.root) / "src" / "shotgun" / "build_constants.py"
62
+ output_path.parent.mkdir(parents=True, exist_ok=True)
63
+
64
+ with open(output_path, "w", encoding="utf-8") as f:
65
+ f.write(constants_content)
66
+
67
+ # Log the build hook execution
68
+ features = []
69
+ if sentry_dsn:
70
+ features.append("Sentry")
71
+ if posthog_api_key:
72
+ features.append("PostHog")
73
+ if logfire_enabled and logfire_token:
74
+ features.append("Logfire")
75
+
76
+ if features:
77
+ build_type = "development" if is_dev_build else "production"
78
+ print(
79
+ f"✅ Generated build_constants.py with {', '.join(features)} ({build_type} build)"
80
+ )
81
+ else:
82
+ print(
83
+ "⚠️ Generated build_constants.py without analytics keys (development build)"
84
+ )
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "shotgun-sh"
3
- version = "0.1.0.dev7"
3
+ version = "0.1.0.dev9"
4
4
  description = "AI-powered research, planning, and task management CLI tool"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -29,6 +29,8 @@ dependencies = [
29
29
  "httpx>=0.27.0",
30
30
  "jinja2>=3.1.0",
31
31
  "logfire[pydantic-ai]>=2.0.0",
32
+ "sentry-sdk[pure_eval]>=2.0.0",
33
+ "posthog>=3.0.0",
32
34
  "textual>=6.1.0",
33
35
  "textual-dev>=1.7.0",
34
36
  "kuzu>=0.7.0",
@@ -42,6 +44,7 @@ dependencies = [
42
44
  "openai>=1.0.0",
43
45
  "anthropic>=0.39.0",
44
46
  "google-generativeai>=0.8.0",
47
+ "packaging>=23.0",
45
48
  ]
46
49
 
47
50
  [project.urls]
@@ -60,7 +63,6 @@ dev = [
60
63
 
61
64
  [project.scripts]
62
65
  shotgun = "shotgun.main:app"
63
- tui = "shotgun.tui.app:run"
64
66
 
65
67
  [build-system]
66
68
  requires = ["hatchling"]
@@ -77,9 +79,18 @@ include = [
77
79
  "/pyproject.toml"
78
80
  ]
79
81
 
82
+ [tool.hatch.build.hooks.custom]
83
+ # Custom build hook configuration
84
+
85
+ [tool.hatch.build]
86
+ artifacts = [
87
+ "src/shotgun/build_constants.py"
88
+ ]
89
+
80
90
  [tool.ruff]
81
91
  line-length = 88
82
92
  target-version = "py310"
93
+ exclude = ["src/shotgun/build_constants.py"]
83
94
 
84
95
  [tool.ruff.lint]
85
96
  select = ["E", "W", "F", "I", "N", "UP", "B", "S", "C4", "PIE", "T20"]
@@ -93,8 +104,8 @@ python_version = "3.10"
93
104
  strict = true
94
105
  warn_return_any = true
95
106
  warn_unused_configs = true
96
- # Remove test exclusion to enable type checking on tests
97
- # exclude = ["^test/"]
107
+ # Exclude build hooks and generated files
108
+ exclude = ["hatch_build.py", "src/shotgun/build_constants.py"]
98
109
  plugins = ["pydantic.mypy"]
99
110
 
100
111
  # Configure test-specific settings
@@ -0,0 +1,5 @@
1
+ """Shotgun CLI package."""
2
+
3
+ from importlib.metadata import version
4
+
5
+ __version__ = version("shotgun-sh")
@@ -1,6 +1,7 @@
1
1
  """Configuration manager for Shotgun CLI."""
2
2
 
3
3
  import json
4
+ import uuid
4
5
  from pathlib import Path
5
6
  from typing import Any
6
7
 
@@ -41,9 +42,11 @@ class ConfigManager:
41
42
 
42
43
  if not self.config_path.exists():
43
44
  logger.info(
44
- "Configuration file not found, using defaults: %s", self.config_path
45
+ "Configuration file not found, creating new config with user_id: %s",
46
+ self.config_path,
45
47
  )
46
- self._config = ShotgunConfig()
48
+ # Create new config with generated user_id
49
+ self._config = self.initialize()
47
50
  return self._config
48
51
 
49
52
  try:
@@ -61,8 +64,8 @@ class ConfigManager:
61
64
  logger.error(
62
65
  "Failed to load configuration from %s: %s", self.config_path, e
63
66
  )
64
- logger.info("Using default configuration")
65
- self._config = ShotgunConfig()
67
+ logger.info("Creating new configuration with generated user_id")
68
+ self._config = self.initialize()
66
69
  return self._config
67
70
 
68
71
  def save(self, config: ShotgunConfig | None = None) -> None:
@@ -72,7 +75,14 @@ class ConfigManager:
72
75
  config: Configuration to save. If None, saves current loaded config
73
76
  """
74
77
  if config is None:
75
- config = self._config or ShotgunConfig()
78
+ if self._config:
79
+ config = self._config
80
+ else:
81
+ # Create a new config with generated user_id
82
+ config = ShotgunConfig(
83
+ user_id=str(uuid.uuid4()),
84
+ config_version=1,
85
+ )
76
86
 
77
87
  # Ensure directory exists
78
88
  self.config_path.parent.mkdir(parents=True, exist_ok=True)
@@ -150,9 +160,17 @@ class ConfigManager:
150
160
  Returns:
151
161
  Default ShotgunConfig
152
162
  """
153
- config = ShotgunConfig()
163
+ # Generate unique user ID for new config
164
+ config = ShotgunConfig(
165
+ user_id=str(uuid.uuid4()),
166
+ config_version=1,
167
+ )
154
168
  self.save(config)
155
- logger.info("Configuration initialized at %s", self.config_path)
169
+ logger.info(
170
+ "Configuration initialized at %s with user_id: %s",
171
+ self.config_path,
172
+ config.user_id,
173
+ )
156
174
  return config
157
175
 
158
176
  def _convert_secrets_to_secretstr(self, data: dict[str, Any]) -> None:
@@ -209,6 +227,15 @@ class ConfigManager:
209
227
 
210
228
  return bool(value.strip())
211
229
 
230
+ def get_user_id(self) -> str:
231
+ """Get the user ID from configuration.
232
+
233
+ Returns:
234
+ The unique user ID string
235
+ """
236
+ config = self.load()
237
+ return config.user_id
238
+
212
239
 
213
240
  def get_config_manager() -> ConfigManager:
214
241
  """Get the global ConfigManager instance."""
@@ -123,3 +123,5 @@ class ShotgunConfig(BaseModel):
123
123
  default_provider: ProviderType = Field(
124
124
  default=ProviderType.OPENAI, description="Default AI provider to use"
125
125
  )
126
+ user_id: str = Field(description="Unique anonymous user identifier")
127
+ config_version: int = Field(default=1, description="Configuration schema version")
@@ -0,0 +1,20 @@
1
+ """Build-time constants generated during packaging.
2
+
3
+ This file is auto-generated during the build process.
4
+ DO NOT EDIT MANUALLY.
5
+ """
6
+
7
+ # Sentry DSN embedded at build time (empty string if not provided)
8
+ SENTRY_DSN = 'https://2818a6d165c64eccc94cfd51ce05d6aa@o4506813296738304.ingest.us.sentry.io/4510045952409600'
9
+
10
+ # PostHog configuration embedded at build time (empty strings if not provided)
11
+ POSTHOG_API_KEY = ''
12
+ POSTHOG_PROJECT_ID = '191396'
13
+
14
+ # Logfire configuration embedded at build time (only for dev builds)
15
+ LOGFIRE_ENABLED = 'true'
16
+ LOGFIRE_TOKEN = 'pylf_v1_us_KZ5NM1pP3NwgJkbBJt6Ftdzk8mMhmrXcGJHQQgDJ1LfK'
17
+
18
+ # Build metadata
19
+ BUILD_TIME_ENV = "production" if SENTRY_DSN else "development"
20
+ IS_DEV_BUILD = True
@@ -259,3 +259,17 @@ def _mask_value(value: str) -> str:
259
259
  if len(value) <= 8:
260
260
  return "••••••••"
261
261
  return f"{value[:4]}{'•' * (len(value) - 8)}{value[-4:]}"
262
+
263
+
264
+ @app.command()
265
+ def get_user_id() -> None:
266
+ """Get the anonymous user ID from configuration."""
267
+ config_manager = get_config_manager()
268
+
269
+ try:
270
+ user_id = config_manager.get_user_id()
271
+ console.print(f"[green]User ID:[/green] {user_id}")
272
+ except Exception as e:
273
+ logger.error(f"Error getting user ID: {e}")
274
+ console.print(f"❌ Failed to get user ID: {str(e)}", style="red")
275
+ raise typer.Exit(1) from e
@@ -38,6 +38,17 @@ def plan(
38
38
  logger.info("📋 Planning Goal: %s", goal)
39
39
 
40
40
  try:
41
+ # Track plan command usage
42
+ from shotgun.posthog_telemetry import track_event
43
+
44
+ track_event(
45
+ "plan_command",
46
+ {
47
+ "non_interactive": non_interactive,
48
+ "provider": provider.value if provider else "default",
49
+ },
50
+ )
51
+
41
52
  # Create agent dependencies
42
53
  agent_runtime_options = AgentRuntimeOptions(
43
54
  interactive_mode=not non_interactive
@@ -58,6 +58,17 @@ async def async_research(
58
58
  provider: ProviderType | None = None,
59
59
  ) -> None:
60
60
  """Async wrapper for research process."""
61
+ # Track research command usage
62
+ from shotgun.posthog_telemetry import track_event
63
+
64
+ track_event(
65
+ "research_command",
66
+ {
67
+ "non_interactive": non_interactive,
68
+ "provider": provider.value if provider else "default",
69
+ },
70
+ )
71
+
61
72
  # Create agent dependencies
62
73
  agent_runtime_options = AgentRuntimeOptions(interactive_mode=not non_interactive)
63
74
 
@@ -44,6 +44,17 @@ def tasks(
44
44
  logger.info("📋 Task Creation Instruction: %s", instruction)
45
45
 
46
46
  try:
47
+ # Track tasks command usage
48
+ from shotgun.posthog_telemetry import track_event
49
+
50
+ track_event(
51
+ "tasks_command",
52
+ {
53
+ "non_interactive": non_interactive,
54
+ "provider": provider.value if provider else "default",
55
+ },
56
+ )
57
+
47
58
  # Create agent dependencies
48
59
  agent_runtime_options = AgentRuntimeOptions(
49
60
  interactive_mode=not non_interactive