shotgun-sh 0.2.1.dev2__tar.gz → 0.2.1.dev4__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.dev2 → shotgun_sh-0.2.1.dev4}/PKG-INFO +1 -1
  2. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/pyproject.toml +1 -1
  3. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/config/manager.py +24 -8
  4. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/config/provider.py +4 -3
  5. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/cli/config.py +16 -4
  6. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/app.py +5 -3
  7. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/screens/chat.py +2 -8
  8. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/screens/chat_screen/command_providers.py +99 -12
  9. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/screens/chat_screen/history.py +3 -1
  10. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/screens/model_picker.py +136 -24
  11. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/screens/provider_config.py +23 -5
  12. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/utils/env_utils.py +12 -0
  13. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/.gitignore +0 -0
  14. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/LICENSE +0 -0
  15. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/README.md +0 -0
  16. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/hatch_build.py +0 -0
  17. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/__init__.py +0 -0
  18. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/__init__.py +0 -0
  19. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/agent_manager.py +0 -0
  20. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/common.py +0 -0
  21. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/config/__init__.py +0 -0
  22. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/config/constants.py +0 -0
  23. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/config/models.py +0 -0
  24. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/conversation_history.py +0 -0
  25. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/conversation_manager.py +0 -0
  26. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/export.py +0 -0
  27. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/history/__init__.py +0 -0
  28. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/history/compaction.py +0 -0
  29. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/history/constants.py +0 -0
  30. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/history/context_extraction.py +0 -0
  31. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/history/history_building.py +0 -0
  32. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/history/history_processors.py +0 -0
  33. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/history/message_utils.py +0 -0
  34. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/history/token_counting/__init__.py +0 -0
  35. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/history/token_counting/anthropic.py +0 -0
  36. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/history/token_counting/base.py +0 -0
  37. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/history/token_counting/openai.py +0 -0
  38. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/history/token_counting/sentencepiece_counter.py +0 -0
  39. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/history/token_counting/tokenizer_cache.py +0 -0
  40. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/history/token_counting/utils.py +0 -0
  41. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/history/token_estimation.py +0 -0
  42. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/llm.py +0 -0
  43. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/messages.py +0 -0
  44. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/models.py +0 -0
  45. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/plan.py +0 -0
  46. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/research.py +0 -0
  47. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/specify.py +0 -0
  48. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/tasks.py +0 -0
  49. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/tools/__init__.py +0 -0
  50. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
  51. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
  52. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
  53. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
  54. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/tools/codebase/models.py +0 -0
  55. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
  56. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
  57. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/tools/file_management.py +0 -0
  58. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/tools/user_interaction.py +0 -0
  59. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
  60. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
  61. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
  62. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/tools/web_search/openai.py +0 -0
  63. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/tools/web_search/utils.py +0 -0
  64. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/agents/usage_manager.py +0 -0
  65. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/build_constants.py +0 -0
  66. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/cli/__init__.py +0 -0
  67. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/cli/codebase/__init__.py +0 -0
  68. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/cli/codebase/commands.py +0 -0
  69. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/cli/codebase/models.py +0 -0
  70. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/cli/export.py +0 -0
  71. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/cli/feedback.py +0 -0
  72. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/cli/models.py +0 -0
  73. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/cli/plan.py +0 -0
  74. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/cli/research.py +0 -0
  75. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/cli/specify.py +0 -0
  76. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/cli/tasks.py +0 -0
  77. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/cli/update.py +0 -0
  78. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/cli/utils.py +0 -0
  79. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/codebase/__init__.py +0 -0
  80. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/codebase/core/__init__.py +0 -0
  81. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/codebase/core/change_detector.py +0 -0
  82. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/codebase/core/code_retrieval.py +0 -0
  83. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/codebase/core/cypher_models.py +0 -0
  84. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/codebase/core/ingestor.py +0 -0
  85. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/codebase/core/language_config.py +0 -0
  86. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/codebase/core/manager.py +0 -0
  87. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/codebase/core/nl_query.py +0 -0
  88. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/codebase/core/parser_loader.py +0 -0
  89. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/codebase/models.py +0 -0
  90. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/codebase/service.py +0 -0
  91. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/llm_proxy/__init__.py +0 -0
  92. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/llm_proxy/clients.py +0 -0
  93. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/llm_proxy/constants.py +0 -0
  94. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/logging_config.py +0 -0
  95. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/main.py +0 -0
  96. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/posthog_telemetry.py +0 -0
  97. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/__init__.py +0 -0
  98. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/agents/__init__.py +0 -0
  99. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/agents/export.j2 +0 -0
  100. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
  101. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
  102. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
  103. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
  104. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/agents/plan.j2 +0 -0
  105. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/agents/research.j2 +0 -0
  106. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/agents/specify.j2 +0 -0
  107. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
  108. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
  109. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/agents/tasks.j2 +0 -0
  110. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/codebase/__init__.py +0 -0
  111. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
  112. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
  113. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
  114. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
  115. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
  116. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
  117. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/history/__init__.py +0 -0
  118. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
  119. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/history/summarization.j2 +0 -0
  120. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/prompts/loader.py +0 -0
  121. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/py.typed +0 -0
  122. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/sdk/__init__.py +0 -0
  123. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/sdk/codebase.py +0 -0
  124. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/sdk/exceptions.py +0 -0
  125. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/sdk/models.py +0 -0
  126. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/sdk/services.py +0 -0
  127. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/sentry_telemetry.py +0 -0
  128. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/telemetry.py +0 -0
  129. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/__init__.py +0 -0
  130. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/commands/__init__.py +0 -0
  131. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/components/prompt_input.py +0 -0
  132. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/components/spinner.py +0 -0
  133. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/components/splash.py +0 -0
  134. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/components/vertical_tail.py +0 -0
  135. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/filtered_codebase_service.py +0 -0
  136. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/screens/chat.tcss +0 -0
  137. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
  138. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
  139. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/screens/directory_setup.py +0 -0
  140. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/screens/feedback.py +0 -0
  141. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/screens/splash.py +0 -0
  142. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/styles.tcss +0 -0
  143. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/utils/__init__.py +0 -0
  144. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/tui/utils/mode_progress.py +0 -0
  145. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/utils/__init__.py +0 -0
  146. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/utils/file_system_utils.py +0 -0
  147. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/src/shotgun/utils/source_detection.py +0 -0
  148. {shotgun_sh-0.2.1.dev2 → shotgun_sh-0.2.1.dev4}/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.dev2
