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

exponent/__init__.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.1.16'
32
- __version_tuple__ = version_tuple = (0, 1, 16)
31
+ __version__ = version = '0.1.17'
32
+ __version_tuple__ = version_tuple = (0, 1, 17)
33
33
 
34
34
  __commit_id__ = commit_id = None
exponent/cli.py CHANGED
@@ -7,7 +7,6 @@ from exponent.commands.config_commands import config_cli
7
7
  from exponent.commands.run_commands import run, run_cli
8
8
  from exponent.commands.types import ExponentGroup, exponent_cli_group
9
9
  from exponent.commands.upgrade import upgrade_cli
10
- from exponent.commands.workflow_commands import workflow_cli
11
10
  from exponent.utils.version import (
12
11
  get_installed_version,
13
12
  )
@@ -34,7 +33,6 @@ sources: list[ExponentGroup] = [
34
33
  run_cli, # Run AI chat commands
35
34
  upgrade_cli, # Upgrade-related commands
36
35
  cloud_cli, # Cloud commands
37
- workflow_cli, # Workflow commands
38
36
  ]
39
37
 
40
38
  for source in sources:
@@ -37,7 +37,8 @@ from exponent.core.remote_execution.exceptions import (
37
37
  )
38
38
  from exponent.core.remote_execution.files import FileCache
39
39
  from exponent.core.remote_execution.git import get_git_info
40
- from exponent.core.remote_execution.types import ChatSource, GitInfo
40
+ from exponent.core.remote_execution.session import send_exception_log
41
+ from exponent.core.remote_execution.types import ChatSource
41
42
 
42
43
  load_dotenv()
43
44
 
@@ -212,6 +213,13 @@ def run_until_complete(coro: Coroutine[Any, Any, Any]) -> Any:
212
213
  except asyncio.CancelledError:
213
214
  pass
214
215
  except ExponentError as e:
216
+ try:
217
+ settings = get_settings()
218
+ loop.run_until_complete(
219
+ send_exception_log(e, session=None, settings=settings)
220
+ )
221
+ except Exception:
222
+ pass
215
223
  click.secho(f"Encountered error: {e}", fg="red")
