indent 0.1.7__py3-none-any.whl → 0.1.9__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.

@@ -7,26 +7,10 @@ from anyio import Path as AsyncPath
7
7
  from exponent.core.remote_execution.git import get_git_info
8
8
  from exponent.core.remote_execution.languages import python_execution
9
9
  from exponent.core.remote_execution.types import (
10
- SystemContextRequest,
11
- SystemContextResponse,
12
10
  SystemInfo,
13
11
  )
14
12
  from exponent.core.remote_execution.utils import safe_read_file
15
13
 
16
- EXPONENT_TXT_FILENAMES = [
17
- "exponent.txt",
18
- ]
19
-
20
-
21
- async def get_system_context(
22
- request: SystemContextRequest, working_directory: str
23
- ) -> SystemContextResponse:
24
- return SystemContextResponse(
25
- correlation_id=request.correlation_id,
26
- exponent_txt=await _read_exponent_txt(working_directory),
27
- system_info=await get_system_info(working_directory),
28
- )
29
-
30
14
 
31
15
  async def get_system_info(working_directory: str) -> SystemInfo:
32
16
  return SystemInfo(
@@ -39,16 +23,5 @@ async def get_system_info(working_directory: str) -> SystemInfo:
39
23
  )
40
24
 
41
25
 
42
- async def _read_exponent_txt(working_directory: str) -> str | None:
43
- for filename in EXPONENT_TXT_FILENAMES:
44
- file_path = AsyncPath(os.path.join(working_directory, filename.lower()))
45
- exists = await file_path.exists()
46
-
47
- if exists:
48
- return await safe_read_file(file_path)
49
-
50
- return None
51
-
52
-
53
26
  def _get_user_shell() -> str:
54
27
  return os.environ.get("SHELL", "bash")
