Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions examples/plugin_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""Example demonstrating how to use plugins with Claude Code SDK.

Plugins allow you to extend Claude Code with custom commands, agents, skills,
and hooks. This example shows how to load a local plugin and verify it's
loaded by checking the system message.

The demo plugin is located in examples/plugins/demo-plugin/ and provides
a custom /greet command.
"""

from pathlib import Path

import anyio

from claude_agent_sdk import (
ClaudeAgentOptions,
SystemMessage,
query,
)


async def plugin_example():
"""Example showing plugins being loaded in the system message."""
print("=== Plugin Example ===\n")

# Get the path to the demo plugin
# In production, you can use any path to your plugin directory
plugin_path = Path(__file__).parent / "plugins" / "demo-plugin"

options = ClaudeAgentOptions(
plugins=[
{
"type": "local",
"path": str(plugin_path),
}
],
max_turns=1, # Limit to one turn for quick demo
)

print(f"Loading plugin from: {plugin_path}\n")

found_plugins = False
async for message in query(prompt="Hello!", options=options):
if isinstance(message, SystemMessage) and message.subtype == "init":
print("System initialized!")
print(f"System message data keys: {list(message.data.keys())}\n")

# Check for plugins in the system message
plugins_data = message.data.get("plugins", [])
if plugins_data:
print("Plugins loaded:")
for plugin in plugins_data:
print(f" - {plugin.get('name')} (path: {plugin.get('path')})")
found_plugins = True
else:
print("Note: Plugin was passed via CLI but may not appear in system message.")
print(f"Plugin path configured: {plugin_path}")
found_plugins = True

if found_plugins:
print("\nPlugin successfully configured!\n")


async def main():
"""Run all plugin examples."""
await plugin_example()


if __name__ == "__main__":
anyio.run(main)
8 changes: 8 additions & 0 deletions examples/plugins/demo-plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "demo-plugin",
"description": "A demo plugin showing how to extend Claude Code with custom commands",
"version": "1.0.0",
"author": {
"name": "Claude Code Team"
}
}
5 changes: 5 additions & 0 deletions examples/plugins/demo-plugin/commands/greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Greet Command

This is a custom greeting command from the demo plugin.

When the user runs this command, greet them warmly and explain that this message came from a custom plugin loaded via the Python SDK. Tell them that plugins can be used to extend Claude Code with custom commands, agents, skills, and hooks.
3 changes: 3 additions & 0 deletions src/claude_agent_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
PreCompactHookInput,
PreToolUseHookInput,
ResultMessage,
SdkPluginConfig,
SettingSource,
StopHookInput,
SubagentStopHookInput,
Expand Down Expand Up @@ -339,6 +340,8 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> Any:
# Agent support
"AgentDefinition",
"SettingSource",
# Plugin support
"SdkPluginConfig",
# MCP Server Support
"create_sdk_mcp_server",
"tool",
Expand Down
8 changes: 8 additions & 0 deletions src/claude_agent_sdk/_internal/transport/subprocess_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,14 @@ def _build_command(self) -> list[str]:
)
cmd.extend(["--setting-sources", sources_value])

# Add plugin directories
if self._options.plugins:
for plugin in self._options.plugins:
if plugin["type"] == "local":
cmd.extend(["--plugin-dir", plugin["path"]])
else:
raise ValueError(f"Unsupported plugin type: {plugin['type']}")

# Add extra args for future CLI flags
for flag, value in self._options.extra_args.items():
if value is None:
Expand Down
12 changes: 12 additions & 0 deletions src/claude_agent_sdk/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,16 @@ class McpSdkServerConfig(TypedDict):
)


class SdkPluginConfig(TypedDict):
"""SDK plugin configuration.

Currently only local plugins are supported via the 'local' type.
"""

type: Literal["local"]
path: str


# Content block types
@dataclass
class TextBlock:
Expand Down Expand Up @@ -542,6 +552,8 @@ class ClaudeAgentOptions:
agents: dict[str, AgentDefinition] | None = None
# Setting sources to load (user, project, local)
setting_sources: list[SettingSource] | None = None
# Plugin configurations for custom plugins
plugins: list[SdkPluginConfig] = field(default_factory=list)


# SDK Control Protocol
Expand Down