Plugin System
clitic provides a flexible plugin system for extending functionality.
ContentPlugin
Base class for content rendering plugins. Content plugins allow you to render different content types (Markdown, code, etc.) in the Conversation widget.
- class clitic.plugins.ContentPlugin[source]
Bases:
ABCAbstract base class for content renderers.
Content plugins are responsible for rendering specific types of content in the conversation display. Each plugin declares what content types it can handle and provides both synchronous and asynchronous rendering.
- name
Human-readable name of the plugin.
- priority
Priority for plugin ordering (higher = preferred).
- property priority: int
Return the priority for this plugin.
Higher priority plugins are checked first when determining which plugin should render content. Default is 0.
- Returns:
Priority value (higher = more preferred).
- abstractmethod can_render(content_type: str, content: str | Renderable) bool[source]
Check if this plugin can render the given content.
- Parameters:
content_type – MIME type or identifier for the content.
content – The content to potentially render.
- Returns:
True if this plugin can render the content, False otherwise.
- abstractmethod render(content: str | Renderable) object[source]
Render content to a renderable object.
- Parameters:
content – The content to render.
- Returns:
A Rich renderable (e.g., rich.markdown.Markdown, rich.text.Text) or Textual Widget that can be rendered by Conversation.
- Raises:
RenderError – If rendering fails.
- async render_async(content: str | Renderable) object[source]
Asynchronously render content to a renderable object.
Default implementation calls the synchronous render method. Subclasses may override for async rendering (e.g., fetching resources).
- Parameters:
content – The content to render.
- Returns:
A Rich renderable or Textual Widget.
- Raises:
RenderError – If rendering fails.
Built-in Plugins
MarkdownPlugin
clitic includes a built-in Markdown plugin that renders Markdown content using Rich’s Markdown renderer:
from clitic import App, Conversation
from clitic.plugins import MarkdownPlugin
app = App(title="My App")
app.register_plugin(MarkdownPlugin())
# Pass plugins to Conversation
conversation = Conversation(plugins=app.get_plugins())
conversation.append(
"assistant",
"# Hello\n\nThis is **bold** text.",
metadata={"content_type": "text/markdown"}
)
The MarkdownPlugin handles these content types:
text/markdown(standard MIME type)markdown(short form)markdown/*(any variant)
Creating a Custom Content Plugin
from clitic.plugins import ContentPlugin
from rich.text import Text
from rich.markdown import Markdown
class MyPlugin(ContentPlugin):
@property
def name(self) -> str:
"""Human-readable name of the plugin."""
return "MyPlugin"
@property
def priority(self) -> int:
"""Priority for plugin ordering (higher = preferred)."""
return 10
def can_render(self, content_type: str, content: str) -> bool:
"""Check if this plugin can render the given content type."""
return content_type == "my-type"
def render(self, content: str):
"""Render content to a Rich renderable.
Returns:
A Rich renderable (e.g., Text, Markdown, Table)
"""
# Return Rich renderable for the content
return Text(f"[MyPlugin] {content}")
async def render_async(self, content: str):
"""Optional async rendering (defaults to sync render)."""
return self.render(content)
def on_register(self, app):
"""Called when registered with app."""
pass
def on_unregister(self, app):
"""Called when unregistered."""
pass
Using Plugins with Conversation
from clitic import App, Conversation
from clitic.plugins import MarkdownPlugin
# Create app and register plugins
app = App(title="My App")
app.register_plugin(MarkdownPlugin())
# Pass plugins to Conversation
conversation = Conversation(plugins=app.get_plugins())
# Add content with content_type metadata
conversation.append(
"assistant",
"# Welcome\n\n- Item 1\n- Item 2\n\n```python\nprint('Hello')\n```",
metadata={"content_type": "text/markdown"}
)
ModeProvider
Base class for input mode providers.
- class clitic.plugins.ModeProvider[source]
Bases:
ABCAbstract base class for input mode providers.
Mode providers detect and handle different input modes (e.g., markdown, code blocks, plain text). They provide syntax highlighting and transform input text when entering or exiting the mode.
- name
Human-readable name of the mode.
- indicator
Short indicator displayed in the input bar.
- priority
Priority for mode detection (higher = preferred).
- abstract property indicator: str
Return the short indicator for this mode.
This is displayed in the input bar to show the current mode.
- property priority: int
Return the priority for this mode provider.
Higher priority providers are checked first when detecting the current input mode. Default is 0.
- Returns:
Priority value (higher = more preferred).
- abstractmethod detect(text: str, cursor_position: int) bool[source]
Detect if this mode should be active.
- Parameters:
text – Current input text.
cursor_position – Current cursor position in the text.
- Returns:
True if this mode should be active, False otherwise.
- abstractmethod get_highlighter() Highlighter | None[source]
Get the syntax highlighter for this mode.
- Returns:
A Highlighter instance, or None if no highlighting is available.
Creating a Mode Provider
from clitic.plugins import ModeProvider, Highlighter
class ShellModeProvider(ModeProvider):
@property
def name(self) -> str:
return "shell"
@property
def indicator(self) -> str:
return "$"
@property
def priority(self) -> int:
return 5
def detect(self, text: str, cursor_position: int) -> bool:
# Return True if this mode should activate
return text.startswith("$ ")
def get_highlighter(self) -> Highlighter | None:
return ShellHighlighter()
def on_enter(self, text: str) -> str:
# Transform text on mode entry
return text
def on_exit(self, text: str) -> str:
# Transform text on mode exit
return text
Protocols
Renderable
Highlighter
Plugin Priority
Plugins are checked in priority order (highest first). Use the priority property to control ordering:
Priority |
Use Case |
|---|---|
100+ |
Custom overrides |
50-99 |
Specialized renderers |
10-49 |
Standard renderers (MarkdownPlugin uses 10) |
1-9 |
Fallback renderers |
0 |
Default (checked last) |
Plugin Architecture
Content Flow
When content is added to a Conversation with content_type metadata:
Conversation checks registered plugins for a match
Plugins are sorted by priority (highest first)
First plugin with
can_render(content_type, content) == Trueis selectedPlugin’s
render()method converts content to Rich renderableRich renderable is converted to Strips for virtual rendering
If no plugin matches or rendering fails, falls back to plain text
Error Handling
If a plugin’s render() method raises an exception, the Conversation falls back to plain text rendering and logs the error. This ensures the application remains functional even with broken plugins.
Performance
Plugin content is pre-rendered to Strips when added to the Conversation, maintaining O(1) virtual rendering performance. The Strips are cached and only re-rendered on resize events.