indent 0.0.8__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 indent might be problematic. Click here for more details.

Files changed (56) hide show
  1. exponent/__init__.py +1 -0
  2. exponent/cli.py +112 -0
  3. exponent/commands/cloud_commands.py +85 -0
  4. exponent/commands/common.py +434 -0
  5. exponent/commands/config_commands.py +581 -0
  6. exponent/commands/github_app_commands.py +211 -0
  7. exponent/commands/listen_commands.py +96 -0
  8. exponent/commands/run_commands.py +208 -0
  9. exponent/commands/settings.py +56 -0
  10. exponent/commands/shell_commands.py +2840 -0
  11. exponent/commands/theme.py +246 -0
  12. exponent/commands/types.py +111 -0
  13. exponent/commands/upgrade.py +29 -0
  14. exponent/commands/utils.py +236 -0
  15. exponent/core/config.py +180 -0
  16. exponent/core/graphql/__init__.py +0 -0
  17. exponent/core/graphql/client.py +59 -0
  18. exponent/core/graphql/cloud_config_queries.py +77 -0
  19. exponent/core/graphql/get_chats_query.py +47 -0
  20. exponent/core/graphql/github_config_queries.py +56 -0
  21. exponent/core/graphql/mutations.py +75 -0
  22. exponent/core/graphql/queries.py +110 -0
  23. exponent/core/graphql/subscriptions.py +452 -0
  24. exponent/core/remote_execution/checkpoints.py +212 -0
  25. exponent/core/remote_execution/cli_rpc_types.py +214 -0
  26. exponent/core/remote_execution/client.py +545 -0
  27. exponent/core/remote_execution/code_execution.py +58 -0
  28. exponent/core/remote_execution/command_execution.py +105 -0
  29. exponent/core/remote_execution/error_info.py +45 -0
  30. exponent/core/remote_execution/exceptions.py +10 -0
  31. exponent/core/remote_execution/file_write.py +410 -0
  32. exponent/core/remote_execution/files.py +415 -0
  33. exponent/core/remote_execution/git.py +268 -0
  34. exponent/core/remote_execution/languages/python_execution.py +239 -0
  35. exponent/core/remote_execution/languages/shell_streaming.py +221 -0
  36. exponent/core/remote_execution/languages/types.py +20 -0
  37. exponent/core/remote_execution/session.py +128 -0
  38. exponent/core/remote_execution/system_context.py +54 -0
  39. exponent/core/remote_execution/tool_execution.py +289 -0
  40. exponent/core/remote_execution/truncation.py +284 -0
  41. exponent/core/remote_execution/types.py +670 -0
  42. exponent/core/remote_execution/utils.py +600 -0
  43. exponent/core/types/__init__.py +0 -0
  44. exponent/core/types/command_data.py +206 -0
  45. exponent/core/types/event_types.py +89 -0
  46. exponent/core/types/generated/__init__.py +0 -0
  47. exponent/core/types/generated/strategy_info.py +225 -0
  48. exponent/migration-docs/login.md +112 -0
  49. exponent/py.typed +4 -0
  50. exponent/utils/__init__.py +0 -0
  51. exponent/utils/colors.py +92 -0
  52. exponent/utils/version.py +289 -0
  53. indent-0.0.8.dist-info/METADATA +36 -0
  54. indent-0.0.8.dist-info/RECORD +56 -0
  55. indent-0.0.8.dist-info/WHEEL +4 -0
  56. indent-0.0.8.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,206 @@
