Centris SDK for building connectors and integrations
Project description
Centris SDK
For Developers: Build connectors that make Centris faster and more capable.
End users don't need this SDK. Users just download the Centris app and control their computer with voice - no setup, no configuration, no technical knowledge required. This SDK is for developers who want to extend what voice commands can do.
What is Centris SDK?
The Centris SDK lets you create browser automation connectors - pre-compiled recipes that automate web applications without OAuth or API keys. Your connectors make the invisible system faster - users just speak and things happen.
┌─────────────────────────────────────────────────────────────────────────────┐
│ BASE CENTRIS (plan-once): WITH CONNECTOR (compiled recipe): │
│ ────────────────────────────────── ───────────────────────────────── │
│ LLM creates plan → execute (1s) Execute pre-compiled steps │
│ DOM-based actions (1-2s total) Selectors already known │
│ No screenshots needed No planning needed │
│ 1 LLM call for planning 0 LLM calls │
│ │
│ TOTAL: 2-4 seconds TOTAL: 1-2 seconds │
└─────────────────────────────────────────────────────────────────────────────┘
Note: Centris uses DOM/accessibility tree, NOT screenshots. The comparison shows that connectors are even faster because they skip the planning step entirely.
Installation
One-Liner (Recommended)
curl -fsSL https://centris.ai/install.sh | bash
Or from GitHub:
curl -fsSL https://raw.githubusercontent.com/centris-ai/centris-ai/main/scripts/install.sh | bash
Fastest dev setup (single command)
pipx install "centris-sdk[all]"
Verify and start:
centris-py --version
centris-py doctor
centris-py init demo-py --template browser --url https://example.com
cd demo-py
centris-py validate .
centris-py test .
centris-py serve .
pip / pipx
# Global install (isolated with pipx - recommended)
pipx install centris-sdk[all]
# Or with pip
pip install centris-sdk[all]
# Verify installation
centris-py --version
centris-py doctor
From Source (Development)
git clone https://github.com/centris-ai/centris-ai.git
cd centris-ai/sdk/python
pip install -e .[all]
Optional Dependencies
Install only what you need:
pip install centris-sdk # Core only (CLI + httpx)
pip install centris-sdk[server] # + FastAPI dev server
pip install centris-sdk[browser] # + Playwright automation
pip install centris-sdk[desktop] # + pyautogui desktop control
pip install centris-sdk[cli] # + rich terminal output
pip install centris-sdk[all] # Everything
Browser Testing Setup
If you installed [browser] or [all], you also need to install Chromium:
# After pip install, run:
playwright install chromium
This enables centris-py test . --browser which verifies your runtime browser interactions in a real browser.
Quick Start (5 Minutes)
1. Create a Connector
# Create a browser connector for your app
centris-py init myapp --template browser --url https://myapp.com
cd myapp
This creates:
myapp/
├── connector.py # Main implementation (edit this)
├── connector.json # Metadata
├── pyproject.toml # Python config
└── README.md # Documentation
2. Define Runtime Targets
Edit connector.py to model runtime actions and node IDs discovered from live snapshots:
class MyAppNodes:
"""Example runtime node targets (replace with live snapshot IDs)."""
SEND_BUTTON = 12
MESSAGE_INPUT = 8
3. Implement Tools
Add browser automation recipes:
async def myapp_send_message(tool_call_id, params, context):
"""Send a message - deterministic, no LLM needed."""
browser = context.get("browser_bridge")
if not browser:
return {"success": False, "error": "Browser bridge not available"}
# Navigate and interact
await browser.navigate_browser("https://myapp.com")
await browser.click_node(node_id=MyAppNodes.MESSAGE_INPUT)
await browser.type_text(params["message"])
await browser.click_node(node_id=MyAppNodes.SEND_BUTTON)
return {"success": True, "message": "Message sent"}
4. Test Locally
# Test with mock browser (fast, no setup needed)
centris-py test .
# See browser operations
centris-py test . --show-ops
Output:
Testing connector at: ./myapp
Using mock browser for testing
Test Results:
--------------------------------------------------
✓ myapp_send_message (12ms)
Browser Operations:
→ navigate_browser(url='https://myapp.com')
→ click_node(node_id=8)
→ type_text(text='test_message')
→ click_node(node_id=12)
--------------------------------------------------
Total: 1 | Passed: 1 | Failed: 0
✓ All tests passed!
5. Publish
# Publish to the Centris registry (auto-login on first use)
centris-py publish .
Your connector is now available to all Centris users worldwide.
CLI Reference
| Command | Description |
|---|---|
centris-py init <id> |
Create new connector project |
centris-py init <id> --template browser |
Create browser automation connector |
centris-py validate [path] |
Validate connector structure |
centris-py test [path] |
Test with mock browser (fast, syntax only) |
centris-py test [path] --browser |
Test with real Playwright browser (verifies runtime actions) |
centris-py test [path] --browser --headed |
Real browser with visible window |
centris-py test [path] --live |
Test via Centris backend (requires server running) |
centris-py test [path] --show-ops |
Show browser operations performed |
centris-py serve [path] |
Start dev server with playground |
centris-py publish [path] |
Publish to registry |
centris-py search <query> |
Search the registry |
centris-py list |
List available connectors |
Browser Bridge API
The browser_bridge is passed to your tool via context. It provides these operations:
Navigation
# Navigate to URL
await browser.navigate_browser("https://example.com")
# Get current tab info
tab = await browser.get_active_tab()
# Returns: {"url": "https://...", "title": "..."}
Clicking
# Click by node ID (preferred)
await browser.click_node(node_id=42)
Typing
# Type at current focus
await browser.type_text("Hello world")
# Press a key
await browser.press_key("Enter")
await browser.press_key("Tab")
Waiting
# Wait for selector to appear
await browser.wait_for_selector('.success-message', timeout=5000)
# Simple delay (milliseconds)
await browser.wait(1000)
Reading Content
# Get page text content
content = await browser.get_page_content()
# Get interactive elements (accessibility tree)
snapshot = await browser.get_interactive_snapshot()
Example: Gmail Connector
Here's a real-world example - the Gmail connector:
"""Gmail Connector - compiled browser automation for Gmail."""
class GmailNodes:
"""Example node IDs from live snapshots."""
COMPOSE_BUTTON = 15
COMPOSE_TO = 29
COMPOSE_SUBJECT = 31
COMPOSE_BODY = 33
COMPOSE_SEND = 47
async def gmail_send_email(tool_call_id, params, context):
"""Send email via Gmail - 10x faster than LLM-in-loop."""
browser = context.get("browser_bridge")
to, subject, body = params["to"], params["subject"], params["body"]
# Ensure we're on Gmail
await browser.navigate_browser("https://mail.google.com")
await browser.wait(2000)
# Click compose
await browser.click_node(node_id=GmailNodes.COMPOSE_BUTTON)
await browser.wait(1000)
# Fill fields
await browser.click_node(node_id=GmailNodes.COMPOSE_TO)
await browser.type_text(to)
await browser.click_node(node_id=GmailNodes.COMPOSE_SUBJECT)
await browser.type_text(subject)
await browser.click_node(node_id=GmailNodes.COMPOSE_BODY)
await browser.type_text(body)
# Send
await browser.click_node(node_id=GmailNodes.COMPOSE_SEND)
return {"success": True, "message": f"Email sent to {to}"}
Testing
Three testing modes, from fastest to most thorough:
1. Mock Testing (Fastest)
# Fast syntax check - records operations but doesn't verify live browser state
centris-py test .
# With verbose output
centris-py test . -v --show-ops
Best for: Quick iteration during development. Verifies your code runs without errors, but does NOT check live browser node state.
2. Real Browser Testing (Recommended)
# Launches a real Playwright browser - verifies runtime interactions
centris-py test . --browser
# Watch the browser (headed mode)
centris-py test . --browser --headed
# Show all operations
centris-py test . --browser --show-ops
Best for: Validating runtime interactions before publishing.
✗ gmail_send_email (3421ms)
Error: Failed to click runtime target (element not found within timeout)
Hint: Capture a fresh snapshot and refresh node IDs for this flow.
Requires: pip install centris-sdk[browser] && playwright install chromium
3. Live Testing (Full Integration)
# Tests via Centris backend - uses your actual browser session
centris-py test . --live
Best for: Final validation with real user state. Requires Centris desktop app or backend running.
Programmatic Testing with pytest
import pytest
from centris_sdk.testing import MockBrowserBridge, PlaywrightBrowserBridge
# Fast mock tests
@pytest.fixture
def mock_browser():
return MockBrowserBridge(initial_url="https://example.com")
@pytest.mark.asyncio
async def test_send_message_mock(mock_browser):
from myapp.connector import myapp_send_message
result = await myapp_send_message(
"test-1",
{"message": "Hello, World!"},
{"browser_bridge": mock_browser}
)
assert result["success"] is True
# Verify browser operations were recorded
ops = mock_browser.get_operations()
assert any(op.action == "navigate_browser" for op in ops)
assert any(op.action == "click_node" for op in ops)
# Real browser tests (slower, but validates runtime interactions)
@pytest.fixture
async def real_browser():
async with PlaywrightBrowserBridge(headless=True) as browser:
yield browser
@pytest.mark.asyncio
@pytest.mark.integration
async def test_send_message_real(real_browser):
from myapp.connector import myapp_send_message
result = await myapp_send_message(
"test-1",
{"message": "Hello, World!"},
{"browser_bridge": real_browser}
)
# If this fails, result contains runtime diagnostics
if not result.get("success"):
print(f"Error: {result.get('error')}")
print(f"Hint: {result.get('hint')}")
print(f"Similar: {result.get('similar')}")
assert result["success"] is True
Security Model
Connectors operate within the user's existing browser session:
| What You CAN Access | What You CANNOT Access |
|---|---|
browser_bridge (click, type, navigate) |
Centris core code |
params (user input for tool call) |
User credentials |
context.config (your connector's config) |
Other connectors' data |
| Direct filesystem | |
| Arbitrary JavaScript | |
| Cookies/localStorage |
Why No OAuth?
Traditional integrations (Zapier, etc.) require users to "connect" accounts with OAuth. Centris connectors don't need this because:
- User is already logged in - They're using their browser where they're already authenticated
- Browser automation - Connectors execute actions like a human clicking around
- Zero configuration - Users just talk to Centris, no account linking needed
Project Structure
A typical connector project:
myapp/
├── connector.py # Main implementation
│ ├── MyAppNodes # Runtime node targets
│ ├── MyAppURLs # URL patterns
│ ├── myapp_* # Tool functions (browser automation recipes)
│ ├── MyAppConnectorApi # Tool registration
│ └── connector # Exported connector instance
├── connector.json # Metadata (id, name, version, categories)
├── pyproject.toml # Python package config
└── README.md # Documentation
API Versioning
Centris uses date-based API versioning. For stability, always specify a version:
from centris_sdk import Centris
# Explicit version (recommended for production)
centris = Centris(
api_key="ck_live_xxx",
api_version="2026-01-30"
)
# Execute commands
result = centris.do("Open Gmail and read my first 3 emails")
CLI with Version
# Set default version
centris-py config set api_version 2026-01-30
# Per-request version
centris-py do "Open Gmail" --api-version 2026-01-30
Handling Deprecation Warnings
# Register callback for deprecation warnings
centris.on_deprecation(lambda endpoint, sunset, alternative:
print(f"Warning: {endpoint} deprecated, use {alternative}")
)
For full versioning documentation, see:
Environment Variables
| Variable | Description | Default |
|---|---|---|
CENTRIS_API_KEY |
API key for publishing | None |
CENTRIS_API_VERSION |
Default API version | Current stable |
CENTRIS_REGISTRY_URL |
Custom registry URL | https://registry.centris.ai |
CENTRIS_DEBUG |
Enable debug logging | false |
Documentation
Essential Reading
| Document | Purpose |
|---|---|
| docs/index.md | SDK docs map - Python SDK modules (client, CLI, API) |
| docs/cli-command-matrix.md | Full flag-by-flag CLI command matrix |
| docs/api-endpoints-examples.md | End-to-end API request/response examples |
| docs/auth-profile-config.md | Auth, profile isolation, and config patterns |
| docs/errors-troubleshooting.md | Error codes and troubleshooting matrix |
| CONNECTOR_FRAMEWORK.md | START HERE - Complete framework guide |
| CONNECTOR_DEVELOPMENT.md | Detailed integration guide (signatures, browser bridge API) |
Critical Concept: Static vs Dynamic DOM
This is the most important concept for building connectors.
What CAN Use Stable Runtime Targets (Static DOM)
| Element | Example | Mappable? |
|---|---|---|
| Buttons | Compose, Search, Reply | ✅ Yes |
| Navigation | Inbox, Sent, Settings | ✅ Yes |
| Form fields | To, Subject, Body inputs | ✅ Yes |
# ✅ CORRECT - Static UI controls
element_map = {
"controls": {
"compose": (1, "clickable", "Compose button"), # Same for everyone
"search": (2, "typeable", "Search bar"), # Same for everyone
}
}
What CANNOT Use Stable Runtime Targets (Dynamic DOM)
| Content | Example | Mappable? |
|---|---|---|
| Individual emails | "Email from John" | ❌ No |
| Calendar events | "Meeting at 3pm" | ❌ No |
| Files in Drive | "Report.docx" | ❌ No |
# ❌ WRONG - Dynamic content, different per user
element_map = {
"emails": {
"johns_email": (47, "clickable", "Email from John"), # ❌ WRONG!
}
}
# ✅ CORRECT - Use API for dynamic content
async def get_api_context(self, user_id: str) -> Dict[str, Any]:
return {
"recent_emails": [
{"from": "John", "subject": "Project Update"}
],
"hint": "Use search to find specific emails"
}
The Hybrid Pattern (Gmail, Calendar, Drive)
┌─────────────────────────────────────────────────────────────────┐
│ STATIC DOM (element_map) API CONTEXT (get_api_context) │
│ ───────────────────────── ───────────────────────────── │
│ │
│ • Compose button = node 1 • "5 unread emails" │
│ • Search bar = node 2 • "Latest: John - Project" │
│ • Reply button = node 30 • "Use search: from:john" │
│ │
│ (same for ALL users) (unique per user via OAuth) │
└─────────────────────────────────────────────────────────────────┘
Static DOM tells the LLM HOW to interact (click this button). API Context tells the LLM WHAT exists (user has email from John).
See CONNECTOR_FRAMEWORK.md for complete patterns and examples.
Contributing
- Fork the repo
- Create your connector in
connectors/your-app/ - Add tests
- Submit PR
See CONTRIBUTING.md for guidelines.
Support
- Discord - Join our community
- GitHub Issues - Bug reports
- Documentation - Full docs
License
MIT
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 centris_sdk-1.1.1.tar.gz.
File metadata
- Download URL: centris_sdk-1.1.1.tar.gz
- Upload date:
- Size: 277.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e883dee0f54d93d5c5e0db2f312bba048fea5ee1cf3059a624f4b582e54b04ac
|
|
| MD5 |
8c35eefd4a58d8a906e1ae43ad4a1c56
|
|
| BLAKE2b-256 |
82ece904a5179dfa6027a14fed0cd8bd324aa8c937378fa1f534ee4398d34a2f
|
File details
Details for the file centris_sdk-1.1.1-py3-none-any.whl.
File metadata
- Download URL: centris_sdk-1.1.1-py3-none-any.whl
- Upload date:
- Size: 316.5 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 |
299f956ef89cc8393d36b3c93f2d06e80dc34dd87d681bfa089298f199bc2942
|
|
| MD5 |
a7391b6a3f82d1f3a82ee44dc561ac24
|
|
| BLAKE2b-256 |
b8002f9f60066b8c7a75eea6b1ce71e8964d3b55df251c6539fa89347e9ad85f
|