216
224
  click.secho(
217
225
  "The Indent team has been notified, "
@@ -321,7 +329,9 @@ async def start_client(
321
329
  if prompt:
322
330
  # If given a prompt, we also need to send a request
323
331
  # to kick off the initial turn loop for the chat
324
- raise NotImplementedError("Kicking off with initial prompt not implemented")
332
+ aux_coros.append(
333
+ start_chat_turn(api_key, base_api_url, base_ws_url, chat_uuid, prompt)
334
+ )
325
335
  elif workflow_id:
326
336
  # Similarly, if given a workflow ID, we need to send
327
337
  # a request to kick off the workflow
@@ -346,34 +356,6 @@ async def create_chat(
346
356
  return None
347
357
 
348
358
 
349
- async def get_gh_app_installation_token(
350
- api_key: str, base_api_url: str, base_ws_url: str, git_info: GitInfo
351
- ) -> dict[str, Any] | None:
352
- try:
353
- async with RemoteExecutionClient.session(
354
- api_key, base_api_url, base_ws_url, os.getcwd()
355
- ) as client:
356
- return await client.get_gh_installation_token(git_info)
357
- except (httpx.ConnectError, ExponentError) as e:
358
- click.secho(f"Error: {e}", fg="red")
359
- return None
360
-
361
-
362
- async def verify_gh_app_installation(
363
- api_key: str, base_api_url: str, base_ws_url: str, git_info: GitInfo
364
- ) -> bool:
365
- try:
366
- async with RemoteExecutionClient.session(
367
- api_key, base_api_url, base_ws_url, os.getcwd()
368
- ) as client:
369
- res = await client.get_gh_installation_token(git_info)
370
- if "token" in res:
371
- return True
372
- except (httpx.ConnectError, ExponentError) as e:
373
- click.secho(f"Error: {e}", fg="red")
374
- return False
375
-
376
-
377
359
  async def set_login_complete(api_key: str, base_api_url: str, base_ws_url: str) -> None:
378
360
  graphql_client = GraphQLClient(
379
361
  api_key=api_key, base_api_url=base_api_url, base_ws_url=base_ws_url
@@ -105,8 +105,7 @@ def run(
105
105
  sys.exit(1)
106
106
 
107
107
  if (
108
- not prompt
109
- and (not inside_ssh_session())
108
+ (not inside_ssh_session())
110
109
  and (not workflow_id)
111
110
  # If the user specified a chat ID, they probably don't want to re-launch the chat
112
111
  and (not chat_id)
@@ -52,6 +52,7 @@ from exponent.core.remote_execution.http_fetch import fetch_http_content
52
52
  from exponent.core.remote_execution.session import (
53
53
  RemoteExecutionClientSession,
54
54
  get_session,
55
+ send_exception_log,
55
56
  )
56
57
  from exponent.core.remote_execution.tool_execution import (
57
58
  execute_bash_tool,
@@ -62,7 +63,6 @@ from exponent.core.remote_execution.types import (
62
63
  ChatSource,
63
64
  CLIConnectedState,
64
65
  CreateChatResponse,
65
- GitInfo,
66
66
  HeartbeatInfo,
67
67
  RemoteExecutionResponseType,
68
68
  RunWorkflowRequest,
@@ -357,6 +357,10 @@ class RemoteExecutionClient:
357
357
  await results.put(response)
358
358
  except Exception as e:
359
359
  logger.info(f"Error handling request {request}:\n\n{e}")
360
+ try:
361
+ await send_exception_log(e, session=self.current_session)
362
+ except Exception:
363
+ pass
360
364
  async with results_lock:
361
365
  await results.put(
362
366
  CliRpcResponse(
@@ -523,13 +527,6 @@ class RemoteExecutionClient:
523
527
  )
524
528
  return await deserialize_api_response(response, CreateChatResponse)
525
529
 
526
- async def get_gh_installation_token(self, git_info: GitInfo) -> dict[str, Any]:
527
- response = await self.api_client.post(
528
- "/github_app/exchange_token",
529
- json=git_info.model_dump(),
530
- )
531
- return cast(dict[str, Any], response.json())
532
-
533
530
  # deprecated
534
531
  async def run_workflow(self, chat_uuid: str, workflow_id: str) -> dict[str, Any]:
535
532
  response = await self.api_client.post(
@@ -149,7 +149,10 @@ async def execute_shell_streaming( # noqa: PLR0915
149
149
  def on_timeout() -> None:
150
150
  nonlocal timed_out
151
151
  timed_out = True
152
- process.kill()
152
+ try:
153
+ process.kill()
154
+ except ProcessLookupError:
155
+ pass
153
156
 
154
157
  try:
155
158
  halt_task = asyncio.create_task(monitor_halt()) if should_halt else None
@@ -69,7 +69,7 @@ def truncate_result[T: ToolResultType](tool_result: T) -> T:
69
69
  return truncate_tool_result(tool_result)
70
70
 
71
71
 
72
- async def execute_read_file( # noqa: PLR0911
72
+ async def execute_read_file( # noqa: PLR0911, PLR0915
73
73
  tool_input: ReadToolInput, working_directory: str
74
74
  ) -> ReadToolResult | ErrorToolResult:
75
75
  # Validate absolute path requirement
@@ -82,11 +82,6 @@ async def execute_read_file( # noqa: PLR0911
82
82
  offset = tool_input.offset if tool_input.offset is not None else 0
83
83
  limit = tool_input.limit if tool_input.limit is not None else 2000
84
84
 
85
- if offset < 0:
86
- return ErrorToolResult(
87
- error_message=f"Offset must be non-negative, got: {offset}"
88
- )
89
-
90
85
  if limit <= 0:
91
86
  return ErrorToolResult(error_message=f"Limit must be positive, got: {limit}")
92
87
 
@@ -138,8 +133,8 @@ async def execute_read_file( # noqa: PLR0911
138
133
  content_lines = content.splitlines(keepends=True)
139
134
  total_lines = len(content_lines)
140
135
 
141
- # Handle offset beyond file length
142
- if offset >= total_lines:
136
+ # Handle offset beyond file length for positive offsets
137
+ if offset >= 0 and offset >= total_lines:
143
138
  return ReadToolResult(
144
139
  content="",
145
140
  num_lines=0,
@@ -148,8 +143,26 @@ async def execute_read_file( # noqa: PLR0911
148
143
  metadata=metadata,
149
144
  )
150
145
 
151
- # Apply offset and limit
152
- content_lines = content_lines[offset : offset + limit]
146
+ # Use Python's native slicing - it handles negative offsets naturally
147
+ # Handle the case where offset + limit < 0 (can't mix negative and non-negative indices)
148
+ if offset < 0 and offset + limit < 0:
149
+ # Both start and end are negative, use negative end index
150
+ end_index = offset + limit
151
+ elif offset < 0 and offset + limit >= 0:
152
+ # Start is negative but end would be positive/zero, slice to end
153
+ end_index = None
154
+ else:
155
+ # Normal case: both indices are non-negative
156
+ end_index = offset + limit
157
+
158
+ content_lines = content_lines[offset:end_index]
159
+
160
+ # Calculate the actual start line for the result
161
+ if offset < 0:
162
+ # For negative offsets, calculate where we actually started
163
+ actual_start_line = max(0, total_lines + offset)
164
+ else:
165
+ actual_start_line = offset
153
166
 
154
167
  # Apply character-level truncation at line boundaries to ensure consistency
155
168
  # This ensures the content field and num_lines field remain in sync
@@ -186,7 +199,7 @@ async def execute_read_file( # noqa: PLR0911
186
199
  return ReadToolResult(
187
200
  content=final_content,
188
201
  num_lines=num_lines,
189
- start_line=offset,
202
+ start_line=actual_start_line,
190
203
  total_lines=total_lines,
191
204
  metadata=metadata,
192
205
  )
@@ -52,6 +52,9 @@ class PrReviewWorkflowInput(BaseModel):
52
52
  class SlackWorkflowInput(BaseModel):
53
53
  channel_id: str
54
54
  thread_ts: str
55
+ slack_url: str | None = None
56
+ channel_name: str | None = None
57
+ message_ts: str | None = None
55
58
 
56
59
 
57
60
  WorkflowInput = PrReviewWorkflowInput | SlackWorkflowInput
@@ -216,8 +219,18 @@ class PromptAttachment(BaseModel):
216
219
  prompt_content: str
217
220
 
218
221
 
222
+ class SQLAttachment(BaseModel):
223
+ attachment_type: Literal["sql"] = "sql"
224
+ query_content: str
225
+ query_id: str
226
+
227
+
219
228
  MessageAttachment = Annotated[
220
- FileAttachment | URLAttachment | TableSchemaAttachment | PromptAttachment,
229
+ FileAttachment
230
+ | URLAttachment
231
+ | TableSchemaAttachment
232
+ | PromptAttachment
233
+ | SQLAttachment,
221
234
  Field(discriminator="attachment_type"),
222
235
  ]
223
236
 
@@ -542,30 +555,18 @@ class CLIConnectedState(BaseModel):
542
555
 
543
556
 
544
557
  class DevboxConnectedState(str, Enum):
545
- # TODO: Only needed if we create devbox async
546
- INITIALIZED = "INITIALIZED"
547
558
  # The chat has been initialized, but the devbox is still loading
548
559
  DEVBOX_LOADING = "DEVBOX_LOADING"
549
- # Devbox is ready to use but not tied to a chat
550
- DEVBOX_READY = "DEVBOX_READY"
551
560
  # CLI is connected and running on devbox
552
561
  CONNECTED = "CONNECTED"
553
- # CLI has disconnected
554
- # TODO: what condition?
555
- CLI_DISCONNECTED = "CLI_DISCONNECTED"
556
- # CLI has an error, devbox is running
557
- CLI_ERROR = "CLI_ERROR"
558
562
  # Devbox has an error
559
563
  DEVBOX_ERROR = "DEVBOX_ERROR"
560
564
  # Devbox is going to idle
561
- GOING_TO_IDLE = "GOING_TO_IDLE"
562
- # Devbox is idle
563
- IDLE = "IDLE"
564
- # Devbox is going to idle
565
- RESUMING_FROM_IDLE = "RESUMING_FROM_IDLE"
566
- # Devbox_shutdown
567
- # TODO: In theory our terminal state, do we want to name something different?
568
- DEVBOX_SHUTDOWN = "DEVBOX_SHUTDOWN"
565
+ PAUSING = "PAUSING"
566
+ # Devbox has been paused and is not running
567
+ PAUSED = "PAUSED"
568
+ # Dev box is starting up. Sandbox exists but devbox is not running
569
+ RESUMING = "RESUMING"
569
570
 
570
571
 
571
572
  class CloudConnectedState(BaseModel):
exponent/utils/version.py CHANGED
@@ -260,7 +260,7 @@ def upgrade_exponent_in_background(
260
260
  return
261
261
 
262
262
  click.secho(
263
- f"\nUpgrading Exponent from {current_version} to {new_version} (this will take effect next time)\n",
263
+ f"\nUpgrading Indent from {current_version} to {new_version} (this will take effect next time)\n",
264
264
  fg="cyan",
265
265
  bold=True,
266
266
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: indent
3
- Version: 0.1.16
3
+ Version: 0.1.17
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
@@ -1,15 +1,14 @@
1
- exponent/__init__.py,sha256=Fs2Vm6Mr4ZbSKXw60jeeSx9iEU9elx4fQcMl_5xXMJI,706
2
- exponent/cli.py,sha256=u3hhZnn5uqVXyNz6wU8j4U5vLJ--pbA-PweHhpvRzY4,3444
1
+ exponent/__init__.py,sha256=TtVjjQ5FSnY_MX0ZAPLaNmAfTJWa0sEMBdMs65ngXMM,706
2
+ exponent/cli.py,sha256=QnIeDTgWaQJrRs5WESCkQpVEQiJiAO4qWgB0rYlkd78,3344
3
3
  exponent/py.typed,sha256=9XZl5avs8yHp89XP_1Fjtbeg_2rjYorCC9I0k_j-h2c,334
4
4
  exponent/commands/cloud_commands.py,sha256=TNSbKnc7VBo7VALj44CqV5tdCACJejEGmtYvc5wjza4,19080
5
- exponent/commands/common.py,sha256=8kp97_MW1KkV0pMqx73l4AJwJ0c-4Q68TRM97afeQ5E,13473
5
+ exponent/commands/common.py,sha256=M2KI9yKjB8fecPoDBphMa123c35-iNeaE9q4DxhkaFU,12817
6
6
  exponent/commands/config_commands.py,sha256=iVIX7LuoO5QshzZNSrfvw5yPIiLlce8GQSMBEp7-nzw,11415
7
- exponent/commands/run_commands.py,sha256=dQu-nrFw540JoJZ0yBZxYgj9wTk7NTlQnEo_7Pr5q6k,6407
7
+ exponent/commands/run_commands.py,sha256=sQlrp2HjRqFc9HjEzPu2MWoLEEptLv7b5iBpFsPzjKc,6384
8
8
  exponent/commands/settings.py,sha256=UwwwoCgCY5hzAFD9slOBbA9Gr1hNfoyJ2blsFDC6V8w,1559
9
9
  exponent/commands/types.py,sha256=iDJL3hdwhO1PrhsJTJBioNYSKo0CWV8Nv-ONcDaWIRs,3670
10
10
  exponent/commands/upgrade.py,sha256=JZr0sNazziuLByQHdT8GZb-lDbRG1YpHW8VB94q-r8w,803
11
11
  exponent/commands/utils.py,sha256=Z3eu3mvYwBh7J_hq17lyt7_MwMG8KcsP7AnsCgOnTNc,4638
12
- exponent/commands/workflow_commands.py,sha256=eQufFbKrKCZtewyDpuVe_0QcndbxuaIKHfHpHF8-XzI,3602
13
12
  exponent/core/config.py,sha256=TNFLUgLnfSocRMVSav_7E4VcaNHXZ_3Mg5Lp1smP46U,5731
14
13
  exponent/core/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
14
  exponent/core/graphql/client.py,sha256=SRagD3YPyoYZSO1RtfO-OXD7b5dm1NvgoL6CTbN380o,2009
@@ -20,7 +19,7 @@ exponent/core/graphql/queries.py,sha256=RYsk8bub0esspqgakilhzX07yJf2652Ey9tBZK1l
20
19
  exponent/core/graphql/subscriptions.py,sha256=SQngv_nYVNJjiZ_P2k0UcLIu1pzc4vi7q7lhH89NCZM,393
21
20
  exponent/core/remote_execution/checkpoints.py,sha256=3QGYMLa8vT7XmxMYTRcGrW8kNGHwRC0AkUfULribJWg,6354
22
21
  exponent/core/remote_execution/cli_rpc_types.py,sha256=8xCDy-3kA-PQew8l7CZtoCrCFHz9FsMWI8aXljh5wCg,7659
23
- exponent/core/remote_execution/client.py,sha256=kQ3WKyGQwA68H76RL_XhL6PEOJkEPFNWAt3L5vB_3tc,29008
22
+ exponent/core/remote_execution/client.py,sha256=l0x0wUlhsEBDAmC_llS4ihP0f-3gdrCvf4wwgDpWKNc,28916
24
23
  exponent/core/remote_execution/code_execution.py,sha256=jYPB_7dJzS9BTPLX9fKQpsFPatwjbXuaFFSxT9tDTfI,2388
25
24
  exponent/core/remote_execution/error_info.py,sha256=Rd7OA3ps06qYejPVcOaMBB9AtftP3wqQoOfiILFASnc,1378
26
25
  exponent/core/remote_execution/exceptions.py,sha256=eT57lBnBhvh-KJ5lsKWcfgGA5-WisAxhjZx-Z6OupZY,135
@@ -30,13 +29,13 @@ exponent/core/remote_execution/git.py,sha256=dGjBpeoKJZsYgRwctSq29GmbsNIN9tbSA3V
30
29
  exponent/core/remote_execution/http_fetch.py,sha256=aFEyXd0S-MRfisSMuIFiEyc1AEAj9nUZ9Rj_P_YRows,2827
31
30
  exponent/core/remote_execution/session.py,sha256=jlQIdeUj0f7uOk3BgzlJtBJ_GyTIjCchBp5ApQuF2-I,3847
32
31
  exponent/core/remote_execution/system_context.py,sha256=QY1zY8_fWj3sh-fmLYtewvgxh7uTX4ITIJqlUTDkj6c,648
33
- exponent/core/remote_execution/tool_execution.py,sha256=ZBDC2RN_l8oCFO8Fc8dkKoLY_0rn-5h394PvWeXqTnI,12972
32
+ exponent/core/remote_execution/tool_execution.py,sha256=g5C0CAlOJhxsNauVQVhXvYGFpA8rDnO05VsWO2ag05k,13654
34
33
  exponent/core/remote_execution/tool_type_utils.py,sha256=7qi6Qd8fvHts019ZSLPbtiy17BUqgqBg3P_gdfvFf7w,1301
35
34
  exponent/core/remote_execution/truncation.py,sha256=noB6c4eaebqq5ghTlYJkXbe2XY8Bz_GBeh9DazJUrrU,9644
36
- exponent/core/remote_execution/types.py,sha256=2lMSikVHo9WMUNEYFHQN-EqyFbZOJAfG-AUV6zlti7o,15119
35
+ exponent/core/remote_execution/types.py,sha256=uo8h_ftcafmDp8YTxAI-H9qSaDygHYRFnnrmrBxukm4,14934
37
36
  exponent/core/remote_execution/utils.py,sha256=6PlBqYJ3OQwZ0dgXiIu3br04a-d-glDeDZpD0XGGPAE,14793
38
37
  exponent/core/remote_execution/languages/python_execution.py,sha256=nsX_LsXcUcHhiEHpSTjOTVNd7CxM146al0kw_iQX5OU,7724
39
- exponent/core/remote_execution/languages/shell_streaming.py,sha256=eEhngdLhiMiDM0KUfLUtj6BdU3ay3G1zJy6dlR2V6nw,7389
38
+ exponent/core/remote_execution/languages/shell_streaming.py,sha256=rqnCipe4bu4N5k0FNZjwL_5xc0rCUihZtKc-8v5Iuhg,7458
40
39
  exponent/core/remote_execution/languages/types.py,sha256=f7FjSRNRSga-ZaE3LddDhxCirUVjlSYMEdoskG6Pta4,314
41
40
  exponent/core/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
41
  exponent/core/types/command_data.py,sha256=_HqQsnamRZeVoVaTpeO3ecVUzNBdG62WXlFy6Q7rtUM,5294
@@ -46,8 +45,8 @@ exponent/core/types/generated/strategy_info.py,sha256=LN6_ykFMszb21Qc3yw77xEKUtd
46
45
  exponent/migration-docs/login.md,sha256=KIeXy3m2nzSUgw-4PW1XzXfHael1D4Zu93CplLMb3hI,4252
47
46
  exponent/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
47
  exponent/utils/colors.py,sha256=HBkqe_ZmhJ9YiL2Fpulqek4KvLS5mwBTY4LQSM5N8SM,2762
49
- exponent/utils/version.py,sha256=utp6uc6x_-diQ8scs0GkLHxFXG1gffAmZL77dWXI9cw,8890
50
- indent-0.1.16.dist-info/METADATA,sha256=jFt9q148SVv-NGfjjcVjyuORrYZ-3MXQSSattDurni0,1308
51
- indent-0.1.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
52
- indent-0.1.16.dist-info/entry_points.txt,sha256=q8q1t1sbl4NULGOR0OV5RmSG4KEjkpEQRU_RUXEGzcs,44
53
- indent-0.1.16.dist-info/RECORD,,
48
+ exponent/utils/version.py,sha256=GHZ9ET1kMyDubJZU3w2sah5Pw8XpiEakS5IOlt3wUnQ,8888
49
+ indent-0.1.17.dist-info/METADATA,sha256=w5ahImXeo_nq_h3eZd92XsjsYzNmPU8nR-D9xSRIRRI,1308
50
+ indent-0.1.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
51
+ indent-0.1.17.dist-info/entry_points.txt,sha256=q8q1t1sbl4NULGOR0OV5RmSG4KEjkpEQRU_RUXEGzcs,44
52
+ indent-0.1.17.dist-info/RECORD,,
@@ -1,111 +0,0 @@
1
- import asyncio
2
- import os
3
- import sys
4
- from typing import cast
5
-
6
- import click
7
-
8
- from exponent.commands.run_commands import run_chat
9
- from exponent.commands.settings import use_settings
10
- from exponent.commands.types import exponent_cli_group
11
- from exponent.core.config import Settings
12
- from exponent.core.remote_execution.client import RemoteExecutionClient, WSDisconnected
13
- from exponent.core.remote_execution.types import (
14
- PrReviewWorkflowInput,
15
- WorkflowTriggerResponse,
16
- )
17
-
18
-
19
- @exponent_cli_group(name="workflow")
20
- def workflow_cli() -> None:
21
- """Workflow commands."""
22
- pass
23
-
24
-
25
- @workflow_cli.group(hidden=True)
26
- def workflow() -> None:
27
- """Workflow management commands."""
28
- pass
29
-
30
-
31
- @workflow.command()
32
- @use_settings
33
- @click.argument("workflow_type", type=click.STRING)
34
- def trigger(settings: Settings, workflow_type: str) -> None:
35
- """Trigger a workflow."""
36
-
37
- if not settings.api_key:
38
- raise click.ClickException(
39
- "No API key found. Use `indent login` to set your API key."
40
- )
41
-
42
- if workflow_type != "pr_review":
43
- raise click.UsageError("Invalid workflow name. Only 'pr_review' is supported.")
44
-
45
- loop = asyncio.get_event_loop()
46
- response = loop.run_until_complete(trigger_pr_review_workflow(settings))
47
-
48
- while True:
49
- result = run_chat(
50
- loop, settings.api_key, response.chat_uuid, settings, None, None, None
51
- )
52
- if result is None or isinstance(result, WSDisconnected):
53
- # NOTE: None here means that handle_connection_changes exited
54
- # first. We should likely have a different message for this.
55
- if result and result.error_message:
56
- click.secho(f"Error: {result.error_message}", fg="red")
57
- sys.exit(10)
58
- else:
59
- click.echo("Disconnected upon user request, shutting down...")
60
- break
61
- else:
62
- raise click.ClickException("Workflow run exited unexpectedly")
63
-
64
-
65
- async def _subprocess_check_output(command: str) -> str:
66
- process = await asyncio.create_subprocess_shell(
67
- command,
68
- stdout=asyncio.subprocess.PIPE,
69
- stderr=asyncio.subprocess.STDOUT,
70
- )
71
- stdout, _ = await process.communicate()
72
-
73
- if process.returncode != 0:
74
- output = stdout.decode().strip()
75
- raise click.ClickException(
76
- f"Command '{command}' failed with exit code {process.returncode}:\n{output}"
77
- )
78
-
79
- return stdout.decode().strip()
80
-
81
-
82
- async def trigger_pr_review_workflow(settings: Settings) -> WorkflowTriggerResponse:
83
- origin_url = await _subprocess_check_output("git ls-remote --get-url origin")
84
- url = origin_url.strip().removesuffix(".git")
85
- remote = url.split(":")[-1]
86
- owner, repo = remote.split("/")[-2:]
87
-
88
- pr_number_str = os.environ.get("PR_NUMBER")
89
- if not pr_number_str:
90
- raise click.ClickException("PR_NUMBER environment variable is not set")
91
- try:
92
- pr_number = int(pr_number_str)
93
- except ValueError:
94
- raise click.ClickException(
95
- "PR_NUMBER environment variable is not a valid integer"
96
- )
97
-
98
- async with RemoteExecutionClient.session(
99
- api_key=cast(str, settings.api_key),
100
- base_url=settings.get_base_api_url(),
101
- base_ws_url=settings.get_base_ws_url(),
102
- working_directory=os.getcwd(),
103
- ) as client:
104
- return await client.trigger_workflow(
105
- workflow_name="pr_review",
106
- workflow_input=PrReviewWorkflowInput(
107
- repo_owner=owner,
108
- repo_name=repo,
109
- pr_number=pr_number,
110
- ),
111
- )