Extension Guide¶
This guide documents VIBE's extension system: how extensions work, how to create them, and the roadmap for a more complete extension API.
Overview¶
VIBE supports extensions through several mechanisms:
- CLI Extensions - Add subcommands to the
vibeCLI - Web Extensions - Add Flask blueprints and routes
- Handler Registry - Register custom data type handlers
- Interview Mode Layouts - Custom UI layouts (currently partially hardcoded)
Current Extension APIs¶
CLI Extensions¶
CLI extensions add subcommands to the vibe command. They follow a protocol-based registration pattern.
Location: vibe/cli/extensions.py
Protocol:
class CLIExtension(Protocol):
"""Protocol for CLI extensions."""
name: str # Command name (e.g., "review")
help: str # Help text for the command
quiet_by_default: bool # Whether to set VIBE_QUIET_MODE
def setup_parser(self, subparsers: _SubParsersAction) -> ArgumentParser:
"""Add extension's parser(s) to the CLI subparsers."""
...
def dispatch(self, args: Namespace) -> int:
"""Handle parsed arguments. Returns exit code."""
...
Registration:
# In your extension's cli.py
from vibe.cli.extensions import register_extension
class MyExtension:
name = "myext"
help = "My extension commands"
quiet_by_default = False
def setup_parser(self, subparsers):
parser = subparsers.add_parser(self.name, help=self.help)
parser.add_argument("--option", help="An option")
return parser
def dispatch(self, args):
# Handle the command
return 0
# Register on module import
register_extension(MyExtension())
Discovery:
Extensions are discovered by importing known module paths in discover_extensions(). New extensions must be added to the discovery list:
# vibe/cli/extensions.py
extension_modules = [
"vibe.review.cli",
"your.extension.cli", # Add here
]
Example: See vibe/review/cli.py for a complete implementation with subcommands.
Web Extensions¶
Web extensions add Flask blueprints to the application. They follow a similar protocol-based pattern.
Location: vibe/web/extensions.py
Protocol:
class WebExtension(Protocol):
"""Protocol for web extensions."""
name: str
@property
def blueprints(self) -> Sequence[tuple[Blueprint, str | None]]:
"""Return blueprints to register as (blueprint, url_prefix) tuples."""
...
def init_app(self, app: Flask) -> None:
"""Initialize extension with the Flask app (optional setup)."""
...
Registration:
# In your extension's web/__init__.py
from flask import Blueprint
from vibe.web.extensions import register_extension
review_bp = Blueprint("review", __name__, template_folder="templates")
class ReviewWebExtension:
name = "review"
@property
def blueprints(self):
return [(review_bp, "/review")]
def init_app(self, app):
# Optional: Add template search paths, context processors, etc.
pass
register_extension(ReviewWebExtension())
Discovery:
Same pattern as CLI - modules are listed in discover_extensions():
Example: See vibe/review/web/__init__.py for a complete implementation.
Handler Registry¶
Data type handlers process different field types (Text, Bool, Enum, etc.). They use a decorator-based registry.
Location: vibe/handlers/base.py
Registration:
from vibe.handlers.base import DataTypeHandler, register_handler
@register_handler("mytype")
class MyTypeHandler(DataTypeHandler):
"""Handler for 'type: mytype' fields."""
def validate(self, value, field_config, context):
# Validate the value
return value
def get_widget_context(self, field_name, field_config, value, context):
# Return template context for rendering
return {"field_name": field_name, "value": value}
Handlers are automatically discovered when the handler modules are imported.
Interview Mode Extensions¶
Interview modes control the UI layout for templates. The Interview Mode Extension API allows extensions to register custom modes without modifying core VIBE code.
Location: vibe/interview_modes/
Available Modes¶
| Mode | Type | Description | Layout Template |
|---|---|---|---|
standard |
Built-in | Two-column: questions + preview | layout/standard.html |
assistant |
Extension | Conversational AI workbench | assistant/layout.html |
review |
Extension | Document review workbench | layout/review.html |
Configuration¶
Modes are specified in config.yml:
Protocol¶
# vibe/interview_modes/base.py
from typing import Protocol, Optional, Any
from flask import Response
from vibe.structures import TemplateData
class InterviewModeExtension(Protocol):
"""Protocol for interview mode extensions."""
name: str # Mode name used in config.yml (e.g., "review", "assistant")
def get_layout_template(self) -> str:
"""Return the Jinja template path for this mode's layout.
Example: 'layout/review.html' or 'assistant/layout.html'
"""
...
def handle_start(
self,
template: TemplateData,
version: Optional[str]
) -> Optional[Response]:
"""Handle the /interview/<template_id>/ route for this mode.
Return a Response to override default behavior (e.g., redirect).
Return None to use the standard interview page with this mode's layout.
"""
...
def get_template_context(
self,
template: TemplateData,
base_context: dict
) -> dict:
"""Extend the template context for this mode's layout.
Args:
template: The template being rendered
base_context: Standard context (questions, preview, etc.)
Returns:
Extended context dict for the layout template
"""
...
Proposed Registration¶
# vibe/interview_modes/registry.py (proposed)
_modes: dict[str, InterviewModeExtension] = {}
def register_interview_mode(mode: InterviewModeExtension) -> None:
"""Register an interview mode extension."""
_modes[mode.name] = mode
def get_interview_mode(name: str) -> InterviewModeExtension | None:
"""Get a registered interview mode by name."""
return _modes.get(name)
def get_layout_for_mode(name: str) -> str:
"""Get the layout template path for a mode."""
mode = _modes.get(name)
if mode:
return mode.get_layout_template()
# Fallback to built-in modes
return {
"standard": "layout/standard.html",
}.get(name, "layout/standard.html")
Example: Review Mode Extension¶
# vibe/review/interview_mode.py
from flask import redirect, url_for
from vibe.interview_modes import register_interview_mode
class ReviewModeExtension:
name = "review"
def get_layout_template(self) -> str:
return "layout/review.html"
def handle_start(self, template, version):
# Redirect to review module's session form
return redirect(url_for(
'review.new_session_form',
template_id=template.template_id
))
def get_template_context(self, template, base_context):
# Add review-specific context
return {
**base_context,
"review_mode": True,
}
# Register on module import
def _register():
register_interview_mode(ReviewModeExtension())
_register()
Example: Assistant Mode Extension¶
# vibe/assistant/interview_mode.py
class AssistantModeExtension:
name = "assistant"
def get_layout_template(self) -> str:
return "assistant/layout.html"
def handle_start(self, template, version):
# Use standard interview page with assistant layout
return None
def get_template_context(self, template, base_context):
# Add assistant mode flags
context = {**base_context, "assistant_mode": True}
if template.assistant_prompts:
context["skip_question_rendering"] = True
context["skip_preview_rendering"] = True
return context
# Register on module import
def _register():
register_interview_mode(AssistantModeExtension())
_register()
Core Integration¶
The core uses the extension API to dispatch to modes:
# vibe/web/routes/interview.py
from vibe.interview_modes import get_interview_mode
@interview_bp.route("/interview/<template_id>/")
def start_interview(template, **kwargs):
# Check for interview mode extension that overrides entry behavior
mode_ext = get_interview_mode(template.interview_mode)
if mode_ext:
response = mode_ext.handle_start(template, version)
if response:
return response
# Standard rendering with mode's layout
return render_interview_page(template, mode=template.interview_mode)
The pages/interview.html template uses dynamic layout selection:
{# pages/interview.html #}
{% extends 'layout/shell.html' %}
{% block content %}
{# Use layout_template from context (set by interview_modes extension API) #}
{% if layout_template is defined %}
{% include layout_template %}
{% else %}
{# Fallback for built-in modes #}
{% include 'layout/standard.html' %}
{% endif %}
{% endblock %}
Type Definition¶
The interview_mode field in structures.py uses Optional[str] with runtime validation:
# vibe/structures.py
@dataclass
class TemplateData:
# Interview mode - validated at runtime via interview_modes.is_valid_interview_mode()
# Built-in modes: "standard"
# Extension modes: "assistant", "review", and any registered via register_interview_mode()
interview_mode: Optional[str] = None
The registry provides validation via is_valid_interview_mode():
from vibe.interview_modes.registry import is_valid_interview_mode
if not is_valid_interview_mode(mode):
raise ValueError(f"Unknown interview_mode: {mode}")
Creating a New Extension¶
Checklist¶
-
Create module structure:
-
Implement CLI extension (if needed):
- Create class implementing
CLIExtensionprotocol - Call
register_extension()at module level - Add module to
vibe/cli/extensions.pydiscovery list
- Create class implementing
-
Implement Web extension (if needed):
- Create Flask blueprint(s)
- Create class implementing
WebExtensionprotocol - Call
register_extension()at module level - Add module to
vibe/web/extensions.pydiscovery list
-
Implement Interview Mode (if creating a new UI mode):
- Create layout template extending
shell.html - Create class implementing
InterviewModeExtensionprotocol - Call
register_interview_mode()at module level - Add module to
vibe/interview_modes/registry.pydiscovery list
- Create layout template extending
Best Practices¶
- Use protocol-based design - Don't subclass, implement protocols
- Register on import - Extensions auto-register when modules are imported
- Keep templates separate - Use
template_folderin blueprints - Extend, don't replace - Build on top of
shell.htmlandfoundation.css - Handle missing dependencies - Extensions should gracefully fail if deps aren't installed
See Also¶
vibe/interview_modes/- Interview mode extension APIvibe/review/- Complete extension example (CLI, Web, Interview Mode)vibe/assistant/- Complex extension with LLM integrationvibe/handlers/- Handler registry patterndoc/ui-customization.md- Template customization guide