e2e/CLI_DESIGN
Tuesday 24 February 2026

Mau E2E Interactive CLI - Design Document

Version: 1.0
Date: 19 February 2026
Critical Requirement: Interactive CLI for manual control and exploratory testing


Executive Summary

The Mau E2E framework provides two modes of operation:

  1. Automated Testing Mode: Run via go test for CI/CD (as described in PLAN.md)
  2. Interactive Mode: CLI-driven test environment for manual exploration, debugging, and demonstrations

Key Insight: Developers need to see P2P synchronization happening, not just assert it worked in tests. The interactive CLI makes the invisible visible.


Architecture: Dual-Mode Design

┌─────────────────────────────────────────────────────────────┐
│                    Mau E2E Framework                        │
│                                                             │
│  ┌──────────────────┐         ┌──────────────────┐         │
│  │  Automated Mode  │         │ Interactive Mode │         │
│  │                  │         │                  │         │
│  │  - go test       │         │  - mau-e2e CLI   │         │
│  │  - CI/CD         │         │  - Docker Compose│         │
│  │  - Assertions    │         │  - Live Control  │         │
│  └────────┬─────────┘         └────────┬─────────┘         │
│           │                            │                   │
│           └──────────┬─────────────────┘                   │
│                      │                                     │
│              ┌───────▼────────┐                            │
│              │  Shared Core   │                            │
│              │  - testenv lib │                            │
│              │  - peer mgmt   │                            │
│              │  - assertions  │                            │
│              └────────────────┘                            │
└─────────────────────────────────────────────────────────────┘

Shared Core: Both modes use the same underlying testenv library, ensuring consistency.


Interactive CLI: mau-e2e

Installation

1cd e2e
2go install ./cmd/mau-e2e
3mau-e2e --help

Command Structure

mau-e2e <command> [options]

ENVIRONMENT MANAGEMENT:
  up          Start a new test environment
  down        Tear down the environment
  status      Show environment status
  logs        Stream logs from peers

PEER MANAGEMENT:
  peer add <name>              Add a new peer
  peer list                    List all peers
  peer rm <name>               Remove a peer
  peer inspect <name>          Show detailed peer state
  peer restart <name>          Restart a peer
  peer exec <name> <cmd>       Execute command in peer container

FRIEND RELATIONSHIPS:
  friend add <peer1> <peer2>   Make peer1 and peer2 friends
  friend list <peer>           Show peer's friend list
  friend rm <peer1> <peer2>    Remove friendship

FILE OPERATIONS:
  file add <peer> <path>       Add file to peer
  file list <peer>             List peer's files
  file get <peer> <file>       Download file from peer
  file cat <peer> <file>       Show decrypted file content
  file rm <peer> <file>        Remove file from peer
  file watch                   Watch file sync events in real-time

DHT OPERATIONS:
  dht lookup <peer> <target>   Lookup target fingerprint from peer
  dht table <peer>             Show peer's routing table
  dht bootstrap <peer>         Trigger bootstrap on peer

NETWORK SIMULATION:
  net partition <group1> <group2>   Create network partition
  net heal                          Heal all partitions
  net latency <peer> <ms>           Add latency to peer
  net limit <peer> <kb/s>           Limit peer bandwidth
  net reset                         Remove all network toxics

UTILITIES:
  snapshot <dir>               Capture full environment snapshot
  restore <snapshot-dir>       Restore environment from snapshot
  scenario <name>              Run predefined scenario
  shell                        Interactive shell mode

Example Workflows

Workflow 1: Basic Sync Exploration

 1# 1. Start environment with 2 peers
 2$ mau-e2e up --peers 2
 3✓ Created Docker network: mau-test-7a3f
 4✓ Started bootstrap node
 5✓ Started peer alice (fingerprint: ABAF...)
 6✓ Started peer bob (fingerprint: BBCF...)
 7Environment ready. Use 'mau-e2e shell' for interactive mode.
 8
 9# 2. Make them friends
