PyVaultRCE 2.2.0__tar.gz → 2.4.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyVaultRCE
3
- Version: 2.2.0
3
+ Version: 2.4.0
4
4
  Summary: Remote Code Execution & Hosting client for PyVault — source never exposed
5
5
  Author: PyVault
6
6
  Keywords: remote execution code hosting vault rce session
@@ -25,7 +25,7 @@ Dynamic: summary
25
25
 
26
26
  # PyVaultRCE
27
27
 
28
- Remote Code Execution & Hosting client for **PyVault**.
28
+ Remote Code Hosting client for **PyVault**.
29
29
 
30
30
  Upload Python scripts to a PyVault server and execute them remotely — the source code **never leaves the server**. Clients only ever hold a 21-character hex Session ID.
31
31
 
@@ -40,7 +40,7 @@ pip install PyVaultRCE
40
40
  ```python
41
41
  from pyvaultrce import CodeManager
42
42
 
43
- # Upload a local .py file → get Session ID
43
+ # Upload a local .py file → get a 21-character Session ID
44
44
  sid = CodeManager.enc("my_script.py")
45
45
  # [PyVault] ✓ Uploaded — Session ID : 3a7f1c9b2d0e8a5f41c7e
46
46
 
@@ -50,6 +50,16 @@ sid = CodeManager.enc_url("https://pastebin.com/abcXYZ123")
50
50
  # Execute remotely (source stays on server)
51
51
  CodeManager.run(sid)
52
52
 
53
+ # Upload with a short share link and library metadata
54
+ sid = CodeManager.enc(
55
+ "my_script.py",
56
+ label="analytics demo",
57
+ libraries=["requests", "pandas"],
58
+ )
59
+
60
+ # Hide PyVault status messages while running
61
+ CodeManager.run(sid, show_terminal=False)
62
+
53
63
  # Check metadata without incrementing execution counter
54
64
  CodeManager.info(sid)
55
65
 
@@ -65,16 +75,29 @@ Set `PYVAULT_URL` to point at your hosted PyVault server:
65
75
  export PYVAULT_URL=https://your-server.replit.app
66
76
  ```
67
77
 
78
+ For local development the client uses `http://localhost:5000` when
79
+ `PYVAULT_URL` is not set. You can also control status output globally:
80
+
81
+ ```bash
82
+ export PYVAULT_TERMINAL=off
83
+ ```
84
+
85
+ Library names are metadata only. PyVault does not auto-install packages or
86
+ execute dependency installers; install the libraries in the Python environment
87
+ where `CodeManager.run()` executes.
88
+
68
89
  ## API Reference
69
90
 
70
91
  | Method | Description |
71
92
  |--------|-------------|
72
93
  | `CodeManager.enc(file_path)` | Upload a local `.py` file → returns Session ID |
94
+ | `CodeManager.enc(file_path, label=..., libraries=[...])` | Upload with a label and visible library metadata |
73
95
  | `CodeManager.enc_url(url)` | Download code from a paste URL and upload → returns Session ID |
74
- | `CodeManager.run(session_id)` | Fetch and execute code by Session ID |
96
+ | `CodeManager.run(session_id, show_terminal=False)` | Fetch and execute code by Session ID with optional quiet status output |
75
97
  | `CodeManager.info(session_id)` | Get session metadata (no exec count increment) |
76
98
  | `CodeManager.ping()` | Check server connectivity |
77
- | `CodeManager.edit(session_id, file_path, admin_token)` | Replace stored code (admin only) |
99
+ | `CodeManager.edit(session_id, file_path, admin_token=...)` | Replace stored code (admin or owner token) |
100
+ | `CodeManager.delete(session_id, owner_token)` | Delete your own session |
78
101
 
79
102
  ## Supported Paste Services (`enc_url`)
80
103
 
@@ -100,8 +123,10 @@ export PYVAULT_URL=https://your-server.replit.app
100
123
 
101
124
  - Session IDs are 21-character cryptographically random hex strings
102
125
  - Source code is never written to disk on the client
