shotgun-sh 0.2.8.dev2__py3-none-any.whl → 0.3.3.dev1__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.
Files changed (175) hide show
  1. shotgun/agents/agent_manager.py +382 -60
  2. shotgun/agents/common.py +15 -9
  3. shotgun/agents/config/README.md +89 -0
  4. shotgun/agents/config/__init__.py +10 -1
  5. shotgun/agents/config/constants.py +0 -6
  6. shotgun/agents/config/manager.py +383 -82
  7. shotgun/agents/config/models.py +122 -18
  8. shotgun/agents/config/provider.py +81 -15
  9. shotgun/agents/config/streaming_test.py +119 -0
  10. shotgun/agents/context_analyzer/__init__.py +28 -0
  11. shotgun/agents/context_analyzer/analyzer.py +475 -0
  12. shotgun/agents/context_analyzer/constants.py +9 -0
  13. shotgun/agents/context_analyzer/formatter.py +115 -0
  14. shotgun/agents/context_analyzer/models.py +212 -0
  15. shotgun/agents/conversation/__init__.py +18 -0
  16. shotgun/agents/conversation/filters.py +164 -0
  17. shotgun/agents/conversation/history/chunking.py +278 -0
  18. shotgun/agents/{history → conversation/history}/compaction.py +36 -5
  19. shotgun/agents/{history → conversation/history}/constants.py +5 -0
  20. shotgun/agents/conversation/history/file_content_deduplication.py +216 -0
  21. shotgun/agents/{history → conversation/history}/history_processors.py +380 -8
  22. shotgun/agents/{history → conversation/history}/token_counting/anthropic.py +25 -1
  23. shotgun/agents/{history → conversation/history}/token_counting/base.py +14 -3
  24. shotgun/agents/{history → conversation/history}/token_counting/openai.py +11 -1
  25. shotgun/agents/{history → conversation/history}/token_counting/sentencepiece_counter.py +8 -0
  26. shotgun/agents/{history → conversation/history}/token_counting/tokenizer_cache.py +3 -1
  27. shotgun/agents/{history → conversation/history}/token_counting/utils.py +0 -3
  28. shotgun/agents/{conversation_manager.py → conversation/manager.py} +36 -20
  29. shotgun/agents/{conversation_history.py → conversation/models.py} +8 -92
  30. shotgun/agents/error/__init__.py +11 -0
  31. shotgun/agents/error/models.py +19 -0
  32. shotgun/agents/export.py +2 -2
  33. shotgun/agents/plan.py +2 -2
  34. shotgun/agents/research.py +3 -3
  35. shotgun/agents/runner.py +230 -0
  36. shotgun/agents/specify.py +2 -2
  37. shotgun/agents/tasks.py +2 -2
  38. shotgun/agents/tools/codebase/codebase_shell.py +6 -0
  39. shotgun/agents/tools/codebase/directory_lister.py +6 -0
  40. shotgun/agents/tools/codebase/file_read.py +11 -2
  41. shotgun/agents/tools/codebase/query_graph.py +6 -0
  42. shotgun/agents/tools/codebase/retrieve_code.py +6 -0
  43. shotgun/agents/tools/file_management.py +27 -7
  44. shotgun/agents/tools/registry.py +217 -0
  45. shotgun/agents/tools/web_search/__init__.py +8 -8
  46. shotgun/agents/tools/web_search/anthropic.py +8 -2
  47. shotgun/agents/tools/web_search/gemini.py +7 -1
  48. shotgun/agents/tools/web_search/openai.py +8 -2
  49. shotgun/agents/tools/web_search/utils.py +2 -2
  50. shotgun/agents/usage_manager.py +16 -11
  51. shotgun/api_endpoints.py +7 -3
  52. shotgun/build_constants.py +2 -2
  53. shotgun/cli/clear.py +53 -0
  54. shotgun/cli/compact.py +188 -0
  55. shotgun/cli/config.py +8 -5
  56. shotgun/cli/context.py +154 -0
  57. shotgun/cli/error_handler.py +24 -0
  58. shotgun/cli/export.py +34 -34
  59. shotgun/cli/feedback.py +4 -2
  60. shotgun/cli/models.py +1 -0
  61. shotgun/cli/plan.py +34 -34
  62. shotgun/cli/research.py +18 -10
  63. shotgun/cli/spec/__init__.py +5 -0
  64. shotgun/cli/spec/backup.py +81 -0
  65. shotgun/cli/spec/commands.py +132 -0
  66. shotgun/cli/spec/models.py +48 -0
  67. shotgun/cli/spec/pull_service.py +219 -0
  68. shotgun/cli/specify.py +20 -19
  69. shotgun/cli/tasks.py +34 -34
  70. shotgun/cli/update.py +16 -2
  71. shotgun/codebase/core/change_detector.py +5 -3
  72. shotgun/codebase/core/code_retrieval.py +4 -2
  73. shotgun/codebase/core/ingestor.py +163 -15
  74. shotgun/codebase/core/manager.py +13 -4
  75. shotgun/codebase/core/nl_query.py +1 -1
  76. shotgun/codebase/models.py +2 -0
  77. shotgun/exceptions.py +357 -0
  78. shotgun/llm_proxy/__init__.py +17 -0
  79. shotgun/llm_proxy/client.py +215 -0
  80. shotgun/llm_proxy/models.py +137 -0
  81. shotgun/logging_config.py +60 -27
  82. shotgun/main.py +77 -11
  83. shotgun/posthog_telemetry.py +38 -29
  84. shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +28 -2
  85. shotgun/prompts/agents/partials/interactive_mode.j2 +3 -3
  86. shotgun/prompts/agents/plan.j2 +16 -0
  87. shotgun/prompts/agents/research.j2 +16 -3
  88. shotgun/prompts/agents/specify.j2 +54 -1
  89. shotgun/prompts/agents/state/system_state.j2 +0 -2
  90. shotgun/prompts/agents/tasks.j2 +16 -0
  91. shotgun/prompts/history/chunk_summarization.j2 +34 -0
  92. shotgun/prompts/history/combine_summaries.j2 +53 -0
  93. shotgun/sdk/codebase.py +14 -3
  94. shotgun/sentry_telemetry.py +163 -16
  95. shotgun/settings.py +243 -0
  96. shotgun/shotgun_web/__init__.py +67 -1
  97. shotgun/shotgun_web/client.py +42 -1
  98. shotgun/shotgun_web/constants.py +46 -0
  99. shotgun/shotgun_web/exceptions.py +29 -0
  100. shotgun/shotgun_web/models.py +390 -0
  101. shotgun/shotgun_web/shared_specs/__init__.py +32 -0
  102. shotgun/shotgun_web/shared_specs/file_scanner.py +175 -0
  103. shotgun/shotgun_web/shared_specs/hasher.py +83 -0
  104. shotgun/shotgun_web/shared_specs/models.py +71 -0
  105. shotgun/shotgun_web/shared_specs/upload_pipeline.py +329 -0
  106. shotgun/shotgun_web/shared_specs/utils.py +34 -0
  107. shotgun/shotgun_web/specs_client.py +703 -0
  108. shotgun/shotgun_web/supabase_client.py +31 -0
  109. shotgun/telemetry.py +10 -33
  110. shotgun/tui/app.py +310 -46
  111. shotgun/tui/commands/__init__.py +1 -1
  112. shotgun/tui/components/context_indicator.py +179 -0
  113. shotgun/tui/components/mode_indicator.py +70 -0
  114. shotgun/tui/components/status_bar.py +48 -0
  115. shotgun/tui/containers.py +91 -0
  116. shotgun/tui/dependencies.py +39 -0
  117. shotgun/tui/layout.py +5 -0
  118. shotgun/tui/protocols.py +45 -0
  119. shotgun/tui/screens/chat/__init__.py +5 -0
  120. shotgun/tui/screens/chat/chat.tcss +54 -0
  121. shotgun/tui/screens/chat/chat_screen.py +1531 -0
  122. shotgun/tui/screens/chat/codebase_index_prompt_screen.py +243 -0
  123. shotgun/tui/screens/chat/codebase_index_selection.py +12 -0
  124. shotgun/tui/screens/chat/help_text.py +40 -0
  125. shotgun/tui/screens/chat/prompt_history.py +48 -0
  126. shotgun/tui/screens/chat.tcss +11 -0
  127. shotgun/tui/screens/chat_screen/command_providers.py +91 -4
  128. shotgun/tui/screens/chat_screen/hint_message.py +76 -1
  129. shotgun/tui/screens/chat_screen/history/__init__.py +22 -0
  130. shotgun/tui/screens/chat_screen/history/agent_response.py +66 -0
  131. shotgun/tui/screens/chat_screen/history/chat_history.py +115 -0
  132. shotgun/tui/screens/chat_screen/history/formatters.py +115 -0
  133. shotgun/tui/screens/chat_screen/history/partial_response.py +43 -0
  134. shotgun/tui/screens/chat_screen/history/user_question.py +42 -0
  135. shotgun/tui/screens/confirmation_dialog.py +191 -0
  136. shotgun/tui/screens/directory_setup.py +45 -41
  137. shotgun/tui/screens/feedback.py +14 -7
  138. shotgun/tui/screens/github_issue.py +111 -0
  139. shotgun/tui/screens/model_picker.py +77 -32
  140. shotgun/tui/screens/onboarding.py +580 -0
  141. shotgun/tui/screens/pipx_migration.py +205 -0
  142. shotgun/tui/screens/provider_config.py +116 -35
  143. shotgun/tui/screens/shared_specs/__init__.py +21 -0
  144. shotgun/tui/screens/shared_specs/create_spec_dialog.py +273 -0
  145. shotgun/tui/screens/shared_specs/models.py +56 -0
  146. shotgun/tui/screens/shared_specs/share_specs_dialog.py +390 -0
  147. shotgun/tui/screens/shared_specs/upload_progress_screen.py +452 -0
  148. shotgun/tui/screens/shotgun_auth.py +112 -18
  149. shotgun/tui/screens/spec_pull.py +288 -0
  150. shotgun/tui/screens/welcome.py +137 -11
  151. shotgun/tui/services/__init__.py +5 -0
  152. shotgun/tui/services/conversation_service.py +187 -0
  153. shotgun/tui/state/__init__.py +7 -0
  154. shotgun/tui/state/processing_state.py +185 -0
  155. shotgun/tui/utils/mode_progress.py +14 -7
  156. shotgun/tui/widgets/__init__.py +5 -0
  157. shotgun/tui/widgets/widget_coordinator.py +263 -0
  158. shotgun/utils/file_system_utils.py +22 -2
  159. shotgun/utils/marketing.py +110 -0
  160. shotgun/utils/update_checker.py +69 -14
  161. shotgun_sh-0.3.3.dev1.dist-info/METADATA +472 -0
  162. shotgun_sh-0.3.3.dev1.dist-info/RECORD +229 -0
  163. {shotgun_sh-0.2.8.dev2.dist-info → shotgun_sh-0.3.3.dev1.dist-info}/WHEEL +1 -1
  164. {shotgun_sh-0.2.8.dev2.dist-info → shotgun_sh-0.3.3.dev1.dist-info}/entry_points.txt +1 -0
  165. {shotgun_sh-0.2.8.dev2.dist-info → shotgun_sh-0.3.3.dev1.dist-info}/licenses/LICENSE +1 -1
  166. shotgun/tui/screens/chat.py +0 -996
  167. shotgun/tui/screens/chat_screen/history.py +0 -335
  168. shotgun_sh-0.2.8.dev2.dist-info/METADATA +0 -126
  169. shotgun_sh-0.2.8.dev2.dist-info/RECORD +0 -155
  170. /shotgun/agents/{history → conversation/history}/__init__.py +0 -0
  171. /shotgun/agents/{history → conversation/history}/context_extraction.py +0 -0
  172. /shotgun/agents/{history → conversation/history}/history_building.py +0 -0
  173. /shotgun/agents/{history → conversation/history}/message_utils.py +0 -0
  174. /shotgun/agents/{history → conversation/history}/token_counting/__init__.py +0 -0
  175. /shotgun/agents/{history → conversation/history}/token_estimation.py +0 -0
