Skip to main content

BranchKit Plugin SDK (Python)

BranchKit is an accessibility plugin platform for the desktop. The platform loads plugins, confines each one to what it declared, tracks what is true right now (which app has focus, which mode is active), and routes voice commands, hotkeys and other input to whichever plugin claims them. This SDK is how a Python program becomes one of those plugins. MIT licensed, standard library only.

Status: BranchKit is pre-launch; the application is in private development, and this SDK is published and usable today. Versions are 0.x, so a minor release can break callers — CHANGELOG.md says what changed and how to migrate.

Install

Python 3.11 or later. A plugin carries its own copy of the SDK — a plugin runs sandboxed, with no network and no pip — so install it into the plugin directory:

python3 -m pip install --target . branchkit                                  # latest on PyPI
python3 -m pip install --target . git+https://github.com/branchkit/plugin-sdk-py  # this repository's main
branchkit-cli runtime install python                                         # once per machine

PyPI releases can lag this repository; the changelog says what each version contains. The last command installs the CPython that BranchKit runs Python plugins with, which the manifest asks for with requires.runtimes.

The fastest start is the scaffold, which writes a working plugin and vendors the SDK for you:

branchkit-cli dev init --name my-plugin --template py

A minimal plugin

A plugin is a directory with a manifest, the commands it contributes, and a program. This is what dev init writes, trimmed.

plugin.json declares who the plugin is, what it may do, and what it offers:

{
  "id": "my-plugin",
  "name": "My Plugin",
  "version": "0.1.0",
  "min_api_version": "0.2.0",
  "requires": { "privileges": ["input"], "runtimes": ["python"] },
  "run": "python3 main.py",
  "action_prefix": "myplugin",
  "action_types": {
    "greet": { "label": "Greet", "fields": [{ "key": "name", "label": "Name", "field_type": "string" }] }
  },
  "collection_data": { "voice_commands": "commands.json" },
  "implements": { "on_action": true }
}

commands.json maps a spoken phrase to an action:

[
  {
    "pattern": ["hello", "branchkit"],
    "action": { "type": "myplugin.greet", "params": { "name": "BranchKit" } },
    "description": "Say Hello BranchKit"
  }
]

main.py handles the action:

import asyncio

import branchkit

plugin = branchkit.Plugin()


@plugin.handle_action("myplugin.greet")
async def greet(req):
    name = (req["params"] or {}).get("name") or "BranchKit"
    await plugin.input_type_text(text=f"Hello, {name}!")


asyncio.run(plugin.run())  # returns when BranchKit stops the plugin

Say "hello branchkit" and the plugin types Hello, BranchKit! at the cursor. Typing needs the input privilege, which is why the manifest asks for it.

branchkit-gen --plugin . generates actions_gen.py from the manifest: a TypedDict for each action's params and a registrar, so the action string is never spelled by hand.

Handlers may be async def, run on the event loop, or plain def, run on a worker thread so a blocking body cannot stall the plugin. In a plain def, call the platform with plugin.call_sync(method, params) instead of await.

Calling the platform

Every platform method has a generated method on Plugin. Its arguments are keyword-only, and it returns the typed result:

rec = await plugin.collection_fetch(id=record_id, name="notes")

await plugin.hud_create_channel(channel="status", accepts_input=True)

Arguments are always named, never positional, so each one says what it is; a positional call fails at once with a TypeError. Leave an optional argument out to mean "absent". The generated methods are in branchkit/methods_gen.py and their result types in branchkit/types_gen.py, with the platform's own description on every parameter.

The manifest is the permission. A call that needs a privilege the manifest did not declare is refused before it runs, with an error of kind forbidden naming the operation. Some privileges also ask the user the first time. Network access works the same way: a host not listed under requires.network is refused with a HostRefusedError.

Errors from the platform are branchkit.RpcCallError (code, kind, data), and branchkit.error_kind_of(e) reads the kind from any exception:

try:
    await plugin.input_type_text(text="hi")
except branchkit.RpcCallError as e:
    if branchkit.error_kind_of(e) == "forbidden":
        ...  # declare the privilege

UnsupportedError (a method this OS does not provide), RecordingDisabledError and CallTimeoutError are the specific cases.

await plugin.call(method, params) is the untyped escape hatch, for a method too new to have a generated wrapper. Prefer the wrapper whenever one exists.

What the SDK covers

