shotgun-sh 0.1.14.dev2__tar.gz → 0.1.15.dev1__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 (136) hide show
  1. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/PKG-INFO +1 -1
  2. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/pyproject.toml +1 -1
  3. shotgun_sh-0.1.15.dev1/src/shotgun/cli/feedback.py +46 -0
  4. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/main.py +12 -1
  5. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/posthog_telemetry.py +51 -0
  6. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/app.py +19 -1
  7. shotgun_sh-0.1.15.dev1/src/shotgun/tui/screens/feedback.py +193 -0
  8. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/.gitignore +0 -0
  9. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/LICENSE +0 -0
  10. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/README.md +0 -0
  11. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/hatch_build.py +0 -0
  12. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/__init__.py +0 -0
  13. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/__init__.py +0 -0
  14. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/agent_manager.py +0 -0
  15. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/common.py +0 -0
  16. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/config/__init__.py +0 -0
  17. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/config/constants.py +0 -0
  18. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/config/manager.py +0 -0
  19. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/config/models.py +0 -0
  20. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/config/provider.py +0 -0
  21. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/conversation_history.py +0 -0
  22. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/conversation_manager.py +0 -0
  23. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/export.py +0 -0
  24. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/history/__init__.py +0 -0
  25. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/history/compaction.py +0 -0
  26. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/history/constants.py +0 -0
  27. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/history/context_extraction.py +0 -0
  28. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/history/history_building.py +0 -0
  29. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/history/history_processors.py +0 -0
  30. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/history/message_utils.py +0 -0
  31. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/history/token_counting.py +0 -0
  32. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/history/token_estimation.py +0 -0
  33. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/messages.py +0 -0
  34. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/models.py +0 -0
  35. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/plan.py +0 -0
  36. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/research.py +0 -0
  37. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/specify.py +0 -0
  38. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/tasks.py +0 -0
  39. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/tools/__init__.py +0 -0
  40. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
  41. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
  42. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
  43. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
  44. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/tools/codebase/models.py +0 -0
  45. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
  46. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
  47. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/tools/file_management.py +0 -0
  48. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/tools/user_interaction.py +0 -0
  49. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
  50. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
  51. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
  52. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/tools/web_search/openai.py +0 -0
  53. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/agents/tools/web_search/utils.py +0 -0
  54. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/build_constants.py +0 -0
  55. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/cli/__init__.py +0 -0
  56. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/cli/codebase/__init__.py +0 -0
  57. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/cli/codebase/commands.py +0 -0
  58. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/cli/codebase/models.py +0 -0
  59. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/cli/config.py +0 -0
  60. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/cli/export.py +0 -0
  61. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/cli/models.py +0 -0
  62. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/cli/plan.py +0 -0
  63. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/cli/research.py +0 -0
  64. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/cli/specify.py +0 -0
  65. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/cli/tasks.py +0 -0
  66. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/cli/update.py +0 -0
  67. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/cli/utils.py +0 -0
  68. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/codebase/__init__.py +0 -0
  69. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/codebase/core/__init__.py +0 -0
  70. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/codebase/core/change_detector.py +0 -0
  71. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/codebase/core/code_retrieval.py +0 -0
  72. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/codebase/core/cypher_models.py +0 -0
  73. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/codebase/core/ingestor.py +0 -0
  74. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/codebase/core/language_config.py +0 -0
  75. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/codebase/core/manager.py +0 -0
  76. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/codebase/core/nl_query.py +0 -0
  77. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/codebase/core/parser_loader.py +0 -0
  78. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/codebase/models.py +0 -0
  79. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/codebase/service.py +0 -0
  80. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/logging_config.py +0 -0
  81. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/__init__.py +0 -0
  82. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/agents/__init__.py +0 -0
  83. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/agents/export.j2 +0 -0
  84. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
  85. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
  86. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
  87. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
  88. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/agents/plan.j2 +0 -0
  89. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/agents/research.j2 +0 -0
  90. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/agents/specify.j2 +0 -0
  91. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
  92. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
  93. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/agents/tasks.j2 +0 -0
  94. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/codebase/__init__.py +0 -0
  95. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
  96. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
  97. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
  98. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
  99. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
  100. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
  101. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/history/__init__.py +0 -0
  102. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
  103. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/history/summarization.j2 +0 -0
  104. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/prompts/loader.py +0 -0
  105. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/py.typed +0 -0
  106. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/sdk/__init__.py +0 -0
  107. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/sdk/codebase.py +0 -0
  108. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/sdk/exceptions.py +0 -0
  109. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/sdk/models.py +0 -0
  110. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/sdk/services.py +0 -0
  111. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/sentry_telemetry.py +0 -0
  112. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/telemetry.py +0 -0
  113. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/__init__.py +0 -0
  114. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/commands/__init__.py +0 -0
  115. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/components/prompt_input.py +0 -0
  116. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/components/spinner.py +0 -0
  117. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/components/splash.py +0 -0
  118. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/components/vertical_tail.py +0 -0
  119. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/filtered_codebase_service.py +0 -0
  120. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/screens/chat.py +0 -0
  121. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/screens/chat.tcss +0 -0
  122. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
  123. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/screens/chat_screen/command_providers.py +0 -0
  124. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
  125. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/screens/chat_screen/history.py +0 -0
  126. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/screens/directory_setup.py +0 -0
  127. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/screens/provider_config.py +0 -0
  128. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/screens/splash.py +0 -0
  129. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/styles.tcss +0 -0
  130. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/utils/__init__.py +0 -0
  131. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/tui/utils/mode_progress.py +0 -0
  132. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/utils/__init__.py +0 -0
  133. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/utils/env_utils.py +0 -0
  134. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/utils/file_system_utils.py +0 -0
  135. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/utils/source_detection.py +0 -0
  136. {shotgun_sh-0.1.14.dev2 → shotgun_sh-0.1.15.dev1}/src/shotgun/utils/update_checker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shotgun-sh
