CLI

A TypeScript command-line interface that wraps the WebSocket API with mDNS auto-discovery, session-aware defaults, colorized terminal output, and a machine-readable --json mode for piping to jq or other tools.

9 commands mDNS auto-discovery Session-aware defaults Colorized output --json mode Node ≥ 18
npx neuroskill <command> [options]

Installation

The CLI lives in the NeuroSkill™ project root as cli.ts. It uses the project's existing dev dependencies:

  • ws — WebSocket client
  • bonjour-service — mDNS discovery
  • tsx — TypeScript execution
# From the NeuroSkill™ project directory
npm install
npx neuroskill --help

No global install needed — npx tsx handles TypeScript compilation on the fly.

Discovery

The CLI auto-discovers the NeuroSkill™ WebSocket server. Resolution order:

  1. --port <n> — explicit port, skips all discovery
  2. mDNS via bonjour-service — browses for _skill._tcp services on the local network (5 second timeout)
  3. lsof fallback (macOS/Linux) — finds processes named "skill" with TCP LISTEN sockets via pgrep + lsof, then probes each port with a test WebSocket handshake (1.5s timeout per port)

Once discovered, the CLI connects with up to 3 retries (1s delay, 5s handshake timeout per attempt). A global 10-minute timeout prevents hangs.

Commands

CommandDescription
statusFull device/session/embeddings/scores snapshot
sessionsList all recording sessions across all days
label "text"Create a timestamped annotation
searchANN similarity search (auto: last session, k=5)
compareSide-by-side A/B metrics (auto: last 2 sessions)
sleepSleep staging (auto: last 24h of sessions)
umapBrain Nebula™ projection with live progress bar
listenStream broadcast events for N seconds
raw '{"command":"..."}' Send arbitrary JSON, print full response

Options

FlagDescription
--port <n>Connect to explicit WebSocket port (skips mDNS discovery)
--jsonOutput raw JSON without ANSI colors — pipeable to jq
--start <utc>Start of time range (unix seconds) for search / sleep
--end <utc>End of time range for search / sleep
--a-start <utc>Session A start for compare / umap
--a-end <utc>Session A end
--b-start <utc>Session B start
--b-end <utc>Session B end
--k <n>Number of nearest neighbors for search (default: 5)
--seconds <n>Duration for listen command (default: 5)
--help, -hShow full help with examples

Auto-Range Defaults

When time parameters are omitted, the CLI fetches your session history and picks smart defaults. A rerun: line is always printed so you can copy-paste for reproducible results.

CommandAuto-Selection Strategy
searchMost recent session (fallback: last 10 min)
compare≥2 sessions → second-to-last as A, last as B. 1 session → split at midpoint. 0 → last 2 hours halved
sleepAll sessions from the last 24 hours (earliest start → latest end). Fallback: last 8 hours
umapSame as compare (last 2 sessions as A/B)

Command Details

status

Returns a comprehensive snapshot: device state, current session, embedding counts (today + total + recording days), latest scores (all 60+ metrics), 48-hour sleep summary, recording history (streak, today vs 7-day averages), signal quality, and calibration info.

Terminal output

$ npx neuroskill status
  Recording History
  sessions: 45  days: 31  streak: 5d  total: 82.3h
  longest: 95min  avg: 42min

  Today vs 7-Day Average
    focus               0.62 vs    0.55  ↑ +12.7%
    relaxation          0.45 vs    0.48  ↓ -6.3%
    engagement          0.68 vs    0.61  ↑ +11.5%
    hr                 68.20 vs   72.40  ↓ -5.8%

JSON response --json

