indent 0.1.8__tar.gz → 0.1.9__tar.gz

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 (57) hide show
  1. {indent-0.1.8 → indent-0.1.9}/.gitignore +3 -0
  2. {indent-0.1.8 → indent-0.1.9}/PKG-INFO +1 -1
  3. indent-0.1.9/exponent/__init__.py +21 -0
  4. {indent-0.1.8 → indent-0.1.9}/exponent/cli.py +8 -10
  5. {indent-0.1.8 → indent-0.1.9}/exponent/commands/config_commands.py +10 -10
  6. {indent-0.1.8 → indent-0.1.9}/exponent/commands/run_commands.py +1 -1
  7. {indent-0.1.8 → indent-0.1.9}/exponent/commands/upgrade.py +3 -3
  8. {indent-0.1.8 → indent-0.1.9}/exponent/commands/utils.py +0 -90
  9. {indent-0.1.8 → indent-0.1.9}/exponent/commands/workflow_commands.py +1 -1
  10. {indent-0.1.8 → indent-0.1.9}/exponent/core/config.py +2 -0
  11. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/client.py +57 -1
  12. {indent-0.1.8 → indent-0.1.9}/pyproject.toml +10 -2
  13. indent-0.1.8/exponent/__init__.py +0 -1
  14. indent-0.1.8/exponent/commands/github_app_commands.py +0 -211
  15. indent-0.1.8/exponent/commands/listen_commands.py +0 -96
  16. indent-0.1.8/exponent/commands/shell_commands.py +0 -2840
  17. indent-0.1.8/exponent/commands/theme.py +0 -246
  18. {indent-0.1.8 → indent-0.1.9}/exponent/commands/cloud_commands.py +0 -0
  19. {indent-0.1.8 → indent-0.1.9}/exponent/commands/common.py +0 -0
  20. {indent-0.1.8 → indent-0.1.9}/exponent/commands/settings.py +0 -0
  21. {indent-0.1.8 → indent-0.1.9}/exponent/commands/types.py +0 -0
  22. {indent-0.1.8 → indent-0.1.9}/exponent/core/graphql/__init__.py +0 -0
  23. {indent-0.1.8 → indent-0.1.9}/exponent/core/graphql/client.py +0 -0
  24. {indent-0.1.8 → indent-0.1.9}/exponent/core/graphql/cloud_config_queries.py +0 -0
  25. {indent-0.1.8 → indent-0.1.9}/exponent/core/graphql/get_chats_query.py +0 -0
  26. {indent-0.1.8 → indent-0.1.9}/exponent/core/graphql/github_config_queries.py +0 -0
  27. {indent-0.1.8 → indent-0.1.9}/exponent/core/graphql/mutations.py +0 -0
  28. {indent-0.1.8 → indent-0.1.9}/exponent/core/graphql/queries.py +0 -0
  29. {indent-0.1.8 → indent-0.1.9}/exponent/core/graphql/subscriptions.py +0 -0
  30. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/checkpoints.py +0 -0
  31. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/cli_rpc_types.py +0 -0
  32. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/code_execution.py +0 -0
  33. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/error_info.py +0 -0
  34. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/exceptions.py +0 -0
  35. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/file_write.py +0 -0
  36. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/files.py +0 -0
  37. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/git.py +0 -0
  38. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/http_fetch.py +0 -0
  39. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/languages/python_execution.py +0 -0
  40. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/languages/shell_streaming.py +0 -0
  41. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/languages/types.py +0 -0
  42. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/session.py +0 -0
  43. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/system_context.py +0 -0
  44. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/tool_execution.py +0 -0
  45. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/truncation.py +0 -0
  46. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/types.py +0 -0
  47. {indent-0.1.8 → indent-0.1.9}/exponent/core/remote_execution/utils.py +0 -0
  48. {indent-0.1.8 → indent-0.1.9}/exponent/core/types/__init__.py +0 -0
  49. {indent-0.1.8 → indent-0.1.9}/exponent/core/types/command_data.py +0 -0
  50. {indent-0.1.8 → indent-0.1.9}/exponent/core/types/event_types.py +0 -0
  51. {indent-0.1.8 → indent-0.1.9}/exponent/core/types/generated/__init__.py +0 -0
  52. {indent-0.1.8 → indent-0.1.9}/exponent/core/types/generated/strategy_info.py +0 -0
  53. {indent-0.1.8 → indent-0.1.9}/exponent/migration-docs/login.md +0 -0
  54. {indent-0.1.8 → indent-0.1.9}/exponent/py.typed +0 -0
  55. {indent-0.1.8 → indent-0.1.9}/exponent/utils/__init__.py +0 -0
  56. {indent-0.1.8 → indent-0.1.9}/exponent/utils/colors.py +0 -0
  57. {indent-0.1.8 → indent-0.1.9}/exponent/utils/version.py +0 -0
