indent 0.1.26__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 (55) hide show
  1. exponent/__init__.py +34 -0
  2. exponent/cli.py +110 -0
  3. exponent/commands/cloud_commands.py +585 -0
  4. exponent/commands/common.py +411 -0
  5. exponent/commands/config_commands.py +334 -0
  6. exponent/commands/run_commands.py +222 -0
  7. exponent/commands/settings.py +56 -0
  8. exponent/commands/types.py +111 -0
  9. exponent/commands/upgrade.py +29 -0
  10. exponent/commands/utils.py +146 -0
  11. exponent/core/config.py +180 -0
  12. exponent/core/graphql/__init__.py +0 -0
  13. exponent/core/graphql/client.py +61 -0
  14. exponent/core/graphql/get_chats_query.py +47 -0
  15. exponent/core/graphql/mutations.py +160 -0
  16. exponent/core/graphql/queries.py +146 -0
  17. exponent/core/graphql/subscriptions.py +16 -0
  18. exponent/core/remote_execution/checkpoints.py +212 -0
  19. exponent/core/remote_execution/cli_rpc_types.py +499 -0
  20. exponent/core/remote_execution/client.py +999 -0
  21. exponent/core/remote_execution/code_execution.py +77 -0
  22. exponent/core/remote_execution/default_env.py +31 -0
  23. exponent/core/remote_execution/error_info.py +45 -0
  24. exponent/core/remote_execution/exceptions.py +10 -0
  25. exponent/core/remote_execution/file_write.py +35 -0
  26. exponent/core/remote_execution/files.py +330 -0
  27. exponent/core/remote_execution/git.py +268 -0
  28. exponent/core/remote_execution/http_fetch.py +94 -0
  29. exponent/core/remote_execution/languages/python_execution.py +239 -0
  30. exponent/core/remote_execution/languages/shell_streaming.py +226 -0
  31. exponent/core/remote_execution/languages/types.py +20 -0
  32. exponent/core/remote_execution/port_utils.py +73 -0
  33. exponent/core/remote_execution/session.py +128 -0
  34. exponent/core/remote_execution/system_context.py +26 -0
  35. exponent/core/remote_execution/terminal_session.py +375 -0
  36. exponent/core/remote_execution/terminal_types.py +29 -0
  37. exponent/core/remote_execution/tool_execution.py +595 -0
  38. exponent/core/remote_execution/tool_type_utils.py +39 -0
  39. exponent/core/remote_execution/truncation.py +296 -0
  40. exponent/core/remote_execution/types.py +635 -0
  41. exponent/core/remote_execution/utils.py +477 -0
  42. exponent/core/types/__init__.py +0 -0
  43. exponent/core/types/command_data.py +206 -0
  44. exponent/core/types/event_types.py +89 -0
  45. exponent/core/types/generated/__init__.py +0 -0
  46. exponent/core/types/generated/strategy_info.py +213 -0
  47. exponent/migration-docs/login.md +112 -0
  48. exponent/py.typed +4 -0
  49. exponent/utils/__init__.py +0 -0
  50. exponent/utils/colors.py +92 -0
  51. exponent/utils/version.py +289 -0
  52. indent-0.1.26.dist-info/METADATA +38 -0
  53. indent-0.1.26.dist-info/RECORD +55 -0
  54. indent-0.1.26.dist-info/WHEEL +4 -0
  55. indent-0.1.26.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,89 @@