{
  "command": "status",
  "ok": true,
  "device": {
    "state": "connected",
    "connected": true,
    "streaming": true,
    "name": "Muse-A1B2",
    "id": "00:55:DA:B7:...",
    "serial_number": "MUSE2-A1B2C3D4",
    "mac_address": "00:55:DA:B7:...",
    "firmware_version": "1.4.2",
    "hardware_version": "6",
    "bootloader_version": "2.1",
    "preset": "p21",
    "battery": 85,
    "sample_count": 128000,
    "ppg_sample_count": 32000,
    "retry_attempt": 0,
    "retry_countdown_secs": null
  },
  "session": {
    "start_utc": 1708790000,
    "duration_secs": 2847
  },
  "embeddings": {
    "today": 342,
    "total": 14820,
    "recording_days": 31,
    "encoder_loaded": true,
    "overlap_secs": 2.5
  },
  "labels": { "total": 12 },
  "calibration": { "last_calibration_utc": 1708750000 },
  "signal_quality": {
    "TP9": 0.95, "AF7": 0.88, "AF8": 0.91, "TP10": 0.97
  },
  "scores": {
    "focus": 55.0, "relaxation": 42.0, "engagement": 68.0,
    "faa": -0.080, "tar": 0.530, "bar": 0.820, "tbr": 1.210,
    "pse": 0.720, "apf": 10.2, "bps": -1.450, "snr": 18.3,
    "sef95": 28.40, "spectral_centroid": 12.10,
    "hjorth_activity": 0.245, "hjorth_mobility": 0.310,
    "hjorth_complexity": 1.420,
    "permutation_entropy": 0.910, "higuchi_fd": 1.420,
    "dfa_exponent": 0.850, "sample_entropy": 1.630,
    "coherence": 0.650, "mu_suppression": 0.890,
    "mood": 62.0, "laterality_index": -0.030,
    "pac_theta_gamma": 0.120,
    "hr": 72.1, "rmssd": 42.1, "sdnn": 58.3, "pnn50": 18.2,
    "lf_hf_ratio": 1.80, "respiratory_rate": 14.5,
    "spo2_estimate": 97.8, "perfusion_index": 2.10,
    "stress_index": 120.5,
    "blink_count": 34, "blink_rate": 12.3,
    "jaw_clench_count": 2, "jaw_clench_rate": 0.7,
    "head_pitch": -5.2, "head_roll": 1.3, "stillness": 95.0,
    "nod_count": 0, "shake_count": 0,
    "meditation": 52.0, "cognitive_load": 38.0, "drowsiness": 12.0,
    "headache_index": 18.3, "migraine_index": 11.7,
    "consciousness_lzc": 62.4, "consciousness_wakefulness": 71.0, "consciousness_integration": 45.8,
    "bands": {
      "rel_delta": 0.274, "rel_theta": 0.180,
      "rel_alpha": 0.338, "rel_beta": 0.149, "rel_gamma": 0.047
    },
    "epoch_timestamp": 1708800000
  },
  "sleep": {
    "window_hours": 48,
    "total_epochs": 420, "epoch_secs": 5,
    "wake_epochs": 38, "n1_epochs": 35, "n2_epochs": 210,
    "n3_epochs": 95, "rem_epochs": 42
  },
  "history": {
    "total_sessions": 45, "recording_days": 31,
    "current_streak_days": 5,
    "total_recording_hours": 82.3,
    "longest_session_min": 95, "avg_session_min": 42,
    "today_vs_avg": {
      "focus": {
        "today": 0.62, "avg_7d": 0.55,
        "delta_pct": 12.7, "direction": "up"
      }
    }
  }
}

sessions

Lists all recording sessions across all daily folders, newest first. Sessions are contiguous ranges of embedding epochs (gap threshold: 120s).

Terminal output

$ npx neuroskill sessions
3 session(s)

  20260223  2/23/2026, 9:15:00 AM → 10:02:33 AM  47m 33s  570 epochs
  20260223  2/23/2026, 2:30:00 PM → 3:12:45 PM   42m 45s  513 epochs
  20260224  2/24/2026, 8:00:00 AM → 8:45:10 AM   45m 10s  541 epochs

JSON response --json

{
  "command": "sessions",
  "ok": true,
  "sessions": [
    {
      "start_utc": 1708790000,
      "end_utc": 1708792847,
      "n_epochs": 541,
      "day": "20260224"
    },
    {
      "start_utc": 1708700000,
      "end_utc": 1708702565,
      "n_epochs": 513,
      "day": "20260223"
    },
    {
      "start_utc": 1708681500,
      "end_utc": 1708684353,
      "n_epochs": 570,
      "day": "20260223"
    }
  ]
}

label text (required)

Creates a timestamped annotation. The label is stored in ~/.skill/labels.sqlite and broadcast as a label-created event to all connected clients (the dashboard shows a marker on the chart).

Terminal output

$ npx neuroskill label "eyes closed relaxation"

JSON response --json

{
  "command": "label",
  "ok": true,
  "label_id": 42
}