3
- Version: 0.1.14.dev2
3
+ Version: 0.1.15.dev1
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "shotgun-sh"
3
- version = "0.1.14.dev2"
3
+ version = "0.1.15.dev1"
4
4
  description = "AI-powered research, planning, and task management CLI tool"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -0,0 +1,46 @@
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
+ config_manager = get_config_manager()
32
+ config_manager.load()
33
+ user_id = config_manager.get_user_id()
34
+
35
+ if not description:
36
+ console.print(
37
+ '❌ Please add your feedback (shotgun feedback "<your feedback>").',
38
+ style="red",
39
+ )
40
+ raise typer.Exit(1)
41
+
42
+ feedback = Feedback(kind=kind, description=description, user_id=user_id)
43
+
44
+ submit_feedback_survey(feedback)
45
+
46
+ console.print("✅ Feedback sent. Thank you!")
@@ -16,7 +16,17 @@ from dotenv import load_dotenv
16
16
 
17
17
  from shotgun import __version__
18
18
  from shotgun.agents.config import get_config_manager
19
- from shotgun.cli import codebase, config, export, plan, research, specify, tasks, update
19
+ from shotgun.cli import (
20
+ codebase,
21
+ config,
22
+ export,
23
+ feedback,
24
+ plan,
25
+ research,
26
+ specify,
27
+ tasks,
28
+ update,
29
+ )
20
30
  from shotgun.logging_config import configure_root_logger, get_logger
21
31
  from shotgun.posthog_telemetry import setup_posthog_observability
22
32
  from shotgun.sentry_telemetry import setup_sentry_observability
