Skip to main content

The Friday Assistant Runtime

Project description

Friday Framework CLI

Rich-based terminal interface components for the Friday Framework.

Version: 0.1.0

Overview

Friday CLI provides professional terminal interface components using the Rich and Prompt Toolkit libraries, including:

  • Display: Status messages, tables, syntax highlighting, progress indicators
  • Input: Readline-style editing, command history, auto-completion

Installation

# From the monorepo root
uv sync

# Or install directly from PyPI
pip install friday-framework-cli

Quick Start

from friday_cli import (
    display_error,
    display_success,
    display_table,
    spinner,
)

# Status messages
display_success("Task completed")
display_error("Something went wrong", exception=ValueError("details"))

# Tables
data = [{"name": "Alice", "role": "Admin"}, {"name": "Bob", "role": "User"}]
display_table(data, title="Users")

# Progress spinner
with spinner("Processing...") as status:
    # do work
    status.update("Almost done...")

API Reference

Console Management

get_console() -> Console

Returns the shared Rich Console instance (singleton pattern).

set_console(console: Console) -> None

Sets a custom console instance. Useful for testing or custom output destinations.

from rich.console import Console
from friday_cli import set_console, get_console

# For testing - capture output
import io
string_io = io.StringIO()
test_console = Console(file=string_io, force_terminal=True)
set_console(test_console)

# Reset to default
set_console(None)

Status Messages

All status functions display styled messages with icons.

display_error(message: str, exception: Exception | None = None, show_traceback: bool = False) -> None

Display an error message with red styling and X icon.

display_error("Operation failed")

# With exception details
try:
    raise ValueError("Invalid input")
except ValueError as e:
    display_error("Validation error", exception=e)

# With full traceback
display_error("Critical failure", exception=e, show_traceback=True)

display_warning(message: str) -> None

Display a warning message with yellow styling and warning icon.

display_warning("Configuration not found, using defaults")

display_success(message: str) -> None

Display a success message with green styling and checkmark icon.

display_success("File saved successfully")

display_info(message: str) -> None

Display an info message with blue styling and info icon.

display_info("Processing 42 items...")

Structured Data Display

display_json(data: Any, title: str | None = None, indent: int = 2) -> None

Display JSON data with syntax highlighting.

data = {"status": "ok", "count": 42, "items": ["a", "b", "c"]}
display_json(data, title="API Response")

display_code(code: str, language: str = "python", line_numbers: bool = True, title: str | None = None) -> None

Display code with syntax highlighting.

code = '''
def greet(name: str) -> str:
    return f"Hello, {name}!"
'''
display_code(code, language="python", title="greeting.py")

display_table(data: list[dict], title: str | None = None, columns: list[str] | None = None, show_header: bool = True, box_style: str = "rounded") -> None

Display data as a formatted table.

users = [
    {"name": "Alice", "age": "30", "role": "Admin"},
    {"name": "Bob", "age": "25", "role": "User"},
]
display_table(users, title="Team Members")

# Custom columns (subset or reorder)
display_table(users, columns=["name", "role"])

# Box styles: "rounded", "simple", "square"
display_table(users, box_style="simple")

Progress Indicators

spinner(message: str, spinner_type: str = "dots") -> Iterator[Status]

Context manager for displaying a spinner during long-running operations.

from friday_cli import spinner

with spinner("Loading data..."):
    # do work
    time.sleep(2)

# With status updates
with spinner("Processing...") as status:
    status.update("Step 1: Loading...")
    # step 1
    status.update("Step 2: Transforming...")
    # step 2

progress_bar(total: int, description: str = "Processing", show_speed: bool = False) -> Iterator[Progress]

Context manager for displaying a progress bar.

from friday_cli import progress_bar

with progress_bar(100, "Downloading") as progress:
    task = progress.add_task("file.zip", total=100)
    for i in range(100):
        # do work
        progress.update(task, advance=1)

ProgressTracker

Helper class for tracking multi-step progress.

from friday_cli import ProgressTracker

tracker = ProgressTracker(
    steps=["Load", "Process", "Save"],
    title="Data Pipeline"
)
with tracker:
    tracker.advance("Loading data...")
    # load step
    tracker.advance("Processing...")
    # process step
    tracker.advance("Saving results...")
    # save step
    tracker.complete()

Banners and Panels

display_banner(title: str, subtitle: str | None = None, version: str | None = None) -> None

Display a welcome banner.

display_banner(
    "Friday CLI",
    subtitle="Rich terminal interface",
    version="0.1.0"
)

display_panel(content: str, title: str | None = None, style: str = "default") -> None

Display content in a styled panel.

display_panel("Important information here", title="Notice", style="info")

# Styles: "default", "info", "warning", "error", "success"
display_panel("Operation completed", style="success")

display_separator(char: str = "─", style: str = "muted") -> None

Display a horizontal separator line.

display_separator()
display_separator(char="=", style="cyan")

Domain-Specific Displays

These functions are designed for the Friday agent framework's specific use cases.

display_help(commands: dict[str, str], tips: list[str] | None = None, title: str = "Available Commands") -> None

Display help information with command descriptions.

commands = {
    "/help": "Show this help message",
    "/history": "Show conversation history",
    "/exit": "Exit the program",
}
tips = [
    "Use Tab for command completion",
    "Type /clear to reset the session",
]
display_help(commands, tips=tips)

display_tool_budget(tools: list[dict], circuit_states: dict[str, str] | None = None) -> None

Display tool execution statistics and circuit breaker states.

tools = [
    {"name": "web_search", "calls": 5, "tokens": 1200, "avg_time": 0.8},
    {"name": "file_read", "calls": 12, "tokens": 800, "avg_time": 0.1},
]
circuit_states = {"web_search": "CLOSED", "file_read": "CLOSED"}
display_tool_budget(tools, circuit_states)

display_memory_stats(stats: dict[str, Any]) -> None

Display memory system statistics.

stats = {
    "entities": 42,
    "facts": 156,
    "insights": 23,
    "summaries": 8,
    "history_turns": 50,
    "sessions": 5,
    "lifecycle": {
        "ACTIVE": 30,
        "CONSOLIDATED": 10,
        "ARCHIVED": 2,
    }
}
display_memory_stats(stats)

display_entities(entities: list[dict], relationships: list[str] | None = None) -> None

Display graph entities and their relationships.

entities = [
    {"id": "abc123", "type": "Person", "label": "Alice", "hit_count": 5},
    {"id": "def456", "type": "Project", "label": "Friday", "hit_count": 12},
]
relationships = [
    "Alice --[WORKS_ON]--> Friday",
    "Alice --[KNOWS]--> Bob",
]
display_entities(entities, relationships)

display_history(turns: list[dict], prior_turns: list[dict] | None = None, user_name: str = "User", assistant_name: str = "Assistant") -> None

Display conversation history.

turns = [
    {
        "timestamp": "2024-01-15 10:30",
        "user_message": "Hello!",
        "assistant_message": "Hi there! How can I help?"
    },
]
prior_turns = [
    {
        "timestamp": "2024-01-14 15:00",
        "user_message": "Previous question",
        "assistant_message": "Previous answer"
    },
]
display_history(turns, prior_turns=prior_turns)

display_context_payload(payload: dict[str, Any], token_counts: dict[str, int] | None = None) -> None

Display the assembled context payload.

payload = {
    "history": [{"user": "hi", "assistant": "hello"}],
    "prior_history": [],
    "insights": [{"text": "User prefers concise answers"}],
    "summaries": ["Previous session summary"],
    "key_facts": ["Fact 1", "Fact 2"],
    "entities": [{"label": "Project X"}],
}
token_counts = {
    "history": 500,
    "insights": 200,
    "summaries": 300,
}
display_context_payload(payload, token_counts)

Input Handling

Advanced input handling with Prompt Toolkit for readline-style editing.

InputHandler

Full-featured input handler with history, completion, and styling.

from friday_cli import InputHandler

handler = InputHandler(
    prompt_name="User",
    commands=["help", "history", "exit"],
    command_descriptions={"help": "Show help", "exit": "Quit"},
    history_file="~/.friday_history",
    multiline=False,
    enable_auto_suggest=True,
)

# Synchronous input
text = handler.get_input()

# Async input (for async contexts)
text = await handler.get_input_async()

Features:

  • Readline-style editing (Ctrl+A, Ctrl+E, Ctrl+K, etc.)
  • Up/down arrows for history navigation
  • Tab completion for slash commands
  • Auto-suggestions from history (grayed out text)
  • Persistent history to file
  • Alt+Enter for newline in multi-line mode

create_friday_input_handler()

Create a pre-configured handler with Friday CLI commands.

from friday_cli import create_friday_input_handler

handler = create_friday_input_handler(
    user_name="Alice",
    history_file="/path/to/history",
    multiline=False,
)

# All Friday commands are pre-loaded for completion
# /help, /history, /entities, /tools, etc.

CommandCompleter

Standalone completer for slash commands.

from friday_cli import CommandCompleter
from prompt_toolkit import PromptSession

completer = CommandCompleter(
    commands=["help", "search", "quit"],
    meta_dict={"help": "Show help"},
)

session = PromptSession(completer=completer)

Constants

from friday_cli import FRIDAY_COMMANDS, FRIDAY_COMMAND_DESCRIPTIONS

# FRIDAY_COMMANDS: list of all Friday CLI command names
# FRIDAY_COMMAND_DESCRIPTIONS: dict of command -> description

Command Registry

Decorator-based command registration with argument parsing, aliasing, and help generation.

CommandRegistry

Registry for CLI commands with dispatch and execution.

from friday_cli import CommandRegistry, arg

registry = CommandRegistry()

@registry.command("greet", aliases=["g", "hello"])
def cmd_greet(ctx, name="World"):
    """Greet someone by name."""
    return f"Hello, {name}!"

@registry.command(
    "export",
    arguments=[arg("format", default="json", choices=["json", "csv"])],
    category="data",
)
async def cmd_export(ctx, format="json"):
    """Export data in the specified format."""
    return f"Exported as {format}"

# Execute commands
was_command, result = await registry.execute("/greet Alice", context)
# was_command=True, result="Hello, Alice!"

was_command, result = await registry.execute("/g Bob", context)
# was_command=True, result="Hello, Bob!" (alias)

was_command, result = await registry.execute("not a command", context)
# was_command=False, result=None

Features:

  • Decorator-based registration with @registry.command()
  • Programmatic registration with registry.register()
  • Command aliases for shortcuts (/h for /history)
  • Argument parsing with defaults and choices
  • Async and sync command support
  • Help text from docstrings
  • Category grouping for organized help

arg() Helper

Create argument specifications for commands.

from friday_cli import arg

# Required argument
arg("name", required=True, description="User name")

# Optional with default
arg("format", default="json", choices=["json", "csv", "xml"])

Help Generation

# Get help as dict
help_dict = registry.get_help()
# {"/greet (/g, /hello)": "Greet someone by name.", ...}

# Get help grouped by category
by_category = registry.get_help_by_category()
# {"general": {...}, "data": {...}}

# Get data for InputHandler completion
names, descriptions = registry.get_command_for_completion()

Integration Pattern

class MySession:
    def __init__(self):
        self.registry = CommandRegistry()
        self._register_commands()

    def _register_commands(self):
        self.registry.register("help", self.cmd_help, aliases=["?"])
        self.registry.register("exit", lambda ctx: "EXIT", aliases=["quit"])

    async def handle_input(self, user_input):
        # Try registry first
        was_cmd, result = await self.registry.execute(user_input, self)
        if was_cmd:
            return result

        # Fall back to other handling
        return await self.process_chat(user_input)

Session Management

Configuration loading, lifecycle management, and graceful shutdown for CLI applications.

SessionConfig

Configuration container that loads from YAML files and provides typed access.

from friday_cli import SessionConfig, load_config

# Load from YAML file
config = SessionConfig.from_yaml("config.yaml")

# Or create from dict
config = SessionConfig.from_dict({"llm": {"model": "gpt-4"}})

# Typed access to common values
print(config.user_name)        # "User" (default)
print(config.assistant_name)   # "Assistant" (default)
print(config.system_prompt)    # "You are a helpful assistant."
print(config.persist_dir)      # Path or None

# Access any configuration section
print(config.llm["model"])
print(config.memory.get("persist_directory"))

# Nested access with defaults
enabled = config.get_nested("memory", "priming", "enabled", default=True)

Configuration Sections:

  • llm - LLM configuration (model, api_key, temperature)
  • embedding - Embedding model configuration
  • memory - Memory system settings
  • chat - Chat display settings (user_name, assistant_name, system_prompt)
  • logging - Logging configuration
  • extraction - Entity/fact extraction settings
  • transcript - Transcript storage settings
  • maintenance - Maintenance task settings
  • lifecycle - Memory lifecycle settings
  • tools - Tool configuration

SessionManager

Async context manager for session lifecycle with signal handling.

from friday_cli import SessionManager, SessionConfig

config = SessionConfig.from_yaml("config.yaml")

# Basic usage
async with SessionManager(config=config) as ctx:
    print(f"User: {ctx.config.user_name}")
    # ctx.session is None without factory

# With session factory
async def create_session(config):
    return MySession(config)

manager = SessionManager(
    config=config,
    session_factory=create_session,
    on_shutdown=lambda ctx: print("Cleaning up..."),
)

async with manager as ctx:
    session = ctx.session  # Created by factory
    # Use session...
# on_shutdown called automatically

Features:

  • Async context manager (async with)
  • Signal handling (SIGINT, SIGTERM) for graceful shutdown
  • Session factory support (sync or async)
  • Shutdown callback for cleanup
  • Resource management via SessionContext

SessionContext

Container for an active session with its resources.

from friday_cli import SessionContext

# Created by SessionManager
ctx = SessionContext(session=my_session, config=config)

# Store resources for cleanup
ctx.set_resource("db", database_connection)
ctx.set_resource("cache", cache_instance)

# Retrieve resources
db = ctx.get_resource("db")

managed_session

Convenience async context manager for simple cases.

from friday_cli import managed_session

async with managed_session(config_path="config.yaml") as ctx:
    print(f"User: {ctx.config.user_name}")

# Or with config object
async with managed_session(config=config, session_factory=factory) as ctx:
    session = ctx.session

validate_config

Validate configuration and return list of errors.

from friday_cli import SessionConfig, validate_config

config = SessionConfig.from_yaml("config.yaml")

# Default validation (requires llm section with model)
errors = validate_config(config)
if errors:
    for error in errors:
        print(f"Config error: {error}")

# Custom required sections
errors = validate_config(config, required_sections=["llm", "memory"])

Integration Pattern

import asyncio
from friday_cli import SessionManager, SessionConfig, validate_config, display_error

async def main():
    # Load and validate config
    config = SessionConfig.from_yaml("config.yaml")
    errors = validate_config(config, required_sections=["llm"])
    if errors:
        for error in errors:
            display_error(error)
        return

    # Run with session management
    async with SessionManager(
        config=config,
        session_factory=create_my_session,
        on_shutdown=cleanup_resources,
    ) as ctx:
        await run_chat_loop(ctx.session, ctx.config)

if __name__ == "__main__":
    asyncio.run(main())

Testing

# Run all tests
uv run pytest apps/friday-cli/tests/ -v

# Run with coverage
uv run pytest apps/friday-cli/tests/ --cov=friday_cli

Testing with Captured Output

import io
from rich.console import Console
from friday_cli import set_console, display_success

def test_display_success():
    string_io = io.StringIO()
    console = Console(file=string_io, force_terminal=True)
    set_console(console)

    display_success("Test passed")

    output = string_io.getvalue()
    assert "Test passed" in output
    assert "✓" in output

    set_console(None)  # Reset

CLI Configuration

Friday CLI supports optional theming via a cli-config.yaml file. If present, it customizes colors, icons, and prompt styling. If absent, sensible defaults are used.

Configuration File Location

Place cli-config.yaml in the same directory as your main config.yaml:

your-project/
├── config.yaml          # Main application config
└── cli-config.yaml      # Optional CLI theming

Loading Configuration

from friday_cli import load_cli_config, reset_console

# Load CLI config (looks for cli-config.yaml next to config.yaml)
load_cli_config("path/to/config.yaml")
reset_console()  # Rebuild console with new theme

Configuration Options

# cli-config.yaml

# Theme colors (Rich style format)
theme:
  error: "bold red"
  warning: "bold yellow"
  success: "bold green"
  info: "bold blue"
  muted: "dim"
  highlight: "bold cyan"

# Status message icons
icons:
  error: "✗"      # or "[X]" for ASCII
  warning: "⚠"    # or "[!]" for ASCII
  success: "✓"    # or "[OK]" for ASCII
  info: "ℹ"       # or "[i]" for ASCII

# Input prompt styling
prompt:
  style: "bold cyan"
  name_style: "bold cyan"
  separator_style: "white"
  continuation_style: "gray"

Default Theme

Style Default Usage
error Bold red Error messages
warning Bold yellow Warning messages
success Bold green Success messages
info Bold blue Info messages
muted Dim Secondary text
highlight Bold cyan Emphasized text

Programmatic Configuration

from friday_cli import CLIConfig, ThemeConfig, set_cli_config, reset_console

# Create custom config
config = CLIConfig(
    theme=ThemeConfig(error="magenta", success="bright_green"),
)
set_cli_config(config)
reset_console()

# Or load from dict
config = CLIConfig.from_dict({
    "theme": {"error": "red"},
    "icons": {"success": "[DONE]"},
})

Dependencies

  • rich>=13.0.0 - Terminal formatting library
  • prompt_toolkit>=3.0.0 - Input handling with readline-style editing
  • pyyaml>=6.0.0 - YAML configuration loading

License

Part of the Friday Framework. See root LICENSE file.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

friday_framework_cli-0.1.0a0.tar.gz (42.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

friday_framework_cli-0.1.0a0-py3-none-any.whl (38.6 kB view details)

Uploaded Python 3

File details

Details for the file friday_framework_cli-0.1.0a0.tar.gz.

File metadata

  • Download URL: friday_framework_cli-0.1.0a0.tar.gz
  • Upload date:
  • Size: 42.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.10

File hashes

Hashes for friday_framework_cli-0.1.0a0.tar.gz
Algorithm Hash digest
SHA256 e0ddda2a152cf164078ac243a2b0929ab28d6744a359af402a8d80642798eee3
MD5 5bec7658ef6a26fd2529bd307a8c74ca
BLAKE2b-256 d9ac1990d3b479445e4372332df1fd79f98ec6514a9746bc653ef313681ebb7c

See more details on using hashes here.

File details

Details for the file friday_framework_cli-0.1.0a0-py3-none-any.whl.

File metadata

File hashes

Hashes for friday_framework_cli-0.1.0a0-py3-none-any.whl
Algorithm Hash digest
SHA256 11169c7f2e801749814545d39e5ac98686894ba59a83f6904b97df17a9bc0cfa
MD5 ca89777fa6009071e73c0f612f191600
BLAKE2b-256 1177bed3e6a4a31379a0d12e323b9bd7ad433c568e69e0a965c532d2ab24f123

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page