Skip to main content

Runtime for desktop GUI automation on macOS. Run procedures locally, or stream them from a remote WebSocket server.

Reason this release was yanked:

Superseded by 0.1.1 : install case-sdk>=0.1.1 (security hardening, dep floor bumps, and the case_ax_tree app-name resolution fix).

Project description

case-sdk

Dual-mode runtime for desktop GUI automation on macOS.

  • Local mode — write your own procedures in Python and run them on your machine.
  • Remote mode — with an API key, stream procedures from a remote WebSocket server. The procedure source never lands on the client's disk.

Both modes share the same Runtime API.

Developers: see docs/DEVELOPER.md for API contracts, JSON schemas (docs/schemas/), procedure authoring, and MCP.

Install

From source (until the package is on PyPI):

git clone https://github.com/daemonlabshq/case-sdk
cd case-sdk
pip install -e .
pip install -e ".[mcp]"    # optional: MCP server for Claude Code / agents

Grant Accessibility in System Settings → Privacy & Security → Accessibility. Then run case-sdk doctor to verify.

Screen Recording is only required if a procedure uses the screenshot sensor.

Quickstart

case-sdk doctor                                      # check permissions + config
case-sdk procedures                                  # list built-in DaVinci procedures
case-sdk run davinci/focus_resolve                   # bring DaVinci frontmost
case-sdk run examples/open_textedit.json --param name=world

case-sdk doctor reports any missing macOS permissions and deep-links to System Settings. case-sdk procedures shows the discoverable catalog (built-ins plus anything under ~/.case/procedures/).

Write a procedure

A procedure is a Python generator. It yields Action to do things and Sensor to read state. The driver runs each yield against the local harness and feeds the result back into the generator via gen.send(...).

# my_proc.py
from case_sdk import Action, Sensor

def procedure(params):
    yield Action("open_app", name="TextEdit")
    yield Action("wait", ms=500)
    yield Action("type", text="hello")
    yield Action("hotkey", combo=["cmd", "s"])
    title = yield Sensor("window_title")
    return {"success": True, "title": title["value"]}

Run a local procedure

from case_sdk import Runtime
Runtime().execute_file("my_proc.py")

Or from the shell:

case-sdk run my_proc.py

Run a remote procedure (BYO server / managed case-api coming)

case-sdk speaks a WebSocket envelope protocol (see case_sdk.protocol) so a server can hold a procedure and stream one step at a time while the client harness executes it locally. You bring your own server today; the managed case-api service is in active development and will be the default hosted backend.

from case_sdk import Runtime
rt = Runtime(api_key="sk_...", api_base="wss://your-server.example/ws")
rt.execute("add_clip_to_timeline", filepath="/path/to/clip.mov")

Or:

case-sdk init --api wss://your-server.example/ws --key sk_...    # one-time
case-sdk run add_clip_to_timeline --param filepath=/x.mov

In remote mode the server holds the procedure. The runtime receives one action at a time over WebSocket, runs it locally, returns the observation, and waits for the next instruction. Procedure source never lands on the client's disk.

Built-in primitives

Actions: open_app, activate_app, click, click_ref, type, hotkey, scroll, mouse_down, mouse_up, wait, ax_press, ax_set_value, fn_key, drag, menu_click.

Sensors: window_title, ax_value, screenshot.

Inspect the AX tree (authoring)

Discover UI elements before writing ax_press / ax_set_value steps:

case-sdk ax-tree --frontmost
case-sdk ax-tree "DaVinci Resolve" --depth 10
case-sdk ax-tree "DaVinci Resolve" --find --role AXButton --title-contains Append

Python API:

from case_sdk import app_tree, find_elements

tree = app_tree("TextEdit", max_depth=8)
refs = find_elements("DaVinci Resolve", role="AXButton", title_contains="Save")

Each node includes role, title, and optional path (child indices). Use those fields in element_ref when yielding AX actions.

Verification (Project.db and more)

After GUI actions, confirm state with verify steps in JSON procedures (or call from Python):

{
  "verify_mode": "soft",
  "steps": [
    {"verify": "davinci.db_snapshot", "as": "before"},
    {"action": "wait", "ms": 500},
    {"verify": "davinci.db_delta", "field": "timeline_video", "baseline": "before", "min_delta": 1}
  ]
}
  • soft (default) — log a warning and continue (good for demos; unsaved Resolve projects).
  • hard — raise ClientError with code E_VERIFY (set "verify_mode": "hard" or CASE_SDK_VERIFY_MODE=hard).

Built-in verifiers:

Name Purpose
davinci.db_snapshot Store row-count baseline (as optional)
davinci.db_delta Poll until a field grows (or max_delta for deletes)
davinci.media_pool_name Clip name appears in media pool
davinci.timeline_clips Read timeline items (min_clips optional)
case-sdk verify-list

Procedure discovery (Phase 0)

Built-in DaVinci procedures ship with the package. Override or add your own under ~/.case/procedures/ (created by case-sdk init).

case-sdk procedures
case-sdk run davinci/import_and_append --param filepath=/path/to/clip.mov
case-sdk run davinci/append_to_timeline

Search path (first match wins): $CASE_SDK_PROCEDURES_PATH~/.case/procedures~/.case-sdk/procedures → built-ins.

