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.
- {simplex-3.0.2/simplex.egg-info → simplex-3.0.4}/PKG-INFO +1 -1
- {simplex-3.0.2 → simplex-3.0.4}/pyproject.toml +1 -1
- {simplex-3.0.2 → simplex-3.0.4}/simplex/__init__.py +1 -1
- {simplex-3.0.2 → simplex-3.0.4}/simplex/_http_client.py +1 -1
- simplex-3.0.4/simplex/cli/sessions.py +250 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex/client.py +81 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex/types.py +45 -0
- {simplex-3.0.2 → simplex-3.0.4/simplex.egg-info}/PKG-INFO +1 -1
- simplex-3.0.2/simplex/cli/sessions.py +0 -119
- {simplex-3.0.2 → simplex-3.0.4}/LICENSE +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/MANIFEST.in +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/README.md +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/requirements.txt +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/setup.cfg +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/__init__.py +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/auth.py +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/config.py +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/connect.py +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/editor.py +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/main.py +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/output.py +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/run.py +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/send.py +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/variables.py +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex/cli/workflows.py +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex/errors.py +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex/webhook.py +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex.egg-info/SOURCES.txt +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex.egg-info/dependency_links.txt +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex.egg-info/entry_points.txt +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex.egg-info/requires.txt +0 -0
- {simplex-3.0.2 → simplex-3.0.4}/simplex.egg-info/top_level.txt +0 -0
|
@@ -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,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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|