PyVaultRCE 2.3.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.3.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
 
@@ -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,13 +75,25 @@ 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
99
  | `CodeManager.edit(session_id, file_path, admin_token=...)` | Replace stored code (admin or owner token) |
@@ -102,6 +124,7 @@ export PYVAULT_URL=https://your-server.replit.app
102
124
  - Session IDs are 21-character cryptographically random hex strings
103
125
  - Source code is never written to disk on the client
104
126
  - Code is encrypted at rest with Fernet on the server
127
+ - Share links expose only session metadata and declared library names, never source
105
128
  - Admin edits require the admin access code; owner edits/deletes require the one-time owner token
106
129
  - Sessions never expire unless an expiry is explicitly selected
107
130
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyVaultRCE
3
- Version: 2.3.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
 
@@ -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,13 +75,25 @@ 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
99
  | `CodeManager.edit(session_id, file_path, admin_token=...)` | Replace stored code (admin or owner token) |
@@ -102,6 +124,7 @@ export PYVAULT_URL=https://your-server.replit.app
102
124
  - Session IDs are 21-character cryptographically random hex strings
103
125
  - Source code is never written to disk on the client
104
126
  - Code is encrypted at rest with Fernet on the server
127
+ - Share links expose only session metadata and declared library names, never source
105
128
  - Admin edits require the admin access code; owner edits/deletes require the one-time owner token
106
129
  - Sessions never expire unless an expiry is explicitly selected
107
130
 
@@ -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
 
@@ -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,13 +50,25 @@ 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
74
  | `CodeManager.edit(session_id, file_path, admin_token=...)` | Replace stored code (admin or owner token) |
@@ -77,6 +99,7 @@ export PYVAULT_URL=https://your-server.replit.app
77
99
  - Session IDs are 21-character cryptographically random hex strings
78
100
  - Source code is never written to disk on the client
79
101
  - Code is encrypted at rest with Fernet on the server
102
+ - Share links expose only session metadata and declared library names, never source
80
103
  - Admin edits require the admin access code; owner edits/deletes require the one-time owner token
81
104
  - Sessions never expire unless an expiry is explicitly selected
82
105
 
@@ -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 = "https://secure-code-runner--diwasreplit.replit.app"
18
+ _DEFAULT_BASE_URL = "http://localhost:5000"
19
19
 
20
20
  # ── Validation helpers ───────────────────────────────────────────────────────
21
21
 
@@ -40,6 +40,19 @@ def _get_base_url(base_url: Optional[str]) -> str:
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,6 +72,8 @@ 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.
@@ -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.3.0"
21
+ __version__ = "2.4.0"
22
22
  __author__ = "PyVault"
@@ -23,7 +23,7 @@ try:
23
23
  except ImportError:
24
24
  _HAS_CRYPTO = False
25
25
 
26
- _DEFAULT_BASE = "https://secure-code-runner--diwasreplit.replit.app"
26
+ _DEFAULT_BASE = "http://localhost:5000"
27
27
  _MAX_LINES = 10_000
28
28
  _SID_LEN = 21
29
29
  _HEX_SET = frozenset("0123456789abcdef")
@@ -47,6 +47,33 @@ def _base(url: Optional[str]) -> str:
47
47
  return (url or os.environ.get("PYVAULT_URL") or _DEFAULT_BASE).rstrip("/")
48
48
 
49
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]
75
+
76
+
50
77
  def _valid_sid(sid: str) -> bool:
51
78
  return isinstance(sid, str) and len(sid) == _SID_LEN and all(c in _HEX_SET for c in sid)
52
79
 
@@ -150,8 +177,10 @@ def _upload_code(
150
177
  timeout: int,
151
178
  expires_in_hours: Optional[float] = None,
152
179
  max_executions: int = 0,
180
+ label: str = "",
181
+ libraries = "",
153
182
  ) -> tuple:
154
- """Internal: push code string to /pyv/save. Returns (session_id, owner_token)."""
183
+ """Internal: push code string to /pyv/save. Returns session metadata."""
155
184
  if not code.strip():
156
185
  raise ValueError("Code is empty — nothing to upload.")
157
186
  line_count = len(code.splitlines())