@@ -68,6 +78,7 @@ app.add_typer(specify.app, name="specify", help="Generate comprehensive specific
68
78
  app.add_typer(tasks.app, name="tasks", help="Generate task lists with agentic approach")
69
79
  app.add_typer(export.app, name="export", help="Export artifacts to various formats")
70
80
  app.add_typer(update.app, name="update", help="Check for and install updates")
81
+ app.add_typer(feedback.app, name="feedback", help="Send us feedback")
71
82
 
72
83
 
73
84
  def version_callback(value: bool) -> None:
@@ -1,11 +1,14 @@
1
1
  """PostHog analytics setup for Shotgun."""
2
2
 
3
+ from enum import Enum
3
4
  from typing import Any
4
5
 
5
6
  import posthog
7
+ from pydantic import BaseModel
6
8
 
7
9
  from shotgun import __version__
8
10
  from shotgun.agents.config import get_config_manager
11
+ from shotgun.agents.conversation_manager import ConversationManager
9
12
  from shotgun.logging_config import get_early_logger
10
13
 
11
14
  # Use early logger to prevent automatic StreamHandler creation
@@ -132,3 +135,51 @@ def shutdown() -> None:
132
135
  logger.warning("Error shutting down PostHog: %s", e)
133
136
  finally:
134
137
  _posthog_client = None
138
+
139
+
140
+ class FeedbackKind(str, Enum):
141
+ BUG = "bug"
142
+ FEATURE = "feature"
143
+ OTHER = "other"
144
+
145
+
146
+ class Feedback(BaseModel):
147
+ kind: FeedbackKind
148
+ description: str
149
+ user_id: str
150
+
151
+
152
+ SURVEY_ID = "01999f81-9486-0000-4fa6-9632959f92f3"
153
+ Q_KIND_ID = "aaa5fcc3-88ba-4c24-bcf5-1481fd5efc2b"
154
+ Q_DESCRIPTION_ID = "a0ed6283-5d4b-452c-9160-6768d879db8a"
155
+
156
+
157
+ def submit_feedback_survey(feedback: Feedback) -> None:
158
+ global _posthog_client
159
+ if _posthog_client is None:
160
+ logger.debug("PostHog not initialized, skipping feedback survey")
161
+ return
162
+
163
+ config_manager = get_config_manager()
164
+ config = config_manager.load()
165
+ conversation_manager = ConversationManager()
166
+ conversation = conversation_manager.load()
167
+ last_10_messages = []
168
+ if conversation is not None:
169
+ last_10_messages = conversation.get_agent_messages()[:10]
170
+
171
+ track_event(
172
+ "survey sent",
173
+ properties={
174
+ "$survey_id": SURVEY_ID,
175
+ "$survey_questions": [
176
+ {"id": Q_KIND_ID, "question": "Feedback type"},
177
+ {"id": Q_DESCRIPTION_ID, "question": "Feedback description"},
178
+ ],
179
+ f"$survey_response_{Q_KIND_ID}": feedback.kind,
180
+ f"$survey_response_{Q_DESCRIPTION_ID}": feedback.description,
181
+ "provider": config.default_provider.value,
182
+ "config_version": config.config_version,
183
+ "last_10_messages": last_10_messages, # last 10 messages
184
+ },
185
+ )
@@ -13,6 +13,7 @@ from shotgun.utils.update_checker import perform_auto_update_async
13
13
 
14
14
  from .screens.chat import ChatScreen
15
15
  from .screens.directory_setup import DirectorySetupScreen
16
+ from .screens.feedback import FeedbackScreen
16
17
  from .screens.provider_config import ProviderConfigScreen
17
18
 
18
19
  logger = get_logger(__name__)
@@ -23,10 +24,12 @@ class ShotgunApp(App[None]):
23
24
  "chat": ChatScreen,
24
25
  "provider_config": ProviderConfigScreen,
25
26
  "directory_setup": DirectorySetupScreen,
27
+ "feedback": FeedbackScreen,
26
28
  }
27
29
  BINDINGS = [
28
30
  Binding("ctrl+c", "quit", "Quit the app"),
29
31
  ]
32
+
30
33
  CSS_PATH = "styles.tcss"
