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.
- exponent/__init__.py +34 -0
- exponent/cli.py +110 -0
- exponent/commands/cloud_commands.py +585 -0
- exponent/commands/common.py +411 -0
- exponent/commands/config_commands.py +334 -0
- exponent/commands/run_commands.py +222 -0
- exponent/commands/settings.py +56 -0
- exponent/commands/types.py +111 -0
- exponent/commands/upgrade.py +29 -0
- exponent/commands/utils.py +146 -0
- exponent/core/config.py +180 -0
- exponent/core/graphql/__init__.py +0 -0
- exponent/core/graphql/client.py +61 -0
- exponent/core/graphql/get_chats_query.py +47 -0
- exponent/core/graphql/mutations.py +160 -0
- exponent/core/graphql/queries.py +146 -0
- exponent/core/graphql/subscriptions.py +16 -0
- exponent/core/remote_execution/checkpoints.py +212 -0
- exponent/core/remote_execution/cli_rpc_types.py +499 -0
- exponent/core/remote_execution/client.py +999 -0
- exponent/core/remote_execution/code_execution.py +77 -0
- exponent/core/remote_execution/default_env.py +31 -0
- exponent/core/remote_execution/error_info.py +45 -0
- exponent/core/remote_execution/exceptions.py +10 -0
- exponent/core/remote_execution/file_write.py +35 -0
- exponent/core/remote_execution/files.py +330 -0
- exponent/core/remote_execution/git.py +268 -0
- exponent/core/remote_execution/http_fetch.py +94 -0
- exponent/core/remote_execution/languages/python_execution.py +239 -0
- exponent/core/remote_execution/languages/shell_streaming.py +226 -0
- exponent/core/remote_execution/languages/types.py +20 -0
- exponent/core/remote_execution/port_utils.py +73 -0
- exponent/core/remote_execution/session.py +128 -0
- exponent/core/remote_execution/system_context.py +26 -0
- exponent/core/remote_execution/terminal_session.py +375 -0
- exponent/core/remote_execution/terminal_types.py +29 -0
- exponent/core/remote_execution/tool_execution.py +595 -0
- exponent/core/remote_execution/tool_type_utils.py +39 -0
- exponent/core/remote_execution/truncation.py +296 -0
- exponent/core/remote_execution/types.py +635 -0
- exponent/core/remote_execution/utils.py +477 -0
- exponent/core/types/__init__.py +0 -0
- exponent/core/types/command_data.py +206 -0
- exponent/core/types/event_types.py +89 -0
- exponent/core/types/generated/__init__.py +0 -0
- exponent/core/types/generated/strategy_info.py +213 -0
- exponent/migration-docs/login.md +112 -0
- exponent/py.typed +4 -0
- exponent/utils/__init__.py +0 -0
- exponent/utils/colors.py +92 -0
- exponent/utils/version.py +289 -0
- indent-0.1.26.dist-info/METADATA +38 -0
- indent-0.1.26.dist-info/RECORD +55 -0
- indent-0.1.26.dist-info/WHEEL +4 -0
- 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
|
exponent/utils/colors.py
ADDED
|
@@ -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
|
+
)
|