# Library Separation Framework: styrene-core

**Version:** 1.0
**Date:** 2026-01-25
**Status:** Planning Document

## Executive Summary

This document outlines the separation of `styrene-tui` into two packages:

1. **styrene-core** - Reusable RNS/LXMF library for Python applications
2. **styrene-tui** - Terminal UI application consuming styrene-core

### Motivation

The current styrene-tui codebase contains ~6,900 lines of TUI-independent service/model code that provides RNS/LXMF messaging capabilities. This core functionality should be extracted into a standalone library to enable:

- **Reusability**: Use styrene-core in headless bots, web services, or other Python applications
- **Testability**: Test core RNS/LXMF logic independently from TUI concerns
- **Maintainability**: Clear separation of concerns between business logic and presentation
- **Distribution**: Publish styrene-core to PyPI for broader ecosystem adoption

### Immediate Use Cases

1. **Agent-work Claude chatbot**: LXMF-enabled Claude agent using styrene-core (no TUI dependency)
2. **Headless fleet nodes**: Daemon mode running styrene-core for RPC/messaging
3. **Web applications**: FastAPI/Flask apps using styrene-core for mesh integration

### Migration Strategy

This is a **non-breaking refactor**. styrene-tui will continue to work unchanged, simply importing from styrene-core instead of local modules. The migration follows these phases:

1. **Phase 1**: Create styrene-core package structure
2. **Phase 2**: Move TUI-independent code to styrene-core
3. **Phase 3**: Update styrene-tui imports to consume styrene-core
4. **Phase 4**: Test and validate both packages
5. **Phase 5**: Publish styrene-core to PyPI

---

## Package Structure

### styrene-core (New Library)

```
styrene-core/
├── src/
│   └── styrene_core/
│       ├── __init__.py           # Public API exports
│       ├── services/             # RNS/LXMF service layer
│       │   ├── __init__.py
│       │   ├── rns_service.py    # RNS lifecycle singleton
│       │   ├── lxmf_service.py   # LXMF messaging with callbacks
│       │   ├── reticulum.py      # Identity, discovery, announces
│       │   ├── auto_reply.py     # Message handling pattern
│       │   ├── node_store.py     # SQLite device storage
│       │   └── app_lifecycle.py  # Service initialization orchestration
│       ├── models/               # Data models (TUI-independent)
│       │   ├── __init__.py
│       │   ├── config.py         # Configuration (core subset)
│       │   ├── rns_error.py      # Error states
│       │   ├── reticulum.py      # Reticulum models
│       │   ├── mesh_device.py    # Network device representation
│       │   ├── messages.py       # SQLAlchemy message models
│       │   └── styrene_wire.py   # Protocol message formats
│       ├── protocols/            # LXMF protocol abstractions
│       │   ├── __init__.py
│       │   ├── base.py           # Protocol interface
│       │   ├── registry.py       # Protocol routing
│       │   ├── chat.py           # Chat protocol
│       │   └── styrene.py        # Styrene node protocol
│       └── daemon.py             # Headless service runner
├── pyproject.toml
├── README.md
└── tests/
```

### styrene-tui (Refactored Application)

```
styrene-tui/
├── src/
│   └── styrene/
│       ├── __init__.py
│       ├── app.py                # Main TUI application
│       ├── screens/              # Textual screens (unchanged)
│       ├── widgets/              # Textual widgets (unchanged)
│       ├── themes/               # CSS theming (unchanged)
│       ├── cli/                  # CLI commands (unchanged)
│       ├── services/             # TUI-specific services only
│       │   ├── config.py         # Config loading (imports from core)
│       │   ├── fleet.py          # Fleet management (TUI-specific)
│       │   ├── provisioner.py    # Device provisioning (TUI-specific)
│       │   ├── hardware.py       # Hardware detection (TUI-specific)
│       │   ├── catalog.py        # Asset catalog (TUI-specific)
│       │   ├── storage.py        # USB storage management (TUI-specific)
│       │   ├── rpc_client.py     # RPC client (fleet management)
│       │   ├── rpc_server.py     # RPC server (imports from core)
│       │   └── hub_connection.py # Hub connectivity (Phase 2 feature)
│       ├── models/               # TUI-specific models only
│       │   ├── config.py         # Full config (imports core.ConfigBase)
│       │   ├── fleet.py          # Fleet device models
│       │   ├── hardware.py       # Hardware models
│       │   ├── catalog.py        # Catalog models
│       │   ├── profiles.py       # Provisioning profiles
│       │   ├── roles.py          # Device roles
│       │   ├── device_hardware.py
│       │   ├── rpc.py            # RPC models (fleet-specific)
│       │   └── rpc_messages.py   # RPC message formats
│       ├── api.py                # FastAPI server (optional)
│       └── __main__.py           # Entry point
└── pyproject.toml                # Depends on styrene-core
```

