plugins
familiar supports plugins via Python entry points:
- agent plugins - add new agents (gemini, aider, etc.)
- linter plugins - add custom lint rules for conjurings and invocations
agent plugins
using agent plugins
install a plugin package and the agent becomes available:
pip install familiar-gemini
familiar invoke gemini bootstrap-python myapp cli
installed agents appear in familiar invoke --help and familiar conjure --help.
creating an agent plugin
1. create the agent class
subclass Agent and implement the required interface:
# src/my_plugin/__init__.py
from __future__ import annotations
import subprocess
from pathlib import Path
from familiar.agents import Agent
class MyAgent(Agent):
"""Agent that uses the my-cli tool."""
name = "myagent" # CLI name: familiar invoke myagent ...
output_file = "MYAGENT.md" # where conjure writes instructions
def run(self, repo_root: Path, prompt: str, headless: bool) -> int:
"""Run the agent with the given prompt.
Args:
repo_root: Repository root directory.
prompt: The rendered invocation text.
headless: If True, run without interactive UI.
Returns:
Exit code from the underlying CLI tool.
"""
if headless:
cmd = ["my-cli", "-p", prompt]
else:
cmd = ["my-cli", prompt]
return subprocess.call(cmd, cwd=repo_root)
2. register the entry point
add to your pyproject.toml:
[project.entry-points."familiar.agents"]
myagent = "my_plugin:MyAgent"
3. install and test
pip install -e .
familiar invoke --help # should show myagent in choices
agent interface
| attribute/method | type | description |
|---|---|---|
name |
str |
agent name for CLI |
output_file |
str |
filename for conjured instructions |
run(repo_root, prompt, headless) |
-> int |
execute the agent, return exit code |
linter plugins
linter plugins add custom validation rules to familiar lint.
creating a linter plugin
1. create the linter function
linters are functions that take content and filename, returning a list of messages:
# src/my_plugin/linters.py
from familiar.lint import LintMessage
def check_max_length(content: str, name: str) -> list[LintMessage]:
"""Warn if any line exceeds 100 characters."""
messages = []
for i, line in enumerate(content.split("\n"), 1):
if len(line) > 100:
messages.append(
LintMessage(
level="warning",
file=name,
line=i,
message=f"line exceeds 100 characters ({len(line)})",
)
)
return messages
2. register the entry point
use familiar.linters.conjurings for conjuring linters, familiar.linters.invocations for invocation linters:
[project.entry-points."familiar.linters.conjurings"]
max-length = "my_plugin.linters:check_max_length"
[project.entry-points."familiar.linters.invocations"]
max-length = "my_plugin.linters:check_max_length"
3. test it
pip install -e .
familiar lint # your rules now run
linter interface
linters are callables with signature:
def my_linter(content: str, name: str) -> list[LintMessage]:
...
| parameter | type | description |
|---|---|---|
content |
str |
full text content of the file |
name |
str |
display name (e.g., .familiar/conjurings/foo.md) |
| returns | list[LintMessage] |
list of warnings/errors |
LintMessage
from dataclasses import dataclass
from typing import Literal
@dataclass
class LintMessage:
level: Literal["error", "warning"]
file: str
line: int | None # None if not line-specific
message: str
example plugins
- familiar-gemini - gemini CLI agent