shotgun/settings.py ADDED
@@ -0,0 +1,243 @@
1
+ """Centralized application settings using Pydantic Settings.
2
+
3
+ All environment variables use the SHOTGUN_ prefix to avoid conflicts with other tools.
4
+ Settings are loaded with the following priority:
5
+ 1. Environment variables (highest priority)
6
+ 2. Build constants (embedded at build time)
7
+ 3. Default values (lowest priority)
8
+
9
+ Example usage:
10
+ from shotgun.settings import settings
11
+
12
+ # Access telemetry settings
13
+ if settings.telemetry.sentry_dsn:
14
+ sentry_sdk.init(dsn=settings.telemetry.sentry_dsn)
15
+
16
+ # Access logging settings
17
+ logger.setLevel(settings.logging.log_level)
18
+
19
+ # Access API settings
20
+ response = httpx.get(settings.api.web_base_url)
21
+ """
22
+
23
+ from typing import Any
24
+
25
+ from pydantic import Field, field_validator
26
+ from pydantic_settings import BaseSettings, SettingsConfigDict
27
+
28
+
29
+ def _get_build_constant(name: str, default: Any = None) -> Any:
30
+ """Get a value from build_constants.py, falling back to default.
31
+
32
+ Args:
33
+ name: The constant name to retrieve (e.g., "SENTRY_DSN")
34
+ default: Default value if constant not found
35
+
36
+ Returns:
37
+ The constant value, or default if not found/import fails
38
+ """
39
+ try:
40
+ from shotgun import build_constants
41
+
42
+ return getattr(build_constants, name, default)
43
+ except ImportError:
44
+ return default
45
+
46
+
47
+ class TelemetrySettings(BaseSettings):
48
+ """Telemetry and observability settings.
49
+
50
+ These settings control error tracking (Sentry), analytics (PostHog),
51
+ and observability (Logfire) integrations.
52
+ """
53
+
54
+ sentry_dsn: str = Field(
55
+ default_factory=lambda: _get_build_constant("SENTRY_DSN", ""),
56
+ description="Sentry DSN for error tracking",
57
+ )
58
+ posthog_api_key: str = Field(
59
+ default_factory=lambda: _get_build_constant("POSTHOG_API_KEY", ""),
60
+ description="PostHog API key for analytics",
61
+ )
62
+ posthog_project_id: str = Field(
63
+ default_factory=lambda: _get_build_constant("POSTHOG_PROJECT_ID", ""),
64
+ description="PostHog project ID",
65
+ )
66
+ logfire_enabled: bool = Field(
67
+ default_factory=lambda: _get_build_constant("LOGFIRE_ENABLED", False),
68
+ description="Enable Logfire observability (dev builds only)",
69
+ )
70
+ logfire_token: str = Field(
71
+ default_factory=lambda: _get_build_constant("LOGFIRE_TOKEN", ""),
72
+ description="Logfire authentication token",
73
+ )
74
+
75
+ model_config = SettingsConfigDict(
76
+ env_prefix="SHOTGUN_",
77
+ env_file=".env",
78
+ env_file_encoding="utf-8",
79
+ extra="ignore",
80
+ )
81
+
82
+ @field_validator("logfire_enabled", mode="before")
83
+ @classmethod
84
+ def parse_bool(cls, v: Any) -> bool:
85
+ """Parse boolean values from strings (matches is_truthy behavior)."""
86
+ if isinstance(v, bool):
87
+ return v
88
+ if isinstance(v, str):
89
+ return v.lower() in ("true", "1", "yes")
90
+ return bool(v)
91
+
92
+
93
+ class LoggingSettings(BaseSettings):
94
+ """Logging configuration settings.
95
+
96
+ Controls log level, console output, and file logging behavior.
97
+ """
98
+
99
+ log_level: str = Field(
100
+ default="INFO",
101
+ description="Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
102
+ )
103
+ logging_to_console: bool = Field(
104
+ default=False,
105
+ description="Enable console logging output",
106
+ )
107
+ logging_to_file: bool = Field(
108
+ default=True,
109
+ description="Enable file logging output",
110
+ )
111
+ max_log_files: int = Field(
112
+ default=10,
113
+ description="Maximum number of log files to keep (older files are deleted)",
114
+ ge=1,
115
+ )
116
+
117
+ model_config = SettingsConfigDict(
118
+ env_prefix="SHOTGUN_",
119
+ env_file=".env",
120
+ env_file_encoding="utf-8",
121
+ extra="ignore",
122
+ )
123
+
124
+ @field_validator("log_level")
125
+ @classmethod
126
+ def validate_log_level(cls, v: str) -> str:
127
+ """Validate log level is one of the allowed values."""
128
+ v = v.upper()
129
+ valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
130
+ if v not in valid_levels:
131
+ return "INFO" # Default to INFO if invalid
132
+ return v
133
+
134
+ @field_validator("logging_to_console", "logging_to_file", mode="before")
135
+ @classmethod
136
+ def parse_bool(cls, v: Any) -> bool:
137
+ """Parse boolean values from strings (matches is_truthy behavior)."""
138
+ if isinstance(v, bool):
139
+ return v
140
+ if isinstance(v, str):
141
+ return v.lower() in ("true", "1", "yes")
142
+ return bool(v)
143
+
144
+
145
+ class ApiSettings(BaseSettings):
146
+ """API endpoint settings.
147
+
148
+ Configuration for Shotgun backend services.
149
+ """
150
+
151
+ web_base_url: str = Field(
152
+ default="https://api-219702594231.us-east4.run.app",
153
+ description="Shotgun Web API base URL (authentication/subscription)",
154
+ )
155
+ account_llm_base_url: str = Field(
156
+ default="https://litellm-219702594231.us-east4.run.app",
157
+ description="Shotgun's LiteLLM proxy base URL (AI model requests)",
158
+ )
159
+
160
+ model_config = SettingsConfigDict(
161
+ env_prefix="SHOTGUN_",
162
+ env_file=".env",
163
+ env_file_encoding="utf-8",
164
+ extra="ignore",
165
+ )
166
+
167
+
168
+ class DevelopmentSettings(BaseSettings):
169
+ """Development and testing settings.
170
+
171
+ These settings are primarily used for testing and development purposes.
172
+ """
173
+
174
+ home: str | None = Field(
175
+ default=None,
176
+ description="Override Shotgun home directory (for testing)",
177
+ )
178
+ pipx_simulate: bool = Field(
179
+ default=False,
180
+ description="Simulate pipx installation (for testing)",
181
+ )
182
+
183
+ model_config = SettingsConfigDict(
184
+ env_prefix="SHOTGUN_",
185
+ env_file=".env",
186
+ env_file_encoding="utf-8",
187
+ extra="ignore",
188
+ )
189
+
190
+ @field_validator("pipx_simulate", mode="before")
191
+ @classmethod
192
+ def parse_bool(cls, v: Any) -> bool:
193
+ """Parse boolean values from strings (matches is_truthy behavior)."""
194
+ if isinstance(v, bool):
195
+ return v
196
+ if isinstance(v, str):
197
+ return v.lower() in ("true", "1", "yes")
198
+ return bool(v)
199
+
200
+
201
+ class Settings(BaseSettings):
202
+ """Main application settings with SHOTGUN_ prefix.
203
+
204
+ This is the main settings class that composes all other settings groups.
205
+ Access settings via the global `settings` singleton instance.
206
+
207
+ Example:
208
+ from shotgun.settings import settings
209
+
210
+ # Telemetry settings
211
+ settings.telemetry.sentry_dsn
212
+ settings.telemetry.posthog_api_key
213
+ settings.telemetry.logfire_enabled
214
+
215
+ # Logging settings
216
+ settings.logging.log_level
217
+ settings.logging.logging_to_console
218
+
219
+ # API settings
220
+ settings.api.web_base_url
221
+ settings.api.account_llm_base_url
222
+
223
+ # Development settings
224
+ settings.dev.home
225
+ settings.dev.pipx_simulate
226
+ """
227
+
228
+ telemetry: TelemetrySettings = Field(default_factory=TelemetrySettings)
229
+ logging: LoggingSettings = Field(default_factory=LoggingSettings)
230
+ api: ApiSettings = Field(default_factory=ApiSettings)
231
+ dev: DevelopmentSettings = Field(default_factory=DevelopmentSettings)
232
+
233
+ model_config = SettingsConfigDict(
234
+ env_prefix="SHOTGUN_",
235
+ env_file=".env",
236
+ env_file_encoding="utf-8",
237
+ extra="ignore",
238
+ )
239
+
240
+
241
+ # Global settings singleton
242
+ # Import this in your modules: from shotgun.settings import settings
243
+ settings = Settings()
@@ -1,14 +1,48 @@
1
- """Shotgun Web API client for subscription and authentication."""
1
+ """Shotgun Web API client for subscription, authentication, and shared specs."""
2
2
 