31
34
 
32
35
  def __init__(
@@ -90,7 +93,22 @@ class ShotgunApp(App[None]):
90
93
  self.exit()
91
94
 
92
95
  def get_system_commands(self, screen: Screen[Any]) -> Iterable[SystemCommand]:
93
- return [] # we don't want any system commands
96
+ return [
97
+ SystemCommand(
98
+ "Feedback", "Send us feedback or report a bug", self.action_feedback
99
+ )
100
+ ] # we don't want any system commands
101
+
102
+ def action_feedback(self) -> None:
103
+ """Open feedback screen and submit feedback."""
104
+ from shotgun.posthog_telemetry import Feedback, submit_feedback_survey
105
+
106
+ def handle_feedback(feedback: Feedback | None) -> None:
107
+ if feedback is not None:
108
+ submit_feedback_survey(feedback)
109
+ self.notify("✅ Feedback sent. Thank you!")
110
+
111
+ self.push_screen("feedback", callback=handle_feedback)
94
112
 
95
113
 
96
114
  def run(no_update_check: bool = False, continue_session: bool = False) -> None:
@@ -0,0 +1,193 @@
1
+ """Screen for submitting user feedback."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, cast
6
+
7
+ from textual import on
8
+ from textual.app import ComposeResult
9
+ from textual.containers import Horizontal, Vertical
10
+ from textual.reactive import reactive
11
+ from textual.screen import Screen
12
+ from textual.widgets import Button, Label, ListItem, ListView, Static, TextArea
13
+
14
+ from shotgun.posthog_telemetry import Feedback, FeedbackKind
15
+
16
+ if TYPE_CHECKING:
17
+ from ..app import ShotgunApp
18
+
19
+
20
+ class FeedbackScreen(Screen[Feedback | None]):
21
+ """Collect feedback from users."""
22
+
23
+ CSS = """
24
+ FeedbackScreen {
25
+ layout: vertical;
26
+ }
27
+
28
+ FeedbackScreen > * {
29
+ height: auto;
30
+ }
31
+
32
+ Label {
33
+ padding: 0 1;
34
+ }
35
+
36
+ #titlebox {
37
+ height: auto;
38
+ margin: 2 0;
39
+ padding: 1;
40
+ border: hkey $border;
41
+ content-align: center middle;
42
+
43
+ & > * {
44
+ text-align: center;
45
+ }
46
+ }
47
+
48
+ #feedback-title {
49
+ padding: 1 0;
50
+ margin-bottom: 2;
51
+ text-style: bold;
52
+ color: $text-accent;
53
+ }
54
+
55
+ #feedback-type-list {
56
+ height: auto;
57
+ & > * {
58
+ padding: 1 0;
59
+ }
60
+ }
61
+
62
+ #feedback-description {
63
+ margin: 1 0;
64
+ height: 10;
65
+ border: solid $border;
66
+ }
67
+
68
+ #feedback-actions {
69
+ padding: 1;
70
+ }
71
+
72
+ #feedback-actions > * {
73
+ margin-right: 2;
74
+ }
75
+
76
+ #feedback-type-list {
77
+ padding: 1;
78
+ }
79
+ """
80
+
81
+ BINDINGS = [
82
+ ("escape", "cancel", "Cancel"),
83
+ ]
84
+
85
+ selected_kind: reactive[FeedbackKind] = reactive(FeedbackKind.BUG)
86
+
87
+ def compose(self) -> ComposeResult:
88
+ with Vertical(id="titlebox"):
89
+ yield Static("Send us feedback", id="feedback-title")
90
+ yield Static(
91
+ "Select the type of feedback and provide details below.",
92
+ id="feedback-summary",
93
+ )
94
+ yield ListView(*self._build_feedback_type_items(), id="feedback-type-list")
95
+ yield TextArea(
96
+ "",
97
+ id="feedback-description",
98
+ )
99
+ with Horizontal(id="feedback-actions"):
100
+ yield Button("Submit", variant="primary", id="submit")
101
+ yield Button("Cancel \\[ESC]", id="cancel")
102
+
103
+ def on_mount(self) -> None:
104
+ list_view = self.query_one(ListView)
105
+ if list_view.children:
106
+ list_view.index = 0
107
+ self.selected_kind = FeedbackKind.BUG
108
+ text_area = self.query_one("#feedback-description", TextArea)
109
+ text_area.focus()
110
+
111
+ def action_cancel(self) -> None:
112
+ self.dismiss(None)
113
+
114
+ @on(ListView.Highlighted)
115
+ def _on_kind_highlighted(self, event: ListView.Highlighted) -> None:
116
+ kind = self._kind_from_item(event.item)
117
+ if kind:
118
+ self.selected_kind = kind
119
+
120
+ @on(ListView.Selected)
121
+ def _on_kind_selected(self, event: ListView.Selected) -> None:
122
+ kind = self._kind_from_item(event.item)
123
+ if kind:
124
+ self.selected_kind = kind
125
+ self.set_focus(self.query_one("#feedback-description", TextArea))
126
+
127
+ @on(Button.Pressed, "#submit")
128
+ def _on_submit_pressed(self) -> None:
129
+ self._submit_feedback()
130
+
131
+ @on(Button.Pressed, "#cancel")
132
+ def _on_cancel_pressed(self) -> None:
133
+ self.action_cancel()
134
+
135
+ def watch_selected_kind(self, kind: FeedbackKind) -> None:
136
+ if not self.is_mounted:
137
+ return
138
+ # Update the placeholder in text area based on selected kind
139
+ text_area = self.query_one("#feedback-description", TextArea)
140
+ text_area.placeholder = self._placeholder_for_kind(kind)
141
+
142
+ def _build_feedback_type_items(self) -> list[ListItem]:
143
+ items: list[ListItem] = []
144
+ for kind in FeedbackKind:
145
+ label = Label(self._kind_label(kind), id=f"label-{kind.value}")
146
+ items.append(ListItem(label, id=f"kind-{kind.value}"))
147
+ return items
148
+
149
+ def _kind_from_item(self, item: ListItem | None) -> FeedbackKind | None:
150
+ if item is None or item.id is None:
151
+ return None
152
+ kind_id = item.id.removeprefix("kind-")
153
+ try:
154
+ return FeedbackKind(kind_id)
155
+ except ValueError:
156
+ return None
157
+
158
+ def _kind_label(self, kind: FeedbackKind) -> str:
159
+ display_names = {
160
+ FeedbackKind.BUG: "Bug Report",
161
+ FeedbackKind.FEATURE: "Feature Request",
162
+ FeedbackKind.OTHER: "Other",
163
+ }
164
+ return display_names.get(kind, kind.value.title())
165
+
166
+ def _placeholder_for_kind(self, kind: FeedbackKind) -> str:
167
+ placeholders = {
168
+ FeedbackKind.BUG: "Describe the bug you encountered...",
169
+ FeedbackKind.FEATURE: "Describe the feature you'd like to see...",
170
+ FeedbackKind.OTHER: "Tell us what's on your mind...",
171
+ }
172
+ return placeholders.get(kind, "Enter your feedback...")
173
+
174
+ def _submit_feedback(self) -> None:
175
+ text_area = self.query_one("#feedback-description", TextArea)
176
+ description = text_area.text.strip()
177
+
178
+ if not description:
179
+ self.notify(
180
+ "Please enter a description before submitting.", severity="error"
181
+ )
182
+ return
183
+
184
+ app = cast("ShotgunApp", self.app)
185
+ user_id = app.config_manager.get_user_id()
186
+
187
+ feedback = Feedback(
188
+ kind=self.selected_kind,
189
+ description=description,
190
+ user_id=user_id,
191
+ )
192
+
193
+ self.dismiss(feedback)