Need API
Handle an action @plugin.handle_action("prefix.name") (alias @plugin.action), registrars from actions_gen.py
Serve your own method @plugin.handle("method"), plugin.handle_command
React to events @plugin.on(event), plugin.on_pattern("ext.acme.**", fn), plugin.current_event_origin(); emit with events_emit
Store state get / list / list_page / count / put / put_many / patch / delete / replace, subscribe
Append-only logs append, append_keyed, list_log, get_log_entry, delete_log_entry
Keep a live copy plugin.mirror_collection(name), plugin.settings(name)
Contribute commands branchkit.command(word("open"), capture("app", "apps")).action(…).build(), push_command_specs, push_command_group
A settings tab @plugin.settings_tab(key) + implements.settings_tabs in the manifest; post_button / signal_button / confirm_button
Show something output_state(state=…) with say_action / dispatch_action, hud_push
Hold a system effect assert_effect, retract_effect, is_effect_active, on_effect_displaced
Trace a request plugin.current_correlation(), with branchkit.acting_for(actor):
Log await plugin.info / warn / error / debug / trace(tag, data) to your plugin's log (debug and trace are off by default)
Outbound HTTP urllib.request.urlopen (routed through the platform's proxy), branchkit.UpstreamClient
Raw TCP (MQTT, a local daemon) branchkit.dial(host, port)
Accept local connections branchkit.listen_local(plugin) with requires.sockets.listen
Test a plugin branchkit.harness.Harness

Package branchkit.pipeline is for pipeline stages (audio and monitor processes on a separate wire), not for ordinary plugins.

Testing a plugin

Harness loads your plugin against a simulated platform and matches phrases the way the real matcher does, without audio:

import unittest

from branchkit.harness import Harness


class GreetTests(unittest.TestCase):
    def test_greet_matches(self):
        with Harness.start(".") as h:
            result = h.must_simulate_command("hello branchkit")
            self.assertEqual(result.action_type(), "myplugin.greet")

It runs the branchkit-test-harness binary that ships inside BranchKit.app; set BRANCHKIT_TEST_HARNESS to its path anywhere else. python3 -m unittest runs your tests; branchkit-cli dev test . checks the manifest and runs the platform's own conformance checks against the plugin.

Against the running app:

branchkit-cli plugin install . --build            # install it
branchkit-cli dev watch .                          # reload on save
branchkit-cli dev say "hello branchkit" --simulate # match and report, execute nothing
branchkit-cli dev plog my-plugin --since 30s       # read its log

Learn more

  • API reference: the docstrings on every export (help(branchkit)), and the type hints your editor reads.
  • Local docs: branchkit-cli docs path prints the documentation bundled with your installed BranchKit, for reading or grepping offline.
  • Worked examples: helloworld-py (the scaffold), and real plugins built on the Go SDK with the same surface: keyboard, system, windows.

Versioning

Tags follow semver, 0.x for now: a minor release may break callers, and CHANGELOG.md names every break with its migration. The Go (plugin-sdk-go), TypeScript (plugin-sdk-ts) and Python SDKs implement the same surface and are held to it by one cross-language conformance suite.

Developing the SDK

python3 -m unittest discover -s tests

Files ending in _gen.py are generated from the platform's contracts; change the contract, not the file.

Metadata

Release files for branchkit 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for branchkit 0.5.0
File Size Uploaded
branchkit-0.5.0.tar.gz 229.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for branchkit 0.5.0
File Interpreter ABI Platform
branchkit-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 448.5 kB

Release files / branchkit-0.5.0.tar.gz

Download URL branchkit-0.5.0.tar.gz
Size 229.4 kB
Tags Source
SHA-256 checksum
How to use checksums
d09763bacb8d7d73fb12d931fd6857058a5bd26cc3e54d21f954ff6b4c46b1e6
BLAKE2b-256 checksum
How to use checksums
34b0958275d2b07bb498d54400820e5160923ce4a4502f0f298534a4422d3833
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.

Transparency log

Release files / branchkit-0.5.0-py3-none-any.whl

Download URL branchkit-0.5.0-py3-none-any.whl
Size 219.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3c99928a2a95dbbf1d568dc1d07f0726d92eb8a9d2202827dc3ad9c205666f3a
BLAKE2b-256 checksum
How to use checksums
510ae4039a2d6b00b950410acd4682c28af82530431b340e0988287be0f84660
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page