3
+ Version: 0.2.1.dev4
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.2.1.dev2"
3
+ version = "0.2.1.dev4"
4
4
  description = "AI-powered research, planning, and task management CLI tool"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -46,13 +46,16 @@ class ConfigManager:
46
46
 
47
47
  self._config: ShotgunConfig | None = None
48
48
 
49
- def load(self) -> ShotgunConfig:
49
+ def load(self, force_reload: bool = True) -> ShotgunConfig:
50
50
  """Load configuration from file.
51
51
 
52
+ Args:
53
+ force_reload: If True, reload from disk even if cached (default: True)
54
+
52
55
  Returns:
53
56
  ShotgunConfig: Loaded configuration or default config if file doesn't exist
54
57
  """
55
- if self._config is not None:
58
+ if self._config is not None and not force_reload:
56
59
  return self._config
57
60
 
58
61
  if not self.config_path.exists():
@@ -109,7 +112,7 @@ class ConfigManager:
109
112
  # Find default model for this provider
110
113
  provider_models = {
111
114
  ProviderType.OPENAI: ModelName.GPT_5,
112
- ProviderType.ANTHROPIC: ModelName.CLAUDE_OPUS_4_1,
115
+ ProviderType.ANTHROPIC: ModelName.CLAUDE_SONNET_4_5,
113
116
  ProviderType.GOOGLE: ModelName.GEMINI_2_5_PRO,
114
117
  }
115
118
 
@@ -210,7 +213,7 @@ class ConfigManager:
210
213
 
211
214
  provider_models = {
212
215
  ProviderType.OPENAI: ModelName.GPT_5,
213
- ProviderType.ANTHROPIC: ModelName.CLAUDE_OPUS_4_1,
216
+ ProviderType.ANTHROPIC: ModelName.CLAUDE_SONNET_4_5,
214
217
  ProviderType.GOOGLE: ModelName.GEMINI_2_5_PRO,
215
218
  }
216
219
  if provider_enum in provider_models:
@@ -243,7 +246,8 @@ class ConfigManager:
243
246
 
244
247
  This checks only the configuration file.
