shotgun-sh 0.1.11.dev1__tar.gz → 0.1.12.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 (134) hide show
  1. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/PKG-INFO +1 -1
  2. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/pyproject.toml +1 -1
  3. shotgun_sh-0.1.12.dev1/src/shotgun/tui/filtered_codebase_service.py +46 -0
  4. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/screens/chat.py +23 -122
  5. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/screens/chat_screen/history.py +36 -6
  6. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/.gitignore +0 -0
  7. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/LICENSE +0 -0
  8. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/README.md +0 -0
  9. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/hatch_build.py +0 -0
  10. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/__init__.py +0 -0
  11. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/__init__.py +0 -0
  12. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/agent_manager.py +0 -0
  13. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/common.py +0 -0
  14. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/config/__init__.py +0 -0
  15. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/config/constants.py +0 -0
  16. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/config/manager.py +0 -0
  17. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/config/models.py +0 -0
  18. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/config/provider.py +0 -0
  19. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/conversation_history.py +0 -0
  20. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/conversation_manager.py +0 -0
  21. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/export.py +0 -0
  22. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/history/__init__.py +0 -0
  23. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/history/compaction.py +0 -0
  24. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/history/constants.py +0 -0
  25. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/history/context_extraction.py +0 -0
  26. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/history/history_building.py +0 -0
  27. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/history/history_processors.py +0 -0
  28. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/history/message_utils.py +0 -0
  29. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/history/token_counting.py +0 -0
  30. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/history/token_estimation.py +0 -0
  31. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/messages.py +0 -0
  32. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/models.py +0 -0
  33. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/plan.py +0 -0
  34. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/research.py +0 -0
  35. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/specify.py +0 -0
  36. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/tasks.py +0 -0
  37. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/tools/__init__.py +0 -0
  38. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
  39. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
  40. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
  41. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
  42. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/tools/codebase/models.py +0 -0
  43. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
  44. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
  45. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/tools/file_management.py +0 -0
  46. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/tools/user_interaction.py +0 -0
  47. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
  48. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
  49. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
  50. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/tools/web_search/openai.py +0 -0
  51. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/agents/tools/web_search/utils.py +0 -0
  52. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/build_constants.py +0 -0
  53. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/cli/__init__.py +0 -0
  54. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/cli/codebase/__init__.py +0 -0
  55. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/cli/codebase/commands.py +0 -0
  56. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/cli/codebase/models.py +0 -0
  57. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/cli/config.py +0 -0
  58. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/cli/export.py +0 -0
  59. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/cli/models.py +0 -0
  60. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/cli/plan.py +0 -0
  61. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/cli/research.py +0 -0
  62. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/cli/specify.py +0 -0
  63. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/cli/tasks.py +0 -0
  64. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/cli/update.py +0 -0
  65. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/cli/utils.py +0 -0
  66. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/codebase/__init__.py +0 -0
  67. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/codebase/core/__init__.py +0 -0
  68. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/codebase/core/change_detector.py +0 -0
  69. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/codebase/core/code_retrieval.py +0 -0
  70. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/codebase/core/cypher_models.py +0 -0
  71. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/codebase/core/ingestor.py +0 -0
  72. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/codebase/core/language_config.py +0 -0
  73. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/codebase/core/manager.py +0 -0
  74. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/codebase/core/nl_query.py +0 -0
  75. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/codebase/core/parser_loader.py +0 -0
  76. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/codebase/models.py +0 -0
  77. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/codebase/service.py +0 -0
  78. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/logging_config.py +0 -0
  79. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/main.py +0 -0
  80. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/posthog_telemetry.py +0 -0
  81. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/__init__.py +0 -0
  82. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/agents/__init__.py +0 -0
  83. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/agents/export.j2 +0 -0
  84. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
  85. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
  86. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
  87. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
  88. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/agents/plan.j2 +0 -0
  89. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/agents/research.j2 +0 -0
  90. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/agents/specify.j2 +0 -0
  91. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
  92. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
  93. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/agents/tasks.j2 +0 -0
  94. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/codebase/__init__.py +0 -0
  95. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
  96. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
  97. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
  98. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
  99. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
  100. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
  101. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/history/__init__.py +0 -0
  102. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
  103. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/history/summarization.j2 +0 -0
  104. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/prompts/loader.py +0 -0
  105. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/py.typed +0 -0
  106. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/sdk/__init__.py +0 -0
  107. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/sdk/codebase.py +0 -0
  108. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/sdk/exceptions.py +0 -0
  109. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/sdk/models.py +0 -0
  110. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/sdk/services.py +0 -0
  111. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/sentry_telemetry.py +0 -0
  112. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/telemetry.py +0 -0
  113. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/__init__.py +0 -0
  114. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/app.py +0 -0
  115. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/commands/__init__.py +0 -0
  116. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/components/prompt_input.py +0 -0
  117. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/components/spinner.py +0 -0
  118. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/components/splash.py +0 -0
  119. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/components/vertical_tail.py +0 -0
  120. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/screens/chat.tcss +0 -0
  121. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
  122. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/screens/chat_screen/command_providers.py +0 -0
  123. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
  124. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/screens/directory_setup.py +0 -0
  125. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/screens/provider_config.py +0 -0
  126. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/screens/splash.py +0 -0
  127. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/styles.tcss +0 -0
  128. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/utils/__init__.py +0 -0
  129. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/tui/utils/mode_progress.py +0 -0
  130. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/utils/__init__.py +0 -0
  131. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/utils/env_utils.py +0 -0
  132. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/utils/file_system_utils.py +0 -0
  133. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.dev1}/src/shotgun/utils/source_detection.py +0 -0
  134. {shotgun_sh-0.1.11.dev1 → shotgun_sh-0.1.12.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.11.dev1
3
+ Version: 0.1.12.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.11.dev1"
3
+ version = "0.1.12.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
+ """Filtered codebase service that restricts access to current directory's codebase only."""
2
+
3
+ from pathlib import Path
4
+
5
+ from shotgun.codebase.models import CodebaseGraph
6
+ from shotgun.codebase.service import CodebaseService
7
+
8
+
9
+ class FilteredCodebaseService(CodebaseService):
10
+ """CodebaseService subclass that filters graphs to only those accessible from CWD.
11
+
12
+ This ensures TUI agents can only see and access the codebase indexed from the
13
+ current working directory, providing isolation between different project directories.
14
+ """
15
+
16
+ def __init__(self, storage_dir: Path | str):
17
+ """Initialize the filtered service.
18
+
19
+ Args:
20
+ storage_dir: Directory to store graph databases
21
+ """
22
+ super().__init__(storage_dir)
23
+ self._cwd = str(Path.cwd().resolve())
24
+
25
+ async def list_graphs(self) -> list[CodebaseGraph]:
26
+ """List only graphs accessible from the current working directory.
27
+
28
+ Returns:
29
+ Filtered list of CodebaseGraph objects accessible from CWD
30
+ """
31
+ # Use the existing filtering logic from list_graphs_for_directory
32
+ return await super().list_graphs_for_directory(self._cwd)
33
+
34
+ async def list_graphs_for_directory(
35
+ self, directory: Path | str | None = None
36
+ ) -> list[CodebaseGraph]:
37
+ """List graphs for directory - always filters to CWD for TUI context.
38
+
39
+ Args:
40
+ directory: Ignored in TUI context, always uses CWD
41
+
42
+ Returns:
43
+ Filtered list of CodebaseGraph objects accessible from CWD
44
+ """
45
+ # Always use CWD regardless of what directory is passed
46
+ return await super().list_graphs_for_directory(self._cwd)
@@ -1,6 +1,5 @@
1
1
  import asyncio
2
2
  import logging
3
- from collections.abc import Iterable
4
3
  from dataclasses import dataclass
5
4
  from pathlib import Path
6
5
  from typing import cast
@@ -20,7 +19,7 @@ from textual.containers import Container, Grid
20
19
  from textual.reactive import reactive
21
20
  from textual.screen import ModalScreen, Screen
22
21
  from textual.widget import Widget
23
- from textual.widgets import Button, DirectoryTree, Input, Label, Markdown, Static
22
+ from textual.widgets import Button, Label, Markdown, Static
24
23
 
25
24
  from shotgun.agents.agent_manager import (
26
25
  AgentManager,
@@ -44,10 +43,11 @@ from shotgun.codebase.core.manager import CodebaseAlreadyIndexedError
44
43
  from shotgun.posthog_telemetry import track_event
45
44
  from shotgun.sdk.codebase import CodebaseSDK
46
45
  from shotgun.sdk.exceptions import CodebaseNotFoundError, InvalidPathError
47
- from shotgun.sdk.services import get_codebase_service
48
46
  from shotgun.tui.commands import CommandHandler
47
+ from shotgun.tui.filtered_codebase_service import FilteredCodebaseService
49
48
  from shotgun.tui.screens.chat_screen.hint_message import HintMessage
50
49
  from shotgun.tui.screens.chat_screen.history import ChatHistory
50
+ from shotgun.utils import get_shotgun_home
51
51
 
52
52
  from ..components.prompt_input import PromptInput
53
53
  from ..components.spinner import Spinner
@@ -167,11 +167,6 @@ class ModeIndicator(Widget):
167
167
  return f"[bold $text-accent]{mode_title}{status_icon} mode[/][$foreground-muted] ({description})[/]"
168
168
 
169
169
 
170
- class FilteredDirectoryTree(DirectoryTree):
171
- def filter_paths(self, paths: Iterable[Path]) -> Iterable[Path]:
172
- return [path for path in paths if path.is_dir()]
173
-
174
-
175
170
  class CodebaseIndexPromptScreen(ModalScreen[bool]):
176
171
  """Modal dialog asking whether to index the detected codebase."""
177
172
 
@@ -226,105 +221,6 @@ class CodebaseIndexPromptScreen(ModalScreen[bool]):
226
221
  self.dismiss(True)
227
222
 
228
223
 
229
- class CodebaseIndexScreen(ModalScreen[CodebaseIndexSelection | None]):
230
- """Modal dialog for choosing a repository and name to index."""
231
-
232
- DEFAULT_CSS = """
233
- CodebaseIndexScreen {
234
- align: center middle;
235
- background: rgba(0, 0, 0, 0.0);
236
- }
237
- CodebaseIndexScreen > #index-dialog {
238
- width: 80%;
239
- max-width: 80;
240
- height: 80%;
241
- max-height: 40;
242
- border: wide $primary;
243
- padding: 1;
244
- layout: vertical;
245
- background: $surface;
246
- }
247
-
248
- #index-dialog DirectoryTree {
249
- height: 1fr;
250
- border: solid $accent;
251
- overflow: auto;
252
- }
253
-
254
- #index-dialog-controls {
255
- layout: horizontal;
256
- align-horizontal: right;
257
- padding-top: 1;
258
- }
259
- """
260
-
261
- def __init__(self, start_path: Path | None = None) -> None:
262
- super().__init__()
263
- self.start_path = Path(start_path or Path.cwd())
264
- self.selected_path: Path | None = self.start_path
265
-
266
- def compose(self) -> ComposeResult:
267
- with Container(id="index-dialog"):
268
- yield Label("Index a codebase", id="index-dialog-title")
269
- yield FilteredDirectoryTree(self.start_path, id="index-directory-tree")
270
- yield Input(
271
- placeholder="Enter a name for the codebase",
272
- id="index-codebase-name",
273
- )
274
- with Container(id="index-dialog-controls"):
275
- yield Button("Cancel", id="index-cancel")
276
- yield Button(
277
- "Index",
278
- id="index-confirm",
279
- variant="primary",
280
- disabled=True,
281
- )
282
-
283
- def on_mount(self) -> None:
284
- name_input = self.query_one("#index-codebase-name", Input)
285
- if not name_input.value and self.selected_path:
286
- name_input.value = self.selected_path.name
287
- self._update_confirm()
288
-
289
- def _update_confirm(self) -> None:
290
- confirm = self.query_one("#index-confirm", Button)
291
- name_input = self.query_one("#index-codebase-name", Input)
292
- confirm.disabled = not (self.selected_path and name_input.value.strip())
293
-
294
- @on(DirectoryTree.DirectorySelected, "#index-directory-tree")
295
- def handle_directory_selected(self, event: DirectoryTree.DirectorySelected) -> None:
296
- event.stop()
297
- selected = event.path if event.path.is_dir() else event.path.parent
298
- self.selected_path = selected
299
- name_input = self.query_one("#index-codebase-name", Input)
300
- if not name_input.value:
301
- name_input.value = selected.name
302
- self._update_confirm()
303
-
304
- @on(Input.Changed, "#index-codebase-name")
305
- def handle_name_changed(self, event: Input.Changed) -> None:
306
- event.stop()
307
- self._update_confirm()
308
-
309
- @on(Button.Pressed, "#index-cancel")
310
- def handle_cancel(self, event: Button.Pressed) -> None:
311
- event.stop()
312
- self.dismiss(None)
313
-
314
- @on(Button.Pressed, "#index-confirm")
315
- def handle_confirm(self, event: Button.Pressed) -> None:
316
- event.stop()
317
- name_input = self.query_one("#index-codebase-name", Input)
318
- if not self.selected_path:
319
- self.dismiss(None)
320
- return
321
- selection = CodebaseIndexSelection(
322
- repo_path=self.selected_path,
323
- name=name_input.value.strip(),
324
- )
325
- self.dismiss(selection)
326
-
327
-
328
224
  class ChatScreen(Screen[None]):
329
225
  CSS_PATH = "chat.tcss"
330
226
 
@@ -349,7 +245,9 @@ class ChatScreen(Screen[None]):
349
245
  super().__init__()
350
246
  # Get the model configuration and services
351
247
  model_config = get_provider_model()
352
- codebase_service = get_codebase_service()
248
+ # Use filtered service in TUI to restrict access to CWD codebase only
249
+ storage_dir = get_shotgun_home() / "codebases"
250
+ codebase_service = FilteredCodebaseService(storage_dir)
353
251
  self.codebase_sdk = CodebaseSDK()
354
252
 
355
253
  # Create shared deps without system_prompt_fn (agents provide their own)
@@ -423,6 +321,7 @@ class ChatScreen(Screen[None]):
423
321
  self.mount_hint(help_text_with_codebase(already_indexed=True))
424
322
  return
425
323
 
324
+ # Ask user if they want to index the current directory
426
325
  should_index = await self.app.push_screen_wait(CodebaseIndexPromptScreen())
427
326
  if not should_index:
428
327
  self.mount_hint(help_text_empty_dir())
@@ -430,7 +329,10 @@ class ChatScreen(Screen[None]):
430
329
 
431
330
  self.mount_hint(help_text_with_codebase(already_indexed=False))
432
331
 
433
- self.index_codebase_command()
332
+ # Auto-index the current directory with its name
333
+ cwd_name = cur_dir.name
334
+ selection = CodebaseIndexSelection(repo_path=cur_dir, name=cwd_name)
335
+ self.call_later(lambda: self.index_codebase(selection))
434
336
 
435
337
  def watch_mode(self, new_mode: AgentType) -> None:
436
338
  """React to mode changes by updating the agent manager."""
@@ -529,12 +431,16 @@ class ChatScreen(Screen[None]):
529
431
  @on(PartialResponseMessage)
530
432
  def handle_partial_response(self, event: PartialResponseMessage) -> None:
531
433
  self.partial_message = event.message
532
-
533
434
  history = self.query_one(ChatHistory)
534
- history.update_messages(
535
- self.messages + cast(list[ModelMessage | HintMessage], event.messages)
435
+
436
+ # Only update messages if the message list changed
437
+ new_message_list = self.messages + cast(
438
+ list[ModelMessage | HintMessage], event.messages
536
439
  )
440
+ if len(new_message_list) != len(history.items):
441
+ history.update_messages(new_message_list)
537
442
 
443
+ # Always update the partial response (reactive property handles the update)
538
444
  history.partial_response = self.partial_message
539
445
 
540
446
  def _clear_partial_response(self) -> None:
@@ -640,16 +546,11 @@ class ChatScreen(Screen[None]):
640
546
  return self.placeholder_hints.get_placeholder_for_mode(mode)
641
547
 
642
548
  def index_codebase_command(self) -> None:
643
- start_path = Path.cwd()
644
-
645
- def handle_result(result: CodebaseIndexSelection | None) -> None:
646
- if result:
647
- self.call_later(lambda: self.index_codebase(result))
648
-
649
- self.app.push_screen(
650
- CodebaseIndexScreen(start_path=start_path),
651
- handle_result,
652
- )
549
+ # Simplified: always index current working directory with its name
550
+ cur_dir = Path.cwd().resolve()
551
+ cwd_name = cur_dir.name
552
+ selection = CodebaseIndexSelection(repo_path=cur_dir, name=cwd_name)
553
+ self.call_later(lambda: self.index_codebase(selection))
653
554
 
654
555
  def delete_codebase_command(self) -> None:
655
556
  self.app.push_screen(
@@ -41,7 +41,6 @@ class PartialResponseWidget(Widget): # TODO: doesn't work lol
41
41
  self.item = item
42
42
 
43
43
  def compose(self) -> ComposeResult:
44
- yield Markdown(markdown="**partial response**")
45
44
  if self.item is None:
46
45
  pass
47
46
  elif self.item.kind == "response":
@@ -82,12 +81,14 @@ class ChatHistory(Widget):
82
81
  self.items: Sequence[ModelMessage | HintMessage] = []
83
82
  self.vertical_tail: VerticalTail | None = None
84
83
  self.partial_response = None
84
+ self._rendered_count = 0 # Track how many messages have been mounted
85
85
 
86
86
  def compose(self) -> ComposeResult:
87
87
  self.vertical_tail = VerticalTail()
88
88
 
89
+ filtered = list(self.filtered_items())
89
90
  with self.vertical_tail:
90
- for item in self.filtered_items():
91
+ for item in filtered:
91
92
  if isinstance(item, ModelRequest):
92
93
  yield UserQuestionWidget(item)
93
94
  elif isinstance(item, HintMessage):
@@ -97,6 +98,9 @@ class ChatHistory(Widget):
97
98
  yield PartialResponseWidget(self.partial_response).data_bind(
98
99
  item=ChatHistory.partial_response
99
100
  )
101
+
102
+ # Track how many messages were rendered during initial compose
103
+ self._rendered_count = len(filtered)
100
104
  self.call_later(self.autoscroll)
101
105
 
102
106
  def filtered_items(self) -> Generator[ModelMessage | HintMessage, None, None]:
@@ -138,17 +142,43 @@ class ChatHistory(Widget):
138
142
  yield next_item
139
143
 
140
144
  def update_messages(self, messages: list[ModelMessage | HintMessage]) -> None:
141
- """Update the displayed messages without recomposing."""
145
+ """Update the displayed messages using incremental mounting."""
142
146
  if not self.vertical_tail:
143
147
  return
144
148
 
145
149
  self.items = messages
146
- self.refresh(recompose=True)
147
- self.call_later(self.autoscroll)
150
+ filtered = list(self.filtered_items())
151
+
152
+ # Only mount new messages that haven't been rendered yet
153
+ if len(filtered) > self._rendered_count:
154
+ new_messages = filtered[self._rendered_count :]
155
+ for item in new_messages:
156
+ widget: Widget
157
+ if isinstance(item, ModelRequest):
158
+ widget = UserQuestionWidget(item)
159
+ elif isinstance(item, HintMessage):
160
+ widget = HintMessageWidget(item)
161
+ elif isinstance(item, ModelResponse):
162
+ widget = AgentResponseWidget(item)
163
+ else:
164
+ continue
165
+
166
+ # Mount before the PartialResponseWidget
167
+ self.vertical_tail.mount(widget, before=self.vertical_tail.children[-1])
168
+
169
+ self._rendered_count = len(filtered)
170
+ self.call_after_refresh(self.autoscroll)
148
171
 
149
172
  def autoscroll(self) -> None:
173
+ """Smoothly scroll to the bottom after new content is added."""
150
174
  if self.vertical_tail:
151
- self.vertical_tail.scroll_end(animate=False, immediate=False, force=True)
175
+ # Scroll to absolute bottom - more reliable during recomposition
176
+ self.vertical_tail.scroll_end(animate=False, force=True)
177
+
178
+ def watch_partial_response(self, partial_response: ModelMessage | None) -> None:
179
+ """Trigger autoscroll when partial response updates during streaming."""
180
+ if partial_response is not None:
181
+ self.call_after_refresh(self.autoscroll)
152
182
 
153
183
 
154
184
  class UserQuestionWidget(Widget):