Skip to main content
The Synheart CLI is the main CLI tool for local development. It generates realistic HSI-compatible sensor data streams, eliminating the need for physical devices during SDK development and testing. This is the primary tool for local development with mock data.
Synheart CLI vs Wear CLI: The Synheart CLI generates mock HSI data locally. For streaming real data from cloud wearables (WHOOP, Garmin, Fitbit), use the Wear CLI.

Features

  • Mock HSI Data Streams: Generate realistic sensor data without physical devices
  • Multiple Scenarios: Pre-built scenarios for baseline, focus, stress, and workout
  • WebSocket Broadcasting: Real-time data streaming on localhost
  • Record & Replay: Capture sessions for reproducible testing
  • Deterministic Mode: Use seeds for consistent, repeatable data generation
  • Developer Friendly: Simple CLI with clear documentation and easy SDK integration

Prerequisites

Before installing the Synheart CLI, ensure you have:
  • Go 1.24 or later installed

Installation

Install the CLI to make it available system-wide:
git clone https://github.com/synheart-ai/synheart-cli
cd synheart-cli
make install
This installs the binary to $GOPATH/bin (typically $HOME/go/bin). Ensure Go’s bin directory is in your PATH: Add this line to your ~/.zshrc, ~/.bashrc, or ~/.bash_profile:
export PATH="$PATH:$HOME/go/bin"
Then reload your shell configuration:
source ~/.zshrc  # or ~/.bashrc
Verify installation:
synheart version

Build Locally (Development)

To build without installing globally:
make build
The binary will be available at bin/synheart. Run it with:
./bin/synheart version

Quick Start

Start the mock server with default settings:
synheart mock start
This will:
  • Generate baseline scenario data
  • Start WebSocket server on ws://127.0.0.1:8787/hsi
  • Broadcast events to all connected clients
Connect to the stream from your SDK:
const ws = new WebSocket('ws://localhost:8787/hsi');
ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log(data);
};

Commands

mock start

Start generating and broadcasting HSI events.
# Basic usage
synheart mock start

# With specific scenario
synheart mock start --scenario stress_spike

# Deterministic output with seed
synheart mock start --scenario focus_session --seed 42

# Custom port and duration
synheart mock start --port 9000 --duration 5m

# Record to file while broadcasting
synheart mock start --scenario workout --out workout.ndjson
Available Flags:
FlagDescriptionDefault
--hostHost to bind to127.0.0.1
--portPort to listen on8787
--scenarioScenario to runbaseline
--durationDuration to run (e.g., 5m, 1h)unlimited
--rateGlobal tick rate50hz
--seedRandom seed for deterministic outputrandom
--outRecord events to file (NDJSON format)-

mock record

Record mock data to an NDJSON file.
synheart mock record --scenario workout --duration 15m --out workout.ndjson
Available Flags:
FlagDescriptionDefault
--scenarioScenario to runbaseline
--durationDuration to record5m
--outOutput file (required)-
--seedRandom seed for deterministic outputrandom
--rateGlobal tick rate50hz

mock replay

Replay events from a recorded file.
# Basic replay
synheart mock replay --in workout.ndjson

# Faster playback
synheart mock replay --in workout.ndjson --speed 2.0

# Loop continuously
synheart mock replay --in workout.ndjson --loop
Available Flags:
FlagDescriptionDefault
--inInput file to replay (required)-
--speedPlayback speed multiplier1.0
--loopLoop playback continuouslyfalse
--hostHost to bind to127.0.0.1
--portPort to listen on8787

mock list-scenarios

List all available scenarios.
synheart mock list-scenarios

mock describe

Show detailed information about a scenario.
synheart mock describe stress_spike

doctor

Check environment and print connection examples.
synheart doctor
Validates:
  • Scenarios directory exists
  • Default port availability
  • Provides SDK connection examples

version

Print version information.
synheart version

Built-in Scenarios

baseline

Normal day idle with minor variance. Suitable for testing basic SDK functionality. Duration: Unlimited Use case: General SDK testing and development

focus_session