245
248
  """
246
- config = self.load()
249
+ # Use force_reload=False to avoid infinite loop when called from load()
250
+ config = self.load(force_reload=False)
247
251
  provider_enum = self._ensure_provider_enum(provider)
248
252
  provider_config = self._get_provider_config(config, provider_enum)
249
253
 
@@ -251,7 +255,8 @@ class ConfigManager:
251
255
 
252
256
  def has_any_provider_key(self) -> bool:
253
257
  """Determine whether any provider has a configured API key."""
254
- config = self.load()
258
+ # Use force_reload=False to avoid infinite loop when called from load()
259
+ config = self.load(force_reload=False)
255
260
  # Check LLM provider keys (BYOK)
256
261
  has_llm_key = any(
257
262
  self._provider_has_api_key(self._get_provider_config(config, provider))
@@ -381,6 +386,17 @@ class ConfigManager:
381
386
  return config.user_id
382
387
 
383
388
 
389
+ # Global singleton instance
390
+ _config_manager_instance: ConfigManager | None = None
391
+
392
+
384
393
  def get_config_manager() -> ConfigManager:
385
- """Get the global ConfigManager instance."""
386
- return ConfigManager()
394
+ """Get the global singleton ConfigManager instance.
395
+
396
+ Returns:
397
+ The singleton ConfigManager instance
398
+ """
399
+ global _config_manager_instance
400
+ if _config_manager_instance is None:
401
+ _config_manager_instance = ConfigManager()
402
+ return _config_manager_instance
@@ -139,7 +139,8 @@ def get_provider_model(
139
139
  ValueError: If provider is not configured properly or model not found
140
140
  """
141
141
  config_manager = get_config_manager()
142
- config = config_manager.load()
142
+ # Use cached config for read-only access (performance)
143
+ config = config_manager.load(force_reload=False)
143
144
 
144
145
  # Priority 1: Check if Shotgun key exists - if so, use it for ANY model
145
146
  shotgun_api_key = _get_api_key(config.shotgun.api_key)
@@ -219,8 +220,8 @@ def get_provider_model(
219
220
  if not api_key:
220
221
  raise ValueError("Anthropic API key not configured. Set via config.")
221
222
 
222
- # Use requested model or default to claude-opus-4-1
223
- model_name = requested_model if requested_model else ModelName.CLAUDE_OPUS_4_1
223
+ # Use requested model or default to claude-sonnet-4-5
224
+ model_name = requested_model if requested_model else ModelName.CLAUDE_SONNET_4_5
224
225
  if model_name not in MODEL_SPECS:
225
226
  raise ValueError(f"Model '{model_name.value}' not found")
226
227
  spec = MODEL_SPECS[model_name]
@@ -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"])
@@ -64,7 +64,8 @@ class ShotgunApp(App[None]):
64
64
  return
65
65
 
66
66
  self.push_screen(
67
- "provider_config", callback=lambda _arg: self.refresh_startup_screen()
67
+ ProviderConfigScreen(),
68
+ callback=lambda _arg: self.refresh_startup_screen(),
68
69
  )
69
70
  return
70
71
 
@@ -73,7 +74,8 @@ class ShotgunApp(App[None]):
73
74
  return
74
75
 
75
76
  self.push_screen(
76
- "directory_setup", callback=lambda _arg: self.refresh_startup_screen()
77
+ DirectorySetupScreen(),
78
+ callback=lambda _arg: self.refresh_startup_screen(),
77
79
  )
78
80
  return
79
81
 
@@ -110,7 +112,7 @@ class ShotgunApp(App[None]):
110
112
  submit_feedback_survey(feedback)
111
113
  self.notify("Feedback sent. Thank you!")
112
114
 
113
- self.push_screen("feedback", callback=handle_feedback)
115
+ self.push_screen(FeedbackScreen(), callback=handle_feedback)
114
116
 
115
117
 
116
118
  def run(no_update_check: bool = False, continue_session: bool = False) -> None:
@@ -54,11 +54,8 @@ from ..components.prompt_input import PromptInput
54
54
  from ..components.spinner import Spinner
55
55
  from ..utils.mode_progress import PlaceholderHints
56
56
  from .chat_screen.command_providers import (
57
- AgentModeProvider,
58
- CodebaseCommandProvider,
59
57
  DeleteCodebasePaletteProvider,
60
- ProviderSetupProvider,
61
- UsageProvider,
58
+ UnifiedCommandProvider,
62
59
  )
63
60
 
64
61
  logger = logging.getLogger(__name__)
@@ -233,10 +230,7 @@ class ChatScreen(Screen[None]):
233
230
  ]
