Python SDK Reference
Complete API reference for the musher-sdk package. The SDK requires Python 3.13+.
Module-Level Functions
pull(ref: str) -> Bundle
Resolve, fetch, and verify a bundle. Returns a Bundle with typed access to its
contents. Uses the global configuration set via configure().
pull_async(ref: str) -> Bundle
Async version of pull().
resolve(ref: str) -> ResolveResult
Resolve a bundle reference to its manifest metadata without fetching asset content. Useful for inspecting versions, listing assets, or validating a reference.
resolve_async(ref: str) -> ResolveResult
Async version of resolve().
configure(**kwargs) -> None
Set global SDK configuration. Accepts any MusherConfig field as a keyword argument.
import musher
musher.configure(
token="mush_...", # API token (auto-discovered if omitted)
verify_checksums=True, # default
timeout=30.0, # seconds
max_retries=2,
)get_config() -> MusherConfig
Retrieve the active global configuration with auto-discovered credentials.
resolve_registry_url() -> str
Get the registry URL from the MUSHER_API_URL environment variable, or return the
default (https://api.musher.dev).
cache_info() -> CacheInfo
Return a snapshot of the local cache contents including path, total size, bundle count, and per-bundle version details. Does not require authentication.
cache_path() -> Path
Return the active cache directory path.
cache_remove(ref: str) -> None
Remove all cached data for a specific bundle reference.
cache_clear() -> None
Delete the entire cache directory.
cache_clean() -> int
Remove expired manifests and refs, then garbage-collect unreferenced blobs. Returns the number of removed entries.
Client Classes
Client
Synchronous bundle registry client. Uses a background event loop internally, so it works in notebooks and other environments where an event loop is already running.
from musher import Client
with Client() as client:
bundle = client.pull("acme/code-review-kit")
result = client.resolve("acme/code-review-kit:^1.0")Constructor
Client(config: MusherConfig | None = None)
If config is None, uses the global configuration from configure() with automatic credential discovery.
Methods
| Method | Returns | Description |
|---|---|---|
pull(ref) | Bundle | Resolve, fetch, and verify a bundle |
resolve(ref) | ResolveResult | Resolve metadata without fetching |
fetch_asset(logical_path, *, namespace, slug, version=None) | Asset | Fetch a single asset by logical path |
cache_info() | CacheInfo | Inspect cached bundles and sizes |
cache_path() | Path | Get cache directory path |
cache_remove(ref) | — | Remove a bundle from cache |
cache_clear() | — | Clear entire cache |
cache_clean() | — | Remove expired entries and GC blobs |
close() | — | Release resources and stop background loop |
Supports context manager protocol (with Client() as client:).
AsyncClient
Asynchronous bundle registry client with the same method signatures as Client.
from musher import AsyncClient
async with AsyncClient() as client:
bundle = await client.pull("acme/code-review-kit")Supports async context manager protocol (async with AsyncClient() as client:). Same
methods as Client, all returning awaitables.
Bundle
A resolved bundle with its fetched assets. Returned by pull() and client.pull().
Typed Accessors
| Method | Returns | Description |
|---|---|---|
files() | list[FileHandle] | All files in the bundle |
file(logical_path) | FileHandle | None | Single file by logical path |
skills() | list[SkillHandle] | All skills in this bundle |
skill(name) | SkillHandle | Single skill by name (raises KeyError) |
prompts() | list[PromptHandle] | All prompts in this bundle |
prompt(name) | PromptHandle | Single prompt by name (raises KeyError) |
toolsets() | list[ToolsetHandle] | All toolsets in this bundle |
toolset(name) | ToolsetHandle | Single toolset by name (raises KeyError) |
agent_specs() | list[AgentSpecHandle] | All agent specs in this bundle |
agent_spec(name) | AgentSpecHandle | Single agent spec by name (raises KeyError) |
Selection
select() returns a BundleSelection — a filtered view with the same
accessor methods as Bundle.
subset = bundle.select(
skills=["code-review"],
prompts=["system"],
toolsets=["lint-rules"],
agent_specs=["reviewer"],
)
for skill in subset.skills():
print(skill.name)Export Methods
| Method | Returns | Description |
|---|---|---|
export_claude_plugin(plugin_name, skills=None, dest=None) | ClaudePluginExport | Export as a Claude plugin directory |
install_claude_skills(dest, skills=None, *, clean=False) | — | Install skills to a Claude skills directory |
Handle Types
FileHandle
| Member | Type | Description |
|---|---|---|
.logical_path | str | Path within the bundle |
.media_type | str | None | MIME type, if known |
.text(encoding="utf-8") | str | Decode content as text |
.bytes() | bytes | Raw content bytes |
SkillHandle
| Member | Type | Description |
|---|---|---|
.name | str | Skill name |
.description | str | Skill description |
.root_path | str | Root path within the bundle |
.files() | list[FileHandle] | All files in this skill |
.file(relative_path) | FileHandle | None | Single file by relative path |
.skill_md() | FileHandle | The SKILL.md file |
.export_openai_local_skill(dest=None) | OpenAILocalSkill | Export for local OpenAI use |
.export_openai_inline_skill() | OpenAIInlineSkill | Export as base64 zip for OpenAI |
.export_path(dest=None) | Path | Write skill files to a directory |
.export_zip(dest=None) | Path | Write skill files to a zip archive |
PromptHandle
| Member | Type | Description |
|---|---|---|
.name | str | Prompt name |
.file | FileHandle | Underlying file handle |
.text() | str | Prompt text content |
ToolsetHandle
| Member | Type | Description |
|---|---|---|
.name | str | Toolset name |
.file | FileHandle | Underlying file handle |
.text() | str | Toolset text content |
.parse_json() | dict[str, object] | Parse content as JSON |
AgentSpecHandle
| Member | Type | Description |
|---|---|---|
.name | str | Agent spec name |
.file | FileHandle | Underlying file handle |
.text() | str | Agent spec text content |
.parse_json() | dict[str, object] | Parse content as JSON |
Export Result Types
Frozen dataclasses returned by export methods.
ClaudePluginExport
.plugin_name: str— Name of the exported plugin.path: Path— Directory where plugin files were written
OpenAILocalSkill
.name: str.description: str.path: Path— Directory where skill files were written.to_dict() -> dict[str, str]— Serialize for tool registration
OpenAIInlineSkill
.name: str.description: str.content_base64: str— Base64-encoded zip content.to_dict() -> dict[str, object]— Generate OpenAI inline skill format
Cache Info Types
Frozen dataclasses returned by cache_info().
CacheInfo
| Field | Type | Description |
|---|---|---|
path | Path | Cache directory path |
total_size_bytes | int | Total cache size in bytes |
bundle_count | int | Number of cached bundles |
version_count | int | Number of cached versions |
blob_count | int | Number of content-addressable blobs on disk |
bundles | tuple[CachedBundle, ...] | Per-bundle details |
CachedBundle
| Field | Type | Description |
|---|---|---|
namespace | str | Bundle namespace |
slug | str | Bundle slug |
host | str | Registry host |
versions | tuple[CachedBundleVersion, ...] | Cached versions |
total_size_bytes | int | Total size of this bundle's cached data in bytes |
CachedBundleVersion
| Field | Type | Description |
|---|---|---|
version | str | Semver version string |
size_bytes | int | Version size in bytes |
fetched_at | datetime | None | When this version was fetched from the registry |
is_fresh | bool | Whether the cached entry is within its TTL |
Configuration
MusherConfig
| Field | Type | Default | Description |
|---|---|---|---|
token | str | None | None | API token — if unset, auto-discovered |
registry_url | str | "https://api.musher.dev" | OCI registry URL |
verify_checksums | bool | True | Verify SHA-256 content integrity on pull |
timeout | float | 30.0 | Request timeout in seconds |
max_retries | int | 2 | Number of retries for failed requests |
cache_dir | Path | Auto | Bundle cache directory |
config_dir | Path | Auto | Configuration directory |
data_dir | Path | Auto | Data directory (credentials, etc.) |
state_dir | Path | Auto | State directory |
Credential Discovery
When token is not set, MusherConfig.resolve_token() checks these sources in
order:
MUSHER_API_KEYenvironment variable- OS keyring (
musher/<host>) - Credential file at
<data_dir>/credentials/<host_id>/api-key(mode 0600)
BundleRef
Parsed bundle reference with slots. Frozen dataclass.
| Field | Type | Description |
|---|---|---|
namespace | str | Organization or user namespace |
slug | str | Bundle slug |
version | str | None | Semver version or range |
digest | str | None | Content digest for pinning |
BundleRef.parse(ref: str) -> BundleRef — Parse a reference string like "acme/code-review-kit:2.0.0".
Exceptions
All exceptions inherit from MusherError.
| Exception | When |
|---|---|
MusherError | Base class for all SDK errors |
AuthenticationError | Authentication failed or token is invalid |
BundleNotFoundError | Requested bundle does not exist |
VersionNotFoundError | Requested version does not exist |
IntegrityError | SHA-256 checksum verification failed (includes expected and actual hashes) |
RegistryError | Failed to communicate with OCI registry |
CacheError | Problems accessing local bundle cache |
RateLimitError | API rate limit exceeded (may include retry timing) |
APIError | RFC 9457 Problem Details response (captures status, title, detail, type URI) |
from musher import pull, BundleNotFoundError, IntegrityError
try:
bundle = pull("acme/code-review-kit:2.0.0")
except BundleNotFoundError:
print("Bundle not found")
except IntegrityError as e:
print(f"Checksum mismatch: expected {e.expected}, got {e.actual}")Enums
String enums (all inherit from StrEnum).
AssetType
AGENT_SPEC, SKILL, TOOLSET, PROMPT, CONFIG, OTHER
BundleVisibility
PRIVATE, PUBLIC
BundleVersionState
PUBLISHED, YANKED
BundleSourceType
CONSOLE, REGISTRY