10$ mau-e2e friend add alice bob
11✓ Exchanged public keys
12✓ alice added bob to keyring
13✓ bob added alice to keyring
14Friendship established.
15
16# 3. Add a file to alice
17$ mau-e2e file add alice hello.txt
18Content (Ctrl-D to finish):
19Hello from Alice!
20^D
21✓ File encrypted for [bob]
22✓ File created: alice/hello.txt.pgp
23File SHA256: a3f8b2c...
24
25# 4. Watch sync in real-time
26$ mau-e2e file watch
27[14:30:02] bob: Polling alice for updates...
28[14:30:03] bob: Found new file: hello.txt (1.2 KB)
29[14:30:03] bob: Downloading alice/hello.txt...
30[14:30:04] bob: Download complete, verifying signature...
31[14:30:04] bob: Signature valid, decrypting...
32[14:30:04] bob: ✓ Sync complete: hello.txt
33
34# 5. Verify bob has the file
35$ mau-e2e file cat bob hello.txt
36Hello from Alice!
37
38# 6. Check detailed state
39$ mau-e2e peer inspect bob
40Peer: bob
41Fingerprint: BBCF11C65A2970B130ABE3C479BE3E4300411887
42Status: running
43Uptime: 2m 34s
44Friends: 1 (alice)
45Files: 1 local, 0 pending sync
46DHT peers: 2
47Recent activity:
48  - 10s ago: Downloaded hello.txt from alice
49  - 2m ago: Added friend alice
50  - 2m ago: Joined DHT
51
52# 7. Tear down when done
53$ mau-e2e down
54✓ Stopped 2 peers
55✓ Removed network
56Environment cleaned up.

Workflow 2: Network Partition Simulation

 1# Start 4 peers in two groups
 2$ mau-e2e up --peers 4
 3✓ Started peers: alice, bob, carol, dave
 4
 5# Make friends: alice-bob, carol-dave
 6$ mau-e2e friend add alice bob
 7$ mau-e2e friend add carol dave
 8
 9# Create network partition: {alice, bob} vs {carol, dave}
10$ mau-e2e net partition alice,bob carol,dave
11✓ Created partition via Toxiproxy
12✓ alice and bob isolated from carol and dave
13
14# Add files on both sides
15$ mau-e2e file add alice fileA.txt <<< "From Group 1"
16$ mau-e2e file add carol fileC.txt <<< "From Group 2"
17
18# Wait a bit, then check sync status
19$ sleep 10
20$ mau-e2e file list bob
21Files on bob:
22  - fileA.txt (synced from alice)23
24$ mau-e2e file list dave
25Files on dave:
26  - fileC.txt (synced from carol)27
28# Heal partition
29$ mau-e2e net heal
30✓ Partition healed
31
32# Watch convergence (if they're friends)
33$ mau-e2e file watch
34# (No sync because alice-carol are not friends)
35
36# Make cross-group friendship
37$ mau-e2e friend add alice carol
38[14:35:12] alice: Polling carol for updates...
39[14:35:13] alice: Found new file: fileC.txt
40[14:35:14] carol: Found new file: fileA.txt
41# Files sync across partition boundary

Workflow 3: Interactive Shell Mode

 1$ mau-e2e shell
 2Welcome to Mau E2E Interactive Shell
 3Type 'help' for commands, 'exit' to quit
 4
 5mau> up --peers 3
 6✓ Started peers: peer-0, peer-1, peer-2
 7
 8mau> peer list
 9NAME     STATUS    FINGERPRINT                                  FRIENDS  FILES