103
- - Edit operations require a server-side admin token
104
- - Regular users cannot modify stored code
126
+ - Code is encrypted at rest with Fernet on the server
127
+ - Share links expose only session metadata and declared library names, never source
128
+ - Admin edits require the admin access code; owner edits/deletes require the one-time owner token
129
+ - Sessions never expire unless an expiry is explicitly selected
105
130
 
106
131
  ## License
107
132
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyVaultRCE
3
- Version: 2.2.0
3
+ Version: 2.4.0
4
4
  Summary: Remote Code Execution & Hosting client for PyVault — source never exposed
5
5
  Author: PyVault
6
6
  Keywords: remote execution code hosting vault rce session
@@ -25,7 +25,7 @@ Dynamic: summary
25
25
 
26
26
  # PyVaultRCE
27
27
 
28
- Remote Code Execution & Hosting client for **PyVault**.
28
+ Remote Code Hosting client for **PyVault**.
29
29
 
30
30
  Upload Python scripts to a PyVault server and execute them remotely — the source code **never leaves the server**. Clients only ever hold a 21-character hex Session ID.
31
31
 
@@ -40,7 +40,7 @@ pip install PyVaultRCE
40
40
  ```python
41
41
  from pyvaultrce import CodeManager
42
42
 
43
- # Upload a local .py file → get Session ID
43
+ # Upload a local .py file → get a 21-character Session ID
44
44
  sid = CodeManager.enc("my_script.py")
45
45
  # [PyVault] ✓ Uploaded — Session ID : 3a7f1c9b2d0e8a5f41c7e
46
46
 
@@ -50,6 +50,16 @@ sid = CodeManager.enc_url("https://pastebin.com/abcXYZ123")
50
50
  # Execute remotely (source stays on server)
51
51
  CodeManager.run(sid)
52
52
 
53
+ # Upload with a short share link and library metadata
54
+ sid = CodeManager.enc(
55
+ "my_script.py",
56
+ label="analytics demo",
57
+ libraries=["requests", "pandas"],
58
+ )
59
+
60
+ # Hide PyVault status messages while running
61
+ CodeManager.run(sid, show_terminal=False)
62
+
53
63
  # Check metadata without incrementing execution counter
54
64
  CodeManager.info(sid)
55
65
 
@@ -65,16 +75,29 @@ Set `PYVAULT_URL` to point at your hosted PyVault server:
65
75
  export PYVAULT_URL=https://your-server.replit.app
66
76
  ```
67
77
 
78
+ For local development the client uses `http://localhost:5000` when
79
+ `PYVAULT_URL` is not set. You can also control status output globally:
80
+
81
+ ```bash
82
+ export PYVAULT_TERMINAL=off
83
+ ```
84
+
85
+ Library names are metadata only. PyVault does not auto-install packages or
86
+ execute dependency installers; install the libraries in the Python environment
87
+ where `CodeManager.run()` executes.
88
+
68
89
  ## API Reference
69
90
 
70
91
  | Method | Description |
71
92
  |--------|-------------|
72
93
  | `CodeManager.enc(file_path)` | Upload a local `.py` file → returns Session ID |
94
+ | `CodeManager.enc(file_path, label=..., libraries=[...])` | Upload with a label and visible library metadata |
73
95
  | `CodeManager.enc_url(url)` | Download code from a paste URL and upload → returns Session ID |
74
- | `CodeManager.run(session_id)` | Fetch and execute code by Session ID |
96
+ | `CodeManager.run(session_id, show_terminal=False)` | Fetch and execute code by Session ID with optional quiet status output |
75
97
  | `CodeManager.info(session_id)` | Get session metadata (no exec count increment) |
76
98
  | `CodeManager.ping()` | Check server connectivity |
77
- | `CodeManager.edit(session_id, file_path, admin_token)` | Replace stored code (admin only) |
99
+ | `CodeManager.edit(session_id, file_path, admin_token=...)` | Replace stored code (admin or owner token) |
100
+ | `CodeManager.delete(session_id, owner_token)` | Delete your own session |
78
101
 
79
102
  ## Supported Paste Services (`enc_url`)
