# NomadNet Integration Strategy

How to bundle NomadNet alongside Styrene for a complete mesh communications platform.

## Overview

NomadNet is a text-based mesh communication system built on Reticulum. It provides:
- **Pages**: Markdown/text content hosting
- **Messaging**: Person-to-person communication
- **Microblog**: Publish/subscribe updates
- **File Sharing**: Transfer files via LXMF

**Goal**: Bundle NomadNet with Styrene so users get both management (Styrene) and communication (NomadNet) in one package.

## Architecture Options

### Option 1: Side-by-Side Installation (Recommended)

Styrene and NomadNet run as separate processes, sharing the same Reticulum instance.

```
┌─────────────────────────────────┐
│    Shared RNS.Reticulum()       │
│    (Single instance, shared)    │
└────────┬────────────────────────┘
         │
    ┌────┴────┐
    │         │
┌───▼──┐  ┌──▼──────┐
│Styrene│  │NomadNet │
│  TUI  │  │   UI    │
└───────┘  └─────────┘
```

**Implementation**:
- Both packages installed: `pip install styrene nomadnet`
- Both use `share_instance = true` in RNS config
- Run separately: `styrene` in one terminal, `nomadnet` in another
- Or use tmux/screen to manage both

**Pros**:
- Clean separation of concerns
- Users can run either or both
- No code coupling
- Easy updates (independent packages)

**Cons**:
- Two separate commands to launch
- Requires terminal multiplexing for simultaneous use

### Option 2: Integrated Launcher

Styrene provides a launcher that starts both TUI and NomadNet.

```python
# In Styrene
def launch_nomadnet() -> None:
    """Launch NomadNet in separate process."""
    try:
        import subprocess
        import nomadnet

        # Find nomadnet executable
        nomadnet_path = shutil.which("nomadnet")
        if nomadnet_path:
            subprocess.Popen([nomadnet_path])
        else:
            logger.warning("NomadNet not installed")
    except ImportError:
        logger.warning("NomadNet not found (install with: pip install nomadnet)")
```

**Add to TUI**:
```python
# In Styrene key bindings
Binding("n", "launch_nomadnet", "NomadNet", show=True)
```

**Pros**:
- Single entry point for users
- Styrene can detect if NomadNet is installed
- Still separate processes (clean)

**Cons**:
- Requires terminal multiplexing to show both UIs
- May confuse users with two fullscreen TUIs

### Option 3: Embedded NomadNet Widget (Complex)

Embed NomadNet functionality as a Textual widget inside Styrene TUI.

```
┌─────────────────────────────────────┐
│         Styrene TUI                 │
│  ┌─────────┐  ┌─────────────────┐  │
│  │Dashboard│  │ NomadNet Widget │  │
│  ├─────────┤  ├─────────────────┤  │
│  │ Devices │  │ • Pages         │  │
│  │ Provision│  │ • Messages      │  │
│  │          │  │ • Microblog     │  │
│  └─────────┘  └─────────────────┘  │
└─────────────────────────────────────┘
```

**Pros**:
- Single unified UI
- Seamless user experience

**Cons**:
- Significant development effort
- Tight coupling with NomadNet codebase
- Maintenance burden (keep up with NomadNet changes)
- NomadNet UI is already well-designed for its purpose

## Recommended Approach: Option 1 + Launcher Helper

**Hybrid strategy**:

1. **Recommend side-by-side installation** (primary workflow)
2. **Provide launcher convenience** (optional helper)
3. **Document tmux/screen setup** (power users)

### Installation

```toml
# pyproject.toml
[project.optional-dependencies]
nomadnet = [
    "nomadnet>=0.4.0",
]
```

```bash
# Install Styrene with NomadNet
pip install styrene[nomadnet]

# Or separately
pip install styrene
pip install nomadnet
```

### Launcher Script

Create `styrene-with-nomadnet` command:

```bash
#!/bin/bash
# Launch Styrene and NomadNet in tmux

# Check if nomadnet is installed
if ! command -v nomadnet &> /dev/null; then
    echo "NomadNet not installed. Install with: pip install nomadnet"
    exit 1
fi

# Check if tmux is available
if ! command -v tmux &> /dev/null; then
    echo "tmux not installed. Install with: apt install tmux (or brew install tmux)"
    exit 1
fi

# Create new tmux session with two panes
tmux new-session -d -s styrene-mesh 'styrene'
tmux split-window -h 'nomadnet'
tmux select-layout even-horizontal
tmux attach-session -t styrene-mesh
```

**Add to pyproject.toml**:
```toml
[project.scripts]
styrene = "styrene.__main__:main"
styrene-with-nomadnet = "styrene.scripts.launch_with_nomadnet:main"
```

### In-TUI Helper

Add binding to launch NomadNet from within Styrene:

```python
# In DashboardScreen
Binding("n", "launch_nomadnet", "Launch NomadNet", show=True)

def action_launch_nomadnet(self) -> None:
    """Launch NomadNet in a new terminal."""
    import subprocess
    import shutil

    nomadnet_path = shutil.which("nomadnet")

    if nomadnet_path:
        try:
            # Detect terminal emulator
            if shutil.which("x-terminal-emulator"):
                subprocess.Popen(["x-terminal-emulator", "-e", "nomadnet"])
            elif shutil.which("gnome-terminal"):
                subprocess.Popen(["gnome-terminal", "--", "nomadnet"])
            elif shutil.which("konsole"):
                subprocess.Popen(["konsole", "-e", "nomadnet"])
            elif shutil.which("alacritty"):
                subprocess.Popen(["alacritty", "-e", "nomadnet"])
            else:
                self.notify("Could not detect terminal emulator", severity="warning")
        except Exception as e:
            self.notify(f"Failed to launch NomadNet: {e}", severity="error")
    else:
        self.notify(
            "NomadNet not installed. Install with: pip install nomadnet",
            severity="warning"
        )
```

## Shared Configuration

Both Styrene and NomadNet use the same Reticulum instance.

**Reticulum config** (`~/.config/reticulum/config`):

```ini
[reticulum]
enable_transport = true   # If running as hub
share_instance = true     # CRITICAL: Allows both to share RNS

[interfaces]
[[AutoInterface]]
type = AutoInterface
enabled = true
```

**NomadNet config** (`~/.config/nomadnet/config`):

```ini
[node]
enable_node = true
node_name = My Styrene Node

[textui]
# NomadNet UI settings
```

**Styrene config** (`~/.config/styrene/config.yaml`):

```yaml
reticulum:
  mode: hub  # or standalone/peer
  interfaces:
    auto: true
    server:
      enabled: true  # If hub mode
```

Both read from the same `~/.config/reticulum/` directory, so configuration is centralized.

## User Workflow

### Basic (Side-by-Side)

```bash
# Terminal 1: Management
styrene

# Terminal 2: Communication
nomadnet
```

### Convenience (Launcher)

```bash
# Launches both in tmux
styrene-with-nomadnet
```

Then use tmux bindings to switch panes:
- `Ctrl-b →` - Switch to right pane (NomadNet)
- `Ctrl-b ←` - Switch to left pane (Styrene)
- `Ctrl-b d` - Detach (leave running)

### Power Users (Custom tmux/screen)

Users can create their own layouts:

```bash
# ~/.tmux-styrene.conf
new-session -s mesh -n main
split-window -h
send-keys -t 0 'styrene' C-m
send-keys -t 1 'nomadnet' C-m
```

Then: `tmux source-file ~/.tmux-styrene.conf`

## Documentation

Add to `README.md`:

### Communication with NomadNet

Styrene focuses on fleet management. For mesh communication (messaging, pages, microblog), install **NomadNet**:

```bash
pip install nomadnet
```

Both Styrene and NomadNet share the same Reticulum instance, giving you:
- **Styrene**: Device provisioning, fleet status, remote management
- **NomadNet**: Messaging, bulletin boards, file sharing

**Launch both**:
```bash
styrene-with-nomadnet  # Convenience launcher (requires tmux)
```

Or run separately:
```bash
# Terminal 1
styrene

# Terminal 2
nomadnet
```

See the [NomadNet docs](https://github.com/markqvist/nomadnet) for usage.

## Testing

Verify both can coexist:

```python
#!/usr/bin/env python3
"""Test Styrene + NomadNet coexistence."""

import time
import subprocess

# Start Styrene in background
styrene_proc = subprocess.Popen(["styrene", "--headless"])
time.sleep(2)

# Start NomadNet (daemon mode if available)
nomadnet_proc = subprocess.Popen(["nomadnet", "--daemon"])
time.sleep(2)

# Check both are running
print(f"Styrene PID: {styrene_proc.pid}")
print(f"NomadNet PID: {nomadnet_proc.pid}")

# Verify RNS shows both destinations
# (would require RNS API access)

# Cleanup
styrene_proc.terminate()
nomadnet_proc.terminate()
```

## Future: Integrated Communication Panel

If demand is high, Phase 3 could add a lightweight communication panel to Styrene:

```
┌─────────────────────────────────────┐
│  Styrene TUI                        │
│  ┌─────────┐  ┌─────────────────┐  │
│  │Dashboard│  │ LXMF Messages   │  │
│  ├─────────┤  ├─────────────────┤  │
│  │ Devices │  │ bob@xyz: Hi     │  │
│  │ Mesh    │  │ alice@abc: ...  │  │
│  └─────────┘  └─────────────────┘  │
└─────────────────────────────────────┘
```

But this is **not necessary**—NomadNet already does this excellently. Better to integrate at the launcher level than duplicate functionality.

## Summary

| Approach | Pros | Cons | Recommendation |
|----------|------|------|----------------|
| **Side-by-side** | Clean separation, easy updates | Two commands | ✅ Primary |
| **Launcher helper** | Convenience, single entry point | Requires tmux/screen | ✅ Optional |
| **Embedded widget** | Unified UI | High maintenance, coupling | ❌ Not recommended |

**Recommendation**: Ship Styrene with optional NomadNet integration via launcher helper. Document side-by-side usage as primary workflow. Let users choose their preferred setup.
