indent 0.1.1__tar.gz → 0.1.3__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 (56) hide show
  1. {indent-0.1.1 → indent-0.1.3}/PKG-INFO +2 -3
  2. indent-0.1.3/exponent/__init__.py +1 -0
  3. {indent-0.1.1 → indent-0.1.3}/exponent/commands/github_app_commands.py +3 -3
  4. {indent-0.1.1 → indent-0.1.3}/exponent/core/remote_execution/cli_rpc_types.py +3 -0
  5. {indent-0.1.1 → indent-0.1.3}/exponent/core/remote_execution/files.py +30 -2
  6. {indent-0.1.1 → indent-0.1.3}/exponent/core/remote_execution/tool_execution.py +3 -8
  7. {indent-0.1.1 → indent-0.1.3}/pyproject.toml +3 -5
  8. indent-0.1.1/exponent/__init__.py +0 -1
  9. indent-0.1.1/exponent/core/remote_execution/command_execution.py +0 -105
  10. {indent-0.1.1 → indent-0.1.3}/.gitignore +0 -0
  11. {indent-0.1.1 → indent-0.1.3}/exponent/cli.py +0 -0
  12. {indent-0.1.1 → indent-0.1.3}/exponent/commands/cloud_commands.py +0 -0
  13. {indent-0.1.1 → indent-0.1.3}/exponent/commands/common.py +0 -0
  14. {indent-0.1.1 → indent-0.1.3}/exponent/commands/config_commands.py +0 -0
  15. {indent-0.1.1 → indent-0.1.3}/exponent/commands/listen_commands.py +0 -0
  16. {indent-0.1.1 → indent-0.1.3}/exponent/commands/run_commands.py +0 -0
  17. {indent-0.1.1 → indent-0.1.3}/exponent/commands/settings.py +0 -0
  18. {indent-0.1.1 → indent-0.1.3}/exponent/commands/shell_commands.py +0 -0
  19. {indent-0.1.1 → indent-0.1.3}/exponent/commands/theme.py +0 -0
  20. {indent-0.1.1 → indent-0.1.3}/exponent/commands/types.py +0 -0
  21. {indent-0.1.1 → indent-0.1.3}/exponent/commands/upgrade.py +0 -0
  22. {indent-0.1.1 → indent-0.1.3}/exponent/commands/utils.py +0 -0
  23. {indent-0.1.1 → indent-0.1.3}/exponent/core/config.py +0 -0
  24. {indent-0.1.1 → indent-0.1.3}/exponent/core/graphql/__init__.py +0 -0
  25. {indent-0.1.1 → indent-0.1.3}/exponent/core/graphql/client.py +0 -0
  26. {indent-0.1.1 → indent-0.1.3}/exponent/core/graphql/cloud_config_queries.py +0 -0
  27. {indent-0.1.1 → indent-0.1.3}/exponent/core/graphql/get_chats_query.py +0 -0
  28. {indent-0.1.1 → indent-0.1.3}/exponent/core/graphql/github_config_queries.py +0 -0
  29. {indent-0.1.1 → indent-0.1.3}/exponent/core/graphql/mutations.py +0 -0
  30. {indent-0.1.1 → indent-0.1.3}/exponent/core/graphql/queries.py +0 -0
  31. {indent-0.1.1 → indent-0.1.3}/exponent/core/graphql/subscriptions.py +0 -0
  32. {indent-0.1.1 → indent-0.1.3}/exponent/core/remote_execution/checkpoints.py +0 -0
  33. {indent-0.1.1 → indent-0.1.3}/exponent/core/remote_execution/client.py +0 -0
  34. {indent-0.1.1 → indent-0.1.3}/exponent/core/remote_execution/code_execution.py +0 -0
  35. {indent-0.1.1 → indent-0.1.3}/exponent/core/remote_execution/error_info.py +0 -0
  36. {indent-0.1.1 → indent-0.1.3}/exponent/core/remote_execution/exceptions.py +0 -0
  37. {indent-0.1.1 → indent-0.1.3}/exponent/core/remote_execution/file_write.py +0 -0
  38. {indent-0.1.1 → indent-0.1.3}/exponent/core/remote_execution/git.py +0 -0
  39. {indent-0.1.1 → indent-0.1.3}/exponent/core/remote_execution/languages/python_execution.py +0 -0
  40. {indent-0.1.1 → indent-0.1.3}/exponent/core/remote_execution/languages/shell_streaming.py +0 -0
  41. {indent-0.1.1 → indent-0.1.3}/exponent/core/remote_execution/languages/types.py +0 -0
  42. {indent-0.1.1 → indent-0.1.3}/exponent/core/remote_execution/session.py +0 -0
  43. {indent-0.1.1 → indent-0.1.3}/exponent/core/remote_execution/system_context.py +0 -0
  44. {indent-0.1.1 → indent-0.1.3}/exponent/core/remote_execution/truncation.py +0 -0
  45. {indent-0.1.1 → indent-0.1.3}/exponent/core/remote_execution/types.py +0 -0
  46. {indent-0.1.1 → indent-0.1.3}/exponent/core/remote_execution/utils.py +0 -0
  47. {indent-0.1.1 → indent-0.1.3}/exponent/core/types/__init__.py +0 -0
  48. {indent-0.1.1 → indent-0.1.3}/exponent/core/types/command_data.py +0 -0
  49. {indent-0.1.1 → indent-0.1.3}/exponent/core/types/event_types.py +0 -0
  50. {indent-0.1.1 → indent-0.1.3}/exponent/core/types/generated/__init__.py +0 -0
  51. {indent-0.1.1 → indent-0.1.3}/exponent/core/types/generated/strategy_info.py +0 -0
  52. {indent-0.1.1 → indent-0.1.3}/exponent/migration-docs/login.md +0 -0
  53. {indent-0.1.1 → indent-0.1.3}/exponent/py.typed +0 -0
  54. {indent-0.1.1 → indent-0.1.3}/exponent/utils/__init__.py +0 -0
  55. {indent-0.1.1 → indent-0.1.3}/exponent/utils/colors.py +0 -0
  56. {indent-0.1.1 → indent-0.1.3}/exponent/utils/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: indent