80
103
 
@@ -100,8 +123,10 @@ export PYVAULT_URL=https://your-server.replit.app
100
123
 
101
124
  - Session IDs are 21-character cryptographically random hex strings
102
125
  - Source code is never written to disk on the client
103
- - Edit operations require a server-side admin token
104
- - Regular users cannot modify stored code
126
+ - Code is encrypted at rest with Fernet on the server
127
+ - Share links expose only session metadata and declared library names, never source
128
+ - Admin edits require the admin access code; owner edits/deletes require the one-time owner token
129
+ - Sessions never expire unless an expiry is explicitly selected
105
130
 
106
131
  ## License
107
132
 
@@ -1,6 +1,6 @@
1
1
  # PyVaultRCE
2
2
 
3
- Remote Code Execution & Hosting client for **PyVault**.
3
+ Remote Code Hosting client for **PyVault**.
4
4
 
5
5
  Upload Python scripts to a PyVault server and execute them remotely — the source code **never leaves the server**. Clients only ever hold a 21-character hex Session ID.
6
6
 
@@ -15,7 +15,7 @@ pip install PyVaultRCE
15
15
  ```python
16
16
  from pyvaultrce import CodeManager
17
17
 
18
- # Upload a local .py file → get Session ID
18
+ # Upload a local .py file → get a 21-character Session ID
19
19
  sid = CodeManager.enc("my_script.py")
20
20
  # [PyVault] ✓ Uploaded — Session ID : 3a7f1c9b2d0e8a5f41c7e
21
21
 
@@ -25,6 +25,16 @@ sid = CodeManager.enc_url("https://pastebin.com/abcXYZ123")
25
25
  # Execute remotely (source stays on server)
26
26
  CodeManager.run(sid)
27
27
 
28
+ # Upload with a short share link and library metadata
29
+ sid = CodeManager.enc(
30
+ "my_script.py",
31
+ label="analytics demo",
32
+ libraries=["requests", "pandas"],
33
+ )
34
+
35
+ # Hide PyVault status messages while running
36
+ CodeManager.run(sid, show_terminal=False)
37
+
28
38
  # Check metadata without incrementing execution counter
29
39
  CodeManager.info(sid)
30
40
 
@@ -40,16 +50,29 @@ Set `PYVAULT_URL` to point at your hosted PyVault server:
40
50
  export PYVAULT_URL=https://your-server.replit.app
41
51
  ```
42
52
 
53
+ For local development the client uses `http://localhost:5000` when
54
+ `PYVAULT_URL` is not set. You can also control status output globally:
55
+
56
+ ```bash
57
+ export PYVAULT_TERMINAL=off
58
+ ```
59
+
60
+ Library names are metadata only. PyVault does not auto-install packages or
61
+ execute dependency installers; install the libraries in the Python environment
62
+ where `CodeManager.run()` executes.
63
+
43
64
  ## API Reference
44
65
 
45
66
  | Method | Description |
46
67
  |--------|-------------|
47
68
  | `CodeManager.enc(file_path)` | Upload a local `.py` file → returns Session ID |
69
+ | `CodeManager.enc(file_path, label=..., libraries=[...])` | Upload with a label and visible library metadata |
48
70
  | `CodeManager.enc_url(url)` | Download code from a paste URL and upload → returns Session ID |
49
- | `CodeManager.run(session_id)` | Fetch and execute code by Session ID |
71
+ | `CodeManager.run(session_id, show_terminal=False)` | Fetch and execute code by Session ID with optional quiet status output |
50
72
  | `CodeManager.info(session_id)` | Get session metadata (no exec count increment) |
51
73
  | `CodeManager.ping()` | Check server connectivity |
52
- | `CodeManager.edit(session_id, file_path, admin_token)` | Replace stored code (admin only) |
74
+ | `CodeManager.edit(session_id, file_path, admin_token=...)` | Replace stored code (admin or owner token) |
75
+ | `CodeManager.delete(session_id, owner_token)` | Delete your own session |
53
76
 
54
77
  ## Supported Paste Services (`enc_url`)
55
78
 
