shotgun-sh 0.2.1.dev1__tar.gz → 0.2.1.dev3__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 (148) hide show
  1. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/PKG-INFO +2 -1
  2. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/pyproject.toml +2 -1
  3. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/agent_manager.py +11 -3
  4. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/config/manager.py +4 -20
  5. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/config/models.py +8 -0
  6. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/config/provider.py +17 -32
  7. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/models.py +7 -0
  8. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/tools/web_search/anthropic.py +8 -10
  9. shotgun_sh-0.2.1.dev3/src/shotgun/agents/usage_manager.py +159 -0
  10. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/cli/config.py +16 -4
  11. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/cli/feedback.py +1 -1
  12. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/app.py +1 -1
  13. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/screens/chat.py +36 -29
  14. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/screens/chat_screen/command_providers.py +127 -10
  15. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/screens/chat_screen/history.py +3 -1
  16. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/screens/model_picker.py +52 -14
  17. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/screens/provider_config.py +25 -6
  18. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/utils/env_utils.py +12 -0
  19. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/.gitignore +0 -0
  20. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/LICENSE +0 -0
  21. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/README.md +0 -0
  22. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/hatch_build.py +0 -0
  23. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/__init__.py +0 -0
  24. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/__init__.py +0 -0
  25. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/common.py +0 -0
  26. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/config/__init__.py +0 -0
  27. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/config/constants.py +0 -0
  28. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/conversation_history.py +0 -0
  29. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/conversation_manager.py +0 -0
  30. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/export.py +0 -0
  31. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/history/__init__.py +0 -0
  32. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/history/compaction.py +0 -0
  33. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/history/constants.py +0 -0
  34. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/history/context_extraction.py +0 -0
  35. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/history/history_building.py +0 -0
  36. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/history/history_processors.py +0 -0
  37. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/history/message_utils.py +0 -0
  38. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/history/token_counting/__init__.py +0 -0
  39. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/history/token_counting/anthropic.py +0 -0
  40. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/history/token_counting/base.py +0 -0
  41. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/history/token_counting/openai.py +0 -0
  42. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/history/token_counting/sentencepiece_counter.py +0 -0
  43. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/history/token_counting/tokenizer_cache.py +0 -0
  44. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/history/token_counting/utils.py +0 -0
  45. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/history/token_estimation.py +0 -0
  46. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/llm.py +0 -0
  47. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/messages.py +0 -0
  48. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/plan.py +0 -0
  49. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/research.py +0 -0
  50. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/specify.py +0 -0
  51. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/tasks.py +0 -0
  52. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/tools/__init__.py +0 -0
  53. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
  54. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
  55. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
  56. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
  57. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/tools/codebase/models.py +0 -0
  58. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
  59. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
  60. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/tools/file_management.py +0 -0
  61. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/tools/user_interaction.py +0 -0
  62. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
  63. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
  64. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/tools/web_search/openai.py +0 -0
  65. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/agents/tools/web_search/utils.py +0 -0
  66. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/build_constants.py +0 -0
  67. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/cli/__init__.py +0 -0
  68. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/cli/codebase/__init__.py +0 -0
  69. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/cli/codebase/commands.py +0 -0
  70. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/cli/codebase/models.py +0 -0
  71. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/cli/export.py +0 -0
  72. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/cli/models.py +0 -0
  73. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/cli/plan.py +0 -0
  74. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/cli/research.py +0 -0
  75. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/cli/specify.py +0 -0
  76. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/cli/tasks.py +0 -0
  77. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/cli/update.py +0 -0
  78. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/cli/utils.py +0 -0
  79. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/codebase/__init__.py +0 -0
  80. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/codebase/core/__init__.py +0 -0
  81. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/codebase/core/change_detector.py +0 -0
  82. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/codebase/core/code_retrieval.py +0 -0
  83. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/codebase/core/cypher_models.py +0 -0
  84. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/codebase/core/ingestor.py +0 -0
  85. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/codebase/core/language_config.py +0 -0
  86. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/codebase/core/manager.py +0 -0
  87. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/codebase/core/nl_query.py +0 -0
  88. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/codebase/core/parser_loader.py +0 -0
  89. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/codebase/models.py +0 -0
  90. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/codebase/service.py +0 -0
  91. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/llm_proxy/__init__.py +0 -0
  92. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/llm_proxy/clients.py +0 -0
  93. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/llm_proxy/constants.py +0 -0
  94. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/logging_config.py +0 -0
  95. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/main.py +0 -0
  96. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/posthog_telemetry.py +0 -0
  97. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/__init__.py +0 -0
  98. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/agents/__init__.py +0 -0
  99. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/agents/export.j2 +0 -0
  100. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
  101. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
  102. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
  103. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
  104. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/agents/plan.j2 +0 -0
  105. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/agents/research.j2 +0 -0
  106. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/agents/specify.j2 +0 -0
  107. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
  108. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
  109. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/agents/tasks.j2 +0 -0
  110. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/codebase/__init__.py +0 -0
  111. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
  112. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
  113. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
  114. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
  115. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
  116. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
  117. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/history/__init__.py +0 -0
  118. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
  119. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/history/summarization.j2 +0 -0
  120. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/prompts/loader.py +0 -0
  121. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/py.typed +0 -0
  122. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/sdk/__init__.py +0 -0
  123. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/sdk/codebase.py +0 -0
  124. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/sdk/exceptions.py +0 -0
  125. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/sdk/models.py +0 -0
  126. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/sdk/services.py +0 -0
  127. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/sentry_telemetry.py +0 -0
  128. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/telemetry.py +0 -0
  129. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/__init__.py +0 -0
  130. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/commands/__init__.py +0 -0
  131. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/components/prompt_input.py +0 -0
  132. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/components/spinner.py +0 -0
  133. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/components/splash.py +0 -0
  134. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/components/vertical_tail.py +0 -0
  135. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/filtered_codebase_service.py +0 -0
  136. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/screens/chat.tcss +0 -0
  137. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
  138. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
  139. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/screens/directory_setup.py +0 -0
  140. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/screens/feedback.py +0 -0
  141. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/screens/splash.py +0 -0
  142. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/styles.tcss +0 -0
  143. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/utils/__init__.py +0 -0
  144. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/tui/utils/mode_progress.py +0 -0
  145. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/utils/__init__.py +0 -0
  146. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/utils/file_system_utils.py +0 -0
  147. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/src/shotgun/utils/source_detection.py +0 -0
  148. {shotgun_sh-0.2.1.dev1 → shotgun_sh-0.2.1.dev3}/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.2.1.dev1
