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.
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 clientbonjour-service— mDNS discoverytsx— TypeScript execution
# From the NeuroSkill™ project directory
npm install
npx neuroskill --helpNo global install needed — npx tsx handles TypeScript compilation on the fly.
Discovery
The CLI auto-discovers the NeuroSkill™ WebSocket server. Resolution order:
--port <n>— explicit port, skips all discovery- mDNS via
bonjour-service— browses for_skill._tcpservices on the local network (5 second timeout) - 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
| Command | Description |
|---|---|
| status | Full device/session/embeddings/scores snapshot |
| sessions | List all recording sessions across all days |
| label "text" | Create a timestamped annotation |
| search | ANN similarity search (auto: last session, k=5) |
| compare | Side-by-side A/B metrics (auto: last 2 sessions) |
| sleep | Sleep staging (auto: last 24h of sessions) |
| umap | Brain Nebula™ projection with live progress bar |
| listen | Stream broadcast events for N seconds |
| raw '{"command":"..."}' | Send arbitrary JSON, print full response |
Options
| Flag | Description |
|---|---|
| --port <n> | Connect to explicit WebSocket port (skips mDNS discovery) |
| --json | Output 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, -h | Show 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.
| Command | Auto-Selection Strategy |
|---|---|
| search | Most 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 |
| sleep | All sessions from the last 24 hours (earliest start → latest end). Fallback: last 8 hours |
| umap | Same 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 epochsJSON 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
}search --start --end --k
Queries the HNSW vector index for nearest-neighbor embeddings. Auto-selects the most recent session when no range is given. Returns results ranked by cosine distance with timestamps, labels, and analysis (distance stats, top days, temporal distribution, neighbor metric averages).
Terminal output
$ npx neuroskill search
⚡ search
range: 1740412800–1740415500 (auto: 2/24/2026 8:00 AM → 8:45 AM, 45m 0s)
k: 5
rerun: npx neuroskill search --start 1740412800 --end 1740415500 --k 5
Search Analysis
total neighbors: 25 span: 6.2h
distance: min=0.018 mean=0.042 max=0.089 σ=0.015
top days: 20260224(15), 20260223(10)
peak hours: 8:00(12), 9:00(8), 14:00(5)JSON response --json
{
"command": "search",
"ok": true,
"result": {
"query_count": 541,
"searched_days": ["20260223", "20260224"],
"results": [
{
"timestamp_unix": 1708795032,
"neighbors": [
{
"distance": 0.023,
"timestamp_unix": 1708791200,
"date": "20260223",
"device_name": "Muse-A1B2",
"labels": [{ "text": "focused reading" }]
},
{
"distance": 0.031,
"timestamp_unix": 1708793100,
"date": "20260224",
"device_name": "Muse-A1B2",
"labels": []
}
]
}
],
"analysis": {
"total_neighbors": 25,
"time_span_hours": 6.2,
"distance_stats": {
"min": 0.018, "mean": 0.042,
"max": 0.089, "stddev": 0.015
},
"top_days": [["20260224", 15], ["20260223", 10]],
"temporal_distribution": { "8": 12, "9": 8, "14": 5 },
"neighbor_metrics": {
"focus": 0.62, "relaxation": 0.45,
"engagement": 0.58, "hr": 70.2
}
}
}
}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, hrJSON 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.8mJSON 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 ×5JSON 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}' --jsonThe 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