@@ -75,8 +98,10 @@ export PYVAULT_URL=https://your-server.replit.app
75
98
 
76
99
  - Session IDs are 21-character cryptographically random hex strings
77
100
  - Source code is never written to disk on the client
78
- - Edit operations require a server-side admin token
79
- - Regular users cannot modify stored code
101
+ - Code is encrypted at rest with Fernet on the server
102
+ - Share links expose only session metadata and declared library names, never source
103
+ - Admin edits require the admin access code; owner edits/deletes require the one-time owner token
104
+ - Sessions never expire unless an expiry is explicitly selected
80
105
 
81
106
  ## License
82
107
 
@@ -21,5 +21,5 @@ Usage:
21
21
  from .manager import CodeManager
22
22
 
23
23
  __all__ = ["CodeManager"]
24
- __version__ = "1.0.0"
24
+ __version__ = "2.4.0"
25
25
  __author__ = "PyVault"
@@ -15,7 +15,7 @@ except ImportError:
15
15
  "The 'requests' library is required. Install it with: pip install requests"
16
16
  )
17
17
 
18
- _DEFAULT_BASE_URL = os.environ.get("PYVAULT_URL", "http://localhost:5000")
18
+ _DEFAULT_BASE_URL = "http://localhost:5000"
19
19
 
20
20
  # ── Validation helpers ───────────────────────────────────────────────────────
21
21
 
@@ -36,10 +36,23 @@ def _validate_session_id(session_id: str) -> None:
36
36
 
37
37
 
38
38
  def _get_base_url(base_url: Optional[str]) -> str:
39
- url = (base_url or _DEFAULT_BASE_URL).rstrip("/")
39
+ url = (base_url or os.environ.get("PYVAULT_URL") or _DEFAULT_BASE_URL).rstrip("/")
40
40
  return url
41
41
 
42
42
 
43
+ def _terminal_enabled(show_terminal=None) -> bool:
44
+ if show_terminal is not None:
45
+ return bool(show_terminal)
46
+ return os.environ.get("PYVAULT_TERMINAL", "on").strip().lower() not in {
47
+ "0", "false", "off", "no", "quiet"
48
+ }
49
+
50
+
51
+ def _emit(message: str, show_terminal=None, *, error=False) -> None:
52
+ if _terminal_enabled(show_terminal):
53
+ print(message, file=sys.stderr if error else sys.stdout, flush=True)
54
+
55
+
43
56
  # ── CodeManager ─────────────────────────────────────────────────────────────
44
57
 
45
58
  class CodeManager:
@@ -59,13 +72,15 @@ class CodeManager:
59
72
  file_path: str,
60
73
  base_url: Optional[str] = None,
61
74
  timeout: int = 30,
75
+ label: str = "",
76
+ libraries = "",
62
77
  ) -> str:
63
78
  """
64
79
  Read a local Python file and upload it to the PyVault backend.
65
80
 
66
81
  Args:
67
82
  file_path: Path to the local .py file to upload.
68
- base_url: Override the server URL (default: PYVAULT_URL env or localhost:5000).
83
+ base_url: Override the server URL (default: PYVAULT_URL env or hosted PyVault URL).
69
84
  timeout: HTTP timeout in seconds.
70
85
 
71
86
  Returns:
@@ -98,7 +113,7 @@ class CodeManager:
98
113
  try:
99
114
  response = requests.post(
100
115
  url,
101
- json={"code": code},
116
+ json={"code": code, "label": label, "libraries": libraries},
102
117
  timeout=timeout,
103
118
  )
104
119
  except requests.exceptions.ConnectionError:
@@ -142,6 +157,7 @@ class CodeManager:
142
157
  base_url: Optional[str] = None,
143
158
  timeout: int = 30,
144
159
  globals_dict: Optional[dict] = None,
160
+ show_terminal=None,
145
161
  ) -> None:
146
162
  """
147
163
  Fetch the code for a Session ID from PyVault and execute it locally.
@@ -208,7 +224,7 @@ class CodeManager:
208
224
  f"Server returned empty code for session '{session_id}'."
