indent 0.1.26__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.
Files changed (55) hide show
  1. exponent/__init__.py +34 -0
  2. exponent/cli.py +110 -0
  3. exponent/commands/cloud_commands.py +585 -0
  4. exponent/commands/common.py +411 -0
  5. exponent/commands/config_commands.py +334 -0
  6. exponent/commands/run_commands.py +222 -0
  7. exponent/commands/settings.py +56 -0
  8. exponent/commands/types.py +111 -0
  9. exponent/commands/upgrade.py +29 -0
  10. exponent/commands/utils.py +146 -0
  11. exponent/core/config.py +180 -0
  12. exponent/core/graphql/__init__.py +0 -0
  13. exponent/core/graphql/client.py +61 -0
  14. exponent/core/graphql/get_chats_query.py +47 -0
  15. exponent/core/graphql/mutations.py +160 -0
  16. exponent/core/graphql/queries.py +146 -0
  17. exponent/core/graphql/subscriptions.py +16 -0
  18. exponent/core/remote_execution/checkpoints.py +212 -0
  19. exponent/core/remote_execution/cli_rpc_types.py +499 -0
  20. exponent/core/remote_execution/client.py +999 -0
  21. exponent/core/remote_execution/code_execution.py +77 -0
  22. exponent/core/remote_execution/default_env.py +31 -0
  23. exponent/core/remote_execution/error_info.py +45 -0
  24. exponent/core/remote_execution/exceptions.py +10 -0
  25. exponent/core/remote_execution/file_write.py +35 -0
  26. exponent/core/remote_execution/files.py +330 -0
  27. exponent/core/remote_execution/git.py +268 -0
  28. exponent/core/remote_execution/http_fetch.py +94 -0
  29. exponent/core/remote_execution/languages/python_execution.py +239 -0
  30. exponent/core/remote_execution/languages/shell_streaming.py +226 -0
  31. exponent/core/remote_execution/languages/types.py +20 -0
  32. exponent/core/remote_execution/port_utils.py +73 -0
  33. exponent/core/remote_execution/session.py +128 -0
  34. exponent/core/remote_execution/system_context.py +26 -0
  35. exponent/core/remote_execution/terminal_session.py +375 -0
  36. exponent/core/remote_execution/terminal_types.py +29 -0
  37. exponent/core/remote_execution/tool_execution.py +595 -0
  38. exponent/core/remote_execution/tool_type_utils.py +39 -0
  39. exponent/core/remote_execution/truncation.py +296 -0
  40. exponent/core/remote_execution/types.py +635 -0
  41. exponent/core/remote_execution/utils.py +477 -0
  42. exponent/core/types/__init__.py +0 -0
  43. exponent/core/types/command_data.py +206 -0
  44. exponent/core/types/event_types.py +89 -0
  45. exponent/core/types/generated/__init__.py +0 -0
  46. exponent/core/types/generated/strategy_info.py +213 -0
  47. exponent/migration-docs/login.md +112 -0
  48. exponent/py.typed +4 -0
  49. exponent/utils/__init__.py +0 -0
  50. exponent/utils/colors.py +92 -0
  51. exponent/utils/version.py +289 -0
  52. indent-0.1.26.dist-info/METADATA +38 -0
  53. indent-0.1.26.dist-info/RECORD +55 -0
  54. indent-0.1.26.dist-info/WHEEL +4 -0
  55. indent-0.1.26.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,585 @@
