Skip to content
Open
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
28 changes: 20 additions & 8 deletions libs/deepagents-cli/deepagents_cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,24 @@ cli/
### `main.py` - Entry Point & Main Loop
- **Purpose**: CLI entry point, argument parsing, main interactive loop
- **Key Functions**:
- `cli_main()` - Console script entry point (called when you run `deepagents`)
- `main()` - Async main function that orchestrates agent creation and CLI
- `simple_cli()` - Main interactive loop handling user input
- `parse_args()` - Command-line argument parsing
- `cli_main()` - Console script entry point with provider switching loop (called when you run `deepagents`)
- `main()` - Async main function that orchestrates agent creation and CLI (returns dict for special actions)
- `simple_cli()` - Main interactive loop handling user input (returns dict for provider switching)
- `parse_args()` - Command-line argument parsing (dual parser for prompts vs commands)
- `check_cli_dependencies()` - Validates required packages are installed

### `config.py` - Configuration & Constants
- **Purpose**: Centralized configuration, constants, and model creation
- **Key Exports**:
- `COLORS` - Color scheme for terminal output
- `DEEP_AGENTS_ASCII` - ASCII art banner
- `COMMANDS` - Available slash commands
- `COMMANDS` - Available slash commands (including `/provider`)
- `console` - Rich Console instance
- `create_model()` - Creates OpenAI or Anthropic model based on API keys
- `SessionState` - Holds mutable session state including `preferred_provider`
- `create_model(force_provider)` - Creates OpenAI or Anthropic model with optional provider override
- `get_default_coding_instructions()` - Loads default agent prompt
- `load_agent_config(agent_name)` - Load agent configuration from config.json
- `save_agent_config(agent_name, config_data)` - Save agent configuration to config.json

### `tools.py` - Custom Agent Tools
- **Purpose**: Additional tools for the agent beyond built-in filesystem operations
Expand Down Expand Up @@ -72,9 +75,9 @@ cli/
- External editor support (Ctrl+E)

### `commands.py` - Command Handlers
- **Purpose**: Handle slash commands (`/help`, `/clear`, etc.) and bash execution
- **Purpose**: Handle slash commands (`/help`, `/clear`, `/provider`, etc.) and bash execution
- **Key Functions**:
- `handle_command()` - Route and execute slash commands
- `handle_command()` - Route and execute slash commands (returns dict for provider switching)
- `execute_bash_command()` - Execute bash commands prefixed with `!`

### `execution.py` - Task Execution & Streaming
Expand Down Expand Up @@ -132,12 +135,20 @@ Type `@filename` and press Tab to autocomplete and inject file content into your
### Interactive Commands
- `/help` - Show help
- `/clear` - Clear screen and reset conversation
- `/provider [openai|anthropic]` - Switch between OpenAI and Anthropic providers
- `/tokens` - Show token usage
- `/quit` or `/exit` - Exit the CLI

### Bash Commands
Type `!command` to execute bash commands directly (e.g., `!ls`, `!git status`)

### Provider Switching
Switch between OpenAI and Anthropic providers during your session:
- Use `/provider openai` to switch to OpenAI (gpt-5-mini)
- Use `/provider anthropic` to switch to Anthropic (claude-sonnet-4-5-20250929)
- The agent will be recreated with the new provider, clearing the conversation history
- Your provider choice is persisted in `~/.deepagents/AGENT_NAME/config.json` and will be used in future sessions

### Todo List Tracking
The agent can create and update a visual todo list for multi-step tasks.

Expand All @@ -162,6 +173,7 @@ Each agent stores its state in `~/.deepagents/AGENT_NAME/`:
- `agent.md` - Agent's custom instructions (long-term memory)
- `memories/` - Additional context files
- `history` - Command history
- `config.json` - Agent configuration (preferred model provider, etc.)

## Development

Expand Down
28 changes: 23 additions & 5 deletions libs/deepagents-cli/deepagents_cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
from .ui import TokenTracker, show_interactive_help


def handle_command(command: str, agent, token_tracker: TokenTracker) -> str | bool:
"""Handle slash commands. Returns 'exit' to exit, True if handled, False to pass to agent."""
cmd = command.lower().strip().lstrip("/")
def handle_command(command: str, agent, token_tracker: TokenTracker) -> str | bool | dict:
"""Handle slash commands. Returns 'exit' to exit, True if handled, dict for special actions."""
cmd_parts = command.lower().strip().lstrip("/").split()
cmd = cmd_parts[0] if cmd_parts else ""

if cmd in ["quit", "exit", "q"]:
return "exit"
Expand Down Expand Up @@ -41,14 +42,31 @@ def handle_command(command: str, agent, token_tracker: TokenTracker) -> str | bo
token_tracker.display_session()
return True

if cmd == "provider":
if len(cmd_parts) < 2:
console.print()
console.print("[yellow]Usage: /provider [openai|anthropic][/yellow]")
console.print("[dim]Switch between OpenAI and Anthropic providers.[/dim]")
console.print()
return True

provider = cmd_parts[1].lower()
if provider not in ["openai", "anthropic"]:
console.print()
console.print(f"[yellow]Invalid provider: {provider}[/yellow]")
console.print("[dim]Valid options: openai, anthropic[/dim]")
console.print()
return True

# Signal to recreate agent with new provider
return {"action": "recreate", "provider": provider}

console.print()
console.print(f"[yellow]Unknown command: /{cmd}[/yellow]")
console.print("[dim]Type /help for available commands.[/dim]")
console.print()
return True

return False


def execute_bash_command(command: str) -> bool:
"""Execute a bash command and display output. Returns True if handled."""
Expand Down
143 changes: 117 additions & 26 deletions libs/deepagents-cli/deepagents_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from pathlib import Path