10peer-0   running   ABAF11C65A2970B130ABE3C479BE3E4300411886   0        0
11peer-1   running   BBCF11C65A2970B130ABE3C479BE3E4300411887   0        0
12peer-2   running   CCDF11C65A2970B130ABE3C479BE3E4300411888   0        0
13
14mau> friend add peer-0 peer-1
15✓ Friendship established
16
17mau> friend add peer-1 peer-2
18✓ Friendship established
19
20mau> file add peer-0 test.txt
21Content: This is a test file
22✓ File created
23
24mau> file watch &
25# Background file watcher started
26
27mau> # Wait for sync...
28[14:40:05] peer-1: Sync complete: test.txt
29
30mau> file cat peer-1 test.txt
31This is a test file
32
33mau> peer add peer-3
34✓ Started peer peer-3
35
36mau> friend add peer-2 peer-3
37✓ Friendship established
38[14:41:10] peer-3: Sync complete: test.txt (via peer-2)
39
40mau> snapshot demo-snapshot
41✓ Captured snapshot to demo-snapshot/
42  - 4 peer states
43  - Network topology
44  - Logs
45
46mau> exit
47Environment still running. Tear down? [y/N]: y
48✓ Cleaned up

Workflow 4: Chaos Testing Demo

 1# Start 5-peer mesh
 2$ mau-e2e scenario 5-peer-mesh
 3✓ Started 5 peers with full mesh friendship
 4✓ Injected 10 test files
 5
 6# Add random latency to all peers (100-300ms)
 7$ mau-e2e net latency --all --random 100-300
 8✓ Applied latency toxic to 5 peers
 9
10# Start monitoring sync
11$ mau-e2e file watch &
12
13# Kill random peer every 30s
14$ mau-e2e scenario chaos-kill --interval 30s
15[14:45:00] Killing peer-2...
16[14:45:30] Killing peer-4...
17[14:46:00] Killing peer-1...
18# Sync continues via remaining peers
19
20# Check sync health
21$ mau-e2e status
22Environment: mau-test-7a3f
23Peers: 5 (2 running, 3 stopped)
24Sync status: 8/10 files synced to all running peers
25Network health: Degraded (latency toxics active)
26
27# Restart all stopped peers
28$ mau-e2e peer restart --all
29✓ Restarted 3 peers
30
31# Remove network chaos
32$ mau-e2e net reset
33✓ Removed all toxics
34
35# Verify eventual consistency
36$ sleep 30
37$ mau-e2e file list --all
38All 5 peers have 10 files ✓

Implementation Details

File Structure

e2e/
├── cmd/
│   └── mau-e2e/               # CLI tool
│       ├── main.go
│       ├── commands/
│       │   ├── up.go          # Environment creation
│       │   ├── peer.go        # Peer management
│       │   ├── friend.go      # Friend operations
│       │   ├── file.go        # File operations
│       │   ├── dht.go         # DHT commands
│       │   ├── net.go         # Network simulation
│       │   ├── scenario.go    # Predefined scenarios
│       │   └── shell.go       # Interactive shell
│       └── ui/
│           ├── table.go       # Table formatting
│           ├── progress.go    # Progress bars
│           └── colors.go      # Color output
│
├── framework/
│   ├── testenv/
│   │   ├── environment.go     # Environment lifecycle
│   │   ├── state.go           # Persistent state (for CLI)
│   │   └── api.go             # HTTP API for peer control
│   └── ...
│
├── scenarios/                 # Predefined scenarios
│   ├── 5-peer-mesh.json
│   ├── chaos-kill.json
│   └── partition-test.json
│
└── docker/
    ├── docker-compose.yml     # Base compose file
    └── docker-compose.override.example.yml

State Persistence

The CLI needs to remember environment state between commands:

State File: ~/.mau-e2e/current-env.json

 1{
 2  "env_id": "mau-test-7a3f",
 3  "created_at": "2026-02-19T14:30:00Z",
 4  "network": "mau-test-7a3f",
 5  "bootstrap_node": "bootstrap-7a3f",
 6  "peers": [
 7    {
 8      "name": "alice",
 9      "container_id": "a3f8b2c...",
10      "fingerprint": "ABAF11C65A2970B130ABE3C479BE3E4300411886",
11      "port": 8001,
12      "status": "running"
13    },
14    {
15      "name": "bob",
16      "container_id": "b4e9c3d...",
17      "fingerprint": "BBCF11C65A2970B130ABE3C479BE3E4300411887",
18      "port": 8002,
19      "status": "running"
20    }
21  ],
22  "friendships": [
23    ["alice", "bob"]
24  ],
25  "toxiproxy": {
26    "enabled": true,
27    "proxies": {
28      "alice": "proxy-alice-7a3f",
29      "bob": "proxy-bob-7a3f"
30    }
31  }
32}

Operations:

  • mau-e2e up → Creates state file
  • All commands read state to find containers
  • mau-e2e down → Deletes state file
  • Multiple environments: --env <name> flag

Docker Compose Integration

File: e2e/docker/docker-compose.yml

 1version: '3.8'
 2
 3services:
 4  bootstrap:
 5    image: mau-e2e:latest
 6    container_name: bootstrap-${ENV_ID:-default}
 7    networks:
 8      - mau-test
 9    environment:
10      - MAU_MODE=bootstrap
11      - MAU_LOG_LEVEL=info
12    healthcheck:
13      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
14      interval: 5s
15      timeout: 3s
16      retries: 5
17
18  toxiproxy:
19    image: ghcr.io/shopify/toxiproxy:latest
20    container_name: toxiproxy-${ENV_ID:-default}
21    networks:
22      - mau-test
23    ports:
24      - "8474:8474"  # API
25      - "8000-8100:8000-8100"  # Peer ports
26
27networks:
28  mau-test:
29    name: mau-test-${ENV_ID:-default}
30    driver: bridge

Dynamic Peer Addition:

The CLI uses docker run to add peers dynamically:

1docker run -d \
2  --name alice-7a3f \
3  --network mau-test-7a3f \
4  -e MAU_PEER_NAME=alice \
5  -e MAU_BOOTSTRAP=bootstrap-7a3f:8080 \
6  -l mau-e2e-env=7a3f \
7  mau-e2e:latest

Why not pure Docker Compose?

  • Compose requires predefining all services
  • CLI needs dynamic peer count
  • Hybrid approach: Compose for infra (bootstrap, toxiproxy), Docker API for peers

CLI Implementation: Key Commands

mau-e2e up

 1package commands
 2
 3import (
 4    "github.com/spf13/cobra"
 5    "github.com/mau-network/mau/e2e/framework/testenv"
 6)
 7
 8var upCmd = &cobra.Command{
 9    Use:   "up",
10    Short: "Start a new test environment",
11    RunE: func(cmd *cobra.Command, args []string) error {
12        peers, _ := cmd.Flags().GetInt("peers")
13        envID := generateEnvID()
14        
15        // Create environment
16        env := testenv.NewEnvironment(envID)
17        
18        // Start bootstrap node
19        if err := env.StartBootstrap(); err != nil {
20            return err
21        }
22        
23        // Start Toxiproxy
24        if err := env.StartToxiproxy(); err != nil {
25            return err
26        }
27        
28        // Start N peers
29        for i := 0; i < peers; i++ {
30            name := generatePeerName(i)
31            peer, err := env.AddPeer(name)
32            if err != nil {
33                return err
34            }
35            fmt.Printf("✓ Started peer %s (fingerprint: %s)\n", 
36                peer.Name, peer.Fingerprint[:8]+"...")
37        }
38        
39        // Save state
40        if err := env.SaveState(); err != nil {
41            return err
42        }
43        
44        fmt.Println("Environment ready.")
45        return nil
46    },
47}
48
49func init() {
50    upCmd.Flags().IntP("peers", "n", 2, "Number of peers to start")
51    upCmd.Flags().Bool("no-toxiproxy", false, "Disable Toxiproxy")
52    upCmd.Flags().StringP("name", "N", "", "Custom environment name")
53}