---

## File-by-File Migration Plan

### Category A: Move to styrene-core (Complete Files)

These files are 100% TUI-independent and move wholesale:

| Source File | Destination | Lines | Notes |
|-------------|-------------|-------|-------|
| `services/rns_service.py` | `styrene_core/services/rns_service.py` | 364 | RNS lifecycle singleton - zero TUI deps |
| `services/lxmf_service.py` | `styrene_core/services/lxmf_service.py` | 537 | LXMF messaging - includes MockLXMFService for testing |
| `services/auto_reply.py` | `styrene_core/services/auto_reply.py` | 298 | Auto-reply handler - pure messaging logic |
| `services/node_store.py` | `styrene_core/services/node_store.py` | 545 | SQLite device storage - no TUI refs |
| `models/rns_error.py` | `styrene_core/models/rns_error.py` | 211 | Error state models - pure data |
| `models/reticulum.py` | `styrene_core/models/reticulum.py` | 128 | Reticulum config models - no TUI deps |
| `models/mesh_device.py` | `styrene_core/models/mesh_device.py` | 292 | Network device representation - pure data |
| `models/messages.py` | `styrene_core/models/messages.py` | 166 | SQLAlchemy models - database only |
| `models/styrene_wire.py` | `styrene_core/models/styrene_wire.py` | 353 | Wire protocol formats - pure serialization |
| `protocols/base.py` | `styrene_core/protocols/base.py` | 141 | Protocol interface - abstract base |
| `protocols/registry.py` | `styrene_core/protocols/registry.py` | 123 | Protocol routing - no TUI deps |
| `protocols/chat.py` | `styrene_core/protocols/chat.py` | 89 | Chat protocol impl - messaging only |
| `protocols/styrene.py` | `styrene_core/protocols/styrene.py` | 124 | Styrene protocol - node communication |
| `daemon.py` | `styrene_core/daemon.py` | 389 | Headless service loop - already TUI-free |

**Subtotal: ~3,760 lines** moving completely to styrene-core.

### Category B: Split Required (Shared Functionality)

These files contain both core and TUI-specific logic:

#### `services/reticulum.py` (840 lines)

**Split Approach:**
- **To styrene-core** (~700 lines):
  - `is_reticulum_configured()` - Config detection
  - `get_reticulum_config()` - Config reading
  - `ensure_operator_identity()` - Identity management
  - `get_operator_identity_object()` - Identity retrieval
  - `StyreneAnnounceHandler` - Device discovery
  - `start_discovery()` / `stop_discovery()` / `discover_devices()` - Discovery API
  - `generate_rns_config()` - Config generation
  - `initialize_reticulum_with_config()` - RNS initialization
- **Stays in styrene-tui** (~140 lines):
  - TUI-specific wizard flows (if any)
  - First-run setup UI integration

**New file structure:**
- `styrene_core/services/reticulum.py` - Core identity/discovery/config
- `styrene/services/reticulum_ui.py` (optional) - TUI-specific helpers

#### `services/config.py` (777 lines)

**Split Approach:**
- **To styrene-core** (~200 lines):
  - `CoreConfig` dataclass (subset of StyreneConfig):
    - `ReticulumConfig`
    - `RPCConfig`
    - `DiscoveryConfig`
    - `ChatConfig`
    - `APIConfig`
  - `load_core_config()` - Load core subset from YAML
  - Config validation for core fields
- **Stays in styrene-tui** (~577 lines):
  - `StyreneConfig` (full config including TUI/fleet/provisioning)
  - `TUIConfig`, `FleetConfig`, `ProvisioningDefaults`, `MeshDefaults`
  - `load_config()` - Full TUI config loading
  - Config migration logic

**Import relationship:**
```python
# In styrene-core
from styrene_core.models.config import CoreConfig, ReticulumConfig, ChatConfig

# In styrene-tui
from styrene_core.models.config import CoreConfig  # Base
from styrene.models.config import StyreneConfig    # Extended
```

#### `services/app_lifecycle.py` (382 lines)

**Split Approach:**
- **To styrene-core** (~250 lines):
  - `CoreLifecycle` class:
    - RNS initialization
    - LXMF initialization
    - Protocol registration
    - Discovery startup
    - Graceful shutdown
- **Stays in styrene-tui** (~132 lines):
  - `StyreneLifecycle` (extends `CoreLifecycle`):
    - Fleet service initialization
    - TUI-specific service startup
    - API server lifecycle (if enabled)

