Version: 1.0
Date: 19 February 2026
Critical Requirement: Interactive CLI for manual control and exploratory testing
The Mau E2E framework provides two modes of operation:
go test for CI/CD (as described in PLAN.md)Key Insight: Developers need to see P2P synchronization happening, not just assert it worked in tests. The interactive CLI makes the invisible visible.
┌─────────────────────────────────────────────────────────────┐
│ 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.
mau-e2e ¶1cd e2e
2go install ./cmd/mau-e2e
3mau-e2e --help
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
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.
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
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
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 ✓
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
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 filemau-e2e down → Deletes state file--env <name> flagFile: 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?
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}
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}
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
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
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
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
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.
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
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
---
## 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
The interactive CLI transforms the E2E framework from “test infrastructure” into “P2P development playground.”
Key Benefits:
Next Steps:
Document Version: 1.0
Last Updated: 19 February 2026
Status: Awaiting Approval