Metadata-Version: 2.4
Name: termstream-py
Version: 0.1.2
Summary: Unified streaming terminal UI framework (client + server)
Author-email: "Seow, Hong Yang" <paradossoeterna@gmail.com>
License-Expression: MIT
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Terminals
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: websockets>=12.0
Requires-Dist: mcp>=1.0.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: windows-curses>=2.3.0; sys_platform == "win32"
Provides-Extra: ssl
Requires-Dist: cryptography>=41.0; extra == "ssl"
Dynamic: license-file

# termstream-py

Unified streaming terminal UI framework (client + server)

TermStream is a Python-based framework for building streaming terminal UI applications with WebSocket communication, curses-based browser client, and MCP (Model Context Protocol) agent support.

## Installation

```bash
pip install termstream-py
```

### Prerequisites

- **Python** >= 3.8

## Usage

### CLI Commands

After installation, the `termstream` command is available:

```bash
# Start the WebSocket server with a Python app module
termstream server -a path/to/your_app.py

# Start the WebSocket server with a YAML configuration file
termstream server -c path/to/your_config.yaml

# Start the WebSocket server from a directory tree (content.md files)
termstream server -d path/to/your_directory/

# Run the terminal browser client
termstream browser -u ws://localhost:8765

# Run MCP server for AI agents
termstream mcp -a path/to/your_app.py --transport stdio
termstream mcp -c path/to/your_config.yaml --transport stdio
```

### CLI Options

#### server
- `-u, --url` - Server WebSocket URL (default: `ws://localhost:8765`)
- `-a, --app` - Path to app module (e.g., `myapp.py`)
- `-c, --config` - Path to YAML configuration file
- `-d, --directory` - Path to directory tree for building routes from `content.md` files

#### browser
- `-u, --url` - Server WebSocket URL (default: `ws://localhost:8765`)

#### mcp
- `-a, --app` - Path to app module (e.g., `myapp.py`)
- `-c, --config` - Path to YAML configuration file
- `-p, --port` - MCP HTTP port (default: 3000)
- `--transport` - MCP transport type: `stdio` or `http` (default: `stdio`)

### YAML Configuration

Define your server pages in a YAML file instead of code:

```yaml
host: localhost
port: 8765

pages:
  - path: /home
    title: Home
    widgets:
      - type: heading
        pos: [0, 0]
        text: Welcome to TermStream
        style:
          bold: true
      - type: text
        pos: [2, 0]
        text: Streaming terminal UI framework.
      - type: button
        pos: [4, 0]
        text: "-> Docs"
        action:
          type: navigate
          target: /docs
```

**Widget types:** `text`, `heading`, `button`, `input`

**Style options:** `bold`, `italic`, `underline`

**Action types:** `navigate` (with `target` path)

**Note:** Widget `id` is optional. If omitted, a unique id is auto-generated. Provide an explicit `id` only for widgets that need server-side updates or input handling.

### Directory-Based Configuration

Instead of a YAML file, you can define your application as a directory tree where each directory contains a `content.md` file:

```
my_app/
├── content.md          # Root page (/)
├── docs/
│   ├── content.md      # /docs
│   └── api/
│       └── content.md  # /docs/api
└── about/
    └── content.md      # /about
```

Each `content.md` file defines the page content using a simple format:

```markdown
# Page Title
This is some text content on the page.
Another line of text.
> Button Label
$ Input Placeholder
```

**Format rules:**
- First line starting with `#` becomes a bold HEADING widget (also used as page title)
- Regular lines become TEXT widgets
- Lines starting with `>` become BUTTON widgets
- Lines starting with `$` become INPUT widgets
- Buttons can include a navigation target using `-> /path` syntax (e.g., `> Go to Docs -> /docs`)
- Empty lines are skipped
- Subdirectories automatically become child navigation nodes
- Directories without `content.md` get a blank page with a warning

**Auto-resolving navigation** works identically with directory-based configuration — the route hierarchy is built from the directory structure.

### Auto-Resolving Navigation

Navigation items are automatically resolved from the route tree — no need to define `nav` manually. The server inspects all registered routes and builds a hierarchical navigation structure:

```
/                  → shows: home, docs
/home              → shows: (children of /home, if any)
/docs              → shows: (end of node) — no children
```

This means you only need to define your page paths and widgets. Navigation is generated automatically based on the URL structure. Leaf pages (with no child routes) display an `(end of node)` indicator that does nothing when activated.

#### Example: 3-Level Hierarchy

```yaml
pages:
  - path: /              # Root
  - path: /console       # Layer 1
  - path: /console/fe3   # Layer 2 (leaf)
  - path: /switch        # Layer 1
  - path: /switch/fe3h   # Layer 2 (leaf)
```

At `/`, the nav shows `Console` and `Switch`. At `/console`, the nav shows `Fe3`. At `/console/fe3`, the nav shows `(end of node)`.

### Python API

```python
from termstream_py import TermStreamServer, Page, Widget, WidgetType

app = TermStreamServer(host="localhost", port=8765)

@app.route("/home")
def home():
    return Page(
        title="Home",
        widgets=[
            Widget("h1", WidgetType.HEADING, (0, 0), "Welcome", bold=True),
            Widget("p1", WidgetType.TEXT, (2, 0), "Streaming terminal UI."),
        ]
    )
```

> **Note:** Navigation items are auto-resolved from the route tree, so you no longer need to pass `nav=[...]` to `Page()`.

