CLI and Package Introspection Specification
Overview
A CLI tool for interrogating Claude Studio Producer’s capabilities, checking provider status, validating configuration, and running productions. Useful for development, debugging, and user onboarding.
Installation
# After pip install
claude-studio --help
# Or via module
python -m claude_studio --help
Command Structure
claude-studio
├── status # Overall system status
├── providers # Provider information
│ ├── list # List all providers
│ ├── check <name> # Check specific provider
│ └── test <name> # Test provider with real API call
├── agents # Agent information
│ ├── list # List all agents
│ └── schema <name> # Show agent input/output schema
├── config # Configuration
│ ├── show # Show current config
│ ├── validate # Validate API keys
│ └── init # Create .env template
├── memory # Memory and learnings management
│ ├── stats # Show memory statistics
│ ├── list [provider] # List learnings by provider
│ ├── search <query> # Search learnings
│ ├── add <provider> <pattern> # Add a learning
│ ├── export # Export learnings to JSON
│ ├── import <file> # Import learnings from JSON
│ ├── promote # Promote a learning to higher level
│ ├── clear # Clear learnings (with confirmation)
│ ├── tree # Show namespace hierarchy
│ ├── preferences # Show user preferences
│ └── set-pref <k> <v> # Set a preference
├── produce # Run production
├── render # Render video from existing run
├── test-provider # Test a single provider
├── luma # Luma API management
├── themes # List and preview color themes
└── version # Version info
Implementation
Entry Point (pyproject.toml)
[project.scripts]
claude-studio = "claude_studio.cli:main"
cli/init.py
"""Claude Studio Producer CLI"""
import click
from .status import status_cmd
from .providers import providers_cmd
from .agents import agents_cmd
from .config import config_cmd
@click.group()
@click.version_option()
def main():
"""Claude Studio Producer - AI Video Production Pipeline"""
pass
main.add_command(status_cmd, name="status")
main.add_command(providers_cmd, name="providers")
main.add_command(agents_cmd, name="agents")
main.add_command(config_cmd, name="config")
if __name__ == "__main__":
main()
cli/status.py
"""System status command"""
import click
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich import box
from claude_studio.core.providers import get_all_providers
from claude_studio.agents import get_all_agents
console = Console()
@click.command()
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
def status_cmd(as_json: bool):
"""Show overall system status"""
if as_json:
import json
click.echo(json.dumps(get_status_dict(), indent=2))
return
console.print(Panel.fit(
"[bold blue]Claude Studio Producer[/bold blue]\n"
"AI Video Production Pipeline",
border_style="blue"
))
# Provider summary
providers = get_all_providers()
implemented = sum(1 for p in providers if p["status"] == "implemented")
stubbed = sum(1 for p in providers if p["status"] == "stub")
provider_table = Table(title="Providers", box=box.ROUNDED)
provider_table.add_column("Category", style="cyan")
provider_table.add_column("Implemented", style="green")
provider_table.add_column("Stubbed", style="yellow")
categories = {}
for p in providers:
cat = p["category"]
if cat not in categories:
categories[cat] = {"implemented": 0, "stub": 0}
categories[cat][p["status"]] += 1
for cat, counts in categories.items():
provider_table.add_row(
cat.title(),
str(counts.get("implemented", 0)),
str(counts.get("stub", 0))
)
console.print(provider_table)
# Agent summary
agents = get_all_agents()
agent_table = Table(title="Agents", box=box.ROUNDED)
agent_table.add_column("Agent", style="cyan")
agent_table.add_column("Status", style="green")
for agent in agents:
status_style = "green" if agent["status"] == "implemented" else "yellow"
agent_table.add_row(
agent["name"],
f"[{status_style}]{agent['status']}[/{status_style}]"
)
console.print(agent_table)
# Config status
config = check_config()
config_table = Table(title="Configuration", box=box.ROUNDED)
config_table.add_column("Key", style="cyan")
config_table.add_column("Status")
for key, present in config.items():
status = "[green]✓ Set[/green]" if present else "[red]✗ Missing[/red]"
config_table.add_row(key, status)
console.print(config_table)
def get_status_dict() -> dict:
"""Get status as dictionary for JSON output"""
return {
"providers": get_all_providers(),
"agents": get_all_agents(),
"config": check_config()
}
def check_config() -> dict:
"""Check which API keys are configured"""
import os
keys = [
"ANTHROPIC_API_KEY",
"RUNWAY_API_KEY",
"PIKA_API_KEY",
"ELEVENLABS_API_KEY",
"OPENAI_API_KEY",
"MUBERT_API_KEY",
]
return {key: bool(os.getenv(key)) for key in keys}
cli/providers.py
"""Provider commands"""
import click
from rich.console import Console
from rich.table import Table
from rich import box
console = Console()
@click.group()
def providers_cmd():
"""Provider information and testing"""
pass
@providers_cmd.command()
@click.option("--category", "-c", help="Filter by category (video, audio, music, image, storage)")
@click.option("--status", "-s", type=click.Choice(["all", "implemented", "stub"]), default="all")
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
def list(category: str, status: str, as_json: bool):
"""List all providers with their status"""
from claude_studio.core.providers import get_all_providers
providers = get_all_providers()
# Filter
if category:
providers = [p for p in providers if p["category"] == category]
if status != "all":
providers = [p for p in providers if p["status"] == status]
if as_json:
import json
click.echo(json.dumps(providers, indent=2))
return
table = Table(title="Providers", box=box.ROUNDED)
table.add_column("Name", style="cyan")
table.add_column("Category")
table.add_column("Status")
table.add_column("Cost")
table.add_column("API Key")
for p in providers:
status_style = "green" if p["status"] == "implemented" else "yellow"
key_status = "[green]✓[/green]" if p["api_key_set"] else "[dim]—[/dim]"
table.add_row(
p["name"],
p["category"],
f"[{status_style}]{p['status']}[/{status_style}]",
p["cost_info"],
key_status
)
console.print(table)
@providers_cmd.command()
@click.argument("name")
def check(name: str):
"""Check detailed status of a specific provider"""
from claude_studio.core.providers import get_provider_info
info = get_provider_info(name)
if not info:
console.print(f"[red]Provider '{name}' not found[/red]")
return
console.print(f"\n[bold cyan]{info['name']}[/bold cyan]")
console.print(f"Category: {info['category']}")
console.print(f"Status: {info['status']}")
console.print(f"Cost: {info['cost_info']}")
console.print(f"API Key Env: {info['api_key_env']}")
console.print(f"API Key Set: {'Yes' if info['api_key_set'] else 'No'}")
if info.get("features"):
console.print("\nFeatures:")
for f in info["features"]:
console.print(f" • {f}")
if info.get("limitations"):
console.print("\nLimitations:")
for l in info["limitations"]:
console.print(f" • {l}")
if info["status"] == "stub":
console.print("\n[yellow]⚠ This provider is not yet implemented[/yellow]")
console.print("Methods will raise NotImplementedError")
@providers_cmd.command()
@click.argument("name")
@click.option("--prompt", "-p", default="A serene mountain landscape", help="Test prompt")
def test(name: str, prompt: str):
"""Test a provider with a real API call"""
import asyncio
from claude_studio.core.providers import get_provider_instance
provider = get_provider_instance(name)
if not provider:
console.print(f"[red]Provider '{name}' not found[/red]")
return
console.print(f"Testing [cyan]{name}[/cyan] with prompt: {prompt[:50]}...")
try:
if hasattr(provider, "generate"):
result = asyncio.run(provider.generate(prompt=prompt, duration=4.0))
console.print(f"[green]✓ Success![/green]")
console.print(f" ID: {result.video_id if hasattr(result, 'video_id') else result.audio_id}")
console.print(f" Cost: ${result.generation_cost:.4f}")
elif hasattr(provider, "generate_speech"):
result = asyncio.run(provider.generate_speech(text=prompt, voice_id="default"))
console.print(f"[green]✓ Success![/green]")
console.print(f" Duration: {result.duration:.1f}s")
console.print(f" Cost: ${result.generation_cost:.4f}")
else:
console.print("[yellow]Provider has no testable generate method[/yellow]")
except NotImplementedError:
console.print(f"[yellow]⚠ Provider is stubbed (not implemented)[/yellow]")
except Exception as e:
console.print(f"[red]✗ Error: {e}[/red]")
cli/agents.py
"""Agent commands"""
import click
from rich.console import Console
from rich.table import Table
from rich.syntax import Syntax
from rich import box
console = Console()
@click.group()
def agents_cmd():
"""Agent information"""
pass
@agents_cmd.command()
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
def list(as_json: bool):
"""List all agents"""
from claude_studio.agents import get_all_agents
agents = get_all_agents()
if as_json:
import json
click.echo(json.dumps(agents, indent=2))
return
table = Table(title="Agents", box=box.ROUNDED)
table.add_column("Name", style="cyan")
table.add_column("Status")
table.add_column("Description")
for agent in agents:
status_style = "green" if agent["status"] == "implemented" else "yellow"
table.add_row(
agent["name"],
f"[{status_style}]{agent['status']}[/{status_style}]",
agent["description"][:50] + "..." if len(agent["description"]) > 50 else agent["description"]
)
console.print(table)
@agents_cmd.command()
@click.argument("name")
def schema(name: str):
"""Show input/output schema for an agent"""
from claude_studio.agents import get_agent_schema
schema = get_agent_schema(name)
if not schema:
console.print(f"[red]Agent '{name}' not found[/red]")
return
console.print(f"\n[bold cyan]{name}[/bold cyan]")
console.print(f"{schema['description']}\n")
console.print("[bold]Inputs:[/bold]")
for input_name, input_info in schema["inputs"].items():
console.print(f" {input_name}: {input_info}")
console.print("\n[bold]Outputs:[/bold]")
console.print(f" {schema['output']}")
if schema.get("example"):
console.print("\n[bold]Example:[/bold]")
syntax = Syntax(schema["example"], "python", theme="monokai")
console.print(syntax)
cli/config.py
"""Configuration commands"""
import click
from rich.console import Console
from rich.table import Table
from rich import box
from pathlib import Path
console = Console()
@click.group()
def config_cmd():
"""Configuration management"""
pass
@config_cmd.command()
def show():
"""Show current configuration"""
import os
from dotenv import dotenv_values
# Load from .env if exists
env_file = Path(".env")
env_values = dotenv_values(env_file) if env_file.exists() else {}
table = Table(title="Configuration", box=box.ROUNDED)
table.add_column("Key", style="cyan")
table.add_column("Source")
table.add_column("Status")
keys = [
("ANTHROPIC_API_KEY", "Required"),
("RUNWAY_API_KEY", "Video"),
("PIKA_API_KEY", "Video"),
("LUMA_API_KEY", "Video"),
("KLING_API_KEY", "Video"),
("STABILITY_API_KEY", "Video/Image"),
("ELEVENLABS_API_KEY", "Audio"),
("OPENAI_API_KEY", "Audio/Image"),
("MUBERT_API_KEY", "Music"),
("SUNO_API_KEY", "Music"),
]
for key, category in keys:
in_env = key in env_values
in_environ = key in os.environ
if in_environ:
source = "environment"
status = "[green]✓ Set[/green]"
elif in_env:
source = ".env file"
status = "[green]✓ Set[/green]"
else:
source = "—"
status = "[dim]Not set[/dim]"
table.add_row(f"{key} ({category})", source, status)
console.print(table)
if not env_file.exists():
console.print("\n[yellow]No .env file found. Run 'claude-studio config init' to create one.[/yellow]")
@config_cmd.command()
def validate():
"""Validate API keys by making test calls"""
import os
import asyncio
console.print("Validating API keys...\n")
validations = [
("ANTHROPIC_API_KEY", validate_anthropic),
("RUNWAY_API_KEY", validate_runway),
("ELEVENLABS_API_KEY", validate_elevenlabs),
]
for key_name, validator in validations:
if os.getenv(key_name):
console.print(f"Checking {key_name}...", end=" ")
try:
asyncio.run(validator())
console.print("[green]✓ Valid[/green]")
except Exception as e:
console.print(f"[red]✗ Invalid: {e}[/red]")
else:
console.print(f"{key_name}: [dim]Not set, skipping[/dim]")
@config_cmd.command()
@click.option("--force", "-f", is_flag=True, help="Overwrite existing .env")
def init(force: bool):
"""Create .env template file"""
env_file = Path(".env")
if env_file.exists() and not force:
console.print("[yellow].env file already exists. Use --force to overwrite.[/yellow]")
return
template = '''# Claude Studio Producer Configuration
# Copy this file to .env and fill in your API keys
# ===================
# REQUIRED
# ===================
ANTHROPIC_API_KEY=
# ===================
# VIDEO PROVIDERS
# ===================
# At least one recommended for video generation
RUNWAY_API_KEY=
PIKA_API_KEY=
LUMA_API_KEY=
KLING_API_KEY=
STABILITY_API_KEY=
# ===================
# AUDIO PROVIDERS
# ===================
# At least one recommended for voiceover
ELEVENLABS_API_KEY=
OPENAI_API_KEY=
# ===================
# MUSIC PROVIDERS
# ===================
MUBERT_API_KEY=
SUNO_API_KEY=
# ===================
# STORAGE (Optional)
# ===================
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-west-2
S3_BUCKET=
# ===================
# DEVELOPMENT
# ===================
ENV=development
DEBUG=true
PROVIDER_MODE=mock
'''
env_file.write_text(template)
console.print(f"[green]Created {env_file}[/green]")
console.print("Edit the file to add your API keys.")
async def validate_anthropic():
from anthropic import Anthropic
client = Anthropic()
# Just check we can create a client without error
# A real validation would make a small API call
async def validate_runway():
import httpx
import os
async with httpx.AsyncClient() as client:
response = await client.get(
"https://api.runwayml.com/v1/account",
headers={"Authorization": f"Bearer {os.getenv('RUNWAY_API_KEY')}"}
)
response.raise_for_status()
async def validate_elevenlabs():
import httpx
import os
async with httpx.AsyncClient() as client:
response = await client.get(
"https://api.elevenlabs.io/v1/user",
headers={"xi-api-key": os.getenv("ELEVENLABS_API_KEY")}
)
response.raise_for_status()
Provider/Agent Registration
core/providers/init.py (additions)
"""Provider registry with introspection"""
from typing import List, Dict, Optional, Any
import os
# Provider metadata for CLI introspection
PROVIDER_REGISTRY = {
# Video
"runway": {
"name": "runway",
"category": "video",
"class": "RunwayProvider",
"module": "core.providers.video.runway",
"status": "stub", # or "implemented"
"api_key_env": "RUNWAY_API_KEY",
"cost_info": "$0.25-0.50/sec",
"features": ["text-to-video", "image-to-video", "10s max"],
"limitations": ["10 second max duration"],
},
"pika": {
"name": "pika",
"category": "video",
"class": "PikaProvider",
"module": "core.providers.video.pika",
"status": "stub",
"api_key_env": "PIKA_API_KEY",
"cost_info": "$0.20/sec",
"features": ["text-to-video", "stylized content", "4s max"],
"limitations": ["4 second max duration"],
},
"stability": {
"name": "stability",
"category": "video",
"class": "StabilityVideoProvider",
"module": "core.providers.video.stability",
"status": "stub",
"api_key_env": "STABILITY_API_KEY",
"cost_info": "$0.10/sec",
"features": ["image-to-video only"],
"limitations": ["Requires input image", "4 second max"],
},
"luma": {
"name": "luma",
"category": "video",
"class": "LumaProvider",
"module": "core.providers.video.luma",
"status": "stub",
"api_key_env": "LUMA_API_KEY",
"cost_info": "$0.30/sec",
"features": ["text-to-video", "camera control", "5s max"],
"limitations": ["5 second max duration"],
},
"kling": {
"name": "kling",
"category": "video",
"class": "KlingProvider",
"module": "core.providers.video.kling",
"status": "stub",
"api_key_env": "KLING_API_KEY",
"cost_info": "$0.15-0.30/sec",
"features": ["text-to-video", "10s max", "good physics"],
"limitations": [],
},
# Audio
"elevenlabs": {
"name": "elevenlabs",
"category": "audio",
"class": "ElevenLabsProvider",
"module": "core.providers.audio.elevenlabs",
"status": "stub",
"api_key_env": "ELEVENLABS_API_KEY",
"cost_info": "$0.30/1K chars",
"features": ["premium voices", "voice cloning", "multilingual"],
"limitations": [],
},
"openai_tts": {
"name": "openai_tts",
"category": "audio",
"class": "OpenAITTSProvider",
"module": "core.providers.audio.openai_tts",
"status": "stub",
"api_key_env": "OPENAI_API_KEY",
"cost_info": "$0.015-0.030/1K chars",
"features": ["6 voices", "fast generation"],
"limitations": ["fewer voice options"],
},
"google_tts": {
"name": "google_tts",
"category": "audio",
"class": "GoogleTTSProvider",
"module": "core.providers.audio.google_tts",
"status": "stub",
"api_key_env": "GOOGLE_CLOUD_API_KEY",
"cost_info": "$0.004-0.016/1K chars",
"features": ["200+ voices", "40+ languages", "cheapest"],
"limitations": ["less natural than others"],
},
# Music
"mubert": {
"name": "mubert",
"category": "music",
"class": "MubertProvider",
"module": "core.providers.music.mubert",
"status": "stub",
"api_key_env": "MUBERT_API_KEY",
"cost_info": "$0.50/track",
"features": ["AI-generated", "royalty-free", "mood control"],
"limitations": [],
},
"suno": {
"name": "suno",
"category": "music",
"class": "SunoProvider",
"module": "core.providers.music.suno",
"status": "stub",
"api_key_env": "SUNO_API_KEY",
"cost_info": "$0.05/sec",
"features": ["vocals support", "lyrics", "full songs"],
"limitations": [],
},
# Image
"dalle": {
"name": "dalle",
"category": "image",
"class": "DalleProvider",
"module": "core.providers.image.dalle",
"status": "stub",
"api_key_env": "OPENAI_API_KEY",
"cost_info": "$0.04-0.08/image",
"features": ["high quality", "prompt revision"],
"limitations": [],
},
# Storage
"local": {
"name": "local",
"category": "storage",
"class": "LocalStorageProvider",
"module": "core.providers.storage.local",
"status": "implemented",
"api_key_env": None,
"cost_info": "Free",
"features": ["local filesystem"],
"limitations": ["development only"],
},
"s3": {
"name": "s3",
"category": "storage",
"class": "S3StorageProvider",
"module": "core.providers.storage.s3",
"status": "stub",
"api_key_env": "AWS_ACCESS_KEY_ID",
"cost_info": "~$0.023/GB",
"features": ["scalable", "CDN integration"],
"limitations": ["requires AWS account"],
},
}
def get_all_providers() -> List[Dict[str, Any]]:
"""Get all providers with their status"""
result = []
for name, info in PROVIDER_REGISTRY.items():
result.append({
**info,
"api_key_set": bool(os.getenv(info["api_key_env"])) if info["api_key_env"] else True
})
return result
def get_provider_info(name: str) -> Optional[Dict[str, Any]]:
"""Get detailed info for a specific provider"""
if name not in PROVIDER_REGISTRY:
return None
info = PROVIDER_REGISTRY[name].copy()
info["api_key_set"] = bool(os.getenv(info["api_key_env"])) if info["api_key_env"] else True
return info
def get_provider_instance(name: str):
"""Get an instance of a provider by name"""
if name not in PROVIDER_REGISTRY:
return None
info = PROVIDER_REGISTRY[name]
# Dynamic import
import importlib
module = importlib.import_module(info["module"])
cls = getattr(module, info["class"])
try:
return cls()
except Exception:
return None
agents/init.py (additions)
"""Agent registry with introspection"""
from typing import List, Dict, Optional, Any
AGENT_REGISTRY = {
"producer": {
"name": "producer",
"class": "ProducerAgent",
"module": "agents.producer",
"status": "implemented",
"description": "Analyzes requests and creates pilot strategies",
"inputs": {
"user_request": "str - Video concept description",
"total_budget": "float - Total budget in USD"
},
"output": "List[PilotStrategy]",
"example": '''
agent = ProducerAgent(claude_client)
pilots = await agent.run(
user_request="60-second product demo",
total_budget=150.0
)
'''
},
"critic": {
"name": "critic",
"class": "CriticAgent",
"module": "agents.critic",
"status": "implemented",
"description": "Evaluates pilot results and makes budget decisions",
"inputs": {
"original_request": "str - Original video concept",
"pilot": "PilotStrategy - Pilot being evaluated",
"scene_results": "List[GeneratedVideo] - Generated test scenes",
"budget_spent": "float - Budget used so far",
"budget_allocated": "float - Total pilot budget"
},
"output": "PilotEvaluation",
},
"script_writer": {
"name": "script_writer",
"class": "ScriptWriterAgent",
"module": "agents.script_writer",
"status": "implemented",
"description": "Breaks video concepts into scenes with detailed specs",
"inputs": {
"video_concept": "str - Video concept",
"target_duration": "int - Duration in seconds",
"num_scenes": "int - Number of scenes",
"seed_assets": "Optional[SeedAssetCollection] - Reference materials"
},
"output": "List[Scene]",
},
"asset_analyzer": {
"name": "asset_analyzer",
"class": "AssetAnalyzerAgent",
"module": "agents.asset_analyzer",
"status": "stub",
"description": "Analyzes seed assets using Claude Vision",
"inputs": {
"collection": "SeedAssetCollection - Assets to analyze"
},
"output": "SeedAssetCollection (enriched with extracted info)",
},
"video_generator": {
"name": "video_generator",
"class": "VideoGeneratorAgent",
"module": "agents.video_generator",
"status": "implemented",
"description": "Generates video content for scenes",
"inputs": {
"scenes": "List[Scene] - Scenes to generate",
"budget_limit": "float - Maximum budget"
},
"output": "List[GeneratedVideo]",
},
"audio_generator": {
"name": "audio_generator",
"class": "AudioGeneratorAgent",
"module": "agents.audio_generator",
"status": "stub",
"description": "Generates voiceover, music, and sound effects",
"inputs": {
"scenes": "List[Scene] - Scenes with audio specs",
"audio_tier": "AudioTier - Quality level"
},
"output": "List[SceneAudio]",
},
"qa_verifier": {
"name": "qa_verifier",
"class": "QAVerifierAgent",
"module": "agents.qa_verifier",
"status": "stub",
"description": "Verifies video quality using Claude Vision",
"inputs": {
"video": "GeneratedVideo - Video to verify",
"scene": "Scene - Expected scene spec"
},
"output": "QAResult",
},
"editor": {
"name": "editor",
"class": "EditorAgent",
"module": "agents.editor",
"status": "stub",
"description": "Creates EDL and edit candidates from raw materials",
"inputs": {
"scenes": "List[Scene]",
"videos": "List[GeneratedVideo]",
"audio": "List[SceneAudio]"
},
"output": "EditDecisionList",
},
}
def get_all_agents() -> List[Dict[str, Any]]:
"""Get all agents with their status"""
return list(AGENT_REGISTRY.values())
def get_agent_schema(name: str) -> Optional[Dict[str, Any]]:
"""Get schema for a specific agent"""
return AGENT_REGISTRY.get(name)
Example Output
$ claude-studio status
╭─────────────────────────────────────╮
│ Claude Studio Producer │
│ AI Video Production Pipeline │
╰─────────────────────────────────────╯
Providers
┌──────────┬─────────────┬─────────┐
│ Category │ Implemented │ Stubbed │
├──────────┼─────────────┼─────────┤
│ Video │ 0 │ 5 │
│ Audio │ 0 │ 3 │
│ Music │ 0 │ 2 │
│ Image │ 0 │ 1 │
│ Storage │ 1 │ 1 │
└──────────┴─────────────┴─────────┘
Agents
┌─────────────────┬─────────────┐
│ Agent │ Status │
├─────────────────┼─────────────┤
│ producer │ implemented │
│ critic │ implemented │
│ script_writer │ implemented │
│ asset_analyzer │ stub │
│ video_generator │ implemented │
│ audio_generator │ stub │
│ qa_verifier │ stub │
│ editor │ stub │
└─────────────────┴─────────────┘
Configuration
┌─────────────────────┬─────────────┐
│ Key │ Status │
├─────────────────────┼─────────────┤
│ ANTHROPIC_API_KEY │ ✓ Set │
│ RUNWAY_API_KEY │ ✗ Missing │
│ PIKA_API_KEY │ ✗ Missing │
│ ELEVENLABS_API_KEY │ ✗ Missing │
│ OPENAI_API_KEY │ ✓ Set │
│ MUBERT_API_KEY │ ✗ Missing │
└─────────────────────┴─────────────┘
$ claude-studio providers list --status stub
Providers
┌───────────┬──────────┬────────┬──────────────────┬─────────┐
│ Name │ Category │ Status │ Cost │ API Key │
├───────────┼──────────┼────────┼──────────────────┼─────────┤
│ runway │ video │ stub │ $0.25-0.50/sec │ — │
│ pika │ video │ stub │ $0.20/sec │ — │
│ stability │ video │ stub │ $0.10/sec │ — │
│ luma │ video │ stub │ $0.30/sec │ — │
│ kling │ video │ stub │ $0.15-0.30/sec │ — │
│ elevenlabs│ audio │ stub │ $0.30/1K chars │ — │
│ ... │ ... │ ... │ ... │ ... │
└───────────┴──────────┴────────┴──────────────────┴─────────┘
$ claude-studio providers check runway
runway
Category: video
Status: stub
Cost: $0.25-0.50/sec
API Key Env: RUNWAY_API_KEY
API Key Set: No
Features:
• text-to-video
• image-to-video
• 10s max
Limitations:
• 10 second max duration
⚠ This provider is not yet implemented
Methods will raise NotImplementedError
Implementation Priority
This can come later with Docker/Server, but the foundation is:
- PROVIDER_REGISTRY dict in
core/providers/__init__.py - AGENT_REGISTRY dict in
agents/__init__.py - Update status as we implement
The CLI itself can wait until we have more implemented!