Marimo Zero Deploy commited on
Commit
8ab334f
·
1 Parent(s): 83e7086

Deploy Marimo Zero 2026-03-24 23:38:58 UTC

Browse files
Files changed (11) hide show
  1. .dockerignore +13 -0
  2. .gitignore +6 -0
  3. Dockerfile +70 -0
  4. README.md +109 -3
  5. config.toml +52 -0
  6. entrypoint.sh +134 -0
  7. marimo_sandbox.py +147 -0
  8. nginx.conf +57 -0
  9. omniroute-accounts.json +12 -0
  10. proxy_server.py +94 -0
  11. static/index.html +337 -0
.dockerignore ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .git
2
+ .gitignore
3
+ *.md
4
+ !README.md
5
+ .env.example
6
+ Dockerfile
7
+ entrypoint.sh
8
+ DEPLOY_HF.md
9
+ STRUCTURE.md
10
+ README.md
11
+ README.spaces.md
12
+ deploy.py
13
+ deploy-to-hf.sh
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ *.pyc
2
+ __pycache__/
3
+ *.log
4
+ .env
5
+ *.swp
6
+ *.swo
Dockerfile ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Marimo Zero - Hugging Face Spaces Dockerfile
2
+ # Optimized for HF Spaces deployment
3
+
4
+ FROM python:3.11-slim
5
+
6
+ # 1. System Dependencies
7
+ RUN apt-get update && \
8
+ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
9
+ curl git bash jq procps net-tools nodejs npm ca-certificates nginx \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ # 2. Install Python Dependencies
13
+ RUN pip install --no-cache-dir \
14
+ marimo \
15
+ huggingface_hub \
16
+ tomli \
17
+ tomli-w \
18
+ fastapi \
19
+ uvicorn \
20
+ requests
21
+
22
+ # 3. Install ZeroClaw (Agent Zero)
23
+ RUN curl -fsSL "https://github.com/zeroclaw-labs/zeroclaw/releases/latest/download/zeroclaw-x86_64-unknown-linux-gnu.tar.gz" \
24
+ -o zeroclaw.tar.gz && \
25
+ tar -xzf zeroclaw.tar.gz && \
26
+ mv zeroclaw /usr/local/bin/zeroclaw && \
27
+ chmod +x /usr/local/bin/zeroclaw && \
28
+ rm zeroclaw.tar.gz
29
+
30
+ # 4. Install OmniRoute
31
+ RUN npm install -g omniroute
32
+
33
+ # 5. Setup Workspace
34
+ WORKDIR /app
35
+ RUN mkdir -p /app/workspace /app/.zeroclaw /app/.omniroute /var/log/marimo-zero && \
36
+ chmod -R 777 /app/workspace /app/.zeroclaw /app/.omniroute /var/log/marimo-zero
37
+
38
+ # 6. Copy Config Files
39
+ COPY config.toml /app/.zeroclaw/config.toml
40
+ COPY omniroute-accounts.json /app/.omniroute/accounts.json
41
+
42
+ # 7. Copy Marimo Notebook
43
+ COPY marimo_sandbox.py /app/marimo_sandbox.py
44
+
45
+ # 8. Copy Static UI
46
+ COPY marimo-zero-ui.html /app/static/index.html
47
+ COPY nginx.conf /etc/nginx/nginx.conf
48
+
49
+ # 9. Copy Proxy Server (routes all ports through 7860)
50
+ COPY proxy_server.py /app/proxy_server.py
51
+
52
+ # 10. Copy Entrypoint
53
+ COPY entrypoint-hf.sh /app/entrypoint.sh
54
+ RUN chmod +x /app/entrypoint.sh
55
+
56
+ # 11. HF Spaces Environment
57
+ ENV PORT=7860
58
+ ENV MARIMO_PORT=8080
59
+ ENV OMNIROUTE_PORT=20128
60
+ ENV WORKSPACE=/app/workspace
61
+ ENV HOME=/app
62
+ ENV HF_SPACE=true
63
+
64
+ # 12. Expose single port (HF Spaces requirement)
65
+ EXPOSE 7860
66
+
67
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
68
+ CMD curl -f http://localhost:7860/health || exit 1
69
+
70
+ ENTRYPOINT ["/app/entrypoint.sh"]
README.md CHANGED
@@ -1,10 +1,116 @@
1
  ---
2
  title: Marimo Zero
3
- emoji: 👀
4
  colorFrom: blue
5
- colorTo: green
6
  sdk: docker
7
  pinned: false
 
 
 
 
 
 
 
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Marimo Zero
3
+ emoji: 🦀
4
  colorFrom: blue
5
+ colorTo: purple
6
  sdk: docker
7
  pinned: false
