# How to write ReplKit2 apps

## Create a basic app

```python
from replkit2 import App
from dataclasses import dataclass, field

@dataclass
class State:
    items: list = field(default_factory=list)
    count: int = 0

app = App("myapp", State)

# With MCP configuration
app = App("myapp", State, mcp_config={
    "instructions": "My app description",
    "uri_scheme": "custom"  # Optional, defaults to app name
})

@app.command(display="table", headers=["ID", "Name", "Status"])
def list_items(state):
    return [{"ID": i, "Name": item["name"], "Status": item["status"]} 
            for i, item in enumerate(state.items)]

@app.command()
def add_item(state, name: str, status: str = "active"):
    state.items.append({"name": name, "status": status})
    state.count += 1
    return f"Added {name}"

# Run as REPL: app.run()
# Run as MCP: app.mcp.run()
# Run as CLI: app.cli()
```

## Use display types

- `display="table"` with list of dicts
- `display="box"` with string
- `display="tree"` with nested dict
- `display="list"` with list of strings
- `display="progress"` with {"value": n, "total": m}
- `display="markdown"` with {"elements": [...]} and optional {"frontmatter": {...}}

## Add MCP support

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

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

@app.command(fastmcp={"type": "prompt"})
def generate(state, topic: str = "general"):
    # Prompt: template for LLMs
    return f"Write about {topic} based on: {state.items}"

@app.command(display="markdown", fastmcp={"type": "prompt"})
def review(state, code: str):
    # Rich message format with elements
    return {"messages": [
        {"role": "system", "content": {"type": "elements", "elements": [
            {"type": "text", "content": "You are a code reviewer."}
        ]}},
        {"role": "user", "content": {"type": "elements", "elements": [
            {"type": "heading", "content": "Review"},
            {"type": "code_block", "content": code, "language": "python"}
        ]}}
    ]}
```

## Handle types correctly for MCP

```python
# GOOD - works with MCP
def command(state,
    required: str,                    # Required param
    optional: str = None,             # Optional (use = None)
    items: List[str] = None,         # Lists work
    config: Dict[str, int] = None,   # Dicts work
):
    pass

# BAD - causes "unknown" in MCP
def command(state,
    param,                           # No type annotation
    optional: Optional[str] = None,  # Don't use Optional[T]
    either: Union[str, int] = "",    # Don't use Union
):
    pass
```

## Resource parameter rules

Resources parse from URIs, so parameters have constraints:

```python
# Required params first, then optional, dict must be last
@app.command(fastmcp={"type": "resource"})
def search(state, 
    category: str,                    # Required: /search/{category}/...
    tags: str = None,                 # Optional: parsed from comma-separated
    filters: dict = None,             # Dict last: consumes remaining segments
):
    tag_list = tags.split(",") if tags else []
    # URI: app://search/books/fiction,mystery/status/active/priority/high
    # Result: category="books", tags="fiction,mystery", filters={"status": "active", "priority": "high"}
```

## Add CLI support

```python
@app.command(typer={"help": "Add a new task"})
def add(state, text: str, priority: str = "normal"):
    # Available in CLI as: myapp add "text" --priority high
    return f"Added: {text}"

@app.command(typer={"enabled": False})
def debug(state):
    # Only in REPL, not CLI
    return vars(state)
```

## Programmatic usage (APIs, web frameworks)

```python
# Access state directly
state = app.state

# Execute commands and get raw data
result = app.execute("add_item", "Task", status="active")
stats = app.execute("list_items")

# Use with FastAPI
from fastapi import FastAPI
api = FastAPI()

@api.get("/items")
def get_items():
    return app.state.items  # Direct state access

@api.post("/items")
def create_item(name: str):
    app.execute("add_item", name)  # Command execution
    return app.state.items[-1]
```

## Context-aware commands

Commands can detect execution mode and adapt behavior. Add `_ctx: ExecutionContext = None` parameter:

```python
from replkit2.types import ExecutionContext

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

**Mode detection:** `_ctx.is_repl()`, `_ctx.is_mcp()`, `_ctx.is_cli()`, `_ctx.is_programmatic()`
**Use for:** Large datasets, different output structures per mode, interactive vs batch behavior

See docs/migration-0.13.md for complete patterns and migration guide.

## Custom display

```python
@app.formatter.register("custom")
def custom_display(data, meta, formatter):
    from replkit2.textkit import box, compose
    return compose(
        box(data["summary"], title="Overview"),
        formatter.format(data["details"], meta),  # Reuse formatter
    )

@app.command(display="custom")
def dashboard(state):
    return {"summary": "Active: 5", "details": state.items}
```

## Markdown with Table and Alert

```python
from replkit2.textkit.markdown import markdown

@app.command(display="markdown")
def report(state):
    return (markdown()
        .frontmatter(title="Report", generated=True)
        .heading("Summary")
        .alert("3 issues found", level="warning")
        .table(headers=["URL", "Size"], rows=state.logs)
        .list_(["Item 1", "Item 2"])  # Use list_() not list()
        .build())

# Element-level truncate/transforms (recommended)
@app.command(display="markdown")
def report_truncated(state):
    return {
        "elements": [
            {"type": "table",
             "headers": ["URL", "Size"],
             "rows": state.logs,
             "truncate": {"URL": {"max": 50, "mode": "middle"}},
             "transforms": {"Size": "format_size"}}
        ]
    }

# NOTE: Decorator-level truncate= and transforms= are DEPRECATED (removed in v0.14)
# Use element-level configuration or command-level with _ctx instead
```

## Run the app

```python
if __name__ == "__main__":
    import sys
    if "--mcp" in sys.argv:
        app.mcp.run()        # MCP server mode
    elif "--cli" in sys.argv:
        app.cli()            # CLI mode  
    else:
        app.run()            # REPL mode (default)
```