Plugin System¶
Sase uses a plugin architecture based on pluggy and Python
entry points to allow extending functionality via
installable packages. The core sase package provides the plugin infrastructure and a minimal set of built-in plugins;
additional functionality is available through optional plugin packages.
Plugin Groups¶
Sase defines five entry point groups for plugin discovery:
| Entry Point Group | Purpose | Example Plugin |
|---|---|---|
sase_vcs |
VCS provider plugins (git, hg, etc.) | sase-github |
sase_workspace |
Workspace provider plugins (ref resolution, submit) | sase-github |
sase_llm |
LLM provider plugins beyond the bundled providers | external provider packages |
sase_xprompts |
XPrompt templates and workflows | sase-google |
sase_config |
Default configuration (default_config.yml) |
sase-google, sase-github |
Available Plugin Packages¶
| Package | Description | Entry Points |
|---|---|---|
sase (core) |
BareGitPlugin, Claude/Codex/Gemini/Qwen/OpenCode LLM providers | sase_vcs: bare_git, sase_workspace: bare_git, sase_llm: claude, codex, gemini, opencode, qwen |
sase-github |
GitHubPlugin with GitHub CLI (gh) PR operations |
sase_vcs: github, sase_workspace: github, sase_config, sase_xprompts |
sase-google |
HgPlugin for Mercurial, sase_hg_* helper commands, config, and xprompts |
sase_vcs: hg, sase_workspace: hg, sase_config, sase_xprompts |
sase-telegram |
Telegram integration via chop scripts (sase_chop_tg_outbound, sase_chop_tg_inbound) |
CLI scripts (not pluggy) |
sase-nvim |
Neovim integration (e.g., project spec syntax highlighting) | standalone (not pluggy) |
Installation¶
# Core sase (includes BareGitPlugin for plain git repos)
pip install sase
# Add GitHub PR support
pip install sase-github
# Add Mercurial support
pip install sase-google
How Plugins Are Discovered¶
Plugin discovery uses importlib.metadata.entry_points() to find installed packages that declare entry points in one of
sase's plugin groups. The shared discovery logic lives in src/sase/main/plugin_discovery.py.
For each group:
- All entry points for the group are loaded and sorted by name (for determinism).
- Each entry point is imported as a module.
- Modules that fail to load are silently skipped (logged at debug level).
VCS Plugins (pluggy)¶
VCS plugins use pluggy's hook system. The hook specification is defined in VCSHookSpec
(src/sase/vcs_provider/_hookspec.py). Each hook method uses firstresult=True, meaning the first plugin to return a
non-None result wins.
VCS plugins are loaded by VCSPluginManager which:
- Creates a
pluggy.PluginManagerwith the"sase_vcs"project name. - Registers the
VCSHookSpec. - Loads plugins from the
sase_vcsentry point group.
Workspace Plugins (pluggy)¶
Workspace plugins use pluggy's hook system, similar to VCS plugins. The hook specification is defined in
WorkspaceHookSpec (src/sase/workspace_provider/_hookspec.py). Most hooks use firstresult=True; the exception is
ws_get_workflow_metadata which collects results from all plugins. All hook method names are prefixed with ws_.
Workspace plugins are loaded by WorkspacePluginManager which:
- Creates a
pluggy.PluginManagerwith the"sase_workspace"project name. - Registers the
WorkspaceHookSpec. - Loads plugins from the
sase_workspaceentry point group.
See docs/workspace.md for the full workspace provider reference.
LLM Plugins (pluggy)¶
LLM provider plugins use pluggy's hook system. The hook specification is defined in LLMHookSpec
(src/sase/llm_provider/_hookspec.py). Core dispatch hooks (llm_invoke, llm_resolve_model_name) use
firstresult=True so the first matching plugin handles a call; metadata hooks (llm_provider_name,
llm_known_model_names, llm_skill_template_context, llm_skill_deploy_subpath, llm_cli_status_color,
llm_autodetect_priority, llm_autodetect_cli_name, llm_default_retry_config) are invoked per-plugin by the registry
so each provider contributes its own metadata. All hook method names are prefixed with llm_.
Core sase ships Claude, Codex, Gemini, Qwen, and OpenCode providers as built-in entry points. Additional providers
belong in external plugin packages that declare sase_llm entry points and provide their own metadata hooks.
See docs/llms.md for the full LLM provider reference, including authoring new providers with @hookimpl.
XPrompt Plugins¶
Plugin packages can contribute xprompt templates by declaring a sase_xprompts entry point that points to a module. The
module's package directory is searched for xprompts/*.md and xprompts/*.yml files. Plugin xprompts are priority 7 in
the discovery order (above built-in, below config-based).
Config Plugins¶
Plugin packages can provide default configuration by declaring a sase_config entry point. The referenced module's
package must contain a default_config.yml file. Plugin configs are merged between the bundled package defaults and the
user's sase.yml. See the Deep-Merge System for details on the merge chain.
Disabling Plugins¶
Plugins can be disabled via environment variables:
| Variable | Effect |
|---|---|
SASE_DISABLE_PLUGINS |
Disable all plugin groups |
SASE_DISABLE_PLUGIN_VCS |
Disable VCS plugins only |
SASE_DISABLE_PLUGIN_WORKSPACE |
Disable workspace plugins only |
SASE_DISABLE_PLUGIN_XPROMPTS |
Disable xprompt plugins only |
SASE_DISABLE_PLUGIN_CONFIG |
Disable config plugins only |
LLM plugins honor the global SASE_DISABLE_PLUGINS switch but do not currently have a dedicated per-group disable flag.
Any non-empty value enables the disable. This is useful for debugging or when a plugin causes issues.
Writing a Plugin¶
A sase plugin is a standard Python package that declares entry points in pyproject.toml.
Example: VCS Plugin¶
# pyproject.toml
[project.entry-points."sase_vcs"]
my_vcs = "my_sase_plugin.vcs:MyVCSPlugin"
[project.entry-points."sase_config"]
my_vcs = "my_sase_plugin"
The VCS plugin class implements hooks from VCSHookSpec using the @hookimpl decorator:
from sase.vcs_provider._hookspec import hookimpl
class MyVCSPlugin:
@hookimpl
def vcs_checkout(self, revision: str, cwd: str) -> tuple[bool, str | None]:
# Implementation here
...
@hookimpl
def vcs_diff(self, cwd: str) -> tuple[bool, str | None]:
# Implementation here
...
Methods should return None (implicitly or explicitly) for operations they don't support, allowing other plugins to
handle them.
Example: Workspace Plugin¶
# pyproject.toml
[project.entry-points."sase_workspace"]
my_workspace = "my_sase_plugin.workspace:MyWorkspacePlugin"
The workspace plugin class implements hooks from WorkspaceHookSpec using the @hookimpl decorator:
from sase.workspace_provider._hookspec import WorkflowMetadata, hookimpl
class MyWorkspacePlugin:
@hookimpl
def ws_get_workflow_metadata(self) -> WorkflowMetadata | None:
return WorkflowMetadata(
workflow_type="my_vcs",
ref_pattern=r"#my_vcs:(\w+)",
display_name="My VCS",
pre_allocated_env_prefix="SASE_MYVCS",
)
@hookimpl
def ws_detect_workflow_type(self, project_file: str) -> str | None:
# Return workflow type if this plugin handles the project
...
Example: XPrompt Plugin¶
Place xprompt files in your package's xprompts/ directory and register the module:
[project.entry-points."sase_xprompts"]
my_plugin = "my_sase_plugin"
my_sase_plugin/
├── __init__.py
└── xprompts/
├── my_template.md
└── my_workflow.yml
Example: Config Plugin¶
Place a default_config.yml alongside your module and register it:
[project.entry-points."sase_config"]
my_plugin = "my_sase_plugin"
my_sase_plugin/
├── __init__.py
└── default_config.yml
Plugin configs are merged using the deep-merge system. User config in sase.yml
takes precedence over plugin defaults.