Built-in name Purpose
davinci/focus_resolve Activate DaVinci
davinci/go_to_page Switch page tab (--param page=edit)
davinci/open_media_pool Edit page + open Media Pool panel
davinci/import_media Import one file (filepath, auto clip_basename)
davinci/import_media_on_edit Go to Edit, then import (filepath)
davinci/append_to_timeline Append selected pool clip (menu click + DB verify)
davinci/append_to_timeline_hotkey Append via Fn+F9 fallback
davinci/import_and_append Import then append

JSON step lists (no Python required)

Write a .json procedure and run it like a .py file:

{
  "steps": [
    {"action": "open_app", "name": "TextEdit"},
    {"action": "wait", "ms": 500},
    {"action": "type", "text": "hello ${name}"}
  ],
  "return": {"success": true}
}
case-sdk run examples/open_textedit.json --param name=world

CLI

case-sdk run <path-or-name> [--key sk_xxx] [--api wss://...] [--param k=v]...
case-sdk ax-tree [app] [--frontmost] [--find] [--role ...] [--title-contains ...]
case-sdk procedures [--json]
case-sdk verify-list
case-sdk init [--key sk_xxx] [--api wss://...] [--force]
case-sdk doctor
case-sdk --version

Configuration resolves in order: CLI flag → environment (CASE_SDK_API_KEY, CASE_SDK_API_BASE) → ~/.case-sdk/config.toml.

MCP (agents — Claude Code, etc.)

The case-mcp server exposes the full local SDK over MCP stdio. It does not duplicate automation logic — every tool delegates to case_sdk (same as the CLI).

pip install -e ".[mcp]"
case-sdk doctor

# Claude Code (use your venv python path)
claude mcp add case-sdk -- python -m case_mcp.server
MCP tool Purpose
case_help Planner guide + tool catalog
case_doctor Permissions and procedure dirs
case_focus_app Activate an app before keystrokes
case_ax_tree Dump AX hierarchy
case_find_elements Search → element_ref dicts
case_list_procedures Built-in + user procedure names
case_list_verifiers JSON verify step names
case_run Run a procedure (params dict, optional verify_mode)

Example agent flow: case_list_procedurescase_focus_app("DaVinci Resolve")case_run("davinci/import_and_append", {"filepath": "/abs/path/clip.mov"}).

Remote streaming procedures remain CLI-only for now (case-sdk run with --key).

MCP troubleshooting

If claude mcp list shows case-sdk: ... ✗ Failed to connect, the most common cause is the mcp package missing from the venv you installed case-sdk into. Verify and fix:

/path/to/your/venv/bin/pip show mcp
/path/to/your/venv/bin/pip install "mcp[cli]>=1.0"
claude mcp remove case-sdk
claude mcp add case-sdk -- /path/to/your/venv/bin/python -m case_mcp.server

A quick import probe that crashes loudly when mcp is missing:

python -c "from case_mcp.server import create_mcp; create_mcp(); print('ok')"

Error handling

Inside a procedure you can catch harness failures and decide what to do:

from case_sdk import Action, ClientError

def procedure(params):
    try:
        yield Action("hotkey", combo=["cmd", "s"])
    except ClientError as e:
        if e.code == "E_TIMEOUT":
            return {"success": False, "reason": "save timed out"}
        raise

ClientError represents a recoverable harness failure (timeout, primitive raised). Uncaught exceptions and re-raised ClientErrors surface to the caller of Runtime.execute* as ProcedureError.

License

Elastic License 2.0 (ELv2). Source-available. You may use, modify, and self-host case-sdk freely. You may not provide it to third parties as a hosted or managed service. See LICENSE for the full text.

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

case_sdk-0.1.0.tar.gz (75.5 kB view details)

Uploaded Source

Built Distribution

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

case_sdk-0.1.0-py3-none-any.whl (58.1 kB view details)

Uploaded Python 3

File details

Details for the file case_sdk-0.1.0.tar.gz.

File metadata

  • Download URL: case_sdk-0.1.0.tar.gz
  • Upload date:
  • Size: 75.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for case_sdk-0.1.0.tar.gz
Algorithm Hash digest
SHA256 762d1bec67fe427c25b04a975ecbe73c0f3eea4d41443f42fb9a98bf697066dc
MD5 696c450697af271c282803d4576f4533
BLAKE2b-256 70b88838f3d2f874dd621268a6c97cf0d50882fc3cc2f0bc99f4e5a74fb5e238

See more details on using hashes here.

Provenance

The following attestation bundles were made for case_sdk-0.1.0.tar.gz:

Publisher: publish.yml on daemonlabshq/case-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file case_sdk-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: case_sdk-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 58.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for case_sdk-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1b401f544112e663bf7e16f5c5e8e634229cceb11c53a3135854a1c3f98d5d8b
MD5 d14cc99d7a5e19c475ccc432273a42cd
BLAKE2b-256 28a93590caa853bcbe8bca224acf31c63696860491df6cbf900f3bae1992fa3d

See more details on using hashes here.

Provenance

The following attestation bundles were made for case_sdk-0.1.0-py3-none-any.whl:

Publisher: publish.yml on daemonlabshq/case-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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