mau-e2e friend add

 1var friendAddCmd = &cobra.Command{
 2    Use:   "add <peer1> <peer2>",
 3    Short: "Make two peers friends",
 4    Args:  cobra.ExactArgs(2),
 5    RunE: func(cmd *cobra.Command, args []string) error {
 6        env, err := testenv.LoadCurrentEnvironment()
 7        if err != nil {
 8            return err
 9        }
10        
11        peer1 := env.GetPeer(args[0])
12        peer2 := env.GetPeer(args[1])
13        
14        // Exchange public keys
15        fmt.Print("Exchanging public keys... ")
16        key1, _ := peer1.GetPublicKey()
17        key2, _ := peer2.GetPublicKey()
18        
19        // Add to each other's keyrings
20        peer1.AddFriend(peer2.Fingerprint, key2)
21        peer2.AddFriend(peer1.Fingerprint, key1)
22        
23        fmt.Println("✓")
24        fmt.Printf("Friendship established: %s ↔ %s\n", args[0], args[1])
25        
26        // Update state
27        env.AddFriendship(args[0], args[1])
28        env.SaveState()
29        
30        return nil
31    },
32}

mau-e2e file watch

 1var fileWatchCmd = &cobra.Command{
 2    Use:   "watch",
 3    Short: "Watch file sync events in real-time",
 4    RunE: func(cmd *cobra.Command, args []string) error {
 5        env, err := testenv.LoadCurrentEnvironment()
 6        if err != nil {
 7            return err
 8        }
 9        
10        // Stream logs from all peers, filter for sync events
11        streams := make([]io.Reader, len(env.Peers))
12        for i, peer := range env.Peers {
13            streams[i] = peer.StreamLogs()
14        }
15        
16        // Multiplex streams
17        combined := io.MultiReader(streams...)
18        scanner := bufio.NewScanner(combined)
19        
20        for scanner.Scan() {
21            line := scanner.Text()
22            
23            // Parse JSON log
24            var log LogEntry
25            json.Unmarshal([]byte(line), &log)
26            
27            // Filter for sync events
28            if log.Component == "sync" {
29                printSyncEvent(log)
30            }
31        }
32        
33        return nil
34    },
35}
36
37func printSyncEvent(log LogEntry) {
38    timestamp := log.Timestamp.Format("15:04:05")
39    
40    switch log.Event {
41    case "file_download_started":
42        fmt.Printf("[%s] %s: Downloading %s from %s...\n",
43            timestamp, log.PeerID, log.File, log.SourcePeer)
44    case "file_download_completed":
45        fmt.Printf("[%s] %s: ✓ Sync complete: %s\n",
46            timestamp, log.PeerID, log.File)
47    case "file_verification_failed":
48        fmt.Printf("[%s] %s: ✗ Verification failed: %s\n",
49            timestamp, log.PeerID, log.File)
50    }
51}

mau-e2e net partition

 1var netPartitionCmd = &cobra.Command{
 2    Use:   "partition <group1> <group2>",
 3    Short: "Create network partition",
 4    Args:  cobra.ExactArgs(2),
 5    RunE: func(cmd *cobra.Command, args []string) error {
 6        env, _ := testenv.LoadCurrentEnvironment()
 7        
 8        group1 := strings.Split(args[0], ",")
 9        group2 := strings.Split(args[1], ",")
10        
11        // For each peer in group1, block traffic to group2
12        for _, p1 := range group1 {
13            peer := env.GetPeer(p1)
14            proxy := env.GetToxiproxy(p1)
15            
16            for _, p2 := range group2 {
17                targetPeer := env.GetPeer(p2)
18                
19                // Add "timeout" toxic for traffic to p2
20                proxy.AddToxic(ToxicConfig{
21                    Name:       fmt.Sprintf("partition-%s-%s", p1, p2),
22                    Type:       "timeout",
23                    Stream:     "downstream",
24                    Toxicity:   1.0,
25                    Attributes: map[string]int{"timeout": 0},
26                    Match:      targetPeer.IP,
27                })
28            }
29        }
30        
31        fmt.Printf("✓ Created partition: {%s} vs {%s}\n", 
32            strings.Join(group1, ","), strings.Join(group2, ","))
33        
34        env.SavePartitionState(group1, group2)
35        return nil
36    },
37}