3
+ Version: 0.2.1.dev3
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
@@ -22,6 +22,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
22
  Classifier: Topic :: Utilities
23
23
  Requires-Python: >=3.11
24
24
  Requires-Dist: anthropic>=0.39.0
25
+ Requires-Dist: genai-prices>=0.0.27
25
26
  Requires-Dist: httpx>=0.27.0
26
27
  Requires-Dist: jinja2>=3.1.0
27
28
  Requires-Dist: kuzu>=0.7.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "shotgun-sh"
3
- version = "0.2.1.dev1"
3
+ version = "0.2.1.dev3"
4
4
  description = "AI-powered research, planning, and task management CLI tool"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -45,6 +45,7 @@ dependencies = [
45
45
  "tiktoken>=0.7.0",
46
46
  "sentencepiece>=0.2.0",
47
47
  "packaging>=23.0",
48
+ "genai-prices>=0.0.27",
48
49
  ]
49
50
 
50
51
  [project.urls]
@@ -115,12 +115,12 @@ class AgentManager(Widget):
115
115
  super().__init__()
116
116
  self.display = False
117
117
 
118
+ if deps is None:
119
+ raise ValueError("AgentDeps must be provided to AgentManager")
120
+
118
121
  # Use provided deps or create default with interactive mode
119
122
  self.deps = deps