@@ -175,3 +175,6 @@ private-key.pem
175
175
 
176
176
  # Claude Code
177
177
  .claude
178
+
179
+ # Generated version files
180
+ python_modules/exponent/exponent/__init__.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: indent
3
- Version: 0.1.8
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
@@ -0,0 +1,21 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
+
6
+ TYPE_CHECKING = False
7
+ if TYPE_CHECKING:
8
+ from typing import Tuple
9
+ from typing import Union
10
+
11
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
12
+ else:
13
+ VERSION_TUPLE = object
14
+
15
+ version: str
16
+ __version__: str
17
+ __version_tuple__: VERSION_TUPLE
18
+ version_tuple: VERSION_TUPLE
19
+
20
+ __version__ = version = '0.1.9'
21
+ __version_tuple__ = version_tuple = (0, 1, 9)
@@ -4,10 +4,7 @@ from click import Context, HelpFormatter
4
4
  from exponent.commands.cloud_commands import cloud_cli
5
5
  from exponent.commands.common import set_log_level
6
6
  from exponent.commands.config_commands import config_cli
7
- from exponent.commands.github_app_commands import github_app_cli
8
- from exponent.commands.listen_commands import listen_cli
9
- from exponent.commands.run_commands import run_cli
10
- from exponent.commands.shell_commands import shell_cli
7
+ from exponent.commands.run_commands import run, run_cli
11
8
  from exponent.commands.types import ExponentGroup, exponent_cli_group
12
9
  from exponent.commands.upgrade import upgrade_cli
13
10
  from exponent.commands.workflow_commands import workflow_cli
@@ -16,26 +13,27 @@ from exponent.utils.version import (
16
13
  )
17
14
 
18
15
 
19
- @exponent_cli_group()
16
+ @exponent_cli_group(invoke_without_command=True)
20
17
  @click.version_option(get_installed_version(), prog_name="Indent CLI")
21
18
  @click.pass_context
22
19
  def cli(ctx: Context) -> None:
23
20
  """
24
21
  Indent: Your AI pair programmer.
25
22
 
26
- Run indent run to start
23
+ Run indent to start (or indent run)
27
24
  """
28
25
  set_log_level()
29
26
 
27
+ # If no command is provided, invoke the 'run' command
28
+ if ctx.invoked_subcommand is None:
29
+ ctx.invoke(run)
30
+
30
31
 
31
32
  sources: list[ExponentGroup] = [
32
33
  config_cli, # Configuration commands
33
34
  run_cli, # Run AI chat commands
34
- shell_cli, # Shell interaction commands
35
35
  upgrade_cli, # Upgrade-related commands
36
36
  cloud_cli, # Cloud commands
37
- listen_cli, # Listen to chat events
38
- github_app_cli, # Setup github app installation
39
37
  workflow_cli, # Workflow commands
40
38
  ]
41
39
 
@@ -99,7 +97,7 @@ def hidden(ctx: Context) -> None:
99
97
  with formatter.section("Usage"):
100
98
  if ctx.parent and ctx.parent.command:
101
99
  formatter.write_usage(
102
- ctx.parent.command.name or "exponent", "COMMAND [ARGS]..."
100
+ ctx.parent.command.name or "indent", "COMMAND [ARGS]..."
103
101
  )
104
102
  formatter.write_paragraph()
105
103
  with formatter.indentation():
