Jean + Ravi
Jean + Ravi
Jean is a native desktop application built by the Coolify team that turns Claude CLI, Codex CLI, and OpenCode into a coherent multi-project development environment. It manages git worktrees, parallel AI sessions, integrated terminals, diff viewers, and GitHub operations — all in one window, all running locally with your own API keys.
Jean is where the identity problem in AI-assisted development becomes most visible. Each worktree is effectively a separate agent context. Each parallel session may be pushing commits, opening PRs, or interacting with external services. When you run five workers simultaneously, the question of “which agent did this and with what credentials” isn’t academic — it’s the difference between a traceable development history and a mess.
Ravi is the identity layer that answers that question.
The Problem
Jean’s local-first design is a feature, not a limitation — but it creates a real credential management challenge at scale.
Parallel sessions share a single GitHub identity. When you run three concurrent Claude Code workers across three worktrees, all three are using your personal ~/.config GitHub token. Commits look the same. PRs open from the same account. If something goes wrong, there’s no way to tell which session was responsible.
Remote access tokens are manually managed. Jean ships a built-in HTTP server with WebSocket support for browser-based remote access. That token is generated once, stored in a config file, and rotated manually. There’s no audit trail of who connected or when. If the token leaks, the rotation is entirely manual.
There’s no notification layer. Jean has no built-in alerting. When a long-running session completes or fails, you find out by checking the UI. For headless or overnight runs, that means polling.
Service accounts bleed personal identity. Agents creating PRs and commits do so under your personal GitHub account. There’s no separation between “what you did” and “what your agent did.”
What Ravi Adds
1. Per-Worktree GitHub Credentials
The cleanest integration: when Jean creates a new worktree, a Ravi identity is provisioned for that session. The agent’s GitHub token is stored in Ravi’s encrypted vault under a key scoped to that worktree — something like JEAN_WORKTREE_AUTH_<slug>. Jean injects that token as an environment variable before spawning the AI session.
The agent runs with its own GitHub identity. Commits are attributable. PRs open from a distinct account. When the worktree is archived on PR merge, the token is revoked and the vault entry is deleted.
This is the full credential lifecycle — provision, inject, use, revoke — without any manual key management.
2. Managed Remote Access Tokens
Jean’s HTTP server accepts a token-based auth scheme. Instead of generating a static token at setup time, Ravi provisions a time-bounded token stored in the vault as JEAN_REMOTE_ACCESS_TOKEN. The token can be rotated on a schedule, and Ravi maintains a log of when it was issued and accessed.
For teams sharing a Jean instance, each member can be issued their own access credential rather than sharing a single static token. The provisioning and rotation are handled without touching config files.
3. Verified GitHub Identity Per Project
Jean supports multiple projects running concurrently. For teams that want clean separation between project identities — especially when agents are creating GitHub issues, PRs, and commits autonomously — Ravi can provision a verified GitHub account per project. Each account has a real email address and phone number for GitHub verification, with credentials stored in Ravi’s password vault.
When Agent Five’s research team spins up three separate project worktrees in Jean, each project’s agent operates from its own GitHub identity. The commit log, PR history, and issue comments are clearly attributed. Code review is cleaner because you know which sessions touched which files.
4. Session Completion Notifications
Jean has no built-in alerting. Ravi fills the gap via its email and SMS tools. A lightweight wrapper around Jean’s completion events — readable from the HTTP API or a file watcher — can call Ravi to send a message when a session finishes, errors, or requires human input.
import subprocess
import time
import ravi
def watch_jean_session(session_id: str, notify_to: str):
"""Poll Jean's HTTP API and notify via Ravi when done."""
import requests
headers = {"Authorization": f"Bearer {ravi.secrets_get('JEAN_REMOTE_ACCESS_TOKEN')}"}
while True:
resp = requests.get(
f"http://localhost:PORT/api/sessions/{session_id}",
headers=headers
)
status = resp.json().get("status")
if status == "completed":
ravi.sms_send(
to_number=notify_to,
body=f"Jean session {session_id} completed successfully."
)
break
elif status == "error":
ravi.email_compose(
to=notify_to,
subject=f"Jean session {session_id} failed",
body=f"Session {session_id} encountered an error. Check the Jean UI for details."
)
break
time.sleep(60)
Concrete Integration: Scoped Identity Lifecycle
Here’s the full pattern for per-worktree identity provisioning:
import ravi
import subprocess
import json
def create_jean_worktree(project_slug: str, branch_name: str):
"""
Create a Jean worktree with a Ravi-provisioned scoped identity.
Credentials are injected as environment variables for the session.
"""
worktree_slug = f"{project_slug}-{branch_name}".lower().replace("/", "-")
secret_key = f"JEAN_GH_TOKEN_{worktree_slug.upper().replace('-', '_')}"
# Provision a scoped GitHub token stored in Ravi vault
# (In practice, use GitHub API to create a fine-grained token)
github_token = provision_github_token(worktree_slug)
ravi.secrets_set(key=secret_key, value=github_token)
# Record the worktree in a local manifest
manifest = load_manifest()
manifest[worktree_slug] = {
"branch": branch_name,
"secret_key": secret_key,
"created_at": ravi.now_iso()
}
save_manifest(manifest)
print(f"Worktree {worktree_slug} ready. Token stored as {secret_key}.")
return worktree_slug
def archive_jean_worktree(worktree_slug: str):
"""
Archive a worktree and revoke its Ravi-managed credentials.
Call this when a PR is merged and the worktree is no longer needed.
"""
manifest = load_manifest()
entry = manifest.get(worktree_slug)
if not entry:
print(f"No manifest entry for {worktree_slug}")
return
# Retrieve and revoke the GitHub token
secret = ravi.secrets_get(entry["secret_key"])
revoke_github_token(secret["value"])
# Delete the vault entry
ravi.secrets_delete(secret["uuid"])
# Remove from manifest
del manifest[worktree_slug]
save_manifest(manifest)
print(f"Worktree {worktree_slug} archived. Token revoked.")
Environment Injection for Jean Sessions
Jean supports custom environment variables per session via its configuration. With Ravi-managed secrets, the injection pattern looks like this:
# Before launching Jean session for a worktree:
export GH_TOKEN=$(ravi secrets get JEAN_GH_TOKEN_MYPROJECT_FEATURE_AUTH)
export JEAN_SESSION_IDENTITY="myproject-feature-auth"
# Jean picks up GH_TOKEN automatically for GitHub operations
Setup Guide
Prerequisites
- Jean installed and configured
- Ravi CLI configured with an active identity (
ravi identity list)
Step 1: Store your baseline Jean remote access token
# Retrieve the token Jean generated at setup and store it in Ravi
ravi secrets set JEAN_REMOTE_ACCESS_TOKEN <your-jean-token>
Future rotations: generate a new token in Jean settings, update in Ravi vault, done. No config file spelunking.
Step 2: Set up per-project service identities (optional)
For teams using Jean to run autonomous agents that open PRs and create commits:
# Create a Ravi identity per project
ravi identity create "jean-myproject"
# Use the provisioned email to register a GitHub account for that project
# Store credentials in Ravi password vault
ravi passwords create --domain github.com --username jean-myproject-bot
Step 3: Add session notification hooks
Wire up Jean’s HTTP API to Ravi for session completion alerts. The pattern from the code example above is the simplest approach — a polling script that watches session status and calls ravi_sms_send or ravi_email_compose on state change.
Step 4: Test the credential lifecycle
Create a test worktree, confirm the scoped token is stored in Ravi, launch a Jean session with the injected token, archive the worktree, and verify the token is revoked and deleted from the vault.
Why Ravi Fits Jean Specifically
Jean’s value proposition is parallel, autonomous development across multiple worktrees. That value compounds when each worktree is a fully isolated context — its own code, its own history, its own identity.
Without Ravi:
- All five concurrent workers share your personal GitHub token
- Remote access relies on a single static token in a config file
- Session completions go unnoticed until you check the UI
- There’s no record of which agent did what with which credentials
With Ravi:
- Each worktree gets a scoped credential that’s provisioned on creation and revoked on archive
- Remote access tokens live in an audited vault with rotation support
- Session events route to you via email or SMS wherever you are
- The credential lifecycle is automated — no manual key management, no shared secrets
Jean solves the “how do I run five AI sessions at once” problem. Ravi solves the “who are those five sessions and what can they do” problem. Together they cover the full picture.