Reduced motion, stable heart rate, screen on patterns. Simulates a 30-minute focused work session. Duration: 30 minutes Use case: Testing focus detection and work session tracking

stress_spike

Sudden heart rate increase, HRV drop, EDA spike followed by recovery. Duration: 8 minutes Use case: Testing stress detection and recovery algorithms

workout

Cardio workout with warmup, run, cooldown phases. Includes HR increase, HRV decrease, and EDA elevation. Duration: 30 minutes Phases: Warmup (5m) → Run (20m) → Cooldown (5m) Use case: Testing activity detection and workout tracking

Event Schema

All events follow the HSI-compatible envelope format:
{
  "schema_version": "hsi.input.v1",
  "event_id": "550e8400-e29b-41d4-a716-446655440000",
  "ts": "2025-12-26T20:05:12.123Z",
  "source": {
    "type": "wearable",
    "id": "mock-watch-01",
    "side": "left"
  },
  "session": {
    "run_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "scenario": "focus_session",
    "seed": 42
  },
  "signal": {
    "name": "ppg.hr_bpm",
    "unit": "bpm",
    "value": 72.4,
    "quality": 0.93
  },
  "meta": {
    "sequence": 12093
  }
}

Supported Signals

Wearable Signals

SignalDescriptionUnit
ppg.hr_bpmHeart ratebpm
ppg.hrv_rmssd_msHeart rate variabilitymilliseconds
accel.xyz_mps23D accelerationm/s²
gyro.xyz_rps3D gyroscoperad/s
temp.skin_cSkin temperatureCelsius
eda.usElectrodermal activitymicrosiemens

Phone Signals

SignalDescription
screen.stateScreen on/off state
app.activityApp foreground/background activity
motion.activityMotion activity (still/walk/run)

SDK Integration

JavaScript/Node.js

const WebSocket = require('ws');

const ws = new WebSocket('ws://localhost:8787/hsi');

ws.on('message', (data) => {
  const event = JSON.parse(data);
  console.log(`${event.signal.name}: ${event.signal.value}`);
});

ws.on('error', (error) => {
  console.error('WebSocket error:', error);
});

Python

import websocket
import json

def on_message(ws, message):
    event = json.loads(message)
    print(f"{event['signal']['name']}: {event['signal']['value']}")

def on_error(ws, error):
    print(f"Error: {error}")

ws = websocket.WebSocketApp(
    "ws://localhost:8787/hsi",
    on_message=on_message,
    on_error=on_error
)

ws.run_forever()

Dart/Flutter

import 'package:web_socket_channel/web_socket_channel.dart';
import 'dart:convert';

final channel = WebSocketChannel.connect(
  Uri.parse('ws://localhost:8787/hsi'),
);

channel.stream.listen((message) {
  final event = json.decode(message);
  print('${event['signal']['name']}: ${event['signal']['value']}');
});

Swift

import Foundation

let url = URL(string: "ws://localhost:8787/hsi")!
let task = URLSession.shared.webSocketTask(with: url)

task.receive { result in
    switch result {
    case .success(let message):
        if case .string(let text) = message {
            let data = text.data(using: .utf8)!
            let event = try? JSONDecoder().decode(HSIEvent.self, from: data)
            print("\(event?.signal.name): \(event?.signal.value)")
        }
    case .failure(let error):
        print("Error: \(error)")
    }
}

task.resume()

Kotlin

import okhttp3.*
import org.json.JSONObject

val client = OkHttpClient()
val request = Request.Builder()
    .url("ws://localhost:8787/hsi")
    .build()

val listener = object : WebSocketListener() {
    override fun onMessage(webSocket: WebSocket, text: String) {
        val event = JSONObject(text)
        val signal = event.getJSONObject("signal")
        println("${signal.getString("name")}: ${signal.get("value")}")
    }

    override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
        println("Error: ${t.message}")
    }
}

client.newWebSocket(request, listener)

Go

package main

import (
    "encoding/json"
    "log"
    "github.com/gorilla/websocket"
)

type Event struct {
    Signal struct {
        Name  string      `json:"name"`
        Value interface{} `json:"value"`
    } `json:"signal"`
}

