v0.2.0 #14

Merged
pptx704 merged 59 commits from dev into main 2026-05-24 05:02:08 +00:00
7 changed files with 196 additions and 772 deletions
Showing only changes of commit a2d9aac78b - Show all commits

View File

@ -172,6 +172,8 @@ import sys
# Stream a new command # Stream a new command
for event in capsule.commands.stream("python", args=["-u", "train.py"]): for event in capsule.commands.stream("python", args=["-u", "train.py"]):
match event.type: match event.type:
case "start":
print(f"PID: {event.pid}")
case "stdout": case "stdout":
print(event.data, end="") print(event.data, end="")
case "stderr": case "stderr":
@ -181,7 +183,10 @@ for event in capsule.commands.stream("python", args=["-u", "train.py"]):
# Connect to a running background process # Connect to a running background process
for event in capsule.commands.connect(handle.pid): for event in capsule.commands.connect(handle.pid):
if event.type == "stdout": match event.type:
case "start":
print(f"PID: {event.pid}")
case "stdout":
print(event.data, end="") print(event.data, end="")
``` ```
@ -211,6 +216,7 @@ capsule.files.exists("/app/main.py") # True
# List directory # List directory
entries = capsule.files.list("/home/user", depth=1) entries = capsule.files.list("/home/user", depth=1)
# FileEntry has: name, type (file/dir), size, modified_at
for entry in entries: for entry in entries:
print(entry.name, entry.type, entry.size) print(entry.name, entry.type, entry.size)
@ -289,8 +295,27 @@ value = capsule.git.get_config("user.name", cwd="/app") # str | None
capsule.git.remote_add("upstream", "https://github.com/org/repo.git", cwd="/app") capsule.git.remote_add("upstream", "https://github.com/org/repo.git", cwd="/app")
url = capsule.git.remote_get("origin", cwd="/app") # str | None url = capsule.git.remote_get("origin", cwd="/app") # str | None
# Reset and restore
capsule.git.reset(mode="hard", ref="HEAD~1", cwd="/app")
capsule.git.restore(["file.txt"], staged=True, cwd="/app")
``` ```
#### Persistent Credential Store
For workflows that need repeated authenticated operations, you can persist credentials via the git credential store:
```python
capsule.git.dangerously_authenticate(
username="user",
password="ghp_token",
host="github.com",
protocol="https",
)
```
> **Warning:** Credentials are written in plaintext inside the capsule and are accessible to any process running there. Prefer per-operation `username`/`password` on `clone`, `push`, and `pull` instead.
Git errors raise `GitCommandError` (or `GitAuthError` for authentication failures), both inheriting from `GitError`: Git errors raise `GitCommandError` (or `GitAuthError` for authentication failures), both inheriting from `GitError`:
```python ```python
@ -308,7 +333,7 @@ except GitAuthError as e:
```python ```python
import sys import sys
with capsule.pty(cmd="/bin/bash", cols=120, rows=40, cwd="/home/user") as term: with capsule.pty(cmd="/bin/bash", cols=80, rows=24, cwd="/home/user") as term:
term.write(b"ls -la\n") term.write(b"ls -la\n")
for event in term: for event in term:
if event.type == "output": if event.type == "output":
@ -451,9 +476,10 @@ result = capsule.run_code("print('running on custom template')")
| `logs` | `Logs` | `.stdout: list[str]` and `.stderr: list[str]` chunks | | `logs` | `Logs` | `.stdout: list[str]` and `.stderr: list[str]` chunks |
| `error` | `ExecutionError \| None` | `.name`, `.value`, `.traceback` | | `error` | `ExecutionError \| None` | `.name`, `.value`, `.traceback` |
| `execution_count` | `int \| None` | Jupyter cell execution counter | | `execution_count` | `int \| None` | Jupyter cell execution counter |
| `timed_out` | `bool` | ``True`` when execution was cut short by the timeout |
| `text` | `str \| None` | (property) `text/plain` of the main `execute_result` | | `text` | `str \| None` | (property) `text/plain` of the main `execute_result` |
Each `Result` has typed MIME fields: `text`, `html`, `markdown`, `svg`, `png`, `jpeg`, `pdf`, `latex`, `json`, `javascript`, plus `extra` for unknown types. The `text` field is Jupyter's `text/plain` bundle verbatim — the Python `repr()` of the cell's last expression. So `run_code("'hi'").text` is `"'hi'"` (with quotes), and `run_code("42").text` is `"42"`. This preserves the distinction between the string `'2'` and the int `2`. Each `Result` has typed MIME fields: `text`, `html`, `markdown`, `svg`, `png`, `jpeg`, `gif`, `pdf`, `latex`, `json`, `javascript`, `plotly`, plus `extra` for unknown types. The `text` field is Jupyter's `text/plain` bundle verbatim — the Python `repr()` of the cell's last expression. So `run_code("'hi'").text` is `"'hi'"` (with quotes), and `run_code("42").text` is `"42"`. This preserves the distinction between the string `'2'` and the int `2`.
### Code Runner + Commands/Files ### Code Runner + Commands/Files
@ -533,8 +559,8 @@ from wrenn import (
WrennNotFoundError, # 404 WrennNotFoundError, # 404
WrennConflictError, # 409 WrennConflictError, # 409
WrennHostHasCapsulesError, # 409 (host has running capsules) WrennHostHasCapsulesError, # 409 (host has running capsules)
WrennAgentError, # 502
WrennInternalError, # 500 WrennInternalError, # 500
WrennAgentError, # 502
WrennHostUnavailableError, # 503 WrennHostUnavailableError, # 503
) )
@ -603,7 +629,7 @@ with WrennClient(api_key="wrn_...") as client:
# Snapshots # Snapshots
template = client.snapshots.create(capsule_id="cl-abc", name="my-snap") template = client.snapshots.create(capsule_id="cl-abc", name="my-snap")
templates = client.snapshots.list() templates = client.snapshots.list(type="custom") # optional type filter
client.snapshots.delete("my-snap") client.snapshots.delete("my-snap")
``` ```

View File

@ -2716,14 +2716,39 @@ paths:
tags: [admin] tags: [admin]
security: security:
- sessionAuth: [] - sessionAuth: []
parameters:
- name: page
in: query
required: false
schema:
type: integer
minimum: 1
default: 1
description: Page number for pagination.
responses: responses:
"200": "200":
description: Teams list description: Paginated teams list
content: content:
application/json: application/json:
schema: schema:
type: object
properties:
teams:
type: array type: array
items: {type: object} items:
$ref: "#/components/schemas/AdminTeam"
total:
type: integer
page:
type: integer
per_page:
type: integer
total_pages:
type: integer
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/v1/admin/teams/{id}/byoc: /v1/admin/teams/{id}/byoc:
put: put:
@ -2743,12 +2768,20 @@ paths:
application/json: application/json:
schema: schema:
type: object type: object
required: [byoc] required: [enabled]
properties: properties:
byoc: {type: boolean} enabled:
type: boolean
description: true to enable BYOC, false to disable.
responses: responses:
"204": "204":
description: Updated description: Updated
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/v1/admin/teams/{id}: /v1/admin/teams/{id}:
delete: delete:
@ -2765,6 +2798,38 @@ paths:
responses: responses:
"204": "204":
description: Deleted description: Deleted
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/admin/hosts:
get:
summary: List all hosts (admin)
operationId: adminListHosts
tags: [admin]
security:
- sessionAuth: []
description: |
Returns all hosts across all teams with per-host resource consumption.
Includes team name for hosts associated with a team.
responses:
"200":
description: Hosts list
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Host"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/v1/admin/users: /v1/admin/users:
get: get:
@ -3581,10 +3646,6 @@ components:
type: integer type: integer
memory_mb_reserved: memory_mb_reserved:
type: integer type: integer
sampled_at:
type: string
format: date-time
nullable: true
peaks: peaks:
type: object type: object
description: Maximum values over the last 30 days. description: Maximum values over the last 30 days.
@ -3633,10 +3694,6 @@ components:
type: integer type: integer
timeout_sec: timeout_sec:
type: integer type: integer
guest_ip:
type: string
host_ip:
type: string
created_at: created_at:
type: string type: string
format: date-time format: date-time
@ -3661,7 +3718,11 @@ components:
agent_version, envd_version) when running. agent_version, envd_version) when running.
disk_size_mb: disk_size_mb:
type: integer type: integer
nullable: true description: Maximum disk capacity in MiB.
disk_used_mb:
type: integer
format: int64
description: Current disk usage in MiB. Only populated on individual capsule GET; omitted in list responses.
CreateSnapshotRequest: CreateSnapshotRequest:
type: object type: object
@ -4013,6 +4074,25 @@ components:
updated_at: updated_at:
type: string type: string
format: date-time format: date-time
team_name:
type: string
nullable: true
description: Team name (included when listing hosts as an admin).
running_vcpus:
type: integer
description: Total vCPUs allocated to running capsules on this host.
running_memory_mb:
type: integer
description: Total memory in MB allocated to running capsules on this host.
running_disk_mb:
type: integer
description: Total disk in MB allocated to running capsules on this host.
paused_memory_mb:
type: integer
description: Total memory in MB allocated to paused capsules on this host.
paused_disk_mb:
type: integer
description: Total disk in MB allocated to paused capsules on this host.
RefreshHostTokenRequest: RefreshHostTokenRequest:
type: object type: object
@ -4124,6 +4204,39 @@ components:
items: items:
$ref: "#/components/schemas/TeamMember" $ref: "#/components/schemas/TeamMember"
AdminTeam:
type: object
properties:
id:
type: string
name:
type: string
slug:
type: string
is_byoc:
type: boolean
created_at:
type: string
format: date-time
deleted_at:
type: string
format: date-time
nullable: true
member_count:
type: integer
owner_name:
type: string
owner_email:
type: string
active_sandbox_count:
type: integer
channel_count:
type: integer
running_vcpus:
type: integer
running_memory_mb:
type: integer
CapsuleMetrics: CapsuleMetrics:
type: object type: object
properties: properties:

View File

@ -1,6 +1,6 @@
[project] [project]
name = "wrenn" name = "wrenn"
version = "0.1.5" version = "0.2.0"
description = "Python SDK for Wrenn" description = "Python SDK for Wrenn"
readme = "README.md" readme = "README.md"
license = "MIT" license = "MIT"

2
uv.lock generated
View File

@ -1166,7 +1166,7 @@ wheels = [
[[package]] [[package]]
name = "wrenn" name = "wrenn"
version = "0.1.5" version = "0.2.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "certifi" }, { name = "certifi" },