120
123
 
121
- if self.deps is None:
122
- raise ValueError("AgentDeps must be provided to AgentManager")
123
-
124
124
  # Create AgentRuntimeOptions from deps for agent creation
125
125
  agent_runtime_options = AgentRuntimeOptions(
126
126
  interactive_mode=self.deps.interactive_mode,
@@ -269,6 +269,7 @@ class AgentManager(Widget):
269
269
  Returns:
270
270
  The agent run result.
271
271
  """
272
+ logger.info(f"Running agent {self._current_agent_type.value}")
272
273
  # Use merged deps (shared state + agent-specific system prompt) if not provided
273
274
  if deps is None:
274
275
  deps = self._create_merged_deps(self._current_agent_type)
@@ -395,6 +396,10 @@ class AgentManager(Widget):
395
396
  # Apply compaction to persistent message history to prevent cascading growth
396
397
  all_messages = result.all_messages()
397
398
  self.message_history = await apply_persistent_compaction(all_messages, deps)
399
+ usage = result.usage()
400
+ deps.usage_manager.add_usage(
401
+ usage, model_name=deps.llm_model.name, provider=deps.llm_model.provider
402
+ )
398
403
 
399
404
  # Log file operations summary if any files were modified
400
405
  file_operations = deps.file_tracker.operations.copy()
@@ -641,6 +646,9 @@ class AgentManager(Widget):
641
646
  filtered_messages.append(msg)
642
647
  return filtered_messages
643
648
 
649
+ def get_usage_hint(self) -> str | None:
650
+ return self.deps.usage_manager.build_usage_hint()
651
+
644
652
  def get_conversation_state(self) -> "ConversationState":
645
653
  """Get the current conversation state.
646
654
 
@@ -1,7 +1,6 @@
1
1
  """Configuration manager for Shotgun CLI."""
2
2
 
3
3
  import json
4
- import os
5
4
  import uuid
6
5
  from pathlib import Path
7
6
  from typing import Any
@@ -12,10 +11,7 @@ from shotgun.logging_config import get_logger
12
11
  from shotgun.utils import get_shotgun_home
13
12
 
14
13
  from .constants import (
15
- ANTHROPIC_API_KEY_ENV,
16
14
  API_KEY_FIELD,
17
- GEMINI_API_KEY_ENV,
18
- OPENAI_API_KEY_ENV,
19
15
  ConfigSection,
20
16
  )
21
17
  from .models import (
@@ -113,7 +109,7 @@ class ConfigManager:
113
109
  # Find default model for this provider
114
110
  provider_models = {
115
111
  ProviderType.OPENAI: ModelName.GPT_5,
116
- ProviderType.ANTHROPIC: ModelName.CLAUDE_OPUS_4_1,
112
+ ProviderType.ANTHROPIC: ModelName.CLAUDE_SONNET_4_5,
117
113
  ProviderType.GOOGLE: ModelName.GEMINI_2_5_PRO,
118
114
  }
119
115
 
@@ -214,7 +210,7 @@ class ConfigManager:
214
210
 
215
211
  provider_models = {
216
212
  ProviderType.OPENAI: ModelName.GPT_5,
217
- ProviderType.ANTHROPIC: ModelName.CLAUDE_OPUS_4_1,
213
+ ProviderType.ANTHROPIC: ModelName.CLAUDE_SONNET_4_5,
218
214
  ProviderType.GOOGLE: ModelName.GEMINI_2_5_PRO,
219
215
  }
220
216
  if provider_enum in provider_models:
@@ -245,25 +241,13 @@ class ConfigManager:
245
241
  def has_provider_key(self, provider: ProviderType | str) -> bool:
246
242
  """Check if the given provider has a non-empty API key configured.
247
243
 
248
- This checks both the configuration file and environment variables.
244
+ This checks only the configuration file.
249
245
  """
250
246
  config = self.load()
251
247
  provider_enum = self._ensure_provider_enum(provider)
252
248
  provider_config = self._get_provider_config(config, provider_enum)
253
249
 
254
- # Check config first
255
- if self._provider_has_api_key(provider_config):
256
- return True
257
-
258
- # Check environment variable
259
- if provider_enum == ProviderType.OPENAI:
260
- return bool(os.getenv(OPENAI_API_KEY_ENV))
261
- elif provider_enum == ProviderType.ANTHROPIC:
262
- return bool(os.getenv(ANTHROPIC_API_KEY_ENV))
263
- elif provider_enum == ProviderType.GOOGLE:
264
- return bool(os.getenv(GEMINI_API_KEY_ENV))
265
-
266
- return False
250
+ return self._provider_has_api_key(provider_config)
267
251
 
268
252
  def has_any_provider_key(self) -> bool:
269
253
  """Determine whether any provider has a configured API key."""
@@ -27,6 +27,7 @@ class ModelName(StrEnum):
27
27
  GPT_5 = "gpt-5"
28
28
  GPT_5_MINI = "gpt-5-mini"
29
29
  CLAUDE_OPUS_4_1 = "claude-opus-4-1"
30
+ CLAUDE_SONNET_4_5 = "claude-sonnet-4-5"
30
31
  GEMINI_2_5_PRO = "gemini-2.5-pro"
31
32
  GEMINI_2_5_FLASH = "gemini-2.5-flash"
32
33
 
@@ -102,6 +103,13 @@ MODEL_SPECS: dict[ModelName, ModelSpec] = {
102
103
  max_output_tokens=32_000,
103
104
  litellm_proxy_model_name="anthropic/claude-opus-4-1",
104
105
  ),
106
+ ModelName.CLAUDE_SONNET_4_5: ModelSpec(
107
+ name=ModelName.CLAUDE_SONNET_4_5,
108
+ provider=ProviderType.ANTHROPIC,
109
+ max_input_tokens=200_000,
110
+ max_output_tokens=16_000,
111
+ litellm_proxy_model_name="anthropic/claude-sonnet-4-5",
112
+ ),
105
113
  ModelName.GEMINI_2_5_PRO: ModelSpec(
106
114
  name=ModelName.GEMINI_2_5_PRO,
107
115
  provider=ProviderType.GOOGLE,
@@ -1,7 +1,5 @@
1
1
  """Provider management for LLM configuration."""
2
2
 
3
- import os
4
-
5
3
  from pydantic import SecretStr
6
4
  from pydantic_ai.models import Model
7
5
  from pydantic_ai.models.anthropic import AnthropicModel, AnthropicModelSettings
@@ -15,12 +13,6 @@ from pydantic_ai.settings import ModelSettings
15
13
  from shotgun.llm_proxy import create_litellm_provider
16
14
  from shotgun.logging_config import get_logger
17
15
 
18
- from .constants import (
19
- ANTHROPIC_API_KEY_ENV,
20
- GEMINI_API_KEY_ENV,
21
- OPENAI_API_KEY_ENV,
22
- SHOTGUN_API_KEY_ENV,
23
- )
24
16
  from .manager import get_config_manager
25
17
  from .models import (
26
18
  MODEL_SPECS,
@@ -150,10 +142,10 @@ def get_provider_model(
150
142
  config = config_manager.load()
151
143
 
152
144
  # Priority 1: Check if Shotgun key exists - if so, use it for ANY model
153
- shotgun_api_key = _get_api_key(config.shotgun.api_key, SHOTGUN_API_KEY_ENV)
145
+ shotgun_api_key = _get_api_key(config.shotgun.api_key)
154
146
  if shotgun_api_key:
155
- # Use selected model or default to claude-opus-4-1
156
- model_name = config.selected_model or ModelName.CLAUDE_OPUS_4_1
147
+ # Use selected model or default to claude-sonnet-4-5
148
+ model_name = config.selected_model or ModelName.CLAUDE_SONNET_4_5
157
149
  if model_name not in MODEL_SPECS:
158
150
  raise ValueError(f"Model '{model_name.value}' not found")
159
151
  spec = MODEL_SPECS[model_name]
@@ -202,11 +194,9 @@ def get_provider_model(
202
194
  requested_model = None # Will use provider's default model
203
195
 
204
196
  if provider_enum == ProviderType.OPENAI:
205
- api_key = _get_api_key(config.openai.api_key, OPENAI_API_KEY_ENV)
197
+ api_key = _get_api_key(config.openai.api_key)
206
198
  if not api_key:
207
- raise ValueError(
208
- f"OpenAI API key not configured. Set via environment variable {OPENAI_API_KEY_ENV} or config."
209
- )
199
+ raise ValueError("OpenAI API key not configured. Set via config.")
210
200
 
211
201
  # Use requested model or default to gpt-5
212
202
  model_name = requested_model if requested_model else ModelName.GPT_5
@@ -225,14 +215,12 @@ def get_provider_model(
225
215
  )
226
216
 
227
217
  elif provider_enum == ProviderType.ANTHROPIC:
228
- api_key = _get_api_key(config.anthropic.api_key, ANTHROPIC_API_KEY_ENV)
218
+ api_key = _get_api_key(config.anthropic.api_key)
229
219
  if not api_key:
230
- raise ValueError(
231
- f"Anthropic API key not configured. Set via environment variable {ANTHROPIC_API_KEY_ENV} or config."
232
- )
220
+ raise ValueError("Anthropic API key not configured. Set via config.")
233
221
 
234
- # Use requested model or default to claude-opus-4-1
235
- model_name = requested_model if requested_model else ModelName.CLAUDE_OPUS_4_1
222
+ # Use requested model or default to claude-sonnet-4-5
223
+ model_name = requested_model if requested_model else ModelName.CLAUDE_SONNET_4_5
236
224
  if model_name not in MODEL_SPECS:
237
225
  raise ValueError(f"Model '{model_name.value}' not found")
238
226
  spec = MODEL_SPECS[model_name]
@@ -248,11 +236,9 @@ def get_provider_model(
248
236
  )
249
237
 
250
238
  elif provider_enum == ProviderType.GOOGLE:
251
- api_key = _get_api_key(config.google.api_key, GEMINI_API_KEY_ENV)
239
+ api_key = _get_api_key(config.google.api_key)
252
240
  if not api_key:
253
- raise ValueError(
254
- f"Gemini API key not configured. Set via environment variable {GEMINI_API_KEY_ENV} or config."
255
- )
241
+ raise ValueError("Gemini API key not configured. Set via config.")
256
242
 
257
243
  # Use requested model or default to gemini-2.5-pro
258
244
  model_name = requested_model if requested_model else ModelName.GEMINI_2_5_PRO
@@ -285,20 +271,19 @@ def _has_provider_key(config: "ShotgunConfig", provider: ProviderType) -> bool:
285
271
  True if provider has a configured API key
286
272
  """
287
273
  if provider == ProviderType.OPENAI:
288
- return bool(_get_api_key(config.openai.api_key, OPENAI_API_KEY_ENV))
274
+ return bool(_get_api_key(config.openai.api_key))
289
275
  elif provider == ProviderType.ANTHROPIC:
290
- return bool(_get_api_key(config.anthropic.api_key, ANTHROPIC_API_KEY_ENV))
276
+ return bool(_get_api_key(config.anthropic.api_key))
291
277
  elif provider == ProviderType.GOOGLE:
292
- return bool(_get_api_key(config.google.api_key, GEMINI_API_KEY_ENV))
278
+ return bool(_get_api_key(config.google.api_key))
293
279
  return False
294
280
 
295
281
 
296
- def _get_api_key(config_key: SecretStr | None, env_var: str) -> str | None:
297
- """Get API key from config or environment variable.
282
+ def _get_api_key(config_key: SecretStr | None) -> str | None:
283
+ """Get API key from config.
298
284
 
299
285
  Args:
300
286
  config_key: API key from configuration
301
- env_var: Environment variable name to check
302
287
 
303
288
  Returns:
304
289
  API key string or None
@@ -306,4 +291,4 @@ def _get_api_key(config_key: SecretStr | None, env_var: str) -> str | None:
306
291
  if config_key is not None:
307
292
  return config_key.get_secret_value()
308
293
 
309
- return os.getenv(env_var)
294
+ return None
@@ -11,6 +11,8 @@ from typing import TYPE_CHECKING
11
11
  from pydantic import BaseModel, ConfigDict, Field
12
12
  from pydantic_ai import RunContext
13
13
 
14
+ from shotgun.agents.usage_manager import SessionUsageManager, get_session_usage_manager
15
+
14
16
  from .config.models import ModelConfig
15
17
 
16
18
  if TYPE_CHECKING:
@@ -108,6 +110,11 @@ class AgentRuntimeOptions(BaseModel):
108
110
  description="Tasks for storing deferred tool results",
109
111
  )
110
112
 
113
+ usage_manager: SessionUsageManager = Field(
114
+ default_factory=get_session_usage_manager,
115
+ description="Usage manager for tracking usage",
116
+ )
117
+
111
118
 
112
119
  class FileOperationType(StrEnum):
113
120
  """Types of file operations that can be tracked."""
@@ -94,7 +94,6 @@ async def anthropic_web_search_tool(query: str) -> str:
94
94
 
95
95
  async def main() -> None:
96
96
  """Main function for testing the Anthropic web search tool."""
97
- import os
98
97
  import sys
99
98
 
100
99
  from shotgun.logging_config import setup_logger
@@ -119,15 +118,14 @@ async def main() -> None:
119
118
  print("=" * 60)
120
119
 
121
120
  # Check if API key is available
122
- if not (
123
- os.getenv("ANTHROPIC_API_KEY")
124
- or (
125
- callable(get_provider_model)
126
- and get_provider_model(ProviderType.ANTHROPIC).api_key
127
- )
128
- ):
129
- print(" Error: ANTHROPIC_API_KEY environment variable not set")
130
- print(" Please set it with: export ANTHROPIC_API_KEY=your_key_here")
121
+ try:
122
+ if callable(get_provider_model):
123
+ model_config = get_provider_model(ProviderType.ANTHROPIC)
124
+ if not model_config.api_key:
125
+ raise ValueError("No API key configured")
126
+ except (ValueError, Exception):
127
+ print("❌ Error: Anthropic API key not configured")
128
+ print(" Please set it in your config file")
131
129
  sys.exit(1)
132
130
 
133
131
  try:
@@ -0,0 +1,159 @@
1
+ import json
2
+ from collections import defaultdict
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
+ from logging import getLogger
6
+ from pathlib import Path
7
+ from typing import TypeAlias
8
+
9
+ from genai_prices import calc_price
10
+ from pydantic import BaseModel, Field
11
+ from pydantic_ai import RunUsage
12
+
13
+ from shotgun.agents.config.models import ProviderType
14
+ from shotgun.utils import get_shotgun_home
15
+
16
+ logger = getLogger(__name__)
17
+ ModelName: TypeAlias = str
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class UsageSummaryEntry:
22
+ model_name: ModelName
23
+ provider: ProviderType
24
+ usage: RunUsage
25
+
26
+
27
+ class UsageLogEntry(BaseModel):
28
+ timestamp: datetime = Field(default_factory=datetime.now)
29
+ model_name: ModelName
30
+ usage: RunUsage
31
+ provider: ProviderType
32
+
33
+
34
+ class SessionUsage(BaseModel):
35
+ usage: RunUsage
36
+ log: list[UsageLogEntry]
37
+
38
+
39
+ class UsageState(BaseModel):
40
+ usage: dict[ModelName, RunUsage] = Field(default_factory=dict)
41
+ model_providers: dict[ModelName, ProviderType] = Field(default_factory=dict)
42
+ usage_log: list[UsageLogEntry] = Field(default_factory=list)
43
+
44
+
45
+ class SessionUsageManager:
46
+ def __init__(self) -> None:
47
+ self.usage: defaultdict[ModelName, RunUsage] = defaultdict(RunUsage)
48
+ self._model_providers: dict[ModelName, ProviderType] = {}
49
+ self._usage_log: list[UsageLogEntry] = []
50
+ self._usage_path: Path = get_shotgun_home() / "usage.json"
51
+ self.restore_usage_state()
52
+
53
+ def add_usage(
54
+ self, usage: RunUsage, *, model_name: ModelName, provider: ProviderType
55
+ ) -> None:
56
+ self.usage[model_name] += usage
57
+ self._model_providers[model_name] = provider
58
+ self._usage_log.append(
59
+ UsageLogEntry(model_name=model_name, usage=usage, provider=provider)
60
+ )
61
+ self.persist_usage_state()
62
+
63
+ def get_usage_report(self) -> dict[ModelName, RunUsage]:
64
+ return self.usage.copy()
65
+
66
+ def get_usage_breakdown(self) -> list[UsageSummaryEntry]:
67
+ breakdown: list[UsageSummaryEntry] = []
68
+ for model_name, usage in self.usage.items():
69
+ provider = self._model_providers.get(model_name)
70
+ if provider is None:
71
+ continue
72
+ breakdown.append(
73
+ UsageSummaryEntry(model_name=model_name, provider=provider, usage=usage)
74
+ )
75
+ breakdown.sort(key=lambda entry: entry.model_name.lower())
76
+ return breakdown
77
+
78
+ def build_usage_hint(self) -> str | None:
79
+ return format_usage_hint(self.get_usage_breakdown())
80
+
81
+ def persist_usage_state(self) -> None:
82
+ state = UsageState(
83
+ usage=dict(self.usage.items()),
84
+ model_providers=self._model_providers.copy(),
85
+ usage_log=self._usage_log.copy(),
86
+ )
87
+
88
+ try:
89
+ self._usage_path.parent.mkdir(parents=True, exist_ok=True)
90
+ with self._usage_path.open("w", encoding="utf-8") as f:
91
+ json.dump(state.model_dump(mode="json"), f, indent=2)
92
+ logger.debug("Usage state persisted to %s", self._usage_path)
93
+ except Exception as exc:
94
+ logger.error(
95
+ "Failed to persist usage state to %s: %s", self._usage_path, exc
96
+ )
97
+
98
+ def restore_usage_state(self) -> None:
99
+ if not self._usage_path.exists():
100
+ logger.debug("No usage state file found at %s", self._usage_path)
101
+ return
102
+
103
+ try:
104
+ with self._usage_path.open(encoding="utf-8") as f:
105
+ data = json.load(f)
106
+
107
+ state = UsageState.model_validate(data)
108
+ except Exception as exc:
109
+ logger.error(
110
+ "Failed to restore usage state from %s: %s", self._usage_path, exc
111
+ )
112
+ return
113
+
114
+ self.usage = defaultdict(RunUsage)
115
+ for model_name, usage in state.usage.items():
116
+ self.usage[model_name] = usage
117
+
118
+ self._model_providers = state.model_providers.copy()
119
+ self._usage_log = state.usage_log.copy()
120
+
121
+
122
+ def format_usage_hint(breakdown: list[UsageSummaryEntry]) -> str | None:
123
+ if not breakdown:
124
+ return None
125
+
126
+ lines = ["# Token usage by model"]
127
+
128
+ for entry in breakdown:
129
+ usage = entry.usage
130
+ input_tokens = usage.input_tokens
131
+ output_tokens = usage.output_tokens
132
+ cached_tokens = usage.cache_read_tokens
133
+
134
+ cost = calc_price(usage=usage, model_ref=entry.model_name)
135
+ input_line = f"* Input: {input_tokens:,}"
136
+ if cached_tokens > 0:
137
+ input_line += f" (+ {cached_tokens:,} cached)"
138
+ input_line += " tokens"
139
+ section = f"""
140
+ ### {entry.model_name}
141
+
142
+ {input_line}
143
+ * Output: {output_tokens:,} tokens
144
+ * Total: {input_tokens + output_tokens:,} tokens
145
+ * Cost: ${cost.total_price:,.2f}
146
+ """.strip()
147
+ lines.append(section)
148
+
149
+ return "\n\n".join(lines)
150
+
151
+
152
+ _usage_manager = None
153
+
154
+
155
+ def get_session_usage_manager() -> SessionUsageManager:
156
+ global _usage_manager
157
+ if _usage_manager is None:
158
+ _usage_manager = SessionUsageManager()
159
+ return _usage_manager
@@ -9,6 +9,7 @@ from rich.table import Table
9
9
 
10
10
  from shotgun.agents.config import ProviderType, get_config_manager
11
11
  from shotgun.logging_config import get_logger
12
+ from shotgun.utils.env_utils import is_shotgun_account_enabled
12
13
 
13
14
  logger = get_logger(__name__)
14
15
  console = Console()
@@ -162,12 +163,17 @@ def _show_full_config(config: Any) -> None:
162
163
  table.add_row("", "") # Separator
163
164
 
164
165
  # Provider configurations
165
- for provider_name, provider_config in [
166
+ providers_to_show = [
166
167
  ("OpenAI", config.openai),
167
168
  ("Anthropic", config.anthropic),
168
169
  ("Google", config.google),
169
- ("Shotgun Account", config.shotgun),
170
- ]:
170
+ ]
171
+
172
+ # Only show Shotgun Account if feature flag is enabled
173
+ if is_shotgun_account_enabled():
174
+ providers_to_show.append(("Shotgun Account", config.shotgun))
175
+
176
+ for provider_name, provider_config in providers_to_show:
171
177
  table.add_row(f"[bold]{provider_name}[/bold]", "")
172
178
 
173
179
  # API Key
@@ -207,7 +213,13 @@ def _show_provider_config(provider: ProviderType, config: Any) -> None:
207
213
 
208
214
  def _mask_secrets(data: dict[str, Any]) -> None:
209
215
  """Mask secrets in configuration data."""
210
- for provider in ["openai", "anthropic", "google", "shotgun"]:
216
+ providers = ["openai", "anthropic", "google"]
217
+
218
+ # Only mask shotgun if feature flag is enabled
219
+ if is_shotgun_account_enabled():
220
+ providers.append("shotgun")
221
+
222
+ for provider in providers:
211
223
  if provider in data and isinstance(data[provider], dict):
212
224
  if "api_key" in data[provider] and data[provider]["api_key"]:
213
225
  data[provider]["api_key"] = _mask_value(data[provider]["api_key"])
@@ -43,4 +43,4 @@ def send_feedback(
43
43
 
44
44
  submit_feedback_survey(feedback)
45
45
 
46
- console.print("Feedback sent. Thank you!")
46
+ console.print("Feedback sent. Thank you!")
@@ -108,7 +108,7 @@ class ShotgunApp(App[None]):
108
108
  def handle_feedback(feedback: Feedback | None) -> None:
109
109
  if feedback is not None:
110
110
  submit_feedback_survey(feedback)
111
- self.notify("Feedback sent. Thank you!")
111
+ self.notify("Feedback sent. Thank you!")
112
112
 
113
113
  self.push_screen("feedback", callback=handle_feedback)
114
114