### PageNode Builder API

The `PageNode` builder provides a hierarchical, tree-based API for constructing complex applications. Pages are organized as a tree of nodes where each node can have child sub-pages:

```python
from termstream_py import (
    TermStreamServer, Page, Widget, WidgetType,
    PageNode, create_page_node, create_server_from_nodes,
    visualise, to_yaml, from_yaml, to_directory, from_directory
)

# Build a hierarchical page tree
home = PageNode("/home", "Home") \
    .with_widget(Widget("h1", WidgetType.HEADING, (0, 0), "Welcome", bold=True))

docs = PageNode("/docs", "Docs") \
    .with_widget(Widget("h1", WidgetType.HEADING, (0, 0), "Documentation", bold=True))

# Add children
home.add_child(docs)

# Convert to server
server = create_server_from_nodes(home)

# Visualize the route tree
print(visualise(home))

# Export to YAML
to_yaml(home, "config.yaml")

# Export to directory structure with content.md files
to_directory(home, "./my_app")

# Load from YAML or directory
root = from_yaml("config.yaml")
root = from_directory("./my_app")
```

**Available functions:**
- `PageNode(path, title)` — Create a node at the given path
- `create_page_node(path, title)` — Factory function for PageNode
- `create_server_from_nodes(root)` — Convert a PageNode tree to a TermStreamServer
- `visualise(node)` — Print the route tree structure
- `to_yaml(node, path)` — Export the tree to a YAML file
- `from_yaml(path)` — Load a PageNode tree from YAML
- `to_directory(node, dir_path)` — Export the tree to a directory with content.md files
- `from_directory(dir_path)` — Load a PageNode tree from a directory structure

### MCP Integration

```python
from termstream_py import TermStreamServer, init_mcp_server

app = TermStreamServer(host="localhost", port=8765)

# Initialize MCP server for AI agent integration
mcp_server = init_mcp_server(app)
mcp_server.run(transport="stdio")
```

## Browser Controls

| Key | Action |
|-----|--------|
| → / ← | Switch between Nav and Content panes |
| ↑↓ | Focus nav item or content widget |
| Enter | Activate focused element |
| `/` | Start search mode (nav pane) |
| Space / PageDown | Scroll down |
| b / PageUp | Scroll up |
| 1-9 | Navigate to specific nav item |
| ESC / Backspace | Go back |
| q | Quit |

## Features

- **WebSocket Server**: Stream UI frames to terminal clients
- **Curses Browser**: Terminal-based browser with navigation support
- **Auto-Resolving Nav**: Navigation generated automatically from route hierarchy
- **Directory-Based Config**: Build routes from a directory tree with `content.md` files
- **MCP Integration**: AI agent support via Model Context Protocol
- **YAML Configuration**: Define pages and widgets in YAML files
- **Route-based Pages**: Define pages using decorators
- **Interactive Widgets**: Text, headings, buttons, and input fields

## HTTPS / WSS Support

TermStream supports encrypted WebSocket connections (WSS) for secure communication.

### Installation

HTTPS requires the `cryptography` package. Install it with the optional `ssl` extra:

```bash
pip install termstream-py[ssl]
```

### CLI Usage

**Auto-generate self-signed certificate (development):**

```bash
termstream server -c config.yaml --ssl
```

**Use your own certificate:**

```bash
termstream server -c config.yaml --ssl-cert /path/to/cert.pem --ssl-key /path/to/key.pem
```

### YAML Configuration

```yaml
host: localhost
port: 8765
ssl: true
ssl_cert: /path/to/cert.pem   # optional; auto-generated if omitted
ssl_key: /path/to/key.pem     # optional; auto-generated if omitted
```

### Browser Client

Connect to WSS servers by using the `wss://` scheme:

```bash
termstream browser -u wss://localhost:8765
```

> **Note:** Self-signed certificates are for development only. For production, use certificates from a trusted CA (e.g., Let's Encrypt).

## Security Notes

- **HTTPS/WSS**: Use `--ssl` or configure `ssl: true` in YAML to enable encrypted connections.
- **Origin filtering**: Use `--allowed-origins` to restrict which origins can connect to the WebSocket server.
- **Dynamic app loading**: The `--app` flag executes arbitrary Python code. It can be disabled by setting `TERMSTREAM_DISABLE_APP=1`. Prefer `--config` (YAML) or `--directory` for untrusted content.

## Disclosure


## Other Versions

TermStream is available in multiple languages. Choose the version that best fits your ecosystem:

| Version | Package | Installation |
|---------|---------|--------------|
| **Python** | `termstream-py` | `pip install termstream-py` |
| **Rust** | `termstream` | `cargo install termstream` |
| **Node.js** | `termstream-npm` | `npm install termstream-npm` |

### Rust Version

The Rust version (`termstream-rust`) provides a native binary with high-performance WebSocket server and crossterm-based TUI client. Install via Cargo:

```bash
# Install from crates.io
cargo install termstream

# Or build from source
cd termstream-rust
cargo build --release
```

The Rust version supports the same CLI commands (`server`, `browser`, `mcp`) and YAML configuration format as the Python version.

### Node.js Version

The Node.js version (`termstream-npm`) wraps the Rust binary via npm, providing a drop-in solution for JavaScript/TypeScript projects. Install via npm:

```bash
npm install termstream-npm
```

The npm version automatically compiles the Rust backend during installation and exposes the same CLI interface.

## License

MIT