compare --a-start --a-end --b-start --b-end

Side-by-side metrics for two time ranges. Auto-selects the last two sessions as A/B. Returns aggregated metrics, sleep staging, compare insights (per-metric deltas with direction/percentage), improved/declined lists, and auto-enqueues a Brain Nebula™ projection job.

Terminal output

$ npx neuroskill compare
⚡ compare
  A: 1740380100–1740382665 (auto: 2/23/2026 2:30 PM → 3:12 PM, 42m 45s)
  B: 1740412800–1740415510 (auto: 2/24/2026 8:00 AM → 8:45 AM, 45m 10s)
  rerun: npx neuroskill compare --a-start 1740380100 --a-end 1740382665 \
         --b-start 1740412800 --b-end 1740415510

  Compare Insights  (513 vs 541 epochs)

  metric                  A        B        Δ      Δ%  dir
  ────────────────────────────────────────────────────────────
  focus                0.62     0.71    +0.09  +14.5%  ↑
  relaxation           0.45     0.38    -0.07  -15.6%  ↓
  engagement           0.61     0.68    +0.07  +11.5%  ↑
  hr                  72.10    68.40    -3.70   -5.1%  ↓
  meditation           0.48     0.52    +0.04   +8.3%  ↑

  ▲ improved: focus, engagement, snr
  ▼ declined: relaxation, hr

JSON response --json

{
  "command": "compare",
  "ok": true,
  "a": {
    "focus": 0.62, "relaxation": 0.45, "engagement": 0.61,
    "faa": -0.05, "tar": 0.58, "bar": 0.85, "tbr": 1.35,
    "hr": 72.1, "rmssd": 38.2, "meditation": 0.48,
    "drowsiness": 0.15, "cognitive_load": 0.42,
    "snr": 16.8, "stillness": 92.0
  },
  "b": {
    "focus": 0.71, "relaxation": 0.38, "engagement": 0.68,
    "faa": -0.03, "tar": 0.51, "bar": 0.78, "tbr": 1.18,
    "hr": 68.4, "rmssd": 44.5, "meditation": 0.52,
    "drowsiness": 0.10, "cognitive_load": 0.38,
    "snr": 19.2, "stillness": 96.0
  },
  "sleep_a": {
    "summary": {
      "total_epochs": 513, "epoch_secs": 5,
      "wake_epochs": 62, "n1_epochs": 45,
      "n2_epochs": 198, "n3_epochs": 142, "rem_epochs": 66
    }
  },
  "sleep_b": {
    "summary": {
      "total_epochs": 541, "epoch_secs": 5,
      "wake_epochs": 72, "n1_epochs": 44,
      "n2_epochs": 223, "n3_epochs": 156, "rem_epochs": 46
    }
  },
  "insights": {
    "n_epochs_a": 513,
    "n_epochs_b": 541,
    "deltas": {
      "focus": {
        "a": 0.62, "b": 0.71,
        "abs": 0.09, "pct": 14.5, "direction": "up"
      },
      "relaxation": {
        "a": 0.45, "b": 0.38,
        "abs": -0.07, "pct": -15.6, "direction": "down"
      },
      "hr": {
        "a": 72.1, "b": 68.4,
        "abs": -3.7, "pct": -5.1, "direction": "down"
      }
    },
    "improved": ["focus", "engagement", "snr"],
    "declined": ["relaxation", "hr"]
  },
  "umap": {
    "queued": true,
    "job_id": 3,
    "estimated_ready_utc": 1708800012,
    "queue_position": 0,
    "estimated_secs": 12,
    "n_a": 513,
    "n_b": 541
  }
}

sleep --start --end

Classifies EEG epochs into sleep stages (Wake/N1/N2/N3/REM) using band-power ratios with AASM heuristics. Auto-selects all sessions from the last 24 hours. Returns per-epoch hypnogram, summary, and analysis (efficiency, onset/REM latency, transitions, awakenings, bout statistics).

Terminal output