**Example:**
```python
# styrene-core
class CoreLifecycle:
    def __init__(self, config: CoreConfig):
        self.config = config
        self.rns_service = get_rns_service()
        self.lxmf_service = get_lxmf_service()

    def initialize(self) -> bool:
        # Initialize RNS, LXMF, discovery
        ...

# styrene-tui
class StyreneLifecycle(CoreLifecycle):
    def __init__(self, config: StyreneConfig):
        super().__init__(config)  # Pass core config subset
        self.fleet_service = FleetService()

    def initialize(self) -> bool:
        if not super().initialize():
            return False
        # Initialize TUI-specific services
        self.fleet_service.load()
        return True
```

#### `models/config.py` (462 lines)

**Split Approach:**
- **To styrene-core** (~180 lines):
  - `LogLevel` enum
  - `DeploymentMode` enum
  - `PeerConfig`, `ServerInterfaceConfig`, `InterfaceConfig`
  - `ReticulumConfig`
  - `APIConfig`, `RPCConfig`, `DiscoveryConfig`, `ChatConfig`
  - `CoreConfig` (root for headless/library use)
- **Stays in styrene-tui** (~282 lines):
  - `ThemeMode` enum
  - `GatewayMode` enum
  - `TUIConfig`, `FleetConfig`, `ProvisioningDefaults`, `MeshDefaults`, `AdvancedConfig`
  - `StyreneConfig` (extends CoreConfig)

### Category C: Stays in styrene-tui (TUI-Specific)

These files are deeply coupled to TUI/provisioning/fleet concerns:

| File | Lines | Reason |
|------|-------|--------|
| `services/fleet.py` | 359 | Fleet inventory management (git-backed YAML) |
| `services/provisioner.py` | 300 | USB device provisioning (platform-specific) |
| `services/hardware.py` | 369 | Hardware detection (psutil, platform APIs) |
| `services/catalog.py` | 320 | Asset catalog (NixOS images, profiles) |
| `services/storage.py` | 310 | USB storage device enumeration |
| `services/rpc_client.py` | 451 | RPC client (fleet command dispatch) |
| `services/rpc_server.py` | 510 | RPC server (imports lxmf_service, could be hybrid) |
| `services/hub_connection.py` | 324 | Hub connectivity (Phase 2 feature, TUI-specific) |
| `services/asset_resolver.py` | 90 | Asset path resolution |
| `models/fleet.py` | 74 | Fleet device models |
| `models/hardware.py` | 134 | Hardware profile models |
| `models/catalog.py` | 182 | Catalog entry models |
| `models/profiles.py` | 74 | Provisioning profiles |
| `models/roles.py` | 27 | Device role enum |
| `models/device_hardware.py` | 36 | Hardware metadata |
| `models/rpc.py` | 205 | RPC models (fleet-specific) |
| `models/rpc_messages.py` | 271 | RPC message formats |
| All `screens/*.py` | ~2,500 | Textual UI screens |
| All `widgets/*.py` | ~2,000 | Textual UI widgets |
| All `themes/*.py` | ~800 | CSS theming |
| All `cli/*.py` | ~500 | CLI commands |
| `app.py` | ~600 | Main TUI application |
| `api.py` | ~200 | FastAPI server (optional) |

---

## Dependency Analysis

### styrene-core Dependencies

**Required (Minimal Set):**
```toml
[project]
name = "styrene-core"
version = "0.1.0"
dependencies = [
    "rns>=0.7.0",           # Reticulum Network Stack
    "lxmf>=0.4.0",          # LXMF messaging protocol
    "pyyaml>=6.0",          # Config file parsing
    "platformdirs>=4.0",    # XDG-compliant paths
    "sqlalchemy>=2.0",      # Message/node persistence
]
```

**Optional:**
- None (core should be dependency-minimal)

### styrene-tui Dependencies

**Required:**
```toml
[project]
name = "styrene-tui"
version = "0.1.0"
dependencies = [
    "styrene-core>=0.1.0",  # Core library
    "textual>=0.47.0",      # TUI framework
    "pyyaml>=6.0",          # Config (also used by core)
    "platformdirs>=4.0",    # Paths (also used by core)
    "psutil>=5.9",          # Hardware detection
]
```

**Optional:**
```toml
[project.optional-dependencies]
api = [
    "fastapi>=0.100",
    "uvicorn>=0.20",
]
```

### Import Dependency Flow

```
┌─────────────────┐
│   styrene-tui   │
│   (Textual UI)  │
└────────┬────────┘
         │ imports
         v
┌─────────────────┐
│  styrene-core   │
│  (RNS/LXMF lib) │
└────────┬────────┘
         │ imports
         v
┌─────────────────┐
│   RNS + LXMF    │
│  (mesh network) │
└─────────────────┘
```

**Critical Rule**: styrene-core must NOT import from styrene-tui. Dependencies flow one-way only.

---

## API Surface Design

### styrene-core Public API

The public API exported from `styrene_core/__init__.py`:

```python
"""styrene-core: Reusable RNS/LXMF library for Python applications.

Public API for building applications on top of Reticulum and LXMF.
"""

# Version
__version__ = "0.1.0"

# Service layer (primary interface)
from styrene_core.services.rns_service import RNSService, get_rns_service
from styrene_core.services.lxmf_service import LXMFService, get_lxmf_service
from styrene_core.services.reticulum import (
    # Identity management
    ensure_operator_identity,
    get_operator_identity,
    get_operator_identity_object,
    # Config detection
    is_reticulum_configured,
    find_reticulum_config,
    get_reticulum_config,
    # Discovery
    start_discovery,
    stop_discovery,
    discover_devices,
    get_styrene_devices,
    get_rnodes,
    # RNS initialization
    initialize_reticulum_with_config,
    generate_rns_config,
)
from styrene_core.services.auto_reply import AutoReplyHandler
from styrene_core.services.node_store import NodeStore, get_node_store
from styrene_core.services.app_lifecycle import CoreLifecycle

# Models (data structures)
from styrene_core.models.config import (
    CoreConfig,
    ReticulumConfig,
    RPCConfig,
    DiscoveryConfig,
    ChatConfig,
    APIConfig,
    DeploymentMode,
    PeerConfig,
    ServerInterfaceConfig,
    InterfaceConfig,
)
from styrene_core.models.rns_error import (
    RNSErrorState,
    RNSErrorCategory,
)
from styrene_core.models.reticulum import (
    ReticulumConfig as ReticulumConfigModel,
    ReticulumIdentity,
    ReticulumInterface,
    ReticulumNotConfiguredError,
)
from styrene_core.models.mesh_device import (
    MeshDevice,
    DeviceType,
    DeviceStatus,
    create_mesh_device,
)
from styrene_core.models.messages import Message, init_db, get_session
from styrene_core.models.styrene_wire import (
    StyreneWireMessage,
    MessageType,
)

# Protocols (LXMF abstractions)
from styrene_core.protocols.base import Protocol, LXMFMessage
from styrene_core.protocols.registry import ProtocolRegistry
from styrene_core.protocols.chat import ChatProtocol
from styrene_core.protocols.styrene import StyreneProtocol

# Daemon (headless service)
from styrene_core.daemon import StyreneDaemon, run_daemon

__all__ = [
    # Version
    "__version__",
    # Services
    "RNSService",
    "get_rns_service",
    "LXMFService",
    "get_lxmf_service",
    "AutoReplyHandler",
    "NodeStore",
    "get_node_store",
    "CoreLifecycle",
    # Reticulum helpers
    "ensure_operator_identity",
    "get_operator_identity",
    "get_operator_identity_object",
    "is_reticulum_configured",
    "find_reticulum_config",
    "get_reticulum_config",
    "start_discovery",
    "stop_discovery",
    "discover_devices",
    "get_styrene_devices",
    "get_rnodes",
    "initialize_reticulum_with_config",
    "generate_rns_config",
    # Config models
    "CoreConfig",
    "ReticulumConfig",
    "RPCConfig",
    "DiscoveryConfig",
    "ChatConfig",
    "APIConfig",
    "DeploymentMode",
    "PeerConfig",
    "ServerInterfaceConfig",
    "InterfaceConfig",
    # Error models
    "RNSErrorState",
    "RNSErrorCategory",
    # Reticulum models
    "ReticulumConfigModel",
    "ReticulumIdentity",
    "ReticulumInterface",
    "ReticulumNotConfiguredError",
    # Device models
    "MeshDevice",
    "DeviceType",
    "DeviceStatus",
    "create_mesh_device",
    # Message models
    "Message",
    "init_db",
    "get_session",
    # Wire protocol
    "StyreneWireMessage",
    "MessageType",
    # Protocols
    "Protocol",
    "LXMFMessage",
    "ProtocolRegistry",
    "ChatProtocol",
    "StyreneProtocol",
    # Daemon
    "StyreneDaemon",
    "run_daemon",
]
```

### Usage Patterns

#### Pattern 1: Minimal LXMF Messaging

```python
from styrene_core import (
    get_rns_service,
    get_lxmf_service,
    ensure_operator_identity,
    initialize_reticulum_with_config,
    CoreConfig,
)

# Create minimal config
config = CoreConfig()

# Initialize RNS
if not initialize_reticulum_with_config(config):
    print("Failed to initialize RNS")
    exit(1)

# Ensure we have an identity
identity_hash = ensure_operator_identity()
print(f"Identity: {identity_hash}")

# Get services
rns = get_rns_service()
lxmf = get_lxmf_service()

# Initialize LXMF
from styrene_core.services.reticulum import get_operator_identity_object
identity_obj = get_operator_identity_object()
if identity_obj and lxmf.initialize(identity_obj):
    # Register message handler
    def handle_message(source: str, payload: dict):
        print(f"Message from {source}: {payload}")

    lxmf.register_callback(handle_message)

    # Send message
    lxmf.send_message("a1b2c3d4...", {"type": "ping"})
```