func main() {
    conn, _, err := websocket.DefaultDialer.Dial("ws://localhost:8787/hsi", nil)
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    for {
        _, message, err := conn.ReadMessage()
        if err != nil {
            log.Fatal(err)
        }

        var event Event
        json.Unmarshal(message, &event)
        log.Printf("%s: %v", event.Signal.Name, event.Signal.Value)
    }
}

Rust

use tokio_tungstenite::{connect_async, tungstenite::Message};
use futures_util::StreamExt;
use serde_json::Value;

#[tokio::main]
async fn main() {
    let (mut socket, _) = connect_async("ws://localhost:8787/hsi")
        .await
        .expect("Failed to connect");

    while let Some(msg) = socket.next().await {
        if let Ok(Message::Text(text)) = msg {
            let event: Value = serde_json::from_str(&text).unwrap();
            println!("{}: {}",
                event["signal"]["name"].as_str().unwrap(),
                event["signal"]["value"]
            );
        }
    }
}

Use Cases

SDK Development

Test HSI SDKs without physical wearables during development

CI/CD Pipeline

Automated testing with deterministic, reproducible data

Demos & Presentations

Scripted scenarios that look realistic for demonstrations

Integration Testing

Validate data pipelines and processing logic locally

Recording Format

Recordings use NDJSON (Newline Delimited JSON) format - one JSON object per line. Benefits:
  • Streaming-friendly
  • Easy to parse
  • Human-readable
  • Compatible with standard tools
Example:
{"schema_version":"hsi.input.v1","event_id":"...","ts":"2025-12-26T20:05:12.123Z",...}
{"schema_version":"hsi.input.v1","event_id":"...","ts":"2025-12-26T20:05:12.143Z",...}
{"schema_version":"hsi.input.v1","event_id":"...","ts":"2025-12-26T20:05:12.163Z",...}

Advanced Usage

Deterministic Testing

Generate the same data sequence across multiple runs:
# Run 1
synheart mock start --scenario baseline --seed 12345 --duration 1m

# Run 2 (generates identical data)
synheart mock start --scenario baseline --seed 12345 --duration 1m

Record and Replay Workflow

# 1. Record a scenario
synheart mock record --scenario workout --duration 30m --out workout.ndjson

# 2. Replay the recording
synheart mock replay --in workout.ndjson

# 3. Replay at 2x speed for faster testing
synheart mock replay --in workout.ndjson --speed 2.0

# 4. Loop for continuous testing
synheart mock replay --in workout.ndjson --loop

Custom Port for Multiple Instances

Run multiple scenarios simultaneously on different ports:
# Terminal 1: Baseline on default port
synheart mock start --scenario baseline

# Terminal 2: Workout on port 8788
synheart mock start --scenario workout --port 8788

# Terminal 3: Stress spike on port 8789
synheart mock start --scenario stress_spike --port 8789

Piping Data to Custom Scripts

# Record and process with custom script
synheart mock record --scenario baseline --duration 1m --out - | python process_data.py

Troubleshooting

If port 8787 is already in use:
# Use a different port
synheart mock start --port 9000
Or find and kill the process using the port:
# macOS/Linux
lsof -i :8787
kill <PID>

# Windows
netstat -ano | findstr :8787
taskkill /PID <PID> /F
Ensure the scenarios/ directory is in:
  1. Current working directory
  2. Same directory as the executable
Run diagnostics:
synheart doctor
  • Ensure the server is running: synheart mock start
  • Check firewall settings
  • Verify the correct host and port
  • Check server logs for errors
The Go bin directory may not be in your PATH.Solution:
# Add to ~/.zshrc or ~/.bashrc
export PATH="$PATH:$HOME/go/bin"

# Reload shell
source ~/.zshrc
Or use the binary directly:
$HOME/go/bin/synheart version

Security Notes

  • Binds to localhost (127.0.0.1) by default for security
  • Only generates synthetic data, never real user data
  • Use --host 0.0.0.0 with caution (exposes on LAN)

Next Steps


Author: Israel Goytom