From 99058f60dbf49dd8baf5a9259d68854cd66a5493 Mon Sep 17 00:00:00 2001 From: "molecule-ai[bot]" <276602405+molecule-ai[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 20:42:33 +0000 Subject: [PATCH 1/3] devrel: gemini-cli demo script (issue #534) --- docs/marketing/devrel/gemini-cli-demo/demo.py | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 docs/marketing/devrel/gemini-cli-demo/demo.py diff --git a/docs/marketing/devrel/gemini-cli-demo/demo.py b/docs/marketing/devrel/gemini-cli-demo/demo.py new file mode 100644 index 00000000..e44f7ca5 --- /dev/null +++ b/docs/marketing/devrel/gemini-cli-demo/demo.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +""" +Gemini CLI runtime adapter — live demo +Molecule AI | feat(adapters): add gemini-cli runtime adapter (#379) + +Spins up a gemini-cli workspace, sends a task via the A2A proxy, +prints the reply, then tears down the workspace. + +Usage: + pip install httpx + export PLATFORM_URL=http://localhost:8080 + export PLATFORM_TOKEN= + export GEMINI_API_KEY= + python demo.py + +No API keys are ever hardcoded or logged. +""" + +import os +import sys +import time +import uuid + +try: + import httpx +except ImportError: + print("Missing dependency: pip install httpx") + sys.exit(1) + +# ── Config (all from environment — no hardcoded values) ────────────────────── +PLATFORM_URL = os.environ.get("PLATFORM_URL", "").rstrip("/") +PLATFORM_TOKEN = os.environ.get("PLATFORM_TOKEN", "") +GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "") + +MISSING = [k for k, v in { + "PLATFORM_URL": PLATFORM_URL, + "PLATFORM_TOKEN": PLATFORM_TOKEN, + "GEMINI_API_KEY": GEMINI_API_KEY, +}.items() if not v] +if MISSING: + print(f"Missing required env vars: {', '.join(MISSING)}") + sys.exit(1) + +HEADERS = { + "Authorization": f"Bearer {PLATFORM_TOKEN}", + "Content-Type": "application/json", +} + +TASK = ( + "List the three biggest advantages of Google Gemini 2.5 Pro " + "over GPT-4o for agentic coding tasks. One sentence each." +) + + +# ── Helpers ─────────────────────────────────────────────────────────────────── + +def step(n: int, msg: str) -> None: + print(f"\n\033[1;34m[{n}]\033[0m {msg}") + + +def die(msg: str) -> None: + print(f"\n\033[1;31m✗\033[0m {msg}") + sys.exit(1) + + +def api(method: str, path: str, **kwargs) -> dict: + """Make an authenticated request; exit on non-2xx.""" + url = f"{PLATFORM_URL}{path}" + with httpx.Client(timeout=kwargs.pop("timeout", 30)) as client: + resp = getattr(client, method)(url, headers=HEADERS, **kwargs) + if resp.status_code not in (200, 201, 204): + die(f"HTTP {resp.status_code} {method.upper()} {path}: {resp.text[:300]}") + return resp.json() if resp.content else {} + + +# ── Main ───────────────────────────────────────────────────────────────────── + +def main() -> None: + workspace_id: str | None = None + + try: + # 1. Create the gemini-cli workspace + step(1, "Creating gemini-cli workspace...") + ws = api("post", "/workspaces", json={ + "name": "gemini-cli-demo", + "role": "Molecule AI gemini-cli adapter demo", + "runtime": "gemini-cli", + "runtime_config": { + "model": "gemini-2.5-flash", # flash: faster boot for demo purposes + "timeout": 0, + }, + "tier": 2, # 2 GB / 2 vCPU + }) + workspace_id = ws["id"] + print(f" created id={workspace_id}") + + # 2. Inject GEMINI_API_KEY as a workspace-scoped secret + step(2, "Storing GEMINI_API_KEY as workspace secret (value never logged)...") + api("put", f"/workspaces/{workspace_id}/secrets", + json={"key": "GEMINI_API_KEY", "value": GEMINI_API_KEY}) + print(" secret stored") + + # 3. Wait for the workspace container to boot and register + step(3, "Waiting for workspace to come online (up to 90 s)...") + for attempt in range(30): + ws = api("get", f"/workspaces/{workspace_id}", timeout=10) + status = ws.get("status", "unknown") + print(f" {status:12s} ({attempt + 1}/30)", end="\r", flush=True) + if status == "online": + print(f"\n online in ~{attempt * 3} s") + break + if status in ("failed", "error"): + die(f"workspace entered error state: {status}") + time.sleep(3) + else: + die("timed out waiting for 'online' status") + + # 4. Send a task via the A2A proxy (JSON-RPC 2.0 over HTTP) + step(4, "Sending task via A2A proxy...") + print(f' Task: "{TASK}"') + result = api( + "post", + f"/workspaces/{workspace_id}/a2a", + json={ + "jsonrpc": "2.0", + "id": str(uuid.uuid4()), + "method": "message/send", + "params": { + "message": { + "role": "user", + "parts": [{"kind": "text", "text": TASK}], + } + }, + }, + timeout=120, # agent may take a moment to reason + ) + + # 5. Extract the text reply from the A2A response envelope + step(5, "Gemini CLI agent reply:") + try: + parts = result["result"]["status"]["message"]["parts"] + reply = "\n".join( + p["text"] for p in parts if p.get("kind") == "text" + ) + except (KeyError, TypeError): + reply = str(result) + + print() + for line in reply.splitlines(): + print(f" {line}") + print() + + finally: + # 6. Always clean up — even if an earlier step failed + if workspace_id: + step(6, "Deleting demo workspace...") + api("delete", f"/workspaces/{workspace_id}", timeout=15) + print(" workspace deleted") + + print("\033[1;32mDemo complete.\033[0m\n") + + +if __name__ == "__main__": + main() From 06bf63078f8c5765c74150717d00327d3954eadb Mon Sep 17 00:00:00 2001 From: "molecule-ai[bot]" <276602405+molecule-ai[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 20:42:35 +0000 Subject: [PATCH 2/3] devrel: Makefile for gemini-cli demo (issue #534) --- docs/marketing/devrel/gemini-cli-demo/Makefile | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 docs/marketing/devrel/gemini-cli-demo/Makefile diff --git a/docs/marketing/devrel/gemini-cli-demo/Makefile b/docs/marketing/devrel/gemini-cli-demo/Makefile new file mode 100644 index 00000000..d0b9d529 --- /dev/null +++ b/docs/marketing/devrel/gemini-cli-demo/Makefile @@ -0,0 +1,15 @@ +.PHONY: run deps check-env + +## Install Python dependency +deps: + pip install httpx + +## Verify required env vars are set before running +check-env: + @test -n "$(PLATFORM_URL)" || (echo "Error: PLATFORM_URL is not set" && exit 1) + @test -n "$(PLATFORM_TOKEN)" || (echo "Error: PLATFORM_TOKEN is not set" && exit 1) + @test -n "$(GEMINI_API_KEY)" || (echo "Error: GEMINI_API_KEY is not set" && exit 1) + +## Run the demo end-to-end +run: deps check-env + python demo.py From 96960fde897f560ac5491767cd47a83f79c32e46 Mon Sep 17 00:00:00 2001 From: "molecule-ai[bot]" <276602405+molecule-ai[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 20:43:22 +0000 Subject: [PATCH 3/3] devrel: gemini-cli demo README walkthrough (issue #534) --- .../devrel/gemini-cli-demo/README.md | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 docs/marketing/devrel/gemini-cli-demo/README.md diff --git a/docs/marketing/devrel/gemini-cli-demo/README.md b/docs/marketing/devrel/gemini-cli-demo/README.md new file mode 100644 index 00000000..b8662434 --- /dev/null +++ b/docs/marketing/devrel/gemini-cli-demo/README.md @@ -0,0 +1,176 @@ +# Gemini CLI Runtime Adapter — Live Demo + +> **Feature:** [`feat(adapters): add gemini-cli runtime adapter`](https://github.com/Molecule-AI/molecule-core/pull/379) +> **Adapter path:** `workspace-template/adapters/gemini_cli/` +> **Runtime key:** `gemini-cli` + +This demo provisions a Gemini CLI workspace on Molecule AI, sends it a task via +the A2A proxy, and prints the result — all in about 60 seconds. + +--- + +## What you'll need + +| Requirement | Where to get it | +|-------------|----------------| +| Running Molecule AI platform | See [Quickstart](../../docs/quickstart.md) | +| Admin bearer token | Printed on first `go run ./cmd/server` startup | +| `GEMINI_API_KEY` | [Google AI Studio → Get API key](https://aistudio.google.com/apikey) | +| Python ≥ 3.11 + pip | `python --version` | +| `@google/gemini-cli` Docker image built | `bash workspace-template/build-all.sh gemini-cli` | + +--- + +## Step-by-step walkthrough + +### 1 — Build the adapter image (one-time) + +```bash +# From the repo root +bash workspace-template/build-all.sh gemini-cli +``` + +Expected output: `Successfully tagged workspace-template:gemini-cli` + +This installs `@google/gemini-cli@0.38.1` globally inside the container and +wires the A2A MCP server into `~/.gemini/settings.json` at boot. The adapter +seeds `GEMINI.md` from `system-prompt.md` so the agent has role context on +first message. + +--- + +### 2 — Set environment variables + +```bash +export PLATFORM_URL=http://localhost:8080 # your running platform +export PLATFORM_TOKEN= # printed at startup +export GEMINI_API_KEY= # NEVER hardcode this +``` + +The demo script reads all credentials from env vars — no secrets in source. + +--- + +### 3 — Run + +```bash +make run +# or: pip install httpx && python demo.py +``` + +--- + +## Expected output + +``` +[1] Creating gemini-cli workspace... + created id=a1b2c3d4-5678-... + +[2] Storing GEMINI_API_KEY as workspace secret (value never logged)... + secret stored + +[3] Waiting for workspace to come online (up to 90 s)... + online in ~18 s + +[4] Sending task via A2A proxy... + Task: "List the three biggest advantages of Google Gemini 2.5 Pro ..." + +[5] Gemini CLI agent reply: + + 1. Gemini 2.5 Pro's one-million-token context window lets it ingest entire + codebases in a single pass, eliminating the repeated context-loading + overhead GPT-4o requires. + 2. Its native multimodal input natively processes screenshots and diagrams + alongside code, so UI-driven debugging tasks need no preprocessing step. + 3. Google's function-calling latency benchmarks show lower P99 for + tool-call round-trips, which compounds in ReAct loops across many steps. + +[6] Deleting demo workspace... + workspace deleted + +Demo complete. +``` + +--- + +## How it works — under the hood + +``` +demo.py + │ + ├─ POST /workspaces → platform creates Docker container + │ runtime: gemini-cli adapter.setup() writes ~/.gemini/settings.json + │ seeds GEMINI.md from system-prompt.md + │ + ├─ PUT /workspaces/:id/secrets → GEMINI_API_KEY stored AES-256-GCM + │ + ├─ GET /workspaces/:id (poll) → waits for status=="online" + │ (workspace registers via POST /registry/register) + │ + ├─ POST /workspaces/:id/a2a → JSON-RPC 2.0 method: message/send + │ platform proxies to gemini CLI subprocess + │ CLI runs: gemini --yolo --model gemini-2.5-flash -p "" + │ MCP tools (delegate_task, commit_memory, …) available via settings.json + │ + └─ DELETE /workspaces/:id → container removed +``` + +### Key adapter decisions (from PR #379) + +| Decision | Why | +|----------|-----| +| `~/.gemini/settings.json` for MCP | Gemini CLI ignores `--mcp-config`; adapter merges A2A server entry on `setup()`, preserving user's existing MCP tools | +| `GEMINI.md` as memory file | Equivalent of `CLAUDE.md` for Claude Code; seeded from `system-prompt.md` on first boot so agents start with role context | +| `--yolo` flag | Non-interactive mode — auto-approves all tool calls, required for headless subprocess execution | +| `gemini-2.5-flash` for demo | Faster boot; switch to `gemini-2.5-pro` for production workspaces needing deeper reasoning | + +--- + +## Swap in a different model + +```bash +# In demo.py, change runtime_config.model: +"model": "gemini-2.5-pro", # full reasoning +"model": "gemini-2.0-flash", # fastest, cheapest +``` + +Or set it per-workspace via the Molecule AI canvas → Config → Runtime. + +--- + +## Multi-provider example + +Once you have a `gemini-cli` workspace running alongside a `claude-code` workspace, +you can delegate tasks between them transparently — the A2A protocol is runtime-agnostic: + +```python +# From your orchestrator workspace (claude-code, hermes, etc.) +result = delegate_task( + workspace_id="", + task="Summarise the attached diff and suggest three test cases.", +) +``` + +No code changes needed. The orchestrator doesn't know (or care) which model +is running on the other side. + +--- + +## Troubleshooting + +| Symptom | Fix | +|---------|-----| +| Workspace stuck in `provisioning` | Check `docker images` for `workspace-template:gemini-cli`; re-run `build-all.sh gemini-cli` if missing | +| `failed` status immediately | Check platform logs: `GEMINI_API_KEY` missing or `npm install -g @google/gemini-cli` failed during image build | +| A2A call times out | `gemini-cli` cold-start on first task can take 15–20 s; increase `timeout=120` in demo.py if needed | +| `code 422` on workspace create | Platform requires `runtime: "gemini-cli"` to be in `RUNTIME_PRESETS`; confirm you're on main after PR #379 | + +--- + +## Related + +- [PR #379 — gemini-cli runtime adapter](https://github.com/Molecule-AI/molecule-core/pull/379) +- [Tutorial: Running a Gemini CLI Workspace](../../docs/tutorials/gemini-cli-runtime.md) *(PR #509)* +- [Adapter source](../../workspace-template/adapters/gemini_cli/adapter.py) +- [CLI executor preset](../../workspace-template/cli_executor.py) +- [A2A proxy API reference](../../docs/api-reference.md#a2a-proxy)