#### Pattern 2: Full Daemon with Auto-Reply

```python
import asyncio
from styrene_core import (
    CoreConfig,
    DeploymentMode,
    CoreLifecycle,
    AutoReplyHandler,
    ChatConfig,
)

# Configure
config = CoreConfig()
config.reticulum.mode = DeploymentMode.PEER
config.reticulum.interfaces.peers.append(
    PeerConfig(host="hub.example.com", port=4242)
)
config.chat.auto_reply_enabled = True

# Initialize lifecycle
lifecycle = CoreLifecycle(config)
if not lifecycle.initialize():
    print("Initialization failed")
    exit(1)

# Start auto-reply
from styrene_core.services.lxmf_service import get_lxmf_service
from styrene_core.services.reticulum import get_operator_identity_object

lxmf = get_lxmf_service()
identity = get_operator_identity_object()

handler = AutoReplyHandler(
    config=config.chat,
    identity=identity,
    router=lxmf.router,
)
lxmf.router.register_delivery_callback(handler.handle_message)

# Run forever
try:
    while True:
        asyncio.sleep(60)
except KeyboardInterrupt:
    lifecycle.shutdown()
```

#### Pattern 3: Device Discovery

```python
from styrene_core import (
    start_discovery,
    discover_devices,
    get_styrene_devices,
)

def on_device_found(device):
    print(f"Found: {device.name} ({device.device_type.value})")

# Start discovery
start_discovery(callback=on_device_found)

# Query discovered devices
import time
time.sleep(10)
all_devices = discover_devices()
styrene_nodes = get_styrene_devices()
print(f"Total: {len(all_devices)}, Styrene nodes: {len(styrene_nodes)}")
```

---

## Migration Steps

### Phase 1: Repository Setup (1-2 hours)

1. **Create styrene-core repository**
   ```bash
   cd ~/workspace/vanderlyn/styrene-lab
   mkdir styrene-core
   cd styrene-core
   git init
   ```

2. **Create package structure**
   ```bash
   mkdir -p src/styrene_core/{services,models,protocols}
   touch src/styrene_core/__init__.py
   touch src/styrene_core/services/__init__.py
   touch src/styrene_core/models/__init__.py
   touch src/styrene_core/protocols/__init__.py
   ```

3. **Create pyproject.toml**
   ```toml
   [project]
   name = "styrene-core"
   version = "0.1.0"
   description = "RNS/LXMF library for Python applications"
   requires-python = ">=3.11"
   dependencies = [
       "rns>=0.7.0",
       "lxmf>=0.4.0",
       "pyyaml>=6.0",
       "platformdirs>=4.0",
       "sqlalchemy>=2.0",
   ]

   [project.scripts]
   styrene-daemon = "styrene_core.daemon:main"
   ```

4. **Create README.md with quick start examples**

### Phase 2: Copy Files (2-3 hours)

1. **Copy Category A files (complete moves)**
   ```bash
   # From styrene-tui/src/styrene to styrene-core/src/styrene_core
   cp services/rns_service.py ../styrene-core/src/styrene_core/services/
   cp services/lxmf_service.py ../styrene-core/src/styrene_core/services/
   cp services/auto_reply.py ../styrene-core/src/styrene_core/services/
   cp services/node_store.py ../styrene-core/src/styrene_core/services/
   cp models/rns_error.py ../styrene-core/src/styrene_core/models/
   cp models/reticulum.py ../styrene-core/src/styrene_core/models/
   cp models/mesh_device.py ../styrene-core/src/styrene_core/models/
   cp models/messages.py ../styrene-core/src/styrene_core/models/
   cp models/styrene_wire.py ../styrene-core/src/styrene_core/models/
   cp protocols/*.py ../styrene-core/src/styrene_core/protocols/
   cp daemon.py ../styrene-core/src/styrene_core/
   ```

2. **Update imports in copied files**
   ```bash
   # Find and replace in styrene-core
   find src/styrene_core -name "*.py" -exec sed -i '' 's/from styrene\./from styrene_core./g' {} \;
   find src/styrene_core -name "*.py" -exec sed -i '' 's/import styrene\./import styrene_core./g' {} \;
   ```

### Phase 3: Split Files (4-6 hours)

1. **Split services/reticulum.py**
   - Create `styrene_core/services/reticulum.py` with core functions
   - Keep TUI-specific helpers in `styrene/services/reticulum_ui.py` (if needed)
   - Update imports in both packages

2. **Split services/config.py**
   - Create `styrene_core/models/config.py` with `CoreConfig`
   - Update `styrene/models/config.py` to extend `CoreConfig`
   - Adjust YAML serialization to handle inheritance