Interactive Shell Implementation

Using: github.com/c-bata/go-prompt

 1package commands
 2
 3import (
 4    "github.com/c-bata/go-prompt"
 5    "github.com/spf13/cobra"
 6)
 7
 8var shellCmd = &cobra.Command{
 9    Use:   "shell",
10    Short: "Interactive shell mode",
11    RunE: func(cmd *cobra.Command, args []string) error {
12        p := prompt.New(
13            executor,
14            completer,
15            prompt.OptionPrefix("mau> "),
16            prompt.OptionTitle("Mau E2E Shell"),
17        )
18        p.Run()
19        return nil
20    },
21}
22
23func executor(line string) {
24    // Parse line as command
25    args := strings.Fields(line)
26    if len(args) == 0 {
27        return
28    }
29    
30    // Execute via cobra
31    rootCmd.SetArgs(args)
32    rootCmd.Execute()
33}
34
35func completer(d prompt.Document) []prompt.Suggest {
36    s := []prompt.Suggest{
37        {Text: "up", Description: "Start environment"},
38        {Text: "down", Description: "Tear down environment"},
39        {Text: "peer", Description: "Peer management"},
40        {Text: "friend", Description: "Friend operations"},
41        {Text: "file", Description: "File operations"},
42        {Text: "net", Description: "Network simulation"},
43        {Text: "status", Description: "Show status"},
44        {Text: "exit", Description: "Exit shell"},
45    }
46    return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
47}

Predefined Scenarios

File: e2e/scenarios/5-peer-mesh.json

 1{
 2  "name": "5-peer-mesh",
 3  "description": "5 peers in full mesh topology",
 4  "steps": [
 5    {
 6      "action": "create_peers",
 7      "count": 5,
 8      "names": ["alice", "bob", "carol", "dave", "eve"]
 9    },
10    {
11      "action": "full_mesh_friends",
12      "peers": ["alice", "bob", "carol", "dave", "eve"]
13    },
14    {
15      "action": "inject_files",
16      "peer": "alice",
17      "files": [
18        {"name": "file1.txt", "content": "Test file 1"},
19        {"name": "file2.txt", "content": "Test file 2"}
20      ]
21    },
22    {
23      "action": "wait",
24      "duration": "30s",
25      "condition": "all_files_synced"
26    }
27  ]
28}

Usage:

1$ mau-e2e scenario 5-peer-mesh
2✓ Created 5 peers
3✓ Established 10 friendships (full mesh)
4✓ Injected 2 files into alice
5⏳ Waiting for sync (max 30s)...
6✓ All peers have 2 files
7Scenario complete in 12.3s

UI/UX Enhancements

Progress Bars

1$ mau-e2e file add alice large-file.bin < /dev/urandom | head -c 100M
2Encrypting file... ████████████████████ 100% (100 MB)
3Uploading to alice... ████████████████████ 100% (102 MB encrypted)
4✓ File added: large-file.bin

Real-time Tables

 1$ mau-e2e status --watch
 2Environment: mau-test-7a3f                         Uptime: 5m 23s
 3
 4PEERS (5)
 5NAME     STATUS    FILES   FRIENDS   DHT PEERS   CPU    MEMORY
 6alice    running   10/10   4         4           2.1%   45 MB
 7bob      running   10/10   4         4           1.8%   43 MB
 8carol    running   8/10    4         4           3.2%   48 MB  ⚠ Syncing
 9dave     stopped   -       -         -           -      -      ✗