@@ -36,7 +36,7 @@ from exponent.utils.version import (
36
36
 
37
37
  @exponent_cli_group()
38
38
  def config_cli() -> None:
39
- """Manage Exponent configuration settings."""
39
+ """Manage Indent configuration settings."""
40
40
  pass
41
41
 
42
42
 
@@ -133,12 +133,12 @@ async def repos_for_github_config_task(
133
133
  @click.option(
134
134
  "--set-git-warning-disabled",
135
135
  is_flag=True,
136
- help="Disable the git warning for running Exponent from a non-git repository",
136
+ help="Disable the git warning for running Indent from a non-git repository",
137
137
  )
138
138
  @click.option(
139
139
  "--set-git-warning-enabled",
140
140
  is_flag=True,
141
- help="Enable the git warning for running Exponent from a non-git repository",
141
+ help="Enable the git warning for running Indent from a non-git repository",
142
142
  )
143
143
  @click.option(
144
144
  "--set-auto-upgrade-enabled",
@@ -198,7 +198,7 @@ def config(
198
198
  set_base_api_url_override: str | None = None,
199
199
  set_base_ws_url_override: str | None = None,
200
200
  ) -> None:
201
- """Display current Exponent configuration."""
201
+ """Display current Indent configuration."""
202
202
 
203
203
  num_options_set = sum(
204
204
  [
@@ -230,7 +230,7 @@ def config(
230
230
  settings.write_settings_to_config_file()
231
231
 
232
232
  click.secho(
233
- "Git warning enabled. Exponent will now check for a git repository.\n",
233
+ "Git warning enabled. Indent will now check for a git repository.\n",
234
234
  fg="yellow",
235
235
  )
236
236
 
@@ -239,7 +239,7 @@ def config(
239
239
  settings.write_settings_to_config_file()
240
240
 
241
241
  click.secho(
242
- "Git warning disabled. Exponent will no longer check for a git repository.\n",
242
+ "Git warning disabled. Indent will no longer check for a git repository.\n",
243
243
  fg="yellow",
244
244
  )
245
245
 
@@ -248,7 +248,7 @@ def config(
248
248
  settings.write_settings_to_config_file()
249
249
 
250
250
  click.secho(
251
- "Automatic upgrades enabled. Exponent will now check for updates.\n",
251
+ "Automatic upgrades enabled. Indent will now check for updates.\n",
252
252
  fg="yellow",
253
253
  )
254
254
 
@@ -257,7 +257,7 @@ def config(
257
257
  settings.write_settings_to_config_file()
258
258
 
259
259
  click.secho(
260
- "Automatic upgrades disabled. Exponent will no longer check for updates.\n",
260
+ "Automatic upgrades disabled. Indent will no longer check for updates.\n",
261
261
  fg="yellow",
262
262
  )
263
263
 
@@ -312,10 +312,10 @@ def config(
312
312
 
313
313
 
314
314
  @config_cli.command()
315
- @click.option("--key", help="Your Exponent API Key")
315
+ @click.option("--key", help="Your Indent API Key")
316
316
  @use_settings
317
317
  def login(settings: Settings, key: str) -> None:
318
- """Log in to Exponent."""
318
+ """Log in to Indent."""
319
319
 
320
320
  if not key:
321
321
  redirect_to_login(settings, "provided")
@@ -68,7 +68,7 @@ def run(
68
68
  prompt: str | None = None,
69
69
  workflow_id: str | None = None,
70
70
  ) -> None:
71
- """Start or reconnect to an Exponent session."""
71
+ """[default] Start or reconnect to an Indent session."""
72
72
  check_exponent_version_and_upgrade(settings)
73
73
 
74
74
  if not settings.api_key:
@@ -6,7 +6,7 @@ from exponent.utils.version import check_exponent_version, upgrade_exponent
6
6
 
7
7
  @exponent_cli_group()
8
8
  def upgrade_cli() -> None:
9
- """Manage Exponent version upgrades."""
9
+ """Manage Indent version upgrades."""
10
10
  pass
11
11
 
12
12
 
@@ -17,7 +17,7 @@ def upgrade_cli() -> None:
17
17
  help="Upgrade without prompting for confirmation, if a new version is available.",
18
18
  )
19
19
  def upgrade(force: bool = False) -> None:
20
- """Upgrade Exponent to the latest version."""
20
+ """Upgrade Indent to the latest version."""
21
21
  if result := check_exponent_version():
22
22
  installed_version, latest_version = result
23
23
  upgrade_exponent(
@@ -26,4 +26,4 @@ def upgrade(force: bool = False) -> None:
26
26
  force=force,
27
27
  )
28
28
  else:
29
- click.echo("Exponent is already up to date.")
29
+ click.echo("Indent is already up to date.")
@@ -1,7 +1,6 @@
1
1
  import asyncio
2
2
  import os
3
3
  import sys
4
- import threading
5
4
  import time
6
5
  import webbrowser
7
6
 
@@ -69,43 +68,6 @@ def launch_exponent_browser(
69
68
  webbrowser.open(url)
70
69
 
71
70
 
72
- def start_background_event_loop() -> asyncio.AbstractEventLoop:
73
- def run_event_loop(loop: asyncio.AbstractEventLoop) -> None:
74
- asyncio.set_event_loop(loop)
75
- loop.run_forever()
76
-
77
- loop = asyncio.new_event_loop()
78
- thread = threading.Thread(target=run_event_loop, args=(loop,), daemon=True)
79
- thread.start()
80
- return loop
81
-
82
-
83
- def read_input(prompt: str) -> str:
84
- sys.stdout.write(prompt)
85
- sys.stdout.flush()
86
- return sys.stdin.readline()
87
-
88
-
89
- def ask_for_quit_confirmation(program_name: str = "Exponent") -> bool:
90
- while True:
91
- try:
92
- answer = (
93
- input(f"Do you want to quit {program_name}? [y/\x1b[1mN\x1b[0m]")
94
- .strip()
95
- .lower()
96
- )
97
- if answer in {"y", "yes"}:
98
- return True
99
- elif answer in {"n", "no", ""}:
100
- return False
101
- except KeyboardInterrupt:
102
- print()
103
- return True
104
- except EOFError:
105
- print()
106
- return True
107
-
108
-
109
71
  class Spinner:
110
72
  def __init__(self, text: str) -> None:
111
73
  self.text = text
@@ -154,58 +116,6 @@ class Spinner:
154
116
  sys.stdout.flush()
155
117
 
156
118
 
157
- class ThinkingSpinner(Spinner):
158
- def __init__(
159
- self,
160
- fg_color: tuple[int, int, int] | None = None,
161
- text: str = "Exponent is thinking",
162
- ) -> None:
163
- super().__init__(text)
164
- self.fg_color = fg_color
165
- self.bold = True
166
- self.animation_chars = "⣾⣽⣻⢿⡿⣟⣯⣷" # More subtle animation
167
- self.animation_speed = 8 # Medium speed animation
168
- self.start_time = time.time() # Track when thinking started
169
-
170
- def show(self) -> None:
171
- if self.task is not None:
172
- return
173
-
174
- async def spinner(base_time: float) -> None:
175
- color_start = ""
176
- if self.fg_color:
177
- if isinstance(self.fg_color, tuple) and len(self.fg_color) == 3:
178
- r, g, b = self.fg_color
179
- color_start = f"\x1b[38;2;{r};{g};{b}m"
180
- elif isinstance(self.fg_color, int):
181
- color_start = f"\x1b[{30 + self.fg_color}m"
182
-
183
- bold_start = "\x1b[1m" if self.bold else ""
184
- style_start = f"{color_start}{bold_start}"
185
- style_end = "\x1b[0m"
186
-
187
- while True:
188
- t = time.time() - base_time
189
- i = round(t * self.animation_speed) % len(self.animation_chars)
190
-
191
- # Calculate elapsed seconds
192
- elapsed = time.time() - self.start_time
193
- if elapsed < 60:
194
- timer = f"{int(elapsed)}s"
195
- else:
196
- mins = int(elapsed // 60)
197
- secs = int(elapsed % 60)
198
- timer = f"{mins}m {secs}s"
199
-
200
- print(
201
- f"\r{style_start}{self.animation_chars[i]} {self.text} ({timer}){style_end}",
202
- end="",
203
- )
204
- await asyncio.sleep(0.1)
205
-
206
- self.task = asyncio.get_event_loop().create_task(spinner(self.base_time))
207
-
208
-
209
119
  class ConnectionTracker:
210
120
  def __init__(self) -> None:
211
121
  self.connected = True
@@ -22,7 +22,7 @@ def workflow_cli() -> None:
22
22
  pass
23
23
 
24
24
 
25
- @workflow_cli.group()
25
+ @workflow_cli.group(hidden=True)
26
26
  def workflow() -> None:
27
27
  """Workflow management commands."""
28
28
  pass
@@ -24,6 +24,8 @@ logger = logging.getLogger(__name__)
24
24
 
25
25
 
26
26
  def is_editable_install() -> bool:
27
+ return True
28
+
27
29
  if os.getenv("ENVIRONMENT") == "test" or os.getenv("EXPONENT_TEST_AUTO_UPGRADE"):
28
30
  # We should explicitly set these variables
29
31
  # in test when needed
@@ -152,7 +152,63 @@ class RemoteExecutionClient:
152
152
  return None
153
153
 
154
154
  data = json.dumps(msg_data["data"])
155
- request = msgspec.json.decode(data, type=CliRpcRequest)
155
+ try:
156
+ request = msgspec.json.decode(data, type=CliRpcRequest)
157
+ except msgspec.DecodeError as e:
158
+ # Try and decode to get request_id if possible
159
+ request = msgspec.json.decode(data)
160
+ if isinstance(request, dict) and "request_id" in request:
161
+ request_id = request["request_id"]
162
+ if (
163
+ request.get("request", {}).get("type", {}) == "tool_execution"
164
+ ) and (
165
+ "tool_input" in request["request"]
166
+ and "tool_name" in request["request"]["tool_input"]
167
+ ):
168
+ tool_name = request["request"]["tool_input"]["tool_name"]
169
+ logger.error(
170
+ f"Error tool {tool_name} received in a request."
171
+ "Please ensure you are running the latest version of Indent. If this issue persists, please contact support."
172
+ )
173
+ await websocket.send(
174
+ json.dumps(
175
+ {
176
+ "type": "result",
177
+ "data": msgspec.to_builtins(
178
+ CliRpcResponse(
179
+ request_id=request_id,
180
+ response=ErrorResponse(
181
+ error_message=f"Unknown tool: {tool_name}. If you are running an older version of Indent, please upgrade to the latest version to ensure compatibility."
182
+ ),
183
+ )
184
+ ),
185
+ }
186
+ )
187
+ )
188
+ else:
189
+ logger.error(
190
+ "Error decoding cli rpc request. Please ensure you are running the latest version of Indent."
191
+ )
192
+ await websocket.send(
193
+ json.dumps(
194
+ {
195
+ "type": "result",
196
+ "data": msgspec.to_builtins(
197
+ CliRpcResponse(
198
+ request_id=request_id,
199
+ response=ErrorResponse(
200
+ error_message=f"Unknown cli rpc request type: {request}",
201
+ ),
202
+ )
203
+ ),
204
+ }
205
+ )
206
+ )
207
+
208
+ return None
209
+ else:
210
+ # If we couldn't get a request_id, re-raise and fail noisily
211
+ raise e
156
212
 
157
213
  if isinstance(request.request, TerminateRequest):
158
214
  await self.halt_all_code_executions()
@@ -1,10 +1,10 @@
1
1
  [build-system]
2
- requires = ["hatchling"]
2
+ requires = ["hatchling", "hatch-vcs"]
3
3
  build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "indent"
7
- version = "0.1.8"
7
+ dynamic = ["version"]
8
8
  description = "Indent is an AI Pair Programmer"
9
9
  authors = [{ name = "Sashank Thupukari", email = "sashank@exponent.run" }]
10
10
  requires-python = ">=3.10,<3.13"
@@ -163,3 +163,11 @@ max-returns = 10
163
163
 
164
164
  [tool.ruff.format]
165
165
  docstring-code-format = true
166
+
167
+ [tool.hatch.version]
168
+ source = "code"
169
+ path = "./version.py"
170
+
171
+ [tool.hatch.build.hooks.vcs]
172
+ version-file = "exponent/__init__.py"
173
+
@@ -1 +0,0 @@
1
- __version__ = "0.1.8" # Keep in sync with pyproject.toml
@@ -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)