3. **Split services/app_lifecycle.py**
   - Create `styrene_core/services/app_lifecycle.py` with `CoreLifecycle`
   - Update `styrene/services/app_lifecycle.py` to extend with `StyreneLifecycle`
   - Test initialization flow

4. **Split models/config.py**
   - Move core config classes to `styrene_core/models/config.py`
   - Keep TUI-specific configs in `styrene/models/config.py`
   - Update config loading logic

### Phase 4: Update styrene-tui Imports (2-3 hours)

1. **Add styrene-core dependency**
   ```toml
   # styrene-tui/pyproject.toml
   dependencies = [
       "styrene-core>=0.1.0",  # Add this
       "textual>=0.47.0",
       # ... rest
   ]
   ```

2. **Global import replacement**
   ```bash
   cd styrene-tui/src/styrene

   # Services that moved to core
   find . -name "*.py" -exec sed -i '' 's/from styrene\.services\.rns_service/from styrene_core.services.rns_service/g' {} \;
   find . -name "*.py" -exec sed -i '' 's/from styrene\.services\.lxmf_service/from styrene_core.services.lxmf_service/g' {} \;
   # ... repeat for all moved modules
   ```

3. **Update __init__.py re-exports**
   ```python
   # styrene/__init__.py
   # Re-export core types for convenience
   from styrene_core import (
       get_rns_service,
       get_lxmf_service,
       CoreConfig,
       # ...
   )
   ```

### Phase 5: Testing (3-4 hours)

1. **Test styrene-core in isolation**
   ```bash
   cd styrene-core
   python -m pytest tests/

   # Manual smoke test
   python -c "from styrene_core import get_rns_service; print('OK')"
   ```

2. **Test styrene-tui with new imports**
   ```bash
   cd styrene-tui
   python -m pytest tests/

   # Run TUI
   python -m styrene
   ```

3. **Test headless daemon from core**
   ```bash
   cd styrene-core
   python -m styrene_core.daemon --help
   ```

4. **Integration test: LXMF messaging**
   - Start daemon with auto-reply
   - Send message from NomadNet
   - Verify auto-reply received

### Phase 6: Documentation (2-3 hours)

1. **styrene-core README.md**
   - Installation instructions
   - Quick start examples
   - API reference (link to generated docs)
   - Usage patterns (minimal, daemon, discovery)

2. **styrene-tui README.md updates**
   - Update installation to include styrene-core
   - Note library separation in architecture docs
   - Update development setup

3. **API documentation**
   - Generate with Sphinx or mkdocs
   - Publish to ReadTheDocs or GitHub Pages

### Phase 7: Publication (1-2 hours)

1. **Publish styrene-core to PyPI (Test)**
   ```bash
   cd styrene-core
   python -m build
   python -m twine upload --repository testpypi dist/*
   ```

2. **Test installation from TestPyPI**
   ```bash
   pip install --index-url https://test.pypi.org/simple/ styrene-core
   ```

3. **Publish to production PyPI**
   ```bash
   python -m twine upload dist/*
   ```

4. **Update styrene-tui to use published package**
   ```toml
   dependencies = [
       "styrene-core>=0.1.0",  # Now from PyPI
       # ...
   ]
   ```

---

## Backwards Compatibility Considerations

### For styrene-tui Users

**No Breaking Changes**: Existing styrene-tui installations continue to work unchanged.

- All TUI screens, commands, and workflows remain identical
- Config file format unchanged (`~/.config/styrene/config.yaml`)
- Reticulum integration unchanged
- Fleet provisioning unchanged

**Migration Path**: When upgrading to the refactored version:

1. `pip install --upgrade styrene-tui` automatically pulls in styrene-core
2. No config changes required
3. No data migration required
4. Existing operator identity reused

### For Developers

**Import Changes**: If you've imported from styrene internals (not recommended, but possible):

**Before:**
```python
from styrene.services.rns_service import get_rns_service
from styrene.models.config import StyreneConfig
```

**After:**
```python
from styrene_core.services.rns_service import get_rns_service
from styrene.models.config import StyreneConfig  # TUI-specific config
from styrene_core.models.config import CoreConfig  # Library config
```

**Mitigation**: We can provide compatibility shims in styrene-tui:

```python
# styrene/services/__init__.py (compatibility layer)
import warnings
from styrene_core.services.rns_service import get_rns_service as _get_rns_service

def get_rns_service():
    warnings.warn(
        "Importing from styrene.services is deprecated. "
        "Use 'from styrene_core.services import get_rns_service' instead.",
        DeprecationWarning,
        stacklevel=2
    )
    return _get_rns_service()
```

---

## Example Usage: LXMF Claude Bot

**Use case**: A headless Claude chatbot that receives LXMF messages and responds with AI-generated answers.

### Implementation