$ npx neuroskill sleep
⚡ sleep
  range: 1740380100–1740415510 (auto: 2/23/2026 2:30 PM → 2/24/2026 8:45 AM)
  rerun: npx neuroskill sleep --start 1740380100 --end 1740415510

  Sleep Summary
  total: 1054 epochs (88 min)
  Wake  134  (12.7%)
  N1     89  (8.4%)
  N2    421  (39.9%)
  N3    298  (28.3%)
  REM   112  (10.6%)

  Sleep Analysis
  efficiency:    87.3%
  onset latency: 8.5 min
  REM latency:   62.0 min
  transitions:   34  awakenings: 6

  Bout analysis:
    N2     8 bouts  avg 4.4m  max 12.1m
    N3     5 bouts  avg 5.0m  max 9.2m
    REM    3 bouts  avg 3.1m  max 5.8m

JSON response --json

{
  "command": "sleep",
  "ok": true,
  "epochs": [
    {
      "utc": 1708750000, "stage": "Wake",
      "rel_delta": 0.22, "rel_theta": 0.25,
      "rel_alpha": 0.30, "rel_beta": 0.18
    },
    { "utc": 1708750005, "stage": "N1", "..." : "..." },
    { "utc": 1708750010, "stage": "N2", "..." : "..." }
  ],
  "summary": {
    "total_epochs": 1054, "epoch_secs": 5,
    "wake_epochs": 134, "n1_epochs": 89,
    "n2_epochs": 421, "n3_epochs": 298, "rem_epochs": 112
  },
  "analysis": {
    "efficiency_pct": 87.3,
    "onset_latency_min": 8.5,
    "rem_latency_min": 62.0,
    "transitions": 34,
    "awakenings": 6,
    "stage_minutes": {
      "wake": 11.2, "n1": 7.4, "n2": 35.1,
      "n3": 24.8, "rem": 9.3
    },
    "bouts": {
      "N2": { "count": 8, "mean_min": 4.4, "max_min": 12.1 },
      "N3": { "count": 5, "mean_min": 5.0, "max_min": 9.2 },
      "REM": { "count": 3, "mean_min": 3.1, "max_min": 5.8 }
    }
  }
}

umap --a-start --a-end --b-start --b-end

Computes a Brain Nebula™ projection via GPU-accelerated UMAP (fast-umap, wgpu/CubeCL backend). This is a two-phase operation: enqueue the job, then poll with a live progress bar showing epoch count, ms/epoch, loss, and ETA. Results are cached in ~/.skill/umap_cache/. 5-minute timeout.

Terminal output

$ npx neuroskill umap
⚡ umap
  A: 1740380100–1740382665 (auto: 2/23/2026 2:30 PM → 3:12 PM, 42m 45s)
  B: 1740412800–1740415510 (auto: 2/24/2026 8:00 AM → 8:45 AM, 45m 10s)
  rerun: npx neuroskill umap --a-start 1740380100 --a-end 1740382665 \
         --b-start 1740412800 --b-end 1740415510
  enqueued job_id=5  n_a=513  n_b=541  est=14s
  ████████████░░░░░░░░░░░░░░░░░░ 40%  epoch 80/200  42ms/ep  ~5s left
  completed in 8432ms

  Brain Nebula™ Cluster Analysis
  separation score: 3.42  (higher = better A/B separation)
  inter-cluster:    4.15
  intra-spread A:   1.21  B: 1.08
  centroid A: (0.82, -0.31, 1.55)  B: (-1.95, 1.42, 0.22)
  outliers: 3 in A, 2 in B  (>2σ from centroid)

JSON response --json

{
  "command": "umap_poll",
  "ok": true,
  "status": "complete",
  "result": {
    "points": [
      {
        "x": 1.23, "y": -0.45, "z": 2.01,
        "session": 0, "utc": 1708700005
      },
      {
        "x": 1.31, "y": -0.52, "z": 1.98,
        "session": 0, "utc": 1708700010,
        "label": "focused reading"
      },
      {
        "x": -2.10, "y": 1.82, "z": 0.33,
        "session": 1, "utc": 1708790005
      }
    ],
    "n_a": 513,
    "n_b": 541,
    "dim": 32,
    "elapsed_ms": 8432,
    "analysis": {
      "separation_score": 3.42,
      "inter_cluster_distance": 4.15,
      "intra_spread_a": 1.21,
      "intra_spread_b": 1.08,
      "centroid_a": [0.82, -0.31, 1.55],
      "centroid_b": [-1.95, 1.42, 0.22],
      "n_outliers_a": 3,
      "n_outliers_b": 2
    }
  }
}

The CLI internally calls umap (enqueue) then polls umap_poll until complete. The JSON above is the final poll result.

