@magpiecloud/mags 1.8.13 → 1.8.14

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.
@@ -3,8 +3,6 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
- import subprocess
7
- import tempfile
8
6
  import time
9
7
  from pathlib import Path
10
8
  from typing import Any, Dict, List, Optional
@@ -355,11 +353,11 @@ class Mags:
355
353
  return resp
356
354
 
357
355
  def exec(self, name_or_id: str, command: str, *, timeout: int = 30) -> dict:
358
- """Execute a command on an existing running/sleeping sandbox via SSH.
356
+ """Execute a command on an existing running/sleeping sandbox.
359
357
 
360
358
  Equivalent to ``mags exec <workspace> '<command>'``.
361
359
 
362
- Returns ``{"exit_code": int, "output": str}``.
360
+ Returns ``{"exit_code": int, "output": str, "stderr": str}``.
363
361
  """
364
362
  job = self.find_job(name_or_id)
365
363
  if not job:
@@ -370,65 +368,17 @@ class Mags:
370
368
  )
371
369
 
372
370
  request_id = job.get("request_id") or job.get("id")
373
-
374
- # Call /access directly — for sleeping VMs this triggers the wake
375
- access = self.enable_access(request_id, port=22)
376
-
377
- if not access.get("success") or not access.get("ssh_host"):
378
- raise MagsError(
379
- f"Failed to enable SSH access: {access.get('error', 'unknown error')}"
380
- )
381
-
382
- ssh_host = access["ssh_host"]
383
- ssh_port = str(access["ssh_port"])
384
- ssh_key = access.get("ssh_private_key", "")
385
-
386
- # Wrap command to handle chroot overlay, same as CLI
387
- escaped = command.replace("'", "'\\''")
388
- wrapped = (
389
- f"if [ -d /overlay/bin ]; then "
390
- f"chroot /overlay /bin/sh -l -c 'cd /root 2>/dev/null; {escaped}'; "
391
- f"else cd /root 2>/dev/null; {escaped}; fi"
371
+ resp = self._request(
372
+ "POST",
373
+ f"/mags-jobs/{request_id}/exec",
374
+ json={"command": command, "timeout": timeout},
375
+ timeout=timeout + 10, # HTTP timeout > command timeout
392
376
  )
393
-
394
- key_file = None
395
- try:
396
- ssh_args = [
397
- "ssh",
398
- "-o", "StrictHostKeyChecking=no",
399
- "-o", "UserKnownHostsFile=/dev/null",
400
- "-o", "LogLevel=ERROR",
401
- "-p", ssh_port,
402
- ]
403
- if ssh_key:
404
- fd, key_file = tempfile.mkstemp(prefix="mags_ssh_")
405
- os.write(fd, ssh_key.encode())
406
- os.close(fd)
407
- os.chmod(key_file, 0o600)
408
- ssh_args.extend(["-i", key_file])
409
-
410
- ssh_args.append(f"root@{ssh_host}")
411
- ssh_args.append(wrapped)
412
-
413
- proc = subprocess.run(
414
- ssh_args,
415
- capture_output=True,
416
- text=True,
417
- timeout=timeout,
418
- )
419
- return {
420
- "exit_code": proc.returncode,
421
- "output": proc.stdout,
422
- "stderr": proc.stderr,
423
- }
424
- except subprocess.TimeoutExpired:
425
- raise MagsError(f"Command timed out after {timeout}s")
426
- finally:
427
- if key_file:
428
- try:
429
- os.unlink(key_file)
430
- except OSError:
431
- pass
377
+ return {
378
+ "exit_code": resp.get("exit_code", -1),
379
+ "output": resp.get("stdout", ""),
380
+ "stderr": resp.get("stderr", ""),
381
+ }
432
382
 
433
383
  def usage(self, *, window_days: int = 30) -> dict:
434
384
  """Get aggregated usage summary."""
package/website/api.html CHANGED
@@ -10,7 +10,7 @@
10
10
  />
11
11
  <meta name="api-base" content="https://api.magpiecloud.com" />
12
12
  <meta name="auth-base" content="https://api.magpiecloud.com" />
13
- <link rel="stylesheet" href="styles.css?v=7" />
13
+ <link rel="stylesheet" href="styles.css?v=8" />
14
14
  <script src="env.js"></script>
15
15
  <style>
16
16
  .endpoint { margin-bottom: 2.5rem; }
@@ -30,10 +30,10 @@
30
30
  font-weight: 600;
31
31
  text-transform: uppercase;
32
32
  }
33
- .method.post { background: rgba(47, 143, 99, 0.15); color: #237c52; }
34
- .method.get { background: rgba(59, 130, 246, 0.15); color: #2563eb; }
35
- .method.patch { background: rgba(245, 158, 11, 0.15); color: #b45309; }
36
- .method.delete { background: rgba(239, 68, 68, 0.15); color: #dc2626; }
33
+ .method.post { background: rgba(47, 155, 102, 0.2); color: #3dbd7e; }
34
+ .method.get { background: rgba(59, 130, 246, 0.2); color: #60a5fa; }
35
+ .method.patch { background: rgba(245, 158, 11, 0.2); color: #fbbf24; }
36
+ .method.delete { background: rgba(239, 68, 68, 0.2); color: #f87171; }
37
37
  .url-path {
38
38
  font-family: var(--mono);
39
39
  font-size: 0.95rem;
@@ -109,7 +109,7 @@
109
109
  </div>
110
110
  <nav class="nav-links">
111
111
  <a href="index.html">Home</a>
112
- <a href="index.html#quickstart">Docs</a>
112
+ <a href="docs.html">Docs</a>
113
113
  <a href="cookbook.html">Cookbook</a>
114
114
  <a href="claude-skill.html">Claude Skill</a>
115
115
  <a href="https://discord.gg/3avpC2nS" target="_blank" rel="noreferrer">Discord</a>
@@ -10,7 +10,7 @@
10
10
  />
11
11
  <meta name="api-base" content="https://api.magpiecloud.com" />
12
12
  <meta name="auth-base" content="https://api.magpiecloud.com" />
13
- <link rel="stylesheet" href="styles.css?v=6" />
13
+ <link rel="stylesheet" href="styles.css?v=8" />
14
14
  <script src="env.js"></script>
15
15
  </head>
16
16
  <body>
@@ -24,7 +24,7 @@
24
24
  </div>
25
25
  <nav class="nav-links">
26
26
  <a href="index.html">Home</a>
27
- <a href="index.html#quickstart">Docs</a>
27
+ <a href="docs.html">Docs</a>
28
28
  <a href="api.html">API</a>
29
29
  <a href="cookbook.html">Cookbook</a>
30
30
  <a href="https://discord.gg/3avpC2nS" target="_blank" rel="noreferrer">Discord</a>
@@ -69,7 +69,7 @@
69
69
  padding: 0.5rem 1rem;
70
70
  border-radius: 999px;
71
71
  border: 1px solid var(--border);
72
- background: #ffffff;
72
+ background: var(--surface);
73
73
  color: var(--text-muted);
74
74
  font-size: 0.85rem;
75
75
  font-family: var(--sans);
@@ -10,7 +10,7 @@
10
10
  />
11
11
  <meta name="api-base" content="https://api.magpiecloud.com" />
12
12
  <meta name="auth-base" content="https://api.magpiecloud.com" />
13
- <link rel="stylesheet" href="styles.css?v=6" />
13
+ <link rel="stylesheet" href="styles.css?v=8" />
14
14
  <script src="env.js"></script>
15
15
  </head>
16
16
  <body>
@@ -24,14 +24,14 @@
24
24
  </div>
25
25
  <nav class="nav-links">
26
26
  <a href="index.html">Home</a>
27
- <a href="index.html#quickstart">Docs</a>
27
+ <a href="docs.html">Docs</a>
28
28
  <a href="api.html">API</a>
29
29
  <a href="claude-skill.html">Claude Skill</a>
30
30
  <a href="https://discord.gg/3avpC2nS" target="_blank" rel="noreferrer">Discord</a>
31
31
  <a href="login.html">Login</a>
32
32
  </nav>
33
33
  <div class="nav-cta">
34
- <a class="button ghost" href="index.html#quickstart">Get started</a>
34
+ <a class="button ghost" href="docs.html#quickstart">Get started</a>
35
35
  </div>
36
36
  </div>
37
37
  </header>