import dotenv
from langchain_anthropic import ChatAnthropic
from langchain_openai import ChatOpenAI
from rich.console import Console

dotenv.load_dotenv()
Expand Down Expand Up @@ -40,6 +42,7 @@
COMMANDS = {
"clear": "Clear screen and reset conversation",
"help": "Show help information",
"provider": "Switch between OpenAI and Anthropic providers",
"tokens": "Show token usage for current session",
"quit": "Exit the CLI",
"exit": "Exit the CLI",
Expand All @@ -59,8 +62,9 @@
class SessionState:
"""Holds mutable session state (auto-approve mode, etc)."""

def __init__(self, auto_approve: bool = False):
def __init__(self, auto_approve: bool = False, preferred_provider: str | None = None):
self.auto_approve = auto_approve
self.preferred_provider = preferred_provider

def toggle_auto_approve(self) -> bool:
"""Toggle auto-approve and return new state."""
Expand All @@ -78,9 +82,53 @@ def get_default_coding_instructions() -> str:
return default_prompt_path.read_text()


def create_model():
def _create_openai_model():
"""Create an OpenAI model instance."""
console.print("[dim]Using OpenAI model: gpt-5-mini[/dim]")
return ChatOpenAI(
model="gpt-5-mini",
temperature=0.7,
)


def _create_anthropic_model():
"""Create an Anthropic model instance."""
console.print("[dim]Using Anthropic model: claude-sonnet-4-5-20250929[/dim]")
return ChatAnthropic(
model_name="claude-sonnet-4-5-20250929",
max_tokens=20000,
)


def _show_api_key_error(provider: str | None = None):
"""Show error message for missing API key and exit."""
if provider == "openai":
console.print("[bold red]Error:[/bold red] OPENAI_API_KEY not configured.")
console.print("\nPlease set your OpenAI API key:")
console.print(" export OPENAI_API_KEY=your_api_key_here")
elif provider == "anthropic":
console.print("[bold red]Error:[/bold red] ANTHROPIC_API_KEY not configured.")
console.print("\nPlease set your Anthropic API key:")
console.print(" export ANTHROPIC_API_KEY=your_api_key_here")
else:
console.print("[bold red]Error:[/bold red] No API key configured.")
console.print("\nPlease set one of the following environment variables:")
console.print(" - OPENAI_API_KEY (for OpenAI models like gpt-5-mini)")
console.print(" - ANTHROPIC_API_KEY (for Claude models)")
console.print("\nExample:")
console.print(" export OPENAI_API_KEY=your_api_key_here")

console.print("\nOr add it to your .env file.")
sys.exit(1)


def create_model(force_provider: str | None = None):
"""Create the appropriate model based on available API keys.

Args:
force_provider: Optional provider to force ("openai" or "anthropic").
If specified, only that provider will be used.

Returns:
ChatModel instance (OpenAI or Anthropic)

Expand All @@ -90,29 +138,72 @@ def create_model():
openai_key = os.environ.get("OPENAI_API_KEY")
anthropic_key = os.environ.get("ANTHROPIC_API_KEY")

# Determine which provider to use
if force_provider == "openai":
if not openai_key:
_show_api_key_error("openai")
return _create_openai_model()

if force_provider == "anthropic":
if not anthropic_key:
_show_api_key_error("anthropic")
return _create_anthropic_model()

# Default behavior: prefer OpenAI, fallback to Anthropic
if openai_key:
from langchain_openai import ChatOpenAI

model_name = os.environ.get("OPENAI_MODEL", "gpt-5-mini")
console.print(f"[dim]Using OpenAI model: {model_name}[/dim]")
return ChatOpenAI(
model=model_name,
temperature=0.7,
)
return _create_openai_model()
if anthropic_key:
from langchain_anthropic import ChatAnthropic

model_name = os.environ.get("ANTHROPIC_MODEL", "claude-sonnet-4-5-20250929")
console.print(f"[dim]Using Anthropic model: {model_name}[/dim]")
return ChatAnthropic(
model_name=model_name,
max_tokens=20000,
)
console.print("[bold red]Error:[/bold red] No API key configured.")
console.print("\nPlease set one of the following environment variables:")
console.print(" - OPENAI_API_KEY (for OpenAI models like gpt-5-mini)")
console.print(" - ANTHROPIC_API_KEY (for Claude models)")
console.print("\nExample:")
console.print(" export OPENAI_API_KEY=your_api_key_here")
console.print("\nOr add it to your .env file.")
sys.exit(1)
return _create_anthropic_model()

_show_api_key_error()


def load_agent_config(agent_name: str) -> dict:
"""Load agent configuration from config.json.

Args:
agent_name: Name of the agent

Returns:
Dictionary with config data, empty dict if file doesn't exist
"""
import json
from pathlib import Path

config_path = Path.home() / ".deepagents" / agent_name / "config.json"

if not config_path.exists():
return {}

try:
with open(config_path) as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
# If file is corrupted or unreadable, return empty config
return {}


def save_agent_config(agent_name: str, config_data: dict) -> None:
"""Save agent configuration to config.json.

Args:
agent_name: Name of the agent
config_data: Dictionary to save as JSON
"""
import json
from datetime import datetime, timezone
from pathlib import Path

agent_dir = Path.home() / ".deepagents" / agent_name
agent_dir.mkdir(parents=True, exist_ok=True)

config_path = agent_dir / "config.json"

# Add timestamp
config_data["last_updated"] = datetime.now(timezone.utc).isoformat()

try:
with open(config_path, "w") as f:
json.dump(config_data, f, indent=2)
except IOError as e:
console.print(f"[yellow]Warning: Could not save config: {e}[/yellow]")
Loading