234
231
 
235
232
  COMMANDS = {
236
- AgentModeProvider,
237
- ProviderSetupProvider,
238
- CodebaseCommandProvider,
239
- UsageProvider,
233
+ UnifiedCommandProvider,
240
234
  }
241
235
 
242
236
  value = reactive("")
@@ -5,6 +5,8 @@ from textual.command import DiscoveryHit, Hit, Provider
5
5
 
6
6
  from shotgun.agents.models import AgentType
7
7
  from shotgun.codebase.models import CodebaseGraph
8
+ from shotgun.tui.screens.model_picker import ModelPickerScreen
9
+ from shotgun.tui.screens.provider_config import ProviderConfigScreen
8
10
 
9
11
  if TYPE_CHECKING:
10
12
  from shotgun.tui.screens.chat import ChatScreen
@@ -139,11 +141,11 @@ class ProviderSetupProvider(Provider):
139
141
 
140
142
  def open_provider_config(self) -> None:
141
143
  """Show the provider configuration screen."""
142
- self.chat_screen.app.push_screen("provider_config")
144
+ self.chat_screen.app.push_screen(ProviderConfigScreen())
143
145
 
144
146
  def open_model_picker(self) -> None:
145
147
  """Show the model picker screen."""
146
- self.chat_screen.app.push_screen("model_picker")
148
+ self.chat_screen.app.push_screen(ModelPickerScreen())
147
149
 
148
150
  async def discover(self) -> AsyncGenerator[DiscoveryHit, None]:
149
151
  yield DiscoveryHit(
@@ -191,30 +193,30 @@ class CodebaseCommandProvider(Provider):
191
193
  return cast(ChatScreen, self.screen)
192
194
 
193
195
  async def discover(self) -> AsyncGenerator[DiscoveryHit, None]:
194
- yield DiscoveryHit(
195
- "Codebase: Index Codebase",
196
- self.chat_screen.index_codebase_command,
197
- help="Index a repository into the codebase graph",
198
- )
199
196
  yield DiscoveryHit(
200
197
  "Codebase: Delete Codebase Index",
201
198
  self.chat_screen.delete_codebase_command,
202
199
  help="Delete an existing codebase index",
203
200
  )
201
+ yield DiscoveryHit(
202
+ "Codebase: Index Codebase",
203
+ self.chat_screen.index_codebase_command,
204
+ help="Index a repository into the codebase graph",
205
+ )
204
206
 
205
207
  async def search(self, query: str) -> AsyncGenerator[Hit, None]:
206
208
  matcher = self.matcher(query)
207
209
  commands = [
208
- (
209
- "Codebase: Index Codebase",
210
- self.chat_screen.index_codebase_command,
211
- "Index a repository into the codebase graph",
212
- ),
213
210
  (
214
211
  "Codebase: Delete Codebase Index",
215
212
  self.chat_screen.delete_codebase_command,
216
213
  "Delete an existing codebase index",
217
214
  ),
215
+ (
216
+ "Codebase: Index Codebase",
217
+ self.chat_screen.index_codebase_command,
218
+ "Index a repository into the codebase graph",
219
+ ),
218
220
  ]
219
221
  for title, callback, help_text in commands:
220
222
  score = matcher.match(title)
@@ -269,3 +271,88 @@ class DeleteCodebasePaletteProvider(Provider):
269
271
  ),
270
272
  help=graph.repo_path,
271
273
  )