```python
#!/usr/bin/env python3
"""LXMF Claude Bot - AI chatbot on Reticulum mesh."""

import asyncio
import logging
from anthropic import Anthropic
from styrene_core import (
    CoreConfig,
    DeploymentMode,
    PeerConfig,
    CoreLifecycle,
    get_lxmf_service,
    get_operator_identity_object,
    ensure_operator_identity,
)

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class ClaudeBot:
    """LXMF chatbot powered by Claude."""

    def __init__(self, api_key: str):
        self.client = Anthropic(api_key=api_key)
        self.lxmf = None
        self.identity = None

    def initialize(self) -> bool:
        """Initialize RNS/LXMF and register message handler."""
        # Create config for peer mode (connect to hub)
        config = CoreConfig()
        config.reticulum.mode = DeploymentMode.PEER
        config.reticulum.interfaces.peers.append(
            PeerConfig(host="brutus.vanderlyn.house", port=4242)
        )
        config.chat.enabled = True

        # Initialize Styrene core
        lifecycle = CoreLifecycle(config)
        if not lifecycle.initialize():
            logger.error("Failed to initialize Styrene core")
            return False

        # Get LXMF service and identity
        self.lxmf = get_lxmf_service()
        self.identity = get_operator_identity_object()

        if not self.identity:
            ensure_operator_identity()
            self.identity = get_operator_identity_object()

        # Initialize LXMF
        if not self.lxmf.initialize(self.identity):
            logger.error("Failed to initialize LXMF")
            return False

        # Register message handler
        self.lxmf.register_callback(self.handle_message)

        logger.info(f"Claude bot initialized: {self.identity.hexhash[:16]}...")
        return True

    def handle_message(self, source_hash: str, payload: dict) -> None:
        """Handle incoming LXMF message."""
        try:
            # Extract message content
            content = payload.get("content", "")
            if not content:
                return

            logger.info(f"Received from {source_hash[:16]}...: {content[:50]}")

            # Generate Claude response
            response = self.client.messages.create(
                model="claude-opus-4-5-20251101",
                max_tokens=1024,
                messages=[{
                    "role": "user",
                    "content": content
                }]
            )

            reply_text = response.content[0].text

            # Send reply via LXMF
            self.lxmf.send_message(
                destination_hash=source_hash,
                payload={"content": reply_text}
            )

            logger.info(f"Sent reply to {source_hash[:16]}...")

        except Exception as e:
            logger.error(f"Error handling message: {e}")


async def main():
    import os
    api_key = os.getenv("ANTHROPIC_API_KEY")
    if not api_key:
        logger.error("Set ANTHROPIC_API_KEY environment variable")
        return

    bot = ClaudeBot(api_key=api_key)
    if not bot.initialize():
        return

    # Run forever
    try:
        while True:
            await asyncio.sleep(60)
    except KeyboardInterrupt:
        logger.info("Shutting down...")


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

### Usage

```bash
# Install dependencies
pip install styrene-core anthropic

# Set API key
export ANTHROPIC_API_KEY="sk-ant-..."