3
3
  from .client import ShotgunWebClient, check_token_status, create_unification_token
4
+ from .exceptions import (
5
+ ConflictError,
6
+ ForbiddenError,
7
+ NotFoundError,
8
+ PayloadTooLargeError,
9
+ RateLimitExceededError,
10
+ ShotgunWebError,
11
+ UnauthorizedError,
12
+ )
4
13
  from .models import (
14
+ # Specs models
15
+ ErrorDetail,
16
+ ErrorResponse,
17
+ FileListResponse,
18
+ FileMetadata,
19
+ FileUploadRequest,
20
+ FileUploadResponse,
21
+ PermissionCheckResponse,
22
+ PublicSpecResponse,
23
+ SpecCreateRequest,
24
+ SpecCreateResponse,
25
+ SpecFileResponse,
26
+ SpecListResponse,
27
+ SpecResponse,
28
+ SpecUpdateRequest,
29
+ SpecVersionCreateRequest,
30
+ SpecVersionResponse,
31
+ SpecVersionState,
32
+ # Token models
5
33
  TokenCreateRequest,
6
34
  TokenCreateResponse,
7
35
  TokenStatus,
8
36
  TokenStatusResponse,
37
+ VersionCloseResponse,
38
+ VersionCreateResponse,
39
+ VersionListResponse,
40
+ WorkspaceRole,
9
41
  )