1
+ import asyncio
2
+ import sys
3
+ from typing import Any, cast
4
+
5
+ import click
6
+
7
+ from exponent.commands.common import (
8
+ check_inside_git_repo,
9
+ check_running_from_home_directory,
10
+ check_ssl,
11
+ create_cloud_chat,
12
+ redirect_to_login,
13
+ start_chat_turn,
14
+ )
15
+ from exponent.commands.settings import use_settings
16
+ from exponent.commands.types import exponent_cli_group
17
+ from exponent.commands.utils import (
18
+ launch_exponent_browser,
19
+ print_exponent_message,
20
+ )
21
+ from exponent.core.config import Settings
22
+ from exponent.core.graphql.client import GraphQLClient
23
+ from exponent.core.graphql.mutations import (
24
+ CREATE_CLOUD_CHAT_FROM_REPOSITORY_MUTATION,
25
+ ENABLE_CLOUD_REPOSITORY_MUTATION,
26
+ REBUILD_CLOUD_REPOSITORY_MUTATION,
27
+ START_CHAT_TURN_MUTATION,
28
+ )
29
+ from exponent.core.graphql.queries import GITHUB_REPOSITORIES_QUERY
30
+ from exponent.utils.version import check_exponent_version_and_upgrade
31
+
32
+
33
+ @exponent_cli_group(hidden=True)
34
+ def cloud_cli() -> None:
35
+ pass
36
+
37
+
38
+ async def enable_cloud_repository(
39
+ api_key: str,
40
+ base_api_url: str,
41
+ base_ws_url: str,
42
+ org_name: str,
43
+ repo_name: str,
44
+ ) -> dict[str, Any]:
45
+ graphql_client = GraphQLClient(
46
+ api_key=api_key, base_api_url=base_api_url, base_ws_url=base_ws_url
47
+ )
48
+
49
+ variables = {
50
+ "orgName": org_name,
51
+ "repoName": repo_name,
52
+ }
53
+
54
+ result = await graphql_client.execute(
55
+ ENABLE_CLOUD_REPOSITORY_MUTATION,
56
+ variables,
57
+ "EnableCloudRepository",
58
+ timeout=120,
59
+ )
60
+
61
+ return cast(dict[str, Any], result["enableCloudRepository"])
62
+
63
+
64
+ async def rebuild_cloud_repository(
65
+ api_key: str,
66
+ base_api_url: str,
67
+ base_ws_url: str,
68
+ org_name: str,
69
+ repo_name: str,
70
+ ) -> dict[str, Any]:
71
+ graphql_client = GraphQLClient(
72
+ api_key=api_key, base_api_url=base_api_url, base_ws_url=base_ws_url
73
+ )
74
+
75
+ variables = {
76
+ "orgName": org_name,
77
+ "repoName": repo_name,
78
+ }
79
+
80
+ result = await graphql_client.execute(
81
+ REBUILD_CLOUD_REPOSITORY_MUTATION,
82
+ variables,
83
+ "RebuildCloudRepository",
84
+ timeout=120,
85
+ )
86
+
87
+ return cast(dict[str, Any], result["rebuildCloudRepository"])
88
+
89
+
90
+ async def list_github_repositories(
91
+ api_key: str,
92
+ base_api_url: str,
93
+ base_ws_url: str,
94
+ ) -> dict[str, Any]:
95
+ graphql_client = GraphQLClient(
96
+ api_key=api_key, base_api_url=base_api_url, base_ws_url=base_ws_url
97
+ )
98
+
99
+ result = await graphql_client.execute(
100
+ GITHUB_REPOSITORIES_QUERY,
101
+ None,
102
+ "GithubRepositories",
103
+ timeout=120,
104
+ )
105
+
106
+ return cast(dict[str, Any], result["githubRepositories"])
107
+
108
+
109
+ async def create_cloud_chat_from_repository(
110
+ api_key: str,
111
+ base_api_url: str,
112
+ base_ws_url: str,
113
+ repository_id: str,
114
+ provider: str | None = None,
115
+ ) -> dict[str, Any]:
116
+ graphql_client = GraphQLClient(
117
+ api_key=api_key, base_api_url=base_api_url, base_ws_url=base_ws_url
118
+ )
119
+
120
+ variables = {
121
+ "repositoryId": repository_id,
122
+ "provider": provider,
123
+ }
124
+
125
+ result = await graphql_client.execute(
126
+ CREATE_CLOUD_CHAT_FROM_REPOSITORY_MUTATION,
127
+ variables,
128
+ "CreateCloudChatFromRepository",
129
+ timeout=120,
130
+ )
131
+
132
+ return cast(dict[str, Any], result["createCloudChat"])
133
+
134
+
135
+ async def start_chat_turn_with_prompt(
136
+ api_key: str,
137
+ base_api_url: str,
138
+ base_ws_url: str,
139
+ chat_uuid: str,
140
+ prompt: str,
141
+ ) -> dict[str, Any]:
142
+ graphql_client = GraphQLClient(
143
+ api_key=api_key, base_api_url=base_api_url, base_ws_url=base_ws_url
144
+ )
145
+
146
+ variables = {
147
+ "chatInput": {"prompt": {"message": prompt, "attachments": []}},
148
+ "parentUuid": None,
149
+ "chatConfig": {
150
+ "chatUuid": chat_uuid,
151
+ "exponentModel": "PREMIUM",
152
+ "requireConfirmation": False,
153
+ "readOnly": False,
154
+ "depthLimit": 20,
155
+ },
156
+ }
157
+
158
+ result = await graphql_client.execute(
159
+ START_CHAT_TURN_MUTATION, variables, "StartChatTurnMutation", timeout=120
160
+ )
161
+
162
+ return cast(dict[str, Any], result["startChatReply"])
163
+
164
+
165
+ @cloud_cli.command(hidden=True)
166
+ @click.option(
167
+ "--org-name",
168
+ help="GitHub organization name",
169
+ required=True,
170
+ )
171
+ @click.option(
172
+ "--repo-name",
173
+ help="GitHub repository name",
174
+ required=True,
175
+ )
176
+ @use_settings
177
+ def enable_repo(
178
+ settings: Settings,
179
+ org_name: str,
180
+ repo_name: str,
181
+ ) -> None:
182
+ """Test utility for enabling cloud repository."""
183
+ check_exponent_version_and_upgrade(settings)
184
+
185
+ if not settings.api_key:
186
+ redirect_to_login(settings)
187
+ return
188
+
189
+ loop = asyncio.get_event_loop()
190
+
191
+ api_key = settings.api_key
192
+ base_api_url = settings.get_base_api_url()
193
+ base_ws_url = settings.get_base_ws_url()
194
+
195
+ try:
196
+ result = loop.run_until_complete(
197
+ enable_cloud_repository(
198
+ api_key, base_api_url, base_ws_url, org_name, repo_name
199
+ )
200
+ )
201
+
202
+ if result["__typename"] == "ContainerImage":
203
+ click.secho(
204
+ f"✓ Successfully enabled repository {org_name}/{repo_name}", fg="green"
205
+ )
206
+ click.echo(f" Build ref: {result.get('buildRef', 'N/A')}")
207
+ click.echo(f" Created at: {result.get('createdAt', 'N/A')}")
208
+ click.echo(f" Updated at: {result.get('updatedAt', 'N/A')}")
209
+ else:
210
+ click.secho(
211
+ f"✗ Failed to enable repository: {result.get('message', 'Unknown error')}",
212
+ fg="red",
213
+ )
214
+ click.echo(f" Error type: {result['__typename']}")
215
+
216
+ except Exception as e:
217
+ click.secho(f"✗ Error enabling repository: {e!s}", fg="red")
218
+ sys.exit(1)
219
+
220
+
221
+ @cloud_cli.command(hidden=True)
222
+ @click.option(
223
+ "--org-name",
224
+ help="GitHub organization name",
225
+ required=True,
226
+ )
227
+ @click.option(
228
+ "--repo-name",
229
+ help="GitHub repository name",
230
+ required=True,
231
+ )
232
+ @use_settings
233
+ def rebuild(
234
+ settings: Settings,
235
+ org_name: str,
236
+ repo_name: str,
237
+ ) -> None:
238
+ """Test utility for full rebuild of cloud repository."""
239
+ check_exponent_version_and_upgrade(settings)
240
+
241
+ if not settings.api_key:
242
+ redirect_to_login(settings)
243
+ return
244
+
245
+ loop = asyncio.get_event_loop()
246
+
247
+ api_key = settings.api_key
248
+ base_api_url = settings.get_base_api_url()
249
+ base_ws_url = settings.get_base_ws_url()
250
+
251
+ try:
252
+ result = loop.run_until_complete(
253
+ rebuild_cloud_repository(
254
+ api_key, base_api_url, base_ws_url, org_name, repo_name
255
+ )
256
+ )
257
+
258
+ if result["__typename"] == "ContainerImage":
259
+ click.secho(
260
+ f"✓ Successfully triggered rebuild for {org_name}/{repo_name}",
261
+ fg="green",
262
+ )
263
+ click.echo(f" Build ref: {result.get('buildRef', 'N/A')}")
264
+ click.echo(f" Created at: {result.get('createdAt', 'N/A')}")
265
+ click.echo(f" Updated at: {result.get('updatedAt', 'N/A')}")
266
+ else:
267
+ click.secho(
268
+ f"✗ Failed to trigger rebuild: {result.get('message', 'Unknown error')}",
269
+ fg="red",
270
+ )
271
+ click.echo(f" Error type: {result['__typename']}")
272
+
273
+ except Exception as e:
274
+ click.secho(f"✗ Error triggering rebuild: {e!s}", fg="red")
275
+ sys.exit(1)
276
+
277
+
278
+ @cloud_cli.command(hidden=True)
279
+ @use_settings
280
+ def list_repos(
281
+ settings: Settings,
282
+ ) -> None:
283
+ """Test utility for listing GitHub repositories."""
284
+ check_exponent_version_and_upgrade(settings)
285
+
286
+ if not settings.api_key:
287
+ redirect_to_login(settings)
288
+ return
289
+
290
+ loop = asyncio.get_event_loop()
291
+
292
+ api_key = settings.api_key
293
+ base_api_url = settings.get_base_api_url()
294
+ base_ws_url = settings.get_base_ws_url()
295
+
296
+ try:
297
+ result = loop.run_until_complete(
298
+ list_github_repositories(api_key, base_api_url, base_ws_url)
299
+ )
300
+
301
+ if result["__typename"] == "GithubRepositories":
302
+ repositories = result.get("repositories", [])
303
+ if repositories:
304
+ click.secho(f"✓ Found {len(repositories)} repositories:", fg="green")
305
+ for repo in repositories:
306
+ click.echo(
307
+ f"\n Repository: {repo['githubOrgName']}/{repo['githubRepoName']}"
308
+ )
309
+ click.echo(f" ID: {repo['id']}")
310
+ if repo.get("baseHost"):
311
+ click.echo(f" Base Host: {repo['baseHost']}")
312
+ if repo.get("containerImageId"):
313
+ click.echo(
314
+ f" Container Image ID: {repo['containerImageId']}"
315
+ )
316
+ click.echo(f" Created: {repo['createdAt']}")
317
+ click.echo(f" Updated: {repo['updatedAt']}")
318
+ else:
319
+ click.secho("No repositories found", fg="yellow")
320
+ else:
321
+ click.secho(
322
+ f"✗ Failed to list repositories: {result.get('message', 'Unknown error')}",
323
+ fg="red",
324
+ )
325
+ click.echo(f" Error type: {result['__typename']}")
326
+
327
+ except Exception as e:
328
+ click.secho(f"✗ Error listing repositories: {e!s}", fg="red")
329
+ sys.exit(1)
330
+
331
+
332
+ def filter_repositories(
333
+ repositories: list[dict[str, Any]], org_name: str | None, repo_name: str | None
334
+ ) -> list[dict[str, Any]]:
335
+ """Filter repositories by organization and/or repository name."""
336
+ if not (org_name or repo_name):
337
+ return repositories
338
+
339
+ filtered = []
340
+ for repo in repositories:
341
+ if org_name and repo["githubOrgName"] != org_name:
342
+ continue
343
+ if repo_name and repo["githubRepoName"] != repo_name:
344
+ continue
345
+ filtered.append(repo)
346
+
347
+ return filtered
348
+
349
+
350
+ def select_repository_interactive(repositories: list[dict[str, Any]]) -> dict[str, Any]:
351
+ """Interactively select a repository from a list."""
352
+ if len(repositories) == 1:
353
+ selected = repositories[0]
354
+ click.secho(
355
+ f"Using repository: {selected['githubOrgName']}/{selected['githubRepoName']}",
356
+ fg="cyan",
357
+ )
358
+ return selected
359
+
360
+ # Show numbered list for selection
361
+ click.secho("Available repositories:", fg="cyan")
362
+ for i, repo in enumerate(repositories, 1):
363
+ click.echo(f" {i}. {repo['githubOrgName']}/{repo['githubRepoName']}")
364
+
365
+ # Get user selection
366
+ while True:
367
+ try:
368
+ choice = click.prompt("Select a repository (number)", type=int)
369
+ if 1 <= choice <= len(repositories):
370
+ return cast(dict[str, Any], repositories[choice - 1])
371
+ else:
372
+ click.secho(
373
+ f"Please enter a number between 1 and {len(repositories)}",
374
+ fg="red",
375
+ )
376
+ except (ValueError, KeyboardInterrupt):
377
+ click.secho("\nCancelled", fg="yellow")
378
+ sys.exit(0)
379
+
380
+
381
+ def send_initial_prompt(
382
+ loop: asyncio.AbstractEventLoop,
383
+ api_key: str,
384
+ base_api_url: str,
385
+ base_ws_url: str,
386
+ chat_uuid: str,
387
+ prompt: str,
388
+ ) -> None:
389
+ """Send an initial prompt to the chat if provided."""
390
+ click.secho(
391
+ f"\nSending initial prompt: {prompt[:50]}{'...' if len(prompt) > 50 else ''}",
392
+ fg="cyan",
393
+ )
394
+
395
+ prompt_result = loop.run_until_complete(
396
+ start_chat_turn_with_prompt(
397
+ api_key, base_api_url, base_ws_url, chat_uuid, prompt
398
+ )
399
+ )
400
+
401
+ if prompt_result["__typename"] == "Chat":
402
+ click.secho("✓ Prompt sent successfully", fg="green")
403
+ else:
404
+ click.secho(
405
+ f"⚠ Failed to send prompt: {prompt_result.get('message', 'Unknown error')}",
406
+ fg="yellow",
407
+ )
408
+ click.echo(f" Error type: {prompt_result['__typename']}")
409
+
410
+
411
+ def fetch_repositories(
412
+ loop: asyncio.AbstractEventLoop,
413
+ api_key: str,
414
+ base_api_url: str,
415
+ base_ws_url: str,
416
+ ) -> list[dict[str, Any]]:
417
+ """Fetch the list of GitHub repositories."""
418
+ result = loop.run_until_complete(
419
+ list_github_repositories(api_key, base_api_url, base_ws_url)
420
+ )
421
+
422
+ if result["__typename"] != "GithubRepositories":
423
+ click.secho(
424
+ f"✗ Failed to list repositories: {result.get('message', 'Unknown error')}",
425
+ fg="red",
426
+ )
427
+ sys.exit(1)
428
+
429
+ repositories = result.get("repositories", [])
430
+ if not repositories:
431
+ click.secho("No repositories found", fg="yellow")
432
+ sys.exit(1)
433
+
434
+ return cast(list[dict[str, Any]], repositories)
435
+
436
+
437
+ @cloud_cli.command(hidden=True)
438
+ @click.option(
439
+ "--org-name",
440
+ help="GitHub organization name (optional, for filtering)",
441
+ required=False,
442
+ )
443
+ @click.option(
444
+ "--repo-name",
445
+ help="GitHub repository name (optional, for direct selection)",
446
+ required=False,
447
+ )
448
+ @click.option(
449
+ "--prompt",
450
+ help="Initial prompt to send to the chat after creation",
451
+ required=False,
452
+ )
453
+ @use_settings
454
+ def create_chat(
455
+ settings: Settings,
456
+ org_name: str | None,
457
+ repo_name: str | None,
458
+ prompt: str | None,
459
+ ) -> None:
460
+ """Create a cloud chat for a GitHub repository with optional initial prompt."""
461
+ check_exponent_version_and_upgrade(settings)
462
+
463
+ if not settings.api_key:
464
+ redirect_to_login(settings)
465
+ return
466
+
467
+ loop = asyncio.get_event_loop()
468
+ api_key = settings.api_key
469
+ base_url = settings.base_url
470
+ base_api_url = settings.get_base_api_url()
471
+ base_ws_url = settings.get_base_ws_url()
472
+
473
+ try:
474
+ # Fetch repositories
475
+ repositories = fetch_repositories(loop, api_key, base_api_url, base_ws_url)
476
+
477
+ # Filter if criteria provided
478
+ filtered_repos = filter_repositories(repositories, org_name, repo_name)
479
+
480
+ if not filtered_repos:
481
+ click.secho(
482
+ f"No repositories found matching {org_name}/{repo_name or '*'}",
483
+ fg="yellow",
484
+ )
485
+ sys.exit(1)
486
+
487
+ # Select repository
488
+ selected_repo = select_repository_interactive(filtered_repos)
489
+
490
+ # Create cloud chat
491
+ click.secho(
492
+ f"\nCreating cloud chat for {selected_repo['githubOrgName']}/{selected_repo['githubRepoName']}...",
493
+ fg="cyan",
494
+ )
495
+
496
+ chat_result = loop.run_until_complete(
497
+ create_cloud_chat_from_repository(
498
+ api_key, base_api_url, base_ws_url, selected_repo["id"]
499
+ )
500
+ )
501
+
502
+ if chat_result["__typename"] != "Chat":
503
+ click.secho(
504
+ f"✗ Failed to create cloud chat: {chat_result.get('message', 'Unknown error')}",
505
+ fg="red",
506
+ )
507
+ click.echo(f" Error type: {chat_result['__typename']}")
508
+ sys.exit(1)
509
+
510
+ # Success - handle chat creation
511
+ chat_uuid = chat_result["chatUuid"]
512
+ click.secho(f"✓ Successfully created cloud chat: {chat_uuid}", fg="green")
513
+ click.echo(f"\nChat URL: {base_url}/chats/{chat_uuid}")
514
+
515
+ # Send initial prompt if provided
516
+ if prompt:
517
+ send_initial_prompt(
518
+ loop, api_key, base_api_url, base_ws_url, chat_uuid, prompt
519
+ )
520
+
521
+ # Open browser
522
+ launch_exponent_browser(settings.environment, base_url, chat_uuid)
523
+
524
+ except Exception as e:
525
+ click.secho(f"✗ Error creating cloud chat: {e!s}", fg="red")
526
+ sys.exit(1)
527
+
528
+
529
+ @cloud_cli.command(hidden=True)
530
+ @click.option(
531
+ "--cloud-config-id",
532
+ help="ID of an existing cloud config to reconnect",
533
+ required=True,
534
+ )
535
+ @click.option(
536
+ "--prompt",
537
+ help="Prompt to kick off the cloud session.",
538
+ required=True,
539
+ )
540
+ @click.option(
541
+ "--background",
542
+ "-b",
543
+ help="Start the cloud session without launching the Exponent UI",
544
+ is_flag=True,
545
+ default=False,
546
+ )
547
+ @use_settings
548
+ def cloud(
549
+ settings: Settings,
550
+ cloud_config_id: str,
551
+ prompt: str,
552
+ background: bool,
553
+ ) -> None:
554
+ check_exponent_version_and_upgrade(settings)
555
+
556
+ if not settings.api_key:
557
+ redirect_to_login(settings)
558
+ return
559
+
560
+ loop = asyncio.get_event_loop()
561
+
562
+ check_running_from_home_directory()
563
+ loop.run_until_complete(check_inside_git_repo(settings))
564
+ check_ssl()
565
+
566
+ api_key = settings.api_key
567
+ base_url = settings.base_url
568
+ base_api_url = settings.get_base_api_url()
569
+ base_ws_url = settings.get_base_ws_url()
570
+
571
+ chat_uuid = loop.run_until_complete(
572
+ create_cloud_chat(api_key, base_api_url, base_ws_url, cloud_config_id)
573
+ )
574
+
575
+ if chat_uuid is None:
576
+ sys.exit(1)
577
+
578
+ loop.run_until_complete(
579
+ start_chat_turn(api_key, base_api_url, base_ws_url, chat_uuid, prompt)
580
+ )
581
+
582
+ print_exponent_message(base_url, chat_uuid)
583
+
584
+ if not background:
585
+ launch_exponent_browser(settings.environment, base_url, chat_uuid)