209
225
  )
210
226
 
211
- print(f"[PyVault] Executing session '{session_id}' (run #{exec_count})…")
227
+ _emit(f"[PyVault] Executing session '{session_id}' (run #{exec_count})…", show_terminal)
212
228
 
213
229
  namespace = globals_dict if globals_dict is not None else {
214
230
  "__name__": "__pyvault__",
@@ -219,19 +235,19 @@ class CodeManager:
219
235
  compiled = compile(code, f"<pyvault:{session_id}>", "exec")
220
236
  exec(compiled, namespace)
221
237
  except SyntaxError as exc:
222
- print(f"\n[PyVault] ✕ Syntax error in remote code:", file=sys.stderr)
223
- print(f" Line {exc.lineno}: {exc.msg}", file=sys.stderr)
238
+ _emit("\n[PyVault] ✕ Syntax error in remote code:", show_terminal, error=True)
239
+ _emit(f" Line {exc.lineno}: {exc.msg}", show_terminal, error=True)
224
240
  if exc.text:
225
- print(f" >>> {exc.text.strip()}", file=sys.stderr)
241
+ _emit(f" >>> {exc.text.strip()}", show_terminal, error=True)
226
242
  raise
227
243
  except Exception as exc:
228
- print(f"\n[PyVault] ✕ Runtime error during execution:", file=sys.stderr)
244
+ _emit("\n[PyVault] ✕ Runtime error during execution:", show_terminal, error=True)
229
245
  tb_lines = traceback.format_exc().splitlines()
230
246
  for line in tb_lines:
231
- print(f" {line}", file=sys.stderr)
247
+ _emit(f" {line}", show_terminal, error=True)
232
248
  raise
233
249
 
234
- print(f"[PyVault] ✓ Execution complete.")
250
+ _emit("[PyVault] ✓ Execution complete.", show_terminal)
235
251
 
236
252
  @staticmethod
237
253
  def edit(
@@ -18,5 +18,5 @@ Quick start:
18
18
  from .manager import CodeManager
19
19
 
20
20
  __all__ = ["CodeManager"]
21
- __version__ = "2.2.0"
21
+ __version__ = "2.4.0"
22
22
  __author__ = "PyVault"
@@ -23,10 +23,7 @@ try:
23
23
  except ImportError:
24
24
  _HAS_CRYPTO = False
25
25
 
26
- _DEFAULT_BASE = os.environ.get(
27
- "PYVAULT_URL",
28
- "https://secure-code-runner--diwasreplit.replit.app"
29
- )
26
+ _DEFAULT_BASE = "http://localhost:5000"
30
27
  _MAX_LINES = 10_000
31
28
  _SID_LEN = 21
32
29
  _HEX_SET = frozenset("0123456789abcdef")
@@ -47,7 +44,34 @@ def _decrypt(data: str) -> str:
47
44
  # ── HTTP helpers ───────────────────────────────────────────────────────────────
48
45
 
49
46
  def _base(url: Optional[str]) -> str:
50
- return (url or _DEFAULT_BASE).rstrip("/")
47
+ return (url or os.environ.get("PYVAULT_URL") or _DEFAULT_BASE).rstrip("/")
48
+
49
+
50
+ def _terminal_enabled(show_terminal: Optional[bool]) -> bool:
51
+ if show_terminal is not None:
52
+ return bool(show_terminal)
53
+ value = os.environ.get("PYVAULT_TERMINAL", "on").strip().lower()
54
+ return value not in {"0", "false", "off", "no", "quiet"}
55
+
56
+
57
+ def _emit(message: str, show_terminal: Optional[bool] = None, *, error: bool = False) -> None:
58
+ if _terminal_enabled(show_terminal):
59
+ print(message, file=sys.stderr if error else sys.stdout, flush=True)
60
+
61
+
62
+ def _normalise_libraries(value) -> str:
63
+ if isinstance(value, (list, tuple)):
64
+ values = value
65
+ elif isinstance(value, str):
66
+ values = re.split(r"[,\n]", value)
67
+ else:
68
+ values = []
69
+ cleaned = []
70
+ for item in values:
71
+ name = str(item).strip()
72
+ if name and re.fullmatch(r"[A-Za-z0-9_.-]{1,80}", name) and name not in cleaned:
73
+ cleaned.append(name)
74
+ return ", ".join(cleaned)[:500]
51
75
 
52
76
 
53
77
  def _valid_sid(sid: str) -> bool:
@@ -153,8 +177,10 @@ def _upload_code(
153
177
  timeout: int,
154
178
  expires_in_hours: Optional[float] = None,
155
179
  max_executions: int = 0,
180
+ label: str = "",
181
+ libraries = "",
156
182
  ) -> tuple:
157
- """Internal: push code string to /pyv/save. Returns (session_id, owner_token)."""
183
+ """Internal: push code string to /pyv/save. Returns session metadata."""
158
184
  if not code.strip():
159
185
  raise ValueError("Code is empty — nothing to upload.")
160
186
  line_count = len(code.splitlines())
@@ -167,6 +193,10 @@ def _upload_code(
167
193
  payload["expires_in_hours"] = expires_in_hours
168
194
  if max_executions and max_executions > 0:
169
195
  payload["max_executions"] = max_executions
196
+ if label:
197
+ payload["label"] = str(label)[:200]
198
+ if libraries:
199
+ payload["libraries"] = _normalise_libraries(libraries)
170
200
 
171
201
  url = _base(base_url) + "/pyv/save"
172
202
  resp = _http_post(url, payload, timeout)
@@ -181,7 +211,7 @@ def _upload_code(
181
211
  owner_token = data.get("owner_token", "")
182
212
  if not _valid_sid(sid):
183
213
  raise RuntimeError(f"Server returned unexpected Session ID: '{sid}'")
184
- return sid, owner_token
214
+ return sid, owner_token, data.get("share_url", ""), data.get("libraries", "")
185
215
 
186
216
 
187
217
  # ── CodeManager ───────────────────────────────────────────────────────────────
@@ -200,7 +230,8 @@ class CodeManager:
200
230
 
201
231
  Environment variables:
202
232
  PYVAULT_URL — base URL of the PyVault server
203
- (default: https://secure-code-runner--diwasreplit.replit.app)
233
+ (default: http://localhost:5000; set PYVAULT_URL for a hosted server)
234
+ PYVAULT_TERMINAL — on/off terminal status messages (default: on)
204
235
  """
205
236
 
206
237
  @staticmethod
@@ -210,6 +241,9 @@ class CodeManager:
210
241
  timeout: int = 30,
211
242
  expires_in_hours: Optional[float] = None,
212
243
  max_executions: int = 0,
244
+ label: str = "",
245
+ libraries = "",
246
+ show_terminal: Optional[bool] = None,
213
247
  ) -> str:
214
248
  """
215
249
  Read a local .py file and upload it to PyVault.
@@ -230,14 +264,20 @@ class CodeManager:
230
264
  code = _read_file(file_path)
231
265
  if not code.strip():
232
266
  raise ValueError(f"The file '{file_path}' is empty — nothing to upload.")
233
- sid, owner_token = _upload_code(code, base_url, timeout, expires_in_hours, max_executions)
234
- print(f"[PyVault] Uploaded — Session ID : {sid}", flush=True)
235
- print(f"[PyVault] Source : {os.path.abspath(file_path)}", flush=True)
236
- print(f"[PyVault] Lines : {len(code.splitlines()):,}", flush=True)
267
+ sid, owner_token, share_url, safe_libraries = _upload_code(
268
+ code, base_url, timeout, expires_in_hours, max_executions, label, libraries
269
+ )
270
+ _emit(f"[PyVault] ✓ Uploaded — Session ID : {sid}", show_terminal)
271
+ _emit(f"[PyVault] Source : {os.path.abspath(file_path)}", show_terminal)
272
+ _emit(f"[PyVault] Lines : {len(code.splitlines()):,}", show_terminal)
273
+ if share_url:
274
+ _emit(f"[PyVault] Share link: {share_url}", show_terminal)
275
+ if safe_libraries:
276
+ _emit(f"[PyVault] Libraries: {safe_libraries}", show_terminal)
237
277
  if owner_token:
238
- print(f"\n[PyVault] ⚠ OWNER TOKEN (save this — shown only once!):", flush=True)
239
- print(f"[PyVault] {owner_token}", flush=True)
240
- print(f"[PyVault] Use this to delete or edit your session later.\n", flush=True)
278
+ _emit("\n[PyVault] ⚠ OWNER TOKEN (save this — shown only once!):", show_terminal)
279
+ _emit(f"[PyVault] {owner_token}", show_terminal)
280
+ _emit("[PyVault] Use this to delete or edit your session later.\n", show_terminal)
241
281
  return sid
242
282
 
243
283
  @staticmethod
@@ -245,6 +285,9 @@ class CodeManager:
245
285
  paste_url: str,
246
286
  base_url: Optional[str] = None,
247
287
  timeout: int = 30,
288
+ label: str = "",
289
+ libraries = "",
290
+ show_terminal: Optional[bool] = None,
248
291
  ) -> str:
249
292
  """
250
293
  Download Python code from a paste service URL and upload to PyVault.
@@ -268,7 +311,7 @@ class CodeManager:
268
311
  resp = requests.get(
269
312
  raw_url,
270
313
  timeout=timeout,
271
- headers={"User-Agent": "PyVaultRCE/2.1"},
314
+ headers={"User-Agent": "PyVaultRCE/2.4"},
272
315
  )
273
316
  resp.raise_for_status()
274
317
  except requests.exceptions.ConnectionError:
@@ -286,14 +329,20 @@ class CodeManager:
286
329
  if not code.strip():
287
330
  raise ValueError("Downloaded content is empty.")
288
331
 
289
- sid, owner_token = _upload_code(code, base_url, timeout)
290
- print(f"[PyVault] Uploaded from URL — Session ID : {sid}", flush=True)
291
- print(f"[PyVault] Source : {paste_url}", flush=True)
292
- print(f"[PyVault] Lines : {len(code.splitlines()):,}", flush=True)
332
+ sid, owner_token, share_url, safe_libraries = _upload_code(
333
+ code, base_url, timeout, label=label, libraries=libraries
334
+ )
335
+ _emit(f"[PyVault] ✓ Uploaded from URL — Session ID : {sid}", show_terminal)
336
+ _emit(f"[PyVault] Source : {paste_url}", show_terminal)
337
+ _emit(f"[PyVault] Lines : {len(code.splitlines()):,}", show_terminal)
338
+ if share_url:
339
+ _emit(f"[PyVault] Share link: {share_url}", show_terminal)
340
+ if safe_libraries:
341
+ _emit(f"[PyVault] Libraries: {safe_libraries}", show_terminal)
293
342
  if owner_token:
294
- print(f"\n[PyVault] ⚠ OWNER TOKEN (save this — shown only once!):", flush=True)
295
- print(f"[PyVault] {owner_token}", flush=True)
296
- print(f"[PyVault] Use this to delete or edit your session later.\n", flush=True)
343
+ _emit("\n[PyVault] ⚠ OWNER TOKEN (save this — shown only once!):", show_terminal)
344
+ _emit(f"[PyVault] {owner_token}", show_terminal)
345
+ _emit("[PyVault] Use this to delete or edit your session later.\n", show_terminal)
297
346
  return sid
298
347
 
299
348
  @staticmethod
@@ -302,6 +351,7 @@ class CodeManager:
302
351
  base_url: Optional[str] = None,
303
352
  timeout: int = 30,
304
353
  _ns: Optional[dict] = None,
354
+ show_terminal: Optional[bool] = None,
305
355
  ) -> None:
306
356
  """
307
357
  Fetch the encrypted code for a Session ID from PyVault, decrypt it,
@@ -366,7 +416,7 @@ class CodeManager:
366
416
  if not code.strip():
367
417
  raise RuntimeError(f"Decrypted code is empty for session '{session_id}'.")
368
418
 
369
- print(f"[PyVault] ▶ Running session '{session_id}' (execution #{run_count})…", flush=True)
419
+ _emit(f"[PyVault] ▶ Running session '{session_id}' (execution #{run_count})…", show_terminal)
370
420
 
371
421
  namespace = _ns if _ns is not None else {
372
422
  "__name__": "__pyvault__",
@@ -376,18 +426,18 @@ class CodeManager:
376
426
  try:
377
427
  exec(compile(code, f"<vault:{session_id[:8]}…>", "exec"), namespace)
378
428
  except SyntaxError as exc:
379
- print(f"\n[PyVault] ✕ Syntax error:", file=sys.stderr, flush=True)
380
- print(f" Line {exc.lineno}: {exc.msg}", file=sys.stderr)
429
+ _emit("\n[PyVault] ✕ Syntax error:", show_terminal, error=True)
430
+ _emit(f" Line {exc.lineno}: {exc.msg}", show_terminal, error=True)
381
431
  if exc.text:
382
- print(f" >>> {exc.text.strip()}", file=sys.stderr)
432
+ _emit(f" >>> {exc.text.strip()}", show_terminal, error=True)
383
433
  raise
384
434
  except Exception:
385
- print(f"\n[PyVault] ✕ Runtime error:", file=sys.stderr, flush=True)
435
+ _emit("\n[PyVault] ✕ Runtime error:", show_terminal, error=True)
386
436
  for line in traceback.format_exc().splitlines():
387
- print(f" {line}", file=sys.stderr)
437
+ _emit(f" {line}", show_terminal, error=True)
388
438
  raise
389
439
 
390
- print(f"[PyVault] ✓ Execution complete.", flush=True)
440
+ _emit("[PyVault] ✓ Execution complete.", show_terminal)
391
441
 
392
442
  @staticmethod
393
443
  def info(
@@ -400,7 +450,8 @@ class CodeManager:
400
450
  incrementing the execution counter.
401
451
 
402
452
  Returns:
403
- dict with keys: session_id, execution_count, created_at, code_size
453
+ dict with keys: session_id, execution_count, created_at,
454
+ encrypted_size, expires_at, max_executions, status
404
455
 
405
456
  Raises:
406
457
  ValueError, ConnectionError, RuntimeError
@@ -417,15 +468,30 @@ class CodeManager:
417
468
  err = resp.text
418
469
  raise RuntimeError(f"Server error (HTTP {resp.status_code}): {err}")
419
470
  data = resp.json()
471
+ encrypted_size = data.get("encrypted_size", data.get("code_size", 0))
420
472
  print(
421
- f"[PyVault] ℹ Session : {data['session_id']}\n"
473
+ f"[PyVault] ℹ Session : {data['session_id']}\n"
422
474
  f"[PyVault] Executions: {data['execution_count']}\n"
423
475
  f"[PyVault] Created : {data['created_at']}\n"
424
- f"[PyVault] Code size : {data['code_size']:,} bytes (encrypted)",
476
+ f"[PyVault] Code size : {encrypted_size:,} bytes (encrypted)\n"
477
+ f"[PyVault] Status : {data.get('status', 'unknown')}",
425
478
  flush=True,
426
479
  )
427
480
  return data
428
481
 
482
+ @staticmethod
483
+ def share(
484
+ session_id: str,
485
+ base_url: Optional[str] = None,
486
+ timeout: int = 30,
487
+ ) -> str:
488
+ """Return the short public metadata link for a session."""
489
+ data = CodeManager.info(session_id, base_url=base_url, timeout=timeout)
490
+ share_url = data.get("share_url", "")
491
+ if not share_url:
492
+ raise RuntimeError("This server did not return a share URL.")
493
+ return share_url
494
+
429
495
  @staticmethod
430
496
  def ping(
431
497
  base_url: Optional[str] = None,
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setup(
7
7
  name="PyVaultRCE",
8
- version="2.2.0",
8
+ version="2.4.0",
9
9
  author="PyVault",
10
10
  description="Remote Code Execution & Hosting client for PyVault — source never exposed",
11
11
  long_description=long_description,
File without changes