@@ -229,10 +229,6 @@ Namespace = Literal[
229
229
  "get_file_attachment",
230
230
  "get_file_attachments",
231
231
  "get_matching_files",
232
- "system_context",
233
- "get_all_tracked_files",
234
- "halt",
235
- "switch_cli_chat",
236
232
  "error",
237
233
  "create_checkpoint",
238
234
  "rollback_to_checkpoint",
@@ -355,27 +351,6 @@ class GetMatchingFilesResponse(RemoteExecutionResponse):
355
351
  files: list[RemoteFile]
356
352
 
357
353
 
358
- class GetAllTrackedFilesResponse(RemoteExecutionResponse):
359
- namespace: ClassVar[Namespace] = "get_all_tracked_files"
360
-
361
- files: list[RemoteFile]
362
-
363
-
364
- class SystemContextResponse(RemoteExecutionResponse):
365
- namespace: ClassVar[Namespace] = "system_context"
366
-
367
- exponent_txt: str | None
368
- system_info: SystemInfo | None
369
-
370
-
371
- class HaltResponse(RemoteExecutionResponse):
372
- namespace: ClassVar[Namespace] = "halt"
373
-
374
-
375
- class SwitchCLIChatResponse(RemoteExecutionResponse):
376
- namespace: ClassVar[Namespace] = "switch_cli_chat"
377
-
378
-
379
354
  class ErrorResponse(RemoteExecutionResponse):
380
355
  namespace: ClassVar[Namespace] = "error"
381
356
  # The namespace of the request that caused the error.
@@ -460,15 +435,6 @@ class StreamingCodeExecutionRequest(
460
435
  timeout: int
461
436
 
462
437
 
463
- class HaltRequest(RemoteExecutionRequest[HaltResponse]):
464
- namespace: ClassVar[Namespace] = "halt"
465
-
466
-
467
- class SwitchCLIChatRequest(RemoteExecutionRequest[SwitchCLIChatResponse]):
468
- namespace: ClassVar[Namespace] = "switch_cli_chat"
469
- new_chat_uuid: str
470
-
471
-
472
438
  class FileWriteRequest(RemoteExecutionRequest[FileWriteResponse]):
473
439
  namespace: ClassVar[Namespace] = "file_write"
474
440
 
@@ -504,14 +470,6 @@ class GetMatchingFilesRequest(RemoteExecutionRequest[GetMatchingFilesResponse]):
504
470
  search_term: str
505
471
 
506
472
 
507
- class GetAllTrackedFilesRequest(RemoteExecutionRequest[GetAllTrackedFilesResponse]):
508
- namespace: ClassVar[Namespace] = "get_all_tracked_files"
509
-
510
-
511
- class SystemContextRequest(RemoteExecutionRequest[SystemContextResponse]):
512
- namespace: ClassVar[Namespace] = "system_context"
513
-
514
-
515
473
  class CreateCheckpointRequest(RemoteExecutionRequest[CreateCheckpointResponse]):
516
474
  namespace: ClassVar[Namespace] = "create_checkpoint"
517
475
 
@@ -557,12 +515,8 @@ RemoteExecutionRequestType = Union[
557
515
  GetFileAttachmentRequest,
558
516
  GetFileAttachmentsRequest,
559
517
  GetMatchingFilesRequest,
560
- SystemContextRequest,
561
- GetAllTrackedFilesRequest,
562
518
  CommandRequest,
563
- HaltRequest,
564
519
  StreamingCodeExecutionRequest,
565
- SwitchCLIChatRequest,
566
520
  CreateCheckpointRequest,
567
521
  RollbackToCheckpointRequest,
568
522
  ]
@@ -576,11 +530,7 @@ RemoteExecutionResponseType = Union[
576
530
  GetFileAttachmentResponse,
577
531
  GetFileAttachmentsResponse,
578
532
  GetMatchingFilesResponse,
579
- GetAllTrackedFilesResponse,
580
- SystemContextResponse,
581
533
  CommandResponse,
582
- HaltResponse,
583
- SwitchCLIChatResponse,
584
534
  ErrorResponse,
585
535
  CreateCheckpointResponse,
586
536
  RollbackToCheckpointResponse,
@@ -35,16 +35,12 @@ from exponent.core.remote_execution.types import (
35
35
  FilePath,
36
36
  FileWriteRequest,
37
37
  FileWriteResponse,
38
- GetAllTrackedFilesRequest,
39
- GetAllTrackedFilesResponse,
40
38
  GetFileAttachmentRequest,
41
39
  GetFileAttachmentResponse,
42
40
  GetFileAttachmentsRequest,
43
41
  GetFileAttachmentsResponse,
44
42
  GetMatchingFilesRequest,
45
43
  GetMatchingFilesResponse,
46
- HaltRequest,
47
- HaltResponse,
48
44
  ListFilesRequest,
49
45
  ListFilesResponse,
50
46
  RemoteExecutionMessage,
@@ -61,10 +57,6 @@ from exponent.core.remote_execution.types import (
61
57
  StreamingCodeExecutionResponse,
62
58
  StreamingCodeExecutionResponseChunk,
63
59
  SupportedLanguage,
64
- SwitchCLIChatRequest,
65
- SwitchCLIChatResponse,
66
- SystemContextRequest,
67
- SystemContextResponse,
68
60
  )
69
61
  from exponent.core.types.command_data import NaturalEditContent
70
62
  from exponent.core.types.event_types import (
@@ -78,82 +70,6 @@ from exponent.utils.version import get_installed_version
78
70
  ### Serde
79
71
 
80
72
 
81
- def deserialize_message_data(
82
- message_data: RemoteExecutionMessageData | str,
83
- ) -> RemoteExecutionMessage:
84
- if isinstance(message_data, str):
85
- message_data = RemoteExecutionMessageData.model_validate_json(message_data)
86
- if message_data.direction == "request":
87
- return deserialize_request_data(cast(RemoteExecutionRequestData, message_data))
88
- elif message_data.direction == "response":
89
- return deserialize_response_data(
90
- cast(RemoteExecutionResponseData, message_data)
91
- )
92
- else:
93
- # type checking trick, if you miss a namespace then
94
- # this won't typecheck due to the input parameter
95
- # having a potential type other than no-return
96
- assert_unreachable(message_data.direction)
97
-
98
-
99
- def deserialize_request_data(
100
- request_data: RemoteExecutionRequestData | str,
101
- ) -> RemoteExecutionRequestType:
102
- request: RemoteExecutionRequestType
103
- if isinstance(request_data, str):
104
- request_data = RemoteExecutionRequestData.model_validate_json(request_data)
105
- if request_data.direction != "request":
106
- raise ValueError(f"Expected request, but got {request_data.direction}")
107
- if request_data.namespace == "code_execution":
108
- request = CodeExecutionRequest.model_validate_json(request_data.message_data)
109
- elif request_data.namespace == "file_write":
110
- request = FileWriteRequest.model_validate_json(request_data.message_data)
111
- elif request_data.namespace == "list_files":
112
- request = ListFilesRequest.model_validate_json(request_data.message_data)
113
- elif request_data.namespace == "get_file_attachment":
114
- request = GetFileAttachmentRequest.model_validate_json(
115
- request_data.message_data
116
- )
117
- elif request_data.namespace == "get_file_attachments":
118
- request = GetFileAttachmentsRequest.model_validate_json(
119
- request_data.message_data
120
- )
121
- elif request_data.namespace == "get_matching_files":
122
- request = GetMatchingFilesRequest.model_validate_json(request_data.message_data)
123
- elif request_data.namespace == "system_context":
124
- request = SystemContextRequest.model_validate_json(request_data.message_data)
125
- elif request_data.namespace == "get_all_tracked_files":
126
- request = GetAllTrackedFilesRequest.model_validate_json(
127
- request_data.message_data
128
- )
129
- elif request_data.namespace == "command":
130
- request = CommandRequest.model_validate_json(request_data.message_data)
131
- elif request_data.namespace == "halt":
132
- request = HaltRequest.model_validate_json(request_data.message_data)
133
- elif request_data.namespace == "streaming_code_execution":
134
- request = StreamingCodeExecutionRequest.model_validate_json(
135
- request_data.message_data
136
- )
137
- elif request_data.namespace == "switch_cli_chat":
138
- request = SwitchCLIChatRequest.model_validate_json(request_data.message_data)
139
- elif request_data.namespace == "streaming_code_execution_chunk":
140
- assert False, "Streaming code execution chunk is a response, not a request"
141
- elif request_data.namespace == "error":
142
- assert False, "Error is a response, not a request"
143
- elif request_data.namespace == "create_checkpoint":
144
- request = CreateCheckpointRequest.model_validate_json(request_data.message_data)
145
- elif request_data.namespace == "rollback_to_checkpoint":
146
- request = RollbackToCheckpointRequest.model_validate_json(
147
- request_data.message_data
148
- )
149
- else:
150
- # type checking trick, if you miss a namespace then
151
- # this won't typecheck due to the input parameter
152
- # having a potential type other than no-return
153
- request = assert_unreachable(request_data.namespace)
154
- return truncate_message(request)
155
-
156
-
157
73
  def deserialize_response_data(
158
74
  response_data: RemoteExecutionResponseData | str,
159
75
  ) -> RemoteExecutionResponseType:
@@ -188,18 +104,9 @@ def deserialize_response_data(
188
104
  response = GetFileAttachmentsResponse.model_validate_json(
189
105
  response_data.message_data
190
106
  )
191
- elif response_data.namespace == "system_context":
192
- response = SystemContextResponse.model_validate_json(response_data.message_data)
193
- elif response_data.namespace == "get_all_tracked_files":
194
- response = GetAllTrackedFilesResponse.model_validate_json(
195
- response_data.message_data
196
- )
107
+
197
108
  elif response_data.namespace == "command":
198
109
  response = CommandResponse.model_validate_json(response_data.message_data)
199
- elif response_data.namespace == "halt":
200
- response = HaltResponse.model_validate_json(response_data.message_data)
201
- elif response_data.namespace == "switch_cli_chat":
202
- response = SwitchCLIChatResponse.model_validate_json(response_data.message_data)
203
110
  elif response_data.namespace == "error":
204
111
  response = ErrorResponse.model_validate_json(response_data.message_data)
205
112
  elif response_data.namespace == "create_checkpoint":
@@ -424,10 +331,8 @@ def truncate_message(response: GetMatchingFilesRequest) -> GetMatchingFilesReque
424
331
  def truncate_message(
425
332
  response: GetMatchingFilesResponse,
426
333
  ) -> GetMatchingFilesResponse: ...
427
- @overload
428
- def truncate_message(response: SystemContextRequest) -> SystemContextRequest: ...
429
- @overload
430
- def truncate_message(response: SystemContextResponse) -> SystemContextResponse: ...
334
+
335
+
431
336
  @overload
432
337
  def truncate_message(
433
338
  response: RemoteExecutionRequestType,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: indent
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Summary: Indent is an AI Pair Programmer
5
5
  Author-email: Sashank Thupukari <sashank@exponent.run>
6
6
  Requires-Python: <3.13,>=3.10
@@ -15,7 +15,7 @@ Requires-Dist: eval-type-backport<0.3,>=0.2.0
15
15
  Requires-Dist: git-python>=1.0.3
16
16
  Requires-Dist: gitignore-parser<0.2,>=0.1.11
17
17
  Requires-Dist: gql[httpx,websockets]<4,>=3.5.0
18
- Requires-Dist: httpx<0.28,>=0.27.0
18
+ Requires-Dist: httpx>=0.28.1
19
19
  Requires-Dist: ipykernel<7,>=6.29.4
20
20
  Requires-Dist: jupyter-client<9,>=8.6.1
21
21
  Requires-Dist: msgspec>=0.19.0
@@ -1,44 +1,40 @@
1
- exponent/__init__.py,sha256=RlRDAWCdKd6KRITEGyfrQ_c86ikzjoYuuCGv07KBb2I,58
2
- exponent/cli.py,sha256=GA-Bw1nys9JNrlob0t3LcS9OZ4U5JcNYjR_r3kDD_qs,3596
1
+ exponent/__init__.py,sha256=bhntibG3PKk5Ai3XlSNEV8gj-ffItuKloY6vzWn6swo,511
2
+ exponent/cli.py,sha256=u3hhZnn5uqVXyNz6wU8j4U5vLJ--pbA-PweHhpvRzY4,3444
3
3
  exponent/py.typed,sha256=9XZl5avs8yHp89XP_1Fjtbeg_2rjYorCC9I0k_j-h2c,334
4
4
  exponent/commands/cloud_commands.py,sha256=4DgS7PjCtCFB5uNN-szzAzOj16UU1D9b9_qS7DskoLE,2026
5
5
  exponent/commands/common.py,sha256=HUzc9yl5L6GZEF5DDsF3IeArwPKrghxmK43vxtkxgGs,13679
6
- exponent/commands/config_commands.py,sha256=lPwi2iueRfrBgCHIFRxAtYZCXjJ65zlwe8wSoAUoJQ8,15979
7
- exponent/commands/github_app_commands.py,sha256=tcjSB5f_m_t6m3fITWGji7Q4MeJw3xRQ7gLSS-KDuHI,6349
8
- exponent/commands/listen_commands.py,sha256=Zh_3HWZ7u0m9T6BIqocMUZzSvg-tJcMuvFYDM1yoAE4,3201
9
- exponent/commands/run_commands.py,sha256=jxZp4F-RNTZbhD6-tIcyxAUDbpUSfaTX-X1Q7hyC_Q0,5857
6
+ exponent/commands/config_commands.py,sha256=5xIWMVMBoY2-90gEe61DzIuvpT5_r0r91jpgDLeb-AU,15959
7
+ exponent/commands/run_commands.py,sha256=syuC_YUFSz57eT7Rl0aFiIfogINmiAvmenLClnogWQU,5865
10
8
  exponent/commands/settings.py,sha256=UwwwoCgCY5hzAFD9slOBbA9Gr1hNfoyJ2blsFDC6V8w,1559
11
- exponent/commands/shell_commands.py,sha256=bh6Ni4Lu5KsrWBqpV_GFrPJKta6F7RrgHNqRUqvsEVk,93892
12
- exponent/commands/theme.py,sha256=XoX9lYDf8YyoU1HoRdctDHjK5MrMRUcBALBLcHDVPc8,8111
13
9
  exponent/commands/types.py,sha256=iDJL3hdwhO1PrhsJTJBioNYSKo0CWV8Nv-ONcDaWIRs,3670
14
- exponent/commands/upgrade.py,sha256=SpCdgEsBesPmXB5XgRJsIqonj73dfuuGVblo_0C2pWE,809
15
- exponent/commands/utils.py,sha256=gmWUPHZtAfuSb3zdrAEkmHVhQYmHghqhvJUFBunn2-4,7572
16
- exponent/commands/workflow_commands.py,sha256=ytetNMzojzJDe9NAsGdkDBGJs1eZIfY8xLNqesnc3AE,3580
17
- exponent/core/config.py,sha256=TNFLUgLnfSocRMVSav_7E4VcaNHXZ_3Mg5Lp1smP46U,5731
10
+ exponent/commands/upgrade.py,sha256=JZr0sNazziuLByQHdT8GZb-lDbRG1YpHW8VB94q-r8w,803
11
+ exponent/commands/utils.py,sha256=c8KO1EjfwmEVXAKIvKSuJE_nl338q8fLSNKxuxQSyAY,4624
12
+ exponent/commands/workflow_commands.py,sha256=1IZCw_EWkpLHF3urOR5kwYyfHWQrjBbaih8FRu6hPW8,3591
13
+ exponent/core/config.py,sha256=faWzY3wZ8MF8BmeZ7mOZbuDDRs3ShVeSDPe2OH2nrI0,5748
18
14
  exponent/core/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
15
  exponent/core/graphql/client.py,sha256=KRveVt4lsrSdF9PnhhlQeqx74FQ9-iN28WQp0WKnQ30,1921
20
16
  exponent/core/graphql/cloud_config_queries.py,sha256=Xy8SRP-sfWFcaq33hF-Ni1KwZDU4AdkkGbntsOxYytA,1968
21
17
  exponent/core/graphql/get_chats_query.py,sha256=9-2N1VfapXUZB3IFIKw5U_gKdmfyviJp5JSUntB_Yyk,1177
22
18
  exponent/core/graphql/github_config_queries.py,sha256=zKRDxF38q73apQcVaEAA_A20FVr3U0ADc5-8Y6Ns5Dw,1260
23
19
  exponent/core/graphql/mutations.py,sha256=WRwgJzMTETvry1yc9-EBlIRWkePjHIskBAm_6tEiRaU,1352
24
- exponent/core/graphql/queries.py,sha256=TXXHLGb7QpeICaofowVYsjyHDfqjCoQ3omLbesuw06s,2612
25
- exponent/core/graphql/subscriptions.py,sha256=gg42wG5HqEuNMJU7OUHruNCAGtM6FKPLRD7KfjcKjC4,9995
20
+ exponent/core/graphql/queries.py,sha256=AJgDM1qc2_Zo0DhLqbogVTVokrzjfqhwhUz0SCdrKNI,2943
21
+ exponent/core/graphql/subscriptions.py,sha256=qDlWQsaY0F4lUaHEAeAa02iuKpUfml4I6kD9HMKGe1E,10301
26
22
  exponent/core/remote_execution/checkpoints.py,sha256=3QGYMLa8vT7XmxMYTRcGrW8kNGHwRC0AkUfULribJWg,6354
27
- exponent/core/remote_execution/cli_rpc_types.py,sha256=k_hxkpP-VoktbW7f_wy0N2_UN8AEhkwgnN7DUJSSzzQ,5040
28
- exponent/core/remote_execution/client.py,sha256=bXjcVgmWke3F9E7dx6uK6Q_ZzBW2uVi6tHTPcVpgOa4,21996
23
+ exponent/core/remote_execution/cli_rpc_types.py,sha256=ZokZXSMYGqJJX9fqzzoXJx9ElZjg6wOUl8dT9QMJQ9Y,5793
24
+ exponent/core/remote_execution/client.py,sha256=gSnR3vJ7HCovZN1cvwFD-PNsS7syWE4WAYK3ibJ3M-U,25586
29
25
  exponent/core/remote_execution/code_execution.py,sha256=jYPB_7dJzS9BTPLX9fKQpsFPatwjbXuaFFSxT9tDTfI,2388
30
26
  exponent/core/remote_execution/error_info.py,sha256=Rd7OA3ps06qYejPVcOaMBB9AtftP3wqQoOfiILFASnc,1378
31
27
  exponent/core/remote_execution/exceptions.py,sha256=eT57lBnBhvh-KJ5lsKWcfgGA5-WisAxhjZx-Z6OupZY,135
32
28
  exponent/core/remote_execution/file_write.py,sha256=j9X4QfCBuZK6VIMfeu53WTN90G4w0AtN4U9GcoCJvJk,12531
33
- exponent/core/remote_execution/files.py,sha256=0EmOP2OdreGHjkKEIhtB_nNcjvLLx_UJWbbxt7cGSNg,12225
29
+ exponent/core/remote_execution/files.py,sha256=ypqDuXhnANWMnwiYrw033oBSDaDT9v9J0jvdHesRw24,11868
34
30
  exponent/core/remote_execution/git.py,sha256=Yo4mhkl6LYzGhVco91j_E8WOUey5KL9437rk43VCCA8,7826
35
- exponent/core/remote_execution/http_fetch.py,sha256=HsELvOPtgq7O9rAR3QdpP5Z3wudPffedJfFRLnMQ6qQ,2839
31
+ exponent/core/remote_execution/http_fetch.py,sha256=ZoQ2EcB1vQys-QmIbt1xC0ftyWkOIohxCfYNVYv4kF0,2856
36
32
  exponent/core/remote_execution/session.py,sha256=cSJcCG1o74mBE6lZS_9VFmhyZdW6BeIOsbq4IVWH0t4,3863
37
- exponent/core/remote_execution/system_context.py,sha256=0FkbsSxEVjdjTF0tQpOkYK_VaVM126C3_K8QP0YXxOs,1510
33
+ exponent/core/remote_execution/system_context.py,sha256=XBvWeLLsrfzdicYBMbIneOBlnelBMb2n-VpHw5q2kcA,749
38
34
  exponent/core/remote_execution/tool_execution.py,sha256=_UQPyOTY49RQ70kfgnf03eev7c7lTWhFBC68cuifj2M,12354
39
35
  exponent/core/remote_execution/truncation.py,sha256=rFQDfT7qf_u6lvhEADWSpoRe_GTPegXXqknk7OZp0uI,10093
40
- exponent/core/remote_execution/types.py,sha256=3x73SJ3jQtaIu01musXgS-VF_MYwYucG3hyA2nWHbII,17567
41
- exponent/core/remote_execution/utils.py,sha256=Hw2zGMq0NKVwkVzNOVekAvpM33rfIz2P36mEcJr43Ag,20688
36
+ exponent/core/remote_execution/types.py,sha256=P1hlhg_cw6A0iplODzI2irs5f7k8zBYYGX4Xvs4-YC4,16212
37
+ exponent/core/remote_execution/utils.py,sha256=Jwn2f753WuyanceUQSyJypcplOlUrfPchebAy7ULoz8,15927
42
38
  exponent/core/remote_execution/languages/python_execution.py,sha256=GcQU5PiAOUD9tUZDrSObgM3L7FjXMdzr4FQg6ts2XjY,7780
43
39
  exponent/core/remote_execution/languages/shell_streaming.py,sha256=eSeHcK_unGpVRRdfgfwhml_tSlCo0VCeUEQY4zuMbDw,7372
44
40
  exponent/core/remote_execution/languages/types.py,sha256=f7FjSRNRSga-ZaE3LddDhxCirUVjlSYMEdoskG6Pta4,314
@@ -51,7 +47,7 @@ exponent/migration-docs/login.md,sha256=KIeXy3m2nzSUgw-4PW1XzXfHael1D4Zu93CplLMb
51
47
  exponent/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
48
  exponent/utils/colors.py,sha256=HBkqe_ZmhJ9YiL2Fpulqek4KvLS5mwBTY4LQSM5N8SM,2762
53
49
  exponent/utils/version.py,sha256=Q4txP7Rg_KO0u0tUpx8O0DoOt32wrX7ctNeDXVKaOfA,8835
54
- indent-0.1.7.dist-info/METADATA,sha256=zifHZkZXa-jGIOm8J9gyS5O1kEw_-HjWE4nvoZ5wzeY,1283
55
- indent-0.1.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
56
- indent-0.1.7.dist-info/entry_points.txt,sha256=q8q1t1sbl4NULGOR0OV5RmSG4KEjkpEQRU_RUXEGzcs,44
57
- indent-0.1.7.dist-info/RECORD,,
50
+ indent-0.1.9.dist-info/METADATA,sha256=Rn7HmjV09LMsxtUQDO2X5vr7rfeWnDrIScyXtKs0ec4,1277
51
+ indent-0.1.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
52
+ indent-0.1.9.dist-info/entry_points.txt,sha256=q8q1t1sbl4NULGOR0OV5RmSG4KEjkpEQRU_RUXEGzcs,44
53
+ indent-0.1.9.dist-info/RECORD,,
@@ -1,211 +0,0 @@
1
- import asyncio
2
- import json
3
- import subprocess
4
- import sys
5
- import webbrowser
6
- from pathlib import Path
7
- from uuid import uuid4
8
-
9
- import click
10
- from git import GitCommandError, Repo
11
-
12
- from exponent.commands.common import (
13
- redirect_to_login,
14
- verify_gh_app_installation,
15
- )
16
- from exponent.commands.settings import use_settings
17
- from exponent.commands.types import exponent_cli_group
18
- from exponent.core.config import Settings
19
- from exponent.core.remote_execution.git import get_git_info
20
-
21
-
22
- @exponent_cli_group()
23
- def github_app_cli() -> None:
24
- """Run AI-powered chat sessions."""
25
- pass
26
-
27
-
28
- @github_app_cli.command()
29
- @use_settings
30
- def install_github_app(
31
- settings: Settings,
32
- ) -> None:
33
- """Start or reconnect to an Exponent session."""
34
- if not settings.api_key:
35
- redirect_to_login(settings)
36
- return
37
-
38
- loop = asyncio.get_event_loop()
39
-
40
- api_key = settings.api_key
41
- base_api_url = settings.get_base_api_url()
42
- base_ws_url = settings.get_base_ws_url()
43
-
44
- git_info = asyncio.run(get_git_info("."))
45
- if not git_info:
46
- raise RuntimeError("Not running inside of valid git repository")
47
-
48
- install_url = "https://github.com/apps/indent/installations/new"
49
- webbrowser.open(install_url)
50
-
51
- click.confirm(
52
- "Press enter once you've installed the github app.",
53
- default=True,
54
- abort=True,
55
- prompt_suffix="",
56
- )
57
-
58
- click.secho("Verifying installation...", fg="yellow")
59
- verified = loop.run_until_complete(
60
- verify_gh_app_installation(api_key, base_api_url, base_ws_url, git_info)
61
- )
62
-
63
- if verified:
64
- click.secho("Verified!", fg="green")
65
- else:
66
- click.secho("No verification found :(", fg="red")
67
- sys.exit(1)
68
-
69
- click.secho("Creating workflow file...", fg="yellow")
70
- _create_workflow_yaml()
71
-
72
-
73
- WORKFLOW_YAML = """
74
- name: Indent Action
75
-
76
- on:
77
- issue_comment:
78
- types: [created]
79
- pull_request_review_comment:
80
- types: [created]
81
- issues:
82
- types: [opened, assigned]
83
- pull_request_review:
84
- types: [submitted]
85
-
86
- jobs:
87
- indent:
88
- if: |
89
- (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@indent')) ||
90
- (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@indent')) ||
91
- (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@indent')) ||
92
- (github.event_name == 'issues' && (contains(github.event.issue.body, '@indent') || contains(github.event.issue.title, '@indent')))
93
- runs-on: ubuntu-latest
94
-
95
- steps:
96
- - name: Generate token for Indent app
97
- id: generate_token
98
- uses: actions/create-github-app-token@v1
99
- with:
100
- app-id: ${{ secrets.INDENT_APP_ID }}
101
- private-key: ${{ secrets.INDENT_APP_PRIVATE_KEY }}
102
-
103
- - name: Respond to mention
104
- uses: actions/github-script@v7
105
- with:
106
- github-token: ${{ steps.generate_token.outputs.token }}
107
- script: |
108
- const issue_number = context.payload.issue?.number ||
109
- context.payload.pull_request?.number ||
110
- context.payload.review?.pull_request?.number;
111
-
112
- await github.rest.issues.createComment({
113
- owner: context.repo.owner,
114
- repo: context.repo.repo,
115
- issue_number: issue_number,
116
- body: 'Hi it\'s me, Indent'
117
- });
118
- """.lstrip()
119
-
120
-
121
- def _create_workflow_yaml() -> None:
122
- git_branch = f"indent-workflow-{uuid4()}"
123
- workflow_file = "indent-review.yml"
124
-
125
- # 1. Locate the repository (searches upward until it finds .git).
126
- repo = Repo(Path.cwd(), search_parent_directories=True)
127
- if repo.bare or not repo.working_tree_dir:
128
- sys.exit("Error: cannot operate inside a bare repository.")
129
-
130
- original_branch = repo.active_branch.name if not repo.head.is_detached else None
131
-
132
- # 2. Create or reuse the branch, then check it out.
133
- try:
134
- branch_ref = repo.create_head(git_branch)
135
- except GitCommandError:
136
- branch_ref = repo.heads[git_branch]
137
- branch_ref.checkout()
138
-
139
- # 3. Ensure workflow directory exists.
140
- wf_dir = Path(repo.working_tree_dir) / ".github" / "workflows"
141
- wf_dir.mkdir(parents=True, exist_ok=True)
142
-
143
- yml = wf_dir / workflow_file
144
- if not yml.exists():
145
- yml.write_text(WORKFLOW_YAML)
146
- # 5. Stage & commit.
147
- repo.index.add([str(yml)])
148
- repo.index.commit("Add Indent workflow template")
149
- print(
150
- f"Created {yml.relative_to(repo.working_tree_dir)} "
151
- f"on branch '{git_branch}'."
152
- )
153
- else:
154
- print(
155
- f"{yml.relative_to(repo.working_tree_dir)} already exists; nothing to do."
156
- )
157
-
158
- subprocess.run(
159
- ["git", "push", "-u", "origin", git_branch],
160
- cwd=repo.working_tree_dir,
161
- check=True,
162
- capture_output=False,
163
- text=True,
164
- )
165
-
166
- pr_url: str | None = None
167
- pr_title = f"Add Indent workflow ({workflow_file})"
168
- pr_body = "This PR introduces an Indent github action workflow"
169
-
170
- def run_gh(cmd: list[str]) -> subprocess.CompletedProcess[str]:
171
- return subprocess.run(
172
- ["gh", *cmd],
173
- cwd=repo.working_tree_dir,
174
- check=True,
175
- capture_output=True,
176
- text=True,
177
- )
178
-
179
- try:
180
- # Does a PR already exist for this head branch?
181
- result = run_gh(["pr", "view", git_branch, "--json", "url"])
182
- pr_url = json.loads(result.stdout)["url"]
183
- except subprocess.CalledProcessError:
184
- # No PR yet → create one
185
- base = original_branch or repo.remotes.origin.refs[0].name.split("/")[-1]
186
- run_gh(
187
- [
188
- "pr",
189
- "create",
190
- "--head",
191
- git_branch,
192
- "--base",
193
- base,
194
- "--title",
195
- pr_title,
196
- "--body",
197
- pr_body,
198
- ]
199
- )
200
- # Fetch the newly created URL
201
- result = run_gh(["pr", "view", git_branch, "--json", "url"])
202
- pr_url = json.loads(result.stdout)["url"]
203
-
204
- if pr_url:
205
- click.secho(f"PR: {pr_url}", fg="green")
206
- webbrowser.open(pr_url)
207
- else:
208
- click.secho("Failed to create PR!", fg="red")
209
-
210
- if original_branch:
211
- repo.git.checkout(original_branch)
@@ -1,96 +0,0 @@
1
- import asyncio
2
-
3
- import click
4
- from websockets.exceptions import ConnectionClosed
5
-
6
- from exponent.commands.settings import use_settings
7
- from exponent.commands.shell_commands import LiveView
8
- from exponent.commands.theme import get_theme
9
- from exponent.commands.types import exponent_cli_group
10
- from exponent.core.config import Settings
11
- from exponent.core.graphql.client import GraphQLClient
12
- from exponent.core.graphql.subscriptions import INDENT_CHAT_EVENT_STREAM_SUBSCRIPTION
13
-
14
-
15
- @exponent_cli_group(hidden=True)
16
- def listen_cli() -> None:
17
- pass
18
-
19
-
20
- @listen_cli.command()
21
- @click.option("--chat-id", help="ID of the chat to listen to", required=True)
22
- @click.option(
23
- "--known-event-uuids",
24
- help="Comma-separated list of known event UUIDs to skip",
25
- default="",
26
- )
27
- @use_settings
28
- def listen(settings: Settings, chat_id: str, known_event_uuids: str) -> None:
29
- """Listen to events from an indent chat session."""
30
- api_key = settings.api_key
31
- if not api_key:
32
- raise click.UsageError("API key is not set")
33
-
34
- base_api_url = settings.get_base_api_url()
35
- base_ws_url = settings.get_base_ws_url()
36
-
37
- gql_client = GraphQLClient(api_key, base_api_url, base_ws_url)
38
-
39
- # Parse known event UUIDs
40
- known_uuids = []
41
- if known_event_uuids:
42
- known_uuids = [
43
- uuid.strip() for uuid in known_event_uuids.split(",") if uuid.strip()
44
- ]
45
-
46
- # Set up the live view with theme
47
- theme = get_theme(settings.options.use_default_colors)
48
- live_view = LiveView(theme, render_user_messages=True)
49
-
50
- asyncio.run(_listen(gql_client, chat_id, known_uuids, live_view))
51
-
52
-
53
- async def _listen(
54
- gql_client: GraphQLClient,
55
- chat_id: str,
56
- known_event_uuids: list[str],
57
- live_view: LiveView,
58
- ) -> None:
59
- """Internal listen function that handles the WebSocket subscription."""
60
- while True:
61
- try:
62
- variables = {
63
- "chatUuid": chat_id,
64
- "lastKnownFullEventUuid": known_event_uuids[-1]
65
- if known_event_uuids
66
- else None,
67
- }
68
-
69
- async for response in gql_client.subscribe(
70
- INDENT_CHAT_EVENT_STREAM_SUBSCRIPTION, variables
71
- ):
72
- event = response["indentChatEventStream"]
73
- kind = event["__typename"]
74
-
75
- # Handle different event types
76
- if kind in ["UserEvent", "AssistantEvent", "SystemEvent"]:
77
- live_view.render_event(kind, event)
78
- elif kind == "Error":
79
- print(f"Error: {event.get('message', 'Unknown error')}")
80
- elif kind == "UnauthenticatedError":
81
- print(
82
- f"Authentication error: {event.get('message', 'Unauthenticated')}"
83
- )
84
- break
85
- else:
86
- print(f"Unknown event type: {kind}")
87
-
88
- except ConnectionClosed:
89
- print("WebSocket disconnected, reconnecting...")
90
- await asyncio.sleep(1)
91
- except KeyboardInterrupt:
92
- print("\nDisconnecting...")
93
- break
94
- except Exception as e:
95
- print(f"Error: {e}")
96
- await asyncio.sleep(1)