8
+ license: mit
9
+ short_description: Agent Zero chat + Marimo sandbox with OmniRoute LLM gateway
10
+ app_port: 7860
11
+ tags:
12
+ - agent-zero
13
+ - marimo
14
+ - llm
15
+ - ai
16
+ - zeroclaw
17
+ - omniroute
18
+ - ai-assistant
19
  ---
20
 
21
+ <div align="center">
22
+ <img src="https://via.placeholder.com/720x400/1a1a2e/00d9ff?text=Marimo+Zero" alt="Marimo Zero" width="720"/>
23
+ <br/><br/>
24
+ <strong>Agent Zero + Marimo Sandbox</strong>
25
+ <br/>
26
+ <sub>Chat-first AI with toggleable code execution environment</sub>
27
+ <br/><br/>
28
+
29
+ [![Agent Zero](https://img.shields.io/badge/Agent%20Zero-Powered-blue)](https://github.com/zeroclaw-labs/zeroclaw)
30
+ [![Marimo](https://img.shields.io/badge/Marimo-Reactive-orange)](https://marimo.io)
31
+ [![OmniRoute](https://img.shields.io/badge/OmniRoute-Multi--Account-green)](https://github.com/omniroute/omniroute)
32
+ </div>
33
+
34
+ ---
35
+
36
+ ## Architecture
37
+
38
+ This Space uses a custom Docker image with **Agent Zero**, **Marimo**, and **OmniRoute**.
39
+
40
+ - **🤖 Agent Zero:** Fast Rust-based AI agent framework
41
+ - **📓 Marimo:** Reactive Python notebook for code execution
42
+ - **🔀 OmniRoute:** Multi-account LLM routing with automatic failover
43
+ - **🔒 Secure:** All API keys managed via HF Spaces secrets
44
+
45
+ ## Quick Start
46
+
47
+ 1. **Duplicate this Space**
48
+ 2. **Set Secrets:**
49
+ - `API_KEYS_JSON`: JSON with your LLM API keys (see below)
50
+ 3. **Wait for build** (~5-10 minutes)
51
+ 4. **Start chatting!**
52
+
53
+ ### API Keys Format
54
+
55
+ Set `API_KEYS_JSON` in **Space Settings → Repository secrets**:
56
+
57
+ ```json
58
+ {
59
+ "openai": "sk-...",
60
+ "anthropic": "sk-ant-...",
61
+ "nvidia": "nvapi-...",
62
+ "groq": "gsk_..."
63
+ }
64
+ ```
65
+
66
+ Supports key rotation: `openai_2`, `openai_3`, etc.
67
+
68
+ ## Features
69
+
70
+ ### Agent Zero Chat
71
+ - Natural language conversations
72
+ - Tool calling (file operations, HTTP requests)
73
+ - Persistent memory per project
74
+ - Code generation with execution context
75
+
76
+ ### Marimo Sandbox (Toggleable)
77
+ - **Code Editor:** Write and execute Python code
78
+ - **File Browser:** View workspace files
79
+ - **File Editor:** Create/edit files directly
80
+ - **Reactive:** Changes propagate instantly
81
+
82
+ ### OmniRoute Integration
83
+ - Multiple LLM account pooling
84
+ - Automatic failover between accounts
85
+ - Support for OpenAI, Anthropic, NVIDIA, Groq
86
+ - Local proxy for privacy
87
+
88
+ ## Routes
89
+
90
+ | Path | Service | Description |
91
+ |------|---------|-------------|
92
+ | `/` | Main UI | Agent Zero chat + Marimo toggle |
93
+ | `/chat/*` | Agent Zero | Chat API |
94
+ | `/sandbox/*` | Marimo | Sandbox notebook |
95
+ | `/api/*` | OmniRoute | LLM proxy |
96
+ | `/health` | Healthcheck | HF Spaces monitoring |
97
+
98
+ ## Workspace
99
+
100
+ All files are stored in `/app/workspace` and persist across restarts:
101
+
102
+ ```
103
+ /workspace
104
+ ├── projects/ # Agent Zero project data
105
+ ├── memory/ # Persistent memory files
106
+ ├── code/ # Generated code
107
+ └── files/ # User files
108
+ ```
109
+
110
+ ## Development
111
+
112
+ See full documentation: [GitHub Repository](https://github.com/YOUR_REPO/marimo-zero)
113
+
114
+ ## License
115
+
116
+ MIT
config.toml ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ZeroClaw / Agent Zero Configuration
2
+ # Uses OmniRoute as the LLM provider
3
+
4
+ default_provider = "omniroute"
5
+ default_model = "gpt-4"
6
+ default_temperature = 0.7
7
+
8
+ [gateway]
9
+ host = "0.0.0.0"
10
+ port = 80
11
+ allow_public_bind = true
12
+ require_pairing = false
13
+
14
+ [skills]
15
+ open_skills_enabled = true
16
+
17
+ [memory]
18
+ backend = "sqlite"
19
+ auto_save = true
20
+
21
+ # OmniRoute Provider
22
+ [providers.omniroute]
23
+ api_url = "http://localhost:20128/v1"
24
+ api_key = "sk-omniroute"
25
+
26
+ [browser]
27
+ enabled = false
28
+
29
+ [http_request]
30
+ enabled = true
31
+ allowed_domains = ["*"]
32
+
33
+ [agents.primary]
34
+ provider = "omniroute"
35
+ model = "gpt-4"
36
+ system_prompt = """
37
+ You are an AI assistant running in Marimo Zero.
38
+
39
+ WORKSPACE:
40
+ - All files are stored in /app/workspace
41
+ - You share this workspace with the Marimo sandbox
42
+
43
+ MARIMO SANDBOX:
44
+ - Users can toggle a Marimo notebook panel for code execution
45
+ - Code written in Marimo runs in the same container
46
+ - Files created in Marimo are visible to you and vice versa
47
+
48
+ CAPABILITIES:
49
+ - Write and execute code via Marimo sandbox
50
+ - Read/write files in /app/workspace
51
+ - Use tools for file operations, HTTP requests, etc.
52
+ """
entrypoint.sh ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ echo "🚀 Starting Marimo Zero for Hugging Face Spaces..."
5
+
6
+ # HF Spaces provides these env vars:
7
+ # - HF_SPACE_ID (e.g., "username/space-name")
8
+ # - HF_TOKEN (for API access)
9
+ # - API_KEYS_JSON (single JSON secret with all provider keys)
10
+
11
+ # Parse API_KEYS_JSON (same as VerdantClaw)
12
+ if [ -n "$API_KEYS_JSON" ]; then
13
+ echo "📝 Parsing API keys from JSON..."
14
+
15
+ # Extract individual keys
16
+ export OPENAI_API_KEY=$(echo $API_KEYS_JSON | jq -r '.openai // empty')
17
+ export ANTHROPIC_API_KEY=$(echo $API_KEYS_JSON | jq -r '.anthropic // empty')
18
+ export NVIDIA_API_KEY=$(echo $API_KEYS_JSON | jq -r '.nvidia // empty')
19
+ export GROQ_API_KEY=$(echo $API_KEYS_JSON | jq -r '.groq // empty')
20
+ export GOOGLE_API_KEY=$(echo $API_KEYS_JSON | jq -r '.google // empty')
21
+
22
+ # Support key rotation (openai_2, openai_3, etc.)
23
+ for provider in openai anthropic nvidia groq google; do
24
+ for i in $(seq 2 5); do
25
+ val=$(echo $API_KEYS_JSON | jq -r ".${provider}_${i} // empty")
26
+ if [ -n "$val" ] && [ "$val" != "null" ]; then
27
+ envname=$(echo "${provider}_API_KEY_${i}" | tr '[:lower:]' '[:upper:]')
28
+ export "$envname=$val"
29
+ echo "✓ Loaded rotation key: $envname"
30
+ fi
31
+ done
32
+ done
33
+
34
+ echo "✓ API keys loaded from API_KEYS_JSON"
35
+ else
36
+ echo "⚠️ WARNING: API_KEYS_JSON not set!"
37
+ fi
38
+
39
+ # 1. Start OmniRoute (LLM Gateway)
40
+ echo "📡 Starting OmniRoute on port $OMNIROUTE_PORT..."
41
+
42
+ # Build accounts.json from parsed env vars
43
+ cat > /app/.omniroute/accounts.json << EOF
44
+ {
45
+ "accounts": [
46
+ EOF
47
+
48
+ first=true
49
+ if [ -n "$OPENAI_API_KEY" ]; then
50
+ cat >> /app/.omniroute/accounts.json << EOF
51
+ {"name": "openai", "provider": "openai", "api_key": "$OPENAI_API_KEY", "default": true}
52
+ EOF
53
+ first=false
54
+ fi
55
+
56
+ if [ -n "$ANTHROPIC_API_KEY" ]; then
57
+ if [ "$first" = false ]; then echo "," >> /app/.omniroute/accounts.json; fi
58
+ cat >> /app/.omniroute/accounts.json << EOF
59
+ {"name": "anthropic", "provider": "anthropic", "api_key": "$ANTHROPIC_API_KEY", "default": false}
60
+ EOF
61
+ first=false
62
+ fi
63
+
64
+ if [ -n "$NVIDIA_API_KEY" ]; then
65
+ if [ "$first" = false ]; then echo "," >> /app/.omniroute/accounts.json; fi
66
+ cat >> /app/.omniroute/accounts.json << EOF
67
+ {"name": "nvidia", "provider": "nvidia", "api_key": "$NVIDIA_API_KEY", "default": false}
68
+ EOF
69
+ first=false
70
+ fi
71
+
72
+ if [ -n "$GROQ_API_KEY" ]; then
73
+ if [ "$first" = false ]; then echo "," >> /app/.omniroute/accounts.json; fi
74
+ cat >> /app/.omniroute/accounts.json << EOF
75
+ {"name": "groq", "provider": "groq", "api_key": "$GROQ_API_KEY", "default": false}
76
+ EOF
77
+ first=false
78
+ fi
79
+
80
+ cat >> /app/.omniroute/accounts.json << EOF
81
+ ]
82
+ }
83
+ EOF
84
+
85
+ echo "✓ Generated accounts.json from API_KEYS_JSON"
86
+
87
+ omniroute start --port $OMNIROUTE_PORT --accounts /app/.omniroute/accounts.json &
88
+ OMNI_PID=$!
89
+ sleep 3
90
+
91
+ # 2. Start Agent Zero (Main Chat UI)
92
+ echo "🤖 Starting Agent Zero on port 80..."
93
+ zeroclaw gateway --host 127.0.0.1 --port 80 --config /app/.zeroclaw/config.toml &
94
+ AZ_PID=$!
95
+ sleep 5
96
+
97
+ # 3. Start Marimo (Sandbox)
98
+ echo "📓 Starting Marimo on port $MARIMO_PORT..."
99
+ marimo run /app/marimo_sandbox.py --host 127.0.0.1 --port $MARIMO_PORT --headless &
100
+ MARIMO_PID=$!
101
+ sleep 3
102
+
103
+ # 4. Start Proxy Server (exposes everything on port 7860)
104
+ echo "🔀 Starting proxy server on port 7860 (HF Spaces public port)..."
105
+ python3 /app/proxy_server.py &
106
+ PROXY_PID=$!
107
+ sleep 2
108
+
109
+ echo ""
110
+ echo "✅ Marimo Zero Ready for Hugging Face Spaces!"
111
+ echo " ─────────────────────────────────────"
112
+ echo " Public URL: https://huggingface.co/spaces/$HF_SPACE_ID"
113
+ echo " ─────────────────────────────────────"
114
+ echo " Routes:"
115
+ echo " / → Main UI (Agent Zero + Marimo toggle)"
116
+ echo " /chat/* → Agent Zero API"
117
+ echo " /sandbox/* → Marimo Sandbox"
118
+ echo " /api/* → OmniRoute LLM API"
119
+ echo " /health → Healthcheck"
120
+ echo " ─────────────────────────────────────"
121
+ echo ""
122
+ echo " Processes: OmniRoute($OMNI_PID) AgentZero($AZ_PID) Marimo($MARIMO_PID) Proxy($PROXY_PID)"
123
+ echo ""
124
+
125
+ # Monitor processes
126
+ while true; do
127
+ for pid in $OMNI_PID $AZ_PID $MARIMO_PID $PROXY_PID; do
128
+ if ! kill -0 $pid 2>/dev/null; then
129
+ echo "❌ Process $pid died, exiting..."
130
+ exit 1
131
+ fi
132
+ done
133
+ sleep 5
134
+ done
marimo_sandbox.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = ["marimo"]
4
+ # ///
5
+
6
+ import marimo
7
+
8
+ __generated_with = "0.17.6"
9
+ app = marimo.App()
10
+
11
+
12
+ @app.cell
13
+ def _():
14
+ import marimo as mo
15
+ import os
16
+
17
+ WORKSPACE = os.environ.get("WORKSPACE", "/app/workspace")
18
+ return WORKSPACE, mo
19
+
20
+
21
+ @app.cell
22
+ def _(mo):
23
+ mo.md("# 🦀 Marimo Sandbox")
24
+ mo.md("Toggle this panel from Agent Zero chat UI")
25
+ mo.md("---")
26
+ return
27
+
28
+
29
+ @app.cell
30
+ def _(mo):
31
+ """Code Editor Cell"""
32
+ mo.md("### 💻 Code Editor")
33
+
34
+ code_editor = mo.ui.code_editor(
35
+ language="python",
36
+ value="# Write your code here\nprint('Hello from Marimo Zero!')",
37
+ label=""
38
+ )
39
+
40
+ def run_code(code):
41
+ import sys
42
+ from io import StringIO
43
+ old_stdout = sys.stdout
44
+ sys.stdout = buf = StringIO()
45
+ try:
46
+ exec(code, {}, {})
47
+ output = buf.getvalue()
48
+ if output:
49
+ return mo.md(f"**Output:**\n```\n{output}\n```")
50
+ else:
51
+ return mo.md("✅ Code executed (no output)")
52
+ except Exception as e:
53
+ return mo.md(f"❌ Error:\n```\n{str(e)}\n```")
54
+ finally:
55
+ sys.stdout = old_stdout
56
+
57
+ result = run_code(code_editor.value)
58
+
59
+ mo.vstack([
60
+ code_editor,
61
+ mo.ui.run_button(label="▶️ Run"),
62
+ result
63
+ ])
64
+ return
65
+
66
+
67
+ @app.cell
68
+ def _(mo, WORKSPACE):
69
+ """File Browser Cell"""
70
+ mo.md("### 📁 Workspace Files")
71
+
72
+ import os
73
+
74
+ def list_files():
75
+ if not os.path.exists(WORKSPACE):
76
+ return mo.md("⚠️ Workspace not found")
77
+
78
+ files = os.listdir(WORKSPACE)
79
+ if not files:
80
+ return mo.md("📭 Empty workspace")
81
+
82
+ file_list = "\n".join([f"- 📄 `{f}`" for f in sorted(files)])
83
+ return mo.md(file_list)
84
+
85
+ list_files()
86
+ return
87
+
88
+
89
+ @app.cell
90
+ def _(mo, WORKSPACE):
91
+ """File Editor Cell"""
92
+ mo.md("### ✏️ File Editor")
93
+
94
+ import os
95
+
96
+ filename = mo.ui.text(
97
+ label="Filename:",
98
+ value="example.md",
99
+ full_width=False
100
+ )
101
+
102
+ filepath = os.path.join(WORKSPACE, filename.value)
103
+
104
+ def read_file(name):
105
+ path = os.path.join(WORKSPACE, name)
106
+ if os.path.exists(path):
107
+ with open(path, 'r') as f:
108
+ return f.read()
109
+ return "# New file\n\nStart writing..."
110
+
111
+ content = mo.ui.text_area(
112
+ label="Content:",
113
+ value=read_file(filename.value),
114
+ full_width=True,
115
+ height=300
116
+ )
117
+
118
+ def save_file(name, text):
119
+ path = os.path.join(WORKSPACE, name)
120
+ with open(path, 'w') as f:
121
+ f.write(text)
122
+ return mo.md(f"✅ Saved to `{path}`")
123
+
124
+ mo.vstack([
125
+ filename,
126
+ content,
127
+ mo.ui.run_button(label="💾 Save"),
128
+ save_file(filename.value, content.value)
129
+ ])
130
+ return
131
+
132
+
133
+ @app.cell
134
+ def _(mo):
135
+ """Terminal Output Cell"""
136
+ mo.md("### 📊 Output")
137
+
138
+ mo.md("""
139
+ View execution results, plots, and outputs here.
140
+
141
+ **Tip:** Use `print()` in code cells to see output here.
142
+ """)
143
+ return
144
+
145
+
146
+ if __name__ == "__main__":
147
+ app.run()
nginx.conf ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ events {
2
+ worker_connections 1024;
3
+ }
4
+
5
+ http {
6
+ upstream agent_zero {
7
+ server localhost:80;
8
+ }
9
+
10
+ upstream marimo {
11
+ server localhost:8080;
12
+ }
13
+
14
+ upstream omniroute {
15
+ server localhost:20128;
16
+ }
17
+
18
+ server {
19
+ listen 7860;
20
+
21
+ # Health check
22
+ location /health {
23
+ return 200 '{"status":"healthy"}';
24
+ add_header Content-Type application/json;
25
+ }
26
+
27
+ # Main UI
28
+ location / {
29
+ root /app/static;
30
+ try_files $uri $uri/ /index.html;
31
+ }
32
+
33
+ # Agent Zero
34
+ location /chat/ {
35
+ proxy_pass http://agent_zero/;
36
+ proxy_http_version 1.1;
37
+ proxy_set_header Upgrade $http_upgrade;
38
+ proxy_set_header Connection "upgrade";
39
+ proxy_set_header Host $host;
40
+ }
41
+
42
+ # Marimo Sandbox
43
+ location /sandbox/ {
44
+ proxy_pass http://marimo/;
45
+ proxy_http_version 1.1;
46
+ proxy_set_header Upgrade $http_upgrade;
47
+ proxy_set_header Connection "upgrade";
48
+ proxy_set_header Host $host;
49
+ }
50
+
51
+ # OmniRoute API
52
+ location /api/ {
53
+ proxy_pass http://omniroute/;
54
+ proxy_set_header Host $host;
55
+ }
56
+ }
57
+ }
omniroute-accounts.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_comment": "This file is auto-generated from API_KEYS_JSON env var at startup",
3
+ "_note": "For HF Spaces, set API_KEYS_JSON as a Space secret",
4
+ "_example": {
5
+ "openai": "sk-...",
6
+ "anthropic": "sk-ant-...",
7
+ "nvidia": "nvapi-...",
8
+ "groq": "gsk_...",
9
+ "openai_2": "sk-..."
10
+ },
11
+ "accounts": []
12
+ }
proxy_server.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # HF Spaces requires all traffic through port 7860
2
+ # This proxy routes:
3
+ # /chat/* → Agent Zero (port 80)
4
+ # /sandbox/* → Marimo (port 8080)
5
+ # /api/* → OmniRoute (port 20128)
6
+
7
+ from fastapi import FastAPI, Request, HTTPException
8
+ from fastapi.responses import HTMLResponse, StreamingResponse
9
+ from fastapi.staticfiles import StaticFiles
10
+ import httpx
11
+ import asyncio
12
+
13
+ app = FastAPI()
14
+
15
+ # Service ports (internal)
16
+ AGENT_ZERO_PORT = 80
17
+ MARIMO_PORT = 8080
18
+ OMNIROUTE_PORT = 20128
19
+ HF_PORT = 7860
20
+
21
+ # HTTP client with connection pooling
22
+ http_client = httpx.AsyncClient(timeout=60.0)
23
+
24
+
25
+ @app.get("/health")
26
+ async def health():
27
+ """HF Spaces healthcheck endpoint"""
28
+ return {"status": "healthy", "service": "marimo-zero"}
29
+
30
+
31
+ @app.get("/")
32
+ async def root():
33
+ """Serve main UI"""
34
+ try:
35
+ with open("/app/static/index.html", "r") as f:
36
+ return HTMLResponse(content=f.read())
37
+ except FileNotFoundError:
38
+ return HTMLResponse("<h1>Marimo Zero</h1><p>UI not found</p>", status_code=404)
39
+
40
+
41
+ @app.api_route("/chat/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
42
+ async def proxy_agent_zero(request: Request, path: str):
43
+ """Proxy to Agent Zero"""
44
+ return await proxy_request(AGENT_ZERO_PORT, path, request)
45
+
46
+
47
+ @app.api_route("/sandbox/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
48
+ async def proxy_marimo(request: Request, path: str):
49
+ """Proxy to Marimo"""
50
+ return await proxy_request(MARIMO_PORT, path, request)
51
+
52
+
53
+ @app.api_route("/api/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
54
+ async def proxy_omniroute(request: Request, path: str):
55
+ """Proxy to OmniRoute"""
56
+ return await proxy_request(OMNIROUTE_PORT, path, request)
57
+
58
+
59
+ async def proxy_request(target_port: int, path: str, request: Request):
60
+ """Generic proxy handler"""
61
+ url = f"http://localhost:{target_port}/{path}"
62
+
63
+ try:
64
+ # Forward headers
65
+ headers = dict(request.headers)
66
+ headers.pop("host", None)
67
+ headers.pop("content-length", None)
68
+
69
+ # Get body
70
+ body = await request.body() if request.method in ["POST", "PUT"] else None
71
+
72
+ # Make request
73
+ response = await http_client.request(
74
+ method=request.method,
75
+ url=url,
76
+ headers=headers,
77
+ content=body
78
+ )
79
+
80
+ # Return response
81
+ return StreamingResponse(
82
+ response.aiter_bytes(),
83
+ status_code=response.status_code,
84
+ headers=dict(response.headers)
85
+ )
86
+ except httpx.ConnectError as e:
87
+ raise HTTPException(status_code=503, detail=f"Service unavailable (port {target_port})")
88
+ except Exception as e:
89
+ raise HTTPException(status_code=500, detail=str(e))
90
+
91
+
92
+ if __name__ == "__main__":
93
+ import uvicorn
94
+ uvicorn.run(app, host="0.0.0.0", port=HF_PORT)
static/index.html ADDED
@@ -0,0 +1,337 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Marimo Zero - Toggle Sandbox</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16
+ background: #1a1a2e;
17
+ color: #eee;
18
+ height: 100vh;
19
+ overflow: hidden;
20
+ }
21
+
22
+ .container {
23
+ display: flex;
24
+ height: 100vh;
25
+ }
26
+
27
+ /* Agent Zero Chat Panel */
28
+ .chat-panel {
29
+ flex: 1;
30
+ display: flex;
31
+ flex-direction: column;
32
+ border-right: 1px solid #333;
33
+ transition: all 0.3s ease;
34
+ }
35
+
36
+ .chat-panel.collapsed {
37
+ flex: 0;
38
+ opacity: 0;
39
+ pointer-events: none;
40
+ }
41
+
42
+ .chat-header {
43
+ padding: 1rem;
44
+ background: #16213e;
45
+ border-bottom: 1px solid #333;
46
+ display: flex;
47
+ justify-content: space-between;
48
+ align-items: center;
49
+ }
50
+
51
+ .chat-header h1 {
52
+ font-size: 1.2rem;
53
+ color: #00d9ff;
54
+ }
55
+
56
+ .chat-body {
57
+ flex: 1;
58
+ overflow-y: auto;
59
+ padding: 1rem;
60
+ }
61
+
62
+ .chat-input {
63
+ padding: 1rem;
64
+ background: #16213e;
65
+ border-top: 1px solid #333;
66
+ }
67
+
68
+ .chat-input textarea {
69
+ width: 100%;
70
+ padding: 0.75rem;
71
+ border: 1px solid #333;
72
+ border-radius: 8px;
73
+ background: #0f0f23;
74
+ color: #eee;
75
+ resize: none;
76
+ font-family: inherit;
77
+ }
78
+
79
+ .chat-input button {
80
+ margin-top: 0.5rem;
81
+ padding: 0.5rem 1rem;
82
+ background: #00d9ff;
83
+ color: #000;
84
+ border: none;
85
+ border-radius: 6px;
86
+ cursor: pointer;
87
+ font-weight: 600;
88
+ }
89
+
90
+ .chat-input button:hover {
91
+ background: #00b8d9;
92
+ }
93
+
94
+ /* Marimo Sandbox Panel */
95
+ .marimo-panel {
96
+ width: 50%;
97
+ display: flex;
98
+ flex-direction: column;
99
+ transition: all 0.3s ease;
100
+ }
101
+
102
+ .marimo-panel.hidden {
103
+ width: 0;
104
+ opacity: 0;
105
+ overflow: hidden;
106
+ }
107
+
108
+ .marimo-header {
109
+ padding: 1rem;
110
+ background: #16213e;
111
+ border-bottom: 1px solid #333;
112
+ display: flex;
113
+ justify-content: space-between;
114
+ align-items: center;
115
+ }
116
+
117
+ .marimo-header h2 {
118
+ font-size: 1.2rem;
119
+ color: #ff6b6b;
120
+ }
121
+
122
+ .marimo-frame {
123
+ flex: 1;
124
+ border: none;
125
+ background: #fff;
126
+ }
127
+
128
+ /* Toggle Button */
129
+ .toggle-btn {
130
+ padding: 0.5rem 1rem;
131
+ background: #ff6b6b;
132
+ color: #fff;
133
+ border: none;
134
+ border-radius: 6px;
135
+ cursor: pointer;
136
+ font-weight: 600;
137
+ transition: background 0.2s;
138
+ }
139
+
140
+ .toggle-btn:hover {
141
+ background: #ff5252;
142
+ }
143
+
144
+ .toggle-btn.active {
145
+ background: #4ecdc4;
146
+ }
147
+
148
+ /* Resize Handle */
149
+ .resize-handle {
150
+ width: 4px;
151
+ background: #333;
152
+ cursor: col-resize;
153
+ transition: background 0.2s;
154
+ }
155
+
156
+ .resize-handle:hover {
157
+ background: #00d9ff;
158
+ }
159
+
160
+ /* Message Styles */
161
+ .message {
162
+ margin-bottom: 1rem;
163
+ padding: 0.75rem;
164
+ border-radius: 8px;
165
+ max-width: 80%;
166
+ }
167
+
168
+ .message.user {
169
+ background: #00d9ff;
170
+ color: #000;
171
+ margin-left: auto;
172
+ }
173
+
174
+ .message.assistant {
175
+ background: #2a2a4a;
176
+ color: #eee;
177
+ }
178
+
179
+ .message pre {
180
+ background: #0f0f23;
181
+ padding: 0.5rem;
182
+ border-radius: 4px;
183
+ overflow-x: auto;
184
+ margin-top: 0.5rem;
185
+ }
186
+
187
+ .message code {
188
+ font-family: 'Consolas', 'Monaco', monospace;
189
+ font-size: 0.9rem;
190
+ }
191
+ </style>
192
+ </head>
193
+ <body>
194
+ <div class="container">
195
+ <!-- Agent Zero Chat Panel -->
196
+ <div class="chat-panel" id="chatPanel">
197
+ <div class="chat-header">
198
+ <h1>🤖 Agent Zero</h1>
199
+ <button class="toggle-btn" id="toggleChatBtn" onclick="toggleChat()">Hide Chat</button>
200
+ </div>
201
+ <div class="chat-body" id="chatBody">
202
+ <div class="message assistant">
203
+ <strong>Agent Zero:</strong>
204
+ <p>Welcome to Marimo Zero! I'm your AI assistant. Use the toggle button to show/hide the Marimo sandbox for code execution.</p>
205
+ </div>
206
+ </div>
207
+ <div class="chat-input">
208
+ <textarea id="chatInput" rows="3" placeholder="Type your message..."></textarea>
209
+ <button onclick="sendMessage()">Send</button>
210
+ </div>
211
+ </div>
212
+
213
+ <!-- Resize Handle -->
214
+ <div class="resize-handle" id="resizeHandle"></div>
215
+
216
+ <!-- Marimo Sandbox Panel -->
217
+ <div class="marimo-panel hidden" id="marimoPanel">
218
+ <div class="marimo-header">
219
+ <h2>📓 Marimo Sandbox</h2>
220
+ <button class="toggle-btn active" id="toggleMarimoBtn" onclick="toggleMarimo()">Hide Sandbox</button>
221
+ </div>
222
+ <iframe
223
+ class="marimo-frame"
224
+ id="marimoFrame"
225
+ src="http://localhost:8080"
226
+ sandbox="allow-scripts allow-same-origin allow-downloads"
227
+ ></iframe>
228
+ </div>
229
+ </div>
230
+
231
+ <script>
232
+ const chatPanel = document.getElementById('chatPanel');
233
+ const marimoPanel = document.getElementById('marimoPanel');
234
+ const chatBody = document.getElementById('chatBody');
235
+ const chatInput = document.getElementById('chatInput');
236
+ const resizeHandle = document.getElementById('resizeHandle');
237
+
238
+ // Toggle Marimo Sandbox
239
+ function toggleMarimo() {
240
+ marimoPanel.classList.toggle('hidden');
241
+ const btn = document.getElementById('toggleMarimoBtn');
242
+ btn.textContent = marimoPanel.classList.contains('hidden') ? 'Show Sandbox' : 'Hide Sandbox';
243
+ btn.classList.toggle('active');
244
+ }
245
+
246
+ // Toggle Chat Panel
247
+ function toggleChat() {
248
+ chatPanel.classList.toggle('collapsed');
249
+ const btn = document.getElementById('toggleChatBtn');
250
+ btn.textContent = chatPanel.classList.contains('collapsed') ? 'Show Chat' : 'Hide Chat';
251
+ }
252
+
253
+ // Send Message (simulated - replace with actual Agent Zero API call)
254
+ async function sendMessage() {
255
+ const message = chatInput.value.trim();
256
+ if (!message) return;
257
+
258
+ // Add user message
259
+ chatBody.innerHTML += `
260
+ <div class="message user">
261
+ <strong>You:</strong>
262
+ <p>${escapeHtml(message)}</p>
263
+ </div>
264
+ `;
265
+
266
+ chatInput.value = '';
267
+ chatBody.scrollTop = chatBody.scrollHeight;
268
+
269
+ // Call Agent Zero API (replace with actual endpoint)
270
+ try {
271
+ const response = await fetch('http://localhost:80/chat', {
272
+ method: 'POST',
273
+ headers: { 'Content-Type': 'application/json' },
274
+ body: JSON.stringify({ message })
275
+ });
276
+ const data = await response.json();
277
+
278
+ chatBody.innerHTML += `
279
+ <div class="message assistant">
280
+ <strong>Agent Zero:</strong>
281
+ <p>${escapeHtml(data.response || 'No response')}</p>
282
+ </div>
283
+ `;
284
+ } catch (err) {
285
+ chatBody.innerHTML += `
286
+ <div class="message assistant">
287
+ <strong>Agent Zero:</strong>
288
+ <p>⚠️ Could not connect to Agent Zero. Make sure it's running on port 80.</p>
289
+ </div>
290
+ `;
291
+ }
292
+
293
+ chatBody.scrollTop = chatBody.scrollHeight;
294
+ }
295
+
296
+ // Escape HTML
297
+ function escapeHtml(text) {
298
+ const div = document.createElement('div');
299
+ div.textContent = text;
300
+ return div.innerHTML;
301
+ }
302
+
303
+ // Resize functionality
304
+ let isResizing = false;
305
+
306
+ resizeHandle.addEventListener('mousedown', (e) => {
307
+ isResizing = true;
308
+ document.body.style.cursor = 'col-resize';
309
+ });
310
+
311
+ document.addEventListener('mousemove', (e) => {
312
+ if (!isResizing) return;
313
+
314
+ const containerWidth = document.querySelector('.container').offsetWidth;
315
+ const newMarimoWidth = ((containerWidth - e.clientX) / containerWidth) * 100;
316
+
317
+ if (newMarimoWidth > 20 && newMarimoWidth < 80) {
318
+ marimoPanel.style.width = `${newMarimoWidth}%`;
319
+ chatPanel.style.flex = `0 0 ${100 - newMarimoWidth}%`;
320
+ }
321
+ });
322
+
323
+ document.addEventListener('mouseup', () => {
324
+ isResizing = false;
325
+ document.body.style.cursor = 'default';
326
+ });
327
+
328
+ // Enter to send
329
+ chatInput.addEventListener('keydown', (e) => {
330
+ if (e.key === 'Enter' && !e.shiftKey) {
331
+ e.preventDefault();
332
+ sendMessage();
333
+ }
334
+ });
335
+ </script>
336
+ </body>
337
+ </html>