simplex 3.0.2__tar.gz → 3.0.4__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.
Files changed (32) hide show
  1. {simplex-3.0.2/simplex.egg-info → simplex-3.0.4}/PKG-INFO +1 -1
  2. {simplex-3.0.2 → simplex-3.0.4}/pyproject.toml +1 -1
  3. {simplex-3.0.2 → simplex-3.0.4}/simplex/__init__.py +1 -1
  4. {simplex-3.0.2 → simplex-3.0.4}/simplex/_http_client.py +1 -1
  5. simplex-3.0.4/simplex/cli/sessions.py +250 -0
  6. {simplex-3.0.2 → simplex-3.0.4}/simplex/client.py +81 -0
  7. {simplex-3.0.2 → simplex-3.0.4}/simplex/types.py +45 -0
  8. {simplex-3.0.2 → simplex-3.0.4/simplex.egg-info}/PKG-INFO +1 -1
  9. simplex-3.0.2/simplex/cli/sessions.py +0 -119
  10. {simplex-3.0.2 → simplex-3.0.4}/LICENSE +0 -0
  11. {simplex-3.0.2 → simplex-3.0.4}/MANIFEST.in +0 -0
  12. {simplex-3.0.2 → simplex-3.0.4}/README.md +0 -0
  13. {simplex-3.0.2 → simplex-3.0.4}/requirements.txt +0 -0
  14. {simplex-3.0.2 → simplex-3.0.4}/setup.cfg +0 -0
  15. {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/__init__.py +0 -0
  16. {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/auth.py +0 -0
  17. {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/config.py +0 -0
  18. {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/connect.py +0 -0
  19. {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/editor.py +0 -0
  20. {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/main.py +0 -0
  21. {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/output.py +0 -0
  22. {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/run.py +0 -0
  23. {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/send.py +0 -0
  24. {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/variables.py +0 -0
  25. {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/workflows.py +0 -0
  26. {simplex-3.0.2 → simplex-3.0.4}/simplex/errors.py +0 -0
  27. {simplex-3.0.2 → simplex-3.0.4}/simplex/webhook.py +0 -0
  28. {simplex-3.0.2 → simplex-3.0.4}/simplex.egg-info/SOURCES.txt +0 -0
  29. {simplex-3.0.2 → simplex-3.0.4}/simplex.egg-info/dependency_links.txt +0 -0
  30. {simplex-3.0.2 → simplex-3.0.4}/simplex.egg-info/entry_points.txt +0 -0
  31. {simplex-3.0.2 → simplex-3.0.4}/simplex.egg-info/requires.txt +0 -0
  32. {simplex-3.0.2 → simplex-3.0.4}/simplex.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simplex
3
- Version: 3.0.2
3
+ Version: 3.0.4
4
4
  Summary: Official Python SDK for the Simplex API
5
5
  Author-email: Simplex <support@simplex.sh>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simplex"
7
- version = "3.0.2"
7
+ version = "3.0.4"
8
8
  description = "Official Python SDK for the Simplex API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -43,7 +43,7 @@ from simplex.types import (
43
43
  )
44
44
  from simplex.webhook import WebhookVerificationError, verify_simplex_webhook
45
45
 
46
- __version__ = "3.0.2"
46
+ __version__ = "3.0.4"
47
47
  __all__ = [
48
48
  "SimplexClient",
49
49
  "SimplexError",
@@ -21,7 +21,7 @@ from simplex.errors import (
21
21
  ValidationError,
22
22
  )
23
23
 
24
- __version__ = "3.0.2"
24
+ __version__ = "3.0.4"
25
25
 
26
26
 
27
27
  class HttpClient:
@@ -0,0 +1,250 @@
1
+ """Session commands: status, events, logs, download, replay."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import time
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ import typer
11
+
12
+ from simplex.cli.config import make_client_kwargs
13
+ from simplex.cli.output import console, print_error, print_json, print_kv, print_success
14
+
15
+ app = typer.Typer(help="Inspect sessions.")
16
+
17
+
18
+ @app.command("status")
19
+ def status(
20
+ session_id: str = typer.Argument(help="Session ID"),
21
+ watch: bool = typer.Option(False, "--watch", "-w", help="Poll until session completes"),
22
+ ) -> None:
23
+ """Get the status of a session."""
24
+ from simplex import SimplexClient, SimplexError
25
+
26
+ try:
27
+ client = SimplexClient(**make_client_kwargs())
28
+ result = client.get_session_status(session_id)
29
+ except SimplexError as e:
30
+ print_error(str(e))
31
+ raise typer.Exit(1)
32
+
33
+ _print_status(result)
34
+
35
+ if not watch:
36
+ return
37
+
38
+ if not result.get("in_progress", False):
39
+ return
40
+
41
+ # Poll until complete
42
+ from rich.live import Live
43
+ from rich.spinner import Spinner
44
+
45
+ spinner = Spinner("dots", text="Waiting for session to complete...")
46
+ try:
47
+ with Live(spinner, console=console, refresh_per_second=4):
48
+ while True:
49
+ time.sleep(2)
50
+ try:
51
+ result = client.get_session_status(session_id)
52
+ except SimplexError as e:
53
+ print_error(str(e))
54
+ raise typer.Exit(1)
55
+ if not result.get("in_progress", True):
56
+ break
57
+ except KeyboardInterrupt:
58
+ console.print("\n[yellow]Watch interrupted.[/yellow]")
59
+ raise typer.Exit(0)
60
+
61
+ console.print()
62
+ _print_status(result)
63
+
64
+ if not result.get("success", False):
65
+ raise typer.Exit(1)
66
+
67
+
68
+ def _print_status(result: dict) -> None:
69
+ """Print session status fields."""
70
+ pairs = [
71
+ ("In Progress", result.get("in_progress", "")),
72
+ ("Success", result.get("success", "")),
73
+ ("Paused", result.get("paused", False)),
74
+ ]
75
+ if result.get("metadata"):
76
+ pairs.append(("Metadata", str(result["metadata"])))
77
+ if result.get("workflow_metadata"):
78
+ pairs.append(("Workflow Metadata", str(result["workflow_metadata"])))
79
+ if result.get("final_message"):
80
+ pairs.append(("Final Message", str(result["final_message"])))
81
+ print_kv(pairs)
82
+
83
+ outputs = result.get("scraper_outputs")
84
+ if outputs:
85
+ console.print("\n[bold]Outputs:[/bold]")
86
+ print_json(outputs)
87
+
88
+ structured = result.get("structured_output")
89
+ if structured:
90
+ console.print("\n[bold]Structured Output:[/bold]")
91
+ print_json(structured)
92
+
93
+ files = result.get("file_metadata")
94
+ if files:
95
+ console.print("\n[bold]Files:[/bold]")
96
+ for f in files:
97
+ size = f.get("file_size", 0)
98
+ if size >= 1_048_576:
99
+ size_str = f"{size / 1_048_576:.1f} MB"
100
+ elif size >= 1024:
101
+ size_str = f"{size / 1024:.1f} KB"
102
+ else:
103
+ size_str = f"{size} B"
104
+ print_kv([
105
+ ("Filename", f.get("filename", "")),
106
+ ("Size", size_str),
107
+ ("Downloaded", f.get("download_timestamp", "")),
108
+ ("URL", f.get("download_url", "")),
109
+ ])
110
+ console.print()
111
+
112
+
113
+ @app.command("events")
114
+ def events(
115
+ workflow_id: str = typer.Argument(help="Workflow ID"),
116
+ since: int = typer.Option(0, "--since", "-s", help="Event index to start from"),
117
+ limit: int = typer.Option(100, "--limit", "-l", help="Max events to return"),
118
+ json_output: bool = typer.Option(False, "--json", help="Output raw JSON"),
119
+ ) -> None:
120
+ """Poll events for a workflow's active session."""
121
+ from simplex import SimplexClient, SimplexError
122
+
123
+ try:
124
+ client = SimplexClient(**make_client_kwargs())
125
+ except (SimplexError, ValueError) as e:
126
+ print_error(str(e))
127
+ raise typer.Exit(1)
128
+
129
+ # Look up active session to get logs_url
130
+ try:
131
+ active = client.get_workflow_active_session(workflow_id)
132
+ logs_url = active.get("logs_url", "")
133
+ except SimplexError as e:
134
+ print_error(f"Could not find active session: {e}")
135
+ raise typer.Exit(1)
136
+
137
+ if not logs_url:
138
+ print_error(f"No active session found for workflow {workflow_id}")
139
+ raise typer.Exit(1)
140
+
141
+ try:
142
+ result = client.poll_events(logs_url, since=since, limit=limit)
143
+ except SimplexError as e:
144
+ print_error(str(e))
145
+ raise typer.Exit(1)
146
+
147
+ if json_output:
148
+ print(json.dumps(result, indent=2, default=str), flush=True)
149
+ return
150
+
151
+ events_list = result.get("events", [])
152
+ next_idx = result.get("next_index", 0)
153
+ total = result.get("total", 0)
154
+ has_more = result.get("has_more", False)
155
+
156
+ for event in events_list:
157
+ etype = event.get("event", "")
158
+ if etype == "RunContent":
159
+ content = event.get("content", "")
160
+ if content and content != "SIMPLEX_AGENT_INITIALIZED":
161
+ console.print(content, end="", highlight=False)
162
+ elif etype == "ToolCallStarted":
163
+ tool = event.get("tool", {})
164
+ tool_name = tool.get("tool_name", "unknown") if isinstance(tool, dict) else "unknown"
165
+ console.print(f" [cyan]>[/cyan] [bold]{tool_name}[/bold]")
166
+ elif etype == "ToolCallCompleted":
167
+ tool = event.get("tool", {})
168
+ if isinstance(tool, dict) and tool.get("tool_call_error"):
169
+ console.print(f" [red]error: {str(tool.get('content', ''))[:200]}[/red]")
170
+ elif etype == "RunCompleted":
171
+ console.print(f"\n[bold green]Completed[/bold green]")
172
+ elif etype == "RunError":
173
+ console.print(f"\n[bold red]Error:[/bold red] {event.get('content', '')}")
174
+ elif etype == "RunStarted":
175
+ console.print("[dim]Agent started[/dim]\n")
176
+
177
+ console.print()
178
+ print_kv([
179
+ ("Next Index", next_idx),
180
+ ("Total", total),
181
+ ("Has More", has_more),
182
+ ])
183
+
184
+
185
+ @app.command("logs")
186
+ def logs(
187
+ session_id: str = typer.Argument(help="Session ID"),
188
+ ) -> None:
189
+ """Retrieve session logs."""
190
+ from simplex import SimplexClient, SimplexError
191
+
192
+ try:
193
+ client = SimplexClient(**make_client_kwargs())
194
+ result = client.retrieve_session_logs(session_id)
195
+ except SimplexError as e:
196
+ print_error(str(e))
197
+ raise typer.Exit(1)
198
+
199
+ if result is None:
200
+ print_error("Session is still running — logs not yet available.")
201
+ raise typer.Exit(1)
202
+
203
+ print_json(result)
204
+
205
+
206
+ @app.command("download")
207
+ def download(
208
+ session_id: str = typer.Argument(help="Session ID"),
209
+ filename: Optional[str] = typer.Option(None, "--filename", "-f", help="Specific file to download"),
210
+ output: Optional[str] = typer.Option(None, "--output", "-o", help="Output path"),
211
+ ) -> None:
212
+ """Download files from a session."""
213
+ from simplex import SimplexClient, SimplexError
214
+
215
+ try:
216
+ client = SimplexClient(**make_client_kwargs())
217
+ data = client.download_session_files(session_id, filename=filename)
218
+ except SimplexError as e:
219
+ print_error(str(e))
220
+ raise typer.Exit(1)
221
+
222
+ if output:
223
+ out_path = Path(output)
224
+ elif filename:
225
+ out_path = Path(filename)
226
+ else:
227
+ out_path = Path(f"{session_id}_files.zip")
228
+
229
+ out_path.write_bytes(data)
230
+ print_success(f"Downloaded to {out_path}")
231
+
232
+
233
+ @app.command("replay")
234
+ def replay(
235
+ session_id: str = typer.Argument(help="Session ID"),
236
+ output: Optional[str] = typer.Option(None, "--output", "-o", help="Output path"),
237
+ ) -> None:
238
+ """Download session replay video."""
239
+ from simplex import SimplexClient, SimplexError
240
+
241
+ try:
242
+ client = SimplexClient(**make_client_kwargs())
243
+ data = client.retrieve_session_replay(session_id)
244
+ except SimplexError as e:
245
+ print_error(str(e))
246
+ raise typer.Exit(1)
247
+
248
+ out_path = Path(output) if output else Path(f"{session_id}_replay.mp4")
249
+ out_path.write_bytes(data)
250
+ print_success(f"Replay saved to {out_path}")
@@ -13,12 +13,15 @@ from typing import Any
13
13
  from simplex._http_client import HttpClient
14
14
  from simplex.errors import WorkflowError
15
15
  from simplex.types import (
16
+ DeleteCredentialResponse,
17
+ ListCredentialsResponse,
16
18
  PauseSessionResponse,
17
19
  ResumeSessionResponse,
18
20
  RunWorkflowResponse,
19
21
  SearchWorkflowsResponse,
20
22
  SessionStatusResponse,
21
23
  StartEditorSessionResponse,
24
+ StoreCredentialResponse,
22
25
  UpdateWorkflowMetadataResponse,
23
26
  )
24
27
 
@@ -666,6 +669,84 @@ class SimplexClient:
666
669
  """
667
670
  return self._http_client.get(f"/workflow/{workflow_id}/active_session")
668
671
 
672
+ def store_credential(self, name: str, value: str) -> StoreCredentialResponse:
673
+ """
674
+ Store an encrypted credential for your organization.
675
+
676
+ The value is encrypted server-side and can later be used by the agent
677
+ via ``type_secret(credential_name="...")``.
678
+
679
+ Args:
680
+ name: A unique name for the credential
681
+ value: The plaintext value to encrypt and store
682
+
683
+ Returns:
684
+ StoreCredentialResponse with credential_id on success
685
+
686
+ Example:
687
+ >>> client.store_credential("github_token", "ghp_xxxx...")
688
+ {'succeeded': True, 'name': 'github_token', 'credential_id': '...'}
689
+ """
690
+ try:
691
+ response: StoreCredentialResponse = self._http_client.post_json(
692
+ "/store_credential",
693
+ data={"name": name, "value": value},
694
+ )
695
+ return response
696
+ except Exception as e:
697
+ if isinstance(e, WorkflowError):
698
+ raise
699
+ raise WorkflowError(f"Failed to store credential: {e}")
700
+
701
+ def list_credentials(self) -> ListCredentialsResponse:
702
+ """
703
+ List all encrypted credentials for your organization.
704
+
705
+ Returns metadata only (name, dates) -- not the encrypted values.
706
+
707
+ Returns:
708
+ ListCredentialsResponse with array of credential metadata
709
+
710
+ Example:
711
+ >>> result = client.list_credentials()
712
+ >>> for cred in result["credentials"]:
713
+ ... print(f"{cred['name']} (created {cred['created_at']})")
714
+ """
715
+ try:
716
+ response: ListCredentialsResponse = self._http_client.get(
717
+ "/list_credentials",
718
+ )
719
+ return response
720
+ except Exception as e:
721
+ if isinstance(e, WorkflowError):
722
+ raise
723
+ raise WorkflowError(f"Failed to list credentials: {e}")
724
+
725
+ def delete_credential(self, name: str) -> DeleteCredentialResponse:
726
+ """
727
+ Delete an encrypted credential by name.
728
+
729
+ Args:
730
+ name: The name of the credential to delete
731
+
732
+ Returns:
733
+ DeleteCredentialResponse
734
+
735
+ Example:
736
+ >>> client.delete_credential("github_token")
737
+ {'succeeded': True}
738
+ """
739
+ try:
740
+ response: DeleteCredentialResponse = self._http_client.post_json(
741
+ "/delete_credential",
742
+ data={"name": name},
743
+ )
744
+ return response
745
+ except Exception as e:
746
+ if isinstance(e, WorkflowError):
747
+ raise
748
+ raise WorkflowError(f"Failed to delete credential: {e}")
749
+
669
750
  def close_session(self, session_id: str) -> Any:
670
751
  """
671
752
  Close a workflow session.
@@ -182,6 +182,51 @@ class StartEditorSessionResponse(TypedDict, total=False):
182
182
  filesystem_url: str | None
183
183
 
184
184
 
185
+ class StoreCredentialResponse(TypedDict, total=False):
186
+ """
187
+ Response from storing a credential.
188
+
189
+ Attributes:
190
+ succeeded: Whether the credential was stored successfully
191
+ name: The credential name
192
+ credential_id: The ID of the stored credential
193
+ error: Error message if the operation failed
194
+ """
195
+
196
+ succeeded: bool
197
+ name: str
198
+ credential_id: str
199
+ error: str
200
+
201
+
202
+ class ListCredentialsResponse(TypedDict, total=False):
203
+ """
204
+ Response from listing credentials.
205
+
206
+ Attributes:
207
+ succeeded: Whether the list operation succeeded
208
+ credentials: List of credential metadata (id, name, created_at, updated_at)
209
+ error: Error message if the operation failed
210
+ """
211
+
212
+ succeeded: bool
213
+ credentials: list[dict[str, Any]]
214
+ error: str
215
+
216
+
217
+ class DeleteCredentialResponse(TypedDict, total=False):
218
+ """
219
+ Response from deleting a credential.
220
+
221
+ Attributes:
222
+ succeeded: Whether the credential was deleted successfully
223
+ error: Error message if the operation failed
224
+ """
225
+
226
+ succeeded: bool
227
+ error: str
228
+
229
+
185
230
  class WebhookPayload(TypedDict, total=False):
186
231
  """
187
232
  Payload received from a Simplex webhook.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simplex
3
- Version: 3.0.2
3
+ Version: 3.0.4
4
4
  Summary: Official Python SDK for the Simplex API
5
5
  Author-email: Simplex <support@simplex.sh>
6
6
  License: MIT
@@ -1,119 +0,0 @@
1
- """Session commands: status, logs, download, replay."""
2
-
3
- from __future__ import annotations
4
-
5
- from pathlib import Path
6
- from typing import Optional
7
-
8
- import typer
9
-
10
- from simplex.cli.config import make_client_kwargs
11
- from simplex.cli.output import print_error, print_json, print_kv, print_success
12
-
13
- app = typer.Typer(help="Inspect sessions.")
14
-
15
-
16
- @app.command("status")
17
- def status(
18
- session_id: str = typer.Argument(help="Session ID"),
19
- ) -> None:
20
- """Get the status of a session."""
21
- from simplex import SimplexClient, SimplexError
22
-
23
- try:
24
- client = SimplexClient(**make_client_kwargs())
25
- result = client.get_session_status(session_id)
26
- except SimplexError as e:
27
- print_error(str(e))
28
- raise typer.Exit(1)
29
-
30
- pairs = [
31
- ("In Progress", result.get("in_progress", "")),
32
- ("Success", result.get("success", "")),
33
- ("Paused", result.get("paused", False)),
34
- ]
35
- if result.get("metadata"):
36
- pairs.append(("Metadata", str(result["metadata"])))
37
- print_kv(pairs)
38
-
39
- outputs = result.get("scraper_outputs")
40
- if outputs:
41
- from rich.console import Console
42
-
43
- Console().print("\n[bold]Outputs:[/bold]")
44
- print_json(outputs)
45
-
46
- structured = result.get("structured_output")
47
- if structured:
48
- from rich.console import Console
49
-
50
- Console().print("\n[bold]Structured Output:[/bold]")
51
- print_json(structured)
52
-
53
-
54
- @app.command("logs")
55
- def logs(
56
- session_id: str = typer.Argument(help="Session ID"),
57
- ) -> None:
58
- """Retrieve session logs."""
59
- from simplex import SimplexClient, SimplexError
60
-
61
- try:
62
- client = SimplexClient(**make_client_kwargs())
63
- result = client.retrieve_session_logs(session_id)
64
- except SimplexError as e:
65
- print_error(str(e))
66
- raise typer.Exit(1)
67
-
68
- if result is None:
69
- print_error("Session is still running — logs not yet available.")
70
- raise typer.Exit(1)
71
-
72
- print_json(result)
73
-
74
-
75
- @app.command("download")
76
- def download(
77
- session_id: str = typer.Argument(help="Session ID"),
78
- filename: Optional[str] = typer.Option(None, "--filename", "-f", help="Specific file to download"),
79
- output: Optional[str] = typer.Option(None, "--output", "-o", help="Output path"),
80
- ) -> None:
81
- """Download files from a session."""
82
- from simplex import SimplexClient, SimplexError
83
-
84
- try:
85
- client = SimplexClient(**make_client_kwargs())
86
- data = client.download_session_files(session_id, filename=filename)
87
- except SimplexError as e:
88
- print_error(str(e))
89
- raise typer.Exit(1)
90
-
91
- if output:
92
- out_path = Path(output)
93
- elif filename:
94
- out_path = Path(filename)
95
- else:
96
- out_path = Path(f"{session_id}_files.zip")
97
-
98
- out_path.write_bytes(data)
99
- print_success(f"Downloaded to {out_path}")
100
-
101
-
102
- @app.command("replay")
103
- def replay(
104
- session_id: str = typer.Argument(help="Session ID"),
105
- output: Optional[str] = typer.Option(None, "--output", "-o", help="Output path"),
106
- ) -> None:
107
- """Download session replay video."""
108
- from simplex import SimplexClient, SimplexError
109
-
110
- try:
111
- client = SimplexClient(**make_client_kwargs())
112
- data = client.retrieve_session_replay(session_id)
113
- except SimplexError as e:
114
- print_error(str(e))
115
- raise typer.Exit(1)
116
-
117
- out_path = Path(output) if output else Path(f"{session_id}_replay.mp4")
118
- out_path.write_bytes(data)
119
- print_success(f"Replay saved to {out_path}")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes