Python SDK for Lumina sandboxes — create, manage, and interact with isolated dev environments
Project description
Lumina Python SDK
The lumina_sandbox Python package provides an async client for the Lumina tool endpoints, plus integration with the bu-agent-sdk for building AI coding agents.
Table of Contents
- Installation
- Quick Start
- LuminaToolClient
- Result Types
- bu-agent-sdk Integration
- Interactive Chat CLI
- Advanced Usage
- API Reference
Installation
The SDK requires Python 3.9+.
# Core SDK
pip install lumina-sandbox
# With agent integration (requires Python >= 3.11)
pip install lumina-sandbox[agent]
Quick Start
import asyncio
from lumina_sandbox import LuminaToolClient
async def main():
async with LuminaToolClient(
base_url="http://localhost:8080",
token="your-api-key-here",
lumina_name="my-sandbox",
) as client:
# Run a command
result = await client.bash("echo Hello from Lumina!")
print(result.output) # "Hello from Lumina!\n"
# Read a file
content = await client.read("/home/user/main.py")
print(f"File has {content.total_lines} lines")
# Search for Python files
files = await client.glob("**/*.py")
print(f"Found {files.count} Python files")
asyncio.run(main())
LuminaToolClient
Initialization
from lumina_sandbox import LuminaToolClient
client = LuminaToolClient(
base_url="http://localhost:8080", # Lumina server URL
token="a1b2c3d4...", # API key (64-char hex) or JWT
lumina_name="my-sandbox", # Sandbox name
tool_session_id=None, # Auto-generated UUID if not provided
timeout=660.0, # HTTP request timeout in seconds
)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
base_url |
str |
yes | Lumina server URL | |
token |
str |
yes | API key or JWT token | |
lumina_name |
str |
yes | Target sandbox name | |
tool_session_id |
str |
no | auto UUID | Session ID for state tracking |
timeout |
float |
no | 660.0 |
HTTP timeout in seconds |
The client is an async context manager:
# Recommended: auto-closes on exit
async with LuminaToolClient(...) as client:
await client.bash("echo hello")
# Manual lifecycle
client = LuminaToolClient(...)
try:
await client.bash("echo hello")
finally:
await client.close()
Sandbox Lifecycle
The client provides methods for the full sandbox lifecycle:
# Create a sandbox and use tools
async with LuminaToolClient(
base_url="http://localhost:8080",
token="your-api-key",
lumina_name="my-sandbox",
) as client:
await client.create_sandbox() # Create and bootstrap
result = await client.bash("echo hi") # Use tools
await client.destroy_sandbox() # Cleanup
Or use the convenience class method to create and connect in one call:
async with await LuminaToolClient.create(
base_url="http://localhost:8080",
token="your-api-key",
name="my-sandbox",
) as client:
result = await client.bash("echo hi")
await client.destroy_sandbox()
create_sandbox()
async def create_sandbox(eager: bool = True) -> dict
Creates the sandbox on the server. If eager=True (default), immediately acquires a container and bootstraps the user environment so the first tool call has zero cold-start latency. Returns the server response dict with sandbox metadata and lease info.
list_sandboxes()
async def list_sandboxes() -> list[dict]
Returns all sandboxes for the authenticated user.
restart_sandbox()
async def restart_sandbox() -> dict
Restarts the sandbox container with the latest available image. Running processes are terminated but persistent storage is retained.
destroy_sandbox()
async def destroy_sandbox() -> None
Destroys the sandbox and releases its container lease.
File Tools
read()
Read a file from the sandbox. Automatically detects text files, images, and PDFs.
async def read(
file_path: str,
offset: Optional[int] = None, # Starting line (1-based)
limit: Optional[int] = None, # Max lines to return
) -> TextFileResult | ImageFileResult | PDFFileResult
Examples:
# Read a text file
result = await client.read("/home/user/main.py")
print(result.content) # " 1\timport os\n 2\t..."
print(result.total_lines) # 150
print(result.lines_returned) # 150
# Read with pagination (lines 50-100)
result = await client.read("/home/user/big_file.py", offset=50, limit=50)
print(result.lines_returned) # 50
# Read an image (returns base64)
result = await client.read("/home/user/screenshot.png")
print(result.mime_type) # "image/png"
print(result.file_size) # 24567
# result.image contains base64-encoded data
# Read a PDF
result = await client.read("/home/user/document.pdf")
for page in result.pages:
print(f"Page {page.page_number}: {page.text[:100]}...")
write()
Write content to a file. Creates parent directories automatically.
async def write(
file_path: str,
content: str,
) -> WriteResult
Examples:
# Write a new file
result = await client.write("/home/user/hello.txt", "Hello, world!\n")
print(result.bytes_written) # 14
# Write a Python script
result = await client.write("/home/user/script.py", """#!/usr/bin/env python3
import sys
def main():
print("Hello from script!")
return 0
if __name__ == "__main__":
sys.exit(main())
""")
# Overwrite requires reading first (safety check)
await client.read("/home/user/hello.txt") # Must read first!
result = await client.write("/home/user/hello.txt", "Updated content\n")
edit()
Edit a file using exact string replacement.
async def edit(
file_path: str,
old_string: str,
new_string: str,
replace_all: bool = False,
) -> EditResult
Examples:
# Read the file first (required)
await client.read("/home/user/main.py")
# Replace a single occurrence
result = await client.edit(
"/home/user/main.py",
old_string="def hello():",
new_string="def hello(name: str = 'World'):",
)
print(result.replacements) # 1
# Replace all occurrences
result = await client.edit(
"/home/user/main.py",
old_string="print",
new_string="logging.info",
replace_all=True,
)
print(result.replacements) # 5
# If old_string appears multiple times and replace_all=False,
# the server returns a 400 error with the count. Use more context
# to make the match unique, or set replace_all=True.
Search Tools
glob()
Search for files matching a glob pattern.
async def glob(
pattern: str,
path: Optional[str] = None, # Search directory (default: user home)
) -> GlobResult
Examples:
# Find all Python files
result = await client.glob("**/*.py")
print(result.count) # 12
print(result.matches) # ["/home/user/main.py", "/home/user/utils.py", ...]
# Find in a specific directory
result = await client.glob("*.ts", path="/home/user/src")
for f in result.matches:
print(f)
# Find test files
result = await client.glob("**/test_*.py", path="/home/user/project")
Results are sorted by modification time (newest first).
grep()
Search file contents using regular expressions (powered by ripgrep).
async def grep(
pattern: str,
path: Optional[str] = None,
glob: Optional[str] = None, # File glob filter
output_mode: str = "files_with_matches",
line_numbers: bool = False,
after: int = 0, # Context lines after match
before: int = 0, # Context lines before match
context: int = 0, # Context lines both sides
file_type: Optional[str] = None, # File type filter (e.g., "py")
case_insensitive: bool = False,
multiline: bool = False,
) -> GrepContentResult | GrepFilesResult | GrepCountResult
Examples:
# Find files containing "TODO"
result = await client.grep("TODO")
print(result.count) # 3
print(result.files) # ["/home/user/main.py", ...]
# Search with context lines (content mode)
result = await client.grep(
"def main",
path="/home/user/project",
output_mode="content",
line_numbers=True,
after=5,
)
for match in result.matches:
print(f"{match.file}:{match.line_number}")
print(f" {match.line}")
for ctx in match.after_context:
print(f" {ctx}")
# Count occurrences per file
result = await client.grep("import", output_mode="count", file_type="py")
for entry in result.counts:
print(f"{entry.file}: {entry.count} imports")
print(f"Total: {result.total}")
# Case-insensitive search in specific file types
result = await client.grep(
"error|warning",
output_mode="content",
case_insensitive=True,
glob="*.log",
)
# Multiline regex
result = await client.grep(
r"class \w+:.*\n\s+def __init__",
output_mode="content",
multiline=True,
file_type="py",
)
Bash Tools
bash()
Execute a bash command in a persistent tmux session.
async def bash(
command: str,
timeout: Optional[int] = None, # Timeout in ms (default: 120000)
description: Optional[str] = None, # Human-readable description
run_in_background: bool = False,
) -> BashResult
Examples:
# Simple command
result = await client.bash("ls -la /home/user")
print(result.output) # directory listing
print(result.exit_code) # 0
# Install packages
result = await client.bash("pip install requests flask", timeout=60000)
if result.exit_code != 0:
print(f"Install failed: {result.output}")
# Run tests
result = await client.bash("cd /home/user/project && python -m pytest -v", timeout=300000)
print(f"Tests {'passed' if result.exit_code == 0 else 'failed'}")
# Environment persists across calls
await client.bash("export DATABASE_URL=postgres://localhost/mydb")
result = await client.bash("echo $DATABASE_URL")
print(result.output) # "postgres://localhost/mydb\n"
# Working directory persists
await client.bash("cd /home/user/project/src")
result = await client.bash("pwd")
print(result.output) # "/home/user/project/src\n"
# Background command
result = await client.bash("npm run dev", run_in_background=True)
print(result.shell_id) # "bash_a1b2c3d4" -- use for polling
# Timeout enforcement
result = await client.bash("sleep 60", timeout=3000)
print(result.killed) # True
print(result.exit_code) # 124
bash_output()
Get incremental output from a background bash command.
async def bash_output(
bash_id: str,
filter: Optional[str] = None, # Only lines containing this substring
) -> BashOutputResult
Examples:
# Start a long-running command
result = await client.bash("npm run build 2>&1", run_in_background=True)
shell_id = result.shell_id
# Poll for output
import asyncio
while True:
status = await client.bash_output(shell_id)
if status.output:
print(status.output, end="")
if status.status in ("completed", "killed", "failed"):
print(f"\nDone with exit code: {status.exit_code}")
break
await asyncio.sleep(2)
# Filter output to only show errors
status = await client.bash_output(shell_id, filter="ERROR")
Status values: "running", "completed", "killed", "failed"
kill_bash()
Terminate a running background command.
async def kill_bash(shell_id: str) -> dict
Example:
# Start a long-running server
result = await client.bash("python -m http.server 8000", run_in_background=True)
# ... do some work ...
# Kill it when done
await client.kill_bash(result.shell_id)
Error Handling
All tool methods raise ToolError on HTTP 4xx/5xx responses:
from lumina_sandbox import ToolError
try:
result = await client.read("/home/user/nonexistent.txt")
except ToolError as e:
print(f"Error {e.status_code}: {e}")
# Error 404: file not found: /home/user/nonexistent.txt
Common error scenarios:
| Status | Cause | Example |
|---|---|---|
400 |
Invalid request | Path not absolute, ambiguous edit match |
404 |
Not found | File doesn't exist, sandbox not found |
409 |
Conflict | Write without prior Read, session binding mismatch |
# Handle write-before-read
try:
await client.write("/home/user/existing.txt", "new content")
except ToolError as e:
if e.status_code == 409:
# Need to read first
await client.read("/home/user/existing.txt")
await client.write("/home/user/existing.txt", "new content")
# Handle ambiguous edit
try:
await client.edit("/home/user/main.py", "x", "y")
except ToolError as e:
if e.status_code == 400 and "occurrences" in str(e):
# Use more context or replace_all=True
await client.edit("/home/user/main.py", "x", "y", replace_all=True)
Result Types
All result types are Python dataclass instances defined in lumina_sandbox.types:
from lumina_sandbox import (
BashResult, # output, exit_code, killed, shell_id
BashOutputResult, # output, status, exit_code (Optional[int])
TextFileResult, # content, total_lines, lines_returned
ImageFileResult, # image (base64), mime_type, file_size
PDFFileResult, # pages: list[PDFPage], total_pages
WriteResult, # message, bytes_written, file_path
EditResult, # message, replacements, file_path
GlobResult, # matches: list[str], count, search_path
GrepContentResult, # matches: list[GrepContentMatch], total_matches
GrepFilesResult, # files: list[str], count
GrepCountResult, # counts: list[GrepCountEntry], total
ToolError, # Exception with status_code: int
)
BashResult
@dataclass
class BashResult:
output: str # Command stdout+stderr (combined)
exit_code: int # Process exit code (124 if timed out)
killed: bool # True if command was killed by timeout
shell_id: str # Non-empty only for background commands
BashOutputResult
@dataclass
class BashOutputResult:
output: str # Incremental output since last read
status: str # "running", "completed", "killed", "failed"
exit_code: Optional[int] # Set when status != "running"
TextFileResult
@dataclass
class TextFileResult:
content: str # File content with line numbers (tab-separated)
total_lines: int # Total lines in the file
lines_returned: int # Lines returned in this response
ImageFileResult
@dataclass
class ImageFileResult:
image: str # Base64-encoded image data
mime_type: str # e.g., "image/png", "image/jpeg"
file_size: int # File size in bytes
PDFFileResult / PDFPage
@dataclass
class PDFPage:
page_number: int
text: str
@dataclass
class PDFFileResult:
pages: list[PDFPage]
total_pages: int
WriteResult
@dataclass
class WriteResult:
message: str # "ok"
bytes_written: int
file_path: str
EditResult
@dataclass
class EditResult:
message: str # "ok"
replacements: int # Number of replacements made
file_path: str
GlobResult
@dataclass
class GlobResult:
matches: list[str] # Matching file paths (newest first)
count: int
search_path: str # Directory that was searched
GrepContentMatch / GrepContentResult
@dataclass
class GrepContentMatch:
file: str
line_number: Optional[int]
line: str
before_context: list[str]
after_context: list[str]
@dataclass
class GrepContentResult:
matches: list[GrepContentMatch]
total_matches: int
GrepFilesResult
@dataclass
class GrepFilesResult:
files: list[str]
count: int
GrepCountEntry / GrepCountResult
@dataclass
class GrepCountEntry:
file: str
count: int
@dataclass
class GrepCountResult:
counts: list[GrepCountEntry]
total: int
bu-agent-sdk Integration
The SDK provides two ways to use Lumina tools with bu-agent-sdk:
Using make_tools()
The make_tools() function creates tool-compatible async functions from a LuminaToolClient:
import asyncio
from bu_agent_sdk import Agent
from bu_agent_sdk.llm import ChatAnthropic
from lumina_sandbox import LuminaToolClient
from lumina_sandbox.tools import make_tools
async def main():
client = LuminaToolClient(
base_url="http://localhost:8080",
token="your-api-key",
lumina_name="my-sandbox",
)
tools = make_tools(client)
# tools = {"Read": fn, "Write": fn, "Edit": fn, "Glob": fn,
# "Grep": fn, "Bash": fn, "BashOutput": fn, "KillBash": fn}
agent = Agent(
llm=ChatAnthropic(model="claude-sonnet-4-20250514"),
tools=list(tools.values()),
)
result = await agent.query("Read /home/user/main.py and add type hints")
print(result)
await client.close()
asyncio.run(main())
The returned tool functions accept the same parameters as the LuminaToolClient methods and return plain dicts (for agent SDK compatibility).
Demo Agent
The demo_agent module provides a complete, ready-to-run agent with 9 tools (8 Lumina tools + done):
from lumina_sandbox.demo_agent import create_agent, run
# Create agent (configures tools, LLM, etc.)
agent = create_agent()
# Run a task
import asyncio
result = asyncio.run(run("Read /home/user/main.py and refactor the main function"))
From the command line:
export LUMINA_URL=http://localhost:8080
export LUMINA_TOKEN=your-api-key
export LUMINA_NAME=my-sandbox
export ANTHROPIC_API_KEY=sk-ant-...
python -m lumina_sandbox.demo_agent "List all Python files and count total lines of code"
Environment variables:
| Variable | Required | Default | Description |
|---|---|---|---|
LUMINA_URL |
no | http://localhost:8080 |
Lumina server URL |
LUMINA_TOKEN |
yes | API key for authentication | |
LUMINA_NAME |
yes | Sandbox name | |
ANTHROPIC_API_KEY |
yes | Anthropic API key | |
AGENT_MODEL |
no | claude-sonnet-4-20250514 |
Model to use |
Building a Custom Agent
import asyncio
import os
from bu_agent_sdk import Agent
from bu_agent_sdk.agent import TaskComplete
from bu_agent_sdk.tools import tool
from bu_agent_sdk.llm import ChatAnthropic
from lumina_sandbox import LuminaToolClient
from lumina_sandbox.tools import make_tools
async def main():
client = LuminaToolClient(
base_url=os.environ.get("LUMINA_URL", "http://localhost:8080"),
token=os.environ["LUMINA_TOKEN"],
lumina_name=os.environ["LUMINA_NAME"],
)
# Get the standard Lumina tools
lumina_tools = make_tools(client)
# Add custom tools
@tool("Signal task completion with a summary message.")
async def done(message: str) -> str:
raise TaskComplete(message)
@tool("Search the web for documentation or answers.")
async def web_search(query: str) -> str:
# Your custom implementation
return f"Search results for: {query}"
# Combine all tools
all_tools = list(lumina_tools.values()) + [done, web_search]
agent = Agent(
llm=ChatAnthropic(model="claude-sonnet-4-20250514"),
tools=all_tools,
system_prompt=(
"You are a coding assistant with access to a Linux sandbox. "
"You can read, write, and edit files, run bash commands, "
"and search the web. When done, call the done tool."
),
require_done_tool=True,
)
result = await agent.query("Set up a Flask web server with tests")
print(result)
await client.close()
asyncio.run(main())
Interactive Chat CLI
The SDK includes a full interactive chat interface that manages the sandbox lifecycle automatically:
# Required environment variables
export LUMINA_HOST=http://localhost:8080 # or LUMINA_URL
export LUMINA_TOKEN=your-api-key
export ANTHROPIC_API_KEY=sk-ant-...
# Launch the chat
python -m lumina_sandbox
The chat CLI will:
- Create a temporary sandbox (named
chat-<random>) - Present an interactive prompt where you type tasks
- Stream the agent's tool calls and responses with colored output
- Destroy the sandbox on exit
Reuse an existing sandbox (sandbox will NOT be destroyed on exit):
export LUMINA_SANDBOX_NAME=my-sandbox
python -m lumina_sandbox
Sample session:
╔══════════════════════════════════════════════════╗
║ Lumina Sandbox — Interactive Chat ║
╚══════════════════════════════════════════════════╝
Server: http://localhost:8080
Sandbox: chat-a1b2c3d4
you > Create a Python project with a calculator module and tests
● bash_cmd(command="mkdir -p /home/user/calc && cd /home/user/calc")
→ ...
● write_file(file_path="/home/user/calc/calculator.py", content="...")
→ {'message': 'ok', 'bytes_written': 340, ...}
● write_file(file_path="/home/user/calc/test_calculator.py", content="...")
→ {'message': 'ok', 'bytes_written': 520, ...}
● bash_cmd(command="cd /home/user/calc && python -m pytest -v")
→ test_calculator.py::test_add PASSED\ntest_calculator.py::test_sub...
agent > Created a calculator project at /home/user/calc with add, subtract,
multiply, and divide functions. All 8 tests pass.
you > exit
Environment variables:
| Variable | Required | Default | Description |
|---|---|---|---|
LUMINA_HOST |
no | http://localhost:8080 |
Lumina server URL |
LUMINA_TOKEN |
yes | API key | |
ANTHROPIC_API_KEY |
yes | Anthropic API key | |
ANTHROPIC_BASE_URL |
no | Custom Anthropic API base URL (for proxies) | |
AGENT_MODEL |
no | claude-sonnet-4-20250514 |
Model to use |
LUMINA_SANDBOX_NAME |
no | Reuse existing sandbox (skip create/destroy) |
Advanced Usage
Tool Sessions
Every LuminaToolClient instance has a tool_session_id that tracks server-side state:
# Auto-generated session ID
client = LuminaToolClient(base_url="...", token="...", lumina_name="sandbox")
print(client.tool_session_id) # "550e8400-e29b-41d4-..."
# Explicit session ID (e.g., to resume a previous session)
client = LuminaToolClient(
base_url="...", token="...", lumina_name="sandbox",
tool_session_id="my-session-id",
)
Session state includes:
- Read file tracking: Which files have been read (for write/edit safety checks)
- Tmux session: Persistent shell with environment variables and working directory
- Background commands: Running processes and their output buffers
Sessions are bound to a (api_key, sandbox) pair. Using the same session ID with a different API key or sandbox returns an error. Sessions expire after 8 hours.
Read-Before-Write Safety
The server enforces a read-before-write pattern: you cannot overwrite an existing file unless you've read it in the current session. This prevents agents from accidentally destroying file contents they haven't seen.
# New file: Write succeeds without prior Read
await client.write("/home/user/new_file.txt", "content") # OK
# Existing file: Must Read first
try:
await client.write("/home/user/new_file.txt", "updated")
except ToolError as e:
print(e.status_code) # 409
await client.read("/home/user/new_file.txt") # Track the read
await client.write("/home/user/new_file.txt", "updated") # Now OK
# Different session = different tracking
client2 = LuminaToolClient(base_url="...", token="...", lumina_name="sandbox")
try:
await client2.write("/home/user/new_file.txt", "from other session")
except ToolError as e:
print(e.status_code) # 409 - this session hasn't read the file
Background Commands
Use background execution for long-running processes (servers, builds, watchers):
# Start a dev server in the background
result = await client.bash("cd /home/user/app && npm run dev", run_in_background=True)
server_id = result.shell_id
# Wait for it to start
import asyncio
await asyncio.sleep(3)
# Check if it's running and see output
status = await client.bash_output(server_id)
print(f"Status: {status.status}") # "running"
print(f"Output: {status.output}") # "Server started on port 3000\n"
# Do some testing
result = await client.bash("curl -s http://localhost:3000/health")
print(result.output) # "ok"
# Subsequent bash_output calls return only NEW output (incremental)
status = await client.bash_output(server_id)
print(f"New output: {status.output}") # Only output since last check
# Filter output to specific lines
status = await client.bash_output(server_id, filter="ERROR")
# Kill when done
await client.kill_bash(server_id)
Environment Persistence
The Bash tool uses tmux sessions under the hood, so shell state persists:
# Set environment variables
await client.bash("export NODE_ENV=production")
await client.bash("export PATH=$HOME/.local/bin:$PATH")
# They persist in subsequent calls
result = await client.bash("echo $NODE_ENV")
print(result.output) # "production\n"
# Working directory also persists
await client.bash("cd /home/user/project/src")
result = await client.bash("pwd")
print(result.output) # "/home/user/project/src\n"
# Shell aliases persist too
await client.bash("alias ll='ls -la'")
result = await client.bash("ll")
print(result.output) # detailed directory listing
API Reference
LuminaToolClient
| Method | Parameters | Returns | Description |
|---|---|---|---|
create (classmethod) |
base_url, token, name, eager?, ... |
LuminaToolClient |
Create sandbox + client |
create_sandbox |
eager? |
dict |
Create the sandbox |
list_sandboxes |
list[dict] |
List all sandboxes | |
restart_sandbox |
dict |
Restart sandbox container | |
destroy_sandbox |
None |
Destroy sandbox | |
read |
file_path, offset?, limit? |
TextFileResult | ImageFileResult | PDFFileResult |
Read a file |
write |
file_path, content |
WriteResult |
Write a file |
edit |
file_path, old_string, new_string, replace_all? |
EditResult |
Edit via string replacement |
glob |
pattern, path? |
GlobResult |
Search for files by glob |
grep |
pattern, path?, glob?, output_mode?, ... |
GrepContentResult | GrepFilesResult | GrepCountResult |
Search file contents |
bash |
command, timeout?, description?, run_in_background? |
BashResult |
Execute shell command |
bash_output |
bash_id, filter? |
BashOutputResult |
Poll background command |
kill_bash |
shell_id |
dict |
Kill background command |
close |
None |
Close HTTP client |
make_tools()
from lumina_sandbox.tools import make_tools
tools = make_tools(client) # Returns dict[str, Callable]
# Keys: "Read", "Write", "Edit", "Glob", "Grep", "Bash", "BashOutput", "KillBash"
Each tool function returns a plain dict (converted from the dataclass via .__dict__), suitable for direct use with bu-agent-sdk.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lumina_sandbox-0.1.1.tar.gz.
File metadata
- Download URL: lumina_sandbox-0.1.1.tar.gz
- Upload date:
- Size: 277.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
723b7a5f6e71331298dde1df2fa3056b4381fa11106586e0edb3ba5e5d9ad0a2
|
|
| MD5 |
39d0cc8f88d6789f09268cb3477ad76f
|
|
| BLAKE2b-256 |
a2f9eeed634d119649b1fdffb41f570704d24e7c7e8077e70b720431a590eb31
|
File details
Details for the file lumina_sandbox-0.1.1-py3-none-any.whl.
File metadata
- Download URL: lumina_sandbox-0.1.1-py3-none-any.whl
- Upload date:
- Size: 24.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a3d7895c0de2eafb270502bdccb092fd6393b75097c32f3770d0be438e9d3325
|
|
| MD5 |
844b5ef4fe662a009a9abe5ff81ddf68
|
|
| BLAKE2b-256 |
c9675622907410e92653f75fd2c7c194ce791e45eeb3d984033c83b62affa27b
|