3
- Version: 0.1.1
3
+ Version: 0.1.3
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
@@ -12,7 +12,6 @@ Requires-Dist: click<9,>=8.1.7
12
12
  Requires-Dist: colour<0.2,>=0.1.5
13
13
  Requires-Dist: diff-match-patch<20230431,>=20230430
14
14
  Requires-Dist: eval-type-backport<0.3,>=0.2.0
15
- Requires-Dist: gevent==24.2.1
16
15
  Requires-Dist: git-python>=1.0.3
17
16
  Requires-Dist: gitignore-parser<0.2,>=0.1.11
18
17
  Requires-Dist: gql[httpx,websockets]<4,>=3.5.0
@@ -26,7 +25,7 @@ Requires-Dist: prompt-toolkit<4,>=3.0.36
26
25
  Requires-Dist: pydantic-settings<3,>=2.2.1
27
26
  Requires-Dist: pydantic[email]<3,>=2.6.4
28
27
  Requires-Dist: pygit2<2,>=1.15.0
29
- Requires-Dist: python-ripgrep==0.0.8
28
+ Requires-Dist: python-ripgrep==0.0.9
30
29
  Requires-Dist: pyyaml>=6.0.2
31
30
  Requires-Dist: questionary<3,>=2.0.1
32
31
  Requires-Dist: rapidfuzz<4,>=3.9.0
@@ -0,0 +1 @@
1
+ __version__ = "0.1.3" # Keep in sync with pyproject.toml
@@ -1,10 +1,10 @@
1
1
  import asyncio
2
2
  import json
3
- from pathlib import Path
4
3
  import subprocess
5
4
  import sys
6
- from uuid import uuid4
7
5
  import webbrowser
6
+ from pathlib import Path
7
+ from uuid import uuid4
8
8
 
9
9
  import click
10
10
  from git import GitCommandError, Repo