1
+ from abc import ABC
2
+ from enum import Enum
3
+ from typing import Annotated, Any, ClassVar, Literal
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+ WRITE_STRATEGY_FULL_FILE_REWRITE: Literal["FULL_FILE_REWRITE"] = "FULL_FILE_REWRITE"
8
+ DEFAULT_CODE_BLOCK_TIMEOUT = 30
9
+ WRITE_STRATEGY_NATURAL_EDIT: Literal["NATURAL_EDIT"] = "NATURAL_EDIT"
10
+ WRITE_STRATEGY_SEARCH_REPLACE: Literal["SEARCH_REPLACE"] = "SEARCH_REPLACE"
11
+ WRITE_STRATEGY_UDIFF: Literal["UDIFF"] = "UDIFF"
12
+
13
+ FileWriteStrategyName = Literal[
14
+ "FULL_FILE_REWRITE", "UDIFF", "SEARCH_REPLACE", "NATURAL_EDIT"
15
+ ]
16
+
17
+
18
+ class CommandType(str, Enum):
19
+ THINKING = "thinking"
20
+ FILE_READ = "file_read"
21
+ SUMMARIZE = "summarize"
22
+ STEP_OUTPUT = "step_output"
23
+ PROTOTYPE = "prototype"
24
+ DB_QUERY = "db_query"
25
+ DB_GET_TABLE_NAMES = "db_get_table_names"
26
+ DB_GET_TABLE_SCHEMA = "db_get_table_schema"
27
+ ANSWER = "answer"
28
+ ASK = "ask"
29
+ SHELL = "shell"
30
+ PYTHON = "python"
31
+ FILE_WRITE = "file_write"
32
+
33
+
34
+ class CommandData(BaseModel):
35
+ executable: ClassVar[bool]
36
+
37
+
38
+ class FileReadCommandData(CommandData):
39
+ executable: ClassVar[bool] = True
40
+ type: Literal[CommandType.FILE_READ] = CommandType.FILE_READ
41
+
42
+ file_path: str
43
+ language: str
44
+ limit: int | None = None
45
+ offset: int | None = None
46
+
47
+
48
+ class ThinkingCommandData(CommandData):
49
+ executable: ClassVar[bool] = False
50
+ type: Literal[CommandType.THINKING] = CommandType.THINKING
51
+
52
+ content: str
53
+ signature: str | None = None
54
+
55
+
56
+ class PrototypeCommandData(CommandData):
57
+ executable: ClassVar[bool] = True
58
+ type: Literal[CommandType.PROTOTYPE] = CommandType.PROTOTYPE
59
+
60
+ command_name: str
61
+ # Structured data extracted from LLM output
62
+ content_json: dict[str, Any]
63
+ # Raw text extracted from LLM output
64
+ content_raw: str
65
+ # Rendered LLM output for frontend display
66
+ content_rendered: str
67
+
68
+ llm_command_name_override: str | None = None
69
+
70
+ @property
71
+ def llm_command_name(self) -> str:
72
+ return self.llm_command_name_override or self.command_name
73
+
74
+
75
+ # deprecated, use StepOutputCommandData instead
76
+ class SummarizeCommandData(CommandData):
77
+ executable: ClassVar[bool] = True
78
+ type: Literal[CommandType.SUMMARIZE] = CommandType.SUMMARIZE
79
+
80
+ summary: str
81
+
82
+
83
+ class StepOutputCommandData(CommandData):
84
+ executable: ClassVar[bool] = True
85
+ type: Literal[CommandType.STEP_OUTPUT] = CommandType.STEP_OUTPUT
86
+
87
+ step_output_raw: str
88
+
89
+
90
+ class DBQueryCommandData(CommandData):
91
+ def __init__(
92
+ self,
93
+ **kwargs: Any,
94
+ ) -> None:
95
+ super().__init__(**kwargs)
96
+
97
+ executable: ClassVar[bool] = True
98
+ type: Literal[CommandType.DB_QUERY] = CommandType.DB_QUERY
99
+
100
+ query: str
101
+ max_gigabytes_billed: float | None = None # BigQuery only
102
+
103
+
104
+ class DBGetTableNamesCommandData(CommandData):
105
+ executable: ClassVar[bool] = True
106
+ type: Literal[CommandType.DB_GET_TABLE_NAMES] = CommandType.DB_GET_TABLE_NAMES
107
+
108
+
109
+ class DBGetTableSchemaCommandData(CommandData):
110
+ executable: ClassVar[bool] = True
111
+ type: Literal[CommandType.DB_GET_TABLE_SCHEMA] = CommandType.DB_GET_TABLE_SCHEMA
112
+
113
+ table_name: str
114
+
115
+
116
+ class AnswerCommandData(CommandData):
117
+ executable: ClassVar[bool] = False
118
+ type: Literal[CommandType.ANSWER] = CommandType.ANSWER
119
+
120
+ answer_raw: str
121
+
122
+
123
+ class AskCommandData(CommandData):
124
+ executable: ClassVar[bool] = False
125
+ type: Literal[CommandType.ASK] = CommandType.ASK
126
+
127
+ ask_raw: str
128
+
129
+
130
+ class ShellCommandData(CommandData):
131
+ exclude_from_schema_gen: ClassVar[bool] = True
132
+
133
+ executable: ClassVar[bool] = True
134
+ type: Literal[CommandType.SHELL] = CommandType.SHELL
135
+
136
+ timeout: int = DEFAULT_CODE_BLOCK_TIMEOUT
137
+ content: str
138
+
139
+
140
+ class PythonCommandData(CommandData):
141
+ exclude_from_schema_gen: ClassVar[bool] = True
142
+
143
+ executable: ClassVar[bool] = True
144
+ type: Literal[CommandType.PYTHON] = CommandType.PYTHON
145
+
146
+ content: str
147
+
148
+
149
+ class EditContent(BaseModel):
150
+ content: str
151
+ original_file: str | None = None
152
+
153
+
154
+ class NaturalEditContent(BaseModel):
155
+ natural_edit: str
156
+ intermediate_edit: str | None
157
+ original_file: str | None
158
+ new_file: str | None
159
+ error_content: str | None
160
+
161
+ @property
162
+ def is_resolved(self) -> bool:
163
+ return self.new_file is not None or self.error_content is not None
164
+
165
+ @property
166
+ def is_noop(self) -> bool:
167
+ return bool(
168
+ self.new_file is not None
169
+ and self.original_file is not None
170
+ and self.new_file == self.original_file
171
+ )
172
+
173
+
174
+ class FileWriteCommandData(CommandData):
175
+ exclude_from_schema_gen: ClassVar[bool] = True
176
+
177
+ executable: ClassVar[bool] = True
178
+ type: Literal[CommandType.FILE_WRITE] = CommandType.FILE_WRITE
179
+
180
+ file_path: str
181
+ language: str
182
+ write_strategy: FileWriteStrategyName
183
+ write_content: NaturalEditContent | EditContent
184
+ content: str
185
+
186
+
187
+ CommandDataType = Annotated[
188
+ FileReadCommandData
189
+ | ThinkingCommandData
190
+ | PrototypeCommandData
191
+ | SummarizeCommandData
192
+ | DBQueryCommandData
193
+ | DBGetTableNamesCommandData
194
+ | DBGetTableSchemaCommandData
195
+ | StepOutputCommandData
196
+ | AnswerCommandData
197
+ | AskCommandData
198
+ | ShellCommandData
199
+ | PythonCommandData
200
+ | FileWriteCommandData,
201
+ Field(discriminator="type"),
202
+ ]
203
+
204
+
205
+ class CommandImpl(ABC):
206
+ command_data_type: ClassVar[type[CommandData]]
@@ -0,0 +1,89 @@
1
+ from datetime import datetime
2
+ from enum import Enum
3
+ from typing import Generic, Protocol, TypeVar, Union
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 = Union[FileWriteEvent, CodeBlockEvent, CommandEvent]
File without changes
@@ -0,0 +1,225 @@
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
+ from exponent.core.remote_execution.types import ChatMode
8
+
9
+
10
+ class StrategyName(str, enum.Enum):
11
+ NATURAL_EDIT_CLAUDE_3_7_XML = "NATURAL_EDIT_CLAUDE_3_7_XML"
12
+ READ_ONLY = "READ_ONLY"
13
+ NATURAL_EDIT_CLAUDE_3_7_XML_WITH_STEPS = "NATURAL_EDIT_CLAUDE_3_7_XML_WITH_STEPS"
14
+ NATURAL_EDIT_CLAUDE_3_7_FUNCTION_CALLING = (
15
+ "NATURAL_EDIT_CLAUDE_3_7_FUNCTION_CALLING"
16
+ )
17
+ SEARCH_REPLACE_FUNCTION_CALLING = "SEARCH_REPLACE_FUNCTION_CALLING"
18
+ FULL_FILE_REWRITE = "FULL_FILE_REWRITE"
19
+ FUNCTION_CALLING = "FUNCTION_CALLING"
20
+ RAW_GPT = "RAW_GPT"
21
+ NATURAL_EDIT = "NATURAL_EDIT"
22
+ AGENT = "AGENT"
23
+ AGENT_STEP = "AGENT_STEP"
24
+ DATABASE = "DATABASE"
25
+ DATABASE_AGENT = "DATABASE_AGENT"
26
+ DATABASE_AGENT_STEP = "DATABASE_AGENT_STEP"
27
+ REVIEW_FILES = "REVIEW_FILES"
28
+ EXPLORE_CODEBASE = "EXPLORE_CODEBASE"
29
+ GENERATE_REVIEW = "GENERATE_REVIEW"
30
+ CUSTOM_STEP = "CUSTOM_STEP"
31
+
32
+
33
+ class StrategyInfo(BaseModel):
34
+ strategy_name: StrategyName
35
+ display_name: str
36
+ description: str
37
+ disabled: bool
38
+ display_order: int
39
+ is_agentic: bool
40
+
41
+
42
+ CHAT_MODE_DEFAULTS: dict[ChatMode, StrategyName] = {
43
+ ChatMode.DEFAULT: StrategyName.RAW_GPT,
44
+ ChatMode.CLI: StrategyName.NATURAL_EDIT_CLAUDE_3_7_XML,
45
+ ChatMode.CLOUD: StrategyName.NATURAL_EDIT_CLAUDE_3_7_XML,
46
+ ChatMode.DATABASE: StrategyName.DATABASE,
47
+ ChatMode.PYTHON_INTERPRETER: StrategyName.NATURAL_EDIT_CLAUDE_3_7_XML,
48
+ ChatMode.WORKFLOW: StrategyName.NATURAL_EDIT_CLAUDE_3_7_XML,
49
+ ChatMode.CODEBASE: StrategyName.READ_ONLY,
50
+ }
51
+
52
+ STRATEGY_INFO_LIST: list[StrategyInfo] = [
53
+ StrategyInfo(
54
+ strategy_name=StrategyName.NATURAL_EDIT_CLAUDE_3_7_XML,
55
+ display_name="Natural Edit Claude 3.7 XML",
56
+ description="A natural file editing strategy that uses Claude 3.7 XML.",
57
+ disabled=False,
58
+ display_order=-1,
59
+ is_agentic=False,
60
+ ),
61
+ StrategyInfo(
62
+ strategy_name=StrategyName.READ_ONLY,
63
+ display_name="Read Only",
64
+ description="A strategy that allows you to read the codebase.",
65
+ disabled=False,
66
+ display_order=-1,
67
+ is_agentic=False,
68
+ ),
69
+ StrategyInfo(
70
+ strategy_name=StrategyName.NATURAL_EDIT_CLAUDE_3_7_XML_WITH_STEPS,
71
+ display_name="Natural Edit Claude 3.7 XML with Steps",
72
+ description="A natural file editing strategy that uses Claude 3.7 XML with steps.",
73
+ disabled=True,
74
+ display_order=0,
75
+ is_agentic=False,
76
+ ),
77
+ StrategyInfo(
78
+ strategy_name=StrategyName.NATURAL_EDIT_CLAUDE_3_7_FUNCTION_CALLING,
79
+ display_name="Natural Edit Claude 3.7 Function Calling",
80
+ description="A natural file editing strategy that uses Claude 3.7 Function Calling.",
81
+ disabled=True,
82
+ display_order=0,
83
+ is_agentic=True,
84
+ ),
85
+ StrategyInfo(
86
+ strategy_name=StrategyName.SEARCH_REPLACE_FUNCTION_CALLING,
87
+ display_name="Search Replace Function Calling",
88
+ description="A search replace strategy that uses Function Calling.",
89
+ disabled=True,
90
+ display_order=0,
91
+ is_agentic=True,
92
+ ),
93
+ StrategyInfo(
94
+ strategy_name=StrategyName.FULL_FILE_REWRITE,
95
+ display_name="Full File Rewrites",
96
+ description="Rewrites the full file every time. Use this if your files are generally less than 300 lines.",
97
+ disabled=False,
98
+ display_order=2,
99
+ is_agentic=False,
100
+ ),
101
+ StrategyInfo(
102
+ strategy_name=StrategyName.FUNCTION_CALLING,
103
+ display_name="Function Calling",
104
+ description="Using native function calling.",
105
+ disabled=True,
106
+ display_order=99,
107
+ is_agentic=False,
108
+ ),
109
+ StrategyInfo(
110
+ strategy_name=StrategyName.RAW_GPT,
111
+ display_name="Raw GPT",
112
+ description="No description",
113
+ disabled=True,
114
+ display_order=99,
115
+ is_agentic=False,
116
+ ),
117
+ StrategyInfo(
118
+ strategy_name=StrategyName.NATURAL_EDIT,
119
+ display_name="Natural Edit",
120
+ description="Deprecated",
121
+ disabled=True,
122
+ display_order=99,
123
+ is_agentic=False,
124
+ ),
125
+ StrategyInfo(
126
+ strategy_name=StrategyName.AGENT,
127
+ display_name="Agent",
128
+ description="No description",
129
+ disabled=True,
130
+ display_order=99,
131
+ is_agentic=True,
132
+ ),
133
+ StrategyInfo(
134
+ strategy_name=StrategyName.AGENT_STEP,
135
+ display_name="Agent Step",
136
+ description="No description",
137
+ disabled=True,
138
+ display_order=99,
139
+ is_agentic=False,
140
+ ),
141
+ StrategyInfo(
142
+ strategy_name=StrategyName.DATABASE,
143
+ display_name="Database",
144
+ description="No description",
145
+ disabled=True,
146
+ display_order=99,
147
+ is_agentic=False,
148
+ ),
149
+ StrategyInfo(
150
+ strategy_name=StrategyName.DATABASE_AGENT,
151
+ display_name="Database Agent",
152
+ description="No description",
153
+ disabled=True,
154
+ display_order=99,
155
+ is_agentic=True,
156
+ ),
157
+ StrategyInfo(
158
+ strategy_name=StrategyName.DATABASE_AGENT_STEP,
159
+ display_name="Database Agent Step",
160
+ description="No description",
161
+ disabled=True,
162
+ display_order=99,
163
+ is_agentic=False,
164
+ ),
165
+ StrategyInfo(
166
+ strategy_name=StrategyName.REVIEW_FILES,
167
+ display_name="Review Files",
168
+ description="No description",
169
+ disabled=True,
170
+ display_order=99,
171
+ is_agentic=False,
172
+ ),
173
+ StrategyInfo(
174
+ strategy_name=StrategyName.EXPLORE_CODEBASE,
175
+ display_name="Explore Codebase",
176
+ description="No description",
177
+ disabled=True,
178
+ display_order=99,
179
+ is_agentic=False,
180
+ ),
181
+ StrategyInfo(
182
+ strategy_name=StrategyName.GENERATE_REVIEW,
183
+ display_name="Generate Review",
184
+ description="No description",
185
+ disabled=True,
186
+ display_order=99,
187
+ is_agentic=False,
188
+ ),
189
+ StrategyInfo(
190
+ strategy_name=StrategyName.CUSTOM_STEP,
191
+ display_name="Custom Step",
192
+ description="No description",
193
+ disabled=True,
194
+ display_order=99,
195
+ is_agentic=False,
196
+ ),
197
+ ]
198
+
199
+
200
+ ENABLED_STRATEGY_INFO_LIST: list[StrategyInfo] = [
201
+ StrategyInfo(
202
+ strategy_name=StrategyName.NATURAL_EDIT_CLAUDE_3_7_XML,
203
+ display_name="Natural Edit Claude 3.7 XML",
204
+ description="A natural file editing strategy that uses Claude 3.7 XML.",
205
+ disabled=False,
206
+ display_order=-1,
207
+ is_agentic=False,
208
+ ),
209
+ StrategyInfo(
210
+ strategy_name=StrategyName.READ_ONLY,
211
+ display_name="Read Only",
212
+ description="A strategy that allows you to read the codebase.",
213
+ disabled=False,
214
+ display_order=-1,
215
+ is_agentic=False,
216
+ ),
217
+ StrategyInfo(
218
+ strategy_name=StrategyName.FULL_FILE_REWRITE,
219
+ display_name="Full File Rewrites",
220
+ description="Rewrites the full file every time. Use this if your files are generally less than 300 lines.",
221
+ disabled=False,
222
+ display_order=2,
223
+ is_agentic=False,
224
+ ),
225
+ ]
@@ -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