10eve      running   10/10   4         3           1.5%   42 MB
11
12NETWORK
13Toxiproxy: enabled
14Active Toxics: latency (alice: 120ms, bob: 98ms)
15
16RECENT ACTIVITY (last 30s)
1714:52:10  carol   Downloaded file8.txt from alice
1814:52:15  carol   Downloaded file9.txt from bob
1914:52:18  dave    Peer stopped (manual)
20
21Press Ctrl-C to exit watch mode

Color-Coded Output

1$ mau-e2e file list alice
2Files on alice (10):
3  ✓ file1.txt        (synced)       1.2 KB
4  ✓ file2.txt        (synced)       3.4 KB
5  ⏳ file3.txt       (syncing)      5.6 KB  [████░░░░] 50%
6  ✗ file4.txt        (failed)       2.1 KB  Signature invalid
7  ✓ file5.txt        (synced)       8.9 KB

Integration with Automated Tests

Shared Testenv Library:

 1// e2e/framework/testenv/environment.go
 2
 3type Environment struct {
 4    ID       string
 5    Network  *DockerNetwork
 6    Peers    []*MauPeer
 7    state    *State
 8}
 9
10// Used by CLI
11func NewEnvironment(id string) *Environment {
12    return &Environment{
13        ID:    id,
14        state: NewState(id),
15    }
16}
17
18// Used by go test
19func NewTestEnvironment(t *testing.T) *Environment {
20    id := fmt.Sprintf("test-%s", t.Name())
21    env := NewEnvironment(id)
22    
23    // Auto-cleanup on test end
24    t.Cleanup(func() {
25        env.Cleanup()
26    })
27    
28    return env
29}

Example Test Using CLI-Compatible Env:

 1func TestFileSync(t *testing.T) {
 2    env := testenv.NewTestEnvironment(t)
 3    
 4    // Same API as CLI uses
 5    alice, _ := env.AddPeer("alice")
 6    bob, _ := env.AddPeer("bob")
 7    env.MakeFriends(alice, bob)
 8    
 9    alice.AddFile("test.txt", strings.NewReader("content"))
10    
11    // Automated assertion
12    assert.Eventually(t, func() bool {
13        return bob.HasFile("test.txt")
14    }, 30*time.Second, 1*time.Second)
15}

Benefit: Tests can be converted to CLI scenarios and vice versa.


Documentation Structure

Quick Start Guide