# Run bot
python claude_bot.py
```

### How It Works

1. **Initialization**:
   - Configures styrene-core in PEER mode to connect to brutus hub
   - Ensures operator identity exists (~/.styrene/operator.key)
   - Initializes RNS and LXMF services

2. **Message Handling**:
   - Registers callback with LXMF service
   - When message arrives, extracts content
   - Sends content to Claude API
   - Returns Claude's response via LXMF

3. **Zero TUI Dependencies**:
   - No Textual import
   - No provisioning/fleet/hardware code
   - Pure messaging logic using styrene-core

### Key Benefits

- **Lightweight**: ~50 lines of bot code + styrene-core (no TUI bloat)
- **Reusable**: styrene-core handles all RNS/LXMF complexity
- **Maintainable**: Clear separation between bot logic and mesh networking
- **Composable**: Easy to add more bots with different behaviors

---

## Risk Assessment

### Low Risk

- **File moves**: Category A files are purely data/logic with no TUI coupling
- **Testing**: Both packages have comprehensive test coverage
- **Versioning**: Semantic versioning ensures compatibility

### Medium Risk

- **Config splitting**: Requires careful YAML serialization to handle inheritance
- **Import updates**: Requires thorough search/replace across styrene-tui
- **Lifecycle refactor**: Core vs TUI service initialization needs testing

### Mitigation

- **Incremental migration**: Move files in small batches, test after each
- **Integration tests**: Full end-to-end tests for TUI after each phase
- **Compatibility shims**: Deprecation warnings for old import paths
- **Documentation**: Clear migration guide for developers

---

## Success Criteria

### For styrene-core

- [ ] Package installs cleanly via pip: `pip install styrene-core`
- [ ] All core tests pass: `pytest tests/`
- [ ] Example bots work (Claude bot, echo bot, discovery scanner)
- [ ] Zero Textual dependencies in package
- [ ] API documentation published (ReadTheDocs or GitHub Pages)
- [ ] Published to PyPI

### For styrene-tui

- [ ] All existing TUI tests pass after refactor
- [ ] TUI starts and functions normally: `styrene`
- [ ] Headless mode works: `styrene --headless`
- [ ] Fleet provisioning unchanged
- [ ] No user-facing changes to config format or workflows

### For Library Users

- [ ] Quick start guide works in <5 minutes
- [ ] Example use cases documented and tested
- [ ] Import paths are intuitive and well-documented
- [ ] Error messages are helpful for common mistakes

---

## Timeline Estimate

| Phase | Hours | Description |
|-------|-------|-------------|
| 1. Repository setup | 1-2 | Create styrene-core, pyproject.toml |
| 2. Copy files | 2-3 | Move Category A files, update imports |
| 3. Split files | 4-6 | Split config, reticulum, lifecycle |
| 4. Update styrene-tui | 2-3 | Update imports, test integration |
| 5. Testing | 3-4 | Unit tests, integration tests, smoke tests |
| 6. Documentation | 2-3 | READMEs, API docs, examples |
| 7. Publication | 1-2 | Publish to PyPI, verify installation |

**Total: 15-23 hours** (2-3 days of focused work)

---

## Post-Separation Roadmap

### Short Term (v0.2.0)

- [ ] Publish styrene-core to PyPI
- [ ] Update styrene-tui to consume published package
- [ ] Create 3-5 example bots (echo, Claude, discovery, status monitor)
- [ ] Add API documentation site

### Medium Term (v0.3.0)

- [ ] Add HTTP API to styrene-core (FastAPI integration)
- [ ] Protocol plugin system (custom protocol handlers)
- [ ] Async message queue (persistent outbound messages)
- [ ] RPC framework abstraction (reusable RPC client/server)

### Long Term (v1.0.0)

- [ ] Web UI using styrene-core backend
- [ ] Mobile app support (via HTTP API)
- [ ] Multi-identity support (multiple operators per node)
- [ ] Federation protocol (inter-hub communication)

---

## Appendix: File Line Counts

Generated via `wc -l src/styrene/**/*.py`:

```
Services (6,956 total):
  rns_service.py: 364
  lxmf_service.py: 537
  reticulum.py: 840
  auto_reply.py: 298
  config.py: 777
  app_lifecycle.py: 382
  node_store.py: 545
  (TUI-specific services: ~3,213)

Models (3,460 total):
  config.py: 462
  rns_error.py: 211
  reticulum.py: 128
  mesh_device.py: 292
  messages.py: 166
  styrene_wire.py: 353
  (TUI-specific models: ~1,848)

Protocols (477 total):
  base.py: 141
  registry.py: 123
  chat.py: 89
  styrene.py: 124

Daemon: 389

Screens: ~2,500
Widgets: ~2,000
Themes: ~800
CLI: ~500
```

**Core extraction**: ~4,200 lines to styrene-core
**TUI remaining**: ~9,400 lines in styrene-tui

---

## Questions & Decisions

### Q: Should RPC server/client go in core or TUI?

**Decision**: Split approach:
- **Core**: `BaseRPCServer`, `BaseRPCClient` (abstract protocol layer)
- **TUI**: `FleetRPCServer`, `FleetRPCClient` (fleet-specific commands)

**Rationale**: RPC framework is reusable, but fleet management is TUI-specific.

### Q: How to handle config inheritance in YAML?

**Decision**: Nested YAML structure:

```yaml
# ~/.config/styrene/config.yaml (styrene-tui)
core:
  reticulum:
    mode: peer
  chat:
    enabled: true
tui:
  theme: mars
  log_level: info
fleet:
  edge_fleet_path: ~/edge-fleet
```

**Rationale**: Clear separation, easy to extract core subset.

### Q: Should daemon.py be executable or library?

**Decision**: Both. Provide `styrene-daemon` CLI entry point and importable `StyreneDaemon` class.

**Rationale**: Enables both standalone headless mode and embedding in other apps.

### Q: How to version styrene-core vs styrene-tui?

**Decision**: Independent versioning with minimum version constraint:

- `styrene-core`: 0.1.0, 0.2.0, 1.0.0 (follows semantic versioning)
- `styrene-tui`: 0.1.0 (requires `styrene-core>=0.1.0,<1.0.0`)

**Rationale**: Core can evolve independently, TUI pins compatible range.

---

## Conclusion

This separation creates a clean, reusable library foundation for RNS/LXMF applications while preserving styrene-tui's existing functionality. The migration is low-risk, well-scoped, and delivers immediate value through the Claude bot and other headless use cases.

**Next Step**: Begin Phase 1 (repository setup) and execute file migrations incrementally with testing at each stage.