listen --seconds

Passively collects broadcast events for a fixed duration. Useful for verifying the headset is streaming, debugging event formats, or piping to external tools. Shows a grouped summary (event type × count) in normal mode.

Terminal output

$ npx neuroskill listen --seconds 5
⚡ listen for 5s…
  eeg-bands ×20
  muse-status ×5

JSON response --json

[
  {
    "event": "eeg-bands",
    "payload": {
      "timestamp": 1708800000.25,
      "channels": [
        {
          "channel": "TP9",
          "delta": 12.3, "theta": 8.1, "alpha": 15.2,
          "beta": 6.7, "gamma": 2.1, "high_gamma": 0.4,
          "rel_delta": 0.274, "rel_theta": 0.180,
          "rel_alpha": 0.338, "rel_beta": 0.149,
          "rel_gamma": 0.047, "rel_high_gamma": 0.009,
          "dominant": "alpha",
          "dominant_symbol": "α",
          "dominant_color": "#22c55e"
        }
      ],
      "faa": -0.08, "tar": 0.53, "bar": 0.82,
      "focus_score": 55.0, "relaxation_score": 42.0,
      "hr": 72.1, "meditation": 52.0
    }
  },
  {
    "event": "muse-status",
    "payload": {
      "state": "connected", "connected": true,
      "name": "Muse-A1B2", "battery": 85,
      "channel_quality": {
        "TP9": 0.95, "AF7": 0.88,
        "AF8": 0.91, "TP10": 0.97
      }
    }
  }
]

In --json mode the output is a JSON array of all events received during the listen window. See the API event reference for full payload schemas.

raw JSON string (required)

Sends arbitrary JSON and prints the server's full response. Useful for testing undocumented commands or constructing precise queries (e.g. custom ef for search, label_start_utc for backdated labels). The JSON must contain a "command" field.

Usage

$ npx neuroskill raw '{"command":"status"}'
$ npx neuroskill raw '{"command":"label","text":"backdated note","label_start_utc":1708790000}' --json
$ npx neuroskill raw '{"command":"search","start_utc":1740412800,"end_utc":1740415500,"k":3}' --json

The response is the raw JSON from the server — same schema as the corresponding command. See API command reference for all response schemas.

Output Modes

Normal Mode

  • ANSI-colored JSON (keys blue, strings green, numbers cyan, booleans yellow, null magenta)
  • Human-readable summaries before raw output
  • Progress bars for long operations
  • Informational messages on stderr

--json Mode

  • Plain JSON on stdout — no ANSI codes
  • Only the command result is printed
  • Errors output as {"error":"..."}
  • Clean pipe to jq, python, etc.

Examples

# ── Basic ──
$ npx neuroskill status                            # full snapshot
$ npx neuroskill status --json | jq '.scores'       # pipe to jq
$ npx neuroskill status --port 62853                # explicit port

# ── Sessions ──
$ npx neuroskill sessions
$ npx neuroskill sessions --json | jq '.sessions[0]'

# ── Labels ──
$ npx neuroskill label "meditation start"
$ npx neuroskill label "eyes closed"
$ npx neuroskill label "feeling anxious"

# ── Search (auto: last session) ──
$ npx neuroskill search
$ npx neuroskill search --k 20
$ npx neuroskill search --start 1740412800 --end 1740415500 --k 10

# ── Compare (auto: last 2 sessions) ──
$ npx neuroskill compare
$ npx neuroskill compare --a-start 1740380100 --a-end 1740382665 \
                        --b-start 1740412800 --b-end 1740415510
$ npx neuroskill compare --json | jq '{ a_focus: .a.focus, b_focus: .b.focus }'

# ── Sleep (auto: last 24h) ──
$ npx neuroskill sleep
$ npx neuroskill sleep --start 1740380100 --end 1740415510
$ npx neuroskill sleep --json | jq '.summary'

# ── Brain Nebula™ ──
$ npx neuroskill umap
$ npx neuroskill umap --json | jq '.result.points | length'

# ── Live event stream ──
$ npx neuroskill listen --seconds 30
$ npx neuroskill listen --seconds 10 --json

# ── Raw JSON ──
$ npx neuroskill raw '{"command":"status"}'
$ npx neuroskill raw '{"command":"search","start_utc":1740412800,"end_utc":1740415500,"k":3}' --json