274
+
275
+
276
+ class UnifiedCommandProvider(Provider):
277
+ """Unified command provider with all commands in alphabetical order."""
278
+
279
+ @property
280
+ def chat_screen(self) -> "ChatScreen":
281
+ from shotgun.tui.screens.chat import ChatScreen
282
+
283
+ return cast(ChatScreen, self.screen)
284
+
285
+ def open_provider_config(self) -> None:
286
+ """Show the provider configuration screen."""
287
+ self.chat_screen.app.push_screen(ProviderConfigScreen())
288
+
289
+ def open_model_picker(self) -> None:
290
+ """Show the model picker screen."""
291
+ self.chat_screen.app.push_screen(ModelPickerScreen())
292
+
293
+ async def discover(self) -> AsyncGenerator[DiscoveryHit, None]:
294
+ """Provide commands in alphabetical order when palette opens."""
295
+ # Alphabetically ordered commands
296
+ yield DiscoveryHit(
297
+ "Codebase: Delete Codebase Index",
298
+ self.chat_screen.delete_codebase_command,
299
+ help="Delete an existing codebase index",
300
+ )
301
+ yield DiscoveryHit(
302
+ "Codebase: Index Codebase",
303
+ self.chat_screen.index_codebase_command,
304
+ help="Index a repository into the codebase graph",
305
+ )
306
+ yield DiscoveryHit(
307
+ "Open Provider Setup",
308
+ self.open_provider_config,
309
+ help="⚙️ Manage API keys for available providers",
310
+ )
311
+ yield DiscoveryHit(
312
+ "Select AI Model",
313
+ self.open_model_picker,
314
+ help="🤖 Choose which AI model to use",
315
+ )
316
+ yield DiscoveryHit(
317
+ "Show usage",
318
+ self.chat_screen.action_show_usage,
319
+ help="Display usage information for the current session",
320
+ )
321
+
322
+ async def search(self, query: str) -> AsyncGenerator[Hit, None]:
323
+ """Search for commands in alphabetical order."""
324
+ matcher = self.matcher(query)
325
+
326
+ # Define all commands in alphabetical order
327
+ commands = [
328
+ (
329
+ "Codebase: Delete Codebase Index",
330
+ self.chat_screen.delete_codebase_command,
331
+ "Delete an existing codebase index",
332
+ ),
333
+ (
334
+ "Codebase: Index Codebase",
335
+ self.chat_screen.index_codebase_command,
336
+ "Index a repository into the codebase graph",
337
+ ),
338
+ (
339
+ "Open Provider Setup",
340
+ self.open_provider_config,
341
+ "⚙️ Manage API keys for available providers",
342
+ ),
343
+ (
344
+ "Select AI Model",
345
+ self.open_model_picker,
346
+ "🤖 Choose which AI model to use",
347
+ ),
348
+ (
349
+ "Show usage",
350
+ self.chat_screen.action_show_usage,
351
+ "Display usage information for the current session",
352
+ ),
353
+ ]
354
+
355
+ for title, callback, help_text in commands:
356
+ score = matcher.match(title)
357
+ if score > 0:
358
+ yield Hit(score, matcher.highlight(title), callback, help=help_text)
@@ -217,7 +217,9 @@ class AgentResponseWidget(Widget):
217
217
  return ""
218
218
  for idx, part in enumerate(self.item.parts):
219
219
  if isinstance(part, TextPart):
220
- acc += f"**⏺** {part.content}\n\n"
220
+ # Only show the circle prefix if there's actual content
221
+ if part.content and part.content.strip():
222
+ acc += f"**⏺** {part.content}\n\n"
221
223
  elif isinstance(part, ToolCallPart):
222
224
  parts_str = self._format_tool_call_part(part)
223
225
  acc += parts_str + "\n\n"
@@ -12,11 +12,14 @@ from textual.screen import Screen
12
12
  from textual.widgets import Button, Label, ListItem, ListView, Static
13
13
 
14
14
  from shotgun.agents.config import ConfigManager
15
- from shotgun.agents.config.models import MODEL_SPECS, ModelName
15
+ from shotgun.agents.config.models import MODEL_SPECS, ModelName, ShotgunConfig
16
+ from shotgun.logging_config import get_logger
16
17
 
17
18
  if TYPE_CHECKING:
18
19
  from ..app import ShotgunApp
19
20
 
21
+ logger = get_logger(__name__)
22
+
20
23
 
21
24
  # Available models for selection
22
25
  AVAILABLE_MODELS = list(ModelName)
@@ -35,10 +38,6 @@ class ModelPickerScreen(Screen[None]):
35
38
  layout: vertical;
36
39
  }
37
40
 
38
- ModelPicker > * {
39
- height: auto;
40
- }
41
-
42
41
  #titlebox {
43
42
  height: auto;
44
43
  margin: 2 0;
@@ -60,6 +59,7 @@ class ModelPickerScreen(Screen[None]):
60
59
  #model-list {
61
60
  margin: 2 0;
62
61
  height: auto;
62
+ padding: 1;
63
63
  & > * {
64
64
  padding: 1 0;
65
65
  }
@@ -70,9 +70,6 @@ class ModelPickerScreen(Screen[None]):
70
70
  #model-actions > * {
71
71
  margin-right: 2;
72
72
  }