1
+ from datetime import datetime
2
+ from enum import Enum
3
+ from typing import Generic, Protocol, TypeVar
4
+
5
+ from pydantic import BaseModel, Field, JsonValue, ValidationInfo, field_validator
6
+
7
+ from exponent.core.types.command_data import (
8
+ DEFAULT_CODE_BLOCK_TIMEOUT,
9
+ WRITE_STRATEGY_NATURAL_EDIT,
10
+ CommandDataType,
11
+ EditContent,
12
+ FileWriteStrategyName,
13
+ NaturalEditContent,
14
+ )
15
+
16
+
17
+ class FileWriteErrorType(str, Enum):
18
+ TERMINATION_REQUESTED = "TERMINATION_REQUESTED"
19
+ NO_OP = "NO_OP"
20
+ FAILED_APPLY = "FAILED_APPLY"
21
+ FAILED_GENERATION = "FAILED_GENERATION"
22
+ CLI_DISCONNECTED = "CLI_DISCONNECTED"
23
+
24
+
25
+ class ExponentEvent(BaseModel):
26
+ chat_uuid: str
27
+ event_uuid: str
28
+ parent_uuid: str | None
29
+ turn_uuid: str
30
+
31
+ metadata: dict[str, JsonValue] = Field(default_factory=dict)
32
+
33
+
34
+ class PersistedExponentEvent(ExponentEvent):
35
+ db_timestamp: datetime | None = None
36
+
37
+
38
+ class CodeBlockEvent(PersistedExponentEvent):
39
+ language: str
40
+ content: str
41
+ timeout: int = DEFAULT_CODE_BLOCK_TIMEOUT
42
+ require_confirmation: bool = False
43
+
44
+
45
+ class FileWriteEvent(PersistedExponentEvent):
46
+ file_path: str
47
+ language: str
48
+ write_strategy: FileWriteStrategyName
49
+ write_content: NaturalEditContent | EditContent
50
+ content: str
51
+ error_content: str | None
52
+ error_type: FileWriteErrorType | None
53
+ require_confirmation: bool = False
54
+
55
+ @field_validator("write_content")
56
+ def validate_write_content_type(
57
+ cls, v: NaturalEditContent | EditContent, info: ValidationInfo
58
+ ) -> NaturalEditContent | EditContent:
59
+ write_strategy = info.data.get("write_strategy")
60
+ if write_strategy == WRITE_STRATEGY_NATURAL_EDIT:
61
+ if not isinstance(v, NaturalEditContent):
62
+ raise ValueError(
63
+ "When write_strategy is NATURAL_EDIT, write_content must be NaturalEditContent"
64
+ )
65
+ elif not isinstance(v, EditContent):
66
+ raise ValueError(
67
+ "For non-NATURAL_EDIT strategies, write_content must be EditContent"
68
+ )
69
+ return v
70
+
71
+
72
+ T = TypeVar("T", bound=CommandDataType)
73
+
74
+
75
+ class HoldsCommandData(Protocol, Generic[T]):
76
+ data: T
77
+
78
+
79
+ class CommandEvent(PersistedExponentEvent):
80
+ data: CommandDataType
81
+ require_confirmation: bool = False
82
+
83
+
84
+ class MultiCommandEvent(PersistedExponentEvent):
85
+ data: list[CommandDataType]
86
+ require_confirmation: bool = False
87
+
88
+
89
+ LocalEventType = FileWriteEvent | CodeBlockEvent | CommandEvent
File without changes
@@ -0,0 +1,213 @@
1
+ # Auto-generated, do not edit directly. Run `make generate_strategy_info` to update.
2
+
3
+ import enum
4
+
5
+ from pydantic import BaseModel
6
+
7
+
8
+ class StrategyName(str, enum.Enum):
9
+ NATURAL_EDIT_CLAUDE_3_7_XML = "NATURAL_EDIT_CLAUDE_3_7_XML"
10
+ READ_ONLY = "READ_ONLY"
11
+ NATURAL_EDIT_CLAUDE_3_7_XML_WITH_STEPS = "NATURAL_EDIT_CLAUDE_3_7_XML_WITH_STEPS"
12
+ NATURAL_EDIT_CLAUDE_3_7_FUNCTION_CALLING = (
13
+ "NATURAL_EDIT_CLAUDE_3_7_FUNCTION_CALLING"
14
+ )
15
+ SEARCH_REPLACE_FUNCTION_CALLING = "SEARCH_REPLACE_FUNCTION_CALLING"
16
+ FULL_FILE_REWRITE = "FULL_FILE_REWRITE"
17
+ FUNCTION_CALLING = "FUNCTION_CALLING"
18
+ RAW_GPT = "RAW_GPT"
19
+ NATURAL_EDIT = "NATURAL_EDIT"
20
+ AGENT = "AGENT"
21
+ AGENT_STEP = "AGENT_STEP"
22
+ DATABASE = "DATABASE"
23
+ DATABASE_AGENT = "DATABASE_AGENT"
24
+ DATABASE_AGENT_STEP = "DATABASE_AGENT_STEP"
25
+ REVIEW_FILES = "REVIEW_FILES"
26
+ EXPLORE_CODEBASE = "EXPLORE_CODEBASE"
27
+ GENERATE_REVIEW = "GENERATE_REVIEW"
28
+ CUSTOM_STEP = "CUSTOM_STEP"
29
+
30
+
31
+ class StrategyInfo(BaseModel):
32
+ strategy_name: StrategyName
33
+ display_name: str
34
+ description: str
35
+ disabled: bool
36
+ display_order: int
37
+ is_agentic: bool
38
+
39
+
40
+ STRATEGY_INFO_LIST: list[StrategyInfo] = [
41
+ StrategyInfo(
42
+ strategy_name=StrategyName.NATURAL_EDIT_CLAUDE_3_7_XML,
43
+ display_name="Natural Edit Claude 3.7 XML",
44
+ description="A natural file editing strategy that uses Claude 3.7 XML.",
45
+ disabled=False,
46
+ display_order=-1,
47
+ is_agentic=False,
48
+ ),
49
+ StrategyInfo(
50
+ strategy_name=StrategyName.READ_ONLY,
51
+ display_name="Read Only",
52
+ description="A strategy that allows you to read the codebase.",
53
+ disabled=False,
54
+ display_order=-1,
55
+ is_agentic=False,
56
+ ),
57
+ StrategyInfo(
58
+ strategy_name=StrategyName.NATURAL_EDIT_CLAUDE_3_7_XML_WITH_STEPS,
59
+ display_name="Natural Edit Claude 3.7 XML with Steps",
60
+ description="A natural file editing strategy that uses Claude 3.7 XML with steps.",
61
+ disabled=True,
62
+ display_order=0,
63
+ is_agentic=False,
64
+ ),
65
+ StrategyInfo(
66
+ strategy_name=StrategyName.NATURAL_EDIT_CLAUDE_3_7_FUNCTION_CALLING,
67
+ display_name="Natural Edit Claude 3.7 Function Calling",
68
+ description="A natural file editing strategy that uses Claude 3.7 Function Calling.",
69
+ disabled=True,
70
+ display_order=0,
71
+ is_agentic=True,
72
+ ),
73
+ StrategyInfo(
74
+ strategy_name=StrategyName.SEARCH_REPLACE_FUNCTION_CALLING,
75
+ display_name="Search Replace Function Calling",
76
+ description="A search replace strategy that uses Function Calling.",
77
+ disabled=True,
78
+ display_order=0,
79
+ is_agentic=True,
80
+ ),
81
+ StrategyInfo(
82
+ strategy_name=StrategyName.FULL_FILE_REWRITE,
83
+ display_name="Full File Rewrites",
84
+ description="Rewrites the full file every time. Use this if your files are generally less than 300 lines.",
85
+ disabled=False,
86
+ display_order=2,
87
+ is_agentic=False,
88
+ ),
89
+ StrategyInfo(
90
+ strategy_name=StrategyName.FUNCTION_CALLING,
91
+ display_name="Function Calling",
92
+ description="Using native function calling.",
93
+ disabled=True,
94
+ display_order=99,
95
+ is_agentic=False,
96
+ ),
97
+ StrategyInfo(
98
+ strategy_name=StrategyName.RAW_GPT,
99
+ display_name="Raw GPT",
100
+ description="No description",
101
+ disabled=True,
102
+ display_order=99,
103
+ is_agentic=False,
104
+ ),
105
+ StrategyInfo(
106
+ strategy_name=StrategyName.NATURAL_EDIT,
107
+ display_name="Natural Edit",
108
+ description="Deprecated",
109
+ disabled=True,
110
+ display_order=99,
111
+ is_agentic=False,
112
+ ),
113
+ StrategyInfo(
114
+ strategy_name=StrategyName.AGENT,
115
+ display_name="Agent",
116
+ description="No description",
117
+ disabled=True,
118
+ display_order=99,
119
+ is_agentic=True,
120
+ ),
121
+ StrategyInfo(
122
+ strategy_name=StrategyName.AGENT_STEP,
123
+ display_name="Agent Step",
124
+ description="No description",
125
+ disabled=True,
126
+ display_order=99,
127
+ is_agentic=False,
128
+ ),
129
+ StrategyInfo(
130
+ strategy_name=StrategyName.DATABASE,
131
+ display_name="Database",
132
+ description="No description",
133
+ disabled=True,
134
+ display_order=99,
135
+ is_agentic=False,
136
+ ),
137
+ StrategyInfo(
138
+ strategy_name=StrategyName.DATABASE_AGENT,
139
+ display_name="Database Agent",
140
+ description="No description",
141
+ disabled=True,
142
+ display_order=99,
143
+ is_agentic=True,
144
+ ),
145
+ StrategyInfo(
146
+ strategy_name=StrategyName.DATABASE_AGENT_STEP,
147
+ display_name="Database Agent Step",
148
+ description="No description",
149
+ disabled=True,
150
+ display_order=99,
151
+ is_agentic=False,
152
+ ),
153
+ StrategyInfo(
154
+ strategy_name=StrategyName.REVIEW_FILES,
155
+ display_name="Review Files",
156
+ description="No description",
157
+ disabled=True,
158
+ display_order=99,
159
+ is_agentic=False,
160
+ ),
161
+ StrategyInfo(
162
+ strategy_name=StrategyName.EXPLORE_CODEBASE,
163
+ display_name="Explore Codebase",
164
+ description="No description",
165
+ disabled=True,
166
+ display_order=99,
167
+ is_agentic=False,
168
+ ),
169
+ StrategyInfo(
170
+ strategy_name=StrategyName.GENERATE_REVIEW,
171
+ display_name="Generate Review",
172
+ description="No description",
173
+ disabled=True,
174
+ display_order=99,
175
+ is_agentic=False,
176
+ ),
177
+ StrategyInfo(
178
+ strategy_name=StrategyName.CUSTOM_STEP,
179
+ display_name="Custom Step",
180
+ description="No description",
181
+ disabled=True,
182
+ display_order=99,
183
+ is_agentic=False,
184
+ ),
185
+ ]
186
+
187
+
188
+ ENABLED_STRATEGY_INFO_LIST: list[StrategyInfo] = [
189
+ StrategyInfo(
190
+ strategy_name=StrategyName.NATURAL_EDIT_CLAUDE_3_7_XML,
191
+ display_name="Natural Edit Claude 3.7 XML",
192
+ description="A natural file editing strategy that uses Claude 3.7 XML.",
193
+ disabled=False,
194
+ display_order=-1,
195
+ is_agentic=False,
196
+ ),
197
+ StrategyInfo(
198
+ strategy_name=StrategyName.READ_ONLY,
199
+ display_name="Read Only",
200
+ description="A strategy that allows you to read the codebase.",
201
+ disabled=False,
202
+ display_order=-1,
203
+ is_agentic=False,
204
+ ),
205
+ StrategyInfo(
206
+ strategy_name=StrategyName.FULL_FILE_REWRITE,
207
+ display_name="Full File Rewrites",
208
+ description="Rewrites the full file every time. Use this if your files are generally less than 300 lines.",
209
+ disabled=False,
210
+ display_order=2,
211
+ is_agentic=False,
212
+ ),
213
+ ]
@@ -0,0 +1,112 @@
1
+ # Authentication and Login System Documentation
2
+
3
+ ## Overview
4
+ The authentication system in Exponent is built around API keys and GraphQL-based authentication. The system supports multiple environments (development, staging, production) with different API endpoints and authentication mechanisms.
5
+
6
+ ## Key Components
7
+
8
+ ### 1. Configuration and Settings
9
+ **Location**: `/Users/dkzlv/Projects/exponent-wrapper/exponent/python_modules/exponent/exponent/core/config.py`
10
+
11
+ The settings system manages:
12
+ - API keys for different environments (development, staging, production)
13
+ - Base URLs for different services
14
+ - Configuration storage in `~/.config/exponent/config.json`
15
+
16
+ ### 2. Login Command
17
+ **Location**: `/Users/dkzlv/Projects/exponent-wrapper/exponent/python_modules/exponent/exponent/commands/config_commands.py`
18
+
19
+ The login command:
20
+ - Accepts an API key via `indent login --key <API-KEY>`
21
+ - Verifies the API key with the server
22
+ - Stores the verified key in the config file
23
+
24
+ ### 3. Authentication Flow
25
+
26
+ #### Initial Authentication
27
+ 1. When a command is run, the system checks for an API key
28
+ 2. If no API key is found:
29
+ - In SSH session: Shows message to run `indent login --key <API-KEY>`
30
+ - Otherwise: Redirects to `<base_url>/cli` for web-based login
31
+
32
+ #### API Key Verification
33
+ **Location**: `/Users/dkzlv/Projects/exponent-wrapper/exponent/python_modules/exponent/exponent/commands/common.py`
34
+
35
+ The system verifies API keys through:
36
+ 1. GraphQL mutation `SET_LOGIN_COMPLETE_MUTATION`
37
+ 2. Verification of returned API key matching the provided key
38
+
39
+ #### API Key Refresh
40
+ **Location**: `/Users/dkzlv/Projects/exponent-wrapper/exponent/python_modules/exponent/exponent/commands/config_commands.py`
41
+
42
+ Users can refresh their API key using:
43
+ - Command: `exponent config refresh-key`
44
+ - Uses `REFRESH_API_KEY_MUTATION` GraphQL mutation
45
+ - Automatically updates the stored API key
46
+
47
+ ### 4. GraphQL Authentication
48
+
49
+ #### Client Setup
50
+ **Location**: `/Users/dkzlv/Projects/exponent-wrapper/exponent/python_modules/exponent/exponent/core/graphql/client.py`
51
+
52
+ The GraphQL client:
53
+ - Adds API key to HTTP headers for REST calls
54
+ - Includes API key in WebSocket init payload
55
+ - Handles both HTTP and WebSocket connections
56
+
57
+ #### Key Mutations and Subscriptions
58
+ **Location**: `/Users/dkzlv/Projects/exponent-wrapper/exponent/python_modules/exponent/exponent/core/graphql/mutations.py`
59
+
60
+ Important GraphQL operations:
61
+ ```graphql
62
+ SET_LOGIN_COMPLETE_MUTATION
63
+ REFRESH_API_KEY_MUTATION
64
+ AUTHENTICATED_USER_SUBSCRIPTION
65
+ ```
66
+
67
+ ### 5. Environment Support
68
+ **Location**: `/Users/dkzlv/Projects/exponent-wrapper/exponent/python_modules/exponent/exponent/core/config.py`
69
+
70
+ Supports multiple environments:
71
+ - Development: `localhost:3000` (API), `localhost:8000` (WebSocket)
72
+ - Staging: `staging.exponent.run`
73
+ - Production: `exponent.run`
74
+
75
+ ## Security Considerations
76
+
77
+ 1. API Key Storage
78
+ - Keys stored in `~/.config/exponent/config.json`
79
+ - Separate keys for different environments
80
+
81
+ 2. SSL/TLS Security
82
+ - System checks for SSL certificates
83
+ - Can install certificates if missing (macOS specific)
84
+ - Uses `certifi` for certificate verification
85
+
86
+ ## Error Handling
87
+
88
+ The system handles various authentication errors:
89
+ - Invalid API keys
90
+ - Network connection issues
91
+ - SSL certificate problems
92
+ - Environment-specific configuration issues
93
+
94
+ ## Related Files (Absolute Paths)
95
+
96
+ 1. `/Users/dkzlv/Projects/exponent-wrapper/exponent/python_modules/exponent/exponent/core/config.py`
97
+ - Core configuration and settings management
98
+
99
+ 2. `/Users/dkzlv/Projects/exponent-wrapper/exponent/python_modules/exponent/exponent/commands/config_commands.py`
100
+ - Login and API key management commands
101
+
102
+ 3. `/Users/dkzlv/Projects/exponent-wrapper/exponent/python_modules/exponent/exponent/commands/common.py`
103
+ - Authentication helpers and utilities
104
+
105
+ 4. `/Users/dkzlv/Projects/exponent-wrapper/exponent/python_modules/exponent/exponent/core/graphql/client.py`
106
+ - GraphQL client implementation
107
+
108
+ 5. `/Users/dkzlv/Projects/exponent-wrapper/exponent/python_modules/exponent/exponent/core/graphql/mutations.py`
109
+ - Authentication-related GraphQL mutations
110
+
111
+ 6. `/Users/dkzlv/Projects/exponent-wrapper/exponent/python_modules/exponent/exponent/core/graphql/subscriptions.py`
112
+ - Authentication-related GraphQL subscriptions
exponent/py.typed ADDED
@@ -0,0 +1,4 @@
1
+ # This is a typing sentinel. It's required to make type checkers regard this as a typed library when the library is installed as a dependency.
2
+ # since this code is installed inside the exponent_server package as well, via a dependency, we have to ensure this sentinel is in place.
3
+
4
+ # further reading: https://peps.python.org/pep-0561/
File without changes
@@ -0,0 +1,92 @@
1
+ import math
2
+
3
+ from colour import Color
4
+
5
+
6
+ def relative_luminance(c: Color) -> float:
7
+ """Compute sRGB-based luminance, per the standard (WCAG-like) formula."""
8
+
9
+ r, g, b = c.rgb
10
+
11
+ def to_linear(channel: float) -> float:
12
+ return (
13
+ channel / 12.92
14
+ if channel <= 0.03928
15
+ else ((channel + 0.055) / 1.055) ** 2.4
16
+ )
17
+
18
+ R, G, B = map(to_linear, (r, g, b))
19
+
20
+ return 0.2126 * R + 0.7152 * G + 0.0722 * B
21
+
22
+
23
+ def contrast_ratio(c1: Color, c2: Color) -> float:
24
+ """Compute ratio = (Llighter + 0.05) / (Ldarker + 0.05)."""
25
+
26
+ L1 = relative_luminance(c1)
27
+ L2 = relative_luminance(c2)
28
+ L_high, L_low = max(L1, L2), min(L1, L2)
29
+
30
+ return (L_high + 0.05) / (L_low + 0.05)
31
+
32
+
33
+ def adjust_color_for_contrast(
34
+ base: Color, target: Color, min_contrast: float = 4.5, step: float = 0.005
35
+ ) -> Color:
36
+ """
37
+ Increments either upward or downward in HSL 'l'
38
+ (depending on whether target is lighter or darker than base)
39
+ until the desired contrast ratio is reached or we hit the boundary (0 or 1).
40
+ """
41
+
42
+ # Work on a copy so we don't mutate the original
43
+ new_color = Color(target.hex)
44
+
45
+ L_base = relative_luminance(base)
46
+ L_target = relative_luminance(new_color)
47
+
48
+ # Check which color is darker:
49
+ # if the target is darker, we'll push it darker; if it's lighter, we push it lighter.
50
+ h, s, l = new_color.hsl # noqa: E741
51
+
52
+ if L_target < L_base:
53
+ # target color is darker => keep making it darker
54
+ while l >= 0.0:
55
+ if contrast_ratio(base, new_color) >= min_contrast:
56
+ return new_color
57
+ l -= step # noqa: E741
58
+ new_color.hsl = (h, s, max(l, 0.0))
59
+ else:
60
+ # target color is lighter => keep making it lighter
61
+ while l <= 1.0:
62
+ if contrast_ratio(base, new_color) >= min_contrast:
63
+ return new_color
64
+ l += step # noqa: E741
65
+ new_color.hsl = (h, s, min(l, 1.0))
66
+
67
+ # If we exhaust the channel range, just return whatever we ended with
68
+ return new_color
69
+
70
+
71
+ def blend_colors_srgb(c1: Color, c2: Color, alpha: float) -> Color:
72
+ """
73
+ Blend c1 and c2 in sRGB space using alpha in [0..1],
74
+ returning a new Color object.
75
+ E.g. alpha=0.0 => c1, alpha=1.0 => c2.
76
+ """
77
+
78
+ # Interpolate each channel separately
79
+ r = (1 - alpha) * c1.red + alpha * c2.red
80
+ g = (1 - alpha) * c1.green + alpha * c2.green
81
+ b = (1 - alpha) * c1.blue + alpha * c2.blue
82
+
83
+ # Return a new Color with the blended channels
84
+ return Color(rgb=(r, g, b))
85
+
86
+
87
+ def color_distance(c1: Color, c2: Color) -> float:
88
+ return math.sqrt(
89
+ math.pow(c1.red - c2.red, 2)
90
+ + math.pow(c1.green - c2.green, 2)
91
+ + math.pow(c1.blue - c2.blue, 2)
92
+ )