shotgun-sh 0.2.23.dev1__py3-none-any.whl → 0.2.29.dev2__py3-none-any.whl

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 (86) hide show
  1. shotgun/agents/agent_manager.py +3 -3
  2. shotgun/agents/common.py +1 -1
  3. shotgun/agents/config/manager.py +36 -21
  4. shotgun/agents/config/models.py +30 -0
  5. shotgun/agents/config/provider.py +27 -14
  6. shotgun/agents/context_analyzer/analyzer.py +6 -2
  7. shotgun/agents/conversation/__init__.py +18 -0
  8. shotgun/agents/conversation/filters.py +164 -0
  9. shotgun/agents/conversation/history/chunking.py +278 -0
  10. shotgun/agents/{history → conversation/history}/compaction.py +27 -1
  11. shotgun/agents/{history → conversation/history}/constants.py +5 -0
  12. shotgun/agents/conversation/history/file_content_deduplication.py +216 -0
  13. shotgun/agents/{history → conversation/history}/history_processors.py +267 -3
  14. shotgun/agents/{conversation_manager.py → conversation/manager.py} +1 -1
  15. shotgun/agents/{conversation_history.py → conversation/models.py} +8 -94
  16. shotgun/agents/tools/web_search/openai.py +1 -1
  17. shotgun/cli/clear.py +1 -1
  18. shotgun/cli/compact.py +5 -3
  19. shotgun/cli/context.py +1 -1
  20. shotgun/cli/spec/__init__.py +5 -0
  21. shotgun/cli/spec/backup.py +81 -0
  22. shotgun/cli/spec/commands.py +130 -0
  23. shotgun/cli/spec/models.py +30 -0
  24. shotgun/cli/spec/pull_service.py +165 -0
  25. shotgun/codebase/core/ingestor.py +153 -7
  26. shotgun/codebase/models.py +2 -0
  27. shotgun/exceptions.py +5 -3
  28. shotgun/main.py +2 -0
  29. shotgun/posthog_telemetry.py +1 -1
  30. shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +3 -3
  31. shotgun/prompts/agents/partials/interactive_mode.j2 +3 -3
  32. shotgun/prompts/agents/research.j2 +0 -3
  33. shotgun/prompts/history/chunk_summarization.j2 +34 -0
  34. shotgun/prompts/history/combine_summaries.j2 +53 -0
  35. shotgun/shotgun_web/__init__.py +67 -1
  36. shotgun/shotgun_web/client.py +42 -1
  37. shotgun/shotgun_web/constants.py +46 -0
  38. shotgun/shotgun_web/exceptions.py +29 -0
  39. shotgun/shotgun_web/models.py +390 -0
  40. shotgun/shotgun_web/shared_specs/__init__.py +32 -0
  41. shotgun/shotgun_web/shared_specs/file_scanner.py +175 -0
  42. shotgun/shotgun_web/shared_specs/hasher.py +83 -0
  43. shotgun/shotgun_web/shared_specs/models.py +71 -0
  44. shotgun/shotgun_web/shared_specs/upload_pipeline.py +291 -0
  45. shotgun/shotgun_web/shared_specs/utils.py +34 -0
  46. shotgun/shotgun_web/specs_client.py +703 -0
  47. shotgun/shotgun_web/supabase_client.py +31 -0
  48. shotgun/tui/app.py +39 -0
  49. shotgun/tui/containers.py +1 -1
  50. shotgun/tui/layout.py +5 -0
  51. shotgun/tui/screens/chat/chat_screen.py +212 -16
  52. shotgun/tui/screens/chat/codebase_index_prompt_screen.py +147 -19
  53. shotgun/tui/screens/chat_screen/command_providers.py +10 -0
  54. shotgun/tui/screens/chat_screen/history/chat_history.py +0 -36
  55. shotgun/tui/screens/confirmation_dialog.py +40 -0
  56. shotgun/tui/screens/model_picker.py +7 -1
  57. shotgun/tui/screens/onboarding.py +149 -0
  58. shotgun/tui/screens/pipx_migration.py +46 -0
  59. shotgun/tui/screens/provider_config.py +41 -0
  60. shotgun/tui/screens/shared_specs/__init__.py +21 -0
  61. shotgun/tui/screens/shared_specs/create_spec_dialog.py +273 -0
  62. shotgun/tui/screens/shared_specs/models.py +56 -0
  63. shotgun/tui/screens/shared_specs/share_specs_dialog.py +390 -0
  64. shotgun/tui/screens/shared_specs/upload_progress_screen.py +452 -0
  65. shotgun/tui/screens/shotgun_auth.py +60 -6
  66. shotgun/tui/screens/spec_pull.py +286 -0
  67. shotgun/tui/screens/welcome.py +91 -0
  68. shotgun/tui/services/conversation_service.py +5 -2
  69. shotgun/tui/widgets/widget_coordinator.py +1 -1
  70. {shotgun_sh-0.2.23.dev1.dist-info → shotgun_sh-0.2.29.dev2.dist-info}/METADATA +1 -1
  71. {shotgun_sh-0.2.23.dev1.dist-info → shotgun_sh-0.2.29.dev2.dist-info}/RECORD +86 -59
  72. {shotgun_sh-0.2.23.dev1.dist-info → shotgun_sh-0.2.29.dev2.dist-info}/WHEEL +1 -1
  73. /shotgun/agents/{history → conversation/history}/__init__.py +0 -0
  74. /shotgun/agents/{history → conversation/history}/context_extraction.py +0 -0
  75. /shotgun/agents/{history → conversation/history}/history_building.py +0 -0
  76. /shotgun/agents/{history → conversation/history}/message_utils.py +0 -0
  77. /shotgun/agents/{history → conversation/history}/token_counting/__init__.py +0 -0
  78. /shotgun/agents/{history → conversation/history}/token_counting/anthropic.py +0 -0
  79. /shotgun/agents/{history → conversation/history}/token_counting/base.py +0 -0
  80. /shotgun/agents/{history → conversation/history}/token_counting/openai.py +0 -0
  81. /shotgun/agents/{history → conversation/history}/token_counting/sentencepiece_counter.py +0 -0
  82. /shotgun/agents/{history → conversation/history}/token_counting/tokenizer_cache.py +0 -0
  83. /shotgun/agents/{history → conversation/history}/token_counting/utils.py +0 -0
  84. /shotgun/agents/{history → conversation/history}/token_estimation.py +0 -0
  85. {shotgun_sh-0.2.23.dev1.dist-info → shotgun_sh-0.2.29.dev2.dist-info}/entry_points.txt +0 -0
  86. {shotgun_sh-0.2.23.dev1.dist-info → shotgun_sh-0.2.29.dev2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,71 @@
1
+ """Pydantic models for the shared specs upload pipeline."""
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from shotgun.shotgun_web.models import FileMetadata
6
+ from shotgun.shotgun_web.shared_specs.utils import UploadPhase
7
+
8
+
9
+ class UploadProgress(BaseModel):
10
+ """Progress information for the upload pipeline.
11
+
12
+ Attributes:
13
+ phase: Current phase of the pipeline
14
+ current: Current item number in the phase
15
+ total: Total items in the phase
16
+ current_file: Name of the file currently being processed
17
+ bytes_uploaded: Total bytes uploaded so far
18
+ total_bytes: Total bytes to upload
19
+ message: Human-readable status message
20
+ """
21
+
22
+ phase: UploadPhase
23
+ current: int = 0
24
+ total: int = 0
25
+ current_file: str | None = None
26
+ bytes_uploaded: int = 0
27
+ total_bytes: int = 0
28
+ message: str = ""
29
+
30
+
31
+ class UploadResult(BaseModel):
32
+ """Result of the upload pipeline.
33
+
34
+ Attributes:
35
+ success: Whether the upload completed successfully
36
+ web_url: URL to view the spec version (on success)
37
+ error: Error message (on failure)
38
+ files_uploaded: Number of files uploaded
39
+ total_bytes: Total bytes uploaded
40
+ """
41
+
42
+ success: bool
43
+ web_url: str | None = None
44
+ error: str | None = None
45
+ files_uploaded: int = 0
46
+ total_bytes: int = 0
47
+
48
+
49
+ class FileWithHash(BaseModel):
50
+ """File metadata with computed hash."""
51
+
52
+ metadata: FileMetadata
53
+ content_hash: str = ""
54
+
55
+
56
+ class UploadState(BaseModel):
57
+ """Internal state for upload progress tracking."""
58
+
59
+ files_uploaded: int = 0
60
+ bytes_uploaded: int = 0
61
+ total_bytes: int = 0
62
+ current_file: str | None = None
63
+ hashes_completed: int = 0
64
+ total_files: int = 0
65
+
66
+
67
+ class ScanResult(BaseModel):
68
+ """Result of scanning .shotgun/ directory."""
69
+
70
+ files: list[FileMetadata]
71
+ total_files_before_filter: int
@@ -0,0 +1,291 @@
1
+ """Upload pipeline for .shotgun/ directory to Specs API."""
2
+
3
+ import asyncio
4
+ from collections.abc import Callable
5
+ from pathlib import Path
6
+
7
+ from shotgun.logging_config import get_logger
8
+ from shotgun.shotgun_web.models import FileMetadata
9
+ from shotgun.shotgun_web.shared_specs.file_scanner import (
10
+ scan_shotgun_directory_with_counts,
11
+ )
12
+ from shotgun.shotgun_web.shared_specs.hasher import calculate_sha256
13
+ from shotgun.shotgun_web.shared_specs.models import (
14
+ FileWithHash,
15
+ UploadProgress,
16
+ UploadResult,
17
+ UploadState,
18
+ )
19
+ from shotgun.shotgun_web.shared_specs.utils import UploadPhase, format_bytes
20
+ from shotgun.shotgun_web.specs_client import SpecsClient
21
+
22
+ logger = get_logger(__name__)
23
+
24
+ # Maximum concurrent hash calculations
25
+ MAX_CONCURRENT_HASHES = 10
26
+
27
+ # Maximum concurrent file uploads
28
+ MAX_CONCURRENT_UPLOADS = 3
29
+
30
+
31
+ async def run_upload_pipeline(
32
+ workspace_id: str,
33
+ spec_id: str,
34
+ version_id: str,
35
+ project_root: Path | None = None,
36
+ on_progress: Callable[[UploadProgress], None] | None = None,
37
+ ) -> UploadResult:
38
+ """Run the complete upload pipeline for a spec version.
39
+
40
+ Scans the .shotgun/ directory, calculates hashes for all files,
41
+ uploads them to the API, and closes the version.
42
+
43
+ Args:
44
+ workspace_id: Workspace UUID
45
+ spec_id: Spec UUID
46
+ version_id: Version UUID
47
+ project_root: Project root containing .shotgun/ directory (defaults to cwd)
48
+ on_progress: Optional callback for progress updates
49
+
50
+ Returns:
51
+ UploadResult with success status and web URL or error message
52
+ """
53
+ if project_root is None:
54
+ project_root = Path.cwd()
55
+
56
+ state = UploadState()
57
+
58
+ def report_progress(progress: UploadProgress) -> None:
59
+ """Report progress to callback if provided."""
60
+ if on_progress:
61
+ on_progress(progress)
62
+
63
+ try:
64
+ # Phase 1: Scan files
65
+ report_progress(
66
+ UploadProgress(
67
+ phase=UploadPhase.SCANNING,
68
+ message="Scanning .shotgun/ directory...",
69
+ )
70
+ )
71
+
72
+ scan_result = await scan_shotgun_directory_with_counts(project_root)
73
+ files = scan_result.files
74
+ state.total_files = len(files)
75
+
76
+ if not files:
77
+ # Distinguish between empty directory and all files filtered
78
+ if scan_result.total_files_before_filter > 0:
79
+ error_message = (
80
+ "No shareable files found. All files matched ignore patterns."
81
+ )
82
+ else:
83
+ error_message = (
84
+ "No files to share. Add specifications to .shotgun/ first."
85
+ )
86
+
87
+ report_progress(
88
+ UploadProgress(
89
+ phase=UploadPhase.ERROR,
90
+ message=error_message,
91
+ )
92
+ )
93
+ return UploadResult(
94
+ success=False,
95
+ files_uploaded=0,
96
+ total_bytes=0,
97
+ error=error_message,
98
+ )
99
+
100
+ # Calculate total size
101
+ state.total_bytes = sum(f.size_bytes for f in files)
102
+
103
+ report_progress(
104
+ UploadProgress(
105
+ phase=UploadPhase.SCANNING,
106
+ total=state.total_files,
107
+ total_bytes=state.total_bytes,
108
+ message=f"Found {state.total_files} files ({format_bytes(state.total_bytes)})",
109
+ )
110
+ )
111
+
112
+ # Phase 2: Calculate hashes
113
+ report_progress(
114
+ UploadProgress(
115
+ phase=UploadPhase.HASHING,
116
+ current=0,
117
+ total=state.total_files,
118
+ message="Calculating file hashes...",
119
+ )
120
+ )
121
+
122
+ files_with_hashes = await _calculate_hashes(files, state, report_progress)
123
+
124
+ # Phase 3: Upload files
125
+ report_progress(
126
+ UploadProgress(
127
+ phase=UploadPhase.UPLOADING,
128
+ current=0,
129
+ total=state.total_files,
130
+ total_bytes=state.total_bytes,
131
+ message="Uploading files...",
132
+ )
133
+ )
134
+
135
+ client = SpecsClient()
136
+ await _upload_files(
137
+ client,
138
+ workspace_id,
139
+ spec_id,
140
+ version_id,
141
+ files_with_hashes,
142
+ state,
143
+ report_progress,
144
+ )
145
+
146
+ # Phase 4: Close version
147
+ report_progress(
148
+ UploadProgress(
149
+ phase=UploadPhase.CLOSING,
150
+ current=state.files_uploaded,
151
+ total=state.total_files,
152
+ bytes_uploaded=state.bytes_uploaded,
153
+ total_bytes=state.total_bytes,
154
+ message="Finalizing version...",
155
+ )
156
+ )
157
+
158
+ close_response = await client.close_version(workspace_id, spec_id, version_id)
159
+
160
+ # Complete
161
+ report_progress(
162
+ UploadProgress(
163
+ phase=UploadPhase.COMPLETE,
164
+ current=state.files_uploaded,
165
+ total=state.total_files,
166
+ bytes_uploaded=state.bytes_uploaded,
167
+ total_bytes=state.total_bytes,
168
+ message="Upload complete!",
169
+ )
170
+ )
171
+
172
+ return UploadResult(
173
+ success=True,
174
+ web_url=close_response.web_url,
175
+ files_uploaded=state.files_uploaded,
176
+ total_bytes=state.bytes_uploaded,
177
+ )
178
+
179
+ except Exception as e:
180
+ logger.error(f"Upload pipeline failed: {e}", exc_info=True)
181
+ report_progress(
182
+ UploadProgress(
183
+ phase=UploadPhase.ERROR,
184
+ current=state.files_uploaded,
185
+ total=state.total_files,
186
+ bytes_uploaded=state.bytes_uploaded,
187
+ total_bytes=state.total_bytes,
188
+ message=f"Upload failed: {e}",
189
+ )
190
+ )
191
+ return UploadResult(
192
+ success=False,
193
+ error=str(e),
194
+ files_uploaded=state.files_uploaded,
195
+ total_bytes=state.bytes_uploaded,
196
+ )
197
+
198
+
199
+ async def _calculate_hashes(
200
+ files: list[FileMetadata],
201
+ state: UploadState,
202
+ report_progress: Callable[[UploadProgress], None],
203
+ ) -> list[FileWithHash]:
204
+ """Calculate hashes for all files with progress reporting.
205
+
206
+ Uses semaphore to limit concurrent hash operations.
207
+ """
208
+ semaphore = asyncio.Semaphore(MAX_CONCURRENT_HASHES)
209
+ files_with_hashes: list[FileWithHash] = []
210
+ lock = asyncio.Lock()
211
+
212
+ async def hash_file(file_meta: FileMetadata) -> FileWithHash:
213
+ async with semaphore:
214
+ content_hash = await calculate_sha256(file_meta.absolute_path)
215
+
216
+ # Update progress
217
+ async with lock:
218
+ state.hashes_completed += 1
219
+ report_progress(
220
+ UploadProgress(
221
+ phase=UploadPhase.HASHING,
222
+ current=state.hashes_completed,
223
+ total=state.total_files,
224
+ current_file=file_meta.relative_path,
225
+ message=f"Hashing {file_meta.relative_path}",
226
+ )
227
+ )
228
+
229
+ return FileWithHash(metadata=file_meta, content_hash=content_hash)
230
+
231
+ # Run hash calculations concurrently
232
+ results = await asyncio.gather(*[hash_file(f) for f in files])
233
+ files_with_hashes = list(results)
234
+
235
+ return files_with_hashes
236
+
237
+
238
+ async def _upload_files(
239
+ client: SpecsClient,
240
+ workspace_id: str,
241
+ spec_id: str,
242
+ version_id: str,
243
+ files: list[FileWithHash],
244
+ state: UploadState,
245
+ report_progress: Callable[[UploadProgress], None],
246
+ ) -> None:
247
+ """Upload all files with progress reporting.
248
+
249
+ Uses semaphore to limit concurrent uploads.
250
+ """
251
+ semaphore = asyncio.Semaphore(MAX_CONCURRENT_UPLOADS)
252
+ lock = asyncio.Lock()
253
+
254
+ async def upload_file(file: FileWithHash) -> None:
255
+ async with semaphore:
256
+ # Initiate upload to get presigned URL
257
+ response = await client.initiate_file_upload(
258
+ workspace_id,
259
+ spec_id,
260
+ version_id,
261
+ file.metadata.relative_path,
262
+ file.metadata.size_bytes,
263
+ file.content_hash,
264
+ )
265
+
266
+ # Upload to presigned URL
267
+ await client.upload_file_to_presigned_url(
268
+ response.upload_url,
269
+ file.metadata.absolute_path,
270
+ )
271
+
272
+ # Update progress
273
+ async with lock:
274
+ state.files_uploaded += 1
275
+ state.bytes_uploaded += file.metadata.size_bytes
276
+ state.current_file = file.metadata.relative_path
277
+
278
+ report_progress(
279
+ UploadProgress(
280
+ phase=UploadPhase.UPLOADING,
281
+ current=state.files_uploaded,
282
+ total=state.total_files,
283
+ current_file=file.metadata.relative_path,
284
+ bytes_uploaded=state.bytes_uploaded,
285
+ total_bytes=state.total_bytes,
286
+ message=f"Uploaded {file.metadata.relative_path}",
287
+ )
288
+ )
289
+
290
+ # Run uploads concurrently
291
+ await asyncio.gather(*[upload_file(f) for f in files])
@@ -0,0 +1,34 @@
1
+ """Utility functions for shared specs module."""
2
+
3
+ from enum import StrEnum
4
+
5
+
6
+ class UploadPhase(StrEnum):
7
+ """Upload pipeline phases."""
8
+
9
+ CREATING = "creating" # Creating spec/version via API
10
+ SCANNING = "scanning"
11
+ HASHING = "hashing"
12
+ UPLOADING = "uploading"
13
+ CLOSING = "closing"
14
+ COMPLETE = "complete"
15
+ ERROR = "error"
16
+
17
+
18
+ def format_bytes(size: int) -> str:
19
+ """Format bytes as human-readable string.
20
+
21
+ Args:
22
+ size: Size in bytes
23
+
24
+ Returns:
25
+ Human-readable string like "1.5 KB" or "2.3 MB"
26
+ """
27
+ if size < 1024:
28
+ return f"{size} B"
29
+ elif size < 1024 * 1024:
30
+ return f"{size / 1024:.1f} KB"
31
+ elif size < 1024 * 1024 * 1024:
32
+ return f"{size / (1024 * 1024):.1f} MB"
33
+ else:
34
+ return f"{size / (1024 * 1024 * 1024):.1f} GB"