42
+ from .specs_client import SpecsClient
10
43
 
11
44
  __all__ = [
45
+ # Existing exports
12
46
  "ShotgunWebClient",
13
47
  "create_unification_token",
14
48
  "check_token_status",
@@ -16,4 +50,36 @@ __all__ = [
16
50
  "TokenCreateResponse",
17
51
  "TokenStatus",
18
52
  "TokenStatusResponse",
53
+ # Specs client
54
+ "SpecsClient",
55
+ # Exceptions
56
+ "ShotgunWebError",
57
+ "UnauthorizedError",
58
+ "ForbiddenError",
59
+ "NotFoundError",
60
+ "ConflictError",
61
+ "PayloadTooLargeError",
62
+ "RateLimitExceededError",
63
+ # Specs models
64
+ "ErrorDetail",
65
+ "ErrorResponse",
66
+ "FileListResponse",
67
+ "FileMetadata",
68
+ "FileUploadRequest",
69
+ "FileUploadResponse",
70
+ "PermissionCheckResponse",
71
+ "PublicSpecResponse",
72
+ "SpecCreateRequest",
73
+ "SpecCreateResponse",
74
+ "SpecFileResponse",
75
+ "SpecListResponse",
76
+ "SpecResponse",
77
+ "SpecUpdateRequest",
78
+ "SpecVersionCreateRequest",
79
+ "SpecVersionResponse",
80
+ "SpecVersionState",
81
+ "VersionCloseResponse",
82
+ "VersionCreateResponse",
83
+ "VersionListResponse",
84
+ "WorkspaceRole",
19
85
  ]
@@ -5,11 +5,13 @@ import httpx
5
5
  from shotgun.logging_config import get_logger
6
6
 
7
7
  from .constants import (
8
+ ME_PATH,
8
9
  SHOTGUN_WEB_BASE_URL,
9
10
  UNIFICATION_TOKEN_CREATE_PATH,
10
11
  UNIFICATION_TOKEN_STATUS_PATH,
11
12
  )
12
13
  from .models import (
14
+ MeResponse,
13
15
  TokenCreateRequest,
14
16
  TokenCreateResponse,
15
17
  TokenStatusResponse,
@@ -66,7 +68,7 @@ class ShotgunWebClient:
66
68
  return result
67
69
 
68
70
  except httpx.HTTPError as e:
69
- logger.error("Failed to create unification token: %s", e)
71
+ logger.error("Failed to create unification token at %s: %s", url, e)
70
72
  raise
71
73
 
72
74
  def check_token_status(self, token: str) -> TokenStatusResponse:
@@ -106,6 +108,45 @@ class ShotgunWebClient:
106
108
  logger.error("Failed to check token status: %s", e)
107
109
  raise
108
110
 
111
+ def get_me(self, jwt: str) -> MeResponse:
112
+ """Get current user info including workspace.
113
+
114
+ Args:
115
+ jwt: Supabase JWT for authentication
116
+
117
+ Returns:
118
+ User info including workspace details
119
+
120
+ Raises:
121
+ httpx.HTTPStatusError: If authentication fails (401) or other HTTP errors
122
+ httpx.HTTPError: For other request failures
123
+ """
124
+ url = f"{self.base_url}{ME_PATH}"
125
+
126
+ logger.debug("Fetching user info from /api/me")
127
+
128
+ try:
129
+ response = httpx.get(
130
+ url,
131
+ headers={"Authorization": f"Bearer {jwt}"},
132
+ timeout=self.timeout,
133
+ )
134
+ response.raise_for_status()
135
+
136
+ data = response.json()
137
+ result = MeResponse.model_validate(data)
138
+
139
+ logger.info("Successfully fetched user info for %s", result.email)
140
+ return result
141
+
142
+ except httpx.HTTPStatusError as e:
143
+ if e.response.status_code == 401:
144
+ logger.error("Authentication failed for /api/me")
145
+ raise
146
+ except httpx.HTTPError as e:
147
+ logger.error("Failed to fetch user info: %s", e)
148
+ raise
149
+
109
150
 
110
151
  # Convenience functions for standalone use
111
152
  def create_unification_token(shotgun_instance_id: str) -> TokenCreateResponse:
@@ -6,16 +6,62 @@ from shotgun.api_endpoints import SHOTGUN_WEB_BASE_URL
6
6
  # API endpoints
7
7
  UNIFICATION_TOKEN_CREATE_PATH = "/api/unification/token/create" # noqa: S105
8
8
  UNIFICATION_TOKEN_STATUS_PATH = "/api/unification/token/{token}/status" # noqa: S105
9
+ ME_PATH = "/api/me"
9
10
 
10
11
  # Polling configuration
11
12
  DEFAULT_POLL_INTERVAL_SECONDS = 3
12
13
  DEFAULT_TOKEN_TIMEOUT_SECONDS = 1800 # 30 minutes
13
14
 
15
+ # Workspaces API endpoint
16
+ WORKSPACES_PATH = "/api/workspaces"
17
+
18
+ # Specs API endpoints
19
+ PERMISSIONS_PATH = "/api/workspaces/{workspace_id}/specs/permissions"
20
+ SPECS_BASE_PATH = "/api/workspaces/{workspace_id}/specs"
21
+ SPECS_DETAIL_PATH = "/api/workspaces/{workspace_id}/specs/{spec_id}"
22
+ VERSIONS_PATH = "/api/workspaces/{workspace_id}/specs/{spec_id}/versions"
23
+ VERSION_DETAIL_PATH = (
24
+ "/api/workspaces/{workspace_id}/specs/{spec_id}/versions/{version_id}"
25
+ )
26
+ VERSION_CLOSE_PATH = (
27
+ "/api/workspaces/{workspace_id}/specs/{spec_id}/versions/{version_id}/close"
28
+ )
29
+ VERSION_SET_LATEST_PATH = (
30
+ "/api/workspaces/{workspace_id}/specs/{spec_id}/versions/{version_id}/set-latest"
31
+ )
32
+ FILES_PATH = (
33
+ "/api/workspaces/{workspace_id}/specs/{spec_id}/versions/{version_id}/files"
34
+ )
35
+ FILE_DETAIL_PATH = "/api/workspaces/{workspace_id}/specs/{spec_id}/versions/{version_id}/files/{file_id}"
36
+ PUBLIC_SPEC_PATH = "/api/public/specs/{spec_id}"
37
+ PUBLIC_SPEC_FILES_PATH = "/api/public/specs/{spec_id}/files"
38
+ PUBLIC_FILE_PATH = "/api/public/specs/{spec_id}/files/{file_id}"
39
+
40
+ # CLI convenience endpoint (version lookup by ID only)
41
+ VERSION_BY_ID_PATH = "/api/versions/{version_id}"
42
+
14
43
  # Re-export for backward compatibility
15
44
  __all__ = [
16
45
  "SHOTGUN_WEB_BASE_URL",
17
46
  "UNIFICATION_TOKEN_CREATE_PATH",
18
47
  "UNIFICATION_TOKEN_STATUS_PATH",
48
+ "ME_PATH",
19
49
  "DEFAULT_POLL_INTERVAL_SECONDS",
20
50
  "DEFAULT_TOKEN_TIMEOUT_SECONDS",
51
+ # Workspaces endpoint
52
+ "WORKSPACES_PATH",
53
+ # Specs endpoints
54
+ "PERMISSIONS_PATH",
55
+ "SPECS_BASE_PATH",
56
+ "SPECS_DETAIL_PATH",
57
+ "VERSIONS_PATH",
58
+ "VERSION_DETAIL_PATH",
59
+ "VERSION_CLOSE_PATH",
60
+ "VERSION_SET_LATEST_PATH",
61
+ "FILES_PATH",
62
+ "FILE_DETAIL_PATH",
63
+ "PUBLIC_SPEC_PATH",
64
+ "PUBLIC_SPEC_FILES_PATH",
65
+ "PUBLIC_FILE_PATH",
66
+ "VERSION_BY_ID_PATH",
21
67
  ]
@@ -0,0 +1,29 @@
1
+ """Typed exceptions for Shotgun Web API operations."""
2
+
3
+
4
+ class ShotgunWebError(Exception):
5
+ """Base exception for Shotgun Web API operations."""
6
+
7
+
8
+ class UnauthorizedError(ShotgunWebError):
9
+ """401 - Missing or invalid authentication token."""
10
+
11
+
12
+ class ForbiddenError(ShotgunWebError):
13
+ """403 - Insufficient permissions for the requested operation."""
14
+
15
+
16
+ class NotFoundError(ShotgunWebError):
17
+ """404 - Resource not found."""
18
+
19
+
20
+ class ConflictError(ShotgunWebError):
21
+ """409 - Name conflict or duplicate path."""
22
+
23
+
24
+ class PayloadTooLargeError(ShotgunWebError):
25
+ """413 - File exceeds maximum size limit."""
26
+
27
+
28
+ class RateLimitExceededError(ShotgunWebError):
29
+ """429 - Rate limit exceeded."""