73
- #model-list {
74
- padding: 1;
75
- }
76
73
  """
77
74
 
78
75
  BINDINGS = [
@@ -88,26 +85,76 @@ class ModelPickerScreen(Screen[None]):
88
85
  "Select the AI model you want to use for your tasks.",
89
86
  id="model-picker-summary",
90
87
  )
91
- yield ListView(*self._build_model_items(), id="model-list")
88
+ yield ListView(id="model-list")
92
89
  with Horizontal(id="model-actions"):
93
90
  yield Button("Select \\[ENTER]", variant="primary", id="select")
94
91
  yield Button("Done \\[ESC]", id="done")
95
92
 
96
- def on_mount(self) -> None:
97
- # Load current selection
93
+ def _rebuild_model_list(self) -> None:
94
+ """Rebuild the model list from current config.
95
+
96
+ This method is called both on first show and when screen is resumed
97
+ to ensure the list always reflects the current configuration.
98
+ """
99
+ logger.debug("Rebuilding model list from current config")
100
+
101
+ # Load current config with force_reload to get latest API keys
98
102
  config_manager = self.config_manager
99
- config = config_manager.load()
100
- current_model = config.selected_model or ModelName.CLAUDE_OPUS_4_1
103
+ config = config_manager.load(force_reload=True)
104
+
105
+ # Log provider key status
106
+ logger.debug(
107
+ "Provider keys: openai=%s, anthropic=%s, google=%s, shotgun=%s",
108
+ config_manager._provider_has_api_key(config.openai),
109
+ config_manager._provider_has_api_key(config.anthropic),
110
+ config_manager._provider_has_api_key(config.google),
111
+ config_manager._provider_has_api_key(config.shotgun),
112
+ )
113
+
114
+ current_model = config.selected_model or ModelName.CLAUDE_SONNET_4_5
101
115
  self.selected_model = current_model
116
+ logger.debug("Current selected model: %s", current_model)
102
117
 
103
- # Find and highlight current selection
118
+ # Rebuild the model list with current available models
104
119
  list_view = self.query_one(ListView)
120
+
121
+ # Remove all existing items
122
+ old_count = len(list(list_view.children))
123
+ for child in list(list_view.children):
124
+ child.remove()
125
+ logger.debug("Removed %d existing model items from list", old_count)
126
+
127
+ # Add new items (labels already have correct text including current indicator)
128
+ new_items = self._build_model_items(config)
129
+ for item in new_items:
130
+ list_view.append(item)
131
+ logger.debug("Added %d available model items to list", len(new_items))
132
+
133
+ # Find and highlight current selection (if it's in the filtered list)
105
134
  if list_view.children:
106
- for i, model_name in enumerate(AVAILABLE_MODELS):
107
- if model_name == current_model:
108
- list_view.index = i
109
- break
110
- self.refresh_model_labels()
135
+ for i, child in enumerate(list_view.children):
136
+ if isinstance(child, ListItem) and child.id:
137
+ model_id = child.id.removeprefix("model-")
138
+ # Find the model name
139
+ for model_name in AVAILABLE_MODELS:
140
+ if _sanitize_model_name_for_id(model_name) == model_id:
141
+ if model_name == current_model:
142
+ list_view.index = i
143
+ break
144
+
145
+ def on_show(self) -> None:
146
+ """Rebuild model list when screen is first shown."""
147
+ logger.debug("ModelPickerScreen.on_show() called")
148
+ self._rebuild_model_list()
149
+
150
+ def on_screenresume(self) -> None:
151
+ """Rebuild model list when screen is resumed (subsequent visits).
152
+
153
+ This is called when returning to the screen after it was suspended,
154
+ ensuring the model list reflects any config changes made while away.
155
+ """
156
+ logger.debug("ModelPickerScreen.on_screenresume() called")
157
+ self._rebuild_model_list()
111
158
 
112
159
  def action_done(self) -> None:
113
160
  self.dismiss()
@@ -139,11 +186,20 @@ class ModelPickerScreen(Screen[None]):
139
186
  return app.config_manager
140
187
 
141
188
  def refresh_model_labels(self) -> None:
142
- """Update the list view entries to reflect current selection."""
143
- current_model = (
144
- self.config_manager.load().selected_model or ModelName.CLAUDE_OPUS_4_1
145
- )
189
+ """Update the list view entries to reflect current selection.
190
+
191
+ Note: This method only updates labels for currently displayed models.
192
+ To rebuild the entire list after provider changes, on_show() should be used.
193
+ """
194
+ # Load config once with force_reload
195
+ config = self.config_manager.load(force_reload=True)
196
+ current_model = config.selected_model or ModelName.CLAUDE_SONNET_4_5
197
+
198
+ # Update labels for available models only
146
199
  for model_name in AVAILABLE_MODELS:
200
+ # Pass config to avoid multiple force reloads
201
+ if not self._is_model_available(model_name, config):
202
+ continue
147
203
  label = self.query_one(
148
204
  f"#label-{_sanitize_model_name_for_id(model_name)}", Label
149
205
  )
@@ -151,10 +207,17 @@ class ModelPickerScreen(Screen[None]):
151
207
  self._model_label(model_name, is_current=model_name == current_model)
152
208
  )
153
209
 
154
- def _build_model_items(self) -> list[ListItem]:
210
+ def _build_model_items(self, config: ShotgunConfig | None = None) -> list[ListItem]:
211
+ if config is None:
212
+ config = self.config_manager.load(force_reload=True)
213
+
155
214
  items: list[ListItem] = []
156
215
  current_model = self.selected_model
157
216
  for model_name in AVAILABLE_MODELS:
217
+ # Only add models that are available
218
+ if not self._is_model_available(model_name, config):
219
+ continue
220
+
158
221
  label = Label(
159
222
  self._model_label(model_name, is_current=model_name == current_model),
160
223
  id=f"label-{_sanitize_model_name_for_id(model_name)}",
@@ -165,6 +228,7 @@ class ModelPickerScreen(Screen[None]):
165
228
  return items
166
229
 
167
230
  def _model_from_item(self, item: ListItem | None) -> ModelName | None:
231
+ """Get ModelName from a ListItem."""
168
232
  if item is None or item.id is None:
169
233
  return None
170
234
  sanitized_id = item.id.removeprefix("model-")
@@ -174,6 +238,50 @@ class ModelPickerScreen(Screen[None]):
174
238
  return model_name
175
239
  return None
176
240
 
241
+ def _is_model_available(
242
+ self, model_name: ModelName, config: ShotgunConfig | None = None
243
+ ) -> bool:
244
+ """Check if a model is available based on provider key configuration.
245
+
246
+ A model is available if:
247
+ 1. Shotgun Account key is configured (provides access to all models), OR
248
+ 2. The model's provider has an API key configured (BYOK mode)
249
+
250
+ Args:
251
+ model_name: The model to check availability for
252
+ config: Optional pre-loaded config to avoid multiple reloads
253
+
254
+ Returns:
255
+ True if the model can be used, False otherwise
256
+ """
257
+ if config is None:
258
+ config = self.config_manager.load(force_reload=True)
259
+
260
+ # If Shotgun Account is configured, all models are available
261
+ if self.config_manager._provider_has_api_key(config.shotgun):
262
+ logger.debug("Model %s available (Shotgun Account configured)", model_name)
263
+ return True
264
+
265
+ # In BYOK mode, check if the model's provider has a key
266
+ if model_name not in MODEL_SPECS:
267
+ logger.debug("Model %s not available (not in MODEL_SPECS)", model_name)
268
+ return False
269
+
270
+ spec = MODEL_SPECS[model_name]
271
+ # Check provider key directly using the loaded config to avoid stale cache
272
+ provider_config = self.config_manager._get_provider_config(
273
+ config, spec.provider
274
+ )
275
+ has_key = self.config_manager._provider_has_api_key(provider_config)
276
+ logger.debug(
277
+ "Model %s available=%s (provider=%s, has_key=%s)",
278
+ model_name,
279
+ has_key,
280
+ spec.provider,
281
+ has_key,
282
+ )
283
+ return has_key
284
+
177
285
  def _model_label(self, model_name: ModelName, is_current: bool) -> str:
178
286
  """Generate label for model with specs and current indicator."""
179
287
  if model_name not in MODEL_SPECS:
@@ -188,6 +296,10 @@ class ModelPickerScreen(Screen[None]):
188
296
 
189
297
  label = f"{display_name} · {input_k}K context · {output_k}K output"
190
298
 
299
+ # Add cost indicator for expensive models
300
+ if model_name == ModelName.CLAUDE_OPUS_4_1:
301
+ label += " · Expensive"
302
+
191
303
  if is_current:
192
304
  label += " · Current"
193
305