File: e2e/README.md

 1# Mau E2E Framework
 2
 3## Quick Start
 4
 5### Interactive Mode (Recommended for exploration)
 6
 7```bash
 8# Install CLI
 9cd e2e
10go install ./cmd/mau-e2e
11
12# Start environment
13mau-e2e up --peers 3
14
15# Make friends
16mau-e2e friend add peer-0 peer-1
17
18# Add file
19mau-e2e file add peer-0 test.txt
20# (Enter content, Ctrl-D to finish)
21
22# Watch sync
23mau-e2e file watch
24
25# Inspect state
26mau-e2e peer inspect peer-1
27
28# Clean up
29mau-e2e down

Automated Testing Mode

1# Run all tests
2make test-e2e
3
4# Run specific suite
5go test ./scenarios/basic/...
6
7# Run in CI
8make test-e2e-ci

Learn More


---

## Implementation Roadmap

### Phase 1: CLI Foundation (Week 1)
- [ ] Basic `mau-e2e` CLI structure (cobra)
- [ ] `up` command (start environment)
- [ ] `down` command (cleanup)
- [ ] `peer add/list` commands
- [ ] State persistence (`~/.mau-e2e/`)
- [ ] Docker Compose base setup

**Deliverable:** Can start/stop environment with N peers

---

### Phase 2: Peer Interaction (Week 2)
- [ ] `friend add/list` commands
- [ ] `file add/list/cat` commands
- [ ] Peer inspection (`peer inspect`)
- [ ] Log streaming (`logs` command)

**Deliverable:** Can manually test 2-peer sync

---

### Phase 3: Real-time Monitoring (Week 3)
- [ ] `file watch` command (real-time sync events)
- [ ] `status --watch` command (live dashboard)
- [ ] Color-coded output
- [ ] Progress bars for long operations

**Deliverable:** Can observe sync happening live

---

### Phase 4: Network Simulation (Week 4)
- [ ] Toxiproxy integration
- [ ] `net partition/heal` commands
- [ ] `net latency/limit` commands
- [ ] Partition state management

**Deliverable:** Can create network partitions interactively

---

### Phase 5: Advanced Features (Week 5)
- [ ] Interactive shell mode
- [ ] Predefined scenarios (`scenario` command)
- [ ] Snapshot/restore functionality
- [ ] DHT commands (`dht lookup/table`)

**Deliverable:** Full-featured interactive environment

---

### Phase 6: Integration & Polish (Week 6)
- [ ] Integrate with automated tests (shared `testenv`)
- [ ] Documentation (guides, examples)
- [ ] Video tutorial (screencast)
- [ ] CI integration (ensure compatibility)

**Deliverable:** Production-ready CLI + tests

---

## Success Criteria

After implementation:

✅ **Emad can spin up 5 Mau instances with one command**  
✅ **Can manually trigger friend relationships via CLI**  
✅ **Can inject files and watch them sync in real-time**  
✅ **Can inspect any peer's state at any time**  
✅ **Can simulate network failures interactively**  
✅ **Can capture snapshots for later analysis**  
✅ **Same environment code powers both CLI and automated tests**  
✅ **New developers can explore Mau P2P behavior without reading code**  

---

## Example Demo Script (For Video Tutorial)

```bash
# Demo: Mau P2P File Sync in Action

# 1. Start environment
$ mau-e2e up --peers 3
# (Shows progress bars, peer fingerprints)

# 2. Enter shell mode
$ mau-e2e shell

mau> peer list
# (Shows 3 peers, all running)

# 3. Create friend triangle: A-B, B-C, C-A
mau> friend add peer-0 peer-1
mau> friend add peer-1 peer-2
mau> friend add peer-2 peer-0

# 4. Start watching sync events
mau> file watch &

# 5. Add file to peer-0
mau> file add peer-0 secret.txt
Content (Ctrl-D to finish):
This is a secret message!
^D
# (Watch shows sync events appearing)

# 6. Verify all peers have it
mau> file list --all
# (Table shows all 3 peers have secret.txt)

# 7. Create network partition
mau> net partition peer-0 peer-1,peer-2
# (Watch shows sync stops)

# 8. Add files on both sides
mau> file add peer-0 isolated.txt <<< "Only peer-0 sees this"
mau> file add peer-1 shared.txt <<< "Peers 1 and 2 see this"
# (Watch confirms no cross-partition sync)

# 9. Heal partition
mau> net heal
# (Watch shows files flooding across)

# 10. Verify eventual consistency
mau> sleep 10
mau> file list --all
# (All peers have all files)

mau> snapshot demo-run
mau> exit

Demo Duration: 3-4 minutes
Impact: Developers see P2P sync working, builds intuition for distributed systems


Conclusion

The interactive CLI transforms the E2E framework from “test infrastructure” into “P2P development playground.”

Key Benefits:

  1. Exploratory Testing: Manually probe edge cases
  2. Debugging: Inspect live peer state when tests fail
  3. Demonstrations: Show Mau P2P behavior to stakeholders
  4. Education: New contributors learn by doing
  5. Scenario Development: Prototype test scenarios interactively before automating

Next Steps:

  1. Review this CLI design
  2. Approve tech stack (Cobra, go-prompt)
  3. Implement Phase 1 (CLI foundation)
  4. Iterate based on real usage

Document Version: 1.0
Last Updated: 19 February 2026
Status: Awaiting Approval