Skip to main content

Centris SDK for building connectors and integrations

Project description

Centris SDK

Tests PyPI version Python 3.10+ License: MIT

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 .

Registry publish API: POST /api/connectors.

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:

  1. User is already logged in - They're using their browser where they're already authenticated
  2. Browser automation - Connectors execute actions like a human clicking around
  3. 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

  1. Fork the repo
  2. Create your connector in connectors/your-app/
  3. Add tests
  4. Submit PR

See CONTRIBUTING.md for guidelines.

Support

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

centris_sdk-1.1.4.tar.gz (303.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

centris_sdk-1.1.4-py3-none-any.whl (353.1 kB view details)

Uploaded Python 3

File details

Details for the file centris_sdk-1.1.4.tar.gz.

File metadata

  • Download URL: centris_sdk-1.1.4.tar.gz
  • Upload date:
  • Size: 303.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for centris_sdk-1.1.4.tar.gz
Algorithm Hash digest
SHA256 b43eb2fffd4390608b4928f4a0174dd460be03561feebb731aa79db47b41d6f3
MD5 00a06c25a7132ccc36cd6ed2436a979f
BLAKE2b-256 1083565c8f7deea50dbdf20dfb045ed5bf8ec30ede7aeb1f11b1673bd7a900aa

See more details on using hashes here.

File details

Details for the file centris_sdk-1.1.4-py3-none-any.whl.

File metadata

  • Download URL: centris_sdk-1.1.4-py3-none-any.whl
  • Upload date:
  • Size: 353.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for centris_sdk-1.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 5d44111dcfdce1f0a41c6029a5f101df61fd72e688212e9f08522f4fcf8d3ddc
MD5 14fa37e0e35958816ecf951b87fd937a
BLAKE2b-256 26b665305ff71f5c24522f1b4f67a53869864df3cbfea7ad8b8f6424596c45b7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page