@@ -45,7 +45,7 @@ def install_github_app(
45
45
  if not git_info:
46
46
  raise RuntimeError("Not running inside of valid git repository")
47
47
 
48
- install_url = "https://github.com/apps/indent-com/installations/new"
48
+ install_url = "https://github.com/apps/indent/installations/new"
49
49
  webbrowser.open(install_url)
50
50
 
51
51
  click.confirm(
@@ -96,14 +96,17 @@ class GrepToolInput(ToolInput, tag=GREP_TOOL_NAME):
96
96
  pattern: str
97
97
  path: str | None = None
98
98
  include: str | None = None
99
+ multiline: bool | None = None
99
100
 
100
101
 
101
102
  class GrepToolResult(ToolResult, tag=GREP_TOOL_NAME):
102
103
  matches: list[str]
103
104
  truncated: bool = False
104
105
 
106
+
105
107
  WEB_FETCH_TOOL_NAME = "web_fetch"
106
108
 
109
+
107
110
  class WebFetchToolInput(ToolInput, tag=WEB_FETCH_TOOL_NAME):
108
111
  query: str
109
112
 
@@ -6,6 +6,7 @@ from anyio import Path as AsyncPath
6
6
  from python_ripgrep import PySortMode, PySortModeKind, files, search
7
7
  from rapidfuzz import process
8
8
 
9
+ from exponent.core.remote_execution.cli_rpc_types import ErrorToolResult, GrepToolResult
9
10
  from exponent.core.remote_execution.types import (
10
11
  FileAttachment,
11
12
  FilePath,
@@ -28,6 +29,7 @@ FILE_NOT_FOUND: Final[str] = "File {} does not exist"
28
29
  MAX_FILES_TO_WALK: Final[int] = 10_000
29
30
 
30
31
  GLOB_MAX_COUNT: Final[int] = 1000
32
+ GREP_MAX_RESULTS = 100
31
33
 
32
34
 
33
35
  class FileCache:
@@ -222,12 +224,32 @@ async def search_files(
222
224
  file_pattern: str | None,
223
225
  regex: str,
224
226
  working_directory: str,
225
- ) -> list[str]:
227
+ multiline: bool | None = None,
228
+ ) -> GrepToolResult | ErrorToolResult:
226
229
  path = AsyncPath(working_directory) / path_str
230
+
231
+ if not await path.exists():
232
+ return ErrorToolResult(
233
+ error_message=f"Path does not exist: {path_str}",
234
+ )
235
+
227
236
  path_resolved = await path.resolve()
228
237
  globs = [file_pattern] if file_pattern else None
229
238
 
230
- return await to_thread(
239
+ if globs:
240
+ matched_files = await to_thread(
241
+ files,
242
+ patterns=[],
243
+ paths=[str(path_resolved)],
244
+ globs=globs,
245
+ max_count=1,
246
+ )
247
+ if not matched_files:
248
+ return ErrorToolResult(
249
+ error_message=f"No files matched the include glob pattern: {file_pattern} at {path_str}",
250
+ )
251
+
252
+ results = await to_thread(
231
253
  search,
232
254
  patterns=[regex],
233
255
  paths=[str(path_resolved)],
@@ -238,6 +260,12 @@ async def search_files(
238
260
  separator_field_context="|",
239
261
  separator_field_match="|",
240
262
  separator_context="\n...\n",
263
+ multiline=multiline,
264
+ )
265
+
266
+ return GrepToolResult(
267
+ matches=results[:GREP_MAX_RESULTS],
268
+ truncated=bool(len(results) > GREP_MAX_RESULTS),
241
269
  )
242
270
 
243
271
 
@@ -37,8 +37,6 @@ from exponent.core.remote_execution.utils import (
37
37
  safe_read_file,
38
38
  )
39
39
 
40
- GREP_MAX_RESULTS = 100
41
-
42
40
 
43
41
  async def execute_tool(
44
42
  tool_input: ToolInputType, working_directory: str
@@ -247,16 +245,13 @@ async def execute_glob_files(
247
245
 
248
246
  async def execute_grep_files(
249
247
  tool_input: GrepToolInput, working_directory: str
250
- ) -> GrepToolResult:
251
- results = await files.search_files(
248
+ ) -> GrepToolResult | ErrorToolResult:
249
+ return await files.search_files(
252
250
  path_str=working_directory if tool_input.path is None else tool_input.path,
253
251
  file_pattern=tool_input.include,
254
252
  regex=tool_input.pattern,
255
253
  working_directory=working_directory,
256
- )
257
- return GrepToolResult(
258
- matches=results[:GREP_MAX_RESULTS],
259
- truncated=bool(len(results) > GREP_MAX_RESULTS),
254
+ multiline=tool_input.multiline,
260
255
  )
261
256
 
262
257
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "indent"
7
- version = "0.1.1"
7
+ version = "0.1.3"
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"
@@ -29,8 +29,7 @@ dependencies = [
29
29
  "toml>=0.10.2,<0.11",
30
30
  "websockets~=11.0",
31
31
  "anyio>=4.6.0,<5",
32
- "python-ripgrep==0.0.8",
33
- "gevent==24.2.1",
32
+ "python-ripgrep==0.0.9",
34
33
  "certifi>=2024.8.30,<2025",
35
34
  "eval-type-backport>=0.2.0,<0.3",
36
35
  "pip>=25.0.1,<26",
@@ -48,7 +47,6 @@ indent = "exponent.cli:cli"
48
47
  dev = [
49
48
  "coverage>=7.6.1,<8",
50
49
  "freezegun>=1.5.1,<2",
51
- "gevent>=24.2.1,<25",
52
50
  "pytest>=8.3.3,<9",
53
51
  "pytest-asyncio>=0.24.0,<0.25",
54
52
  "pytest-cov>=5.0.0,<6",
@@ -103,7 +101,7 @@ filterwarnings = [
103
101
 
104
102
  [tool.coverage.run]
105
103
  # handle forms of concurrency
106
- concurrency = ["gevent", "thread", "multiprocessing"]
104
+ concurrency = ["thread", "multiprocessing"]
107
105
  parallel = true
108
106
  # provides sensible defaults for coverage reporting
109
107
  plugins = ["covdefaults"]
@@ -1 +0,0 @@
1
- __version__ = "0.1.1" # Keep in sync with pyproject.toml
@@ -1,105 +0,0 @@
1
- from pathlib import Path
2
- from typing import Any
3
-
4
- from exponent.core.remote_execution import files
5
- from exponent.core.remote_execution.types import (
6
- CommandRequest,
7
- CommandResponse,
8
- )
9
- from exponent.core.types.command_data import (
10
- FileReadCommandData,
11
- PrototypeCommandData,
12
- )
13
-
14
- # Intentionally split the separator into two parts
15
- # to avoid matching it in the content
16
- CONTEXT_BATCH_SEPARATOR = "\n<batch_sep" + "arator>\n"
17
- CONTEXT_FILE_SEPARATOR = "\n<file_sep" + "arator>\n"
18
-
19
-
20
- async def execute_command(
21
- request: CommandRequest,
22
- working_directory: str,
23
- ) -> CommandResponse:
24
- try:
25
- if isinstance(request.data, FileReadCommandData):
26
- correlation_id = request.correlation_id
27
- file_path = request.data.file_path
28
- path = Path(working_directory, file_path)
29
- content, _ = await files.get_file_content(
30
- path, request.data.offset, request.data.limit
31
- )
32
-
33
- return CommandResponse(
34
- subcommand=request.data.type.value,
35
- content=content,
36
- correlation_id=correlation_id,
37
- )
38
- elif isinstance(request.data, PrototypeCommandData):
39
- correlation_id = request.correlation_id
40
- command_name = request.data.command_name
41
- content_json = request.data.content_json
42
- content_raw = request.data.content_raw
43
- content_rendered = request.data.content_rendered
44
-
45
- content = await execute_prototype_command(
46
- command_name=command_name,
47
- content_json=content_json,
48
- content_raw=content_raw,
49
- content_rendered=content_rendered,
50
- working_directory=working_directory,
51
- )
52
-
53
- return CommandResponse(
54
- subcommand=command_name,
55
- content=content,
56
- correlation_id=correlation_id,
57
- )
58
- else:
59
- raise ValueError(f"Unknown command request: {request}")
60
- except Exception as e: # noqa: BLE001 - TODO (Josh): Specialize errors for execution
61
- return CommandResponse(
62
- content="An error occurred during command execution: " + str(e),
63
- correlation_id=request.correlation_id,
64
- )
65
-
66
-
67
- async def execute_prototype_command(
68
- command_name: str,
69
- content_json: dict[str, Any],
70
- content_raw: str,
71
- content_rendered: str,
72
- working_directory: str,
73
- ) -> str:
74
- if command_name == "file_open":
75
- return f'Successfully opened file "{content_json["file_path"]}"'
76
- elif command_name == "search_files":
77
- results = await files.search_files(
78
- path_str=content_json["path"],
79
- file_pattern=content_json["file_pattern"],
80
- regex=content_json["regex"],
81
- working_directory=working_directory,
82
- )
83
- return "\n".join(results)
84
- elif command_name == "codebase_context":
85
- batches = await files.get_all_file_contents(
86
- working_directory=working_directory,
87
- )
88
-
89
- return CONTEXT_BATCH_SEPARATOR.join(
90
- CONTEXT_FILE_SEPARATOR.join(batch) for batch in batches
91
- )
92
- elif command_name == "ls":
93
- results = await files.file_walk(
94
- directory=content_json["path"],
95
- ignore_extra=files.DEFAULT_IGNORES,
96
- max_files=1000,
97
- )
98
- return "\n".join(results)
99
- elif command_name == "glob":
100
- results = await files.glob(
101
- path=content_json["path"],
102
- glob_pattern=content_json["glob"],
103
- )
104
- return "\n".join(results)
105
- raise ValueError(f"Unhandled prototype command: {command_name}")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes