Skip to main content

A minimal Python framework for building stateful REPL applications with ASCII display

Project description

ReplKit2

Flask-style framework for building stateful REPL applications with rich display, MCP integration, and multi-mode deployment.

✨ Features

  • 🚀 Multi-mode: REPL, CLI, MCP server, API integration from one codebase
  • 🎨 Rich display: Tables, trees, boxes, charts, markdown
  • 🔌 MCP ready: Tools, resources, prompts for Claude/LLMs
  • CLI support: Traditional command-line interface via Typer
  • 🌐 API integration: Programmatic access for FastAPI, Flask, etc.
  • 🎯 Type safe: Full type hints, MCP-compatible validation

📦 Installation

# With uv (recommended)
uv add replkit2                      # Core library only
uv add "replkit2[all]"               # MCP + CLI support
uv add "replkit2[mcp,cli]"           # Same as above
uv add "replkit2[examples]"          # For running examples

# Or with pip
pip install replkit2
pip install replkit2[all]            # MCP + CLI support
pip install replkit2[mcp,cli]        # Same as above
pip install replkit2[examples]       # For running examples

🚀 Quick Start

from dataclasses import dataclass, field
from replkit2 import App

@dataclass
class State:
    tasks: list = field(default_factory=list)
    next_id: int = 1

app = App("todo", State)

@app.command(display="table", headers=["ID", "Task", "Done"])
def list_tasks(state):
    """List all tasks."""
    return [{"ID": t["id"], "Task": t["text"], "Done": "✓" if t["done"] else ""} 
            for t in state.tasks]

@app.command()
def add(state, text: str):
    """Add a task."""
    task = {"id": state.next_id, "text": text, "done": False}
    state.tasks.append(task)
    state.next_id += 1
    return f"Added: {text}"

@app.command()
def done(state, id: int):
    """Mark task as done."""
    for task in state.tasks:
        if task["id"] == id:
            task["done"] = True
            return f"Completed task {id}"
    return f"Task {id} not found"

if __name__ == "__main__":
    import sys
    if "--mcp" in sys.argv:
        app.mcp.run()     # MCP server for Claude/LLMs
    elif "--cli" in sys.argv:
        app.cli()         # Traditional CLI
    else:
        app.run(title="Todo Manager")  # Interactive REPL

Run it:

python todo.py                    # Interactive REPL
python todo.py --cli add "Buy milk"  # CLI mode
python todo.py --cli list_tasks      # CLI mode
python todo.py --mcp              # MCP server

🎨 Display Types

@app.command(display="table", headers=["Name", "Status"])
def show_table(state):
    return [{"Name": "Item 1", "Status": "Active"}]

@app.command(display="box", title="Info")
def show_box(state):
    return "This is in a box!"

@app.command(display="tree")
def show_tree(state):
    return {"root": {"child1": "leaf", "child2": ["item1", "item2"]}}

@app.command(display="progress", show_percentage=True)
def show_progress(state):
    return {"value": 7, "total": 10}

@app.command(display="markdown")
def show_markdown(state):
    return {
        "elements": [
            {"type": "heading", "content": "Status Report"},
            {"type": "alert", "content": "System is operational", "level": "success"},
            {"type": "table", "headers": ["Task", "Status"], 
             "rows": [{"Task": "Backup", "Status": "Complete"}]},
        ]
    }

🔌 MCP Integration

# Tool (callable action)
@app.command(fastmcp={"type": "tool"})
def process(state, text: str, count: int = 1):
    return f"Processed '{text}' {count} times"

# Resource (readable data at app://get_task/123)
@app.command(fastmcp={"type": "resource"})
def get_task(state, id: int):
    return {"id": id, "data": state.tasks.get(id)}

# Prompt template
@app.command(fastmcp={"type": "prompt"})
def brainstorm(state, topic: str = ""):
    context = "\n".join(t["text"] for t in state.tasks[:5])
    return f"Based on these tasks:\n{context}\n\nBrainstorm about: {topic}"

Configure Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "todo": {
      "command": "python",
      "args": ["/path/to/todo.py", "--mcp"]
    }
  }
}

⚡ CLI Support

@app.command(
    typer={"name": "ls", "help": "List tasks with filters"}
)
def list_tasks(state, done: bool = False, limit: int = 10):
    tasks = [t for t in state.tasks if not done or t.get("done")]
    return tasks[:limit]

# Usage:
# python todo.py --cli ls --done --limit 5
# python todo.py --cli add "New task"
# python todo.py --cli done 1

🌐 Multi-Mode Deployment

Write once, deploy everywhere - REPL, CLI, MCP server, or programmatic API:

if __name__ == "__main__":
    import sys
    if "--mcp" in sys.argv:
        app.mcp.run()           # MCP server for Claude/LLMs
    elif "--cli" in sys.argv:
        app.cli()               # Traditional CLI
    else:
        app.run(title="My App") # Interactive REPL

Integration properties: app.mcp (MCP server), app.cli (CLI), app.state (direct access), app.execute() (programmatic)

See docs/integrations.md for complete integration patterns and web framework examples.

🎭 Context-Aware Commands

Commands can detect execution mode (REPL, MCP, CLI, programmatic) and adapt behavior accordingly:

from replkit2.types import ExecutionContext

@app.command(display="table", fastmcp={"type": "tool"})
def preview(state, file: str, limit: int = None, _ctx: ExecutionContext = None):
    """Preview with smart defaults based on execution mode."""
    if limit is None:
        limit = 5 if _ctx and _ctx.is_repl() else None  # Compact for REPL, full for MCP/CLI
    return load_file(state, file)[:limit] if limit else load_file(state, file)

When to use: Commands with large datasets or different output needs per mode. Key features: Opt-in, auto-injected, backward compatible, user override.

See docs/integrations.md for complete guide.

🎯 Type Safety

# ✅ Good - MCP compatible
def cmd(state, 
    required: str,                  # Required param
    optional: str = None,           # Optional with None
    items: List[str] = None,        # Typed list
    config: Dict[str, int] = None,  # Typed dict
):
    pass

# ❌ Bad - causes "unknown" in MCP
def cmd(state,
    untyped,                        # Missing annotation
    opt: Optional[str] = None,      # Don't use Optional
    either: Union[str, int] = "",   # Don't use Union
):
    pass

📁 Examples

Run examples:

cd examples
python todo.py                  # REPL mode
python dataset.py               # Context-aware demo
python notes.py --mcp           # MCP server
python tasks.py --cli --help    # CLI help

📚 Documentation

🛠️ Development

# Clone and install
git clone https://github.com/angelsen/replkit2
cd replkit2
uv sync --group dev

# Type check
uv run basedpyright src/replkit2

# Format & lint
uv run ruff format src/
uv run ruff check src/

📄 License

MIT - see LICENSE for details.

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

replkit2-0.13.1.tar.gz (123.6 kB view details)

Uploaded Source

Built Distribution

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

replkit2-0.13.1-py3-none-any.whl (61.3 kB view details)

Uploaded Python 3

File details

Details for the file replkit2-0.13.1.tar.gz.

File metadata

  • Download URL: replkit2-0.13.1.tar.gz
  • Upload date:
  • Size: 123.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"EndeavourOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for replkit2-0.13.1.tar.gz
Algorithm Hash digest
SHA256 6614b2e7c53400f749cb89c598fe573c7c857043b70df135b657b18d69ec261c
MD5 f36ed0ee64ad5380af10a8d61a3473a0
BLAKE2b-256 15f0b3c2bb967c965b0341e923f0111df61da54f43bc14834a9f080627d8843e

See more details on using hashes here.

File details

Details for the file replkit2-0.13.1-py3-none-any.whl.

File metadata

  • Download URL: replkit2-0.13.1-py3-none-any.whl
  • Upload date:
  • Size: 61.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"EndeavourOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for replkit2-0.13.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f88101e82f82fe604e9d65b136ae14dde9248200620e01faf84f6cd17462fbbc
MD5 f0ed6318711246e26dce6cd92aa05f63
BLAKE2b-256 e99ca571dcb39e867488e0407fb3ab6aea58d267caae091fa5b69b17ad2028b2

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