@@ -164,6 +193,10 @@ def _upload_code(
164
193
  payload["expires_in_hours"] = expires_in_hours
165
194
  if max_executions and max_executions > 0:
166
195
  payload["max_executions"] = max_executions
196
+ if label:
197
+ payload["label"] = str(label)[:200]
198
+ if libraries:
199
+ payload["libraries"] = _normalise_libraries(libraries)
167
200
 
168
201
  url = _base(base_url) + "/pyv/save"
169
202
  resp = _http_post(url, payload, timeout)
@@ -178,7 +211,7 @@ def _upload_code(
178
211
  owner_token = data.get("owner_token", "")
179
212
  if not _valid_sid(sid):
180
213
  raise RuntimeError(f"Server returned unexpected Session ID: '{sid}'")
181
- return sid, owner_token
214
+ return sid, owner_token, data.get("share_url", ""), data.get("libraries", "")
182
215
 
183
216
 
184
217
  # ── CodeManager ───────────────────────────────────────────────────────────────
@@ -197,7 +230,8 @@ class CodeManager:
197
230
 
198
231
  Environment variables:
199
232
  PYVAULT_URL — base URL of the PyVault server
200
- (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)
201
235
  """
202
236
 
203
237
  @staticmethod
@@ -207,6 +241,9 @@ class CodeManager:
207
241
  timeout: int = 30,
208
242
  expires_in_hours: Optional[float] = None,
209
243
  max_executions: int = 0,
244
+ label: str = "",
245
+ libraries = "",
246
+ show_terminal: Optional[bool] = None,
210
247
  ) -> str:
211
248
  """
212
249
  Read a local .py file and upload it to PyVault.
@@ -227,14 +264,20 @@ class CodeManager:
227
264
  code = _read_file(file_path)
228
265
  if not code.strip():
229
266
  raise ValueError(f"The file '{file_path}' is empty — nothing to upload.")
230
- sid, owner_token = _upload_code(code, base_url, timeout, expires_in_hours, max_executions)
231
- print(f"[PyVault] Uploaded — Session ID : {sid}", flush=True)
232
- print(f"[PyVault] Source : {os.path.abspath(file_path)}", flush=True)
233
- 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)
234
277
  if owner_token:
235
- print(f"\n[PyVault] ⚠ OWNER TOKEN (save this — shown only once!):", flush=True)
236
- print(f"[PyVault] {owner_token}", flush=True)
237
- 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)
238
281
  return sid
239
282
 
240
283
  @staticmethod
@@ -242,6 +285,9 @@ class CodeManager:
242
285
  paste_url: str,
243
286
  base_url: Optional[str] = None,
244
287
  timeout: int = 30,
288
+ label: str = "",
289
+ libraries = "",
290
+ show_terminal: Optional[bool] = None,
245
291
  ) -> str:
246
292
  """
247
293
  Download Python code from a paste service URL and upload to PyVault.
@@ -265,7 +311,7 @@ class CodeManager:
265
311
  resp = requests.get(
266
312
  raw_url,
267
313
  timeout=timeout,
268
- headers={"User-Agent": "PyVaultRCE/2.3"},
314
+ headers={"User-Agent": "PyVaultRCE/2.4"},
269
315
  )
270
316
  resp.raise_for_status()
271
317
  except requests.exceptions.ConnectionError:
@@ -283,14 +329,20 @@ class CodeManager:
283
329
  if not code.strip():
284
330
  raise ValueError("Downloaded content is empty.")
285
331
 
286
- sid, owner_token = _upload_code(code, base_url, timeout)
287
- print(f"[PyVault] Uploaded from URL — Session ID : {sid}", flush=True)
288
- print(f"[PyVault] Source : {paste_url}", flush=True)
289
- 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)
290
342
  if owner_token:
291
- print(f"\n[PyVault] ⚠ OWNER TOKEN (save this — shown only once!):", flush=True)
292
- print(f"[PyVault] {owner_token}", flush=True)
293
- 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)
294
346
  return sid
295
347
 
296
348
  @staticmethod
@@ -299,6 +351,7 @@ class CodeManager:
299
351
  base_url: Optional[str] = None,
300
352
  timeout: int = 30,
301
353
  _ns: Optional[dict] = None,
354
+ show_terminal: Optional[bool] = None,
302
355
  ) -> None:
303
356
  """
304
357
  Fetch the encrypted code for a Session ID from PyVault, decrypt it,
@@ -363,7 +416,7 @@ class CodeManager:
363
416
  if not code.strip():
364
417
  raise RuntimeError(f"Decrypted code is empty for session '{session_id}'.")
365
418
 
366
- 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)
367
420
 
368
421
  namespace = _ns if _ns is not None else {
369
422
  "__name__": "__pyvault__",
@@ -373,18 +426,18 @@ class CodeManager:
373
426
  try:
374
427
  exec(compile(code, f"<vault:{session_id[:8]}…>", "exec"), namespace)
375
428
  except SyntaxError as exc:
376
- print(f"\n[PyVault] ✕ Syntax error:", file=sys.stderr, flush=True)
377
- 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)
378
431
  if exc.text:
379
- print(f" >>> {exc.text.strip()}", file=sys.stderr)
432
+ _emit(f" >>> {exc.text.strip()}", show_terminal, error=True)
380
433
  raise
381
434
  except Exception:
382
- print(f"\n[PyVault] ✕ Runtime error:", file=sys.stderr, flush=True)
435
+ _emit("\n[PyVault] ✕ Runtime error:", show_terminal, error=True)
383
436
  for line in traceback.format_exc().splitlines():
384
- print(f" {line}", file=sys.stderr)
437
+ _emit(f" {line}", show_terminal, error=True)
385
438
  raise
386
439
 
387
- print(f"[PyVault] ✓ Execution complete.", flush=True)
440
+ _emit("[PyVault] ✓ Execution complete.", show_terminal)
388
441
 
389
442
  @staticmethod
390
443
  def info(
@@ -426,6 +479,19 @@ class CodeManager:
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.3.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