diff --git a/.cargo/audit.toml b/.cargo/audit.toml new file mode 100644 index 000000000..0e6fec01d --- /dev/null +++ b/.cargo/audit.toml @@ -0,0 +1,50 @@ +# cargo-audit ignore list — all transitive deps we cannot directly upgrade. +# These are informational-only advisories (unmaintained / unsound); +# no actual vulnerabilities are present. + +# Yanked crates — transitive deps we can't control. +[yanked] +enabled = false + +[advisories] +ignore = [ + # ── gtk-rs GTK3 family — unmaintained, no GTK4 upgrade path until Tauri migrates ── + "RUSTSEC-2024-0411", # gdkwayland-sys + "RUSTSEC-2024-0412", # gdk + "RUSTSEC-2024-0413", # atk + "RUSTSEC-2024-0414", # gdkx11-sys + "RUSTSEC-2024-0415", # gtk + "RUSTSEC-2024-0416", # atk-sys + "RUSTSEC-2024-0417", # gdkx11 + "RUSTSEC-2024-0418", # gdk-sys + "RUSTSEC-2024-0419", # gtk3-macros + "RUSTSEC-2024-0420", # gtk-sys + + # ── glib — unsound VariantStrIter, patched in >=0.20.0; blocked by gtk3 above ── + "RUSTSEC-2024-0429", # glib + + # ── Other unmaintained transitive deps ──────────────────────────────────── + "RUSTSEC-2021-0139", # ansi_term (via structopt/clap2 in iroh test clients) + "RUSTSEC-2023-0089", # atomic-polyfill (via heapless 0.7 in iroh stack) + "RUSTSEC-2024-0370", # proc-macro-error (via gtk3-macros) + "RUSTSEC-2024-0375", # atty unmaintained (via structopt/clap2 in iroh test clients) + "RUSTSEC-2024-0436", # paste + "RUSTSEC-2025-0057", # fxhash + "RUSTSEC-2025-0075", # unic-char-range + "RUSTSEC-2025-0080", # unic-common + "RUSTSEC-2025-0081", # unic-char-property + "RUSTSEC-2025-0098", # unic-ucd-version + "RUSTSEC-2025-0100", # unic-ucd-ident + "RUSTSEC-2025-0119", # number_prefix + "RUSTSEC-2025-0134", # rustls-pemfile (via oura-api/reqwest 0.11) + "RUSTSEC-2025-0141", # bincode + + # ── Unsound warnings in legacy transitive deps ──────────────────────────── + "RUSTSEC-2021-0145", # atty potential unaligned read (legacy clap2 chain) + + # ── lru — unsound IterMut, patched in >=0.16.3; transitive dep ─────────── + "RUSTSEC-2026-0002", # lru + + # ── rand — unsound with custom logger using rand::rng(); transitive dep ── + "RUSTSEC-2026-0097", # rand (0.7.3 via tauri/phf, 0.8.5 via keyring/emotiv/etc.) +] diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..9dda61f03 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,204 @@ +# ── Workspace-level Cargo configuration ──────────────────────────────────────── +# +# Moved from src-tauri/.cargo/config.toml to the workspace root so that all +# workspace members share the same build configuration. +# +# RLX: Cargo.toml points all rlx* deps at GitHub (MIT-RLX/rlx + rlx-models). +# Local — `scripts/rlx local` / direnv overlays sibling checkouts via +# ../.cargo/config.toml (never commit path patches). +# CI — uses Cargo.lock git pins; no sibling clone. +# +# `relative = true` paths are resolved relative to this file's parent +# directory (the project root). + +# ── Default build target ────────────────────────────────────────────────────── +# +# cargo's [build] section has no per-host-OS conditionals, so a hardcoded +# `target = "aarch64-apple-darwin"` here would break Windows / Linux builds +# ("can't find crate for `core`"). +# +# Platform default is therefore left as the host triple. Per-platform +# targeting is handled at two levels: +# +# 1. npm / Tauri builds (recommended): +# npm run tauri:build ← auto-selects target via scripts/tauri-build.js +# macOS → --target aarch64-apple-darwin +# Windows/Linux → host triple (no --target flag) +# +# 2. Raw `cargo` on macOS: +# Either pass --target aarch64-apple-darwin explicitly, or let direnv +# source the repo-root .envrc which exports CARGO_BUILD_TARGET for you. +# +# ── Windows build prerequisite: LLVM / libclang ─────────────────────────────── +# +# llama-cpp-sys uses bindgen to generate Rust↔C bindings at build time. +# bindgen requires libclang.dll, which ships with the LLVM toolchain. +# +# One-time setup (run once, then restart your terminal): +# +# winget install LLVM.LLVM +# +# # Then add to your user environment (PowerShell): +# [System.Environment]::SetEnvironmentVariable( +# "LIBCLANG_PATH", "C:\Program Files\LLVM\bin", "User") +# +# Without this, cargo build fails with: +# "Unable to find libclang: couldn't find any valid shared libraries +# matching: ['clang.dll', 'libclang.dll']" +# +# LIBCLANG_PATH is intentionally NOT set here because the path is +# installation-dependent and force=true would break machines where LLVM +# lives elsewhere. Set it in your system/user environment variables instead. + +[build] +# target intentionally omitted — defaults to the host triple +# +# ── Shared target directory ─────────────────────────────────────────────────── +# +# All workspace members compile into src-tauri/target/. This keeps the +# binary output where the Tauri CLI and build scripts (tauri-build.js, +# assemble-macos-app.sh, etc.) expect it, and avoids the multi-GB +# duplication that occurred when individual crates had their own target/. +target-dir = "src-tauri/target" +# +# ── Build caching (sccache) ─────────────────────────────────────────────────── +# +# Do NOT hardcode `rustc-wrapper = "sccache"` here. +# +# Some developer machines (especially fresh Windows setups) do not have +# sccache installed, and a workspace-level rustc-wrapper makes *every* cargo +# command fail with: +# "could not execute process `sccache ... rustc.exe -vV` (program not found)" +# +# Instead, scripts/tauri-build.js auto-detects sccache and sets +# RUSTC_WRAPPER only when the executable is present. + +[env] +# Dynamic CRT for cmake-based -sys crates (llama-cpp-sys, etc.). +# Several native dependencies (DirectML, ONNX Runtime, Vulkan loader) ship +# as pre-built DLLs linked against the dynamic CRT (/MD). Mixing /MT and +# /MD in the same process causes heap corruption and "side-by-side +# configuration is incorrect" errors. Using /MD everywhere is consistent +# and the NSIS installer bundles the VC++ Redistributable DLLs app-locally +# so end users don't need a separate install. +# On non-Windows platforms cmake ignores this variable. +CMAKE_MSVC_RUNTIME_LIBRARY = "MultiThreadedDLL" + +# ── macOS: cmake / sccache paths ────────────────────────────────────────────── +# Homebrew cmake/sccache are not on the default PATH when cargo invokes build +# scripts. The cmake-0.1.x crate respects the CMAKE env var as the binary path. +# +# These are NOT set here because cargo's [env] table is unconditional: a value +# like "/opt/homebrew/bin/cmake" leaks to Windows / Linux runners where the +# path doesn't exist, causing cmake-rs to panic with "is `cmake` not installed?" +# (os error 3 / ENOENT). Instead they are exported only on macOS by: +# - .envrc (for `cargo build` via direnv) +# - scripts/tauri-build.js (for `npm run tauri:build`) + +# ── macOS deployment target ─────────────────────────────────────────────────── +# +# 14.0 satisfies both: +# - llama.cpp's std::filesystem (available since 10.15) +# - MLX Metal kernels using simdgroup_matrix (requires Metal 3.0 / macOS 14+) +# +# Apple Silicon (M-series, MacBook Neo A-series) — same Rust triple for all arm64 Macs. +# Aliases: mac-neo, aarch64-apple-darwin-neo, apple-a18 (see scripts/lib/target-triples.mjs). +# +# Apple Silicon (M1+) ships with macOS 11.0 minimum, so 14.0 is fine. +# +# Two separate knobs are needed: +# +# MACOSX_DEPLOYMENT_TARGET +# Read by the `cc` crate to generate -mmacosx-version-min in +# CMAKE_C_FLAGS / CMAKE_CXX_FLAGS (passed by cmake-0.1.57). +# +# CMAKE_OSX_DEPLOYMENT_TARGET +# llama-cpp-sys-2/build.rs forwards all CMAKE_* env vars directly to +# cmake configure via config.define(key, value). cmake then appends +# its own -mmacosx-version-min AFTER CMAKE_C_FLAGS, so Clang's +# "last flag wins" rule overrides any default baked into CMAKE_C_FLAGS. +# +# force=true is required so that a system-level MACOSX_DEPLOYMENT_TARGET +# (e.g. set by Xcode to 10.13) cannot silently override these values. +# +# ⚠️ After changing either value you MUST delete the stale cmake cache: +# rm -rf src-tauri/target/release/build/llama-cpp-sys-2-*/ +# rm -rf src-tauri/target/debug/build/mlx-sys-burn-*/ +MACOSX_DEPLOYMENT_TARGET = { value = "14.0", force = true } +CMAKE_OSX_DEPLOYMENT_TARGET = { value = "14.0", force = true } + +# ── macOS: increase main-thread stack size ──────────────────────────────────── +# +# macOS defaults to an 8 MB main-thread stack. The Tauri `run()` function +# has an enormous frame because `generate_handler!` expands ~150 command +# handlers inline, plus the setup/run closures, `AppState::default()`, and +# deep generics from serde / HNSW / llama.cpp. 8 MB is not enough. +# +# The stack-size flag is emitted from build.rs via `cargo:rustc-link-arg-bins` +# so it only applies to the final executable, NOT to the lib crate (ld64 +# rejects `-stack_size` on dylibs). See build.rs for the actual value. +# +# NOTE: [target.aarch64-apple-darwin] and [target.x86_64-apple-darwin] +# sections are intentionally absent here because macOS needs no target-wide +# rustflags. The stack size is handled exclusively by build.rs. + +# ── Linux: fast linker (mold) ───────────────────────────────────────────────── +# +# mold is a drop-in replacement for ld/lld that is significantly faster for +# large binaries. When mold + clang are installed, scripts/tauri-build.js +# automatically sets CARGO_TARGET_*_LINKER and CARGO_TARGET_*_RUSTFLAGS +# environment variables so cargo uses mold without hardcoding it here. +# +# Do NOT hardcode [target.*-unknown-linux-gnu] linker/rustflags here — it +# breaks builds for developers who don't have mold or clang installed. +# +# Install (Debian/Ubuntu): sudo apt install mold clang +# Install (Fedora): sudo dnf install mold clang +# Install (Arch): sudo pacman -S mold clang +# Install (macOS): not needed — Apple's ld is already fast + +# ── Windows MSVC: increase main-thread stack size ──────────────────────────── +# +# Windows allocates only 1 MB of stack to the main thread by default (vs 8 MB +# on macOS / Linux). The Tauri + llama.cpp initialisation path uses enough +# stack (serde, HNSW, deep generics) to hit the guard page and produce a +# STATUS_STACK_OVERFLOW (0xC00000FD) crash before any window is shown. +# +# /STACK:[,] — PE header stack reservation. +# reserve = 8 MB (8 * 1024 * 1024 = 8388608) +# commit = 64 KB (default; pages are committed lazily, so this is fine) +# +# The flag is passed through -C link-args so it works without a separate +# .cargo/config linker directive. It only affects the main thread; worker +# threads set their own size via std::thread::Builder::stack_size. +# ── Windows MSVC: fast linker (lld-link) ───────────────────────────────────── +# +# lld-link is LLVM's drop-in replacement for MSVC's link.exe. It is +# significantly faster for large Rust projects (the Windows release build +# spends most of its time linking). When LLVM is installed (winget or CI), +# lld-link.exe is available in the LLVM bin directory. +# +# The linker is set via CARGO_TARGET_*_LINKER in the CI workflow and in +# tauri-build.js (auto-detected). Do NOT hardcode it here because it +# breaks builds for developers who don't have LLVM installed — they +# fall back to the default MSVC link.exe which always works. +# +# Install: winget install LLVM.LLVM + +# Dynamic CRT: the binary links against vcruntime140.dll / msvcp140.dll. +# The NSIS installer bundles these DLLs app-locally (in $INSTDIR alongside +# skill.exe) so users don't need the VC++ Redistributable pre-installed. +# The installer also offers to download the full VC++ Redist as a fallback. +[target.x86_64-pc-windows-msvc] +rustflags = ["-C", "link-args=/STACK:8388608"] + +[target.aarch64-pc-windows-msvc] +rustflags = ["-C", "link-args=/STACK:8388608"] + +# ── Windows MinGW (x86_64-pc-windows-gnu): MinGW bash build script ──────────── +# +[target.x86_64-pc-windows-gnu] +rustflags = ["-C", "link-args=-Wl,--stack,8388608"] + +[target.i686-pc-windows-gnu] +rustflags = ["-C", "link-args=-Wl,--stack,8388608"] diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..237b787f2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,36 @@ +# Heavy artifacts that should never be sent to the Docker build context. +# Without this, `docker build` for Dockerfile.upgrade-test transfers tens of +# GB of local target/ output and chokes on I/O. + +# Rust build artifacts +target/ +src-tauri/target/ +crates/*/target/ +**/*.rlib + +# Node / frontend +node_modules/ +.svelte-kit/ +build/ +.vite/ + +# IDE / OS +.git/ +.idea/ +.vscode/ +.DS_Store + +# Logs / test results +test-results/ +playwright-report/ +logs/ +*.log +*.log.zst + +# Local data +.skill/ +data/ +fixtures/ + +# Cargo registry should come from the volume mount, not the context +.cargo/ diff --git a/.envrc b/.envrc new file mode 100644 index 000000000..81adfe384 --- /dev/null +++ b/.envrc @@ -0,0 +1,48 @@ +# direnv — https://direnv.net/ +# Run `direnv allow` once after cloning. +# +# On macOS: sets the default cargo target to Apple Silicon so that +# `cargo build`, `cargo check`, `cargo clippy` etc. all target +# aarch64-apple-darwin without an explicit --target flag. +# +# On Windows / Linux: CARGO_BUILD_TARGET is left unset, cargo uses the +# host triple (x86_64-pc-windows-msvc / x86_64-unknown-linux-gnu). + +if [ "$(uname)" = "Darwin" ]; then + export CARGO_BUILD_TARGET=aarch64-apple-darwin + + # Use GNU ar if available (Homebrew binutils) to suppress + # "illegal option -- D" warnings from Apple's system ar. + # Install: brew install binutils + GAR="$(brew --prefix binutils 2>/dev/null)/bin/gar" + if [ -x "$GAR" ]; then + export AR="$GAR" + fi + + # Homebrew cmake/sccache are not on the default PATH when cargo invokes + # build scripts. The cmake-0.1.x crate respects CMAKE as the binary path. + # Kept out of .cargo/config.toml because [env] is unconditional and would + # leak these macOS paths to Windows / Linux runners (cmake-rs panics with + # "is `cmake` not installed?" / os error 3). + if [ -z "${CMAKE:-}" ] && [ -x "/opt/homebrew/bin/cmake" ]; then + export CMAKE="/opt/homebrew/bin/cmake" + fi + if [ -z "${SCCACHE_PATH:-}" ] && [ -x "/opt/homebrew/bin/sccache" ]; then + export SCCACHE_PATH="/opt/homebrew/bin/sccache" + fi +fi + +# Use prebuilt llama.cpp if available (skip cmake rebuild). +# Run `npm run setup:llama-prebuilt` to download. +if [ -d ".llama-prebuilt/lib" ]; then + export LLAMA_PREBUILT_DIR="$(pwd)/.llama-prebuilt" +fi + +# Sibling RLX checkout for optional llm-rlx / text-embeddings-rlx features. +# Default: symlink ../rlx -> /Users/Shared/rlx (override via rlx.path or RLX_ROOT). +if [ -f "scripts/ensure-rlx.sh" ]; then + bash scripts/ensure-rlx.sh +fi +if [ -f "scripts/ensure-rlx-models.sh" ]; then + bash scripts/ensure-rlx-models.sh +fi diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..9a212154f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,101 @@ +# ── Line endings ────────────────────────────────────────────────────────────── +# +# Normalise all text files to LF in the repository; Git converts to the +# platform native ending on checkout only when core.autocrlf is set. +# This prevents the "LF will be replaced by CRLF" warnings on Windows and +# keeps diffs clean regardless of the developer's OS. + +# Catch-all: treat everything Git auto-detects as text with LF in the repo. +* text=auto eol=lf + +# ── Source files (always LF) ────────────────────────────────────────────────── +*.rs text eol=lf +*.toml text eol=lf +*.lock text eol=lf +*.ts text eol=lf +*.svelte text eol=lf +*.js text eol=lf +*.mjs text eol=lf +*.cjs text eol=lf +*.json text eol=lf +*.json5 text eol=lf +*.css text eol=lf +*.html text eol=lf +*.md text eol=lf +*.txt text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +*.sh text eol=lf +*.bash text eol=lf +*.env text eol=lf +*.envrc text eol=lf +*.gitignore text eol=lf +*.gitattributes text eol=lf +*.editorconfig text eol=lf + +# ── Config / build files ────────────────────────────────────────────────────── +Makefile text eol=lf +Dockerfile* text eol=lf +*.cmake text eol=lf +CMakeLists.txt text eol=lf +*.plist text eol=lf +*.entitlements text eol=lf + +# ── Windows-specific scripts (CRLF on checkout so cmd.exe / PowerShell work) ── +*.ps1 text eol=crlf +*.cmd text eol=crlf +*.bat text eol=crlf + +# ── Binary files (no line-ending conversion, no diff) ──────────────────────── +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.webp binary +*.ico binary +*.icns binary +*.otf binary +*.ttf binary +*.woff binary +*.woff2 binary +*.glb binary +*.gltf binary +*.wav binary +*.mp3 binary +*.npy binary +*.safetensors binary +*.gguf binary +*.hnsw binary +*.sqlite binary +*.db binary +*.a binary +*.lib binary +*.dll binary +*.so binary +*.dylib binary +*.exe binary + +# ── Diff drivers ───────────────────────────────────────────────────────────── +# +# Tell git diff to use sensible language labels so hunks are easier to read +# in `git diff`, `git log -p`, and GitHub's PR view. + +*.rs diff=rust +*.toml diff=toml +*.ts diff=typescript +*.svelte diff=html +*.js diff=javascript +*.mjs diff=javascript +*.json diff=json +*.css diff=css +*.md diff=markdown +*.sh diff=bash +*.ps1 diff=powershell + +# ── Vendored TTS model bundle ──────────────────────────────────────────────── +# +# Store the committed Inflect-Nano bundle verbatim so its bytes match exactly +# what scripts/bundle-tts-weights.sh stages and the app loads at runtime — no +# line-ending conversion (the catch-all `* text=auto eol=lf` would otherwise +# rewrite the dictionary/JSON files) and no diff noise. +src-tauri/resources/tts/inflect-nano/** binary diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 000000000..0dce83f6f --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-3.0-only +# Copyright (C) 2026 NeuroSkill.com +# +# Minimal pre-commit hook — basic validation only. +# Full checks run in pre-push and CI. + +set -euo pipefail + +STAGED_FILES="$(git diff --cached --name-only --diff-filter=ACMR 2>/dev/null || true)" + +if [ -z "$STAGED_FILES" ]; then + exit 0 +fi + +# ── rlx source guard ────────────────────────────────────────────────────────── +# Commits must never carry a local ../rlx path patch in Cargo.toml, and +# Cargo.lock must pin `rlx` to the MIT-RLX/rlx GitHub git source (not a +# path+file checkout). Safety net when Cargo.lock skip-worktree is bypassed +# (fresh clone, `git add -A`, GUI clients, etc. — see scripts/ensure-rlx.sh). + +# 1) Cargo.toml must not reintroduce a ../rlx path patch (hard block). +if ! git diff --cached --quiet -- Cargo.toml 2>/dev/null; then + if git show :Cargo.toml 2>/dev/null | grep -vE '^[[:space:]]*#' | grep -qE 'path[[:space:]]*=[[:space:]]*"\.\./rlx'; then + echo "❌ Staged Cargo.toml adds a ../rlx path patch — that must stay local-only." >&2 + echo " Remove it and use 'scripts/rlx local' (writes an override OUTSIDE the repo)." >&2 + exit 1 + fi +fi + +# 2) Cargo.lock (as it will be committed = the index blob) must pin rlx to +# GitHub MIT-RLX/rlx, not a local path checkout. +# Disable pipefail for the early-exit awk: git show gets SIGPIPE (141) when +# awk closes the pipe after the first `source =` line. +if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then + set +o pipefail + rlx_blob="$(git show :Cargo.lock 2>/dev/null | awk '/^name = "rlx"$/{p=1} p{print; if(/^source =/ || (/^\[\[package\]\]/ && ++n>1)){exit}}')" + set -o pipefail + if printf '%s\n' "$rlx_blob" | grep -qE 'source = "path\+'; then + echo "❌ Cargo.lock pins rlx to a LOCAL path checkout — refusing to commit." >&2 + echo " Fix: run 'scripts/rlx deployed' (restores the GitHub-pinned lock), then commit." >&2 + echo " Run 'scripts/rlx local' afterward to resume local development." >&2 + exit 1 + fi + if ! printf '%s\n' "$rlx_blob" | grep -qE 'source = "git\+https://github\.com/MIT-RLX/rlx(\.git)?'; then + echo "❌ Cargo.lock does not pin rlx to git+https://github.com/MIT-RLX/rlx — refusing to commit." >&2 + echo " Expected a GitHub git source (see Cargo.toml workspace.dependencies)." >&2 + echo " Fix: run 'scripts/rlx deployed', then 'cargo update -p rlx', then commit." >&2 + exit 1 + fi +fi + +# Basic JSON validation for catalog files +if echo "$STAGED_FILES" | grep -q 'llm_catalog.json'; then + if ! python3 -m json.tool src-tauri/llm_catalog.json > /dev/null 2>&1; then + echo "❌ Invalid JSON in llm_catalog.json" + exit 1 + fi +fi + +# Basic i18n JSON validation +if echo "$STAGED_FILES" | grep -q '^src/lib/i18n/.*\.ts$'; then + echo "🌐 i18n files changed — running basic JSON validation…" + npm run -s check:i18n:locales || exit 1 +fi + +# Run cargo fmt on staged Rust files +if echo "$STAGED_FILES" | grep -q '\.rs$'; then + echo "🦀 Running cargo fmt…" + # GUI git clients (editors, Tower, etc.) launch hooks in a non-interactive + # shell that doesn't source ~/.zshrc, so rustup's ~/.cargo/bin is missing + # from PATH. Source the env file rustup ships, or fall back to the default + # install path. + if [ -f "$HOME/.cargo/env" ]; then + . "$HOME/.cargo/env" + elif [ -d "$HOME/.cargo/bin" ]; then + export PATH="$HOME/.cargo/bin:$PATH" + fi + if ! command -v cargo >/dev/null 2>&1; then + echo "⚠️ cargo not found on PATH; skipping cargo fmt." >&2 + echo " Install rustup (https://rustup.rs) or add ~/.cargo/bin to your shell's PATH." >&2 + else + cargo fmt + git add $( echo "$STAGED_FILES" | grep '\.rs$' ) + fi +fi + +echo "✅ Pre-commit checks passed (basic validation only)." +echo " Full checks will run on git push." diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 000000000..1bc7c2bb3 --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-3.0-only +# Copyright (C) 2026 NeuroSkill.com +# +# Pre-push gate: scoped by default, full mode with PREPUSH_FULL=1. + +set -euo pipefail + +FULL_MODE="${PREPUSH_FULL:-0}" +CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)" + +# Speed up local Rust builds if sccache is available. +if command -v sccache >/dev/null 2>&1; then + export RUSTC_WRAPPER=sccache + export CMAKE_C_COMPILER_LAUNCHER=sccache + export CMAKE_CXX_COMPILER_LAUNCHER=sccache +fi + +if [ "$FULL_MODE" = "1" ]; then + echo "🔎 Pre-push: full mode enabled (PREPUSH_FULL=1)" + + echo "🔎 Pre-push: changelog fragments" + if git rev-parse --verify HEAD~1 >/dev/null 2>&1; then + node scripts/check-changelog-fragments.js --base "HEAD~1" --head HEAD + else + node scripts/check-changelog-fragments.js --quiet + fi + + echo "🔎 Pre-push: frontend checks" + npm run -s check + npm test + + echo "🔎 Pre-push: cargo deny" + cargo deny check -A no-license-field -A parse-error -A license-not-encountered + + echo "🔎 Pre-push: Rust clippy" + cargo clippy --locked --workspace --exclude skill -- -D warnings + cargo clippy -p skill --locked -- -D warnings + + echo "🔎 Pre-push: Rust tests (all tiers)" + bash scripts/test-fast.sh --all + + echo "✅ Pre-push checks passed (full mode)." + exit 0 +fi + +BASE_REF="" +if git rev-parse --abbrev-ref --symbolic-full-name "@{upstream}" >/dev/null 2>&1; then + BASE_REF="$(git merge-base HEAD "@{upstream}")" +elif git rev-parse --verify HEAD~1 >/dev/null 2>&1; then + BASE_REF="HEAD~1" +else + BASE_REF="HEAD" +fi + +CHANGED_FILES="$(git diff --name-only --diff-filter=d "$BASE_REF...HEAD" 2>/dev/null || true)" + +if [ -z "$CHANGED_FILES" ]; then + echo "✅ Pre-push checks passed (no changed files detected)." + exit 0 +fi + +NON_DOC_CHANGED="$(echo "$CHANGED_FILES" | grep -Ev '^(README\.md|CHANGELOG\.md|CONTRIBUTING\.md|AGENTS\.md|TODO\.md|changes/|docs/|\.github/)' || true)" +if [ -z "$NON_DOC_CHANGED" ]; then + echo "✅ Pre-push checks passed (docs/changelog-only changes)." + exit 0 +fi + +echo "🔎 Pre-push: scoped mode on branch '$CURRENT_BRANCH'" +echo "Changed files baseline: $BASE_REF" + +echo "🔎 Pre-push: changelog fragments" +node scripts/check-changelog-fragments.js --base "$BASE_REF" --head HEAD + +FRONTEND_CHANGED="$(echo "$CHANGED_FILES" | grep -E '^(src/|scripts/|package.json|package-lock.json|svelte.config.js|vite.config.js|tsconfig.json|biome.json)' || true)" +if [ -n "$FRONTEND_CHANGED" ]; then + echo "🔎 Pre-push: frontend checks (changed files)" + + FRONTEND_BIOME_FILES="$(echo "$FRONTEND_CHANGED" | grep -E '\.(ts|js|svelte|json)$' || true)" + if [ -n "$FRONTEND_BIOME_FILES" ]; then + # shellcheck disable=SC2086 + npx biome check --no-errors-on-unmatched $FRONTEND_BIOME_FILES + fi + + VITEST_RELATED_FILES="$(echo "$FRONTEND_CHANGED" | grep -E '^src/.*\.(ts|js|svelte)$' || true)" + if [ -n "$VITEST_RELATED_FILES" ]; then + # shellcheck disable=SC2086 + npx vitest related --run --exclude 'src/tests/*-e2e*' $VITEST_RELATED_FILES + fi + + # Guard/meta tests read source files via fs, not import — vitest related + # can't detect the dependency. Run them when daemon or scripts change. + DAEMON_OR_SCRIPTS="$(echo "$FRONTEND_CHANGED" | grep -E '^(src/lib/daemon/|scripts/)' || true)" + if [ -n "$DAEMON_OR_SCRIPTS" ]; then + npx vitest run src/tests/daemon-client.test.ts + fi +fi + +# CI script self-test — catches broken commands before they hit CI +CI_CHANGED="$(echo "$CHANGED_FILES" | grep -E '^scripts/ci\.mjs$|^\.github/workflows/' || true)" +if [ -n "$CI_CHANGED" ]; then + echo "🔎 Pre-push: CI script self-test" + node scripts/ci.mjs self-test +fi + +RUST_CHANGED="$(echo "$CHANGED_FILES" | grep -E '\.rs$|^Cargo.toml$|^Cargo.lock$|^src-tauri/Cargo.toml$|^crates/.*/Cargo.toml$' || true)" +if [ -n "$RUST_CHANGED" ]; then + echo "🔎 Pre-push: Rust checks (changed crates)" + cargo deny check -A no-license-field -A parse-error -A license-not-encountered + cargo fmt --check + + declare -a CRATES=() + NEED_SKILL=0 + + while IFS= read -r f; do + [ -z "$f" ] && continue + case "$f" in + crates/*) + crate_dir="${f#crates/}" + crate_name="${crate_dir%%/*}" + if [[ "$crate_name" == skill-* ]]; then + CRATES+=("-p" "$crate_name") + fi + ;; + src-tauri/*) + NEED_SKILL=1 + ;; + Cargo.toml|Cargo.lock) + NEED_SKILL=1 + ;; + esac + done <<< "$CHANGED_FILES" + + # Deduplicate crate flags (bash 3 compatible) + if [ "${#CRATES[@]}" -gt 0 ]; then + uniq_pairs="$(printf '%s\n' "${CRATES[@]}" | awk 'NR%2{p=$0;next}{print p" "$0}' | sort -u)" + CRATES=() + while IFS= read -r pair; do + [ -z "$pair" ] && continue + CRATES+=("${pair%% *}" "${pair#* }") + done <- + Force the rlx and rlx-models git dependencies to the tip of `main` before the + build, so every GitHub build compiles the latest upstream — not the commit + pinned in Cargo.lock. Cargo.lock pins are a moving target here (skill and rlx + are co-developed); this makes "latest main" the CI contract. Run AFTER the Rust + toolchain + cache are set up and BEFORE any `--locked` cargo invocation (this + step rewrites Cargo.lock; the subsequent `--locked` build then reads the fresh + lock). No-op safe: if main is unchanged the update is a fast metadata refresh. + +runs: + using: composite + steps: + - name: Refresh rlx + rlx-models to tip of main + shell: bash + run: | + set -euo pipefail + echo "::group::Refresh rlx + rlx-models to tip of main" + before="$(grep -oE 'MIT-RLX/rlx(-models)?\.git[^\"]*#[0-9a-f]{40}' Cargo.lock | sort -u || true)" + # Both crates come from a single git source each; updating one package per + # source advances that whole source (url+branch) to the latest main commit. + # Also reconciles any workspace [patch] additions into Cargo.lock so the + # following `--locked` build stays consistent. + cargo update -p rlx -p rlx-models-core + echo "rlx / rlx-models git commits now pinned in Cargo.lock:" + grep -oE 'MIT-RLX/rlx(-models)?\.git[^\"]*#[0-9a-f]{40}' Cargo.lock | sort -u || true + if [ "$before" != "$(grep -oE 'MIT-RLX/rlx(-models)?\.git[^\"]*#[0-9a-f]{40}' Cargo.lock | sort -u || true)" ]; then + echo "::notice::rlx / rlx-models advanced to newer main commit(s)." + fi + echo "::endgroup::" diff --git a/.github/actions/release-setup/action.yml b/.github/actions/release-setup/action.yml new file mode 100644 index 000000000..3d9755798 --- /dev/null +++ b/.github/actions/release-setup/action.yml @@ -0,0 +1,167 @@ +name: Release setup +description: >- + Shared release-job bootstrap: Rust toolchain + Swatinem/rust-cache (sole CI + compile cache — do not re-add sccache here), Node 22 + npm ci, optional + frontend build, and platform hooks (mac tools, Windows protoc, Linux + Vulkan/apt/bootstrap). + +inputs: + target: + description: Rust target triple to install (e.g. aarch64-apple-darwin). + required: true + cache-key: + description: Optional Swatinem/rust-cache shared-key (platform-specific). + required: false + default: "" + cache-save-if: + description: Passed to rust-cache save-if (use "always()" on Windows). + required: false + default: "true" + skip-rust-toolchain: + description: >- + When "true", skip dtolnay/rust-toolchain. Implied when linux-deps is true + (uses setup-rust-bootstrap-linux instead). + required: false + default: "false" + macos-tools: + description: When "true", brew-install protobuf + GNU ar and export AR. + required: false + default: "false" + windows-protoc: + description: When "true", install protoc via scripts/ci.mjs. + required: false + default: "false" + linux-deps: + description: >- + When "true", install Vulkan apt repo + Tauri/system apt packages, then + run setup-rust-bootstrap-linux (skips dtolnay/rust-toolchain). + required: false + default: "false" + linux-apt-version: + description: cache-apt-pkgs-action version key (bump to bust apt cache). + required: false + default: tauri-release-linux-v7 + linux-vulkan-packages: + description: >- + Vulkan apt packages to install. Defaults to LunarG's `vulkan-sdk`, which + is only published for amd64 — an arm64 runner must pass + "libvulkan-dev libvulkan1" instead (Ubuntu's own loader + headers, which + is the same fallback scripts/install-vulkan-sdk.sh already uses when the + LunarG repo has nothing installable). + required: false + default: vulkan-sdk + build-frontend: + description: When "true", run npm run build + verify:tauri:frontend after npm ci. + required: false + default: "false" + +runs: + using: composite + steps: + # ── Linux: Vulkan repo + apt (before Rust) ──────────────────────────────── + # Only add LunarG's apt repo when we actually intend to install from it. + # LunarG publishes amd64 only, so on an arm64 runner the repo contributes + # nothing and just makes `apt-get update` complain about a missing index. + - name: Setup Vulkan SDK repository + if: inputs.linux-deps == 'true' && inputs.linux-vulkan-packages == 'vulkan-sdk' + uses: ./.github/actions/setup-vulkan-linux + + # NOTE: `packages:` below is a YAML folded scalar — every line in it is + # literal text handed to apt, so a `#` does NOT start a comment there. Put + # explanations here instead; a comment inside the block becomes a package + # name and the step fails with `Unable to locate package #`. + # + # `rpm` — rpmbuild, for the .rpm in package-linux-system-bundles.sh. + # `dpkg-dev` — dpkg-shlibdeps, which derives the .deb Depends from the + # shipped ELFs. Present via build-essential on the hosted + # runners; named explicitly so a slimmer image cannot silently + # fall back to the incomplete hand-written dependency list. + - name: Cache + install system dependencies (Linux) + if: inputs.linux-deps == 'true' + uses: awalsh128/cache-apt-pkgs-action@v1 + with: + packages: >- + libwebkit2gtk-4.1-dev + libappindicator3-dev + librsvg2-dev + patchelf + libssl-dev + libudev-dev + libdbus-1-dev + pkg-config + libasound2-dev + libpipewire-0.3-dev + libgbm-dev + cmake + binutils + rpm + dpkg-dev + mold + clang + protobuf-compiler + libopenblas-dev + ${{ inputs.linux-vulkan-packages }} + version: ${{ inputs.linux-apt-version }} + + - name: Setup Rust bootstrap (Linux) + if: inputs.linux-deps == 'true' + uses: ./.github/actions/setup-rust-bootstrap-linux + with: + targets: ${{ inputs.target }} + + - name: Install Rust stable + if: inputs.linux-deps != 'true' && inputs.skip-rust-toolchain != 'true' + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ inputs.target }} + + # Sole CI compile cache. Local builds may still use sccache via tauri-build.js; + # do not add Mozilla sccache-action here (dual caches thrash and hide misses). + - name: Cache Cargo registry + build artifacts + uses: Swatinem/rust-cache@v2.9.1 + with: + workspaces: ". -> src-tauri/target" + shared-key: ${{ inputs.cache-key }} + save-if: ${{ inputs.cache-save-if }} + cache-targets: false + cache-on-failure: false + + # Always build the latest rlx / rlx-models main (rewrites Cargo.lock pins). + # Must run before the release job's `cargo tauri build --locked`. + - name: Refresh rlx to main + uses: ./.github/actions/refresh-rlx-main + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: "22" + cache: npm + + - name: Update npm to latest + shell: bash + run: npm install -g npm@latest + + - name: Install JS dependencies + shell: bash + run: npm ci + + - name: Install protoc + GNU ar (macOS) + if: inputs.macos-tools == 'true' + shell: bash + run: | + brew install protobuf binutils + # Use GNU ar to suppress "illegal option -- D" from Apple's system ar + echo "AR=$(brew --prefix binutils)/bin/gar" >> "$GITHUB_ENV" + + - name: Install protoc (Windows) + if: inputs.windows-protoc == 'true' + shell: bash + run: node scripts/ci.mjs install-protoc-windows + + - name: Build frontend + if: inputs.build-frontend == 'true' + shell: bash + run: | + set -euo pipefail + npm run build + npm run -s verify:tauri:frontend diff --git a/.github/actions/setup-rust-bootstrap-linux/action.yml b/.github/actions/setup-rust-bootstrap-linux/action.yml new file mode 100644 index 000000000..7f55f0e37 --- /dev/null +++ b/.github/actions/setup-rust-bootstrap-linux/action.yml @@ -0,0 +1,21 @@ +name: Setup Rust bootstrap (Linux) +description: Install Rust toolchain for Linux jobs (rust-cache is configured by the caller). + +inputs: + targets: + description: Optional rustup targets (comma-separated or single target) + required: false + default: "" + +runs: + using: composite + steps: + - name: Install Rust stable (default target) + if: inputs.targets == '' + uses: dtolnay/rust-toolchain@stable + + - name: Install Rust stable (custom targets) + if: inputs.targets != '' + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ inputs.targets }} diff --git a/.github/actions/setup-vulkan-linux/action.yml b/.github/actions/setup-vulkan-linux/action.yml new file mode 100644 index 000000000..790fe7e36 --- /dev/null +++ b/.github/actions/setup-vulkan-linux/action.yml @@ -0,0 +1,22 @@ +name: Setup Vulkan SDK (Linux) +description: Add LunarG apt repository for Vulkan SDK availability + +runs: + using: composite + steps: + # Add the LunarG apt repository so vulkan-sdk is available to apt. + # The actual package install is handled by the caller's cache-apt-pkgs-action + # step (vulkan-sdk must be included in that step's package list). + - name: Add LunarG Vulkan apt repository + shell: bash + run: | + set -eo pipefail + CODENAME="$(lsb_release -cs 2>/dev/null || (source /etc/os-release && echo "${VERSION_CODENAME:-noble}"))" + if [[ ! -f "/etc/apt/sources.list.d/lunarg-vulkan-${CODENAME}.list" ]]; then + curl -fsSL https://packages.lunarg.com/lunarg-signing-key-pub.asc \ + | sudo tee /etc/apt/trusted.gpg.d/lunarg.asc > /dev/null + sudo curl -fsSLo \ + "/etc/apt/sources.list.d/lunarg-vulkan-${CODENAME}.list" \ + "https://packages.lunarg.com/vulkan/lunarg-vulkan-${CODENAME}.list" + sudo apt-get update -y -q + fi diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..f6c3fc069 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,43 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: "/" + schedule: + interval: weekly + day: monday + time: "06:00" + timezone: "UTC" + open-pull-requests-limit: 5 + commit-message: + prefix: "deps(npm)" + groups: + npm-all: + patterns: + - "*" + ignore: + # typescript is capped at 6.x: @sveltejs/kit's peer range is + # "^5.3.3 || ^6.0.0", so a major bump to 7.x breaks a fresh + # `npm install` with ERESOLVE (see issue #81, regression from #75). + # Drop this once kit widens its peerDependencies to include TS 7. + - dependency-name: "typescript" + update-types: ["version-update:semver-major"] + + - package-ecosystem: cargo + directory: "/" + schedule: + interval: weekly + day: monday + time: "06:15" + timezone: "UTC" + open-pull-requests-limit: 5 + commit-message: + prefix: "deps(cargo)" + groups: + cargo-all: + patterns: + - "*" + ignore: + # wgpu cannot be upgraded independently: zuna-rs and luna-rs require wgpu 26.x. + # Upgrade wgpu together with the encoder crates. + - dependency-name: "wgpu" + versions: [">=27"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index adb589609..566e57e64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,128 +7,1064 @@ on: branches: [main, develop] # Allow manual re-runs from the Actions tab on any branch. workflow_dispatch: + inputs: + run_all_tests: + description: "Run ALL crate tests (skip change detection)" + type: boolean + default: false + run_llm_e2e: + description: "Run LLM E2E integration test" + type: boolean + default: false + run_lsl_e2e: + description: "Run LSL E2E integration test (32-ch 256 Hz pipeline, ~30 s)" + type: boolean + default: false + run_coverage: + description: "Run Rust code coverage (cargo-llvm-cov, ~20 min)" + type: boolean + default: false + coverage_min_lines: + description: "Minimum line coverage percentage gate" + type: string + default: '55' + +# Cancel superseded runs on the same branch / PR — avoids wasting runner +# minutes when multiple commits are pushed in quick succession. +concurrency: + group: ci-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +# Opt into Node.js 24 now so we surface any action incompatibilities before +# GitHub forces the switch on June 2, 2026. +# ilammy/msvc-dev-cmd, KyleMayes/install-llvm-action and +# releases — update these pins when node24 builds ship. +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: + version-gate: + name: VERSION sync + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 1 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: "22" + + # Source of truth: repo-root VERSION. Fails if package.json / + # tauri.conf.json / src-tauri/Cargo.toml drift (bump.js keeps them synced). + - name: Verify VERSION matches derived version files + run: node scripts/ci.mjs resolve-version + + changelog-gate: + name: Changelog fragments gate + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22' + + - name: Validate changelog fragment format (if present) + run: | + node scripts/check-changelog-fragments.js \ + --base "${{ github.event.pull_request.base.sha }}" \ + --head "${{ github.event.pull_request.head.sha }}" + + hf-downloads-cache-gate: + name: HF downloads cache + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 1 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: "22" + + # Offline integrity check only (no Hub crawl on every PR). The weekly + # hf-downloads-cache workflow refreshes counts and opens a PR. + - name: Verify committed downloads cache covers the catalog + run: npm run sync:hf:downloads:check + # ── Rust backend ───────────────────────────────────────────────────────────── + # + # clippy is a strict superset of `cargo check` — it performs the same + # compilation AND runs lint passes. Running both wastes a full compile + # cycle (~3-5 min). We therefore run clippy only. + # + # `--workspace --exclude skill` covers library crates. Product GPU backends + # are OS umbrellas on skill-daemon (`apple` / `linux` / `windows`); PR CI + # keeps defaults / cpu leaves so we do not need CUDA toolkits here. + # Full Tauri + daemon OS builds run on release tags / Preview Build. + # + # mold (fast linker) is installed to match the release-linux workflow and + # shave 30-60 s off every link-heavy operation. rust-check: - name: Rust — cargo check + clippy + name: Rust — clippy + test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + # ── Free pre-installed software we don't use (~10 GB reclaimed) ────────── + # ubuntu-latest ships Android SDK, .NET, Swift, Haskell, CodeQL, large + # Docker layers, and mono — none of which this project needs. + # Without this, the Rust build cache restore + compile fills the ~14 GB + # runner disk and causes "No space left on device" failures. + - name: Free disk space + run: | + sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc /usr/local/.ghcup \ + /usr/local/share/powershell /usr/local/share/chromium /usr/share/swift \ + /opt/hostedtoolcache/CodeQL + sudo docker image prune -af 2>/dev/null || true + df -h / - - name: Install Rust stable - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@v6 + with: + fetch-depth: 1 + + - name: Fetch skills submodule + run: git submodule update --init --depth=1 skills - - name: Cache Cargo - uses: actions/cache@v4 + - name: Setup Rust bootstrap (Linux) + uses: ./.github/actions/setup-rust-bootstrap-linux + + # Swatinem/rust-cache gives smarter per-crate invalidation and automatic + # pruning vs manual actions/cache — matches the release workflows. + - name: Cache Cargo registry + build artifacts + uses: Swatinem/rust-cache@v2.9.1 with: - path: | - ~/.cargo/registry - ~/.cargo/git - src-tauri/target - key: linux-cargo-${{ hashFiles('src-tauri/Cargo.lock') }} - restore-keys: linux-cargo- + workspaces: ". -> src-tauri/target" + shared-key: linux-ci-x86_64-unknown-linux-gnu-v1 + cache-targets: false + cache-on-failure: false + + - name: Setup Vulkan SDK repository + uses: ./.github/actions/setup-vulkan-linux - - name: Cache apt packages - uses: actions/cache@v4 + - name: Cache + install Tauri system deps (incl. mold + clang) + uses: awalsh128/cache-apt-pkgs-action@v1 with: - path: /var/cache/apt/archives - # Bust the cache when the package list changes (workflow file edit). - key: apt-${{ runner.os }}-tauri-${{ hashFiles('.github/workflows/ci.yml') }} - restore-keys: apt-${{ runner.os }}-tauri- + packages: >- + libwebkit2gtk-4.1-dev libappindicator3-dev + librsvg2-dev patchelf libssl-dev + libudev-dev libdbus-1-dev pkg-config + libespeak-ng-dev libasound2-dev + libpipewire-0.3-dev libgbm-dev + cmake binutils mold clang + protobuf-compiler libopenblas-dev + vulkan-sdk + version: tauri-rust-check-v7 + + # Always lint/test against the latest rlx / rlx-models main (rewrites + # Cargo.lock pins before the `--locked` clippy/test steps below). + - name: Refresh rlx to main + uses: ./.github/actions/refresh-rlx-main - - name: Install Tauri system deps + # clippy (workspace crates — no Tauri app / espeak link) + - name: cargo clippy (workspace crates) + env: + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: clang + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: -C link-arg=-fuse-ld=mold + shell: bash run: | - sudo apt-get update -y - sudo apt-get install -y \ - libwebkit2gtk-4.1-dev libappindicator3-dev \ - librsvg2-dev patchelf libssl-dev \ - libudev-dev libdbus-1-dev pkg-config \ - libespeak-ng-dev libasound2-dev \ - cmake binutils + set -euo pipefail + run_cmd() { + cargo clippy --locked --target x86_64-unknown-linux-gnu \ + --workspace --exclude skill --exclude skill-router \ + -- -D warnings + cargo clippy --locked --target x86_64-unknown-linux-gnu \ + --no-default-features -p skill-router --features cpu \ + -- -D warnings + } + run_cmd - - name: Cache espeak-ng static lib - id: cache-espeak - uses: actions/cache@v4 - with: - path: | - src-tauri/espeak-static - src-tauri/resources/espeak-ng-data - key: linux-x86_64-espeak-${{ hashFiles('scripts/build-espeak-static.sh') }} + # clippy (Tauri app crate — needs espeak; GPU LLM lives in skill-daemon) + - name: cargo clippy (app) + env: + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: clang + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: -C link-arg=-fuse-ld=mold + shell: bash + run: | + set -euo pipefail + run_cmd() { + cargo clippy -p skill --locked --target x86_64-unknown-linux-gnu -- -D warnings + } + run_cmd + + # Determine which crates need testing based on changed files. + # On PRs: compare against the base branch. + # On pushes to main/develop: compare against the previous commit. + # On manual dispatch: test everything. + - name: Determine changed crates + id: changed + run: | + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + BASE="${{ github.event.pull_request.base.sha }}" + elif [[ "${{ github.event_name }}" == "push" ]]; then + BASE="${{ github.event.before }}" + else + BASE="" + fi - - name: Build espeak-ng static lib - if: steps.cache-espeak.outputs.cache-hit != 'true' - run: bash scripts/build-espeak-static.sh + ALL_CRATE_FLAGS="-p skill-eeg -p skill-data -p skill-constants -p skill-tools -p skill-devices -p skill-settings -p skill-history -p skill-health -p skill-router -p skill-llm -p skill-autostart -p skill-tts -p skill-gpu -p skill-headless -p skill-label-index -p skill-skills -p skill-jobs -p skill-commands -p skill-exg" - - name: cargo check - working-directory: src-tauri - run: cargo check --locked + if [[ "${{ inputs.run_all_tests }}" == "true" ]]; then + # Manual dispatch with run_all_tests — test everything + CRATE_FLAGS="$ALL_CRATE_FLAGS" + elif [[ -z "$BASE" || "$BASE" == "0000000000000000000000000000000000000000" ]]; then + # First push or manual dispatch — test everything + CRATE_FLAGS="$ALL_CRATE_FLAGS" + else + CRATE_FLAGS=$(bash scripts/changed-crates.sh "$BASE") + fi - - name: cargo clippy - working-directory: src-tauri - run: cargo clippy --locked -- -D warnings + echo "crate_flags=$CRATE_FLAGS" >> "$GITHUB_OUTPUT" + if [[ -z "$CRATE_FLAGS" || "$CRATE_FLAGS" == "# "* ]]; then + echo "skip=true" >> "$GITHUB_OUTPUT" + echo "::notice::No testable crates affected — skipping cargo test" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + echo "::notice::Testing (unit): $CRATE_FLAGS" + fi + + # Filter for crates that have integration tests (tests/*.rs). + # skill-llm E2E is excluded — it's gated by manual dispatch. + INT_CRATES="skill-eeg skill-data skill-headless skill-jobs skill-history skill-settings skill-router skill-label-index skill-skills skill-tools skill-commands skill-llm" + INT_FLAGS="" + for c in $INT_CRATES; do + if echo "$CRATE_FLAGS" | grep -qw "\-p $c"; then + INT_FLAGS="$INT_FLAGS -p $c" + fi + done + INT_FLAGS="$(echo "$INT_FLAGS" | xargs)" # trim + + if [[ -n "$INT_FLAGS" ]]; then + echo "int_flags=$INT_FLAGS" >> "$GITHUB_OUTPUT" + echo "skip_int=false" >> "$GITHUB_OUTPUT" + echo "::notice::Testing (integration): $INT_FLAGS" + else + echo "int_flags=" >> "$GITHUB_OUTPUT" + echo "skip_int=true" >> "$GITHUB_OUTPUT" + echo "::notice::No integration-testable crates affected" + fi + + if echo "$CRATE_FLAGS" | grep -qw "\-p skill-router"; then + echo "test_router=true" >> "$GITHUB_OUTPUT" + else + echo "test_router=false" >> "$GITHUB_OUTPUT" + fi + + ROUTERLESS_FLAGS="$(echo "$CRATE_FLAGS" | sed 's/-p skill-router//g' | xargs || true)" + echo "routerless_flags=$ROUTERLESS_FLAGS" >> "$GITHUB_OUTPUT" + + ROUTERLESS_INT="$(echo "$INT_FLAGS" | sed 's/-p skill-router//g' | xargs || true)" + echo "routerless_int_flags=$ROUTERLESS_INT" >> "$GITHUB_OUTPUT" + + - name: cargo test (affected crates — unit) + id: rust_unit_tests + if: steps.changed.outputs.skip != 'true' + env: + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: clang + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: -C link-arg=-fuse-ld=mold + shell: bash + run: | + set -euo pipefail + start_ts="$(date +%s)" + run_cmd() { + local flags="$1" + [[ -z "$flags" ]] && return 0 + cargo test --locked --target x86_64-unknown-linux-gnu \ + $flags \ + --lib + } + run_router_cmd() { + cargo test --locked --target x86_64-unknown-linux-gnu \ + --no-default-features -p skill-router --features cpu \ + --lib + } + run_all() { + run_cmd "${{ steps.changed.outputs.routerless_flags }}" + if [[ "${{ steps.changed.outputs.test_router }}" == "true" ]]; then + run_router_cmd + fi + } + run_all + end_ts="$(date +%s)" + echo "duration_sec=$((end_ts - start_ts))" >> "$GITHUB_OUTPUT" + + - name: cargo test (affected crates — integration) + id: rust_integration_tests + if: steps.changed.outputs.skip_int != 'true' + env: + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: clang + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: -C link-arg=-fuse-ld=mold + shell: bash + run: | + set -euo pipefail + start_ts="$(date +%s)" + run_cmd() { + local flags="$1" + [[ -z "$flags" ]] && return 0 + cargo test --locked --target x86_64-unknown-linux-gnu \ + $flags \ + --test '*' + } + run_router_cmd() { + cargo test --locked --target x86_64-unknown-linux-gnu \ + --no-default-features -p skill-router --features cpu \ + --test '*' + } + run_all() { + run_cmd "${{ steps.changed.outputs.routerless_int_flags }}" + if [[ "${{ steps.changed.outputs.test_router }}" == "true" ]]; then + run_router_cmd + fi + } + run_all + end_ts="$(date +%s)" + echo "duration_sec=$((end_ts - start_ts))" >> "$GITHUB_OUTPUT" + + - name: Collect Rust health metrics + if: always() + shell: bash + run: | + set -euo pipefail + mkdir -p ci-metrics + UNIT_SEC="${{ steps.rust_unit_tests.outputs.duration_sec || '0' }}" + INT_SEC="${{ steps.rust_integration_tests.outputs.duration_sec || '0' }}" + + { + echo '{' + echo ' "job": "rust-check",' + echo ' "unit_test_duration_sec":' "$UNIT_SEC" ',' + echo ' "integration_test_duration_sec":' "$INT_SEC" + echo '}' + } > ci-metrics/rust-check.json + + { + echo "## Rust health metrics" + echo "- Unit test duration: ${UNIT_SEC}s" + echo "- Integration test duration: ${INT_SEC}s" + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload Rust health metrics + if: always() + uses: actions/upload-artifact@v7 + with: + name: ci-health-rust-check + path: ci-metrics/ + retention-days: 30 # ── Frontend ───────────────────────────────────────────────────────────────── frontend-check: name: Frontend — check + i18n + tests runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 + with: + fetch-depth: 1 + submodules: recursive - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '20' + node-version: '22' cache: 'npm' + - name: Update npm to latest + run: npm install -g npm@latest + + - name: Cache node_modules + id: cache-node-modules + uses: actions/cache@v5 + with: + path: node_modules + key: node-modules-${{ runner.os }}-${{ hashFiles('package-lock.json') }} + + # Retry handles transient ETXTBSY from esbuild postinstall on Linux. - name: Install dependencies - run: npm ci + if: steps.cache-node-modules.outputs.cache-hit != 'true' + run: npm ci || npm ci # Run i18n sync first — it's cheap and catches missing translation keys # before the slower svelte-check. - name: i18n sync check run: npm run sync:i18n:check + - name: i18n audit check + run: npm run audit:i18n:check + + - name: i18n locales check (all non-en locales) + run: npm run -s check:i18n:locales + + # Biome — format check (fast, ~100ms; lint is advisory via warnings) + - name: Biome format check + run: npx biome format src/ scripts/ + + - name: Biome lint + run: npx biome lint src/ scripts/ + + - name: Daemon thin-client invoke guard + run: npm run -s check:daemon-invokes + - name: svelte-check - run: npm run check + id: frontend_svelte_check + shell: bash + run: | + set -euo pipefail + start_ts="$(date +%s)" + npm run check + end_ts="$(date +%s)" + echo "duration_sec=$((end_ts - start_ts))" >> "$GITHUB_OUTPUT" - name: Unit tests - run: npm test + id: frontend_unit_tests + shell: bash + run: | + set -euo pipefail + start_ts="$(date +%s)" + npm test + end_ts="$(date +%s)" + echo "duration_sec=$((end_ts - start_ts))" >> "$GITHUB_OUTPUT" + + - name: Collect Frontend health metrics + if: always() + shell: bash + run: | + set -euo pipefail + mkdir -p ci-metrics + CHECK_SEC="${{ steps.frontend_svelte_check.outputs.duration_sec || '0' }}" + TEST_SEC="${{ steps.frontend_unit_tests.outputs.duration_sec || '0' }}" + + { + echo '{' + echo ' "job": "frontend-check",' + echo ' "svelte_check_duration_sec":' "$CHECK_SEC" ',' + echo ' "unit_test_duration_sec":' "$TEST_SEC" + echo '}' + } > ci-metrics/frontend-check.json + + { + echo "## Frontend health metrics" + echo "- svelte-check duration: ${CHECK_SEC}s" + echo "- Unit test duration: ${TEST_SEC}s" + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload Frontend health metrics + if: always() + uses: actions/upload-artifact@v7 + with: + name: ci-health-frontend-check + path: ci-metrics/ + retention-days: 30 + + # ── Windows Rust backend ───────────────────────────────────────────────────── + # + # Same optimisation as Linux: clippy replaces check (superset), removing + # a redundant full compilation pass. + windows-check: + name: Windows — cargo clippy + runs-on: windows-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 1 + + - name: Validate Windows app manifest + shell: bash + run: node scripts/check-windows-manifest.mjs src-tauri/manifest.xml + + # Puts cl.exe, link.exe, lib.exe, and signtool.exe in PATH. + # Required for the espeak-ng static library build (lib.exe). + - name: Set up MSVC developer environment + uses: ilammy/msvc-dev-cmd@v1 + + - name: Validate Windows app manifest with mt.exe + shell: powershell + run: | + $mt = Get-Command mt.exe -ErrorAction SilentlyContinue + if (-not $mt) { + Write-Error "mt.exe not found in PATH" + exit 1 + } + & $mt.Source -nologo -manifest src-tauri/manifest.xml -validate_manifest + if ($LASTEXITCODE -ne 0) { + Write-Error "mt.exe manifest validation failed" + exit $LASTEXITCODE + } + Write-Host "[ok] mt.exe manifest validation passed" + + # Git for Windows ships a Unix hard-link utility at + # C:\Program Files\Git\usr\bin prepends two problematic binaries to PATH + # at every step (GITHUB_PATH is re-applied each step so runtime deletions + # don't survive, but file-level deletions do): + # + # link.exe — silently shadows MSVC's linker → build picks wrong linker + # tar.exe — GNU tar used by actions/cache post-cleanup with --posix, + # which restricts filenames to POSIX-portable characters and + # corrupts / drops Windows paths in the Rust build cache. + # Removing it forces actions/cache to use the built-in + # C:\Windows\System32\tar.exe (bsdtar 3.x) instead. + - name: Remove Git's link.exe and tar.exe (shadow MSVC linker / corrupt cache) + shell: powershell + run: | + foreach ($bin in @("link.exe", "tar.exe")) { + $path = "C:\Program Files\Git\usr\bin\$bin" + if (Test-Path $path) { + Remove-Item $path -Force + Write-Host "[ok] Deleted $path" + } else { + Write-Host "[skip] $path not found" + } + } + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-pc-windows-msvc + + # Swatinem/rust-cache is used here (matching the release workflow) for + # smarter per-crate invalidation vs the manual actions/cache approach. + - name: Cache Cargo registry + build artifacts + uses: Swatinem/rust-cache@v2.9.1 + with: + workspaces: ". -> src-tauri/target" + shared-key: windows-ci-x86_64-pc-windows-msvc-v1 + cache-targets: false + cache-on-failure: false + + # Always lint against the latest rlx / rlx-models main (rewrites Cargo.lock + # pins before the `--locked` clippy step below). + - name: Refresh rlx to main + uses: ./.github/actions/refresh-rlx-main + + - name: Install protoc (Windows) + run: node scripts/ci.mjs install-protoc-windows + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22' + cache: 'npm' + + - name: Update npm to latest + run: npm install -g npm@latest + + # Retry handles transient ETXTBSY from esbuild postinstall on Linux. + - name: Install JS dependencies + run: npm ci || npm ci + + # Catch PowerShell encoding bugs (e.g. UTF-8 multi-byte sequences that + # Windows-1252-reading PS5.1 misinterprets as string delimiters) before + # they break the build. Uses the PS parser directly — no execution needed. + - name: Validate PowerShell scripts (syntax check) + shell: powershell + run: | + $failed = $false + Get-ChildItem scripts\*.ps1 | ForEach-Object { + $parseErrors = $null + $null = [System.Management.Automation.Language.Parser]::ParseFile( + $_.FullName, [ref]$null, [ref]$parseErrors + ) + if ($parseErrors.Count -gt 0) { + Write-Host "::error file=$($_.Name)::$($parseErrors.Count) parse error(s):" + $parseErrors | ForEach-Object { Write-Host " $_" } + $failed = $true + } else { + Write-Host "[ok] $($_.Name)" + } + } + if ($failed) { exit 1 } + Write-Host "[ok] All PowerShell scripts parse cleanly." + + # ── Standalone LLVM 19 for bindgen ────────────────────────────────────── + # VS2022's bundled Clang 19 is an MSVC-compat build (clang-cl) whose + # mmintrin.h references __builtin_ia32_* GCC-style intrinsics that + # clang-cl doesn't implement, causing bindgen to panic + # with ~20 "use of undeclared identifier '__builtin_ia32_…'" errors. + # + # Fix: standalone LLVM 19 (non-MSVC-compat) implements all those builtins + # AND its headers match the same version VS2022 ships, so there is no + # version mismatch. KyleMayes/install-llvm-action downloads directly + # from the LLVM GitHub releases (no Chocolatey dependency), has its own + # built-in cache keyed on the version string, and installs to C:\LLVM + # (a path without spaces, which avoids quoting issues in env vars). + # + # clang-sys checks LIBCLANG_PATH before PATH, so the explicit export + # below guarantees bindgen finds standalone LLVM's libclang.dll even + # when ilammy/msvc-dev-cmd has already put VS's clang-cl on PATH. + - name: Install LLVM 19 + uses: KyleMayes/install-llvm-action@v2.0.9 + with: + version: "19" + directory: C:\LLVM + + - name: Export LIBCLANG_PATH + shell: powershell + run: | + # KyleMayes/install-llvm-action sets LLVM_PATH to the install root. + # libclang.dll lives in the bin/ subdirectory for Windows builds + # downloaded from LLVM GitHub releases. + $dll = Get-ChildItem "C:\LLVM" -Recurse -Filter "libclang.dll" ` + -ErrorAction SilentlyContinue | + Select-Object -First 1 + if (-not $dll) { + Write-Error "libclang.dll not found under C:\LLVM" + exit 1 + } + $dir = Split-Path $dll.FullName + "LIBCLANG_PATH=$dir" | + Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + Write-Host "[ok] LIBCLANG_PATH=$dir (libclang.dll at $($dll.FullName))" + + # Also set LLVM_CONFIG to help with toolchain detection + "LLVM_CONFIG=C:\LLVM\bin\llvm-config.exe" | + Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + Write-Host "[ok] LLVM_CONFIG=C:\LLVM\bin\llvm-config.exe" + + # clippy replaces check — clippy performs the same compilation plus lint + # passes, so running both wastes a full compile cycle. + # Only the Tauri UI crate (`skill`) — daemon GPU backends are exercised on + # release/`windows` builds, not PR CI (avoids CUDA + Vulkan SDK here). + - name: cargo clippy + shell: bash + env: + LIBCLANG_PATH: ${{ env.LIBCLANG_PATH }} + LLVM_CONFIG: ${{ env.LLVM_CONFIG }} + run: | + set -euo pipefail + run_cmd() { + cargo clippy -p skill \ + --target x86_64-pc-windows-msvc \ + -- -D warnings + } + run_cmd + + llm-e2e: + name: LLM — E2E integration test + runs-on: ubuntu-latest + # Run after rust-check so we reuse its cached build artifacts (clippy + # already compiled the full workspace including skill-llm). This + # turns the E2E compilation into a cheap incremental build (~30 s) + # instead of a full rebuild (~3-5 min). + needs: rust-check + # Optional — only runs on manual dispatch with run_llm_e2e checked. + if: github.event_name == 'workflow_dispatch' && inputs.run_llm_e2e == true + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 1 + + - name: Setup Rust bootstrap (Linux) + uses: ./.github/actions/setup-rust-bootstrap-linux + + # Reuse the same cache bucket as rust-check (default shared-key) + # so the E2E job picks up clippy's already-compiled artifacts. + # save-if: false — only rust-check writes to this cache. + - name: Cache Cargo registry + build artifacts + uses: Swatinem/rust-cache@v2.9.1 + with: + workspaces: ". -> src-tauri/target" + shared-key: linux-ci-x86_64-unknown-linux-gnu-v1 + save-if: false + cache-targets: false + cache-on-failure: false + + - name: Setup Vulkan SDK repository + uses: ./.github/actions/setup-vulkan-linux + + # Install all system deps in one cached step (includes mold + clang + # to match rust-check's linker and reuse its build artifacts). + - name: Cache + install system deps + uses: awalsh128/cache-apt-pkgs-action@v1 + with: + packages: >- + libssl-dev pkg-config cmake binutils + libespeak-ng-dev libasound2-dev + libpipewire-0.3-dev mold clang + protobuf-compiler libopenblas-dev + vulkan-sdk + version: llm-e2e-v6 + + - name: Cache HuggingFace model + uses: actions/cache@v5 + with: + path: ~/.cache/huggingface + key: hf-llm-e2e-v1 + + # Latest rlx / rlx-models main + reconcile Cargo.lock before the --locked test. + - name: Refresh rlx to main + uses: ./.github/actions/refresh-rlx-main + + - name: Run LLM E2E test + env: + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: clang + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: -C link-arg=-fuse-ld=mold + run: | + cargo test -p skill-llm --locked --target x86_64-unknown-linux-gnu \ + --features llm \ + --test llm_e2e -- --nocapture 2>&1 | tee llm-e2e-report.txt + # Propagate the test exit code (tee masks it). + exit ${PIPESTATUS[0]} + + - name: Write Job Summary + if: always() + run: | + FILE=llm-e2e-report.txt + [ -f "$FILE" ] || { echo "No report file found" >> "$GITHUB_STEP_SUMMARY"; exit 0; } + + echo "## LLM E2E Integration Test" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + + # Extract model info + MODEL=$(grep -oP '(?<=Model: )\S+' "$FILE" | head -1 || true) + TOTAL=$(grep 'TOTAL' "$FILE" | grep -oP '[0-9]+\.[0-9]+s' | head -1 || true) + if [ -n "$MODEL" ]; then + echo "**Model:** \`$MODEL\` **Total:** \`$TOTAL\`" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + fi + + # Extract the report box (between the two ╔ headers) and format as + # a fenced code block — preserves the box-drawing layout perfectly. + echo "### Report" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + # Grab everything from "E2E INTEGRATION TEST REPORT" to the closing ╚ + sed -n '/E2E INTEGRATION TEST REPORT/,/^╚/p' "$FILE" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + + # Verdict + if grep -q 'ALL CHECKS PASSED' "$FILE"; then + echo "### Result: ALL CHECKS PASSED ✅" >> "$GITHUB_STEP_SUMMARY" + else + echo "### Result: SOME CHECKS FAILED ❌" >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Upload E2E report + if: always() + uses: actions/upload-artifact@v7 + with: + name: llm-e2e-report + path: llm-e2e-report.txt + retention-days: 30 + + # ── LSL end-to-end integration test ─────────────────────────────────────────── + lsl-e2e: + name: LSL — E2E integration test + runs-on: ubuntu-latest + needs: rust-check + # Optional — only runs on manual dispatch with run_lsl_e2e checked. + if: github.event_name == 'workflow_dispatch' && inputs.run_lsl_e2e == true + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 1 + + - name: Setup Rust bootstrap (Linux) + uses: ./.github/actions/setup-rust-bootstrap-linux + + # Reuse rust-check's cached artifacts (save-if: false — only rust-check writes). + - name: Cache Cargo registry + build artifacts + uses: Swatinem/rust-cache@v2.9.1 + with: + workspaces: ". -> src-tauri/target" + shared-key: linux-ci-x86_64-unknown-linux-gnu-v1 + save-if: false + cache-targets: false + cache-on-failure: false + + # rlsl is pure Rust (no liblsl system dependency) so only the minimal + # set of packages is needed: mold+clang for the fast linker, and the + # basic dev headers shared with the workspace build. + - name: Cache + install system deps + uses: awalsh128/cache-apt-pkgs-action@v1 + with: + packages: >- + libssl-dev pkg-config cmake binutils + libespeak-ng-dev libasound2-dev + libpipewire-0.3-dev mold clang + protobuf-compiler libopenblas-dev + version: lsl-e2e-v2 + + # Latest rlx / rlx-models main + reconcile Cargo.lock before the --locked test. + - name: Refresh rlx to main + uses: ./.github/actions/refresh-rlx-main + + - name: Run LSL E2E test + env: + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: clang + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: -C link-arg=-fuse-ld=mold + run: | + cargo test -p skill-lsl --locked --target x86_64-unknown-linux-gnu \ + --test lsl_e2e -- --nocapture 2>&1 | tee lsl-e2e-report.txt + exit ${PIPESTATUS[0]} + + - name: Write Job Summary + if: always() + run: | + FILE=lsl-e2e-report.txt + [ -f "$FILE" ] || { echo "No report file found" >> "$GITHUB_STEP_SUMMARY"; exit 0; } + + echo "## LSL E2E Integration Test" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + + echo '### Report' >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + sed -n '/LSL E2E INTEGRATION TEST REPORT/,/^╔/{ /^╔/{ p; d }; p }' "$FILE" \ + || sed -n '/LSL E2E INTEGRATION TEST REPORT/,/^╚/p' "$FILE" \ + >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + + if grep -q 'ALL STEPS PASSED' "$FILE"; then + echo "### Result: ALL STEPS PASSED ✅" >> "$GITHUB_STEP_SUMMARY" + else + echo "### Result: SOME STEPS FAILED ❌" >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Upload E2E report + if: always() + uses: actions/upload-artifact@v7 + with: + name: lsl-e2e-report + path: lsl-e2e-report.txt + retention-days: 30 + + # ── Rust code coverage ──────────────────────────────────────────────────────── + coverage: + name: Rust — code coverage + runs-on: ubuntu-latest + needs: rust-check + timeout-minutes: 90 + if: github.event_name == 'workflow_dispatch' && inputs.run_coverage == true + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 1 + + - name: Fetch skills submodule + run: git submodule update --init --depth=1 skills + + - name: Free disk space + run: node scripts/ci.mjs free-disk-space + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache Cargo registry + build artifacts + uses: Swatinem/rust-cache@v2.9.1 + with: + workspaces: ". -> src-tauri/target" + shared-key: linux-ci-x86_64-unknown-linux-gnu-v1 + save-if: false + cache-targets: false + cache-on-failure: false + + - name: Setup Vulkan SDK repository + uses: ./.github/actions/setup-vulkan-linux + + - name: Cache + install Tauri system deps (incl. mold + clang + Vulkan) + uses: awalsh128/cache-apt-pkgs-action@v1 + with: + packages: >- + libwebkit2gtk-4.1-dev libappindicator3-dev + librsvg2-dev patchelf libssl-dev + libudev-dev libdbus-1-dev pkg-config + libespeak-ng-dev libasound2-dev + libpipewire-0.3-dev libgbm-dev + cmake binutils mold clang + protobuf-compiler libopenblas-dev + vulkan-sdk + version: tauri-rust-check-v7 + + - name: Install cargo-llvm-cov + run: cargo install cargo-llvm-cov --locked + + # Cover the latest rlx / rlx-models main + reconcile Cargo.lock (the patch). + - name: Refresh rlx to main + uses: ./.github/actions/refresh-rlx-main + + - name: Generate coverage (lcov + threshold) + env: + COVERAGE_MIN_LINES: ${{ inputs.coverage_min_lines || '55' }} + run: | + cargo llvm-cov clean --workspace + cargo llvm-cov \ + --workspace \ + --exclude iroh_test_client \ + --exclude iroh-example-client \ + --exclude skill \ + --no-default-features \ + --features llm-rlx-cpu,skill-router/cpu,skill-daemon/cpu-only \ + --lcov \ + --output-path lcov.info + + cargo llvm-cov \ + --workspace \ + --exclude iroh_test_client \ + --exclude iroh-example-client \ + --exclude skill \ + --no-default-features \ + --features llm-rlx-cpu,skill-router/cpu,skill-daemon/cpu-only \ + --summary-only \ + --fail-under-lines ${COVERAGE_MIN_LINES} + + - name: Upload lcov artifact + if: always() + uses: actions/upload-artifact@v7 + with: + name: rust-lcov + path: lcov.info + retention-days: 30 # ── Security audit ──────────────────────────────────────────────────────────── + # + # Merged the previous separate cargo-audit + audit jobs into one. + # Runs only on main/develop pushes and manual triggers — advisory only, + # not worth blocking every PR. audit: name: Security audit runs-on: ubuntu-latest + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + permissions: + contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 + with: + fetch-depth: 1 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '20' + node-version: '22' cache: 'npm' + - name: Update npm to latest + run: npm install -g npm@latest + - name: npm audit run: npm audit --omit=dev --audit-level=high continue-on-error: true - - name: Install Rust stable + - name: Setup Rust uses: dtolnay/rust-toolchain@stable - - name: Cache Cargo (audit) - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - ~/.cargo/bin/cargo-audit - # cargo-audit's own deps change with Cargo.lock; bin is also covered. - key: linux-cargo-audit-${{ hashFiles('src-tauri/Cargo.lock') }} - restore-keys: linux-cargo-audit- + - name: Install cargo-audit + run: cargo install --locked cargo-audit - name: cargo audit - working-directory: src-tauri + run: cargo audit --deny warnings + + - name: Install cargo-deny + run: cargo install --locked cargo-deny + + - name: cargo deny + run: cargo deny check -A no-license-field -A parse-error -A license-not-encountered + + # ── Discord notification ────────────────────────────────────────────────── + notify: + name: Discord notification + runs-on: ubuntu-latest + needs: [version-gate, rust-check, windows-check, frontend-check, audit, llm-e2e, lsl-e2e, coverage] + if: always() + # Secret is empty for Dependabot PRs (separate secret store) and forks. + # Hoist to job-level env so the step's `if` can gate on it. + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 1 + - name: Send CI result to Discord + if: env.DISCORD_WEBHOOK_URL != '' + env: + # Take the real commit message from the event payload. On + # pull_request the checked-out HEAD is an auto-generated merge + # commit, so `git log` would show "Merge into " instead + # of the actual change. Passed via env to avoid script injection. + EVENT_COMMIT_MSG: ${{ github.event.head_commit.message || github.event.pull_request.title }} run: | - cargo install cargo-audit --locked 2>/dev/null || true - cargo audit - continue-on-error: true + COMMIT_MSG="$EVENT_COMMIT_MSG" + if [ -z "$COMMIT_MSG" ]; then + COMMIT_MSG=$(git log -1 --format='%B') + fi + # Cap length for the Discord embed field (limit 1024) and add an + # ellipsis so it's clear when there's more. Substring is char-based + # so it never splits a multi-byte UTF-8 sequence. + if [ "${#COMMIT_MSG}" -gt 500 ]; then + COMMIT_MSG="${COMMIT_MSG:0:500}…" + fi + VERSION="${{ needs.version-gate.result }}" + RUST="${{ needs.rust-check.result }}" + WINDOWS="${{ needs.windows-check.result }}" + FRONTEND="${{ needs.frontend-check.result }}" + AUDIT="${{ needs.audit.result }}" + LLM_E2E="${{ needs.llm-e2e.result }}" + LSL_E2E="${{ needs.lsl-e2e.result }}" + COVERAGE="${{ needs.coverage.result }}" + + # Determine overall status — VERSION, Rust (Linux), Windows, and Frontend + # are required gates. Audit and LLM E2E are advisory. + if [[ "$VERSION" == "success" && "$RUST" == "success" && "$WINDOWS" == "success" && "$FRONTEND" == "success" ]]; then + COLOR=3066993 # green + STATUS="✅ Passed" + else + COLOR=15158332 # red + STATUS="❌ Failed" + fi + + EMOJI_VERSION=$([ "$VERSION" = "success" ] && echo "✅" || echo "❌") + EMOJI_RUST=$([ "$RUST" = "success" ] && echo "✅" || echo "❌") + EMOJI_WINDOWS=$([ "$WINDOWS" = "success" ] && echo "✅" || echo "❌") + EMOJI_FRONTEND=$([ "$FRONTEND" = "success" ] && echo "✅" || echo "❌") + EMOJI_AUDIT=$([ "$AUDIT" = "success" ] && echo "✅" || ([ "$AUDIT" = "skipped" ] && echo "⏭️" || echo "⚠️")) + EMOJI_LLM=$([ "$LLM_E2E" = "success" ] && echo "✅" || ([ "$LLM_E2E" = "skipped" ] && echo "⏭️" || echo "⚠️")) + EMOJI_LSL=$([ "$LSL_E2E" = "success" ] && echo "✅" || ([ "$LSL_E2E" = "skipped" ] && echo "⏭️" || echo "⚠️")) + EMOJI_COV=$([ "$COVERAGE" = "success" ] && echo "✅" || ([ "$COVERAGE" = "skipped" ] && echo "⏭️" || echo "⚠️")) + + RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + REF="${{ github.ref_name }}" + # On pull_request github.sha is the merge commit; prefer the PR's + # real head commit so the SHA and its link point at the change. + SHA="${{ github.event.pull_request.head.sha || github.sha }}" + COMMIT_URL="${{ github.server_url }}/${{ github.repository }}/commit/$SHA" + ACTOR="${{ github.actor }}" + EVENT="${{ github.event_name }}" + + curl -s -X POST "$DISCORD_WEBHOOK_URL" \ + -H "Content-Type: application/json" \ + -d "{ + \"embeds\": [{ + \"title\": \"CI — $STATUS\", + \"url\": \"$RUN_URL\", + \"color\": $COLOR, + \"fields\": [ + {\"name\": \"Branch\", \"value\": \"\`$REF\`\", \"inline\": true}, + {\"name\": \"Commit\", \"value\": \"[\`${SHA:0:8}\`]($COMMIT_URL)\", \"inline\": true}, + {\"name\": \"Triggered\", \"value\": \"$EVENT by $ACTOR\", \"inline\": true}, + {\"name\": \"Commit Message\", \"value\": $(echo "$COMMIT_MSG" | jq -Rs .), \"inline\": false}, + {\"name\": \"VERSION\", \"value\": \"$EMOJI_VERSION $VERSION\", \"inline\": true}, + {\"name\": \"Rust (Linux)\", \"value\": \"$EMOJI_RUST $RUST\", \"inline\": true}, + {\"name\": \"Rust (Windows)\", \"value\": \"$EMOJI_WINDOWS $WINDOWS\", \"inline\": true}, + {\"name\": \"Frontend\", \"value\": \"$EMOJI_FRONTEND $FRONTEND\", \"inline\": true}, + {\"name\": \"Audit\", \"value\": \"$EMOJI_AUDIT $AUDIT\", \"inline\": true}, + {\"name\": \"LLM E2E\", \"value\": \"$EMOJI_LLM $LLM_E2E\", \"inline\": true}, + {\"name\": \"LSL E2E\", \"value\": \"$EMOJI_LSL $LSL_E2E\", \"inline\": true}, + {\"name\": \"Coverage\", \"value\": \"$EMOJI_COV $COVERAGE\", \"inline\": true} + ], + \"footer\": {\"text\": \"${{ github.repository }}\"} + }] + }" diff --git a/.github/workflows/hf-downloads-cache.yml b/.github/workflows/hf-downloads-cache.yml new file mode 100644 index 000000000..e9110a2b6 --- /dev/null +++ b/.github/workflows/hf-downloads-cache.yml @@ -0,0 +1,92 @@ +name: HF downloads cache + +# Refreshes src-tauri/hf_downloads_cache.json by crawling HuggingFace Hub +# (catalog repos + top mlx-community models by downloads), then opens a PR +# when the cache drifted. Keeps offline catalog sorting current without +# requiring network at app runtime. +# +# Local: npm run sync:hf:downloads +# Check: npm run sync:hf:downloads:check + +on: + schedule: + - cron: "23 7 * * 1" # Mondays 07:23 UTC + workflow_dispatch: + inputs: + mlx_top: + description: "How many mlx-community models to crawl (default 100)" + required: false + default: "100" + +permissions: + contents: write + pull-requests: write + +concurrency: + group: hf-downloads-cache + cancel-in-progress: false + +jobs: + refresh: + name: Refresh HF downloads cache + runs-on: ubuntu-latest + steps: + - name: Checkout default branch + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: "22" + + - name: Crawl HuggingFace + write cache + env: + HF_TOKEN: ${{ secrets.HF_TOKEN }} + HF_DOWNLOADS_MLX_TOP: ${{ github.event.inputs.mlx_top || '100' }} + run: node scripts/update-hf-downloads-cache.mjs --write --verbose + + - name: Open PR if the cache drifted + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + if git diff --quiet -- src-tauri/hf_downloads_cache.json src/lib/generated/hf-downloads-cache.json; then + echo "Cache already current — nothing to do." + exit 0 + fi + + UPDATED="$(python3 -c 'import json; print(json.load(open("src-tauri/hf_downloads_cache.json")).get("updated_at","unknown"))')" + BRANCH="automated/hf-downloads-cache" + TITLE="chore(llm): refresh HuggingFace downloads cache" + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -B "$BRANCH" + git add src-tauri/hf_downloads_cache.json src/lib/generated/hf-downloads-cache.json + git commit -m "$(cat </dev/null | grep -q OPEN; then + echo "PR already open for $BRANCH — branch updated in place." + gh pr edit "$BRANCH" --title "$TITLE" --body "$BODY" || true + else + gh pr create --title "$TITLE" --body "$BODY" + fi diff --git a/.github/workflows/homebrew-cask.yml b/.github/workflows/homebrew-cask.yml new file mode 100644 index 000000000..e5301ae2e --- /dev/null +++ b/.github/workflows/homebrew-cask.yml @@ -0,0 +1,87 @@ +name: Homebrew cask + +# Keeps Casks/neuroskill.rb pinned to the latest *stable* GitHub release. +# +# Why a reconcile (not a step inside Release - Mac): a release published with the +# default GITHUB_TOKEN cannot, by design, trigger another workflow, so a +# `release: [released]` trigger would never fire. Instead this job runs after the +# mac release build completes (and on a weekly safety net), reads the current +# latest stable release, and opens a PR whenever the cask has drifted. RC builds +# are harmless: the reconcile always targets the latest *stable*, so an RC run +# finds no drift and does nothing. +on: + workflow_run: + workflows: ["Release - Mac"] + types: [completed] + schedule: + - cron: "17 6 * * 1" # Mondays 06:17 UTC + workflow_dispatch: + inputs: + version: + description: "Version to pin (e.g. 0.0.130); defaults to the latest stable release" + required: false + +permissions: + contents: write # push the reconcile branch + pull-requests: write # open the bump PR + +concurrency: + group: homebrew-cask + cancel-in-progress: false + +jobs: + reconcile: + name: Reconcile Homebrew cask + runs-on: ubuntu-latest + # For workflow_run, only proceed when the upstream build actually succeeded. + if: >- + github.event_name != 'workflow_run' || + github.event.workflow_run.conclusion == 'success' + steps: + - name: Checkout default branch + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "22" + + - name: Reconcile cask to latest stable release + env: + GH_TOKEN: ${{ github.token }} + run: | + ARGS="" + if [ -n "${{ github.event.inputs.version }}" ]; then + ARGS="--version ${{ github.event.inputs.version }}" + fi + node scripts/ci.mjs update-cask $ARGS + + - name: Open PR if the cask drifted + env: + GH_TOKEN: ${{ github.token }} + run: | + if git diff --quiet -- Casks/neuroskill.rb; then + echo "Cask already current — nothing to do." + exit 0 + fi + + VERSION="$(sed -n 's/^[[:space:]]*version "\(.*\)"/\1/p' Casks/neuroskill.rb | head -1)" + BRANCH="automated/homebrew-cask" + TITLE="chore(homebrew): bump cask to v${VERSION}" + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -B "$BRANCH" + git add Casks/neuroskill.rb + git commit -m "$TITLE" + git push --force origin "$BRANCH" + + if gh pr view "$BRANCH" --json state --jq .state 2>/dev/null | grep -q OPEN; then + echo "PR already open for $BRANCH — branch updated in place." + else + gh pr create \ + --base main \ + --head "$BRANCH" \ + --title "$TITLE" \ + --body "Automated reconcile of \`Casks/neuroskill.rb\` to the latest stable release (v${VERSION}). Generated by \`.github/workflows/homebrew-cask.yml\`." + fi diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index 0b20a6053..217c4eb9d 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -60,6 +60,12 @@ permissions: contents: read # checkout pull-requests: write # post download-link comment +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + # WidgetKit signing currently breaks Apple notarization (Invalid). Set to + # 'true' to re-enable once appex signing/timestamps are fixed. + ENABLE_WIDGETKIT: 'false' + jobs: preview: # ── [FIX 1] Fork rejection ──────────────────────────────────────────────── @@ -94,7 +100,7 @@ jobs: # maintainer. - name: Verify labeler has write access if: github.event_name == 'pull_request' - uses: actions/github-script@60a0d83039f74a4aee543508d474c4c82a9f1b84 # v7.0.1 + uses: actions/github-script@v9 with: script: | const { data } = await github.rest.repos.getCollaboratorPermissionLevel({ @@ -129,24 +135,10 @@ jobs: APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - run: | - ok=true - for var in \ - APPLE_CERTIFICATE \ - APPLE_CERTIFICATE_PASSWORD \ - APPLE_SIGNING_IDENTITY \ - APPLE_ID \ - APPLE_PASSWORD \ - APPLE_TEAM_ID \ - TAURI_SIGNING_PRIVATE_KEY - do - if [ -z "${!var}" ]; then - echo "::error::Secret '$var' is empty or not set in the Release environment." - ok=false - fi - done - $ok || exit 1 - echo "✓ All signing and notarization secrets are present." + run: >- + node scripts/ci.mjs verify-secrets + APPLE_CERTIFICATE APPLE_CERTIFICATE_PASSWORD APPLE_SIGNING_IDENTITY + APPLE_ID APPLE_PASSWORD APPLE_TEAM_ID TAURI_SIGNING_PRIVATE_KEY # ── Resolve what to check out ─────────────────────────────────────────── # @@ -170,7 +162,7 @@ jobs: # ── Checkout ──────────────────────────────────────────────────────────── # Reached only after all three security gates above have passed. - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@v6 with: ref: ${{ steps.meta.outputs.ref }} fetch-depth: 0 @@ -183,11 +175,7 @@ jobs: PR="${{ steps.meta.outputs.pr }}" BRANCH="${{ steps.meta.outputs.branch }}" - CONF_VERSION=$( - grep '"version"' src-tauri/tauri.conf.json \ - | head -1 \ - | sed 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' - ) + CONF_VERSION=$(tr -d '[:space:]' < VERSION) if [ -n "$PR" ]; then ARTIFACT_NAME="skill-preview-pr${PR}-${SHA}" @@ -211,40 +199,42 @@ jobs: # ── Rust toolchain ────────────────────────────────────────────────────── - name: Install Rust stable - uses: dtolnay/rust-toolchain@4305c38b25c5dd5af9c4cfe7dd25e3c5c7ba2e58 # stable (2025-02) + uses: dtolnay/rust-toolchain@stable with: targets: aarch64-apple-darwin - name: Cache Cargo registry + build artifacts - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34b5c520640 # v2.7.8 + uses: Swatinem/rust-cache@v2.9.1 with: - workspaces: src-tauri + workspaces: ". -> src-tauri/target" # ── Node.js ───────────────────────────────────────────────────────────── - name: Setup Node.js - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + uses: actions/setup-node@v6 with: - node-version: '20' + node-version: '22' cache: 'npm' + - name: Update npm to latest + run: npm install -g npm@latest + - name: Install JS dependencies run: npm ci - # ── ImageMagick (needed by scripts/stamp-dmg-icon.sh) ──────────────────── - - name: Install ImageMagick - run: brew install imagemagick + - name: Install protoc + GNU ar (macOS) + run: | + brew install protobuf binutils + # Use GNU ar to suppress "illegal option -- D" warnings from Apple's system ar + echo "AR=$(brew --prefix binutils)/bin/gar" >> "$GITHUB_ENV" - # ── espeak-ng static library ───────────────────────────────────────────── - - name: Cache espeak-ng static lib - id: cache-espeak - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c6158d # v4.2.0 - with: - path: src-tauri/espeak-static - key: macos-arm64-espeak-${{ hashFiles('scripts/build-espeak-static.sh') }} + - name: i18n sync check + run: npm run -s sync:i18n:check - - name: Build espeak-ng static lib - if: steps.cache-espeak.outputs.cache-hit != 'true' - run: ESPEAK_ARCHS=arm64 bash scripts/build-espeak-static.sh + - name: i18n audit check + run: npm run -s audit:i18n:check + + - name: i18n locales check (all non-en locales) + run: npm run -s check:i18n:locales # ── Validate Apple notarization credentials ────────────────────────────── - name: Validate Apple notarization credentials @@ -252,68 +242,95 @@ jobs: APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - run: | - echo "Checking notarization credentials…" - if ! xcrun notarytool history \ - --apple-id "$APPLE_ID" \ - --password "$APPLE_PASSWORD" \ - --team-id "$APPLE_TEAM_ID" \ - --output-format json 2>&1 | grep -q '"history"'; then - echo "::error::Apple notarization credentials are invalid." - echo "::error::Generate a fresh app-specific password at:" - echo "::error:: https://appleid.apple.com → App-Specific Passwords" - echo "::error::Then update APPLE_PASSWORD in: Settings → Environments → Release" - exit 1 - fi - echo "✓ Notarization credentials are valid." + run: node scripts/ci.mjs validate-notarization # ── Import Apple Developer certificate into a temporary keychain ───────── - name: Import Apple Developer certificate env: APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + run: node scripts/ci.mjs import-apple-cert + + # ── WidgetKit extension (optional; off until notarization-safe) ───────── + - name: Install XcodeGen + if: env.ENABLE_WIDGETKIT == 'true' + run: brew install xcodegen + + - name: Setup WidgetKit signing + if: env.ENABLE_WIDGETKIT == 'true' + env: + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPLE_ASC_KEY_ID: ${{ secrets.APPLE_ASC_KEY_ID }} + APPLE_ASC_KEY_ISSUER_ID: ${{ secrets.APPLE_ASC_KEY_ISSUER_ID }} + APPLE_ASC_KEY_BASE64: ${{ secrets.APPLE_ASC_KEY_BASE64 }} + APPLE_WIDGET_PROVISIONING_PROFILE_BASE64: ${{ secrets.APPLE_WIDGET_PROVISIONING_PROFILE_BASE64 }} + run: node scripts/ci.mjs setup-widget-signing + + - name: Build WidgetKit extension + if: env.ENABLE_WIDGETKIT == 'true' + env: + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + AUTH_KEY_PATH: ${{ env.AUTH_KEY_PATH }} + AUTH_KEY_ID: ${{ env.AUTH_KEY_ID }} + AUTH_KEY_ISSUER_ID: ${{ env.AUTH_KEY_ISSUER_ID }} + run: node scripts/ci.mjs build-widgets + + # ── Compile: frontend + Rust product (daemon + tty + app) ──────────────── + # Same recipe as release-mac.yml (scripts/compile-product.mjs). + - name: Compile (frontend + Rust) run: | - KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db" - KEYCHAIN_PASSWORD="$(openssl rand -base64 32)" - echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV" - echo "KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> "$GITHUB_ENV" - - security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" - security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - - echo -n "$APPLE_CERTIFICATE" \ - | base64 --decode -o "$RUNNER_TEMP/cert.p12" - security import "$RUNNER_TEMP/cert.p12" \ - -k "$KEYCHAIN_PATH" \ - -P "$APPLE_CERTIFICATE_PASSWORD" \ - -T /usr/bin/codesign \ - -T /usr/bin/security - rm -f "$RUNNER_TEMP/cert.p12" - - security set-key-partition-list \ - -S apple-tool:,apple: \ - -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - security list-keychains -d user \ - -s "$KEYCHAIN_PATH" login.keychain - - # ── Tauri build — sign + notarize ──────────────────────────────────────── + set -euo pipefail + npm run build + node scripts/compile-product.mjs \ + --target aarch64-apple-darwin \ + --release --locked + + # ── Assemble .app bundle ────────────────────────────────────────────── + - name: Create .app bundle + env: + SKIP_WIDGETS: ${{ env.ENABLE_WIDGETKIT == 'true' && '0' || '1' }} + run: bash scripts/assemble-macos-app.sh aarch64-apple-darwin + + # ── Sign .app + create DMG (background, README, CHANGELOG, LICENSE) ───── # - # Identical to the official release build. The only difference is that no - # GitHub Release is created and the artifact name includes the SHA. - - name: Build Tauri app (sign + notarize) + # Uses sindresorhus/create-dmg via scripts/create-macos-dmg.sh which + # signs .app, creates DMG with composed icon + Retina background + SLA, + # signs the DMG, and notarizes with Apple. + - name: Install DMG dependencies + run: | + # npm@latest no longer runs dependency install scripts by default, so + # appdmg's native deps (fs-xattr, macos-alias) skip their node-gyp build + # and `volume.node` goes missing. Allowlist their scripts explicitly. + npm install --global appdmg --allow-scripts=fs-xattr,macos-alias + pip3 install --quiet Pillow + + - name: Sign .app + Create DMG + env: + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + run: bash scripts/create-macos-dmg.sh aarch64-apple-darwin + + # ── Create updater artifact from the signed + stapled .app ───────────── + # Runs after DMG so the .app is already notarized and stapled. + - name: Recreate updater artifact from signed .app env: - APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} - APPLE_ID: ${{ secrets.APPLE_ID }} - APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} - APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - ESPEAK_LIB_DIR: ${{ github.workspace }}/src-tauri/espeak-static/lib run: | - npx tauri build \ - --target aarch64-apple-darwin \ - --bundles app,dmg + BUNDLE_DIR="src-tauri/target/aarch64-apple-darwin/release/bundle" + APP_BUNDLE="$(find "$BUNDLE_DIR/macos" -name "*.app" -maxdepth 1 | head -1)" + + find "$BUNDLE_DIR/macos" \ + \( -name "*.app.tar.gz" -o -name "*.app.tar.gz.sig" \) -delete + + TAR_PATH="$BUNDLE_DIR/macos/$(basename "$APP_BUNDLE").tar.gz" + tar -czf "$TAR_PATH" \ + -C "$(dirname "$APP_BUNDLE")" "$(basename "$APP_BUNDLE")" + + npx tauri signer sign -f "$TAR_PATH" # ── Always delete the temporary keychain ───────────────────────────────── - name: Delete temporary keychain @@ -353,13 +370,10 @@ jobs: echo " TAR : $APP_TAR ($(du -sh "$APP_TAR" | cut -f1))" echo " SIG : $APP_SIG" - # ── Stamp version badge onto the DMG icon ──────────────────────────────── - - name: Stamp version badge onto DMG icon - run: | - bash scripts/stamp-dmg-icon.sh \ - "${{ steps.artifacts.outputs.dmg }}" \ - "${{ env.VERSION }}" \ - "src-tauri/icons/icon.png" + - name: Prepare changelog notes for preview artifact + run: >- + node scripts/ci.mjs prepare-changelog + "${{ steps.label.outputs.conf_version }}" preview-notes.md # ── Upload to GitHub Actions artifacts (14-day retention) ──────────────── # @@ -381,6 +395,62 @@ jobs: ${{ steps.artifacts.outputs.dmg }} ${{ steps.artifacts.outputs.app_tar }} ${{ steps.artifacts.outputs.app_sig }} + preview-notes.md + + # ── Notify Discord of preview build result ─────────────────────────────── + - name: Notify Discord of preview build + if: always() + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + JOB_STATUS: ${{ job.status }} + run: | + # Full commit message (subject + body); HEAD here is the PR's real + # head sha (checked out with fetch-depth: 0), not a merge commit. + COMMIT_MSG=$(git log -1 --format='%B') + if [ "${#COMMIT_MSG}" -gt 500 ]; then + COMMIT_MSG="${COMMIT_MSG:0:500}…" + fi + RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + SHA="${{ steps.label.outputs.sha }}" + VERSION="${{ steps.label.outputs.conf_version }}" + TITLE="${{ steps.label.outputs.build_title }}" + ARTIFACT="${{ steps.label.outputs.artifact_name }}" + PR="${{ steps.meta.outputs.pr }}" + BRANCH="${{ steps.meta.outputs.branch }}" + + if [[ "$JOB_STATUS" == "success" ]]; then + COLOR=3066993 # green + EMOJI="✅" + DESC="Preview build succeeded. Artifact available for 14 days." + else + COLOR=15158332 # red + EMOJI="❌" + DESC="Preview build failed. Check the run for details." + fi + + PR_FIELD="" + if [[ -n "$PR" ]]; then + PR_URL="${{ github.server_url }}/${{ github.repository }}/pull/$PR" + PR_FIELD="{\"name\": \"PR\", \"value\": \"[#$PR]($PR_URL)\", \"inline\": true}," + fi + + curl -s -X POST "$DISCORD_WEBHOOK_URL" \ + -H "Content-Type: application/json" \ + -d "{ + \"embeds\": [{ + \"title\": \"$EMOJI Preview Build — \`v$VERSION\` @ \`$SHA\`\", + \"description\": \"$DESC\n\n**[View run & download artifact]($RUN_URL)**\", + \"url\": \"$RUN_URL\", + \"color\": $COLOR, + \"fields\": [ + $PR_FIELD + {\"name\": \"Branch\", \"value\": \"\`$BRANCH\`\", \"inline\": true}, + {\"name\": \"Artifact\", \"value\": \"\`$ARTIFACT\`\", \"inline\": true}, + {\"name\": \"Commit Message\", \"value\": $(echo "$COMMIT_MSG" | jq -Rs .), \"inline\": false} + ], + \"footer\": {\"text\": \"${{ github.repository }}\"} + }] + }" # ── Post (or update) a download-link comment on the PR ─────────────────── # @@ -389,7 +459,7 @@ jobs: # rather than creating a new one — keeps the PR thread tidy. - name: Comment on PR with download link if: steps.meta.outputs.pr != '' - uses: actions/github-script@60a0d83039f74a4aee543508d474c4c82a9f1b84 # v7.0.1 + uses: actions/github-script@v9 with: script: | const pr = Number('${{ steps.meta.outputs.pr }}'); diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml new file mode 100644 index 000000000..66bb70e64 --- /dev/null +++ b/.github/workflows/pr-checks.yml @@ -0,0 +1,74 @@ +name: Release PR checks + +# Soft-enforces the merge strategy required by the RC pipeline. +# +# The promote workflow keys off the *head commit's message* on main matching +# the version string (`0.5.1-rc.3`). Rebase merge and squash merge both +# preserve that message; a regular merge commit doesn't, so promote would +# silently skip and the stable channel would stay pinned to the previous +# release. +# +# This workflow doesn't block merging — repo-level merge-strategy settings +# can do that, and GitHub's merge dropdown is the right place to enforce +# allowed methods. What this provides is an unmissable inline reminder on +# every PR labeled `release`, so the person clicking "Merge" sees the +# constraint right next to the button. + +on: + pull_request: + types: [labeled, opened, reopened, synchronize] + +permissions: + pull-requests: write + contents: read + +jobs: + remind-merge-strategy: + name: Remind merger to use rebase or squash + runs-on: ubuntu-latest + if: contains(github.event.pull_request.labels.*.name, 'release') + steps: + - name: Upsert merge-strategy reminder comment + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REPO: ${{ github.repository }} + run: | + set -euo pipefail + + # Marker keeps this idempotent: we either find and update the + # existing reminder, or post one new one. Multiple labels/syncs + # don't pile up. + MARKER='' + + BODY=$(cat <<'EOF' + + ### Merge strategy: **rebase** or **squash** only + + This PR is labeled `release`. Promotion of the most recent RC to stable depends on the merged commit message on `main` matching the version string exactly (e.g. `0.5.1-rc.3`). + + A regular merge commit would: + - create a different commit hash than the tested RC → **break bit-identity** between RC and stable + - carry a generic merge message → **the promote workflow silently skips**, leaving stable pointing at the previous release + + When merging, click the dropdown next to the green button and pick **Rebase and merge** or **Squash and merge**. + EOF + ) + + # Find existing comment by marker. `gh pr view --json comments` + # returns all comments; we filter client-side for the marker. + EXISTING_ID=$(gh pr view "$PR_NUMBER" --repo "$REPO" \ + --json comments \ + --jq ".comments[] | select(.body | contains(\"$MARKER\")) | .id" \ + | head -1 || true) + + if [ -n "$EXISTING_ID" ]; then + # `gh pr comment` doesn't have an --edit mode; use the API directly. + # The comment ID from `gh pr view` is a node ID; the REST API needs + # the numeric comments ID. Fall back to creating a new comment if + # editing fails — the marker prevents duplication on next sync. + echo "Existing reminder found ($EXISTING_ID); leaving in place." + else + echo "$BODY" | gh pr comment "$PR_NUMBER" --repo "$REPO" --body-file - + echo "Posted merge-strategy reminder on PR #$PR_NUMBER." + fi diff --git a/.github/workflows/promote.yml b/.github/workflows/promote.yml new file mode 100644 index 000000000..cc0ef4b90 --- /dev/null +++ b/.github/workflows/promote.yml @@ -0,0 +1,73 @@ +name: Promote RC to stable + +# Fired when a release PR is merged to main using rebase or squash merge. +# bump.js commits with the version string as the message (e.g. "0.5.1-rc.3"), +# and rebase/squash preserves that commit hash + message on main. We detect it +# here and promote the matching RC release: clear the prerelease flag and mark +# it as latest. The promoted artifacts are byte-identical to the tested RC — +# no rebuild, no re-notarization. GitHub's `releases/latest` URL semantics do +# the rest, and stable channel users see the new version on their next poll. +# +# A regular merge commit creates a different head SHA whose message doesn't +# match the version regex, so promote silently skips. The companion workflow +# (pr-checks.yml) flags release PRs that would create such a merge commit. + +on: + push: + branches: [main] + +permissions: + contents: write # needed to edit release metadata + +jobs: + promote: + name: Promote matching RC tag + runs-on: ubuntu-latest + # Cheap pre-filter: head commit message must contain `-rc.` somewhere. + # The strict regex check happens in the step below. + if: contains(github.event.head_commit.message, '-rc.') + steps: + - uses: actions/checkout@v4 + + - name: Identify the RC to promote + id: rc + env: + HEAD_MSG: ${{ github.event.head_commit.message }} + run: | + set -euo pipefail + + # bump.js commits exactly the version string. We accept both the bare + # message and an optional leading "v" out of paranoia; nothing else. + if [[ "$HEAD_MSG" =~ ^v?([0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+)$ ]]; then + VERSION="${BASH_REMATCH[1]}" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "tag=v$VERSION" >> "$GITHUB_OUTPUT" + echo "should_promote=true" >> "$GITHUB_OUTPUT" + echo "::notice::Detected RC promotion target: v$VERSION" + else + echo "should_promote=false" >> "$GITHUB_OUTPUT" + echo "::notice::Head commit message is not a release-bump commit, skipping promotion." + echo " message: $HEAD_MSG" + fi + + - name: Verify the release exists + if: steps.rc.outputs.should_promote == 'true' + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + if ! gh release view "${{ steps.rc.outputs.tag }}" >/dev/null 2>&1; then + echo "::error::Release ${{ steps.rc.outputs.tag }} does not exist. Did the build workflow finish?" + exit 1 + fi + + - name: Clear prerelease flag and mark as latest + if: steps.rc.outputs.should_promote == 'true' + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + gh release edit "${{ steps.rc.outputs.tag }}" \ + --prerelease=false \ + --latest + echo "::notice::Promoted ${{ steps.rc.outputs.tag }} to stable. The releases/latest/download/latest.json URL now resolves here." diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml new file mode 100644 index 000000000..1feb9f1ba --- /dev/null +++ b/.github/workflows/release-linux.yml @@ -0,0 +1,355 @@ +name: Release — Linux + +# Triggered by the same semver tag as the macOS and Windows release workflows. +# All three run in parallel; each uploads its own binaries and then merges +# its platform entry into the shared latest.json updater manifest. +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + - 'v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+' + workflow_dispatch: + +permissions: + contents: write # create/update GitHub Releases and upload assets + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + release-linux: + name: Build & Release — x86_64-unknown-linux-gnu + runs-on: ubuntu-24.04 + environment: Release + + steps: + # ── Free pre-installed software we don't use (~10 GB reclaimed) ────────── + - name: Free disk space + run: | + sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc /usr/local/.ghcup \ + /usr/local/share/powershell /usr/local/share/chromium /usr/share/swift \ + /opt/hostedtoolcache/CodeQL + sudo docker image prune -af 2>/dev/null || true + df -h / + + # ── Checkout ────────────────────────────────────────────────────────────── + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 # full history for git tag --format and changelog generation + + # ── Verify tag matches VERSION ──────────────────────────────────────────── + - name: Resolve version + id: version_meta + env: + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_REF: ${{ github.ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + run: node scripts/ci.mjs resolve-version + + - name: Release setup (Linux deps + Rust + Node + frontend) + uses: ./.github/actions/release-setup + with: + target: x86_64-unknown-linux-gnu + linux-deps: "true" + cache-key: linux-release-x86_64-unknown-linux-gnu-v1 + build-frontend: "true" + + # ── Pre-flight: verify every required secret is set ────────────────────── + # Linux builds don't need platform code-signing credentials (there is no + # Linux equivalent of Apple notarization or Windows Authenticode), but the + # shared Tauri Ed25519 updater signing key is still required. + - name: Verify signing secrets are present + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + run: node scripts/ci.mjs verify-secrets TAURI_SIGNING_PRIVATE_KEY + + # ── Clean cached outputs that can mask stale release artifacts ────────── + # rust-cache restores target/dist directories between runs. If a previous + # job left bundle outputs behind, later fallback checks can accidentally + # pick stale artifacts. + - name: Clean stale Linux release outputs + shell: bash + run: | + set -euo pipefail + TARGET_ROOT="src-tauri/target/x86_64-unknown-linux-gnu/release" + rm -rf "$TARGET_ROOT/bundle" + rm -f "$TARGET_ROOT/skill" + rm -rf "dist/linux/x86_64-unknown-linux-gnu" + echo "✓ Cleared stale target/dist outputs before Linux release build" + + # ── Compile (Rust + daemon via shared recipe) ─────────────────────────── + - name: Compile (Rust + daemon) + env: + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: clang + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: -C link-arg=-fuse-ld=mold + shell: bash + run: | + set -euo pipefail + # Shared recipe: daemon (linux = CUDA+wgpu) → tty → app (custom-protocol). + # Frontend was built in release-setup (build-frontend: true). + node scripts/compile-product.mjs \ + --target x86_64-unknown-linux-gnu \ + --release --locked --timings + + # ── Stage files for packaging ─────────────────────────────────────--- + # The daemon binary sits next to the app binary. At runtime the + # Tauri app starts the daemon and calls its /service/install endpoint + # which self-registers a systemd --user service. No system-level + # service file is needed in the package. + - name: Stage files for packaging + shell: bash + run: | + set -euo pipefail + PKGROOT="$RUNNER_TEMP/pkgroot" + mkdir -p "$PKGROOT/opt/NeuroSkill" + + # Copy Tauri app binary + cp src-tauri/target/x86_64-unknown-linux-gnu/release/skill "$PKGROOT/opt/NeuroSkill/skill" + chmod +x "$PKGROOT/opt/NeuroSkill/skill" + + # Copy daemon + tty next to the app binary (same layout as macOS / deb/rpm) + cp src-tauri/target/x86_64-unknown-linux-gnu/release/skill-daemon "$PKGROOT/opt/NeuroSkill/skill-daemon" + chmod +x "$PKGROOT/opt/NeuroSkill/skill-daemon" + cp src-tauri/target/x86_64-unknown-linux-gnu/release/skill-tty "$PKGROOT/opt/NeuroSkill/skill-tty" + chmod +x "$PKGROOT/opt/NeuroSkill/skill-tty" + + # Verify product binaries are present + ls -la "$PKGROOT/opt/NeuroSkill/skill" "$PKGROOT/opt/NeuroSkill/skill-daemon" "$PKGROOT/opt/NeuroSkill/skill-tty" + + # Show staged files + find "$PKGROOT" + + # ── Build Linux portable tarball with daemon and service ───────────── + - name: Build Linux portable tarball (with daemon) + shell: bash + run: | + set -euo pipefail + cd "$RUNNER_TEMP/pkgroot" + tar -czf "$GITHUB_WORKSPACE/NeuroSkill-linux-x86_64.tar.gz" . + + # ── Upload cargo build timings ───────────────────────────────────────── + # The --timings flag produces an HTML report showing per-crate compile + # durations. Upload it so slow crates can be identified and targeted. + - name: Upload cargo build timings + if: always() + uses: actions/upload-artifact@v7 + with: + name: cargo-timings-linux + path: src-tauri/target/cargo-timings/ + retention-days: 30 + + - name: Build Linux deb/rpm via system tools + run: bash scripts/package-linux-system-bundles.sh --target x86_64-unknown-linux-gnu --skip-build + + - name: Build Linux portable tarball + run: bash scripts/package-linux-dist.sh --target x86_64-unknown-linux-gnu --skip-build + + # ── Post-build sanity check ───────────────────────────────────────────── + - name: Verify Linux bundle directories + shell: bash + run: | + set -euo pipefail + TARGET_DIR="src-tauri/target/x86_64-unknown-linux-gnu/release/bundle" + PORTABLE_DIR="dist/linux/x86_64-unknown-linux-gnu" + PORTABLE_TAR_COUNT="$(find "$PORTABLE_DIR" -maxdepth 1 -name "*.tar.gz" | wc -l | tr -d ' ')" + + if [ ! -d "$TARGET_DIR/deb" ] || [ ! -d "$TARGET_DIR/rpm" ] || [ "$PORTABLE_TAR_COUNT" -eq 0 ]; then + echo "::error::Expected Linux bundle directories are missing." + echo "Expected: $TARGET_DIR/deb, $TARGET_DIR/rpm, and at least one tar.gz in $PORTABLE_DIR" + echo "Present under $TARGET_DIR and $PORTABLE_DIR:" + find "$TARGET_DIR" -maxdepth 2 -type d 2>/dev/null | sort || true + find "$PORTABLE_DIR" -maxdepth 2 2>/dev/null | sort || true + exit 1 + fi + + echo "✓ Artifacts exist: $TARGET_DIR/deb, $TARGET_DIR/rpm, $PORTABLE_DIR/*.tar.gz" + + # ── Locate build artifacts ──────────────────────────────────────────────── + - name: Collect artifact paths + id: artifacts + run: | + TARGET_DIR="src-tauri/target/x86_64-unknown-linux-gnu/release/bundle" + PORTABLE_DIR="dist/linux/x86_64-unknown-linux-gnu" + + DEB="$(find "$TARGET_DIR/deb" -maxdepth 1 -name "*.deb" | head -1)" + RPM="$(find "$TARGET_DIR/rpm" -maxdepth 1 -name "*.rpm" | head -1)" + TAR="$(find "$PORTABLE_DIR" -maxdepth 1 -name "*.tar.gz" | head -1)" + + if [ -z "$DEB" ] || [ -z "$RPM" ] || [ -z "$TAR" ]; then + echo "::error::One or more expected artifacts are missing." + echo " DEB : ${DEB:-(not found)}" + echo " RPM : ${RPM:-(not found)}" + echo " TAR : ${TAR:-(not found)}" + exit 1 + fi + + echo "deb=$DEB" >> "$GITHUB_OUTPUT" + echo "rpm=$RPM" >> "$GITHUB_OUTPUT" + echo "portable_tar=$TAR" >> "$GITHUB_OUTPUT" + echo "portable_tar_name=$(basename "$TAR")" >> "$GITHUB_OUTPUT" + + echo "Artifacts found:" + echo " DEB : $DEB ($(du -sh "$DEB" | cut -f1))" + echo " RPM : $RPM ($(du -sh "$RPM" | cut -f1))" + echo " TAR : $TAR ($(du -sh "$TAR" | cut -f1))" + + - name: Write Linux release health metrics + shell: bash + run: | + set -euo pipefail + mkdir -p ci-metrics + + DEB="${{ steps.artifacts.outputs.deb }}" + RPM="${{ steps.artifacts.outputs.rpm }}" + TAR="${{ steps.artifacts.outputs.portable_tar }}" + BIN="src-tauri/target/x86_64-unknown-linux-gnu/release/skill" + + DEB_BYTES="$(stat -c %s "$DEB")" + RPM_BYTES="$(stat -c %s "$RPM")" + TAR_BYTES="$(stat -c %s "$TAR")" + BIN_BYTES="$(stat -c %s "$BIN")" + + { + echo '{' + echo ' "job": "release-linux",' + echo ' "target": "x86_64-unknown-linux-gnu",' + echo ' "binary_bytes":' "$BIN_BYTES" ',' + echo ' "deb_bytes":' "$DEB_BYTES" ',' + echo ' "rpm_bytes":' "$RPM_BYTES" ',' + echo ' "portable_tar_bytes":' "$TAR_BYTES" + echo '}' + } > ci-metrics/release-linux.json + + { + echo "## Linux release health metrics" + echo "- skill binary size: ${BIN_BYTES} bytes" + echo "- .deb size: ${DEB_BYTES} bytes" + echo "- .rpm size: ${RPM_BYTES} bytes" + echo "- portable tarball size: ${TAR_BYTES} bytes" + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload Linux release health metrics + if: always() + uses: actions/upload-artifact@v7 + with: + name: ci-health-release-linux + path: ci-metrics/release-linux.json + retention-days: 30 + + - name: Generate checksums + detached signatures + id: integrity + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + run: | + set -euo pipefail + TARGET_DIR="src-tauri/target/x86_64-unknown-linux-gnu/release/bundle" + CHECKSUMS="$TARGET_DIR/SHA256SUMS-linux-x86_64.txt" + + sha256sum \ + "${{ steps.artifacts.outputs.deb }}" \ + "${{ steps.artifacts.outputs.rpm }}" \ + "${{ steps.artifacts.outputs.portable_tar }}" \ + > "$CHECKSUMS" + + npx tauri signer sign "${{ steps.artifacts.outputs.deb }}" + npx tauri signer sign "${{ steps.artifacts.outputs.rpm }}" + npx tauri signer sign "${{ steps.artifacts.outputs.portable_tar }}" + + echo "checksums=$CHECKSUMS" >> "$GITHUB_OUTPUT" + echo "deb_sig=${{ steps.artifacts.outputs.deb }}.sig" >> "$GITHUB_OUTPUT" + echo "rpm_sig=${{ steps.artifacts.outputs.rpm }}.sig" >> "$GITHUB_OUTPUT" + echo "portable_tar_sig=${{ steps.artifacts.outputs.portable_tar }}.sig" >> "$GITHUB_OUTPUT" + + echo "Integrity artifacts:" + echo " SHA256SUMS : $CHECKSUMS" + echo " DEB SIG : ${{ steps.artifacts.outputs.deb }}.sig" + echo " RPM SIG : ${{ steps.artifacts.outputs.rpm }}.sig" + echo " TAR SIG : ${{ steps.artifacts.outputs.portable_tar }}.sig" + + # ── Create / update GitHub Release with Linux binaries ─────────────────── + # + # softprops/action-gh-release creates the release if it doesn't exist yet, + # or adds files to it if it was already created by the macOS or Windows + # workflow. latest.json is handled in the next step. + # + # Linux assets uploaded here: + # *.deb — Debian/Ubuntu package + # *.rpm — Fedora/RHEL/openSUSE package + # *linux-portable.tar.gz — standalone Linux portable package + # SHA256SUMS-linux-x86_64.txt — checksums for Linux artifacts + # *.deb.sig / *.rpm.sig / *.tar.gz.sig — detached signatures + - name: Prepare changelog notes for release + if: steps.version_meta.outputs.is_release == 'true' + run: | + TAG="${{ steps.version_meta.outputs.tag }}" + PREV_TAG="$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || true)" + RANGE="${PREV_TAG:+$PREV_TAG..}$TAG" + node scripts/ci.mjs prepare-changelog \ + "${{ steps.version_meta.outputs.version }}" release-notes.md "$RANGE" + + - name: Create / update GitHub Release + if: steps.version_meta.outputs.is_release == 'true' + uses: softprops/action-gh-release@v3 + with: + tag_name: ${{ steps.version_meta.outputs.tag }} + name: Skill v${{ steps.version_meta.outputs.version }} + generate_release_notes: false + body_path: release-notes.md + fail_on_unmatched_files: true + prerelease: ${{ steps.version_meta.outputs.prerelease == 'true' }} + files: | + ${{ steps.artifacts.outputs.deb }} + ${{ steps.artifacts.outputs.rpm }} + ${{ steps.artifacts.outputs.portable_tar }} + ${{ steps.integrity.outputs.checksums }} + ${{ steps.integrity.outputs.deb_sig }} + ${{ steps.integrity.outputs.rpm_sig }} + ${{ steps.integrity.outputs.portable_tar_sig }} + + # ── Update latest.json ──────────────────────────────────────────────────── + # + # Downloads the current latest.json from the release (created by whichever + # platform workflow ran first), merges the linux-x86_64 entry, then + # re-uploads. If no latest.json exists yet, creates one from scratch. + - name: Update latest.json + if: steps.version_meta.outputs.is_release == 'true' + env: + GH_TOKEN: ${{ github.token }} + run: >- + node scripts/ci.mjs update-latest-json + --platform linux-x86_64 + --url "https://github.com/${{ github.repository }}/releases/download/${{ steps.version_meta.outputs.tag }}/${{ steps.artifacts.outputs.portable_tar_name }}" + --sig-file "${{ steps.integrity.outputs.portable_tar_sig }}" + --tag "${{ steps.version_meta.outputs.tag }}" + --version "${{ steps.version_meta.outputs.version }}" + --upload + --mirror-to-rc-latest + + # ── Upload CI artifacts (on-demand builds only) ────────────────────────── + - name: Upload build artifacts + if: steps.version_meta.outputs.is_release != 'true' + uses: actions/upload-artifact@v7 + with: + name: NeuroSkill-Linux-${{ steps.version_meta.outputs.version }} + path: | + ${{ steps.artifacts.outputs.deb }} + ${{ steps.artifacts.outputs.rpm }} + ${{ steps.artifacts.outputs.portable_tar }} + retention-days: 14 + + # ── Discord notification ────────────────────────────────────────────────── + - name: Notify Discord of release + if: always() && steps.version_meta.outputs.is_release == 'true' + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + run: >- + node scripts/ci.mjs discord-notify + --status "${{ job.status }}" + --title "${{ job.status == 'success' && '🚀' || '💥' }} NeuroSkill™ v${{ steps.version_meta.outputs.version }} — Linux ${{ job.status == 'success' && 'Published' || 'Failed' }}" + --version "${{ steps.version_meta.outputs.version }}" + --tag "${{ steps.version_meta.outputs.tag }}" + --platform "Linux x86_64" + --release-url "${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ steps.version_meta.outputs.tag }}" + --run-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" diff --git a/.github/workflows/release-mac.yml b/.github/workflows/release-mac.yml new file mode 100644 index 000000000..0c247a502 --- /dev/null +++ b/.github/workflows/release-mac.yml @@ -0,0 +1,410 @@ +name: Release - Mac + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + - 'v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+' + workflow_dispatch: + +permissions: + contents: write + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + # WidgetKit signing currently breaks Apple notarization (Invalid). Set to + # 'true' to re-enable once appex signing/timestamps are fixed. + ENABLE_WIDGETKIT: 'false' + +jobs: + release: + name: Build & Release — aarch64-apple-darwin + runs-on: macos-26 + environment: Release + + steps: + # ── Checkout ────────────────────────────────────────────────────────────── + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 # full history so git tag --format can read the annotation + + # ── Verify tag matches VERSION ──────────────────────────────────────────── + # Catches "forgot to bump the version" before wasting 20 min on a build. + - name: Resolve version + id: version_meta + env: + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_REF: ${{ github.ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + run: node scripts/ci.mjs resolve-version + + - name: Release setup (Rust + Node) + uses: ./.github/actions/release-setup + with: + target: aarch64-apple-darwin + macos-tools: "true" + + # ── Pre-flight: verify every secret is set ─────────────────────────────── + # Catches missing / renamed secrets in seconds instead of after a 15-minute + # build. Each variable is checked for non-empty; none of the values are + # printed. + - name: Verify signing and notarization secrets are present + env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + run: >- + node scripts/ci.mjs verify-secrets + APPLE_CERTIFICATE APPLE_CERTIFICATE_PASSWORD APPLE_SIGNING_IDENTITY + APPLE_ID APPLE_PASSWORD APPLE_TEAM_ID TAURI_SIGNING_PRIVATE_KEY + + # ── Pre-flight: validate APPLE_SIGNING_IDENTITY is the right cert type ─── + # Only `Developer ID Application` certs pass Gatekeeper for distribution. + # Apple Development / Mac Developer / ad-hoc / self-signed all silently + # break the runtime Safari extension flow on end-user Macs (Safari refuses + # to load extensions from Gatekeeper-rejected apps). Fail fast here. + - name: Validate Apple signing identity is Developer ID Application + env: + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + run: node scripts/ci.mjs validate-apple-signing-identity + + # ── Pre-flight: validate Apple notarization credentials ─────────────────── + # xcrun notarytool history authenticates against Apple's API using the same + # credentials the build will use, but without submitting anything. + # A 401 here means the app-specific password is wrong or expired. + # Generate a fresh one at https://appleid.apple.com → App-Specific Passwords, + # then update the APPLE_PASSWORD secret in the repository environment. + - name: Validate Apple notarization credentials + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + run: node scripts/ci.mjs validate-notarization + + # ── Apple Developer certificate → temporary keychain ───────────────────── + # + # Secrets required: + # APPLE_CERTIFICATE — base64-encoded .p12 (Developer ID Application cert) + # APPLE_CERTIFICATE_PASSWORD — password protecting the .p12 + # + # How to export the .p12: + # Keychain Access → right-click cert → Export → Personal Information Exchange + # Then: base64 -i cert.p12 | pbcopy (paste into the GitHub secret) + - name: Import Apple Developer certificate + env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + run: node scripts/ci.mjs import-apple-cert + + # ── WidgetKit extension (optional; off until notarization-safe) ───────── + - name: Install XcodeGen + if: env.ENABLE_WIDGETKIT == 'true' + run: brew install xcodegen + + - name: Setup WidgetKit signing + if: env.ENABLE_WIDGETKIT == 'true' + env: + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPLE_ASC_KEY_ID: ${{ secrets.APPLE_ASC_KEY_ID }} + APPLE_ASC_KEY_ISSUER_ID: ${{ secrets.APPLE_ASC_KEY_ISSUER_ID }} + APPLE_ASC_KEY_BASE64: ${{ secrets.APPLE_ASC_KEY_BASE64 }} + APPLE_WIDGET_PROVISIONING_PROFILE_BASE64: ${{ secrets.APPLE_WIDGET_PROVISIONING_PROFILE_BASE64 }} + run: node scripts/ci.mjs setup-widget-signing + + - name: Build WidgetKit extension + if: env.ENABLE_WIDGETKIT == 'true' + env: + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + AUTH_KEY_PATH: ${{ env.AUTH_KEY_PATH }} + AUTH_KEY_ID: ${{ env.AUTH_KEY_ID }} + AUTH_KEY_ISSUER_ID: ${{ env.AUTH_KEY_ISSUER_ID }} + run: node scripts/ci.mjs build-widgets + + # ── Compile: frontend + Rust binary + sidecar ─────────────────────────── + # + # `npx tauri build` crashes on macOS 26 with SIGILL *after* compilation, + # inside the Tauri CLI napi binary's bundling code. The crash occurs + # regardless of --no-sign, pointing to the icon/resource pipeline (likely + # SIMD image-processing code that emits ud2 on this CPU). + # + # Fix: invoke the two compile steps directly and assemble the .app in the + # next step, bypassing the Tauri CLI bundler entirely. + # + # npm run build → SvelteKit/Vite frontend (output: build/, embedded by + # generate_context!() when custom-protocol is enabled) + # cargo build → Rust binary; build.rs also copies espeak-ng-data into + # src-tauri/resources/ so it is ready for bundling. + # + # --features custom-protocol is required: without it Tauri compiles in + # dev mode and the binary tries to load the UI from localhost:1420 + # instead of serving the embedded SvelteKit build output. The Tauri + # CLI (`npx tauri build`) injects this feature automatically, but we + # bypass the CLI here to avoid its post-compilation SIGILL. + - name: Compile (frontend + Rust + sidecar) + shell: bash + run: | + set -euo pipefail + npm run build + npm run -s verify:tauri:frontend + # Shared recipe: daemon (apple) → tty → app (custom-protocol). + node scripts/compile-product.mjs \ + --target aarch64-apple-darwin \ + --release --locked --timings + + # ── Assemble + sign .app bundle ───────────────────────────────────── + - name: Create .app bundle + env: + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + FRONTEND_BUILD_DIR: build + SKIP_WIDGETS: ${{ env.ENABLE_WIDGETKIT == 'true' && '0' || '1' }} + run: bash scripts/assemble-macos-app.sh aarch64-apple-darwin + + # ── Smoke-test bundled binaries ────────────────────────────────────── + - name: Smoke-test bundled binaries + run: | + set -euo pipefail + APP_DIR="src-tauri/target/aarch64-apple-darwin/release/bundle/macos/NeuroSkill.app" + + echo "── codesign verification ──" + codesign --verify --deep --strict "$APP_DIR" + echo "✓ codesign --verify passed" + + echo "── daemon binary check ──" + DAEMON_BIN="$APP_DIR/Contents/MacOS/skill-daemon.app/Contents/MacOS/skill-daemon" + file "$DAEMON_BIN" + file "$DAEMON_BIN" | grep -q "Mach-O.*arm64" \ + || { echo "::error::skill-daemon is not a valid arm64 Mach-O binary"; exit 1; } + echo "✓ skill-daemon is a valid arm64 Mach-O binary" + + echo "── main binary architecture check ──" + MAIN_BIN="$APP_DIR/Contents/MacOS/NeuroSkill" + lipo -info "$MAIN_BIN" + lipo -info "$MAIN_BIN" | grep -q "arm64" \ + || { echo "::error::NeuroSkill binary does not contain arm64 architecture"; exit 1; } + echo "✓ NeuroSkill binary contains arm64 architecture" + + # ── Upload cargo build timings ───────────────────────────────────────── + # The --timings flag produces an HTML report showing per-crate compile + # durations. Upload it so slow crates can be identified and targeted. + - name: Upload cargo build timings + if: always() + uses: actions/upload-artifact@v7 + with: + name: cargo-timings-macos + path: src-tauri/target/cargo-timings/ + retention-days: 30 + + # ── Create DMG (background, README, CHANGELOG, LICENSE) ──────────────── + # + # Uses sindresorhus/create-dmg via scripts/create-macos-dmg.sh which: + # • Creates DMG with composed icon, Retina background, SLA + # • Notarizes with Apple (staples both DMG and .app) + - name: Install DMG dependencies + run: | + # npm@latest no longer runs dependency install scripts by default, so + # appdmg's native deps (fs-xattr, macos-alias) skip their node-gyp build + # and `volume.node` goes missing. Allowlist their scripts explicitly. + npm install --global appdmg --allow-scripts=fs-xattr,macos-alias + pip3 install --quiet --break-system-packages Pillow + + - name: Create DMG + env: + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + run: | + echo "Creating DMG with version ${{ steps.version_meta.outputs.version }}" + bash scripts/create-macos-dmg.sh aarch64-apple-darwin + # Verify the DMG was created with the correct version + DMG_FILE=$(find src-tauri/target/aarch64-apple-darwin/release/bundle/dmg -name "*.dmg" -maxdepth 1 | head -1) + if [[ "$DMG_FILE" == *"${{ steps.version_meta.outputs.version }}"* ]]; then + echo "✓ DMG filename contains correct version: ${{ steps.version_meta.outputs.version }}" + else + echo "::error::DMG filename does not contain expected version" + exit 1 + fi + + # ── Create updater artifact from the signed + stapled .app ───────────── + # + # Runs after Create DMG so the .app is already notarized and stapled. + # The Tauri bundler never ran, so there is no stale .tar.gz to clean up. + # This step creates the updater bundle from scratch and signs it with the + # Ed25519 key. `npx tauri signer sign` is safe here — it is pure + # cryptography with no macOS-API bundling code, so it does not hit the + # SIGILL that kills `npx tauri build`. + - name: Recreate updater artifact from signed .app + if: steps.version_meta.outputs.is_release == 'true' + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + run: | + BUNDLE_DIR="src-tauri/target/aarch64-apple-darwin/release/bundle" + APP_BUNDLE="$(find "$BUNDLE_DIR/macos" -name "*.app" -maxdepth 1 | head -1)" + + # No stale artifacts to remove (Tauri bundler never ran). + # Belt-and-suspenders: delete any partial files if this step retries. + find "$BUNDLE_DIR/macos" \ + \( -name "*.app.tar.gz" -o -name "*.app.tar.gz.sig" \) -delete + + # Create tarball from the signed + notarized + stapled .app. + # Use an ASCII-only filename for the tarball so the URL stored in + # latest.json is a valid RFC 3986 URI; non-ASCII characters such as ™ + # (U+2122) in the filename cause the Tauri updater to produce a 404 + # because its HTTP client rejects or misroutes an un-encoded URL. + # The .app bundle inside the tar keeps its original NeuroSkill.app name. + VERSION="${{ steps.version_meta.outputs.version }}" + TAR_PATH="$BUNDLE_DIR/macos/NeuroSkill_${VERSION}_aarch64.app.tar.gz" + tar -czf "$TAR_PATH" \ + -C "$(dirname "$APP_BUNDLE")" "$(basename "$APP_BUNDLE")" + + # Sign with Tauri's Ed25519 key. + # FILE is a positional argument in Tauri v2; -f is now --private-key-path + # and would conflict with the TAURI_SIGNING_PRIVATE_KEY env var (--private-key). + npx tauri signer sign "$TAR_PATH" + + # ── Always delete the temporary keychain ────────────────────────────────── + - name: Delete temporary keychain + if: always() + run: security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true + + # ── Locate build artifacts ──────────────────────────────────────────────── + - name: Collect artifact paths + if: steps.version_meta.outputs.is_release == 'true' + id: artifacts + run: | + BUNDLE_DIR="src-tauri/target/aarch64-apple-darwin/release/bundle" + + APP_TAR="$(find "$BUNDLE_DIR/macos" \ + -name "*.app.tar.gz" ! -name "*.sig" \ + -maxdepth 1 | head -1)" + APP_SIG="$(find "$BUNDLE_DIR/macos" \ + -name "*.app.tar.gz.sig" \ + -maxdepth 1 | head -1)" + DMG="$(find "$BUNDLE_DIR/dmg" \ + -name "*.dmg" \ + -maxdepth 1 | head -1)" + + if [ -z "$APP_TAR" ] || [ -z "$APP_SIG" ] || [ -z "$DMG" ]; then + echo "::error::One or more expected artifacts are missing." + echo " APP_TAR : $APP_TAR" + echo " APP_SIG : $APP_SIG" + echo " DMG : $DMG" + exit 1 + fi + + echo "app_tar=$APP_TAR" >> "$GITHUB_OUTPUT" + echo "app_sig=$APP_SIG" >> "$GITHUB_OUTPUT" + echo "dmg=$DMG" >> "$GITHUB_OUTPUT" + echo "app_tar_name=$(basename "$APP_TAR")" >> "$GITHUB_OUTPUT" + echo "dmg_name=$(basename "$DMG")" >> "$GITHUB_OUTPUT" + + echo "Artifacts found:" + echo " TAR : $APP_TAR ($(du -sh "$APP_TAR" | cut -f1))" + echo " SIG : $APP_SIG" + echo " DMG : $DMG ($(du -sh "$DMG" | cut -f1))" + + # ── Create GitHub Release + upload macOS binary assets ─────────────────── + # + # Creates (or updates) the release and uploads the macOS-specific binaries. + # latest.json is handled in the next step so that the Windows and Linux + # release workflows can each merge their own platform entry without + # overwriting each other. + # + # macOS assets uploaded here: + # NeuroSkill__aarch64.dmg — drag-to-install disk image + # NeuroSkill.app.tar.gz — Tauri updater bundle + # NeuroSkill.app.tar.gz.sig — Ed25519 signature for the updater + - name: Prepare changelog notes for release + if: steps.version_meta.outputs.is_release == 'true' + run: | + TAG="${{ steps.version_meta.outputs.tag }}" + PREV_TAG="$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || true)" + RANGE="${PREV_TAG:+$PREV_TAG..}$TAG" + node scripts/ci.mjs prepare-changelog \ + "${{ steps.version_meta.outputs.version }}" release-notes.md "$RANGE" + + - name: Create GitHub Release + if: steps.version_meta.outputs.is_release == 'true' + uses: softprops/action-gh-release@v3 + with: + tag_name: ${{ steps.version_meta.outputs.tag }} + name: Skill v${{ steps.version_meta.outputs.version }} + generate_release_notes: false + body_path: release-notes.md + fail_on_unmatched_files: true + # `prerelease` is derived from the version string: any `-rc.N` suffix + # marks the build as a release candidate. GitHub treats prereleases + # specially — `releases/latest/download/...` URLs skip them — so the + # stable update channel automatically ignores RCs without any client + # configuration. RC users hit the rc-latest mirror updated below. + prerelease: ${{ steps.version_meta.outputs.prerelease == 'true' }} + files: | + ${{ steps.artifacts.outputs.dmg }} + ${{ steps.artifacts.outputs.app_tar }} + ${{ steps.artifacts.outputs.app_sig }} + + # ── Update latest.json ──────────────────────────────────────────────────── + # + # Downloads the current latest.json from the release (if a Windows or Linux + # platform workflow already uploaded one), merges the darwin-aarch64 entry, + # then re-uploads via `gh release upload --clobber`. + # If no latest.json exists yet this step creates one from scratch. + # + # All three platform release workflows (macOS, Windows, Linux) use the same + # download → merge → upload pattern, so the single file at: + # https://github.com/NeuroSkill-com/skill/releases/latest/download/latest.json + # accumulates entries for every platform as each build completes. + # The Tauri updater reads only its own platform key (e.g. "darwin-aarch64") + # and ignores the rest, so a partial manifest is valid while other platform + # builds are still in flight. + - name: Update latest.json + if: steps.version_meta.outputs.is_release == 'true' + env: + GH_TOKEN: ${{ github.token }} + # `--mirror-to-rc-latest` also pushes the manifest to the mutable + # `rc-latest` GitHub Release, which is the URL the RC update channel + # polls. Both stable and RC builds mirror, so RC opt-in users always + # see the most recent build of either kind. The release is bootstrapped + # automatically on the first run via cmdEnsureRcLatestRelease. + run: >- + node scripts/ci.mjs update-latest-json + --platform darwin-aarch64 + --url "https://github.com/${{ github.repository }}/releases/download/${{ steps.version_meta.outputs.tag }}/${{ steps.artifacts.outputs.app_tar_name }}" + --sig-file "${{ steps.artifacts.outputs.app_sig }}" + --tag "${{ steps.version_meta.outputs.tag }}" + --version "${{ steps.version_meta.outputs.version }}" + --upload + --mirror-to-rc-latest + + # ── Upload CI artifacts (manual / on-demand builds only) ──────────────── + - name: Upload build artifacts + if: steps.version_meta.outputs.is_release != 'true' + uses: actions/upload-artifact@v7 + with: + name: NeuroSkill-macOS-${{ steps.version_meta.outputs.version }} + path: | + src-tauri/target/aarch64-apple-darwin/release/bundle/macos/*.app + src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/*.dmg + retention-days: 14 + + # ── Discord notification ──────────────────────────────────────────────── + - name: Notify Discord of release + if: always() && steps.version_meta.outputs.is_release == 'true' + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + run: >- + node scripts/ci.mjs discord-notify + --status "${{ job.status }}" + --title "${{ job.status == 'success' && '🚀' || '💥' }} NeuroSkill™ v${{ steps.version_meta.outputs.version }} — macOS ${{ job.status == 'success' && 'Published' || 'Failed' }}" + --version "${{ steps.version_meta.outputs.version }}" + --tag "${{ steps.version_meta.outputs.tag }}" + --platform "macOS aarch64" + --release-url "${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ steps.version_meta.outputs.tag }}" + --run-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml new file mode 100644 index 000000000..846f2b4fc --- /dev/null +++ b/.github/workflows/release-windows.yml @@ -0,0 +1,751 @@ +name: Release — Windows + +# Triggered by the same semver tag as the macOS and Linux release workflows. +# All three run in parallel; each uploads its own binaries and then merges +# its platform entry into the shared latest.json updater manifest. +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + - 'v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+' + workflow_dispatch: + inputs: + dry_run: + description: 'CI test only — build but skip release upload' + type: boolean + default: true + +permissions: + contents: write # create/update GitHub Releases and upload assets + +# Opt into Node.js 24 now — same rationale as ci.yml. +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + release-windows: + name: Build & Release — x86_64-pc-windows-msvc + runs-on: windows-latest + environment: ${{ inputs.dry_run && 'CI-Test' || 'Release' }} + + steps: + # ── Checkout ────────────────────────────────────────────────────────────── + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Validate Windows app manifest + shell: bash + run: node scripts/check-windows-manifest.mjs src-tauri/manifest.xml + + # ── Resolve version ───────────────────────────────────────────────────── + # Tag push: verify tag matches VERSION (and derived package.json / + # tauri.conf.json / Cargo.toml). workflow_dispatch: read VERSION. + - name: Resolve version + id: version_meta + shell: bash + env: + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_REF: ${{ github.ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + DRY_RUN: ${{ inputs.dry_run || 'false' }} + run: node scripts/ci.mjs resolve-version + + # ── MSVC developer environment ──────────────────────────────────────────── + # Puts cl.exe, link.exe, lib.exe, and signtool.exe in PATH. + # Required for the espeak-ng static library build (lib.exe) and for + # Tauri's Windows code-signing step (signtool.exe). + - name: Set up MSVC developer environment + uses: ilammy/msvc-dev-cmd@v1 + + - name: Validate Windows app manifest with mt.exe + shell: powershell + run: | + $mt = Get-Command mt.exe -ErrorAction SilentlyContinue + if (-not $mt) { + Write-Error "mt.exe not found in PATH" + exit 1 + } + & $mt.Source -nologo -manifest src-tauri/manifest.xml -validate_manifest + if ($LASTEXITCODE -ne 0) { + Write-Error "mt.exe manifest validation failed" + exit $LASTEXITCODE + } + Write-Host "[ok] mt.exe manifest validation passed" + + # ── Remove Git's link.exe and tar.exe ──────────────────────────────────── + # Git for Windows prepends C:\Program Files\Git\usr\bin to PATH at every + # step via $GITHUB_PATH (re-applied each step, so runtime PATH changes + # don't survive — only direct file deletion works). Two binaries cause + # problems: + # + # link.exe — Unix hard-link utility; shadows MSVC's linker, producing + # "extra operand" build errors. + # tar.exe — GNU tar used by actions/cache with --posix, which strips + # or corrupts non-POSIX Windows paths in the saved archive, + # breaking cache restores. Deleting it forces the use of + # C:\Windows\System32\tar.exe (built-in bsdtar 3.x). + - name: Remove Git's link.exe and tar.exe (shadow MSVC linker / corrupt cache) + shell: powershell + run: | + foreach ($bin in @("link.exe", "tar.exe")) { + $path = "C:\Program Files\Git\usr\bin\$bin" + if (Test-Path $path) { + Remove-Item $path -Force + Write-Host "[ok] Deleted $path" + } else { + Write-Host "[skip] $path not found" + } + } + + # ── Release setup: Rust + rust-cache + Node + frontend ─────────────────── + - name: Release setup (Rust + Node + frontend) + uses: ./.github/actions/release-setup + with: + target: x86_64-pc-windows-msvc + cache-key: windows-release-x86_64-msvc-v2 + cache-save-if: always() + windows-protoc: "true" + build-frontend: "true" + + # ── ONNX Runtime — no longer used on any platform ───────────────────── + # KittenTTS was the only `ort` consumer and now runs natively on RLX, so + # `ort` / `ort-sys` are absent from Cargo.lock entirely. The install and + # cache steps, and the setup-onnxruntime-{linux,windows} composite actions, + # have been deleted along with the .so/.dll bundling in the Linux packaging + # scripts. Reintroducing an ORT-backed backend means adding all of that + # back, not just a cache step. + + # ── NSIS installer tooling ─────────────────────────────────────────────── + # Product Windows builds ship CUDA + wgpu (runtime falls back without NVIDIA). + # LunarG Vulkan SDK is not required. + - name: Cache NSIS + uses: actions/cache@v5 + with: + path: | + C:\Program Files (x86)\NSIS + C:\nsis + key: windows-nsis-v1 + + - name: Install NSIS + shell: powershell + run: | + $makensis = Get-Command makensis -ErrorAction SilentlyContinue + if ($makensis) { + Write-Output "[ok] NSIS already on PATH: $($makensis.Source)" + } else { + $installed = $false + for ($i = 1; $i -le 2; $i++) { + try { + Write-Output "[info] NSIS not found; installing via Chocolatey (attempt $i)" + choco install nsis -y --no-progress 2>&1 + if ($LASTEXITCODE -eq 0) { $installed = $true; break } + } catch { + Write-Output "[warn] choco attempt $i failed: $($_.Exception.Message)" + } + Start-Sleep -Seconds (3 * $i) + } + + if (-not $installed) { + Write-Output "[warn] Chocolatey unavailable; falling back to direct NSIS download" + $nsisVer = "3.10" + $url = "https://sourceforge.net/projects/nsis/files/NSIS%203/$nsisVer/nsis-$nsisVer.zip/download" + $zip = Join-Path $env:TEMP "nsis-$nsisVer.zip" + $dest = "C:\nsis" + Invoke-WebRequest -Uri $url -OutFile $zip -UserAgent "Mozilla/5.0" -MaximumRedirection 10 + Expand-Archive -Path $zip -DestinationPath $dest -Force + $innerDir = Get-ChildItem -Path $dest -Directory | Select-Object -First 1 + if ($innerDir) { + Get-ChildItem -Path $innerDir.FullName | Move-Item -Destination $dest -Force + Remove-Item $innerDir.FullName -Recurse -Force -ErrorAction SilentlyContinue + } + if (-not (Test-Path (Join-Path $dest "makensis.exe"))) { + throw "NSIS direct download failed: makensis.exe not found in $dest" + } + Write-Output "[ok] Installed NSIS $nsisVer via direct download to $dest" + } + } + + # Locate makensis and export NSIS_DIR + $makensis = Get-Command makensis -ErrorAction SilentlyContinue + if ($makensis) { + $nsisDir = Split-Path -Parent $makensis.Source + } else { + $candidatePaths = @( + "C:\Program Files (x86)\NSIS", + "C:\Program Files\NSIS", + "$env:LOCALAPPDATA\NSIS", + "C:\nsis" + ) + $nsisDir = $null + foreach ($path in $candidatePaths) { + if (Test-Path (Join-Path $path "makensis.exe")) { + $nsisDir = $path + break + } + } + } + + if (-not $nsisDir -or -not (Test-Path (Join-Path $nsisDir "makensis.exe"))) { + Write-Error "NSIS installation succeeded but makensis.exe is still not discoverable" + exit 1 + } + + "NSIS_DIR=$nsisDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + $nsisDir | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + Write-Host "[ok] NSIS_DIR=$nsisDir" + + # ── OpenBLAS (upstream binary release) ──────────────────────────────────── + # rlx-cpu emits `cargo:rustc-link-lib=openblas` on all non-macOS targets, + # so the Windows link step needs `openblas.lib` + `openblas.dll`. We + # download the upstream OpenBLAS Windows archive because vcpkg's port + # omits LAPACK and rlx-cpu's `blas` feature calls dgesv_/sgesv_. + # Cached so the download only happens once per script change. + - name: Cache OpenBLAS (upstream Windows binary) + uses: actions/cache@v5 + with: + path: C:\OpenBLAS + key: windows-openblas-${{ hashFiles('scripts/install-openblas-windows.ps1') }} + + - name: Install OpenBLAS + export OPENBLAS_DIR + shell: powershell + run: .\scripts\install-openblas-windows.ps1 + + # ── Ensure VC++ Redistributable DLLs are available ─────────────────────── + # The binary uses dynamic CRT (/MD). The NSIS installer bundles the CRT + # DLLs app-locally alongside skill.exe. This step ensures they exist on + # the runner — either via the VS installation or by downloading the + # official VC++ Redistributable from Microsoft. + - name: Ensure VC++ CRT DLLs available + id: vcredist + shell: powershell + run: | + & (Join-Path $env:GITHUB_WORKSPACE "scripts/ensure-vcredist.ps1") + + # Export for subsequent steps + if (-not $env:CRT_REDIST_DIR) { + Write-Error "ensure-vcredist.ps1 did not set CRT_REDIST_DIR" + exit 1 + } + + # ── Pre-flight: verify every required secret is set ────────────────────── + # Fails fast (before the 15-min build) if any secret is missing. + # Values are never printed. + # + # WINDOWS_CERTIFICATE — base64-encoded .pfx code-signing certificate + # (OV or EV cert from DigiCert, Sectigo, etc.) + # How to encode: + # certutil -encode cert.pfx cert.b64 + # — or on PowerShell: + # [Convert]::ToBase64String([IO.File]::ReadAllBytes("cert.pfx")) + # + # WINDOWS_CERTIFICATE_PASSWORD — password protecting the .pfx + # Optional — if omitted the installer is built + # unsigned (SmartScreen will warn on first run). + # + # TAURI_SIGNING_PRIVATE_KEY — Ed25519 private key for signing Tauri + # updater artifacts (same key as macOS/Linux) + - name: Verify signing secrets are present + if: steps.version_meta.outputs.dry_run != 'true' + id: signing_meta + shell: bash + env: + WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} + WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + run: | + # TAURI_SIGNING_PRIVATE_KEY is always required (updater artifact signing). + if [ -z "$TAURI_SIGNING_PRIVATE_KEY" ]; then + echo "::error::Secret 'TAURI_SIGNING_PRIVATE_KEY' is empty or not set." + exit 1 + fi + echo "[ok] TAURI_SIGNING_PRIVATE_KEY is present." + + # Windows code-signing certificate is optional. + # Both variables must be set together or both must be absent. + HAVE_CERT=false + if [ -n "$WINDOWS_CERTIFICATE" ] && [ -n "$WINDOWS_CERTIFICATE_PASSWORD" ]; then + HAVE_CERT=true + elif [ -n "$WINDOWS_CERTIFICATE" ] || [ -n "$WINDOWS_CERTIFICATE_PASSWORD" ]; then + echo "::error::WINDOWS_CERTIFICATE and WINDOWS_CERTIFICATE_PASSWORD" \ + "must both be set or both be absent." + exit 1 + fi + + if [ "$HAVE_CERT" = "true" ]; then + echo "[ok] Windows code-signing certificate is present - installer will be signed." + else + echo "::warning::WINDOWS_CERTIFICATE is not set." \ + "The NSIS installer will be built unsigned." \ + "Windows SmartScreen may show a warning to end-users." + fi + + # Expose for later steps. + echo "have_windows_cert=$HAVE_CERT" >> "$GITHUB_OUTPUT" + echo "HAVE_WINDOWS_CERT=$HAVE_CERT" >> "$GITHUB_ENV" + + # ── Import Windows code-signing certificate (optional) ─────────────────── + # Skipped when WINDOWS_CERTIFICATE is not set (unsigned build). + # When present: imports the .pfx into the Current User certificate store, + # captures the SHA-1 thumbprint, and exposes it as CERTIFICATE_THUMBPRINT + # for the build step. The temp .pfx file is deleted immediately after import. + # + # SmartScreen note: an OV (Organisation Validated) certificate will still + # trigger SmartScreen warnings on first run until the binary accumulates + # reputation. An EV (Extended Validation) certificate skips this entirely. + - name: Import Windows code-signing certificate + if: steps.version_meta.outputs.dry_run != 'true' && steps.signing_meta.outputs.have_windows_cert == 'true' + shell: powershell + env: + WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} + WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} + run: | + $certBytes = [System.Convert]::FromBase64String($env:WINDOWS_CERTIFICATE) + $certPath = Join-Path $env:RUNNER_TEMP "signing.pfx" + [IO.File]::WriteAllBytes($certPath, $certBytes) + + $password = ConvertTo-SecureString $env:WINDOWS_CERTIFICATE_PASSWORD ` + -AsPlainText -Force + $cert = Import-PfxCertificate ` + -FilePath $certPath ` + -CertStoreLocation Cert:\CurrentUser\My ` + -Password $password + + Remove-Item $certPath -Force + + $thumbprint = $cert.Thumbprint + "CERTIFICATE_THUMBPRINT=$thumbprint" | + Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + $expiry = $cert.NotAfter.ToString("yyyy-MM-dd") + Write-Host "[ok] Certificate imported" + Write-Host " Thumbprint : $thumbprint" + Write-Host " Subject : $($cert.Subject)" + Write-Host " Expires : $expiry" + + if ($cert.NotAfter -lt (Get-Date)) { + Write-Error "::error::Code-signing certificate has expired ($expiry). Renew it." + exit 1 + } + + # ── Standalone LLVM 19 for bindgen + lld-link ────��──────────────────────── + # VS2022's bundled clang-cl can't handle __builtin_ia32_* intrinsics in + # mmintrin.h, causing bindgen to fail. Standalone LLVM 19 fixes this + # and also provides lld-link for faster linking. + # + - name: Install LLVM 19 + uses: KyleMayes/install-llvm-action@v2.0.9 + with: + version: "19" + directory: C:\LLVM + + - name: Export LLVM env vars + shell: powershell + run: | + $dll = Get-ChildItem "C:\LLVM" -Recurse -Filter "libclang.dll" ` + -ErrorAction SilentlyContinue | + Select-Object -First 1 + if (-not $dll) { + Write-Error "libclang.dll not found under C:\LLVM" + exit 1 + } + $bin = Split-Path $dll.FullName + + @( + "LIBCLANG_PATH=$bin", + "LLVM_CONFIG=C:\LLVM\bin\llvm-config.exe" + ) | ForEach-Object { + $_ | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + Write-Host "[ok] $_" + } + + # NOTE: this step used to pin the linker to MSVC link.exe and set + # SKILL_NO_LLD below, on the theory that lld-link could not resolve + # Windows SDK import libraries like combase.lib. That diagnosis was + # wrong. `combase.lib` does not exist in the Windows SDK at all — the + # WinRT exports live in combase.dll but are imported from + # runtimeobject.lib — so BOTH linkers correctly reported a missing + # file. The real fault was `#[link(name = "combase")]` in + # webbluetooth-windows 0.0.1, fixed upstream in 0.0.2. + # + # lld-link links the fixed graph fine (verified by cross-linking the + # whole BLE subgraph against a real Windows SDK), and it is materially + # faster on a job that is link-bound. Do not re-pin the linker without + # reading the actual linker error first. + + # ── Compile Rust (shared compile-product recipe) ───────────────────────── + # + # Daemon gets `--features windows` (CUDA + wgpu); app gets `custom-protocol` only. + - name: Compile (Rust) + shell: bash + env: + LIBCLANG_PATH: ${{ env.LIBCLANG_PATH }} + LLVM_CONFIG: ${{ env.LLVM_CONFIG }} + # Dynamic CRT: native deps (DirectML, ONNX Runtime) + # ship as pre-built /MD DLLs. Using /MD everywhere avoids CRT + # mismatch. The NSIS installer bundles the VC++ Redist DLLs + # app-locally so end users don't need a separate install. + CMAKE_MSVC_RUNTIME_LIBRARY: MultiThreadedDLL + run: | + set -euo pipefail + node scripts/compile-product.mjs \ + --target x86_64-pc-windows-msvc \ + --release --locked --timings + + # rlx-cpu and turbovec resolve cblas_sgemm via openblas.dll at runtime. + # Bundle it next to skill.exe / skill_daemon.exe so the installed app + # works on machines without OpenBLAS preinstalled. The upstream 0.3.30 + # DLL is statically linked against the MinGW runtime (depends only on + # KERNEL32.dll + msvcrt.dll), so no extra runtime DLLs need shipping. + - name: Stage openblas.dll for packaging + shell: powershell + run: | + if (-not $env:OPENBLAS_DLL) { + Write-Error "OPENBLAS_DLL is not set (install-openblas-windows.ps1 must run before this step)" + exit 1 + } + if (-not (Test-Path $env:OPENBLAS_DLL)) { + Write-Error "openblas.dll not found at $env:OPENBLAS_DLL" + exit 1 + } + + $releaseDirs = @( + "src-tauri/target/release", + "src-tauri/target/x86_64-pc-windows-msvc/release" + ) + + foreach ($dir in $releaseDirs) { + New-Item -ItemType Directory -Force -Path $dir | Out-Null + Copy-Item $env:OPENBLAS_DLL (Join-Path $dir "openblas.dll") -Force + Write-Host "[ok] Copied openblas.dll to $dir" + } + + # ── Upload cargo build timings ─────────────────────────────────────────── + # The --timings flag produces an HTML report showing per-crate compile + # durations. Upload it so slow crates can be identified and targeted. + - name: Upload cargo build timings + if: always() + uses: actions/upload-artifact@v7 + with: + name: cargo-timings-windows + path: src-tauri/target/cargo-timings/ + retention-days: 30 + + # ── Log binary dependencies (diagnostic) ────────────────────────────── + # CRT DLLs were already located in the "Ensure VC++ CRT DLLs available" + # step before the build. This step just logs the dependency list. + - name: Log binary dependencies + shell: powershell + run: | + $exeCandidates = @( + "src-tauri/target/x86_64-pc-windows-msvc/release/skill.exe", + "src-tauri/target/release/skill.exe" + ) + + $exe = $null + foreach ($candidate in $exeCandidates) { + if (Test-Path $candidate) { + $exe = $candidate + break + } + } + + if (-not $exe) { + Write-Error "skill.exe not found. Checked: $(($exeCandidates -join ', '))" + exit 1 + } + + Write-Host "Binary dependencies:" + & dumpbin /dependents $exe + + Write-Host "" + Write-Host "CRT DLLs will be bundled from: $env:CRT_REDIST_DIR" + + # ── Package NSIS installer ─────────────────────────────────────────────── + # + # Runs the NSIS packaging script against the pre-built binary, then + # optionally signs the installer and creates updater artifacts. + - name: Package NSIS installer + shell: powershell + run: | + & powershell -ExecutionPolicy Bypass -File scripts/create-windows-nsis.ps1 + if ($LASTEXITCODE -ne 0) { + Write-Host "[error] create-windows-nsis.ps1 failed with exit code $LASTEXITCODE" + exit $LASTEXITCODE + } + + $bundleDirCandidates = @( + (Join-Path $PWD "src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis"), + (Join-Path $PWD "src-tauri/target/release/bundle/nsis") + ) + + $bundleDir = $null + $installer = $null + foreach ($candidate in $bundleDirCandidates) { + if (-not (Test-Path $candidate)) { continue } + $candidateInstaller = Get-ChildItem -Path $candidate -File -Filter "*.exe" -ErrorAction SilentlyContinue | + Where-Object { $_.Name -notmatch "(?i)uninstall" } | + Sort-Object LastWriteTime -Descending | + Select-Object -First 1 + if ($candidateInstaller) { + $bundleDir = $candidate + $installer = $candidateInstaller + break + } + } + + if (-not $installer) { + Write-Host "[error] NSIS installer .exe not found in expected bundle directories" + foreach ($candidate in $bundleDirCandidates) { + if (Test-Path $candidate) { + Write-Host "[diag] bundle dir snapshot: $candidate" + Get-ChildItem -Path $candidate -File -ErrorAction SilentlyContinue | + Select-Object Name, Length | + Format-Table -AutoSize | + Out-String | + Write-Host + } else { + Write-Host "[diag] bundle dir not found: $candidate" + } + } + exit 1 + } + + Write-Host "[ok] NSIS installer built: $($installer.FullName) ($([math]::Round($installer.Length / 1MB, 1)) MB)" + + # ── Sign installer + create updater artifacts ──────────────────────────── + # Skipped in dry-run mode (no signing secrets available). + - name: Sign installer + create updater artifacts + if: steps.version_meta.outputs.dry_run != 'true' + shell: powershell + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + run: | + $bundleDirCandidates = @( + (Join-Path $PWD "src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis"), + (Join-Path $PWD "src-tauri/target/release/bundle/nsis") + ) + + $bundleDir = $null + $installer = $null + foreach ($candidate in $bundleDirCandidates) { + if (-not (Test-Path $candidate)) { continue } + $candidateInstaller = Get-ChildItem -Path $candidate -File -Filter "*.exe" -ErrorAction SilentlyContinue | + Where-Object { $_.Name -notmatch "(?i)uninstall" } | + Sort-Object LastWriteTime -Descending | + Select-Object -First 1 + if ($candidateInstaller) { + $bundleDir = $candidate + $installer = $candidateInstaller + break + } + } + + if ($env:CERTIFICATE_THUMBPRINT) { + Write-Host "[ok] signing installer: $($installer.FullName)" + & signtool sign ` + /sha1 $env:CERTIFICATE_THUMBPRINT ` + /fd sha256 ` + /tr http://timestamp.digicert.com ` + /td sha256 ` + /v ` + $installer.FullName + if ($LASTEXITCODE -ne 0) { + Write-Host "[error] signtool failed while signing installer" + exit $LASTEXITCODE + } + } else { + Write-Host "[warn] CERTIFICATE_THUMBPRINT is not set; installer remains unsigned" + } + + $zipPath = Join-Path $bundleDir ("$($installer.BaseName).nsis.zip") + $sigPath = "$zipPath.sig" + + if (Test-Path $zipPath) { Remove-Item $zipPath -Force } + if (Test-Path $sigPath) { Remove-Item $sigPath -Force } + + # Use Deflate (CompressionLevel.Optimal) for the updater zip. + # zip 4.6.1 now has flate2 + deflate64 enabled via the workspace + # dependency in Cargo.toml, so both method 8 (Deflate) and method + # 9 (Deflate64) are supported by tauri-plugin-updater at extract time. + Add-Type -AssemblyName System.IO.Compression + Add-Type -AssemblyName System.IO.Compression.FileSystem + $zip = [System.IO.Compression.ZipFile]::Open( + $zipPath, + [System.IO.Compression.ZipArchiveMode]::Create + ) + [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile( + $zip, + $installer.FullName, + $installer.Name, + [System.IO.Compression.CompressionLevel]::Optimal + ) | Out-Null + $zip.Dispose() + Write-Host "[ok] created updater zip (Deflate): $zipPath" + + & npx tauri signer sign $zipPath + if ($LASTEXITCODE -ne 0) { + Write-Host "[error] tauri signer failed for updater zip" + exit $LASTEXITCODE + } + + if (-not (Test-Path $sigPath)) { + Write-Host "[error] expected updater signature not found: $sigPath" + exit 1 + } + + Write-Host "[ok] updater artifacts created (.nsis.zip + .sig)" + + # ── Locate build artifacts ──────────────────────────────────────────────── + - name: Collect artifact paths + if: steps.version_meta.outputs.dry_run != 'true' + id: artifacts + shell: bash + run: | + CANDIDATES=( + "src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis" + "src-tauri/target/release/bundle/nsis" + ) + + BUNDLE_DIR="" + NSIS_EXE="" + NSIS_ZIP="" + NSIS_SIG="" + + for candidate in "${CANDIDATES[@]}"; do + [ -d "$candidate" ] || continue + + candidate_exe="$(find "$candidate" -maxdepth 1 -name "*.exe" ! -iname "*uninstall*" | head -1)" + candidate_zip="$(find "$candidate" -maxdepth 1 -name "*.nsis.zip" ! -name "*.sig" | head -1)" + candidate_sig="$(find "$candidate" -maxdepth 1 -name "*.nsis.zip.sig" | head -1)" + + if [ -n "$candidate_exe" ] && [ -n "$candidate_zip" ] && [ -n "$candidate_sig" ]; then + BUNDLE_DIR="$candidate" + NSIS_EXE="$candidate_exe" + NSIS_ZIP="$candidate_zip" + NSIS_SIG="$candidate_sig" + break + fi + done + + if [ -z "$NSIS_EXE" ] || [ -z "$NSIS_ZIP" ] || [ -z "$NSIS_SIG" ]; then + echo "::error::One or more expected artifacts are missing." + for candidate in "${CANDIDATES[@]}"; do + if [ -d "$candidate" ]; then + echo "[diag] listing $candidate" + find "$candidate" -maxdepth 1 -type f | sort || true + else + echo "[diag] bundle dir not found: $candidate" + fi + done + echo " EXE : ${NSIS_EXE:-(not found)}" + echo " ZIP : ${NSIS_ZIP:-(not found)}" + echo " SIG : ${NSIS_SIG:-(not found)}" + exit 1 + fi + + echo "nsis_exe=$NSIS_EXE" >> "$GITHUB_OUTPUT" + echo "nsis_zip=$NSIS_ZIP" >> "$GITHUB_OUTPUT" + echo "nsis_sig=$NSIS_SIG" >> "$GITHUB_OUTPUT" + echo "nsis_zip_name=$(basename "$NSIS_ZIP")" >> "$GITHUB_OUTPUT" + + echo "Artifacts found:" + echo " EXE : $NSIS_EXE ($(du -sh "$NSIS_EXE" | cut -f1))" + echo " ZIP : $NSIS_ZIP ($(du -sh "$NSIS_ZIP" | cut -f1))" + echo " SIG : $NSIS_SIG" + + # ── Create / update GitHub Release with Windows binaries ───────────────── + # + # softprops/action-gh-release creates the release if it doesn't exist yet, + # or adds files to it if it was already created by the macOS or Linux + # workflow. latest.json is handled in the next step. + # + # Windows assets uploaded here: + # *_x64-setup.exe — NSIS installer (distribute to users) + # *.nsis.zip — Tauri updater bundle + # *.nsis.zip.sig — Ed25519 signature for the updater + - name: Prepare changelog notes for release + if: steps.version_meta.outputs.dry_run != 'true' + shell: bash + run: | + TAG="${{ steps.version_meta.outputs.tag }}" + PREV_TAG="$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || true)" + RANGE="${PREV_TAG:+$PREV_TAG..}$TAG" + node scripts/ci.mjs prepare-changelog \ + "${{ steps.version_meta.outputs.version }}" release-notes.md "$RANGE" + + - name: Create / update GitHub Release + if: steps.version_meta.outputs.dry_run != 'true' + uses: softprops/action-gh-release@v3 + with: + tag_name: ${{ steps.version_meta.outputs.tag }} + name: Skill v${{ steps.version_meta.outputs.version }} + generate_release_notes: false + body_path: release-notes.md + fail_on_unmatched_files: true + prerelease: ${{ steps.version_meta.outputs.prerelease == 'true' }} + files: | + ${{ steps.artifacts.outputs.nsis_exe }} + ${{ steps.artifacts.outputs.nsis_zip }} + ${{ steps.artifacts.outputs.nsis_sig }} + + # ── Update latest.json ──────────────────────────────────────────────────── + # + # Downloads the current latest.json from the release (created by whichever + # platform workflow ran first), merges the windows-x86_64 entry, then + # re-uploads. If no latest.json exists yet, creates one from scratch. + - name: Update latest.json + if: steps.version_meta.outputs.dry_run != 'true' + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: >- + node scripts/ci.mjs update-latest-json + --platform windows-x86_64 + --url "https://github.com/${{ github.repository }}/releases/download/${{ steps.version_meta.outputs.tag }}/${{ steps.artifacts.outputs.nsis_zip_name }}" + --sig-file "${{ steps.artifacts.outputs.nsis_sig }}" + --tag "${{ steps.version_meta.outputs.tag }}" + --version "${{ steps.version_meta.outputs.version }}" + --upload + --mirror-to-rc-latest + + # ── Upload CI artifacts (on-demand / dry-run builds only) ──────────────── + - name: Upload build artifacts + if: steps.version_meta.outputs.is_release != 'true' + uses: actions/upload-artifact@v7 + with: + name: NeuroSkill-Windows-${{ steps.version_meta.outputs.version }} + path: | + ${{ steps.artifacts.outputs.nsis_exe }} + ${{ steps.artifacts.outputs.nsis_zip }} + retention-days: 14 + + # ── Remove the certificate from the runner's cert store ────────────────── + - name: Remove signing certificate + if: always() && steps.version_meta.outputs.dry_run != 'true' && steps.signing_meta.outputs.have_windows_cert == 'true' + shell: powershell + run: | + if ($env:CERTIFICATE_THUMBPRINT) { + Get-ChildItem Cert:\CurrentUser\My\$env:CERTIFICATE_THUMBPRINT ` + -ErrorAction SilentlyContinue | + Remove-Item -Force + Write-Host "[ok] Certificate removed from store." + } + + # ── Discord notification ────────────────────────────────────────────────── + - name: Notify Discord of release + if: always() && steps.version_meta.outputs.is_release == 'true' + shell: bash + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + run: >- + node scripts/ci.mjs discord-notify + --status "${{ job.status }}" + --title "${{ job.status == 'success' && '🚀' || '💥' }} NeuroSkill™ v${{ steps.version_meta.outputs.version }} — Windows ${{ job.status == 'success' && 'Published' || 'Failed' }}" + --version "${{ steps.version_meta.outputs.version }}" + --tag "${{ steps.version_meta.outputs.tag }}" + --platform "Windows x86_64" + --release-url "${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ steps.version_meta.outputs.tag }}" + --run-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index a8bb9d614..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,378 +0,0 @@ -name: Release - -# Triggered by pushing a semver tag: git tag v1.2.3 && git push origin v1.2.3 -on: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' - -# Write permission is required to create GitHub Releases and upload assets. -permissions: - contents: write - -jobs: - release: - name: Build & Release — aarch64-apple-darwin - runs-on: macos-26 - environment: Release - - steps: - # ── Checkout ────────────────────────────────────────────────────────────── - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 # full history so git tag --format can read the annotation - - # ── Verify tag matches tauri.conf.json version ──────────────────────────── - # Catches "forgot to bump the version" before wasting 20 min on a build. - - name: Verify version matches tag - run: | - TAG="${{ github.ref_name }}" - VERSION="${TAG#v}" # strip leading 'v' → 1.2.3 - CONF_VERSION=$( - grep '"version"' src-tauri/tauri.conf.json \ - | head -1 \ - | sed 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' - ) - if [ "$VERSION" != "$CONF_VERSION" ]; then - echo "::error::Tag version ($VERSION) does not match" \ - "tauri.conf.json version ($CONF_VERSION)." - echo "::error::Bump the version in src-tauri/tauri.conf.json and" \ - "src-tauri/Cargo.toml, then re-tag." - exit 1 - fi - echo "VERSION=$VERSION" >> "$GITHUB_ENV" - echo "TAG=$TAG" >> "$GITHUB_ENV" - echo "✓ Version verified: $VERSION" - - # ── Rust toolchain ──────────────────────────────────────────────────────── - - name: Install Rust stable - uses: dtolnay/rust-toolchain@stable - with: - targets: aarch64-apple-darwin - - # ── Cargo cache ─────────────────────────────────────────────────────────── - # Swatinem/rust-cache is purpose-built for Rust: it strips test artifacts, - # stale deps, and other bloat before saving, keeping the cache lean. - - name: Cache Cargo registry + build artifacts - uses: Swatinem/rust-cache@v2 - with: - workspaces: src-tauri - - # ── Node.js ─────────────────────────────────────────────────────────────── - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - - - name: Install JS dependencies - run: npm ci - - # ── ImageMagick (needed by scripts/stamp-dmg-icon.sh) ──────────────────── - # sips, iconutil, and Python/AppKit ship with the macOS runner image. - # ImageMagick is NOT pre-installed on macos-26 (confirmed against the - # actions/runner-images macos-26-arm64-Readme.md manifest), so we install - # it via Homebrew when missing. The guard avoids a redundant install if - # a future runner image ever bundles it. - - name: Ensure ImageMagick is available - run: | - if command -v magick >/dev/null 2>&1; then - echo "ImageMagick already present: $(magick --version | head -1)" - else - echo "ImageMagick not found — installing via Homebrew…" - brew install imagemagick - echo "Installed: $(magick --version | head -1)" - fi - - # ── Pre-flight: verify every secret is set ─────────────────────────────── - # Catches missing / renamed secrets in seconds instead of after a 15-minute - # build. Each variable is checked for non-empty; none of the values are - # printed. - - name: Verify signing and notarization secrets are present - env: - APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} - APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} - APPLE_ID: ${{ secrets.APPLE_ID }} - APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} - APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - run: | - ok=true - for var in \ - APPLE_CERTIFICATE \ - APPLE_CERTIFICATE_PASSWORD \ - APPLE_SIGNING_IDENTITY \ - APPLE_ID \ - APPLE_PASSWORD \ - APPLE_TEAM_ID \ - TAURI_SIGNING_PRIVATE_KEY - do - if [ -z "${!var}" ]; then - echo "::error::Secret '$var' is empty or not set." - ok=false - fi - done - $ok || exit 1 - echo "✓ All signing and notarization secrets are present." - - # ── Pre-flight: validate Apple notarization credentials ─────────────────── - # xcrun notarytool history authenticates against Apple's API using the same - # credentials the build will use, but without submitting anything. - # A 401 here means the app-specific password is wrong or expired. - # Generate a fresh one at https://appleid.apple.com → App-Specific Passwords, - # then update the APPLE_PASSWORD secret in the repository environment. - - name: Validate Apple notarization credentials - env: - APPLE_ID: ${{ secrets.APPLE_ID }} - APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} - APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - run: | - echo "Checking notarization credentials …" - if ! xcrun notarytool history \ - --apple-id "$APPLE_ID" \ - --password "$APPLE_PASSWORD" \ - --team-id "$APPLE_TEAM_ID" \ - --output-format json 2>&1 | grep -q '"history"'; then - echo "::error::Apple notarization credentials are invalid." - echo "::error::Generate a new app-specific password at" - echo "::error:: https://appleid.apple.com → Sign-In and Security" - echo "::error:: → App-Specific Passwords → Generate" - echo "::error::Then update the APPLE_PASSWORD secret in:" - echo "::error:: GitHub → Settings → Environments → Release → Secrets" - exit 1 - fi - echo "✓ Notarization credentials are valid." - - # ── espeak-ng static library ────────────────────────────────────────────── - # Cache keyed on the build script itself; a script change busts the cache. - - name: Cache espeak-ng static lib - id: cache-espeak - uses: actions/cache@v4 - with: - path: src-tauri/espeak-static - key: macos-arm64-espeak-${{ hashFiles('scripts/build-espeak-static.sh') }} - - - name: Build espeak-ng static lib - if: steps.cache-espeak.outputs.cache-hit != 'true' - run: ESPEAK_ARCHS=arm64 bash scripts/build-espeak-static.sh - - # ── Apple Developer certificate → temporary keychain ───────────────────── - # - # Secrets required: - # APPLE_CERTIFICATE — base64-encoded .p12 (Developer ID Application cert) - # APPLE_CERTIFICATE_PASSWORD — password protecting the .p12 - # - # How to export the .p12: - # Keychain Access → right-click cert → Export → Personal Information Exchange - # Then: base64 -i cert.p12 | pbcopy (paste into the GitHub secret) - - name: Import Apple Developer certificate - env: - APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} - APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - run: | - KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db" - KEYCHAIN_PASSWORD="$(openssl rand -base64 32)" - # Persist to env so the cleanup step can always find the path. - echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV" - echo "KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> "$GITHUB_ENV" - - security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - # Keep the keychain unlocked for the duration of the job (6 h max). - security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" - security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - - # Decode and import the certificate. - echo -n "$APPLE_CERTIFICATE" \ - | base64 --decode -o "$RUNNER_TEMP/cert.p12" - security import "$RUNNER_TEMP/cert.p12" \ - -k "$KEYCHAIN_PATH" \ - -P "$APPLE_CERTIFICATE_PASSWORD" \ - -T /usr/bin/codesign \ - -T /usr/bin/security - rm -f "$RUNNER_TEMP/cert.p12" - - # Allow codesign to use the key without a password prompt. - security set-key-partition-list \ - -S apple-tool:,apple: \ - -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - - # Make this keychain searchable by codesign / Tauri. - security list-keychains -d user \ - -s "$KEYCHAIN_PATH" login.keychain - - # ── Tauri build — sign + notarize + create updater artifacts ───────────── - # - # Secrets required: - # APPLE_SIGNING_IDENTITY — e.g. "Developer ID Application: Jane (A1B2C3D4E5)" - # APPLE_ID — Apple ID email used with notarytool - # APPLE_PASSWORD — app-specific password (appleid.apple.com) - # APPLE_TEAM_ID — 10-character Team ID - # TAURI_SIGNING_PRIVATE_KEY — base64 Ed25519 private key (from generate-keys.py) - # TAURI_SIGNING_PRIVATE_KEY_PASSWORD — key password (empty string if none) - # - # What Tauri does automatically when these vars are set: - # 1. codesign --deep --options runtime --entitlements ./entitlements.plist - # 2. xcrun notarytool submit … --wait - # 3. xcrun stapler staple - # 4. Creates .tar.gz + .tar.gz.sig (updater artifacts) - # 5. Packages the signed + notarized .dmg - - name: Build Tauri app (sign + notarize) - env: - APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} - APPLE_ID: ${{ secrets.APPLE_ID }} - APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} - APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - ESPEAK_LIB_DIR: ${{ github.workspace }}/src-tauri/espeak-static/lib - run: | - npx tauri build \ - --target aarch64-apple-darwin \ - --bundles app,dmg - - # ── Always delete the temporary keychain ────────────────────────────────── - - name: Delete temporary keychain - if: always() - run: security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true - - # ── Locate build artifacts ──────────────────────────────────────────────── - - name: Collect artifact paths - id: artifacts - run: | - BUNDLE_DIR="src-tauri/target/aarch64-apple-darwin/release/bundle" - - APP_TAR="$(find "$BUNDLE_DIR/macos" \ - -name "*.app.tar.gz" ! -name "*.sig" \ - -maxdepth 1 | head -1)" - APP_SIG="$(find "$BUNDLE_DIR/macos" \ - -name "*.app.tar.gz.sig" \ - -maxdepth 1 | head -1)" - DMG="$(find "$BUNDLE_DIR/dmg" \ - -name "*.dmg" \ - -maxdepth 1 | head -1)" - - if [ -z "$APP_TAR" ] || [ -z "$APP_SIG" ] || [ -z "$DMG" ]; then - echo "::error::One or more expected artifacts are missing." - echo " APP_TAR : $APP_TAR" - echo " APP_SIG : $APP_SIG" - echo " DMG : $DMG" - exit 1 - fi - - echo "app_tar=$APP_TAR" >> "$GITHUB_OUTPUT" - echo "app_sig=$APP_SIG" >> "$GITHUB_OUTPUT" - echo "dmg=$DMG" >> "$GITHUB_OUTPUT" - echo "app_tar_name=$(basename "$APP_TAR")" >> "$GITHUB_OUTPUT" - echo "dmg_name=$(basename "$DMG")" >> "$GITHUB_OUTPUT" - - echo "Artifacts found:" - echo " TAR : $APP_TAR ($(du -sh "$APP_TAR" | cut -f1))" - echo " SIG : $APP_SIG" - echo " DMG : $DMG ($(du -sh "$DMG" | cut -f1))" - - # ── Stamp version badge onto the DMG icon ──────────────────────────────── - # Composites "v" over the app icon and attaches the result as the - # DMG file's custom Finder icon via NSWorkspace so every build is instantly - # distinguishable in Finder's icon / gallery view without reading filenames. - - name: Stamp version badge onto DMG icon - run: | - bash scripts/stamp-dmg-icon.sh \ - "${{ steps.artifacts.outputs.dmg }}" \ - "${{ env.VERSION }}" \ - "src-tauri/icons/icon.png" - - # ── Generate latest.json ────────────────────────────────────────────────── - # - # latest.json is uploaded as a release asset so it is reachable at: - # https://github.com/NeuroSkill-com/skill/releases/latest/download/latest.json - # - # Point tauri.conf.json → plugins.updater.endpoints at that URL so the - # built-in updater can find new versions without any server-side logic: - # - # "endpoints": [ - # "https://github.com/NeuroSkill-com/skill/releases/latest/download/latest.json" - # ] - # - # The Tauri v2 updater JSON schema: - # https://tauri.app/plugin/updater/#update-server-json-format - - name: Generate latest.json - env: - GH_REPO: ${{ github.repository }} - TAG: ${{ env.TAG }} - VERSION: ${{ env.VERSION }} - APP_TAR_NAME: ${{ steps.artifacts.outputs.app_tar_name }} - APP_SIG_PATH: ${{ steps.artifacts.outputs.app_sig }} - run: | - python3 - <<'PYEOF' - import json, os, subprocess, datetime, sys - - version = os.environ["VERSION"] - tag = os.environ["TAG"] - repo = os.environ["GH_REPO"] - app_tar_name = os.environ["APP_TAR_NAME"] - sig_path = os.environ["APP_SIG_PATH"] - - with open(sig_path) as fh: - signature = fh.read().strip() - - pub_date = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ") - - # Use the git tag annotation as release notes; fall back gracefully. - try: - notes = subprocess.check_output( - ["git", "tag", "-l", "--format=%(contents)", tag], - text=True - ).strip() - except Exception: - notes = "" - if not notes: - notes = f"NeuroSkill™ v{version}" - - download_url = ( - f"https://github.com/{repo}/releases/download/{tag}/{app_tar_name}" - ) - - manifest = { - "version": version, - "notes": notes, - "pub_date": pub_date, - "platforms": { - "darwin-aarch64": { - "url": download_url, - "signature": signature, - } - }, - } - - with open("latest.json", "w") as fh: - json.dump(manifest, fh, indent=2) - fh.write("\n") - - print("Generated latest.json:") - print(json.dumps(manifest, indent=2)) - PYEOF - - # ── Create GitHub Release + upload all assets ───────────────────────────── - # - # Uploaded assets for this release: - # skill__aarch64.dmg — installer (distribute to users) - # skill__aarch64.app.tar.gz — updater bundle (consumed by Tauri updater) - # skill__aarch64.app.tar.gz.sig — Ed25519 signature for the updater bundle - # latest.json — updater manifest (static endpoint for the updater) - # - # The `latest.json` asset is accessible at the stable URL: - # https://github.com/NeuroSkill-com/skill/releases/latest/download/latest.json - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ env.TAG }} - name: Skill v${{ env.VERSION }} - generate_release_notes: true - fail_on_unmatched_files: true - files: | - ${{ steps.artifacts.outputs.dmg }} - ${{ steps.artifacts.outputs.app_tar }} - ${{ steps.artifacts.outputs.app_sig }} - latest.json diff --git a/.github/workflows/trial-linux-arm64-packages.yml b/.github/workflows/trial-linux-arm64-packages.yml new file mode 100644 index 000000000..6b6d5dffa --- /dev/null +++ b/.github/workflows/trial-linux-arm64-packages.yml @@ -0,0 +1,159 @@ +name: Trial — Linux arm64 packages + +# Proves out an aarch64 Linux .deb/.rpm before the release pipeline depends on +# one. Today `Release — Linux` builds x86_64 only, so there is no ARM Linux +# package at all, even though scripts/package-linux-system-bundles.sh already +# knows the aarch64 arch mapping. +# +# Deliberately `workflow_dispatch` only. It is NOT wired into the tag-driven +# release: +# * an unproven job on the release path can fail a release, and +# * a second full Linux build doubles the cost of every tag. +# Run it by hand, confirm the artifacts, and only then consider promoting it +# into release-linux.yml. +# +# Scope is the daemon + system packages, not the signed/published app: enough to +# answer "does an aarch64 rpm build and is it well-formed", which is the open +# question. Artifacts are uploaded to the workflow run, never to a Release, so a +# trial cannot publish anything. +# +# Known arm64 deltas from the x86_64 job, both handled below: +# * LunarG publishes `vulkan-sdk` for amd64 only -> Ubuntu's libvulkan-dev. +# * CUDA: the `linux` umbrella pulls rlx-cuda -> cudarc, which defaults to +# `fallback-dynamic-loading` and dlopens libcuda at run time, so it builds +# with no CUDA toolkit present and simply finds no device at run time +# (the documented CUDA -> wgpu -> CPU fallback order). + +on: + workflow_dispatch: + inputs: + features: + description: >- + skill-daemon feature umbrella. `linux` mirrors the x86_64 release. + Use `product` to isolate a failure from the GPU backends. + required: false + default: linux + type: choice + options: + - linux + - product + +permissions: + contents: read + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + trial-arm64: + name: Trial — aarch64-unknown-linux-gnu packages + runs-on: ubuntu-24.04-arm + timeout-minutes: 120 + + steps: + - name: Free disk space + run: | + sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc /usr/local/.ghcup \ + /usr/local/share/powershell /usr/local/share/chromium /usr/share/swift \ + /opt/hostedtoolcache/CodeQL + sudo docker image prune -af 2>/dev/null || true + df -h / + + - name: Checkout + uses: actions/checkout@v6 + + - name: Confirm the runner really is arm64 + shell: bash + run: | + set -euo pipefail + arch="$(dpkg --print-architecture)" + echo "dpkg arch : $arch" + echo "uname -m : $(uname -m)" + if [[ "$arch" != "arm64" ]]; then + echo "::error::Expected an arm64 runner, got '$arch'. This trial proves nothing on amd64." + exit 1 + fi + + - name: Release setup (Linux deps + Rust + Node) + uses: ./.github/actions/release-setup + with: + target: aarch64-unknown-linux-gnu + linux-deps: "true" + # LunarG is amd64-only; use Ubuntu's loader + headers instead. + linux-vulkan-packages: "libvulkan-dev libvulkan1" + cache-key: linux-trial-aarch64-unknown-linux-gnu-v1 + build-frontend: "false" + + - name: Compile skill-daemon (aarch64) + shell: bash + run: | + set -euo pipefail + cargo build -p skill-daemon \ + --features "${{ inputs.features }}" \ + --release --locked --target aarch64-unknown-linux-gnu + + # package-linux-system-bundles.sh stages `src-tauri/target//release/skill`. + # This trial does not build the Tauri app (that needs the frontend and a + # signed bundle), so stand in the daemon binary to exercise the packaging + # and metadata path, which is what is actually under test here. + - name: Stage a binary for packaging + shell: bash + run: | + set -euo pipefail + dir="src-tauri/target/aarch64-unknown-linux-gnu/release" + if [[ ! -f "$dir/skill" ]]; then + cp "$dir/skill-daemon" "$dir/skill" + echo "note: packaged the daemon binary as a stand-in for the app" + fi + + - name: Build deb + rpm (aarch64) + shell: bash + run: bash scripts/package-linux-system-bundles.sh --target aarch64-unknown-linux-gnu --skip-build + + # The point of the trial: is the rpm well-formed, correctly versioned, and + # does rpm's own dependency generator produce the OpenBLAS soname? + - name: Inspect the rpm + shell: bash + run: | + set -euo pipefail + bundle="src-tauri/target/aarch64-unknown-linux-gnu/release/bundle" + rpm_file="$(find "$bundle/rpm" -name '*.rpm' | head -1)" + deb_file="$(find "$bundle/deb" -name '*.deb' | head -1)" + if [[ -z "$rpm_file" || -z "$deb_file" ]]; then + echo "::error::expected both a .deb and a .rpm under $bundle" + find "$bundle" -maxdepth 2 || true + exit 1 + fi + + echo "rpm : $rpm_file" + echo "deb : $deb_file" + rpm -qp --queryformat 'NAME=%{NAME}\nVERSION=%{VERSION}\nRELEASE=%{RELEASE}\nARCH=%{ARCH}\n' "$rpm_file" + + arch="$(rpm -qp --queryformat '%{ARCH}' "$rpm_file")" + [[ "$arch" == "aarch64" ]] || { echo "::error::rpm ARCH is '$arch', expected aarch64"; exit 1; } + + # An RC must carry a tilde so it sorts BELOW the release it precedes. + ver="$(rpm -qp --queryformat '%{VERSION}' "$rpm_file")" + case "$ver" in + *-*) echo "::error::rpm VERSION '$ver' contains '-', which rpm forbids"; exit 1 ;; + *'\'*) echo "::error::rpm VERSION '$ver' contains a backslash"; exit 1 ;; + esac + if [[ "$(cat VERSION)" == *-rc.* && "$ver" != *'~'* ]]; then + echo "::error::VERSION is an RC but rpm VERSION '$ver' has no '~' — upgrade ordering would invert" + exit 1 + fi + + echo "--- requires ---" + rpm -qp --requires "$rpm_file" + rpm -qp --requires "$rpm_file" | grep -q 'libopenblas\.so\.0' \ + || echo "::warning::no libopenblas soname requirement was auto-generated" + + - name: Upload trial packages + uses: actions/upload-artifact@v7 + with: + name: linux-arm64-trial-packages + path: | + src-tauri/target/aarch64-unknown-linux-gnu/release/bundle/deb/*.deb + src-tauri/target/aarch64-unknown-linux-gnu/release/bundle/rpm/*.rpm + if-no-files-found: error + retention-days: 14 diff --git a/.gitignore b/.gitignore index 25578ecee..d94e72925 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,13 @@ node_modules .env .env.* !.env.example +!.envrc +rlx.path +rlx-models.path +# Local rlx/rlx-models [patch] overlay written by scripts/ensure-rlx.sh. +# Repo-scoped on purpose: in ../.cargo it would also apply to the sibling rlx +# and rlx-models builds. Never commit it — CI resolves rlx* from Cargo.lock. +/.cargo/ vite.config.js.timestamp-* vite.config.ts.timestamp-* @@ -18,5 +25,39 @@ src-tauri/keys/.env.keys **/*.gguf **/*.safetensors +# Rust build artifacts (main crate + workspace crates) +**/target/ + rust_out -TODO.md \ No newline at end of file +TODO.md +AGENTS.md +# docs/screenshots/gifs/*.gif + +.gitrub_activity +# Rust lock files — Cargo.lock is committed (application, not library) +test-results/ +src-tauri/binaries/ +errors/ +logs/ +output/ + +# Bundled TTS engine weights — staged by scripts/bundle-tts-weights.sh before packaging. +# inflect-nano is committed (declared in tauri.conf.json bundle.resources so CI can build the app); +# other engine bundles (e.g. tiny-tts) stay local-only. +src-tauri/resources/tts/* +!src-tauri/resources/tts/inflect-nano/ +# Re-include the bundle contents too, overriding the global **/*.safetensors ignore above. +!src-tauri/resources/tts/inflect-nano/** + +# OCR weights bundled into skill-screenshots via include_bytes! (rlx-ocr, converted +# offline from the ocrs rten checkpoints) so CI can build the app offline. +# Override the global **/*.safetensors ignore above. +!crates/skill-screenshots/assets/ocr-detection.safetensors +!crates/skill-screenshots/assets/ocr-recognition.safetensors + +# Scratch output from scripts/verify-rpm-docker.sh: downloaded release rpms and +# synthetic test packages, both large and both reproducible on demand. +dist/rpm-verify/ + +# Python bytecode from scripts/lib helpers. +__pycache__/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..85db65335 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,15 @@ +[submodule "skills"] + path = skills + url = https://github.com/NeuroSkill-com/skills.git +[submodule "neuroloop"] + path = neuroloop + url = https://github.com/NeuroSkill-com/neuroloop +[submodule "neuroskill"] + path = neuroskill + url = https://github.com/NeuroSkill-com/neuroskill +[submodule "extensions/vscode"] + path = extensions/vscode + url = https://github.com/NeuroSkill-com/vscode-neuroskill.git +[submodule "extensions/browser"] + path = extensions/browser + url = https://github.com/NeuroSkill-com/browser-extension.git diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 61343e9b8..76ac8ff94 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,6 +2,7 @@ "recommendations": [ "svelte.svelte-vscode", "tauri-apps.tauri-vscode", - "rust-lang.rust-analyzer" + "rust-lang.rust-analyzer", + "neuroskill.neuroskill" ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 54a8158cc..f0085430d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,87 +1,753 @@ # Changelog All notable changes to NeuroSkill™ are documented here. +Pending changes live as fragments in [`changes/unreleased/`](changes/unreleased/). +Past releases are archived in [`changes/releases/`](changes/releases/). --- -## [0.0.6] — 2026-03-06 - -### Do Not Disturb / Focus Mode - -- Replaced hand-rolled ObjC FFI + XPC DND implementation with the - [`macos-focus`](https://crates.io/crates/macos-focus) crate — pure Rust, - no private frameworks, no special entitlements -- DND now works reliably on macOS 12–15; the XPC path that consistently - returned "operation failed" errors has been removed -- Added **Focus mode picker** in Settings → Goals: choose any Focus mode - configured in System Settings (Do Not Disturb, Work, Personal, Sleep, - Driving, …) rather than always activating Do Not Disturb -- `focus_mode_identifier` persisted in settings; defaults to Do Not Disturb - for backwards compatibility with existing configs -- Added `list_focus_modes` Tauri command backed by - `FocusManager::available_modes()`; falls back to the full first-party mode - list if `ModeConfigurations.json` is unreadable -- Added TODO stubs for Linux (D-Bus / xdg-portal) and Windows - (WinRT / IQuietHoursSettings) DND support - -### Quit Dialog - -- macOS quit confirmation dialog now uses `NSAlert` via `objc2-app-kit` - dispatched through `dispatch2::DispatchQueue::main().exec_sync()`, - eliminating the `CFUserNotificationDisplayAlert: called from main - application thread` log warning that `rfd` triggered - -### Bug fixes and warnings - -- Fixed `CFStringCreateWithCString` / `CFRelease` clashing `extern "C"` - signatures between `dnd.rs` and `gpu_stats.rs` -- Removed three unnecessary `unsafe {}` blocks around safe `iimp()` closure - calls in the (now-deleted) ObjC FFI path -- Removed unused `vm_deallocate` extern declaration in `gpu_stats.rs` -- Removed unnecessary `unsafe {}` block wrapping safe `NSAlert` method calls -- Fixed unescaped ASCII `"` inside German DND strings in `de.ts` that caused - 587 cascading TypeScript parse errors -- Replaced `map_or(false, |v| v == 1)` with `== Some(1)` in `gpu_stats.rs` -- Replaced manual `div_ceil` closure with `u64::div_ceil` in `job_queue.rs` -- Replaced `&&` range assertions with `.contains()` in `ppg_analysis.rs` -- Replaced `vec![…]` with array literals in test push calls -- Replaced `for ch in 0..N` index loops with `enumerate()` iterators in - `eeg_bands.rs` and `eeg_filter.rs` -- Moved constant-value `assert!` calls into `const { }` blocks in - `constants.rs` -- Fixed doc comment continuation indent warnings in `gpu_stats.rs` +## [Unreleased] + +## [0.0.131-rc.35] — 2026-10-12 + +### Features + +- **Oura Ring (Gen 3 / Ring 4 / Ring 5)**: added support for reading an Oura ring directly over Bluetooth, with no Oura account and no cloud round trip, using the reverse-engineered protocol from [open_oura](https://github.com/Th0rgal/open_oura). A connected ring reports live heart rate with its inter-beat interval, the latest cached SpO2, battery state of charge, and — opt-in — accelerometer motion. Discovery, pairing and connection work like any other BLE device. +- **Direct-BLE and cloud Oura are independent**: the existing Oura integration syncs sleep, readiness and activity scores from Oura's V2 REST API with a personal access token. The new one talks to the ring itself. Both are configured in Settings → Devices → Device API and neither replaces the other; `docs/DEVICES.md` lays out which gives you what. +- **Cloud-free Oura history sync** (Settings → Devices → Device API → *Sync from ring*): drains the history the ring itself stores over Bluetooth and writes it to `health.sqlite` — heart rate, HRV (RMSSD), skin temperature, SpO2, perfusion index, respiratory rate, sleep stages, MET levels and the ring's own detected bedtime window. Incremental: a cursor records where the last sync stopped, and `{"reset": true}` re-reads everything the ring still holds. Daemon command `oura_ring_sync`; status via `oura_ring_status`. +- **Session vitals recording**: a recorded session with a vitals device in it now produces an `exg_*_vitals` sidecar (`.csv` and/or `.parquet`, following the session storage format) with `timestamp_s, hr_bpm, ibi_ms, spo2_pct, temperature_c`. The table is deliberately sparse — a wearable reports one vital at a time, so an absent value is an empty CSV field and a Parquet null, never a zero that would read as a measurement. +- **Device-attributed vitals events**: the daemon broadcasts a new `Vitals` event carrying `device_kind` and `device_id`, so heart rate from a ring stays distinguishable from anything else streaming at the same time. + +- **Vital Neuro headphones**: added support for Vital Neuro EEG headphones over BLE via the `vital` crate — three EEG channels at 250 Hz, accelerometer and battery telemetry, plus electrode impedance when the device is put in impedance mode. Discovery, pairing and connection work like any other BLE headset, and the headphones appear in Settings → Supported Devices. +- **BLE connect, faster when there is one headset**: a Vital Neuro connect stops at the first advertisement when the scanner can see that only one is in range, instead of always running the scan window out to pick by id. +- **BLE discovery by advertised service**: the scanner now also identifies a device from the service UUID it advertises, not only its name. Vital Neuro stores its name on the unit and lets the owner change it, so a renamed pair is still discovered and still routed to the right driver. + +### Performance + +- **Local chat uses far less memory**: the Qwen3.5 predict graph was compiled twice — once into a cache nothing reads, then again to return — and the chat UI's typing-time warm pass built that graph even though generation never uses it. Both are gone. Metal buffer allocation for the default 0.8B model drops from 25.9 GB to 3.97 GB and peak process memory from ~31 GB to ~11.7 GB. +- **Prefill width is configurable (`llm.rlx_prefill_seq`)**: Qwen3.5 runs every prompt through one static prefill graph whose width defaulted to the whole decode context, so a one-line question was prefilled as if it were 8192 tokens. Setting this decouples the two — measured on the 0.8B with a 25-token prompt, time to first token falls from 10.2 s to 1.2 s and decode rises from 5.6 to 35.6 tokens/s. A prompt longer than the configured width is rejected outright, so raise it when tools are on. +- **Agent Skills are no longer injected when they cannot be used**: the skills block tells the model to load files with the `read_file` tool, which is off by default, so 33 discovered skills added roughly 3,900 tokens to every prompt for nothing — a one-line question arrived as 3,970 tokens. It is now sent only when that tool is actually offered, and the daemon says once at startup when skills are being withheld for that reason. + +### Bugfixes + +- **Local chat no longer dies silently mid-request**: a panic inside a model kernel unwound the inference actor, so the request's stream just ended — no error to the caller, no status event, and a log that stopped dead after `chat request — …`. Each request now runs behind a panic boundary: the failure is reported on the request and in the LLM log, and the server keeps serving. +- **Replies containing an accent, CJK or an emoji could kill the actor**: the token streamer tracked how much it had sent as a byte offset into the previous decode. Byte-level BPE revises earlier bytes when a codepoint spans tokens — a 3-byte replacement char becomes a 2-byte `é` — so that offset could land mid-character and the slice panicked. English-only output never tripped it. The cursor is now resynchronised on whole characters. +- **Qwen3 models failed every chat with `weight not found: model.embed_tokens.weight`**: the runner's context was left at the library default of 128 tokens, which sized its decode-bucket ladder to 256. One token past that, decoding fell back to a path that needs F32 weights a packed GGUF does not have. The error named a tensor and looked like a GGUF/safetensors mismatch; it was a context cap. Context is now passed through, as it already was for Qwen3-VL and Qwen3.5. +- **Small models answered with the same phrase over and over**: the stop signal a caller returns from its token callback was discarded on the Qwen3 path, so chat EOS, user stop strings and the repetition guard were all ignored and generation always ran to the token cap. Qwen3-0.6B asked for the capital of France produced 303 tokens of "The capital of France's capital of France's …". It also made `finish_reason` permanently `length`. +- **Loading a model the catalogue does not describe budgeted memory for the wrong one**: context was sized from the active catalogue entry even when a different file was being loaded (a path set directly, a discovered model, `--model`). Loading a 1-bit 8B while the catalogue still described a 0.8B picked a 16K context for the smaller model and drove the daemon to 16.5 GB resident and 24.6 GB of swap. Mismatches are now detected, warned about, and fall back to a conservative context. +- **`max_tokens` sent by an OpenAI-compatible client had no effect**: it was forwarded under a key nothing reads, so every such request silently ran to the 2048 default. +- **Web search left a panic in the log on every call**: the headless renderer tried to create its event loop on a background thread, which macOS forbids, then reported an unrelated timeout before falling back to a plain fetch. It now declines up front with the reason and the supported alternative. + +### Build + +- **`npm run daemon` builds a GPU-capable daemon by default** and takes `--model ` to load a GGUF directly; previously it built without the platform's GPU features, so models ran on CPU, and pointing it at a local file meant four API calls by hand. + +### UI + +- **Live vitals card**: a connected vitals device now shows its readings in the dashboard's Physiology view — heart rate, inter-beat interval, SpO2 and skin temperature. Separate from the PPG card, which charts values computed here from an optical waveform: a ring does its own beat detection, so there is no waveform and the PPG card would stay empty. Each tile appears only once a value for it has arrived rather than showing a placeholder zero, and the card explains itself while the device has not measured yet instead of looking broken. +- **A ring no longer shows a Muse head map**: the electrode guide gained an Oura tab reporting zero electrodes and no positions, the same treatment Hermes and Ganglion get. Previously any unrecognised device kind fell through to the Muse four-electrode montage. +- **Ring BLE key setting**: Settings → Devices → Device API takes the ring's 16-byte app-auth key as 32 hex characters, stored in the OS keychain beside the cloud token. The ring challenges for this key on every connection and answers nothing but device info without it, so connecting without one fails with a message saying where the key comes from rather than producing a session that streams nothing. + +- **Electrode guide knows Vital Neuro**: connecting one no longer falls back to the Muse head map. The device reports no electrode montage, so the guide names the headset and shows no positions — the same thing it does for Hermes and Ganglion — instead of four electrodes that are not there. + +- **Progress is shown while a tool runs**: a tool round-trip sent the client nothing at all — 21 of 22 seconds silent on a search — because only generation-phase markers were forwarded. Tool start and finish now appear as phases, so the chat shows "Running tools…" instead of looking stalled. +- **The onboarding model card names the model it will actually download**: the title was a fixed string reading "Qwen3.5 4B (Q4_K_M)" in every language while the download was Qwen3.5 0.8B, with the quant badge beside it already showing the truth. +- **Start honours a model picked elsewhere**: onboarding ignored the selected model and fetched its own default instead. +- **The LLM panel shows the port the API is really on**: it displayed a configurable advertisement value (8375) rather than the port the daemon bound (18444), so the endpoint list and the `curl` example were both wrong. + +### Server + +- **One BLE session, including the ring**: the ring's link is an `oura-link` `Transport` implemented directly over `webbluetooth`, rather than that crate's own btleplug backend. Every radio operation therefore goes through the single process-wide `Bluetooth::shared()` session the rest of the app uses — on macOS a second session is a second `CBCentralManager`, and a device found through one cannot be connected through the other. The service-UUID constant is shared between the daemon and the driver, so a dependency graph that ever split webbluetooth in two would fail the build instead of failing at runtime. +- **Discovery by service UUID, for a ring with no name**: a bonded Oura ring usually advertises no local name at all, so it is recognised by its vendor service UUID (`98ed0001-…`) — the same mechanism a renamed Vital Neuro uses. Name matching for the ring now delegates to the device registry, so BLE discovery and connect routing cannot disagree about what counts as a ring. + +- **`has_vitals` capability flag**: the session status now distinguishes a device that reports vitals it derived itself from one with a PPG sensor this app reads a waveform from (`has_ppg`), so the UI can pick the right card instead of inferring from the device kind. Cleared on disconnect with the other capability flags. +- **Ring timestamps are dated properly, or not at all**: a history event is stamped with the ring's own counter in *deciseconds*, which restarts on every reboot — it is not Unix time. `skill-oura`'s new `RingClock` reconstructs boot epochs and interpolates from `time_sync` / `rtc_beacon` anchors (a port of open_oura's `epoch_time.py`). Events that genuinely cannot be dated — no anchor in their boot, a counter that ran faster than wall time, or lost time spanning several reboots — are **dropped and counted** rather than stored at the moment they were downloaded, because a wrong date in a health database is worse than a missing row. The sync result reports `undated`, `untrusted` and `anchors`, and the UI explains a sync that read events but could date none of them instead of flashing a success tick. +- **Health conversion refuses unvalidated decodes**: only event layouts upstream has confirmed against real captures are stored. Anything carrying `"_status": "unvalidated"`, `"inferred"` or `"part1_raw"` is skipped generically, so a future upstream decoder cannot silently start feeding the health database guesses, and every value is range-checked before it is written. `ibi_and_amplitude_event` is excluded on the same grounds — 7–13 % of its beats read above 100 bpm during sleep on two different rings. +- **A history sync will not fight a live session**: the ring accepts one BLE connection, and the protocol matches a response by tag order, so two clients on one link would take each other's answers. A sync refuses with an actionable error while a ring session is connected. + +## [0.0.131-rc.34] — 2026-10-04 + +### Build + +- Minor updates and improvements + +## [0.0.131-rc.33] — 2026-09-29 + +### Build + +- **Fix the Linux release failing at the dependency step.** `release-setup`'s + `packages:` is a YAML *folded scalar*: every line inside it is literal text + handed to apt, so a leading `#` does not start a comment. A comment added there + in rc.32 turned each word of the prose into a package name, and the whole + Linux release died 3 minutes in with `Unable to locate package #`, + `dpkg-shlibdeps`, `used`, `by`, `derive`, and the rest. + + The explanation now lives above the step, where YAML does treat it as a + comment, and the block holds package names only — with a note there saying + exactly this, so the next person does not repeat it. Verified by rendering the + list straight out of the YAML and resolving every name against real Ubuntu on + both amd64 and arm64: all 19 resolve, with `vulkan-sdk` correctly coming from + the LunarG repo rather than the base archive. + + `dpkg-dev` (the reason for the comment) stays in the list: it provides + `dpkg-shlibdeps`, which derives the `.deb` Depends from the shipped ELFs. + +## [0.0.131-rc.32] — 2026-09-29 + +### Bugfixes + +- **Reconnect could stop silently mid-retry.** `eval_reconnect_tick` waits while + the device state is `"connecting"` — correct, since a connect is already in + flight — but nothing guaranteed that state ever moved on. + `spawn_device_session`'s failure arm only resets it when `still_current` holds, + so a failing attempt whose target had since been rewritten left `"connecting"` + behind permanently. The reconnect loop then waited forever: no further + attempts, and never the give-up branch either, so the log simply went quiet + with the device stuck. Reported as "gives up after 4 attempts"; the budget is + 12 and was never reached — the loop had stopped asking. The reconnect task now + detects `"connecting"` with no live session task for 15 s and treats it as + disconnected so retrying resumes. See github.com/NeuroSkill-com/skill#89. + +- **The daemon and the app could authenticate against different tokens.** Path + resolution for the daemon's config directory existed in four hand-rolled + copies, and they had diverged: only the app honoured + `SKILL_DAEMON_CONFIG_ROOT`, so with that variable set the app read a tmpdir + `auth.token` while the daemon read the real one under `$HOME` — every request + answering 401 with no way for the UI to drive the daemon. It also meant e2e + runs that pin daemon paths into a tmpdir silently authenticated against the + user's real token. + +### Refactor + +- **One definition of the daemon's paths.** New `skill_daemon_common::paths` + (`config_root`, `token_path`) replaces the four copies in + `skill-daemon-state::util`, `src-tauri/daemon_cmds`, + `src-tauri/daemon_upgrade` and `skill-daemon/main`. Each call site keeps its + own error type and fallback, so behaviour is unchanged where it was already + correct. Bearer auth compares a secret both processes read from disk, so the + two resolving different paths cannot degrade gracefully — this is the kind of + duplication that fails closed. + +- **Removed the BLE scan pause.** `ble_scan_paused`, the `needs_ble_pause` + predicate, its 400 ms sleep on every BLE connect and the scanner's 300 ms poll + are gone, along with the `AppState` field. The hazard was real but is now + unconstructible: each btleplug crate used to open its own `CBCentralManager`, + and on macOS two concurrent `scanForPeripherals()` calls suppress the + `didConnect:` callback so `connect()` hangs forever. Every BLE consumer now + goes through one `webbluetooth::Bluetooth::shared()` session, and one manager + scanning while it connects is ordinary CoreBluetooth usage. + +### Build + +- **Changelog is readable again.** `CHANGELOG.md` keeps the 20 newest releases + in full and links the rest to their files under `changes/releases/`, taking it + from 6,065 lines / 595 KB to 496 lines / 31 KB. Every release remains + reachable — verified none missing and none duplicated. + +- **The changelog's git-log fallback no longer mislabels everything.** When no + fragment is written, `bump` derives entries from commit subjects; it filed all + of them under `### Features` regardless of content, which is how "fixed glib + with a patch" shipped as a feature. Entries are now bucketed by + conventional-commit prefix, unlabelled commits go to `Refactor` rather than + being guessed as features, and the fallback prints a visible warning that raw + commit subjects are about to become user-facing release notes. + +- **Dropped the Windows lld-link workaround.** `SKILL_NO_LLD` and the forced + `link.exe` were added on the theory that lld-link could not resolve Windows SDK + import libraries like `combase.lib`. `combase.lib` does not exist in the SDK at + all, so both linkers were correctly reporting a missing file; the real fault was + upstream and is fixed. `SKILL_NO_LLD` was also inert here — only + `tauri-build.js` reads it, and the release path uses `compile-product.mjs`. + +- **The Linux `.deb` declared one dependency and needed fifteen.** `dpkg-deb + --build` is a raw packer: unlike `rpmbuild`, which reads each ELF and emits a + soname requirement per NEEDED entry, it ships exactly what the control file + says. The control file said `Depends: libopenblas0` and nothing else, so the + package installed cleanly on a machine missing the entire GTK/WebKit stack and + the app then failed to start — a worse failure than refusing to install. + + Measured against the rc.31 binaries with `dpkg-shlibdeps`, the real set is: + `libasound2t64`, `libc6`, `libcairo2`, `libdbus-1-3`, `libgcc-s1`, + `libgdk-pixbuf-2.0-0`, `libglib2.0-0t64`, `libgtk-3-0t64`, + `libjavascriptcoregtk-4.1-0`, `libopenblas0`, `libsoup-3.0-0`, `libssl3t64`, + `libstdc++6`, `libudev1`, `libwebkit2gtk-4.1-0` — each with a minimum version. + `Depends` is now derived from the three shipped binaries at package time rather + than hand-maintained, and `dpkg-dev` is named explicitly in the release apt + list so a slimmer image cannot quietly lose the tool. + + If `dpkg-shlibdeps` is missing or yields nothing the script still falls back to + the old single-dependency value, but now says so loudly on stderr. A silent + fallback is how this shipped under-declared in the first place: it built, it + installed, and only the user saw the failure. + +- **Removed dead ONNX Runtime plumbing.** `ort` / `ort-sys` are absent from + `Cargo.lock` entirely — KittenTTS was the only consumer and now runs natively + on RLX — but the build tooling still carried six pieces of machinery for it: + the `.so` bundling blocks in `package-linux-system-bundles.sh` and + `package-linux-dist.sh`, an `actions/cache` step in `release-linux.yml` + keyed on a path nothing writes, the unreferenced + `setup-onnxruntime-{linux,windows}` composite actions, and + `scripts/install-onnxruntime-linux.sh` which only that dead action called. All + gone, plus the stale `ort-sys` entries in the standalone + `crates/skill-tts/e2e` lockfile, and two comments that still described ORT as + live on non-Windows targets. + + This also retires a latent silent-exit bug rather than papering over it. The + lookup was `ORT_SO="$(find .../release/build ... | head -1)"` under + `set -euo pipefail`; `find` exits non-zero when that directory does not exist, + so the command substitution killed the whole script with **no output at all** + and the graceful "not found" warning below it was unreachable. The release + always has a `build/` directory so it never showed there, but any packaging run + without one died mute. Deleting the block removes the hazard outright. + +- **RPM packaging hardened.** The `-` → `~` rewrite of RC versions is no longer + written in a form whose meaning depends on the bash version (bare `~` is + tilde-expanded to `$HOME` on bash 5; `\~` leaves a literal backslash on bash + 3.2), and an allowlist guard now refuses to build rather than let `rpmbuild` + silently rewrite an invalid `Version`. The manual `Requires: openblas` is + dropped: rpmbuild already auto-generates `libopenblas.so.0()(64bit)` from the + shipped ELF, and the literal package name does not exist on every distro this + rpm targets. + +- **Groundwork for an aarch64 Linux package.** `Release — Linux` builds x86_64 + only, so no ARM Linux `.deb`/`.rpm` ships even though + `package-linux-system-bundles.sh` already knows the aarch64 arch mapping. The + `release-setup` action's apt list is now arch-aware — LunarG publishes + `vulkan-sdk` for amd64 only, so a caller can substitute Ubuntu's + `libvulkan-dev libvulkan1` (the same fallback + `scripts/install-vulkan-sdk.sh` already uses) and the LunarG repo step is + skipped when it would contribute nothing. Existing callers pass nothing and + behave identically. + + A `workflow_dispatch`-only `Trial — Linux arm64 packages` workflow builds the + daemon for `aarch64-unknown-linux-gnu`, runs the packaging script and asserts + the resulting rpm: `ARCH=aarch64`, no `-` or backslash in `Version`, a `~` + whenever `VERSION` is an RC, and an auto-generated `libopenblas.so.0` + requirement. It is deliberately not on the tag-driven release path — an + unproven job there can fail a release, and a second full Linux build doubles + the cost of every tag — and it uploads to the workflow run rather than to a + Release, so a trial cannot publish anything. + +- **RPM verification harness.** `npm run verify:rpm` (or + `scripts/verify-rpm-docker.sh`) installs a NeuroSkill `.rpm` on a real Fedora + in Docker and checks it, for `x86_64` and `aarch64`. We build the rpm on Ubuntu + but ship it to Fedora/RHEL/openSUSE, so the failures that matter are + distro-side — whether `dnf` can satisfy the dependencies rpmbuild generated, + and whether the binary's NEEDED libraries exist under those package names. + Building it on Ubuntu says nothing about either. + + It takes the package from `--rpm`, else a local release build, else the newest + published GitHub release; for an architecture with no real rpm yet (aarch64, + since `Release — Linux` is x86_64-only) `--synthetic` builds one from the same + spec the release script emits, with a genuinely OpenBLAS-linked binary so the + dependency and linkage checks still mean something. Checks: name/arch/licence, + `Version` legal and tilde-ordered below its release (via `rpm.vercmp`), + auto-generated `libopenblas.so.0` present and no unsatisfiable bare + `Requires: openblas`, `dnf install` resolves, declared paths exist, `rpm -V` + clean, ELF arch matches the package, `ldd` fully resolves, and removal cleans + up. Image: `Dockerfile.rpm-verify`; the checks live in + `scripts/lib/verify-rpm.sh`, mounted at run time so editing one needs no + rebuild. + + The metadata half needs no container: `scripts/lib/rpm_meta.py` parses the rpm + header directly (and lists the payload via bsdtar, which macOS ships and which + understands the rpm container), so version/arch/dependency assertions run on a + dev box or when the Docker daemon is wedged. The driver probes Docker with a + *bounded* wait — `docker info` blocks forever against a stuck daemon, and macOS + has no coreutils `timeout` — then verifies metadata natively and reports the + install phase as skipped rather than hanging. + + Results on a real Fedora 41, both architectures: + + | package | result | + |---|---| + | shipped rc.31 `x86_64` (downloaded from the release) | 23 pass, **1 fail** — the bare `Requires: openblas` | + | `x86_64` via the real packaging script, post-fix | **24 pass, 0 fail** | + | `aarch64` via the real packaging script, post-fix | **24 pass, 0 fail** | + + `--use-release-script` is the mode that makes the last two meaningful: it runs + `scripts/package-linux-system-bundles.sh` itself inside the container against a + stand-in binary, rather than reproducing the spec by hand, so what gets verified + is the artifact the release pipeline would actually emit. rpmbuild reports + `Requires: /usr/bin/bash libc.so.6()(64bit) libopenblas.so.0()(64bit) + rtld(GNU_HASH)` — the soname, no bare package name. + + So the defect is demonstrated on the artifact users actually download, and a + package built from the fixed spec passes every check — including + `dnf install` resolving the full dependency set, `rpm -V` clean, all three + shipped ELFs (`skill`, `skill-daemon`, `skill-tty`) matching the declared + architecture with every dynamic library resolving, and a clean erase. + `rpm.vercmp` confirms `0.0.131~rc.31 < 0.0.131` on both. + + Two flaws in the harness itself surfaced by running it, both fixed: the image + has to be built per `--platform` (building once for the host arch and running + with `--platform linux/amd64` makes docker try to pull a local-only tag), and + `/usr/bin/neuroskill` is a bash launcher rather than the binary — checking + *it* for an ELF architecture and running `ldd` against it passed for the wrong + reason. The checks now inspect the real executables under `/opt/neuroskill`. + +## [0.0.131-rc.31] — 2026-09-28 + +### Features + +- fix the session drop issue +- improve cli + +## [0.0.131-rc.30] — 2026-09-28 + +### Refactor + +- **One `CBCentralManager` per process, by construction.** `btleplug` is now + replaced wholesale by `patches/btleplug-0.11.9` — its public API + reimplemented over `webbluetooth` — via a single `[patch.crates-io]` entry. + The five device crates still written against btleplug (`awear`, `idun`, + `mendi`, `mw75`, `openbci`) compile against it **unchanged**, and every BLE + operation in the process now funnels through one + `webbluetooth::Bluetooth::shared()` session. + + This replaces the `eugenehp/btleplug` fork, whose only purpose was a macOS + scanning fix. + + Upstream btleplug's CoreBluetooth backend has no singleton of any kind — no + `OnceLock`, `OnceCell`, `lazy_static` or `static` — so every + `Manager::new().adapters()` allocated a fresh `CBCentralManager` on a detached + thread running `loop { wait_for_message().await }` with no exit condition. + Each call permanently leaked a thread, a tokio runtime, a dispatch queue, a + delegate and a manager. Those calls were not one-time init either: in `mw75` + they sit inside `scan_all()` and `connect()`, so it was one more manager per + scan and per connect attempt, and on macOS a second manager cannot discover + peripherals while another is scanning. + + Migrating the five crates one at a time could not have fixed this — it relies + on each crate being correct and leaves a second manager live throughout. The + shim makes a second session *unconstructible*: `Bluetooth::new()` and + `with_chooser()` appear nowhere in it and have no btleplug concept that maps + to them. `platform::tests::every_adapter_is_the_same_session` asserts it. + + `src/api/` and `src/common/` are byte-identical copies of upstream 0.11.8, so + the consuming crates see the same types and a future re-sync stays a straight + copy; only the backend (`src/platform.rs`) is new. Two places the mapping is + not 1:1, both handled there: **notification fan-in** (btleplug hands out one + merged stream per peripheral, valid before any connection and across + reconnects, while webbluetooth subscribes per characteristic — so each + `subscribe()` spawns a pump that tags values with their characteristic UUID + into a per-peripheral broadcast channel) and **scan refcounting** (an + `Adapter` is a shared handle, so dropping one must not tear down a scan + another holder still wants). + +- **Remove the Hermes V1 driver.** The `hermes-ble` dependency, + `skill-devices/src/session/hermes.rs`, `connect_hermes`, the + `ConnectRoute::Hermes` route and Hermes BLE-name recognition in the scanner + are all gone. + + `hermes-ble` was the one crate pinned to `btleplug ^0.12.0`, which the + workspace's 0.11 patch could not satisfy — so it resolved unpatched from the + registry and put a second, entirely separate btleplug stack in the graph. One + `[patch.crates-io]` entry supplies one version, so removing it is what let the + webbluetooth shim cover every remaining btleplug user at once. + + A device paired on an older build still carries its name into routing, and + `select_connect_route` ends in a `ConnectRoute::Muse` catch-all — so a Hermes + headset would have been silently handed the Muse protocol. Connecting one now + fails with an explicit message instead, guarded by + `removed_hermes_does_not_fall_back_to_muse`. + + Note the Hermes *device kind* is still listed in the app's supported-devices + catalogue (`DeviceKind::Hermes`, its i18n strings and device image), which + carries no btleplug dependency. Delisting it from the UI is a separate change. + +### Build + +- **Fix the Windows release link failure** that broke rc.26 through rc.29. The + BLE migration pulled in `webbluetooth-windows` 0.0.1, which declared + `#[link(name = "combase")]`; the Windows SDK ships no `combase.lib`, so every + Windows build died at the final link of `skill-daemon` with `LNK1181: cannot + open input file 'combase.lib'`. The WinRT string and activation exports it + wants (`WindowsCreateString`, `RoGetActivationFactory`, and four more) live in + `combase.dll` at run time but are imported from `runtimeobject.lib`. Fixed + upstream in `webbluetooth` 0.0.2 and picked up here. + + The earlier `SKILL_NO_LLD` / forced-`link.exe` workaround in + `release-windows.yml` was treating a symptom — both linkers were correctly + reporting a library that does not exist — and can be removed separately. + +### Dependencies + +- Bump `webbluetooth` 0.0.1 → 0.0.3 and `muse-rs` 0.2.0 → 0.2.1. **Both had to + move together**: Cargo treats 0.0.x releases as mutually incompatible, so + bumping only one resolved to two copies of `webbluetooth` — which kept the + unfixed 0.0.1 Windows backend in the graph *and* meant two + `Bluetooth::shared()` `OnceLock`s, so two `CBCentralManager`s and devices that + would not connect. The invariant is now recorded in the workspace root + `Cargo.toml`: `cargo tree -p skill-daemon -i webbluetooth` must print exactly + one node. + +## [0.0.131-rc.29] — 2026-09-22 + +### Features + +- MSVC vs LLD + +## [0.0.131-rc.28] — 2026-09-22 + +### Features + +- updated release windows action + +## [0.0.131-rc.27] — 2026-09-22 + +### Features + +- fixed windows build + +## [0.0.131-rc.26] — 2026-09-22 + +### Bugfixes + +- **Paired devices survive the BLE backend change**: webbluetooth reports Apple device ids as uppercase `NSUUID` strings where btleplug reported lowercase UUIDs, which would have made every entry in `paired_devices.json` stop matching after upgrade — the headset would reappear as unpaired and auto-reconnect would never fire. BLE ids are now canonicalised to one spelling (`skill_daemon_common::ble_id`) at every write, compared case-insensitively at every lookup, and migrated in place on first start. +- **"Forget device" no longer silently fails**: the paired-list removal compared ids with exact string equality, so a case difference left the device paired. +- **Duplicate scanner entries**: a paired device whose stored id differed in case from the scanner's was pushed into the device list a second time, listing the same headset twice. +- **Daemon can request Bluetooth under launchd**: `skill-daemon.app`'s generated `Info.plist` was missing `NSBluetoothAlwaysUsageDescription`. Spawned by the Tauri app the outer bundle's key covered it, but started by launchd (`RunAtLoad`) the daemon is its own responsible process and was denied Bluetooth with no prompt — presenting as a scan that silently found nothing. + +### Refactor + +- **BLE scanner moved to webbluetooth**: the daemon's advertisement scanner now runs on the process-wide `Bluetooth::shared()` session instead of its own btleplug `CBCentralManager`, so it shares one radio session with muse-rs rather than competing with it. The session coexists with an in-flight connect by design, which removes the need to tear the scanner down and rebuild it around every BLE connect attempt. + +### Docs + +- **`docs/webbluetooth-migration.md`**: what moved to webbluetooth, what is still on btleplug, and what has to happen before the `[patch.crates-io] btleplug` override can be dropped. + +### Dependencies + +- **muse-rs 0.1 → 0.2**: switches the Muse BLE backend from btleplug to webbluetooth. The `MuseClient` / `MuseClientConfig` / `MuseDevice` / `MuseHandle` / `MuseEvent` API is unchanged, so the adapter and connect paths needed no edits. +- **webbluetooth 0.0.1** added as a direct dependency of `skill-daemon`, replacing its direct `btleplug` dependency. Its backends have no native dependencies — no `bluer`, no `dbus`, no `windows` crates — which drops a runtime `libdbus` requirement from the Linux packages. + +## [0.0.131-rc.25] — 2026-09-18 + +### Features + +- **`exg_auto_download_weights` setting** (Settings → EEG Model, off by default): when an EEG session starts without encoder weights on disk, fetch them automatically instead of recording metrics-only. Off by default because the default ZUNA encoder is a 380M-parameter model. Reuses the existing download machinery, including progress events and cancellation. + +### Performance + +- **Screenshot backfill: one paged query instead of three full table scans.** `rows_needing_backfill` replaces issuing `rows_without_ocr` + `rows_without_embedding` + `rows_without_ocr_embedding` and merging them into a map of every pending row; it carries the per-row flags plus `ocr_text` and `timestamp`, which also removes two per-row lookups. Rows are walked newest-first via an `id` cursor, so an interrupted pass has covered the most recently captured screenshots rather than an arbitrary hash-order subset. +- Backfill progress now reports to the UI on the existing `screenshot-reembed-progress` event (`done`/`total`/`elapsed_secs`/`eta_secs`, plus `embedded`/`skipped`/`complete`), and logs an embedded/skipped/elapsed tally on completion. Previously a multi-minute pass was silent and unreadable files were skipped without being counted. +- **An empty screenshot HNSW index could never be loaded again.** `fast-hnsw` serializes a zero-node labeled index but rejects it on read ("file contains no payload section"), so an empty index that reached disk failed to load on every subsequent boot, was rebuilt — still empty — and written again. Empty indexes are no longer written, and a stale empty file is removed so the next load takes the clean path. +- **EEG sessions recorded with no embeddings gave no actionable signal.** The weights resolver only probes the HuggingFace cache and never downloads, so a machine without encoder weights recorded every session metrics-only behind a single ERROR line. `EmbedWorkerStatus` now reports a `reason` (`weights_missing` vs `encoder_unavailable`) and the worker emits `ExgWeightsMissing`. +- **Auto-connect attempted a BLE device before it was discoverable.** The first connect fired 900 ms after daemon boot against the cached preferred device, burning a full scan timeout per attempt — typically 7 failed attempts and ~70 s of ERROR lines before a headband appeared, out of a 12-attempt budget. BLE targets now wait for the scanner to see the device (60 s cap, then attempt anyway); wired and manual targets are unaffected. Retryable failures log at WARN until the retry budget is exhausted. +- **ANT Neuro SDK was re-initialized every 10 seconds.** The scanner built a fresh `AntNeuroSdk` on every other tick, and its `NativeBackend` logs its version unconditionally — one INFO line every 10 s for the life of the daemon, on machines with no ANT Neuro hardware. The handle is now built once per process. + +### Bugfixes + +- **Screenshot capture was silently disabled on default settings.** The daemon's `ScreenshotContext::is_session_active()` returned a hardcoded `false` — a stub left by the thin-client migration (`ba3f07d4`), where the Tauri implementation had checked `session_start_utc.is_some()`. Since `session_only` defaults to `true`, the capture loop's gate was permanently shut: the worker spawned, logged "worker spawned", then spun on a 1-second no-op forever, never capturing, embedding, or backfilling. It now reads the live `session_handle`, matching how the rest of the daemon defines an active session. +- **Screenshot backfill was decided once at boot and effectively never ran.** The gate was read a single time when the embed worker spawned — before any session exists — so with the default `session_only = true` the historical catch-up was skipped on every boot and never re-evaluated. The backfill now runs from the embed loop's idle time, re-reading the gate every pass. +- **The screenshot backfill starved live capture.** It walked every owed row in one uninterruptible pass on the embed thread; since the job channel is `bounded(4)` with `try_send`, the capture thread *dropped* new screenshots for the whole window — manufacturing the debt the backfill was paying down, recoverable only on a later daemon start. It now runs in bounded 25-row chunks during idle ticks, so live jobs always take priority, a mid-pass disable stops it, and completed work survives a restart (indexes are saved per chunk). + +### Server + +- **Canonical `unix_ms` column on the `embeddings` table.** `timestamp` carries three historical encodings (Unix ms, `YYYYMMDDHHmmss`, and `YYYYMMDDHHmmss × 1000`), so every range query had to go through `DualTimestampRange` and match all three — with an explicit warning never to write a raw `WHERE timestamp >= ?`. Day stores now gain a `unix_ms` column, backfilled from whichever format each row uses, plus an index. + + The migration is deliberately **additive: `timestamp` is never rewritten**, because older builds of the app read it directly and expect the format they wrote — normalising it in place would corrupt their view of existing recordings. This is the expand half of expand/contract; `timestamp` can only be retired once no old build remains in the wild. Applied on open by both writers (the day store and the session pipeline's `EpochStore`, which share the table), idempotent, and resumable if interrupted part-way. + + Verified against a real 296-row recording containing both encodings: all rows backfilled, every `timestamp` byte-for-byte unchanged, every `unix_ms` a plausible instant, and a re-run a no-op. An `#[ignore]`d fixture test (`SKILL_MIGRATION_FIXTURE`) re-runs that check against any real day store before a release. + +### Dependencies + +- Refresh `rlx` / `rlx-models` git pins from the stale 0.2.14 lock to current `main` (0.2.16). +- **Clear all outstanding `cargo deny` advisories.** `rustls` 0.23.42 → 0.23.45 (RUSTSEC-2026-0285, TLS 1.3 handshake messages accepted across encryption-level boundaries), `h2` 0.4.15 → 0.4.19 and `chacha20` 0.10.1 → 0.10.2 (yanked). +- **Fork `oura-api` 0.1.2 to `reqwest` 0.12** (`patches/oura-api-0.1.2`, vendored; the only change from crates.io is the dependency version). Upstream's newest release pins `reqwest` 0.11, which pulled the entire hyper 0.14 stack including `h2` 0.3.x — the branch RUSTSEC-2026-0258 is unpatched on, fixed in `h2` >= 0.4.16 only. This removes the last advisory and drops a duplicate HTTP stack: the tree now resolves a single `hyper` (1.11) and a single `h2` (0.4.19), where it previously carried both 0.14/1.x and 0.3/0.4. +- Remove dead `deny.toml` entries that made `cargo deny` fail or warn: the `RUSTSEC-2024-0415` gtk ignore (unreachable — `unmaintained = "workspace"` already scopes that lint to our own crates, and an ignore that never matches is a hard error), the `winreg@0.55.0` duplicate skip (deduplicated by dropping `reqwest` 0.11), and re-pin the `winnow` skip 1.0.3 → 1.0.4. + +## [0.0.131-rc.24] — 2026-08-12 + +### Features + +- added i18n + +## [0.0.131-rc.23] — 2026-08-09 + +### Features + +- updated inference + +## [0.0.131-rc.22] — 2026-08-04 + +### Features + +- lock + +## [0.0.131-rc.21] — 2026-08-04 + +### Features + +- new distributed + +## [0.0.131-rc.20] — 2026-07-31 + +### Features + +- updated deps +- vendored gpu allocator +- updated cache + +## [0.0.131-rc.19] — 2026-07-28 + +### Features + +- Minor updates and improvements + +## [0.0.131-rc.18] — 2026-07-28 + +### Bugfixes + +- **Chat cancel handler shadowing**: Fix recursive symbol shadowing so chat tool cancel calls the daemon client correctly. +- **Tokens copy action typing**: Update Tokens tab Copy behavior to use typed token access and `common.copy` label wiring. + +### Build + +- **Workspace cleanup**: Remove duplicate `Cargo.toml` workspace members (`skill-daemon`, `skill-daemon-common`). + +- **Cache freshness automation**: Add a weekly GitHub Action to refresh HF download counts and open a PR; add CI validation that the committed cache covers the current catalog. + +- **Desktop CSP hardening**: Replace `csp: null` with a localhost/daemon-aware Content-Security-Policy in `tauri.conf.json`. + +- **VERSION as release source of truth**: Add repo-root `VERSION` and make bump/tag/release/CI/packaging flows read it, then sync `package.json`, `tauri.conf.json`, and `Cargo.toml` from that value. +- **Shared compile-product pipeline**: Introduce `scripts/compile-product.mjs` to build daemon (OS umbrella) -> tty -> app (`custom-protocol`) and use it across release workflows, dry-run, daemon-sidecar prep, and tauri-build. +- **compile-product hardening**: Tighten target-specific tty gating, strict OS-feature mapping, `CARGO_TARGET_DIR` plus `cargo metadata` staging, daemon-only `--timings`, fail-closed local daemon builds, and Linux `skill-tty` packaging. +- **Product build follow-ups**: Include `product` in umbrellas, report `VERSION` from daemon, ship CUDA+wgpu with runtime fallback (CUDA -> wgpu -> CPU) on Linux/Windows, enforce macOS tty hard-fail, and improve Linux daemon path discovery. + +### CLI + +- **Typed daemon client checks**: Migrate tokens, chat rename/cancel-tool, and device prefer/forget/retry/status call sites off `daemonInvoke`; fix `tokens.ts` ACL wire types to snake_case; add `npm run check:typed-daemon-clients`. + +### UI + +- **Primary app shell navigation**: Add a Live / Find / Ask / History destinations bar across main feature windows while keeping Add Label in the main titlebar. +- **Settings information architecture**: Group tabs by Signal, Intelligence, Capture & Privacy, Automation, and App, with a collapsible Advanced section and Devices as the default landing tab. +- **Live dashboard view modes**: Add Waveform / Physiology / State switching with progressive disclosure based on connected modality support. +- **Onboarding flow simplification**: Reduce core first-run path to connect -> fit -> tray -> done, require research-use acknowledgement, and move calibration/models/permissions to optional setup from Done. +- **Getting Started deep links**: Make checklist items open Devices, Calibration, Goals, Downloads, Search, and API targets directly. +- **Find UX updates**: Add example chips, prioritize Interactive query + Search action, and move pipeline/filters under Advanced with titlebar source labeling. +- **Command palette IA**: Add a Primary section for Live / Find / Ask / History and separate Add Label from Browse Labels actions. +- **Labels discoverability alignment**: Standardize Browse Labels naming for window title and `labels.openLabels`; change History toggle wording to "Show labels"; localize titlebar Help/Reload labels. +- **Shell discoverability improvements**: Show accelerator hints for Find / Ask / History and include shortcuts in tooltips. +- **Shell interaction fix**: Render primary nav inside `#main-content` so it is not blocked by the draggable titlebar region. +- **Chart accessibility**: Add hatch patterns plus active scheme colors on band-power tiles so differentiation does not rely on color alone. +- **Window-title consistency**: Open labels window as "Browse Labels" from Rust side and align `labels.title` text. + +### LLM + +- **HF downloads cache for model ranking**: Add `scripts/update-hf-downloads-cache.mjs` to crawl Hub download counts for every catalog repo plus top mlx-community models, writing both `src-tauri/hf_downloads_cache.json` and `src/lib/generated/hf-downloads-cache.json`. +- **Family sort by popularity**: Sort the LLM family picker by Hub downloads descending by default and show `↓ N` download hints in the dropdown. +- **MLX search UX**: Add an MLX tab in HF search (also sorted by downloads) and support importing an entire mlx-community snapshot via "Add pack". + +- **Mistral VL runner support**: Promote `mistral3`/`mistral4` out of unimplemented paths in `rlx-models`, wire `MistralRunner` as `LmRunner`, add `rlx-mistral-vl` for Pixtral ViT/projector on Metal/CUDA/CPU, and route mistral+mmproj via `auto_runner_with_mmproj`. +- **NeuroSkill loader integration**: Add `try_mistral_vl_runner` in `load_with_mmproj` and document Ministral as supported. + +- **MLX community model path**: Wire huggingface.co/mlx-community packs through `rlx-models` (`MlxLoader` / `Qwen3Runner::from_mlx_packed`). +- **Catalog entries for MLX Qwen3**: Add Qwen3 0.6B / 1.7B / 4B 4-bit entries and snapshot download handling for `tags: ["mlx"]`. +- **Discovery + loader behavior**: Surface safetensors snapshot directories (not GGUF-only) in local discovery and prefer the MLX device when loading compatible packs. +- **Feature-gated rollout**: Keep discovery and `from_mlx_packed` behind `llm-model-discovery` until the `rlx-models` pin is bumped. + +- **Model download integrity checks**: SHA-256 verify HF LFS blobs before promoting `.incomplete` to final blob, re-verify existing blobs, and delete mismatched files. + +- **Feature-flag cleanup**: Remove stale `llm-vulkan` / `llm-metal` references from scripts, docs, CONTRIBUTING, skill-llm README, and i18n copy in favor of OS umbrellas (`apple`, `linux`, `windows`). + +- **VL mmproj runner wiring**: In `rlx-models`, add multimodal support via `GemmaRunner.mmproj` and `LmRunner` multimodal handling, add combined `Qwen3VlRunner` and `LfmVlRunner`, and make `auto_runner_with_mmproj` attach mmproj for supported families while erroring clearly for unsupported families instead of silently ignoring mmproj. +- **Qwen3 VL promotion**: Promote `qwen3vl*` out of unimplemented paths. +- **Loader routing in app**: Update NeuroSkill `load_with_mmproj` to prefer Qwen3-VL / LFM-VL / Gemma+mmproj before plain Qwen3 and return explicit errors when mmproj is set on non-multimodal runners. + +- **Windows KittenTTS enablement**: Enable `rlx-kittentts` on Windows (CUDA + GPU features) and activate `tts_kitten_active` across desktop OSes so the default voice engine is no longer a no-op. + +### Server + +- **Daemon routes extraction**: Move iroh HTTP routes into `skill-daemon-routes` as the first non-stub extracted module, and document remaining extraction blockers in the crate and `docs/architecture.md`. + +- **Tool safety defaults**: Default `require_bash_edit` to `true`, expand bash denylist coverage (`curl|bash`, interpreters), and require approval for secret home paths (`.ssh`, `.aws`, `.env`, `auth.token`) and Windows system directories. +- **`web_fetch` SSRF guardrails**: Refuse loopback, private, link-local, and cloud-metadata URLs. +- **LAN bind safety**: Refuse non-loopback `SKILL_DAEMON_ADDR` unless `SKILL_DAEMON_ALLOW_LAN=1`, warn on `0.0.0.0` WebSocket host usage, and document CORS plus loopback defaults. + +- **Tool-safety fail-closed behavior**: Register native approval hooks in `skill-daemon` and deny bash-edit execution when no hook is present instead of executing unmodified scripts. ### i18n -- Added `dnd.focusMode`, `dnd.focusModeDesc`, `dnd.focusModeLoading` keys - to all five language files (en, de, fr, he, uk) +- **Locale completion for new IA strings**: Translate shell, settings-group, live-view, onboarding, Find, and command-palette additions across de, es, fr, he, ja, ko, uk, zh and replace auto-synced English fallbacks. ---- +### Docs -## [0.0.3] — 2026-03-06 +- **LLM and hooks ownership docs**: Rewrite `docs/LLM.md` and `docs/HOOKS.md` for RLX-in-daemon ownership, correct the `docs/AI.md` llama.cpp claim, and align Node prerequisite docs to >=20 (`docs/DEVELOPMENT.md`, `package.json` engines). -- Added NeuTTS engine support alongside KittenTTS, with seamless switching between engines -- TTS engine switching now works reliably in both directions -- Graceful shutdown for NeuTTS on engine change or app exit -- TTS caching and quality improvements -- UI updates for TTS tab including progress/error state display -- Fixed TypeScript type for TTS error phase -- Added translations -- Better updater configuration +- **Release/CI documentation alignment**: Document release-setup composite options (including optional `build-frontend`), standardize rust-cache guidance as the only compile cache, clarify PR vs release CI behavior, and refresh skill-daemon feature docs. ---- +### Dependencies -## [0.0.2] — 2026-03-04 +- **Windows Vulkan SDK removal**: Drop Vulkan SDK requirements from Windows PR clippy/release paths where no longer needed. -- Improved EEG, Band, and GPU charts -- UI polish for main page -- Dependency and version bumps +## [0.0.131-rc.17] — 2026-07-24 ---- +### Features + +- Minor updates and improvements + +## [0.0.131-rc.16] — 2026-07-24 + +### Features + +- updates skill daemono# Please enter the commit message for your changes. Lines starting +- Gate LLM e2e tests on llm-rlx so default cargo test skips them. +- Feature unification from skill-daemon was enabling the llm marker without a backend, which made pre-push fail. +- Allow GPL for new rlx TTS crates and ignore protobuf advisory. +- rlx-tiny-tts pulled onnx/protobuf back into the graph; extend cargo-deny exceptions so pre-push stays green. +- Fix release CI: patch remaining crates.io rlx models and drop Windows wgpu. +- rc.15 failed on Mac/Linux from registry rlx-qwen3 vs git rlx-flow, and on Windows from wgpu-hal/windows-rs 0.62 vs gpu-allocator on 0.61. Patch the leftover model crates and keep the Windows umbrella on CUDA until gpu-allocator aligns. +- Simplify release CI setup and pin rlx git patches. +- Fold Linux Vulkan/apt into a shared release-setup action, drop sccache from CI, scope daemon features per OS, and keep Cargo.lock on GitHub rlx main. + +## Earlier releases -## [0.0.1] — 2026-03-01 +The 144 releases before this point are kept in full under [`changes/releases/`](changes/releases/), one file each. -- Initial release -- CI/CD pipeline with signing, notarization, and auto-updater -- EEG visualisation, metrics, and GPU monitoring -- TTS foundation with KittenTTS +- [0.0.131-rc.15](changes/releases/0.0.131-rc.15.md) +- [0.0.131-rc.14](changes/releases/0.0.131-rc.14.md) +- [0.0.131-rc.13](changes/releases/0.0.131-rc.13.md) +- [0.0.131-rc.12](changes/releases/0.0.131-rc.12.md) +- [0.0.131-rc.11](changes/releases/0.0.131-rc.11.md) +- [0.0.131-rc.10](changes/releases/0.0.131-rc.10.md) +- [0.0.131-rc.9](changes/releases/0.0.131-rc.9.md) +- [0.0.131-rc.8](changes/releases/0.0.131-rc.8.md) +- [0.0.131-rc.7](changes/releases/0.0.131-rc.7.md) +- [0.0.131-rc.6](changes/releases/0.0.131-rc.6.md) +- [0.0.131-rc.5](changes/releases/0.0.131-rc.5.md) +- [0.0.131-rc.4](changes/releases/0.0.131-rc.4.md) +- [0.0.131-rc.3](changes/releases/0.0.131-rc.3.md) +- [0.0.131-rc.2](changes/releases/0.0.131-rc.2.md) +- [0.0.130-rc.31](changes/releases/0.0.130-rc.31.md) +- [0.0.130-rc.30](changes/releases/0.0.130-rc.30.md) +- [0.0.130-rc.29](changes/releases/0.0.130-rc.29.md) +- [0.0.130-rc.28](changes/releases/0.0.130-rc.28.md) +- [0.0.130-rc.27](changes/releases/0.0.130-rc.27.md) +- [0.0.130-rc.26](changes/releases/0.0.130-rc.26.md) +- [0.0.130-rc.25](changes/releases/0.0.130-rc.25.md) +- [0.0.130-rc.24](changes/releases/0.0.130-rc.24.md) +- [0.0.130-rc.23](changes/releases/0.0.130-rc.23.md) +- [0.0.130-rc.22](changes/releases/0.0.130-rc.22.md) +- [0.0.130-rc.21](changes/releases/0.0.130-rc.21.md) +- [0.0.130-rc.20](changes/releases/0.0.130-rc.20.md) +- [0.0.130-rc.19](changes/releases/0.0.130-rc.19.md) +- [0.0.130-rc.18](changes/releases/0.0.130-rc.18.md) +- [0.0.130-rc.17](changes/releases/0.0.130-rc.17.md) +- [0.0.130-rc.16](changes/releases/0.0.130-rc.16.md) +- [0.0.130-rc.15](changes/releases/0.0.130-rc.15.md) +- [0.0.130-rc.14](changes/releases/0.0.130-rc.14.md) +- [0.0.130-rc.13](changes/releases/0.0.130-rc.13.md) +- [0.0.130-rc.12](changes/releases/0.0.130-rc.12.md) +- [0.0.130-rc.11](changes/releases/0.0.130-rc.11.md) +- [0.0.130-rc.10](changes/releases/0.0.130-rc.10.md) +- [0.0.130-rc.9](changes/releases/0.0.130-rc.9.md) +- [0.0.130-rc.8](changes/releases/0.0.130-rc.8.md) +- [0.0.130-rc.7](changes/releases/0.0.130-rc.7.md) +- [0.0.130-rc.6](changes/releases/0.0.130-rc.6.md) +- [0.0.130-rc.5](changes/releases/0.0.130-rc.5.md) +- [0.0.130-rc.4](changes/releases/0.0.130-rc.4.md) +- [0.0.130-rc.3](changes/releases/0.0.130-rc.3.md) +- [0.0.130-rc.2](changes/releases/0.0.130-rc.2.md) +- [0.0.130-rc.1](changes/releases/0.0.130-rc.1.md) +- [0.0.129](changes/releases/0.0.129.md) +- [0.0.128](changes/releases/0.0.128.md) +- [0.0.127](changes/releases/0.0.127.md) +- [0.0.126](changes/releases/0.0.126.md) +- [0.0.125](changes/releases/0.0.125.md) +- [0.0.124](changes/releases/0.0.124.md) +- [0.0.123](changes/releases/0.0.123.md) +- [0.0.122](changes/releases/0.0.122.md) +- [0.0.121](changes/releases/0.0.121.md) +- [0.0.120](changes/releases/0.0.120.md) +- [0.0.119](changes/releases/0.0.119.md) +- [0.0.118](changes/releases/0.0.118.md) +- [0.0.117](changes/releases/0.0.117.md) +- [0.0.116](changes/releases/0.0.116.md) +- [0.0.115](changes/releases/0.0.115.md) +- [0.0.114](changes/releases/0.0.114.md) +- [0.0.113](changes/releases/0.0.113.md) +- [0.0.112](changes/releases/0.0.112.md) +- [0.0.111](changes/releases/0.0.111.md) +- [0.0.110](changes/releases/0.0.110.md) +- [0.0.109](changes/releases/0.0.109.md) +- [0.0.106](changes/releases/0.0.106.md) +- [0.0.104](changes/releases/0.0.104.md) +- [0.0.103](changes/releases/0.0.103.md) +- [0.0.102](changes/releases/0.0.102.md) +- [0.0.101](changes/releases/0.0.101.md) +- [0.0.100](changes/releases/0.0.100.md) +- [0.0.99](changes/releases/0.0.99.md) +- [0.0.98](changes/releases/0.0.98.md) +- [0.0.97](changes/releases/0.0.97.md) +- [0.0.96](changes/releases/0.0.96.md) +- [0.0.95](changes/releases/0.0.95.md) +- [0.0.94](changes/releases/0.0.94.md) +- [0.0.93](changes/releases/0.0.93.md) +- [0.0.88](changes/releases/0.0.88.md) +- [0.0.87](changes/releases/0.0.87.md) +- [0.0.86](changes/releases/0.0.86.md) +- [0.0.85](changes/releases/0.0.85.md) +- [0.0.84](changes/releases/0.0.84.md) +- [0.0.83](changes/releases/0.0.83.md) +- [0.0.82](changes/releases/0.0.82.md) +- [0.0.81](changes/releases/0.0.81.md) +- [0.0.80](changes/releases/0.0.80.md) +- [0.0.79](changes/releases/0.0.79.md) +- [0.0.78](changes/releases/0.0.78.md) +- [0.0.77](changes/releases/0.0.77.md) +- [0.0.76](changes/releases/0.0.76.md) +- [0.0.75](changes/releases/0.0.75.md) +- [0.0.72](changes/releases/0.0.72.md) +- [0.0.71](changes/releases/0.0.71.md) +- [0.0.70](changes/releases/0.0.70.md) +- [0.0.69](changes/releases/0.0.69.md) +- [0.0.68](changes/releases/0.0.68.md) +- [0.0.67](changes/releases/0.0.67.md) +- [0.0.63](changes/releases/0.0.63.md) +- [0.0.62](changes/releases/0.0.62.md) +- [0.0.61](changes/releases/0.0.61.md) +- [0.0.60](changes/releases/0.0.60.md) +- [0.0.59](changes/releases/0.0.59.md) +- [0.0.58](changes/releases/0.0.58.md) +- [0.0.57](changes/releases/0.0.57.md) +- [0.0.56](changes/releases/0.0.56.md) +- [0.0.55](changes/releases/0.0.55.md) +- [0.0.54](changes/releases/0.0.54.md) +- [0.0.53](changes/releases/0.0.53.md) +- [0.0.52](changes/releases/0.0.52.md) +- [0.0.51](changes/releases/0.0.51.md) +- [0.0.50](changes/releases/0.0.50.md) +- [0.0.49](changes/releases/0.0.49.md) +- [0.0.47](changes/releases/0.0.47.md) +- [0.0.46](changes/releases/0.0.46.md) +- [0.0.45](changes/releases/0.0.45.md) +- [0.0.44](changes/releases/0.0.44.md) +- [0.0.43](changes/releases/0.0.43.md) +- [0.0.42](changes/releases/0.0.42.md) +- [0.0.41](changes/releases/0.0.41.md) +- [0.0.40](changes/releases/0.0.40.md) +- [0.0.39](changes/releases/0.0.39.md) +- [0.0.38](changes/releases/0.0.38.md) +- [0.0.37](changes/releases/0.0.37.md) +- [0.0.36](changes/releases/0.0.36.md) +- [0.0.35](changes/releases/0.0.35.md) +- [0.0.34](changes/releases/0.0.34.md) +- [0.0.33](changes/releases/0.0.33.md) +- [0.0.32](changes/releases/0.0.32.md) +- [0.0.31](changes/releases/0.0.31.md) +- [0.0.30](changes/releases/0.0.30.md) +- [0.0.29](changes/releases/0.0.29.md) +- [0.0.27](changes/releases/0.0.27.md) +- [0.0.24](changes/releases/0.0.24.md) +- [0.0.23](changes/releases/0.0.23.md) +- [0.0.17](changes/releases/0.0.17.md) +- [0.0.16](changes/releases/0.0.16.md) +- [0.0.15](changes/releases/0.0.15.md) +- [0.0.13](changes/releases/0.0.13.md) +- [0.0.11](changes/releases/0.0.11.md) +- [0.0.9](changes/releases/0.0.9.md) +- [0.0.6](changes/releases/0.0.6.md) +- [0.0.3](changes/releases/0.0.3.md) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..76ef629ef --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,419 @@ +# Contributing to NeuroSkill + +## Prerequisites + +- **Rust** (latest stable) via [rustup](https://rustup.rs/) +- **Node.js** ≥ 20 with npm +- **Tauri CLI**: installed via `npm install` (workspace devDependency) +- **Platform SDKs**: Xcode (macOS), Visual Studio Build Tools + LLVM (Windows), build-essential (Linux) +- **Vulkan SDK** (optional Linux tooling; LLM GPU backends use OS umbrellas `apple` / `linux` / `windows`) + +## Quick Start + +```bash +# Clone +git clone https://github.com/NeuroSkill-com/skill.git +cd skill + +# Install all platform dependencies + JS deps (interactive) +npm run setup + +# Ensure Rust is up to date (CI uses latest stable) +rustup update stable + +# Run in development mode (starts Vite dev server + Tauri) +npm run tauri dev + +# Or build a release +npm run tauri:build +``` + +`npm run setup` auto-detects your platform and installs everything needed +(protobuf, OpenMP, GNU ar, sccache, etc.). Pass `--yes` to skip prompts. +See also `npm run setup:build-cache` +for optional build acceleration, and `npm run setup:rlx` for the optional +[RLX](https://github.com/MIT-RLX/rlx) sibling checkout (required for CI and +for `llm-rlx` / `text-embeddings-rlx` features). Details: +[docs/DEVELOPMENT.md#rlx-optional-path-dependency](docs/DEVELOPMENT.md#rlx-optional-path-dependency). + +## Project Structure + +``` +├── crates/ # Rust workspace crates (Tauri-independent) +│ ├── skill-daemon/ # Standalone HTTP daemon (sidecar) +│ ├── skill-daemon-routes/ # Extracted daemon HTTP routes (iroh first; more pending) +│ ├── skill-daemon-state/ # Daemon shared state + DB +│ ├── skill-daemon-common/ # Shared daemon types +│ ├── skill-eeg/ # EEG signal processing +│ ├── skill-exg/ # ExG (multi-modal biosignal) inference +│ ├── skill-gpu/ # GPU FFT / UMAP backends (MLX, CUDA, Vulkan) +│ ├── skill-llm/ # Local LLM inference (RLX runtime) +│ ├── skill-tools/ # LLM function-calling +│ ├── skill-vision/ # Screen content + camera vision +│ ├── skill-screenshots/ # Screenshot capture + encoding +│ ├── skill-history/ # Activity / conversation history +│ ├── skill-skills/ # Pluggable "skill" runtime +│ ├── skill-commands/ # Shell command tracking + categorization +│ ├── skill-tts/ # Text-to-speech (NeuTTS) +│ ├── skill-tray/ # System tray +│ ├── skill-headless/ # Headless daemon entrypoint +│ ├── skill-iroh/ # P2P sync (iroh) +│ ├── skill-lsl/ # Lab Streaming Layer +│ ├── skill-oura/ # Oura ring integration +│ ├── skill-calendar/ # Calendar/meeting sync +│ ├── skill-location/ # Location context +│ ├── skill-autostart/ # OS-level autostart +│ ├── skill-data/ # Data layer (SQLite, FTS5, embeddings) +│ ├── skill-router/ # Request router +│ ├── skill-jobs/ # Background job runner +│ ├── skill-health/ # Health checks +│ ├── skill-settings/ # Settings store + keychain +│ ├── skill-label-index/ # HNSW semantic label index +│ ├── skill-devices/ # EEG/biosignal device drivers +│ └── skill-constants/ # Shared constants +├── src/ # SvelteKit frontend +├── src-tauri/ # Tauri app shell +├── neuroskill/ # `neuroskill` CLI (TypeScript) +├── neuroloop/ # `neuroloop` CLI (TypeScript) +├── extensions/ # External integrations +│ ├── vscode/ # VS Code / VSCodium / Cursor extension (submodule) +│ ├── browser/ # Browser extension +│ └── widgets/ # macOS WidgetKit extension +├── skills/ # Pluggable skill definitions (markdown + manifests) +├── scripts/ # Build, CI & test scripts (Node.js + bash, no Python) +│ ├── ci.mjs # Cross-platform CI helpers +│ ├── test-picker.mjs # Interactive test suite picker +│ ├── test-all.sh # Suite runner (fmt, lint, clippy, daemon, e2e, …) +│ ├── test-fast.sh # Tiered Rust unit tests +│ └── shell-hooks/ # zsh/bash/fish/PowerShell preexec hooks +├── docs/ # Architecture + coverage docs +├── Casks/ # Generated Homebrew cask +└── changes/ # Changelog fragments +``` + +See `AGENTS.md` for the authoritative crate inventory and architecture rules. + +## Development Workflow + +### Running Tests + +```bash +# ── Interactive picker (shows all options) ──────────────────────── +npm test # Arrow keys + space to pick, enter to run + +# ── Quick shortcuts ─────────────────────────────────────────────── +npm run test:fast # fmt + lint + clippy + vitest + rust + ci + types +npm run test:all # everything (deny, smoke, daemon, e2e, mlx-e2e, widgets, …) + +# ── Individual suites ───────────────────────────────────────────── +npm run test:fmt # Rust + frontend formatting +npm run test:lint # Frontend lint (biome) +npm run test:clippy # Rust clippy +npm run test:deny # Dependency audit (cargo deny) +npm run test:vitest # Frontend unit tests (vitest) +npm run test:types # TypeScript/Svelte type checking +npm run test:rust # Tier 1 Rust tests (~5 s warm) +npm run test:rust:all # All Rust tiers (~65 s clean) +npm run test:ci # CI script self-test +npm run test:a11y # Accessibility audit +npm run test:i18n # i18n key validation +npm run test:changelog # Changelog fragment check +npm run test:smoke # Build verification +npm run test:e2e # LLM E2E (downloads model, ~15 s cached) +npm run test:mlx-e2e # MLX backend E2E — UMAP + FFT (macOS only) +npm run test:llm:e2e # Direct cargo LLM E2E (no shell wrapper) + +# ── Daemon packaging (sidecar install/upgrade) ──────────────────── +npm run test:daemon-packaging # Auto-detects OS +npm run test:daemon-packaging:mac # macOS-specific +npm run test:daemon-packaging:linux # Linux-specific +npm run test:daemon-packaging:win # Windows (PowerShell) + +# ── Git hook checks ────────────────────────────────────────────── +npm run test:hooks # pre-commit + pre-push (full) +npm run test:pre-commit # Just pre-commit checks +npm run test:pre-push # Full pre-push suite + +# ── Mix and match ───────────────────────────────────────────────── +bash scripts/test-all.sh clippy vitest ci # Run specific suites +bash scripts/test-all.sh --continue all # Don't stop on first failure +bash scripts/test-all.sh --list # Show available suites +``` + +#### Rust Test Tiers + +Tests are split into tiers by compilation cost so you get fast feedback: + +| Tier | What it tests | Clean build | Warm cache | Tests | +|------|---------------|-------------|------------|-------| +| **1** | Core logic: eeg, tools, jobs, constants, exg, gpu | ~27 s | ~5 s | ~350 | +| **2** | + data, settings, history, health, router, tts, labels, skills, commands | ~53 s | ~8 s | ~550 | +| **3** | + screenshots, devices, llm (adds ML, TLS, heavy native deps) | ~65 s | ~15 s | ~650 | +| **E2E** | LLM download → inference → tool calling (manual/CI dispatch) | ~15 s | ~12 s | 1 | + +> **Tip:** Install [sccache](https://github.com/mozilla/sccache) (`brew install sccache`) and +> set `RUSTC_WRAPPER=sccache` for faster clean rebuilds. + +See [docs/TEST-COVERAGE.md](docs/TEST-COVERAGE.md) for a detailed coverage analysis and gaps. + +### Code Quality + +```bash +# Frontend lint & format +npm run lint # Check +npm run lint:fix # Auto-fix +npm run format # Format only +npm run format:check # Check formatting + +# Rust +cargo fmt # Format +cargo fmt --check # Check formatting +cargo clippy --workspace # Lint + +# Type checking +npm run check # Svelte + TypeScript + +# i18n +npm run sync:i18n:check # Verify translation keys +npm run sync:i18n:fix # Auto-fix missing keys +npm run audit:i18n # Find unused / undefined keys +npm run check:i18n:critical # Critical-locale coverage + +# Catalog + README sync +npm run sync:llm:catalog:check # LLM model catalog drift +npm run sync:readme:supported:check + +# Accessibility +npm run audit:a11y:check + +# Dependency audit +cargo audit +cargo deny check -A no-license-field -A parse-error -A license-not-encountered +``` + +### Faster local `cargo check` + +`cargo check -p skill` can be slow on a cold cache because it compiles many heavy deps. +Use these faster options during day-to-day work: + +```bash +# Check only crates affected by your branch (same idea as CI) +BASE=origin/main +FLAGS=$(bash scripts/changed-crates.sh "$BASE") +cargo check $FLAGS + +# Or check only core app lib target +cargo check -p skill --lib + +# Optional: speed up rebuilds with sccache +export RUSTC_WRAPPER=sccache +``` + +### Commit Checklist + +The **pre-commit** hook automatically checks: +- i18n key synchronisation (when i18n files are staged) +- Frontend formatting via Biome +- Rust formatting via `cargo fmt` + +The **pre-push** hook runs scoped checks based on changed files: +- Frontend: `biome check` + `vitest related` on changed files +- Rust: `cargo clippy` + `cargo test` on affected crates +- CI scripts: `node scripts/ci.mjs self-test` when `ci.mjs` or workflows change +- Daemon guard: `vitest run daemon-client.test.ts` when daemon proxy or scripts change + +### CI & Releases + +PR / branch CI (`ci.yml`) runs VERSION sync, clippy, frontend checks, and +optional E2E jobs. Signed Tauri product builds run on release tags +(`release-{mac,linux,windows}.yml`) or Preview Build — not on every PR. + +Shared release bootstrap lives in `.github/actions/release-setup` (Rust + +**rust-cache only** + Node; optional frontend build). Product crates are +compiled via `scripts/compile-product.mjs` (daemon OS umbrella → tty → app +`custom-protocol`) so feature flags cannot drift across OS workflows. +OS-specific signing and packaging stay in the thin release workflows. +Helpers live in `scripts/ci.mjs` (macOS / Linux / Windows): + +```bash +# Validate ci.mjs and workflow references +npm run ci:test + +# Local release dry-run (builds frontend + Rust + .app bundle + changelog) +npm run ci:dry-run + +# Same but skip compile (reuse existing binaries — fast iteration) +npm run ci:dry-run:fast +``` + +**Available `ci.mjs` commands:** + +| Command | What it does | +|---------|-------------| +| `resolve-version` | Read `VERSION`, verify derived files, validate git tag when present | +| `verify-secrets NAME...` | Check that env vars are non-empty (no values printed) | +| `prepare-changelog VER OUT [RANGE]` | Extract changelog + contributors to markdown | +| `update-latest-json --platform P ...` | Merge platform entry into Tauri updater manifest | +| `discord-notify --status S ...` | Send Discord webhook notification | +| `import-apple-cert` | Import .p12 certificate into temporary keychain (macOS) | +| `validate-notarization` | Check Apple notarization credentials pre-flight (macOS) | +| `free-disk-space` | Remove unused toolchains on Linux runners | +| `install-protoc-windows` | Install protoc via Chocolatey or direct download | +| `self-test` | Validate all commands + workflow references | +| `dry-run-release [--target T] [--skip-compile]` | Full local release pipeline | + +**Release workflows** (all support on-demand via `workflow_dispatch`): +- `release-mac.yml` — macOS aarch64 (.app + .dmg) +- `release-linux.yml` — Linux x86_64 (.deb + .rpm + portable tar) +- `release-windows.yml` — Windows x64 (.exe NSIS installer) + +Tag-triggered runs publish to GitHub Releases. On-demand runs upload artifacts (14-day retention). + +### Changelog Fragments + +Every feature or bugfix **must** include a changelog fragment: + +1. Create a `.md` file in `changes/unreleased/` (e.g., `feat-my-change.md`) +2. Use the format: + ```markdown + ### Features + + - **Short title**: description of the change. + ``` +3. Valid categories: `Features`, `Performance`, `Bugfixes`, `Refactor`, `Build`, `CLI`, `UI`, `LLM`, `Server`, `i18n`, `Docs`, `Dependencies` + +At release time, `npm run bump` compiles fragments into versioned release notes. + +**Note**: The `bump` command includes safety checks to prevent accidental multiple bumps. It verifies that the current version has been properly tagged and pushed to the remote. If you need to bypass these checks (e.g., during recovery), use: + +```bash +npm run bump --force +``` + +### Cutting a release + +Releases are PR-driven. Cutting an RC opens a release PR; merging the PR (rebase or squash) promotes the most recent RC to stable with no rebuild. + +```bash +# Cut the first RC for the next release (run from main) +npm run release -- --rc + +# Iterate as bugs surface (from the release/X.Y.Z branch) +npm run release -- --rc + +# Approve and merge the PR with rebase or squash merge. +# promote.yml flips the matching tag to "latest" automatically. +``` + +See [docs/DEVELOPMENT.md → Release](docs/DEVELOPMENT.md#release) for the full flow, channel system (stable / rc), and the moving parts. + +For emergency hotfixes that must skip RC, the low-level `bump` + `tag` primitives still work (also in `docs/DEVELOPMENT.md`). + +## Secrets & Keychain + +In **release builds**, API tokens and device credentials are stored in the +system keychain (macOS Keychain, Windows Credential Manager, Linux Secret +Service) and stripped from `settings.json`. + +In **debug builds** (`tauri dev` / `cargo run`), the keychain is **skipped +entirely**. Secrets stay in `settings.json` so they persist across rebuilds. +This avoids macOS prompting for keychain access on every launch — the dev +binary has a different code signature on every build, which triggers a new +authorization dialog each time. + +> **No password prompts during development.** If you're seeing keychain +> dialogs, make sure you're running a debug build (`npm run tauri dev`), +> not a release build. + +## Architecture Notes + +- **Thin client + daemon**: The Tauri app is a UI shell. All persistent business logic + (EEG, LLM, history, embeddings, HTTP API) runs in **`skill-daemon`**, a standalone + binary shipped as a Tauri sidecar and also installable as a system service. +- All Rust crates are **Tauri-independent** — they can be tested and used standalone. +- The workspace shares a single `target/` directory (configured in `.cargo/config.toml`). +- Frontend uses **SvelteKit** in SPA mode with **Tailwind CSS v4**. +- EEG processing uses the device's actual sample rate — never hardcode 256 Hz. +- GPU backends (CUDA / Metal / MLX / wgpu / Burn) are feature-gated. Product + builds use `scripts/compile-product.mjs` with OS umbrellas `apple` / + `linux` / `windows`. Linux and Windows compile CUDA **and** wgpu so the + daemon can fall back at runtime (CUDA → wgpu → CPU) when no NVIDIA driver + is present. CUDA compiles without a local toolkit (cudarc dynamic-loading). +- See `AGENTS.md` for comprehensive rules on encoding, accent colors, session files, + multi-device DSP, and crate boundaries. + +### Daemon + +```bash +# Run the daemon manually (dev) +npm run daemon # tsx scripts/daemon.ts + +# Stop any running daemon +npm run stop # pkill -f skill-daemon + +# Audit daemon HTTP routes vs frontend invokes +npm run audit:daemon-routes +npm run check:daemon-invokes + +# Health check +npm run health +``` + +On a fresh install (any OS), the installer kills any old daemon, installs the new +binary, and starts it — never leave two daemons running side by side. + +## CLI Tools + +Two TypeScript CLIs ship alongside the desktop app: + +- **`neuroskill`** — query the daemon for activity, brain state, dev loops, terminal + impact, and install shell hooks / VS Code extensions. Top-level subcommands include + `activity`, `brain`, `terminal`, and `vscode`. +- **`neuroloop`** — focus loop / pomodoro CLI driven by EEG state. + +```bash +npm run skill # Run neuroskill from source (tsx) +npm run neuroskill # Build + run neuroskill +npm run neuroloop # Build + run neuroloop +``` + +## Extensions + +Located under `extensions/`: + +- **`extensions/vscode/`** — VS Code / VSCodium / Cursor extension (separate + repo, included as a git submodule). Tracks 50+ event types and renders the brain + dashboard in the activity bar. Use `git submodule update --init --recursive` after + cloning, or run `npm run pull`. +- **`extensions/browser/`** — browser extension. +- **`extensions/widgets/`** — macOS WidgetKit extension (built/tested via the + `widgets` test suite on macOS). + +## Packaging + +Cross-platform release builds are produced via `scripts/tauri-build.js`: + +```bash +# macOS +npm run tauri:build:mac:app # .app bundle +npm run tauri:build:mac:dmg # .app + .dmg + +# Linux (native arch) — daemon built with `--features linux` +npm run tauri:build:linux:arm64 # aarch64 AppImage +npm run tauri:build:linux:x64:native # x86_64 AppImage +npm run package:linux:portable # Portable tarball +npm run package:linux:system:arm64:native # .deb / .rpm +npm run package:linux:system:x64:native # .deb / .rpm + +# Windows — daemon built with `--features windows` +npm run tauri:build:win:nsis # NSIS installer (.exe) + +# Homebrew cask (post-release) +npm run brew:cask:generate +``` + +## Encoding + +- All source files and CI artifacts use **UTF-8 without BOM**. +- No literal non-ASCII in CI scripts — use language escapes instead. +- See `AGENTS.md` § "CI / Shared Artifact Encoding Rule" for details. diff --git a/src-tauri/Cargo.lock b/Cargo.lock similarity index 55% rename from src-tauri/Cargo.lock rename to Cargo.lock index 1d66c1b8d..947d6e01d 100644 --- a/src-tauri/Cargo.lock +++ b/Cargo.lock @@ -3,13 +3,10 @@ version = 4 [[package]] -name = "addr2line" -version = "0.25.1" +name = "accelerate-src" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" -dependencies = [ - "gimli", -] +checksum = "415ed64958754dbe991900f3940677e6a7eefb4d7367afd70d642677b0c7d19d" [[package]] name = "adler2" @@ -17,6 +14,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + [[package]] name = "aes" version = "0.8.4" @@ -24,10 +31,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures 0.2.17", ] +[[package]] +name = "aes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +dependencies = [ + "cipher 0.5.2", + "cpubits", + "cpufeatures 0.3.0", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes 0.8.4", + "cipher 0.4.4", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.8.12" @@ -35,6 +67,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", + "const-random", "getrandom 0.3.4", "once_cell", "serde", @@ -51,24 +84,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "aligned" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" -dependencies = [ - "as-slice", -] - -[[package]] -name = "aligned-vec" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" -dependencies = [ - "equator", -] - [[package]] name = "alloc-no-stdlib" version = "2.0.4" @@ -77,13 +92,22 @@ checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -97,7 +121,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d" dependencies = [ "alsa-sys", - "bitflags 2.11.0", + "bitflags 2.13.1", "cfg-if", "libc", ] @@ -121,11 +145,27 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "annotate-snippets" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" +dependencies = [ + "anstyle", + "unicode-width 0.2.2", +] + [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -138,15 +178,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -157,7 +197,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -168,14 +208,48 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", +] + +[[package]] +name = "antneuro" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "255a0f2697ab4a54a6a014bfb84c9113d7fb5c23a4bc50ed033728722f83d6e0" +dependencies = [ + "anyhow", + "chrono", + "csv", + "env_logger", + "log", + "rusb", + "thiserror 2.0.19", + "tokio", ] [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ar_archive_writer" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "4087686b4b0a3427190bae57a1d9a478dbb2d40c5dc1bd6e2b6d797913bdd348" +dependencies = [ + "object 0.37.3", +] [[package]] name = "arbitrary" @@ -187,31 +261,109 @@ dependencies = [ ] [[package]] -name = "arg_enum_proc_macro" -version = "0.3.4" +name = "arc-swap" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "rustversion", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] -name = "as-slice" -version = "0.2.1" +name = "arrow-array" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +checksum = "ae33dad492b7df00a217563a7b0ef2874df68a0deea1b1a3acf628152f7f7a69" dependencies = [ - "stable_deref_trait", + "ahash", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "half", + "hashbrown 0.17.1", + "num-complex", + "num-integer", + "num-traits", +] + +[[package]] +name = "arrow-buffer" +version = "58.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9552f96391c005e6ab449fa941420935e7e062489b12b8b1b08879b2163f5b5" +dependencies = [ + "bytes", + "half", + "num-bigint", + "num-traits", +] + +[[package]] +name = "arrow-data" +version = "58.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b24852db04738907e06c04ea61e42fe7fda962a34513022dc0d0e754fb7976b" +dependencies = [ + "arrow-buffer", + "arrow-schema", + "half", + "num-integer", + "num-traits", +] + +[[package]] +name = "arrow-ipc" +version = "58.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29a908a11fcfb3fb2f6730f4ac15e367bc644e419155e96238f68cf3adde572b" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "flatbuffers", +] + +[[package]] +name = "arrow-schema" +version = "58.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21ca356ad6425cecb6eb7b28e4f659f1ee7880fbb1a16127de7dd62901efee9e" + +[[package]] +name = "arrow-select" +version = "58.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58da39eb3d8350ad4a549e5c2bc49284dac554016c69829310350f1731b0aad" +dependencies = [ + "ahash", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "num-traits", ] +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + [[package]] name = "ash" version = "0.38.0+1.3.281" @@ -314,14 +466,14 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "async-signal" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" dependencies = [ "async-io", "async-lock", @@ -335,6 +487,28 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "async-task" version = "4.7.1" @@ -343,13 +517,24 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", +] + +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version", ] [[package]] @@ -372,75 +557,67 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "atomic_float" -version = "1.1.0" +name = "atomic" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", +] [[package]] -name = "autocfg" -version = "1.5.0" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "av-scenechange" -version = "0.14.1" +name = "attohttpc" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" dependencies = [ - "aligned", - "anyhow", - "arg_enum_proc_macro", - "arrayvec", + "base64 0.22.1", + "http", "log", - "num-rational", - "num-traits", - "pastey", - "rayon", - "thiserror 2.0.18", - "v_frame", - "y4m", + "url", ] [[package]] -name = "av1-grain" -version = "0.2.5" +name = "autocfg" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" -dependencies = [ - "anyhow", - "arrayvec", - "log", - "nom 8.0.0", - "num-rational", - "v_frame", -] +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] -name = "avif-serialize" -version = "0.8.8" +name = "awear" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375082f007bd67184fb9c0374614b29f9aaa604ec301635f72338bb65386a53d" +checksum = "11ad906bc59e0a2c78a3f671425351166b05040faea432178e37ca0ca81bccda" dependencies = [ - "arrayvec", + "anyhow", + "btleplug", + "env_logger", + "futures", + "hex", + "hmac", + "log", + "serde", + "serde_json", + "sha2 0.10.9", + "tokio", + "uuid", ] [[package]] name = "axum" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", "base64 0.22.1", @@ -465,7 +642,7 @@ dependencies = [ "sha1", "sync_wrapper", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.29.0", "tower", "tower-layer", "tower-service", @@ -492,20 +669,28 @@ dependencies = [ ] [[package]] -name = "backtrace" -version = "0.3.76" +name = "backon" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-link 0.2.1", + "fastrand", + "gloo-timers", + "tokio", ] +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + +[[package]] +name = "base32" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" + [[package]] name = "base64" version = "0.13.1" @@ -530,23 +715,29 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + [[package]] name = "bincode" -version = "2.0.1" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ "serde", - "unty", ] [[package]] name = "bindgen" -version = "0.71.1" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.11.0", + "annotate-snippets", + "bitflags 2.13.1", "cexpr", "clang-sys", "itertools 0.13.0", @@ -555,29 +746,18 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash 2.1.1", - "shlex", - "syn 2.0.117", + "rustc-hash 2.1.3", + "shlex 1.3.0", + "syn 2.0.119", ] [[package]] -name = "bindgen" -version = "0.72.1" +name = "bit-set" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ - "bitflags 2.11.0", - "cexpr", - "clang-sys", - "itertools 0.13.0", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash 2.1.1", - "shlex", - "syn 2.0.117", + "bit-vec 0.6.3", ] [[package]] @@ -586,20 +766,35 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec", + "bit-vec 0.8.0", ] [[package]] -name = "bit-vec" -version = "0.8.0" +name = "bit-set" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2f926cc3060f09db9ebc5b52823d85268d24bb917e472c0c4bea35780a7d" +dependencies = [ + "bit-vec 0.9.1", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bit-vec" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] -name = "bit_field" -version = "0.10.3" +name = "bit-vec" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" [[package]] name = "bitflags" @@ -609,27 +804,36 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" dependencies = [ "serde_core", ] [[package]] -name = "bitstream-io" -version = "4.9.0" +name = "blake3" +version = "1.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" dependencies = [ - "core2", + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq 0.4.2", + "cpufeatures 0.3.0", ] [[package]] -name = "block" -version = "0.1.6" +name = "blas-src" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +checksum = "b95e83dc868db96e69795c0213143095f03de9dd3252f205d4ac716e4076a7e0" +dependencies = [ + "accelerate-src", + "openblas-src", +] [[package]] name = "block-buffer" @@ -641,12 +845,21 @@ dependencies = [ ] [[package]] -name = "block2" -version = "0.5.1" +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" dependencies = [ - "objc2 0.5.2", + "generic-array", ] [[package]] @@ -655,7 +868,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" dependencies = [ - "objc2 0.6.4", + "objc2", ] [[package]] @@ -672,39 +885,84 @@ dependencies = [ ] [[package]] -name = "bluez-async" -version = "0.8.2" +name = "bluer" +version = "0.17.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84ae4213cc2a8dc663acecac67bbdad05142be4d8ef372b6903abf878b0c690a" +checksum = "af68112f5c60196495c8b0eea68349817855f565df5b04b2477916d09fb1a901" dependencies = [ - "bitflags 2.11.0", - "bluez-generated", + "custom_debug", "dbus", + "dbus-crossroads", "dbus-tokio", + "displaydoc", "futures", - "itertools 0.14.0", + "hex", + "lazy_static", + "libc", "log", + "macaddr", + "nix 0.29.0", + "num-derive", + "num-traits", + "pin-project", "serde", - "serde-xml-rs", - "thiserror 2.0.18", + "serde_json", + "strum 0.26.3", "tokio", + "tokio-stream", "uuid", ] [[package]] -name = "bluez-generated" -version = "0.4.0" +name = "borrow-or-share" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" + +[[package]] +name = "brainbit" +version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9676783265eadd6f11829982792c6f303f3854d014edfba384685dcf237dd062" +checksum = "bc1683e979143ca36761fe3c0bd473c5a4b468c135318ea6556fbcdf5823dfac" dependencies = [ - "dbus", + "ctrlc", + "env_logger", + "libc", + "libloading 0.8.9", + "log", + "thiserror 2.0.19", +] + +[[package]] +name = "brainmaster" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbff1286420780dbf5480466aa922d838fbc496edfcbc6a0057cd10825ebdedf" +dependencies = [ + "log", + "serde", + "serde_json", + "serialport", + "thiserror 2.0.19", +] + +[[package]] +name = "brainvision" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73a1f095a9006f4c671af5eb6ac11c5f1e50326fa01ddedde027b1b4692aa46c" +dependencies = [ + "env_logger", + "libc", + "log", + "thiserror 2.0.19", ] [[package]] name = "brotli" -version = "8.0.2" +version = "8.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -713,950 +971,504 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "5.0.0" +version = "5.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bstr" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530" dependencies = [ "memchr", "regex-automata", - "serde", + "serde_core", ] [[package]] name = "btleplug" -version = "0.11.8" -source = "git+https://github.com/eugenehp/btleplug.git?branch=imrpoved_mac_version#c6055fe0e7b40c771a8b6d6c7548f00276a41d36" +version = "0.11.9" dependencies = [ "async-trait", - "bitflags 2.11.0", - "bluez-async", - "dashmap 6.1.0", - "dbus", + "bitflags 2.13.1", + "dashmap", "futures", - "jni 0.19.0", - "jni-utils", "log", - "objc2 0.5.2", - "objc2-core-bluetooth", - "objc2-foundation 0.2.2", - "once_cell", - "static_assertions", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "tokio-stream", "uuid", - "windows 0.61.3", - "windows-future 0.2.1", + "webbluetooth", ] [[package]] -name = "built" -version = "0.8.0" +name = "bumpalo" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] -name = "bumpalo" -version = "3.20.2" +name = "by_address" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] -name = "burn" -version = "0.20.1" +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + +[[package]] +name = "bytemuck" +version = "1.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b78ff10ed98b73e1d477ea6e6e1ec1b9cf9f71a17afc3fea9f4dca482d43dcd4" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" dependencies = [ - "burn-autodiff", - "burn-candle", - "burn-collective", - "burn-core", - "burn-cpu", - "burn-cuda", - "burn-ndarray", - "burn-nn", - "burn-optim", - "burn-remote", - "burn-rocm", - "burn-router", - "burn-store", - "burn-tch", - "burn-train", - "burn-wgpu", + "bytemuck_derive", ] [[package]] -name = "burn-autodiff" -version = "0.20.1" +name = "bytemuck_derive" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2f04955e9b4acd5e6a6229f80217dae79742975a97dc2253003f226333ad307" +checksum = "f65693059b6b9c588b9f62fed1cedbf0a8b805631457ea162d68f0de186f3de5" dependencies = [ - "burn-backend", - "burn-std", - "derive-new", - "hashbrown 0.16.1", - "log", - "num-traits", - "parking_lot", - "portable-atomic", - "spin 0.10.0", - "tracing", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "burn-backend" -version = "0.20.1" +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a724a5d8d5865a1f6b304f629eb19f51489760689501c583b3e1f4209f067357" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" dependencies = [ - "burn-std", - "bytemuck", - "cubecl", - "derive-new", - "hashbrown 0.16.1", - "num-traits", - "rand 0.9.2", - "rand_distr", "serde", - "thiserror 2.0.18", ] [[package]] -name = "burn-candle" -version = "0.20.1" +name = "bzip2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21c752d5008923eb9299783da5edae3242b94afdb956e88d2b37b025244b5071" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" dependencies = [ - "burn-backend", - "burn-std", - "candle-core", - "derive-new", + "bzip2-sys", + "libc", ] [[package]] -name = "burn-collective" -version = "0.20.1" +name = "bzip2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5b2199984291a0f3828d5ac04039fb864c56c3bdd9ee4172a63e457b668e4c" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" dependencies = [ - "burn-communication", - "burn-std", - "burn-tensor", - "bytes", - "futures", - "log", - "rmp-serde", - "serde", - "tokio", - "tokio-util", + "libbz2-rs-sys", ] [[package]] -name = "burn-communication" -version = "0.20.1" +name = "bzip2-sys" +version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585526cd672adc35918d8aea9bcb50669067904f36090caaffa9573d22fc7ade" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ - "axum", - "burn-std", - "burn-tensor", - "bytes", - "derive-new", - "futures", - "futures-util", - "log", - "rmp-serde", - "serde", - "serde_bytes", - "tokio", - "tokio-tungstenite", - "tokio-util", - "tracing", - "tracing-core", - "tracing-subscriber", + "cc", + "pkg-config", ] [[package]] -name = "burn-core" -version = "0.20.1" +name = "cairo-rs" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3634c3ba84397bcf2977ce746954d7e0a40e2d862e92362dd694c29e18df62" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "ahash", - "bincode", - "burn-dataset", - "burn-derive", - "burn-std", - "burn-tensor", - "data-encoding", - "derive-new", - "flate2", - "half", - "hashbrown 0.16.1", - "log", - "num-traits", - "portable-atomic", - "portable-atomic-util", - "rand 0.9.2", - "regex", - "rmp-serde", - "serde", - "serde_json", - "spin 0.10.0", - "uuid", + "bitflags 2.13.1", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", ] [[package]] -name = "burn-cpu" -version = "0.20.1" +name = "cairo-sys-rs" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60aa53c4536719f1c91c250d4b4348daca473c44cf0c45b81096785f5510c192" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" dependencies = [ - "burn-backend", - "burn-cubecl", - "burn-fusion", - "cubecl", + "glib-sys", + "libc", + "system-deps 6.2.2", ] [[package]] -name = "burn-cubecl" -version = "0.20.1" +name = "camino" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6d13aff03fec966da4300459688883f8a1d741dddbf19d1bfc2562656a9a9b" +checksum = "5f2d30e4173c4026932d51d31d6b0613b1fd3014bf3f9f8943d4ba139c437ba0" dependencies = [ - "burn-backend", - "burn-cubecl-fusion", - "burn-fusion", - "burn-ir", - "burn-std", - "cubecl", - "cubek", - "derive-new", - "futures-lite", - "log", - "serde", - "text_placeholder", + "serde_core", ] [[package]] -name = "burn-cubecl-fusion" -version = "0.20.1" +name = "cargo-platform" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17d25b2e9fb805931401f79782aabd92462d65e60bc207035a3e554de8d7cd9f" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ - "burn-backend", - "burn-fusion", - "burn-ir", - "burn-std", - "cubecl", - "cubek", - "derive-new", "serde", ] [[package]] -name = "burn-cuda" -version = "0.20.1" +name = "cargo_metadata" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d0c68cf653eb9c27dcbe046bb7b04cc18c6b33afda4c09317c102e6f4ae7cb6" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ - "burn-backend", - "burn-cubecl", - "burn-fusion", - "cubecl", + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.19", ] [[package]] -name = "burn-dataset" -version = "0.20.1" +name = "cargo_toml" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e87741e2ff9015845ed2b41b47f9e82795cf274bf2328a29619a2e6f662495c" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" dependencies = [ - "csv", - "derive-new", - "dirs", - "gix-tempfile", - "image 0.25.9", - "r2d2", - "r2d2_sqlite", - "rand 0.9.2", - "rmp-serde", - "rusqlite", - "sanitize-filename", "serde", - "serde_json", - "serde_rusqlite", - "strum 0.27.2", - "tempfile", - "thiserror 2.0.18", + "toml 0.9.12+spec-1.1.0", ] [[package]] -name = "burn-derive" -version = "0.20.1" +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "castaway" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "102d7e2f705b0cda2f89dd0e55e9bbfc6184029929d53487beb606c3303b29a5" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ - "derive-new", - "proc-macro2", - "quote", - "syn 2.0.117", + "rustversion", ] [[package]] -name = "burn-fusion" -version = "0.20.1" +name = "cbc" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea83d7f8574bcc07967291c5bb679ddc0a655c8db0642eca62755e2fffc8047" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" dependencies = [ - "burn-backend", - "burn-ir", - "derive-new", - "hashbrown 0.16.1", - "log", - "serde", - "spin 0.10.0", - "tracing", + "cipher 0.4.4", ] [[package]] -name = "burn-ir" -version = "0.20.1" +name = "cblas-sys" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd2b1b37a7289bd85438800deaaebde50507336429b80f96a71730794db5bc31" +checksum = "b6feecd82cce51b0204cf063f0041d69f24ce83f680d87514b004248e7b0fa65" dependencies = [ - "burn-backend", - "hashbrown 0.16.1", - "serde", + "libc", ] [[package]] -name = "burn-ndarray" -version = "0.20.1" +name = "cc" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96be578991cecef163e41a73bf985d8d7eb7fb8ef7bececf8d48523c481ecddf" +checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" dependencies = [ - "atomic_float", - "burn-autodiff", - "burn-backend", - "burn-ir", - "burn-std", - "bytemuck", - "const-random", - "itertools 0.14.0", - "libm", - "macerator", - "matrixmultiply", - "ndarray 0.17.2", - "num-traits", - "paste", - "portable-atomic", - "portable-atomic-util", - "rand 0.9.2", - "rayon", - "seq-macro", + "find-msvc-tools", + "jobserver", + "libc", + "shlex 2.0.1", ] [[package]] -name = "burn-nn" -version = "0.20.1" +name = "cesu8" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b8c6c14b94e5b1dddd68f8e6d669f20bac8f99fcb2e4f1a480212d1b598133" -dependencies = [ - "burn-core", - "num-traits", -] +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] -name = "burn-optim" -version = "0.20.1" +name = "cexpr" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a8c376d835d92ea363c05c6f48ac19bb687b683c7958c310a716ef8d5d77ba3" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "burn-core", - "derive-new", - "hashbrown 0.16.1", - "log", - "num-traits", - "serde", + "nom 7.1.3", ] [[package]] -name = "burn-remote" -version = "0.20.1" +name = "cfb" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7238df1d59dcbb6880fe92ca0aa7b02b64d02394815e618da3d7933950bdaf39" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" dependencies = [ - "async-channel", - "axum", - "burn-communication", - "burn-ir", - "burn-router", - "burn-std", - "burn-tensor", - "bytes", - "derive-new", - "futures-util", - "log", - "rmp-serde", - "serde", - "serde_bytes", - "tokio", - "tokio-tungstenite", - "tokio-util", - "tracing-core", - "tracing-subscriber", + "byteorder", + "fnv", + "uuid", ] [[package]] -name = "burn-rocm" -version = "0.20.1" +name = "cfg-expr" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73e2abda6ee63bdcb730f1a335349a9ff83f03048130d405b6ecdccd2df3ff23" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ - "burn-backend", - "burn-cubecl", - "burn-fusion", - "cubecl", + "smallvec", + "target-lexicon 0.12.16", ] [[package]] -name = "burn-router" -version = "0.20.1" +name = "cfg-expr" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "823ccb88484736a2861d53dc7f67db375ef050b0446bb02dd7cb8783ac6b69a2" +checksum = "fb693542bcafa528e198be0ebd9d3632ca5b7c93dbe7237460e199910835997c" dependencies = [ - "burn-backend", - "burn-ir", - "burn-std", - "hashbrown 0.16.1", - "log", - "spin 0.10.0", + "smallvec", + "target-lexicon 0.13.5", ] [[package]] -name = "burn-std" -version = "0.20.1" +name = "cfg-if" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a9ed8e34a4a49d3754586f306075d6b55a5e08343ac75c06f47e7d9f825271" -dependencies = [ - "bytemuck", - "bytes", - "cubecl", - "cubecl-common", - "half", - "num-traits", - "serde", -] +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] -name = "burn-store" -version = "0.20.1" +name = "cfg_aliases" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be80a7b084a19901dc1d0a2e9b77e226c5c575879fe66de891c67062db41a6d" -dependencies = [ - "burn-core", - "burn-nn", - "burn-tensor", - "byteorder", - "bytes", - "half", - "hashbrown 0.16.1", - "memmap2", - "regex", - "safetensors 0.7.0", - "textdistance", -] +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] -name = "burn-tch" -version = "0.20.1" +name = "chacha20" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061cd3df7b4126949561185454b9b67623f6885657361cad2a07a2340b3b3108" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ - "burn-backend", - "cc", - "libc", - "log", - "tch", - "torch-sys", + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", ] [[package]] -name = "burn-tensor" -version = "0.20.1" +name = "chrono" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3720e52e00ed0155ced4f8681d0e8a362e699cee36494ec5b97ad44fcc5194c0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ - "burn-backend", - "burn-std", - "colored", - "derive-new", + "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", + "windows-link 0.2.1", ] [[package]] -name = "burn-train" -version = "0.20.1" +name = "chunked_transfer" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c3128c7571992c382a5ad057c72654c1048ea4dcf138d1f394313047e69803a" -dependencies = [ - "async-channel", - "burn-core", - "burn-ndarray", - "burn-optim", - "derive-new", - "log", - "nvml-wrapper", - "ratatui", - "rstest", - "serde", - "sysinfo 0.37.2", - "systemstat", - "thiserror 2.0.18", - "tracing-appender", - "tracing-core", - "tracing-subscriber", -] +checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" [[package]] -name = "burn-wgpu" -version = "0.20.1" +name = "ciborium" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df78d62afc9b9fbb8ee4e49b72006485bb64f778a790e185a2d919479bcfc008" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ - "burn-backend", - "burn-cubecl", - "burn-fusion", - "cubecl", + "ciborium-io", + "ciborium-ll", + "serde", ] [[package]] -name = "bytemuck" -version = "1.25.0" +name = "ciborium-io" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" -dependencies = [ - "bytemuck_derive", -] +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] -name = "bytemuck_derive" -version = "1.10.2" +name = "ciborium-ll" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "ciborium-io", + "half", ] [[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - -[[package]] -name = "bytes" -version = "1.11.1" +name = "cipher" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "portable-atomic", - "serde", + "crypto-common 0.1.7", + "inout 0.1.4", ] [[package]] -name = "bytesize" -version = "1.3.3" +name = "cipher" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "crypto-common 0.2.2", + "inout 0.2.2", +] [[package]] -name = "bzip2" -version = "0.4.4" +name = "clang-sys" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ - "bzip2-sys", + "glob", "libc", + "libloading 0.8.9", ] [[package]] -name = "bzip2-sys" -version = "0.1.13+1.0.8" +name = "clap" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" dependencies = [ - "cc", - "pkg-config", + "clap_builder", + "clap_derive", ] [[package]] -name = "cairo-rs" -version = "0.18.5" +name = "clap_builder" +version = "4.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" dependencies = [ - "bitflags 2.11.0", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", - "thiserror 1.0.69", + "anstream", + "anstyle", + "clap_lex", + "strsim", ] [[package]] -name = "cairo-sys-rs" -version = "0.18.2" +name = "clap_derive" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" dependencies = [ - "glib-sys", - "libc", - "system-deps", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 3.0.3", ] [[package]] -name = "camino" -version = "1.2.2" +name = "clap_lex" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" -dependencies = [ - "serde_core", -] +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] -name = "candle-core" -version = "0.9.2" +name = "cmake" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c15b675b80d994b2eadb20a4bbe434eabeb454eac3ee5e2b4cf6f147ee9be091" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ - "byteorder", - "candle-metal-kernels", - "candle-ug", - "float8 0.6.1", - "gemm 0.19.0", - "half", - "libm", - "memmap2", - "num-traits", - "num_cpus", - "objc2-foundation 0.3.2", - "objc2-metal", - "rand 0.9.2", - "rand_distr", - "rayon", - "safetensors 0.7.0", - "thiserror 2.0.18", - "yoke 0.8.1", - "zip 7.2.0", + "cc", ] [[package]] -name = "candle-metal-kernels" -version = "0.9.2" +name = "cmov" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fdfe9d06de16ce49961e49084e5b79a75a9bdf157246e7c7b6328e87a7aa25d" -dependencies = [ - "half", - "objc2 0.6.4", - "objc2-foundation 0.3.2", - "objc2-metal", - "once_cell", - "thiserror 2.0.18", - "tracing", -] +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" [[package]] -name = "candle-nn" -version = "0.9.2" +name = "cobs" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3045fa9e7aef8567d209a27d56b692f60b96f4d0569f4c3011f8ca6715c65e03" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "candle-core", - "candle-metal-kernels", - "half", - "libc", - "num-traits", - "objc2-metal", - "rayon", - "safetensors 0.7.0", - "serde", - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] -name = "candle-ug" -version = "0.9.2" +name = "codespan-reporting" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22d62be69068bf58987a45f690612739d8d2ea1bf508c1b87dc6815a019575d" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" dependencies = [ - "ug", - "ug-metal", + "serde", + "termcolor", + "unicode-width 0.2.2", ] [[package]] -name = "cargo-platform" -version = "0.1.9" +name = "coe-rs" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] +checksum = "7e8f1e641542c07631228b1e0dc04b69ae3c1d58ef65d5691a439711d805c698" [[package]] -name = "cargo_metadata" -version = "0.19.2" +name = "cognionics" +version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +checksum = "13152f74c48f78489423bb0d6bbe204e4ddc50e208c8e39db014ba69726b5b21" dependencies = [ - "camino", - "cargo-platform", - "semver", + "anyhow", + "env_logger", + "log", "serde", "serde_json", - "thiserror 2.0.18", + "serialport", + "tokio", ] [[package]] -name = "cargo_toml" -version = "0.22.3" +name = "color_quant" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" -dependencies = [ - "serde", - "toml 0.9.12+spec-1.1.0", -] +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] -name = "caseless" -version = "0.2.2" +name = "colorchoice" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6fd507454086c8edfd769ca6ada439193cdb209c7681712ef6275cccbfe5d8" -dependencies = [ - "unicode-normalization", -] - -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - -[[package]] -name = "castaway" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" -dependencies = [ - "rustversion", -] - -[[package]] -name = "cc" -version = "1.2.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom 7.1.3", -] - -[[package]] -name = "cfb" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid", -] - -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chacha20" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "rand_core 0.10.0", -] - -[[package]] -name = "chrono" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-link 0.2.1", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading 0.8.9", -] - -[[package]] -name = "clap" -version = "4.5.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "clap_lex" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" - -[[package]] -name = "cmake" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" -dependencies = [ - "cc", -] - -[[package]] -name = "codespan-reporting" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" -dependencies = [ - "serde", - "termcolor", - "unicode-width 0.2.0", -] - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - -[[package]] -name = "colored" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" -dependencies = [ - "windows-sys 0.61.2", -] +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "combine" @@ -1670,23 +1482,9 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "rustversion", - "ryu", - "static_assertions", -] - -[[package]] -name = "compact_str" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" dependencies = [ "castaway", "cfg-if", @@ -1697,20 +1495,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "comrak" -version = "0.39.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fefab951771fc3beeed0773ce66a4f7b706273fc6c4c95b08dd1615744abcf5" -dependencies = [ - "caseless", - "entities", - "memchr", - "slug", - "typed-arena", - "unicode_categories", -] - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1722,29 +1506,21 @@ dependencies = [ [[package]] name = "console" -version = "0.15.11" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c" dependencies = [ "encode_unicode", "libc", - "once_cell", - "unicode-width 0.2.0", - "windows-sys 0.59.0", + "unicode-width 0.2.2", + "windows-sys 0.61.2", ] [[package]] -name = "console" -version = "0.16.2" +name = "const-oid" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" -dependencies = [ - "encode_unicode", - "libc", - "once_cell", - "unicode-width 0.2.0", - "windows-sys 0.61.2", -] +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] name = "const-random" @@ -1773,25 +1549,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] -name = "constcat" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d3e02915a2cea4d74caa8681e2d44b1c3254bdbf17d11d41d587ff858832c" - -[[package]] -name = "convert_case" -version = "0.4.0" +name = "constant_time_eq" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] -name = "convert_case" -version = "0.8.0" +name = "constant_time_eq" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" -dependencies = [ - "unicode-segmentation", -] +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "convert_case" @@ -1813,6 +1580,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "cookie-factory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" + [[package]] name = "cookie_store" version = "0.22.1" @@ -1822,7 +1595,7 @@ dependencies = [ "cookie", "document-features", "idna", - "indexmap 2.13.0", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -1831,6 +1604,16 @@ dependencies = [ "url", ] +[[package]] +name = "cordyceps" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" +dependencies = [ + "loom", + "tracing", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -1857,82 +1640,37 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core-graphics" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "core-graphics-types 0.1.3", - "foreign-types 0.5.0", - "libc", -] - [[package]] name = "core-graphics" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "core-foundation 0.10.1", - "core-graphics-types 0.2.0", + "core-graphics-types", "foreign-types 0.5.0", "libc", ] -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "libc", -] - [[package]] name = "core-graphics-types" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "core-foundation 0.10.1", "libc", ] [[package]] -name = "core-text" -version = "20.1.0" +name = "coreaudio-rs" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" +checksum = "7d5d7dca3ebcf65a035582c9ad4385371a9d9ee6537474d2a278f4e1e475bb58" dependencies = [ - "core-foundation 0.9.4", - "core-graphics 0.23.2", - "foreign-types 0.5.0", - "libc", -] - -[[package]] -name = "core2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" -dependencies = [ - "memchr", -] - -[[package]] -name = "coreaudio-rs" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15c3c3cee7c087938f7ad1c3098840b3ef1f1bdc7f6e496336c3b1e7a6f3914" -dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "libc", "objc2-audio-toolbox", "objc2-core-audio", @@ -1957,19 +1695,25 @@ dependencies = [ "ndk-context", "num-derive", "num-traits", - "objc2 0.6.4", + "objc2", "objc2-audio-toolbox", "objc2-avf-audio", "objc2-core-audio", "objc2-core-audio-types", "objc2-core-foundation", - "objc2-foundation 0.3.2", + "objc2-foundation", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows 0.62.2", + "windows 0.61.3", ] +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1997,6 +1741,41 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +dependencies = [ + "cast", + "itertools 0.13.0", +] + [[package]] name = "critical-section" version = "1.2.0" @@ -2005,18 +1784,18 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] name = "crossbeam-channel" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -2024,31 +1803,33 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crossterm" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "crossterm_winapi", + "derive_more", + "document-features", "mio", "parking_lot", - "rustix 0.38.44", - "signal-hook 0.3.18", + "rustix 1.1.4", + "signal-hook", "signal-hook-mio", "winapi", ] @@ -2078,21 +1859,37 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", + "rand_core 0.10.1", +] + +[[package]] +name = "csscolorparser" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" +dependencies = [ + "lab", + "phf 0.11.3", +] + [[package]] name = "cssparser" -version = "0.29.6" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" dependencies = [ "cssparser-macros", "dtoa-short", "itoa", - "matches", - "phf 0.10.1", - "proc-macro2", - "quote", + "phf 0.13.1", "smallvec", - "syn 1.0.109", ] [[package]] @@ -2102,7 +1899,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -2128,12 +1925,27 @@ dependencies = [ [[package]] name = "ctor" -version = "0.2.9" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" dependencies = [ - "quote", - "syn 2.0.117", + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher 0.4.4", ] [[package]] @@ -2143,615 +1955,310 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" dependencies = [ "dispatch2", - "nix 0.31.2", + "nix 0.31.3", "windows-sys 0.61.2", ] [[package]] -name = "cubecl" -version = "0.9.0" +name = "ctutils" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053856efd5436224775b9423d43d86f53d5b1d3af9a6b9983d9a313a0922638f" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" dependencies = [ - "cubecl-core", - "cubecl-cpu", - "cubecl-cuda", - "cubecl-hip", - "cubecl-ir", - "cubecl-runtime", - "cubecl-std", - "cubecl-wgpu", - "half", + "cmov", ] [[package]] -name = "cubecl-common" -version = "0.9.0" +name = "cudarc" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60bf8aaeb572c8cf2f2ffd07fa9bb1a2cf9336d1aa11ecd4d9a2f2e30c4be706" +checksum = "42310153e06cf4cd532901f7096beb27504d681736a29ee90728ae4e2d93b2a8" dependencies = [ - "backtrace", - "bytemuck", - "bytes", - "cfg-if", - "cfg_aliases", - "derive-new", - "derive_more 2.1.1", - "dirs", - "embassy-futures", - "embassy-time", - "float4", - "float8 0.4.2", - "futures-lite", - "half", - "hashbrown 0.15.5", - "log", - "num-traits", - "parking_lot", - "portable-atomic", - "portable-atomic-util", - "rand 0.9.2", - "sanitize-filename", - "serde", - "serde_bytes", - "serde_json", - "spin 0.10.0", - "tracing", - "wasm-bindgen-futures", - "web-time", + "libloading 0.9.0", ] [[package]] -name = "cubecl-core" -version = "0.9.0" +name = "curve25519-dalek" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98374a31d2b68b55709891169832ccf205408c201c5e023964482441f213d0b9" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" dependencies = [ - "bitflags 2.11.0", - "bytemuck", - "cubecl-common", - "cubecl-ir", - "cubecl-macros", - "cubecl-runtime", - "derive-new", - "derive_more 2.1.1", - "enumset", - "float-ord", - "half", - "hashbrown 0.15.5", - "log", - "num-traits", - "paste", + "cfg-if", + "cpufeatures 0.3.0", + "curve25519-dalek-derive", + "digest 0.11.3", + "fiat-crypto", + "rand_core 0.10.1", + "rustc_version", "serde", - "serde_json", - "tracing", - "variadics_please", + "subtle", + "zeroize", ] [[package]] -name = "cubecl-cpp" -version = "0.9.0" +name = "curve25519-dalek-derive" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb24d96c1ff84ab4def0a529e384311a15cb771310aaf2b640c312384c3bca23" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ - "bytemuck", - "cubecl-common", - "cubecl-core", - "cubecl-opt", - "cubecl-runtime", - "derive-new", - "half", - "itertools 0.14.0", - "log", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "cubecl-cpu" -version = "0.9.0" +name = "custom_debug" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152588a6e16b6bda5e8216af7a6fad3d7de4697294b6ce0f6acbe3a9029ff674" +checksum = "2da7d1ad9567b3e11e877f1d7a0fa0360f04162f94965fc4448fbed41a65298e" dependencies = [ - "bytemuck", - "cubecl-common", - "cubecl-core", - "cubecl-opt", - "cubecl-runtime", - "cubecl-std", - "derive-new", - "half", - "log", - "paste", - "serde", - "sysinfo 0.36.1", - "tracel-llvm", - "tracel-llvm-bundler", + "custom_debug_derive", ] [[package]] -name = "cubecl-cuda" -version = "0.9.0" +name = "custom_debug_derive" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f74a5750c45090d1fc5ddf6a19fea9a099aa1f6800b78f1167a2d60182d1d96" +checksum = "a707ceda8652f6c7624f2be725652e9524c815bf3b9d55a0b2320be2303f9c11" dependencies = [ - "bytemuck", - "cubecl-common", - "cubecl-core", - "cubecl-cpp", - "cubecl-runtime", - "cubecl-zspace", - "cudarc", - "derive-new", - "half", - "log", - "serde", - "tracing", + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", ] [[package]] -name = "cubecl-hip" -version = "0.9.0" +name = "darling" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbae9bc7ee6093d0d7a549c05873dff3478f9087b59eb09b223a97d642c849aa" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "bytemuck", - "cubecl-common", - "cubecl-core", - "cubecl-cpp", - "cubecl-hip-sys", - "cubecl-runtime", - "cubecl-zspace", - "derive-new", - "half", - "log", - "paste", - "serde", - "tracing", + "darling_core 0.20.11", + "darling_macro 0.20.11", ] [[package]] -name = "cubecl-hip-sys" -version = "7.1.5280200" +name = "darling" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcdd98f72d6f17836a0477bcd5ae5dd6b57a80fb62a3c0919f867a231f897f28" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "libc", - "regex", + "darling_core 0.23.0", + "darling_macro 0.23.0", ] [[package]] -name = "cubecl-ir" -version = "0.9.0" +name = "darling_core" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "361b608ff9f05024c7a7e381852689acd95b6af5af956d68734692b27d5f75ef" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ - "cubecl-common", - "cubecl-macros-internal", - "derive-new", - "derive_more 2.1.1", - "enumset", - "float-ord", "fnv", - "half", - "hashbrown 0.15.5", - "num-traits", - "portable-atomic", - "serde", - "variadics_please", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", ] [[package]] -name = "cubecl-macros" -version = "0.9.0" +name = "darling_core" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9a872d16207c6a27ed45942fd311a281394dd384b14a21f72131db1556a977" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "cubecl-common", - "darling 0.21.3", - "derive-new", "ident_case", - "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", + "strsim", + "syn 2.0.119", ] [[package]] -name = "cubecl-macros-internal" -version = "0.9.0" +name = "darling_macro" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa3fa0626cdf28b9c49084c2bb51493bfde44378e22d90624aacaafb81da3588" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling 0.21.3", - "proc-macro2", + "darling_core 0.20.11", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] -name = "cubecl-opt" -version = "0.9.0" +name = "darling_macro" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdcff25fdcbd82ea4277c30a81e162722859f57c6ae105c0a3c53f8bb91154f6" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ - "cubecl-common", - "cubecl-core", - "cubecl-ir", - "float-ord", - "log", - "num", - "petgraph", - "smallvec", - "stable-vec", - "type-map", + "darling_core 0.23.0", + "quote", + "syn 2.0.119", ] [[package]] -name = "cubecl-runtime" -version = "0.9.0" +name = "dary_heap" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b02e28997a8d75311afae4d2cea7b593eb125312f845874118a59d78c7a6b34c" +checksum = "8b1e3a325bc115f096c8b77bbf027a7c2592230e70be2d985be950d3d5e60ebe" dependencies = [ - "async-channel", - "bytemuck", - "cfg-if", - "cfg_aliases", - "cubecl-common", - "cubecl-ir", - "derive-new", - "derive_more 2.1.1", - "dirs", - "enumset", - "foldhash 0.1.5", - "hashbrown 0.15.5", - "log", - "md5", "serde", - "serde_json", - "spin 0.10.0", - "thiserror 2.0.18", - "toml 0.9.12+spec-1.1.0", - "tracing", - "variadics_please", - "wasm-bindgen-futures", - "web-time", ] [[package]] -name = "cubecl-std" -version = "0.9.0" +name = "dashmap" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ff5741c98b7a7a5944b4afb0b67dd7f5e0be41ce7f303b587f8b0d6430b29b" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ - "cubecl-common", - "cubecl-core", - "cubecl-runtime", - "foldhash 0.1.5", - "half", - "num-traits", - "paste", - "serde", - "spin 0.10.0", - "variadics_please", + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", ] [[package]] -name = "cubecl-wgpu" -version = "0.9.0" +name = "dasp_sample" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29787364632fc7ec6a11cf3d95187f82f6fcce17d6bb4f0fb0dde580b837631d" -dependencies = [ - "async-channel", - "bytemuck", - "cfg-if", - "cfg_aliases", - "cubecl-common", - "cubecl-core", - "cubecl-ir", - "cubecl-runtime", - "derive-new", - "derive_more 2.1.1", - "half", - "hashbrown 0.15.5", - "log", - "sanitize-filename", - "tracing", - "wgpu", -] +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" [[package]] -name = "cubecl-zspace" -version = "0.9.0" +name = "data-encoding" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0f819071413b19a00b7105497e0f6d2cf3e7e9d65cbb8d4ecf1ddb29c61dc2" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] -name = "cubek" -version = "0.1.1" +name = "data-encoding-macro" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bb1cce47db02017925301bedec92ae84628493df3f9761ea7ac42a60c6146f8" +checksum = "3259c913752a86488b501ed8680446a5ed2d5aeac6e596cb23ba3800768ea32c" dependencies = [ - "cubecl", - "cubek-attention", - "cubek-convolution", - "cubek-matmul", - "cubek-quant", - "cubek-random", - "cubek-reduce", + "data-encoding", + "data-encoding-macro-internal", ] [[package]] -name = "cubek-attention" -version = "0.1.1" +name = "data-encoding-macro-internal" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7278bd122b2428af479f9af05285160613733c33c93b63ab3c6d25cd0460c18b" +checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" dependencies = [ - "bytemuck", - "cubecl", - "cubecl-common", - "cubek-matmul", - "cubek-random", - "half", - "serde", + "data-encoding", + "syn 1.0.109", ] [[package]] -name = "cubek-convolution" -version = "0.1.1" +name = "dbgf" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18eb04bca4ae104d62a56def04b04f3d079c42fe49aac62202c96876f90fa28b" -dependencies = [ - "bytemuck", - "cubecl", - "cubecl-common", - "cubek-matmul", - "derive-new", - "enumset", - "half", - "serde", -] +checksum = "e6ca96b45ca70b8045e0462f191bd209fcb3c3bfe8dbfb1257ada54c4dd59169" [[package]] -name = "cubek-matmul" -version = "0.1.1" -source = "git+https://github.com/eugenehp/cubek.git?branch=cubek-matmul#73052f085f8b625aa3ead838fcc701f87829166a" -dependencies = [ - "bytemuck", - "cubecl", - "cubecl-common", - "half", - "serde", -] - -[[package]] -name = "cubek-quant" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ec3ae04af324df2d615c2b394e270d58d6f08cb833d67633e2ba794de75916" -dependencies = [ - "cubecl", - "cubecl-common", - "half", - "serde", -] - -[[package]] -name = "cubek-random" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65a34844d8b7f739185c1d24896137dcb73f458830444103b45f678585ad983e" -dependencies = [ - "cubecl", - "cubecl-common", - "half", - "num-traits", - "rand 0.9.2", - "serde", -] - -[[package]] -name = "cubek-reduce" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42397d9ed85bb3084dfb56ed26de75690b5b07caf42a32f4006b57eb23d5b6d6" -dependencies = [ - "cubecl", - "half", - "num-traits", - "serde", - "thiserror 2.0.18", -] - -[[package]] -name = "cudarc" -version = "0.18.2" +name = "dbus" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aa12038120eb13347a6ae2ffab1d34efe78150125108627fd85044dd4d6ff1e" +checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" dependencies = [ - "libloading 0.8.9", + "futures-channel", + "futures-util", + "libc", + "libdbus-sys", + "windows-sys 0.61.2", ] [[package]] -name = "darling" -version = "0.20.11" +name = "dbus-crossroads" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +checksum = "64bff0bd181fba667660276c6b7ebdc50cff37ce593e7adf9e734f89c8f444e8" dependencies = [ - "darling_core 0.20.11", - "darling_macro 0.20.11", + "dbus", ] [[package]] -name = "darling" -version = "0.21.3" +name = "dbus-secret-service" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6" dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", + "aes 0.8.4", + "block-padding", + "cbc", + "dbus", + "fastrand", + "hkdf", + "num", + "once_cell", + "sha2 0.10.9", + "zeroize", ] [[package]] -name = "darling" -version = "0.23.0" +name = "dbus-tokio" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +checksum = "007688d459bc677131c063a3a77fb899526e17b7980f390b69644bdbc41fad13" dependencies = [ - "darling_core 0.23.0", - "darling_macro 0.23.0", + "dbus", + "libc", + "tokio", ] [[package]] -name = "darling_core" -version = "0.20.11" +name = "deflate64" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.117", -] +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" [[package]] -name = "darling_core" -version = "0.21.3" +name = "defmt" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.117", + "bitflags 1.3.2", + "defmt-macros", ] [[package]] -name = "darling_core" -version = "0.23.0" +name = "defmt-macros" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" dependencies = [ - "ident_case", + "defmt-parser", "proc-macro2", "quote", - "strsim", - "syn 2.0.117", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core 0.20.11", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core 0.21.3", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "darling_macro" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" -dependencies = [ - "darling_core 0.23.0", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "dary_heap" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06d2e3287df1c007e74221c49ca10a95d557349e54b3a75dc2fb14712c751f04" -dependencies = [ - "serde", + "syn 2.0.119", ] [[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - -[[package]] -name = "dashmap" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - -[[package]] -name = "dasp_sample" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" - -[[package]] -name = "data-encoding" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" - -[[package]] -name = "dbus" -version = "0.9.10" +name = "defmt-parser" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b3aa68d7e7abee336255bd7248ea965cc393f3e70411135a6f6a4b651345d4" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "futures-channel", - "futures-util", - "libc", - "libdbus-sys", - "windows-sys 0.59.0", + "thiserror 2.0.19", ] [[package]] -name = "dbus-tokio" -version = "0.7.6" +name = "deltae" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "007688d459bc677131c063a3a77fb899526e17b7980f390b69644bdbc41fad13" -dependencies = [ - "dbus", - "libc", - "tokio", -] +checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" [[package]] name = "der" -version = "0.7.10" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" dependencies = [ + "const-oid", "pem-rfc7468", "zeroize", ] @@ -2762,21 +2269,9 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "powerfmt", "serde_core", ] -[[package]] -name = "derive-new" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cdc8d50f426189eef89dac62fabfa0abb27d5cc008f25bf4156a0203325becc" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "derive_arbitrary" version = "1.4.2" @@ -2785,7 +2280,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -2806,7 +2301,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -2816,20 +2311,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.117", -] - -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case 0.4.0", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -2847,19 +2329,19 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ - "convert_case 0.10.0", + "convert_case", "proc-macro2", "quote", "rustc_version", - "syn 2.0.117", + "syn 2.0.119", "unicode-xid", ] [[package]] -name = "deunicode" -version = "1.6.2" +name = "diatomic-waker" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" +checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" [[package]] name = "digest" @@ -2867,11 +2349,21 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", +] + [[package]] name = "dirs" version = "6.0.0" @@ -2900,7 +2392,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2920,21 +2412,21 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.0", - "block2 0.6.2", + "bitflags 2.13.1", + "block2", "libc", - "objc2 0.6.4", + "objc2", ] [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -2943,7 +2435,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" dependencies = [ - "libloading 0.8.9", + "libloading 0.7.4", ] [[package]] @@ -2966,7 +2458,7 @@ checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -2979,52 +2471,140 @@ dependencies = [ ] [[package]] -name = "dpi" -version = "0.1.2" +name = "dom_query" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" dependencies = [ - "serde", + "bit-set 0.8.0", + "cssparser", + "foldhash", + "html5ever", + "precomputed-hash", + "selectors", + "tendril", ] [[package]] -name = "dtoa" -version = "1.0.11" +name = "downcast-rs" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] -name = "dtoa-short" -version = "0.3.5" +name = "dpi" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" dependencies = [ - "dtoa", + "serde", ] [[package]] -name = "dunce" -version = "1.0.5" +name = "drm" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +checksum = "80bc8c5c6c2941f70a55c15f8d9f00f9710ebda3ffda98075f996a0e6c92756f" +dependencies = [ + "bitflags 2.13.1", + "bytemuck", + "drm-ffi", + "drm-fourcc", + "libc", + "rustix 0.38.44", +] [[package]] -name = "dwrote" -version = "0.11.5" +name = "drm" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b35532432acc8b19ceed096e35dfa088d3ea037fe4f3c085f1f97f33b4d02" +checksum = "7a41816e58f47f49acfd956651055ddcf137c4882c2098c30c448817af21183a" dependencies = [ - "lazy_static", + "bitflags 2.13.1", + "bytemuck", + "bytemuck_derive", + "drm-ffi", + "drm-fourcc", "libc", - "winapi", - "wio", + "rustix 1.1.4", ] [[package]] -name = "dyn-clone" -version = "1.0.20" +name = "drm-ffi" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +checksum = "51a91c9b32ac4e8105dec255e849e0d66e27d7c34d184364fb93e469db08f690" +dependencies = [ + "drm-sys", + "rustix 1.1.4", +] + +[[package]] +name = "drm-fourcc" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" + +[[package]] +name = "drm-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8e1361066d91f5ffccff060a3c3be9c3ecde15be2959c1937595f7a82a9f8" +dependencies = [ + "libc", + "linux-raw-sys 0.9.4", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dtor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "dyn-stack" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf6fa63092e3ca9f602f6500fddd05502412b748c4c4682938565b44eb9e0066" +dependencies = [ + "bytemuck", +] [[package]] name = "dyn-stack" @@ -3043,52 +2623,72 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d926b4d407d372f141f93bb444696142c29d32962ccbd3531117cf3aa0bfa9" [[package]] -name = "either" -version = "1.15.0" +name = "ed25519" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" +dependencies = [ + "pkcs8", + "serdect", + "signature", +] [[package]] -name = "embassy-futures" -version = "0.1.2" +name = "ed25519-dalek" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" +checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.10.1", + "serde", + "sha2 0.11.0", + "signature", + "subtle", + "zeroize", +] [[package]] -name = "embassy-time" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f820157f198ada183ad62e0a66f554c610cdcd1a9f27d4b316358103ced7a1f8" +name = "eegdino" +version = "0.2.0" dependencies = [ - "cfg-if", - "critical-section", - "document-features", - "embassy-time-driver", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0", - "embedded-hal-async", - "futures-util", + "anyhow", + "half", + "rayon", + "rlx", + "rustfft", + "safetensors 0.7.0", + "serde", + "serde_json", + "thiserror 2.0.19", ] [[package]] -name = "embassy-time-driver" -version = "0.2.1" +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "email_address" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" dependencies = [ - "document-features", + "serde", ] [[package]] name = "embed-resource" -version = "3.0.6" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" dependencies = [ "cc", "memchr", "rustc_version", - "toml 0.9.12+spec-1.1.0", + "toml 1.1.3+spec-1.1.0", "vswhom", "winreg", ] @@ -3100,28 +2700,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" [[package]] -name = "embedded-hal" -version = "0.2.7" +name = "embedded-io" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" -dependencies = [ - "nb 0.1.3", - "void", -] +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" [[package]] -name = "embedded-hal" -version = "1.0.0" +name = "embedded-io" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" [[package]] -name = "embedded-hal-async" -version = "1.0.0" +name = "emotiv" +version = "0.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +checksum = "c28936decb4e5cd37d5e93f3e7edd790970ef34e2f49b969c0193d375c24f14a" dependencies = [ - "embedded-hal 1.0.0", + "anyhow", + "chrono", + "env_logger", + "futures-util", + "log", + "rustls", + "serde", + "serde_json", + "thiserror 2.0.19", + "tokio", + "tokio-tungstenite 0.26.2", + "tracing", + "uuid", ] [[package]] @@ -3145,12 +2753,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" -[[package]] -name = "entities" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" - [[package]] name = "enum-as-inner" version = "0.6.1" @@ -3160,57 +2762,46 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "enumflags2" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" -dependencies = [ - "enumflags2_derive", - "serde", + "syn 2.0.119", ] [[package]] -name = "enumflags2_derive" -version = "0.7.12" +name = "enum-assoc" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +checksum = "3ed8956bd5c1f0415200516e78ff07ec9e16415ade83c056c230d7b7ea0d55b7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] -name = "enumset" -version = "1.1.10" +name = "enumflags2" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b07a8dfbbbfc0064c0a6bdf9edcf966de6b1c33ce344bdeca3b41615452634" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" dependencies = [ - "enumset_derive", + "enumflags2_derive", "serde", ] [[package]] -name = "enumset_derive" -version = "0.14.0" +name = "enumflags2_derive" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43e744e4ea338060faee68ed933e46e722fb7f3617e722a5772d7e856d8b3ce" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ - "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "env_filter" -version = "1.0.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ "log", "regex", @@ -3218,9 +2809,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.9" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" dependencies = [ "anstream", "anstyle", @@ -3229,13 +2820,33 @@ dependencies = [ "log", ] +[[package]] +name = "equator" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c35da53b5a021d2484a7cc49b2ac7f2d840f8236a286f84202369bd338d761ea" +dependencies = [ + "equator-macro 0.2.1", +] + [[package]] name = "equator" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" dependencies = [ - "equator-macro", + "equator-macro 0.4.2", +] + +[[package]] +name = "equator-macro" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf679796c0322556351f287a51b49e48f7c4986e727b5dd78c972d30e2e16cc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -3246,7 +2857,7 @@ checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -3273,7 +2884,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3282,6 +2893,70 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6" +[[package]] +name = "espeak-ng" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "083cee3337773bfa7f1c38fe022dab392438ced57f3a64c883011952d13b83a1" +dependencies = [ + "bitflags 2.13.1", + "espeak-ng-data-dict-ru", + "espeak-ng-data-dicts", + "espeak-ng-data-phonemes 0.1.0", + "thiserror 1.0.69", +] + +[[package]] +name = "espeak-ng" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213ed87669515192b61f34dc6d6c36adeb75ae424b63e32612a65a1e21e9bee3" +dependencies = [ + "bitflags 2.13.1", + "espeak-ng-data-dict-en", + "espeak-ng-data-phonemes 0.2.0", + "thiserror 1.0.69", +] + +[[package]] +name = "espeak-ng-data-dict-en" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f231805f816e50caa02fc9266ba9e7ba842bc1439ed5dc4adc8aa09a745aa4a" + +[[package]] +name = "espeak-ng-data-dict-ru" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b62f5cac7f103b5ef324f02b2fd883897404eac9766dd21d90864eeb633b7ff" + +[[package]] +name = "espeak-ng-data-dicts" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "393d19356251c91ffc4f08f446af2a5331efb6b6a5e7d109b26172bdea594eea" + +[[package]] +name = "espeak-ng-data-phonemes" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65418fdbf64db690f177ae55b6cb0f01eec5ffd46f5e1cba6f841ae3f214ec0" + +[[package]] +name = "espeak-ng-data-phonemes" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73639acc81fa0d350dd6fef933d4a935b6f8b93ef74455c9eebf1f3873253e0d" + +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + [[package]] name = "event-listener" version = "5.4.1" @@ -3305,9 +2980,9 @@ dependencies = [ [[package]] name = "exg" -version = "0.0.1" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f96fd9ff4b0e97d18a9b5e6185301e783385aafea8bb4ddf5342bb1de57d1921" +checksum = "a3250007dee9c140f3e974b92ac04937bed030287f27f793ba8340188e455579" dependencies = [ "anyhow", "clap", @@ -3317,22 +2992,18 @@ dependencies = [ "safetensors 0.7.0", "serde", "serde_json", - "thiserror 2.0.18", ] [[package]] -name = "exr" -version = "1.74.0" +name = "exg-luna" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +checksum = "c7ec7573cd743e0827a2ead2b0692637cefb77d1a4f2513b2eb7688df1f9941a" dependencies = [ - "bit_field", - "half", - "lebe", - "miniz_oxide", - "rayon-core", - "smallvec", - "zune-inflate", + "anyhow", + "exg", + "ndarray 0.17.2", + "serde_json", ] [[package]] @@ -3341,6 +3012,50 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" +[[package]] +name = "faer" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2b19b8c3570ea226e507fe3dbae2aa9d7e0f16676abd35ea3adeeb9f90f7b5d" +dependencies = [ + "bytemuck", + "coe-rs", + "dbgf", + "dyn-stack 0.11.0", + "equator 0.4.2", + "faer-entity", + "gemm", + "generativity", + "libm", + "matrixcompare", + "matrixcompare-core", + "nano-gemm", + "npyz", + "num-complex", + "num-traits", + "paste", + "rand 0.8.7", + "rand_distr", + "rayon", + "reborrow", + "serde", +] + +[[package]] +name = "faer-entity" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "072f96f1bc8b2b30dfc26c086baeadb63aae08019b1ed84721809b9fd2006685" +dependencies = [ + "bytemuck", + "coe-rs", + "libm", + "num-complex", + "num-traits", + "pulp 0.18.22", + "reborrow", +] + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -3353,94 +3068,59 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "fancy-regex" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set 0.5.3", + "regex", +] + [[package]] name = "fancy-regex" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" dependencies = [ - "bit-set", + "bit-set 0.8.0", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "fancy-regex" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277" +dependencies = [ + "bit-set 0.8.0", "regex-automata", "regex-syntax", ] [[package]] name = "fast-hnsw" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64519a75152c70cb64416b582c5ad2c4041fa3e36e8dc7d40b7d6f594d770831" +checksum = "bf2403f0c8ef8efb4716e7d5683a31ef5be24cb2491be06dfea9802b26dbc9f9" dependencies = [ "memmap2", - "rand 0.8.5", + "rand 0.8.7", ] [[package]] -name = "fast-umap" -version = "1.2.2" +name = "fast-srgb8" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ede18884a2062c5f97d7fff6ee94501a311dd7b59910c520c9336cf2e555c18" -dependencies = [ - "burn", - "burn-cubecl", - "crossbeam-channel", - "ctrlc", - "cubecl", - "hsl", - "indicatif 0.18.4", - "ndarray 0.16.1", - "num", - "num-traits", - "plotters", - "prettytable", - "rand 0.9.2", - "rayon", - "serde", -] - -[[package]] -name = "fastembed" -version = "5.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7b01c79c5cb8ab3ce31c3d52916fda278e14cac027ff3a9cb66c419ed7288f8" -dependencies = [ - "anyhow", - "candle-core", - "candle-nn", - "hf-hub 0.4.3", - "image 0.25.9", - "ndarray 0.17.2", - "ort", - "safetensors 0.7.0", - "serde", - "serde_json", - "tokenizers", -] +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" [[package]] name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fax" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" -dependencies = [ - "fax_derive", -] - -[[package]] -name = "fax_derive" -version = "0.2.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "fdeflate" @@ -3451,6 +3131,12 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + [[package]] name = "field-offset" version = "0.3.6" @@ -3461,15 +3147,25 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + [[package]] name = "filetime" -version = "0.2.27" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" dependencies = [ "cfg-if", "libc", - "libredox", ] [[package]] @@ -3479,13 +3175,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] -name = "find_cuda_helper" -version = "0.2.0" +name = "finl_unicode" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f9e65c593dd01ac77daad909ea4ad17f0d6d1776193fc8ea766356177abdad" -dependencies = [ - "glob", -] +checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" [[package]] name = "fixedbitset" @@ -3494,57 +3187,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "float-ord" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" - -[[package]] -name = "float4" -version = "0.1.0" +name = "fixedbitset" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5939bac0ef2ad7c83a53e4fb889c1d81f007b07061d648cd271071984d86f257" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] -name = "float8" -version = "0.4.2" +name = "flatbuffers" +version = "25.12.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4203231de188ebbdfb85c11f3c20ca2b063945710de04e7b59268731e728b462" +checksum = "35f6839d7b3b98adde531effaf34f0c2badc6f4735d26fe74709d8e513a96ef3" dependencies = [ - "half", + "bitflags 2.13.1", + "rustc_version", ] [[package]] -name = "float8" -version = "0.6.1" +name = "flate2" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719a903cc23e4a89e87962c2a80fdb45cdaad0983a89bd150bb57b4c8571a7d5" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ - "half", - "num-traits", - "rand 0.9.2", - "rand_distr", + "crc32fast", + "miniz_oxide", + "zlib-rs", ] [[package]] -name = "flume" -version = "0.11.1" +name = "fluent-uri" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" dependencies = [ - "futures-core", - "futures-sink", - "spin 0.9.8", + "borrow-or-share", + "ref-cast", + "serde", ] [[package]] @@ -3553,43 +3230,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" -[[package]] -name = "font-kit" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3" -dependencies = [ - "bitflags 2.11.0", - "byteorder", - "core-foundation 0.9.4", - "core-graphics 0.23.2", - "core-text", - "dirs", - "dwrote", - "float-ord", - "freetype-sys", - "lazy_static", - "libc", - "log", - "pathfinder_geometry", - "pathfinder_simd", - "walkdir", - "winapi", - "yeslogic-fontconfig-sys", -] - [[package]] name = "foreign-types" version = "0.3.2" @@ -3611,13 +3257,13 @@ dependencies = [ [[package]] name = "foreign-types-macros" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", ] [[package]] @@ -3642,31 +3288,29 @@ dependencies = [ ] [[package]] -name = "freetype-sys" -version = "0.20.1" +name = "fraction" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +checksum = "e076045bb43dac435333ed5f04caf35c7463631d0dae2deb2638d94dd0a5b872" dependencies = [ - "cc", - "libc", - "pkg-config", + "lazy_static", + "num", ] [[package]] -name = "futf" -version = "0.1.5" +name = "fsevent-sys" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" dependencies = [ - "mac", - "new_debug_unreachable", + "libc", ] [[package]] name = "futures" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" dependencies = [ "futures-channel", "futures-core", @@ -3677,11 +3321,24 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-buffered" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4421cb78ee172b6b06080093479d3c50f058e7c81b7d577bbb8d118d551d4cd5" +dependencies = [ + "cordyceps", + "diatomic-waker", + "futures-core", + "pin-project-lite", + "spin", +] + [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", "futures-sink", @@ -3689,15 +3346,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" dependencies = [ "futures-core", "futures-task", @@ -3706,9 +3363,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" [[package]] name = "futures-lite" @@ -3725,38 +3382,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" [[package]] name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-timer" -version = "3.0.3" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-channel", "futures-core", @@ -3778,6 +3429,30 @@ dependencies = [ "byteorder", ] +[[package]] +name = "gbm" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce852e998d3ca5e4a97014fb31c940dc5ef344ec7d364984525fd11e8a547e6a" +dependencies = [ + "bitflags 2.13.1", + "drm 0.14.1", + "drm-fourcc", + "gbm-sys", + "libc", + "wayland-backend", + "wayland-server", +] + +[[package]] +name = "gbm-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13a5f2acc785d8fb6bf6b7ab6bfb0ef5dad4f4d97e8e70bb8e470722312f76f" +dependencies = [ + "libc", +] + [[package]] name = "gdk" version = "0.18.2" @@ -3816,7 +3491,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -3833,7 +3508,7 @@ dependencies = [ "libc", "pango-sys", "pkg-config", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -3847,7 +3522,7 @@ dependencies = [ "gobject-sys", "libc", "pkg-config", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -3873,7 +3548,7 @@ dependencies = [ "gdk-sys", "glib-sys", "libc", - "system-deps", + "system-deps 6.2.2", "x11", ] @@ -3883,33 +3558,13 @@ version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab96b703d31950f1aeddded248bc95543c9efc7ac9c4a21fda8703a83ee35451" dependencies = [ - "dyn-stack", - "gemm-c32 0.18.2", - "gemm-c64 0.18.2", - "gemm-common 0.18.2", - "gemm-f16 0.18.2", - "gemm-f32 0.18.2", - "gemm-f64 0.18.2", - "num-complex", - "num-traits", - "paste", - "raw-cpuid", - "seq-macro", -] - -[[package]] -name = "gemm" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa0673db364b12263d103b68337a68fbecc541d6f6b61ba72fe438654709eacb" -dependencies = [ - "dyn-stack", - "gemm-c32 0.19.0", - "gemm-c64 0.19.0", - "gemm-common 0.19.0", - "gemm-f16 0.19.0", - "gemm-f32 0.19.0", - "gemm-f64 0.19.0", + "dyn-stack 0.13.2", + "gemm-c32", + "gemm-c64", + "gemm-common", + "gemm-f16", + "gemm-f32", + "gemm-f64", "num-complex", "num-traits", "paste", @@ -3923,23 +3578,8 @@ version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6db9fd9f40421d00eea9dd0770045a5603b8d684654816637732463f4073847" dependencies = [ - "dyn-stack", - "gemm-common 0.18.2", - "num-complex", - "num-traits", - "paste", - "raw-cpuid", - "seq-macro", -] - -[[package]] -name = "gemm-c32" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "086936dbdcb99e37aad81d320f98f670e53c1e55a98bee70573e83f95beb128c" -dependencies = [ - "dyn-stack", - "gemm-common 0.19.0", + "dyn-stack 0.13.2", + "gemm-common", "num-complex", "num-traits", "paste", @@ -3953,23 +3593,8 @@ version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfcad8a3d35a43758330b635d02edad980c1e143dc2f21e6fd25f9e4eada8edf" dependencies = [ - "dyn-stack", - "gemm-common 0.18.2", - "num-complex", - "num-traits", - "paste", - "raw-cpuid", - "seq-macro", -] - -[[package]] -name = "gemm-c64" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c8aeeeec425959bda4d9827664029ba1501a90a0d1e6228e48bef741db3a3f" -dependencies = [ - "dyn-stack", - "gemm-common 0.19.0", + "dyn-stack 0.13.2", + "gemm-common", "num-complex", "num-traits", "paste", @@ -3984,7 +3609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a352d4a69cbe938b9e2a9cb7a3a63b7e72f9349174a2752a558a8a563510d0f3" dependencies = [ "bytemuck", - "dyn-stack", + "dyn-stack 0.13.2", "half", "libm", "num-complex", @@ -3998,54 +3623,15 @@ dependencies = [ "sysctl", ] -[[package]] -name = "gemm-common" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88027625910cc9b1085aaaa1c4bc46bb3a36aad323452b33c25b5e4e7c8e2a3e" -dependencies = [ - "bytemuck", - "dyn-stack", - "half", - "libm", - "num-complex", - "num-traits", - "once_cell", - "paste", - "pulp 0.22.2", - "raw-cpuid", - "rayon", - "seq-macro", - "sysctl", -] - [[package]] name = "gemm-f16" version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cff95ae3259432f3c3410eaa919033cd03791d81cebd18018393dc147952e109" dependencies = [ - "dyn-stack", - "gemm-common 0.18.2", - "gemm-f32 0.18.2", - "half", - "num-complex", - "num-traits", - "paste", - "raw-cpuid", - "rayon", - "seq-macro", -] - -[[package]] -name = "gemm-f16" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3df7a55202e6cd6739d82ae3399c8e0c7e1402859b30e4cb780e61525d9486e" -dependencies = [ - "dyn-stack", - "gemm-common 0.19.0", - "gemm-f32 0.19.0", + "dyn-stack 0.13.2", + "gemm-common", + "gemm-f32", "half", "num-complex", "num-traits", @@ -4061,23 +3647,8 @@ version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc8d3d4385393304f407392f754cd2dc4b315d05063f62cf09f47b58de276864" dependencies = [ - "dyn-stack", - "gemm-common 0.18.2", - "num-complex", - "num-traits", - "paste", - "raw-cpuid", - "seq-macro", -] - -[[package]] -name = "gemm-f32" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e0b8c9da1fbec6e3e3ab2ce6bc259ef18eb5f6f0d3e4edf54b75f9fd41a81c" -dependencies = [ - "dyn-stack", - "gemm-common 0.19.0", + "dyn-stack 0.13.2", + "gemm-common", "num-complex", "num-traits", "paste", @@ -4091,8 +3662,8 @@ version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35b2a4f76ce4b8b16eadc11ccf2e083252d8237c1b589558a49b0183545015bd" dependencies = [ - "dyn-stack", - "gemm-common 0.18.2", + "dyn-stack 0.13.2", + "gemm-common", "num-complex", "num-traits", "paste", @@ -4101,18 +3672,24 @@ dependencies = [ ] [[package]] -name = "gemm-f64" -version = "0.19.0" +name = "generativity" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c81fb5260e37854d09d5c87183309fd8c555b75289427884b25660bc87a85e" + +[[package]] +name = "generator" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "056131e8f2a521bfab322f804ccd652520c79700d81209e9d9275bbdecaadc6a" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" dependencies = [ - "dyn-stack", - "gemm-common 0.19.0", - "num-complex", - "num-traits", - "paste", - "raw-cpuid", - "seq-macro", + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link 0.1.3", + "windows-result 0.3.4", ] [[package]] @@ -4135,17 +3712,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.17" @@ -4155,7 +3721,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] @@ -4175,49 +3741,42 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", - "rand_core 0.10.0", - "wasip2", - "wasip3", + "rand_core 0.10.1", + "wasm-bindgen", ] [[package]] -name = "gif" -version = "0.12.0" +name = "ghash" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" dependencies = [ - "color_quant", - "weezl", + "opaque-debug", + "polyval", ] [[package]] name = "gif" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" dependencies = [ "color_quant", "weezl", ] -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - [[package]] name = "gio" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +version = "0.18.5" +source = "git+https://github.com/eugenehp/gtk-rs-core.git?branch=0.18-patched#801780c451f4bfc0e9f50ac098ff73b6b870d16d" dependencies = [ "futures-channel", "futures-core", @@ -4234,93 +3793,23 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +version = "0.18.5" +source = "git+https://github.com/eugenehp/gtk-rs-core.git?branch=0.18-patched#801780c451f4bfc0e9f50ac098ff73b6b870d16d" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 6.2.2", "winapi", ] [[package]] -name = "gix-features" -version = "0.45.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56aad357ae016449434705033df644ac6253dfcf1281aad3af3af9e907560d1" -dependencies = [ - "gix-trace", - "gix-utils", - "libc", -] - -[[package]] -name = "gix-fs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "785b9c499e46bc78d7b81c148c21b3fca18655379ee729a856ed19ce50d359ec" -dependencies = [ - "bstr", - "fastrand", - "gix-features", - "gix-path", - "gix-utils", - "thiserror 2.0.18", -] - -[[package]] -name = "gix-path" -version = "0.10.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cb06c3e4f8eed6e24fd915fa93145e28a511f4ea0e768bae16673e05ed3f366" -dependencies = [ - "bstr", - "gix-trace", - "gix-validate", - "thiserror 2.0.18", -] - -[[package]] -name = "gix-tempfile" -version = "20.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad89218e74850f42d364ed3877c7291f0474c8533502df91bb877ecc5cb0dd40" -dependencies = [ - "dashmap 6.1.0", - "gix-fs", - "libc", - "parking_lot", - "signal-hook 0.4.3", - "signal-hook-registry", - "tempfile", -] - -[[package]] -name = "gix-trace" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f69a13643b8437d4ca6845e08143e847a36ca82903eed13303475d0ae8b162e0" - -[[package]] -name = "gix-utils" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "befcdbdfb1238d2854591f760a48711bed85e72d80a10e8f2f93f656746ef7c5" -dependencies = [ - "fastrand", - "unicode-normalization", -] - -[[package]] -name = "gix-validate" -version = "0.10.1" +name = "gl" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1e63a5b516e970a594f870ed4571a8fdcb8a344e7bd407a20db8bd61dbfde4" +checksum = "a94edab108827d67608095e269cf862e60d920f144a5026d3dbcfd8b877fb404" dependencies = [ - "bstr", - "thiserror 2.0.18", + "gl_generator", ] [[package]] @@ -4337,10 +3826,9 @@ dependencies = [ [[package]] name = "glib" version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +source = "git+https://github.com/eugenehp/gtk-rs-core.git?branch=0.18-patched#801780c451f4bfc0e9f50ac098ff73b6b870d16d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "futures-channel", "futures-core", "futures-executor", @@ -4360,141 +3848,123 @@ dependencies = [ [[package]] name = "glib-macros" version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +source = "git+https://github.com/eugenehp/gtk-rs-core.git?branch=0.18-patched#801780c451f4bfc0e9f50ac098ff73b6b870d16d" dependencies = [ "heck 0.4.1", "proc-macro-crate 2.0.2", "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +version = "0.18.5" +source = "git+https://github.com/eugenehp/gtk-rs-core.git?branch=0.18-patched#801780c451f4bfc0e9f50ac098ff73b6b870d16d" dependencies = [ "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] name = "glob" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "global-hotkey" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9247516746aa8e53411a0db9b62b0e24efbcf6a76e0ba73e5a91b512ddabed7" +checksum = "8c386b0a4a70cb2d39fffd74480f985b6f0bfbcb934b6a6b6b7e630e448f242e" dependencies = [ "crossbeam-channel", "keyboard-types", - "objc2 0.6.4", + "objc2", "objc2-app-kit", "once_cell", "serde", - "thiserror 2.0.18", + "thiserror 2.0.19", "windows-sys 0.59.0", "x11rb", "xkeysym", ] [[package]] -name = "glow" -version = "0.16.0" +name = "globset" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +checksum = "e47d37d2ae4464254884b60ab7071be2b876a9c35b696bd018ddcc76847309cd" dependencies = [ - "js-sys", - "slotmap", - "wasm-bindgen", - "web-sys", + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", ] [[package]] -name = "glutin_wgl_sys" -version = "0.6.1" +name = "gloo-timers" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ - "gl_generator", + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", ] [[package]] name = "gobject-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +version = "0.18.5" +source = "git+https://github.com/eugenehp/gtk-rs-core.git?branch=0.18-patched#801780c451f4bfc0e9f50ac098ff73b6b870d16d" dependencies = [ "glib-sys", "libc", - "system-deps", -] - -[[package]] -name = "gpu-alloc" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" -dependencies = [ - "bitflags 2.11.0", - "gpu-alloc-types", + "system-deps 6.2.2", ] [[package]] -name = "gpu-alloc-types" -version = "0.3.0" +name = "good_lp" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +checksum = "745190412d5ff4a54335cd16229a475ad3fb8f5474a5c1358292d62932187ea7" dependencies = [ - "bitflags 2.11.0", + "fnv", + "highs", ] [[package]] name = "gpu-allocator" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +version = "0.28.0" dependencies = [ + "ash", + "hashbrown 0.16.1", "log", "presser", - "thiserror 1.0.69", - "windows 0.58.0", -] - -[[package]] -name = "gpu-descriptor" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" -dependencies = [ - "bitflags 2.11.0", - "gpu-descriptor-types", - "hashbrown 0.15.5", + "thiserror 2.0.19", + "windows 0.62.2", ] [[package]] -name = "gpu-descriptor-types" -version = "0.2.0" +name = "grayscale" +version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" -dependencies = [ - "bitflags 2.11.0", -] +checksum = "042686e9bd899e5fc60e43899a8c1ac312a1114c693c836e6b1e895f42083969" [[package]] -name = "gpu-fft" -version = "1.1.1" +name = "gtec" +version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4745b92955a4b0788612ffb4b8e44e8a69deaa2a0bea0403c74b310a3fd499" +checksum = "3d7c162ee5089d7b975f98b80df0ae656050d3dd2e94314cb4c266c05f1f16b2" dependencies = [ - "cubecl", + "env_logger", + "libc", + "libloading 0.8.9", + "log", + "thiserror 2.0.19", ] [[package]] @@ -4533,7 +4003,7 @@ dependencies = [ "gobject-sys", "libc", "pango-sys", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -4546,14 +4016,14 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "h2" -version = "0.4.13" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" dependencies = [ "atomic-waker", "bytes", @@ -4561,7 +4031,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.13.0", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -4578,9 +4048,6 @@ dependencies = [ "cfg-if", "crunchy", "num-traits", - "rand 0.9.2", - "rand_distr", - "serde", "zerocopy", ] @@ -4590,15 +4057,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.14.5" @@ -4607,36 +4065,47 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash 0.1.5", + "foldhash", "serde", + "serde_core", ] [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ "allocator-api2", "equivalent", - "foldhash 0.2.0", + "foldhash", "serde", "serde_core", ] [[package]] name = "hashlink" -version = "0.10.0" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "hdrhistogram" +version = "7.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +checksum = "f49d1053f4708f0af3cf9fc5bffc7e68a914a3c45becb231c80068c9c3f78bea" dependencies = [ - "hashbrown 0.15.5", + "byteorder", + "num-traits", ] [[package]] @@ -4663,33 +4132,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hexf-parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" - -[[package]] -name = "hf-hub" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629d8f3bbeda9d148036d6b0de0a3ab947abd08ce90626327fc3547a49d59d97" -dependencies = [ - "dirs", - "http", - "indicatif 0.17.11", - "libc", - "log", - "native-tls", - "rand 0.9.2", - "reqwest 0.12.28", - "serde", - "serde_json", - "thiserror 2.0.18", - "ureq 2.12.1", - "windows-sys 0.60.2", -] - [[package]] name = "hf-hub" version = "0.5.0" @@ -4699,110 +4141,281 @@ dependencies = [ "dirs", "futures", "http", - "indicatif 0.18.4", + "indicatif", "libc", "log", "native-tls", "num_cpus", - "rand 0.9.2", + "rand 0.9.5", "reqwest 0.12.28", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", - "ureq 3.2.0", + "ureq 3.3.0", "windows-sys 0.61.2", ] [[package]] -name = "hmac" -version = "0.12.1" +name = "hickory-net" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" dependencies = [ - "digest", + "async-trait", + "bytes", + "cfg-if", + "data-encoding", + "futures-channel", + "futures-io", + "futures-util", + "h2", + "hickory-proto", + "http", + "idna", + "ipnet", + "jni 0.22.4", + "rand 0.10.2", + "rustls", + "thiserror 2.0.19", + "tinyvec", + "tokio", + "tokio-rustls", + "tracing", + "url", ] [[package]] -name = "hmac-sha256" -version = "1.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec9d92d097f4749b64e8cc33d924d9f40a2d4eb91402b458014b781f5733d60f" - -[[package]] -name = "hound" -version = "3.5.1" +name = "hickory-proto" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" +checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" +dependencies = [ + "data-encoding", + "idna", + "ipnet", + "jni 0.22.4", + "once_cell", + "prefix-trie", + "rand 0.10.2", + "ring", + "thiserror 2.0.19", + "tinyvec", + "tracing", + "url", +] [[package]] -name = "hsl" -version = "0.1.1" +name = "hickory-resolver" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "575fb7f1167f3b88ed825e90eb14918ac460461fdeaa3965c6a50951dee1c970" +checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-net", + "hickory-proto", + "ipconfig", + "ipnet", + "jni 0.22.4", + "moka", + "ndk-context", + "once_cell", + "parking_lot", + "rand 0.10.2", + "resolv-conf", + "rustls", + "smallvec", + "system-configuration", + "thiserror 2.0.19", + "tokio", + "tokio-rustls", + "tracing", +] [[package]] -name = "html5ever" -version = "0.29.1" +name = "highs" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +checksum = "7dfc791427e78fdc1a0a37983f79c20f86a0b7b7dfdb5d6a57a6f72d77cd9074" dependencies = [ + "highs-sys", "log", - "mac", - "markup5ever", - "match_token", ] [[package]] -name = "http" -version = "1.4.0" +name = "highs-sys" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "e4da0c52322648e2360a5a8896d23301fa5ed99ed7607c5faf229bd0bbb30d3f" dependencies = [ - "bytes", - "itoa", + "bindgen", + "cmake", ] [[package]] -name = "http-body" -version = "1.0.1" +name = "hkdf" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "bytes", - "http", + "hmac", ] [[package]] -name = "http-body-util" -version = "0.1.3" +name = "hmac" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", + "digest 0.10.7", ] [[package]] -name = "httparse" -version = "1.10.1" +name = "hostname" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] [[package]] -name = "httpdate" -version = "1.0.3" +name = "hotpath" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.8.1" +checksum = "bff002d5c53fa1c6891f32156b9451d16654bc8a761894d7660b25c0a332d517" +dependencies = [ + "arc-swap", + "cfg-if", + "crossbeam-channel", + "flate2", + "futures-util", + "hdrhistogram", + "hotpath-macros", + "hotpath-meta", + "libc", + "object 0.36.7", + "pin-project-lite", + "prettytable-rs", + "quanta", + "regex", + "rustc-demangle", + "serde", + "serde_json", + "tiny_http", + "tokio", +] + +[[package]] +name = "hotpath-macros" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2f4ac4534511584b7082657e133dcf3d8727b2f456a6b2a2c3eb02b82c1277" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "hotpath-macros-meta" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a87070853e9402ec79184f8d8d930d7eb86cd274aecdcf973f73b6f40271b0" + +[[package]] +name = "hotpath-meta" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31cfef2b9d280ad754c23b40b50cc74676489597f3cebfe0c180389e08a53ed" +dependencies = [ + "hotpath-macros-meta", +] + +[[package]] +name = "hound" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" + +[[package]] +name = "html5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +dependencies = [ + "log", + "markup5ever", +] + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" dependencies = [ "atomic-waker", "bytes", @@ -4815,7 +4428,6 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -4823,19 +4435,17 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.6", ] [[package]] @@ -4871,7 +4481,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.5.10", "system-configuration", "tokio", "tower-service", @@ -4891,7 +4501,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core 0.61.2", ] [[package]] @@ -4915,22 +4525,23 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", - "yoke 0.8.1", + "utf8_iter", + "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -4941,9 +4552,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -4955,15 +4566,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -4975,37 +4586,37 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", "writeable", - "yoke 0.8.1", + "yoke", "zerofrom", "zerotrie", "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "identity-hash" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfdd7caa900436d8f13b2346fe10257e0c05c1f1f9e351f4f5d57c03bd5f45da" + [[package]] name = "idna" version = "1.1.0" @@ -5019,60 +4630,85 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] -name = "if-addrs" -version = "0.15.0" +name = "idun" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0a05c691e1fae256cf7013d99dad472dc52d5543322761f83ec8d47eab40d2b" +checksum = "ef4870c15acd4691d274c8817b5987f968b8bf9b3be2ebeed3d1cfe14f1547c0" dependencies = [ - "libc", - "windows-sys 0.61.2", + "anyhow", + "base64 0.22.1", + "btleplug", + "env_logger", + "futures", + "log", + "serde", + "serde_json", + "tokio", + "tokio-tungstenite 0.24.0", + "uuid", ] [[package]] -name = "image" -version = "0.24.9" +name = "igd-next" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +checksum = "de7238d487a9aff61f81b5ab41c0a841532a115a398b5fa92a2fadd0885e2581" dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "jpeg-decoder", - "num-traits", - "png 0.17.16", + "attohttpc", + "bytes", + "futures", + "http", + "http-body-util", + "hyper", + "hyper-util", + "log", + "rand 0.10.2", + "tokio", + "url", + "xmltree", +] + +[[package]] +name = "ignore" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8a7b8211e695a1d0cd91cace480d4d0bd57667ab10277cc412c5f7f4884f83" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", ] [[package]] name = "image" -version = "0.25.9" +version = "0.25.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" dependencies = [ "bytemuck", "byteorder-lite", "color_quant", - "exr", - "gif 0.14.1", + "gif", "image-webp", "moxcms", "num-traits", "png 0.18.1", - "qoi", - "ravif", - "rayon", - "rgb", - "tiff", - "zune-core 0.5.1", - "zune-jpeg 0.5.12", + "zune-core", + "zune-jpeg", ] [[package]] @@ -5082,15 +4718,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" dependencies = [ "byteorder-lite", - "quick-error", + "quick-error 2.0.1", ] -[[package]] -name = "imgref" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" - [[package]] name = "indexmap" version = "1.9.3" @@ -5104,38 +4734,25 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "serde", "serde_core", ] [[package]] name = "indicatif" -version = "0.17.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" -dependencies = [ - "console 0.15.11", - "number_prefix", - "portable-atomic", - "unicode-width 0.2.0", - "web-time", -] - -[[package]] -name = "indicatif" -version = "0.18.4" +version = "0.18.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb" +checksum = "9433806cd6b4ec1aba79c021c7e4c58fb4c3b9977c085062e611ac929998fb0c" dependencies = [ - "console 0.16.2", + "console", "portable-atomic", - "unicode-width 0.2.0", + "unicode-width 0.2.2", "unit-prefix", "web-time", ] @@ -5158,37 +4775,71 @@ dependencies = [ "cfb", ] +[[package]] +name = "inotify" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "153be1941a183ec9ccd095ddbe17a8b8d435ef6c76e9e02451b933c3999af2c8" +dependencies = [ + "bitflags 2.13.1", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" +dependencies = [ + "libc", +] + [[package]] name = "inout" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ + "block-padding", "generic-array", ] +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + [[package]] name = "instability" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d" +checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" dependencies = [ "darling 0.23.0", "indoc", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] -name = "interpolate_name" -version = "0.2.4" +name = "integer-encoding" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" + +[[package]] +name = "inventory" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "rustversion", ] [[package]] @@ -5202,48 +4853,257 @@ dependencies = [ ] [[package]] -name = "ipnet" -version = "2.12.0" +name = "ipconfig" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2 0.6.5", + "widestring", + "windows-registry", + "windows-result 0.4.1", + "windows-sys 0.61.2", +] [[package]] -name = "iri-string" -version = "0.7.10" +name = "ipnet" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" dependencies = [ - "memchr", "serde", ] [[package]] -name = "is-docker" -version = "0.2.0" +name = "iroh" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +checksum = "460de6bc52163b41b1646931f2897e5ab986f0966ade444467fec25024751a72" dependencies = [ - "once_cell", + "backon", + "blake3", + "bytes", + "cfg_aliases", + "ctutils", + "data-encoding", + "derive_more", + "ed25519-dalek", + "futures-util", + "getrandom 0.4.3", + "hickory-resolver", + "http", + "ipnet", + "iroh-base", + "iroh-dns", + "iroh-metrics", + "iroh-relay", + "n0-error", + "n0-future", + "n0-watcher", + "netwatch", + "noq", + "noq-proto", + "noq-udp", + "papaya", + "pin-project", + "portable-atomic", + "portmapper", + "rand 0.10.2", + "reqwest 0.13.4", + "rustc-hash 2.1.3", + "rustls", + "rustls-pki-types", + "serde", + "smallvec", + "strum 0.28.0", + "time", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "url", + "wasm-bindgen-futures", ] [[package]] -name = "is-terminal" -version = "0.4.17" +name = "iroh-base" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +checksum = "6be73e16ee21c923aca9b3121aaa0db936f7c7ecc156ff47b8dac944c68d59a8" dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.61.2", + "curve25519-dalek", + "data-encoding", + "data-encoding-macro", + "derive_more", + "ed25519-dalek", + "getrandom 0.4.3", + "n0-error", + "rand 0.10.2", + "serde", + "url", + "zeroize", ] [[package]] -name = "is-wsl" -version = "0.4.0" +name = "iroh-dns" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +checksum = "46f6a9b39d18e6345f5c151afd299f2488e2cb5c520fe41b107b6bd3dc4c3349" dependencies = [ - "is-docker", + "arc-swap", + "cfg_aliases", + "derive_more", + "hickory-resolver", + "iroh-base", + "n0-error", + "n0-future", + "ndk-context", + "portable-atomic", + "rand 0.10.2", + "rustls", + "simple-dns", + "strum 0.28.0", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "iroh-example-client" +version = "0.1.0" +dependencies = [ + "anyhow", + "base32", + "iroh", + "iroh-base", + "serde", + "serde_json", + "skill-iroh", + "tempfile", + "tokio", + "totp-rs", + "url", +] + +[[package]] +name = "iroh-metrics" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291065721ad7c477b972e581bbc528df031dc8eb5e39fe1ff3300ae5dfb157ef" +dependencies = [ + "iroh-metrics-derive", + "itoa", + "n0-error", + "portable-atomic", + "ryu", + "serde", + "tracing", +] + +[[package]] +name = "iroh-metrics-derive" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae5f0c4405d1fbc9fb16ff422ca40620e93dc36c30ecaba0c2aee3992b7bd48" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "iroh-relay" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24bd586cf927f7b700f56ec3639b53cb5fa901ce284784051ff71092bfbf8193" +dependencies = [ + "blake3", + "bytes", + "cfg_aliases", + "data-encoding", + "derive_more", + "getrandom 0.4.3", + "hickory-resolver", + "http", + "http-body-util", + "hyper", + "hyper-util", + "iroh-base", + "iroh-dns", + "iroh-metrics", + "lru", + "n0-error", + "n0-future", + "noq", + "noq-proto", + "num_enum", + "pin-project", + "postcard", + "rand 0.10.2", + "reqwest 0.13.4", + "rustls", + "rustls-pki-types", + "serde", + "serde_bytes", + "strum 0.28.0", + "tokio", + "tokio-rustls", + "tokio-util", + "tokio-websockets", + "tracing", + "url", + "webpki-roots 1.0.9", + "ws_stream_wasm", +] + +[[package]] +name = "iroh_test_client" +version = "0.1.0" +dependencies = [ + "anyhow", + "base32", + "clap", + "env_logger", + "iroh", + "iroh-base", + "rand 0.10.2", + "serde", + "serde_json", + "skill-iroh", + "tempfile", + "tokio", + "totp-rs", + "url", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", "once_cell", ] @@ -5273,9 +5133,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "javascriptcore-rs" @@ -5297,15 +5157,17 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] name = "jiff" -version = "0.2.23" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "e184d09547b80eb7e20d141ba2fb1fbac843ca53f4cf1b31210adc4c1adc6e16" dependencies = [ + "defmt", + "jiff-core", "jiff-static", "log", "portable-atomic", @@ -5313,91 +5175,119 @@ dependencies = [ "serde_core", ] +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + [[package]] name = "jiff-static" -version = "0.2.23" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "323da076b7a6faf914dc677cb05a4b907742ff7375c8322c9e7f5061e5e0e9de" dependencies = [ + "jiff-core", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "jni" -version = "0.19.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", + "cfg-if", "combine", - "jni-sys", + "jni-sys 0.3.1", "log", "thiserror 1.0.69", "walkdir", + "windows-sys 0.45.0", ] [[package]] name = "jni" -version = "0.21.1" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-macros", + "jni-sys 0.4.1", "log", - "thiserror 1.0.69", + "simd_cesu8", + "thiserror 2.0.19", "walkdir", - "windows-sys 0.45.0", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", ] [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] [[package]] -name = "jni-utils" -version = "0.1.1" +name = "jni-sys" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "259e9f2c3ead61de911f147000660511f07ab00adeed1d84f5ac4d0386e7a6c4" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" dependencies = [ - "dashmap 5.5.3", - "futures", - "jni 0.19.0", - "log", - "once_cell", - "static_assertions", - "uuid", + "jni-sys-macros", ] [[package]] -name = "jobserver" -version = "0.1.34" +name = "jni-sys-macros" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ - "getrandom 0.3.4", - "libc", + "quote", + "syn 2.0.119", ] [[package]] -name = "jpeg-decoder" -version = "0.3.2" +name = "jobserver" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] @@ -5423,17 +5313,81 @@ dependencies = [ "serde_json", ] +[[package]] +name = "jsonschema" +version = "0.46.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a699d3e77675e6aa4bfffe3b907c8b5f7ed3241f9965bffb25475ad4b08d05" +dependencies = [ + "ahash", + "bytecount", + "data-encoding", + "email_address", + "fancy-regex 0.18.0", + "fraction", + "getrandom 0.3.4", + "idna", + "itoa", + "jsonschema-regex", + "num-cmp", + "num-traits", + "percent-encoding", + "referencing", + "regex", + "serde", + "serde_json", + "unicode-general-category", + "uuid-simd", +] + +[[package]] +name = "jsonschema-regex" +version = "0.46.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbd1086b01b9349fd4ef9a07433965af64c8ce8159abe633a189e4ff817bd13" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "kasuari" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" +dependencies = [ + "hashbrown 0.16.1", + "portable-atomic", + "thiserror 2.0.19", +] + [[package]] name = "keyboard-types" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "serde", "unicode-segmentation", ] +[[package]] +name = "keyring" +version = "3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" +dependencies = [ + "byteorder", + "dbus-secret-service", + "linux-keyutils", + "log", + "secret-service", + "security-framework 2.11.1", + "security-framework 3.7.0", + "windows-sys 0.60.2", + "zeroize", +] + [[package]] name = "khronos-egl" version = "6.0.0" @@ -5441,7 +5395,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading 0.8.9", "pkg-config", ] @@ -5452,53 +5405,85 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] -name = "kittentts" -version = "0.2.2" +name = "kitoken" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "761cc66da3cf015a5bafd30c284e05a23bbc96a1715aa85767728b187d165dfe" +checksum = "e4663da0884a672d8705d339aad6367006e88df910bee436afdfbc898b2c7847" dependencies = [ - "anyhow", - "fancy-regex", - "hf-hub 0.5.0", - "hound", + "base64 0.22.1", + "bstr", + "derive_more", + "fancy-regex 0.18.0", + "hashbrown 0.17.1", + "log", + "memchr", + "multiversion", "once_cell", - "ort", - "regex", + "orx-priority-queue", + "postcard", + "regex-automata", + "regex-syntax", + "sentencepiece-model", + "serde", + "serde_json", + "thiserror 2.0.19", + "unicode-normalization", +] + +[[package]] +name = "kitten_tts_mini_rlx" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "dirs", + "half", + "rlx-compile", + "rlx-cpu", + "rlx-cuda", + "rlx-gguf", + "rlx-ir", + "rlx-metal", + "rlx-mlx", + "rlx-onnx-import", + "rlx-opt", + "rlx-runtime", + "safetensors 0.7.0", "serde", "serde_json", - "thiserror 1.0.69", - "zip 2.4.2", ] [[package]] -name = "kuchikiki" -version = "0.8.8-speedreader" +name = "kqueue" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" +checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" dependencies = [ - "cssparser", - "html5ever", - "indexmap 2.13.0", - "selectors", + "kqueue-sys", + "libc", ] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "kqueue-sys" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags 2.13.1", + "libc", +] [[package]] -name = "leb128fmt" -version = "0.1.0" +name = "lab" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" [[package]] -name = "lebe" -version = "0.5.3" +name = "lazy_static" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libappindicator" @@ -5524,11 +5509,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "libbz2-rs-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" + [[package]] name = "libc" -version = "0.2.182" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libdbus-sys" @@ -5539,16 +5530,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "libfuzzer-sys" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" -dependencies = [ - "arbitrary", - "cc", -] - [[package]] name = "libloading" version = "0.7.4" @@ -5570,24 +5551,13 @@ dependencies = [ ] [[package]] -name = "liblzma" -version = "0.4.6" +name = "libloading" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6033b77c21d1f56deeae8014eb9fbe7bdf1765185a6c508b5ca82eeaed7f899" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" dependencies = [ - "liblzma-sys", - "num_cpus", -] - -[[package]] -name = "liblzma-sys" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f2db66f3268487b5033077f266da6777d057949b8f93c8ad82e441df25e6186" -dependencies = [ - "cc", - "libc", - "pkg-config", + "cfg-if", + "windows-link 0.2.1", ] [[package]] @@ -5598,21 +5568,45 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.14" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +dependencies = [ + "libc", +] + +[[package]] +name = "libspa" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +checksum = "2909f3be29d674e7f10604aff18d1bbe1bb03c4cd61c8a8ba19c0b1d162f7d4e" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", + "cc", + "cookie-factory", "libc", - "plain", - "redox_syscall 0.7.3", + "libspa-sys", + "nom 8.0.0", + "rustix 1.1.4", + "system-deps 7.0.8", +] + +[[package]] +name = "libspa-sys" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ad52764fca54818486f3cf75afec844d1f1a1568c24dcee25d41b1ab007dda" +dependencies = [ + "bindgen", + "cc", + "system-deps 7.0.8", ] [[package]] name = "libsqlite3-sys" -version = "0.35.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" +checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" dependencies = [ "cc", "pkg-config", @@ -5639,12 +5633,70 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libusb1-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da050ade7ac4ff1ba5379af847a10a10a8e284181e060105bf8d86960ce9ce0f" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libwayshot-xcap" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea8a46e4d016ef464e386e4970f5415fe0939c2d63abe0c2f539c05ef88c5fe5" +dependencies = [ + "drm 0.15.0", + "gbm", + "gl", + "image", + "khronos-egl", + "memmap2", + "rustix 1.1.4", + "thiserror 2.0.19", + "tracing", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-wlr", +] + +[[package]] +name = "line-clipping" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "linux-keyutils" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83270a18e9f90d0707c41e9f35efada77b64c0e6f3f1810e71c8368a864d5590" +dependencies = [ + "bitflags 2.13.1", + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -5653,9 +5705,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "litrs" @@ -5664,31 +5716,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] -name = "llama-cpp-2" -version = "0.1.138" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2947ab625c59d1fdf42e61f538c3fa66f43de2f78316971920873f359483d1d8" -dependencies = [ - "encoding_rs", - "enumflags2", - "llama-cpp-sys-2", - "thiserror 2.0.18", - "tracing", - "tracing-core", -] - -[[package]] -name = "llama-cpp-sys-2" -version = "0.1.138" +name = "llmfit-core" +version = "0.9.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a529006bf16af70c7485ba957820dc2bc9467d75697e97970c81d2da73c76f" +checksum = "68bc7da401ce3326a63ac7373c33212d8adf883bcc91c36595fb33b00367ecc5" dependencies = [ - "bindgen 0.72.1", - "cc", - "cmake", - "find_cuda_helper", - "glob", - "walkdir", + "dirs", + "http", + "regex", + "serde", + "serde_json", + "serde_yml", + "sysinfo 0.39.6", + "ureq 3.3.0", + "which", ] [[package]] @@ -5702,26 +5743,63 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] -name = "loop9" -version = "0.1.5" +name = "logos" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7251356ef8cb7aec833ddf598c6cb24d17b689d20b993f9d11a3d764e34e6458" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f80069600c0d66734f5ff52cc42f2dabd6b29d205f333d61fd7832e9e9963f" +dependencies = [ + "beef", + "fnv", + "lazy_static", + "proc-macro2", + "quote", + "regex-syntax", + "syn 2.0.119", +] + +[[package]] +name = "logos-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24fb722b06a9dc12adb0963ed585f19fc61dc5413e6a9be9422ef92c091e731d" +dependencies = [ + "logos-codegen", +] + +[[package]] +name = "loom" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" dependencies = [ - "imgref", + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", ] [[package]] name = "lru" -version = "0.12.5" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.17.1", ] [[package]] @@ -5730,57 +5808,99 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "lumamba" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d17e6b978b58809d2995ef9108d0c2e9b05153f9606422cf4c69e610dce63ff" +dependencies = [ + "anyhow", + "clap", + "half", + "libm", + "rayon", + "rlx", + "rustfft", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "luna-rs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7ea83f9eb94e4abb51a78206b8576ee70c990ac300b56c4fc54b625ab85258" +dependencies = [ + "anyhow", + "bytemuck", + "clap", + "exg", + "exg-luna", + "half", + "libm", + "ndarray 0.17.2", + "rayon", + "rlx", + "rustfft", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "lz4_flex" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" +dependencies = [ + "twox-hash", +] + [[package]] name = "lzma-rust2" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1670343e58806300d87950e3401e820b519b9384281bbabfb15e3636689ffd69" +checksum = "e20f57f9918e5bd7bc58c22cdd70a6afc7375d4dd9683af5f2b34bd3d2bba619" +dependencies = [ + "sha2 0.10.9", +] [[package]] -name = "mac" -version = "0.1.1" +name = "mac-addr" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +checksum = "d3d25b0e0b648a86960ac23b7ad4abb9717601dec6f66c165f5b037f3f03065f" [[package]] name = "mac-notification-sys" -version = "0.6.9" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fd3f75411f4725061682ed91f131946e912859d0044d39c4ec0aac818d7621" +checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca" dependencies = [ "cc", - "objc2 0.6.4", - "objc2-foundation 0.3.2", + "log", + "objc2", + "objc2-foundation", "time", + "uuid", ] [[package]] -name = "macerator" -version = "0.2.10" +name = "mac_address" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b0b2dbe8b22f9e96ba12e29964889010117f92e6bd006010887320ae58e2f0" +checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" dependencies = [ - "bytemuck", - "cfg_aliases", - "half", - "macerator-macros", - "moddef", - "num-traits", - "paste", - "rustc_version", + "nix 0.29.0", + "winapi", ] [[package]] -name = "macerator-macros" -version = "0.1.4" +name = "macaddr" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ee1819976b67f4d782390c55a75c13401c7a988517f7f8e60a33484dc2e00a" -dependencies = [ - "darling 0.20.11", - "proc-macro2", - "quote", - "syn 2.0.117", -] +checksum = "baee0bbc17ce759db233beb01648088061bf678383130602a298e6998eedb2d8" [[package]] name = "mach2" @@ -5840,28 +5960,20 @@ dependencies = [ [[package]] name = "markup5ever" -version = "0.14.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" dependencies = [ "log", - "phf 0.11.3", - "phf_codegen 0.11.3", - "string_cache", - "string_cache_codegen", "tendril", + "web_atoms", ] [[package]] -name = "match_token" +name = "match_cfg" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] name = "matchers" @@ -5872,23 +5984,33 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - [[package]] name = "matchit" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +[[package]] +name = "matrixcompare" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37832ba820e47c93d66b4360198dccb004b43c74abc3ac1ce1fed54e65a80445" +dependencies = [ + "matrixcompare-core", + "num-traits", +] + +[[package]] +name = "matrixcompare-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0bdabb30db18805d5290b3da7ceaccbddba795620b86c02145d688e04900a73" + [[package]] name = "matrixmultiply" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +checksum = "3f607c237553f086e7043417a51df26b2eb899d3caff94e6a67592ff992fedc7" dependencies = [ "autocfg", "num_cpus", @@ -5898,51 +6020,31 @@ dependencies = [ ] [[package]] -name = "maybe-rayon" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" -dependencies = [ - "cfg-if", - "rayon", -] - -[[package]] -name = "md5" -version = "0.8.0" +name = "memchr" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] -name = "mdns-sd" -version = "0.18.1" +name = "memmap2" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3efd5e35bde8fe2bf6f698a0158c198eade41805bb89788ee59853baeb877" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" dependencies = [ - "fastrand", - "flume", - "if-addrs", - "log", - "mio", - "socket-pktinfo", - "socket2", + "libc", ] [[package]] -name = "memchr" -version = "2.8.0" +name = "memmem" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" [[package]] -name = "memmap2" -version = "0.9.10" +name = "memo-map" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" -dependencies = [ - "libc", - "stable_deref_trait", -] +checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" [[package]] name = "memoffset" @@ -5954,33 +6056,48 @@ dependencies = [ ] [[package]] -name = "metal" -version = "0.29.0" +name = "mendi" +version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +checksum = "e22f15003e7594bd1a24b0591408612d1b7bc191fe3f61e9191a18bd926fc03c" dependencies = [ - "bitflags 2.11.0", - "block", - "core-graphics-types 0.1.3", - "foreign-types 0.5.0", + "anyhow", + "btleplug", + "env_logger", + "futures", "log", - "objc", - "paste", + "prost 0.13.5", + "rand 0.9.5", + "tokio", + "uuid", ] [[package]] -name = "metal" -version = "0.32.0" +name = "micromap" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" + +[[package]] +name = "miette" +version = "7.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" dependencies = [ - "bitflags 2.11.0", - "block", - "core-graphics-types 0.2.0", - "foreign-types 0.5.0", - "log", - "objc", - "paste", + "cfg-if", + "miette-derive", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -5990,10 +6107,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] -name = "minimal-lexical" -version = "0.2.1" +name = "mime_guess" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minijinja" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3d648e68cea56d9858d535ee28f9538404e2dd8cb08ed0bd05dca379477f39" +dependencies = [ + "memo-map", + "serde", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "minisign-verify" @@ -6013,21 +6150,32 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "log", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.61.2", ] [[package]] -name = "moddef" -version = "0.3.0" +name = "moka" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0b3262dc837d2513fe2ef31ff8461352ef932dcca31ba0c0abe33547cf6b9b" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] [[package]] name = "monostate" @@ -6048,14 +6196,14 @@ checksum = "e4db6d5580af57bf992f59068d4ea26fd518574ff48d7639b255a36f9de6e7e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "moxcms" -version = "0.7.11" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" dependencies = [ "num-traits", "pxfm", @@ -6063,68 +6211,286 @@ dependencies = [ [[package]] name = "muda" -version = "0.17.1" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" +checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" dependencies = [ "crossbeam-channel", "dpi", "gtk", "keyboard-types", - "objc2 0.6.4", + "objc2", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.2", + "objc2-foundation", "once_cell", - "png 0.17.16", + "png 0.18.1", "serde", - "thiserror 2.0.18", + "thiserror 2.0.19", "windows-sys 0.60.2", ] +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "multiversion" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edb7f0ff51249dfda9ab96b5823695e15a052dc15074c9dbf3d118afaf2c201" +dependencies = [ + "multiversion-macros", + "target-features", +] + +[[package]] +name = "multiversion-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b093064383341eb3271f42e381cb8f10a01459478446953953c75d24bd339fc0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "target-features", +] + [[package]] name = "muse-rs" -version = "0.1.0" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "591da56ca151661e2cb17dc83a760ea498cace4ea1a10c9d2547e5837e72fbd6" +dependencies = [ + "anyhow", + "env_logger", + "log", + "serde", + "serde_json", + "tokio", + "uuid", + "webbluetooth", + "webbluetooth-wasm", +] + +[[package]] +name = "mw75" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8573c4cf142f2571ba34f5db2824003dcb6711fd9894b2a4fae4795cc041b4a" +checksum = "8886e4b7b1a83160427e4820ecb22980b91586ea8257a9070ae02dcf8254b60c" dependencies = [ "anyhow", + "bluer", "btleplug", + "crossterm", "env_logger", "futures", + "libc", "log", + "objc2", + "objc2-foundation", + "objc2-io-bluetooth", + "rand 0.9.5", + "ratatui", "serde", "serde_json", "tokio", "uuid", + "windows 0.62.2", +] + +[[package]] +name = "n0-error" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c37e81176a83a77d2514528b91bdafc70ef88aab428f0e1b91aebb8d99888895" +dependencies = [ + "n0-error-macros", + "spez", +] + +[[package]] +name = "n0-error-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2acd8b070213b0299282f884b4beba4e7b52d624fdcd504a3ad3665390c11e1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "n0-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ab99dfb861450e68853d34ae665243a88b8c493d01ba957321a1e9b2312bbe" +dependencies = [ + "cfg_aliases", + "derive_more", + "futures-buffered", + "futures-lite", + "futures-util", + "js-sys", + "pin-project", + "send_wrapper", + "tokio", + "tokio-util", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-time", +] + +[[package]] +name = "n0-watcher" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc618745ad0b7414b149d0517ad8b5573b2fb4d4e2717add3d2446ce1fdd826" +dependencies = [ + "derive_more", + "n0-error", + "n0-future", ] [[package]] name = "naga" -version = "26.0.0" +version = "30.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916cbc7cb27db60be930a4e2da243cf4bc39569195f22fd8ee419cd31d5b662c" +checksum = "23bf0a141a9ab6f07dbb492db53245e464bc9db42f407772d9ae03d83a2c1033" dependencies = [ "arrayvec", - "bit-set", - "bitflags 2.11.0", + "bit-set 0.10.0", + "bitflags 2.13.1", "cfg-if", "cfg_aliases", "codespan-reporting", "half", - "hashbrown 0.15.5", - "hexf-parse", - "indexmap 2.13.0", + "hashbrown 0.17.1", + "indexmap 2.14.0", "libm", "log", + "naga-types", "num-traits", "once_cell", "rustc-hash 1.1.0", "spirv", - "thiserror 2.0.18", + "thiserror 2.0.19", "unicode-ident", ] +[[package]] +name = "naga-types" +version = "30.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658200ddc25c6c7b860747516d132d1b284c0fafb7a380233acee9a72fb30e11" +dependencies = [ + "hashbrown 0.17.1", + "indexmap 2.14.0", + "rustc-hash 1.1.0", + "thiserror 2.0.19", +] + +[[package]] +name = "nalgebra" +version = "0.32.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "rand 0.8.7", + "rand_distr", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "nano-gemm" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb5ba2bea1c00e53de11f6ab5bd0761ba87dc0045d63b0c87ee471d2d3061376" +dependencies = [ + "equator 0.2.2", + "nano-gemm-c32", + "nano-gemm-c64", + "nano-gemm-codegen", + "nano-gemm-core", + "nano-gemm-f32", + "nano-gemm-f64", + "num-complex", +] + +[[package]] +name = "nano-gemm-c32" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a40449e57a5713464c3a1208c4c3301c8d29ee1344711822cf022bc91373a91b" +dependencies = [ + "nano-gemm-codegen", + "nano-gemm-core", + "num-complex", +] + +[[package]] +name = "nano-gemm-c64" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743a6e6211358fba85d1009616751e4107da86f4c95b24e684ce85f25c25b3bf" +dependencies = [ + "nano-gemm-codegen", + "nano-gemm-core", + "num-complex", +] + +[[package]] +name = "nano-gemm-codegen" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963bf7c7110d55430169dc74c67096375491ed580cd2ef84842550ac72e781fa" + +[[package]] +name = "nano-gemm-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe3fc4f83ae8861bad79dc3c016bd6b0220da5f9de302e07d3112d16efc24aa6" + +[[package]] +name = "nano-gemm-f32" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3681b7ce35658f79da94b7f62c60a005e29c373c7111ed070e3bf64546a8bb" +dependencies = [ + "nano-gemm-codegen", + "nano-gemm-core", +] + +[[package]] +name = "nano-gemm-f64" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc1e619ed04d801809e1f63e61b669d380c4119e8b0cdd6ed184c6b111f046d8" +dependencies = [ + "nano-gemm-codegen", + "nano-gemm-core", +] + [[package]] name = "native-tls" version = "0.2.18" @@ -6137,32 +6503,19 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", + "security-framework 3.7.0", "security-framework-sys", "tempfile", ] -[[package]] -name = "nb" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" -dependencies = [ - "nb 1.1.0", -] - -[[package]] -name = "nb" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" - [[package]] name = "ndarray" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" dependencies = [ + "cblas-sys", + "libc", "matrixmultiply", "num-complex", "num-integer", @@ -6170,6 +6523,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "rawpointer", + "rayon", ] [[package]] @@ -6178,6 +6532,8 @@ version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" dependencies = [ + "cblas-sys", + "libc", "matrixmultiply", "num-complex", "num-integer", @@ -6194,8 +6550,8 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.11.0", - "jni-sys", + "bitflags 2.13.1", + "jni-sys 0.3.1", "log", "ndk-sys", "num_enum", @@ -6215,114 +6571,369 @@ version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" dependencies = [ - "jni-sys", + "jni-sys 0.3.1", ] [[package]] -name = "neutts" -version = "0.0.4" +name = "netdev" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683f2af09abe06e6bdbdfbd36963da1af8679c6fbfc26f8c44f6877acd7298c3" +checksum = "569dfbdd2efd771b24ec9bb57f956e04d4fbfc72f62b2f11961723f9b3f4b020" dependencies = [ - "anyhow", - "burn", - "dirs", - "fancy-regex", - "hf-hub 0.5.0", - "hound", - "llama-cpp-2", - "memmap2", - "ndarray 0.16.1", + "block2", + "dispatch2", + "dlopen2", + "ipnet", + "jni 0.21.1", + "libc", + "mac-addr", + "ndk-context", + "netlink-packet-core", + "netlink-packet-route", + "netlink-sys", + "objc2", + "objc2-core-foundation", + "objc2-core-wlan", + "objc2-foundation", + "objc2-system-configuration", "once_cell", - "rand 0.8.5", - "regex", - "rustfft", - "safetensors 0.5.3", - "serde", - "serde_json", - "sha2", - "thiserror 1.0.69", - "zip 2.4.2", + "plist", + "windows-sys 0.61.2", ] [[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nix" -version = "0.26.4" +name = "netlink-packet-core" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4" dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", + "paste", ] [[package]] -name = "nix" -version = "0.31.2" +name = "netlink-packet-route" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" +checksum = "e2288fcb784eb3defd5fb16f4c4160d5f477de192eac730f43e1d11c24d9a007" dependencies = [ - "bitflags 2.11.0", - "cfg-if", - "cfg_aliases", + "bitflags 2.13.1", "libc", + "log", + "netlink-packet-core", ] [[package]] -name = "no-std-compat" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df270209a7f04d62459240d890ecb792714d5db12c92937823574a09930276b4" - -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - -[[package]] -name = "nom" -version = "7.1.3" +name = "netlink-proto" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "b65d130ee111430e47eed7896ea43ca693c387f097dd97376bffafbf25812128" dependencies = [ - "memchr", - "minimal-lexical", + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror 2.0.19", ] [[package]] -name = "nom" -version = "8.0.0" +name = "netlink-sys" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae" dependencies = [ - "memchr", + "bytes", + "futures-util", + "libc", + "log", + "tokio", ] [[package]] -name = "noop_proc_macro" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" - -[[package]] -name = "notify-rust" -version = "4.12.0" +name = "netwatch" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21af20a1b50be5ac5861f74af1a863da53a11c38684d9818d82f1c42f7fdc6c2" +checksum = "4d9cbe01741347ef750d743d6690603f5eed8341e679fb51c8e629337aa11976" dependencies = [ - "futures-lite", + "atomic-waker", + "bytes", + "cfg_aliases", + "derive_more", + "ipnet", + "js-sys", + "libc", + "n0-error", + "n0-future", + "n0-watcher", + "netdev", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "netlink-sys", + "noq-udp", + "objc2-core-foundation", + "objc2-system-configuration", + "pin-project-lite", + "serde", + "socket2 0.6.5", + "time", + "tokio", + "tokio-util", + "tracing", + "web-sys", + "windows 0.62.2", + "windows-result 0.4.1", + "wmi", +] + +[[package]] +name = "neurofield" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13eaa73c77084513832d5f142cd4b5f5e8cc8fa0d8078213e94b40a15a19210" +dependencies = [ + "env_logger", + "libloading 0.8.9", + "log", + "thiserror 2.0.19", +] + +[[package]] +name = "neurorvq-rs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd07dc38966199465efe54a82bfe066e96cc256e00c0bd8e3f0637b69c10bbc5" +dependencies = [ + "anyhow", + "bytemuck", + "clap", + "half", + "libm", + "ndarray 0.16.1", + "rayon", + "rlx", + "safetensors 0.7.0", + "serde", + "serde_json", + "serde_yaml", +] + +[[package]] +name = "neurosity" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1eff82cf68a76b6bffb3dbada16100d404ce17b254d2599b866c44439be502be" +dependencies = [ + "env_logger", + "libc", + "log", + "serde", + "serde_json", + "thiserror 2.0.19", + "ureq 2.12.1", +] + +[[package]] +name = "neurosky" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77b2f902d736135d528ef4af4767cf2099d8a64da6ab2146093b446ac084b80" +dependencies = [ + "env_logger", + "libc", + "log", + "serialport", + "thiserror 2.0.19", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noq" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11803df44ac03a30988d61585ea50885d5428e42da944fe1e498799da7886a2" +dependencies = [ + "bytes", + "cfg_aliases", + "derive_more", + "noq-proto", + "noq-udp", + "pin-project-lite", + "rustc-hash 2.1.3", + "rustls", + "socket2 0.5.10", + "thiserror 2.0.19", + "tokio", + "tokio-stream", + "tracing", + "web-time", +] + +[[package]] +name = "noq-proto" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "334c3c9833f7b2c573cceb9896ddc7aaeb58c8807cbb63211b24d1fe88bf866e" +dependencies = [ + "aes-gcm", + "bytes", + "derive_more", + "enum-assoc", + "getrandom 0.4.3", + "identity-hash", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash 2.1.3", + "rustls", + "rustls-pki-types", + "slab", + "sorted-index-buffer", + "thiserror 2.0.19", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "noq-udp" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde7a5d5102f1cff03d482240f0ed20551661f63663620f4b26112ed751165e9" +dependencies = [ + "cfg_aliases", + "libc", + "socket2 0.5.10", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.13.1", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-rust" +version = "4.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b4c1b4f2aa9f25f63a7a49d3dd0ed567b3670da15330a66b29434be899b891" +dependencies = [ + "futures-lite", "log", "mac-notification-sys", "serde", "tauri-winrt-notification", - "zbus", + "zbus 5.18.0", +] + +[[package]] +name = "notify-types" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "noyalib" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e493c05128df7a83b9676b709d590e0ebc285c7ed3152bc679668e8c1e506af5" +dependencies = [ + "indexmap 2.14.0", + "memchr", + "rustc-hash 2.1.3", + "serde", + "smallvec", +] + +[[package]] +name = "npyz" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f0e759e014e630f90af745101b614f761306ddc541681e546649068e25ec1b9" +dependencies = [ + "byteorder", + "half", + "num-bigint", + "py_literal", + "zip 0.6.6", ] [[package]] @@ -6340,7 +6951,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -6359,14 +6970,20 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", ] +[[package]] +name = "num-cmp" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" + [[package]] name = "num-complex" version = "0.4.6" @@ -6375,13 +6992,14 @@ checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "bytemuck", "num-traits", + "rand 0.8.7", ] [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-derive" @@ -6391,7 +7009,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -6405,11 +7023,10 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -6447,9 +7064,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ "num_enum_derive", "rustversion", @@ -6457,14 +7074,14 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -6476,35 +7093,6 @@ dependencies = [ "libc", ] -[[package]] -name = "number_prefix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" - -[[package]] -name = "nvml-wrapper" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d5c6c0ef9702176a570f06ad94f3198bc29c524c8b498f1b9346e1b1bdcbb3a" -dependencies = [ - "bitflags 2.11.0", - "libloading 0.8.9", - "nvml-wrapper-sys", - "static_assertions", - "thiserror 1.0.69", - "wrapcenum-derive", -] - -[[package]] -name = "nvml-wrapper-sys" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd23dbe2eb8d8335d2bce0299e0a07d6a63c089243d626ca75b770a962ff49e6" -dependencies = [ - "libloading 0.8.9", -] - [[package]] name = "objc" version = "0.2.7" @@ -6514,22 +7102,6 @@ dependencies = [ "malloc_buf", ] -[[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" - -[[package]] -name = "objc2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" -dependencies = [ - "objc-sys", - "objc2-encode", -] - [[package]] name = "objc2" version = "0.6.4" @@ -6546,10 +7118,10 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.11.0", - "block2 0.6.2", + "bitflags 2.13.1", + "block2", "libc", - "objc2 0.6.4", + "objc2", "objc2-cloud-kit", "objc2-core-data", "objc2-core-foundation", @@ -6557,7 +7129,7 @@ dependencies = [ "objc2-core-image", "objc2-core-text", "objc2-core-video", - "objc2-foundation 0.3.2", + "objc2-foundation", "objc2-quartz-core", ] @@ -6567,34 +7139,56 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "libc", - "objc2 0.6.4", + "objc2", "objc2-core-audio", "objc2-core-audio-types", "objc2-core-foundation", - "objc2-foundation 0.3.2", + "objc2-foundation", ] [[package]] -name = "objc2-avf-audio" +name = "objc2-av-foundation" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be" +checksum = "478ae33fcac9df0a18db8302387c666b8ef08a3e2d62b510ca4fc278a384b6c0" dependencies = [ - "objc2 0.6.4", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" + "bitflags 2.13.1", + "block2", + "dispatch2", + "objc2", + "objc2-avf-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-video", + "objc2-foundation", + "objc2-image-io", + "objc2-media-toolbox", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-avf-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "bitflags 2.11.0", - "objc2 0.6.4", - "objc2-foundation 0.3.2", + "bitflags 2.13.1", + "objc2", + "objc2-foundation", ] [[package]] @@ -6604,10 +7198,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2" dependencies = [ "dispatch2", - "objc2 0.6.4", + "objc2", "objc2-core-audio-types", "objc2-core-foundation", - "objc2-foundation 0.3.2", + "objc2-foundation", ] [[package]] @@ -6616,19 +7210,8 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" dependencies = [ - "bitflags 2.11.0", - "objc2 0.6.4", -] - -[[package]] -name = "objc2-core-bluetooth" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a644b62ffb826a5277f536cf0f701493de420b13d40e700c452c36567771111" -dependencies = [ - "bitflags 2.11.0", - "objc2 0.5.2", - "objc2-foundation 0.2.2", + "bitflags 2.13.1", + "objc2", ] [[package]] @@ -6637,9 +7220,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" dependencies = [ - "bitflags 2.11.0", - "objc2 0.6.4", - "objc2-foundation 0.3.2", + "bitflags 2.13.1", + "objc2", + "objc2-foundation", ] [[package]] @@ -6648,11 +7231,11 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.0", - "block2 0.6.2", + "bitflags 2.13.1", + "block2", "dispatch2", "libc", - "objc2 0.6.4", + "objc2", ] [[package]] @@ -6661,11 +7244,14 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", + "block2", "dispatch2", - "objc2 0.6.4", + "libc", + "objc2", "objc2-core-foundation", "objc2-io-surface", + "objc2-metal", ] [[package]] @@ -6674,8 +7260,34 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" dependencies = [ - "objc2 0.6.4", - "objc2-foundation 0.3.2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-media" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ec576860167a15dd9fce7fbee7512beb4e31f532159d3482d1f9c6caedf31d" +dependencies = [ + "bitflags 2.13.1", + "block2", + "dispatch2", + "objc2", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-video", ] [[package]] @@ -6684,8 +7296,8 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags 2.11.0", - "objc2 0.6.4", + "bitflags 2.13.1", + "objc2", "objc2-core-foundation", "objc2-core-graphics", ] @@ -6696,11 +7308,27 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" dependencies = [ - "bitflags 2.11.0", - "objc2 0.6.4", + "bitflags 2.13.1", + "block2", + "objc2", "objc2-core-foundation", "objc2-core-graphics", "objc2-io-surface", + "objc2-metal", +] + +[[package]] +name = "objc2-core-wlan" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e34919aba0d701380d911702455038a8a3587467fe0141d6a71501e7ffe48" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-security", + "objc2-security-foundation", ] [[package]] @@ -6720,27 +7348,38 @@ dependencies = [ [[package]] name = "objc2-foundation" -version = "0.2.2" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.11.0", - "block2 0.5.1", + "bitflags 2.13.1", + "block2", "libc", - "objc2 0.5.2", + "objc2", + "objc2-core-foundation", ] [[package]] -name = "objc2-foundation" +name = "objc2-image-io" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +checksum = "32b0446e98cf4a784cc7a0177715ff317eeaa8463841c616cfc78aa4f953c4ea" +dependencies = [ + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-io-bluetooth" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558b52e7b77f6a68e1e2cc700ff2266779ede47bacb260a33d363b9a623cc761" dependencies = [ - "bitflags 2.11.0", - "block2 0.6.2", "libc", - "objc2 0.6.4", + "objc2", "objc2-core-foundation", + "objc2-foundation", ] [[package]] @@ -6759,9 +7398,21 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.11.0", - "objc2 0.6.4", + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-media-toolbox" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd9fdde720df3da7046bb9097811000c1e7ab5cd579fa89d96b27d56781fb30" +dependencies = [ + "objc2", + "objc2-core-audio-types", "objc2-core-foundation", + "objc2-core-media", ] [[package]] @@ -6770,12 +7421,21 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" dependencies = [ - "bitflags 2.11.0", - "block2 0.6.2", - "dispatch2", - "objc2 0.6.4", + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-open-directory" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb82bed227edf5201dfedf072bba4015a33d3d4a98519837295a90f0a23f676d" +dependencies = [ + "objc2", "objc2-core-foundation", - "objc2-foundation 0.3.2", + "objc2-foundation", ] [[package]] @@ -6784,10 +7444,10 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" dependencies = [ - "bitflags 2.11.0", - "objc2 0.6.4", + "bitflags 2.13.1", + "objc2", "objc2-app-kit", - "objc2-foundation 0.3.2", + "objc2-foundation", ] [[package]] @@ -6796,10 +7456,47 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.11.0", - "objc2 0.6.4", + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-security" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-security-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef76382e9cedd18123099f17638715cc3d81dba3637d4c0d39ab69df2ef345a5" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-system-configuration" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "libc", + "objc2", "objc2-core-foundation", - "objc2-foundation 0.3.2", + "objc2-security", ] [[package]] @@ -6808,10 +7505,29 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.11.0", - "objc2 0.6.4", + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", "objc2-core-foundation", - "objc2-foundation 0.3.2", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", ] [[package]] @@ -6820,12 +7536,21 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" dependencies = [ - "bitflags 2.11.0", - "block2 0.6.2", - "objc2 0.6.4", + "bitflags 2.13.1", + "block2", + "objc2", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.2", + "objc2-foundation", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", ] [[package]] @@ -6839,9 +7564,13 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] [[package]] name = "once_cell_polyfill" @@ -6851,11 +7580,11 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "onig" -version = "6.5.1" +version = "6.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" +checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "libc", "once_cell", "onig_sys", @@ -6863,24 +7592,35 @@ dependencies = [ [[package]] name = "onig_sys" -version = "69.9.1" +version = "69.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" +checksum = "1e68317604e77e53b85896388e1a803c1d21b74c899ec9e5e1112db90735edd7" dependencies = [ "cc", "pkg-config", ] +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "open" -version = "5.3.3" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" +checksum = "a0b3d059e795d52b8a72fef45658620edd4d9c359b338564aa14391ffa511ed5" dependencies = [ "dunce", "is-wsl", "libc", - "pathdiff", ] [[package]] @@ -6900,17 +7640,42 @@ dependencies = [ "ureq 2.12.1", ] +[[package]] +name = "openblas-build" +version = "0.10.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb9c85e9e7dd5acdc67b9f3f0c99656b550df716bc63540c6a224a920754a5c2" +dependencies = [ + "anyhow", + "cc", + "flate2", + "tar", + "thiserror 2.0.19", + "ureq 3.3.0", +] + +[[package]] +name = "openblas-src" +version = "0.10.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a81a5e467f1861ad6ac32d5ec1690ad097d19854753b7424250fe27da46b98" +dependencies = [ + "dirs", + "openblas-build", + "pkg-config", + "vcpkg", +] + [[package]] name = "openssl" -version = "0.10.75" +version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "cfg-if", "foreign-types 0.3.2", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -6923,7 +7688,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -6934,9 +7699,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.111" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" dependencies = [ "cc", "libc", @@ -6952,9 +7717,18 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ordered-float" -version = "5.0.0" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-float" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2c1f9f56e534ac6a9b8a4600bdf0f530fb393b5f393e7b4d03489c3cf0c3f01" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" dependencies = [ "num-traits", ] @@ -6970,42 +7744,100 @@ dependencies = [ ] [[package]] -name = "ort" -version = "2.0.0-rc.11" +name = "orx-priority-queue" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89722d987db848624cf8fca21245d59cf6382b01b4ca79ae70261cea5747495" + +[[package]] +name = "osakit" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5df903c0d2c07b56950f1058104ab0c8557159f2741782223704de9be73c3c" +checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" dependencies = [ - "ndarray 0.17.2", - "ort-sys", - "smallvec", + "objc2", + "objc2-foundation", + "objc2-osa-kit", + "serde", + "serde_json", + "thiserror 2.0.19", +] + +[[package]] +name = "oura-api" +version = "0.1.2" +dependencies = [ + "paste", + "reqwest 0.12.28", + "serde", + "typed-builder", +] + +[[package]] +name = "oura-link" +version = "0.1.0" +source = "git+https://github.com/Th0rgal/open_oura?rev=e57c43ce5fe0cf9a646afa790bda3731be767507#e57c43ce5fe0cf9a646afa790bda3731be767507" +dependencies = [ + "async-trait", + "futures", + "hex", + "oura-protocol", + "thiserror 1.0.69", + "tokio", "tracing", - "ureq 3.2.0", + "uuid", +] + +[[package]] +name = "oura-protocol" +version = "0.1.0" +source = "git+https://github.com/Th0rgal/open_oura?rev=e57c43ce5fe0cf9a646afa790bda3731be767507#e57c43ce5fe0cf9a646afa790bda3731be767507" +dependencies = [ + "aes 0.8.4", + "hex", + "serde", + "serde_json", + "uuid", ] [[package]] -name = "ort-sys" -version = "2.0.0-rc.11" +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "page_size" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06503bb33f294c5f1ba484011e053bfa6ae227074bdb841e9863492dc5960d4b" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" dependencies = [ - "hmac-sha256", - "lzma-rust2", - "pkg-config", - "ureq 3.2.0", + "libc", + "winapi", ] [[package]] -name = "osakit" -version = "0.3.1" +name = "palette" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" dependencies = [ - "objc2 0.6.4", - "objc2-foundation 0.3.2", - "objc2-osa-kit", - "serde", - "serde_json", - "thiserror 2.0.18", + "approx", + "fast-srgb8", + "libm", + "palette_derive", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -7030,7 +7862,17 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 6.2.2", +] + +[[package]] +name = "papaya" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "997ee03cd38c01469a7046643714f0ad28880bcb9e6679ff0666e24817ca19b7" +dependencies = [ + "equivalent", + "seize", ] [[package]] @@ -7057,11 +7899,40 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link 0.2.1", ] +[[package]] +name = "parquet" +version = "58.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d298093b2dec60289dce0684c986d0f7679e9dd15771c2c65406e1aaf604a704" +dependencies = [ + "ahash", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-ipc", + "arrow-schema", + "arrow-select", + "base64 0.22.1", + "bytes", + "chrono", + "half", + "hashbrown 0.17.1", + "num-bigint", + "num-integer", + "num-traits", + "paste", + "seq-macro", + "snap", + "thrift", + "twox-hash", + "zstd 0.13.3", +] + [[package]] name = "password-hash" version = "0.4.2" @@ -7080,91 +7951,102 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "pastey" -version = "0.1.1" +name = "pbkdf2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", + "hmac", + "password-hash", + "sha2 0.10.9", +] [[package]] -name = "pathdiff" -version = "0.2.3" +name = "pbkdf2" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac", +] [[package]] -name = "pathfinder_geometry" -version = "0.5.1" +name = "pem-rfc7468" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" dependencies = [ - "log", - "pathfinder_simd", + "base64ct", ] [[package]] -name = "pathfinder_simd" -version = "0.5.5" +name = "percent-encoding" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf9027960355bf3afff9841918474a81a5f972ac6d226d518060bba758b5ad57" -dependencies = [ - "rustc_version", -] +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] -name = "pbkdf2" -version = "0.11.0" +name = "pest" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" dependencies = [ - "digest", - "hmac", - "password-hash", - "sha2", + "memchr", + "ucd-trie", ] [[package]] -name = "pem-rfc7468" -version = "0.7.0" +name = "pest_derive" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd" dependencies = [ - "base64ct", + "pest", + "pest_generator", ] [[package]] -name = "percent-encoding" -version = "2.3.2" +name = "pest_generator" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] -name = "petgraph" -version = "0.6.5" +name = "pest_meta" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c" dependencies = [ - "fixedbitset", - "indexmap 2.13.0", + "pest", ] [[package]] -name = "phf" -version = "0.8.0" +name = "petgraph" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ - "phf_shared 0.8.0", + "fixedbitset 0.5.7", + "indexmap 2.14.0", ] [[package]] -name = "phf" -version = "0.10.1" +name = "pharos" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" dependencies = [ - "phf_macros 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", + "futures", + "rustc_version", ] [[package]] @@ -7178,13 +8060,14 @@ dependencies = [ ] [[package]] -name = "phf_codegen" -version = "0.8.0" +name = "phf" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", + "phf_macros 0.13.1", + "phf_shared 0.13.1", + "serde", ] [[package]] @@ -7198,100 +8081,104 @@ dependencies = [ ] [[package]] -name = "phf_generator" -version = "0.8.0" +name = "phf_codegen" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", + "phf_generator 0.13.1", + "phf_shared 0.13.1", ] [[package]] name = "phf_generator" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.5", + "phf_shared 0.11.3", + "rand 0.8.7", ] [[package]] name = "phf_generator" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.5", + "fastrand", + "phf_shared 0.13.1", ] [[package]] name = "phf_macros" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", + "phf_generator 0.11.3", + "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.119", ] [[package]] name = "phf_macros" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", + "phf_generator 0.13.1", + "phf_shared 0.13.1", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "phf_shared" -version = "0.8.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher 0.3.11", + "siphasher", ] [[package]] name = "phf_shared" -version = "0.10.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" dependencies = [ - "siphasher 0.3.11", + "siphasher", ] [[package]] -name = "phf_shared" -version = "0.11.3" +name = "pin-project" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ - "siphasher 1.0.2", + "pin-project-internal", ] [[package]] -name = "pin-project-lite" -version = "0.2.17" +name = "pin-project-internal" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "pin-project-lite" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "piper" @@ -7305,26 +8192,55 @@ dependencies = [ ] [[package]] -name = "pkg-config" -version = "0.3.32" +name = "pipewire" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "8585aba8a52ad74ccc633b8e293c1dc4277976bd5d510b925533f34fd6685f38" +dependencies = [ + "bitflags 2.13.1", + "libc", + "libspa", + "libspa-sys", + "pipewire-sys", + "rustix 1.1.4", +] [[package]] -name = "plain" -version = "0.2.3" +name = "pipewire-sys" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2089f245b548723e60325773c27f586b7a2372c79ea941b246cd0d654706adc" +dependencies = [ + "bindgen", + "libspa-sys", + "system-deps 7.0.8", +] + +[[package]] +name = "pkcs8" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plist" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" dependencies = [ "base64 0.22.1", - "indexmap 2.13.0", - "quick-xml 0.38.4", + "indexmap 2.14.0", + "quick-xml 0.41.0", "serde", "time", ] @@ -7335,16 +8251,9 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ - "chrono", - "font-kit", - "image 0.24.9", - "lazy_static", "num-traits", - "pathfinder_geometry", "plotters-backend", - "plotters-bitmap", "plotters-svg", - "ttf-parser", "wasm-bindgen", "web-sys", ] @@ -7355,17 +8264,6 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" -[[package]] -name = "plotters-bitmap" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ce181e3f6bf82d6c1dc569103ca7b1bd964c60ba03d7e6cdfbb3e3eb7f7405" -dependencies = [ - "gif 0.12.0", - "image 0.24.9", - "plotters-backend", -] - [[package]] name = "plotters-svg" version = "0.3.7" @@ -7394,7 +8292,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "crc32fast", "fdeflate", "flate2", @@ -7415,35 +8313,106 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + [[package]] name = "pollster" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + [[package]] name = "portable-atomic" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" dependencies = [ "serde", ] [[package]] name = "portable-atomic-util" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] +[[package]] +name = "portmapper" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3713e4977408279158444a18c1a01ac9bf2e7eaf1fbfd1a19ac9cd18d90721" +dependencies = [ + "base64 0.22.1", + "bytes", + "derive_more", + "hyper-util", + "igd-next", + "iroh-metrics", + "libc", + "n0-error", + "n0-future", + "netwatch", + "num_enum", + "rand 0.10.2", + "serde", + "smallvec", + "socket2 0.6.5", + "time", + "tokio", + "tokio-util", + "tower-layer", + "tracing", + "url", +] + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "postcard-derive", + "serde", +] + +[[package]] +name = "postcard-derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0232bd009a197ceec9cc881ba46f727fcd8060a2d8d6a9dde7a69030a6fe2bb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -7454,6 +8423,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppmd-rust" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -7469,6 +8444,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "prefix-trie" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf6e3177f0684016a5c209b00882e15f8bdd3f3bb48f0491df10cd102d0c6e7" +dependencies = [ + "either", + "ipnet", + "num-traits", +] + [[package]] name = "presser" version = "0.3.1" @@ -7482,16 +8468,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] -name = "prettytable" +name = "prettytable-rs" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46480520d1b77c9a3482d39939fcf96831537a250ec62d4fd8fbdf8e0302e781" +checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a" dependencies = [ - "csv", "encode_unicode", "is-terminal", "lazy_static", @@ -7534,7 +8519,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.4+spec-1.1.0", + "toml_edit 0.25.13+spec-1.1.0", ] [[package]] @@ -7561,233 +8546,368 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" -version = "1.0.17" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" + +[[package]] +name = "proptest" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ - "profiling-procmacros", + "bit-set 0.8.0", + "bit-vec 0.8.0", + "bitflags 2.13.1", + "num-traits", + "rand 0.9.5", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", ] [[package]] -name = "profiling-procmacros" -version = "1.0.17" +name = "prost" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" dependencies = [ - "quote", - "syn 2.0.117", + "bytes", + "prost-derive 0.13.5", ] [[package]] -name = "pulp" -version = "0.21.5" +name = "prost" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b86df24f0a7ddd5e4b95c94fc9ed8a98f1ca94d3b01bdce2824097e7835907" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" dependencies = [ - "bytemuck", - "cfg-if", - "libm", - "num-complex", - "reborrow", - "version_check", + "bytes", + "prost-derive 0.14.4", ] [[package]] -name = "pulp" -version = "0.22.2" +name = "prost-build" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e205bb30d5b916c55e584c22201771bcf2bad9aabd5d4127f38387140c38632" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ - "bytemuck", - "cfg-if", - "libm", - "num-complex", - "paste", - "pulp-wasm-simd-flag", - "raw-cpuid", - "reborrow", - "version_check", + "heck 0.4.1", + "itertools 0.14.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost 0.13.5", + "prost-types", + "regex", + "syn 2.0.119", + "tempfile", ] [[package]] -name = "pulp-wasm-simd-flag" -version = "0.1.0" +name = "prost-derive" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40e24eee682d89fb193496edf918a7f407d30175b2e785fe057e4392dfd182e0" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] -name = "pxfm" -version = "0.1.28" +name = "prost-derive" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] -name = "qoi" -version = "0.4.1" +name = "prost-reflect" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +checksum = "7b5edd582b62f5cde844716e66d92565d7faf7ab1445c8cebce6e00fba83ddb2" dependencies = [ - "bytemuck", + "logos", + "miette", + "once_cell", + "prost 0.13.5", + "prost-types", ] [[package]] -name = "quick-error" -version = "2.0.1" +name = "prost-types" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost 0.13.5", +] [[package]] -name = "quick-xml" -version = "0.37.5" +name = "protobuf" +version = "1.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" -dependencies = [ - "memchr", -] +checksum = "e14ccd6b79ec748412d4f2dfde1a80fa363a67def4062969f8aed3d790a30f28" [[package]] -name = "quick-xml" -version = "0.38.4" +name = "protoc-bin-vendored" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +checksum = "d1c381df33c98266b5f08186583660090a4ffa0889e76c7e9a5e175f645a67fa" dependencies = [ - "memchr", + "protoc-bin-vendored-linux-aarch_64", + "protoc-bin-vendored-linux-ppcle_64", + "protoc-bin-vendored-linux-s390_64", + "protoc-bin-vendored-linux-x86_32", + "protoc-bin-vendored-linux-x86_64", + "protoc-bin-vendored-macos-aarch_64", + "protoc-bin-vendored-macos-x86_64", + "protoc-bin-vendored-win32", ] [[package]] -name = "quinn" -version = "0.11.9" +name = "protoc-bin-vendored-linux-aarch_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c350df4d49b5b9e3ca79f7e646fde2377b199e13cfa87320308397e1f37e1a4c" + +[[package]] +name = "protoc-bin-vendored-linux-ppcle_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55a63e6c7244f19b5c6393f025017eb5d793fd5467823a099740a7a4222440c" + +[[package]] +name = "protoc-bin-vendored-linux-s390_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dba5565db4288e935d5330a07c264a4ee8e4a5b4a4e6f4e83fad824cc32f3b0" + +[[package]] +name = "protoc-bin-vendored-linux-x86_32" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8854774b24ee28b7868cd71dccaae8e02a2365e67a4a87a6cd11ee6cdbdf9cf5" + +[[package]] +name = "protoc-bin-vendored-linux-x86_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b38b07546580df720fa464ce124c4b03630a6fb83e05c336fea2a241df7e5d78" + +[[package]] +name = "protoc-bin-vendored-macos-aarch_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89278a9926ce312e51f1d999fee8825d324d603213344a9a706daa009f1d8092" + +[[package]] +name = "protoc-bin-vendored-macos-x86_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81745feda7ccfb9471d7a4de888f0652e806d5795b61480605d4943176299756" + +[[package]] +name = "protoc-bin-vendored-win32" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95067976aca6421a523e491fce939a3e65249bac4b977adee0ee9771568e8aa3" + +[[package]] +name = "protox" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "6f352af331bf637b8ecc720f7c87bf903d2571fa2e14a66e9b2558846864b54a" dependencies = [ "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash 2.1.1", - "rustls", - "socket2", - "thiserror 2.0.18", - "tokio", - "tracing", - "web-time", + "miette", + "prost 0.13.5", + "prost-reflect", + "prost-types", + "protox-parse", + "thiserror 1.0.69", ] [[package]] -name = "quinn-proto" -version = "0.11.13" +name = "protox-parse" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "a3a462d115462c080ae000c29a47f0b3985737e5d3a995fcdbcaa5c782068dde" dependencies = [ - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash 2.1.1", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.18", - "tinyvec", - "tracing", - "web-time", + "logos", + "miette", + "prost-types", + "thiserror 1.0.69", ] [[package]] -name = "quinn-udp" -version = "0.5.14" +name = "psm" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +checksum = "645dbe486e346d9b5de3ef16ede18c26e6c70ad97418f4874b8b1889d6e761ea" dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.60.2", + "ar_archive_writer", + "cc", ] [[package]] -name = "quote" -version = "1.0.45" +name = "pulp" +version = "0.18.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "a0a01a0dc67cf4558d279f0c25b0962bd08fc6dec0137699eae304103e882fe6" dependencies = [ - "proc-macro2", + "bytemuck", + "libm", + "num-complex", + "reborrow", ] [[package]] -name = "r-efi" -version = "5.3.0" +name = "pulp" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +checksum = "96b86df24f0a7ddd5e4b95c94fc9ed8a98f1ca94d3b01bdce2824097e7835907" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "reborrow", + "version_check", +] [[package]] -name = "r-efi" -version = "6.0.0" +name = "pxfm" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" [[package]] -name = "r2d2" -version = "0.8.10" +name = "py_literal" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +checksum = "102df7a3d46db9d3891f178dcc826dc270a6746277a9ae6436f8d29fd490a8e1" dependencies = [ - "log", - "parking_lot", - "scheduled-thread-pool", + "num-bigint", + "num-complex", + "num-traits", + "pest", + "pest_derive", ] [[package]] -name = "r2d2_sqlite" -version = "0.31.0" +name = "qrcodegen" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63417e83dc891797eea3ad379f52a5986da4bca0d6ef28baf4d14034dd111b0c" +checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142" + +[[package]] +name = "qrcodegen-image" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e3dd60f5b603f72c307455fc52deec52ada1ba53c7580918bb2a8e3247d4fe7" dependencies = [ - "r2d2", - "rusqlite", - "uuid", + "base64 0.22.1", + "image", + "qrcodegen", ] [[package]] -name = "rand" -version = "0.7.3" +name = "quanta" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" dependencies = [ - "getrandom 0.1.16", + "crossbeam-utils", "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", +] + +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" -version = "0.8.5" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -7796,9 +8916,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -7806,23 +8926,13 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ "chacha20", - "getrandom 0.4.2", - "rand_core 0.10.0", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "getrandom 0.4.3", + "rand_core 0.10.1", ] [[package]] @@ -7845,15 +8955,6 @@ dependencies = [ "rand_core 0.9.5", ] -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - [[package]] name = "rand_core" version = "0.6.4" @@ -7874,36 +8975,36 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rand_distr" -version = "0.5.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", - "rand 0.9.2", + "rand 0.8.7", ] [[package]] -name = "rand_hc" -version = "0.2.0" +name = "rand_pcg" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" dependencies = [ - "rand_core 0.5.1", + "rand_core 0.10.1", ] [[package]] -name = "rand_pcg" -version = "0.2.1" +name = "rand_xorshift" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.5.1", + "rand_core 0.9.5", ] [[package]] @@ -7914,105 +9015,146 @@ checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" [[package]] name = "ratatui" -version = "0.29.0" +version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +checksum = "3274ba0a2c5e1bcad2a2005d20f4dc59dad26b2eb0940fb094500dba4099d57d" dependencies = [ - "bitflags 2.11.0", - "cassowary", - "compact_str 0.8.1", - "crossterm", - "indoc", "instability", - "itertools 0.13.0", + "ratatui-core", + "ratatui-crossterm", + "ratatui-macros", + "ratatui-termina", + "ratatui-termwiz", + "ratatui-widgets", + "serde", +] + +[[package]] +name = "ratatui-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c" +dependencies = [ + "bitflags 2.13.1", + "compact_str", + "critical-section", + "hashbrown 0.17.1", + "itertools 0.14.0", + "kasuari", "lru", - "paste", - "strum 0.26.3", - "time", + "palette", + "serde", + "strum 0.28.0", + "thiserror 2.0.19", "unicode-segmentation", "unicode-truncate", - "unicode-width 0.2.0", + "unicode-width 0.2.2", ] [[package]] -name = "rav1e" -version = "0.8.1" +name = "ratatui-crossterm" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +checksum = "567584a3b0e6a8203c23de40b4861497266725eb5363dbfd18a1edd603cca9f0" dependencies = [ - "aligned-vec", - "arbitrary", - "arg_enum_proc_macro", - "arrayvec", - "av-scenechange", - "av1-grain", - "bitstream-io", - "built", "cfg-if", - "interpolate_name", - "itertools 0.14.0", - "libc", - "libfuzzer-sys", - "log", - "maybe-rayon", - "new_debug_unreachable", - "noop_proc_macro", - "num-derive", - "num-traits", - "paste", - "profiling", - "rand 0.9.2", - "rand_chacha 0.9.0", - "simd_helpers", - "thiserror 2.0.18", - "v_frame", - "wasm-bindgen", + "crossterm", + "instability", + "ratatui-core", ] [[package]] -name = "ravif" -version = "0.12.0" +name = "ratatui-macros" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285" +checksum = "ed7dc68daa7498a43e4d68e0eb078427e10c38fbcfbb1e42d955f1fa2140d814" dependencies = [ - "avif-serialize", - "imgref", - "loop9", - "quick-error", - "rav1e", - "rayon", - "rgb", + "ratatui-core", + "ratatui-widgets", ] [[package]] -name = "raw-cpuid" -version = "11.6.0" +name = "ratatui-termina" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +checksum = "c0bf912d9e66f057a759d92e386a280ea886b352ab757d6ac4d653c7ed2c43c2" dependencies = [ - "bitflags 2.11.0", + "instability", + "ratatui-core", + "termina", ] [[package]] -name = "raw-window-handle" -version = "0.6.2" +name = "ratatui-termwiz" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +checksum = "faf03e0380b7744054d6cb74224fe3adf062a029754933f575ca1e3b4c2ce977" +dependencies = [ + "ratatui-core", + "termwiz", +] [[package]] -name = "rawpointer" -version = "0.2.1" +name = "ratatui-widgets" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" +checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1" +dependencies = [ + "bitflags 2.13.1", + "hashbrown 0.17.1", + "indoc", + "instability", + "itertools 0.14.0", + "line-clipping", + "ratatui-core", + "serde", + "strum 0.28.0", + "time", + "unicode-segmentation", + "unicode-width 0.2.2", +] [[package]] -name = "rayon" -version = "1.11.0" +name = "raw-cpuid" +version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "either", - "rayon-core", + "bitflags 2.13.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", ] [[package]] @@ -8036,6 +9178,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "realfft" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f821338fddb99d089116342c46e9f1fbf3828dba077674613e734e01d6ea8677" +dependencies = [ + "rustfft", +] + [[package]] name = "reborrow" version = "0.5.5" @@ -8048,16 +9199,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", -] - -[[package]] -name = "redox_syscall" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" -dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", ] [[package]] @@ -8079,34 +9221,51 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] name = "ref-cast" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", +] + +[[package]] +name = "referencing" +version = "0.46.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbf332a2f81899f6836f22c03da73dae8a664c32e3016b84692c23cddadc95d" +dependencies = [ + "ahash", + "fluent-uri", + "getrandom 0.3.4", + "hashbrown 0.16.1", + "itoa", + "micromap", + "parking_lot", + "percent-encoding", + "serde_json", ] [[package]] name = "regex" -version = "1.12.3" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -8116,9 +9275,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" dependencies = [ "aho-corasick", "memchr", @@ -8127,15 +9286,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - -[[package]] -name = "relative-path" -version = "1.9.3" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "renderdoc-sys" @@ -8169,8 +9322,6 @@ dependencies = [ "native-tls", "percent-encoding", "pin-project-lite", - "quinn", - "rustls", "rustls-pki-types", "serde", "serde_json", @@ -8178,7 +9329,6 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -8188,14 +9338,13 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams 0.4.2", "web-sys", - "webpki-roots 1.0.6", ] [[package]] name = "reqwest" -version = "0.13.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64 0.22.1", "bytes", @@ -8230,23 +9379,29 @@ dependencies = [ "web-sys", ] +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + [[package]] name = "rfd" version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20dafead71c16a34e1ff357ddefc8afc11e7d51d6d2b9fbd07eaa48e3e540220" dependencies = [ - "block2 0.6.2", + "block2", "dispatch2", "js-sys", "libc", "log", - "objc2 0.6.4", + "objc2", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.2", + "objc2-foundation", "percent-encoding", - "pollster", + "pollster 0.4.0", "raw-window-handle", "wasm-bindgen", "wasm-bindgen-futures", @@ -8254,12 +9409,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "rgb" -version = "0.8.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" - [[package]] name = "ring" version = "0.17.14" @@ -8275,863 +9424,4373 @@ dependencies = [ ] [[package]] -name = "rmp" -version = "0.8.15" +name = "rlsl" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +checksum = "285ef6a8dbee750b4cee85378693f3c71d92408ad8669a9939adfdb0d51b0056" dependencies = [ - "num-traits", + "crossbeam-channel", + "fxhash", + "hostname", + "log", + "once_cell", + "parking_lot", + "socket2 0.5.10", + "tokio", + "uuid", ] [[package]] -name = "rmp-serde" -version = "1.3.1" +name = "rlsl" +version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +checksum = "82be1852a0603b035d754ba6013581afea291c3dd944dac74f391ce66a12d5da" dependencies = [ - "rmp", - "serde", + "crossbeam-channel", + "fxhash", + "hostname", + "log", + "once_cell", + "parking_lot", + "socket2 0.5.10", + "tokio", + "uuid", ] [[package]] -name = "rodio" -version = "0.22.2" +name = "rlsl-iroh" +version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a536bb79db59098ef71a4dd4246c02eb87b316deceb1b68e0cde7167ec01eb" +checksum = "7663957cfd3ef08e719d34d7e7cd6856e43a1436e1a4576e549562cbd48bf11a" dependencies = [ - "cpal", - "dasp_sample", - "num-rational", - "symphonia", - "thiserror 2.0.18", + "anyhow", + "base64 0.22.1", + "bytes", + "clap", + "env_logger", + "iroh", + "log", + "lz4_flex", + "rlsl 0.0.5", + "serde", + "serde_json", + "snap", + "tokio", + "zstd 0.13.3", ] [[package]] -name = "rstest" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" +name = "rlx" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" dependencies = [ - "futures-timer", - "futures-util", - "rstest_macros", + "anyhow", + "rlx-driver", + "rlx-gguf", + "rlx-ir", + "rlx-macros", + "rlx-opt", + "rlx-runtime", + "rlx-tensor", ] [[package]] -name = "rstest_macros" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" +name = "rlx-asr" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "cfg-if", - "glob", - "proc-macro-crate 3.5.0", - "proc-macro2", - "quote", - "regex", - "relative-path", - "rustc_version", - "syn 2.0.117", - "unicode-ident", + "anyhow", + "rlx-gguf", + "rlx-ir", + "rlx-pkg", + "rlx-runtime", + "serde_json", + "zip 2.4.2", ] [[package]] -name = "rusqlite" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" +name = "rlx-assets" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "bitflags 2.11.0", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "smallvec", + "anyhow", + "memmap2", + "rlx-ir", + "rlx-onnx-import", + "rlx-pkg", + "serde", + "serde_json", ] [[package]] -name = "rustc-demangle" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +name = "rlx-autodiff" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "half", + "rlx-fusion", + "rlx-ir", + "thiserror 2.0.19", +] [[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +name = "rlx-bert" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "safetensors 0.7.0", + "serde", +] [[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +name = "rlx-bioclip2" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "semver", + "anyhow", + "half", + "image", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", + "tokenizers", ] [[package]] -name = "rustfft" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21db5f9893e91f41798c88680037dba611ca6674703c1a18601b01a72c8adb89" +name = "rlx-bonsai" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "num-complex", - "num-integer", - "num-traits", - "primal-check", - "strength_reduce", - "transpose", + "anyhow", + "rlx-cli", + "rlx-gguf", + "rlx-llama-base", + "rlx-llama32", + "rlx-qwen35", + "rlx-runtime", ] [[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +name = "rlx-chatterbox" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "bitflags 2.11.0", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "anyhow", + "half", + "hound", + "rlx-ir", + "rlx-llama32", + "rlx-models-core", + "rlx-onnx-import", + "rlx-runtime", + "tokenizers", ] [[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +name = "rlx-cli" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "bitflags 2.11.0", - "errno", - "libc", - "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "anyhow", + "clap", + "phf 0.11.3", + "rlx-gguf", + "rlx-models-core", + "rlx-runtime", + "rlx-text", + "serde_json", ] [[package]] -name = "rustls" -version = "0.23.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +name = "rlx-clinicalbert" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", + "anyhow", + "rlx-bert", + "rlx-cpu", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", ] [[package]] -name = "rustls-native-certs" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +name = "rlx-cohere" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", + "anyhow", + "rlx-cli", + "rlx-llama-base", + "rlx-llama32", ] [[package]] -name = "rustls-pki-types" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +name = "rlx-compile" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" dependencies = [ - "web-time", - "zeroize", + "rlx-autodiff", + "rlx-fusion", + "rlx-ir", ] [[package]] -name = "rustls-platform-verifier" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +name = "rlx-conformer-ctc" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "core-foundation 0.10.1", - "core-foundation-sys", - "jni 0.21.1", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", + "anyhow", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-nemo", + "rlx-runtime", + "rustfft", ] [[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - -[[package]] -name = "rustls-webpki" -version = "0.103.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +name = "rlx-coreml" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", + "bytemuck", + "cc", + "half", + "prost 0.13.5", + "prost-build", + "protoc-bin-vendored", + "rlx-cpu", + "rlx-gguf", + "rlx-ir", + "rlx-mlx-io", + "rlx-opt", ] [[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "safetensors" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93279b86b3de76f820a8854dd06cbc33cfa57a417b19c47f6a25280112fb1df" +name = "rlx-cpu" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" dependencies = [ - "serde", - "serde_json", + "half", + "rayon", + "rlx-gguf", + "rlx-ir", + "rlx-mlx-io", + "rlx-opt", + "strum_macros 0.28.0", ] [[package]] -name = "safetensors" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44560c11236a6130a46ce36c836a62936dc81ebf8c36a37947423571be0e55b6" +name = "rlx-cuda" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" dependencies = [ + "bytemuck", + "cudarc", + "libloading 0.8.9", + "rlx-cpu", + "rlx-gguf", + "rlx-gpu-dispatch", + "rlx-gpu-host", + "rlx-gpu-kernels", + "rlx-ir", + "rlx-opt", + "rlx-unfuse", "serde", "serde_json", ] [[package]] -name = "safetensors" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc0cdb7198d738a111f6df8fef42cb175412c311d0c4ac9126ff4e550ad1a0e8" +name = "rlx-dac" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ + "anyhow", + "hound", + "ndarray 0.16.1", + "rlx-cpu", + "rlx-ir", + "rlx-metal", + "rlx-models-core", + "rlx-runtime", + "rlx-wgpu", + "safetensors 0.4.5", "serde", "serde_json", ] [[package]] -name = "safetensors" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675656c1eabb620b921efea4f9199f97fc86e36dd6ffd1fbbe48d0f59a4987f5" +name = "rlx-dduf" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" dependencies = [ - "hashbrown 0.16.1", - "serde", + "anyhow", + "half", + "safetensors 0.7.0", "serde_json", + "zip 2.4.2", ] [[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] +name = "rlx-diamond" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" [[package]] -name = "sanitize-filename" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc984f4f9ceb736a7bb755c3e3bd17dc56370af2600c9780dcc48c66453da34d" +name = "rlx-diarize" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "regex", + "anyhow", + "rlx-runtime", + "rlx-wespeaker", + "serde", ] [[package]] -name = "schannel" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +name = "rlx-dinov2" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "windows-sys 0.61.2", + "anyhow", + "image", + "rlx-cli", + "rlx-flow", + "rlx-gguf", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", ] [[package]] -name = "scheduled-thread-pool" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +name = "rlx-dinov3" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "parking_lot", + "anyhow", + "image", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "serde", + "serde_json", ] [[package]] -name = "schemars" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +name = "rlx-distributed" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" dependencies = [ - "dyn-clone", - "indexmap 1.9.3", - "schemars_derive", + "anyhow", + "libc", + "rlx-driver", + "rlx-ir", + "rlx-runtime", "serde", "serde_json", - "url", - "uuid", + "toml 0.8.2", ] [[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +name = "rlx-driver" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", + "half", + "rlx-ir", ] [[package]] -name = "schemars" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +name = "rlx-embed" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "dyn-clone", - "ref-cast", + "anyhow", + "hf-hub", + "rlx-bert", + "rlx-cpu", + "rlx-flow", + "rlx-gguf", + "rlx-ir", + "rlx-models-core", + "rlx-nomic", + "rlx-opt", + "rlx-runtime", + "rlx-vision", + "safetensors 0.7.0", "serde", "serde_json", + "tokenizers", ] [[package]] -name = "schemars_derive" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +name = "rlx-encodec" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.117", + "anyhow", + "half", + "rlx-ir", + "rlx-metal", + "rlx-models-core", + "rlx-runtime", + "rlx-wgpu", + "safetensors 0.7.0", + "serde", + "serde_json", ] [[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +name = "rlx-f0" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" [[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +name = "rlx-f5tts" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "bitflags 2.11.0", - "core-foundation 0.10.1", - "core-foundation-sys", - "libc", - "security-framework-sys", + "anyhow", + "half", + "hound", + "rlx-runtime", + "rlx-tiny-tts", ] [[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +name = "rlx-fara" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "core-foundation-sys", - "libc", + "anyhow", + "image", + "rlx-cli", + "rlx-models-core", + "rlx-qwen35", + "rlx-runtime", + "serde", + "serde_json", + "tokenizers", ] [[package]] -name = "selectors" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" +name = "rlx-fft" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "bitflags 1.3.2", - "cssparser", - "derive_more 0.99.20", - "fxhash", - "log", - "phf 0.8.0", - "phf_codegen 0.8.0", - "precomputed-hash", - "servo_arc", - "smallvec", + "anyhow", + "getrandom 0.3.4", + "half", + "rand 0.8.7", + "rlx-autodiff", + "rlx-cli", + "rlx-compile", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "rustfft", + "safetensors 0.7.0", + "serde", + "serde_json", ] [[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +name = "rlx-fireredaudio" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "serde", - "serde_core", + "anyhow", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-qwen35", + "rlx-runtime", + "rlx-whisper", + "rustfft", + "serde_json", + "tokenizers", ] [[package]] -name = "seq-macro" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +name = "rlx-florence2" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "serde_core", - "serde_derive", + "anyhow", + "half", + "image", + "rayon", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", + "tokenizers", ] [[package]] -name = "serde-untagged" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +name = "rlx-flow" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" dependencies = [ - "erased-serde", + "anyhow", + "rlx-ir", "serde", - "serde_core", - "typeid", + "toml 0.8.2", ] [[package]] -name = "serde-xml-rs" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2215ce3e6a77550b80a1c37251b7d294febaf42e36e21b7b411e0bf54d540d" +name = "rlx-flux2" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "log", + "anyhow", + "bytemuck", + "half", + "image", + "rlx-cli", + "rlx-cpu", + "rlx-diamond", + "rlx-flow", + "rlx-gguf", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-qwen3", + "rlx-runtime", + "safetensors 0.7.0", "serde", - "thiserror 2.0.18", - "xml", + "serde_json", + "tokenizers", ] [[package]] -name = "serde_bytes" -version = "0.11.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +name = "rlx-funasr" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ + "anyhow", + "half", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-nemo", + "rlx-opt", + "rlx-runtime", + "rustfft", + "safetensors 0.7.0", "serde", - "serde_core", + "serde_json", + "symphonia", + "zip 2.4.2", ] [[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +name = "rlx-fusion" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" dependencies = [ - "serde_derive", + "rlx-ir", ] [[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +name = "rlx-gemma" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "anyhow", + "half", + "image", + "rlx-cli", + "rlx-cpu", + "rlx-flow", + "rlx-gguf", + "rlx-ir", + "rlx-mlx-io", + "rlx-models-core", + "rlx-opt", + "rlx-qwen3", + "rlx-qwen35", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", + "tokenizers", ] [[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +name = "rlx-gepard" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "anyhow", + "clap", + "fastrand", + "hound", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-nanocodec", + "rlx-qwen35", + "rlx-runtime", + "rlx-whisper", + "safetensors 0.7.0", + "serde", + "serde_json", + "tokenizers", + "tokio", ] [[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +name = "rlx-gguf" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", + "anyhow", + "bytemuck", + "half", + "libc", + "memmap2", + "rayon", ] [[package]] -name = "serde_path_to_error" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +name = "rlx-gpu-dispatch" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" + +[[package]] +name = "rlx-gpu-host" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" dependencies = [ - "itoa", - "serde", - "serde_core", + "bytemuck", + "half", + "rlx-compile", + "rlx-cpu", + "rlx-gpu-dispatch", + "rlx-ir", + "rlx-mlx-io", ] [[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +name = "rlx-gpu-kernels" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "rlx-gpu-dispatch", + "rlx-ir", + "rlxsl", ] [[package]] -name = "serde_rusqlite" -version = "0.40.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8bd74f47e124e760475a7e863b5820dcef09cae50782d03d65961f5ca1e6d9" +name = "rlx-granite" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "rusqlite", - "serde_core", + "anyhow", + "rlx-cli", + "rlx-llama-base", + "rlx-llama32", ] [[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +name = "rlx-hoct" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ + "anyhow", + "byteorder", + "good_lp", + "ndarray 0.16.1", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "safetensors 0.7.0", "serde", + "serde_json", ] [[package]] -name = "serde_spanned" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +name = "rlx-hy-mt" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "serde_core", + "anyhow", + "rlx-cli", + "rlx-models-core", + "rlx-qwen3", + "rlx-runtime", + "serde", + "serde_json", ] [[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +name = "rlx-inflect-nano" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "form_urlencoded", - "itoa", - "ryu", + "anyhow", + "hound", + "ndarray 0.16.1", + "once_cell", + "regex", + "rlx", + "rlx-ir", + "rlx-models-core", + "rlx-runtime", + "safetensors 0.7.0", "serde", + "serde_json", + "tokenizers", ] [[package]] -name = "serde_with" -version = "3.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +name = "rlx-ir" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.13.0", - "schemars 0.9.0", - "schemars 1.2.1", - "serde_core", + "aes 0.9.1", + "bincode", + "serde", "serde_json", - "serde_with_macros", - "time", + "smallvec", ] [[package]] -name = "serde_with_macros" -version = "3.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +name = "rlx-jina-ocr" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "darling 0.21.3", - "proc-macro2", - "quote", - "syn 2.0.117", + "anyhow", + "hf-hub", + "image", + "rlx-cli", + "rlx-models-core", + "rlx-runtime", + "rlx-unlimited-ocr", + "serde", + "serde_json", + "tokenizers", ] [[package]] -name = "serialize-to-javascript" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +name = "rlx-kittentts" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ + "anyhow", + "espeak-ng 0.2.0", + "fancy-regex 0.14.0", + "hf-hub", + "hound", + "kitten_tts_mini_rlx", + "once_cell", + "regex", + "rlx", + "rlx-cli", + "rlx-ir", + "rlx-runtime", "serde", "serde_json", - "serialize-to-javascript-impl", + "zip 2.4.2", ] [[package]] -name = "serialize-to-javascript-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +name = "rlx-kokoro" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "anyhow", + "hound", + "once_cell", + "rlx-kittentts", + "rlx-runtime", + "rlx-tiny-tts", + "serde", + "serde_json", ] [[package]] -name = "serialport" -version = "4.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acaf3f973e8616d7ceac415f53fc60e190b2a686fbcf8d27d0256c741c5007b" +name = "rlx-kyutai-tts" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "bitflags 2.11.0", - "cfg-if", - "core-foundation 0.10.1", - "core-foundation-sys", - "io-kit-sys", - "libudev", - "mach2 0.4.3", - "nix 0.26.4", - "scopeguard", - "unescaper", - "winapi", + "anyhow", + "half", + "hf-hub", + "ndarray 0.16.1", + "rand 0.8.7", + "rayon", + "rlx-cli", + "rlx-ir", + "rlx-mimi", + "rlx-models-core", + "rlx-runtime", + "safetensors 0.7.0", + "sentencepiece", + "serde", + "serde_json", ] [[package]] -name = "servo_arc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +name = "rlx-lfm" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "nodrop", - "stable_deref_trait", + "anyhow", + "rlx-cli", + "rlx-cpu", + "rlx-flow", + "rlx-gguf", + "rlx-ir", + "rlx-llama-base", + "rlx-models-core", + "rlx-runtime", + "rlx-ssm", + "serde", + "serde_json", ] [[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +name = "rlx-lfm-vl" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest", + "anyhow", + "image", + "rlx-cli", + "rlx-flow", + "rlx-gguf", + "rlx-ir", + "rlx-lfm", + "rlx-llama-base", + "rlx-models-core", + "rlx-qwen35", + "rlx-runtime", + "rlx-vlm-base", + "serde", + "serde_json", ] [[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +name = "rlx-llada2" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest", + "anyhow", + "bytemuck", + "rlx-cpu", + "rlx-flow", + "rlx-ir", + "rlx-metal", + "rlx-mlx", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", ] [[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +name = "rlx-llama-base" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "lazy_static", + "anyhow", + "rlx-gguf", + "serde", ] [[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +name = "rlx-llama32" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "libc", - "signal-hook-registry", + "anyhow", + "rlx-cli", + "rlx-cpu", + "rlx-flow", + "rlx-gguf", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-qwen3", + "rlx-qwen35", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", + "tokenizers", ] [[package]] -name = "signal-hook" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b57709da74f9ff9f4a27dce9526eec25ca8407c45a7887243b031a58935fb8e" +name = "rlx-llama4" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "libc", - "signal-hook-registry", + "anyhow", + "half", + "image", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", + "tokenizers", ] [[package]] -name = "signal-hook-mio" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +name = "rlx-locateanything" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "libc", - "mio", - "signal-hook 0.3.18", + "anyhow", + "hf-hub", + "image", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-qwen3", + "rlx-runtime", + "serde", + "serde_json", + "tokenizers", ] [[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +name = "rlx-luxtts" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" dependencies = [ - "errno", - "libc", + "anyhow", + "hound", + "rlx-kittentts", + "rlx-runtime", + "rlx-tiny-tts", + "rustfft", + "serde", + "serde_json", ] [[package]] -name = "simd-adler32" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" - -[[package]] -name = "simd_helpers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +name = "rlx-macros" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" dependencies = [ + "proc-macro2", "quote", + "syn 2.0.119", ] [[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - +name = "rlx-maya1" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "hound", + "rlx-orpheus", + "rlx-runtime", +] + +[[package]] +name = "rlx-melotts" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-runtime", + "rlx-tiny-tts", +] + +[[package]] +name = "rlx-metal" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "bytemuck", + "half", + "objc", + "rayon", + "rlx-cpu", + "rlx-gguf", + "rlx-gpu-dispatch", + "rlx-gpu-host", + "rlx-ir", + "rlx-opt", + "rlxsl", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-metavoice" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "base64 0.22.1", + "fancy-regex 0.14.0", + "hound", + "ndarray 0.16.1", + "rlx-cli", + "rlx-encodec", + "rlx-models-core", + "rlx-runtime", + "rustfft", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-mimi" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "hf-hub", + "hound", + "ndarray 0.16.1", + "rlx-cli", + "rlx-ir", + "rlx-metal", + "rlx-mlx", + "rlx-models-core", + "rlx-runtime", + "rlx-wgpu", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-minicpm5" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-llama-base", + "rlx-llama32", + "rlx-runtime", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-minimax" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-cpu", + "rlx-flow", + "rlx-gguf", + "rlx-ir", + "rlx-llama-base", + "rlx-models-core", + "rlx-runtime", + "rlx-ssm", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-miotts" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "clap", + "hound", + "rlx-qwen3", + "rlx-runtime", + "rlx-tiny-tts", + "rlx-whisper", + "rlx-xcodec", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-miratts" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "hound", + "rlx-qwen3", + "rlx-runtime", + "rlx-tiny-tts", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-mistral" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-llama-base", + "rlx-llama32", + "rlx-runtime", +] + +[[package]] +name = "rlx-mistral-vl" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "image", + "rlx-cli", + "rlx-flow", + "rlx-gguf", + "rlx-ir", + "rlx-llama32", + "rlx-mistral", + "rlx-models-core", + "rlx-qwen35", + "rlx-runtime", +] + +[[package]] +name = "rlx-mllama" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "half", + "image", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-llama32", + "rlx-models-core", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-mlx" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "half", + "libc", + "rlx-cpu", + "rlx-driver", + "rlx-gguf", + "rlx-ir", + "rlx-mlx-io", + "rlx-mlx-sys", + "rlx-opt", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-mlx-io" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "anyhow", + "half", + "libc", + "memmap2", + "npyz", + "rayon", + "rlx-ir", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-mlx-sys" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "cc", + "cmake", +] + +[[package]] +name = "rlx-models" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "bytemuck", + "half", + "hound", + "rlx-asr", + "rlx-bert", + "rlx-bioclip2", + "rlx-bonsai", + "rlx-cli", + "rlx-clinicalbert", + "rlx-cohere", + "rlx-conformer-ctc", + "rlx-cpu", + "rlx-diarize", + "rlx-dinov2", + "rlx-dinov3", + "rlx-embed", + "rlx-f5tts", + "rlx-fara", + "rlx-fireredaudio", + "rlx-florence2", + "rlx-flow", + "rlx-flux2", + "rlx-funasr", + "rlx-gemma", + "rlx-gguf", + "rlx-granite", + "rlx-hoct", + "rlx-hy-mt", + "rlx-ir", + "rlx-jina-ocr", + "rlx-kittentts", + "rlx-kokoro", + "rlx-kyutai-tts", + "rlx-lfm", + "rlx-lfm-vl", + "rlx-llada2", + "rlx-llama32", + "rlx-llama4", + "rlx-locateanything", + "rlx-luxtts", + "rlx-maya1", + "rlx-metal", + "rlx-minicpm5", + "rlx-minimax", + "rlx-miratts", + "rlx-mistral", + "rlx-mistral-vl", + "rlx-mllama", + "rlx-mlx", + "rlx-models-core", + "rlx-moonshine", + "rlx-moshi", + "rlx-moss-nano", + "rlx-nanbeige", + "rlx-nanowakeword", + "rlx-nemotron-asr", + "rlx-neutrino", + "rlx-neutts", + "rlx-nllb", + "rlx-nomic", + "rlx-ocr", + "rlx-omnicoder", + "rlx-openwakeword", + "rlx-opt", + "rlx-phi", + "rlx-piper", + "rlx-porcupine", + "rlx-qwen3", + "rlx-qwen3-asr", + "rlx-qwen3-tts", + "rlx-qwen3-vl", + "rlx-qwen35", + "rlx-runtime", + "rlx-s1", + "rlx-sam", + "rlx-sam-ir", + "rlx-sam2", + "rlx-sam3", + "rlx-supertonic", + "rlx-ten-vad", + "rlx-timesfm3", + "rlx-tinyllama", + "rlx-translategemma", + "rlx-trellis2", + "rlx-tts", + "rlx-uni2", + "rlx-unlimited-ocr", + "rlx-vad", + "rlx-vibevoice-asr", + "rlx-vision", + "rlx-vjepa2", + "rlx-voice-gender", + "rlx-voxrt", + "rlx-voxtral", + "rlx-voxtral-tts", + "rlx-wake", + "rlx-wakeword", + "rlx-wav2vec2-bert", + "rlx-wgpu", + "rlx-whisper", + "rlx-zipvoice", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-models-core" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "bytemuck", + "half", + "image", + "libc", + "memmap2", + "rayon", + "rlx-assets", + "rlx-autodiff", + "rlx-cpu", + "rlx-cuda", + "rlx-distributed", + "rlx-flow", + "rlx-gguf", + "rlx-ir", + "rlx-metal", + "rlx-mlx", + "rlx-mlx-io", + "rlx-opt", + "rlx-rocm", + "rlx-runtime", + "rlx-wgpu", + "safetensors 0.7.0", + "serde", + "serde_json", + "unicode-normalization", +] + +[[package]] +name = "rlx-moonshine" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rayon", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-moshi" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "hound", + "ndarray 0.16.1", + "rand 0.8.7", + "rayon", + "rlx-cli", + "rlx-ir", + "rlx-mimi", + "rlx-models-core", + "rlx-runtime", + "safetensors 0.4.5", + "sentencepiece", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "rlx-moss-nano" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "hound", + "rlx-assets", + "rlx-gguf", + "rlx-runtime", + "rlx-tiny-tts", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-nanbeige" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-flow", + "rlx-llama-base", + "rlx-llama32", + "rlx-models-core", + "rlx-runtime", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-nanocodec" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "hound", + "rlx-ir", + "rlx-models-core", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-nanowakeword" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-runtime", + "rlx-wake", +] + +[[package]] +name = "rlx-nemo" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "anyhow", + "rlx-ir", + "rlx-torch-ckpt", + "serde_yaml", +] + +[[package]] +name = "rlx-nemotron" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-cpu", + "rlx-flow", + "rlx-gguf", + "rlx-ir", + "rlx-llama-base", + "rlx-llama32", + "rlx-models-core", + "rlx-runtime", + "rlx-ssm", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-nemotron-asr" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-nemo", + "rlx-opt", + "rlx-runtime", + "rustfft", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-neutrino" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-gguf", + "rlx-qwen3", + "rlx-qwen35", +] + +[[package]] +name = "rlx-neutts" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "encoding_rs", + "memmap2", + "ndarray 0.16.1", + "once_cell", + "rand 0.8.7", + "regex", + "rlx-llama-base", + "rlx-llama32", + "rlx-models-core", + "rlx-qwen3", + "rlx-qwen35", + "rlx-runtime", + "rustfft", + "safetensors 0.7.0", + "thiserror 1.0.69", +] + +[[package]] +name = "rlx-nllb" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "half", + "rayon", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-nomic" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "safetensors 0.7.0", + "serde", +] + +[[package]] +name = "rlx-ocr" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "bytemuck", + "half", + "image", + "memmap2", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-runtime", + "rten-imageproc", + "rten-tensor", + "safetensors 0.7.0", +] + +[[package]] +name = "rlx-omnicoder" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-llama-base", + "rlx-qwen3", +] + +[[package]] +name = "rlx-onnx-import" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "anyhow", + "bytemuck", + "half", + "protobuf", + "rlx-cpu", + "rlx-ir", + "rlx-onnx-proto", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-onnx-proto" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "protobuf", +] + +[[package]] +name = "rlx-openwakeword" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-runtime", + "rlx-wake", +] + +[[package]] +name = "rlx-opt" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "rlx-autodiff", + "rlx-compile", + "rlx-fusion", + "rlx-ir", +] + +[[package]] +name = "rlx-orpheus" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "hf-hub", + "hound", + "ndarray 0.16.1", + "once_cell", + "rand 0.8.7", + "rand_distr", + "regex", + "rlx-cli", + "rlx-gguf", + "rlx-ir", + "rlx-llama-base", + "rlx-llama32", + "rlx-models-core", + "rlx-qwen3", + "rlx-qwen35", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-parlertts" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "hound", + "rlx-cli", + "rlx-dac", + "rlx-ir", + "rlx-models-core", + "rlx-onnx-import", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-phi" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-llama-base", + "rlx-llama32", + "rlx-runtime", +] + +[[package]] +name = "rlx-piper" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "hound", + "rlx-kittentts", + "rlx-runtime", + "rlx-tiny-tts", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-pkg" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "anyhow", + "bincode", + "memmap2", + "rayon", + "rlx-dduf", + "rlx-gguf", + "rlx-ir", + "rlx-mlx-io", + "rlx-nemo", + "serde", + "serde_json", + "serde_yaml", + "xxhash-rust", + "zip 2.4.2", + "zstd 0.13.3", +] + +[[package]] +name = "rlx-pocket-tts" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "half", + "hound", + "ndarray 0.16.1", + "rand 0.8.7", + "rand_distr", + "rlx", + "rlx-runtime", + "safetensors 0.7.0", + "sentencepiece", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-porcupine" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-runtime", + "rlx-wake", +] + +[[package]] +name = "rlx-qwen3" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "hf-hub", + "rlx-cli", + "rlx-distributed", + "rlx-embed", + "rlx-flow", + "rlx-gguf", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-qwen3-asr" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-qwen3", + "rlx-runtime", + "rlx-whisper", + "rustfft", + "safetensors 0.7.0", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-qwen3-tts" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "hf-hub", + "memmap2", + "ndarray 0.16.1", + "rand 0.8.7", + "rayon", + "rlx-cli", + "rlx-cpu", + "rlx-fft", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-qwen3", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-qwen3-vl" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "image", + "rlx-cli", + "rlx-flow", + "rlx-gguf", + "rlx-ir", + "rlx-llama-base", + "rlx-models-core", + "rlx-qwen3", + "rlx-qwen35", + "rlx-runtime", + "rlx-vlm-base", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-qwen35" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "image", + "rlx-cli", + "rlx-cpu", + "rlx-flow", + "rlx-gguf", + "rlx-ir", + "rlx-llada2", + "rlx-models-core", + "rlx-opt", + "rlx-qwen3", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-rocm" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "libloading 0.8.9", + "rlx-cpu", + "rlx-gguf", + "rlx-gpu-dispatch", + "rlx-gpu-host", + "rlx-gpu-kernels", + "rlx-ir", + "rlx-opt", + "rlx-unfuse", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-runtime" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "anyhow", + "half", + "inventory", + "memmap2", + "rayon", + "rlx-compile", + "rlx-coreml", + "rlx-cpu", + "rlx-cuda", + "rlx-dduf", + "rlx-driver", + "rlx-gguf", + "rlx-gpu-dispatch", + "rlx-ir", + "rlx-macros", + "rlx-metal", + "rlx-mlx", + "rlx-mlx-io", + "rlx-opt", + "rlx-pkg", + "rlx-rocm", + "rlx-wgpu", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-s1" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-qwen3", + "rlx-qwen35", + "rlx-runtime", + "serde_json", +] + +[[package]] +name = "rlx-sam" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "image", + "rlx-cli", + "rlx-cpu", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "rlx-sam-ir", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-sam-ir" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cpu", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", +] + +[[package]] +name = "rlx-sam2" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-cpu", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "rlx-sam", + "rlx-sam-ir", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-sam3" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-cpu", + "rlx-flow", + "rlx-gguf", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "rlx-sam", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-sesame" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "clap", + "fastrand", + "hound", + "memmap2", + "rlx-cli", + "rlx-cpu", + "rlx-flow", + "rlx-ir", + "rlx-mimi", + "rlx-models-core", + "rlx-runtime", + "rlx-whisper", + "safetensors 0.7.0", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-soprano" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "bytemuck", + "hound", + "rlx-assets", + "rlx-cli", + "rlx-gguf", + "rlx-ir", + "rlx-models-core", + "rlx-onnx-import", + "rlx-qwen3", + "rlx-runtime", + "rlx-tiny-tts", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-ssm" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cpu", + "rlx-flow", + "rlx-ir", +] + +[[package]] +name = "rlx-styletts2" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "clap", + "hound", + "rlx-kokoro", + "rlx-runtime", + "serde", +] + +[[package]] +name = "rlx-supertonic" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "hound", + "rlx-runtime", + "rlx-tiny-tts", + "serde", + "serde_json", + "unicode-normalization", +] + +[[package]] +name = "rlx-ten-vad" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-ir", + "rlx-models-core", + "rlx-runtime", + "rlx-ten-vad-core", +] + +[[package]] +name = "rlx-ten-vad-core" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "libm", +] + +[[package]] +name = "rlx-tensor" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "rlx-ir", + "rlx-runtime", +] + +[[package]] +name = "rlx-text" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "anyhow", + "minijinja", + "rlx-gguf", + "rlx-runtime", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-timesfm3" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "ndarray 0.16.1", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-tiny-tts" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "dirs", + "hound", + "ndarray 0.16.1", + "rlx-assets", + "rlx-compile", + "rlx-cpu", + "rlx-inflect-nano", + "rlx-ir", + "rlx-metal", + "rlx-mlx", + "rlx-models-core", + "rlx-onnx-import", + "rlx-opt", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-tinyllama" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-llama-base", + "rlx-llama32", + "rlx-runtime", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-torch-ckpt" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "anyhow", + "flate2", + "half", +] + +[[package]] +name = "rlx-translategemma" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-gemma", + "rlx-models-core", + "rlx-runtime", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-trellis2" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "bytemuck", + "half", + "image", + "rlx-cli", + "rlx-cpu", + "rlx-dinov3", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-tts" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "half", + "hound", + "ndarray 0.16.1", + "once_cell", + "regex", + "rlx-assets", + "rlx-cpu", + "rlx-fft", + "rlx-gguf", + "rlx-ir", + "rlx-pkg", + "rlx-runtime", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-tts-bench" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "clap", + "hound", + "libc", + "rlx-chatterbox", + "rlx-f5tts", + "rlx-gepard", + "rlx-kittentts", + "rlx-kyutai-tts", + "rlx-luxtts", + "rlx-melotts", + "rlx-metavoice", + "rlx-miotts", + "rlx-miratts", + "rlx-moss-nano", + "rlx-orpheus", + "rlx-parlertts", + "rlx-piper", + "rlx-pocket-tts", + "rlx-qwen3-tts", + "rlx-runtime", + "rlx-sesame", + "rlx-soprano", + "rlx-styletts2", + "rlx-supertonic", + "rlx-tts", + "rlx-voxtral-tts", + "rlx-whisper", + "rlx-zonos", + "rustfft", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-umap" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "crossbeam-channel", + "ctrlc", + "rand 0.9.5", + "rayon", + "rlx-autodiff", + "rlx-compile", + "rlx-cpu", + "rlx-driver", + "rlx-gguf", + "rlx-ir", + "rlx-metal", + "rlx-mlx", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-unfuse" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "rlx-ir", +] + +[[package]] +name = "rlx-uni2" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "image", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-unlimited-ocr" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "half", + "hf-hub", + "image", + "rlx-cli", + "rlx-flow", + "rlx-gguf", + "rlx-ir", + "rlx-models-core", + "rlx-runtime", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-vad" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-cpu", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-runtime", +] + +[[package]] +name = "rlx-vibevoice-asr" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-flow", + "rlx-gguf", + "rlx-ir", + "rlx-models-core", + "rlx-qwen3", + "rlx-runtime", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-vision" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "safetensors 0.7.0", + "serde", +] + +[[package]] +name = "rlx-vjepa2" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-cpu", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-vlm-base" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", +] + +[[package]] +name = "rlx-voice-gender" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-f0", + "rlx-runtime", + "rlx-tiny-tts", +] + +[[package]] +name = "rlx-voxrt" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-runtime", + "rlx-wake", +] + +[[package]] +name = "rlx-voxtral" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-llama32", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "rlx-whisper", + "safetensors 0.7.0", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-voxtral-tts" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "bytemuck", + "half", + "hound", + "kitoken", + "memmap2", + "ndarray 0.16.1", + "rand 0.8.7", + "rand_distr", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-llama32", + "rlx-models-core", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", + "zip 2.4.2", +] + +[[package]] +name = "rlx-wake" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-cpu", + "rlx-models-core", + "rlx-runtime", + "safetensors 0.7.0", +] + +[[package]] +name = "rlx-wakeword" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-vad", + "rlx-wake", + "rlx-wakeword-core", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-wakeword-core" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" + +[[package]] +name = "rlx-wav2vec2-bert" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-cli", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "rustfft", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-wespeaker" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-runtime", + "rlx-tiny-tts", +] + +[[package]] +name = "rlx-wgpu" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "bytemuck", + "half", + "pollster 0.3.0", + "rlx-compile", + "rlx-cpu", + "rlx-gguf", + "rlx-gpu-dispatch", + "rlx-gpu-host", + "rlx-ir", + "rlx-opt", + "rlx-unfuse", + "rlxsl", + "serde", + "serde_json", + "wgpu", +] + +[[package]] +name = "rlx-whisper" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rayon", + "rlx-cli", + "rlx-diarize", + "rlx-flow", + "rlx-ir", + "rlx-models-core", + "rlx-opt", + "rlx-runtime", + "rustfft", + "safetensors 0.7.0", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "rlx-xcodec" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "rlx-ir", + "rlx-models-core", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "rlx-zipvoice" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "hound", + "rlx-luxtts", + "rlx-runtime", +] + +[[package]] +name = "rlx-zonos" +version = "0.2.16" +source = "git+https://github.com/MIT-RLX/rlx-models.git?branch=main#50285bc1c8fd6232aa2cc1a51630eea7fc0ccf1c" +dependencies = [ + "anyhow", + "hound", + "matrixmultiply", + "rlx-cli", + "rlx-dac", + "rlx-ir", + "rlx-kittentts", + "rlx-models-core", + "rlx-runtime", + "safetensors 0.7.0", + "serde", + "serde_json", +] + +[[package]] +name = "rlxsl" +version = "0.2.17" +source = "git+https://github.com/MIT-RLX/rlx.git?branch=main#5efa904b4b6a654ac21d5c952425351881cec1ef" +dependencies = [ + "rlx-ir", +] + +[[package]] +name = "rodio" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a536bb79db59098ef71a4dd4246c02eb87b316deceb1b68e0cde7167ec01eb" +dependencies = [ + "cpal", + "dasp_sample", + "num-rational", + "symphonia", + "thiserror 2.0.19", +] + +[[package]] +name = "rsqlite-vfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" +dependencies = [ + "hashbrown 0.16.1", + "thiserror 2.0.19", +] + +[[package]] +name = "rten-base" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2738cf8bb4c27f828ac788d01ccf4e367e8e773cfec6851f81851b5211de6a79" +dependencies = [ + "rayon", +] + +[[package]] +name = "rten-imageproc" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f148e7e941fb5727b9046a5fa1b45525543d5105f14b384fd9261df0ee49bc" +dependencies = [ + "rten-tensor", +] + +[[package]] +name = "rten-tensor" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05dc744a270aa32d154f1a3df8e48740ccc1be9dfbcf23295ada66d83aa98de6" +dependencies = [ + "rayon", + "rten-base", + "smallvec", + "typeid", +] + +[[package]] +name = "rubato" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5258099699851cfd0082aeb645feb9c084d9a5e1f1b8d5372086b989fc5e56a1" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "realfft", +] + +[[package]] +name = "rusb" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9f9ff05b63a786553a4c02943b74b34a988448671001e9a27e2f0565cc05a4" +dependencies = [ + "libc", + "libusb1-sys", +] + +[[package]] +name = "rusqlite" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e" +dependencies = [ + "bitflags 2.13.1", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", + "sqlite-wasm-rs", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustfft" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21db5f9893e91f41798c88680037dba611ca6674703c1a18601b01a72c8adb89" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "primal-check", + "strength_reduce", + "transpose", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.7.0", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni 0.22.4", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework 3.7.0", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error 1.2.3", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "safetensors" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44560c11236a6130a46ce36c836a62936dc81ebf8c36a37947423571be0e55b6" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "safetensors" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc0cdb7198d738a111f6df8fef42cb175412c311d0c4ac9126ff4e550ad1a0e8" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "safetensors" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675656c1eabb620b921efea4f9199f97fc86e36dd6ffd1fbbe48d0f59a4987f5" +dependencies = [ + "hashbrown 0.16.1", + "serde", + "serde_json", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.119", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "secret-service" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4" +dependencies = [ + "aes 0.8.4", + "cbc", + "futures-util", + "generic-array", + "hkdf", + "num", + "once_cell", + "rand 0.8.7", + "serde", + "sha2 0.10.9", + "zbus 4.4.0", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "seize" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b55fb86dfd3a2f5f76ea78310a88f96c4ea21a3031f8d212443d56123fd0521" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "selectors" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +dependencies = [ + "bitflags 2.13.1", + "cssparser", + "derive_more", + "log", + "new_debug_unreachable", + "phf 0.13.1", + "phf_codegen 0.13.1", + "precomputed-hash", + "rustc-hash 2.1.3", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + +[[package]] +name = "sentencepiece" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "199cd424616675812f71a43866e2fb6e4725538dd7cee7d537fea81c0d66c102" +dependencies = [ + "libc", + "num-derive", + "num-traits", + "prost 0.14.4", + "prost-derive 0.14.4", + "sentencepiece-sys", + "thiserror 2.0.19", +] + +[[package]] +name = "sentencepiece-model" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b87bf750a8322c3236d7aa63c1f4a6862187d00d2d8b038e1dfe263bfe43ec" +dependencies = [ + "miette", + "prost 0.13.5", + "prost-build", + "protox", +] + +[[package]] +name = "sentencepiece-sys" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4f7b039e2b80f69283f56211f9457ea3f4a8d3045b9ba4282326b446fa3a9a" +dependencies = [ + "cc", + "cmake", + "pkg-config", +] + +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "base64 0.22.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.14.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "serde_yml" +version = "0.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909764a65f86829ccdb5eea9ab355843aa02c019a7bfd47465092953565caa05" +dependencies = [ + "noyalib", + "serde", +] + +[[package]] +name = "serdect" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serialport" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4d91116f97173694f1642263b2ff837f80d933aa837e2314969f6728f661df3" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "core-foundation 0.10.1", + "core-foundation-sys", + "io-kit-sys", + "libudev", + "mach2 0.4.3", + "nix 0.26.4", + "scopeguard", + "unescaper", + "windows-sys 0.52.0", +] + +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "simba" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simple-dns" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a75cbde1bf934313596a004973e462f9a82caa814dcf1a5f507bdf51597eeb4" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "skill" +version = "0.0.131-rc.35" +dependencies = [ + "anyhow", + "base64 0.22.1", + "chrono", + "csv", + "dirs", + "dispatch2", + "erased-serde", + "hound", + "image", + "libc", + "llmfit-core", + "log", + "macos-focus", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "oura-api", + "png 0.18.1", + "regex", + "rfd", + "rodio", + "rustls", + "serde", + "serde_json", + "serde_yaml", + "serialport", + "sha2 0.10.9", + "skill-autostart", + "skill-calendar", + "skill-constants", + "skill-daemon-common", + "skill-data", + "skill-devices", + "skill-eeg", + "skill-headless", + "skill-history", + "skill-jobs", + "skill-label-index", + "skill-llm", + "skill-location", + "skill-neutts", + "skill-oura", + "skill-settings", + "skill-skills", + "skill-tools", + "skill-tray", + "skill-tts", + "skill-vision", + "stacker", + "sysinfo 0.38.4", + "tauri", + "tauri-build", + "tauri-plugin-global-shortcut", + "tauri-plugin-notification", + "tauri-plugin-opener", + "tauri-plugin-process", + "tauri-plugin-single-instance", + "tauri-plugin-updater", + "tempfile", + "tokio", + "tokio-util", + "ureq 3.3.0", + "url", + "zip 7.2.0", +] + +[[package]] +name = "skill-asr" +version = "0.0.1" +dependencies = [ + "anyhow", + "cpal", + "dirs", + "hf-hub", + "rlx-asr", + "rlx-funasr", + "rlx-nemotron-asr", + "rlx-qwen3-asr", + "rlx-runtime", + "rlx-vad", + "rlx-voxtral", + "rlx-whisper", + "serde", + "serde_json", + "skill-constants", +] + +[[package]] +name = "skill-autostart" +version = "0.0.1" +dependencies = [ + "anyhow", + "skill-constants", +] + +[[package]] +name = "skill-calendar" +version = "0.1.0" +dependencies = [ + "anyhow", + "cc", + "serde", + "serde_json", + "thiserror 2.0.19", +] + +[[package]] +name = "skill-commands" +version = "0.0.1" +dependencies = [ + "fast-hnsw", + "rusqlite", + "serde", + "serde_json", + "skill-constants", + "skill-data", +] + +[[package]] +name = "skill-constants" +version = "0.0.1" +dependencies = [ + "libc", +] + +[[package]] +name = "skill-daemon" +version = "0.0.131-rc.35" +dependencies = [ + "antneuro", + "anyhow", + "async-trait", + "axum", + "base64 0.22.1", + "brainbit", + "brainmaster", + "brainvision", + "chrono", + "cognionics", + "dirs", + "fast-hnsw", + "futures", + "glob", + "gtec", + "hex", + "hf-hub", + "image", + "libc", + "luna-rs", + "mendi", + "mw75", + "ndarray 0.17.2", + "neurofield", + "neurosity", + "neurosky", + "notify", + "notify-rust", + "objc2", + "objc2-app-kit", + "rand 0.10.2", + "regex", + "rlsl 0.0.4", + "rlx", + "rusqlite", + "serde", + "serde_json", + "serialport", + "sha2 0.10.9", + "skill-asr", + "skill-calendar", + "skill-commands", + "skill-constants", + "skill-daemon-common", + "skill-daemon-routes", + "skill-daemon-state", + "skill-data", + "skill-devices", + "skill-eeg", + "skill-exg", + "skill-health", + "skill-history", + "skill-iroh", + "skill-jobs", + "skill-label-index", + "skill-llm", + "skill-location", + "skill-lsl", + "skill-oura", + "skill-router", + "skill-screenshots", + "skill-settings", + "skill-skills", + "skill-tools", + "skill-tts", + "sysinfo 0.38.4", + "tempfile", + "thiserror 2.0.19", + "tokio", + "tokio-stream", + "tokio-tungstenite 0.28.0", + "tower", + "tower-http", + "tracing", + "tracing-subscriber", + "tribev2", + "ureq 3.3.0", + "urlencoding", + "uuid", + "webbluetooth", + "zstd 0.13.3", + "zuna-rs", +] + +[[package]] +name = "skill-daemon-common" +version = "0.1.0" +dependencies = [ + "dirs", + "serde", + "serde_json", +] + +[[package]] +name = "skill-daemon-routes" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "serde_json", + "skill-daemon-state", + "skill-iroh", +] + +[[package]] +name = "skill-daemon-state" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64 0.22.1", + "dirs", + "hex", + "hf-hub", + "rand 0.10.2", + "rlx", + "rlx-models", + "serde", + "serde_json", + "sha2 0.10.9", + "skill-constants", + "skill-daemon-common", + "skill-data", + "skill-devices", + "skill-eeg", + "skill-iroh", + "skill-label-index", + "skill-llm", + "skill-lsl", + "skill-settings", + "tempfile", + "thiserror 2.0.19", + "tokenizers", + "tokio", + "tracing", +] + +[[package]] +name = "skill-data" +version = "0.0.1" +dependencies = [ + "anyhow", + "arrow-array", + "arrow-schema", + "chrono", + "csv", + "grayscale", + "libc", + "llmfit-core", + "macos-focus", + "parquet", + "rusqlite", + "serde", + "serde_json", + "skill-constants", + "skill-eeg", + "skill-gpu", + "skill-health", + "skill-oura", + "sysinfo 0.38.4", + "tempfile", + "thiserror 2.0.19", +] + +[[package]] +name = "skill-devices" +version = "0.0.1" +dependencies = [ + "antneuro", + "anyhow", + "async-trait", + "awear", + "bitflags 2.13.1", + "cognionics", + "emotiv", + "futures", + "hex", + "idun", + "mendi", + "muse-rs", + "mw75", + "openbci", + "oura-link", + "oura-protocol", + "rusb", + "serde", + "serde_json", + "skill-constants", + "skill-data", + "skill-eeg", + "skill-iroh", + "tokio", + "tracing", + "vital", + "webbluetooth", +] + +[[package]] +name = "skill-eeg" +version = "0.0.1" +dependencies = [ + "criterion", + "proptest", + "rlx", + "rustfft", + "serde", + "serde_json", + "skill-constants", +] + [[package]] -name = "siphasher" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +name = "skill-exg" +version = "0.0.1" +dependencies = [ + "anyhow", + "eegdino", + "hf-hub", + "log", + "lumamba", + "neurorvq-rs", + "rlx", + "serde", + "serde_json", + "skill-constants", + "skill-data", + "skill-devices", + "skill-eeg", + "ureq 3.3.0", +] [[package]] -name = "skill" -version = "0.0.7" +name = "skill-gpu" +version = "0.0.1" dependencies = [ - "axum", + "libc", + "llmfit-core", + "serde", + "serde_json", + "sysinfo 0.38.4", +] + +[[package]] +name = "skill-headless" +version = "0.0.1" +dependencies = [ + "anyhow", "base64 0.22.1", - "btleplug", - "burn", - "burn-cubecl", "crossbeam-channel", + "gtk", + "http", + "serde", + "serde_json", + "tao 0.34.8", + "thiserror 2.0.19", + "wry 0.54.4", +] + +[[package]] +name = "skill-health" +version = "0.0.1" +dependencies = [ + "rusqlite", + "serde", + "serde_json", + "tempfile", +] + +[[package]] +name = "skill-history" +version = "0.0.1" +dependencies = [ + "anyhow", + "arrow-array", "csv", - "cubecl", - "cubecl-runtime", - "dirs", - "dispatch2", + "parquet", + "rusqlite", + "serde", + "serde_json", + "skill-commands", + "skill-constants", + "skill-data", + "tempfile", +] + +[[package]] +name = "skill-iroh" +version = "0.0.1" +dependencies = [ + "anyhow", + "base64 0.22.1", + "image", + "iroh", + "qrcodegen", + "rand 0.9.5", + "serde", + "serde_json", + "tempfile", + "thiserror 2.0.19", + "tokio", + "totp-rs", + "ureq 3.3.0", + "zstd 0.13.3", +] + +[[package]] +name = "skill-jobs" +version = "0.0.1" +dependencies = [ + "anyhow", + "serde", + "serde_json", + "skill-constants", + "thiserror 2.0.19", +] + +[[package]] +name = "skill-label-index" +version = "0.0.1" +dependencies = [ + "accelerate-src", "fast-hnsw", - "fast-umap", - "fastembed", - "futures-util", - "gpu-fft", - "hf-hub 0.5.0", + "rusqlite", + "serde", + "skill-commands", + "skill-constants", + "skill-data", + "tempfile", + "turbovec", +] + +[[package]] +name = "skill-llm" +version = "0.0.1" +dependencies = [ + "anyhow", + "async-stream", + "axum", + "base64 0.22.1", + "either", + "glob", + "hex", + "hf-hub", + "image", + "log", + "rlx", + "rlx-gemma", + "rlx-minicpm5", + "rlx-minimax", + "rlx-models", + "rlx-nemotron", + "rusqlite", + "serde", + "serde_json", + "sha2 0.10.9", + "skill-constants", + "skill-data", + "skill-skills", + "skill-tools", + "tempfile", + "thiserror 2.0.19", + "tokio", + "ureq 3.3.0", +] + +[[package]] +name = "skill-location" +version = "0.1.0" +dependencies = [ + "anyhow", + "cc", + "serde", + "serde_json", + "thiserror 2.0.19", + "tokio", + "ureq 3.3.0", +] + +[[package]] +name = "skill-lsl" +version = "0.0.1" +dependencies = [ + "anyhow", + "async-trait", + "iroh", + "log", + "rand 0.10.2", + "rlsl 0.0.4", + "rlsl-iroh", + "serde", + "serde_json", + "skill-constants", + "skill-devices", + "tempfile", + "tokio", +] + +[[package]] +name = "skill-neutts" +version = "0.0.1" +dependencies = [ + "anyhow", + "dirs", + "espeak-ng 0.1.2", + "fancy-regex 0.14.0", + "hf-hub", "hound", - "kittentts", - "libc", - "macos-focus", - "mdns-sd", - "muse-rs", - "ndarray 0.17.2", - "neutts", - "objc2 0.6.4", + "once_cell", + "rlx-neutts", + "safetensors 0.5.3", + "sha2 0.10.9", + "zip 7.2.0", +] + +[[package]] +name = "skill-oura" +version = "0.0.1" +dependencies = [ + "anyhow", + "chrono", + "oura-api", + "oura-protocol", + "serde", + "serde_json", + "skill-health", + "tempfile", +] + +[[package]] +name = "skill-router" +version = "0.0.1" +dependencies = [ + "anyhow", + "hotpath", + "rlx-runtime", + "rlx-umap", + "rusqlite", + "serde", + "serde_json", + "skill-commands", + "skill-constants", + "skill-data", + "skill-settings", +] + +[[package]] +name = "skill-screenshots" +version = "0.0.1" +dependencies = [ + "anyhow", + "base64 0.22.1", + "chrono", + "crossbeam-channel", + "fast-hnsw", + "gif", + "hf-hub", + "image", + "objc2", "objc2-app-kit", - "objc2-foundation 0.3.2", - "openbci", - "png 0.17.16", + "rlx", + "rlx-models", + "rlx-ocr", + "rlx-runtime", + "serde", + "serde_json", + "skill-constants", + "skill-data", + "skill-settings", + "skill-vision", + "thiserror 2.0.19", + "ureq 3.3.0", + "xcap", +] + +[[package]] +name = "skill-settings" +version = "0.0.1" +dependencies = [ + "dirs", + "keyring", + "serde", + "serde_json", + "skill-asr", + "skill-constants", + "skill-data", + "skill-eeg", + "skill-llm", + "skill-tts", + "tempfile", + "tracing", +] + +[[package]] +name = "skill-skills" +version = "0.0.1" +dependencies = [ + "anyhow", + "dirs", + "flate2", + "ignore", + "serde", + "serde_json", + "skill-constants", + "tar", + "ureq 3.3.0", +] + +[[package]] +name = "skill-tools" +version = "0.0.1" +dependencies = [ + "anyhow", + "chrono", + "dirs", + "jsonschema", + "proptest", + "regex", "rfd", + "serde", + "serde_json", + "sha2 0.10.9", + "skill-constants", + "skill-headless", + "skill-location", + "thiserror 2.0.19", + "tokio", + "ureq 3.3.0", + "url", + "urlencoding", +] + +[[package]] +name = "skill-tray" +version = "0.0.1" +dependencies = [ + "skill-constants", +] + +[[package]] +name = "skill-tts" +version = "0.0.1" +dependencies = [ + "anyhow", + "dirs", + "hf-hub", + "hound", + "rlx-inflect-nano", + "rlx-kittentts", + "rlx-kyutai-tts", + "rlx-orpheus", + "rlx-qwen3-tts", + "rlx-runtime", + "rlx-tts-bench", + "rlx-whisper", "rodio", - "rusqlite", "serde", "serde_json", - "serialport", - "sha2", - "tauri", - "tauri-build", - "tauri-plugin-global-shortcut", - "tauri-plugin-notification", - "tauri-plugin-opener", - "tauri-plugin-process", - "tauri-plugin-updater", - "tempfile", + "sha2 0.10.9", + "skill-constants", + "skill-neutts", "tokio", - "tower-http", - "zuna-rs", ] [[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +name = "skill-tty" +version = "0.0.131-rc.35" +dependencies = [ + "anyhow", + "chrono", + "dirs", + "libc", +] [[package]] -name = "slotmap" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +name = "skill-vision" +version = "0.1.0" dependencies = [ - "version_check", + "cc", + "image", ] [[package]] -name = "slug" -version = "0.1.6" +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" dependencies = [ - "deunicode", - "wasm-bindgen", + "serde", ] [[package]] -name = "smallvec" -version = "1.15.1" +name = "snap" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "199905e6153d6405f9728fe44daace35f8f837bbf830bb6e85fbd5828709a886" [[package]] -name = "socket-pktinfo" -version = "0.3.2" +name = "socket2" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927136cc2ae6a1b0e66ac6b1210902b75c3f726db004a73bc18686dcd0dcd22f" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", - "socket2", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.60.2", @@ -9157,19 +13816,25 @@ dependencies = [ "bytemuck", "js-sys", "ndk", - "objc2 0.6.4", + "objc2", "objc2-core-foundation", "objc2-core-graphics", - "objc2-foundation 0.3.2", + "objc2-foundation", "objc2-quartz-core", "raw-window-handle", - "redox_syscall 0.5.18", + "redox_syscall", "tracing", "wasm-bindgen", "web-sys", "windows-sys 0.61.2", ] +[[package]] +name = "sorted-index-buffer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea06cc588e43c632923a55450401b8f25e628131571d4e1baea1bdfdb2b5ed06" + [[package]] name = "soup3" version = "0.5.0" @@ -9193,35 +13858,43 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] -name = "spin" -version = "0.9.8" +name = "spez" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "c87e960f4dca2788eeb86bbdde8dd246be8948790b7618d656e68f9b720a86e8" dependencies = [ - "lock_api", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] name = "spin" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" + +[[package]] +name = "spirv" +version = "0.4.0+sdk-1.4.341.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" dependencies = [ - "lock_api", - "portable-atomic", + "bitflags 2.13.1", ] [[package]] -name = "spirv" -version = "0.3.0+sdk-1.3.268.0" +name = "spki" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" dependencies = [ - "bitflags 2.11.0", + "base64ct", + "der", ] [[package]] @@ -9237,12 +13910,15 @@ dependencies = [ ] [[package]] -name = "stable-vec" -version = "0.4.1" +name = "sqlite-wasm-rs" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1dff32a2ce087283bec878419027cebd888760d8760b2941ad0843531dc9ec8" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" dependencies = [ - "no-std-compat", + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", ] [[package]] @@ -9251,12 +13927,37 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "stacker" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "640c8cdd92b6b12f5bcb1803ca3bbf5ab96e5e6b6b96b9ab77dabe9e880b3190" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.60.2", +] + [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "statrs" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f697a07e4606a0a25c044de247e583a330dbb1731d11bc7350b81f48ad567255" +dependencies = [ + "approx", + "nalgebra", + "num-traits", + "rand 0.8.7", +] + [[package]] name = "strength_reduce" version = "0.2.4" @@ -9265,25 +13966,24 @@ checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" [[package]] name = "string_cache" -version = "0.8.9" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared 0.11.3", + "phf_shared 0.13.1", "precomputed-hash", - "serde", ] [[package]] name = "string_cache_codegen" -version = "0.5.4" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", + "phf_generator 0.13.1", + "phf_shared 0.13.1", "proc-macro2", "quote", ] @@ -9305,11 +14005,11 @@ dependencies = [ [[package]] name = "strum" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" dependencies = [ - "strum_macros 0.27.2", + "strum_macros 0.28.0", ] [[package]] @@ -9322,19 +14022,19 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "strum_macros" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -9361,11 +14061,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5773a4c030a19d9bfaa090f49746ff35c75dfddfa700df7a5939d5e076a57039" dependencies = [ "lazy_static", + "symphonia-bundle-flac", + "symphonia-bundle-mp3", + "symphonia-codec-aac", "symphonia-core", + "symphonia-format-isomp4", "symphonia-format-riff", "symphonia-metadata", ] +[[package]] +name = "symphonia-bundle-flac" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91565e180aea25d9b80a910c546802526ffd0072d0b8974e3ebe59b686c9976" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-bundle-mp3" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4872dd6bb56bf5eac799e3e957aa1981086c3e613b27e0ac23b176054f7c57ed" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-codec-aac" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c263845aa86881416849c1729a54c7f55164f8b96111dba59de46849e73a790" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", +] + [[package]] name = "symphonia-core" version = "0.5.5" @@ -9379,6 +14118,19 @@ dependencies = [ "log", ] +[[package]] +name = "symphonia-format-isomp4" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243739585d11f81daf8dac8d9f3d18cc7898f6c09a259675fc364b382c30e0a5" +dependencies = [ + "encoding_rs", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + [[package]] name = "symphonia-format-riff" version = "0.5.5" @@ -9403,6 +14155,16 @@ dependencies = [ "symphonia-core", ] +[[package]] +name = "symphonia-utils-xiph" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27c85ab799a338446b68eec77abf42e1a6f1bb490656e121c6e27bfbab9f16" +dependencies = [ + "symphonia-core", + "symphonia-metadata", +] + [[package]] name = "syn" version = "1.0.109" @@ -9416,9 +14178,20 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -9442,7 +14215,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -9451,7 +14224,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "byteorder", "enum-as-inner", "libc", @@ -9461,30 +14234,31 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.36.1" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d" +checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" dependencies = [ "libc", "memchr", "ntapi", "objc2-core-foundation", "objc2-io-kit", - "windows 0.61.3", + "windows 0.62.2", ] [[package]] name = "sysinfo" -version = "0.37.2" +version = "0.39.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" +checksum = "d2071df9448915b71c4fe6d25deaf1c22f12bd234f01540b77312bb8e41361e6" dependencies = [ "libc", "memchr", "ntapi", "objc2-core-foundation", "objc2-io-kit", - "windows 0.61.3", + "objc2-open-directory", + "windows 0.62.2", ] [[package]] @@ -9493,7 +14267,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -9514,7 +14288,7 @@ version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ - "cfg-expr", + "cfg-expr 0.15.8", "heck 0.5.0", "pkg-config", "toml 0.8.2", @@ -9522,30 +14296,72 @@ dependencies = [ ] [[package]] -name = "systemstat" -version = "0.2.6" +name = "system-deps" +version = "7.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6e89b75de097d0c52a1dc2114e19439d55f0e2e42d32168c6df44f139dfb66f" +checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" dependencies = [ - "bytesize", - "lazy_static", + "cfg-expr 0.20.8", + "heck 0.5.0", + "pkg-config", + "toml 1.1.3+spec-1.1.0", + "version-compare", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tao" +version = "0.34.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20" +dependencies = [ + "bitflags 2.13.1", + "block2", + "core-foundation 0.10.1", + "core-graphics", + "crossbeam-channel", + "dispatch2", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gtk", + "jni 0.21.1", "libc", - "nom 7.1.3", - "time", - "winapi", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", + "parking_lot", + "raw-window-handle", + "tao-macros", + "unicode-segmentation", + "url", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-version", ] [[package]] name = "tao" -version = "0.34.6" +version = "0.35.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d52c379e63da659a483a958110bbde891695a0ecb53e48cc7786d5eda7bb" +checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" dependencies = [ - "bitflags 2.11.0", - "block2 0.6.2", + "bitflags 2.13.1", + "block2", "core-foundation 0.10.1", - "core-graphics 0.25.0", + "core-graphics", "crossbeam-channel", + "dbus", "dispatch2", "dlopen2", "dpi", @@ -9556,13 +14372,14 @@ dependencies = [ "libc", "log", "ndk", - "ndk-context", "ndk-sys", - "objc2 0.6.4", + "objc2", "objc2-app-kit", - "objc2-foundation 0.3.2", + "objc2-foundation", + "objc2-ui-kit", "once_cell", "parking_lot", + "percent-encoding", "raw-window-handle", "tao-macros", "unicode-segmentation", @@ -9581,31 +14398,43 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "tar" -version = "0.4.44" +version = "0.4.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" dependencies = [ "filetime", "libc", "xattr", ] +[[package]] +name = "target-features" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1bbb9f3c5c463a01705937a24fdabc5047929ac764b2d5b9cf681c1f5041ed5" + [[package]] name = "target-lexicon" version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + [[package]] name = "tauri" -version = "2.10.3" +version = "2.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da77cc00fb9028caf5b5d4650f75e31f1ef3693459dfca7f7e506d1ecef0ba2d" +checksum = "667b20e2726d572dea2de7370da16e188eb06008faf9a92fab7cdc46791190b5" dependencies = [ "anyhow", "bytes", @@ -9618,21 +14447,21 @@ dependencies = [ "gtk", "heck 0.5.0", "http", - "image 0.25.9", + "image", "jni 0.21.1", "libc", "log", "mime", "muda", - "objc2 0.6.4", + "objc2", "objc2-app-kit", - "objc2-foundation 0.3.2", + "objc2-foundation", "objc2-ui-kit", "objc2-web-kit", "percent-encoding", "plist", "raw-window-handle", - "reqwest 0.13.2", + "reqwest 0.13.4", "serde", "serde_json", "serde_repr", @@ -9643,7 +14472,7 @@ dependencies = [ "tauri-runtime", "tauri-runtime-wry", "tauri-utils", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "tray-icon", "url", @@ -9655,9 +14484,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.5.6" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bbc990d1dbf57a8e1c7fa2327f2a614d8b757805603c1b9ba5c81bade09fd4d" +checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632" dependencies = [ "anyhow", "cargo_toml", @@ -9671,15 +14500,14 @@ dependencies = [ "serde_json", "tauri-utils", "tauri-winres", - "toml 0.9.12+spec-1.1.0", "walkdir", ] [[package]] name = "tauri-codegen" -version = "2.5.5" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a24476afd977c5d5d169f72425868613d82747916dd29e0a357c84c4bd6d29" +checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5" dependencies = [ "base64 0.22.1", "brotli", @@ -9692,10 +14520,10 @@ dependencies = [ "semver", "serde", "serde_json", - "sha2", - "syn 2.0.117", + "sha2 0.10.9", + "syn 2.0.119", "tauri-utils", - "thiserror 2.0.18", + "thiserror 2.0.19", "time", "url", "uuid", @@ -9704,23 +14532,23 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.5.5" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39b349a98dadaffebb73f0a40dcd1f23c999211e5a2e744403db384d0c33de7" +checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "tauri-codegen", "tauri-utils", ] [[package]] name = "tauri-plugin" -version = "2.5.4" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddde7d51c907b940fb573006cdda9a642d6a7c8153657e88f8a5c3c9290cd4aa" +checksum = "74be5dd4bed9afbd145e5716b5fa2ec28cbc29c34ffa61c258c9273d896c8020" dependencies = [ "anyhow", "glob", @@ -9729,15 +14557,14 @@ dependencies = [ "serde", "serde_json", "tauri-utils", - "toml 0.9.12+spec-1.1.0", "walkdir", ] [[package]] name = "tauri-plugin-global-shortcut" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "424af23c7e88d05e4a1a6fc2c7be077912f8c76bd7900fd50aa2b7cbf5a2c405" +checksum = "b4dd9f4c5136c09cd962da0c86dc4accd4666db2ea591cf16e6597435843bd2b" dependencies = [ "global-hotkey", "log", @@ -9745,7 +14572,7 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] @@ -9756,37 +14583,37 @@ checksum = "01fc2c5ff41105bd1f7242d8201fdf3efd70749b82fa013a17f2126357d194cc" dependencies = [ "log", "notify-rust", - "rand 0.9.2", + "rand 0.9.5", "serde", "serde_json", "serde_repr", "tauri", "tauri-plugin", - "thiserror 2.0.18", + "thiserror 2.0.19", "time", "url", ] [[package]] name = "tauri-plugin-opener" -version = "2.5.3" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc624469b06f59f5a29f874bbc61a2ed737c0f9c23ef09855a292c389c42e83f" +checksum = "17e1bea14edce6b793a04e2417e3fd924b9bc4faae83cdee7d714156cceeed29" dependencies = [ "dunce", "glob", "objc2-app-kit", - "objc2-foundation 0.3.2", + "objc2-foundation", "open", "schemars 0.8.22", "serde", "serde_json", "tauri", "tauri-plugin", - "thiserror 2.0.18", + "thiserror 2.0.19", "url", "windows 0.61.3", - "zbus", + "zbus 5.18.0", ] [[package]] @@ -9799,11 +14626,27 @@ dependencies = [ "tauri-plugin", ] +[[package]] +name = "tauri-plugin-single-instance" +version = "2.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3214becf9ef5783c0ae99a3bb25adf5353a7a16ebf53e74b909e29205735c6c" +dependencies = [ + "serde", + "serde_json", + "tauri", + "thiserror 2.0.19", + "tokio", + "tracing", + "windows-sys 0.60.2", + "zbus 5.18.0", +] + [[package]] name = "tauri-plugin-updater" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fe8e9bebd88fc222938ffdfbdcfa0307081423bd01e3252fc337d8bde81fc61" +checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af" dependencies = [ "base64 0.22.1", "dirs", @@ -9815,7 +14658,7 @@ dependencies = [ "minisign-verify", "osakit", "percent-encoding", - "reqwest 0.13.2", + "reqwest 0.13.4", "rustls", "semver", "serde", @@ -9824,7 +14667,7 @@ dependencies = [ "tauri", "tauri-plugin", "tempfile", - "thiserror 2.0.18", + "thiserror 2.0.19", "time", "tokio", "url", @@ -9834,23 +14677,23 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.10.1" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2826d79a3297ed08cd6ea7f412644ef58e32969504bc4fbd8d7dbeabc4445ea2" +checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8" dependencies = [ "cookie", "dpi", "gtk", "http", "jni 0.21.1", - "objc2 0.6.4", + "objc2", "objc2-ui-kit", "objc2-web-kit", "raw-window-handle", "serde", "serde_json", "tauri-utils", - "thiserror 2.0.18", + "thiserror 2.0.19", "url", "webkit2gtk", "webview2-com", @@ -9859,50 +14702,50 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.10.1" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11ea2e6f801d275fdd890d6c9603736012742a1c33b96d0db788c9cdebf7f9e" +checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" dependencies = [ "gtk", "http", "jni 0.21.1", "log", - "objc2 0.6.4", + "objc2", "objc2-app-kit", "once_cell", "percent-encoding", "raw-window-handle", "softbuffer", - "tao", + "tao 0.35.3", "tauri-runtime", "tauri-utils", "url", "webkit2gtk", "webview2-com", "windows 0.61.3", - "wry", + "wry 0.55.1", ] [[package]] name = "tauri-utils" -version = "2.8.3" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219a1f983a2af3653f75b5747f76733b0da7ff03069c7a41901a5eb3ace4557d" +checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" dependencies = [ "anyhow", "brotli", "cargo_metadata", "ctor", + "dom_query", "dunce", "glob", - "html5ever", "http", "infer", "json-patch", - "kuchikiki", "log", "memchr", - "phf 0.11.3", + "phf 0.13.1", + "plist", "proc-macro2", "quote", "regex", @@ -9913,8 +14756,8 @@ dependencies = [ "serde_json", "serde_with", "swift-rs", - "thiserror 2.0.18", - "toml 0.9.12+spec-1.1.0", + "thiserror 2.0.19", + "toml 1.1.3+spec-1.1.0", "url", "urlpattern", "uuid", @@ -9923,66 +14766,46 @@ dependencies = [ [[package]] name = "tauri-winres" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" +checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" dependencies = [ "dunce", "embed-resource", - "toml 0.9.12+spec-1.1.0", + "toml 1.1.3+spec-1.1.0", ] [[package]] name = "tauri-winrt-notification" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9" +checksum = "9ed071c670382e85fc2f48ae706492d8c338f4f89bf72520d32f8abfe880aade" dependencies = [ - "quick-xml 0.37.5", - "thiserror 2.0.18", + "thiserror 2.0.19", "windows 0.61.3", "windows-version", ] -[[package]] -name = "tch" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e09b91610202dc4820c21eb474a42b386ef69f323b1c0902b5472ba7456ebb5" -dependencies = [ - "half", - "lazy_static", - "libc", - "ndarray 0.16.1", - "rand 0.8.5", - "safetensors 0.3.3", - "thiserror 1.0.69", - "torch-sys", - "zip 0.6.6", -] - [[package]] name = "tempfile" -version = "3.26.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] name = "tendril" -version = "0.4.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08" dependencies = [ - "futf", - "mac", - "utf-8", + "new_debug_unreachable", ] [[package]] @@ -10006,21 +14829,80 @@ dependencies = [ ] [[package]] -name = "text_placeholder" -version = "0.5.1" +name = "termina" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5008f74a09742486ef0047596cf35df2b914e2a8dca5727fcb6ba6842a766b" +checksum = "9048a889effe34a5cddee0af7f53285198b16dca3be510858d38dfdb3e62a04e" dependencies = [ - "hashbrown 0.13.2", - "serde", - "serde_json", + "bitflags 2.13.1", + "parking_lot", + "rustix 1.1.4", + "signal-hook", + "windows-sys 0.60.2", ] [[package]] -name = "textdistance" -version = "1.1.1" +name = "terminfo" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" +dependencies = [ + "fnv", + "nom 7.1.3", + "phf 0.11.3", + "phf_codegen 0.11.3", +] + +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + +[[package]] +name = "termwiz" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa672c55ab69f787dbc9126cc387dbe57fdd595f585e4524cf89018fa44ab819" +checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" +dependencies = [ + "anyhow", + "base64 0.22.1", + "bitflags 2.13.1", + "fancy-regex 0.11.0", + "filedescriptor", + "finl_unicode", + "fixedbitset 0.4.2", + "hex", + "lazy_static", + "libc", + "log", + "memmem", + "nix 0.29.0", + "num-derive", + "num-traits", + "ordered-float 4.6.0", + "pest", + "pest_derive", + "phf 0.11.3", + "sha2 0.10.9", + "signal-hook", + "siphasher", + "terminfo", + "termios", + "thiserror 1.0.69", + "ucd-trie", + "unicode-segmentation", + "vtparse", + "wezterm-bidi", + "wezterm-blob-leases", + "wezterm-color-types", + "wezterm-dynamic", + "wezterm-input-types", + "winapi", +] [[package]] name = "thiserror" @@ -10033,11 +14915,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.19", ] [[package]] @@ -10048,18 +14930,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", ] [[package]] @@ -10073,35 +14955,32 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" dependencies = [ "cfg-if", ] [[package]] -name = "tiff" -version = "0.10.3" +name = "thrift" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +checksum = "7e54bc85fc7faa8bc175c4bab5b92ba8d9a3ce893d0e9f42cc455c8ab16a9e09" dependencies = [ - "fax", - "flate2", - "half", - "quick-error", - "weezl", - "zune-jpeg 0.4.21", + "byteorder", + "integer-encoding", + "ordered-float 2.10.1", ] [[package]] name = "time" -version = "0.3.47" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" dependencies = [ "deranged", - "itoa", + "js-sys", "libc", "num-conv", "num_threads", @@ -10113,15 +14992,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -10136,21 +15015,43 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tiny_http" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" +dependencies = [ + "ascii", + "chunked_transfer", + "httpdate", + "log", +] + [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -10169,7 +15070,7 @@ checksum = "b238e22d44a15349529690fb07bd645cf58149a1b1e44d6cb5bd1641ff1a6223" dependencies = [ "ahash", "aho-corasick", - "compact_str 0.9.0", + "compact_str", "dary_heap", "derive_builder", "esaxx-rs", @@ -10180,7 +15081,7 @@ dependencies = [ "monostate", "onig", "paste", - "rand 0.9.2", + "rand 0.9.5", "rayon", "rayon-cond", "regex", @@ -10188,7 +15089,7 @@ dependencies = [ "serde", "serde_json", "spm_precompiled", - "thiserror 2.0.18", + "thiserror 2.0.19", "unicode-normalization-alignments", "unicode-segmentation", "unicode_categories", @@ -10196,9 +15097,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.50.0" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -10206,21 +15107,20 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.5", "tokio-macros", - "tracing", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -10245,9 +15145,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" dependencies = [ "futures-core", "pin-project-lite", @@ -10255,6 +15155,36 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "tokio-tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite 0.24.0", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite 0.26.2", +] + [[package]] name = "tokio-tungstenite" version = "0.28.0" @@ -10264,22 +15194,59 @@ dependencies = [ "futures-util", "log", "tokio", - "tungstenite", + "tungstenite 0.28.0", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.29.0", ] [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-sink", + "futures-util", + "libc", "pin-project-lite", "tokio", ] +[[package]] +name = "tokio-websockets" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52efb639344a7c6adb8e62c6f3d2c19c001ff1b79a5041ba1c6ed42e19c6aa5" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-sink", + "getrandom 0.4.3", + "http", + "httparse", + "rand 0.10.2", + "ring", + "rustls-pki-types", + "sha1_smol", + "simdutf8", + "tokio", + "tokio-rustls", + "tokio-util", +] + [[package]] name = "toml" version = "0.8.2" @@ -10298,15 +15265,30 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "serde_core", - "serde_spanned 1.0.4", + "serde_spanned 1.1.1", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", "winnow 0.7.15", ] +[[package]] +name = "toml" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.4", +] + [[package]] name = "toml_datetime" version = "0.6.3" @@ -10327,9 +15309,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] @@ -10340,7 +15322,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "toml_datetime 0.6.3", "winnow 0.5.40", ] @@ -10351,7 +15333,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.3", @@ -10360,44 +15342,46 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.4+spec-1.1.0" +version = "0.25.13+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ - "indexmap 2.13.0", - "toml_datetime 1.0.0+spec-1.1.0", + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 0.7.15", + "winnow 1.0.4", ] [[package]] name = "toml_parser" -version = "1.0.9+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 0.7.15", + "winnow 1.0.4", ] [[package]] name = "toml_writer" -version = "1.0.6+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" [[package]] -name = "torch-sys" -version = "0.22.0" +name = "totp-rs" +version = "5.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef40c585e342df95b66a1fa7c923188623999c2b657227befb481dfb03a6a42" +checksum = "50e69a15e21b2ff22c415446983978bded3244195f17d59cb113551c1e806f91" dependencies = [ - "anyhow", - "cc", - "libc", - "serde", - "serde_json", - "ureq 2.12.1", - "zip 0.6.6", + "base32", + "constant_time_eq 0.3.1", + "hmac", + "qrcodegen-image", + "rand 0.9.5", + "sha1", + "sha2 0.10.9", + "url", + "urlencoding", ] [[package]] @@ -10418,20 +15402,29 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "bytes", + "futures-core", "futures-util", "http", "http-body", - "iri-string", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", "pin-project-lite", + "tokio", + "tokio-util", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -10446,85 +15439,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" -[[package]] -name = "tracel-llvm" -version = "20.1.4-7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982535db9eb1a30ac0f2c50239a0eec3e5cf50993a88e92b04747bd2f4d365b2" -dependencies = [ - "tracel-mlir-rs", - "tracel-mlir-sys", -] - -[[package]] -name = "tracel-llvm-bundler" -version = "20.1.4-7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c75b8e477cb8d49d907afab029ca74d48459f5b88c27bdb4c6cd6acb5e61977" -dependencies = [ - "anyhow", - "bytes", - "constcat", - "dirs", - "liblzma", - "regex", - "reqwest 0.12.28", - "serde", - "serde_json", - "sha2", - "tar", - "walkdir", -] - -[[package]] -name = "tracel-mlir-rs" -version = "20.1.4-7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a478a35efd68d0ba73f747adfb7923b121c64e7f5be9cd8364ca1dcb772d5c" -dependencies = [ - "tracel-mlir-rs-macros", - "tracel-mlir-sys", -] - -[[package]] -name = "tracel-mlir-rs-macros" -version = "20.1.4-7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a94f36868c3b10b1825945223d99d106c73f4d249f063caa4651deeb9379344" -dependencies = [ - "comrak", - "convert_case 0.8.0", - "proc-macro2", - "quote", - "regex", - "syn 2.0.117", - "tracel-llvm-bundler", - "tracel-tblgen-rs", - "unindent", -] - -[[package]] -name = "tracel-mlir-sys" -version = "20.1.4-7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02f26d31af0c225a6d2e3d65d012fd6de848c9fc776897b152ee83b7d1bd15c4" -dependencies = [ - "tracel-llvm-bundler", -] - -[[package]] -name = "tracel-tblgen-rs" -version = "20.1.4-7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00d2581070380418ccc33b500f3739e4d4869421fdb477fcea51ff97c6253a52" -dependencies = [ - "bindgen 0.71.1", - "cc", - "paste", - "thiserror 2.0.18", - "tracel-llvm-bundler", -] - [[package]] name = "tracing" version = "0.1.44" @@ -10537,18 +15451,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-appender" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" -dependencies = [ - "crossbeam-channel", - "thiserror 2.0.18", - "time", - "tracing-subscriber", -] - [[package]] name = "tracing-attributes" version = "0.1.31" @@ -10557,7 +15459,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -10583,9 +15485,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", @@ -10611,26 +15513,86 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.21.3" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" +checksum = "65ba1e5f6b9ef9fd87e21b9c6f351554dbd717960089168fcfdef854686961dc" dependencies = [ "crossbeam-channel", "dirs", "libappindicator", "muda", - "objc2 0.6.4", + "objc2", "objc2-app-kit", "objc2-core-foundation", "objc2-core-graphics", - "objc2-foundation 0.3.2", + "objc2-foundation", "once_cell", - "png 0.17.16", + "png 0.18.1", "serde", - "thiserror 2.0.18", + "thiserror 2.0.19", "windows-sys 0.60.2", ] +[[package]] +name = "tribev2" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8787f1c923262aee6f4a8f012467b759c282b3ad5f1196a6dda61faa00e083c" +dependencies = [ + "anyhow", + "bytemuck", + "cc", + "clap", + "flate2", + "half", + "rlx", + "rlx-flow", + "rlx-llama32", + "rlx-models-core", + "safetensors 0.7.0", + "serde", + "serde_json", + "serde_yaml", + "tempfile", + "tracing", + "tribev2-audio", + "tribev2-video", +] + +[[package]] +name = "tribev2-audio" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1554989d1923d4a75689aae1edb38395fbf651ca959b69ff67d64a40674d885" +dependencies = [ + "anyhow", + "hound", + "rlx", + "rlx-flow", + "rlx-models-core", + "rlx-wav2vec2-bert", + "rubato", + "serde", + "serde_json", + "tempfile", +] + +[[package]] +name = "tribev2-video" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7adc19ef4d8c101e2f94652a3028421a020a57ac2b02f2e0c36ecb8756c04d58" +dependencies = [ + "anyhow", + "image", + "rlx", + "rlx-models-core", + "rlx-vjepa2", + "serde", + "serde_json", + "tempfile", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -10638,42 +15600,119 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "ttf-parser" -version = "0.20.0" +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "native-tls", + "rand 0.8.7", + "sha1", + "thiserror 1.0.69", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.5", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 2.0.19", + "utf-8", +] [[package]] name = "tungstenite" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.5", + "sha1", + "thiserror 2.0.19", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" dependencies = [ "bytes", "data-encoding", "http", "httparse", "log", - "rand 0.9.2", + "rand 0.9.5", "sha1", - "thiserror 2.0.18", - "utf-8", + "thiserror 2.0.19", ] [[package]] -name = "type-map" -version = "0.5.1" +name = "turbovec" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +checksum = "0185030ca15ea4f8b00c22f02021baa613c2ed3fc83449e32c368debd4a4fe37" dependencies = [ - "rustc-hash 2.1.1", + "blas-src", + "faer", + "ndarray 0.17.2", + "openblas-src", + "ordered-float 4.6.0", + "rand 0.8.7", + "rand_chacha 0.3.1", + "rand_distr", + "rayon", + "statrs", ] [[package]] -name = "typed-arena" -version = "2.0.2" +name = "twox-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8464ec13c3691491391d9fce00f6416c9a48e46972f72d7865688be2080192c9" + +[[package]] +name = "typed-builder" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34085c17941e36627a879208083e25d357243812c30e7d7387c3b954f30ade16" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" +checksum = "f03ca4cb38206e2bef0700092660bb74d696f808514dae47fa1467cbfe26e96e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] name = "typed-path" @@ -10689,63 +15728,40 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] -name = "uds_windows" -version = "1.2.0" +name = "ucd-trie" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b70b87d15e91f553711b40df3048faf27a7a04e01e0ddc0cf9309f0af7c2ca" -dependencies = [ - "memoffset", - "tempfile", - "windows-sys 0.61.2", -] +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] -name = "ug" -version = "0.5.0" +name = "uds_windows" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b761acf8af3494640d826a8609e2265e19778fb43306c7f15379c78c9b05b0" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ - "gemm 0.18.2", - "half", - "libloading 0.8.9", - "memmap2", - "num", - "num-traits", - "num_cpus", - "rayon", - "safetensors 0.4.5", - "serde", - "thiserror 1.0.69", - "tracing", - "yoke 0.7.5", + "memoffset", + "tempfile", + "windows-sys 0.60.2", ] [[package]] -name = "ug-metal" -version = "0.5.0" +name = "unarray" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7adf545a99a086d362efc739e7cf4317c18cbeda22706000fd434d70ea3d95" -dependencies = [ - "half", - "metal 0.29.0", - "objc", - "serde", - "thiserror 1.0.69", - "ug", -] +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unescaper" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4064ed685c487dbc25bd3f0e9548f2e34bab9d18cefc700f9ec2dba74ba1138e" +checksum = "7285e83a80ce76f5e7bce79fa41f68d78ba62d1003cf27bf748ab24413808cf4" dependencies = [ - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] @@ -10789,6 +15805,18 @@ dependencies = [ "unic-common", ] +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-general-category" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -10815,19 +15843,19 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-truncate" -version = "1.1.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" dependencies = [ - "itertools 0.13.0", + "itertools 0.14.0", "unicode-segmentation", - "unicode-width 0.1.14", + "unicode-width 0.2.2", ] [[package]] @@ -10838,9 +15866,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" @@ -10854,12 +15882,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" -[[package]] -name = "unindent" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" - [[package]] name = "unit-prefix" version = "0.5.2" @@ -10867,16 +15889,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" [[package]] -name = "untrusted" -version = "0.9.0" +name = "universal-hash" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] [[package]] -name = "unty" -version = "0.0.4" +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" @@ -10887,22 +15919,20 @@ dependencies = [ "base64 0.22.1", "flate2", "log", - "native-tls", "once_cell", "rustls", "rustls-pki-types", "serde", "serde_json", - "socks", "url", "webpki-roots 0.26.11", ] [[package]] name = "ureq" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" dependencies = [ "base64 0.22.1", "cookie_store", @@ -10917,16 +15947,16 @@ dependencies = [ "serde_json", "socks", "ureq-proto", - "utf-8", + "utf8-zero", "webpki-root-certs", - "webpki-roots 1.0.6", + "webpki-roots 1.0.9", ] [[package]] name = "ureq-proto" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" dependencies = [ "base64 0.22.1", "http", @@ -10947,6 +15977,12 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "urlpattern" version = "0.3.0" @@ -10965,6 +16001,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -10979,26 +16021,25 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.22.0" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" dependencies = [ - "getrandom 0.4.2", + "atomic", + "getrandom 0.4.3", "js-sys", - "rand 0.10.0", "serde_core", "wasm-bindgen", ] [[package]] -name = "v_frame" -version = "0.3.9" +name = "uuid-simd" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" dependencies = [ - "aligned-vec", - "num-traits", - "wasm-bindgen", + "outref", + "vsimd", ] [[package]] @@ -11007,17 +16048,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "variadics_please" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "vcpkg" version = "0.2.15" @@ -11037,10 +16067,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] -name = "void" -version = "1.0.2" +name = "vital" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a9d23daf944214827f451628d9cb918d37cb77591907035004b04ae48f8aaf2" +dependencies = [ + "anyhow", + "env_logger", + "log", + "tokio", + "uuid", + "webbluetooth", + "webbluetooth-wasm", +] + +[[package]] +name = "vsimd" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vswhom" @@ -11062,6 +16107,24 @@ dependencies = [ "libc", ] +[[package]] +name = "vtparse" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -11081,12 +16144,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -11095,27 +16152,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -11126,128 +16174,278 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.114" +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wayland-backend" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016ccf01d1c58b6f8999612813e17c9b2390f7d70671428869913310f83f54b8" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.4", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" +dependencies = [ + "bitflags 2.13.1", + "rustix 1.1.4", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" +dependencies = [ + "bitflags 2.13.1", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.13.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" +dependencies = [ + "proc-macro2", + "quick-xml 0.41.0", + "quote", +] + +[[package]] +name = "wayland-server" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dde9c29be0f723a573977de51ee455bf3dfa03652730a74f9dd3b337e374d75" +dependencies = [ + "bitflags 2.13.1", + "downcast-rs", + "rustix 1.1.4", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "libc", + "log", + "memoffset", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 2.0.117", - "wasm-bindgen-shared", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.114" +name = "web_atoms" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "075474b12bcb3d2e3d4546580e9de478eeeead668a1761e2a8860c836b7ef297" dependencies = [ - "unicode-ident", + "phf 0.13.1", + "phf_codegen 0.13.1", + "string_cache", + "string_cache_codegen", ] [[package]] -name = "wasm-encoder" -version = "0.244.0" +name = "webbluetooth" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +checksum = "15ee140f813b72a7edbae7daa5fba43d8aaa5356b499ddebafd41ce28add0835" dependencies = [ - "leb128fmt", - "wasmparser", + "futures-channel", + "futures-core", + "futures-util", + "webbluetooth-android", + "webbluetooth-apple", + "webbluetooth-core", + "webbluetooth-linux", + "webbluetooth-wasm", + "webbluetooth-windows", ] [[package]] -name = "wasm-metadata" -version = "0.244.0" +name = "webbluetooth-android" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +checksum = "67b3f0bf21c5efb1ef3dd051490732a5c2ce8f8fffda948182f4030270b4ff7f" dependencies = [ - "anyhow", - "indexmap 2.13.0", - "wasm-encoder", - "wasmparser", + "futures-channel", + "futures-core", + "futures-util", + "webbluetooth-core", ] [[package]] -name = "wasm-streams" -version = "0.4.2" +name = "webbluetooth-apple" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +checksum = "42ed71e8318511169cebc673f7a8feb869f08beda93e3e47fc345e696a5c389d" dependencies = [ + "futures-channel", + "futures-core", "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", + "webbluetooth-core", ] [[package]] -name = "wasm-streams" -version = "0.5.0" +name = "webbluetooth-core" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +checksum = "2d9d4e0b866240f37513d958260ae4673d44af9479bc454d40aa521ea0d02c37" dependencies = [ + "futures-channel", + "futures-core", "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", + "uuid", ] [[package]] -name = "wasmparser" -version = "0.244.0" +name = "webbluetooth-linux" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +checksum = "251d1f9762dcdbf14a9844c0b7f195a311453d49532c1c7af0131bc87962039c" dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.15.5", - "indexmap 2.13.0", - "semver", + "futures-channel", + "futures-core", + "futures-executor", + "futures-util", + "webbluetooth-core", ] [[package]] -name = "web-sys" -version = "0.3.91" +name = "webbluetooth-wasm" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "dc6966f6a21279b36e86bb9d7c6b313dc3d57863de68a80b62b86eb915fe1cbe" dependencies = [ - "js-sys", - "wasm-bindgen", + "webbluetooth-core", ] [[package]] -name = "web-time" -version = "1.1.0" +name = "webbluetooth-windows" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +checksum = "6d175f8cd5c80260caa54986ab7135f0752676e22ddb7c4c12f0c1412d87dbd8" dependencies = [ - "js-sys", - "wasm-bindgen", + "futures-channel", + "futures-core", + "futures-util", + "webbluetooth-core", + "windows-sys 0.59.0", ] [[package]] @@ -11291,14 +16489,14 @@ dependencies = [ "libc", "pkg-config", "soup3-sys", - "system-deps", + "system-deps 6.2.2", ] [[package]] name = "webpki-root-certs" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" dependencies = [ "rustls-pki-types", ] @@ -11309,14 +16507,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.6", + "webpki-roots 1.0.9", ] [[package]] name = "webpki-roots" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" dependencies = [ "rustls-pki-types", ] @@ -11331,8 +16529,8 @@ dependencies = [ "webview2-com-sys", "windows 0.61.3", "windows-core 0.61.2", - "windows-implement 0.60.2", - "windows-interface 0.59.3", + "windows-implement", + "windows-interface", ] [[package]] @@ -11343,7 +16541,7 @@ checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -11352,7 +16550,7 @@ version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" dependencies = [ - "thiserror 2.0.18", + "thiserror 2.0.19", "windows 0.61.3", "windows-core 0.61.2", ] @@ -11363,18 +16561,91 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" +[[package]] +name = "wezterm-bidi" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" +dependencies = [ + "log", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-blob-leases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" +dependencies = [ + "getrandom 0.3.4", + "mac_address", + "sha2 0.10.9", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "wezterm-color-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" +dependencies = [ + "csscolorparser", + "deltae", + "lazy_static", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-dynamic" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" +dependencies = [ + "log", + "ordered-float 4.6.0", + "strsim", + "thiserror 1.0.69", + "wezterm-dynamic-derive", +] + +[[package]] +name = "wezterm-dynamic-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "wezterm-input-types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" +dependencies = [ + "bitflags 1.3.2", + "euclid", + "lazy_static", + "serde", + "wezterm-dynamic", +] + [[package]] name = "wgpu" -version = "26.0.1" +version = "30.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70b6ff82bbf6e9206828e1a3178e851f8c20f1c9028e74dd3a8090741ccd5798" +checksum = "6d8f4bd44d92da5270f03409dba9f952dab24f128e05d6a554926101d1bf9114" dependencies = [ "arrayvec", - "bitflags 2.11.0", + "bitflags 2.13.1", + "bytemuck", "cfg-if", "cfg_aliases", "document-features", - "hashbrown 0.15.5", + "hashbrown 0.17.1", "js-sys", "log", "naga", @@ -11394,20 +16665,22 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "26.0.1" +version = "30.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f62f1053bd28c2268f42916f31588f81f64796e2ff91b81293515017ca8bd9" +checksum = "08763620e76fc980bca7bf84de82568614487a53172dd968d89187282eb87fa2" dependencies = [ "arrayvec", - "bit-set", - "bit-vec", - "bitflags 2.11.0", + "bit-set 0.10.0", + "bit-vec 0.9.1", + "bitflags 2.13.1", + "bytemuck", "cfg_aliases", "document-features", - "hashbrown 0.15.5", - "indexmap 2.13.0", + "hashbrown 0.17.1", + "indexmap 2.14.0", "log", "naga", + "naga-types", "once_cell", "parking_lot", "portable-atomic", @@ -11415,103 +16688,130 @@ dependencies = [ "raw-window-handle", "rustc-hash 1.1.0", "smallvec", - "thiserror 2.0.18", + "thiserror 2.0.19", "wgpu-core-deps-apple", - "wgpu-core-deps-emscripten", "wgpu-core-deps-windows-linux-android", "wgpu-hal", + "wgpu-naga-bridge", "wgpu-types", ] [[package]] name = "wgpu-core-deps-apple" -version = "26.0.0" +version = "30.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18ae5fbde6a4cbebae38358aa73fcd6e0f15c6144b67ef5dc91ded0db125dbdf" -dependencies = [ - "wgpu-hal", -] - -[[package]] -name = "wgpu-core-deps-emscripten" -version = "26.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7670e390f416006f746b4600fdd9136455e3627f5bd763abf9a65daa216dd2d" +checksum = "3283b6da70d7fc892de95840215aa36381b5d0cbc479e88ee2b173c7b992b225" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-windows-linux-android" -version = "26.0.0" +version = "30.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "720a5cb9d12b3d337c15ff0e24d3e97ed11490ff3f7506e7f3d98c68fa5d6f14" +checksum = "f76bc9c1c186ff3d9054e0d224c93c8c1c79d6653907c5249a5c1ea1a2cb1e43" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-hal" -version = "26.0.6" +version = "30.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d0e67224cc7305b3b4eb2cc57ca4c4c3afc665c1d1bee162ea806e19c47bdd" +checksum = "cf765132d8d5f50e192e7880464890c13f4e7457aafe8e5466e8174586e9f101" dependencies = [ "android_system_properties", "arrayvec", "ash", - "bit-set", - "bitflags 2.11.0", - "block", + "bit-set 0.10.0", + "bitflags 2.13.1", + "block2", "bytemuck", "cfg-if", "cfg_aliases", - "core-graphics-types 0.2.0", - "glow", - "glutin_wgl_sys", - "gpu-alloc", "gpu-allocator", - "gpu-descriptor", - "hashbrown 0.15.5", - "js-sys", - "khronos-egl", + "hashbrown 0.17.1", "libc", "libloading 0.8.9", "log", - "metal 0.32.0", "naga", - "ndk-sys", - "objc", - "ordered-float", + "naga-types", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-metal", + "objc2-quartz-core", + "once_cell", + "ordered-float 4.6.0", "parking_lot", "portable-atomic", "portable-atomic-util", "profiling", "range-alloc", "raw-window-handle", + "raw-window-metal", "renderdoc-sys", "smallvec", - "thiserror 2.0.18", - "wasm-bindgen", - "web-sys", + "static_assertions", + "thiserror 2.0.19", + "wgpu-naga-bridge", + "wgpu-types", + "windows 0.62.2", + "windows-core 0.62.2", +] + +[[package]] +name = "wgpu-naga-bridge" +version = "30.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9eaac644e5008925c78567d272b9d66ef83da55a53cc17fc7daade7bb6e66e5" +dependencies = [ + "naga", "wgpu-types", - "windows 0.58.0", - "windows-core 0.58.0", ] [[package]] name = "wgpu-types" -version = "26.0.0" +version = "30.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca7a8d8af57c18f57d393601a1fb159ace8b2328f1b6b5f80893f7d672c9ae2" +checksum = "1a9c93c2b35edde326df60ffdee4c0f5864eac3011d6768b70d43f028ad93565" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "bytemuck", "js-sys", "log", - "thiserror 2.0.18", + "naga-types", + "raw-window-handle", + "static_assertions", "web-sys", ] +[[package]] +name = "which" +version = "8.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3ef584124b911bcc3875c2f1472e80f24361ceb789bd1c62b3e9a3df9ff43c" +dependencies = [ + "libc", +] + +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + [[package]] name = "winapi" version = "0.3.9" @@ -11534,7 +16834,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -11549,25 +16849,15 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" dependencies = [ - "objc2 0.6.4", + "objc2", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.2", + "objc2-foundation", "raw-window-handle", "windows-sys 0.59.0", "windows-version", ] -[[package]] -name = "windows" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" -dependencies = [ - "windows-core 0.58.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows" version = "0.61.3" @@ -11611,27 +16901,14 @@ dependencies = [ "windows-core 0.62.2", ] -[[package]] -name = "windows-core" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", + "windows-implement", + "windows-interface", "windows-link 0.1.3", "windows-result 0.3.4", "windows-strings 0.4.2", @@ -11643,8 +16920,8 @@ version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", + "windows-implement", + "windows-interface", "windows-link 0.2.1", "windows-result 0.4.1", "windows-strings 0.5.1", @@ -11672,17 +16949,6 @@ dependencies = [ "windows-threading 0.2.1", ] -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "windows-implement" version = "0.60.2" @@ -11691,18 +16957,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -11713,7 +16968,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -11759,15 +17014,6 @@ dependencies = [ "windows-strings 0.5.1", ] -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-result" version = "0.3.4" @@ -11786,16 +17032,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows-strings" version = "0.4.2" @@ -12086,6 +17322,12 @@ name = "winnow" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" dependencies = [ "memchr", ] @@ -12100,156 +17342,111 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "wio" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" -dependencies = [ - "winapi", -] - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck 0.5.0", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap 2.13.0", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.0", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] -name = "wit-parser" -version = "0.244.0" +name = "wmi" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +checksum = "7c81b85c57a57500e56669586496bf2abd5cf082b9d32995251185d105208b64" dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.13.0", + "chrono", + "futures", "log", - "semver", "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", + "thiserror 2.0.19", + "windows 0.61.3", + "windows-core 0.61.2", ] [[package]] -name = "wrapcenum-derive" -version = "0.4.1" +name = "writeable" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76ff259533532054cfbaefb115c613203c73707017459206380f03b3b3f266e" -dependencies = [ - "darling 0.20.11", - "proc-macro2", - "quote", - "syn 2.0.117", -] +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] -name = "writeable" -version = "0.6.2" +name = "wry" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "e5a8135d8676225e5744de000d4dff5a082501bf7db6a1c1495034f8c314edbc" +dependencies = [ + "base64 0.22.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs", + "dom_query", + "dpi", + "dunce", + "gdkx11", + "gtk", + "http", + "javascriptcore-rs", + "jni 0.21.1", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2 0.10.9", + "soup3", + "tao-macros", + "thiserror 2.0.19", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] [[package]] name = "wry" -version = "0.54.2" +version = "0.55.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb26159b420aa77684589a744ae9a9461a95395b848764ad12290a14d960a11a" +checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" dependencies = [ "base64 0.22.1", - "block2 0.6.2", + "block2", "cookie", "crossbeam-channel", "dirs", + "dom_query", "dpi", "dunce", "gdkx11", "gtk", - "html5ever", "http", "javascriptcore-rs", "jni 0.21.1", - "kuchikiki", "libc", "ndk", - "objc2 0.6.4", + "objc2", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.2", + "objc2-foundation", "objc2-ui-kit", "objc2-web-kit", "once_cell", "percent-encoding", "raw-window-handle", - "sha2", + "sha2 0.10.9", "soup3", "tao-macros", - "thiserror 2.0.18", + "thiserror 2.0.19", "url", "webkit2gtk", "webkit2gtk-sys", @@ -12260,6 +17457,25 @@ dependencies = [ "x11-dl", ] +[[package]] +name = "ws_stream_wasm" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version", + "send_wrapper", + "thiserror 2.0.19", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "x11" version = "2.21.0" @@ -12309,92 +17525,144 @@ dependencies = [ ] [[package]] -name = "xkeysym" -version = "0.2.1" +name = "xcap" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" +checksum = "0b31c7f57ddfb14983525d9149eaed160de7d3973aaa546f3e19eb3394122b6b" +dependencies = [ + "dispatch2", + "image", + "libwayshot-xcap", + "log", + "objc2", + "objc2-app-kit", + "objc2-av-foundation", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-media", + "objc2-core-video", + "objc2-foundation", + "percent-encoding", + "pipewire", + "rand 0.10.2", + "scopeguard", + "serde", + "thiserror 2.0.19", + "url", + "widestring", + "windows 0.62.2", + "xcb", + "zbus 5.18.0", +] [[package]] -name = "xml" -version = "1.2.1" +name = "xcb" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8aa498d22c9bbaf482329839bc5620c46be275a19a812e9a22a2b07529a642a" +checksum = "ee4c580d8205abb0a5cf4eb7e927bd664e425b6c3263f9c5310583da96970cf6" +dependencies = [ + "bitflags 1.3.2", + "libc", + "quick-xml 0.30.0", +] [[package]] -name = "xml-rs" -version = "0.8.28" +name = "xdg-home" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] [[package]] -name = "y4m" -version = "0.8.0" +name = "xkeysym" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] -name = "yeslogic-fontconfig-sys" -version = "6.0.0" +name = "xml-rs" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd" -dependencies = [ - "dlib", - "once_cell", - "pkg-config", -] +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" [[package]] -name = "yoke" -version = "0.7.5" +name = "xmltree" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive 0.7.5", - "zerofrom", + "xml-rs", ] +[[package]] +name = "xxhash-rust" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" + [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", - "yoke-derive 0.8.1", + "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "synstructure", ] [[package]] -name = "yoke-derive" -version = "0.8.1" +name = "zbus" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", - "synstructure", + "async-broadcast", + "async-process", + "async-recursion", + "async-trait", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix 0.29.0", + "ordered-stream", + "rand 0.8.7", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros 4.4.0", + "zbus_names 3.0.0", + "zvariant 4.2.0", ] [[package]] name = "zbus" -version = "5.14.0" +version = "5.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc" +checksum = "fe18fb60dc696039e738717b76eaea21e7a4489bbb1885020b43c94236d7e98a" dependencies = [ "async-broadcast", "async-executor", @@ -12419,116 +17687,154 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow 0.7.15", - "zbus_macros", - "zbus_names", - "zvariant", + "winnow 1.0.4", + "zbus_macros 5.18.0", + "zbus_names 4.3.4", + "zvariant 5.13.1", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "zvariant_utils 2.1.0", ] [[package]] name = "zbus_macros" -version = "5.14.0" +version = "5.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222" +checksum = "fe96480bed92df2b442a1a30df364e12d08eed03aeb061f2b8dc6afb2be91119" dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.117", - "zbus_names", - "zvariant", - "zvariant_utils", + "syn 2.0.119", + "zbus_names 4.3.4", + "zvariant 5.13.1", + "zvariant_utils 3.5.0", ] [[package]] name = "zbus_names" -version = "4.3.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" dependencies = [ "serde", - "winnow 0.7.15", - "zvariant", + "static_assertions", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus_names" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +dependencies = [ + "serde", + "winnow 1.0.4", + "zvariant 5.13.1", ] [[package]] name = "zerocopy" -version = "0.8.40" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.40" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", - "yoke 0.8.1", + "yoke", "zerofrom", ] [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ - "yoke 0.8.1", + "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -12537,18 +17843,18 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" dependencies = [ - "aes", + "aes 0.8.4", "byteorder", - "bzip2", - "constant_time_eq", + "bzip2 0.4.4", + "constant_time_eq 0.1.5", "crc32fast", "crossbeam-utils", "flate2", "hmac", - "pbkdf2", + "pbkdf2 0.11.0", "sha1", "time", - "zstd", + "zstd 0.11.2+zstd.1.5.2", ] [[package]] @@ -12562,9 +17868,9 @@ dependencies = [ "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.13.0", + "indexmap 2.14.0", "memchr", - "thiserror 2.0.18", + "thiserror 2.0.19", "zopfli", ] @@ -12576,7 +17882,7 @@ checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" dependencies = [ "arbitrary", "crc32fast", - "indexmap 2.13.0", + "indexmap 2.14.0", "memchr", ] @@ -12586,17 +17892,39 @@ version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" dependencies = [ + "aes 0.8.4", + "bzip2 0.6.1", + "constant_time_eq 0.3.1", "crc32fast", - "indexmap 2.13.0", + "deflate64", + "flate2", + "generic-array", + "getrandom 0.3.4", + "hmac", + "indexmap 2.14.0", + "lzma-rust2", "memchr", + "pbkdf2 0.12.2", + "ppmd-rust", + "sha1", + "time", "typed-path", + "zeroize", + "zopfli", + "zstd 0.13.3", ] +[[package]] +name = "zlib-rs" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5" + [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zopfli" @@ -12616,7 +17944,16 @@ version = "0.11.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" dependencies = [ - "zstd-safe", + "zstd-safe 5.0.2+zstd.1.5.2", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe 7.2.4", ] [[package]] @@ -12629,6 +17966,15 @@ dependencies = [ "zstd-sys", ] +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + [[package]] name = "zstd-sys" version = "2.0.16+zstd.1.5.7" @@ -12641,96 +17987,110 @@ dependencies = [ [[package]] name = "zuna-rs" -version = "0.0.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db64c95f7c48c8c29b5a7fc938152812f8c1d9550741cca19bcd1b3c7f9b1e1b" +checksum = "4daa31d5dcaadd23095a6d894caa539febbe40f43296f6d7d375708a4c70073c" dependencies = [ "anyhow", - "burn", "clap", "exg", "half", "ndarray 0.17.2", + "rayon", + "rlx", "safetensors 0.7.0", "serde", "serde_json", ] -[[package]] -name = "zune-core" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" - [[package]] name = "zune-core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" -[[package]] -name = "zune-inflate" -version = "0.2.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" -dependencies = [ - "simd-adler32", -] - [[package]] name = "zune-jpeg" -version = "0.4.21" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" dependencies = [ - "zune-core 0.4.12", + "zune-core", ] [[package]] -name = "zune-jpeg" -version = "0.5.12" +name = "zvariant" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "410e9ecef634c709e3831c2cfdb8d9c32164fae1c67496d5b68fff728eec37fe" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" dependencies = [ - "zune-core 0.5.1", + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive 4.2.0", ] [[package]] name = "zvariant" -version = "5.10.0" +version = "5.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b" +checksum = "bee2a0bcd2a907786a456fff45aaaaf54c9ba5f50b71ae9ec1a4edd200c94911" dependencies = [ "endi", "enumflags2", "serde", - "winnow 0.7.15", - "zvariant_derive", - "zvariant_utils", + "winnow 1.0.4", + "zvariant_derive 5.13.1", + "zvariant_utils 3.5.0", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "zvariant_utils 2.1.0", ] [[package]] name = "zvariant_derive" -version = "5.10.0" +version = "5.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c" +checksum = "38a708216a18780796770bfe3f4739c7c83a3e8f789b755534bbbc06e4e23e12" dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.117", - "zvariant_utils", + "syn 2.0.119", + "zvariant_utils 3.5.0", ] [[package]] name = "zvariant_utils" -version = "3.3.0" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zvariant_utils" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" +checksum = "90cb9383f9b45290407a1258b202d3f8f01db719eb60b4e4055c6375af4fc7c7" dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.117", - "winnow 0.7.15", + "syn 2.0.119", + "winnow 1.0.4", ] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..cc0f95def --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,338 @@ +# ── Cargo Workspace ──────────────────────────────────────────────────────────── +# +# All Rust crates are members of a single workspace so they share one target +# directory (src-tauri/target/). This avoids multi-GB duplication that occurs +# when crates under crates/ are built independently (each getting its own +# target/ with duplicate copies of every transitive dependency). +# +# The shared target directory is configured via .cargo/config.toml: +# [build] +# target-dir = "src-tauri/target" +# +# This keeps the binary output where the Tauri CLI and build scripts expect it. + +[workspace] +resolver = "2" +exclude = [] +members = [ + "src-tauri", + "crates/skill-autostart", + "crates/skill-calendar", + "crates/skill-commands", + "crates/skill-constants", + "crates/skill-daemon", + "crates/skill-daemon-common", + "crates/skill-data", + "crates/skill-devices", + "crates/skill-eeg", + "crates/skill-exg", + "crates/skill-headless", + "crates/skill-health", + "crates/skill-oura", + "crates/skill-gpu", + "crates/skill-history", + "crates/skill-iroh", + "crates/skill-jobs", + "crates/skill-label-index", + "crates/skill-lsl", + "crates/skill-llm", + "crates/skill-location", + "crates/skill-router", + "crates/skill-screenshots", + "crates/skill-settings", + "crates/skill-skills", + "crates/skill-tools", + "crates/skill-tray", + "crates/skill-neutts", + "crates/skill-tts", + "crates/skill-asr", + "crates/skill-tty", + "crates/skill-vision", + "crates/iroh_test_client", + "crates/iroh-example-client", + "crates/skill-daemon-state", + "crates/skill-daemon-routes", +] + +# btleplug: improved macOS BLE scanning behaviour. +# NOTE: upstream branch has a typo ("imrpoved" → "improved"); tracked for rename. +# +# Still required after the webbluetooth migration: five device crates are on +# btleplug and pull it transitively — awear, idun, mendi, mw75, openbci, all on +# 0.11, so this override now covers every btleplug user and only ONE major is +# in the graph. (hermes-ble was the lone ^0.12 holdout and has been removed.) +# +# That single range is what makes the next step possible: one +# `[patch.crates-io] btleplug = ` entry can now replace +# btleplug for all five at once, which is the only way to guarantee a single +# CBCentralManager — btleplug has no singleton and allocates a fresh one (plus a +# leaked thread) on every `Manager::new().adapters()`. Drop this override then; +# see docs/webbluetooth-migration.md. +# +# [patch] sections MUST live in the workspace root — they are NOT inherited +# from member Cargo.toml files. + +[patch.crates-io] +# btleplug replaced wholesale: patches/btleplug-0.11.9 is btleplug's API +# reimplemented over webbluetooth, so awear/idun/mendi/mw75/openbci keep +# compiling unchanged while every BLE operation in the process funnels through +# one `Bluetooth::shared()` session. Upstream had no singleton and leaked a +# fresh CBCentralManager per `Manager::new().adapters()`. Supersedes the +# eugenehp fork, whose only purpose was the macOS scanning fix. +btleplug = { path = "patches/btleplug-0.11.9" } +# webbluetooth / muse-rs / vital need NO patch: all are published, and muse-rs +# 0.2.1, vital 0.0.1 and skill-daemon declare the same registry `webbluetooth` +# version (0.0.3), so the resolver unifies them onto one package. +# +# That unification is a correctness requirement, not a tidiness one: +# `Bluetooth::shared()` memoises into a `OnceLock`, so two copies of the +# webbluetooth crate in one graph = two CBCentralManagers = devices that will +# not connect. Re-check after any change to the BLE deps: +# +# cargo tree -p skill-daemon -i webbluetooth # must be exactly one node +# +# Bumping ONE of the three is never enough, and the failure is not subtle. Cargo +# treats 0.0.x releases as mutually incompatible — `0.0.1` means `>=0.0.1, +# <0.0.2` — so skill-daemon on 0.0.2 while muse-rs asked for 0.0.1 resolved to +# both, which is how the Windows release broke for rc.26..rc.29: the older copy +# dragged in `webbluetooth-windows` 0.0.1, whose `#[link(name = "combase")]` +# names an import library the Windows SDK does not ship, and the skill-daemon +# link died on `LNK1181: cannot open input file 'combase.lib'`. Fixed upstream +# in webbluetooth 0.0.2 (`runtimeobject.lib`); both requirements must move +# together. +# +# See docs/webbluetooth-migration.md. +# Fix glib VariantStrIter unsoundness (GHSA-wrw7-89jp-8q8g) — &p → &mut p +# All gtk-rs-core crates must come from the same source to keep -sys types aligned. +glib = { git = "https://github.com/eugenehp/gtk-rs-core.git", branch = "0.18-patched" } +glib-sys = { git = "https://github.com/eugenehp/gtk-rs-core.git", branch = "0.18-patched" } +gobject-sys = { git = "https://github.com/eugenehp/gtk-rs-core.git", branch = "0.18-patched" } +gio = { git = "https://github.com/eugenehp/gtk-rs-core.git", branch = "0.18-patched" } +gio-sys = { git = "https://github.com/eugenehp/gtk-rs-core.git", branch = "0.18-patched" } +glib-macros = { git = "https://github.com/eugenehp/gtk-rs-core.git", branch = "0.18-patched" } +# tribev2: using crates.io 0.1.0 (no patch needed — restore path dep when developing locally) +# eegdino 0.2.0: device_label missing OneApi/Hexagon arms for rlx 0.2.10 +eegdino = { path = "patches/eegdino-0.2.0" } +# gpu-allocator 0.28.0: pin its `windows` dep to the 0.62 bucket. wgpu-hal 30.0.0 +# uses `windows 0.62`, but gpu-allocator's upstream `windows >=0.53,<=0.62` lets the +# resolver attach it to the 0.61 node the Tauri stack anchors — the version skew +# breaks the D3D12 suballocator on Windows (rc.18+ release failures). See the patch's +# Cargo.toml for detail. Vendored copy differs from crates.io only in that pin. +gpu-allocator = { path = "patches/gpu-allocator-0.28.0" } +# oura-api 0.1.2: bump its `reqwest` dep 0.11 → 0.12. Upstream's newest release +# pins reqwest 0.11, which pulls the hyper 0.14 stack and with it `h2 0.3.x` — +# the branch RUSTSEC-2026-0258 is unpatched on (fixed in h2 >= 0.4.16 only). +# The vendored copy differs from crates.io only in that version bump; see the +# patch's Cargo.toml. Drop it when upstream ships a reqwest 0.12 release. +oura-api = { path = "patches/oura-api-0.1.2" } +# Minimal [patch] set that keeps crates.io out of the lock (verified): rlx-models +# still declares `version = "^0.2.x"` registry deps on the runtime stack, and +# third-party crates (tribev2, …) pull registry `rlx-models-*`. Dropping any of +# these reintroduces dual git+crates.io packages. Local: `scripts/rlx local`. +rlx = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-ir = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-flow = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-runtime = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-driver = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-opt = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-autodiff = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-compile = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-fusion = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-cpu = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-gguf = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-pkg = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-text = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-metal = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-mlx = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-mlx-io = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-cuda = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-wgpu = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-rocm = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-onnx-import = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-nemo = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +# New rlx workspace crate (the `torch.save` ZIP+pickle reader `rlx-nemo` was +# split onto). It is published, so without an entry here it would resolve from +# crates.io while every other rlx crate came from git — the dual-source split +# this block exists to prevent. +rlx-torch-ckpt = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-umap = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-tensor = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-macros = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-distributed = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main" } +rlx-models-core = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main" } +rlx-llama32 = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main" } +rlx-vjepa2 = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main" } +rlx-wav2vec2-bert = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main" } +# rc.15 still pulled these from crates.io (API skew vs git rlx-flow `mask` field). +rlx-qwen3 = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main" } +rlx-qwen35 = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main" } +rlx-cli = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main" } +rlx-llada2 = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main" } +rlx-assets = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main" } + +# ── Workspace dependencies ───────────────────────────────────────────────────── +# +# Shared across multiple crates to ensure a single version + feature set. + +[workspace.dependencies] +anyhow = "1" +thiserror = "2" +base64 = "0.22" +rusqlite = { version = "0.39", features = ["bundled"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +# ── TLS crypto provider ────────────────────────────────────────────────────── +# +# rustls defaults to aws-lc-rs whose C build (aws-lc-sys) takes ~45 s on a +# clean compile — the single biggest bottleneck. ring is already in the dep +# tree (via iroh/noq-proto) and builds in <2 s, so we force all workspace +# consumers onto ring instead. Crates that depend on rustls (directly or +# transitively) should use this workspace dep. +rustls = { version = "0.23", default-features = false, features = ["ring", "logging", "std", "tls12"] } +# Force the `deflate` feature on zip 4.x so tauri-plugin-updater can extract +# Deflate-compressed entries. Without this, zip 4.6.1 is compiled without +# flate2 and produces "unsupported compression method" when the updater tries +# to unpack a .nsis.zip built with standard Deflate (method 8). +zip = { version = "7", features = ["deflate"] } + +# RLX — from GitHub (`branch = "main"`; Cargo.lock pins the resolved commit). +# rlx — https://github.com/MIT-RLX/rlx — runtime + IR (+ rlx-runtime, rlx-umap, …) +# rlx-models — https://github.com/MIT-RLX/rlx-models — model loaders / runners +# Local sibling checkouts: `bash scripts/ensure-rlx.sh` / `scripts/rlx local` +# (writes `[patch.""]` outside the repo; never commit `../` paths). +# CI fetches these git URLs from main (commit pinned in Cargo.lock) — no sibling clone. +rlx = { git = "https://github.com/MIT-RLX/rlx.git", version = "0.2.16", branch = "main", default-features = false } +rlx-runtime = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main", default-features = false } +rlx-umap = { git = "https://github.com/MIT-RLX/rlx.git", branch = "main", default-features = false } + +rlx-models = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +rlx-minicpm5 = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +rlx-minimax = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +rlx-nemotron = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +rlx-gemma = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +rlx-neutts = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +rlx-kittentts = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +rlx-inflect-nano = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +rlx-tiny-tts = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +rlx-whisper = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +rlx-vad = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +rlx-qwen3-asr = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +rlx-qwen3-tts = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +rlx-kyutai-tts = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +rlx-orpheus = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +rlx-voxtral = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +rlx-funasr = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +rlx-nemotron-asr = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +rlx-asr = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +# Unified native TTS adapter catalog (publish = false). Never enable `ort`. +rlx-tts-bench = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main", default-features = false } +# Bonsai-27B (prism-ml/Bonsai-27B-gguf) is a qwen35-arch GGUF: it routes through +# rlx-models' auto_runner → rlx-qwen35 Qwen35Runner (packed Q1_0), same as the +# other Bonsai catalog families — no direct rlx-bonsai/rlx-qwen35 dep needed. +# rlx-ocr keeps its default features (rlx + tensor-ops); consumers add `convert-rten`. +rlx-ocr = { git = "https://github.com/MIT-RLX/rlx-models.git", branch = "main" } + +# ── Workspace lints ──────────────────────────────────────────────────────────── +# +# Consistent lint policy across every crate. Individual crates inherit these +# via `[lints] workspace = true` in their own Cargo.toml. +# +# We warn (not deny) so CI stays green while we migrate, but clippy will flag +# every new `unwrap()` / missing safety doc / needless pass-by-value. + +[workspace.lints.rust] +unsafe_op_in_unsafe_fn = "warn" +# skill-daemon gates two EEG encoders (reve, brainjepa) that are pending RLX +# migration — their `reve-rs` / `brainjepa` deps aren't published yet, so the +# features stay commented out in skill-daemon/Cargo.toml while the +# `#[cfg(feature = "embed-reve" / "embed-brainjepa")]` guards remain in src. +# Declare those cfg values as known so `unexpected_cfgs` stays quiet without +# making the half-wired features selectable (an empty stub feature would break +# `--all-features`). Drop a value here once its real feature lands. +unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(feature, values("embed-reve", "embed-brainjepa"))', +] } + +[workspace.lints.clippy] +# Phase 1 hardening: make critical runtime-safety lints hard errors. +# NOTE: some tests and helper crates are still using `unwrap()`/`panic!()`; +# keep these as allow so the workspace can pass the strict push hook. +unwrap_used = "allow" +# expect_used — downgraded: thread::spawn().expect(), NonZero::new(1).expect(), +# and Tauri app builder .expect() are idiomatic and unrecoverable. The lint +# catches ~50 legitimate uses vs ~0 bugs. Use `lock_or_recover()` for Mutex. +# expect_used = "warn" +panic = "allow" +undocumented_unsafe_blocks = "deny" +# needless_pass_by_value — disabled: Tauri #[command] handlers and actor-thread +# spawn sites require owned types; the lint produces ~280 false positives. +# needless_pass_by_value = "warn" +redundant_closure_for_method_calls = "allow" +redundant_closure = "allow" +manual_let_else = "allow" +semicolon_if_nothing_returned = "warn" +field_reassign_with_default = "allow" +manual_range_contains = "allow" +manual_is_multiple_of = "allow" +bool_assert_comparison = "allow" +useless_conversion = "allow" +useless_vec = "allow" +items_after_test_module = "allow" +module_inception = "allow" +assertions_on_constants = "allow" +unnecessary_cast = "allow" +# doc_lazy_continuation — clippy 1.95 made this a default warn and it fires on +# dozens of pre-existing doc comments across the workspace (a wrapped line under +# a `-`/`*`/`+`/`N.` list item that isn't re-indented). It is a doc-rendering +# nit, not a correctness issue; allow it so the strict `-D warnings` push hook +# passes without churning every crate's docs. (Specific-lint allow overrides the +# `warnings` group even under `-D warnings`.) +doc_lazy_continuation = "allow" + +# ── Build profiles ──────────────────────────────────────────────────────────── + +[profile.dev] +codegen-units = 8 +# Local/dev checks are CPU-bound and do not need *full* debug symbols, but +# `debug = 0` also strips line tables, which leaves panics without a usable +# location. That cost real time: when `throughput_stress_32ch_2000hz` blew its +# wall-clock budget under parallel load, the unwinder failed +# (`_URC_END_OF_STACK`), the binary aborted with no message, and the failing +# test could only be identified from a macOS crash report. +# +# `debug = 1` keeps line tables and drops variable/type info — the useful part +# of a backtrace at a fraction of the size and link time of `debug = 2`. +debug = 1 + +# ── Release profile ────────────────────────────────────────────────────────── +# +# thin LTO gives ~90 % of full (fat) LTO's runtime performance benefit at a +# fraction of the link-time cost. On Windows (the slowest CI platform) this +# shaves 2-5 min off release builds compared to the Cargo default (no LTO). +# +# codegen-units = 8 allows the backend to parallelise within each crate. +# Combined with thin LTO the final binary is only ~1-2 % larger than +# codegen-units = 1 + fat LTO, but compiles significantly faster. +# ── Test profile ────────────────────────────────────────────────────────────── +# +[profile.release] +lto = "thin" +codegen-units = 8 + +# ── Size-optimised release profile (opt-in) ──────────────────────────────── +# Builds a noticeably smaller binary than `release` at the cost of much +# longer link time. Used by the release-min CI builds and the size-comparison +# matrix. Use with `cargo build --profile release-min ...`. +# +# Wins (measured on skill-daemon llm-rlx-metal): +# * lto = "fat" — cross-crate dead-code elimination (~5-10%) +# * codegen-units = 1 — wider monomorphisation merging (~2-5%) +# * strip = "symbols" — drops local + debuginfo + symtab (~3-8%) +# Combined: typically 10-20% smaller than `release`. +# +# Not the default because the link step grows from ~3 min to ~10+ min on M4. +[profile.release-min] +inherits = "release" +lto = "fat" +codegen-units = 1 +strip = "symbols" diff --git a/Casks/neuroskill.rb b/Casks/neuroskill.rb new file mode 100644 index 000000000..c8b4da33b --- /dev/null +++ b/Casks/neuroskill.rb @@ -0,0 +1,27 @@ +cask "neuroskill" do + # version + sha256 are kept current by .github/workflows/homebrew-cask.yml, + # which reconciles them against the latest stable GitHub release. + version "0.0.129" + sha256 "3597bb5a2a68f5cae8ffb589c66eba3f3d6bf178fae81f611fabb8b37ba8212e" + + url "https://github.com/NeuroSkill-com/skill/releases/download/v#{version}/NeuroSkill_#{version}_aarch64.dmg" + name "NeuroSkill" + desc "State of Mind brain-computer interface system" + homepage "https://github.com/NeuroSkill-com/skill" + + depends_on arch: :arm64 + # The bundled skill-daemon dynamically links Homebrew's libusb + # (/opt/homebrew/opt/libusb/lib/libusb-1.0.0.dylib) and aborts on launch if + # it is missing. Declaring it here makes `brew install` pull libusb in first, + # so cask installs launch cleanly even while issue #55 is open. + depends_on formula: "libusb" + + app "NeuroSkill.app" + + zap trash: [ + "~/Library/Application Support/com.neuroskill.skill", + "~/Library/Caches/com.neuroskill.skill", + "~/Library/Preferences/com.neuroskill.skill.plist", + "~/Library/Saved Application State/com.neuroskill.skill.savedState" + ] +end diff --git a/README.md b/README.md index 61ad403a9..d8f58a31e 100644 --- a/README.md +++ b/README.md @@ -1,521 +1,142 @@ -# NeuroSkill™ — State of Mind Brain-Computer Interface system +# NeuroSkill™ [www.neuroskill.com](https://neuroskill.com) -> **⚠️ Research Use Only — Not a Medical Device** -> -> NeuroSkill™ is an open-source research tool for exploratory EEG analysis. It is **NOT** a medical device -> and has **NOT** been cleared or approved by the FDA, CE, or any regulatory body. It must not be -> used for clinical diagnosis, treatment decisions, or any medical purpose. All metrics are -> experimental research outputs — not validated clinical measurements. Do not rely on any output of -> this software for health-related decisions. Consult a qualified healthcare professional for any -> medical concerns. -> -> **This software is provided for non-commercial research and educational use only.** - -**NeuroSkill™** is a desktop neurofeedback and brain-computer interface application for the BCI devices. It streams, analyses, embeds, and visualises EEG data in real time — all processing runs locally on-device. - -Built with **Tauri v2** (Rust backend) + **SvelteKit** (TypeScript/Svelte 5 frontend). - ---- - -## Features - -| Feature | Description | -|---------|-------------| -| **Live EEG Waveforms** | multi-channel real-time scrolling waveform with glow effect, gradient fill, live-edge pulse dot, configurable bandpass filter, and signal-quality indicators | -| **GPU Band-Power Analysis** | Hann-windowed 512-sample FFT via `gpu_fft` — all 4 channels in a single GPU dispatch at ~4 Hz. Six clinical EEG bands (0.5–100 Hz) | -| **ZUNA Neural Embeddings** | GPU-accelerated transformer encoder (ZUNA) converts 5-second EEG epochs into 32-dimensional embedding vectors for similarity search | -| **Session Compare** | Side-by-side comparison of any two recording sessions: band powers, derived scores, FAA, sleep staging, and 3D UMAP embedding projection. Launch directly from History via **Quick Compare** mode | -| **Quick Compare** | Select any two sessions from the History list with checkboxes; opens compare window pre-loaded with both sessions | -| **3D UMAP Viewer** | Interactive Three.js scatter plot of session embeddings projected to 3D. Auto-orbit, hover tooltips, click-to-connect labelled points with multi-label support | -| **Sleep Staging** | Automatic Wake/N1/N2/N3/REM classification from band-power ratios with hypnogram visualisation | -| **Label System** | Attach user-defined tags to moments during recording. Full CRUD management window (search, inline edit, delete). Labels stored alongside embeddings and visualised in UMAP | -| **Focus Timer** | Pomodoro-style work/break timer (25/5, 50/10, 15/5, or custom). Optional auto-label EEG at each phase transition. Launched from tray, Command Palette, or global shortcut | -| **Calibration Presets** | 9 one-click presets (Baseline, Focus/Relax, Meditation, TBR, Pre-sleep, Gaming, Children, Clinical, Stress) that fill all calibration fields automatically | -| **Onboarding Checklist** | Dashboard card tracking first-time setup: pair device → calibrate → 5-min session → set goal. Progress persisted in localStorage | -| **Similarity Search** | Approximate nearest-neighbour search across daily HNSW indices to find similar brain states. Streaming results via Tauri `Channel` — results appear as each query completes | -| **WebSocket API** | JSON-based LAN API with mDNS discovery (`_skill._tcp`). Commands: `status`, `label`, `search`, `compare`, `sessions`, `sleep`, `umap`, `umap_poll`. Built-in CLI/Python/Node.js code examples in the API window | -| **Job Queue** | Serial background queue for expensive compute (UMAP). Context menu shows live queue depth and ETA. Returns ETA immediately; frontend polls for results | -| **PPG Sensor** | Records ambient, infrared, and red PPG channels alongside EEG | -| **Calibration** | Guided eyes-open / eyes-closed protocol with labelled epoch recording. 9 use-case presets for common scenarios | -| **Tray Menu** | System tray with connection status, Open NeuroSkill™ (⌘⇧O), Focus Timer (⌘⇧P), Session Compare (⌘⇧M), and quick actions | -| **Keyboard Shortcuts** | Press `?` anywhere for a full shortcut cheat sheet. All global shortcuts configurable in Settings → Shortcuts | -| **Quit Protection** | Modal confirmation when quitting during an active recording — prevents accidental data loss | -| **History — Day Streaming** | Session history loads day-by-day with a live progress bar, so large archives appear incrementally rather than blocking | -| **i18n** | English, German, French, Hebrew, Ukrainian. Automated sync script (`npm run sync:i18n:check`) verifies all 5 locales stay in sync | - ---- - -## Architecture - -``` -┌─────────────────────────────────────────────────────┐ -│ SvelteKit Frontend │ -│ Svelte 5 · Tailwind · Three.js · shadcn-svelte │ -├─────────────────────────────────────────────────────┤ -│ Tauri v2 Bridge │ -│ IPC commands · Event emitters │ -├─────────────────────────────────────────────────────┤ -│ Rust Backend │ -│ CoreBluetooth/BlueZ · gpu_fft · ZUNA (wgpu) │ -│ rusqlite · fast_hnsw · umap_rs · job_queue │ -└─────────────────────────────────────────────────────┘ -``` - -### Data Flow - -1. **BLE** → Raw EEG samples at 256 Hz (4 channels × 12 samples/packet) -2. **Signal Filter** → Bandpass + notch filter for display -3. **Band Analyzer** → GPU FFT every 64 samples (~4 Hz) → `BandSnapshot` -4. **ZUNA Encoder** → Every 5 s epoch → 32-D embedding vector (wgpu) -5. **Storage** → HNSW index + SQLite database per day in `~/.skill/YYYYMMDD/` - ---- - -## Data Storage - -All data is stored locally in `~/.skill/` organised by UTC date: - -``` -~/.skill/ - 20260224/ - eeg.sqlite ← embeddings, metrics, labels - eeg_embeddings.hnsw ← daily HNSW approximate-NN index - session_*.csv ← raw EEG samples - 20260225/ - ... -``` - -### SQLite Schema (`embeddings` table) - -Each row represents one 2.5-second epoch (5 s window, 50% overlap): - -| Column | Type | Description | -|--------|------|-------------| -| `id` | INTEGER | Primary key (autoincrement) | -| `timestamp` | INTEGER | `YYYYMMDDHHmmss` UTC | -| `device_id` | TEXT | BLE peripheral identifier | -| `device_name` | TEXT | Headset display name | -| `hnsw_id` | INTEGER | Zero-based row in daily HNSW file | -| `eeg_embedding` | BLOB | f32 LE × 32 (128 bytes) | -| `label` | TEXT | User-defined tag (nullable) | -| `rel_delta` … `rel_high_gamma` | REAL | Relative band powers (6 bands, averaged across channels) | -| `focus_score` | REAL | Focus score (0–100) | -| `relaxation_score` | REAL | Relaxation score (0–100) | -| `engagement_score` | REAL | Engagement score (0–100) | -| `faa` | REAL | Frontal Alpha Asymmetry | -| `tar` | REAL | Theta / Alpha ratio | -| `bar` | REAL | Beta / Alpha ratio | -| `dtr` | REAL | Delta / Theta ratio | -| `pse` | REAL | Power Spectral Entropy | -| `apf` | REAL | Alpha Peak Frequency (Hz) | -| `bps` | REAL | Band-Power Slope (1/f exponent) | -| `snr` | REAL | Signal-to-Noise Ratio (dB) | -| `coherence` | REAL | Inter-channel alpha coherence | -| `mu_suppression` | REAL | Mu suppression index | -| `mood` | REAL | Mood composite index (0–100) | -| `tbr` | REAL | Theta/Beta ratio (absolute) | -| `sef95` | REAL | Spectral Edge Frequency 95% (Hz) | -| `spectral_centroid` | REAL | Spectral centre of mass (Hz) | -| `hjorth_activity` | REAL | Hjorth Activity (signal variance) | -| `hjorth_mobility` | REAL | Hjorth Mobility (mean frequency) | -| `hjorth_complexity` | REAL | Hjorth Complexity (bandwidth) | -| `permutation_entropy` | REAL | Permutation Entropy (0–1) | -| `higuchi_fd` | REAL | Higuchi Fractal Dimension | -| `dfa_exponent` | REAL | DFA scaling exponent | -| `sample_entropy` | REAL | Sample Entropy | -| `pac_theta_gamma` | REAL | Phase-Amplitude Coupling (θ–γ) | -| `laterality_index` | REAL | Generalised L/R asymmetry | -| `hr` | REAL | Heart rate (bpm) | -| `rmssd` | REAL | RMSSD — HRV parasympathetic (ms) | -| `sdnn` | REAL | SDNN — HRV total variability (ms) | -| `pnn50` | REAL | pNN50 — % of successive IBIs >50 ms apart | -| `lf_hf_ratio` | REAL | LF/HF ratio — sympathovagal balance | -| `respiratory_rate` | REAL | Respiratory rate (breaths/min) | -| `spo2_estimate` | REAL | SpO₂ estimate (%) — uncalibrated | -| `perfusion_idx` | REAL | Perfusion Index — AC/DC ratio (%) | -| `stress_index` | REAL | Baevsky Stress Index | -| `ppg_ambient` | REAL | Mean PPG ambient ADC | -| `ppg_infrared` | REAL | Mean PPG infrared ADC | -| `ppg_red` | REAL | Mean PPG red ADC | -| `band_channels_json` | TEXT | Full per-channel band powers (JSON array) | - ---- - -## Computed Metrics Reference - -All metrics are computed from a **512-sample Hann-windowed FFT** at 256 Hz (0.5 Hz/bin resolution) and stored in `eeg.sqlite` every epoch. - -### Band Powers - -| Band | Range (Hz) | Association | -|------|-----------|-------------| -| Delta (δ) | 0.5 – 4 | Deep sleep, slow-wave activity | -| Theta (θ) | 4 – 8 | Drowsiness, meditation, memory encoding | -| Alpha (α) | 8 – 13 | Relaxed wakefulness, eyes-closed rest | -| Beta (β) | 13 – 30 | Active cognition, focus, anxiety | -| Gamma (γ) | 30 – 50 | High-level processing, perceptual binding | -| High-Gamma | 50 – 100 | Broadband activity, often EMG artefact | - -Absolute power (µV²) is computed from the Heinzel-normalised one-sided PSD. Relative power is each band divided by the broadband total. - -> Heinzel, G., Rüdiger, A., & Schilling, R. (2002). *Spectrum and spectral density estimation by the Discrete Fourier Transform (DFT), including a comprehensive list of window functions and some new flat-top windows.* Max Planck Institute for Gravitational Physics. [https://hdl.handle.net/11858/00-001M-0000-0013-557A-5](https://hdl.handle.net/11858/00-001M-0000-0013-557A-5) - -### Derived Scores - -| Metric | Formula | Range | Description | Reference | -|--------|---------|-------|-------------|-----------| -| **Focus** | σ(β / (α + θ)) | 0–100 | Sustained attention / concentration index | Lubar, J. F. (1991). *Discourse on the development of EEG diagnostics and biofeedback for attention-deficit/hyperactivity disorders.* Biofeedback and Self-regulation, 16(3), 201–225. | -| **Relaxation** | σ(α / (β + θ)) | 0–100 | Calm wakefulness / alpha-dominance index | Aftanas, L. I., & Golocheikine, S. A. (2001). *Human anterior and frontal midline theta and lower alpha reflect emotionally positive state and internalized attention.* Neuroscience Letters, 310(1), 57–60. | -| **Engagement** | σ(β / (α + θ), k=2) | 0–100 | Cognitive engagement with gentler sigmoid | Pope, A. T., Bogart, E. H., & Bartolome, D. S. (1995). *Biocybernetic system evaluates indices of operator engagement in automated task.* Biological Psychology, 40(1-2), 187–195. | - -### Frontal Alpha Asymmetry (FAA) - -| Metric | Formula | Unit | Description | Reference | -|--------|---------|------|-------------|-----------| -| **FAA** | ln(AF8 α) − ln(AF7 α) | ln(µV²) | Positive → approach motivation; negative → withdrawal/avoidance | Coan, J. A., & Allen, J. J. B. (2004). *Frontal EEG asymmetry as a moderator and mediator of emotion.* Biological Psychology, 67(1-2), 7–50. | - -### Cross-Band Ratios - -| Metric | Formula | Description | Reference | -|--------|---------|-------------|-----------| -| **TAR** | θ / α | Theta/Alpha ratio — drowsiness, meditative states, anxiety | Putman, P. (2011). *Resting state EEG delta–beta coherence in relation to anxiety, behavioral inhibition, and selective attentional processing of threatening stimuli.* International Journal of Psychophysiology, 80(1), 63–68. | -| **BAR** | β / α | Beta/Alpha ratio — attention, stress, cortical arousal | Angelidis, A., Hagenaars, M., van Son, D., van der Does, W., & Putman, P. (2018). *Do not look away! Spontaneous frontal EEG theta/beta ratio as a marker for cognitive control over attention to mild and high threat.* Biological Psychology, 135, 8–17. | -| **DTR** | δ / θ | Delta/Theta ratio — deep sleep, deep relaxation | Knyazev, G. G. (2012). *EEG delta oscillations as a correlate of basic homeostatic and motivational processes.* Neuroscience & Biobehavioral Reviews, 36(1), 677–695. | -| **TBR** | θ_abs / β_abs | Theta/Beta ratio (absolute power) — FDA-cleared ADHD biomarker | Monastra, V. J., Lubar, J. F., & Linden, M. (2001). *The development of a quantitative electroencephalographic scanning process for attention deficit–hyperactivity disorder: Reliability and validity studies.* Neuropsychology, 15(1), 136–144. | - -### Spectral Shape Metrics - -| Metric | Formula | Range | Description | Reference | -|--------|---------|-------|-------------|-----------| -| **PSE** | −Σ pᵢ ln(pᵢ) / ln(5) | 0–1 | Power Spectral Entropy — spectral complexity. Higher = more uniform distribution | Inouye, T., Shinosaki, K., Sakamoto, H., Toi, S., Ukai, S., Iyama, A., Katsuda, Y., & Hirano, M. (1991). *Quantification of EEG irregularity by use of the entropy of the power spectrum.* Electroencephalography and Clinical Neurophysiology, 79(3), 204–210. | -| **APF** | argmax PSD(8–13 Hz) | Hz | Alpha Peak Frequency — individual alpha frequency, cognitive speed marker | Klimesch, W. (1999). *EEG alpha and theta oscillations reflect cognitive and memory performance: a review and analysis.* Brain Research Reviews, 29(2-3), 169–195. | -| **SEF95** | freq at 95th percentile of cumulative PSD | Hz | Spectral Edge Frequency — consciousness depth / anaesthesia monitoring. Drops during sleep | Rampil, I. J. (1998). *A primer for EEG signal processing in anesthesia.* Anesthesiology, 89(4), 980–1002. | -| **Spectral Centroid** | Σ(f·P(f)) / Σ P(f) | Hz | Centre of mass of the power spectrum — global arousal indicator. Higher = more alert | Gudmundsson, S., Runarsson, T. P., Sigurdsson, S., Eiriksdottir, G., & Johnsen, K. (2007). *Reliability of quantitative EEG features.* Clinical Neurophysiology, 118(10), 2162–2171. | -| **BPS** | slope of log₁₀(P) vs log₁₀(f), 1–50 Hz | — | Band-Power Slope (aperiodic 1/f exponent). More negative = steeper spectral fall-off | Donoghue, T., Haller, M., Peterson, E. J., Varma, P., Sebastian, P., Gao, R., Noto, T., Lara, A. H., Wallings, J. D., Knight, R. T., Shestyuk, A., & Voytek, B. (2020). *Parameterizing neural power spectra into periodic and aperiodic components.* Nature Neuroscience, 23(12), 1655–1665. | -| **SNR** | 10 log₁₀(P₁₋₅₀ / P₅₀₋₆₀) | dB | Signal-to-Noise Ratio — broadband (1–50 Hz) power vs line-noise (50–60 Hz) | Cohen, M. X. (2014). *Analyzing Neural Time Series Data: Theory and Practice.* MIT Press. ISBN: 978-0262019873 | - -### Cross-Channel Metrics - -| Metric | Formula | Range | Description | Reference | -|--------|---------|-------|-------------|-----------| -| **Coherence** | Pearson r of α_rel across channel pairs | −1 to 1 | Inter-channel alpha synchrony (simplified coherence) | Lachaux, J.-P., Rodriguez, E., Martinerie, J., & Varela, F. J. (1999). *Measuring phase synchrony in brain signals.* Human Brain Mapping, 8(4), 194–208. | -| **Mu Suppression** | α_current / α_baseline (EMA) | 0–5 | Mu rhythm suppression index. <1.0 = suppression (motor imagery, action observation) | Pfurtscheller, G., & Lopes da Silva, F. H. (1999). *Event-related EEG/MEG synchronization and desynchronization: basic principles.* Clinical Neurophysiology, 110(11), 1842–1857. | - -### Time-Domain Features (Hjorth Parameters) - -| Metric | Formula | Unit | Description | Reference | -|--------|---------|------|-------------|-----------| -| **Hjorth Activity** | var(x) | µV² | Total signal variance (power). Higher = more active signal | Hjorth, B. (1970). *EEG analysis based on time domain properties.* Electroencephalography and Clinical Neurophysiology, 29(3), 306–310. | -| **Hjorth Mobility** | √(var(x') / var(x)) | — | Estimate of mean frequency (time-domain). Higher = faster oscillations | Hjorth (1970), same as above | -| **Hjorth Complexity** | mobility(x') / mobility(x) | — | Spectral bandwidth / spectral spread. Deviation from a pure sine wave | Hjorth (1970), same as above | - -### Nonlinear Complexity Measures - -| Metric | Formula | Range | Description | Reference | -|--------|---------|-------|-------------|-----------| -| **Permutation Entropy** | H(ordinal patterns, m=3) / ln(3!) | 0–1 | Complexity of the signal's ordinal structure. Higher = more irregular/complex. Robust to noise | Bandt, C. & Pompe, B. (2002). *Permutation entropy: a natural complexity measure for time series.* Physical Review Letters, 88(17), 174102. | -| **Higuchi Fractal Dimension** | slope of log(L(k)) vs log(1/k), k=1..8 | ~1–2 | Fractal complexity of the signal. Higher = more complex. Effective for seizure/consciousness discrimination | Higuchi, T. (1988). *Approach to an irregular time series on the basis of the fractal theory.* Physica D: Nonlinear Phenomena, 31(2), 277–283. | -| **DFA Exponent** | slope of log(F(n)) vs log(n) | ~0.5–1.5 | Detrended Fluctuation Analysis scaling exponent. α≈0.5 = white noise, α≈1.0 = 1/f noise, α≈1.5 = Brownian motion. Healthy EEG ≈ 0.6–0.8 | Peng, C.-K., Buldyrev, S. V., Havlin, S., Simons, M., Stanley, H. E., & Goldberger, A. L. (1994). *Mosaic organization of DNA nucleotides.* Physical Review E, 49(2), 1685–1689. | -| **Sample Entropy** | −ln(A/B), m=2, r=0.2·σ | ≥ 0 | Signal regularity. Lower = more regular/predictable. Robust improvement over Approximate Entropy | Richman, J. S. & Moorman, J. R. (2000). *Physiological time-series analysis using approximate entropy and sample entropy.* American Journal of Physiology – Heart and Circulatory Physiology, 278(6), H2039–H2049. | - -### Cross-Frequency Coupling - -| Metric | Formula | Range | Description | Reference | -|--------|---------|-------|-------------|-----------| -| **PAC (θ–γ)** | |corr(θ_power, γ_power)| across sub-windows | 0–1 | Phase-Amplitude Coupling proxy — theta-gamma cross-frequency interaction. Higher = stronger coupling, associated with memory encoding and cognitive binding | Canolty, R. T., Edwards, E., Dalal, S. S., Soltani, M., Nagarajan, S. S., Kirsch, H. E., Berger, M. S., Barbaro, N. M., & Knight, R. T. (2006). *High gamma power is phase-locked to theta oscillations in human neocortex.* Science, 313(5793), 1626–1628. | - -### Spatial Asymmetry - -| Metric | Formula | Range | Description | Reference | -|--------|---------|-------|-------------|-----------| -| **Laterality Index** | (right − left) / (right + left) | −1 to 1 | Generalised left/right asymmetry across all bands and all channel pairs. Positive = right-dominant | Homan, R. W., Herman, J., & Purdy, P. (1987). *Cerebral location of international 10–20 system electrode placement.* Electroencephalography and Clinical Neurophysiology, 66(4), 376–382. | - -### PPG-Derived Metrics - -All PPG metrics require the infrared and red optical sensors available on device. Inter-beat intervals (IBIs) are extracted from the IR channel via adaptive peak detection at 64 Hz. - -| Metric | Formula | Unit | Description | Reference | -|--------|---------|------|-------------|-----------| -| **Heart Rate (HR)** | 60 / mean(IBI) | bpm | Beats per minute from PPG peak detection | Elgendi, M. (2012). *On the analysis of fingertip photoplethysmogram signals.* Current Cardiology Reviews, 8(1), 14–25. | -| **RMSSD** | √(mean(ΔIBI²)) | ms | Root mean square of successive IBI differences — parasympathetic / vagal tone. Higher = more relaxed | Shaffer, F. & Ginsberg, J. P. (2017). *An overview of heart rate variability metrics and norms.* Frontiers in Public Health, 5, 258. | -| **SDNN** | std(IBI) | ms | Standard deviation of IBIs — overall autonomic variability | Task Force of ESC/NASPE (1996). *Heart rate variability: standards of measurement, physiological interpretation, and clinical use.* Circulation, 93(5), 1043–1065. | -| **pNN50** | % of successive IBIs differing >50 ms | % | Parasympathetic activity marker | Task Force of ESC/NASPE (1996), same as above | -| **LF/HF Ratio** | power(0.04–0.15 Hz) / power(0.15–0.4 Hz) of IBI series | — | Sympathovagal balance. Higher = sympathetic dominance. Computed via Goertzel on resampled IBI series | Task Force of ESC/NASPE (1996), same as above | -| **Respiratory Rate** | Peak frequency of PPG envelope modulation (0.15–0.5 Hz) | breaths/min | Breathing rate extracted from PPG amplitude modulation | Nilsson, L., Johansson, A., & Kalman, S. (2003). *Respiratory variations in the reflection mode photoplethysmographic signal.* Medical & Biological Engineering & Computing, 41(3), 249–254. | -| **SpO₂ Estimate** | 110 − 25 × R, where R = (AC_red/DC_red)/(AC_ir/DC_ir) | % | Blood oxygen saturation estimate. **Uncalibrated** — relative trends only, not for clinical use | Mendelson, Y. & Ochs, B. D. (1988). *Noninvasive pulse oximetry utilizing skin reflectance photoplethysmography.* IEEE Transactions on Biomedical Engineering, 35(10), 798–805. | -| **Perfusion Index (PI)** | (2 × std(IR) / mean(IR)) × 100 | % | Peripheral blood perfusion. Higher = better perfusion. Lower during vasoconstriction/stress | Lima, A. & Bakker, J. (2005). *Noninvasive monitoring of peripheral perfusion.* Intensive Care Medicine, 31(10), 1316–1326. | -| **Stress Index (SI)** | AMo / (2 × Mo × MxDMn) from IBI histogram | — | Baevsky's Stress Index — autonomic stress level. Higher = greater sympathetic activation | Baevsky, R. M. & Chernikova, A. G. (2017). *Heart rate variability analysis: physiological foundations and main methods.* Cardiometry, 10, 66–76. | - -### Composite Indices - -| Metric | Formula | Range | Description | -|--------|---------|-------|-------------| -| **Mood** | 50 + 20·FAA_norm + 15·(TAR_norm − 0.5) + 15·(BAR_norm − 0.5) | 0–100 | Weighted composite of FAA (approach valence), inverse TAR (alertness), and BAR (focus). Higher = more positive/approach valence | - ---- - -## WebSocket API - -NeuroSkill™ broadcasts EEG data and accepts commands over a local WebSocket server, advertised via mDNS as `_skill._tcp`. - -### Discovery +[![Release](https://img.shields.io/github/v/release/NeuroSkill-com/skill?style=for-the-badge&logo=github&logoColor=white&label=Latest)](https://github.com/NeuroSkill-com/skill/releases/latest) +[![License](https://img.shields.io/badge/License-GPL--3.0-blue?style=for-the-badge)](https://github.com/NeuroSkill-com/skill/blob/main/LICENSE) +[![Discord](https://img.shields.io/badge/Discord-Join%20Community-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/Rcvb8Cx4cZ) + +> ⚠️ **Research use only — not a medical device.** +> NeuroSkill is an open-source EXG/BCI research tool. It is not FDA/CE approved and must not be used for diagnosis or treatment. + +NeuroSkill is a local-first desktop neurofeedback + BCI app built with **Tauri v2 (Rust)** and **SvelteKit**. + +

+ NeuroSkill Dashboard + NeuroSkill Supported Devices +

+ +## Quick links + +- **Downloads:** [macOS](https://github.com/NeuroSkill-com/skill/releases/latest/download/NeuroSkill.dmg) · [Windows](https://github.com/NeuroSkill-com/skill/releases/latest/download/NeuroSkill.exe) · [Linux](https://github.com/NeuroSkill-com/skill/releases/latest/download/NeuroSkill.AppImage) +- **Install (Homebrew):** `brew install --cask https://raw.githubusercontent.com/NeuroSkill-com/skill/main/Casks/neuroskill.rb` +- **Contributing:** [CONTRIBUTING.md](CONTRIBUTING.md) +- **Changelog:** [CHANGELOG.md](CHANGELOG.md) + +## What it does + +- Real-time EXG streaming and visualization +- GPU band-power analysis + neural embeddings +- Session comparison, sleep staging, and UMAP visualization +- Labeling, similarity search, and screenshot-based search +- Local LLM + TTS integration +- LSL + remote rlsl-iroh streaming +- WebSocket/HTTP API + +## Supported devices + + + +- **ANT Neuro:** eego 8, eego 24, eego 64, eego mylab, eego sport, eego rt, eego hub +- **AttentivU:** AttentivU Glasses *(iOS bridge only)* +- **AWEAR:** AWEAR EEG +- **BrainBit LLC:** BrainBit, BrainBit 2, BrainBit Flex 4/8 +- **BrainMaster Technologies:** Atlantis 4×4 (4-ch), Discovery (24-ch), Freedom (24-ch wireless) +- **Cognionics / CGX:** Quick-20r, Quick-32r, Quick-8r, AIM-2 +- **Emotiv:** EPOC X, FLEX Saline, Insight, MN8 +- **g.tec medical engineering:** Unicorn Hybrid Black +- **IDUN:** Guardian +- **InteraXon:** Muse 2016, Muse 2, Muse S, Muse S Athena +- **Mendi:** Mendi Headband +- **Neurofield Inc:** Q21 (20-ch) +- **Neurosity:** Crown / Notion +- **NeuroSky:** MindWave / MindWave Mobile +- **Neurable:** MW75 Neuro +- **OpenBCI:** Cyton, Cyton Daisy, Galea, Ganglion +- **Ōura:** Oura Ring (Gen 3 / Gen 4) +- **Brain Products:** BrainVision RDA +- **RE-AK:** Nucleus-Hermès +- **Vital Neuro:** Vital Neuro Headphones + +Plus any compatible **LSL** source (e.g. BrainFlow, MATLAB, pylsl). + + +## Supported EXG/BCI foundation models + +Canonical source: `src-tauri/exg_catalog.json` + + + +- **ZUNA** (`Zyphra/ZUNA`) +- **LUNA Base** (`PulpBio/LUNA`) +- **LUNA Large** (`PulpBio/LUNA`) +- **LUNA Huge** (`PulpBio/LUNA`) +- **REVE Base** (`brain-bzh/reve-base`) +- **REVE Large** (`brain-bzh/reve-large`) +- **Brain-JEPA** (`eugenehp/BrainJEPA`) +- **OpenTSLM** (`StanfordBDHG/OpenTSLM`) +- **SensorLM** (`google/sensorlm`) +- **SleepFM** (`zou-group/sleepfm-clinical`) +- **SleepLM** (`yang-ai-lab/SleepLM`) +- **OSF Base** (`yang-ai-lab/OSF-Base`) +- **SignalJEPA** (`braindecode/SignalJEPA-PreLocal-pretrained`) +- **LaBraM** (`braindecode/labram-pretrained`) +- **EEGPT** (`braindecode/eegpt-pretrained`) +- **TRIBE v2** (`eugenehp/tribev2`) +- **EEG-DINO Small** (`eugenehp/eegdino`) +- **EEG-DINO Medium** (`eugenehp/eegdino`) +- **EEG-DINO Large** (`eugenehp/eegdino`) +- **LuMamba (Reconstruction)** (`PulpBio/LuMamba`) +- **LuMamba (LeJEPA+Recon 128)** (`PulpBio/LuMamba`) +- **LuMamba (LeJEPA+Recon 300)** (`PulpBio/LuMamba`) +- **LuMamba (LeJEPA only)** (`PulpBio/LuMamba`) +- **NeuroRVQ** (`eugenehp/NeuroRVQ`) +- **CBraMod** (`braindecode/cbramod-pretrained`) +- **ST-EEGFormer Small** (`eugenehp/ST-EEGFormer`) +- **ST-EEGFormer Base** (`eugenehp/ST-EEGFormer`) +- **ST-EEGFormer Large** (`eugenehp/ST-EEGFormer`) + + +## Documentation + +Long-form documentation lives in `./docs`: + +- Docs index: [docs/README.md](docs/README.md) +- Architecture overview: [docs/architecture.md](docs/architecture.md) +- Device support and integration notes: [docs/DEVICES.md](docs/DEVICES.md) +- UI notes: [docs/UI.md](docs/UI.md) +- Metrics reference and formulas: [docs/METRICS.md](docs/METRICS.md) +- Hooks: [docs/HOOKS.md](docs/HOOKS.md) +- LLM internals: [docs/LLM.md](docs/LLM.md) +- LSL integration notes: [docs/lsl-integration.md](docs/lsl-integration.md) +- API reference (WebSocket + HTTP): [docs/API.md](docs/API.md) +- Development guide: [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) +- Linux build/packaging: [docs/LINUX.md](docs/LINUX.md) +- Windows build setup: [docs/WINDOWS.md](docs/WINDOWS.md) + +## Development (quickstart) + +Product version lives in the repo-root [`VERSION`](VERSION) file (`npm run bump` +keeps `package.json`, `src-tauri/tauri.conf.json`, and `src-tauri/Cargo.toml` in +sync). Release builds enable one daemon OS umbrella (`apple` / `linux` / +`windows`). PR CI runs clippy/tests; signed product builds run on release tags +or Preview Build. Details: [CONTRIBUTING.md](CONTRIBUTING.md) · +[docs/DEVELOPMENT.md](docs/DEVELOPMENT.md). + +Install the [Hugging Face CLI](https://huggingface.co/docs/huggingface_hub/guides/cli) (`curl -LsSf https://hf.co/cli/install.sh | bash` on macOS/Linux, `powershell -ExecutionPolicy ByPass -c "irm https://hf.co/cli/install.ps1 | iex"` on Windows), then: ```bash -# macOS -dns-sd -B _skill._tcp - -# Linux -avahi-browse _skill._tcp -``` - -### Broadcast Events (server → client) - -```json -{ "event": "eeg-bands", "payload": { "channels": [...], "faa": 0.12, ... } } -{ "event": "muse-status", "payload": { "state": "connected", ... } } -{ "event": "label-created", "payload": { "id": 42, "text": "eyes closed" } } +npm run setup -- --yes +hf download Zyphra/ZUNA +npm run tauri dev ``` -| Event | Rate | Description | -|-------|------|-------------| -| `eeg-bands` | ~4 Hz | Derived scores, band powers, heart rate, head pose — all 60+ fields | -| `muse-status` | ~1 Hz | Device heartbeat: battery, sample counts, connection state | -| `label-created` | on-demand | Fired when any client creates a label | - -> **Note:** Raw EEG samples (256 Hz), PPG (64 Hz), IMU (50 Hz), and spectrogram -> slices are **not** broadcast over the WebSocket API — their high frequency -> would overwhelm the connection. Use the `eeg-bands` event for real-time -> derived metrics, or the `status` command for a one-shot snapshot. - -### Commands (client → server) - -| Command | Parameters | Description | -|---------|-----------|-------------| -| `status` | — | Device state, scores, embeddings count, sleep summary | -| `label` | `text` | Attach a label to the current moment | -| `search` | `start_utc`, `end_utc`, `k` | Find k-nearest EEG embeddings | -| `sessions` | — | List all recording sessions | -| `compare` | `a_start_utc`, `a_end_utc`, `b_start_utc`, `b_end_utc` | Full comparison: metrics A/B, sleep A/B, UMAP ticket | -| `sleep` | `start_utc`, `end_utc` | Sleep staging for a time range | -| `umap` | `a_start_utc`, `a_end_utc`, `b_start_utc`, `b_end_utc` | Enqueue 3D UMAP projection (non-blocking) | -| `umap_poll` | `job_id` | Poll for UMAP result | - -### Testing +Build production app: ```bash -node test.js # auto-discover via mDNS -node test.js 62853 # explicit port +npm run tauri:build ``` ---- - -## Keyboard Shortcuts - -### Global (system-wide, work even when window is hidden) - -| Default (macOS) | Default (Win/Linux) | Action | -|----------------|---------------------|--------| -| ⌘⇧O | Ctrl+Shift+O | Open NeuroSkill™ window | -| ⌘⇧L | Ctrl+Shift+L | Add EEG label | -| ⌘⇧F | Ctrl+Shift+F | Open similarity search | -| ⌘⇧, | Ctrl+Shift+, | Open Settings | -| ⌘⇧C | Ctrl+Shift+C | Open Calibration | -| ⌘⇧M | Ctrl+Shift+M | Open Session Compare | -| ⌘⇧P | Ctrl+Shift+P | Open Focus Timer | -| ⌘⇧H | Ctrl+Shift+H | Open History | -| ⌘⇧A | Ctrl+Shift+A | Open API Status | -| ⌘⇧T | Ctrl+Shift+T | Toggle Theme | - -All global shortcuts are fully **configurable** in **Settings → Shortcuts**. - -### In-app - -| Shortcut | Action | -|----------|--------| -| `?` | Open keyboard shortcut cheat sheet | -| ⌘K / Ctrl+K | Open Command Palette | -| `Esc` | Close overlay / dialog | -| ⌘↵ / Ctrl+↵ | Submit label (in label window) | - ---- - -## Development - -### Prerequisites - -- [Rust](https://rustup.rs/) (stable) -- [Node.js](https://nodejs.org/) ≥ 18 -- [Tauri CLI v2](https://v2.tauri.app/start/prerequisites/) -- ZUNA weights from Hugging Face (see below) - -### Setup - -```bash -# Install frontend dependencies -npm install - -# Download ZUNA encoder weights -python3 -c "from huggingface_hub import snapshot_download; snapshot_download('mariozechner/zuna-eeg-v1')" - -# Run in development mode -cargo tauri dev -``` - -### Build - -```bash -cargo tauri build -``` - -### Project Structure - -``` -skill/ -├── src/ # SvelteKit frontend -│ ├── routes/ # Pages (dashboard, compare, settings, …) -│ └── lib/ # Components, i18n, utilities -│ ├── UmapViewer3D.svelte # 3D UMAP scatter (raw Three.js) -│ ├── HelpFaq.svelte # Help & FAQ content -│ ├── EegChart.svelte # Real-time waveform display -│ └── i18n/ # en, de, fr, he, uk -├── src-tauri/ # Rust backend -│ └── src/ -│ ├── lib.rs # App state, Tauri commands, BLE -│ ├── eeg_bands.rs # GPU FFT band-power analysis -│ ├── eeg_embeddings.rs # ZUNA encoder + SQLite/HNSW storage -│ ├── ws_commands.rs # WebSocket command handlers -│ ├── ws_server.rs # WebSocket server + mDNS -│ ├── job_queue.rs # Serial background job queue -│ └── constants.rs # Sample rates, bands, channels -├── test.js # WebSocket API smoke test -└── README.md -``` - -### Pre-commit Checks - -A Git pre-commit hook runs two fast sanity checks before every commit: - -| Check | Command | -|---|---| -| `cargo clippy` | `cd src-tauri && cargo clippy --all-targets --all-features -- -D warnings` | -| `svelte-check` | `npm run check` | - -The hook is already installed at `.git/hooks/pre-commit` — no setup required. Both checks must pass; any warning from Clippy is treated as an error. To bypass in an emergency: - -```bash -git commit --no-verify -``` - ---- - -## Versioning - -The `bump` script keeps `package.json`, `src-tauri/tauri.conf.json`, and `src-tauri/Cargo.toml` in sync in one step. - -| Command | Behaviour | -|---|---| -| `npm run bump` | Auto-increments the **patch** digit (`0.0.3 → 0.0.4`) | -| `npm run bump 1.2.0` | Sets all three files to the exact version supplied | - -```bash -# patch bump (0.0.x) -npm run bump - -# explicit version -npm run bump 1.2.0 -``` - ---- - -## Release - -Generate new keys and update `tauri.conf.json`: - -```shell -npm run tauri signer generate -- -w ~/.tauri/skill.key -``` - -Requirements (macOS): - -```shell -brew install create-dmg -``` - - ### Required GitHub secrets - -| Secret | What it is | -|------------------------------------|----------------------------------------------| -| APPLE_CERTIFICATE | `base64 -i cert.p12 output` | -| APPLE_CERTIFICATE_PASSWORD | P12 export password | -| APPLE_SIGNING_IDENTITY | `"Developer ID Application: Name (TEAMID)"` | -| APPLE_ID | Apple ID email | -| APPLE_PASSWORD | App-specific password from appleid.apple.com | -| APPLE_TEAM_ID | 10-character Team ID | -| TAURI_SIGNING_PRIVATE_KEY | Output of generate-keys.py | -| TAURI_SIGNING_PRIVATE_KEY_PASSWORD | Key password (empty string if none) | - -### Testing the CI/CD pipeline locally - -**CI workflow** (lint, type-check, tests) — runs in Docker via [`act`](https://github.com/nektos/act): - -```shell -brew install act - -act push # all three jobs in parallel -act push --job rust-check -act push --job frontend-check -act push --job audit -``` - -Pick **Medium** image (`catthehacker/ubuntu:act-latest`) on first run. - -**Release workflow** — `macos-26` can't run in Docker, so test the pieces separately: - -```shell -# Dry-run: prints every command without executing anything destructive -bash release.sh --dry-run - -# Build only (no signing, no upload) -ESPEAK_LIB_DIR="$(pwd)/src-tauri/espeak-static/lib" \ - npx tauri build --target aarch64-apple-darwin --bundles app,dmg - -# Full local release with real Apple credentials (skips S3 upload) -SKIP_UPLOAD=1 bash release.sh # reads credentials from env.txt -``` - -Check that the tag version matches `tauri.conf.json` before pushing a tag: - -```shell -TAG=v0.0.1 -VERSION="${TAG#v}" -CONF=$(grep '"version"' src-tauri/tauri.conf.json | head -1 \ - | sed 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') -[ "$VERSION" = "$CONF" ] && echo "✓ versions match" || echo "✗ mismatch: tag=$VERSION conf=$CONF" -``` - -| Goal | Command | -|---|---| -| Run all CI checks | `act push` | -| Single CI job | `act push --job frontend-check` | -| Dry-run release | `bash release.sh --dry-run` | -| Build without signing | `SKIP_NOTARIZE=1 SKIP_UPLOAD=1 bash release.sh` | -| Full local release | `SKIP_UPLOAD=1 bash release.sh` | - ---- - ## License -This program is free software: you can redistribute it and/or modify it under -the terms of the **GNU General Public License version 3** as published by the -Free Software Foundation. - -This program is distributed in the hope that it will be useful, but **without -any warranty**; without even the implied warranty of merchantability or fitness -for a particular purpose. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along with -this program. If not, see . - -SPDX-License-Identifier: `GPL-3.0-only` +GPL-3.0-only. See [LICENSE](LICENSE). diff --git a/SKILL.md b/SKILL.md index 2fe78acb4..b9b82eb0f 100644 --- a/SKILL.md +++ b/SKILL.md @@ -8,32 +8,44 @@ automation pipeline. ## Contents -1. [Transport — WebSocket & HTTP](#transport--websocket--http) -2. [Quick Start](#quick-start) -3. [Output Modes](#output-modes) +1. [Supported Devices](#supported-devices) +2. [Transport — WebSocket & HTTP](#transport--websocket--http) +3. [Quick Start](#quick-start) +4. [Output Modes](#output-modes) - [Default — summary only](#default-no-flag--human-readable-summary-only) - [`--json` — raw JSON, pipe-safe](#--json--raw-json-only-pipe-safe) - [`--full` — summary and JSON](#--full--human-readable-summary-and-colorized-json) - [What `--full` reveals](#what---full-reveals) -4. [Global Options](#global-options) -5. [Polling with `status`](#polling-with-status) -6. [Commands](#commands) +5. [Global Options](#global-options) +6. [Polling with `status`](#polling-with-status) +7. [Commands](#commands) - [status](#status) - [session](#session) - [sessions](#sessions) + - [say](#say) - [label](#label) + - [hooks](#hooks) - [search-labels](#search-labels) - [interactive](#interactive) - [search](#search) - [compare](#compare) - [sleep](#sleep) + - [sleep-schedule](#sleep-schedule) - [umap](#umap) - [listen](#listen) - [notify](#notify) + - [calibrations](#calibrations) - [calibrate](#calibrate) - [timer](#timer) + - [health](#health) + - [dnd](#dnd) - [raw](#raw) -7. [Data Reference](#data-reference) + - [search-images](#search-images) + - [screenshots-around](#screenshots-around) + - [screenshots-for-eeg](#screenshots-for-eeg) + - [eeg-for-screenshots](#eeg-for-screenshots) +8. [Screenshots](#screenshots) +9. [Data Reference](#data-reference) - [EEG Band Powers](#eeg-band-powers) - [EEG Ratios & Indices](#eeg-ratios--indices) - [Core Scores](#core-scores) @@ -43,7 +55,7 @@ automation pipeline. - [Sleep Stages](#sleep-stages) - [Headache & Migraine EEG Correlates](#headache--migraine-eeg-correlates) - [Consciousness Metrics](#consciousness-metrics) -7. [Use-Case Recipes](#use-case-recipes) +10. [Use-Case Recipes](#use-case-recipes) - [Focus & Productivity](#focus--productivity) - [Stress & Anxiety](#stress--anxiety) - [Sleep Quality](#sleep-quality) @@ -55,6 +67,38 @@ automation pipeline. --- +## Supported Devices + +NeuroSkill™ supports multiple EEG headsets. The CLI and API work identically +regardless of which device is connected — all commands, scores, and metrics are +device-agnostic. + +| Device | Channels | Sample Rate | Connection | Notes | +|---|---|---|---|---| +| **Muse** (S, 2, 2016) | 4 (TP9, AF7, AF8, TP10) | 256 Hz | BLE | Default device. PPG + IMU included. | +| **OpenBCI Ganglion** | 4 | 200 Hz | BLE | Open-source research-grade board. | +| **OpenBCI Cyton / Cyton+Daisy** | 8 / 16 | 250 Hz | Serial / WiFi | Full 10-20 montage capable. | +| **Neurable MW75 Neuro** | 12 (FT7/T7/TP7/CP5/P7/C5, FT8/T8/TP8/CP6/P8/C6) | 500 Hz | BLE + RFCOMM | Noise-cancelling headphones with EEG. Behind `mw75-rfcomm` feature flag. | +| **Vital Neuro** | 3 (ch1–ch3) | 250 Hz | BLE | Headphones with EEG; audio runs over a separate Bluetooth Classic link. Accelerometer + battery. Device reports no electrode montage. | +| **RE-AK Nucleus Hermes** | 8 (Fp1, Fp2, AF3, AF4, F3, F4, FC1, FC2) | 250 Hz | BLE GATT | ADS1299 + 9-DOF IMU. | +| **Emotiv** (EPOC/Insight/Flex/MN8) | 14 / 5 / 32 | 128 Hz | Cortex WebSocket | IMU included. Connected via Emotiv Cortex API. | +| **IDUN Guardian** | 1 (bipolar in-ear) | 250 Hz | BLE | Single-channel earbud EEG + 6-DOF IMU. | +| **Mendi** | — (fNIRS optical) | — | BLE | Frontal fNIRS headband with IMU + battery telemetry. Not traditional EEG. | +| **Cognionics / CGX** (Quick-20/32r/8r, AIM-2, Patch) | 2–30 | up to 500 Hz | USB Serial (FTDI) | Research-grade; full 10-20 montage on Quick-20+. IMU on r/m variants. | +| **AttentivU** | 4 ExG | 250 Hz | BLE | EEG glasses with 9-axis IMU. | +| **BrainBit** (Original, 2, Pro, Flex 4/8) | 4 (O1, O2, T3, T4) | 250 Hz | BLE (NeuroSDK2) | Consumer EEG headband. | +| **g.tec Unicorn Hybrid Black** | 8 | 250 Hz | BLE (Unicorn API) | Research-grade headset with IMU. | +| **NeuroField Q21** | 20 | 256 Hz | PCAN-USB (CAN bus) | Full 10-20 montage. | +| **BrainMaster** (Atlantis/Discovery/Freedom) | 2–24 | 256 Hz | USB Serial | Neurofeedback amplifiers. Discovery/Freedom support up to 24 channels. | +| **NeuroSky MindWave** | 1 (Fp1) | 512 Hz | Serial (ThinkGear) | Single-channel consumer headset. | +| **Neurosity** (Crown / Notion) | 8 (CP3, C3, F5, PO3, PO4, F6, C4, CP4) | 256 Hz | Cloud-streamed | IMU included. | +| **Brain Products BrainVision** | 16 | 500 Hz | TCP/IP (RDA) | Research-grade; full 10-20 montage. | + +The DSP pipeline dynamically scales to the active channel count (1–32). +Inactive channels have zero overhead. + +--- + ## Transport — WebSocket & HTTP NeuroSkill™ runs a local server (auto-discovered via mDNS or `lsof`). All commands work over @@ -216,12 +260,24 @@ summary**. It is only visible with `--full` (colorized) or `--json` (plain). | `scores.faa`, `scores.tar`, `scores.bar` … | numbers | EEG ratios and spectral indices not surfaced in the summary's Scores section | | `calibration.actions[]` | array | Full ordered list of calibration step objects (name, duration, …) | | `labels.recent[]` | array | Full label objects; summary only prints text + timestamp | +| `hooks.latest_trigger` | object | Most recent hook trigger across all hooks: `{ hook, triggered_at_utc, distance, label_id, label_text }`. The summary shows hook name, timestamp, and distance; the JSON has the full object. | | `history.today_vs_avg` | object | Per-metric today-vs-7-day-avg comparison table (metric, today, avg_7d, delta_pct, direction) | +| `apps` | object | Most-used apps by window-switch count. Contains `top_all_time`, `top_24h`, `top_7d` arrays. Each entry: `{ app_name, switches, last_seen }`. | +| `labels.top_all_time` | array | Most frequent label texts (all time). Each: `{ text, count }`. | +| `labels.top_24h` / `labels.top_7d` | array | Most frequent label texts in the last 24 h / 7 d. | +| `labels.embedded` | number | Count of labels with text embeddings computed. | +| `screenshots` | object | Screenshot/OCR summary counts: `total`, `with_embedding`, `with_ocr`, `with_ocr_embedding`. Also `top_apps_all_time` and `top_apps_24h` arrays (each: `{ app_name, count }`). | ```bash node cli.ts status --json | jq '.history.today_vs_avg' node cli.ts status --json | jq '.calibration.actions' node cli.ts status --json | jq '.labels.recent' +node cli.ts status --json | jq '.hooks.latest_trigger' +node cli.ts status --json | jq '.apps.top_all_time[:5]' +node cli.ts status --json | jq '.apps.top_24h' +node cli.ts status --json | jq '.labels.top_all_time' +node cli.ts status --json | jq '.screenshots' +node cli.ts status --json | jq '.screenshots.top_apps_24h' ``` #### `session` @@ -331,15 +387,81 @@ node cli.ts search-labels "meditation" --json | jq '.results[0].context' | `nodes[]` | array | All graph nodes — the summary prints each layer; `--json` gives the raw array with all fields | | `edges[]` | array | All graph edges with `from_id`, `to_id`, `distance`, `kind` | | `dot` | string | Complete Graphviz DOT source — only accessible via `--dot` or `--json` (never printed in default or `--full`) | +| `svg` | string | Pre-rendered SVG — PCA-scatter layout for found_labels | +| `svg_col` | string | Pre-rendered SVG — column-per-EEG-parent layout | +| `svg_3d` | string | Pre-rendered SVG — 3-D perspective-projected view of all nodes including screenshots (dark theme with depth cues) | | `nodes[].eeg_metrics` | object | Full EEG metrics for `text_label` nodes — the summary shows 5 fields; JSON has all | +| `nodes[].proj_x/y/z` | numbers | 3-D PCA coordinates (normalised [-1, 1]) for all nodes with text embeddings | +| `nodes[].filename` | string | Screenshot image path (screenshot nodes only) | +| `nodes[].app_name` | string | Application name at capture time (screenshot nodes only) | +| `nodes[].window_title` | string | Window title at capture time (screenshot nodes only) | +| `nodes[].ocr_text` | string | OCR-extracted text (screenshot nodes only) | +| `nodes[].ocr_similarity` | number | Cosine similarity between query and OCR text (screenshot nodes only) | ```bash node cli.ts interactive "deep focus" --json | jq '.nodes | length' node cli.ts interactive "meditation" --json | jq '.edges | map(.kind) | unique' node cli.ts interactive "anxiety" --json | jq '[.nodes[] | select(.kind == "text_label") | .text]' +node cli.ts interactive "coding" --json | jq '[.nodes[] | select(.kind == "screenshot") | {file: .filename, app: .app_name}]' node cli.ts interactive "focus" --dot | dot -Tsvg > graph.svg # visualize with graphviz node cli.ts interactive "stress" --dot | dot -Tpng > graph.png node cli.ts interactive "relaxed" --json | jq '.dot' -r | dot -Tsvg > graph.svg # same via --json +node cli.ts interactive "work" --json | jq '.svg_3d' -r > graph_3d.svg # 3D perspective SVG +``` + +--- + +#### `say` + +Like `notify`, `say` produces only a one-line confirmation in default mode. + +| Hidden field | Type | Contents | +|---|---|---| +| `ok` | boolean | Confirmation that the utterance was enqueued | +| `spoken` | string | Echo of the text that was sent to TTS | +| `voice` | string | Voice name used (only present when `--voice` was specified) | + +```bash +node cli.ts say "Eyes open" --json +# → { "command": "say", "ok": true, "spoken": "Eyes open" } +``` + +--- + +#### `calibrations` + +The `calibrations` summary formats profiles into a table but the JSON contains +the full profile objects with all actions, durations, and settings. + +| Hidden field | Type | Contents | +|---|---|---| +| `profiles[].actions[]` | array | Full ordered list of action objects (name, duration_secs) | +| `profiles[].break_duration_secs` | number | Break between action loops | +| `profiles[].auto_start` | boolean | Whether the profile auto-starts on window open | + +```bash +node cli.ts calibrations --json | jq '.profiles[0].actions' +node cli.ts calibrations get 3 --json | jq '.profile' +``` + +--- + +#### `dnd` + +The `dnd` summary prints a rich visual status (bars, scores, tips) but several +raw fields are only visible via `--json`. + +| Hidden field | Type | Contents | +|---|---|---| +| `avg_score` | number | Current rolling average focus score (0–100) | +| `sample_count` | number | How many focus score samples have been collected | +| `window_size` | number | Target number of samples for the rolling window | +| `mode_identifier` | string | DND automation mode identifier | +| `dnd_active` | boolean | Whether this app activated DND | +| `os_active` | boolean | Whether the OS reports DND as active | + +```bash +node cli.ts dnd --json | jq '{avg: .avg_score, threshold: .threshold, active: .dnd_active}' ``` --- @@ -474,11 +596,13 @@ node cli.ts calibrate --json | jq -e '.ok' > /dev/null \ | Hidden field | Type | Contents | |---|---|---| -| events array | array | Full array of every raw broadcast event received. The summary only prints `event_type × count`; `--full` appends every packet. | +| events array | array | Full array of every raw broadcast event received. The summary only prints `event_type × count` (plus a dedicated 🪝 section for hook triggers); `--full` appends every packet. | +| hook events | objects | Full `{ event: "hook", payload: { hook, scenario, distance, label_id, label_text, triggered_at_utc, command, text, context } }` for each Proactive Hook trigger — the summary shows hook name, distance, and matched label; `--json` gives the complete payload for scripting. | ```bash node cli.ts listen --seconds 10 --json | jq '[.[] | select(.event == "scores")]' node cli.ts listen --seconds 5 --json | jq '.[0]' # first event in full +node cli.ts listen --seconds 60 --json | jq '[.[] | select(.event == "hook") | .payload]' ``` --- @@ -492,10 +616,16 @@ node cli.ts listen --seconds 5 --json | jq '.[0]' # first event in full | `--http` | Force HTTP REST; no live-event commands | | `--json` | Raw JSON only — no summary, no colors, pipe-safe | | `--full` | Human-readable summary **and** colorized full JSON appended below | +| `--no-color` | Disable ANSI color output (also honoured via `NO_COLOR` env var or non-TTY stdout) | +| `--version`, `-v` | Print CLI version and exit | +| `--help`, `-h` | Show full help and exit | +| `--poll ` | (`status` only) Re-poll every N seconds; keeps the socket open | | `--dot` | (`interactive` only) Graphviz DOT to stdout — pipe to `dot -Tsvg` | | `--trends` | (`sessions` only) Show first-half → second-half deltas | -| `--mode ` | (`search-labels`) `text` \| `context` \| `both` | -| `--k ` | Number of nearest neighbors (`search`, `search-labels`) | +| `--mode ` | (`search-labels`) `text` \| `context` \| `both`; (`search-images`) `semantic` \| `substring` | +| `--k ` | Number of nearest neighbors (`search`, `search-labels`, `search-images`, `eeg-for-screenshots`) | +| `--by-image ` | (`search-images`) Search by visual similarity using CLIP embeddings instead of OCR text | +| `--window ` | (`screenshots-for-eeg`, `eeg-for-screenshots`) Temporal window in seconds (default 30 / 60) | | `--k-text ` | (`interactive`) k for text-label HNSW search (default 5) | | `--k-eeg ` | (`interactive`) k for EEG-similarity HNSW search (default 5) | | `--k-labels ` | (`interactive`) k for label-proximity step (default 3) | @@ -503,7 +633,31 @@ node cli.ts listen --seconds 5 --json | jq '.[0]' # first event in full | `--ef ` | HNSW ef parameter (`search-labels`; default `max(k×4, 64)`) | | `--seconds ` | Duration for `listen` (default 5) | | `--profile

` | Profile name or UUID for `calibrate` | -| `--help` | Show full help and exit | +| `--context "..."` | (`label`) Long-form annotation body; used by `search-labels --mode context` | +| `--at ` | (`label`) Backdate to a specific unix second (default: now) | +| `--voice ` | (`say`) Voice name to use (e.g. `Jasper`); omit for server default | +| `--keywords ` | (`hooks add`/`update`) Comma-separated keywords | +| `--scenario ` | (`hooks add`/`update`) `any` \| `cognitive` \| `emotional` \| `physical` | +| `--command ` | (`hooks add`/`update`) Command to run on trigger | +| `--hook-text ` | (`hooks add`/`update`) Payload text | +| `--threshold ` | (`hooks add`/`update`) Distance threshold (0.01–1.0) | +| `--recent ` | (`hooks add`/`update`) Recent-refs limit (10–20) | +| `--limit ` | (`hooks log`) Page size (default: 20) | +| `--offset ` | (`hooks log`) Row offset (default: 0) | +| `--actions "L1:20,L2:20"` | (`calibrations create`/`update`) Actions as `label:secs` pairs | +| `--loops ` | (`calibrations create`/`update`) Loop count (default: 3) | +| `--break ` | (`calibrations create`/`update`) Break duration in seconds (default: 5) | +| `--auto-start` | (`calibrations create`/`update`) Auto-start when opened | +| `--name "…"` | (`calibrations update`) Rename the profile | +| `--system "..."` | (`llm chat`) Prepend a system prompt | +| `--temperature ` | (`llm chat`) Sampling temperature 0–2 (default 0.8) | +| `--max-tokens ` | (`llm chat`) Maximum tokens to generate per turn (default 2048) | +| `--image ` | (`llm chat`) Attach image file (can repeat: `--image a.jpg --image b.png`) | +| `--mmproj ` | (`llm add`) Also download a vision projector from the same repo | +| `--bedtime ` | (`sleep-schedule set`) Bedtime in 24-h format (e.g. `23:00`) | +| `--wake ` | (`sleep-schedule set`) Wake-up time in 24-h format (e.g. `07:00`) | +| `--preset ` | (`sleep-schedule set`) Apply a named preset: `default`, `early_bird`, `night_owl`, `short_sleeper`, `long_sleeper` | +| `--metric-type ` | (`health metrics`) Metric type to query: `restingHeartRate`, `hrv`, `vo2Max`, `bodyMass`, etc. | --- @@ -512,11 +666,18 @@ node cli.ts listen --seconds 5 --json | jq '.[0]' # first event in full `status` is the single fastest call to get a complete system snapshot. Poll it periodically from any script or external tool to react to EEG state changes. +The `--poll ` flag keeps the WebSocket connection open and re-polls every N seconds +with a live timestamp header — no need for a shell loop: + ```bash +# Built-in polling (single connection, live updates): +node cli.ts status --poll 5 # refresh every 5 s +node cli.ts status --poll 10 --json # JSON snapshot every 10 s (Ctrl+C to stop) + # One-shot snapshot: node cli.ts status --json -# Poll every 5 seconds and print focus score: +# Manual shell loop (opens a new connection each time): while true; do node cli.ts status --json | jq '.scores.relaxation' sleep 5 @@ -540,12 +701,13 @@ done "ok": true, "device": { "state": "connected", // "connected" | "connecting" | "disconnected" - "name": "Muse-A1B2", + "name": "Muse-A1B2", // or "MW75-Neuro-XXXX", "Hermes-XXXX", "Ganglion-XXXX" "battery": 73, // percent "firmware": "1.3.4", "eeg_samples": 195840, // cumulative samples this run - "ppg_samples": 30600, - "imu_samples": 122400 + "ppg_samples": 30600, // Muse only (PPG sensor) + "imu_samples": 122400, + "eeg_channels": 4 // 4 (Muse/Ganglion), 8 (Hermes), or 12 (MW75) }, "session": { "start_utc": 1740412800, // Unix seconds (UTC) @@ -553,6 +715,8 @@ done "n_epochs": 369 // 5-second embedding epochs computed so far }, "signal_quality": { + // Keys vary by device — Muse: tp9/af7/af8/tp10; Hermes: fp1/fp2/af3/af4/f3/f4/fc1/fc2 + // MW75: ft7/t7/tp7/cp5/p7/c5/ft8/t8/tp8/cp6/p8/c6 "tp9": 0.95, // 0–1; ≥0.9 = good, ≥0.7 = acceptable "af7": 0.88, "af8": 0.91, @@ -594,9 +758,35 @@ done }, "labels": { "total": 58, + "embedded": 42, "recent": [ { "id": 42, "text": "meditation start", "created_at": 1740413100 } - ] + ], + "top_all_time": [ + { "text": "deep focus", "count": 12 }, + { "text": "meditation", "count": 8 } + ], + "top_24h": [ { "text": "deep focus", "count": 3 } ], + "top_7d": [ { "text": "deep focus", "count": 7 } ] + }, + "apps": { + "top_all_time": [ + { "app_name": "VS Code", "switches": 342, "last_seen": 1740413000 }, + { "app_name": "Firefox", "switches": 198, "last_seen": 1740412900 } + ], + "top_24h": [ { "app_name": "VS Code", "switches": 28, "last_seen": 1740413000 } ], + "top_7d": [ { "app_name": "VS Code", "switches": 142, "last_seen": 1740413000 } ] + }, + "screenshots": { + "total": 1842, + "with_embedding": 1840, + "with_ocr": 1780, + "with_ocr_embedding": 1756, + "top_apps_all_time": [ + { "app_name": "VS Code", "count": 620 }, + { "app_name": "Firefox", "count": 310 } + ], + "top_apps_24h": [ { "app_name": "VS Code", "count": 48 } ] }, "sleep": { // Last 48 h sleep staging summary: @@ -608,6 +798,17 @@ done "rem_epochs": 112, "epoch_secs": 5 }, + "hooks": { + "total": 3, // total configured hooks + "enabled": 2, // how many are enabled + "latest_trigger": { // most recent trigger across all hooks (null if never) + "hook": "Deep Work Guard", // hook name that fired + "triggered_at_utc": 1740413100, + "distance": 0.0892, // cosine distance to matched reference + "label_id": 7, + "label_text": "focused reading session" + } + }, "history": { "total_sessions": 63, "recording_days": 31, @@ -625,8 +826,12 @@ done ### `status` -Full snapshot: device state, session, signal quality, scores, bands, embeddings, labels, -sleep summary, and recording history. +Full snapshot: device state, session, signal quality, scores, bands, embeddings, labels +(with most-frequent texts), hooks (with latest trigger), sleep summary, recording history, +app usage analytics, and screenshot/OCR statistics. + +Use `--poll ` to re-poll every N seconds over the same open connection +(keeps the socket open; press Ctrl+C to stop). ```bash node cli.ts status @@ -637,6 +842,16 @@ node cli.ts status --json | jq '.device.battery' node cli.ts status --json | jq '.signal_quality' node cli.ts status --json | jq '.sleep' node cli.ts status --json | jq '.history.current_streak_days' +node cli.ts status --json | jq '.hooks' # hook summary + latest trigger +node cli.ts status --json | jq '.hooks.latest_trigger' # most recent hook trigger +node cli.ts status --json | jq '.hooks.latest_trigger.hook' # which hook fired last +node cli.ts status --json | jq '.apps.top_24h' # most-used apps in last 24h +node cli.ts status --json | jq '.apps.top_all_time[:5]' # top 5 apps all-time +node cli.ts status --json | jq '.labels.top_all_time' # most frequent label texts +node cli.ts status --json | jq '.screenshots' # screenshot/OCR counts +node cli.ts status --json | jq '.screenshots.top_apps_24h' # top screenshot apps (24h) +node cli.ts status --poll 5 # refresh every 5 seconds +node cli.ts status --poll 10 --json # JSON snapshot every 10 seconds ``` **HTTP:** @@ -793,11 +1008,55 @@ curl -s -X POST http://127.0.0.1:8375/ \ --- +### `say` + +Speak text aloud via the on-device KittenTTS engine (fire-and-forget). +The server enqueues the utterance on a dedicated TTS thread and returns immediately — +the response arrives before audio playback begins. + +Requires `espeak-ng` on PATH. First run downloads a ~30 MB TTS model from HuggingFace Hub. + +```bash +node cli.ts say "Eyes open. Starting calibration." +node cli.ts say "Break time. Next: Eyes Closed." --voice Jasper +node cli.ts say "Calibration complete." --http +node cli.ts say "Hello!" --json +``` + +**HTTP:** +```bash +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"say","text":"Eyes open. Starting calibration."}' + +# With a specific voice: +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"say","text":"Break time.","voice":"Jasper"}' +``` + +**Response:** +```jsonc +{ "command": "say", "ok": true, "spoken": "Eyes open. Starting calibration." } +// With --voice Jasper: +{ "command": "say", "ok": true, "spoken": "Break time.", "voice": "Jasper" } +``` + +> **Note:** `--voice` is optional; omitting it uses the voice last selected in Settings → Voice. + +--- + ### `label` Create a timestamped text annotation on the current EEG moment. Labels are stored in the database, shown in the dashboard, and searchable via `search-labels`. +Optional flags: +- `--context "..."` — long-form body stored alongside the short text; used by + `search-labels --mode context` and `--mode both`. +- `--at ` — backdate the label to a specific unix second instead of using + the current time (useful for retrospective annotation). + ```bash node cli.ts label "meditation start" node cli.ts label "eyes closed" @@ -806,6 +1065,8 @@ node cli.ts label "coffee just finished" node cli.ts label "task switch: coding → email" node cli.ts label "phone notification distracted me" node cli.ts label --json "focus block start" # just print the label_id +node cli.ts label "breathwork" --context "box breathing 4-4-4-4, 10 min" +node cli.ts label "retrospective note" --at 1740412800 ``` **HTTP:** @@ -814,6 +1075,16 @@ curl -s -X POST http://127.0.0.1:8375/ \ -H "Content-Type: application/json" \ -d '{"command":"label","text":"meditation start"}' +# With long-form context: +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"label","text":"eyes closed","context":"4-7-8 breathing exercise"}' + +# Backdated to a specific timestamp: +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"label","text":"retrospective note","label_start_utc":1740412800}' + # Save the label_id for later reference: LABEL_ID=$(curl -s -X POST http://127.0.0.1:8375/ \ -H "Content-Type: application/json" \ @@ -828,6 +1099,238 @@ echo "Created label #$LABEL_ID" --- +### `hooks` + +List configured Proactive Hooks with scenario + last-trigger metadata. + +Supports the following subcommands: + +| Subcommand | Description | +|---|---| +| `hooks` (or `hooks status`) | List hooks with scenario + last-trigger metadata | +| `hooks list` | List raw hook rules (name, keywords, threshold, enabled, …) | +| `hooks add [opts]` | Add a new hook rule | +| `hooks remove ` | Delete a hook by name | +| `hooks enable ` | Enable a hook | +| `hooks disable ` | Disable a hook | +| `hooks update [opts]` | Update fields on an existing hook | +| `hooks suggest "kw1,kw2"` | Suggest threshold from matching labels + recent EEG embeddings | +| `hooks log [--limit N --offset M]` | View paginated hook trigger audit log rows | + +**Hook mutation flags** (for `hooks add` and `hooks update`): + +| Flag | Description | +|---|---| +| `--keywords ` | Comma-separated keywords (e.g. `"focus,deep work,flow"`) | +| `--scenario ` | `any` \| `cognitive` \| `emotional` \| `physical` | +| `--command ` | Command to run on trigger | +| `--hook-text ` | Payload text | +| `--threshold ` | Distance threshold (0.01–1.0) | +| `--recent ` | Recent-refs limit (10–20) | + +```bash +# Status (default) — hooks with scenario + last trigger +node cli.ts hooks +node cli.ts hooks --json +node cli.ts hooks --json | jq '.hooks[] | {name: .hook.name, scenario: .hook.scenario, last: .last_trigger.triggered_at_utc}' + +# List raw hook rules +node cli.ts hooks list +node cli.ts hooks list --json + +# Add a new hook +node cli.ts hooks add "Deep Work Guard" --keywords "focus,deep work,flow" --scenario cognitive --threshold 0.14 +node cli.ts hooks add "Stress Alert" --keywords "stress,anxious,overwhelmed" --scenario emotional --threshold 0.12 + +# Update an existing hook +node cli.ts hooks update "Deep Work Guard" --keywords "focus,flow" --threshold 0.12 + +# Enable / disable +node cli.ts hooks enable "Deep Work Guard" +node cli.ts hooks disable "Deep Work Guard" + +# Remove +node cli.ts hooks remove "Deep Work Guard" + +# Suggest threshold from real EEG/label data +node cli.ts hooks suggest "focus,deep work" +node cli.ts hooks suggest "focus" --json | jq '.suggestion.suggested' + +# Scenario-focused quick examples: +# cognitive: deep work overload guard +node cli.ts hooks suggest "focus,deep work" +# emotional: stress-recovery style labels +node cli.ts hooks suggest "stress,anxious,overwhelmed" +# physical: fatigue/body-state style labels +node cli.ts hooks suggest "fatigue,tired,slump" + +# View hook trigger audit log +node cli.ts hooks log --limit 20 --offset 0 +node cli.ts hooks log --json | jq '.rows[] | {ts: .triggered_at_utc, hook: (.hook_json|fromjson).name, scenario: (.hook_json|fromjson).scenario}' +``` + +**HTTP:** +```bash +# Status +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"hooks_status"}' + +# List raw rules +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"hooks_get"}' + +# Set hooks (add/remove/update — sends the full array) +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"hooks_set","hooks":[...]}' + +# Suggest threshold +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"hooks_suggest","keywords":["focus","deep work"]}' + +# Audit log +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"hooks_log","limit":20,"offset":0}' +``` + +#### How Proactive Hooks Work (Broadcast → `listen`) + +Proactive Hooks are a **real-time pattern matching system** that runs inside the +EEG embedding pipeline. Every 5 seconds, when a new EEG embedding epoch is +computed, the server checks all enabled hooks against the live brain state: + +``` +EEG stream → 5 s epoch → embedding vector → cosine distance to hook references + ↓ + distance ≤ threshold? + ↓ + scenario gate passes? + ↓ + broadcast { event: "hook", payload: {...} } + + audit log in hooks.sqlite + + OS toast notification +``` + +**Step by step:** + +1. **Labels as reference patterns** — When you create labels (`node cli.ts label "deep focus"`), + the server embeds both the text and the surrounding EEG window. Each hook's + `keywords` are matched against label texts to build a set of reference EEG + embeddings (up to `recent_limit` most recent matches). + +2. **Live comparison** — Every new 5-second EEG epoch is compared (cosine distance) + against every enabled hook's reference embeddings. If the closest match is + within the hook's `distance_threshold`, the hook fires. + +3. **Scenario gating** — Before firing, the hook checks the current epoch's metrics + against the scenario filter: + - `any` — always passes + - `cognitive` — requires elevated theta/beta ratio or cognitive load ≥ 55 + - `emotional` — requires stress index ≥ 55, mood ≤ 45, or relaxation ≤ 35 + - `physical` — requires drowsiness ≥ 55, headache/migraine index ≥ 45, or extreme HR + +4. **Cooldown** — A hook cannot fire more than once every 10 seconds (prevents + rapid-fire spam when the brain state is sustained). + +5. **Broadcast** — When a hook fires, the server pushes a `{ "event": "hook" }` message + over WebSocket to **all connected clients**. This is the same broadcast mechanism + used for EEG, scores, and label events — any WebSocket listener receives it. + +6. **Audit log** — Every trigger is persisted to `hooks.sqlite` for later review + via `hooks log`. + +**Why this matters for the CLI:** + +Because hook triggers are broadcast events, `listen` captures them automatically. +This lets you build automation pipelines that react to your brain state in real time: + +```bash +# ── React to hook triggers in a shell script ────────────────────────────────── +# Listen for 5 minutes and act on any hook triggers: +node cli.ts listen --seconds 300 --json | jq -c '.[] | select(.event == "hook") | .payload' | while read -r payload; do + HOOK=$(echo "$payload" | jq -r '.hook') + DIST=$(echo "$payload" | jq -r '.distance') + LABEL=$(echo "$payload" | jq -r '.label_text') + echo "Hook triggered: $HOOK (dist=$DIST, label=$LABEL)" + + # Run custom actions based on hook name: + case "$HOOK" in + "Deep Work Guard") + node cli.ts notify "Deep Focus Detected" "Distance: $DIST to '$LABEL'" + ;; + "Stress Alert") + osascript -e 'display notification "Take a break" with title "Stress Detected"' + ;; + esac +done + +# ── Python real-time hook listener ──────────────────────────────────────────── +python3 - <<'EOF' +import asyncio, json +import websockets + +async def listen_for_hooks(port: int): + async with websockets.connect(f"ws://127.0.0.1:{port}") as ws: + print("Listening for hook triggers…") + async for raw in ws: + msg = json.loads(raw) + if msg.get("event") == "hook": + p = msg["payload"] + print(f"🪝 {p['hook']} fired! distance={p['distance']:.4f} label=\"{p['label_text']}\"") + # Do something: send Slack message, log to CSV, trigger HomeKit, etc. + +asyncio.run(listen_for_hooks(8375)) +EOF + +# ── End-to-end workflow: create labels, add hook, listen for triggers ───────── +# 1. Record some EEG while in a specific state and label it: +node cli.ts label "deep focus coding" +node cli.ts label "deep concentration" + +# 2. Create a hook that fires when your brain returns to that state: +node cli.ts hooks add "Focus Mode" \ + --keywords "focus,concentration,deep" \ + --scenario cognitive \ + --threshold 0.15 + +# 3. Verify the threshold makes sense: +node cli.ts hooks suggest "focus,concentration,deep" + +# 4. Listen and watch for triggers: +node cli.ts listen --seconds 300 + +# 5. Check the audit log after the session: +node cli.ts hooks log --limit 10 +``` + +**Hook trigger event shape (WebSocket):** +```jsonc +{ + "event": "hook", + "payload": { + "hook": "Deep Work Guard", // hook rule name + "scenario": "cognitive", // scenario filter + "context": "labels", // match source + "distance": 0.0892, // cosine distance to closest reference + "label_id": 7, // which label's EEG pattern matched + "label_text": "focused reading session", + "triggered_at_utc": 1740412830, // unix seconds + "command": "notify", // configured action + "text": "You're in deep focus!" // configured payload + } +} +``` + +> **Note:** Hook broadcast events are only available over WebSocket. HTTP transport +> (`--http`) has no push streaming, so you cannot receive hook triggers via HTTP. +> Use `listen` (which requires WebSocket) or connect directly via `ws://`. + +--- + ### `search-labels` Semantic (vector) search across all your EEG annotations. @@ -911,8 +1414,8 @@ curl -s -X POST http://127.0.0.1:8375/ \ ### `interactive` -Cross-modal 4-layer graph search. Combines semantic text search, EEG similarity search, -and temporal label proximity into a single directed graph: +Cross-modal 5-layer graph search. Combines semantic text search, EEG similarity search, +temporal label proximity, and screenshot discovery into a single directed graph: ``` "deep focus" → text_label nodes (semantically similar annotations) @@ -920,6 +1423,9 @@ and temporal label proximity into a single directed graph: eeg_point nodes (raw EEG moments from label time windows) ↓ found_label nodes (labels near those EEG moments in time) + ↓ + screenshot nodes (screenshots near EEG timestamps, ranked by + window-title / OCR-text proximity to query) ``` Four output formats — choose exactly one: @@ -950,6 +1456,9 @@ node cli.ts interactive "anxiety" --json | jq '[.nodes[] | select(.kind == "eeg_ # Extract discovered nearby labels: node cli.ts interactive "stress" --json | jq '[.nodes[] | select(.kind == "found_label") | .text]' +# Extract discovered screenshots: +node cli.ts interactive "coding" --json | jq '[.nodes[] | select(.kind == "screenshot") | {file: .filename, app: .app_name, ocr_sim: .ocr_similarity}]' + # Render graph as SVG (requires graphviz): node cli.ts interactive "deep focus" --dot | dot -Tsvg > graph.svg @@ -1066,16 +1575,38 @@ curl -s -X POST http://127.0.0.1:8375/ \ "distance": 0.133, // fraction of reach window (0 = right at the EEG point) "eeg_metrics": null, "parent_id": "ep_1740413565" + }, + { + "id": "ss_20260224080530", + "kind": "screenshot", + "text": null, + "timestamp_unix": 1740413130, + "distance": 0.05, + "eeg_metrics": null, + "parent_id": "ep_1740413565", + "filename": "20260224/20260224080530.webp", + "app_name": "VS Code", + "window_title": "main.rs", + "ocr_text": "fn dispatch(app: &AppHandle, command: &str…", + "ocr_similarity": 0.42, + "proj_x": 0.31, + "proj_y": -0.18, + "proj_z": 0.72 } // ... more nodes ], "edges": [ { "from_id": "query", "to_id": "tl_0", "distance": 0.1204, "kind": "text_sim" }, { "from_id": "tl_0", "to_id": "ep_1740413565", "distance": 0.0231, "kind": "eeg_bridge" }, - { "from_id": "ep_1740413565","to_id": "fl_42", "distance": 0.133, "kind": "label_prox" } + { "from_id": "ep_1740413565","to_id": "fl_42", "distance": 0.133, "kind": "label_prox" }, + { "from_id": "ep_1740413565","to_id": "ss_20260224080530","distance": 2.0, "kind": "screenshot_prox" }, + { "from_id": "ep_1740413565","to_id": "ss_20260224080530","distance": 0.42, "kind": "ocr_sim" } // ... ], - "dot": "digraph interactive_search {\n graph [rankdir=TB, ...];\n \"query\" [...];\n ..." + "dot": "digraph interactive_search {\n graph [rankdir=TB, ...];\n \"query\" [...];\n ...", + "svg": "...", + "svg_col": "...", + "svg_3d": "..." } ``` @@ -1087,6 +1618,7 @@ curl -s -X POST http://127.0.0.1:8375/ \ | `text_label` | 1 | blue | Annotations semantically similar to the query | | `eeg_point` | 2 | amber | Raw EEG moments from label time windows | | `found_label` | 3 | emerald | Annotations discovered near EEG moments in time | +| `screenshot` | 4 | pink | Screenshots near EEG timestamps, ranked by window-title / OCR proximity | **Edge kinds:** @@ -1096,6 +1628,26 @@ curl -s -X POST http://127.0.0.1:8375/ \ | `eeg_bridge` | text_label → eeg_point | Cosine distance in EEG embedding space | | `eeg_sim` | eeg_point → eeg_point | Cosine distance (shared EEG point, cross-edge) | | `label_prox` | eeg_point → found_label | Temporal proximity (fraction of reach window) | +| `screenshot_prox` | eeg_point → screenshot | Temporal proximity (seconds between EEG epoch and screenshot capture) | +| `ocr_sim` | eeg_point → screenshot | Cosine similarity between query text and screenshot OCR text | + +**Screenshot node fields** (only present on `screenshot` kind nodes): + +| Field | Type | Description | +|---|---|---| +| `filename` | string | Relative path to the screenshot image file | +| `app_name` | string | Application name at capture time | +| `window_title` | string | Window title at capture time | +| `ocr_text` | string | OCR-extracted text from the screenshot | +| `ocr_similarity` | number | Cosine similarity (0–1) between query text and OCR text | + +**3-D projection fields** (present on all nodes when text embeddings are available): + +| Field | Type | Description | +|---|---|---| +| `proj_x` | number | PCA axis 1, normalised to [-1, 1] | +| `proj_y` | number | PCA axis 2, normalised to [-1, 1] | +| `proj_z` | number | PCA axis 3, normalised to [-1, 1] | > **Empty results:** If no labels have been embedded yet, only the query node is returned > (`nodes.length === 1`, `edges.length === 0`). Annotate moments with `label` first, then @@ -1376,6 +1928,118 @@ curl -s -X POST http://127.0.0.1:8375/ \ --- +### `sleep-schedule` + +View or update the sleep schedule used for session classification and sleep staging analysis. +The schedule defines your expected bedtime and wake-up time, and can be set to one of five +built-in presets or a fully custom window. + +```bash +# Show current schedule: +node cli.ts sleep-schedule +node cli.ts sleep-schedule --json + +# Update with explicit times: +node cli.ts sleep-schedule set --bedtime 23:00 --wake 07:00 +node cli.ts sleep-schedule set --bedtime 01:00 --wake 09:00 + +# Apply a named preset: +node cli.ts sleep-schedule set --preset early_bird +node cli.ts sleep-schedule set --preset night_owl + +# Combine preset with override: +node cli.ts sleep-schedule set --preset short_sleeper +node cli.ts sleep-schedule set --bedtime 00:30 --wake 06:30 --preset custom +``` + +**Available presets:** + +| Preset | Bedtime | Wake | Duration | +|---|---|---|---| +| `default` | 23:00 | 07:00 | 8 h | +| `early_bird` | 21:30 | 05:30 | 8 h | +| `night_owl` | 01:00 | 09:00 | 8 h | +| `short_sleeper` | 00:00 | 06:00 | 6 h | +| `long_sleeper` | 22:00 | 08:00 | 10 h | + +When you set `--bedtime` or `--wake` without `--preset`, the preset is automatically +set to `custom`. + +**HTTP:** +```bash +# Get current schedule: +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"sleep_schedule"}' + +# Update schedule: +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"sleep_schedule_set","bedtime":"23:00","wake_time":"07:00","preset":"default"}' + +# Apply a preset (only the fields you send are updated): +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"sleep_schedule_set","preset":"early_bird","bedtime":"21:30","wake_time":"05:30"}' +``` + +**Example output (default):** +``` +⚡ sleep-schedule current schedule + + Sleep Schedule + bedtime 23:00 + wake 07:00 + duration 8h (480 min) + preset default + + Available presets: + ● default 23:00 — 07:00 (8h) + ○ early_bird 21:30 — 05:30 (8h) + ○ night_owl 01:00 — 09:00 (8h) + ○ short_sleeper 00:00 — 06:00 (6h) + ○ long_sleeper 22:00 — 08:00 (10h) +``` + +**Example output (set):** +``` +⚡ sleep-schedule set + bedtime: 01:00 + wake: 09:00 + preset: night_owl + + updated + bedtime: 01:00 + wake: 09:00 + duration: 8h + preset: night_owl +``` + +**JSON response (get):** +```jsonc +{ + "command": "sleep_schedule", + "bedtime": "23:00", + "wake_time": "07:00", + "preset": "default", + "duration_minutes": 480 +} +``` + +**JSON response (set):** +```jsonc +{ + "command": "sleep_schedule_set", + "ok": true, + "bedtime": "01:00", + "wake_time": "09:00", + "preset": "night_owl", + "duration_minutes": 480 +} +``` + +--- + ### `umap` Compute a 3D UMAP projection of EEG embedding vectors from two sessions. @@ -1460,7 +2124,8 @@ completed in 8432ms ### `listen` Passively collect real-time broadcast events from the server for a fixed duration. -Events include raw EEG packets, PPG, IMU, scores, and label-created notifications. +Events include raw EEG packets, PPG, IMU, scores, label-created notifications, +and **Proactive Hook triggers**. > Requires WebSocket (`--http` mode has no push streaming). @@ -1470,19 +2135,71 @@ node cli.ts listen --seconds 30 node cli.ts listen --seconds 10 --json node cli.ts listen --seconds 5 --json | jq '[.[] | select(.event == "scores")]' node cli.ts listen --seconds 5 --json | jq 'map(select(.event == "eeg")) | length' + +# ── Capture hook triggers ──────────────────────────────────────────────────── +node cli.ts listen --seconds 60 --json | jq '[.[] | select(.event == "hook")]' +node cli.ts listen --seconds 300 --json | jq '[.[] | select(.event == "hook") | .payload]' ``` **Example output:** ``` -⚡ listen for 5s… +⚡ listen for 30s… + + eeg ×282 + ppg ×72 + scores ×30 + imu ×150 + hook ×1 + + 🪝 Hook Triggers + Deep Work Guard [cognitive] dist: 0.0892 + matched label: "focused reading session" id: 7 + command: notify + text: You're in deep focus — stay in the zone! +``` + +### subscribe — Per-connection broadcast filter + +Control which broadcast events reach your WebSocket connection, with optional +field projection and rate limiting. Reduces bandwidth for mobile clients, +widgets, and integrations that only need specific metrics. + +> Requires WebSocket (`--http` mode has no push streaming). + +```bash +# Only receive eeg-bands, keep focus + hr fields, at max 1 Hz: +node cli.ts subscribe --events eeg-bands --fields focus,hr --max-hz 1 + +# Receive eeg-bands and hook events, all fields, unlimited rate: +node cli.ts subscribe --events eeg-bands,hook + +# Reset to default (all events, all fields, unlimited): +node cli.ts subscribe --events '*' +node cli.ts subscribe # same — no args = reset +``` - eeg ×47 - ppg ×12 - scores ×5 - imu ×25 - label_created ×0 +**WebSocket JSON:** +```json +→ {"command":"subscribe","events":["eeg-bands"],"fields":["focus","hr","timestamp"],"max_hz":1} +← {"command":"subscribe","ok":true,"events":["eeg-bands"],"fields":["focus","hr","timestamp"],"max_hz":1.0} ``` +| Parameter | Type | Default | Description | +|------------|------------|---------------|-------------| +| `events` | `string[]` | `[]` (all) | Event types to receive. `["*"]` = all. | +| `fields` | `string[]` | `[]` (all) | Payload keys to keep. `timestamp` always included. | +| `max_hz` | `number` | `0` (unlimited) | Max updates per second per event type. Server drops excess. | + +**Use cases:** +- **iOS/Android client on LTE:** `events: ["eeg-bands"], fields: ["focus","hr","relaxation"], max_hz: 1` → ~200 bytes/sec instead of 12 KB/sec +- **Lock screen widget:** `events: ["eeg-bands"], fields: ["focus"], max_hz: 0.2` +- **Hook monitor:** `events: ["hook"]` — only receive hook triggers +- **Full firehose:** omit all params or `events: ["*"]` + +When a hook fires during the listen window, the CLI prints a dedicated +**🪝 Hook Triggers** section showing the hook name, scenario, cosine distance +to the matched label, and the label that triggered it. + **JSON event shapes:** ```jsonc // EEG packet (4 channels × N samples): @@ -1501,6 +2218,22 @@ node cli.ts listen --seconds 5 --json | jq 'map(select(.event == "eeg")) | lengt // Label created (by dashboard or CLI): { "event": "label_created", "label_id": 43, "text": "distracted", "created_at": 1740412830 } + +// Proactive Hook trigger (fired when live EEG matches a labeled pattern): +{ + "event": "hook", + "payload": { + "hook": "Deep Work Guard", // hook rule name + "scenario": "cognitive", // scenario: any | cognitive | emotional | physical + "context": "labels", // what matched (always "labels" currently) + "distance": 0.0892, // cosine distance to the matched label's EEG embedding + "label_id": 7, // label that triggered the match + "label_text": "focused reading session", + "triggered_at_utc": 1740412830, // unix seconds when the hook fired + "command": "notify", // configured hook command + "text": "You're in deep focus — stay in the zone!" // configured hook text + } +} ``` --- @@ -1527,52 +2260,555 @@ curl -s -X POST http://127.0.0.1:8375/ \ --- -### `calibrate` +### `calibrations` -Open the calibration window and start a profile immediately. -With `--profile`, matches by profile name (case-insensitive substring) or exact UUID. +Full CRUD for calibration profiles stored on the server. + +| Subcommand | Description | +|---|---| +| `calibrations` / `calibrations list` | List all profiles | +| `calibrations get ` | Inspect a single profile by numeric ID | +| `calibrations create "name" --actions "L:s,…"` | Create a new profile | +| `calibrations update [opts]` | Update an existing profile | +| `calibrations delete ` | Delete a profile | + +**Calibration profile flags** (for `create` and `update`): + +| Flag | Description | +|---|---| +| `--actions "L1:20,L2:20"` | Actions as `label:duration_secs` pairs (comma-separated) | +| `--loops ` | Loop count — how many times to repeat the action sequence (default: 3) | +| `--break ` | Break duration in seconds between loops (default: 5) | +| `--auto-start` | Auto-start when the calibration window opens | +| `--name "…"` | (`update` only) Rename the profile | + +The `--actions` format is `"Label1:seconds,Label2:seconds"`. If no colon is present, +defaults to 20 seconds per action. ```bash -node cli.ts calibrate # uses active profile -node cli.ts calibrate --profile "Eyes Open" # by name -node cli.ts calibrate --profile default # by id -node cli.ts calibrate --json | jq '.ok' +# List / inspect +node cli.ts calibrations # list all profiles +node cli.ts calibrations list # same as above +node cli.ts calibrations get 3 # full detail for profile id=3 +node cli.ts calibrations --json | jq '.profiles[].name' +node cli.ts calibrations get 3 --json | jq '.profile.actions' + +# Create a new profile +node cli.ts calibrations create "My Protocol" --actions "Eyes Open:20,Eyes Closed:20" --loops 3 --break 5 +node cli.ts calibrations create "Quick Baseline" --actions "Relax:30,Focus:30" --auto-start +node cli.ts calibrations create "Three Phase" --actions "Rest:15,Concentrate:30,Breathe:10" --loops 2 + +# Update an existing profile (by name or UUID) +node cli.ts calibrations update "My Protocol" --loops 5 --break 10 +node cli.ts calibrations update "My Protocol" --name "Renamed Protocol" +node cli.ts calibrations update "My Protocol" --actions "Eyes Open:30,Eyes Closed:30,Breathe:15" +node cli.ts calibrations update "a1b2c3d4" --loops 4 + +# Delete a profile (by name or UUID) +node cli.ts calibrations delete "My Protocol" +node cli.ts calibrations delete "a1b2c3d4-e5f6-7890-abcd-ef1234567890" ``` **HTTP:** ```bash -# List profiles first: +# List all profiles: curl -s -X POST http://127.0.0.1:8375/ \ -H "Content-Type: application/json" \ -d '{"command":"list_calibrations"}' | jq '.profiles[].name' -# Run the active profile: +# Get a specific profile: curl -s -X POST http://127.0.0.1:8375/ \ -H "Content-Type: application/json" \ - -d '{"command":"run_calibration"}' + -d '{"command":"get_calibration","id":3}' -# Run a specific profile by UUID: +# Create a new profile: curl -s -X POST http://127.0.0.1:8375/ \ -H "Content-Type: application/json" \ - -d '{"command":"run_calibration","id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"}' -``` + -d '{ + "command": "create_calibration", + "name": "My Protocol", + "actions": [ + { "label": "Eyes Open", "duration_secs": 20 }, + { "label": "Eyes Closed", "duration_secs": 20 } + ], + "loop_count": 3, + "break_duration_secs": 5, + "auto_start": false + }' ---- +# Update a profile: +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"update_calibration","id":"a1b2c3d4-...","loop_count":5}' -### `timer` +# Delete a profile: +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"delete_calibration","id":"a1b2c3d4-..."}' -Open the Focus Timer window and auto-start the work phase using the last saved preset -(Pomodoro 25/5, Deep Work 50/10, or Short Focus 15/5). +# REST equivalents: +curl -s -X POST http://127.0.0.1:8375/calibrations \ + -H "Content-Type: application/json" \ + -d '{"name":"My Protocol","actions":[{"label":"Eyes Open","duration_secs":20}]}' +curl -s -X PATCH http://127.0.0.1:8375/calibrations/a1b2c3d4-... \ + -H "Content-Type: application/json" \ + -d '{"loop_count":5}' +curl -s -X DELETE http://127.0.0.1:8375/calibrations/a1b2c3d4-... +``` -```bash -node cli.ts timer +**Example output (list):** ``` +⚡ calibrations -**HTTP:** -```bash -curl -s -X POST http://127.0.0.1:8375/ \ - -H "Content-Type: application/json" \ - -d '{"command":"timer"}' + id name actions loop + ────────────────────────────────────────────────────────── + 1 Eyes Open/Closed 4 2 + 2 Relaxation 3 1 + 3 Focus Baseline 5 1 +``` + +**Example output (create):** +``` +⚡ calibrations create "My Protocol" + ✓ created My Protocol id: a1b2c3d4-e5f6-7890-abcd-ef1234567890 + actions: Eyes Open (20s) → Eyes Closed (20s) + loops: 3 break: 5s auto-start: false +``` + +**JSON response (create):** +```jsonc +{ + "command": "create_calibration", + "ok": true, + "profile": { + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "name": "My Protocol", + "loop_count": 3, + "break_duration_secs": 5, + "auto_start": false, + "actions": [ + { "label": "Eyes Open", "duration_secs": 20 }, + { "label": "Eyes Closed", "duration_secs": 20 } + ] + } +} +``` + +--- + +### `calibrate` + +Open the calibration window and start a profile immediately. +With `--profile`, matches by profile name (case-insensitive substring) or exact UUID. + +```bash +node cli.ts calibrate # uses active profile +node cli.ts calibrate --profile "Eyes Open" # by name +node cli.ts calibrate --profile default # by id +node cli.ts calibrate --json | jq '.ok' +``` + +**HTTP:** +```bash +# List profiles first: +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"list_calibrations"}' | jq '.profiles[].name' + +# Run the active profile: +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"run_calibration"}' + +# Run a specific profile by UUID: +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"run_calibration","id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"}' +``` + +--- + +### `timer` + +Open the Focus Timer window and auto-start the work phase using the last saved preset +(Pomodoro 25/5, Deep Work 50/10, or Short Focus 15/5). + +```bash +node cli.ts timer +``` + +**HTTP:** +```bash +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"timer"}' +``` + +--- + +### `health` + +Query and manage Apple HealthKit data synced from the iOS companion app. +Data is stored in `~/.skill/health.sqlite` and covers sleep analysis, workouts, +heart rate, step counts, mindfulness sessions, arbitrary scalar health metrics, +and GPS location fixes recorded by CoreLocation. + +**Subcommands:** + +| Subcommand | Description | +|---|---| +| `health` (or `health summary`) | Aggregate counts for a time range (default: last 24h) | +| `health sleep` | Query sleep analysis samples (InBed, Asleep, Awake, REM, Core, Deep) | +| `health workouts` | Query workout sessions (type, duration, calories, distance, HR) | +| `health hr` | Query heart rate samples (bpm, context) | +| `health steps` | Query step count aggregates | +| `health metrics --metric-type ` | Query scalar health metrics (restingHeartRate, hrv, vo2Max, …) | +| `health metric-types` | List all distinct metric types stored in the database | +| `health location` | Query GPS fixes from CoreLocation (lat, lon, altitude, accuracy, speed) | +| `health sync ''` | Push HealthKit data from the iOS companion app | + +**Options (apply to query subcommands):** + +| Flag | Description | +|---|---| +| `--start ` | Start of time range (unix seconds; default: now − 24h) | +| `--end ` | End of time range (unix seconds; default: now) | +| `--limit ` | Max results to return (default: 100, max: 10,000) | +| `--metric-type ` | Required for `health metrics` — e.g. `restingHeartRate`, `hrv`, `vo2Max`, `bodyMass` | + +```bash +# Summary — aggregate counts (last 24h): +node cli.ts health +node cli.ts health --json +node cli.ts health summary --start 1740000000 --end 1740086400 + +# Sleep samples: +node cli.ts health sleep +node cli.ts health sleep --json | jq '.results[] | {stage: .value, start: .start_utc}' + +# Workouts: +node cli.ts health workouts +node cli.ts health workouts --json | jq '.results[] | {type: .workout_type, cal: .active_calories}' + +# Heart rate: +node cli.ts health hr --limit 20 +node cli.ts health hr --json | jq '.results[] | {bpm: .bpm, ctx: .context}' + +# Steps: +node cli.ts health steps +node cli.ts health steps --json | jq '.results[].count' + +# Scalar metrics: +node cli.ts health metrics --metric-type restingHeartRate +node cli.ts health metrics --metric-type hrv --json | jq '.results[].value' + +# List available metric types: +node cli.ts health metric-types + +# GPS location fixes: +node cli.ts health location +node cli.ts health location --limit 50 +node cli.ts health location --json | jq '.results[] | {lat:.latitude,lon:.longitude,ts:.timestamp}' +node cli.ts health location --start 1740000000 --end 1740086400 --json + +# Sync data from iOS companion: +node cli.ts health sync '{"steps":[{"start_utc":1740000000,"end_utc":1740086400,"count":9500}]}' +``` + +**HTTP:** +```bash +# Summary (GET — last 24h): +curl -s http://127.0.0.1:8375/v1/health/summary | jq '.' + +# Summary (POST — custom range): +curl -s -X POST http://127.0.0.1:8375/v1/health/summary \ + -H "Content-Type: application/json" \ + -d '{"start_utc":1740000000,"end_utc":1740086400}' + +# Query by type: +curl -s -X POST http://127.0.0.1:8375/v1/health/query \ + -H "Content-Type: application/json" \ + -d '{"type":"sleep","start_utc":1740000000,"end_utc":1740086400,"limit":100}' + +curl -s -X POST http://127.0.0.1:8375/v1/health/query \ + -H "Content-Type: application/json" \ + -d '{"type":"metrics","metric_type":"restingHeartRate","start_utc":1740000000,"end_utc":1740086400}' + +# List metric types: +curl -s http://127.0.0.1:8375/v1/health/metric_types | jq '.metric_types' + +# GPS location fixes: +curl -s -X POST http://127.0.0.1:8375/v1/health/query \ + -H "Content-Type: application/json" \ + -d '{"type":"location","start_utc":1740000000,"end_utc":1740086400,"limit":100}' + +# Sync from iOS: +curl -s -X POST http://127.0.0.1:8375/v1/health/sync \ + -H "Content-Type: application/json" \ + -d '{ + "sleep": [{"source_id":"watch","start_utc":1740000000,"end_utc":1740028800,"value":"REM"}], + "workouts": [{"workout_type":"Running","start_utc":1740030000,"end_utc":1740033600, + "duration_secs":3600,"active_calories":450,"distance_meters":8000}], + "heart_rate": [{"timestamp":1740030000,"bpm":72.0,"context":"sedentary"}], + "steps": [{"start_utc":1740000000,"end_utc":1740086400,"count":9500}], + "mindfulness": [{"start_utc":1740040000,"end_utc":1740041200}], + "metrics": [{"metric_type":"restingHeartRate","timestamp":1740000000,"value":58.0,"unit":"bpm"}], + "location": [{"source_id":"iphone","timestamp":1740030000,"latitude":37.3317,"longitude":-122.0307, + "altitude":25.0,"horizontal_accuracy":5.0,"speed":1.4,"course":270.0}] + }' +``` + +**Example output (summary):** +``` +⚡ health last 24h + + Apple Health Summary + sleep samples 12 + workouts 2 + heart rate 148 samples + total steps 9500 + mindfulness 1 sessions + metrics 24 entries + location fixes 312 +``` + +**Example output (location):** +``` +⚡ health location last 24h limit: 100 + + type: location count: 3 + + 37.33170, -122.03070 alt 25m ±5m 1.4 km/h [iphone] 3/29/2026, 10:30:00 AM + 37.33251, -122.03140 alt 24m ±8m 0.0 km/h [iphone] 3/29/2026, 10:25:00 AM + 37.33089, -122.02980 alt 26m ±6m 4.2 km/h [iphone] 3/29/2026, 10:20:00 AM +``` + +**Example output (workouts):** +``` +⚡ health workouts last 24h limit: 100 + + type: workouts count: 2 + + Running 3/17/2026, 6:30:00 AM 45m 450 kcal 8.0 km avg HR 145 + Yoga 3/16/2026, 8:00:00 PM 30m 120 kcal +``` + +**JSON responses:** + +Summary: +```jsonc +{ + "start_utc": 1740000000, + "end_utc": 1740086400, + "sleep_samples": 12, + "workouts": 2, + "heart_rate_samples": 148, + "total_steps": 9500, + "mindfulness_sessions": 1, + "metric_entries": 24 +} +``` + +Query: +```jsonc +{ + "type": "sleep", + "count": 12, + "results": [ + { "id": 1, "source_id": "watch", "start_utc": 1740000000, "end_utc": 1740028800, + "value": "REM", "created_at": 1740100000 } + ] +} +``` + +Sync: +```jsonc +{ + "sleep_upserted": 1, + "workouts_upserted": 1, + "heart_rate_upserted": 1, + "steps_upserted": 1, + "mindfulness_upserted": 1, + "metrics_upserted": 1 +} +``` + +**Sync payload format (all arrays optional, idempotent upsert):** + +```jsonc +{ + "sleep": [{ "source_id": "watch", "start_utc": 0, "end_utc": 0, "value": "REM" }], + "workouts": [{ "workout_type": "Running", "start_utc": 0, "end_utc": 0, "duration_secs": 3600, + "active_calories": 450, "distance_meters": 8000, "avg_heart_rate": 145 }], + "heart_rate": [{ "timestamp": 0, "bpm": 72.0, "context": "sedentary" }], + "steps": [{ "start_utc": 0, "end_utc": 0, "count": 9500 }], + "mindfulness": [{ "start_utc": 0, "end_utc": 0 }], + "metrics": [{ "metric_type": "restingHeartRate", "timestamp": 0, "value": 58.0, "unit": "bpm" }] +} +``` + +> **Common metric types:** `restingHeartRate`, `hrv` (SDNN ms), `vo2Max`, `bodyMass`, +> `bloodPressureSystolic`, `bloodPressureDiastolic`, `respiratoryRate`, `bodyTemperature`, +> `oxygenSaturation`, `bloodGlucose`. +> +> **Oura Ring metric types:** `oura_sleep_score`, `oura_readiness_score`, `oura_activity_score`, +> `oura_deep_sleep_secs`, `oura_rem_sleep_secs`, `oura_light_sleep_secs`, `oura_awake_time_secs`, +> `oura_total_sleep_secs`, `oura_sleep_efficiency`, `oura_breath_rate`, `oura_sleep_avg_hr`, +> `oura_temperature_deviation`, `oura_temperature_trend_deviation`, `oura_active_calories`, +> `oura_total_calories`, `oura_high_activity_time`, `oura_medium_activity_time`, +> `oura_low_activity_time`, `oura_sedentary_time`, `oura_resting_time`, +> `oura_equivalent_walking_distance`, plus standard types `hrv`, `restingHeartRate`, `spo2`. + +--- + +### `oura` + +Sync data from the Oura Ring V2 Cloud API and store it in the unified health database. +Data flows through the same `health.sqlite` pipeline as Apple HealthKit — query with +`health` commands after syncing. + +| Subcommand | Description | +|---|---| +| `oura` / `oura status` | Check if Oura token is configured and test API connectivity | +| `oura sync` | Sync last 30 days of Oura Ring data (sleep, activity, readiness, HR, SpO2, workouts) | + +```bash +node cli.ts oura # check token + connectivity +node cli.ts oura status # user info from Oura API +node cli.ts oura status --json # raw JSON +node cli.ts oura sync # sync last 30 days +node cli.ts oura sync --oura-start 2026-03-01 --oura-end 2026-03-28 # custom date range +node cli.ts oura sync --json # raw JSON response +``` + +**HTTP:** +```bash +# Status: +curl -s http://127.0.0.1:8375/v1/oura/status | jq '.' + +# Sync (last 30 days): +curl -s -X POST http://127.0.0.1:8375/v1/oura/sync \ + -H "Content-Type: application/json" \ + -d '{"start_date":"2026-03-01","end_date":"2026-03-28"}' +``` + +**WebSocket:** +```jsonc +{ "command": "oura_status" } +{ "command": "oura_sync", "start_date": "2026-03-01", "end_date": "2026-03-28" } +``` + +**Example output (status):** +``` +⚡ oura status + + token configured + connected yes + email user@example.com + age 32 +``` + +**Example output (sync):** +``` +⚡ oura sync 2026-03-01 → 2026-03-28 + + synced from Oura Ring + sleep: 28 fetched → 28 stored + workouts: 12 fetched → 12 stored + heart rate: 8064 fetched → 8064 stored + steps: 28 fetched → 28 stored + mindfulness: 5 fetched → 5 stored + metrics: 392 fetched → 392 stored +``` + +> After syncing, query Oura data with standard health commands: +> ```bash +> node cli.ts health metrics --metric-type oura_sleep_score +> node cli.ts health metrics --metric-type oura_readiness_score +> node cli.ts health hr --limit 20 # includes Oura HR samples +> node cli.ts health steps # includes Oura step counts +> node cli.ts health metric-types # lists all oura_* metric types +> ``` + +--- + +### `dnd` + +Show Do Not Disturb automation status, or force-override it. + +With no subcommand, shows the full DND config and live state: automation enabled/disabled, +focus threshold, rolling average score, sample window fill, and OS-level Focus state. + +With `on` or `off`, immediately activates or deactivates DND, bypassing the EEG threshold. + +```bash +node cli.ts dnd # show config + live eligibility state +node cli.ts dnd on # force-enable DND (bypass EEG threshold) +node cli.ts dnd off # force-disable DND +node cli.ts dnd --json # raw JSON (pipe to jq) +node cli.ts dnd --json | jq '{enabled: .enabled, avg_score: .avg_score, threshold: .threshold}' +``` + +**HTTP:** +```bash +# Status: +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"dnd"}' + +# Force on: +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"dnd_set","enabled":true}' + +# Force off: +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"dnd_set","enabled":false}' +``` + +**Example output (status):** +``` +⚡ dnd automation status + + DND automation + enabled yes + threshold 60 avg focus score (0–100) required to activate + window 60s (≈ 240 samples at ~4 Hz) + mode standard + + Rolling average (avg of last 240 focus scores) + ████████████████████░░░░ 72.5 / 60 + ▶ above threshold — DND is active + + Sample window + ████████████████████████ 240 / 240 samples + + State + app activated yes (this app set DND) + OS active yes (macOS Assertions.json / defaults read) +``` + +**JSON response (status):** +```jsonc +{ + "command": "dnd", + "ok": true, + "enabled": true, + "threshold": 60, + "duration_secs": 60, + "window_size": 240, + "mode_identifier": "standard", + "avg_score": 72.5, + "sample_count": 240, + "dnd_active": true, + "os_active": true +} +``` + +**JSON response (dnd on/off):** +```jsonc +{ "command": "dnd_set", "ok": true } ``` --- @@ -1600,6 +2836,971 @@ curl -s -X POST http://127.0.0.1:8375/ \ --- +### `llm` + +Control the built-in on-device LLM inference server (OpenAI-compatible, powered by the RLX runtime). +All subcommands route to the WebSocket/HTTP API — no GPU dependency for status/catalog/logs queries. + +#### Subcommands + +| Subcommand | Description | +|---|---| +| `llm status` | Server state (stopped/loading/running), model name, context window | +| `llm start` | Load the active model and start the inference server | +| `llm stop` | Stop the server and free GPU/CPU memory | +| `llm catalog` | List all GGUF models with download states and active selections | +| `llm add ` | Add an external HF model to the catalog and download it | +| `llm add ` | Add from a full HuggingFace URL | +| `llm add ... --mmproj ` | Also add and download a vision projector from the same repo | +| `llm select ` | Set the active text model | +| `llm mmproj ` | Set the active vision projector (or `none` to disable) | +| `llm autoload-mmproj ` | Toggle auto-loading of vision projector on start | +| `llm download ` | Download a model by filename (fire-and-forget; poll catalog for progress) | +| `llm pause ` | Pause an in-progress model download | +| `llm resume ` | Resume a paused model download | +| `llm cancel ` | Cancel an in-progress download | +| `llm delete ` | Delete a locally-cached model file | +| `llm downloads` | List all downloads with status and progress | +| `llm refresh` | Re-probe the HF Hub cache for externally downloaded models | +| `llm fit` | Check which models fit in available RAM/VRAM | +| `llm logs` | Print the last 500 LLM server log lines | +| `llm chat` | **Interactive multi-turn REPL** — type `exit` to quit (**WebSocket only**) | +| `llm chat "message"` | Single-shot: send one message, stream the reply, and exit (**WebSocket only**) | + +```bash +# Server lifecycle +node cli.ts llm status +node cli.ts llm start # loads model — may take several seconds +node cli.ts llm stop + +# Model management — catalog, add, select, download +node cli.ts llm catalog +node cli.ts llm catalog --json | jq '.entries[] | select(.state == "downloaded")' +node cli.ts llm add bartowski/Phi-4-mini-reasoning-GGUF Phi-4-mini-reasoning-Q4_K_M.gguf +node cli.ts llm add bartowski/Phi-4-mini-reasoning-GGUF Phi-4-mini-reasoning-Q4_K_M.gguf --mmproj mmproj-Phi-4-mini-reasoning-BF16.gguf +node cli.ts llm add https://huggingface.co/bartowski/Phi-4-mini-reasoning-GGUF/blob/main/Phi-4-mini-reasoning-Q4_K_M.gguf +node cli.ts llm select "Qwen_Qwen3.5-4B-Q4_K_M.gguf" +node cli.ts llm mmproj "mmproj-Qwen_Qwen3.5-4B-BF16.gguf" +node cli.ts llm mmproj none # disable vision projector +node cli.ts llm autoload-mmproj on +node cli.ts llm download "Qwen3-1.7B-Q4_K_M.gguf" # fire-and-forget +node cli.ts llm pause "Qwen3-1.7B-Q4_K_M.gguf" +node cli.ts llm resume "Qwen3-1.7B-Q4_K_M.gguf" +node cli.ts llm cancel "Qwen3-1.7B-Q4_K_M.gguf" +node cli.ts llm delete "Qwen3-1.7B-Q4_K_M.gguf" +node cli.ts llm downloads # list all downloads with progress +node cli.ts llm refresh # re-probe HF Hub cache +node cli.ts llm fit # check which models fit in RAM/VRAM + +# Logs +node cli.ts llm logs + +# ── Interactive multi-turn chat REPL ────────────────────────────────────────── +node cli.ts llm chat # opens interactive REPL + +# With a system prompt (persists for the whole session): +node cli.ts llm chat --system "You are a concise EEG neuroscience assistant." + +# With GenParam overrides: +node cli.ts llm chat --temperature 0.3 --max-tokens 512 + +# Combined — system prompt + lower temperature for factual answers: +node cli.ts llm chat \ + --system "Answer in one sentence. Only use what you know about EEG." \ + --temperature 0.2 + +# ── Single-shot chat (pipe-friendly) ───────────────────────────────────────── +node cli.ts llm chat "What EEG frequency bands are associated with meditation?" +node cli.ts llm chat "Explain delta waves" --temperature 0.3 --max-tokens 256 +node cli.ts llm chat "Summarize my session" --json # JSON output: {text, tokens} +``` + +**Interactive REPL commands** (type these at the `You:` prompt): + +| Command | Effect | +|---|---| +| `/clear` | Clear conversation history (system prompt is kept) | +| `/history` | Print all messages in the current conversation | +| `/help` | Show REPL command help | +| `exit` or `quit` | End the session | +| `Ctrl+C` or `Ctrl+D` | End the session immediately | + +**Chat persistence:** + +All conversations initiated via WebSocket (`llm_chat`) and HTTP (`POST /llm/chat`) +are **automatically persisted** to the same SQLite chat store used by the Chat window UI +(`~/.skill/chats/chat_history.sqlite`). This means: + +- WebSocket / HTTP chats appear in the Chat window sidebar and can be reviewed later. +- The server returns a `session_id` that you can pass in subsequent requests to continue + the same conversation (multi-turn persistence across connections). +- New sessions are auto-titled from the first user message (up to 60 characters). +- Tool calls executed during the conversation are also persisted. + +To continue an existing conversation, include `"session_id": ` in your request. +If omitted, a new session is created automatically. + +**WebSocket protocol — `llm_chat` (streaming):** + +`llm_chat` is the only WebSocket command that returns **multiple frames** per request. +Tokens stream back as `delta` frames; generation ends with a single `done` (or `error`) frame. + +```js +ws.send(JSON.stringify({ + command: "llm_chat", + messages: [ + { role: "system", content: "You are a concise EEG assistant." }, + { role: "user", content: "What does high theta power indicate?" }, + ], + // Optional — continue an existing session: + // session_id: 42, + // Optional GenParams — all have sensible defaults: + // temperature: 0.8, top_k: 40, top_p: 0.9, repeat_penalty: 1.1, + // max_tokens: 2048, thinking_budget: 512 (set to 0 to skip blocks) +})); + +// Short-hand for single user message: +ws.send(JSON.stringify({ command: "llm_chat", message: "Hello!" })); +``` + +**Server sends multiple frames back:** + +```jsonc +// Session frame (sent first — contains the persistent session_id): +{ "command": "llm_chat", "type": "session", "session_id": 42 } + +// Delta frames (one per token batch): +{ "command": "llm_chat", "type": "delta", "text": "High theta" } +{ "command": "llm_chat", "type": "delta", "text": " power (4–8 Hz)" } +// ... + +// Final done frame (also includes session_id): +{ + "command": "llm_chat", + "ok": true, + "type": "done", + "finish_reason": "stop", // "stop" | "length" + "prompt_tokens": 42, + "completion_tokens": 87, + "n_ctx": 4096, + "session_id": 42 +} + +// Or on error: +{ + "command": "llm_chat", + "ok": false, + "type": "error", + "error": "LLM server not running — send { \"command\": \"llm_start\" } first" +} +``` + +**HTTP REST shortcuts** (non-streaming): + +```bash +# Status +curl -s http://127.0.0.1:8375/llm/status | jq '{status, model_name, n_ctx}' + +# Start / stop +curl -s -X POST http://127.0.0.1:8375/llm/start +curl -s -X POST http://127.0.0.1:8375/llm/stop + +# Catalog +curl -s http://127.0.0.1:8375/llm/catalog | jq '.entries[] | select(.state == "downloaded") | .filename' + +# Add an external model (via WebSocket/universal tunnel) +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"llm_add_model","repo":"bartowski/Phi-4-mini-reasoning-GGUF","filename":"Phi-4-mini-reasoning-Q4_K_M.gguf","download":true}' + +# Select active model / mmproj +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"llm_select_model","filename":"Qwen_Qwen3.5-4B-Q4_K_M.gguf"}' +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"llm_select_mmproj","filename":"mmproj-Qwen_Qwen3.5-4B-BF16.gguf"}' + +# Download a model (fire-and-forget; poll /llm/catalog for progress) +curl -s -X POST http://127.0.0.1:8375/llm/download \ + -H "Content-Type: application/json" \ + -d '{"filename":"Qwen3-1.7B-Q4_K_M.gguf"}' + +# Pause / resume / cancel / delete +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"llm_pause_download","filename":"Qwen3-1.7B-Q4_K_M.gguf"}' +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"llm_resume_download","filename":"Qwen3-1.7B-Q4_K_M.gguf"}' +curl -s -X POST http://127.0.0.1:8375/llm/cancel_download \ + -H "Content-Type: application/json" \ + -d '{"filename":"Qwen3-1.7B-Q4_K_M.gguf"}' +curl -s -X POST http://127.0.0.1:8375/llm/delete \ + -H "Content-Type: application/json" \ + -d '{"filename":"Qwen3-1.7B-Q4_K_M.gguf"}' + +# Downloads list / refresh / hardware fit +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"llm_downloads"}' +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"llm_refresh_catalog"}' +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"llm_hardware_fit"}' + +# Logs +curl -s http://127.0.0.1:8375/llm/logs | jq '.logs[-10:]' + +# ── POST /llm/chat — non-streaming chat with optional image upload ──────────── +# All conversations are persisted to chat_history.sqlite (same store as the Chat window). +# A session_id is returned in the response; pass it in subsequent requests to continue +# the same conversation. + +# Plain text message (creates a new session automatically) +curl -s -X POST http://127.0.0.1:8375/llm/chat \ + -H "Content-Type: application/json" \ + -d '{"message":"What is EEG coherence?"}' | jq '{text, finish_reason, completion_tokens, session_id}' + +# Continue an existing session (multi-turn) +curl -s -X POST http://127.0.0.1:8375/llm/chat \ + -H "Content-Type: application/json" \ + -d '{"message":"Can you elaborate on that?","session_id":42}' + +# With a system prompt and GenParams +curl -s -X POST http://127.0.0.1:8375/llm/chat \ + -H "Content-Type: application/json" \ + -d '{"message":"Summarize in one line.","system":"Be extremely brief.","temperature":0.3}' + +# With an image (base64 data-URL in the "images" array) +IMAGE_B64=$(base64 -i screenshot.png) +curl -s -X POST http://127.0.0.1:8375/llm/chat \ + -H "Content-Type: application/json" \ + -d "{\"message\":\"What do you see in this image?\",\"images\":[\"data:image/png;base64,${IMAGE_B64}\"]}" + +# Full OpenAI messages format (multi-turn, vision content parts) +curl -s -X POST http://127.0.0.1:8375/llm/chat \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + {"role":"system","content":"You are a neuroscience assistant."}, + {"role":"user","content":[ + {"type":"image_url","image_url":{"url":"data:image/jpeg;base64,..."}}, + {"type":"text","text":"What brain region might this scan show?"} + ]} + ] + }' | jq '.text' +``` + +**`POST /llm/chat` response** (always complete JSON, never streamed): +```json +{ + "command": "llm_chat", + "ok": true, + "text": "EEG coherence measures…", + "finish_reason": "stop", + "prompt_tokens": 42, + "completion_tokens": 87, + "n_ctx": 4096 +} +``` + +**Image upload via CLI** (WebSocket streaming): + +```bash +# Single-shot with one image +node cli.ts llm chat "What do you see?" --image screenshot.png + +# Multiple images +node cli.ts llm chat "Compare these EEG plots" --image session1.png --image session2.png + +# With system prompt + GenParams +node cli.ts llm chat "Describe this headset" \ + --image headset.jpg \ + --system "You are a hardware expert." \ + --temperature 0.2 + +# HTTP non-streaming (works without WebSocket) +node cli.ts llm chat "What's in this scan?" --image brain.png --http + +# Interactive REPL with image staging (type /image inside the session) +node cli.ts llm chat +# You: /image eeg_plot.png +# You: What anomalies do you see in this EEG trace? +``` + +**Image upload via WebSocket** (`llm_chat` streaming protocol): + +Images are embedded directly in the `messages` array as OpenAI-format `image_url` content parts using base64 `data:` URLs. The server extracts and decodes them automatically before passing to the inference actor. + +```js +// Single image + text (standard OpenAI vision format) +ws.send(JSON.stringify({ + command: "llm_chat", + messages: [ + { + role: "user", + content: [ + { type: "image_url", image_url: { url: "data:image/png;base64,iVBORw0KGgo…" } }, + { type: "text", text: "What do you see in this EEG spectrogram?" }, + ], + }, + ], +})); + +// Multiple images (in document order) +ws.send(JSON.stringify({ + command: "llm_chat", + messages: [ + { + role: "user", + content: [ + { type: "image_url", image_url: { url: "data:image/jpeg;base64,/9j/4AAQ…" } }, + { type: "image_url", image_url: { url: "data:image/jpeg;base64,/9j/4BBQ…" } }, + { type: "text", text: "Compare these two EEG recordings." }, + ], + }, + ], +})); +``` + +> **Vision requirement:** Image input requires the LLM server to be started with a +> vision-capable model that has an mmproj (multi-modal projector) loaded. +> Check `supports_vision: true` in `llm_status` before sending images. +> If `supports_vision` is false the server will still attempt inference but will +> ignore the image content. + +**WebSocket commands** (also available via `POST /` universal tunnel): + +| Command | Required params | Optional params | Description | +|---|---|---|---| +| `llm_status` | — | — | Server state + model info | +| `llm_start` | — | — | Start inference server (blocks until model loaded) | +| `llm_stop` | — | — | Stop server + free resources | +| `llm_catalog` | — | — | Full model catalog with live download progress | +| `llm_add_model` | `repo` (string), `filename` (string) | `download` (bool), `mmproj` (string) | Add an external HF model to the catalog | +| `llm_select_model` | `filename` (string) | — | Set the active text model | +| `llm_select_mmproj` | `filename` (string) | — | Set the active vision projector (`""` to disable) | +| `llm_set_autoload_mmproj` | `enabled` (bool) | — | Toggle auto-loading of vision projector on start | +| `llm_download` | `filename` (string) | — | Start model download | +| `llm_pause_download` | `filename` (string) | — | Pause an in-progress download | +| `llm_resume_download` | `filename` (string) | — | Resume a paused download | +| `llm_cancel_download` | `filename` (string) | — | Cancel in-progress download | +| `llm_delete` | `filename` (string) | — | Delete cached model file | +| `llm_downloads` | — | — | List all downloads with status and progress | +| `llm_refresh_catalog` | — | — | Re-probe HF Hub cache for externally downloaded models | +| `llm_hardware_fit` | — | — | Check which models fit in available RAM/VRAM | +| `llm_logs` | — | — | Last ≤500 log entries | +| `llm_chat` | `messages` (array) **or** `message` (string) | `temperature`, `top_k`, `top_p`, `repeat_penalty`, `seed`, `max_tokens`, `thinking_budget` | Streaming chat (multi-frame response; `messages` grows per turn for multi-turn) | +| `subscribe` | — | `events` (string[]), `fields` (string[]), `max_hz` (number) | Filter broadcasts for this connection (WS only). Empty/omit = reset to all. | + +**GenParams reference** (applicable to `llm_chat`): + +| Field | Type | Default | Description | +|---|---|---|---| +| `temperature` | float | 0.8 | Sampling temperature (0 = deterministic) | +| `top_k` | int | 40 | Top-K sampling | +| `top_p` | float | 0.9 | Nucleus sampling threshold | +| `repeat_penalty` | float | 1.1 | Repetition penalty | +| `seed` | uint | 0xDEADBEEF | RNG seed for reproducible output | +| `max_tokens` | uint | 2048 | Maximum tokens to generate | +| `thinking_budget` | uint \| null | 512 | Max tokens in `…` block (`0` = skip thinking, `null` = unlimited) | + +#### Built-in Tool Calling + +When the LLM server is running, `llm_chat` supports **automatic tool calling** — the +model can invoke built-in tools to perform actions and return results within the +conversation. Tools are injected into the system prompt automatically when enabled. + +| Tool | Description | +|---|---| +| `bash` | Execute shell commands (with safety approval for dangerous operations) | +| `read_file` | Read file contents from disk | +| `write_file` | Write / create files | +| `edit_file` | Surgical find-and-replace edits in files | +| `web_search` | Search the web (DuckDuckGo/Brave/SearXNG + fallback handling) | +| `web_fetch` | Fetch a URL and return its content | +| `search_output` | Navigate large tool outputs (paginated) | +| `skill` | Query the NeuroSkill™ API — status, sessions, search, labels, screenshots, sleep, hooks, DND | + +Tool calls are detected in the model's output, executed server-side, and results are +fed back into the conversation automatically. The CLI's `llm chat` command handles +this transparently — you'll see tool-call cards in the interactive REPL and results +streamed back inline. + +##### Tool policy settings (LLM → Tools) + +The following runtime settings control tool behavior: + +| Field | Type | Default | Meaning | +|---|---:|---:|---| +| `chat_mode` | `automatic \| chat \| query` | `automatic` | `automatic`: use tools when relevant; `chat`: free-form responses allowed; `query`: strict grounded mode (requires tool evidence). | +| `query_refusal_response` | string | built-in sentence | Response returned in `query` mode when no relevant tool evidence is available. | +| `intelligent_selection_enabled` | bool | `true` | Enables per-prompt tool reranking to reduce tool injection bloat. | +| `intelligent_selection_top_n` | number | `8` | Max tools kept after reranking. | +| `prefer_native_tool_calling` | bool | `true` | Prompts models to prefer OpenAI-style native `tool_calls` JSON, with `[TOOL_CALL]...` fallback. | +| `strict_path_safety` | bool | `true` | Enforces trusted-root path integrity checks (`cwd`, `home`, `tmp`) for file tools. | +| `prompt_variables` | object | `{}` | Runtime `{key}` replacements for system/user prompts before inference. | + +Built-in prompt variables include: `{time}`, `{date}`, `{datetime}`, `{os}`, +`{arch}`, `{home}`, `{cwd}`, `{llm.model}`, `{llm.n_ctx}`. Custom keys from +`prompt_variables` are merged on top. + +##### Query mode behavior + +When `chat_mode = query`, the assistant is prevented from answering with ungrounded +free-text if no successful tool-backed evidence was produced in the turn. In that +case, it returns `query_refusal_response` exactly. + +**The `skill` tool** lets the LLM query and control NeuroSkill™ directly. Commands +are sent as `{ "command": "", "args": { ... } }` objects. Supported +commands include: `status`, `sessions`, `session_metrics`, `search`, `compare`, +`sleep`, `sleep_schedule`, `sleep_schedule_set`, `label`, `search_labels`, +`interactive_search`, `search_screenshots`, `screenshots_around`, +`screenshots_for_eeg`, `eeg_for_screenshots`, `search_screenshots_vision`, +`search_screenshots_by_image_b64`, `hooks_status`, `hooks_get`, `hooks_suggest`, +`hooks_log`, `dnd`, `notify`, `say`, `health_summary`, `health_query`, +`health_metric_types`, `health_sync`, `oura_sync`, `oura_status`, and others. + +**Blocked commands:** For safety, the LLM cannot invoke self-management commands: +`llm_start`, `llm_stop`, `llm_select_model`, `llm_select_mmproj`, `llm_add_model`, +`llm_download`, `llm_cancel_download`, `llm_pause_download`, `llm_resume_download`, +`llm_refresh_catalog`, `llm_logs`, `llm_delete`, `run_calibration`, `timer`. + +**Safety:** Dangerous operations (e.g. `rm -rf`, writing to system paths) trigger an +approval dialog before execution. With `strict_path_safety` enabled, file tools also +reject paths outside trusted roots (`cwd`/`home`/`tmp`) before execution. The tool +system enforces output size limits and automatic history trimming to stay within the +model's context window. + +--- + +**LLM server status values:** + +| `status` | Meaning | +|---|---| +| `"stopped"` | No model loaded; `llm_start` required | +| `"loading"` | Model is being loaded from disk / initialising | +| `"running"` | Model ready; `llm_chat` and `/v1/*` endpoints are live | + +**Download state values** (in `llm_catalog` entries): + +| `state` | Meaning | +|---|---| +| `"not_downloaded"` | Model not present locally | +| `"downloading"` | Download in progress; check `progress` (0.0–1.0) | +| `"downloaded"` | Model cached locally; ready to use | +| `"cancelled"` | Download was cancelled | +| `"failed"` | Download failed; `status_msg` has details | + +> **Note:** The LLM server also exposes an OpenAI-compatible HTTP API on the same +> port at `/v1/chat/completions`, `/v1/completions`, and `/v1/embeddings` once started. +> Use any OpenAI client library by pointing it at `http://127.0.0.1:`. + +**Python example — streaming over WebSocket:** + +```python +import asyncio, json +import websockets + +async def llm_chat(port: int, message: str): + async with websockets.connect(f"ws://127.0.0.1:{port}") as ws: + # Start server if needed + await ws.send(json.dumps({"command": "llm_start"})) + resp = json.loads(await ws.recv()) + print("server:", resp.get("result")) + + # Stream a chat response + await ws.send(json.dumps({"command": "llm_chat", "message": message})) + text = "" + async for raw in ws: + frame = json.loads(raw) + if frame.get("command") != "llm_chat": + continue # broadcast event — skip + if frame.get("type") == "delta": + print(frame["text"], end="", flush=True) + text += frame["text"] + elif frame.get("type") == "done": + print(f"\n[{frame['finish_reason']} | {frame['completion_tokens']} tokens]") + break + elif frame.get("type") == "error" or frame.get("ok") is False: + raise RuntimeError(frame.get("error", "llm_chat error")) + +asyncio.run(llm_chat(8375, "Explain delta waves in one sentence.")) +``` + +**Node.js example — streaming via ws:** + +```js +const WebSocket = require("ws"); +const ws = new WebSocket("ws://127.0.0.1:8375"); + +ws.on("open", () => { + // Start server + ws.send(JSON.stringify({ command: "llm_start" })); +}); + +ws.on("message", (raw) => { + const frame = JSON.parse(raw); + + if (frame.command === "llm_start" && frame.ok) { + // Server ready — send a chat message + ws.send(JSON.stringify({ command: "llm_chat", message: "What is EEG coherence?" })); + return; + } + + if (frame.command !== "llm_chat") return; + + switch (frame.type) { + case "delta": process.stdout.write(frame.text); break; + case "done": + console.log(`\n[${frame.finish_reason} | ${frame.completion_tokens} tokens]`); + ws.close(); + break; + case "error": + console.error("Error:", frame.error); + ws.close(); + break; + } +}); +``` + +--- + +## Screenshots + +NeuroSkill™ periodically captures screenshots of the active window, embeds them +with CLIP vision (ONNX), runs OCR text extraction, and indexes both modalities in +separate HNSW indices for similarity search. + +**Capabilities:** +- **Capture:** macOS (CoreGraphics FFI), Linux (X11/Wayland), Windows (GDI) +- **Vision embedding:** Nomic Embed Vision v1.5 via the rlx runtime (GPU or CPU) +- **OCR:** on-device via `rlx-ocr` (Linux/Windows) or Apple Vision (macOS) +- **Search:** dual HNSW architecture — visual similarity and OCR text similarity +- **Configuration:** interval, image size, quality, embedding backend, OCR engine, + GPU/CPU toggle — all in Settings → Screenshots +- **Image serving:** `GET /screenshots//` on the API server + +Screenshot images appear as indicators on the History day-view heatmap grid and can +be searched from the CLI via `search-images`, `screenshots-around`, `screenshots-for-eeg`, +and `eeg-for-screenshots`. + +--- + +### `search-images` + +Search screenshots by OCR text — either semantic (embedding similarity via HNSW) +or substring (SQL LIKE matching). + +Each result includes the screenshot filename, timestamp, app name, window title, +OCR text, and a similarity score (for semantic mode). + +Screenshot images can be viewed at: `http://127.0.0.1:/screenshots/` + +**Modes:** +- `semantic` (default) — embeds the query text and searches the OCR HNSW index + for semantically similar on-screen text +- `substring` — direct SQL LIKE matching against raw OCR text +- `vision` (via `--by-image`) — embeds a reference image with CLIP and searches + the visual HNSW index for visually similar screenshots + +```bash +node cli.ts search-images "compiler error" +node cli.ts search-images "login page" --k 5 +node cli.ts search-images "TODO" --mode substring +node cli.ts search-images "dashboard" --json | jq '.results[].filename' +node cli.ts search-images "meeting notes" --json | jq '.results[] | {file: .filename, app: .app_name, sim: .similarity}' +node cli.ts search-images "error stack trace" --mode semantic --k 10 + +# Vision search — find screenshots visually similar to a reference image: +node cli.ts search-images --by-image reference.png +node cli.ts search-images --by-image screenshot.jpg --k 5 +node cli.ts search-images --by-image layout-mock.webp --json | jq '.results[].filename' +``` + +**HTTP:** +```bash +# Semantic search (default): +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"search_screenshots","query":"compiler error","k":20,"mode":"semantic"}' + +# Substring search: +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d '{"command":"search_screenshots","query":"TODO","k":10,"mode":"substring"}' + +# Vision search (base64-encoded image): +IMAGE_B64=$(base64 -w0 reference.png) +curl -s -X POST http://127.0.0.1:8375/ \ + -H "Content-Type: application/json" \ + -d "{\"command\":\"search_screenshots_by_image_b64\",\"image_b64\":\"$IMAGE_B64\",\"k\":20}" +``` + +**Example output:** +``` +⚡ search-images "compiler error" (mode: semantic, k: 20) + + mode: semantic + k: 20 results: 3 + + 20260315/20260315143025.webp + time: 3/15/2026, 2:30:25 PM + app: VS Code window: skill — cli.ts + similarity: 87% + ocr: error[E0308]: mismatched types expected `Result` injection after a budget + cap; orphaned tail tokens are discarded (decoded into KV cache for + coherence, suppressed from output) until the next clean line boundary +- **File upload** in chat — images attachable to messages; previewed in + the UI before sending +- **Markdown renderer** (`MarkdownRenderer.svelte`) — renders streamed + assistant output with code blocks, tables, and inline formatting +- **Chat window** (`src/routes/chat/+page.svelte`) — full chat UI with + message history, streaming tokens, stop button, model/mmproj selectors, + generation parameter controls +- **Global chat shortcut** — configurable keyboard shortcut (stored in + settings) focuses the existing chat window or opens a new one +- **i18n** — `llm.*` keys added to all five language files (en, de, fr, + he, uk) + +### Build / CI (0.0.11) + +- **Bypass Tauri's built-in signing pipeline** in both `release.yml` and + `pr-build.yml` — Tauri's `create-dmg` subprocess crashes with `SIGILL` + on macOS 26 (hdiutil API change); replaced with explicit steps: + 1. `npx tauri build --bundles app --no-sign` — compile only + 2. `codesign` — deep-sign with `--options runtime` + `--entitlements` + 3. `xcrun notarytool submit … --wait` — notarize + 4. `xcrun stapler staple` — staple ticket to bundle + 5. Recreate `.app.tar.gz` from the signed bundle, then + `npx tauri signer sign` — re-sign the updater artifact with Ed25519 +- `release.sh` — minor fix to `TAURI_TARGET` default propagation + +--- diff --git a/changes/releases/0.0.110.md b/changes/releases/0.0.110.md new file mode 100644 index 000000000..8c879a1bd --- /dev/null +++ b/changes/releases/0.0.110.md @@ -0,0 +1,48 @@ +## [0.0.110] — 2026-04-11 + +### Features + +- feat: add CPU backend support to skill-router for CI coverage +- Add a new feature to skill-router that uses CPU-based UMAP +- Changes: +- The GPU backend remains the default for normal builds, while the +- fix: disable all GPU-dependent features in coverage workflow +- The coverage CI was still failing because the embedding features +- Solution: Disable all embedding features in the coverage workflow +- The embedding functionality will still be tested in local +- fix: add llm-native feature and use it in coverage workflow +- The coverage CI was failing because is not a valid +- Solution: +- This ensures that llama-cpp-4 is statically linked in the coverage +- fix: add native feature to llm in coverage workflow +- The coverage CI was failing because it couldn't find libllama.so.0. +- Solution: Change to in the coverage workflow's +- This matches the Cargo.toml configuration where we added the native +- fix: remove aggressive -static flag from macOS config +- The -static flag in the macOS cargo config was causing issues and +- Instead, we rely on: +- This change ensures that: +- Verified that skill-daemon runs without libllama.0.dylib present. +- chore: enable static linking for llama-cpp-4 +- Add the feature to llama-cpp-4 dependencies to enable static +- This change: +- Static linking improves: +- The binary size will increase, but this is acceptable for a desktop +- chore: update llama-cpp-4 to 0.2.43 +- Update llama-cpp-4 dependency from version 0.2.42 to 0.2.43. +- This brings in the latest improvements and bug fixes from the +- The update is applied to all feature variants (ggml, metal, vulkan). +- fix: disable GPU features in coverage CI +- The coverage CI was failing because tests that use GPU acceleration +- Solution: Run coverage tests with CPU-only features by: +- This allows the coverage workflow to complete successfully while still +- revert: remove CI checks from tests +- The previous approach of checking CI environment inside tests didn't work +- This commit reverts the CI checks and we'll try a different approach +- fix: use CPU-only backend in CI tests +- The coverage CI was still failing because even though we added CI checks, +- Solution: Modify test_state() to use the LUNA CPU-only backend instead of +- LUNA is a topology-agnostic encoder that runs entirely on CPU, making it +- fix: skip GPU tests in CI environment +- Solution: Add a check for the CI environment variable at the beginning +- Tests affected: diff --git a/changes/releases/0.0.111.md b/changes/releases/0.0.111.md new file mode 100644 index 000000000..c6d79e3a8 --- /dev/null +++ b/changes/releases/0.0.111.md @@ -0,0 +1,7 @@ +## [0.0.111] — 2026-04-11 + +### Features + +- updated DMG version +- fixed the build +- llama linking fixed diff --git a/changes/releases/0.0.112.md b/changes/releases/0.0.112.md new file mode 100644 index 000000000..535c52ed3 --- /dev/null +++ b/changes/releases/0.0.112.md @@ -0,0 +1,24 @@ +## [0.0.112] — 2026-04-11 + +### Features + +- New daemon files created: +- - crates/skill-daemon/src/reconnect.rs — Reconnect state machine (backoff, retry countdown, MAX_RETRY_ATTEMPTS) with background loop and REST endpoints +- Daemon state.rs changes: +- - Added broadcast() convenience method on AppState +- Daemon main.rs changes: +- - Registered new modules (reconnect, monitor, background) +- Daemon session metadata enriched: +- - write_session_meta_full() now accepts optional StatusResponse for richer metadata (battery, signal quality, device identity, phone info) +- Tauri files simplified: +- - lifecycle.rs — Removed reconnect state machine (130+ lines). go_disconnected now just cleans local state and delegates to daemon +- updated coverage.ci +- Removed direct deps (37 total): +- lean tauri +- decouple tauri from wgpu +- chinese +- exclude skill from coverage ci +- Both GPU and CPU modes compile. Here's a summary of all changes made: +- 1. scripts/create-macos-dmg.sh — Fixed unbound CLEANUP_DIRS[@] under set -u +- fixed ci +- updated CI diff --git a/changes/releases/0.0.113.md b/changes/releases/0.0.113.md new file mode 100644 index 000000000..038e1d26c --- /dev/null +++ b/changes/releases/0.0.113.md @@ -0,0 +1,14 @@ +## [0.0.113] — 2026-04-11 + +### Features + +- updated CI +- Daemon side (business logic added) +- crates/skill-daemon/src/background.rs: +- crates/skill-daemon/src/handlers.rs: +- crates/skill-daemon/src/routes/settings_calibration.rs: +- Tauri side (logic removed) +- src-tauri/src/background.rs: +- src-tauri/src/lifecycle.rs: +- Moved to daemon / removed from Tauri +- 1. detect_device_kind() — Added DeviceKind::from_id_and_name() to skill-data/src/device.rs with the ID-prefix logic (cortex:, usb:, cgx:, etc.) that was missing from the existing from_name(). Removed the dead function + 250 diff --git a/changes/releases/0.0.114.md b/changes/releases/0.0.114.md new file mode 100644 index 000000000..434f40b9d --- /dev/null +++ b/changes/releases/0.0.114.md @@ -0,0 +1,11 @@ +## [0.0.114] — 2026-04-11 + +### Features + +- migrated to nomic-ai/nomic-embed-text-v1.5 model for text and image embeddings +- cleanup +- Task 8 — Removed mirror state (~150 lines) +- Task 9 — Removed device upsert helpers (~90 lines) +- Task 10 — Removed Cortex WS state (~55 lines) +- Task 11 — Refactored settings persistence +- Task 12 — Removed calibration profile local cache diff --git a/changes/releases/0.0.115.md b/changes/releases/0.0.115.md new file mode 100644 index 000000000..0fb7ec008 --- /dev/null +++ b/changes/releases/0.0.115.md @@ -0,0 +1,5 @@ +## [0.0.115] — 2026-04-11 + +### Features + +- fixed a bug with the labels, libomp, cpu/gpu backend choice for llm diff --git a/changes/releases/0.0.116.md b/changes/releases/0.0.116.md new file mode 100644 index 000000000..2f0603b4f --- /dev/null +++ b/changes/releases/0.0.116.md @@ -0,0 +1,5 @@ +## [0.0.116] — 2026-04-11 + +### Features + +- Minor updates and improvements diff --git a/changes/releases/0.0.117.md b/changes/releases/0.0.117.md new file mode 100644 index 000000000..8fe8468c2 --- /dev/null +++ b/changes/releases/0.0.117.md @@ -0,0 +1,25 @@ +## [0.0.117] — 2026-04-13 + +### Features + +- neuroskill +- fixed cargo clippy +- fixed svelte errors +- updated main app for neuroskill and neuroloop +- Neuroloop TUI: +- Daemon: +- Neuroloop provider registration: +- No Tauri changes needed — the desktop UI already handles tags natively with collapsible thinking blocks. +- fixed neuroskill + added japanese +- fixed neuroskill +- updated neuroloop +- updated iroh +- updated neuroskill +- tests +- updated cli +- updated faq +- refactored faq and help +- updated disconnect +- added submodules +- fixed tests +- biome fixes diff --git a/changes/releases/0.0.118.md b/changes/releases/0.0.118.md new file mode 100644 index 000000000..f92f840e2 --- /dev/null +++ b/changes/releases/0.0.118.md @@ -0,0 +1,98 @@ +## [0.0.118] — 2026-04-14 + +### Features + +- updated neuroloop +- neuroloop 0.1.1 +- added new logo +- updated neuroloop and neuroskill +- added korean and a11y + fixed i18n +- merged PRs +- deps(cargo): bump the cargo-all group across 1 directory with 8 updates +- deps(npm): bump the npm-all group with 11 updates +- ndarray optimization +- updated embeddings and hnsw +- zuna-rs 0.0.4 — zero-cost model init (no random waste) +- fixed labels + +### Compare Window Performance + +- **SQLite timestamp index** — Added `CREATE INDEX idx_embeddings_timestamp ON embeddings(timestamp)` to all day databases. Range queries on the `embeddings` table went from full table scan to indexed lookup (40ms → 5ms for COUNT on 146K rows). +- **Directory date filtering** (`dirs_for_range`) — Compare endpoints now only open SQLite files for days that overlap the requested time range, skipping irrelevant directories (e.g., 363 of 365 day dirs skipped for a 2-day comparison). +- **Single JSON parse per row** — Replaced 49-50 `json_extract()` calls per SQLite row with `SELECT metrics_json` + one serde deserialize in Rust. For `get_session_metrics` on 60K rows: 1.03s → 82ms (12x faster). +- **SQL-level timeseries downsampling** — `get_session_timeseries` now uses `ROW_NUMBER() OVER ... WHERE rn % step = 1` to return ~800 evenly-spaced rows instead of all 145K. Transfer + serde reduced from ~3s to ~40ms. +- **Read-only SQLite connections** — Analysis endpoints now use `SQLITE_OPEN_READ_ONLY` to eliminate write-lock contention when 6+ parallel queries run during compare. +- **Two-phase progressive loading** — Compare UI shows metrics first (fast SQL query), then loads sleep and timeseries in the background. Results appear in <100ms instead of waiting 3+ seconds. +- **CSS `.spin` animation** — Added missing `@keyframes spin` and `.spin` class to `app.css`. Loading spinners across the app were using the class but it was never defined. + +### UMAP / Brain Nebula Fixes + +- **Poll handler: "running" status** — Fixed `pollUmapJob` to treat `status: "running"` same as `"pending"` (was silently calling `finishUmap()` and aborting the poll loop). +- **HTTP timeout for UMAP** — UMAP computation request now uses 5-minute timeout instead of the default 10 seconds (which killed the request mid-computation). +- **Empty embedding detection** — `umap_compute_inner` now filters out 0-length embedding BLOBs, reports `total_a`/`total_b` (total epochs found) vs `n_a`/`n_b` (epochs with actual embeddings), and returns a `reason` field explaining why UMAP can't compute. +- **User-facing error UI** — When UMAP returns empty (no embeddings), shows a warning panel with explanation ("Brain Nebula requires EEG embedding vectors…") and a Retry button. +- **UMAP progress bar** — Added estimated progress bar during UMAP computation based on elapsed/estimated time, with countdown. Shows epoch-level progress when available from the daemon. + +### Embedding Pipeline & Batch Reembed + +- **Encoder failure logging** — Upgraded encoder load failure from `warn!` to `error!` with actionable message. Added per-epoch `metrics_only_streak` counter that logs first failure, every 100th consecutive failure, and recovery. +- **Slow encode warning** — Logs when a single epoch encode exceeds 2 seconds. +- **Batch reembed implementation** — `POST /v1/models/trigger-reembed` was a stub returning "queued". Now fully implemented: loads encoder, scans all day directories, reads raw EEG CSVs, encodes epochs, writes embeddings back to SQLite. +- **`estimate_reembed` fixed** — Now correctly counts epochs with empty `eeg_embedding` BLOBs (was always returning 0). +- **Relative CSV timestamp fix** — Batch reembed now handles both absolute Unix timestamps and relative device-internal timestamps in CSV files by using the session start time from the filename. +- **Transaction batching** — SQLite writes during reembed use batched transactions (50 epochs per COMMIT) for write performance. +- **Progress events** — Emits `reembed-progress` WebSocket events every batch with status, total, done, failed, and current day. + +### GPU-Accelerated Encoding + +- **zuna-rs 0.1.0** — Upgraded from 0.0.4. Brings parallel processing (rayon), CPU preprocessing pipeline, and `wgpu-f16` feature for half-precision GPU inference. +- **GPU encoder (Metal/Vulkan)** — New `embed-zuna-gpu` and `embed-zuna-gpu-f16` features. `ZunaGpuState` loads ZUNA on wgpu (Metal on macOS). Encoding: ~120ms/epoch on GPU vs 5,600ms on CPU (47x speedup). +- **f16 half-precision** — Default GPU precision is f16 (`embed-zuna-gpu-f16`), faster than f32 on Apple Silicon. Configurable via `gpu_precision` setting. +- **Encoder fallback chain** — `load_encoder_public` tries GPU f16 → GPU f32 → CPU (NdArray + Accelerate BLAS). + +### Idle Background Reembed + +- **`idle_reembed` background loop** — New `idle_reembed.rs` module spawned at daemon startup. Monitors device connection state every 10 seconds. When device is disconnected for `idle_reembed_delay_secs` (default 30 minutes), starts background embedding of unprocessed epochs. +- **Backpressure on device connect** — `idle_reembed_cancel` AtomicBool flag set to `true` in `DeviceEvent::Connected` handler. Batch reembed checks cancel flag between every batch and pauses immediately, emitting a "paused" status event. +- **Configurable settings** — `ReembedConfig` extended with `idle_reembed_enabled`, `idle_reembed_delay_secs`, `idle_reembed_gpu`, `gpu_precision`, `idle_reembed_throttle_ms`. All exposed via `GET/POST /v1/settings/reembed-config`. + +### Embedding Status UI + +- **Timeline picker indicator** — Small colored dot next to session status: green (90%+ embedded), amber + percentage (partial), red (0% embedded). +- **UMAP section indicator** — "Calculate UMAP" button disabled when <5 embeddings available, with explanation text. Shows reembed progress bar when background embedding is running. +- **Reembed progress in compare** — Live progress bar with rate estimate (e.g., "6.3 embeddings/sec") and ETA (e.g., "~11h 32m remaining") from WebSocket `reembed-progress` events. +- **EEG Model settings** — Added GPU Precision dropdown (f16/f32), Background Embedding toggle, and Idle Delay selector (5min/15min/30min/1hr) to the EEG Model tab. + +### Search Improvements + +- **Interactive search implemented** — `POST /v1/search/eeg` now handles `interactive_search` requests: embeds query text → searches label HNSW → finds nearby EEG epochs → discovers temporal neighbor labels. Returns full graph with nodes and edges. +- **Text search routing fixed** — `search_labels_by_text` was routed to the wrong endpoint (`/v1/search/eeg` which returned `{ results: [] }`). Now correctly routes to `/v1/labels/search` with response unwrapping. +- **EEG search: empty embedding filter** — `read_embeddings_in_range` now filters `WHERE length(eeg_embedding) >= 4` at the SQL level, avoiding loading and searching 65K+ empty BLOBs. Search time for 66K epoch range: >60s → 3.6s. +- **SSE streaming search** — New `POST /v1/search/eeg/stream` endpoint sends results as Server-Sent Events as they're found. Frontend reads via `ReadableStream` with fallback to full-body read. Results appear progressively in the UI. +- **Cancel search** — `cancelSearch()` exported from invoke-proxy aborts the SSE fetch. Cancel button in search UI now actually stops the streaming request (not just sets a flag). +- **Channel command timeouts** — Search uses 2-minute timeout, chat uses 5-minute timeout (was 10 seconds for both). +- **Null-safety** — Added comprehensive null guards for `nb.labels`, `nb.distance`, `nb.neighbors`, `q.neighbors`, `x.text`, `x.context` across search page and search-types. Prevents crashes when daemon returns entries with null/undefined fields. +- **Streaming performance** — `$derived` computations (`searchAnalysis`, `temporalHeatmap`, `filtered`) skip during active streaming to avoid O(n^2) recomputation on every incoming result. + +### Screenshot Image Serving + +- **Static file serving** — Daemon now serves screenshot images at `/screenshots/{filename}` and `/screenshots/{date}/{filename}`. Infers the `YYYYMMDD` subdirectory from the first 8 characters of the filename. +- **Auth required** — Screenshot routes go through the auth middleware. `` tags use `?token=` query parameter for authentication. Token loaded from daemon bootstrap at page mount. + +### Search Tab UX + +- **Active tab styling** — Search mode tabs now have clear visual distinction: inactive tabs are dimmed (45% opacity), active tab has bold text, accent-colored background tint, and a solid 2px bottom border. + +### Crash Fix + +- **muda ZeroWidth panic** — Patched `muda 0.17.2` locally to replace `.unwrap()` with safe fallbacks in `to_png()` (macOS icon.rs). Prevents crash when macOS event loop processes a zero-width icon image during window creation. + +### i18n + +- Added embedding status translations (`compare.embeddingsReady`, `compare.embeddingsPartial`, `compare.embeddingsNone`, `compare.embeddingsProcessing`) to all 9 locales (en, de, es, fr, he, ja, ko, uk, zh). +- Added GPU/idle reembed translations (`model.gpuPrecision`, `model.gpuPrecisionDesc`, `model.gpuPrecisionF16`, `model.gpuPrecisionF32`, `model.idleReembed`, `model.idleReembedDesc`, `model.idleDelay`) to all 9 locales. + +### Tests + +- **compare-analysis-e2e.test.ts** — 18 end-to-end tests against live daemon covering metrics, timeseries, sleep, UMAP, parallel requests, and invoke-proxy routing. +- **search-null-safety.spec.ts** — Playwright test for EEG/text/interactive search with null/undefined fields in results. diff --git a/changes/releases/0.0.119.md b/changes/releases/0.0.119.md new file mode 100644 index 000000000..39885a128 --- /dev/null +++ b/changes/releases/0.0.119.md @@ -0,0 +1,7 @@ +## [0.0.119] — 2026-04-14 + +### Features + +- added openblas to ci +- fixed tests limits +- missing keys diff --git a/changes/releases/0.0.120.md b/changes/releases/0.0.120.md new file mode 100644 index 000000000..456698df5 --- /dev/null +++ b/changes/releases/0.0.120.md @@ -0,0 +1,5 @@ +## [0.0.120] — 2026-04-15 + +### Features + +- updated API docs diff --git a/changes/releases/0.0.121.md b/changes/releases/0.0.121.md new file mode 100644 index 000000000..f02e28ae5 --- /dev/null +++ b/changes/releases/0.0.121.md @@ -0,0 +1,5 @@ +## [0.0.121] — 2026-04-15 + +### Features + +- update failing apple notary tool diff --git a/changes/releases/0.0.122.md b/changes/releases/0.0.122.md new file mode 100644 index 000000000..c87d6ff78 --- /dev/null +++ b/changes/releases/0.0.122.md @@ -0,0 +1,84 @@ +## [0.0.122] — 2026-04-16 + +### Features + +- Add recent/frequent command ranking to Cmd-K. Commands you use most often and most recently appear at the top in a "Recent" section, with frequency and recency boosts applied to search scoring. +- Add typo tolerance to Cmd-K search. Misspellings like "drak mode" or "calbriation" now find the right result using Damerau-Levenshtein edit distance as a fallback when fuzzy matching fails. +- Add contextual boosting to Cmd-K. Device, EEG, Calibration, and LSL commands are ranked higher when a device is connected; Devices settings are boosted when no device is connected. +- Add keyboard-driven toggles in Cmd-K. Toggleable settings (dark mode, high contrast) show an ON/OFF indicator and can be flipped with the Tab key without closing the palette. +- Add prefix-based command filtering to Cmd-K. Type `>` to show only system commands or `@` to show only settings. +- Add semantic search fallback to Cmd-K via fastembed embeddings. When fuzzy results are sparse and the query is 6+ characters, a debounced request to the daemon's `/v1/search/commands` endpoint returns semantically similar commands in a "Suggested" section. Handles natural language queries like "make text bigger" or "reduce eye strain". +- Add toggle dark/light mode command to the Cmd-K palette. + +- Add deep-link search for individual settings in the Command Palette (Cmd-K). 154 settings across all 20 tabs are now searchable by name, description, and synonyms — typing "DND", "dark mode", "GPU", "OCR", etc. instantly finds the right setting. +- Auto-generate per-locale search indexes from i18n translation files and Tab components. Indexes are built for all 9 languages (EN, DE, ES, FR, HE, JA, KO, UK, ZH) and stay in sync automatically via a Vite plugin that watches for changes during development. +- Add synonym expansion to Cmd-K fuzzy search. Common abbreviations like DND, BT, GPU, OCR, LLM, and 30+ others are expanded before matching so users can search using shorthand. +- Add Command Palette button to the title bar of every window. Clicking the command-key icon opens Cmd-K, matching the keyboard shortcut behavior. +- Selecting a setting in Cmd-K now opens the Settings window, switches to the correct tab, scrolls to the exact setting, and flashes a blue highlight to draw attention to it. +- Wire settings index generation into `npm run dev`, `npm run build`, and `npm run bump` so indexes are always up to date before commits and builds. +- Add Chat and Compare shortcuts to the Shortcuts settings tab, making all 11 global shortcuts user-configurable. The Compare shortcut (previously hardcoded to Cmd+Shift+M) is now customizable like all others. +- Tray menu now reflects user-customized shortcuts for all actions including Chat and Compare. +- The keyboard shortcuts overlay (? key) now shows all 11 global shortcuts, matching the Shortcuts settings tab and tray menu. +- Global keyboard shortcuts no longer steal keystrokes from other apps. Shortcuts are now only registered when a Skill window is focused and unregistered when all windows lose focus, so the focused app always gets the keystroke first. The "Open NeuroSkill" shortcut (Cmd+Shift+O) remains always-on so users can bring the app to the foreground from anywhere. + +- **Session sidecar `device_kind`**: JSON sidecars now include `device_kind` (e.g. "muse", "awear", "openbci") alongside `device_name` and `channel_names`. Enables reliable device identification during reembedding when a day directory contains sessions from different devices. Backward compatible — old sidecars without this field are handled gracefully. + +- **Embedding coverage dashboard** in Settings → EXG → Re-embed. Shows a color-coded coverage bar (green ≥95%, amber ≥50%, red <50%), total/embedded/missing epoch counts, and estimated time remaining based on current embed speed. +- **Per-day breakdown table** in the re-embed section. Expandable table showing each day's total epochs, embedded count, missing count, and a mini coverage bar — makes it easy to identify which days have gaps. +- **Idle reembed status indicator**. When background embedding is enabled, the UI now shows whether it's actively processing (with day + progress), or waiting for the idle timeout to elapse, instead of running silently. +- **Re-embed progress improvements**. Progress bar now shows indeterminate animation during encoder loading, percentage + ETA during processing, amber bar when paused (device reconnected), red bar with error detail on failure. +- **EEG embedding coverage in Search page**. Corpus stats banner and empty-state panel now show epoch embedding coverage (e.g. "12,400/14,200 (87%)") with color coding, so you know at a glance whether your data is fully searchable. + +- **Multi-modal search**: Interactive search now returns screenshots (proximity + OCR text similarity) and EEG epochs alongside text labels. New `kScreenshots`, `kLabels`, and `reachMinutes` parameters control result depth. +- **Search corpus stats**: New `GET /search/stats` endpoint and SSE streaming variant (`/search/stats/stream`) show EEG days, sessions, recording time, label counts, screenshot counts, and embedding model info. Stats appear in the search UI for all modes. +- **Daemon test mode**: `POST /v1/test/begin` pauses background work (screenshots, OCR, re-embed) for stable E2E testing. `POST /v1/test/end` resumes. Debug builds only (`#[cfg(debug_assertions)]`). +- **Daemon readiness**: `/readyz` now returns `{ok, ready, test_mode}` — `ready` is set after the TCP listener binds. `SKILL_DAEMON_TOKEN` env var allows injecting auth tokens for isolated test daemons. +- **Interactive test picker**: `npm test` shows a TUI picker (Node.js, cross-platform) with 17 suites. Arrow keys + space to toggle, enter to run. +- **Rust version guard**: `npm run tauri dev` and `npm run test:clippy` check Rust >= 1.95 and prompt `rustup update stable` if outdated. + +### Bugfixes + +- Fix Chat shortcut not being loaded from saved settings on app startup. + +- **Reembed silent failures**: Added diagnostic logging on first extract failure and first encode failure per batch. Previously all epoch failures were completely silent, making it impossible to debug why 27K+ epochs would fail with zero output. +- **CSV channel length mismatch**: The CSV parser now skips entire rows when any channel value fails to parse, preventing channels from accumulating different sample counts. Previously, individual parse failures were silently skipped per-channel, creating mismatched-length arrays that the ZUNA encoder rejected. +- **Mixed-device reembedding**: CSVs with fewer columns than the metadata expects (e.g. different device in the same day directory) now use available columns instead of skipping all rows. The column count is detected per-file from the CSV header. +- **Reconnect respects test mode**: The BLE reconnect loop now pauses when `test_mode` is active, preventing background connection attempts from interfering with E2E tests. +- **`cpu-only` feature build**: Fixed compilation errors in `skill-daemon` when built with `--no-default-features --features cpu-only`. The `#[cfg(not(feature = "llm"))]` fallback paths referenced `AppState` fields that don't exist without the `llm` feature. Replaced with static stubs. This fixes the CI coverage job. +- **Clippy 1.95**: Fixed `sort_by` → `sort_by_key(Reverse)` in skill-history, `collapsible_match` in connect_wired, `checked_div` in iroh_remote. +- **Prebuilt llama naming**: Updated download URL from `q1-metal`/`q1-vulkan` to `metal-static`/`vulkan-static` to match current release asset names. + +- **Fix macOS DMG creation stream error**: Added retry logic (up to 3 attempts) to the `appdmg` invocation in `scripts/create-macos-dmg.sh`. The `ERR_STREAM_WRITE_AFTER_END` race condition in appdmg's internal file-copy stream is now caught and retried automatically, with partial DMG cleanup between attempts. + +- **Search hang fixed**: Removed expensive `collect_search_meta` (session JSON parsing) from the search hot path. Corpus stats are now fetched separately via a dedicated endpoint. +- **Label stale count**: `count_needing_embed()` uses `SELECT COUNT(*)` instead of loading all stale rows into memory. +- **Heredoc terminators**: Fixed indented heredoc terminators (`DPLIST`, `PYEOF`) in release-mac, release-linux, and pr-build workflows that caused `syntax error: unexpected end of file`. +- **Flaky env var test**: Added `Mutex` guard to `enforce_path_integrity` tests in skill-tools to prevent parallel env var races. +- **A11y errors**: Added `aria-label` to 3 unlabeled inputs in `EegModelTab.svelte`. +- **Smoke test auth**: Added Bearer token to all HTTP requests in `test.ts` (was sending unauthenticated). Updated 20+ REST paths from old Tauri-era shortcuts to `/v1/` daemon endpoints. Score: 83/275 → 341/0. +- **E2E test isolation**: Daemon token and data E2E tests now spawn isolated daemons on random ports with temp skill dirs. Can run in parallel. +- **Pre-checkout CI step**: `free-disk-space` step inlined for workflows that run it before `actions/checkout`. +- **ESM import fix**: Removed Node 22-only `glob` import and `require()` calls from `ci.mjs` (broke on Node 20 CI runners). + +### Refactor + +- **Dead scripts removed**: Deleted `prepare-daemon-sidecar.sh` (superseded by `.js`), `adopt_anyhow.py` (unused), `check_windows_manifest.py` (ported to Node). +- **Screenshot store reuse**: Interactive search opens the screenshot store once and reuses it for both proximity and OCR searches. +- **E2E helpers**: New `src/tests/e2e-helpers.ts` with `spawnDaemon()`, `testBegin()`/`testEnd()`, `isDaemonReady()`, `freePort()` shared across all E2E test files. + +### Build + +- **CI scripts consolidated**: Replaced ~960 lines of duplicated inline YAML with a single `scripts/ci.mjs` (Node.js, 12 subcommands). Zero Python dependency for builds — all scripts are Node.js + bash. +- **Release workflows refactored**: All 3 release workflows (macOS, Linux, Windows) support `workflow_dispatch` for on-demand builds. On-demand runs upload artifacts (14-day retention) without touching GitHub Releases. +- **macOS .app bundling**: Extracted 120-line inline YAML into `scripts/assemble-macos-app.sh`. Fixed heredoc terminator bug that caused CI failures. +- **Pre-push hook**: Runs `ci.mjs self-test` when CI scripts change, and `daemon-client.test.ts` when daemon proxy files change. +- **CI dry-run**: `npm run ci:dry-run` runs the full release pipeline locally without signing/pushing. `--skip-compile` for fast iteration. +- **Clippy 1.95 fixes**: `sort_by` → `sort_by_key(Reverse)`, collapsible match arms, `checked_div`. + +### Server + +- Add `POST /v1/search/commands` endpoint for semantic command search. Accepts a query and candidate list, embeds both with nomic-embed-text-v1.5 via fastembed, and returns the top 5 results ranked by cosine similarity. + +- `GET /v1/models/estimate-reembed` response now includes `embedded`, `missing`, `coverage_pct`, `avg_embed_ms`, `eta_secs`, `per_day` (per-day breakdown array), and `idle_reembed` (background reembed status with active/idle_secs/done/total/current_day). +- `GET /v1/search/stats/stream` slow-tier stats now include `eeg_total_epochs`, `eeg_embedded_epochs`, and `eeg_missing_epochs`. +- New `IdleReembedStatus` struct in daemon state tracks background reembed progress and idle countdown, updated in real-time by the idle reembed loop. diff --git a/changes/releases/0.0.123.md b/changes/releases/0.0.123.md new file mode 100644 index 000000000..0230b8f4a --- /dev/null +++ b/changes/releases/0.0.123.md @@ -0,0 +1,214 @@ +## [0.0.123] — 2026-04-19 + +### Features + +- Add daemon calibration session HTTP routes: `POST /v1/calibration/session/start`, `POST /v1/calibration/session/cancel`, `GET /v1/calibration/session/status`. +- Add `POST /v1/calibration/record-completed` and `GET /v1/calibration/active-profile` daemon routes. +- Daemon broadcasts `calibration-tts` events with spoken text before each phase, giving the frontend a 4-second window to play TTS audio before the countdown begins. + +- Auto-install daemon as persistent OS service on first app launch. The Tauri app calls the daemon's `/service/install` endpoint after startup, registering a LaunchAgent (macOS), systemd user unit (Linux), or Windows service. The daemon now persists across app restarts and reboots. +- Add `--uninstall` CLI flag to skill-daemon for clean service removal. + +- **LLM catalog**: Added Qwen3.6 35B-A3B (MoE), MiniMax M2.7, and GLM 5.1 models to the local LLM catalog. +- **HuggingFace GGUF search**: New UI section in Settings → LLM to search HuggingFace Hub for GGUF models. Browse repos by downloads/likes, expand to see individual quant files with sizes, and add models to the catalog with one click (optionally starting download immediately). Each repo shows a collapsible README (rendered as markdown with proper table and heading styling, YAML frontmatter stripped) and a direct link to the HuggingFace page. Two new daemon endpoints: `GET /llm/catalog/search` and `GET /llm/catalog/search/files` (the latter also returns the repo README, capped at 32 KB). Fully localized (9 languages). + +- **Interactive search: AI summary with streaming.** "Generate" button in the Insights panel sends search context (labels, EEG metrics, sessions, screenshots) to the local LLM via `chat_completions_ipc` with a channel callback. Response streams token-by-token with live markdown rendering via `MarkdownRenderer`. Thinking blocks are shown in a collapsible `

` element. + +- **Interactive search: rich AI prompt with full EEG context.** The LLM prompt now includes: + - Time range and epoch count + - Labels with timestamps and similarity distances + - EEG epoch metrics: engagement, relaxation, SNR, α/β/θ band powers, heart rate, relevance score, session ID + - On-demand timeseries fetch when graph nodes lack stored metrics (computes averages from raw data) + - Session metrics with stddev, best-session flag + - Screenshot context: app name, window title, timestamp, OCR similarity + - Fallback session derivation from node session_ids when backend sessions are empty + +- **Interactive search: prompt visibility.** Collapsible "Prompt" section shown immediately when "Generate" is clicked (open by default during generation, collapses after). Shows the exact data sent to the LLM in monospace font. + +- **Interactive search: LLM auto-start.** When the LLM is not running, shows "Start LLM and try again" button that calls `start_llm_server`, waits for initialization, and retries the summary automatically. + +- **Interactive search: Continue in Chat.** "Continue in Chat →" button after the AI summary creates a new chat session named "Search: {query}", saves the prompt and response as messages, and opens the chat window. Users can ask follow-up questions about their search results. + +- **Interactive search: auto-save to chat history.** Every AI summary is automatically saved as a chat session, making all LLM conversations from search discoverable in the chat history regardless of whether the user clicks "Continue in Chat". + +- **Interactive search: insights panel.** Collapsible "Insights & Patterns" card with: + - Optimal conditions: auto-detected peak engagement hour and best app + - App × Engagement correlation: bar chart showing avg engagement per app (uses `window_title` fallback) + - Hour-of-day engagement pattern: bar chart across 24 hours + - AI summary with streaming, thinking blocks, markdown rendering, and chat integration + +- **Interactive search: bookmark/findings system.** Star button in node detail panel saves nodes to localStorage (max 50). Saved findings appear in the empty state with quick re-search and delete controls. + +- **Interactive search: graph color mode switcher.** Dropdown to recolor EEG nodes by timestamp (turbo), engagement level, SNR quality, or session (hash-based). Graph reactivity fixed with explicit dependency tracking. + +- **Interactive search: timeline scrubber.** Horizontal SVG timeline below the graph showing all timestamped nodes as color-coded clickable dots. Clicking selects the node in the detail panel. + +- **Interactive search: EEG sparkline.** "Load EEG bands ±60s" button on EEG point nodes fetches actual timeseries data and renders α (blue), β (amber), θ (green) band power chart with a red marker at the epoch timestamp. + +- **Interactive search: screenshot preview.** Inline screenshot thumbnail in the detail panel when a screenshot node is selected. + +- **Interactive search: compare mode.** Select two EEG points for side-by-side metrics comparison with green/red diff highlighting and time gap display. + +- **Interactive search: node detail panel improvements.** Moved to a separate card below the graph with colored left border, generous spacing, text-sm font sizes, responsive metrics grid, breadcrumb trail, and "More like this" / bookmark buttons. + +- **Interactive search: 3D graph enhancements.** Double-click zoom with smooth camera tween, minimap, node kind filtering, subtle grid floor, reset view button, enhanced tooltips with metrics/session/relevance. + +- Add device filter to EEG embedding search. A dropdown in the search controls lets users filter by device (e.g. MuseS-F921, AttentivU-053) or search across all devices. Backed by `GET /v1/search/devices` and `device_name` parameter on the streaming search endpoint. + +- **Per-epoch EEG metrics in AI summary prompt.** The interactive search AI summary now includes per-epoch metrics (engagement, relaxation, SNR, α/β/θ band powers, FAA, mood, TAR, meditation, cognitive load, drowsiness, heart rate) instead of just "(no EEG metrics stored)". Each 5-second epoch shows its own brain state data. + +- **On-the-fly CSV metrics fallback.** When embeddings lack `metrics_json`, the interactive search now lazily loads session `_metrics.csv` files and matches epochs by timestamp (±3s tolerance) to populate metrics from CSV data. This is transparent to the user — metrics appear without needing a backfill. + +- **Background metrics backfill from CSV.** New `POST /v1/analysis/backfill-metrics` endpoint patches `metrics_json` in the SQLite embeddings table for rows where it is NULL, matching against `_metrics.csv` by timestamp. Fires automatically on search page load (fire-and-forget). After first run, all future searches find metrics directly in the DB. + +- **Recomputation of derived EEG metrics.** When `meditation`, `cognitive_load`, `drowsiness`, or `stress_index` are null in stored `metrics_json` but band powers (α, β, θ, δ) are present, they are recomputed on-the-fly using the same formulas as the live pipeline. + +- **Shared EEG score formulas (`skill-data/eeg_scores.rs`).** New module with pure-math functions for computing derived EEG metrics from averaged band powers. + +- **Interactive search: EEG metrics on graph nodes.** Each EEG epoch node now carries its full metrics (engagement, relaxation, SNR, alpha/beta/theta, FAA, heart rate, etc.) via the `NeighborMetrics` struct, visible in the tooltip and detail panel. + +- **Interactive search: session grouping & cross-session comparison.** Every node gets a `session_id` derived from its timestamp. The results include a session summary with averaged metrics, stddev, min/max engagement/SNR, duration, and band power ratios. The best session (highest avg engagement) is auto-flagged with a ★ marker. + +- **Interactive search: cross-session trend chart.** SVG sparkline rendered above the session summary, showing engagement trend across sessions. Best session dot highlighted in green. + +- **Interactive search: relevance scoring.** Each EEG node receives a composite `relevance_score` combining text similarity (50%), temporal distance (30%), and engagement (20%). Nodes are optionally sortable by relevance in the UI and rendered larger in the 3D graph for high-relevance matches. + +- **Interactive search: SNR quality filter.** New `snrPositiveOnly` toggle (step 6) excludes EEG epochs with non-positive SNR (bad signal quality). Backed by server-side filtering before graph construction. + +- **Interactive search: device filter.** Device dropdown (step 7) in the interactive pipeline, filtering EEG epochs by device name at the SQL query level via `get_session_timeseries_filtered()`. + +- **Interactive search: date-range filter with presets.** Date-range inputs (step 8) constrain results to a time window. Quick preset buttons (24h, 7d, 30d) with a clear button. + +- **Interactive search: EEG epoch ranking.** Rank-by dropdown (step 9) sorts EEG epochs by engagement, SNR, or relaxation before selecting top-k, surfacing the most interesting epochs first. + +- **Interactive search: collapsible advanced filters.** Steps 6–9 are grouped behind an "Advanced filters" toggle to keep the default view clean. An amber dot indicator shows when any filter is active. + +- **Interactive search: performance timing & system load.** Each search returns `embed_ms`, `graph_ms`, `total_ms` timing plus CPU usage and memory stats via `sysinfo`. Displayed in a compact perf bar. + +- **Interactive search: search result caching.** LRU-style cache (8 entries, 5-minute TTL) in the daemon. Repeated searches with identical parameters return instantly without re-embedding. + +- **Interactive search: CSV export.** "Export CSV" button in the sessions summary panel downloads a CSV with all session metrics. + +- **Interactive search: search history.** Recent queries persisted in localStorage (max 10). Displayed as clickable chips in the empty state for quick re-runs. ArrowUp in empty textarea recalls the last query. + +- **Interactive search: pipeline settings persistence.** All pipeline parameters (kText, kEeg, kLabels, reachMinutes, SNR, rankBy, advanced toggle) saved to localStorage and restored on page load. + +- **Interactive search: 3D graph improvements.** + - **Double-click to zoom:** smoothly flies the camera to the selected node with easeInOutQuad tweening. + - **Minimap:** 80×80px SVG overlay in the bottom-right showing all node positions projected to 2D. + - **Node kind filtering:** toggle checkboxes to hide/show EEG, Labels, and Screenshot nodes. + - **Color mode switcher:** dropdown to recolor EEG nodes by timestamp, engagement, SNR, or session. + - **Node sizing by relevance:** higher-relevance EEG nodes render 1.0–1.3× larger. + - **Enhanced tooltips:** show relevance score, session ID, and full EEG metrics summary on hover. + - **Edge kind labels:** selected node tooltip shows connected edge types (→ text_sim, ← eeg_bridge). + - **Subtle grid floor:** transparent grid pattern at y=-15, adapts to dark/light theme. + - **Reset view button:** "⌂ Reset" button + click-empty-space to fly camera back to default position. + +- **Interactive search: node detail panel.** + - Separate card below the graph with generous spacing and readable typography. + - Colored left border matching node kind. + - Breadcrumb trail showing the full path from query → text_label → eeg_point → found_label. + - "More like this" button to re-search using the selected node's text. + - Bookmark button to save interesting nodes as findings. + - EEG sparkline: "Load EEG bands ±60s" fetches actual timeseries and renders α/β/θ band chart. + - Screenshot preview: inline thumbnail for screenshot nodes. + - Compare mode: select two EEG points for side-by-side metrics comparison with diff highlighting. + +- **Interactive search: timeline scrubber.** Horizontal SVG timeline below the graph showing all timestamped nodes as color-coded dots. Click any dot to select that node. + +- **Interactive search: insights & patterns panel.** + - Auto-computed activity-engagement correlation: groups screenshots by app and shows avg engagement per app as a bar chart. + - Hour-of-day engagement pattern: bar chart showing which hours have highest engagement. + - Optimal conditions report: identifies peak engagement time and best app. + - AI summary: sends search context to the local LLM for a natural-language analysis of patterns and recommendations. + - Bookmark/findings system: save interesting nodes to localStorage, displayed in the empty state for future reference. + +- **Interactive search: loading skeleton.** Graph-shaped SVG skeleton with animated nodes and edges replaces the simple spinner during search. + +- **Interactive search: animated session bars.** Engagement and SNR progress bars in the session summary have smooth CSS transitions on load. + +- **Interactive search: EEG epoch deduplication.** `seen_eeg_ts` HashSet prevents duplicate EEG nodes when multiple text labels have overlapping time windows. + +- **Interactive search: parallel compare_search.** Both range queries in `/search/compare` now run concurrently via `tokio::join!`. + +- **Interactive search: `get_session_timeseries_filtered()`.** New function in `skill-history` that filters timeseries by device name at the SQL level, used by the interactive search device filter. + +- Add text embedding model selection. Settings → Embeddings now shows 16 fastembed models (nomic, BGE, MiniLM, E5, MxBAI, GTE) with dimensions and descriptions. Selecting a model downloads weights and hot-swaps the ONNX runtime in the daemon. Persisted to settings and loaded on startup. Backed by `GET/POST /v1/models/text-embedding` daemon API. Unknown model codes are rejected. + +### Bugfixes + +- Fix calibration data not being recorded because `record_calibration_completed` wrote to stale local state instead of the daemon. +- Fix `list_calibration_profiles` returning default profiles instead of daemon-authoritative data. + +- Fix prebuilt llama archives missing `libmtmd` (multimodal library). The collect step in the prebuilt CI workflow now includes `libmtmd*` alongside `libllama*`/`libggml*`/`libcommon*`. +- Bump llama-cpp-4 from 0.2.45 to 0.2.46. +- Fix double daemon spawn on service install. The installer now checks `/service/status` before calling `/service/install`, and `install_launchagent` skips if the plist already exists with a matching binary path. +- Fix LaunchAgent label mismatch. Update hooks (`pre-update.cjs`, `post-update.cjs`) and uninstall script now use `com.skill.daemon` instead of stale `com.neuroskill.skill-daemon`. +- Remove stale `com.neuroskill.skill-daemon.plist` from app bundle Resources. +- Fix daemon logs written to `/tmp/` (wiped on reboot). LaunchAgent now logs to `~/Library/Logs/NeuroSkill/`. +- Add rollback binary as last-resort candidate in daemon path resolver. + +- Fix EXG reembedding: resolve GPU f16 TypeMismatch bug by using f32 GPU encoder for batch reembed, fix per-segment channel metadata for mixed-device sessions, require full 5s epoch extraction, skip channel counts with consecutive failures, and process most recent days first. +- Fix reembed progress events not reaching UI (Tauri `listen` → daemon WebSocket `onDaemonEvent`), fix `"day"` → `"date"` field in progress payload, and emit `loading_encoder`/`scanning` status immediately for responsive feedback. +- Rebuild label EEG HNSW index after manual and idle reembed so interactive search can bridge text labels to EEG epochs. +- Add 32 unit tests for reembed edge cases: mixed devices, relative timestamps, partial rows, empty files, boundary extraction, and channel count handling. + +- Fix daemon binary missing from DMG when prebuilt llama retry triggers on CI. The failed link left partial build state that cargo considered "fresh", so the retry silently skipped rebuilding the daemon. + +- **Fixed EEG metrics not appearing in search results.** The `MetricsBlob` deserializer in `skill-history/cache.rs` used `#[serde(default)]` on `f64` fields, which caused the entire JSON deserialization to fail silently when any field was `null` (e.g., `cognitive_load: null`, `drowsiness: null`). All metrics defaulted to zero, causing every epoch to show "(no EEG metrics stored)" in the AI summary prompt. Fixed by adding a `null_as_zero` custom deserializer that treats JSON `null` as `0.0` without failing the parse. + +- **Fixed search cache serving stale results after daemon restart.** Added `cache_clear()` to the search module and call it after metrics backfill completes, ensuring enriched metrics appear immediately. + +### Refactor + +- Move calibration timing loop from frontend to daemon. The daemon now drives all calibration phases (action countdowns, breaks, label submission) via a session runner with wall-clock-aligned timing. The frontend subscribes to WebSocket events for UI updates. +- Route all calibration commands through daemon HTTP endpoints. Profile CRUD, active profile selection, and calibration completion recording are now daemon-authoritative — the Tauri app is a thin proxy. +- Remove stale calibration state from Tauri `AppState`. Calibration profiles and active profile ID are no longer cached locally, preventing the save overlay from clobbering daemon-written timestamps. +- Delete `calibration_service.rs` — inline daemon HTTP calls directly in `window_cmds.rs`. +- Migrate onboarding calibration wizard to use daemon-driven sessions instead of a local timing loop. +- Remove unused `emit_calibration_event` Tauri command. + +- Make `ensure_daemon_running` non-blocking. Window now appears immediately while daemon connection is established on a background thread. +- Share a single `ureq::Agent` via `OnceLock` instead of creating one per HTTP call. + +### Build + +- Fix macOS CI smoke test checking wrong path for daemon binary (`Contents/MacOS/skill-daemon` → `Contents/MacOS/skill-daemon.app/Contents/MacOS/skill-daemon`). +- Fix Linux CI Vulkan SDK cache restoring system library paths with permission errors. Vulkan SDK is now installed via `cache-apt-pkgs-action` alongside other system dependencies. +- Fix Linux CI daemon link failure: add `cargo:rustc-link-lib=vulkan` and openblas search paths in daemon `build.rs` for prebuilt llama archives. +- Fix Linux CI Discord notification firing on manual dispatch builds. +- Remove duplicate unguarded portable tarball upload step from Linux release workflow. +- Exclude E2E test files from `vitest related` in pre-push hook. +- Add all calibration commands to `check-daemon-invokes.js` validation script. + +- Pin prebuilt llama download to specific tag (`0.2.46`) in `ci.mjs` instead of floating `latest`. +- Add explicit error and exit on daemon binary missing after prebuilt retry in release workflow. +- Add smoke-test step in release workflow: `codesign --verify`, architecture check with `file`/`lipo`. +- Add `scripts/test-daemon-e2e.sh` — 19 end-to-end tests covering fresh install, update hooks, uninstall, degraded states, edge cases, and connection reuse. + +- Add `mtmd` (multimodal) to llama-cpp-4 target-specific dependency features in skill-llm, ensuring prebuilt archives include multimodal symbols. +- Copy Frameworks directory (dynamic llama dylibs) into daemon `.app` bundle during assembly. +- Fail app bundle assembly if daemon binary is missing instead of silently continuing. +- Clean daemon build artifacts during prebuilt llama retry to force a full re-link. + +- Add `embed-zuna-gpu` (f32) feature to `embed-exg` for reliable GPU batch reembed. +- Fix all clippy warnings (dead code, large enum variant, too-many-arguments). +- Ignore Node.js DeprecationWarning in bump script warning detection. + +### UI + +- Add embedding health banner to search UI. Shows color-coded progress bars for EXG epochs, screenshots, and label EEG index when any modality is incomplete. Includes inline "Rebuild EEG index" button when the label-to-EEG bridge is empty. + +- Move Embeddings tab directly below EXG in the settings sidebar. + +### i18n + +- Added 80+ translation keys across all 9 locales (en, de, es, fr, he, ja, ko, uk, zh) for AI summary, insights, bookmarks, color modes, timeline, compare, LLM controls, and chat integration. + +- Added 60+ new translation keys across all 9 locales (en, de, es, fr, he, ja, ko, uk, zh) covering all new search features: filters, insights, timeline, compare, bookmarks, color modes, and performance stats. + +### Dependencies + +- Bump llama-cpp-4 / llama-cpp-sys-4 from 0.2.44 to 0.2.45. + +- Bump zuna-rs to 0.1.3 (fixes burn f16→f32 tensor extraction bug). diff --git a/changes/releases/0.0.124.md b/changes/releases/0.0.124.md new file mode 100644 index 000000000..e0ffa2a51 --- /dev/null +++ b/changes/releases/0.0.124.md @@ -0,0 +1,10 @@ +## [0.0.124] — 2026-04-20 + +### Features + +- fixed daemon invoke +- linter +- fixed calibration +- fixed `set default` +- better connect +- fixed connectivity diff --git a/changes/releases/0.0.125.md b/changes/releases/0.0.125.md new file mode 100644 index 000000000..a56e1fa04 --- /dev/null +++ b/changes/releases/0.0.125.md @@ -0,0 +1,5 @@ +## [0.0.125] — 2026-04-20 + +### Features + +- fixed auto connect diff --git a/changes/releases/0.0.126.md b/changes/releases/0.0.126.md new file mode 100644 index 000000000..f9e93b5da --- /dev/null +++ b/changes/releases/0.0.126.md @@ -0,0 +1,5 @@ +## [0.0.126] — 2026-04-20 + +### Features + +- daemon restart on launch diff --git a/changes/releases/0.0.127.md b/changes/releases/0.0.127.md new file mode 100644 index 000000000..557231aef --- /dev/null +++ b/changes/releases/0.0.127.md @@ -0,0 +1,22 @@ +## [0.0.127] — 2026-04-22 + +### Features + +- Add ANT Neuro eego amplifier family support via the `antneuro` crate (native USB backend, no vendor SDK required) +- Supported models: eego 8, eego 24, eego 64, eego mylab, eego sport, eego rt, eego hub +- Auto-detect channel count and sample rate from the amplifier on first data block +- Configurable cap layout via Device API settings (`antneuro_cap`: auto, 10-20, 10-10, or explicit channel count) +- Configurable sample rate via Device API settings (`antneuro_sample_rate`: 500, 512, 1000, 2048, etc.) +- Waveguard electrode name mappings for 8, 21, 24, 25, 32, and 64-channel cap configurations +- USB scanner auto-detects connected eego amplifiers every other tick +- Device images and company logo for all 7 amplifier models in the Supported Devices catalog + +### Bugfixes + +- Fix PPG and IMU sample counts not being tracked in daemon status (ppg_sample_count/imu_sample_count always stayed at 0) +- Fix `cmd_status` device block missing `firmware`, `ppg_samples`, and `imu_samples` fields (neuroskill CLI showed nulls) +- Fix `format_status_as_text` reading stale key names (`sample_count` instead of `eeg_samples`) + +### UI + +- Improved supported devices card grid: wider columns, taller images with `object-contain`, text wraps instead of truncating diff --git a/changes/releases/0.0.128.md b/changes/releases/0.0.128.md new file mode 100644 index 000000000..feef09f83 --- /dev/null +++ b/changes/releases/0.0.128.md @@ -0,0 +1,13 @@ +## [0.0.128] — 2026-04-23 + +### Features + +- llmfit +- glib +- notify +- linter +- updTed settings and engine +- muda +- fixed glib with a patch +- refactored UI +- Added unsloth/Qwen3.6-27B-GGUF diff --git a/changes/releases/0.0.129.md b/changes/releases/0.0.129.md new file mode 100644 index 000000000..a04f0c60c --- /dev/null +++ b/changes/releases/0.0.129.md @@ -0,0 +1,21 @@ +## [0.0.129] — 2026-04-24 + +### Features + +- **Grayscale display mode**: optional system-wide grayscale display that activates and deactivates in sync with Do Not Disturb. Reduces visual distraction during deep work. macOS only, default off — the default focus action remains Do Not Disturb only. + +### i18n + +- **Grayscale translations**: added `dnd.grayscale` and `dnd.grayscaleDesc` keys to all 9 locales (en, de, es, fr, he, ja, ko, uk, zh). + +### Dependencies + +- **Fix rand 0.7.3 vulnerability**: vendored `phf_generator 0.8.0` locally with `rand` bumped from 0.7 to 0.8, eliminating the vulnerable `rand 0.7.3` transitive dependency pulled in by `selectors 0.24 → phf_codegen 0.8`. +- **Remove atty**: migrated `iroh_test_client` from `structopt` (clap v2) to `clap v4` derive, dropping the unmaintained `atty` crate. +- **Dismiss stale Dependabot alerts**: dismissed alerts for `crossbeam-deque`, `crossbeam-utils`, `crossbeam-queue`, `memoffset`, and `glib` — all already at patched versions or fixed in fork. + +- **Update kittentts to 0.4.0**: bumped `kittentts` from 0.3.0 to 0.4.0 in both `skill-tts` and `src-tauri`. + +- **Update llama-cpp-4 to 0.2.50**: bumped `llama-cpp-4` and `llama-cpp-sys-4` from 0.2.47 to 0.2.50, picking up upstream llama.cpp fixes including `common_*` symbol renames and `llama-common-base` static library linking. + +- **Add grayscale crate**: added `grayscale 0.0.1` as a macOS-only dependency in `skill-data`. diff --git a/changes/releases/0.0.13.md b/changes/releases/0.0.13.md new file mode 100644 index 000000000..928279879 --- /dev/null +++ b/changes/releases/0.0.13.md @@ -0,0 +1,85 @@ +## [0.0.13] — 2026-03-10 + +### Onboarding (0.0.13) + +- **Recommended models quick setup** — onboarding now starts staged + background downloads automatically while the user proceeds through steps, + in this order: ZUNA → KittenTTS → NeuTTS → Qwen 3.5 4B (`Q4_K_M` target) +- **Persistent footer download status** — all onboarding views now show a + subtle footer line with staged model setup progress (ZUNA, Kitten, NeuTTS, + LLM), and the onboarding window size was increased to keep spacing readable + with the always-visible footer indicator + +### Dependencies (0.0.13) + +- `llama-cpp-4` `0.2.3` → `0.2.5` +- `kittentts` `0.2.2` → `0.2.4` +- `neutts` `0.0.5` → `0.0.7` + +### Bug fixes + +- **Blank main window after long idle** — after a full day in the system + tray with the window hidden, macOS can silently terminate WKWebView's + web-content process under memory pressure, leaving a blank white page + that only a full app restart could recover from + - `+layout.svelte` sets `window.__skill_loaded = true` in `onMount` as + a renderer-liveness sentinel + - New `show_and_recover_main()` Rust helper checks the sentinel on every + show via `eval()`; if the flag is absent it triggers `location.reload()` + (renderer alive but content cleared), and falls back to `navigate()` if + `eval()` itself returns `Err` (renderer process fully dead, WKWebView + needs a fresh process spawned) + - `RunEvent::Reopen` handler added — clicking the macOS Dock icon while + all windows are hidden now shows the main window and runs the same + two-layer recovery (previously a silent no-op) + +- **Update loop — first check delayed by full interval** — the background + updater slept `interval_secs` *before* the first check, so with the + default 1-hour interval the first background check fired ~61 minutes after + launch; pattern changed to check-then-sleep so the first check fires 30 + seconds after startup as intended + +- **Update loop — update silently dropped on CDN race** — when the Rust + background task emitted `update-available`, the frontend had to re-run + `check()` to obtain a downloadable `Update` object; if `check()` returned + `null` (latest.json not yet propagated to all CDN edge nodes), `available` + was wiped and `phase` reverted to `"idle"` with no user feedback; fixed + by threading the event payload as a `hint` through `checkAndDownload()` — + the known version stays visible in the UI during the re-check, and a CDN + race surfaces an actionable "Retry" error instead of a silent reset + +- **What's New — dismiss race with uninitialised version** — `appVersion` + started as the string `"…"` and was populated asynchronously via IPC; + clicking "Got it" before the call resolved stored `"…"` in + `last_seen_whats_new_version`, causing the window to reopen on every + subsequent launch; fixed by seeding `appVersion` synchronously from the + CHANGELOG version embedded at build time + +- **What's New — markdown not rendered** — changelog entries containing + `**bold**`, `` `code` `` spans, multi-line bullet continuations, and + numbered sub-lists were all rendered as plain text; replaced the + hand-rolled `parseChangelog` parser (which dropped any line not starting + with `-` plus a trailing space) and the manual `{#each sections}` template with + `MarkdownRenderer` (existing component backed by `marked` + GFM); scoped + CSS overrides inside `.wn-body` preserve the compact window style without + affecting the chat renderer + +### Build / CI (0.0.13) + +- **CI `cargo check --locked` failing on Linux** — `Cargo.lock` generated + on macOS caused the Linux CI job to fail with "cannot update the lock file + because --locked was passed"; added `cargo fetch --target + x86_64-unknown-linux-gnu` before `cargo check --locked` to resolve + platform-specific dependencies for Linux without touching the network + during the check itself + +- **Release — single notarization round trip** — the release workflow + previously issued two separate `xcrun notarytool submit --wait` calls + (one for the `.app` as a ZIP, one for the DMG), each waiting up to 20+ + minutes; consolidated to a single DMG submission — Apple's service + registers notarization tickets for all signed content inside the container, + so `xcrun stapler staple` succeeds on both the DMG and the `.app` + afterward without a second submission; the updater tarball step is + reordered to run after the DMG step so it always packages a stapled `.app` + +--- diff --git a/changes/releases/0.0.130-rc.1.md b/changes/releases/0.0.130-rc.1.md new file mode 100644 index 000000000..f1f4b4ffc --- /dev/null +++ b/changes/releases/0.0.130-rc.1.md @@ -0,0 +1,769 @@ +## [0.0.130-rc.1] — 2026-04-29 + +### Features + +- **Human vs AI activity tracking**: every edit and commit is now classified as `source: "human"` or `source: "ai"` in real-time. `AIActivityTracker` watches VSCode commands (Copilot/Codeium completions, inline chat, AI-generated commit messages) to tag subsequent edits/commits, exposes `getAIRatioForFile()` (rolling 5-minute ratio) for CodeLens + sidebar, and forwards the `source` field to the daemon for `build_events` / `ai_events` storage. + +## How it works + +The `AIActivityTracker` monitors VSCode command execution to detect AI tool usage: + +- **Inline completions** (Copilot, Codeium) — edits within 5 seconds after an AI command are tagged `source: "ai"` +- **Inline chat** (`inlineChat.start`, `copilot.interactiveEditor.*`) — subsequent edits in the same file are tagged AI +- **Commit messages** — `github.copilot.git.generateCommitMessage` marks the next commit as AI-assisted +- **Everything else** — classified as `source: "human"` + +## What's tracked + +| Signal | Classification | +|--------|---------------| +| Manual typing | `human` | +| Copilot inline suggestion accepted | `ai` | +| Copilot inline chat edits | `ai` | +| Paste from external source | `human` | +| AI-generated commit message | `ai` | +| Manually typed commit message | `human` | + +## Per-file AI ratio + +`AIActivityTracker.getAIRatioForFile(path)` returns a rolling 5-minute ratio (0.0 = all human, 1.0 = all AI) used by: +- CodeLens annotations (shows "AI-Assisted" vs focus score) +- Sidebar (Human/AI percentage display) +- Brain status command (Human/AI split) + +## Daemon integration + +The `source` field is sent on every `edit` and `git_commit` event to the daemon. The daemon stores: +- AI commits as `"git commit (ai-assisted)"` in `build_events` +- AI commits also as `ai_events` for analytics weighting +- Completion acceptances as `ai_events` with type `"suggestion_accepted"` + +## Files + +- `src/ai-tracker.ts` — Core tracker (new) +- `src/events.ts` — Wired to classify edits and commits +- `crates/skill-daemon/src/routes/settings_hooks_activity.rs` — Daemon-side storage + +- **Focus-aware code review CodeLens**: annotations at the top of each file show the developer's focus level when the file was last edited (`⚠ Low Focus (42)`, `ℹ Focus: 65/100`, `🤖 AI-Assisted (85%)`, or none for high focus). `NeuroSkill: Show Files Needing Review` command lists low-focus human-authored files. Toggle via `neuroskill.focusCodeLens`. + +## What you see + +- `⚠ Low Focus (42) — Review Recommended` — File was edited during low focus. Click to see all files needing review. +- `ℹ Focus: 65/100` — Moderate focus, informational only. +- `🤖 AI-Assisted (85%)` — Most edits were AI-generated, focus score not applicable. +- No annotation — High focus (>70) or no data yet. + +## Commands + +**NeuroSkill: Show Files Needing Review** (`Cmd+Shift+P`) +- Shows a QuickPick list of files edited during low focus (<50) that were mostly human-authored +- Sorted by focus score (lowest first) +- Select a file to open it + +## How it works + +- `FocusCodeLensProvider` queries `/brain/cognitive-load` (grouped by file) every 30 seconds +- Combines focus data with `AIActivityTracker.getAIRatioForFile()` to distinguish human vs AI code +- Files with high AI ratio (>70%) show AI label instead of focus score — AI code doesn't reflect human cognitive state + +## Settings + +`neuroskill.focusCodeLens` (default: `true`) — Toggle CodeLens annotations on/off. + +## Files + +- `src/codelens-provider.ts` — CodeLens provider (new) + +- **Smart Interruption Shield**: when `/brain/flow-state` reports `in_flow: true`, VS Code's Do Not Disturb mode auto-enables and the status bar shows `$(shield) In Flow 12m`. `NeuroSkill: Toggle Flow Shield` cycles Auto / Forced-on / Forced-off. Toggle via `neuroskill.flowShield`. + +## How it works + +- When `/brain/flow-state` reports `in_flow: true`, the Flow Shield activates +- Enables VSCode's Do Not Disturb mode (VSCode 1.90+) +- Shows `$(shield) In Flow 12m` in the status bar with elapsed time +- When flow state ends, DND is automatically disabled + +## Manual override + +**NeuroSkill: Toggle Flow Shield** (`Cmd+Shift+P`) + +Cycles through three modes: +1. **Auto** (default) — activates/deactivates based on EEG flow detection +2. **Forced on** — always active regardless of flow state +3. **Forced off** — never active + +## Settings + +`neuroskill.flowShield` (default: `true`) — Enable/disable the flow shield feature. + +## Files + +- `src/flow-shield.ts` — Flow shield implementation (new) +- `src/brain.ts` — Calls `flowShield.update()` every 30s + +- **Adaptive Break Coach**: personalized break timing based on the developer's actual EEG focus cycle (via `/brain/break-timing`), not generic Pomodoro. Status bar countdown `$(clock) Break in 8m`, max one notification per cycle, auto-resets when fatigue indicates idleness. `NeuroSkill: Take a Break` resets the timer manually. Toggle via `neuroskill.breakCoach`. + +## How it works + +- Queries `/brain/break-timing` to learn the developer's natural focus cycle length +- Shows a countdown in the status bar: `$(clock) Break in 8m` +- When the predicted focus drop is imminent (<5 min), the countdown turns visible +- When the cycle ends, shows `$(clock) Break time` and optionally notifies + +## Notifications + +- Max one notification per focus cycle +- Message: "You've been focused for 47m. Your natural cycle is 42m — take a break?" +- Buttons: "Take Break" (resets timer) or "Dismiss" + +## Timer sync + +The break coach automatically syncs with the daemon's fatigue data. If `continuous_work_mins` drops below 5 (indicating the user was idle), the session timer resets — no false break suggestions after returning from lunch. + +## Commands + +**NeuroSkill: Take a Break** (`Cmd+Shift+P`) — Manually acknowledge a break and reset the timer. + +## Settings + +`neuroskill.breakCoach` (default: `true`) — Enable/disable break coaching. + +## Files + +- `src/break-coach.ts` — Break coach implementation (new) +- `src/brain.ts` — Calls `breakCoach.refresh()` and `resetSessionIfIdle()` every 30s + +- **Struggle → AI assist bridge**: when `/brain/struggle-predict` flags a file (EEG focus + undo rate + velocity drop + time-on-file), shows an actionable notification with **Open Copilot Chat**, **Open Terminal**, and **Step Back** buttons. Debounced to one suggestion per file per 10 minutes. Toggle via `neuroskill.struggleBridge`. + +## How it works + +- Monitors `/brain/struggle-predict` (EEG focus + undo rate + velocity drop + time-on-file) +- When `struggling: true`, shows an actionable notification: + > "Stuck on auth.ts? (score: 78) Consider breaking the problem into smaller pieces." + +## Action buttons + +| Button | Action | +|--------|--------| +| **Open Copilot Chat** | Opens GitHub Copilot interactive chat (or generic chat panel) | +| **Open Terminal** | Toggles terminal for CLI debugging | +| **Step Back** | Dismiss and take a mental break | + +## Debouncing + +- Max one suggestion per file per 10 minutes +- Prevents notification fatigue while still catching genuine struggles + +## Settings + +`neuroskill.struggleBridge` (default: `true`) — Enable/disable struggle detection and AI suggestions. + +## Files + +- `src/struggle-bridge.ts` — Struggle bridge implementation (new) +- `src/brain.ts` — Calls `struggleBridge.check()` every 30s (replaces the old generic struggle notification) + +- **Personal Flow Triggers dashboard**: collapsible "Your Flow Recipe" sidebar section mining 7-day EEG + activity history — best language (`/brain/code-eeg`), peak hours (`/brain/optimal-hours`), natural cycle length (`/brain/break-timing`), top flow killer (`/brain/context-cost`). Toggle via `neuroskill.flowTriggers`. + +## What you see + +In the NeuroSkill sidebar panel, a collapsible "Your Flow Recipe" section shows: + +- **Best language** — "Focus best on Rust (82)" — from `/brain/code-eeg` +- **Peak hours** — "Peak hours: 9:00, 10:00, 14:00" — from `/brain/optimal-hours` +- **Natural cycle** — "Natural cycle: 42m" — from `/brain/break-timing` +- **Flow killer** — "Flow killer: Slack (focus 38 at switch)" — from `/brain/context-cost` + +## Data sources + +| Insight | API Endpoint | Time Range | +|---------|-------------|------------| +| Best languages | `/brain/code-eeg` | Last 7 days | +| Peak hours | `/brain/optimal-hours` | Last 7 days | +| Natural cycle | `/brain/break-timing` | Last 7 days | +| Flow killers | `/brain/context-cost` | Last 7 days | + +## Settings + +`neuroskill.flowTriggers` (default: `true`) — Show/hide the flow triggers section in the sidebar. + +## Files + +- `src/sidebar.ts` — `_fetchFlowTriggers()` and `_renderFlowTriggers()` methods + +- **Focus-scored git commits in sidebar**: collapsible "Recent Commits" section shows the last 8 commits with author marker (👤 human / 🤖 AI) and a color-coded focus badge captured at commit time via `/brain/flow-state`. AI-assisted commits show "AI" instead of a focus score. Toggle via `neuroskill.focusCommits`. + +## What you see + +In the NeuroSkill sidebar, a collapsible "Recent Commits" section shows the last 8 commits: + +``` +👤 82 fix: resolve auth race condition +👤 45 chore: update dependencies +🤖 AI refactor: extract helper functions +👤 71 feat: add user preferences +``` + +- **👤** = human-authored commit +- **🤖** = AI-assisted commit (message generated by Copilot) +- **Focus badge** = color-coded: green (>70), yellow (40-70), red (<40) +- AI commits show "AI" instead of a focus score — AI output doesn't measure human cognition + +## How it works + +- When the extension detects a git commit (SCM input box clears), it: + 1. Snapshots current EEG focus via `/brain/flow-state` + 2. Checks `AIActivityTracker.isCommitAIAssisted()` + 3. Records the commit with focus score + source label +- Commits stored in-memory (last 15), refreshed on sidebar render +- The daemon also stores commits with human/AI distinction in `build_events` + +## Settings + +`neuroskill.focusCommits` (default: `true`) — Show/hide the commits section in the sidebar. + +## Files + +- `src/sidebar.ts` — `recordCommit()`, `_renderCommits()` +- `src/extension.ts` — Wires commit detection to sidebar recording +- `src/events.ts` — `onCommit` callback with human/AI source + +- **Optimal Task Router**: monitors flow score every 30 s and, when it changes by >20 points, suggests an appropriate task type (refactoring/new features at high focus, code review at moderate, docs/routine at low). Debounced to one suggestion per 15 minutes. Toggle via `neuroskill.taskRouter`. + +## How it works + +- Monitors the flow state score every 30 seconds +- When focus changes by >20 points from the last reading, suggests an appropriate task type: + +| Focus Level | Suggestion | +|------------|------------| +| >75 | "Focus is high (85) — great time for complex work like refactoring or new features." | +| 45-75 | "Focus moderate (58) — good for code review, testing, or incremental tasks." | +| <45 | "Focus low (32) — consider documentation, routine tasks, or a break." | + +## Debouncing + +- Maximum one suggestion every 15 minutes +- No suggestion on the first reading (establishes baseline) +- No suggestion if focus stays within 20 points of the last reading + +## Settings + +`neuroskill.taskRouter` (default: `true`) — Enable/disable task routing suggestions. + +## Files + +- `src/task-router.ts` — Task router implementation (new) +- `src/brain.ts` — Calls `taskRouter.check()` every 30s + +- **Activity dashboard tab**: new Settings tab with daily summary, productivity score (0-100 composite), hourly heatmap, top files/projects, language breakdown, focus sessions, meetings, and stale file alerts. +- **Focus session replay**: click any session to expand a detailed view with file timeline, edit counts, EEG focus overlay bar, and meeting interruptions. +- **Weekly report with CSV export**: 7-day activity digest with daily breakdown chart and one-click CSV download. +- **Productivity scoring**: composite score from edit velocity + deep work time + context stability + EEG focus. +- **Stale file detection**: files edited but untouched for 7+ days. +- **Code-Brain correlation**: focus ranked by language, project, and individual files — shows which code drains or energizes you. +- **Activity timeline**: unified chronological feed of all events (files, builds, meetings, AI, clipboard) with EEG focus indicators. + +- **Brain awareness API**: 14 endpoints under `/v1/brain/*` — flow state, cognitive load, meeting recovery, optimal hours, fatigue check, undo struggle, daily brain report, break timing, deep work streak, task type detection, struggle prediction, interruption recovery, code-EEG correlation, and unified timeline. +- **Flow state detector**: real-time check if user is in deep focus (high focus + low switches + sustained editing). +- **Task type detection**: auto-classify coding/debugging/reviewing/refactoring/testing from activity patterns. +- **Struggle prediction**: fuse undo rate + velocity drop + focus decline to predict when user is stuck. Includes actionable suggestion. +- **Interruption recovery measurement**: measure actual focus recovery time per interruption source (Slack avg 12min, Zoom avg 23min, etc.). +- **Fatigue monitor**: 15-minute background check broadcasts `fatigue-alert` and sends OS notification when focus declines. +- **Daily brain report**: 6pm OS notification with morning/afternoon/evening brain summary. +- **Weekly digest notification**: Monday 9am OS notification with weekly summary. ISO week dedup prevents re-fire on daemon restart. +- **Break timing optimizer**: detect natural focus cycle length from 5-minute EEG buckets. +- **Deep work streak**: gamified consecutive days with 60+ minutes of deep work. + +- **Developer insights endpoint**: `POST /v1/brain/developer-insights` returns 7 actionable EEG-fused insights in one call. +- **Test failure by focus level**: correlates build/test exit codes with EEG focus (high/mid/low) — shows how focus level predicts test outcomes. +- **Hourly productivity**: churn volume + undo rate + avg EEG focus by hour of day — identifies peak and trough hours. +- **Context switch recovery**: EEG focus level at each editor/terminal/panel transition — measures the cognitive cost of switching. +- **AI tool impact on focus**: per-app (Claude, Pi) focus delta vs baseline — quantifies whether AI tools help or hurt flow state. +- **Focus by language**: avg EEG focus + undo rate per programming language — identifies which languages are cognitively easiest/hardest. +- **Dev loop efficiency by hour**: build/test cycle time + pass rate by time of day — shows when edit-test loops are fastest. +- **Tool focus impact**: avg EEG focus per command category (docker, git, deploy, etc.) — reveals which tools correlate with lowest focus. +- **EEG timeseries table**: periodic JSON snapshots of all brain metrics (every 5s during recording). Extensible — new metrics without schema changes. Any event correlatable by timestamp join. +- **EEG timeseries worker**: background thread writes full band powers to `eeg_timeseries` every 5s when an EEG session is active. +- **`POST /v1/brain/eeg-at`**: get EEG metrics at a specific timestamp (nearest sample). +- **`POST /v1/brain/eeg-range`**: get EEG time-series in a range for charts and correlation analysis. +- **Window focus tracking**: `window_focus` events sent when VS Code gains/loses focus. EEG not attributed to coding when VS Code is in background. +- **Live EEG attachment**: `latest_bands` focus/mood injected at event storage time for terminal commands, zone switches, and conversations. + +- **`mlx-e2e` test suite**: new suite in `scripts/test-all.sh` running UMAP and FFT e2e tests with MLX features. Auto-skips on non-macOS. Available via `npm run test:mlx-e2e`. + +- **Conversations table**: stores all AI coding assistant messages (Claude, Pi) with app, role (user/assistant/tool), text, cwd, timestamp, session ID, EEG focus/mood. +- **FTS5 full-text search**: SQLite virtual table for instant keyword search across all conversation text. `POST /v1/brain/search-conversations {"query":"JWT","mode":"fts"}`. +- **Fuzzy search**: LIKE-based substring matching for partial queries. `{"query":"rate limit","mode":"fuzzy"}`. +- **Structured search**: filter by app, role, time range. `{"mode":"structured","app":"claude","role":"user","since":...}`. +- **Semantic search**: user prompts embedded via fastembed (nomic-embed-text-v1.5, local, no API credits) and stored in HNSW label index. Searchable by meaning, not just keywords. +- **Generic embedding store**: `embeddings` table decoupled from specific data tables. Multi-model support — can re-embed with different models, store multiple vectors per item. Source tracking (source_type, source_id). +- **Code context HNSW index**: separate `code_context_index.hnsw` file for code-specific semantic search, keeping EEG label searches uncontaminated. +- **Conversation events**: VS Code extension sends `conversation_message` events to daemon for each Claude/Pi message. User prompts get embedded; assistant responses and tool calls get FTS-indexed only (saves compute). +- **Session tracking**: messages grouped by JSONL filename (session ID). Timestamps from app's own data (ISO 8601 UTC), not extension read time. +- **Embedding settings**: `neuroskill.embedding.maxInputLength` (default 1000 chars) and `neuroskill.embedding.enableConversations` (default true) in VS Code settings. + +- **Meeting detection**: automatically detect Zoom, Teams, Slack, Google Meet, FaceTime, Discord, and Webex meetings from window titles. Track start/end times in `meeting_events` table. +- **Browser tab extraction**: extract page titles from Chrome, Safari, Firefox, Edge, Brave, Arc, Opera, Vivaldi, Chromium. Stored in `browser_title` column on `active_windows`. +- **Clipboard monitoring**: opt-in macOS clipboard change tracking (metadata only — content never stored). Records source app, content type, and size. Includes Automation permission check and settings UI. +- **Multi-monitor awareness**: track windows on secondary monitors across macOS (AppleScript), Linux (wmctrl), Windows (EnumWindows). Dynamic primary screen resolution detection. +- **Undo frequency tracking**: detect undo/redo from file edit diffs via reversal heuristic. `undo_estimate` per 5-second chunk, `undo_count` per file interaction. + +- **Design tokens** (`webview-ui/src/lib/tokens.css`): 30+ CSS custom properties organized into core palette (5 colors), surfaces (4 levels), backgrounds (14 translucent tints), borders (3 colors), semantic aliases (7), and typography (3). Zero inline `rgba()` in App.svelte. +- **Reusable Svelte components** (`webview-ui/src/lib/`): + - `Card` — wrapper with title, info button, info panel, header-right slot, variant borders (warn/danger/info) + - `MetricRow` — label + value pair with variant colors (warn/accent/dim/good/bad) + - `Chevron` — collapsible section with chevron toggle, count badge, slot content + - `ProgressBar` — horizontal bar with value/max, 5 color variants, optional label + - `Gauge` — circular SVG ring with animated fill, value, label + - `Badge` — text badge with 7 variants (default/good/warn/bad/blue/live/score-circle/si) + - `Callout` — alert box with 3 variants (warn/danger/info) +- **Component index** (`webview-ui/src/lib/index.ts`): barrel export for all components. +- **Timestamp compliance**: 25 automated tests (`src/test-timestamps.ts`) verifying: + - No `toLocaleTimeString`/`toLocaleDateString` in data layer (activity-tracker, events, brain, config, vt-parser) + - `toLocaleTimeString` used in UI layer (App.svelte) for display + - `Date.now()` returns UTC milliseconds + - ISO 8601 strings parsed to UTC millis + - No hardcoded timezone offsets in data layer + - All stored timestamps are UTC; local conversion only at UI boundary + +- **Dev loop detection**: identifies edit-build-test cycles from terminal command history. Groups consecutive runs of the same build/test command into loops. +- **`dev_loops` table**: loop type, command, iteration count, pass/fail counts, avg cycle time, fastest/slowest cycle, EEG focus start/end, focus trend (rising/falling/stable). +- **`POST /v1/brain/dev-loops`**: returns detected loops for a time window with all metrics. +- **Enhanced `predict_struggle()`**: terminal failures (+8/fail, max +40) and re-running same failing command 3+ times (+15/rerun, max +30) boost struggle score. New suggestions: "You're re-running the same failing command", "Multiple failures — read the error messages." +- **Enhanced `detect_task_type()`**: terminal command categories override heuristics with higher confidence — docker/kubectl → infrastructure (0.8), deploy → deploying (0.85), git dominating → git_management (0.7), test → testing (0.85), debug → debugging (0.9). +- **Dev loops in sidebar**: command, iteration count, pass rate, avg cycle time, focus trend arrow. Failing loops get red left border. +- **Dev loops in Tauri Activity tab**: expanded view with pass rate percentage, cycle time, focus trend. +- **Loop efficiency by hour**: insight showing build/test cycle time + pass rate by time of day. + +- **Inference backend selector**: `exg_inference_device` setting expanded from `gpu | cpu` to `auto | mlx | gpu | cpu`. Default changed from `gpu` to `auto` (picks MLX on macOS, GPU elsewhere). +- **Four-button selector in EXG Settings**: Auto, MLX, GPU, CPU — each with localized label and description. Reconnect headset hint shown after change. +- **`embed-exg-mlx` feature**: new daemon Cargo feature that enables `skill-router/mlx`, `skill-eeg/mlx`, and `embed-zuna-mlx` together. + +- **fast-umap 1.6.0**: updated from 1.5.1 with MLX, PCA, and nearest-neighbor descent support. +- **MLX UMAP backend**: Apple Silicon native UMAP projection via `burn-mlx`. Runtime dispatch between MLX and GPU (wgpu) based on user preference. Auto defaults to MLX on macOS, GPU elsewhere. +- **Precision selector**: F32 / F16 precision for the GPU (wgpu) backend. MLX is F32-only (fast-umap trait constraint). Exposed in UMAP settings as chip group. +- **Backend & precision UI**: new "Compute Backend" section in UMAP settings with Auto / MLX / GPU chips and F32 / F16 precision chips. Pipeline summary badge shows active backend and precision. + +- **UMAP e2e benchmarks**: `umap_e2e_small` (200 pts), `umap_e2e_medium` (1K pts), `umap_e2e_large` (5K pts) with synthetic 32-dim EEG embeddings. Reports backend, timing, throughput (pts/sec), and separation score. + +- **gpu-fft 1.2.0**: updated from 1.1.1 with MLX FFT backend. EEG signal filtering (overlap-save convolution) uses MLX when `skill-eeg/mlx` is enabled. ~3.7x faster than wgpu at N=65536. +- **`skill-eeg/mlx` feature**: new feature gate that enables `gpu-fft/mlx` alongside `gpu-fft/wgpu`. Wired into `embed-exg-mlx` daemon feature. + +- **FFT MLX e2e**: `fft_e2e_roundtrip_256`, `fft_e2e_batch_4ch`, `fft_e2e_psd_peak_detection`, `fft_e2e_large_batch` (32 channels x 1024 samples). Validates round-trip accuracy, PSD peak detection, and batch throughput. + +- **Grayscale display mode**: optional system-wide grayscale that activates/deactivates in sync with Do Not Disturb. Reduces visual distraction during deep work. macOS only, default off. + +- **File activity in history sessions**: expanded EEG sessions show files worked on during the session with focus indicators, edit counts (+/-), language, and meeting interruption badges. +- **File activity in interactive search**: `file_activity` nodes in the 3D search graph linked to EEG epochs and text labels, showing which files were being edited during matching brain states. +- **Meeting nodes in search graph**: detected meetings appear alongside EEG results in the interactive search as amber nodes with temporal proximity edges. + +- **Git context card**: current branch (monospace), dirty/staged file count, ahead/behind indicators. Uses VS Code git extension API. +- **Session timeline card**: horizontal bar chart showing activity events by hour of day. Color-coded by volume. +- **Workspace activity card**: per-file edits, lines added/removed, focus time. Grouped by workspace folder (project), sorted by activity. Active file indicator (blue dot). Top 10 files per project. +- **Environment card**: editor/terminal/panel time split (colored stacked bar), tab count, editor groups, terminal count. Per-terminal cards with shell type, CWD, PID, shell integration status. +- **Terminal I/O sections**: collapsible with chevrons, minimized by default. Input shows timestamped commands with CWD. Output shows VT-parsed session content. +- **Terminal input card**: keystroke intensity per program (keys/min), duration, collapsible behind chevron. +- **Terminal impact card**: EEG focus delta by command category with pass rate percentage. +- **Context switch cost card**: focus level at each zone transition type with switch count. +- **Dev loops card**: edit-build-test cycles with iteration count, pass/fail rate, cycle time, focus trend (rising/falling/stable arrow). +- **Today's report card**: productivity score, morning/afternoon/evening period breakdown with focus bars and churn. +- **Energy card**: fatigue bar (inverse of focus decline), continuous work time, streak badge. +- **Struggle card**: EEG-only (hidden without recording), score 0-100, contributing factors. +- **Optimal hours card**: peak/avoid hours grid. +- **AI usage card**: acceptance rate bar, suggestion count, source breakdown. +- **Today vs yesterday card**: files and churn comparison with directional arrows. +- **Code review detection**: auto-detected when file switches > 5 and edit velocity < 1 line/min. +- **Process monitor card**: running dev servers (vite, next, webpack, cargo, node, python, docker, postgres) with ports and PIDs. +- **Info toggles**: every card has a `?` button explaining how metrics are calculated. +- **Dual-speed updates**: local data every 5s (visible), brain data every 30s. Pauses when sidebar hidden. + +- **OS-wide shell hooks**: preexec/precmd hooks for zsh, bash, fish, PowerShell. Background curl sends every command to daemon — zero delay to prompt. Self-contained scripts generated by daemon, stored in `~/.skill/shell-hooks/`. +- **Shell hook management**: `GET /v1/activity/shell-hook?shell=zsh` returns hook script, `POST /v1/activity/install-shell-hook` writes to rc file, `POST /v1/activity/uninstall-shell-hook` removes cleanly, `POST /v1/activity/shell-hook-status` health check per shell. +- **Terminal command table**: `terminal_commands` with command text, binary name, args, cwd, exit code, duration, auto-categorized (284 CLI tools across 18 categories), EEG focus at start + end for delta calculation. +- **Binary extraction**: raw binary name stored separately from args. Lazy categorization — `POST /v1/brain/recategorize-commands` reruns rules on all historical data. `POST /v1/brain/binary-stats` discovers uncategorized tools by frequency. +- **284 CLI tools recognized**: git, gh, glab, hf, docker, kubectl, helm, aws, gcloud, az, brew, pip, cargo, npm, psql, redis-cli, ollama, claude, pi, vim, tmux, htop, terraform, and 260+ more across 18 categories (build, test, run, git, docker, deploy, install, navigate, debug, network, database, ai, editor, multiplexer, monitor, env, system, other). +- **Script session recording**: `script` PTY proxy wraps shell sessions, captures all terminal I/O (including input inside interactive programs). Logs stored in `~/.skill/terminal-logs/` with auto-rotation. +- **VT100 terminal emulator**: `vt-parser.ts` replays script recordings through a virtual terminal with scrollback capture. Handles cursor movement, screen clears, alternate screen buffer. Separates input from output via prompt pattern detection. +- **App-specific JSONL parsing**: reads Claude Code (`~/.claude/projects/`) and Pi agent (`~/.pi/agent/sessions/`) conversation files directly. Extracts user prompts with real timestamps, assistant responses, and tool calls. Supports multiple parallel sessions. +- **Readline history polling**: monitors `~/.python_history`, `~/.node_repl_history`, `~/.irb_history`, `~/.psql_history` for REPL input. +- **`neuroskill terminal` CLI**: `status` (hook health per shell), `install [shell]`, `uninstall [shell]`, `commands` (recent tracked), `impact` (focus delta by category), `loops` (dev loop detection). +- **Tauri Terminal settings tab**: per-shell install/uninstall/repair buttons with health indicators (green/yellow/red dot), recent commands preview, "How it works" documentation. + +- **Validation / fatigue-research daemon backend**: foundations for calibrating the Break Coach and Focus Score against four external instruments — KSS (Karolinska Sleepiness Scale), NASA-TLX (workload), PVT (Psychomotor Vigilance Task), and an EEG-derived fatigue index per Jap et al. 2009. +- **`skill-data::validation_store`** — new SQLite store at `~/.skill/validation.sqlite` with five tables (`config`, `kss_responses`, `tlx_responses`, `pvt_runs`, `prompt_log`). Persistent config is a single-row JSON blob with serde defaults so adding a new channel later is a non-migration. New constant `VALIDATION_FILE` in `skill-constants`. +- **EEG fatigue index**: pure function `eeg_fatigue_index(bands) → Option` computing `(α + θ) / β` over the existing band-power snapshot. Guards against missing bands and zero-β. Passive; ships **on** because it costs nothing when no headset is attached. +- **Pure scheduler `decide_prompt(ctx, ...) → PromptDecision`**: respects the `respect_flow` master gate, configurable quiet hours, per-channel daily caps, runtime snoozes, and rate limits. Channel ordering: KSS first (lightest), TLX after a long task unit, PVT on a weekly cadence. Live `read_in_flow` and `read_break_coach_active` open the activity store read-only and ask `flow_state_now(300).in_flow` and `fatigue_check().fatigued` so the gates actually mean something. +- **Sane defaults — opt-in everywhere**: KSS, TLX, PVT all ship `enabled = false`. Only the passive EEG fatigue index ships on. The `respect_flow` master gate ships on. + +- **Validation & Research settings tab** (`src/lib/settings/ValidationTab.svelte`): new top-level settings tab gathering five `SettingsCard` blocks — global gates (`respect_flow`, quiet hours), KSS, NASA-TLX, PVT, and EEG fatigue index — plus a Calibration Week button and a recent-results card. + - Every toggle / numeric input PATCHes one nested field via `daemonPatch("/v1/validation/config", …)`; the daemon's recursive JSON merge keeps the rest of the config untouched. + - EEG fatigue card shows the live `(α + θ) / β` value polled every 5 s from `/v1/validation/fatigue-index`. + - Calibration Week: single button that batch-updates all four channels to higher-frequency presets (KSS 8/day, TLX after every flow block ≥ 20 min, PVT mid-week) in one PATCH. +- **PVT panel** (`src/lib/settings/PvtPanel.svelte`): full 3-minute Psychomotor Vigilance Task. Random ITIs (2–10 s), green-dot stimulus, `performance.now()` for sub-millisecond RT measurement, tracks responses + false starts. On finish computes mean RT, median RT, slowest-10% mean RT (anticipation-resistant), and lapse count (RT > 500 ms per Dinges & Powell 1985), POSTs to `/v1/validation/pvt`. State machine: intro → running → done. +- **NASA-TLX form** (`src/lib/settings/TlxForm.svelte`): six-slider modal for the raw (un-weighted) NASA-TLX. Performance scale uses inverted endpoints ("Failure" → "Perfect") per Hart 2006. POSTs to `/v1/validation/tlx` with `task_kind`, `task_duration_secs`, optional `prompt_id` echo-back. +- **Pure stats helper** (`src/lib/settings/pvt-stats.ts`): extracted `mean`, `median`, `slowest10Mean`, `lapseCount`, `computeStats` from the PVT panel so the math is unit-testable without a Svelte renderer. +- **`daemonPatch(path, body)`** in `src/lib/daemon/http.ts` — needed for the validation config endpoint. + +- **Test coverage for the validation feature across all three layers** — 72 tests total, all passing. + - **Rust unit tests** (`crates/skill-data/src/validation_store.rs`): 19 tests covering config persistence, store round-trips, the `(α + θ) / β` fatigue index (Jap et al. 2009) including zero-β and missing-band edge cases, every scheduler branch (flow respect, quiet hours, snooze blocking, KSS rate limit, TLX trigger after long task, TLX skip on short task, PVT weekly cadence, PVT silence after recent run, quiet-window-equals-end semantics), and prompt-log queries. + - **Rust HTTP integration tests** (`crates/skill-daemon/src/routes/validation.rs`): 10 tests using `tower::ServiceExt::oneshot()` against a tempdir-backed `AppState` — `GET /config` defaults, `PATCH /config` partial-update + round-trip, `POST /kss` happy path, KSS score-out-of-range → 400, TLX subscale-out-of-range → 400, `GET /should-prompt` returns `{kind: "none"}` with default config, `POST /snooze` envelope, plus three `merge_json` tests covering deep nesting. + - **Vitest — PVT statistics** (`src/tests/pvt-stats.test.ts`, 12 tests): edge cases for `mean`, `median`, `slowest10Mean` (including the n<10 fallback), `lapseCount` with the 500 ms Dinges & Powell threshold and explicit-threshold override, plus a known-fixture `computeStats` round-trip. + - **Vitest — i18n coverage** (`src/tests/validation-i18n.test.ts`, 23 tests): every VS Code l10n bundle exists and contains every user-facing key (KSS prompt + scores 1/5/9, TLX/PVT prompts, all four escape-hatch labels); every Tauri language `validation.ts` imports cleanly; English Tauri bundle covers every required key; every non-English Tauri bundle has at least the tab name and disclaimer translated. + +- **VS Code extension joins the validation prompt loop**: new `ValidationManager` (`extensions/vscode/src/validation.ts`) polls `/v1/validation/should-prompt` every 90 s and renders whichever channel the daemon decides to fire. + - **KSS** (1–9 sleepiness) — full QuickPick with Karolinska wording, plus Snooze 30m / Don't ask today / Stop these prompts escape hatches in-line. POSTs the answer to `/v1/validation/kss` echoing the daemon's `prompt_id` so the prompt log can mark it answered. + - **NASA-TLX** — fallback path: shows an information message offering to open the form in the Tauri app (deep link `neuroskill://validation/tlx`) plus the same escape hatches. + - **PVT** — weekly nudge: offers to deep-link into the Tauri PVT panel (`neuroskill://validation/pvt`) or skip a week (snoozes for 6 days so the next reminder fires on cadence). +- **Two new commands**: `NeuroSkill: Open Validation Settings…` (deep links into the Tauri preferences pane) and `NeuroSkill: Check for Validation Prompt Now` (forces a single scheduler poll — useful for opt-in onboarding). +- **`DaemonClient.patch()`**: extension's daemon client gained a PATCH method so the "Stop these prompts" escape hatch can flip `enabled = false` on the persistent config. + +- **VS Code extension**: separate repo (`NeuroSkill-com/vscode-neuroskill`) as git submodule at `extensions/vscode/`. 50+ tracked event types across editing, navigation, debugging, git, AI, terminal, clipboard, and more. +- **Sidebar webview (Svelte 5)**: full brain dashboard in the VS Code activity bar with circular flow gauge, metrics strip, daily report, energy, struggle, optimal hours, workspace activity, environment, terminal impact, context cost, and dev loops. +- **Activity bar icon**: neural network SVG icon in the VS Code sidebar; NeuroSkill logo (PNG) in the webview header. +- **Brain status bar in VS Code**: polls daemon every 30s showing flow state, fatigue, streak, task type, and struggle score. Shows "offline" when daemon unreachable. Notifications for fatigue and struggle. +- **Dual-speed sidebar updates**: local data (files, terminals, layout) refreshes every 5s when sidebar is visible; brain endpoints every 30s. Pauses when hidden. +- **Event caching**: offline-resilient `pendingEvents` array (10K cap) persisted to disk via `globalStorageUri`. Auto-flushes on reconnect. Survives VS Code restarts. Cache count shown in status bar tooltip. +- **Workspace activity tracking**: per-file edits, lines added/removed, focus time, active file indicator. Grouped by workspace folder (project), sorted by activity. Top 10 files per project. +- **Terminal command tracking**: full command text, exit code, cwd, output streaming (via `execution.read()`), shell type detection (zsh/bash/fish/powershell/cmd/node/python), focus time per terminal, PID. Expandable command output in sidebar. +- **Terminal shell integration**: captures commands via `onDidStartTerminalShellExecution` / `onDidEndTerminalShellExecution` (VS Code 1.93+). CWD tracked via `onDidChangeTerminalShellIntegration`. +- **Zone tracking**: editor/terminal/panel time split with stacked color bar (blue/green/yellow). Layout chips showing tab count, editor groups, terminal count. +- **Auto EEG labeling**: every significant VS Code event auto-inserts a searchable label into EEG recordings with smart categorization (editing, debugging, git commits, AI assistance, meetings, errors, navigation, terminal commands, zone switches). +- **Inline label embedding**: auto-labels embedded immediately via fastembed for instant searchability (no idle reembed wait). +- **Command execution tracking**: 40+ VS Code commands tracked (go-to-definition, rename, find, format, fold, git, AI, debug, layout) with semantic categorization. +- **IntelliSense acceptance detection**: multi-char single-line insertions heuristically identified as autocomplete acceptances. +- **Clipboard tracking**: clipboard content changes polled every 5s with debounce. +- **Layout snapshots**: periodic (60s) capture of editor groups, visible editors, open tabs, terminal count sent to daemon. +- **Zone switch events**: editor/terminal/panel transitions sent to daemon with EEG focus snapshot. +- **File system watcher**: selective watching of package.json, Cargo.toml, go.mod, .git/HEAD for external change detection. +- **Environment context**: one-time capture of appHost, remoteName, shell, uiKind, language. +- **Info toggles**: every sidebar card has a `?` button explaining how metrics are calculated (flow score formula, struggle signals, energy bar, optimal hours, dev loops, terminal impact, context cost). +- **Open NeuroSkill button**: launches native app from sidebar (cross-platform: `open -a` macOS, `start` Windows, `xdg-open` Linux). Also in command palette. +- **Command palette → sidebar**: `Show Brain Status`, `Today's Report`, `Am I Stuck?`, `Best Time to Code` now open sidebar and scroll to relevant section instead of showing toast notifications. + +- **Developer insights endpoint**: `POST /v1/brain/developer-insights` returns 7 actionable insights in one call. +- **Test failure by focus level**: correlates build/test exit codes with EEG focus (high/mid/low) — "your tests fail 45% more when focus is low." +- **Hourly productivity**: churn + undo rate + avg focus by hour of day — "you write 3x more bugs after 2pm." +- **Context switch recovery**: focus level at editor/terminal/panel transitions — "switching to terminal costs 4 focus points." +- **AI tool impact**: Claude/Pi focus delta vs baseline — "Claude conversations drop focus by 8 points." +- **Focus by language**: avg EEG focus + undo rate per programming language — "best focus in Rust, worst in CSS." +- **Dev loop efficiency by hour**: build/test cycle time + pass rate by time of day. +- **Tool focus impact**: avg focus when using each command category (docker, git, deploy, etc.). + +- **Widget accessibility and localization**. + +- **Analysis widgets (Optimal Hours, Daily Report, Weekly Trend)**. + +- **Biometric widgets (Heart Rate, Cognitive Load, EEG Band Power, Sleep)**. + +- **Brain Dashboard widget (medium)**. + +- **Calendar Mind State widget (large)**. + +- **Widget deep links (neuroskill:// URL scheme)**. + +- **Widget development infrastructure**. + +- **Core desktop widgets (Focus, Streak, Session, Break Timer)**. + +- **Interactive widget buttons (macOS 14+)**. + +- **Widget offline data caching**. + +- **Widget timeline reload on state changes**. + +- **zuna-rs 0.1.4**: updated from 0.1.3 with native MLX backend for EEG embedding inference. +- **`embed-zuna-mlx` feature**: new `ZunaMlxState` struct with `ZunaEncoder`. Adds `load_zuna_mlx()` and `encode_zuna_mlx()` functions matching the existing GPU/CPU variants. +- **Load priority**: when user selects Auto or MLX, encoder loading tries MLX first, then GPU f16, then GPU f32, then CPU. + +### Performance + +- **MLX vs GPU benchmarks** (Mac mini, Apple Silicon): + +| Dataset | Points | GPU (wgpu) | MLX | Speedup | +|---|---|---|---|---| +| Small | 200 | 120.9 s | 2.3 s | **51x** | +| Medium | 1,000 | 136.6 s | 7.1 s | **19x** | +| Large | 5,000 | 152.8 s | 23.8 s | **6.4x** | + +- **`open_readonly` for read-only handlers**: added `ActivityStore::open_readonly()` that skips 15+ ALTER TABLE migrations and opens in read-only mode. Switched 38+ HTTP handlers and 3 background tasks from `open` to `open_readonly`. +- **`PRAGMA busy_timeout=5000`**: added to `init_wal_pragmas` and `util::open_readonly` so all database connections (activity, labels, screenshots, EEG embeddings) wait up to 5 s on lock contention instead of failing immediately with SQLITE_BUSY. +- **Composite indexes**: added `(seen_at, file_path)` and `(seen_at, project)` indexes on `file_interactions` for queries that filter by time range and group by path or project. +- **Lock consolidation**: `productivity_score` reduced from 3 separate mutex acquisitions to 1 via internal `_q` query helpers. `weekly_digest` reduced from 12 to 4 locks. Introduced `daily_summary_q`, `context_switch_rate_q`, `get_focus_sessions_in_range_q` static methods that take `&Connection` directly. +- **`PRAGMA optimize` after pruning**: runs after hourly retention pruning to keep query planner statistics fresh. +- **Batch label lookup in interactive search**: replaced 15 per-epoch `get_labels_near` calls (each opening a new DB connection) with a single `get_labels_near_batch` call per text label, then in-memory filtering. Reduces search DB round-trips from ~28 to ~16. + +### Bugfixes + +- **Daemon-side event storage**: `git_commit`/`push`/`pull`/`checkout`/`stage`/`unstage`/`stash` events were received but never written to `build_events` (used only for EEG labels). Now persisted, with AI-assisted commits also recorded as `ai_events` (type `"ai_commit"`) and labelled `"git commit (AI)"`. +- **Completion-accepted events were dropped**: previously ignored by the daemon. Now stored as `ai_events` (type `"suggestion_accepted"`) and as edit chunks so they show up in code metrics and AI usage analytics. + +## Impact on analysis + +Brain analysis endpoints can now: +- Count human vs AI commits (`/brain/developer-insights`) +- Track AI suggestion acceptance rates (`/brain/ai-usage`) +- Include git activity in the activity timeline +- Weight human-authored code differently from AI output in focus/productivity scores + +## Files + +- `crates/skill-daemon/src/routes/settings_hooks_activity.rs` — Event handler updates + +- **Schema migration**: ALTER TABLE for existing databases missing columns added across releases. Runs before DDL to handle all legacy schemas. +- **Retention pruning**: meetings, clipboard, and secondary_windows pruned alongside file interactions during hourly maintenance. + +- **Dismiss stale Dependabot alerts**: crossbeam-deque, crossbeam-utils, crossbeam-queue, memoffset, and glib alerts dismissed (already at patched versions or fixed in fork). + +- **CORS allow-methods now includes `PATCH`**: `crates/skill-daemon/src/main.rs` was advertising only `GET, POST, PUT, DELETE, OPTIONS`, so the browser preflight blocked `PATCH /v1/validation/config` from the Tauri webview. Added `Method::PATCH` to the list. + +- **Equal padding on screenshot edges**: viewport width was 360px while the body was 320px wide, leaving an asymmetric 40px right gutter. Matched viewport to body width so left and right gutters are now identical. + +- **Deadlock in daily-report endpoint**: `daily_brain_report` held the database mutex while calling `productivity_score`, which re-acquires the same mutex. Scoped the lock to drop before the nested call. +- **Deadlock in struggle-predict endpoint**: same pattern in `predict_struggle` — held mutex while calling `get_recent_files`. +- **`overall_focus` returned `-0.0`**: always wrapped in `Some` even with no EEG data. Now returns `null` when no focus samples exist. +- **`best_period` arbitrary without EEG**: picked the first SQL result when all `avg_focus` were `None`. Now returns empty string when no EEG data to compare. +- **`weekly_avg_deep_mins` returned `-0.0`**: divided by hardcoded 7 regardless of actual days. Now divides by actual day count, returns `0.0` when empty. +- **Notification text ugly without EEG**: daily report notification showed "Best: . Score: 25. Focus: 0." — now conditionally includes only available fields. +- **Brain endpoints return HTTP 200 on errors**: all 18 brain handlers silently returned `null` with 200 OK when the activity store was offline or a query failed. Refactored to use `run_query` helper that returns 503 (db_unavailable) or 500 (task_error) with structured `ApiError` JSON. + +- **Worker thread panic recovery with auto-restart**: all 4 activity worker threads (poller, input monitor, file watcher, clipboard monitor) now wrapped in `catch_unwind` via `spawn_resilient`. On panic, the worker logs the error and restarts after 5 s with freshly cloned `AppState` and `Arc`. +- **osascript timeout**: all `osascript` calls (active window poll, secondary windows, clipboard monitor) now use a 3-second timeout via `run_osascript` helper. Previously, a hung app could block the poller thread indefinitely, silently killing all activity tracking. +- **Daily report catch-up on late startup**: if the daemon starts after 18:00 local, the daily brain report fires on the first tick instead of waiting up to 1 hour for the hourly check. +- **WAL checkpoint on shutdown**: daemon now runs `PRAGMA optimize` on the activity database during graceful shutdown, ensuring the WAL is checkpointed and the database is clean for next start. +- **Missing WAL pragmas on EEG embedding store**: `day_store.rs` opened connections without `init_wal_pragmas`, missing both WAL mode and `busy_timeout`. +- **Retention pruning for new tables**: added `prune_terminal_commands`, `prune_ai_events`, `prune_zone_switches`, `prune_layout_snapshots` — wired into the hourly maintenance cycle so these tables don't grow unbounded. +- **TOCTOU race in calibration settings**: concurrent profile CRUD could lose writes. Added `modify_settings_blocking()` helper that runs under a global mutex on tokio's blocking thread pool — serializes read-modify-write without blocking the async runtime. + +- **Duration-averaged EEG**: `FileSnapshot` now samples EEG focus/mood every 5 s (at each edit-chunk tick) and writes the average to `file_interactions` at finalize, replacing the single snapshot captured at file-switch time. Falls back to the initial snapshot if no samples were collected (`COALESCE`). +- **EEG mood/focus sample count mismatch**: both `FileSnapshot` and `build_focus_sessions` used a single counter for focus and mood samples, inflating mood averages when they arrived independently. Split into separate `focus_count` and `mood_count`. +- **EEG ghost data after disconnect**: `latest_bands` was not cleared when an EEG device disconnected, causing stale focus/mood values to persist in reports and the activity pipeline. Now cleared on all 3 disconnect paths (idle timeout, stream end, explicit disconnect). + +- **InteractiveGraph3D event listener leaks**: `pointerdown` and `dblclick` listeners on the WebGL canvas were never removed in `onDestroy`. Extracted into named refs with cleanup. +- **UmapViewer3D event listener leaks**: 4 canvas event listeners (pointermove, pointerleave, pointerdown, pointerup) added as anonymous functions were never removed on unmount. Extracted into named module-level refs with removal in `onDestroy`. +- **ActivityTab brain polling leak**: `startBrainPolling()` was called on mount but `stopBrainPolling()` was never called on destroy, leaving a 30-second interval running after leaving the tab. +- **Brain polling stopped by peer component**: `stopBrainPolling()` unconditionally killed the interval even when other components still needed it. Added reference counting so polling only stops when the last consumer unmounts. +- **Memory leak in terminal_command_end labeling**: `Box::leak` was used to format non-zero exit codes in EEG auto-labeling, permanently leaking memory for every failed command. Replaced with owned `String`. +- **Screenshot encode failures untracked**: `encode_webp` failures (disk full, I/O error) silently continued without incrementing the `capture_errors` counter. Now counted so metrics reflect actual failures. +- **401 auto-retry**: daemon HTTP client now retries once with a fresh token on 401 (stale token after daemon restart) instead of failing immediately. + +- **Path traversal in `delete_session`**: `csv_path` parameter was passed unsanitized to `fs::remove_file`. Now validates the path is within `skill_dir` via canonicalize + starts_with. +- **Timing-vulnerable token comparison**: default bearer token checks in auth middleware used `==` (variable-time). Switched to `constant_time_eq` for both in-memory and on-disk token paths. +- **Unbounded CSV memory in EXG loader**: loading a CSV with millions of rows would allocate unlimited RAM. Added a 4M row cap (~4.3 hours at 256 Hz). + +- **Timezone-wrong period classification**: `daily_brain_report`, `hourly_edit_heatmap`, and `optimal_hours` used `seen_at % 86400` (UTC hour) instead of local hour. Daily report now uses `seen_at - day_start`; heatmap and optimal-hours accept a timezone offset computed server-side. +- **UTC midnight in all clients**: search page, CLI (`cmdActivity`, `cmdBrain`), VS Code extension, and Swift widget all computed `day_start` as UTC midnight instead of local midnight. Fixed to use `Date.setHours(0,0,0,0)` (JS/TS) and `Calendar.startOfDay` (Swift). +- **Widget empty daily-report body**: `DaemonClient.fetchDailyReport()` sent an empty POST body; the endpoint requires `dayStart`. Now sends local midnight. +- **Widget UTC sleep/calendar endpoints**: `fetchSleep()` and `fetchCalendarEvents()` used `% 86400` UTC approximations. Now use `Calendar.current` for correct local time boundaries. + +### Refactor + +- **Shared `DaemonClient` for VS Code extension**: extracted the repeated `fetch` + auth-token + port-discovery + 3 s timeout pattern into one class. All features now use `client.post(path, body)`. Returns `null` on failure (never throws), with `setToken()` for refresh on reconnect. + +## Before + +Every component (brain.ts, sidebar.ts, extension.ts) independently constructed fetch calls: +```typescript +const port = await discoverDaemonPort(config); +const base = `http://${config.daemonHost}:${port}/v1`; +const headers = { "Content-Type": "application/json" }; +if (token) headers["Authorization"] = `Bearer ${token}`; +const resp = await fetch(`${base}${path}`, { method: "POST", headers, body, signal: AbortSignal.timeout(3000) }); +``` + +## After + +```typescript +const client = new DaemonClient(config, token); +const result = await client.post("/brain/flow-state", { windowSecs: 300 }); +``` + +## Benefits + +- Single place to update auth, timeout, port discovery +- All 8 new features use the shared client +- `setToken()` method for token refresh on reconnect +- Returns `null` on any failure (never throws) — all features handle gracefully + +## Files + +- `src/daemon-client.ts` — DaemonClient class (new) + +- **Sidebar disclaimer test now accepts either localiser**: existing `vscode-sidebar-disclaimer.test.ts` updated so the `vscode.l10n.t("sidebar.disclaimer")` ↔ `tr("sidebar.disclaimer")` migration doesn't break the assertion. Both look up the same key. + +- **Conversations table + FTS5**: full-text search on all AI conversation messages (user/assistant/tool). Three search modes: FTS, fuzzy, structured. +- **EEG timeseries table**: periodic JSON snapshots of all brain metrics. Extensible — new metrics without schema changes. Join-at-query-time correlation with any event. +- **Embeddings store**: generic, multi-model. User prompts embedded via fastembed (local, no API credits). Can re-embed with different models. +- **Code context HNSW index**: separate from label index for code-specific semantic search. +- **Binary extraction**: terminal commands store raw binary name + args separately. Lazy categorization — 284 CLI tools recognized, rerun anytime. +- **EEG attachment**: live focus/mood from `latest_bands` injected at event storage time. +- **EEG timeseries worker**: writes full band powers to `eeg_timeseries` every 5s during recording. + +### Build + +- **CI for the VS Code extension** (`extensions/vscode/.github/workflows/`): two workflows live in the submodule repo (`vscode-neuroskill`). + - **`ci.yml`** — runs on every PR and `main` push. `npm ci` → `tsc` build → `vsce package` → uploads the `.vsix` as a 14-day artifact. No secrets, no publish. + - **`release.yml`** — fires two ways. *Tag push* (`git push --tags` after `npm version patch`) verifies the tag matches `package.json` and publishes. *Manual dispatch* with a `patch | minor | major | x.y.z` input bumps `package.json`, commits, tags, pushes, then publishes. Both paths run `npx @vscode/vsce publish` (uses `VSCE_PAT`) and `npx ovsx publish` (uses `OVSX_PAT`), then create a GitHub release with the `.vsix` attached and the latest `## ` block from `CHANGELOG.md` as release notes. +- **Skip-on-missing-secret**: if either `VSCE_PAT` or `OVSX_PAT` is unset, the corresponding publish step emits a `::warning::` and is skipped — letting you wire up Open VSX later without breaking the workflow. +- **Releasing section in the extension README**: documents the secret setup (Azure DevOps PAT, Open VSX token), the two release flows, and the one-time namespace claims (Marketplace publisher registration + `ovsx create-namespace`). + +- **macOS: link libusb statically into `skill-daemon`**: vendored libusb via `rusb`'s `vendored` feature in `crates/skill-devices`. The `antneuro` USB driver pulls in `rusb` → `libusb1-sys`, which by default uses `pkg-config` to find a system libusb-1.0.dylib. On the macOS-26 release runner that resolved to Homebrew's `/opt/homebrew/opt/libusb/lib/libusb-1.0.0.dylib`, so end users without Homebrew hit a launch-time `dyld: Library not loaded` error. Cargo feature unification now flips `libusb1-sys/vendored`, which compiles libusb from source and links `libusb-vendored.a` into the binary — verified by 105 `_libusb_*` text symbols present in the release binary and zero libusb references in `otool -L`. + +### CLI + +- **`neuroskill activity` expanded**: 19 subcommands — bands, window, input, windows, files, top-files, top-projects, languages, sessions, meetings, clipboard, builds, heatmap, switches, summary, score, digest, stale, timeline. +- **`neuroskill brain` added**: 14 subcommands — flow, load, load-files, recovery, optimal, fatigue, struggle, report, breaks, streak, task, stuck, interruptions, correlation. +- **`neuroskill vscode` added**: auto-build and install VS Code/VSCodium/Cursor extension from source on macOS, Linux, and Windows. + +### UI + +- **EEG focus timeline heatmap**: collapsible "Focus Timeline" sidebar section renders a ~280×36 px SVG sparkline of today's EEG focus (`/brain/eeg-range`, max 120 points), color-graded green/yellow/red, with hour labels and file-name annotations at peaks/valleys (merged from `/activity/timeline`). Toggle via `neuroskill.eegHeatmap`. + +## What you see + +In the NeuroSkill sidebar, a collapsible "Focus Timeline" section shows: + +- A ~280px wide, ~36px tall SVG sparkline +- Color gradient: green (>70 focus), yellow (40-70), red (<40) +- Hour labels along the bottom (0:00, 3:00, 6:00, ...) +- File names annotated at focus peaks and valleys + +## Data sources + +| Data | API Endpoint | +|------|-------------| +| EEG time-series | `/brain/eeg-range` (today, max 120 points) | +| File context | `/activity/timeline` (today, last 200 events) | + +The heatmap merges EEG data points with the closest timeline events to show which files correspond to focus peaks and dips. + +## Settings + +`neuroskill.eegHeatmap` (default: `true`) — Show/hide the heatmap in the sidebar. + +## Files + +- `src/sidebar.ts` — `_fetchHeatmap()` and `_renderHeatmap()` methods + +- **Brain status bar**: persistent bottom bar in Tauri app showing flow state, fatigue, streak, edit velocity, and focus score. +- **Brain dashboard card**: flow/fatigue/streak + focus progress bar on the EEG dashboard alongside existing BrainStateScores. +- **Brain indicators in ActivityTab**: flow state card (green), fatigue alert card (amber), and streak card (violet) at top. + +- **Tauri Brain Insights section** in Activity tab: test failure rates by focus level, focus by language bars, AI impact delta, tool focus grid, hourly productivity chart. +- **Tauri EEG focus inline**: terminal commands and conversation messages show EEG focus score (green/yellow/red) at the moment they occurred. +- **VS Code Brain Insights card**: test failure by focus chips, focus by language bars, tool impact chips. Only shown when EEG data is available. +- **VS Code Struggle card**: hidden when no EEG recording (focus is EEG-only, not activity-based). +- **VS Code Flow gauge label**: shows "focus" with EEG, "activity" without — no false claim of brain measurement. + +- **Tauri AI Conversations section** in Activity tab: timestamped message thread with role icons (user/assistant/tool), app name, EEG focus score, scrollable. +- **VS Code Conversations card**: timestamped messages with role icons, app badge, collapsible. +- **VS Code AI Usage card**: Copilot/Codeium acceptance rate bar, suggestion count, source breakdown chips. + +- **Search mode dropdown**: replaced 4-tab bar with a styled dropdown supporting 7 search modes — Interactive, EEG, Text, Images, Code, Meetings, Brain. +- **Diamond EEG nodes**: EEG epochs rendered as faceted octahedrons (diamonds) in the 3D search graph. Meeting nodes rendered as tetrahedrons (pyramids). Visual distinction by data type. +- **New search modes**: Code (file activity + brain state), Meetings (meeting events + recovery), Brain (flow/fatigue/struggle insights). +- **Terminal commands in Cmd-K**: added 4 terminal commands to the command palette — Recent Terminal Commands, Terminal Focus Impact, Context Switch Cost, Dev Loops. Translated labels in all 9 locales. + +- **Configurable alerts**: `neuroskill.alerts.focusLow` (warn when EEG focus drops below threshold), `neuroskill.alerts.continuousWorkMins` (warn after N minutes continuous work). +- **Daily digest notification**: at configurable time (default 17:30), shows productivity score + stats. "Full Report" button opens detail panel. +- **Full report panel**: `Cmd+Shift+R` opens full-width webview with same data as sidebar. +- **Keyboard shortcuts**: `Cmd+Shift+N` (toggle sidebar), `Cmd+Shift+R` (full report), `Cmd+Shift+Alt+N` (reconnect). +- **Open NeuroSkill button**: launches native app (cross-platform). +- **Event caching**: offline-resilient `pendingEvents` array (10K cap) persisted to disk. Auto-flushes on reconnect. + +- **Spacing pass on the validation tab and modals**: every card uses `flex flex-col gap-5 p-6` instead of the old `space-y-4` (which provided no padding override). Conditional sub-settings under a channel toggle are now indented under a left border (`ml-1 border-l-2 border-border pl-5`) so the parent/child relationship reads visually. Modals padded to `p-8` with `gap-3` button rows. TLX sliders gained `mt-1` lift off the description and `uppercase tracking-wide` low/high legend labels. + +- **Design tokens** (`tokens.css`): 30+ CSS custom properties — palette, surfaces, backgrounds, borders, semantic, typography. Zero inline rgba() in sidebar. +- **7 reusable Svelte components**: Card, MetricRow, Chevron, ProgressBar, Gauge, Badge, Callout. +- **Timestamp compliance**: 25 tests verifying UTC in data layer, local conversion only in UI. + +- **Theme-aware sidebar screenshots in the README**: every screenshot now ships in two variants (`*-dark.png`, `*-light.png`) and is embedded via a `` element with `prefers-color-scheme` sources. GitHub serves the variant matching the reader's OS theme; the marketplace gets the dark fallback. Six states captured: in-flow, stuck, fatigued, low-focus / off-peak, daemon-disconnected, status-bar strip. +- **Sidebar mock library + Playwright generator**: HTML stubs in `extensions/vscode/media/preview/` render the sidebar webview offline (no daemon required). A shared `_styles.css` drives both themes via a `:root.light` override; `npm run screenshots` (Playwright) toggles `` between captures and writes 12 PNGs to `media/screenshots/`. +- **Marketplace README pipeline**: `scripts/build-marketplace-readme.mjs` rewrites every relative `media/...` path (including ``, which `vsce` doesn't touch) to absolute GitHub raw URLs at package time. The source `README.md` keeps relative paths for GitHub + offline viewing; the generated `.marketplace.readme.md` is what `vsce package --readme-path` ships. + +- **Sidebar webview now renders correctly in light themes** (`extensions/vscode/src/sidebar.ts`). Three theme-fragile spots fixed: + - `.metric-card` background switched from `--vscode-sideBar-background` (which is the same colour as the surrounding sidebar — cards collapsed into a faint border in light mode) to `--vscode-editorWidget-background`, with `--vscode-input-background` and a translucent grey as cascading fallbacks. + - `.ai-bar` track switched from `--vscode-panel-border` to `--vscode-progressBar-background` so the empty bar is visible against a white background. + - Row-divider opacity bumped from 0.08 to 0.18; added `:last-child { border-bottom: none }` on commit and AI-metric rows so the last row doesn't double up against the disclaimer footer. +- **State colours stay hard-coded** — red ring for stuck, green for flow, amber for warning. They're semantic, not chrome, and they read fine on both themes. + +### Server + +- **`POST /v1/activity/shell-command`**: receives commands from shell hooks with command text, cwd, shell type, exit code. +- **`POST /v1/brain/terminal-commands`**: query recent commands with exit codes, durations, categories, EEG correlation. +- **`POST /v1/brain/terminal-impact`**: avg EEG focus delta by command category. +- **`POST /v1/brain/binary-stats`**: usage frequency per binary for discovering uncategorized tools. +- **`POST /v1/brain/recategorize-commands`**: rerun categorization rules on all historical commands. +- **Terminal EEG auto-labels**: `"running: cargo test"` on start, `"cargo test passed"` / `"cargo test failed (exit 1)"` on end. +- **Zone switch events**: editor/terminal/panel transitions stored with EEG focus snapshot. `POST /v1/brain/context-cost` returns focus at each transition type. +- **Layout snapshots**: periodic (60s) tab/group/terminal counts from VS Code. + +- **`/v1/validation/*` HTTP endpoints** (axum): `GET /config`, `PATCH /config` (recursive JSON merge for partial updates), `POST /snooze`, `POST /disable-today`, `GET /should-prompt` (returns the daemon's prompt decision; logs the fire so the rate-limiter sees it), `POST /kss`, `POST /tlx`, `POST /pvt`, `POST /close-prompt`, `GET /results`, `GET /fatigue-index` (live `(α+θ)/β` from the latest band snapshot). +- **`AppState.validation_runtime`** (`crates/skill-daemon-state/src/state.rs`): new `Arc>` field holding ephemeral snooze/disable-today state. Resets on daemon restart by design — a crash loop shouldn't keep prompts suppressed forever. + +- **`terminal_commands` table**: shell command text, cwd, exit code, duration, auto-categorized (50+ patterns: build/test/run/git/docker/deploy/install/navigate/debug/network/other), EEG focus at start and end for delta calculation. +- **`dev_loops` table**: edit→build/test cycle tracking with iteration count, pass/fail rate, avg cycle time, focus trend (rising/falling/stable). +- **`zone_switches` table**: editor/terminal/panel transitions with EEG focus snapshot at moment of switch. +- **`layout_snapshots` table**: periodic tab/group/terminal counts from VS Code. +- **Event handler**: new match arms for `terminal_command_start`, `terminal_command_end`, `zone_switch`, `layout_snapshot` in `activity_vscode_events_impl()`. +- **Command categorizer**: `categorize_command()` with 50+ patterns across build, test, run, git, docker, deploy, install, navigate, debug, network. +- **`POST /v1/brain/terminal-impact`**: avg EEG focus delta by command category — shows how builds, tests, git, docker affect brain state. +- **`POST /v1/brain/context-cost`**: focus level at each zone transition type with switch counts. +- **`POST /v1/brain/dev-loops`**: edit-build-test cycle detection with iterations, pass rate, cycle time, focus trend. +- **`POST /v1/brain/terminal-commands`**: recent commands with exit codes, durations, categories, EEG correlation. +- **Enhanced `predict_struggle()`**: terminal failures (+8/fail, max +40) and re-running same failing command 3+ times (+15/rerun, max +30) boost struggle score. New suggestions: "You're re-running the same failing command", "Multiple failures — read the error messages". +- **Enhanced `detect_task_type()`**: terminal command categories override heuristics with higher confidence — docker/kubectl→infrastructure (0.8), deploy→deploying (0.85), git dominating→git_management (0.7), test→testing (0.85), debug→debugging (0.9). +- **Terminal EEG auto-labels**: `"running: cargo test"` on start, `"cargo test passed"` / `"cargo test failed (exit 1)"` on end, `"switched to terminal"` on zone changes. +- **AI events table**: `ai_events` SQLite table tracking suggestion shown/accepted/rejected and chat sessions with source attribution. +- **OS-wide shell hooks**: `scripts/shell-hooks/` with preexec/precmd hooks for zsh, bash, fish, PowerShell. Sends every command to daemon via background curl. No delay to prompt. +- **Shell hook daemon endpoints**: `GET /v1/activity/shell-hook?shell=zsh` returns hook script, `POST /v1/activity/install-shell-hook` writes to `~/.skill/shell-hooks/` and appends to rc file, `POST /v1/activity/uninstall-shell-hook` removes cleanly, `POST /v1/activity/shell-hook-status` health check. +- **`neuroskill terminal` CLI**: `status` (hook health per shell), `install [shell]`, `uninstall [shell]`, `commands` (recent tracked), `impact` (focus delta by category), `loops` (dev loop detection). +- **`neuroskill brain` new subactions**: `terminal-impact`, `context-cost`, `dev-loops`. +- **`neuroskill activity` new subaction**: `terminal-commands`. +- **Tauri Terminal settings tab**: per-shell install/uninstall/repair buttons, health indicators (green/yellow/red), recent commands preview, "How it works" documentation. +- **`neuroskill vscode` CLI**: auto-install extension to VS Code, VSCodium, or Cursor on macOS, Linux, and Windows. +- **Meeting nodes in search graph**: meetings appear as amber nodes in the interactive 3D search graph linked by `meeting_prox` edges. + +### i18n + +- Activity dashboard, clipboard, file history, session replay, weekly report, brain status, timeline, and code-brain correlation translations for all 9 locales. + +- Added `settings.inferenceDeviceAuto`, `settings.inferenceDeviceAutoDesc`, `settings.inferenceDeviceMlx`, `settings.inferenceDeviceMlxDesc` to all 9 locales (en, de, es, fr, he, ja, ko, uk, zh). + +- Added `umapSettings.backend`, `umapSettings.backendDesc`, `umapSettings.precision`, `umapSettings.precisionDesc` to all 9 locales (en, de, es, fr, he, ja, ko, uk, zh). + +- Added `dnd.grayscale` and `dnd.grayscaleDesc` translations to all 9 locales. + +- Search mode labels (Code, Meetings, Brain) translated in all 9 locales. +- Terminal command palette entries translated in all 9 locales. + +- **New `validation` namespace** in all 9 languages (`src/lib/i18n/{de,en,es,fr,he,ja,ko,uk,zh}/validation.ts`): ~95 keys in English (full coverage), ~55 in each other language for the user-visible strings (long-tail descriptions fall back via the runtime `t()` chain). `index.ts` barrel exports updated for every language. + +- **22 new `validation.*` keys per bundle** in all 9 languages (`bundle.l10n.{de,en,es,fr,he,ja,ko,uk,zh-cn}.json`): KSS prompt + 9 score labels with translated Karolinska wording, TLX/PVT prompts, all four escape-hatch labels, the disabled-permanently acknowledgement. +- **Two new `cmd.*` keys** for the validation commands in `package.nls.json`. + +- Added `settings.inferenceDeviceAuto`, `settings.inferenceDeviceAutoDesc`, `settings.inferenceDeviceMlx`, `settings.inferenceDeviceMlxDesc` to all 9 locales (en, de, es, fr, he, ja, ko, uk, zh). + +### Docs + +- VS Code extension design plan at `docs/vscode-extension.md`. +- `neuroskill-activity` skill documentation with 18 activity + 24 brain subcommands, terminal integration, shell hook reference, command categorization table. +- Updated `neuroskill-dnd` skill with grayscale mode. +- Updated `neuroskill/README.md` with terminal, brain awareness, and VS Code extension features. +- Updated `skills/SKILL.md` index with terminal tracking skill reference. + +- **VS Code extension README rewritten for skeptical developers**: scientific framing throughout — every derived metric (focus score, flow detector, deep-work minute, struggle predictor, optimal hours, fatigue) now ships with its formula, hyperparameters, and a "what can go wrong" line. Added an explicit *What this is, and isn't* section, a *Validation status* table tracking each metric's evidence level (production / pilot / heuristic / descriptive-only), and a *Validation roadmap* describing the planned KSS / NASA-TLX / PVT / EEG-fatigue calibration channels. Replaced the marketing pain-points table with a per-API signal taxonomy and an architecture paragraph. +- **References section with verified citations**: nine entries with DOIs cross-checked via CrossRef and `doi.org` redirects — Csikszentmihalyi 1990 (flow), Klimesch 1999 (α/θ oscillations), Lubar 1991 (θ/β attention ratio), Barry et al. 2005 (caffeine confound), Newport 2016 (deep work), Roenneberg et al. 2003 (chronotypes), Hart & Staveland 1988 + Hart 2006 (NASA-TLX), Jap et al. 2009 (EEG fatigue index). Each reference linked back to the metric it grounds. +- **Repository metadata pointed at the right repo**: discovered `extensions/vscode/` is a git submodule of `vscode-neuroskill`, not a subdirectory of the monorepo. Reverted `repository.url`, `bugs.url`, `homepage`, `qna` to the submodule's actual repo so the marketplace links resolve. + +### Dependencies + +- **burn-mlx**: added `burn-mlx` from git (`eidola-ai/burn-mlx`, branch `burn-0-20`) as optional dependency in `skill-router` and `skill-daemon`. Workspace `[patch.crates-io]` redirects all `burn-mlx` references to the git version (crates.io 0.1.2 targets burn 0.16; project uses burn 0.20). +- **macOS deployment target**: bumped from 10.15 to 14.0 in `.cargo/config.toml` for Metal 3.0 `simdgroup_matrix` intrinsics required by MLX. Still satisfies llama.cpp's `std::filesystem` (available since 10.15). +- **half 2.4**: added to `skill-router` for GPU f16 precision backend type (`CubeBackend`). + +- **Fix rand 0.7.3 vulnerability**: vendored `phf_generator 0.8.0` with rand bumped from 0.7 to 0.8, eliminating the vulnerable transitive dependency. +- **Remove atty**: migrated `iroh_test_client` from `structopt` (clap v2) to `clap v4` derive, dropping the unmaintained `atty` crate. +- **Update llama-cpp-4 to 0.2.50**: upstream llama.cpp fixes including `common_*` symbol renames. +- **Update kittentts to 0.4.1**: TTS engine update. +- **Add grayscale 0.0.1**: macOS grayscale display control for DND mode. diff --git a/changes/releases/0.0.130-rc.10.md b/changes/releases/0.0.130-rc.10.md new file mode 100644 index 000000000..f0623da0f --- /dev/null +++ b/changes/releases/0.0.130-rc.10.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.10] — 2026-05-02 + +### Features + +- fix windows ci diff --git a/changes/releases/0.0.130-rc.11.md b/changes/releases/0.0.130-rc.11.md new file mode 100644 index 000000000..48183a52a --- /dev/null +++ b/changes/releases/0.0.130-rc.11.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.11] — 2026-05-02 + +### Features + +- 1. GPU f16 SHADER_F16 panic → `catch_unwind` in worker.rs diff --git a/changes/releases/0.0.130-rc.12.md b/changes/releases/0.0.130-rc.12.md new file mode 100644 index 000000000..a251d8806 --- /dev/null +++ b/changes/releases/0.0.130-rc.12.md @@ -0,0 +1,9 @@ +## [0.0.130-rc.12] — 2026-05-03 + +### Features + +- translations +- 1. Settings tab font-size lint rule (scripts/check-settings-font-sizes.js) +- The baseline doubles as a punch list — top offenders are ActivityTab (95), ValidationTab (36), and TerminalSessionsCard (27). +- 2. Tray hint when auto-update is OFF and an update is detected +- auto-update + update RC settings diff --git a/changes/releases/0.0.130-rc.13.md b/changes/releases/0.0.130-rc.13.md new file mode 100644 index 000000000..087d190d5 --- /dev/null +++ b/changes/releases/0.0.130-rc.13.md @@ -0,0 +1,19 @@ +## [0.0.130-rc.13] — 2026-05-04 + +### Features + +- deps(npm): bump the npm-all group with 10 updates +- Bumps the npm-all group with 10 updates: +- | Package | From | To | +- Updates `@tauri-apps/api` from 2.10.1 to 2.11.0 +- Updates `@tauri-apps/plugin-opener` from 2.5.3 to 2.5.4 +- Updates `@threlte/core` from 8.5.9 to 8.5.11 +- Updates `@threlte/extras` from 9.14.9 to 9.15.1 +- Updates `bits-ui` from 2.18.0 to 2.18.1 +- Updates `marked` from 18.0.2 to 18.0.3 +- Updates `@biomejs/biome` from 2.4.13 to 2.4.14 +- Updates `@sveltejs/kit` from 2.58.0 to 2.59.0 +- Updates `@tauri-apps/cli` from 2.10.1 to 2.11.0 +- Updates `svelte-check` from 4.4.6 to 4.4.7 +- --- +- Signed-off-by: dependabot[bot] diff --git a/changes/releases/0.0.130-rc.14.md b/changes/releases/0.0.130-rc.14.md new file mode 100644 index 000000000..a5d3693ce --- /dev/null +++ b/changes/releases/0.0.130-rc.14.md @@ -0,0 +1,9 @@ +## [0.0.130-rc.14] — 2026-05-05 + +### Features + +- **ECHT (Endpoint-Corrected Hilbert Transform)**: new EEG metric measuring alpha-band rhythmicity (0–1) via a causal complex-Morlet kernel. Phase estimates remain valid at the buffer edge, where FFT-based Hilbert breaks down. Surfaced in the live dashboard, session detail view, comparison view, recordings (CSV/Parquet), and history aggregates. Reference: Schreglmann et al., *Nat. Commun.* 12:363 (2021), [doi:10.1038/s41467-020-20581-7](https://doi.org/10.1038/s41467-020-20581-7). + +### i18n + +- **ECHT translations**: added `sd.echt`, `compare.echt`, `dashboard.echt`, and `tip.echt` for all 9 supported locales (en, de, es, fr, he, ja, ko, uk, zh). diff --git a/changes/releases/0.0.130-rc.15.md b/changes/releases/0.0.130-rc.15.md new file mode 100644 index 000000000..c9d216f7d --- /dev/null +++ b/changes/releases/0.0.130-rc.15.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.15] — 2026-05-05 + +### Features + +- normalized fonts diff --git a/changes/releases/0.0.130-rc.16.md b/changes/releases/0.0.130-rc.16.md new file mode 100644 index 000000000..546162d3b --- /dev/null +++ b/changes/releases/0.0.130-rc.16.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.16] — 2026-05-05 + +### Features + +- Minor updates and improvements diff --git a/changes/releases/0.0.130-rc.17.md b/changes/releases/0.0.130-rc.17.md new file mode 100644 index 000000000..72bf2627b --- /dev/null +++ b/changes/releases/0.0.130-rc.17.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.17] — 2026-05-05 + +### Features + +- address ZUNA GPU f16 fallback failure and embedding search timestamp mismatch diff --git a/changes/releases/0.0.130-rc.18.md b/changes/releases/0.0.130-rc.18.md new file mode 100644 index 000000000..78b82e982 --- /dev/null +++ b/changes/releases/0.0.130-rc.18.md @@ -0,0 +1,23 @@ +## [0.0.130-rc.18] — 2026-05-06 + +### Performance + +- **Idle re-embed throttle default**: bumped from 10 ms to 200 ms between epochs. The previous value drove the daemon to ~100% CPU on machines without a fast GPU whenever the device was idle. A migration in `load_settings` promotes any existing `idle_reembed_throttle_ms == 10` to 200 and rewrites the file, so users who hit the bug get fixed on next launch. +- **Adaptive scanner cadence**: the auto-started device scanner stays at 5 s while devices are paired or being seen, then backs off to a 30 s tick after 5 minutes of empty scans with no paired devices. Any new discovery (or BLE/USB activity) snaps it back to fast cadence. Previously every transport — USB serial, BLE cache, Cortex, NeuroField, BrainBit, g.tec, ANT Neuro, BrainMaster — was probed forever every 5 s even on installs with no hardware. +- **Active-window poll** slowed from 1 s to 3 s. Still snappy enough for app-switch tracking; ~3× fewer wakeups for the platform window probe (Accessibility on macOS, X11/Wayland calls on Linux). +- **macOS clipboard monitor** now reads everything natively via `objc2-app-kit`: `NSPasteboard.changeCount` for the change gate, `NSPasteboard.types`/`dataForType:` for content classification and size, and `dataForType:NSPasteboardTypePNG` for clipboard image capture. Removes every `osascript` subprocess fork and the Apple Events permission prompt that used to come with it. Steady-state and copy events both run inside the daemon process. + +- **Idle re-embed throttle migration logging**: `load_settings` now emits a `tracing::info!` line when it promotes a legacy `idle_reembed_throttle_ms == 10` to 200, so support can confirm the migration ran from the daemon log without diffing the settings file. + +### UI + +- **Daemon Background Activity panel** in Settings → Settings tab: lists every recurring daemon task with a one-line description, a `Why:` explanation, interval, cost class, and a live "running"/"idle" badge plus `last ran Ns ago · took X ms · N ticks`. Subscribes to the `activity-state` WebSocket event for live updates and falls back to a 30 s safety-net `/v1/activity` poll. Users can decide which trackers to disable based on what each one is actually for and how active it currently is. + +### Server + +- **`GET /v1/activity`**: new endpoint returns a manifest of every recurring background task the daemon runs, with `name`, `does`, `why`, `interval_secs`, `cost`, `user_toggleable`, plus live state (running flag, idle countdown) and `heartbeat` (`last_tick_unix_ms`, `last_duration_ms`, `tick_count`) read from a central registry on `AppState`. So users who notice CPU usage can see — and challenge — exactly which workers are active rather than guessing. +- **Background task registry + `activity-state` event**: `AppState::record_task_heartbeat(id, duration_ms)` is called once per tick by `device-scanner`, `status-monitor`, `idle-reembed`, `active-window-poll`, `clipboard-monitor`, `tty-embedder`, `reconnect`, and `skills-sync`. Each call updates the registry and (time-throttled per task to one broadcast every 5s, so a 100ms loop wouldn't flood the bus) broadcasts an `activity-state` WebSocket event with the heartbeat payload, so connected clients update without polling. Adding a new background loop without registering its id surfaces as a static row with a zeroed heartbeat — a built-in drift signal. The `idle-reembed` heartbeat additionally fires inside the embed-progress event consumer, so the panel reflects real per-batch wall-clock time rather than the outer 10s polling cadence. + +### i18n + +- Translated all `daemonActivity.*` keys (title, intro, loading, running, idle, eventDriven, whyPrefix, costLow/Medium/High, never, lastRanSecondsAgo / MinutesAgo / HoursAgo, tickDuration, tickCount) into all 9 locales: `en`, `de`, `es`, `fr`, `he`, `ja`, `ko`, `uk`, `zh`. Strings are now idiomatic (e.g. ES "carga baja" instead of "coste bajo", JA "実行回数: {n}" instead of "{n} 回実行", FR "il y a {n} s" instead of "{n} ms écoulées") and avoid singular/plural mismatches by using register-neutral phrasings ("Cycles : {n}" rather than "{n} exécutions"). diff --git a/changes/releases/0.0.130-rc.19.md b/changes/releases/0.0.130-rc.19.md new file mode 100644 index 000000000..ca05573e8 --- /dev/null +++ b/changes/releases/0.0.130-rc.19.md @@ -0,0 +1,6 @@ +## [0.0.130-rc.19] — 2026-05-06 + +### Features + +- cargo deny +- fix tty diff --git a/changes/releases/0.0.130-rc.2.md b/changes/releases/0.0.130-rc.2.md new file mode 100644 index 000000000..79eba5fa1 --- /dev/null +++ b/changes/releases/0.0.130-rc.2.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.2] — 2026-04-29 + +### Features + +- fix win/linux diff --git a/changes/releases/0.0.130-rc.20.md b/changes/releases/0.0.130-rc.20.md new file mode 100644 index 000000000..c2420ce90 --- /dev/null +++ b/changes/releases/0.0.130-rc.20.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.20] — 2026-05-07 + +### Features + +- fix --deep signature diff --git a/changes/releases/0.0.130-rc.21.md b/changes/releases/0.0.130-rc.21.md new file mode 100644 index 000000000..d4c496f45 --- /dev/null +++ b/changes/releases/0.0.130-rc.21.md @@ -0,0 +1,19 @@ +## [0.0.130-rc.21] — 2026-05-08 + +### Features + +- deps(npm): bump the npm-all group with 10 updates (#60) +- Bumps the npm-all group with 10 updates: +- | Package | From | To | +- Updates `@tauri-apps/api` from 2.10.1 to 2.11.0 +- Updates `@tauri-apps/plugin-opener` from 2.5.3 to 2.5.4 +- Updates `@threlte/core` from 8.5.9 to 8.5.11 +- Updates `@threlte/extras` from 9.14.9 to 9.15.1 +- Updates `bits-ui` from 2.18.0 to 2.18.1 +- Updates `marked` from 18.0.2 to 18.0.3 +- Updates `@biomejs/biome` from 2.4.13 to 2.4.14 +- Updates `@sveltejs/kit` from 2.58.0 to 2.59.0 +- Updates `@tauri-apps/cli` from 2.10.1 to 2.11.0 +- Updates `svelte-check` from 4.4.6 to 4.4.7 +- --- +- Signed-off-by: dependabot[bot] diff --git a/changes/releases/0.0.130-rc.22.md b/changes/releases/0.0.130-rc.22.md new file mode 100644 index 000000000..19afc15a8 --- /dev/null +++ b/changes/releases/0.0.130-rc.22.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.22] — 2026-05-08 + +### Features + +- updated iroh diff --git a/changes/releases/0.0.130-rc.23.md b/changes/releases/0.0.130-rc.23.md new file mode 100644 index 000000000..e5b691247 --- /dev/null +++ b/changes/releases/0.0.130-rc.23.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.23] — 2026-05-09 + +### Features + +- upgraded llama.cpp and added mtp diff --git a/changes/releases/0.0.130-rc.24.md b/changes/releases/0.0.130-rc.24.md new file mode 100644 index 000000000..ac0a117f0 --- /dev/null +++ b/changes/releases/0.0.130-rc.24.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.24] — 2026-05-09 + +### Features + +- fixed biome diff --git a/changes/releases/0.0.130-rc.25.md b/changes/releases/0.0.130-rc.25.md new file mode 100644 index 000000000..64da79486 --- /dev/null +++ b/changes/releases/0.0.130-rc.25.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.25] — 2026-05-10 + +### Features + +- fixed sscache + cmake diff --git a/changes/releases/0.0.130-rc.26.md b/changes/releases/0.0.130-rc.26.md new file mode 100644 index 000000000..c3c891386 --- /dev/null +++ b/changes/releases/0.0.130-rc.26.md @@ -0,0 +1,21 @@ +## [0.0.130-rc.26] — 2026-05-16 + +### Performance + +- **Local-only `hotpath` profiling for skill-router**: added optional `hotpath = "0.16"` dep behind three opt-in features (`hotpath`, `hotpath-cpu`, `hotpath-alloc`) so the default build pulls nothing extra. Annotated the suspected UMAP hot paths (`load_embeddings_range`, `load_labels_range`, `analyze_umap_points`, `fit_umap_gpu`, `fit_umap_mlx`, `umap_compute_inner`) with `#[cfg_attr(feature = "hotpath", hotpath::measure)]` — zero-cost when the feature is off. New `crates/skill-router/examples/umap_hotpath.rs` runner uses `#[hotpath::main]` to seed 500+500 synthetic 32-dim embeddings and print a per-function timing table on exit. Run with `cargo run -p skill-router --release --example umap_hotpath --features='gpu,hotpath'` (add `hotpath-alloc` for allocation tracking; swap `gpu` for `mlx` on Apple). First run on Apple M4 Pro confirmed 99.37% of UMAP wall-clock is inside `fit_umap_gpu` (44.96s of 45.24s on 1000×32-dim points); I/O and post-processing are five orders of magnitude smaller — useful signal for where *not* to optimize. + +### Bugfixes + +- **Fix daemon WS command dispatch starvation under event load**: the `/v1/events` handler ran `socket.send` (broadcast events) and `socket.recv` (incoming commands) in the same `tokio::select!` arm-set. With a Muse @ 256 Hz producing ~300–500 frames/sec across `EegSample`/`EegBands`/`ImuSample`/`PpgSample`/`SignalQuality`, the event arm won repeatedly, `sender.send().await` kept the task busy filling the kernel TCP buffer, and incoming commands timed out client-side at 15s. The smoke test hit this on every WS command. Restructured `handle_ws`: split the socket into `(sender, receiver)` halves; sender task drains a two-channel priority queue (responses via `biased` select, then events) with per-command dispatch spawned so slow handlers (`umap`, `sessions`) can't block the loop. High-rate event types are gated behind a per-connection subscribed set (default: none) — clients opt in via `{command:"subscribe",events:["EegSample",...]}` or `events:["*"]`; the neuroskill UI (`src/lib/daemon/ws.ts`) and neuroloop CLI auto-subscribe to the types they consume. Two regression tests (`ws_command_responds_under_event_flood`, `ws_filters_high_rate_events_by_default`) lock in the priority-queue invariant + default-filter behavior. + +- **Fix `interactive_search` hang on empty query**: with `query=""` the SQL `text LIKE '%' || '' || '%'` matched every label in `labels.sqlite`, then looped `search_embeddings_in_range(±10 minutes)` across every daily DB — 30s+ before the test harness gave up. The daemon now short-circuits to `{"ok":false,"error":"empty query"}` when the query is empty or whitespace-only. + +- **Fix smoke-test port discovery latching onto VS Code / dev-tool ports**: `test.ts`'s mDNS-fallback used `pgrep -if 'skill'`, which matched any process whose command line contained the substring "skill" — including VS Code Helpers running in `/Users/Shared/skill/...` workspaces. Once a wrong port was picked, `testWs()`'s bare-WebSocket handshake accepted it (VS Code, Vite HMR, etc. all accept WS upgrades), and every command then timed out at 15s, burning the entire 180s smoke budget. Tightened the pgrep regex to `(^|/)skill-daemon($|\s)|target/(debug|release)/skill($|\s)`, swapped `testWs()` to use the daemon's `DaemonStarted` welcome envelope as a protocol discriminator, and moved auth-token loading ahead of discovery so the probe can authenticate. + +### LLM + +- **MTP (Multi-Token Prediction) speculative decoding**: wired the upstream MTP API from `llama-cpp-4` 0.2.56 into the text-only generation path. When the active model is catalog-flagged `mtp: true` (e.g. the `froggeric/Qwen3.6-27B-MTP-GGUF` family) and the user sets `mtp_draft_count > 0`, the actor builds the target context with `with_n_rs_seq` so partial KV rollback works on hybrid/recurrent models (Qwen3.6 M-RoPE), runs a one-shot draft-context smoke check at load time (downgrades to the standard path on failure), and per-request constructs a `LlamaContextType::Mtp` draft context plus `MtpSession` to drive the full `draft → verify-batch → match-prefix → KV-rollback → accept` loop. Acceptance rate is logged per request. Vision (mtmd) requests stay on the non-MTP path. Verified end-to-end against `Qwen3.6-27B-IQ2_M-mtp.gguf` on Apple M4 Pro (3/3 drafts accepted on a short greedy prompt) via the new `tests/llm_mtp_e2e.rs` integration test, which skips gracefully when no MTP-capable GGUF is cached. + +### Dependencies + +- **Update llama-cpp-4 to 0.2.56**: bumped `llama-cpp-4` and `llama-cpp-sys-4` from 0.2.54 to 0.2.56, picking up upstream llama.cpp `64b38b561` (May 2026) which now ships MTP support natively (PR ggml-org/llama.cpp#22673). Breaking changes in the fork: the in-tree MTP patch is gone, so the `mtp` Cargo feature, `LlamaContext::set_mtp`, and `LlamaModelParams::with_override_arch` no longer exist. Dropped `"mtp"` from the metal/vulkan dependency feature lists in `skill-llm/Cargo.toml` and removed the dangling `llm-mtp` workspace feature (no downstream consumer). The new upstream API (`LlamaContextType::Mtp`, `with_ctx_type`, `with_n_rs_seq`, `llama_cpp_4::mtp::MtpSession`) is wired separately in the MTP speculative-decoding feature. diff --git a/changes/releases/0.0.130-rc.27.md b/changes/releases/0.0.130-rc.27.md new file mode 100644 index 000000000..dc39fe104 --- /dev/null +++ b/changes/releases/0.0.130-rc.27.md @@ -0,0 +1,9 @@ +## [0.0.130-rc.27] — 2026-05-17 + +### Build + +- **Fix release retry: cargo failures inside `run_cmd` were silently ignored**: `release-mac.yml`, `release-linux.yml`, and `release-windows.yml` call `run_cmd` via `if ! run_cmd; then`, which inhibits `set -e` inside the function body. A failing `cargo build -p skill-daemon` (e.g. link error against stale prebuilt llama libs) would silently continue to the next `cargo build`, the function would return 0, and the prebuilt→source-build fallback would never fire — leaving the assemble/package step to fail later with a confusing "missing daemon binary" error. Added explicit `|| return $?` after each cargo invocation so failures propagate regardless of bash's `set -e` inhibition rules. + +### Dependencies + +- **Bump llama-cpp-4 to 0.2.57**: bumped `llama-cpp-4` and `llama-cpp-sys-4` from 0.2.56 to 0.2.57, picking up the Windows MSVC bindgen fix (the `LLAMA_CONTEXT_TYPE_*` constants are `i32` on MSVC but the `LlamaContextType` enum is `#[repr(u32)]`, which broke the Windows release build). Pinned `LLAMA_PREBUILT_TAG` in `scripts/ci.mjs` to `v0.2.57` so the prebuilt llama libs ship the same MTP symbols (`mtp_session_new`, `mtp_session_draft`, etc.) the crate now expects — the previous `0.2.46` pin caused undefined-symbol link failures for `skill-daemon` on macOS and Linux after the 0.2.56 MTP upgrade. diff --git a/changes/releases/0.0.130-rc.28.md b/changes/releases/0.0.130-rc.28.md new file mode 100644 index 000000000..616bac909 --- /dev/null +++ b/changes/releases/0.0.130-rc.28.md @@ -0,0 +1,11 @@ +## [0.0.130-rc.28] — 2026-05-19 + +### Features + +- **Label index benchmark validation**: add side-by-side HNSW and TurboQuant label search benchmarks with top-result agreement, top-k overlap, and cosine-distance delta checks so users can verify result proximity before switching backends. + +- **TurboQuant label index backend**: add TurboQuant as an alternative label search backend alongside HNSW, while keeping HNSW as the default and maintaining both indexes during rebuilds and incremental label inserts. + +### UI + +- **Configurable label search backend**: add Settings controls for choosing between HNSW and TurboQuant, showing index counts, rebuilding indexes, and persisting the selected backend. diff --git a/changes/releases/0.0.130-rc.29.md b/changes/releases/0.0.130-rc.29.md new file mode 100644 index 000000000..a9ffa611c --- /dev/null +++ b/changes/releases/0.0.130-rc.29.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.29] — 2026-05-19 + +### Features + +- Minor updates and improvements diff --git a/changes/releases/0.0.130-rc.3.md b/changes/releases/0.0.130-rc.3.md new file mode 100644 index 000000000..c2f9fb6bc --- /dev/null +++ b/changes/releases/0.0.130-rc.3.md @@ -0,0 +1,6 @@ +## [0.0.130-rc.3] — 2026-04-29 + +### Features + +- fixed issues with the metrics, keychain access (3 -> 1 + on demand), windows CI +- umap e2e diff --git a/changes/releases/0.0.130-rc.30.md b/changes/releases/0.0.130-rc.30.md new file mode 100644 index 000000000..c35e83a02 --- /dev/null +++ b/changes/releases/0.0.130-rc.30.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.30] — 2026-05-19 + +### Features + +- turboquant index diff --git a/changes/releases/0.0.130-rc.31.md b/changes/releases/0.0.130-rc.31.md new file mode 100644 index 000000000..27f5f2910 --- /dev/null +++ b/changes/releases/0.0.130-rc.31.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.31] — 2026-05-20 + +### Features + +- llama-cpp-rs@0.3.0 diff --git a/changes/releases/0.0.130-rc.4.md b/changes/releases/0.0.130-rc.4.md new file mode 100644 index 000000000..5e46fef90 --- /dev/null +++ b/changes/releases/0.0.130-rc.4.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.4] — 2026-04-29 + +### Features + +- Minor updates and improvements diff --git a/changes/releases/0.0.130-rc.5.md b/changes/releases/0.0.130-rc.5.md new file mode 100644 index 000000000..b092198b9 --- /dev/null +++ b/changes/releases/0.0.130-rc.5.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.5] — 2026-04-30 + +### Features + +- fix vulkan cache on windows ci diff --git a/changes/releases/0.0.130-rc.6.md b/changes/releases/0.0.130-rc.6.md new file mode 100644 index 000000000..9769dc363 --- /dev/null +++ b/changes/releases/0.0.130-rc.6.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.6] — 2026-04-30 + +### Features + +- Minor updates and improvements diff --git a/changes/releases/0.0.130-rc.7.md b/changes/releases/0.0.130-rc.7.md new file mode 100644 index 000000000..9b8e30de8 --- /dev/null +++ b/changes/releases/0.0.130-rc.7.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.7] — 2026-05-02 + +### Features + +- Replace per-app AppleScript window tracking (which triggered a macOS TCC Automation dialog for every new foreground app) with native Accessibility API (AXUIElement) + CoreGraphics calls that require only a single one-time "Accessibility" permission for NeuroSkill. diff --git a/changes/releases/0.0.130-rc.8.md b/changes/releases/0.0.130-rc.8.md new file mode 100644 index 000000000..710ed7351 --- /dev/null +++ b/changes/releases/0.0.130-rc.8.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.8] — 2026-05-02 + +### Features + +- Minor updates and improvements diff --git a/changes/releases/0.0.130-rc.9.md b/changes/releases/0.0.130-rc.9.md new file mode 100644 index 000000000..2e6109e14 --- /dev/null +++ b/changes/releases/0.0.130-rc.9.md @@ -0,0 +1,5 @@ +## [0.0.130-rc.9] — 2026-05-02 + +### Features + +- Minor updates and improvements diff --git a/changes/releases/0.0.131-rc.10.md b/changes/releases/0.0.131-rc.10.md new file mode 100644 index 000000000..213e0a675 --- /dev/null +++ b/changes/releases/0.0.131-rc.10.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.10] — 2026-07-23 + +### Features + +- disable widgekit diff --git a/changes/releases/0.0.131-rc.11.md b/changes/releases/0.0.131-rc.11.md new file mode 100644 index 000000000..4a6c7a057 --- /dev/null +++ b/changes/releases/0.0.131-rc.11.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.11] — 2026-07-23 + +### Features + +- Minor updates and improvements diff --git a/changes/releases/0.0.131-rc.12.md b/changes/releases/0.0.131-rc.12.md new file mode 100644 index 000000000..583281a71 --- /dev/null +++ b/changes/releases/0.0.131-rc.12.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.12] — 2026-07-24 + +### Features + +- update deps diff --git a/changes/releases/0.0.131-rc.13.md b/changes/releases/0.0.131-rc.13.md new file mode 100644 index 000000000..2e2dc858d --- /dev/null +++ b/changes/releases/0.0.131-rc.13.md @@ -0,0 +1,6 @@ +## [0.0.131-rc.13] — 2026-07-24 + +### Features + +- fixed CI +- simplified CI diff --git a/changes/releases/0.0.131-rc.14.md b/changes/releases/0.0.131-rc.14.md new file mode 100644 index 000000000..4a53d2046 --- /dev/null +++ b/changes/releases/0.0.131-rc.14.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.14] — 2026-07-24 + +### Features + +- updated CI diff --git a/changes/releases/0.0.131-rc.15.md b/changes/releases/0.0.131-rc.15.md new file mode 100644 index 000000000..1b45f4d0a --- /dev/null +++ b/changes/releases/0.0.131-rc.15.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.15] — 2026-07-24 + +### Features + +- rlx core models diff --git a/changes/releases/0.0.131-rc.16.md b/changes/releases/0.0.131-rc.16.md new file mode 100644 index 000000000..7c5f98f8c --- /dev/null +++ b/changes/releases/0.0.131-rc.16.md @@ -0,0 +1,13 @@ +## [0.0.131-rc.16] — 2026-07-24 + +### Features + +- updates skill daemono# Please enter the commit message for your changes. Lines starting +- Gate LLM e2e tests on llm-rlx so default cargo test skips them. +- Feature unification from skill-daemon was enabling the llm marker without a backend, which made pre-push fail. +- Allow GPL for new rlx TTS crates and ignore protobuf advisory. +- rlx-tiny-tts pulled onnx/protobuf back into the graph; extend cargo-deny exceptions so pre-push stays green. +- Fix release CI: patch remaining crates.io rlx models and drop Windows wgpu. +- rc.15 failed on Mac/Linux from registry rlx-qwen3 vs git rlx-flow, and on Windows from wgpu-hal/windows-rs 0.62 vs gpu-allocator on 0.61. Patch the leftover model crates and keep the Windows umbrella on CUDA until gpu-allocator aligns. +- Simplify release CI setup and pin rlx git patches. +- Fold Linux Vulkan/apt into a shared release-setup action, drop sccache from CI, scope daemon features per OS, and keep Cargo.lock on GitHub rlx main. diff --git a/changes/releases/0.0.131-rc.17.md b/changes/releases/0.0.131-rc.17.md new file mode 100644 index 000000000..95b702df0 --- /dev/null +++ b/changes/releases/0.0.131-rc.17.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.17] — 2026-07-24 + +### Features + +- Minor updates and improvements diff --git a/changes/releases/0.0.131-rc.18.md b/changes/releases/0.0.131-rc.18.md new file mode 100644 index 000000000..fe2c1e520 --- /dev/null +++ b/changes/releases/0.0.131-rc.18.md @@ -0,0 +1,86 @@ +## [0.0.131-rc.18] — 2026-07-28 + +### Bugfixes + +- **Chat cancel handler shadowing**: Fix recursive symbol shadowing so chat tool cancel calls the daemon client correctly. +- **Tokens copy action typing**: Update Tokens tab Copy behavior to use typed token access and `common.copy` label wiring. + +### Build + +- **Workspace cleanup**: Remove duplicate `Cargo.toml` workspace members (`skill-daemon`, `skill-daemon-common`). + +- **Cache freshness automation**: Add a weekly GitHub Action to refresh HF download counts and open a PR; add CI validation that the committed cache covers the current catalog. + +- **Desktop CSP hardening**: Replace `csp: null` with a localhost/daemon-aware Content-Security-Policy in `tauri.conf.json`. + +- **VERSION as release source of truth**: Add repo-root `VERSION` and make bump/tag/release/CI/packaging flows read it, then sync `package.json`, `tauri.conf.json`, and `Cargo.toml` from that value. +- **Shared compile-product pipeline**: Introduce `scripts/compile-product.mjs` to build daemon (OS umbrella) -> tty -> app (`custom-protocol`) and use it across release workflows, dry-run, daemon-sidecar prep, and tauri-build. +- **compile-product hardening**: Tighten target-specific tty gating, strict OS-feature mapping, `CARGO_TARGET_DIR` plus `cargo metadata` staging, daemon-only `--timings`, fail-closed local daemon builds, and Linux `skill-tty` packaging. +- **Product build follow-ups**: Include `product` in umbrellas, report `VERSION` from daemon, ship CUDA+wgpu with runtime fallback (CUDA -> wgpu -> CPU) on Linux/Windows, enforce macOS tty hard-fail, and improve Linux daemon path discovery. + +### CLI + +- **Typed daemon client checks**: Migrate tokens, chat rename/cancel-tool, and device prefer/forget/retry/status call sites off `daemonInvoke`; fix `tokens.ts` ACL wire types to snake_case; add `npm run check:typed-daemon-clients`. + +### UI + +- **Primary app shell navigation**: Add a Live / Find / Ask / History destinations bar across main feature windows while keeping Add Label in the main titlebar. +- **Settings information architecture**: Group tabs by Signal, Intelligence, Capture & Privacy, Automation, and App, with a collapsible Advanced section and Devices as the default landing tab. +- **Live dashboard view modes**: Add Waveform / Physiology / State switching with progressive disclosure based on connected modality support. +- **Onboarding flow simplification**: Reduce core first-run path to connect -> fit -> tray -> done, require research-use acknowledgement, and move calibration/models/permissions to optional setup from Done. +- **Getting Started deep links**: Make checklist items open Devices, Calibration, Goals, Downloads, Search, and API targets directly. +- **Find UX updates**: Add example chips, prioritize Interactive query + Search action, and move pipeline/filters under Advanced with titlebar source labeling. +- **Command palette IA**: Add a Primary section for Live / Find / Ask / History and separate Add Label from Browse Labels actions. +- **Labels discoverability alignment**: Standardize Browse Labels naming for window title and `labels.openLabels`; change History toggle wording to "Show labels"; localize titlebar Help/Reload labels. +- **Shell discoverability improvements**: Show accelerator hints for Find / Ask / History and include shortcuts in tooltips. +- **Shell interaction fix**: Render primary nav inside `#main-content` so it is not blocked by the draggable titlebar region. +- **Chart accessibility**: Add hatch patterns plus active scheme colors on band-power tiles so differentiation does not rely on color alone. +- **Window-title consistency**: Open labels window as "Browse Labels" from Rust side and align `labels.title` text. + +### LLM + +- **HF downloads cache for model ranking**: Add `scripts/update-hf-downloads-cache.mjs` to crawl Hub download counts for every catalog repo plus top mlx-community models, writing both `src-tauri/hf_downloads_cache.json` and `src/lib/generated/hf-downloads-cache.json`. +- **Family sort by popularity**: Sort the LLM family picker by Hub downloads descending by default and show `↓ N` download hints in the dropdown. +- **MLX search UX**: Add an MLX tab in HF search (also sorted by downloads) and support importing an entire mlx-community snapshot via "Add pack". + +- **Mistral VL runner support**: Promote `mistral3`/`mistral4` out of unimplemented paths in `rlx-models`, wire `MistralRunner` as `LmRunner`, add `rlx-mistral-vl` for Pixtral ViT/projector on Metal/CUDA/CPU, and route mistral+mmproj via `auto_runner_with_mmproj`. +- **NeuroSkill loader integration**: Add `try_mistral_vl_runner` in `load_with_mmproj` and document Ministral as supported. + +- **MLX community model path**: Wire huggingface.co/mlx-community packs through `rlx-models` (`MlxLoader` / `Qwen3Runner::from_mlx_packed`). +- **Catalog entries for MLX Qwen3**: Add Qwen3 0.6B / 1.7B / 4B 4-bit entries and snapshot download handling for `tags: ["mlx"]`. +- **Discovery + loader behavior**: Surface safetensors snapshot directories (not GGUF-only) in local discovery and prefer the MLX device when loading compatible packs. +- **Feature-gated rollout**: Keep discovery and `from_mlx_packed` behind `llm-model-discovery` until the `rlx-models` pin is bumped. + +- **Model download integrity checks**: SHA-256 verify HF LFS blobs before promoting `.incomplete` to final blob, re-verify existing blobs, and delete mismatched files. + +- **Feature-flag cleanup**: Remove stale `llm-vulkan` / `llm-metal` references from scripts, docs, CONTRIBUTING, skill-llm README, and i18n copy in favor of OS umbrellas (`apple`, `linux`, `windows`). + +- **VL mmproj runner wiring**: In `rlx-models`, add multimodal support via `GemmaRunner.mmproj` and `LmRunner` multimodal handling, add combined `Qwen3VlRunner` and `LfmVlRunner`, and make `auto_runner_with_mmproj` attach mmproj for supported families while erroring clearly for unsupported families instead of silently ignoring mmproj. +- **Qwen3 VL promotion**: Promote `qwen3vl*` out of unimplemented paths. +- **Loader routing in app**: Update NeuroSkill `load_with_mmproj` to prefer Qwen3-VL / LFM-VL / Gemma+mmproj before plain Qwen3 and return explicit errors when mmproj is set on non-multimodal runners. + +- **Windows KittenTTS enablement**: Enable `rlx-kittentts` on Windows (CUDA + GPU features) and activate `tts_kitten_active` across desktop OSes so the default voice engine is no longer a no-op. + +### Server + +- **Daemon routes extraction**: Move iroh HTTP routes into `skill-daemon-routes` as the first non-stub extracted module, and document remaining extraction blockers in the crate and `docs/architecture.md`. + +- **Tool safety defaults**: Default `require_bash_edit` to `true`, expand bash denylist coverage (`curl|bash`, interpreters), and require approval for secret home paths (`.ssh`, `.aws`, `.env`, `auth.token`) and Windows system directories. +- **`web_fetch` SSRF guardrails**: Refuse loopback, private, link-local, and cloud-metadata URLs. +- **LAN bind safety**: Refuse non-loopback `SKILL_DAEMON_ADDR` unless `SKILL_DAEMON_ALLOW_LAN=1`, warn on `0.0.0.0` WebSocket host usage, and document CORS plus loopback defaults. + +- **Tool-safety fail-closed behavior**: Register native approval hooks in `skill-daemon` and deny bash-edit execution when no hook is present instead of executing unmodified scripts. + +### i18n + +- **Locale completion for new IA strings**: Translate shell, settings-group, live-view, onboarding, Find, and command-palette additions across de, es, fr, he, ja, ko, uk, zh and replace auto-synced English fallbacks. + +### Docs + +- **LLM and hooks ownership docs**: Rewrite `docs/LLM.md` and `docs/HOOKS.md` for RLX-in-daemon ownership, correct the `docs/AI.md` llama.cpp claim, and align Node prerequisite docs to >=20 (`docs/DEVELOPMENT.md`, `package.json` engines). + +- **Release/CI documentation alignment**: Document release-setup composite options (including optional `build-frontend`), standardize rust-cache guidance as the only compile cache, clarify PR vs release CI behavior, and refresh skill-daemon feature docs. + +### Dependencies + +- **Windows Vulkan SDK removal**: Drop Vulkan SDK requirements from Windows PR clippy/release paths where no longer needed. diff --git a/changes/releases/0.0.131-rc.19.md b/changes/releases/0.0.131-rc.19.md new file mode 100644 index 000000000..8f9446786 --- /dev/null +++ b/changes/releases/0.0.131-rc.19.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.19] — 2026-07-28 + +### Features + +- Minor updates and improvements diff --git a/changes/releases/0.0.131-rc.2.md b/changes/releases/0.0.131-rc.2.md new file mode 100644 index 000000000..e79027df9 --- /dev/null +++ b/changes/releases/0.0.131-rc.2.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.2] — 2026-05-31 + +### Features + +- Minor updates and improvements diff --git a/changes/releases/0.0.131-rc.20.md b/changes/releases/0.0.131-rc.20.md new file mode 100644 index 000000000..5914c56d6 --- /dev/null +++ b/changes/releases/0.0.131-rc.20.md @@ -0,0 +1,7 @@ +## [0.0.131-rc.20] — 2026-07-31 + +### Features + +- updated deps +- vendored gpu allocator +- updated cache diff --git a/changes/releases/0.0.131-rc.21.md b/changes/releases/0.0.131-rc.21.md new file mode 100644 index 000000000..c5b941b5e --- /dev/null +++ b/changes/releases/0.0.131-rc.21.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.21] — 2026-08-04 + +### Features + +- new distributed diff --git a/changes/releases/0.0.131-rc.22.md b/changes/releases/0.0.131-rc.22.md new file mode 100644 index 000000000..c01c23059 --- /dev/null +++ b/changes/releases/0.0.131-rc.22.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.22] — 2026-08-04 + +### Features + +- lock diff --git a/changes/releases/0.0.131-rc.23.md b/changes/releases/0.0.131-rc.23.md new file mode 100644 index 000000000..103434e7b --- /dev/null +++ b/changes/releases/0.0.131-rc.23.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.23] — 2026-08-09 + +### Features + +- updated inference diff --git a/changes/releases/0.0.131-rc.24.md b/changes/releases/0.0.131-rc.24.md new file mode 100644 index 000000000..50064d27c --- /dev/null +++ b/changes/releases/0.0.131-rc.24.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.24] — 2026-08-12 + +### Features + +- added i18n diff --git a/changes/releases/0.0.131-rc.25.md b/changes/releases/0.0.131-rc.25.md new file mode 100644 index 000000000..0dfc9e2da --- /dev/null +++ b/changes/releases/0.0.131-rc.25.md @@ -0,0 +1,35 @@ +## [0.0.131-rc.25] — 2026-09-18 + +### Features + +- **`exg_auto_download_weights` setting** (Settings → EEG Model, off by default): when an EEG session starts without encoder weights on disk, fetch them automatically instead of recording metrics-only. Off by default because the default ZUNA encoder is a 380M-parameter model. Reuses the existing download machinery, including progress events and cancellation. + +### Performance + +- **Screenshot backfill: one paged query instead of three full table scans.** `rows_needing_backfill` replaces issuing `rows_without_ocr` + `rows_without_embedding` + `rows_without_ocr_embedding` and merging them into a map of every pending row; it carries the per-row flags plus `ocr_text` and `timestamp`, which also removes two per-row lookups. Rows are walked newest-first via an `id` cursor, so an interrupted pass has covered the most recently captured screenshots rather than an arbitrary hash-order subset. +- Backfill progress now reports to the UI on the existing `screenshot-reembed-progress` event (`done`/`total`/`elapsed_secs`/`eta_secs`, plus `embedded`/`skipped`/`complete`), and logs an embedded/skipped/elapsed tally on completion. Previously a multi-minute pass was silent and unreadable files were skipped without being counted. +- **An empty screenshot HNSW index could never be loaded again.** `fast-hnsw` serializes a zero-node labeled index but rejects it on read ("file contains no payload section"), so an empty index that reached disk failed to load on every subsequent boot, was rebuilt — still empty — and written again. Empty indexes are no longer written, and a stale empty file is removed so the next load takes the clean path. +- **EEG sessions recorded with no embeddings gave no actionable signal.** The weights resolver only probes the HuggingFace cache and never downloads, so a machine without encoder weights recorded every session metrics-only behind a single ERROR line. `EmbedWorkerStatus` now reports a `reason` (`weights_missing` vs `encoder_unavailable`) and the worker emits `ExgWeightsMissing`. +- **Auto-connect attempted a BLE device before it was discoverable.** The first connect fired 900 ms after daemon boot against the cached preferred device, burning a full scan timeout per attempt — typically 7 failed attempts and ~70 s of ERROR lines before a headband appeared, out of a 12-attempt budget. BLE targets now wait for the scanner to see the device (60 s cap, then attempt anyway); wired and manual targets are unaffected. Retryable failures log at WARN until the retry budget is exhausted. +- **ANT Neuro SDK was re-initialized every 10 seconds.** The scanner built a fresh `AntNeuroSdk` on every other tick, and its `NativeBackend` logs its version unconditionally — one INFO line every 10 s for the life of the daemon, on machines with no ANT Neuro hardware. The handle is now built once per process. + +### Bugfixes + +- **Screenshot capture was silently disabled on default settings.** The daemon's `ScreenshotContext::is_session_active()` returned a hardcoded `false` — a stub left by the thin-client migration (`ba3f07d4`), where the Tauri implementation had checked `session_start_utc.is_some()`. Since `session_only` defaults to `true`, the capture loop's gate was permanently shut: the worker spawned, logged "worker spawned", then spun on a 1-second no-op forever, never capturing, embedding, or backfilling. It now reads the live `session_handle`, matching how the rest of the daemon defines an active session. +- **Screenshot backfill was decided once at boot and effectively never ran.** The gate was read a single time when the embed worker spawned — before any session exists — so with the default `session_only = true` the historical catch-up was skipped on every boot and never re-evaluated. The backfill now runs from the embed loop's idle time, re-reading the gate every pass. +- **The screenshot backfill starved live capture.** It walked every owed row in one uninterruptible pass on the embed thread; since the job channel is `bounded(4)` with `try_send`, the capture thread *dropped* new screenshots for the whole window — manufacturing the debt the backfill was paying down, recoverable only on a later daemon start. It now runs in bounded 25-row chunks during idle ticks, so live jobs always take priority, a mid-pass disable stops it, and completed work survives a restart (indexes are saved per chunk). + +### Server + +- **Canonical `unix_ms` column on the `embeddings` table.** `timestamp` carries three historical encodings (Unix ms, `YYYYMMDDHHmmss`, and `YYYYMMDDHHmmss × 1000`), so every range query had to go through `DualTimestampRange` and match all three — with an explicit warning never to write a raw `WHERE timestamp >= ?`. Day stores now gain a `unix_ms` column, backfilled from whichever format each row uses, plus an index. + + The migration is deliberately **additive: `timestamp` is never rewritten**, because older builds of the app read it directly and expect the format they wrote — normalising it in place would corrupt their view of existing recordings. This is the expand half of expand/contract; `timestamp` can only be retired once no old build remains in the wild. Applied on open by both writers (the day store and the session pipeline's `EpochStore`, which share the table), idempotent, and resumable if interrupted part-way. + + Verified against a real 296-row recording containing both encodings: all rows backfilled, every `timestamp` byte-for-byte unchanged, every `unix_ms` a plausible instant, and a re-run a no-op. An `#[ignore]`d fixture test (`SKILL_MIGRATION_FIXTURE`) re-runs that check against any real day store before a release. + +### Dependencies + +- Refresh `rlx` / `rlx-models` git pins from the stale 0.2.14 lock to current `main` (0.2.16). +- **Clear all outstanding `cargo deny` advisories.** `rustls` 0.23.42 → 0.23.45 (RUSTSEC-2026-0285, TLS 1.3 handshake messages accepted across encryption-level boundaries), `h2` 0.4.15 → 0.4.19 and `chacha20` 0.10.1 → 0.10.2 (yanked). +- **Fork `oura-api` 0.1.2 to `reqwest` 0.12** (`patches/oura-api-0.1.2`, vendored; the only change from crates.io is the dependency version). Upstream's newest release pins `reqwest` 0.11, which pulled the entire hyper 0.14 stack including `h2` 0.3.x — the branch RUSTSEC-2026-0258 is unpatched on, fixed in `h2` >= 0.4.16 only. This removes the last advisory and drops a duplicate HTTP stack: the tree now resolves a single `hyper` (1.11) and a single `h2` (0.4.19), where it previously carried both 0.14/1.x and 0.3/0.4. +- Remove dead `deny.toml` entries that made `cargo deny` fail or warn: the `RUSTSEC-2024-0415` gtk ignore (unreachable — `unmaintained = "workspace"` already scopes that lint to our own crates, and an ignore that never matches is a hard error), the `winreg@0.55.0` duplicate skip (deduplicated by dropping `reqwest` 0.11), and re-pin the `winnow` skip 1.0.3 → 1.0.4. diff --git a/changes/releases/0.0.131-rc.26.md b/changes/releases/0.0.131-rc.26.md new file mode 100644 index 000000000..00533d5de --- /dev/null +++ b/changes/releases/0.0.131-rc.26.md @@ -0,0 +1,21 @@ +## [0.0.131-rc.26] — 2026-09-22 + +### Bugfixes + +- **Paired devices survive the BLE backend change**: webbluetooth reports Apple device ids as uppercase `NSUUID` strings where btleplug reported lowercase UUIDs, which would have made every entry in `paired_devices.json` stop matching after upgrade — the headset would reappear as unpaired and auto-reconnect would never fire. BLE ids are now canonicalised to one spelling (`skill_daemon_common::ble_id`) at every write, compared case-insensitively at every lookup, and migrated in place on first start. +- **"Forget device" no longer silently fails**: the paired-list removal compared ids with exact string equality, so a case difference left the device paired. +- **Duplicate scanner entries**: a paired device whose stored id differed in case from the scanner's was pushed into the device list a second time, listing the same headset twice. +- **Daemon can request Bluetooth under launchd**: `skill-daemon.app`'s generated `Info.plist` was missing `NSBluetoothAlwaysUsageDescription`. Spawned by the Tauri app the outer bundle's key covered it, but started by launchd (`RunAtLoad`) the daemon is its own responsible process and was denied Bluetooth with no prompt — presenting as a scan that silently found nothing. + +### Refactor + +- **BLE scanner moved to webbluetooth**: the daemon's advertisement scanner now runs on the process-wide `Bluetooth::shared()` session instead of its own btleplug `CBCentralManager`, so it shares one radio session with muse-rs rather than competing with it. The session coexists with an in-flight connect by design, which removes the need to tear the scanner down and rebuild it around every BLE connect attempt. + +### Docs + +- **`docs/webbluetooth-migration.md`**: what moved to webbluetooth, what is still on btleplug, and what has to happen before the `[patch.crates-io] btleplug` override can be dropped. + +### Dependencies + +- **muse-rs 0.1 → 0.2**: switches the Muse BLE backend from btleplug to webbluetooth. The `MuseClient` / `MuseClientConfig` / `MuseDevice` / `MuseHandle` / `MuseEvent` API is unchanged, so the adapter and connect paths needed no edits. +- **webbluetooth 0.0.1** added as a direct dependency of `skill-daemon`, replacing its direct `btleplug` dependency. Its backends have no native dependencies — no `bluer`, no `dbus`, no `windows` crates — which drops a runtime `libdbus` requirement from the Linux packages. diff --git a/changes/releases/0.0.131-rc.27.md b/changes/releases/0.0.131-rc.27.md new file mode 100644 index 000000000..b391f2413 --- /dev/null +++ b/changes/releases/0.0.131-rc.27.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.27] — 2026-09-22 + +### Features + +- fixed windows build diff --git a/changes/releases/0.0.131-rc.28.md b/changes/releases/0.0.131-rc.28.md new file mode 100644 index 000000000..34dfe41dd --- /dev/null +++ b/changes/releases/0.0.131-rc.28.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.28] — 2026-09-22 + +### Features + +- updated release windows action diff --git a/changes/releases/0.0.131-rc.29.md b/changes/releases/0.0.131-rc.29.md new file mode 100644 index 000000000..9ee6c3c0e --- /dev/null +++ b/changes/releases/0.0.131-rc.29.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.29] — 2026-09-22 + +### Features + +- MSVC vs LLD diff --git a/changes/releases/0.0.131-rc.3.md b/changes/releases/0.0.131-rc.3.md new file mode 100644 index 000000000..b4b32a4d0 --- /dev/null +++ b/changes/releases/0.0.131-rc.3.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.3] — 2026-06-01 + +### Features + +- fix release ci diff --git a/changes/releases/0.0.131-rc.30.md b/changes/releases/0.0.131-rc.30.md new file mode 100644 index 000000000..95d7eac7e --- /dev/null +++ b/changes/releases/0.0.131-rc.30.md @@ -0,0 +1,88 @@ +## [0.0.131-rc.30] — 2026-09-28 + +### Refactor + +- **One `CBCentralManager` per process, by construction.** `btleplug` is now + replaced wholesale by `patches/btleplug-0.11.9` — its public API + reimplemented over `webbluetooth` — via a single `[patch.crates-io]` entry. + The five device crates still written against btleplug (`awear`, `idun`, + `mendi`, `mw75`, `openbci`) compile against it **unchanged**, and every BLE + operation in the process now funnels through one + `webbluetooth::Bluetooth::shared()` session. + + This replaces the `eugenehp/btleplug` fork, whose only purpose was a macOS + scanning fix. + + Upstream btleplug's CoreBluetooth backend has no singleton of any kind — no + `OnceLock`, `OnceCell`, `lazy_static` or `static` — so every + `Manager::new().adapters()` allocated a fresh `CBCentralManager` on a detached + thread running `loop { wait_for_message().await }` with no exit condition. + Each call permanently leaked a thread, a tokio runtime, a dispatch queue, a + delegate and a manager. Those calls were not one-time init either: in `mw75` + they sit inside `scan_all()` and `connect()`, so it was one more manager per + scan and per connect attempt, and on macOS a second manager cannot discover + peripherals while another is scanning. + + Migrating the five crates one at a time could not have fixed this — it relies + on each crate being correct and leaves a second manager live throughout. The + shim makes a second session *unconstructible*: `Bluetooth::new()` and + `with_chooser()` appear nowhere in it and have no btleplug concept that maps + to them. `platform::tests::every_adapter_is_the_same_session` asserts it. + + `src/api/` and `src/common/` are byte-identical copies of upstream 0.11.8, so + the consuming crates see the same types and a future re-sync stays a straight + copy; only the backend (`src/platform.rs`) is new. Two places the mapping is + not 1:1, both handled there: **notification fan-in** (btleplug hands out one + merged stream per peripheral, valid before any connection and across + reconnects, while webbluetooth subscribes per characteristic — so each + `subscribe()` spawns a pump that tags values with their characteristic UUID + into a per-peripheral broadcast channel) and **scan refcounting** (an + `Adapter` is a shared handle, so dropping one must not tear down a scan + another holder still wants). + +- **Remove the Hermes V1 driver.** The `hermes-ble` dependency, + `skill-devices/src/session/hermes.rs`, `connect_hermes`, the + `ConnectRoute::Hermes` route and Hermes BLE-name recognition in the scanner + are all gone. + + `hermes-ble` was the one crate pinned to `btleplug ^0.12.0`, which the + workspace's 0.11 patch could not satisfy — so it resolved unpatched from the + registry and put a second, entirely separate btleplug stack in the graph. One + `[patch.crates-io]` entry supplies one version, so removing it is what let the + webbluetooth shim cover every remaining btleplug user at once. + + A device paired on an older build still carries its name into routing, and + `select_connect_route` ends in a `ConnectRoute::Muse` catch-all — so a Hermes + headset would have been silently handed the Muse protocol. Connecting one now + fails with an explicit message instead, guarded by + `removed_hermes_does_not_fall_back_to_muse`. + + Note the Hermes *device kind* is still listed in the app's supported-devices + catalogue (`DeviceKind::Hermes`, its i18n strings and device image), which + carries no btleplug dependency. Delisting it from the UI is a separate change. + +### Build + +- **Fix the Windows release link failure** that broke rc.26 through rc.29. The + BLE migration pulled in `webbluetooth-windows` 0.0.1, which declared + `#[link(name = "combase")]`; the Windows SDK ships no `combase.lib`, so every + Windows build died at the final link of `skill-daemon` with `LNK1181: cannot + open input file 'combase.lib'`. The WinRT string and activation exports it + wants (`WindowsCreateString`, `RoGetActivationFactory`, and four more) live in + `combase.dll` at run time but are imported from `runtimeobject.lib`. Fixed + upstream in `webbluetooth` 0.0.2 and picked up here. + + The earlier `SKILL_NO_LLD` / forced-`link.exe` workaround in + `release-windows.yml` was treating a symptom — both linkers were correctly + reporting a library that does not exist — and can be removed separately. + +### Dependencies + +- Bump `webbluetooth` 0.0.1 → 0.0.3 and `muse-rs` 0.2.0 → 0.2.1. **Both had to + move together**: Cargo treats 0.0.x releases as mutually incompatible, so + bumping only one resolved to two copies of `webbluetooth` — which kept the + unfixed 0.0.1 Windows backend in the graph *and* meant two + `Bluetooth::shared()` `OnceLock`s, so two `CBCentralManager`s and devices that + would not connect. The invariant is now recorded in the workspace root + `Cargo.toml`: `cargo tree -p skill-daemon -i webbluetooth` must print exactly + one node. diff --git a/changes/releases/0.0.131-rc.31.md b/changes/releases/0.0.131-rc.31.md new file mode 100644 index 000000000..b9031a771 --- /dev/null +++ b/changes/releases/0.0.131-rc.31.md @@ -0,0 +1,6 @@ +## [0.0.131-rc.31] — 2026-09-28 + +### Features + +- fix the session drop issue +- improve cli diff --git a/changes/releases/0.0.131-rc.32.md b/changes/releases/0.0.131-rc.32.md new file mode 100644 index 000000000..4556c6b96 --- /dev/null +++ b/changes/releases/0.0.131-rc.32.md @@ -0,0 +1,193 @@ +## [0.0.131-rc.32] — 2026-09-29 + +### Bugfixes + +- **Reconnect could stop silently mid-retry.** `eval_reconnect_tick` waits while + the device state is `"connecting"` — correct, since a connect is already in + flight — but nothing guaranteed that state ever moved on. + `spawn_device_session`'s failure arm only resets it when `still_current` holds, + so a failing attempt whose target had since been rewritten left `"connecting"` + behind permanently. The reconnect loop then waited forever: no further + attempts, and never the give-up branch either, so the log simply went quiet + with the device stuck. Reported as "gives up after 4 attempts"; the budget is + 12 and was never reached — the loop had stopped asking. The reconnect task now + detects `"connecting"` with no live session task for 15 s and treats it as + disconnected so retrying resumes. See github.com/NeuroSkill-com/skill#89. + +- **The daemon and the app could authenticate against different tokens.** Path + resolution for the daemon's config directory existed in four hand-rolled + copies, and they had diverged: only the app honoured + `SKILL_DAEMON_CONFIG_ROOT`, so with that variable set the app read a tmpdir + `auth.token` while the daemon read the real one under `$HOME` — every request + answering 401 with no way for the UI to drive the daemon. It also meant e2e + runs that pin daemon paths into a tmpdir silently authenticated against the + user's real token. + +### Refactor + +- **One definition of the daemon's paths.** New `skill_daemon_common::paths` + (`config_root`, `token_path`) replaces the four copies in + `skill-daemon-state::util`, `src-tauri/daemon_cmds`, + `src-tauri/daemon_upgrade` and `skill-daemon/main`. Each call site keeps its + own error type and fallback, so behaviour is unchanged where it was already + correct. Bearer auth compares a secret both processes read from disk, so the + two resolving different paths cannot degrade gracefully — this is the kind of + duplication that fails closed. + +- **Removed the BLE scan pause.** `ble_scan_paused`, the `needs_ble_pause` + predicate, its 400 ms sleep on every BLE connect and the scanner's 300 ms poll + are gone, along with the `AppState` field. The hazard was real but is now + unconstructible: each btleplug crate used to open its own `CBCentralManager`, + and on macOS two concurrent `scanForPeripherals()` calls suppress the + `didConnect:` callback so `connect()` hangs forever. Every BLE consumer now + goes through one `webbluetooth::Bluetooth::shared()` session, and one manager + scanning while it connects is ordinary CoreBluetooth usage. + +### Build + +- **Changelog is readable again.** `CHANGELOG.md` keeps the 20 newest releases + in full and links the rest to their files under `changes/releases/`, taking it + from 6,065 lines / 595 KB to 496 lines / 31 KB. Every release remains + reachable — verified none missing and none duplicated. + +- **The changelog's git-log fallback no longer mislabels everything.** When no + fragment is written, `bump` derives entries from commit subjects; it filed all + of them under `### Features` regardless of content, which is how "fixed glib + with a patch" shipped as a feature. Entries are now bucketed by + conventional-commit prefix, unlabelled commits go to `Refactor` rather than + being guessed as features, and the fallback prints a visible warning that raw + commit subjects are about to become user-facing release notes. + +- **Dropped the Windows lld-link workaround.** `SKILL_NO_LLD` and the forced + `link.exe` were added on the theory that lld-link could not resolve Windows SDK + import libraries like `combase.lib`. `combase.lib` does not exist in the SDK at + all, so both linkers were correctly reporting a missing file; the real fault was + upstream and is fixed. `SKILL_NO_LLD` was also inert here — only + `tauri-build.js` reads it, and the release path uses `compile-product.mjs`. + +- **The Linux `.deb` declared one dependency and needed fifteen.** `dpkg-deb + --build` is a raw packer: unlike `rpmbuild`, which reads each ELF and emits a + soname requirement per NEEDED entry, it ships exactly what the control file + says. The control file said `Depends: libopenblas0` and nothing else, so the + package installed cleanly on a machine missing the entire GTK/WebKit stack and + the app then failed to start — a worse failure than refusing to install. + + Measured against the rc.31 binaries with `dpkg-shlibdeps`, the real set is: + `libasound2t64`, `libc6`, `libcairo2`, `libdbus-1-3`, `libgcc-s1`, + `libgdk-pixbuf-2.0-0`, `libglib2.0-0t64`, `libgtk-3-0t64`, + `libjavascriptcoregtk-4.1-0`, `libopenblas0`, `libsoup-3.0-0`, `libssl3t64`, + `libstdc++6`, `libudev1`, `libwebkit2gtk-4.1-0` — each with a minimum version. + `Depends` is now derived from the three shipped binaries at package time rather + than hand-maintained, and `dpkg-dev` is named explicitly in the release apt + list so a slimmer image cannot quietly lose the tool. + + If `dpkg-shlibdeps` is missing or yields nothing the script still falls back to + the old single-dependency value, but now says so loudly on stderr. A silent + fallback is how this shipped under-declared in the first place: it built, it + installed, and only the user saw the failure. + +- **Removed dead ONNX Runtime plumbing.** `ort` / `ort-sys` are absent from + `Cargo.lock` entirely — KittenTTS was the only consumer and now runs natively + on RLX — but the build tooling still carried six pieces of machinery for it: + the `.so` bundling blocks in `package-linux-system-bundles.sh` and + `package-linux-dist.sh`, an `actions/cache` step in `release-linux.yml` + keyed on a path nothing writes, the unreferenced + `setup-onnxruntime-{linux,windows}` composite actions, and + `scripts/install-onnxruntime-linux.sh` which only that dead action called. All + gone, plus the stale `ort-sys` entries in the standalone + `crates/skill-tts/e2e` lockfile, and two comments that still described ORT as + live on non-Windows targets. + + This also retires a latent silent-exit bug rather than papering over it. The + lookup was `ORT_SO="$(find .../release/build ... | head -1)"` under + `set -euo pipefail`; `find` exits non-zero when that directory does not exist, + so the command substitution killed the whole script with **no output at all** + and the graceful "not found" warning below it was unreachable. The release + always has a `build/` directory so it never showed there, but any packaging run + without one died mute. Deleting the block removes the hazard outright. + +- **RPM packaging hardened.** The `-` → `~` rewrite of RC versions is no longer + written in a form whose meaning depends on the bash version (bare `~` is + tilde-expanded to `$HOME` on bash 5; `\~` leaves a literal backslash on bash + 3.2), and an allowlist guard now refuses to build rather than let `rpmbuild` + silently rewrite an invalid `Version`. The manual `Requires: openblas` is + dropped: rpmbuild already auto-generates `libopenblas.so.0()(64bit)` from the + shipped ELF, and the literal package name does not exist on every distro this + rpm targets. + +- **Groundwork for an aarch64 Linux package.** `Release — Linux` builds x86_64 + only, so no ARM Linux `.deb`/`.rpm` ships even though + `package-linux-system-bundles.sh` already knows the aarch64 arch mapping. The + `release-setup` action's apt list is now arch-aware — LunarG publishes + `vulkan-sdk` for amd64 only, so a caller can substitute Ubuntu's + `libvulkan-dev libvulkan1` (the same fallback + `scripts/install-vulkan-sdk.sh` already uses) and the LunarG repo step is + skipped when it would contribute nothing. Existing callers pass nothing and + behave identically. + + A `workflow_dispatch`-only `Trial — Linux arm64 packages` workflow builds the + daemon for `aarch64-unknown-linux-gnu`, runs the packaging script and asserts + the resulting rpm: `ARCH=aarch64`, no `-` or backslash in `Version`, a `~` + whenever `VERSION` is an RC, and an auto-generated `libopenblas.so.0` + requirement. It is deliberately not on the tag-driven release path — an + unproven job there can fail a release, and a second full Linux build doubles + the cost of every tag — and it uploads to the workflow run rather than to a + Release, so a trial cannot publish anything. + +- **RPM verification harness.** `npm run verify:rpm` (or + `scripts/verify-rpm-docker.sh`) installs a NeuroSkill `.rpm` on a real Fedora + in Docker and checks it, for `x86_64` and `aarch64`. We build the rpm on Ubuntu + but ship it to Fedora/RHEL/openSUSE, so the failures that matter are + distro-side — whether `dnf` can satisfy the dependencies rpmbuild generated, + and whether the binary's NEEDED libraries exist under those package names. + Building it on Ubuntu says nothing about either. + + It takes the package from `--rpm`, else a local release build, else the newest + published GitHub release; for an architecture with no real rpm yet (aarch64, + since `Release — Linux` is x86_64-only) `--synthetic` builds one from the same + spec the release script emits, with a genuinely OpenBLAS-linked binary so the + dependency and linkage checks still mean something. Checks: name/arch/licence, + `Version` legal and tilde-ordered below its release (via `rpm.vercmp`), + auto-generated `libopenblas.so.0` present and no unsatisfiable bare + `Requires: openblas`, `dnf install` resolves, declared paths exist, `rpm -V` + clean, ELF arch matches the package, `ldd` fully resolves, and removal cleans + up. Image: `Dockerfile.rpm-verify`; the checks live in + `scripts/lib/verify-rpm.sh`, mounted at run time so editing one needs no + rebuild. + + The metadata half needs no container: `scripts/lib/rpm_meta.py` parses the rpm + header directly (and lists the payload via bsdtar, which macOS ships and which + understands the rpm container), so version/arch/dependency assertions run on a + dev box or when the Docker daemon is wedged. The driver probes Docker with a + *bounded* wait — `docker info` blocks forever against a stuck daemon, and macOS + has no coreutils `timeout` — then verifies metadata natively and reports the + install phase as skipped rather than hanging. + + Results on a real Fedora 41, both architectures: + + | package | result | + |---|---| + | shipped rc.31 `x86_64` (downloaded from the release) | 23 pass, **1 fail** — the bare `Requires: openblas` | + | `x86_64` via the real packaging script, post-fix | **24 pass, 0 fail** | + | `aarch64` via the real packaging script, post-fix | **24 pass, 0 fail** | + + `--use-release-script` is the mode that makes the last two meaningful: it runs + `scripts/package-linux-system-bundles.sh` itself inside the container against a + stand-in binary, rather than reproducing the spec by hand, so what gets verified + is the artifact the release pipeline would actually emit. rpmbuild reports + `Requires: /usr/bin/bash libc.so.6()(64bit) libopenblas.so.0()(64bit) + rtld(GNU_HASH)` — the soname, no bare package name. + + So the defect is demonstrated on the artifact users actually download, and a + package built from the fixed spec passes every check — including + `dnf install` resolving the full dependency set, `rpm -V` clean, all three + shipped ELFs (`skill`, `skill-daemon`, `skill-tty`) matching the declared + architecture with every dynamic library resolving, and a clean erase. + `rpm.vercmp` confirms `0.0.131~rc.31 < 0.0.131` on both. + + Two flaws in the harness itself surfaced by running it, both fixed: the image + has to be built per `--platform` (building once for the host arch and running + with `--platform linux/amd64` makes docker try to pull a local-only tag), and + `/usr/bin/neuroskill` is a bash launcher rather than the binary — checking + *it* for an ELF architecture and running `ldd` against it passed for the wrong + reason. The checks now inspect the real executables under `/opt/neuroskill`. diff --git a/changes/releases/0.0.131-rc.33.md b/changes/releases/0.0.131-rc.33.md new file mode 100644 index 000000000..1debe31eb --- /dev/null +++ b/changes/releases/0.0.131-rc.33.md @@ -0,0 +1,20 @@ +## [0.0.131-rc.33] — 2026-09-29 + +### Build + +- **Fix the Linux release failing at the dependency step.** `release-setup`'s + `packages:` is a YAML *folded scalar*: every line inside it is literal text + handed to apt, so a leading `#` does not start a comment. A comment added there + in rc.32 turned each word of the prose into a package name, and the whole + Linux release died 3 minutes in with `Unable to locate package #`, + `dpkg-shlibdeps`, `used`, `by`, `derive`, and the rest. + + The explanation now lives above the step, where YAML does treat it as a + comment, and the block holds package names only — with a note there saying + exactly this, so the next person does not repeat it. Verified by rendering the + list straight out of the YAML and resolving every name against real Ubuntu on + both amd64 and arm64: all 19 resolve, with `vulkan-sdk` correctly coming from + the LunarG repo rather than the base archive. + + `dpkg-dev` (the reason for the comment) stays in the list: it provides + `dpkg-shlibdeps`, which derives the `.deb` Depends from the shipped ELFs. diff --git a/changes/releases/0.0.131-rc.34.md b/changes/releases/0.0.131-rc.34.md new file mode 100644 index 000000000..8c6ff66ac --- /dev/null +++ b/changes/releases/0.0.131-rc.34.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.34] — 2026-10-04 + +### Build + +- Minor updates and improvements diff --git a/changes/releases/0.0.131-rc.35.md b/changes/releases/0.0.131-rc.35.md new file mode 100644 index 000000000..b6a2f4c22 --- /dev/null +++ b/changes/releases/0.0.131-rc.35.md @@ -0,0 +1,56 @@ +## [0.0.131-rc.35] — 2026-10-12 + +### Features + +- **Oura Ring (Gen 3 / Ring 4 / Ring 5)**: added support for reading an Oura ring directly over Bluetooth, with no Oura account and no cloud round trip, using the reverse-engineered protocol from [open_oura](https://github.com/Th0rgal/open_oura). A connected ring reports live heart rate with its inter-beat interval, the latest cached SpO2, battery state of charge, and — opt-in — accelerometer motion. Discovery, pairing and connection work like any other BLE device. +- **Direct-BLE and cloud Oura are independent**: the existing Oura integration syncs sleep, readiness and activity scores from Oura's V2 REST API with a personal access token. The new one talks to the ring itself. Both are configured in Settings → Devices → Device API and neither replaces the other; `docs/DEVICES.md` lays out which gives you what. +- **Cloud-free Oura history sync** (Settings → Devices → Device API → *Sync from ring*): drains the history the ring itself stores over Bluetooth and writes it to `health.sqlite` — heart rate, HRV (RMSSD), skin temperature, SpO2, perfusion index, respiratory rate, sleep stages, MET levels and the ring's own detected bedtime window. Incremental: a cursor records where the last sync stopped, and `{"reset": true}` re-reads everything the ring still holds. Daemon command `oura_ring_sync`; status via `oura_ring_status`. +- **Session vitals recording**: a recorded session with a vitals device in it now produces an `exg_*_vitals` sidecar (`.csv` and/or `.parquet`, following the session storage format) with `timestamp_s, hr_bpm, ibi_ms, spo2_pct, temperature_c`. The table is deliberately sparse — a wearable reports one vital at a time, so an absent value is an empty CSV field and a Parquet null, never a zero that would read as a measurement. +- **Device-attributed vitals events**: the daemon broadcasts a new `Vitals` event carrying `device_kind` and `device_id`, so heart rate from a ring stays distinguishable from anything else streaming at the same time. + +- **Vital Neuro headphones**: added support for Vital Neuro EEG headphones over BLE via the `vital` crate — three EEG channels at 250 Hz, accelerometer and battery telemetry, plus electrode impedance when the device is put in impedance mode. Discovery, pairing and connection work like any other BLE headset, and the headphones appear in Settings → Supported Devices. +- **BLE connect, faster when there is one headset**: a Vital Neuro connect stops at the first advertisement when the scanner can see that only one is in range, instead of always running the scan window out to pick by id. +- **BLE discovery by advertised service**: the scanner now also identifies a device from the service UUID it advertises, not only its name. Vital Neuro stores its name on the unit and lets the owner change it, so a renamed pair is still discovered and still routed to the right driver. + +### Performance + +- **Local chat uses far less memory**: the Qwen3.5 predict graph was compiled twice — once into a cache nothing reads, then again to return — and the chat UI's typing-time warm pass built that graph even though generation never uses it. Both are gone. Metal buffer allocation for the default 0.8B model drops from 25.9 GB to 3.97 GB and peak process memory from ~31 GB to ~11.7 GB. +- **Prefill width is configurable (`llm.rlx_prefill_seq`)**: Qwen3.5 runs every prompt through one static prefill graph whose width defaulted to the whole decode context, so a one-line question was prefilled as if it were 8192 tokens. Setting this decouples the two — measured on the 0.8B with a 25-token prompt, time to first token falls from 10.2 s to 1.2 s and decode rises from 5.6 to 35.6 tokens/s. A prompt longer than the configured width is rejected outright, so raise it when tools are on. +- **Agent Skills are no longer injected when they cannot be used**: the skills block tells the model to load files with the `read_file` tool, which is off by default, so 33 discovered skills added roughly 3,900 tokens to every prompt for nothing — a one-line question arrived as 3,970 tokens. It is now sent only when that tool is actually offered, and the daemon says once at startup when skills are being withheld for that reason. + +### Bugfixes + +- **Local chat no longer dies silently mid-request**: a panic inside a model kernel unwound the inference actor, so the request's stream just ended — no error to the caller, no status event, and a log that stopped dead after `chat request — …`. Each request now runs behind a panic boundary: the failure is reported on the request and in the LLM log, and the server keeps serving. +- **Replies containing an accent, CJK or an emoji could kill the actor**: the token streamer tracked how much it had sent as a byte offset into the previous decode. Byte-level BPE revises earlier bytes when a codepoint spans tokens — a 3-byte replacement char becomes a 2-byte `é` — so that offset could land mid-character and the slice panicked. English-only output never tripped it. The cursor is now resynchronised on whole characters. +- **Qwen3 models failed every chat with `weight not found: model.embed_tokens.weight`**: the runner's context was left at the library default of 128 tokens, which sized its decode-bucket ladder to 256. One token past that, decoding fell back to a path that needs F32 weights a packed GGUF does not have. The error named a tensor and looked like a GGUF/safetensors mismatch; it was a context cap. Context is now passed through, as it already was for Qwen3-VL and Qwen3.5. +- **Small models answered with the same phrase over and over**: the stop signal a caller returns from its token callback was discarded on the Qwen3 path, so chat EOS, user stop strings and the repetition guard were all ignored and generation always ran to the token cap. Qwen3-0.6B asked for the capital of France produced 303 tokens of "The capital of France's capital of France's …". It also made `finish_reason` permanently `length`. +- **Loading a model the catalogue does not describe budgeted memory for the wrong one**: context was sized from the active catalogue entry even when a different file was being loaded (a path set directly, a discovered model, `--model`). Loading a 1-bit 8B while the catalogue still described a 0.8B picked a 16K context for the smaller model and drove the daemon to 16.5 GB resident and 24.6 GB of swap. Mismatches are now detected, warned about, and fall back to a conservative context. +- **`max_tokens` sent by an OpenAI-compatible client had no effect**: it was forwarded under a key nothing reads, so every such request silently ran to the 2048 default. +- **Web search left a panic in the log on every call**: the headless renderer tried to create its event loop on a background thread, which macOS forbids, then reported an unrelated timeout before falling back to a plain fetch. It now declines up front with the reason and the supported alternative. + +### Build + +- **`npm run daemon` builds a GPU-capable daemon by default** and takes `--model ` to load a GGUF directly; previously it built without the platform's GPU features, so models ran on CPU, and pointing it at a local file meant four API calls by hand. + +### UI + +- **Live vitals card**: a connected vitals device now shows its readings in the dashboard's Physiology view — heart rate, inter-beat interval, SpO2 and skin temperature. Separate from the PPG card, which charts values computed here from an optical waveform: a ring does its own beat detection, so there is no waveform and the PPG card would stay empty. Each tile appears only once a value for it has arrived rather than showing a placeholder zero, and the card explains itself while the device has not measured yet instead of looking broken. +- **A ring no longer shows a Muse head map**: the electrode guide gained an Oura tab reporting zero electrodes and no positions, the same treatment Hermes and Ganglion get. Previously any unrecognised device kind fell through to the Muse four-electrode montage. +- **Ring BLE key setting**: Settings → Devices → Device API takes the ring's 16-byte app-auth key as 32 hex characters, stored in the OS keychain beside the cloud token. The ring challenges for this key on every connection and answers nothing but device info without it, so connecting without one fails with a message saying where the key comes from rather than producing a session that streams nothing. + +- **Electrode guide knows Vital Neuro**: connecting one no longer falls back to the Muse head map. The device reports no electrode montage, so the guide names the headset and shows no positions — the same thing it does for Hermes and Ganglion — instead of four electrodes that are not there. + +- **Progress is shown while a tool runs**: a tool round-trip sent the client nothing at all — 21 of 22 seconds silent on a search — because only generation-phase markers were forwarded. Tool start and finish now appear as phases, so the chat shows "Running tools…" instead of looking stalled. +- **The onboarding model card names the model it will actually download**: the title was a fixed string reading "Qwen3.5 4B (Q4_K_M)" in every language while the download was Qwen3.5 0.8B, with the quant badge beside it already showing the truth. +- **Start honours a model picked elsewhere**: onboarding ignored the selected model and fetched its own default instead. +- **The LLM panel shows the port the API is really on**: it displayed a configurable advertisement value (8375) rather than the port the daemon bound (18444), so the endpoint list and the `curl` example were both wrong. + +### Server + +- **One BLE session, including the ring**: the ring's link is an `oura-link` `Transport` implemented directly over `webbluetooth`, rather than that crate's own btleplug backend. Every radio operation therefore goes through the single process-wide `Bluetooth::shared()` session the rest of the app uses — on macOS a second session is a second `CBCentralManager`, and a device found through one cannot be connected through the other. The service-UUID constant is shared between the daemon and the driver, so a dependency graph that ever split webbluetooth in two would fail the build instead of failing at runtime. +- **Discovery by service UUID, for a ring with no name**: a bonded Oura ring usually advertises no local name at all, so it is recognised by its vendor service UUID (`98ed0001-…`) — the same mechanism a renamed Vital Neuro uses. Name matching for the ring now delegates to the device registry, so BLE discovery and connect routing cannot disagree about what counts as a ring. + +- **`has_vitals` capability flag**: the session status now distinguishes a device that reports vitals it derived itself from one with a PPG sensor this app reads a waveform from (`has_ppg`), so the UI can pick the right card instead of inferring from the device kind. Cleared on disconnect with the other capability flags. +- **Ring timestamps are dated properly, or not at all**: a history event is stamped with the ring's own counter in *deciseconds*, which restarts on every reboot — it is not Unix time. `skill-oura`'s new `RingClock` reconstructs boot epochs and interpolates from `time_sync` / `rtc_beacon` anchors (a port of open_oura's `epoch_time.py`). Events that genuinely cannot be dated — no anchor in their boot, a counter that ran faster than wall time, or lost time spanning several reboots — are **dropped and counted** rather than stored at the moment they were downloaded, because a wrong date in a health database is worse than a missing row. The sync result reports `undated`, `untrusted` and `anchors`, and the UI explains a sync that read events but could date none of them instead of flashing a success tick. +- **Health conversion refuses unvalidated decodes**: only event layouts upstream has confirmed against real captures are stored. Anything carrying `"_status": "unvalidated"`, `"inferred"` or `"part1_raw"` is skipped generically, so a future upstream decoder cannot silently start feeding the health database guesses, and every value is range-checked before it is written. `ibi_and_amplitude_event` is excluded on the same grounds — 7–13 % of its beats read above 100 bpm during sleep on two different rings. +- **A history sync will not fight a live session**: the ring accepts one BLE connection, and the protocol matches a response by tag order, so two clients on one link would take each other's answers. A sync refuses with an actionable error while a ring session is connected. diff --git a/changes/releases/0.0.131-rc.4.md b/changes/releases/0.0.131-rc.4.md new file mode 100644 index 000000000..153365d5e --- /dev/null +++ b/changes/releases/0.0.131-rc.4.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.4] — 2026-06-01 + +### Features + +- fixed windows and linux builds diff --git a/changes/releases/0.0.131-rc.5.md b/changes/releases/0.0.131-rc.5.md new file mode 100644 index 000000000..8131c4403 --- /dev/null +++ b/changes/releases/0.0.131-rc.5.md @@ -0,0 +1,6 @@ +## [0.0.131-rc.5] — 2026-06-10 + +### Features + +- upgraded to rlx 0.2.5 +- upgrade to rlx 0.2.4 diff --git a/changes/releases/0.0.131-rc.6.md b/changes/releases/0.0.131-rc.6.md new file mode 100644 index 000000000..429823e5a --- /dev/null +++ b/changes/releases/0.0.131-rc.6.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.6] — 2026-06-10 + +### Features + +- Minor updates and improvements diff --git a/changes/releases/0.0.131-rc.7.md b/changes/releases/0.0.131-rc.7.md new file mode 100644 index 000000000..ac6f0ed21 --- /dev/null +++ b/changes/releases/0.0.131-rc.7.md @@ -0,0 +1,7 @@ +## [0.0.131-rc.7] — 2026-07-19 + +### Features + +- update tts weights +- updated tts +- updated to rlx 0.2.11 diff --git a/changes/releases/0.0.131-rc.8.md b/changes/releases/0.0.131-rc.8.md new file mode 100644 index 000000000..33b92bd90 --- /dev/null +++ b/changes/releases/0.0.131-rc.8.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.8] — 2026-07-19 + +### Features + +- build: commit rlx-ocr safetensors bundle so CI can build skill-screenshots diff --git a/changes/releases/0.0.131-rc.9.md b/changes/releases/0.0.131-rc.9.md new file mode 100644 index 000000000..bfb692ed7 --- /dev/null +++ b/changes/releases/0.0.131-rc.9.md @@ -0,0 +1,5 @@ +## [0.0.131-rc.9] — 2026-07-19 + +### Features + +- updated rlx metal diff --git a/changes/releases/0.0.15.md b/changes/releases/0.0.15.md new file mode 100644 index 000000000..0c6ef18a3 --- /dev/null +++ b/changes/releases/0.0.15.md @@ -0,0 +1,135 @@ +## [0.0.15] — 2026-03-11 + +### Windows support + +- **espeak-ng static build — Windows MSVC** — `scripts/build-espeak-static.ps1` + builds `espeak-ng.lib` from source using CMake + MSVC on first run, then + links it statically into the binary; subsequent builds are a no-op. + Produces a single merged `.lib` (CMake + `lib.exe /OUT`) so that the linker + sees no duplicate symbol conflicts +- **espeak-ng static build — Windows MinGW/GNU** — `scripts/build-espeak-static-mingw.sh` + handles the `*-windows-gnu` target ABI (MSYS2 / cross-compile from + Linux/macOS); output goes to `espeak-static-mingw/` to avoid collisions + with the MSVC archive +- **`build.rs` — multi-platform espeak dispatch** — selects the correct + build script and archive name based on `CARGO_CFG_TARGET_ENV`: + `msvc` → PowerShell `.ps1`, `gnu` on Windows → MinGW `.sh`, + macOS/Linux → Unix `.sh`; links `-lc++` on macOS, `-lstdc++` on Linux/MinGW, + and omits the C++ flag on MSVC (runtime linked automatically) +- **`build.rs` — espeak data copy deferred to release** — the espeak-ng data + directory is no longer copied during `cargo build` / `tauri dev`; the copy + is skipped in debug builds to break the infinite rebuild loop where + `build.rs` copies → Tauri watcher detects the change → `cargo run` → repeat. + An empty placeholder directory is still created so Tauri's resource-path + validation does not error at startup +- **`fast-hnsw` — vendored with Windows fix** — `memmap2::Mmap::advise()` and + `memmap2::Advice` are `#[cfg(unix)]` and not available on Windows; the + `fast_hnsw` crate's unconditional `advise(Advice::Random)` call caused a + compile error on the MSVC target; patched locally via + `src-tauri/vendor/fast-hnsw` with the `advise` call wrapped in + `#[cfg(unix)]`; vendored until upstream releases a fix +- **`WINDOWS.md`** — updated prerequisites: Visual Studio Build Tools 2022 + (**Desktop development with C++** workload, provides `cl.exe`, `lib.exe`, + Windows SDK) now listed as step 1; CMake doc note updated to cover + espeak-ng's build system in addition to llama.cpp; added Git as a + prerequisite for cloning the espeak-ng source; renumbered all steps + +### Build / tooling + +- **`scripts/tauri-build.js` refactored** — now a general Tauri wrapper that + handles `dev`, `build`, and any other subcommand; non-`dev`/`build` + subcommands (e.g. `tauri info`, `tauri signer`) pass straight through without + triggering an espeak pre-build; platform detection now also covers + `*-windows-gnu` (MinGW) +- **npm `tauri` script** — `"tauri": "node scripts/tauri-build.js"` routes all + `npm run tauri …` invocations through the wrapper, so `npm run tauri info`, + `npm run tauri dev`, `npm run tauri build -- --debug`, etc. all work + consistently across platforms + +### LLM — WebSocket / REST API + +- **WebSocket commands** — `llm_status`, `llm_start`, `llm_stop`, + `llm_catalog`, `llm_download`, `llm_cancel_download`, `llm_delete`, + `llm_logs` added to the WebSocket command dispatcher (all behind the `llm` + Cargo feature flag) +- **REST endpoints** — matching HTTP shortcuts added to the axum router in + `api.rs`: + - `GET /llm/status` — running state, active model name, context size, vision flag + - `POST /llm/start` — load the active model and start the inference server + - `POST /llm/stop` — stop the inference server and free GPU/CPU resources + - `GET /llm/catalog` — model catalog with per-entry download states + - `POST /llm/download` — start a background model download `{ "filename": "…" }` + - `POST /llm/cancel_download` — cancel an in-progress download + - `POST /llm/delete` — delete a locally-cached model file + - `GET /llm/logs` — last 500 LLM server log lines + - `POST /llm/chat` — non-streaming chat completion; body: `{ message, images?, system?, temperature?, max_tokens? }`; returns `{ text, finish_reason, tokens }` +- **`LlmServerState::chat()`** — new method on the server-state actor handle; + submits a generate request to the actor's channel and returns an + `UnboundedReceiver` for streaming; returns `Err` immediately if + the model is still loading or the actor has exited +- **`extract_images_from_messages()`** — helper that decodes all + `data:;base64,…` data-URL image parts from an OpenAI-style messages + array into raw `Vec` bytes; plain `https://…` URLs are silently skipped; + call before passing messages to the actor so it receives pre-decoded bytes + +### LLM — verbose logging + +- **`LlmConfig.verbose`** (`bool`, default `false`) — when `false` (default), + all internal llama.cpp / ggml and clip logs are silenced; set `true` to + see raw tensor-load progress and other low-level detail +- **`mtmd_log_set` silence** — `clip_model_loader` uses a separate logger + (`mtmd_log_set`) that is not affected by `llama_log_set`; the clip logger is + now silenced via a no-op `extern "C"` callback when `verbose = false`, + eliminating the tensor-load spam when loading a multimodal projector + +### CLI (`cli.ts`) + +- **`llm` subcommand group** added: + - `llm status` — print LLM server status (stopped / loading / running) + - `llm start` — load the active model and start the inference server + - `llm stop` — stop the inference server, free GPU memory + - `llm catalog` — list all catalog models with download states + - `llm download ` — start a background model download + - `llm cancel ` — cancel an in-progress download + - `llm delete ` — delete a cached model file + - `llm logs` — print the last 500 LLM server log lines + - `llm chat` — interactive multi-turn chat REPL (WebSocket streaming) + - `llm chat "message"` — single-shot: send one message and stream the reply + - `llm chat "message" --image a.jpg --image b.png` — vision: attach images + (files are base64-encoded and embedded as `image_url` content parts; + requires a vision-capable model with mmproj loaded) +- **`--image `** — new flag (repeatable) for attaching image files to + `llm chat` turns +- **`--system `** — system prompt prepended as a `{ role: "system" }` + message for `llm chat` +- **`--max-tokens `** — maximum tokens to generate per turn +- **`--temperature `** — sampling temperature (0 = deterministic, 1 = creative) + +### Settings UI + +- **Sidebar navigation** — the tab bar in Settings has been replaced with a + persistent sidebar; each tab entry shows a 24 × 24 stroked SVG icon alongside + the label and an active-indicator bar +- **Keyboard shortcuts** — `Cmd/Ctrl + 1–9` switch between the first nine + settings tabs; tooltips on each sidebar item show the shortcut hint + +### Help UI + +- **Sidebar navigation + search** — the Help window now uses the same sidebar + layout as Settings; a search box in the top bar filters across all help + sections with keyboard-navigable results + +### Internals + +- **`SKILL_DIR` constant** — `src-tauri/src/constants.rs` now exports + `pub const SKILL_DIR: &str = ".skill"` so the directory name is defined + in one place; `default_skill_dir()` in `settings.rs` uses it +- **Data directory hardcoded** — the `data_dir` field has been removed from + persisted settings; the skill directory is always `~/.skill` and is never + configurable at runtime; `expand_tilde` helper and its tests removed + +### Dependencies (0.0.15) + +- `kittentts` `0.2.4` → `0.2.5` + +--- diff --git a/changes/releases/0.0.16.md b/changes/releases/0.0.16.md new file mode 100644 index 000000000..334263d3c --- /dev/null +++ b/changes/releases/0.0.16.md @@ -0,0 +1,67 @@ +## [0.0.16] — 2026-03-11 + +### EEG / Embeddings + +- **Cross-day HNSW index** — similarity search is no longer scoped per-day; + a persistent cross-day index (rolling 30-day merged index) is maintained + under `~/.skill`; near-neighbours across months can be found in a single + query +- **Label fuzzy semantic search** — label search now uses the vendored + `fast-hnsw` label index for semantic matching in addition to plain-text + filtering; queries like "find sessions where I felt anxious" surface + nearest-neighbour label clusters rather than exact string hits + +### LLM — Chat + +- **Chat history persisted** — conversations are stored in SQLite at + `~/.skill/chat_history.sqlite`; messages survive closing and reopening + the chat window +- **Multi-conversation sidebar** — the chat window now has a sidebar listing + named conversation threads; threads are persisted to disk and can be + renamed or deleted +- **System prompt editor** — the system prompt is exposed as a text area in + the chat settings panel so users can bias the model (e.g. "you are a + neurofeedback coach") without recompiling +- **EEG context injection** — the current `eeg-bands` WebSocket event is + automatically wired into the system prompt: "User's current focus: 72, + relaxation: 58, SNR: 14 dB…" so the model can give contextualised advice +- **Prompt library** — a built-in set of neurofeedback prompt templates + (e.g. "Summarise today's session", "Suggest a relaxation technique", + "Explain what high theta means") is accessible from a `+` button in the + chat input + +### LLM — Downloads + +- **Model download resumption** — interrupted downloads no longer restart + from zero; the downloader uses `Content-Range` byte-range requests to + resume from the last received byte + +### UMAP Viewer + +- **Export PNG / JSON** — "Export PNG" and "Export JSON" buttons added to + the 3D scatter plot toolbar; PNG captures the current WebGL viewport, + JSON exports the full point cloud with labels and timestamps + +### Focus Timer + +- **Session log** — a summary panel shows today's completed Pomodoro cycles, + total focus time, and total break time; entries are labelled and persisted + across restarts + +### Onboarding (0.0.16) + +- **Extended checklist** — onboarding now includes four additional steps: + download an LLM model, run a similarity search, set a DND threshold, and + try the REST API; previous four steps preserved + +### UI / UX + +- **Command Palette — fuzzy scoring** — the palette filter now uses an + fzf-style scored fuzzy algorithm; partial matches are ranked by relevance + instead of simple `includes()` containment +- **Theme — custom accent colour** — a primary-hue colour picker has been + added to Settings → Appearance; the chosen accent is stored in + `~/.skill/theme.json` and applied app-wide; dark / light / system mode + selection is unaffected + +--- diff --git a/changes/releases/0.0.17.md b/changes/releases/0.0.17.md new file mode 100644 index 000000000..f693c9f4a --- /dev/null +++ b/changes/releases/0.0.17.md @@ -0,0 +1,84 @@ +## [0.0.17] — 2026-03-11 + +### UI / Build (0.0.17) + +- **Tailwind Vite parser crash in MarkdownRenderer fixed** — resolved + `[plugin:@tailwindcss/vite:generate:serve] Invalid declaration: Marked` + by refactoring `src/lib/MarkdownRenderer.svelte` to use `marked.parse(...)` + with a local renderer object and removing an empty local `` blocks in `whats-new/+page.svelte` and `UmapViewer3D.svelte`. +- Fixed mmproj crash when the vision projector file is missing on disk — added an `exists()` guard before calling `mtmd_init_from_file` (which can abort/segfault on some platforms instead of returning null); switched from `active_mmproj_path()` to `resolve_mmproj_path(autoload)` so auto-detection works properly; stale paths where the file has been deleted are now filtered out with a warning instead of passed to the C library. +- Fixed app crash after mmproj fails to load — the clip/vision GPU warmup in `MtmdContextParams` (enabled by default) could corrupt Vulkan GPU state when the mmproj file was incompatible with the text model, causing the subsequent text-model warmup decode to abort the process. Disabled the clip warmup at init time (deferred to the first real multimodal request); wired up `no_mmproj_gpu` and `mmproj_n_threads` settings that were defined in `settings.rs` but never passed to the native library; added a file-size sanity check (files < 1 KB are rejected as truncated downloads); wrapped `init_from_file` in `catch_unwind` so a native panic cannot take down the application; improved error messages to include the file path and size for easier diagnostics. +- Fixed Linux mmproj startup crashes caused by unstable mtmd/Vulkan projector initialization paths on some driver stacks: mmproj now defaults to CPU projector init on Linux (while preserving normal text-model GPU offload), and advanced users can explicitly re-enable mmproj GPU init with `SKILL_FORCE_MMPROJ_GPU=1`. +- Fixed stale `mmproj` fallback selection on startup: when the active text model belongs to a known catalog repo, startup now rejects projector paths from a different repo (for example, a 27B projector with a 4B model), logs a clear mismatch warning, and continues in text-only mode without calling mtmd on the incompatible file. +- Fixed Linux WebKit startup abort on Wayland caused by `stacker::maybe_grow` swapping the main-thread stack before JavaScriptCoreGTK initialised. Linux now raises `RLIMIT_STACK` to 64 MiB and runs Tauri on the original main-thread stack; macOS and Windows keep the existing `stacker` path. +- Fixed Linux app auto-close after startup caused by implicit `RunEvent::ExitRequested` handling: implicit exits are now prevented consistently, main window is hidden instead, and only explicit quit paths run full shutdown. +- Fixed intermittent `npm run tauri dev` startup failure on Linux (`scripts/build-espeak-static.sh` exit `141`): replaced a SIGPIPE-prone `ar -t ... | head -1` cache-check pipeline (with `set -o pipefail`) by a safe `mapfile`-based first-object read, preventing false build-script aborts on valid archives. + +### LLM Catalog + +- Added Qwen3.5 27B Claude 4.6 Opus Reasoning Distilled model family (`eugenehp/Qwen3.5-27B-Claude-4.6-Opus-Reasoning-Distilled-GGUF`) to the LLM catalog with 17 quant variants (Q2_K through BF16/F16). +- Added OmniCoder 9B model family (`Tesslate/OmniCoder-9B-GGUF`) to the LLM catalog with 13 quant variants (Q2_K through BF16) — a coding-focused 9B model. diff --git a/changes/releases/0.0.38.md b/changes/releases/0.0.38.md new file mode 100644 index 000000000..a28255617 --- /dev/null +++ b/changes/releases/0.0.38.md @@ -0,0 +1,191 @@ +## [0.0.38] — 2026-03-15 + +### Features + +- **Proactive Hooks**: background EEG monitoring that triggers actions when brain-state matches configured labels. Per-hook scenarios (cognitive/emotional/physical), keyword suggestions, distance threshold, fire history, WebSocket events, and full CLI CRUD. + +- **Hook distance suggestion**: analyses HNSW data to recommend optimal thresholds with percentile visualization. + +- **LLM coding-agent tools**: bash execution, read/write/edit file, web search (DuckDuckGo JSON + HTML fallback), web fetch, search_output for navigating large outputs. Safety approval dialogs for dangerous operations. Context-aware tool calling with automatic history trimming. + +- **Tool-call cards with rich detail views**: expandable cards per tool type with cancel/stop, danger detection, and structured argument display. + +- **Chat session archive (soft-delete)**: archive instead of permanent delete, with restore and permanent-delete from archive section. + +- **Neurable MW75 Neuro headphone support**: Full 12-channel EEG session at 500 Hz. BLE activation + RFCOMM data streaming (behind `mw75-rfcomm` feature flag). Electrode placement guide shows MW75 ear-cup layout with 6 electrodes per ear (FT7/T7/TP7/CP5/P7/C5 left, FT8/T8/TP8/CP6/P8/C6 right). All 12 channels render in the dashboard: signal quality dots, EEG waveforms, spectrogram, and band powers. DSP pipeline processes all active channels. Device presets for Muse (4ch), Ganglion (4ch), and MW75 (12ch) in electrode guides. + +- **Hermes 10-20 electrode labels**: Replaced generic `Ch1`–`Ch8` channel names with standard 10-20 positions (`Fp1`, `Fp2`, `AF3`, `AF4`, `F3`, `F4`, `FC1`, `FC2`) in `skill-constants` and the electrode placement SVG guide. + +- **Hermes V1 EEG headset support**: Added full session support for the Hermes V1 headset (8-channel ADS1299 at 250 Hz, 9-DOF IMU). The `hermes-ble` crate is added to `skill-devices` and re-exported. All data streams over BLE GATT — no RFCOMM needed. BLE scanner recognises devices whose name starts with "Hermes". Session handles EEG (8 channels through DSP pipeline), IMU (accel + gyro → head pose), and packet-drop detection. Dashboard renders 8 channels dynamically with device-specific labels and colours. Electrode placement guide and 3D electrode guide include Hermes V1 tab. Constants added to `skill-constants` (`HERMES_EEG_CHANNELS`, `HERMES_SAMPLE_RATE`, `HERMES_CHANNEL_NAMES`). + +- **Screenshot capture + vision embedding system**: periodic active-window capture with CLIP vision embedding (ONNX) and HNSW index. macOS CoreGraphics FFI, Linux X11/Wayland, Windows GDI. Configurable interval, size, quality, and embedding backend. + +- **OCR text extraction + text embedding**: on-device OCR via `ocrs` crate. Dual HNSW architecture for visual and text similarity search. + +- **Screenshots Settings UI tab**: full configuration with live re-embed progress. + +- **Configurable OCR engine, GPU/CPU toggle**: `ScreenshotConfig` extended with OCR engine, model, and GPU settings. + +### Performance + +- **Unblock main & settings window startup by moving I/O-heavy Tauri commands to async threads**: Converted 12 synchronous `#[tauri::command]` handlers that performed directory scanning, JSON parsing, or SQLite queries on the Tauri IPC executor thread to `async` commands using `tokio::task::spawn_blocking`. This prevents those operations from stalling window rendering and other IPC calls during startup. Affected commands: `list_sessions`, `delete_session`, `list_embedding_sessions` (history_cmds); `get_daily_recording_mins`, `suggest_hook_distances`, `get_hook_log`, `get_hook_log_count`, `list_serial_ports` (settings_cmds); `query_annotations`, `get_recent_labels`, `delete_label`, `update_label`, `get_stale_label_count` (label_cmds). + +- **History view: batch metrics loading with disk cache**: Replaced per-session IPC waterfall (4 concurrent `get_csv_metrics` calls) with a single `get_day_metrics_batch` call that loads all sessions' metrics in one roundtrip. Added persistent `_metrics_cache.json` disk cache next to each session CSV — subsequent loads skip CSV re-parsing entirely. Timeseries payloads are downsampled to ≤360 points on the backend, reducing transfer size for sparklines and heatmaps. Week view also uses a single batch call for all 7 days. Adjacent-day prefetching now batch-loads as well. + +- **Shared text embedder — eliminate 3 redundant model copies (~390 MB RAM saved)**: consolidated four independent `fastembed::TextEmbedding` instances into a single app-wide `Arc>`. + +- **GPU optimization across all model systems**: enabled flash attention and KQV offloading in LLM; GPU-first mmproj loading on Linux; DirectML on Windows and CUDA on Linux for screenshot embeddings. + +- **Async history commands**: Converted `list_session_days`, `list_sessions_for_day`, `get_session_metrics`, `get_session_timeseries`, `get_csv_metrics`, `get_day_metrics_batch`, and `get_sleep_stages` from synchronous Tauri commands to async commands using `tokio::task::spawn_blocking`, preventing UI thread blocking during heavy file I/O and CSV/SQLite parsing. + +### Bugfixes + +- **Heatmap session counts**: `daySessionCounts` now reads from the localStorage day cache to return actual session counts instead of always 1, making month/year heatmap intensity meaningful. + +- **Fix clippy warnings and a11y lint in history page**: Resolved 9 clippy warnings — `let_and_return` in ws_commands.rs, `double_ended_iterator_last` and `collapsible_str_replace` in llm.rs, `iter_cloned_collect` in hermes/mw75 sessions, `needless_borrow` in settings_cmds.rs. Converted label dot `` elements to ` + {/each} + +{/if} diff --git a/src/lib/AppearanceTab.svelte b/src/lib/AppearanceTab.svelte deleted file mode 100644 index 36454da67..000000000 --- a/src/lib/AppearanceTab.svelte +++ /dev/null @@ -1,179 +0,0 @@ - - - - - -
- - -
- - {t("settings.fontSize")} - - - - -

- {t("settings.fontSizeDesc")} -

-
- {#each FONT_SIZE_PRESETS as preset} - - {/each} -
-
-
-
- - -
- - {t("appearance.theme")} - - - - - - -
- {t("appearance.colorMode")} -
- {#each THEME_OPTIONS as opt} - - {/each} -
-
- - -
- -
- -
-
-
- - -
- - {t("appearance.chartColors")} - - - - -

- {t("appearance.chartColorsDesc")} -

- -
- {#each CHART_SCHEMES as scheme (scheme.id)} - - {/each} -
-
-
-
- -
diff --git a/src/lib/BandChart.svelte b/src/lib/BandChart.svelte deleted file mode 100644 index cd10bdd05..000000000 --- a/src/lib/BandChart.svelte +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - -
- -
diff --git a/src/lib/CalibrationTab.svelte b/src/lib/CalibrationTab.svelte deleted file mode 100644 index a848fe13d..000000000 --- a/src/lib/CalibrationTab.svelte +++ /dev/null @@ -1,496 +0,0 @@ - - - - - -
- - - {#if !editing} - -
- {t("calibration.profiles")} - -
- -
- {#each profiles as p} - {@const isActive = p.id === activeId} -
-
- - - - -
- {p.name} -
- {#each p.actions as a} - - {a.label} - - {/each} - - {t("settings.nLoops", { n: p.loop_count })} · {p.break_duration_secs}s {t("calibration.breakLabel")} - -
- {#if p.last_calibration_utc} - - {t("calibration.lastAtAgo", { date: fmtDate(p.last_calibration_utc), ago: timeAgo(p.last_calibration_utc) })} - - {:else} - - {t("calibration.neverCalibrated")} - - {/if} -
- - -
- - {#if profiles.length > 1} - - {/if} -
-
-
- {/each} -
- - -
- - - {activeProfile ? `"${activeProfile.name}"` : ""} - -
- - {:else} - - -
- - - {isNew ? t("calibration.newProfile") : t("calibration.editProfile")} - -
- - -
- - {t("calibration.presets")} - -
- {#each PRESETS as preset} - - {/each} -
-
- - - - - -
- - -
- - -
-
- {t("settings.actionLabels")} - -
- - {#each editing.actions as action, i} -
- -
- - -
- - - -
- {#each [5,10,15,20,30] as secs} - - {/each} -
- - {#if editing.actions.length > 1} - - {/if} -
- {/each} -
- - -
- {t("settings.timing")} -
-
- - {t("settings.breakDurationSecs")} - -
- {#each [3,5,10,15,20] as secs} - - {/each} -
-
-
- - {t("settings.iterations")} - -
- {#each [1,2,3,5,10] as n} - - {/each} -
-
-
-
- - -
- -
- - -
- - {t("settings.summary")} - - {#each editing.actions as action, i} - {@const colors = ["bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20", - "bg-violet-500/10 text-violet-600 dark:text-violet-400 border-violet-500/20", - "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20", - "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20", - "bg-rose-500/10 text-rose-600 dark:text-rose-400 border-rose-500/20"]} - - {action.label || "?"} - - {/each} - - {t("settings.nLoops", { n: editing.loop_count })} - - - {t("settings.totalSecs", { n: totalSecs })} - -
- -
-
- - -
- - -
- - {/if} - -
diff --git a/src/lib/CommandPalette.svelte b/src/lib/CommandPalette.svelte index ed1200709..cf49d6911 100644 --- a/src/lib/CommandPalette.svelte +++ b/src/lib/CommandPalette.svelte @@ -10,250 +10,816 @@ the Free Software Foundation, version 3 only. --> Supports fuzzy text filtering and keyboard navigation. --> {#if open} @@ -271,7 +837,7 @@ the Free Software Foundation, version 3 only. --> class="fixed top-[15%] left-[50%] z-[9999] w-full max-w-[480px] translate-x-[-50%] rounded-2xl border border-border dark:border-white/[0.1] - bg-white dark:bg-[#18181f] shadow-2xl + bg-surface-1 shadow-2xl flex flex-col overflow-hidden" transition:fade={{ duration: 100 }} role="dialog" @@ -292,13 +858,13 @@ the Free Software Foundation, version 3 only. --> onkeydown={handleInputKeydown} type="text" placeholder={t("cmdK.placeholder")} - class="flex-1 bg-transparent text-[0.82rem] text-foreground + class="flex-1 bg-transparent text-ui-lg text-foreground placeholder:text-muted-foreground/50 focus:outline-none" spellcheck="false" autocomplete="off" /> - Esc @@ -306,17 +872,19 @@ the Free Software Foundation, version 3 only. -->
- {#if filtered.length === 0} -

+ {#if scored.length === 0} +

{t("cmdK.noResults")}

{:else} {#each sections as [sectionLabel, cmds], sIdx} -
-

- {sectionLabel} -

-
+ {#if sectionLabel} +
+

+ {sectionLabel} +

+
+ {/if} {#each cmds as cmd, cIdx} {@const fi = flatIndex(sIdx, cIdx)} @@ -334,10 +902,22 @@ the Free Software Foundation, version 3 only. --> aria-selected={fi === active} tabindex="-1" > - {cmd.icon} - {cmd.label} + {cmd.icon} + + {#each highlightSegments(cmd.label, cmd.labelPositions) as seg} + {#if seg.hi}{seg.t}{:else}{seg.t}{/if} + {/each} + + {#if cmd.toggle} + + {cmd.toggle.get() ? "ON" : "OFF"} + + {/if} {#if cmd.shortcut} - {cmd.shortcut} @@ -350,9 +930,12 @@ the Free Software Foundation, version 3 only. -->
+ text-ui-xs text-muted-foreground/50"> ↑↓ {t("cmdK.navigate")} ↵ {t("cmdK.run")} + ⇥ Toggle + @ settings + > commands {t("cmdK.footerHint")}
diff --git a/src/lib/CustomTitleBar.svelte b/src/lib/CustomTitleBar.svelte new file mode 100644 index 000000000..4ee6b75a9 --- /dev/null +++ b/src/lib/CustomTitleBar.svelte @@ -0,0 +1,873 @@ + + + + + + + +{#snippet iconChevronLeft()} + + + +{/snippet} +{#snippet iconChevronRight()} + + + +{/snippet} +{#snippet iconClose()} + + + +{/snippet} +{#snippet iconMaximize()} + + + +{/snippet} +{#snippet iconMinimize()} + + + +{/snippet} + + +{#snippet tbBtn(title: string, label: string, onclick: () => void, iconPath: string, activeClass?: string)} + +{/snippet} + + +{#snippet windowControls(order: "mac" | "win")} +
+ {#if order === "mac"} + + + + {:else} + + + + {/if} +
+{/snippet} + + +{#snippet centerContent()} + {#if isSearchWindow} +
+ {t("search.sourceLabel")} + +
+ {:else if isDownloadsWindow} + +
+ {t("downloads.windowTitle")} + {t("downloads.subtitle")} +
+ {:else if isHistoryWindow} +
+ {@render historyHead()} +
+ {:else if isHelpWindow} + +
+
+ + + {#if helpTitlebarState.query} + + {/if} +
+ v{helpTitlebarState.version} + {t("settings.license")} +
+ {:else if isChatWindow} +
+ + {#if chatTitlebarState.status === 'running' && chatTitlebarState.modelName} + + {:else} + + + {#if chatTitlebarState.status === 'loading'} + {t("chat.status.loading")} + {:else if chatTitlebarState.status === 'running'} + {t("chat.status.running")} + {:else} + {t("chat.status.stopped")} + {/if} + + {/if} +
+ {:else} + +
+ {#if !isMainWindow}{windowTitle}{/if} +
+ {/if} +{/snippet} + + +{#snippet actionButtons()} +
+ {#if isMainWindow} + {@render tbBtn(t("cmdK.openLabel"), t("cmdK.openLabel"), openLabel, + '')} + {:else if isHistoryWindow} + {@render tbBtn( + hBar.compareMode ? t("history.exitCompare") : t("history.compare"), + "Compare", hCbs.toggleCompare, + '', + hBar.compareMode ? 'text-blue-500 bg-blue-500/10' : undefined + )} + {#if hBar.compareMode && hBar.compareCount >= 2} + + {/if} + {@render tbBtn(t("history.labels"), t("history.labels"), hCbs.toggleLabels, + '', + hBar.showLabels ? 'text-amber-500 bg-amber-500/10' : undefined + )} + {@render tbBtn(t("common.reload"), t("common.reload"), hCbs.reload, + '')} + {:else if isSettingsWindow} + {@render tbBtn(t("settingsTabs.help"), t("settingsTabs.help"), openHelp, + '')} + {:else if isApiWindow} + {@render tbBtn(t("apiStatus.refresh"), t("apiStatus.refresh"), emitApiRefresh, + '')} + {/if} + + + +
+{/snippet} + + +{#snippet historyHead()} +
+ {#each HISTORY_VIEW_MODES as mode} + + {/each} +
+ + {#if hBar.viewMode === "day"} +
+ + + {#if hBar.daysLoading}{:else}{hBar.currentDayLabel || "—"}{/if} + + + {#if !hBar.daysLoading && hBar.dayCount > 0} + {hBar.currentDayIdx + 1}/{hBar.dayCount} + {/if} +
+ {:else} +
+ + {hBar.calendarLabel || "—"} + +
+ {/if} +{/snippet} + + + + +
+ {#if isMac} + {@render windowControls("mac")} + {@render centerContent()} + +
+ {@render actionButtons()} + {:else} + {#if isMainWindow} + {@render actionButtons()} + {/if} + {@render centerContent()} + +
+ {#if !isMainWindow} + {@render actionButtons()} + {/if} + {@render windowControls("win")} + {/if} + + {#if isLabelWindow && labelTitlebarState.active} + +
+ {t("label.eegWindow", { elapsed: labelTitlebarState.elapsed })} +
+ {/if} +
+ + +{#if modelPickerOpen && downloadedModels.length > 0} + + +
e.stopPropagation()}> + {#each pickerGroups as group} + {#if pickerGroups.length > 1} +
{group.family}
+ {/if} + {#each group.entries as entry} + {@const isActive = entry.filename === activeFilename} + {@const hasVision = visionRepos.has(entry.repo)} + + {/each} + {/each} +
+{/if} + + diff --git a/src/lib/DisclaimerFooter.svelte b/src/lib/DisclaimerFooter.svelte index fa68b32d1..7c48306f5 100644 --- a/src/lib/DisclaimerFooter.svelte +++ b/src/lib/DisclaimerFooter.svelte @@ -6,20 +6,20 @@ it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. -->
-

+

{t("disclaimer.copyright", { year: new Date().getFullYear() })}

@@ -31,24 +31,24 @@ the Free Software Foundation, version 3 only. --> role="presentation" onclick={(e) => { if (e.target === e.currentTarget) open = false; }} onkeydown={(e) => { if (e.key === "Escape") open = false; }}> -
⚠️ - + {t("disclaimer.title")}
-

+

{t("disclaimer.body")}

-

+

{t("disclaimer.nonCommercial")}

diff --git a/src/lib/EegModelTab.svelte b/src/lib/EegModelTab.svelte deleted file mode 100644 index c3a172d7b..000000000 --- a/src/lib/EegModelTab.svelte +++ /dev/null @@ -1,537 +0,0 @@ - - - - - - -
-
- - {t("model.encoder")} - - - {#if isDownloading} - - - {:else if modelStatus.encoder_loaded} - - {:else if needsRestart} - - {:else} - - {/if} -
- - - - - - {#if modelStatus.encoder_loaded} -
-
- {t("model.zunaEncoder")} - {#if modelStatus.encoder_describe} - - {modelStatus.encoder_describe} - - {/if} -
- - {t("model.ready")} - -
- - - {:else if isDownloading} -
-
-
- {t("model.zunaEncoder")} - - {modelStatus.download_status_msg ?? t("model.downloading")} - -
- - {t("model.downloading")} - -
- - -
-
-
- - - {#if modelStatus.download_status_msg} -

- {modelStatus.download_status_msg} -

- {/if} - - -
- -
-
- - - {:else if isAutoRetrying} -
-
-
- {t("model.zunaEncoder")} - - {t("model.autoRetryIn", { secs: String(modelStatus.download_retry_in_secs) })} - · {t("model.autoRetryAttempt", { n: String(modelStatus.download_retry_attempt + 1) })} - -
- - {t("model.unavailable")} - -
- {#if modelStatus.download_status_msg && modelStatus.download_status_msg !== "Download cancelled."} -

- {modelStatus.download_status_msg} -

- {/if} -
-
- - - - - - {modelStatus.download_retry_in_secs} - -
-

- {t("model.downloadFailed")} — {t("model.autoRetryIn", { secs: String(modelStatus.download_retry_in_secs) })} -

-
-
- - -
-
- - - {:else if hasFailed} -
-
-
- {t("model.zunaEncoder")} - - {t("model.downloadFailed")} - -
- - {t("model.unavailable")} - -
- - {#if modelStatus.download_status_msg} -

- {modelStatus.download_status_msg} -

- {/if} -
- -
-
- - - {:else if wasCancelled} -
-
- {t("model.zunaEncoder")} - {t("model.downloadCancelled")} -
- -
- - - {:else if needsRestart} -
-
-
- {t("model.zunaEncoder")} - - {t("model.restartToLoad")} - -
- - {t("model.ready")} - -
- -
-
-
-
- -
-
- - - {:else if encoderLoading} -
-
-
- {t("model.zunaEncoder")} - {t("model.encoderLoading")} -
- - {t("common.loading")} - -
-
-
-
-
- - - {:else} -
-
-
- {t("model.zunaEncoder")} - - {t("model.notFoundInCache")} - -
- - {t("model.unavailable")} - -
- -
-
- - {modelConfig.hf_repo} - -
- -
-
- {/if} - - - {#if modelStatus.weights_path} -
- {t("model.weightsPath")} - - {modelStatus.weights_path} - -
- {/if} - - -
-
- - {t("model.todaysDb")} - - - {modelStatus.daily_db_path || "—"} - -
-
- - {t("model.embeddingsToday")} - - - {modelStatus.embeddings_today} - -
-
- -
-
-
- - -
-
- - {t("model.hnswIndex")} - - {#if modelConfigSaving} - {t("common.saving")} - {/if} - {t("model.takesEffectRestart")} -
- - - - - -
-
- - {t("model.connectivity")} M - - {t("model.currently", { n: modelConfig.hnsw_m })} -
-

- {t("model.connectivityDesc")} -

-
- {#each HNSW_M_PRESETS as m} - - {/each} -
-
- - -
-
- - {t("model.buildQuality")} ef - - {t("model.currently", { n: modelConfig.hnsw_ef_construction })} -
-

- {t("model.buildQualityDesc")} -

-
- {#each HNSW_EF_PRESETS as ef} - - {/each} -
-
- - -
- {t("model.index")} - - M = {modelConfig.hnsw_m} - - - ef = {modelConfig.hnsw_ef_construction} - - - {t("model.cosineDistance")} - -
- -
-
-
- - -
- - {t("model.modelSource")} - - - - - -
- {t("model.hfRepo")} - {modelConfig.hf_repo} -
- -
-
- {t("model.dataNormalisation")} - {t("model.dataNormDesc")} -
- {modelConfig.data_norm} -
- -
-
-
diff --git a/src/lib/ElectrodeGuide.svelte b/src/lib/ElectrodeGuide.svelte deleted file mode 100644 index 63e6a9bd5..000000000 --- a/src/lib/ElectrodeGuide.svelte +++ /dev/null @@ -1,308 +0,0 @@ - - - - - -
- - -
- {#each TABS as tab} - - {/each} - {#if activeTab !== "muse"} - - {getElectrodes(activeTab as ElectrodeSystem).length} electrodes - - {/if} -
- - - {#if activeTab === "muse"} -
- {#each museChannels as name, idx} - {@const q = effectiveQuality ? effectiveQuality[idx] : 0} - {@const label = effectiveLabels[idx]} - {@const el = visible.find(e => e.name === name)} - - {/each} -
- {:else} - -
- Muse signal - {#each museChannels as name, idx} - {@const q = effectiveQuality ? effectiveQuality[idx] : 0} - {@const label = effectiveLabels[idx]} -
- - {name} -
- {/each} -
- {/if} - - - - -
- {#if Head3D} - - - - {:else} -
- Loading 3D view… -
- {/if} - - -
- {#each Object.entries(regionColors) as [region, color]} - {#if region !== "reference"} - - - {regionLabels[region as BrainRegion]} - - {/if} - {/each} -
- - -
- Drag to rotate · Click electrode -
-
- - - {#if selectedElectrode} - {@const el = selectedElectrode} -
-
- - {el.name} - {#if el.muse} - Muse - {/if} - {regionLabels[el.region]} - {#if el.muse && effectiveQuality} - {@const chIdx = museChannels.indexOf(el.name)} - {#if chIdx >= 0} - {@const q = effectiveQuality[chIdx]} - - Signal: {labelToText(effectiveLabels[chIdx])} - - {/if} - {/if} - -
-
-
- Position -

{el.lobe} — {el.function}

-
-
- Signals -

{el.signals}

-
- {#if el.museRole} -
- Muse Role -

{el.museRole}

-
- {/if} -
-
- {/if} -
diff --git a/src/lib/ElectrodeHead3D.svelte b/src/lib/ElectrodeHead3D.svelte deleted file mode 100644 index af1ca68bb..000000000 --- a/src/lib/ElectrodeHead3D.svelte +++ /dev/null @@ -1,459 +0,0 @@ - - - - - ref?.lookAt(0, 0, 0)} -> - - - - - - - - - - { group = ref; }}> - {#if gltfScene} - - {/if} - - - - {#if modelLoaded} - - {/if} - - - - {#if selRing} - { - if (selRing) { - const target = selRing.position.clone().add(selRing.normal); - ref.lookAt(target.x, target.y, target.z); - } - }} - /> - {/if} - - diff --git a/src/lib/ElectrodePlacement.svelte b/src/lib/ElectrodePlacement.svelte deleted file mode 100644 index 3f508ff84..000000000 --- a/src/lib/ElectrodePlacement.svelte +++ /dev/null @@ -1,174 +0,0 @@ - - - - - - diff --git a/src/lib/EmbeddingsTab.svelte b/src/lib/EmbeddingsTab.svelte deleted file mode 100644 index 7afd39255..000000000 --- a/src/lib/EmbeddingsTab.svelte +++ /dev/null @@ -1,215 +0,0 @@ - - - - -
- - -
-
- - {t("embeddings.model")} - - -
- - - - - {#if activeModel} -
-
-
- - {activeModel.code} - - - {activeModel.dim}d - -
-

- {activeModel.description} -

-
-
- {/if} -
- - -
- - - - - -

- {t("embeddings.info")} -

-
- - -
-
-
-
- - {t("embeddings.reembed")} - - {#if staleCount > 0} - - {staleCount} {t("embeddings.stale")} - - {/if} -
- - {t("embeddings.reembedDesc")} - -
- -
- - - {#if progress !== null} - {@const pct = progress.total > 0 ? Math.round(progress.done / progress.total * 100) : 0} -
-
-
-
- - {progress.done} / {progress.total} — {pct}% - -
- {/if} -
- - -
- - {t("embeddings.dimLegend")} - - {#each [[384,"≤384d","emerald"],[768,"≤768d","blue"],[1024,"≤1024d","violet"]] as [,label,color]} - - {label} - - {/each} - {t("embeddings.dimHint")} -
- -
diff --git a/src/lib/GoalsTab.svelte b/src/lib/GoalsTab.svelte deleted file mode 100644 index 48e9819b9..000000000 --- a/src/lib/GoalsTab.svelte +++ /dev/null @@ -1,848 +0,0 @@ - - - - - -
- - -
-
- 🎯 -
-
- {t("goals.title")} - - {t("goals.subtitle")} - -
- -
- - {dailyGoalMin}m - - - {goalHours >= 1 ? `${goalHours.toFixed(1)} hours` : `${dailyGoalMin} minutes`} / day - - {#if streak > 0} - - 🔥 {streak}d streak - - {/if} -
-
- - - - - -
-
- {t("goals.targetMinutes")} - {dailyGoalMin}m -
- -
- 5m - 1h - 2h - 4h - 8h -
-
- - -
- - {t("goals.presets")} - -
- {#each PRESETS as [label, val]} - - {/each} -
-
- -
-
- - - - - -
- {t("goals.chartTitle")} - {#if loading} - {t("common.loading")} - {/if} -
- - {#if chartDays.length} - -
- -
- - {fmtMins(dailyGoalMin)} - -
- - -
- {#each chartDays as day, i} - {@const pct = Math.min(100, (day.minutes / chartMax) * 100)} - {@const color = barColor(day.minutes)} - {@const isToday = i === chartDays.length - 1} -
- - {#if day.minutes > 0} -
- - {#if isToday} -
- {/if} -
- {:else} -
- {/if} - -
- {day.label} -
{fmtMins(day.minutes)} - {#if day.minutes >= dailyGoalMin} ✓{/if} -
-
- {/each} -
-
- - -
- {chartDays[0]?.label ?? ""} - {chartDays[Math.floor(chartDays.length / 2)]?.label ?? ""} - {t("goals.today")} -
- - -
- - - {t("goals.legendGoalMet")} - - - - {t("goals.legendHalfway")} - - - - {t("goals.legendSomeProgress")} - -
- {:else if !loading} -

- {t("goals.noData")} -

- {/if} - -
-
- - -
- - {t("goals.howItWorks")} - -
    -
  • - 📊 - {t("goals.info1")} -
  • -
  • - 🔔 - {t("goals.info2")} -
  • -
  • - 🔥 - {t("goals.info3")} -
  • -
-
- - -
- -
- - {t("dnd.section")} - - - {#if dndConfig.enabled} - - {dndActive ? t("dnd.statusActive") : t("dnd.statusInactive")} - - {/if} - - {#if dndOsActive !== null} - - {dndOsActive ? "System: ON" : "System: OFF"} - - {/if} - {#if dndSaving} - saving… - {/if} -
- - -
- - - - - {#if dndConfig.enabled} - -
-
- - {t("dnd.threshold")} (engagement) - - - {Math.round(dndConfig.focus_threshold)} - -
-

- {t("dnd.thresholdDesc")} -

- setDndThreshold(Number((e.currentTarget as HTMLInputElement).value))} - class="w-full accent-violet-500 h-2" /> -
- 10 - 40 - 60 - 80 - 95 -
-
- - -
-
- - {t("dnd.duration")} - - - {dndConfig.duration_secs}s - -
-

- {t("dnd.durationDesc")} -

-
- {#each DND_DURATION_PRESETS as [label, secs]} - - {/each} -
-
- - -
-
- - {t("dnd.exitDuration")} - - - {t("dnd.exitDurationValue", { min: String(Math.round(dndConfig.exit_duration_secs / 60)) })} - -
-

- {t("dnd.exitDurationDesc")} -

-
- {#each DND_EXIT_PRESETS as [label, secs]} - - {/each} -
-
- - -
-
- - {t("dnd.focusLookback")} - - - {dndConfig.focus_lookback_secs >= 60 - ? t("dnd.focusLookbackValue_min", { min: String(Math.round(dndConfig.focus_lookback_secs / 60)) }) - : t("dnd.focusLookbackValue", { secs: String(dndConfig.focus_lookback_secs) })} - -
-

- {t("dnd.focusLookbackDesc")} -

-
- {#each DND_LOOKBACK_PRESETS as [label, secs]} - - {/each} -
-
- - - {#if focusModes.length > 0} -
-
- - {t("dnd.focusMode")} - - {#if !focusModesLoaded} - {t("dnd.focusModeLoading")} - {/if} -
-

- {t("dnd.focusModeDesc")} -

-
- {#each focusModes as mode} - - {/each} -
-
- {/if} - - - - - - - {@const isHeld = dndActive && dndExitHeldByLookback} - {@const isCounting = dndActive && !dndExitHeldByLookback && dndExitSecsRemain > 0} - {@const isActive = dndActive && !dndExitHeldByLookback && dndExitSecsRemain === 0} - - -
- - - {#if isHeld} - - - {:else if isCounting} - - - {:else if isActive} - - - {:else} - - {/if} - - - - - {#if isHeld} - {t("dnd.exitHeld", { ago: String(dndConfig.focus_lookback_secs) })} - {:else if isCounting} - {t("dnd.exitingFocusMode")} - {:else} - {dndActive ? t("dnd.statusActive") : t("dnd.statusInactive")} - {/if} - - - -
- - {focusModes.find(m => m.identifier === dndConfig.focus_mode_identifier)?.name ?? "Do Not Disturb"} - · ≥{Math.round(dndConfig.focus_threshold)} for {dndConfig.duration_secs}s - - {#if dndOsActive !== null} - - {#if dndOsActive && !dndActive} - ⚠ System Focus active (set externally) - {:else} - System: {dndOsActive ? "ON" : "OFF"} - {/if} - - {/if} -
-
- - - {#if isCounting || isHeld} - {@const totalSecs = dndConfig.exit_duration_secs} - {@const elapsedSecs = isCounting ? totalSecs - dndExitSecsRemain : 0} - {@const pct = isCounting ? Math.min(100, (elapsedSecs / totalSecs) * 100) : 0} - {@const mm = isCounting ? Math.floor(dndExitSecsRemain / 60) : 0} - {@const ss = isCounting ? dndExitSecsRemain % 60 : 0} -
- - -
- {#if isCounting} - - {String(mm).padStart(2, "0")}:{String(ss).padStart(2, "0")} - - - {t("dnd.untilExit")} - - {:else} - - - {t("dnd.exitHeld", { ago: String(dndConfig.focus_lookback_secs) })} - - {/if} -
- - -
- {#if isCounting} -
- {:else} - -
- {/if} -
- - -
- 0s - {#if totalSecs >= 120} - {Math.round(totalSecs / 2)}s - {/if} - {totalSecs}s -
-
- {/if} - {/if} - - - {#if dndActive} -
- {t("dnd.forceOff")} - -
- {/if} - -
-
- - -

- 🍎 - {t("dnd.requiresMacOS")} -

-
- -
diff --git a/src/lib/GpuBar.svelte b/src/lib/GpuBar.svelte deleted file mode 100644 index 4504bd26f..000000000 --- a/src/lib/GpuBar.svelte +++ /dev/null @@ -1,118 +0,0 @@ - - - - - -{#if gpuStats !== null} -
- - - GPU - - - - {#if spark} - - - - - {/if} - - -
-
-
-
- - - - {(gpuStats.overall * 100).toFixed(0)}% - -
-{/if} diff --git a/src/lib/GpuChart.svelte b/src/lib/GpuChart.svelte deleted file mode 100644 index 25fbd388b..000000000 --- a/src/lib/GpuChart.svelte +++ /dev/null @@ -1,297 +0,0 @@ - - - - - -{#if visible} -
- -
- {t("gpu.title")} - - - {t("gpu.render")} {(gpuStats!.render * 100).toFixed(0)}% · {t("gpu.tiler")} {(gpuStats!.tiler * 100).toFixed(0)}% - - - {(gpuStats!.overall * 100).toFixed(0)}% - - - -
- - {#if !collapsed} -
- -
- {/if} - -
-{/if} - - diff --git a/src/lib/HelpFaq.svelte b/src/lib/HelpFaq.svelte deleted file mode 100644 index 3eb542872..000000000 --- a/src/lib/HelpFaq.svelte +++ /dev/null @@ -1,222 +0,0 @@ - - - - - -
- - -
-

{t("helpOld.trayIconStates")}

-

{t("helpOld.trayIconDesc")}

-
- {#each [ - { dot:"bg-slate-400", labelKey:"helpOld.greyDisconnected", descKey:"helpOld.greyDesc" }, - { dot:"bg-yellow-400 animate-spin", labelKey:"helpOld.spinningScanning", descKey:"helpOld.spinningDesc" }, - { dot:"bg-green-500", labelKey:"helpOld.greenConnected", descKey:"helpOld.greenDesc" }, - { dot:"bg-red-500", labelKey:"helpOld.redBtOff", descKey:"helpOld.redDesc" }, - ] as row} -
- -
- {t(row.labelKey)} - {t(row.descKey)} -
-
- {/each} -
-
- - -
-

{t("helpOld.btLifecycle")}

-

{t("helpOld.btLifecycleDesc")}

-
-
    - {#each [ - ["helpOld.btStep1", "helpOld.btStep1Desc"], - ["helpOld.btStep2", "helpOld.btStep2Desc"], - ["helpOld.btStep3", "helpOld.btStep3Desc"], - ["helpOld.btStep4", "helpOld.btStep4Desc"], - ["helpOld.btStep5", "helpOld.btStep5Desc"], - ] as [stepKey, descKey], i} -
  1. - {i + 1} -
    - {t(stepKey)} - {t(descKey)} -
    -
  2. - {/each} -
-
-
- - -
-

{t("helpOld.examples")}

-
- - {#each [ - { - titleKey: "helpOld.example1Title", - steps: [ - { color: "text-slate-400", icon: "⬤", textKey: "helpOld.ex1Step1" }, - { color: "text-yellow-400", icon: "⟳", textKey: "helpOld.ex1Step2" }, - { color: "text-green-500", icon: "⬤", textKey: "helpOld.ex1Step3" }, - ], - }, - { - titleKey: "helpOld.example2Title", - steps: [ - { color: "text-green-500", icon: "⬤", textKey: "helpOld.ex2Step1" }, - { color: "text-red-500", icon: "⬤", textKey: "helpOld.ex2Step2" }, - { color: "text-slate-400", icon: " ", textKey: "helpOld.ex2Step3", muted: true }, - { color: "text-yellow-400", icon: "⟳", textKey: "helpOld.ex2Step4" }, - { color: "text-green-500", icon: "⬤", textKey: "helpOld.ex2Step5" }, - ], - }, - { - titleKey: "helpOld.example3Title", - steps: [ - { color: "text-yellow-400", icon: "⟳", textKey: "helpOld.ex3Step1" }, - { color: "text-slate-400", icon: " ", textKey: "helpOld.ex3Step2", muted: true }, - { color: "text-yellow-400", icon: "⟳", textKey: "helpOld.ex3Step3" }, - { color: "text-green-500", icon: "⬤", textKey: "helpOld.ex3Step4" }, - ], - }, - ] as ex} -
-

- {t(ex.titleKey)} -

-
- {#each ex.steps as s} -
- {s.icon} - {t(s.textKey)} -
- {/each} -
-
- {/each} - -
-
- - -
-

{t("helpOld.wsTitle")}

-

{t("helpOld.wsDesc")}

-
-

- {t("helpOld.discoverService")} -

-
# macOS
-dns-sd -B _skill._tcp
-
-# Linux
-avahi-browse _skill._tcp
-

- {t("helpOld.broadcastEvents")} -

-
{`{
-  "event":   "eeg-bands" | "muse-status" | "label-created",
-  "payload": { … }
-}`}
-

- {t("helpOld.commands")} -

-
{`status     — device state, scores, sleep summary
-label      — attach a label to the current moment
-search     — find nearest EEG embeddings
-sessions   — list all recording sessions
-compare    — full comparison: metrics, sleep, UMAP
-sleep      — classify sleep stages for a time range
-umap       — enqueue 3D UMAP projection (non-blocking)
-umap_poll  — poll for UMAP result by job_id`}
-
-
- - -
-

{t("helpOld.faq")}

-
- {#each [ - ["helpOld.faqQ1", "helpOld.faqA1"], - ["helpOld.faqQ2", "helpOld.faqA2"], - ["helpOld.faqQ3", "helpOld.faqA3"], - ["helpOld.faqQ4", "helpOld.faqA4"], - ["helpOld.faqQ5", "helpOld.faqA5"], - ["helpOld.faqQ6", "helpOld.faqA6"], - ["helpOld.faqQ7", "helpOld.faqA7"], - ["helpOld.faqQ8", "helpOld.faqA8"], - ["helpOld.faqQ9", "helpOld.faqA9"], - ["helpOld.faqQ10", "helpOld.faqA10"], - ["helpOld.faqQ11", "helpOld.faqA11"], - ["helpOld.faqQ12", "helpOld.faqA12"], - ["helpOld.faqQ13", "helpOld.faqA13"], - ["helpOld.faqQ14", "helpOld.faqA14"], - ["helpOld.faqQ15", "helpOld.faqA15"], - ["helpOld.faqQ16", "helpOld.faqA16"], - ["helpOld.faqQ17", "helpOld.faqA17"], - ["helpOld.faqQ18", "helpOld.faqA18"], - ["helpOld.faqQ19", "helpOld.faqA19"], - ] as [qKey, aKey]} -
- - {t(qKey)} - - - - -

- {t(aKey)} -

-
- {/each} -
-
- -
- - diff --git a/src/lib/Hypnogram.svelte b/src/lib/Hypnogram.svelte deleted file mode 100644 index e1e9fc5b1..000000000 --- a/src/lib/Hypnogram.svelte +++ /dev/null @@ -1,138 +0,0 @@ - - - - - -
- - - - -
- {#each STAGES as s} - {@const count = s.stage === 0 ? summary.wake_epochs - : s.stage === 5 ? summary.rem_epochs - : s.stage === 1 ? summary.n1_epochs - : s.stage === 2 ? summary.n2_epochs - : summary.n3_epochs} -
- - {t(s.key)} - - {stagePct(count)}% · {stageDur(count)} - -
- {/each} -
-
diff --git a/src/lib/InteractiveGraph3D.svelte b/src/lib/InteractiveGraph3D.svelte deleted file mode 100644 index 5f000efa9..000000000 --- a/src/lib/InteractiveGraph3D.svelte +++ /dev/null @@ -1,750 +0,0 @@ - - - - - - -
{ tooltip = null; hoveredNodeId = null; }} - role="img" aria-label="Interactive search 3D graph"> - - - {#if tooltip} -
- {#each tooltip.lines as line, li} -
- {line} -
- {/each} -
- {/if} - - - {#if selectedNodeId !== null} -
- click node again or click empty space to deselect -
- {/if} - - - {#if !loaded} -
-
-
- {/if} -
- - -
- - -
- {#each LEGEND_BASE as l} -
- - {l.label} -
- {/each} -
- - {foundLabelLegend} -
- · - {#each EDGE_LEGEND as l} -
- - {l.label} -
- {/each} - - hover · click to highlight · click again to clear - -
- - - {#if eegDots.length > 0} -
- - EEG node time scale - - - - - {#each Array.from({ length:12 }, (_,i) => i/11) as t} - - {/each} - - - - {#each eegDots as dot} - {@const x = dot.t * 500} - - - {/each} - {#each eegTicks as tick} - {@const x = tick.t * 500} - - - {tick.label} - - {/each} - - - {eegDots.length} EEG point{eegDots.length !== 1 ? "s" : ""} · - {fmtTs(eegTimeMin)} → {fmtTs(eegTimeMax)} - -
- {:else} -
- EEG nodes colored by session time (turbo gradient) -
- {/if} -
diff --git a/src/lib/KeyboardShortcuts.svelte b/src/lib/KeyboardShortcuts.svelte index 83fda2d16..aebc8730a 100644 --- a/src/lib/KeyboardShortcuts.svelte +++ b/src/lib/KeyboardShortcuts.svelte @@ -11,130 +11,164 @@ the Free Software Foundation, version 3 only. --> in-app keyboard shortcuts hardcoded in the frontend. --> {#if open} @@ -152,7 +186,7 @@ the Free Software Foundation, version 3 only. --> class="fixed top-[50%] left-[50%] z-[9999] w-full max-w-[420px] translate-x-[-50%] translate-y-[-50%] rounded-2xl border border-border dark:border-white/[0.08] - bg-white dark:bg-[#16161e] shadow-2xl + bg-surface-1 shadow-2xl p-5 flex flex-col gap-4" transition:fade={{ duration: 150 }} role="dialog" @@ -162,7 +196,7 @@ the Free Software Foundation, version 3 only. --> >
-

+

{t("shortcuts.title")}

diff --git a/src/lib/MarkdownRenderer.svelte b/src/lib/MarkdownRenderer.svelte new file mode 100644 index 000000000..4312f6685 --- /dev/null +++ b/src/lib/MarkdownRenderer.svelte @@ -0,0 +1,61 @@ + + + + + + +
+ {@html html} +
diff --git a/src/lib/OnboardingChecklist.svelte b/src/lib/OnboardingChecklist.svelte new file mode 100644 index 000000000..c8d3c7cb3 --- /dev/null +++ b/src/lib/OnboardingChecklist.svelte @@ -0,0 +1,85 @@ + + + + + +
+
+ + {t("dashboard.gettingStarted")} + + + {doneCount}/{steps.length} + +
+ +
+
+
+
+
+ {#each steps as step (step.key)} + {#if step.done || !onStepClick} +
+
+ {#if step.done} + + + + {/if} +
+ + {step.label} + +
+ {:else} + + {/if} + {/each} +
+ +
diff --git a/src/lib/PairBrowserModal.svelte b/src/lib/PairBrowserModal.svelte new file mode 100644 index 000000000..7ffc123ac --- /dev/null +++ b/src/lib/PairBrowserModal.svelte @@ -0,0 +1,114 @@ + + + + + + +{#if visible} +
+
+
+ + + + + +

Pair browser extension

+
+ +

+ {clientName} wants to connect to NeuroSkill + and start tracking browsing activity for EEG correlation. +

+ +
+
• Domain & content type tracking (privacy-respecting)
+
• Brain state correlation (focus, distraction)
+
• You can revoke access anytime in Settings → Extensions
+
+ +
+ + +
+
+
+{/if} diff --git a/src/lib/PermissionsTab.svelte b/src/lib/PermissionsTab.svelte deleted file mode 100644 index b252fac58..000000000 --- a/src/lib/PermissionsTab.svelte +++ /dev/null @@ -1,322 +0,0 @@ - - - - - -
- - -
-

- {t("perm.intro")} -

-
- - - {#if isMac} -
- - {t("perm.accessibility")} - - - - - -
-
- ⌨️ - {t("perm.accessibility")} -
-
- - - {statusLabel(accessStatus)} - - -
-
- - -

- {t("perm.accessibilityDesc")} -

- - {#if accessStatus === "denied"} - -
- {t("perm.howToGrant")} -
    -
  1. {t("perm.accessStep1")}
  2. -
  3. {t("perm.accessStep2")}
  4. -
  5. {t("perm.accessStep3")}
  6. -
  7. {t("perm.accessStep4")}
  8. -
-
- {:else if accessStatus === "granted"} -

- {t("perm.accessibilityOk")} -

- {:else} -

- {t("perm.accessibilityPending")} -

- {/if} - -
- -
- -
-
-
- {/if} - - -
- - {t("perm.bluetooth")} - - - - -
-
- 📶 - {t("perm.bluetooth")} -
- - - {t("perm.systemManaged")} - -
- -

- {t("perm.bluetoothDesc")} -

- - {#if isMac} -
- -
- {/if} - -
-
-
- - -
- - {t("perm.notifications")} - - - - -
-
- 🔔 - {t("perm.notifications")} -
- - - {t("perm.systemManaged")} - -
- -

- {t("perm.notificationsDesc")} -

- -
- -
- -
-
-
- - -
- - {t("perm.matrix")} - - - - - - - - - - - - - - {#each [ - [t("perm.matrixBluetooth"), "✅ " + t("perm.matrixNone"), "✅ " + t("perm.matrixNone"), "✅ " + t("perm.matrixNone")], - [t("perm.matrixKeyboardMouse"), "🔑 " + t("perm.matrixAccessibility"), "✅ libxtst", "✅ " + t("perm.matrixNone")], - [t("perm.matrixActiveWindow"), "✅ " + t("perm.matrixNone"), "✅ xdotool", "✅ " + t("perm.matrixNone")], - [t("perm.matrixNotifications"), "⚙️ " + t("perm.matrixOsPrompt"), "✅ " + t("perm.matrixNone"), "⚙️ " + t("perm.matrixOsPrompt")], - ] as [feat, mac, linux, win]} - - - - - - - {/each} - -
{t("perm.feature")}macOSLinuxWindows
{feat}{mac}{linux}{win}
-
- ✅ {t("perm.legendNone")} - 🔑 {t("perm.legendRequired")} - ⚙️ {t("perm.legendPrompt")} -
-
-
-
- - -
- - {t("perm.why")} - - - -
-

🔵 {t("perm.whyBluetooth")} — {t("perm.whyBluetoothDesc")}

-

⌨️ {t("perm.whyAccessibility")} — {t("perm.whyAccessibilityDesc")}

-

🔔 {t("perm.whyNotifications")} — {t("perm.whyNotificationsDesc")}

-

{t("perm.privacyNote")}

-
-
-
-
- -
diff --git a/src/lib/SettingsTab.svelte b/src/lib/SettingsTab.svelte deleted file mode 100644 index ee14d46b2..000000000 --- a/src/lib/SettingsTab.svelte +++ /dev/null @@ -1,1433 +0,0 @@ - - - - - - -
- - {t("settings.museDevices")} - - - - {#if hasNewUnpaired} -
- 📡 -
- - {t("settings.newDeviceNotice")} - - - {t("settings.newDeviceNoticeHint")} - -
-
- {/if} - - - {#if devices.length === 0} - - 📡 -

{t("settings.noDevices")}

-

- {t("settings.powerOnMuse")} -

-
- {:else} - {#each devices as dev, i (dev.id)} - {#if i > 0}{/if} - -
- - - {#if museImage(dev.name, dev.hardware_version)} - {dev.name} - {:else} -
🧠
- {/if} - -
-
- {dev.name} - {#if dev.is_preferred} - ★ - {/if} - {#if dev.is_paired} - - {t("settings.paired")} - - {:else if dev.last_rssi !== 0} - - {t("settings.new")} - - {/if} -
- -
- {dev.id} - {#if dev.last_rssi !== 0} - - {fmtRssi(dev.last_rssi)} - - {/if} - - {fmtLastSeen(dev.last_seen)} - -
- - - {#if !dev.is_paired} - - {t("settings.pairToConnect")} - - {/if} - - {#if dev.id === connected.device_id && (connected.serial_number || connected.mac_address)} -
- {#if connected.serial_number} - - {/if} - {#if connected.mac_address} - - {/if} -
- {/if} -
- -
- {#if dev.is_paired} - - - - {:else} - - - {/if} -
-
- {/each} - {/if} -
-
- - -
- - - {#if openbciExpanded} -

- {t("settings.openbciDesc")} -

- - - - - -
-

{t("settings.openbciBoard")}

-
- {#snippet boardRadio(val: OpenBciBoard, key: string)} - - {/snippet} - {@render boardRadio("ganglion", "settings.openbciBoardGanglion")} - {@render boardRadio("ganglion_wifi", "settings.openbciBoardGanglionWifi")} - {@render boardRadio("cyton", "settings.openbciBoardCyton")} - {@render boardRadio("cyton_wifi", "settings.openbciBoardCytonWifi")} - {@render boardRadio("cyton_daisy", "settings.openbciBoardCytonDaisy")} - {@render boardRadio("cyton_daisy_wifi", "settings.openbciBoardCytonDaisyWifi")} - {@render boardRadio("galea", "settings.openbciBoardGalea")} -
- - - {#if OPENBCI_IMAGES[openbci.board]} -
- {t(`settings.openbciBoard${openbci.board.replace(/_([a-z])/g, c.toUpperCase()).replace(/^./, s => s.toUpperCase())}`) || openbci.board} - class="h-36 max-w-full object-contain rounded-xl - bg-muted/30 dark:bg-white/[0.03] p-2 - border border-border dark:border-white/[0.06] - transition-all duration-200" /> -
- {/if} -
- - - - - {#if isBle} -
- - { openbciChanged = true; }} - class="w-16 text-[0.73rem] text-center px-2 py-1 rounded-md border border-border - bg-background text-foreground tabular-nums" /> - {t("settings.openbciScanTimeoutSuffix")} -
- - {/if} - - - {#if isSerial} -
-

{t("settings.openbciSerialPort")}

-
- {#if serialPorts.length > 0} - - {:else} - { openbciChanged = true; }} - placeholder={t("settings.openbciSerialPortPlaceholder")} - class="flex-1 min-w-0 text-[0.73rem] font-mono px-2 py-1 rounded-md border border-border bg-background text-foreground" /> - {/if} - -
- {t("settings.openbciSerialPortHint")} -
- - {/if} - - - {#if isWifi} -
- -
- OpenBCI WiFi Shield -

{t("settings.openbciWifiShieldIp")}

-
- { openbciChanged = true; }} - placeholder={t("settings.openbciWifiShieldIpPlaceholder")} - class="text-[0.73rem] font-mono px-2 py-1 rounded-md border border-border bg-background text-foreground" /> -
- - { openbciChanged = true; }} - class="w-20 text-[0.73rem] text-center px-2 py-1 rounded-md border border-border - bg-background text-foreground tabular-nums" /> -
-
- - {/if} - - - {#if isGalea} -
-

{t("settings.openbciGaleaIp")}

- { openbciChanged = true; }} - placeholder={t("settings.openbciGaleaIpPlaceholder")} - class="text-[0.73rem] font-mono px-2 py-1 rounded-md border border-border bg-background text-foreground" /> -
- - {/if} - - -
- - {t("settings.openbciChannelLabels")} - ({channelCount}) - - - {#if channelCount > 4} -

- {t("settings.openbciChannelsBeyond4", { n: channelCount })} -

- {/if} - - -
- - -
- - -
- {#each Array.from({ length: channelCount }, (_, i) => i) as i} -
- {i + 1} - setChannelLabel(i, (e.currentTarget as HTMLInputElement).value)} - placeholder={defaultChannelNames[i] ?? `Ch${i+1}`} - class="w-full min-w-0 text-[0.7rem] font-mono text-center px-1 py-0.5 rounded - border border-border bg-background text-foreground - placeholder:text-muted-foreground/35 - focus:outline-none focus:ring-1 focus:ring-blue-500/50" /> -
- {/each} -
- - {t("settings.openbciChannelLabelsHint")} -
- -
-
- - -
- - - {#if !isBle} - - {/if} -
- - {#if openbciError} -

{openbciError}

- {/if} - {/if} -
- - -
-
- - {t("settings.signalProcessing")} - - {#if filterSaving} - {t("common.saving")} - {/if} -
- - - - - -
- {t("settings.powerlineNotch")} -
- {#each ([["Hz60","🇺🇸",t("settings.us60Hz"),t("settings.us60HzSub")],["Hz50","🇪🇺",t("settings.eu50Hz"),t("settings.eu50HzSub")]] as const) as [val, flag, label, sub]} - - {/each} - - -
-
- - -
- {t("settings.highPassCutoff")} -
- {#each ([null, 0.5, 1, 4, 8] as const) as hz} - - {/each} -
-
- - -
- {t("settings.lowPassCutoff")} -
- {#each ([null, 30, 50, 100] as const) as hz} - - {/each} -
-
- - -
- - {t("settings.pipeline")} - - {#if filter.high_pass_hz !== null} - - HP {filter.high_pass_hz} Hz - - {/if} - {#if filter.low_pass_hz !== null} - - LP {filter.low_pass_hz} Hz - - {/if} - {#if filter.notch !== null} - - Notch {filter.notch === "Hz60" ? "60+120 Hz" : "50+100 Hz"} - - {/if} - {#if filter.high_pass_hz === null && filter.low_pass_hz === null && filter.notch === null} - - {t("settings.passthrough")} - - {/if} - {t("settings.gpuLatency")} -
- -
-
-
- - -
-
- - {t("settings.eegEmbedding")} - - {#if overlapSaving} - saving… - {/if} -
- - - - -
-
- {t("settings.epochOverlap")} - - {t("settings.everyNSecs", { n: (EMBEDDING_EPOCH_SECS - overlapSecs).toFixed(2).replace(/\.?0+$/, "") })} - -
-

- {t("settings.overlapDescription")} -

-
- {#each OVERLAP_PRESETS as [label, val]} - - {/each} -
-
- -
- - Pipeline - - - {EMBEDDING_EPOCH_SECS} s window - - - {overlapSecs} s overlap - - - {Math.round(overlapSecs / EMBEDDING_EPOCH_SECS * 100)}% shared - - ZUNA · wgpu -
- -
-
-
- - - -{#if gpuStats} - {@const fmtBytes = (b: number | null) => { - if (b === null || b <= 0) return null; - const gb = b / (1024 ** 3); - return gb >= 1 ? `${gb.toFixed(1)} GB` : `${(b / (1024 ** 2)).toFixed(0)} MB`; - }} - {@const usedBytes = (gpuStats.totalMemoryBytes !== null && gpuStats.freeMemoryBytes !== null) - ? gpuStats.totalMemoryBytes - gpuStats.freeMemoryBytes : null} - {@const usedPct = (usedBytes !== null && gpuStats.totalMemoryBytes) - ? Math.round(usedBytes / gpuStats.totalMemoryBytes * 100) : null} - {@const memLabel = gpuStats.isUnifiedMemory ? "Unified Memory (RAM)" : "VRAM"} - -
- - GPU · {memLabel} - - - - - - - {#if gpuStats.totalMemoryBytes} -
-
- {memLabel} - {#if fmtBytes(gpuStats.totalMemoryBytes)} - - {fmtBytes(gpuStats.totalMemoryBytes)} - {#if gpuStats.isUnifiedMemory}total{/if} - - {/if} -
- - {#if usedPct !== null && gpuStats.freeMemoryBytes !== null} - -
-
-
-
-
- - {fmtBytes(usedBytes)} used - · - {fmtBytes(gpuStats.freeMemoryBytes)} free - - - {usedPct}% - -
- {:else if gpuStats.freeMemoryBytes} -

- {fmtBytes(gpuStats.freeMemoryBytes)} free -

- {/if} - - {#if gpuStats.isUnifiedMemory} -

- Apple Silicon uses a single unified memory pool shared by CPU and GPU. - "Free" includes inactive pages that can be reclaimed immediately. -

- {/if} -
- {/if} - - - {#if gpuStats.overall > 0 || gpuStats.render > 0 || gpuStats.tiler > 0} -
- - GPU Usage - - {#each ([ - ["Render", gpuStats.render], - ["Tiler", gpuStats.tiler], - ["Overall", gpuStats.overall], - ] as [string, number][]).filter(([, v]) => v > 0) as [label, val]} -
-
-
-
- - {label} {Math.round(val * 100)}% - -
- {/each} -
- {/if} - -
-
-
-{/if} - - -
-
- - {t("settings.activityTracking")} - - {t("settings.activityDb")} -
- - - - - - - - - {#if trackActiveWindow} -
- - {t("settings.activeWindowCurrent")} - - {#if currentActiveWindow} -
- {#each ([ - [t("settings.activeWindowApp"), currentActiveWindow.app_name, "font-semibold text-foreground"], - [t("settings.activeWindowTitle"), currentActiveWindow.window_title, "text-foreground/80"], - [t("settings.activeWindowPath"), currentActiveWindow.app_path, "font-mono text-muted-foreground"], - [t("settings.activeWindowSince"), fmtLastSeen(currentActiveWindow.activated_at), "text-muted-foreground"], - ] as [string, string, string][]).filter(([, v]) => v) as [label, value, cls]} -
- {label} - {value} -
- {/each} -
- {:else} -

{t("settings.activeWindowNone")}

- {/if} -
- {/if} - - - - - - - - {#if trackInputActivity} - {@const hasData = lastInputActivity[0] > 0 || lastInputActivity[1] > 0} -
- - -
- - {#if hasData} - - - {:else} - - {/if} - - - {hasData ? t("settings.inputActivityActive") : t("settings.inputActivityNoData")} - -
- - -
- {#each ([ - [t("settings.inputActivityKeyboard"), lastInputActivity[0]], - [t("settings.inputActivityMouse"), lastInputActivity[1]], - ] as [string, number][]) as [label, ts]} -
- {label} - - {ts > 0 ? fmtLastSeen(ts) : t("settings.inputActivityNever")} - -
- {/each} -
- - -

- {t("settings.inputActivityPermNote")} -

-
- {/if} - -
-
-
- - -
-
- - {t("settings.logging")} - - {dataDirCurrent}/log_config.json -
- - - -
- {#each ([ - ["embedder", t("settings.logEmbedder"), t("settings.logEmbedderDesc")], - ["bluetooth", t("settings.logBluetooth"), t("settings.logBluetoothDesc")], - ["websocket", t("settings.logWebsocket"), t("settings.logWebsocketDesc")], - ["csv", t("settings.logCsv"), t("settings.logCsvDesc")], - ["filter", t("settings.logFilter"), t("settings.logFilterDesc")], - ["bands", t("settings.logBands"), t("settings.logBandsDesc")], - ["history", t("settings.logHistory"), t("settings.logHistoryDesc")], - ] as [keyof LogConfig, string, string][]) as [key, label, desc]} - - {/each} -
-
-
-
- - -
- - {t("settings.dataDir")} - - - - -

- {t("settings.dataDirDesc")} -

- -
- - {t("settings.dataDirDefault", { path: dataDirDefault })} - -
- -
- { dataDirChanged = dataDirInput !== dataDirCurrent; }} - placeholder={dataDirDefault} - class="flex-1 h-7 rounded-md border border-border bg-background px-2 text-[0.68rem] - font-mono text-foreground placeholder:text-muted-foreground/40 - focus:outline-none focus:ring-1 focus:ring-ring" /> - {#if dataDirInput !== dataDirDefault} - - {/if} -
- - {#if dataDirChanged} -
- - {t("settings.dataDirRestart")} - - -
- {/if} -
-
-
- - -
- - {t("settings.wsConfig")} - - - - - - -
- {t("settings.wsHost")} -
- {#each [["127.0.0.1", t("settings.wsHostLoopback")], ["0.0.0.0", t("settings.wsHostLan")]] as [val, lbl]} - - {/each} -
- {#if wsHost === "0.0.0.0"} -

- {t("settings.wsHostDesc")} -

- {/if} -
- - - - -
- {t("settings.wsPort")} -

{t("settings.wsPortDesc")}

-
- { - const n = parseInt(wsPortInput, 10); - if (isNaN(n) || n < 1024 || n > 65535) { - wsPortError = t("settings.wsPortInvalid"); - wsPortChanged = false; - } else { - wsPortError = ""; - wsPortChanged = n !== wsPort; - } - }} - class="w-28 h-7 rounded-md border border-border bg-background px-2 text-[0.68rem] - font-mono text-foreground focus:outline-none focus:ring-1 focus:ring-ring" /> - {#if wsPortError} - {wsPortError} - {/if} -
-
- - - {#if wsChanged && !wsPortError} -
- - {t("settings.wsRestart")} - - -
- {/if} - -
-
-
- diff --git a/src/lib/ShortcutsTab.svelte b/src/lib/ShortcutsTab.svelte deleted file mode 100644 index 8b16ef8af..000000000 --- a/src/lib/ShortcutsTab.svelte +++ /dev/null @@ -1,301 +0,0 @@ - - - - - - - -
-
- - {t("settings.globalShortcuts")} - - {t("settings.hotkeys")} -
- - - - - {#each ([ - ["label", t("settings.shortcutAddLabel"), shortcut], - ["search", t("settings.shortcutSearch"), searchShortcut], - ["calibration", t("settings.shortcutCalibration"), calibrationShortcut], - ["settings", t("settings.shortcutSettings"), settingsShortcut], - ["help", t("settings.shortcutHelp"), helpShortcut], - ["history", t("settings.shortcutHistory"), historyShortcut], - ["api", t("settings.shortcutApi"), apiShortcut], - ["theme", t("settings.shortcutTheme"), themeShortcut], - ["focusTimer", t("settings.shortcutFocusTimer"), focusTimerShortcut], - ] as [string, string, string][]) as [target, label, value]} -
- {label} - -
- {#if recording && recordingTarget === target} - - {t("settings.pressKeysEsc")} - - {:else if value} - {#each shortcutTokens(value) as token} - - {token} - - {/each} - {:else} - {t("settings.notSet")} - {/if} -
- - {#if shortcutError && recordingTarget === target} - - {shortcutError} - - {/if} - -
- {#if value && !(recording && recordingTarget === target)} - - {/if} - {#if recording && recordingTarget === target} - - {:else} - - {/if} -
-
- {/each} - -
-
-
- - -
-
- - {t("shortcutsTab.commandPalette")} - -
- - - -
-
- {t("shortcutsTab.cmdKTitle")} - - {t("shortcutsTab.cmdKDesc")} - -
- - {isMac ? "⌘" : "Ctrl"}K - -
-
-
-
- - -
-
- - {t("shortcutsTab.inAppShortcuts")} - -
- - - - - {#each ([ - ["?", t("shortcuts.showOverlay")], - [isMac ? "⌘K" : "Ctrl+K", t("shortcutsTab.cmdKTitle")], - ["Esc", t("shortcuts.closeOverlay")], - [isMac ? "⌘↵" : "Ctrl+↵", t("shortcuts.submitLabel")], - ] as [string, string][]) as [keys, label]} -
- {label} - - {keys} - -
- {/each} - -
-
-
diff --git a/src/lib/ThemeToggle.svelte b/src/lib/ThemeToggle.svelte index 2d4643e52..3ee42c39e 100644 --- a/src/lib/ThemeToggle.svelte +++ b/src/lib/ThemeToggle.svelte @@ -8,56 +8,54 @@ the Free Software Foundation, version 3 only. --> Shows: ◐ system, ☀ light, ● dark. Teleports tooltip to body to escape overflow clipping. --> - - - -
- - - -
- - {t("ttsTab.statusSection")} - - - - - - -
- {#if enginePhase === "ready"} - - - {t("ttsTab.statusReady")} - - {:else if enginePhase === "loading"} - - - {t("ttsTab.statusLoading")} - - {:else if enginePhase === "error"} - - - {t("ttsTab.statusError")} - - {:else if enginePhase === "unloaded"} - - - {t("ttsTab.statusUnloaded")} - - {:else} - - - {t("ttsTab.statusIdle")} - - {/if} - - -
- - -
-
- - - {#if enginePhase === "loading"} -
-
- - {loadLabel || "Connecting…"} - - {#if loadTotal > 0} - - {loadStep}/{loadTotal} - - {/if} -
-
-
-
-
- {/if} - - - {#if enginePhase === "error" && errorMsg} -
-

- {t("ttsTab.errorTitle")} -

-

- {errorMsg} -

-
- {/if} - - -

- {t("ttsTab.requirements")} · {t("ttsTab.requirementsDesc")} -

- -
-
-
- - - {#if activeBackend === "kitten"} -
- - {t("ttsTab.kittenConfigSection")} - - - - - - -
- - {t("ttsTab.kittenVoiceLabel")} - -
- {#each kittenVoices as v} - - {/each} -
-
- - -

- {t("ttsTab.kittenModelInfo")} -

- -
-
-
- {/if} - - - {#if activeBackend === "neutts"} -
- - {t("ttsTab.neuttsConfigSection")} - - - - - - -
-
- - {t("ttsTab.neuttsModelLabel")} - - - {t("ttsTab.neuttsModelDesc")} - -
- - - - {#if selectedModel()} - {@const m = selectedModel()!} -
- ✓ {m.pros} - ✗ {m.cons} -
- {/if} -
- - -
-
- - {t("ttsTab.neuttsVoiceSection")} - - - {t("ttsTab.neuttsVoiceDesc")} - -
- - -
- - {t("ttsTab.neuttsPresetLabel")} - -
- {#each PRESET_VOICES as pv} - - {/each} - - - -
-
- - - {#if neuttsConfig.voice_preset === ""} -
- - -
- - {t("ttsTab.neuttsRefWavLabel")} - -
- - {neuttsConfig.ref_wav_path || t("ttsTab.neuttsRefWavNone")} - - -
-
- - -
- - {t("ttsTab.neuttsRefTextLabel")} - - -
- -
- {/if} -
- - -
- -
- -
-
-
- {/if} - - -
- - {t("ttsTab.testSection")} - -

- {t("ttsTab.testDesc")} -

- -
- - -
- - {t("ttsTab.apiSection")} - -

- {t("ttsTab.apiDesc")} -

-
- {#each [ - ["WebSocket", `{"command":"say","text":"Eyes closed. Relax."}`], - ["HTTP (curl)", `curl -X POST http://localhost:/say \\ - -H 'Content-Type: application/json' \\ - -d '{"text":"Eyes closed. Relax."}'`], - ["websocat (CLI)", `echo '{"command":"say","text":"Eyes closed."}' | websocat ws://localhost:`], - ] as [label, code]} -
- - {label} - -
{code}
-
- {/each} -
-
- - -
- - {t("ttsTab.startupSection")} - - - - - - - -
- - -
- - {t("ttsTab.loggingSection")} - - - - - - - -
- - diff --git a/src/lib/UmapScene.svelte b/src/lib/UmapScene.svelte deleted file mode 100644 index a206f2e86..000000000 --- a/src/lib/UmapScene.svelte +++ /dev/null @@ -1,401 +0,0 @@ - - - - - - - diff --git a/src/lib/UmapTab.svelte b/src/lib/UmapTab.svelte deleted file mode 100644 index a61c44d50..000000000 --- a/src/lib/UmapTab.svelte +++ /dev/null @@ -1,419 +0,0 @@ - - - - - -{#if !loaded} -
- {t("common.loading")} -
-{:else} - - -
-
- - {t("umapSettings.repulsion")} - - {#if saving} - {t("common.saving")} - {/if} - {#if dirty} - - {/if} -
- - - - - -
-
- {t("umapSettings.repulsionStrength")} - {cfg.repulsion_strength.toFixed(1)} -
-

- {t("umapSettings.repulsionDesc")} -

-
- {#each REPULSION_PRESETS as [label, val]} - - {/each} -
- -
- - -
-
- {t("umapSettings.negSampleRate")} - {cfg.neg_sample_rate} -
-

- {t("umapSettings.negSampleRateDesc")} -

-
- {#each NEG_SAMPLE_PRESETS as [label, val]} - - {/each} -
-
-
-
-
- - -
- - {t("umapSettings.graphOptimisation")} - - - - - - -
-
- {t("umapSettings.nNeighbors")} - {cfg.n_neighbors} -
-

- {t("umapSettings.nNeighborsDesc")} -

-
- {#each NEIGHBOR_PRESETS as [label, val]} - - {/each} -
-
- - -
-
- {t("umapSettings.nEpochs")} - {cfg.n_epochs} -
-

- {t("umapSettings.nEpochsDesc")} -

-
- {#each EPOCH_PRESETS as [label, val]} - - {/each} -
-
-
-
-
- - -
- - {t("umapSettings.safetyPerformance")} - - - - - - -
-
- {t("umapSettings.timeout")} - {cfg.timeout_secs}s -
-

- {t("umapSettings.timeoutDesc")} -

-
- {#each TIMEOUT_PRESETS as [label, val]} - - {/each} -
-
- - -
-
- {t("umapSettings.cooldownMs")} - - {cfg.cooldown_ms === 0 ? "0 ms" : cfg.cooldown_ms < 1000 ? `${cfg.cooldown_ms} ms` : `${(cfg.cooldown_ms / 1000).toFixed(1)}s`} - -
-

- {t("umapSettings.cooldownMsDesc")} -

- -
- 0s5s10s -
-
- - -
- - {t("umapSettings.pipeline")} - - - repulsion {cfg.repulsion_strength.toFixed(1)} - - - neg×{cfg.neg_sample_rate} - - - k={cfg.n_neighbors} - - - {cfg.n_epochs} epochs - - - {cfg.timeout_secs}s timeout - - - {cfg.cooldown_ms}ms cooldown - - fast-umap 1.2.2 · wgpu -
-
-
-
- - -
- - {#if dirty} - - {/if} -
- -{/if} - - diff --git a/src/lib/UmapViewer3D.svelte b/src/lib/UmapViewer3D.svelte deleted file mode 100644 index f8d0cda81..000000000 --- a/src/lib/UmapViewer3D.svelte +++ /dev/null @@ -1,1657 +0,0 @@ - - - - - - -
- - - {#if traceActive && traceTimeRange && traceTimeTicks.length > 0} - {@const prog = traceTotal > 0 ? traceProgress / traceTotal : 0} -
-
- -
- -
-
- -
- {#each traceTimeTicks as tick, i} - {@const align = i === 0 ? 'left-0' : i === traceTimeTicks.length - 1 ? 'right-0' : '-translate-x-1/2'} - 0 && i < traceTimeTicks.length - 1 ? `left:${tick.pct}%` : ''}> - {tick.label} - - {/each} -
-
-
- {/if} - -
- - - {#if sidebarOpen && uniqueLabels.length > 0} - {@const groups = [ - { key: "common", label: t("umap.common"), items: uniqueLabels.filter(l => l.inA && l.inB) }, - { key: "a-only", label: t("umap.sessionA"), items: uniqueLabels.filter(l => l.inA && !l.inB) }, - { key: "b-only", label: t("umap.sessionB"), items: uniqueLabels.filter(l => !l.inA && l.inB) }, - ].filter(g => g.items.length > 0)} -
- -
- - {t("umap.labels")} - - - - { sidebarOpen = false; selectedLabel = null; applyHighlight(); }}>✕ -
- -
- - - {t("umap.sessionA")} - - - - {t("umap.sessionB")} - -
- - -
- {#each groups as group} - -
- {group.label} -
-
- - {#each group.items as entry} - {@const isSelected = selectedLabel === entry.label} - {@const isProximate = proximateLabels.includes(entry.label)} - {@const hex = labelHex(entry.hue)} - - -
{ selectedLabel = selectedLabel === entry.label ? null : entry.label; applyHighlight(); }}> - -
- {#if entry.inA && entry.inB} - - - - {:else if entry.inA} - - {:else} - - {/if} - {entry.label} -
- {#if entry.inA} - A - {/if} - {#if entry.inB} - B - {/if} -
-
- -
- {#each entry.timestamps.slice(0, 6) as ts} - - {fmtUtcTime(ts.utc)} - - {/each} - {#if entry.timestamps.length > 6} - - +{entry.timestamps.length - 6} - - {/if} -
-
- {/each} - {/each} -
- - -
-
- - {t("umap.proximity")} - - - {proximityDist.toFixed(1)} - -
- -
- {#if selectedLabel && !animating} - {#if proximateLabels.length > 0} - - {proximateLabels.length} {t("umap.nearbyLabels")} - - {:else} - {t("umap.noNearbyLabels")} - {/if} - {:else if animating} - {t("umap.computing")} - {:else} - {t("umap.selectLabelHint")} - {/if} -
-
-
- {/if} - - -
- - - {#if cmdHeld} -
- ⌘ Pan -
- {/if} - - {#if error} -
- {t("umap.error")} -
{error}
-
- {:else if !loaded} -
-
- - {t("umap.loading")} -
-
- {/if} - - - {#if computing} -
-
-
-
- -
- {t("umap.computing")} - {#if progress && progress.total_epochs > 0} - {@const pct = Math.round(progress.epoch / progress.total_epochs * 100)} - -
-
-
-
- {pct}% -
- - - epoch {progress.epoch}/{progress.total_epochs} · {progress.epoch_ms.toFixed(0)}ms/ep - - {:else} - {t("umap.placeholder")} - {/if} -
-
- {/if} - - - {#if activeLabels.length > 0} -
- {#each activeLabels as label} - {@const entry = linkGroups.get(label)} - {@const col = entry ? LINK_PALETTE[entry.colorIdx % LINK_PALETTE.length] : 0xffffff} -
- - {label} - - - removeLinkGroup(label)}>✕ -
- {/each} - {#if activeLabels.length > 1} - - -
clearAllLinks()}> - {t("umap.clearAll")} -
- {/if} -
- {/if} - - -
- - - {#if timeGradient && gradientRange} - {@const span = gradientRange.maxUtc - gradientRange.minUtc} -
- - Session {timeGradient} · time → - -
- - {fmtGradientTs(gradientRange.minUtc, span)} - -
-
- - {fmtGradientTs(gradientRange.maxUtc, span)} - -
-
- {/if} - - - {#if !timeGradient && colorByDate && dateLegend.length > 0} -
- {#each dateLegend as dl} -
- - {dl.date} -
- {/each} -
- {/if} - - - {#if loaded && !error && uniqueLabels.length > 0} - {@const colAcss = `#${UMAP_COLOR_A.toString(16).padStart(6,"0")}`} - {@const colBcss = `#${UMAP_COLOR_B.toString(16).padStart(6,"0")}`} -
- - -
- - {t("umap.sessionA")} — {t("umap.shapeSphere")} -
-
- - {t("umap.sessionB")} — {t("umap.shapeDiamond")} -
- - - {#if selectedLabel} -
-
- - {selectedLabel} - {t("umap.legendSelected")} -
- {#if proximateLabels.length > 0} -
- - {proximateLabels.length} {t("umap.legendNearby")} -
- {:else} -
- - {t("umap.noNearbyLabels")} -
- {/if} -
- - {t("umap.legendDimmed")} -
- {/if} -
- {/if} -
- - - {#if loaded && !error && curPoints.length >= 2} -
- - - -
-
{ timeGradient = timeGradient === 'A' ? null : 'A'; }}> - 🌈 A -
-
-
{ timeGradient = timeGradient === 'B' ? null : 'B'; }}> - 🌈 B -
-
- - - - -
- - {traceActive ? t("umap.traceStop") : t("umap.trace")} - - {#if traceActive && traceTotal > 0} - {traceProgress}/{traceTotal} - {/if} -
- - - {#if uniqueLabels.length > 0} - - -
{ sidebarOpen = !sidebarOpen; if (!sidebarOpen) { selectedLabel = null; applyHighlight(); } }}> - - 🏷 {uniqueLabels.length} - -
- {/if} -
- {/if} - - - {#if loaded && !error} -
- {t("umap.nodeSize")} - - {pointScale.toFixed(1)}× -
- {/if} - - - {#if tooltip} -
- {tooltip.text} -
- {/if} -
-
-
- - diff --git a/src/lib/UpdatesTab.svelte b/src/lib/UpdatesTab.svelte deleted file mode 100644 index de20acd96..000000000 --- a/src/lib/UpdatesTab.svelte +++ /dev/null @@ -1,488 +0,0 @@ - - - - - -
- - -
-
- ⬆ -
-
- {t("updates.title")} - - {t("updates.currentVersion", { version: appVersion })} - -
- - {#if phase === "ready"} - - ✅ {t("updates.readyToRestart")} - - {:else if phase === "downloading"} - {progress}% - {:else if phase === "checking"} - - - - {/if} -
- - - - - -
- - {#if phase === "ready"} - -
-
- ✅ -
-
- - {t("updates.installed", { version: available?.version ?? "" })} - - - {t("updates.restartingIn", { secs: countdown })} - -
-
- - -
-
- - -
-
-
- - {:else if phase === "downloading"} - -
-
- - {t("updates.downloading", { version: available?.version ?? "" })} - - - {progress}% - -
-
-
-
- {#if available?.body} -

{available.body}

- {/if} -
- - {:else} - -
- {#if phase === "error" && available} - -
- ⚠ -
-
- - v{available.version} {t("updates.available")} - - - {t("updates.downloadFailed")} - -
- {:else} -
- - {phase === "checking" ? t("updates.checking") : t("updates.upToDate")} - - - {t("updates.lastChecked")}: {fmtLastChecked()} - -
- {/if} - - - -
- - - {#if phase === "error" && error} -
- {error} -
- {/if} - {/if} - -
-
-
- - - - -
-
-
- - {t("updates.checkInterval")} - - - {t("updates.checkIntervalDesc")} - -
- {#if intervalSaving} - - - - {/if} -
- -
- {#each INTERVAL_OPTIONS as [secs, labelKey]} - - {/each} -
- - {#if checkIntervalSecs === 0} -

- {t("updates.intervalOffWarning")} -

- {/if} -
-
-
- - - - - - - {#if autostartError} -
- {autostartError} -
- {/if} -
-
- - -
- - {t("updates.footer")} - -
- -
diff --git a/src/lib/WhatsNew.svelte b/src/lib/WhatsNew.svelte index bbd7271e8..f4bc928a4 100644 --- a/src/lib/WhatsNew.svelte +++ b/src/lib/WhatsNew.svelte @@ -5,203 +5,33 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. --> - -{#if latest} - - - - -
- - -
-
- -
-
- - {t("whatsNew.title")} - - - {t("whatsNew.version", { version: latest.version })} -  ·  - {latest.date} - -
-
- -
- - -
- - {#each latest.sections as section} -
- - {#if section.heading} -

- {section.heading} -

- {/if} - -
    - {#each section.items as item} -
  • - - {item} -
  • - {/each} -
- -
- {/each} - -
- - -
- -
- -
-
-{/if} diff --git a/src/lib/charts/BandChart.svelte b/src/lib/charts/BandChart.svelte new file mode 100644 index 000000000..e33495653 --- /dev/null +++ b/src/lib/charts/BandChart.svelte @@ -0,0 +1,367 @@ + + + + + + + +
+ +
diff --git a/src/lib/EegChart.svelte b/src/lib/charts/EegChart.svelte similarity index 83% rename from src/lib/EegChart.svelte rename to src/lib/charts/EegChart.svelte index 0fd93ed7c..bb54bd4ca 100644 --- a/src/lib/EegChart.svelte +++ b/src/lib/charts/EegChart.svelte @@ -5,22 +5,22 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. --> + +
+ + + +
+ + + + {#each genericTabs as tab} + + {/each} +
+ + + {#if activeTab === "muse"} +
+ {#each museChannels as name, idx} + {@const q = effectiveQuality ? effectiveQuality[idx] : 0} + {@const label = effectiveLabels[idx]} + {@const el = visible.find(e => e.name === name)} + + {/each} +
+ {:else} + + {@const stripNames = channelNames.length > 0 ? channelNames : visible.map(e => e.name)} + {@const stripCount = stripNames.length} +
+ Signal + {#each stripNames as chName, idx} + {@const label = (qualityLabels ?? [])[idx] ?? "no_signal"} + {@const q = labelToNum(label)} +
+ + {chName} +
+ {/each} +
+ {/if} + + + + +
+ {#if Head3D} + + + + {:else} +
+ Loading 3D view… +
+ {/if} + + +
+ {#each Object.entries(regionColors) as [region, color]} + {#if region !== "reference"} + + + {regionLabels[region as BrainRegion]} + + {/if} + {/each} +
+ + +
+ Drag to rotate · Click electrode +
+
+ + + {#if selectedElectrode} + {@const el = selectedElectrode} +
+
+ + {el.name} + {#if el.muse} + Muse + {/if} + {regionLabels[el.region]} + {#if el.muse && effectiveQuality} + {@const chIdx = museChannels.indexOf(el.name)} + {#if chIdx >= 0} + {@const q = effectiveQuality[chIdx]} + + Signal: {labelToText(effectiveLabels[chIdx])} + + {/if} + {/if} + +
+
+
+ Position +

{el.lobe} — {el.function}

+
+
+ Signals +

{el.signals}

+
+ {#if el.museRole} +
+ Muse Role +

{el.museRole}

+
+ {/if} +
+
+ {/if} +
diff --git a/src/lib/charts/ElectrodeHead3D.svelte b/src/lib/charts/ElectrodeHead3D.svelte new file mode 100644 index 000000000..413da9623 --- /dev/null +++ b/src/lib/charts/ElectrodeHead3D.svelte @@ -0,0 +1,477 @@ + + + + + ref?.lookAt(0, 0, 0)} +> + + + + + + + + + + { group = ref; }}> + {#if gltfScene} + + {/if} + + + + {#if modelLoaded} + + {/if} + + + + {#if selRing} + { + if (selRing) { + const target = selRing.position.clone().add(selRing.normal); + ref.lookAt(target.x, target.y, target.z); + } + }} + /> + {/if} + + diff --git a/src/lib/charts/ElectrodePlacement.svelte b/src/lib/charts/ElectrodePlacement.svelte new file mode 100644 index 000000000..e347b430f --- /dev/null +++ b/src/lib/charts/ElectrodePlacement.svelte @@ -0,0 +1,234 @@ + + + + + + diff --git a/src/lib/charts/FnirsChart.svelte b/src/lib/charts/FnirsChart.svelte new file mode 100644 index 000000000..6686cd877 --- /dev/null +++ b/src/lib/charts/FnirsChart.svelte @@ -0,0 +1,127 @@ + + + +
+
+ fNIRS Trends + WL30 {summary.wl30.toFixed(1)} · Oxy30 {summary.oxy30.toFixed(1)} · Fatigue {summary.fatigue.toFixed(1)} +
+ +
diff --git a/src/lib/charts/GpuBar.svelte b/src/lib/charts/GpuBar.svelte new file mode 100644 index 000000000..dd0843989 --- /dev/null +++ b/src/lib/charts/GpuBar.svelte @@ -0,0 +1,128 @@ + + + + + +{#if gpuStats !== null} +
+ + + GPU + + + + {#if spark} + + + + + {/if} + + +
+
+
+
+ + + + {(gpuStats.overall * 100).toFixed(0)}% + +
+{/if} diff --git a/src/lib/charts/GpuChart.svelte b/src/lib/charts/GpuChart.svelte new file mode 100644 index 000000000..3826bc1ab --- /dev/null +++ b/src/lib/charts/GpuChart.svelte @@ -0,0 +1,272 @@ + + + + + +{#if visible} +
+ +
+ {t("gpu.title")} + + + {t("gpu.render")} {(gpuStats!.render * 100).toFixed(0)}% · {t("gpu.tiler")} {(gpuStats!.tiler * 100).toFixed(0)}% + + + {(gpuStats!.overall * 100).toFixed(0)}% + + + +
+ + {#if !collapsed} +
+ +
+ {/if} + +
+{/if} + + diff --git a/src/lib/charts/Hypnogram.svelte b/src/lib/charts/Hypnogram.svelte new file mode 100644 index 000000000..1efa36237 --- /dev/null +++ b/src/lib/charts/Hypnogram.svelte @@ -0,0 +1,127 @@ + + + + + +
+ + + + +
+ {#each STAGES as s} + {@const count = s.stage === 0 ? summary.wake_epochs + : s.stage === 5 ? summary.rem_epochs + : s.stage === 1 ? summary.n1_epochs + : s.stage === 2 ? summary.n2_epochs + : summary.n3_epochs} +
+ + {t(s.key)} + + {stagePct(count)}% · {stageDur(count)} + +
+ {/each} +
+
diff --git a/src/lib/ImuChart.svelte b/src/lib/charts/ImuChart.svelte similarity index 74% rename from src/lib/ImuChart.svelte rename to src/lib/charts/ImuChart.svelte index 93c7c0136..9039c367e 100644 --- a/src/lib/ImuChart.svelte +++ b/src/lib/charts/ImuChart.svelte @@ -5,18 +5,18 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. -->
- {t("dashboard.accel")} + {t("dashboard.accel")} {#each AXIS_LABELS as axis, i} - + {axis}{latestAccel[i] >= 0 ? "+" : ""}{latestAccel[i].toFixed(3)} {/each}
- {t("dashboard.gyro")} + {t("dashboard.gyro")} {#each AXIS_LABELS as axis, i} - + {axis}{latestGyro[i] >= 0 ? "+" : ""}{latestGyro[i].toFixed(1)} {/each}
- +
diff --git a/src/lib/charts/InteractiveGraph3D.svelte b/src/lib/charts/InteractiveGraph3D.svelte new file mode 100644 index 000000000..613c3a2a7 --- /dev/null +++ b/src/lib/charts/InteractiveGraph3D.svelte @@ -0,0 +1,1176 @@ + + + + + + +
{ tooltip = null; hoveredNodeId = null; }} + role="img" aria-label="Interactive search 3D graph"> + + + {#if tooltip} +
+ {#each tooltip.lines as line, li} +
+ {line} +
+ {/each} +
+ {/if} + + + {#if loaded && nodeEntries.length > 1} + {@const mmSize = 80} + {@const mmNodes = nodeEntries.map(ne => ({ + x: ne.mesh.position.x, + z: ne.mesh.position.z, + kind: ne.node.kind, + selected: ne.node.id === selectedNodeId, + }))} + {@const xMin = Math.min(...mmNodes.map(n => n.x)) - 2} + {@const xMax = Math.max(...mmNodes.map(n => n.x)) + 2} + {@const zMin = Math.min(...mmNodes.map(n => n.z)) - 2} + {@const zMax = Math.max(...mmNodes.map(n => n.z)) + 2} + {@const xRange = xMax - xMin || 1} + {@const zRange = zMax - zMin || 1} +
+ + {#each mmNodes as mn} + {@const mx = ((mn.x - xMin) / xRange) * (mmSize - 8) + 4} + {@const mz = ((mn.z - zMin) / zRange) * (mmSize - 8) + 4} + + {/each} + +
+ {/if} + + + + + + {#if selectedNodeId !== null} +
+ click node or empty space to deselect · double-click to zoom · ⌂ to reset +
+ {/if} + + + {#if !loaded} +
+
+
+ {/if} +
+ + +
+ + +
+ {#each LEGEND_BASE as l} +
+ + {l.label} +
+ {/each} +
+ + {foundLabelLegend} +
+ {#if nodes.some(n => n.kind === "screenshot")} +
+ + Screenshot +
+ {/if} + · + {#each EDGE_LEGEND as l} +
+ + {l.label} +
+ {/each} + {#if nodes.some(n => n.kind === "screenshot")} +
+ + Screenshot link +
+ {/if} + + hover · click to highlight · click again to clear + +
+ + + {#if eegDots.length > 0} +
+ + EEG node time scale + + + + + {#each Array.from({ length:12 }, (_,i) => i/11) as t} + + {/each} + + + + {#each eegDots as dot} + {@const x = dot.t * 500} + + + {/each} + {#each eegTicks as tick} + {@const x = tick.t * 500} + + + {tick.label} + + {/each} + + + {eegDots.length} EEG point{eegDots.length !== 1 ? "s" : ""} · + {fmtTs(eegTimeMin)} → {fmtTs(eegTimeMax)} + +
+ {:else} +
+ EEG nodes colored by session time (turbo gradient) +
+ {/if} +
diff --git a/src/lib/PpgChart.svelte b/src/lib/charts/PpgChart.svelte similarity index 78% rename from src/lib/PpgChart.svelte rename to src/lib/charts/PpgChart.svelte index ca7df3511..fe1e5321e 100644 --- a/src/lib/PpgChart.svelte +++ b/src/lib/charts/PpgChart.svelte @@ -5,29 +5,29 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. -->
@@ -246,21 +233,21 @@ the Free Software Foundation, version 3 only. --> {#each CH as label, i}
- {label} - + {label} + {latestValues[i] > 0 ? latestValues[i].toFixed(0) : "—"}
{/each}
- +
@@ -271,9 +258,9 @@ the Free Software Foundation, version 3 only. -->
{ ppgTooltip = null; }} > {ppgTooltip.text} diff --git a/src/lib/charts/chart-theme.ts b/src/lib/charts/chart-theme.ts new file mode 100644 index 000000000..484627e0d --- /dev/null +++ b/src/lib/charts/chart-theme.ts @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 only. +/** + * Shared chart theme helper — reads CSS custom properties defined in app.css + * and provides them as typed values for canvas-based chart rendering. + * + * Usage: + * const theme = readChartTheme(canvas); + * ctx.fillStyle = theme.bg; + */ + +export interface ChartTheme { + bg: string; + bgStrip: string; + grid: string; + baseline: string; + label: string; +} + +/** + * Read the chart CSS custom properties from the computed style of an element. + * Falls back to sensible dark-mode defaults if the properties aren't set. + */ +export function readChartTheme(el: HTMLElement): ChartTheme { + const cs = getComputedStyle(el); + return { + bg: cs.getPropertyValue("--chart-bg").trim() || "#0d0d1a", + bgStrip: cs.getPropertyValue("--chart-bg-strip").trim() || "#111120", + grid: cs.getPropertyValue("--chart-grid").trim() || "rgba(255,255,255,0.07)", + baseline: cs.getPropertyValue("--chart-baseline").trim() || "rgba(255,255,255,0.12)", + label: cs.getPropertyValue("--chart-label").trim() || "rgba(255,255,255,0.4)", + }; +} diff --git a/src/lib/charts/graph3d-logic.ts b/src/lib/charts/graph3d-logic.ts new file mode 100644 index 000000000..a62eb27cb --- /dev/null +++ b/src/lib/charts/graph3d-logic.ts @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +/** + * InteractiveGraph3D pure logic — extracted from InteractiveGraph3D.svelte. + * + * 3D vector math, Fibonacci sphere layout, and Turbo colormap. + */ + +export type Vec3 = [number, number, number]; + +const GOLDEN = Math.PI * (3 - Math.sqrt(5)); // golden angle in radians + +// ── Vector math ─────────────────────────────────────────────────────────────── + +export function add3(a: Vec3, b: Vec3): Vec3 { + return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; +} + +export function scale3(v: Vec3, s: number): Vec3 { + return [v[0] * s, v[1] * s, v[2] * s]; +} + +export function normalize3(v: Vec3): Vec3 { + const len = Math.sqrt(v[0] ** 2 + v[1] ** 2 + v[2] ** 2) || 1; + return [v[0] / len, v[1] / len, v[2] / len]; +} + +export function length3(v: Vec3): number { + return Math.sqrt(v[0] ** 2 + v[1] ** 2 + v[2] ** 2); +} + +// ── Fibonacci sphere ────────────────────────────────────────────────────────── + +/** + * Compute a point on the Fibonacci sphere for index `i` out of `n` total points. + * Produces an approximately uniform distribution on the unit sphere. + */ +export function fibSphere(i: number, n: number): Vec3 { + const y = 1 - (i / Math.max(n - 1, 1)) * 2; + const r = Math.sqrt(Math.max(0, 1 - y * y)); + const theta = GOLDEN * i; + return [Math.cos(theta) * r, y, Math.sin(theta) * r]; +} + +// ── Turbo colormap (simplified) ─────────────────────────────────────────────── + +/** + * Attempt a piecewise approximation of the Turbo colormap for t in [0, 1]. + * Returns [r, g, b] in [0, 1]. + */ +export function turbo(t: number): Vec3 { + const tc = Math.max(0, Math.min(1, t)); + const r = Math.max(0, Math.min(1, 1.5 - Math.abs(tc - 0.75) * 4)); + const g = Math.max(0, Math.min(1, 1.5 - Math.abs(tc - 0.5) * 4)); + const b = Math.max(0, Math.min(1, 1.5 - Math.abs(tc - 0.25) * 4)); + return [r, g, b]; +} + +/** Convert a Turbo colormap value to a CSS hex string. */ +export function turboCss(t: number): string { + const [r, g, b] = turbo(t); + const hex = (v: number) => + Math.round(v * 255) + .toString(16) + .padStart(2, "0"); + return `#${hex(r)}${hex(g)}${hex(b)}`; +} + +/** Convert a Turbo colormap value to a packed 0xRRGGBB integer. */ +export function turboHex(t: number): number { + const [r, g, b] = turbo(t); + return (Math.round(r * 255) << 16) | (Math.round(g * 255) << 8) | Math.round(b * 255); +} diff --git a/src/lib/charts/index.ts b/src/lib/charts/index.ts new file mode 100644 index 000000000..f7932fe71 --- /dev/null +++ b/src/lib/charts/index.ts @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 only. + +export { type ChartTheme, readChartTheme } from "./chart-theme"; +// Re-export chart infrastructure +export { type AnimatedCanvasOpts, animatedCanvas } from "./use-canvas"; diff --git a/src/lib/charts/use-canvas.ts b/src/lib/charts/use-canvas.ts new file mode 100644 index 000000000..d92d00e99 --- /dev/null +++ b/src/lib/charts/use-canvas.ts @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +// +// Shared canvas lifecycle helper — DRY replacement for the ResizeObserver + +// requestAnimationFrame + DPR boilerplate duplicated across EegChart, +// BandChart, PpgChart, GpuChart, ImuChart, etc. +// +// Usage (Svelte action): +// +// +// +// The action: +// 1. Scales the canvas for the current device-pixel-ratio. +// 2. Observes `container ?? canvas.parentElement` for size changes. +// 3. Runs a `requestAnimationFrame` loop calling `draw(ctx, cssW, cssH)`. +// 4. Cleans up on destroy (cancels RAF, disconnects observer). + +import { getDpr } from "$lib/format"; + +export interface AnimatedCanvasOpts { + /** Called every animation frame with the 2-D context and logical size. */ + draw: (ctx: CanvasRenderingContext2D, width: number, height: number) => void; + + /** Fixed CSS height of the canvas (logical px). */ + heightPx: number; + + /** + * Fixed CSS width (logical px). When set, the canvas is sized to this + * constant instead of tracking the container's width. + */ + widthPx?: number; + + /** + * Element to observe for width changes. Defaults to `canvas.parentElement`. + * Ignored when `widthPx` is set (no need to track container width). + */ + container?: HTMLElement; + + /** + * If true the RAF loop is NOT started automatically — call the returned + * `start()` function manually (useful when the chart is initially hidden). + */ + manual?: boolean; +} + +/** + * Svelte action that manages the RAF + ResizeObserver lifecycle for a ``. + */ +export function animatedCanvas( + canvas: HTMLCanvasElement, + opts: AnimatedCanvasOpts, +): { destroy: () => void; update: (o: AnimatedCanvasOpts) => void } { + let { draw, heightPx, widthPx, container, manual } = opts; + let raf: number | undefined; + let ro: ResizeObserver | undefined; + let running = false; + + function resize() { + const dpr = getDpr(); + if (widthPx != null) { + canvas.width = Math.round(widthPx * dpr); + canvas.height = Math.round(heightPx * dpr); + } else { + const observed = container ?? canvas.parentElement ?? canvas; + const w = observed.clientWidth || canvas.clientWidth; + canvas.width = Math.round(w * dpr); + canvas.height = Math.round(heightPx * dpr); + canvas.style.width = `${w}px`; + canvas.style.height = `${heightPx}px`; + } + } + + function tick() { + raf = requestAnimationFrame(tick); + const ctx = canvas.getContext("2d"); + if (!ctx) return; + const dpr = getDpr(); + const w = canvas.width / dpr; + const h = canvas.height / dpr; + try { + draw(ctx, w, h); + } catch (_err) {} + } + + function start() { + if (running) return; + running = true; + raf = requestAnimationFrame(tick); + } + + function stop() { + running = false; + if (raf !== undefined) { + cancelAnimationFrame(raf); + raf = undefined; + } + } + + // Initial setup + if (widthPx == null) { + const observed = container ?? canvas.parentElement ?? canvas; + ro = new ResizeObserver(resize); + ro.observe(observed); + } + resize(); + + if (!manual) start(); + + return { + update(o: AnimatedCanvasOpts) { + draw = o.draw; + heightPx = o.heightPx; + widthPx = o.widthPx; + if (o.container !== container) { + container = o.container; + ro?.disconnect(); + if (widthPx == null) { + const newObs = container ?? canvas.parentElement ?? canvas; + ro = new ResizeObserver(resize); + ro.observe(newObs); + } + } + resize(); + }, + destroy() { + stop(); + ro?.disconnect(); + }, + }; +} diff --git a/src/lib/chat/ChatContextBreakdown.svelte b/src/lib/chat/ChatContextBreakdown.svelte new file mode 100644 index 000000000..c3da36dd4 --- /dev/null +++ b/src/lib/chat/ChatContextBreakdown.svelte @@ -0,0 +1,128 @@ + + + + + + + +
+
e.stopPropagation()} + > + +
+ {t("chat.ctx.title")} + {#if isEstimate} + {t("chat.ctx.estimated")} + {/if} +
+ + +
+ {#each sortedSegments as seg, i (seg.key)} +
+ {/each} +
+ + +
+ {#each sortedSegments as seg (seg.key)} + {@const share = pctOfUsed(seg.tokens)} +
+ + {t(seg.labelKey)} + {fmtNum(seg.tokens)} + {fmtPct1(share)}% +
+ {/each} + + +
+ + {t("chat.ctx.free")} + {fmtNum(freeTokens)} + {fmtPct1(pctOfCtx(freeTokens))}% +
+
+ + +
+ {t("chat.ctx.total")} + + {fmtNum(totalUsed)} / {fmtNum(nCtx)} + ({fmtPct1(pctOfCtx(totalUsed))}%) + +
+ + + {#if onViewFull} + + {/if} +
+
diff --git a/src/lib/chat/ChatContextViewer.svelte b/src/lib/chat/ChatContextViewer.svelte new file mode 100644 index 000000000..c8ae4a200 --- /dev/null +++ b/src/lib/chat/ChatContextViewer.svelte @@ -0,0 +1,227 @@ + + + + + + + + +
+ +
+ + + + +
e.stopPropagation()} + > + +
+
+

{t("chat.ctx.viewerTitle")}

+ + ~{totalTokens.toLocaleString()} {t("chat.ctx.tokens")} / {nCtx.toLocaleString()} {t("chat.ctx.nCtx")} + +
+
+ + + + +
+
+ + +
+ {#each apiMessages as msg, i (i)} + {@const tokens = estimateTokens(msg.content) + 10} +
+ +
+
+ + {msg.role} + + {#if msg.label} + — {msg.label} + {/if} +
+ ~{tokens.toLocaleString()} {t("chat.ctx.tokens")} +
+ +
{msg.content}
+
+ {/each} + + {#if apiMessages.length === 0} +
+ {t("chat.ctx.empty")} +
+ {/if} +
+ + +
+ {apiMessages.length} {t("chat.ctx.messagesCount")} + + ~{totalTokens.toLocaleString()} / {nCtx.toLocaleString()} {t("chat.ctx.tokens")} + ({nCtx > 0 ? ((totalTokens / nCtx) * 100).toFixed(1) : "0.0"}%) + +
+
+
diff --git a/src/lib/chat/ChatHeader.svelte b/src/lib/chat/ChatHeader.svelte new file mode 100644 index 000000000..ded04473a --- /dev/null +++ b/src/lib/chat/ChatHeader.svelte @@ -0,0 +1,259 @@ + + + + + + diff --git a/src/lib/chat/ChatInputBar.svelte b/src/lib/chat/ChatInputBar.svelte new file mode 100644 index 000000000..f619d7a0e --- /dev/null +++ b/src/lib/chat/ChatInputBar.svelte @@ -0,0 +1,333 @@ + + + + + +
+ + + {#if eegBlocked} +
+ + + + + + + {t("chat.eeg.noSignalBlockedHint")} + +
+ {/if} + + + {#if attachments.length > 0 && !supportsVision} +
+ + + + + + {t("chat.noVision")} + +
+ {/if} + + + {#if attachments.length > 0} +
+ {#each attachments as att, i} +
+ {att.name} + +
+ {/each} +
+ {/if} + + + + + + {#if nCtx > 0 && liveUsedTokens > 0} + {@const warnPct = Math.round((liveUsedTokens / nCtx) * 100)} + {#if warnPct >= 85} +
+ + + + + + {t("chat.ctxWarning", { pct: warnPct })} + +
+ {/if} + {/if} + + +
+ + + + + + {t("chat.hint.llmWarning")} + +
+ +
+ + + + + + + + + + {#if generating} + + {:else} + + {/if} +
+ +

+ {#if eegBlocked} + {t("chat.hint.noEeg")} + {:else if status === "running"} + {t("chat.hint.running")} + {:else if status === "loading"} + {t("chat.hint.loading")} + {:else} + {t("chat.hint.stopped")} + {/if} +

+
+ + + diff --git a/src/lib/chat/ChatMessageList.svelte b/src/lib/chat/ChatMessageList.svelte new file mode 100644 index 000000000..a6d72563a --- /dev/null +++ b/src/lib/chat/ChatMessageList.svelte @@ -0,0 +1,451 @@ + + + + + +
+
+ + + {#if messages.length === 0} +
+
+ + + + +
+ {#if status === "stopped"} +
+

{t("chat.empty.stopped")}

+

+ {t("chat.empty.stoppedHint")} +

+
+ + +
+
+ {:else if status === "loading"} +
+

{t("chat.status.loading")}

+ + +
+ {#each loadingSteps as step, i} +
+ {#if loadingActiveIdx > i} + + + + {:else if loadingActiveIdx === i} + + + + + {:else} +
+
+
+ {/if} + {step.label} +
+ {/each} +
+ + +
+
+
+
+ {:else} +

{t("chat.empty.ready")}

+ {/if} +
+ + {:else} + {#each messages as msg (msg.id)} + + {#if msg.role === "user"} +
+
+ {#if msg.attachments?.length} +
+ {#each msg.attachments as att} + {att.name} + {/each} +
+ {/if} + {#if msg.content} +
+
+ {msg.content} +
+ {#if status === "running" && !generating} +
+ +
+ {/if} +
+ {/if} +
+
+ + + {:else if msg.role === "assistant"} +
+ {#if msg.pending} +
+ + + + +
+ {:else} +
+ AI +
+ {/if} + +
+ + {#if msg.thinking || (msg.pending && msg.content === "" && !msg.thinking && !msg.toolUses?.length && !msg.leadIn?.trim())} +
+ + {#if msg.thinkOpen && msg.thinking} +
+ +
+ {/if} +
+ {/if} + + + {#if cleanLeadInForDisplay(msg.leadIn ?? "", !!msg.toolUses?.length)} +
+ {cleanLeadInForDisplay(msg.leadIn ?? "", !!msg.toolUses?.length)} +
+ {/if} + + + {#if msg.toolUses?.length} +
+ {#each msg.toolUses as tu, tuIdx} + onUpdateToolUse(msg.id, tuIdx, { expanded: !tu.expanded })} + onCancel={() => onCancelToolCall(msg.id, tuIdx, tu.toolCallId)} + /> + {/each} +
+ {/if} + + + {#if msg.content.trim()} +
+
+ +
+ {#if !msg.pending && msg.content} +
+ + {#if msg.id === messages[messages.length - 1]?.id && status === "running" && !generating} + + {/if} +
+ {/if} +
+ {/if} + + + {#if msg.pending && generating && streamTokens > 0} + {@const elapsedSec = (performance.now() - streamStartMs) / 1000} + {@const tokSec = elapsedSec > 0.1 ? (streamTokens / elapsedSec).toFixed(1) : "…"} + + {tokSec} {t("chat.tokSec")} + + {/if} + + + {#if !msg.pending && (msg.elapsed !== undefined || msg.usage)} +
+ {#if msg.elapsed !== undefined} + + {fmtMs(msg.elapsed)} + {#if msg.ttft !== undefined} · {t("chat.firstToken")} {fmtMs(msg.ttft)}{/if} + {#if msg.usage} + · {msg.usage.prompt_tokens}+{msg.usage.completion_tokens} {t("chat.tok")} + {#if msg.elapsed && msg.usage.completion_tokens > 0} + · {(msg.usage.completion_tokens / (msg.elapsed / 1000)).toFixed(1)} {t("chat.tokSec")} + {/if} + {/if} + + {/if} + {#if msg.usage && msg.usage.n_ctx > 0} + {@const usedPct = Math.round((msg.usage.total_tokens / msg.usage.n_ctx) * 100)} + {@const barColor = usedPct >= 90 ? "bg-red-500" + : usedPct >= 70 ? "bg-amber-500" + : "bg-emerald-500"} +
+
+
+
+ + {msg.usage.total_tokens}/{msg.usage.n_ctx} + +
+ {/if} +
+ {/if} +
+
+ {/if} + {/each} + {/if} + +
+ + +{#if !pinned} + +{/if} +
diff --git a/src/lib/chat/ChatSettingsPanel.svelte b/src/lib/chat/ChatSettingsPanel.svelte new file mode 100644 index 000000000..f35e285d8 --- /dev/null +++ b/src/lib/chat/ChatSettingsPanel.svelte @@ -0,0 +1,219 @@ + + + + + +
+ + +
+
+ +
+ + {t("chat.systemPrompt.chars", { n: systemPrompt.length })} + + {#if !isDefaultPrompt} + + {/if} +
+
+ + + + +
+ + {t("chat.systemPrompt.presets")} + +
+ {#each SYSTEM_PROMPT_PRESETS as preset} + {@const isActive = systemPrompt.trim() === preset.prompt.trim()} + + {/each} +
+
+
+ + +
+
+ + + + + {t("chat.eeg.contextLabel")} + +
+ +
+ + + {#if eegActive && latestBands} + {@const b = latestBands} + {@const n = b.channels.length || 1} + {@const rA = b.channels.reduce((s,c)=>s+c.rel_alpha,0)/n} + {@const rB = b.channels.reduce((s,c)=>s+c.rel_beta,0)/n} + {@const rT = b.channels.reduce((s,c)=>s+c.rel_theta,0)/n} +
+ {#each [ + { label: "SNR", val: (b.snr ?? 0).toFixed(1) + "dB" }, + { label: "Mood", val: (b.mood ?? 0).toFixed(0) + "/100" }, + { label: "α", val: (rA*100).toFixed(0) + "%" }, + { label: "β/α", val: (b.bar ?? 0).toFixed(2) }, + { label: "θ/α", val: (b.tar ?? 0).toFixed(2) }, + { label: "FAA", val: (b.faa>=0?"+":"")+b.faa.toFixed(2) }, + ...(b.hr != null ? [{ label: "HR", val: b.hr.toFixed(0)+"bpm" }] : []), + ...(b.meditation != null ? [{ label: "Med", val: b.meditation.toFixed(0)+"/100" }] : []), + ] as s} +
+ + {s.label} + + + {s.val} + +
+ {/each} +
+ {/if} + + +
+ + {t("chat.thinkDepth")} + +
+ {#each THINKING_LEVELS as lvl} + + {/each} +
+

+ {t(`chat.think.${thinkingLevel}Desc`)} +

+
+ + +
+ {#each [ + { labelKey: "chat.param.temperature", min: 0, max: 2, step: 0.05, value: temperature, set: (v: number) => temperature = v }, + { labelKey: "chat.param.maxTokens", min: 64, max: 8192, step: 64, value: maxTokens, set: (v: number) => maxTokens = v }, + { labelKey: "chat.param.topK", min: 1, max: 200, step: 1, value: topK, set: (v: number) => topK = v }, + { labelKey: "chat.param.topP", min: 0, max: 1, step: 0.05, value: topP, set: (v: number) => topP = v }, + ] as s} +
+
+ {t(s.labelKey)} + {s.value} +
+ s.set(+(e.target as HTMLInputElement).value)} + class="w-full accent-violet-500 h-1 cursor-pointer" /> +
+ {/each} +
+
diff --git a/src/lib/chat/ChatSidebar.svelte b/src/lib/chat/ChatSidebar.svelte new file mode 100644 index 000000000..f0a5bfbd7 --- /dev/null +++ b/src/lib/chat/ChatSidebar.svelte @@ -0,0 +1,406 @@ + + + + + + +
+ + +
+ + {t("chat.sidebar.chats")} + + +
+ + +
+ + {#if sessions.length === 0 && !showArchive} +

+ {t("chat.sidebar.empty")} +

+ {:else} +
    + {#each sessions as s (s.id)} + {@const isActive = s.id === activeId} + {@const isEditing = editingId === s.id} + +
  • +
    { if (!isEditing) onSelect(s.id); }} + ondblclick={(e) => startEdit(s, e)} + onkeydown={(e) => { + if (!isEditing && (e.key === "Enter" || e.key === " ")) { + e.preventDefault(); + onSelect(s.id); + } + }} + title={isEditing ? undefined : (s.title || displayLabel(s))} + class="group w-full text-left flex items-start gap-0 px-3 py-2 transition-colors + {isActive + ? 'bg-violet-500/10 dark:bg-violet-500/15' + : 'hover:bg-muted dark:hover:bg-white/[0.04]'} + cursor-pointer relative"> + + {#if isActive} + + {/if} + +
    + {#if isEditing} + { + if (e.key === "Enter") { e.preventDefault(); commitEdit(); } + else cancelEdit(e); + }} + onclick={(e) => e.stopPropagation()} + class="w-full text-ui-md font-medium bg-background border border-violet-500/40 + rounded px-1.5 py-0.5 text-foreground focus:outline-none + focus:ring-1 focus:ring-primary/50" + /> + {:else} +

    + {shortLabel(s)} +

    + {/if} + +
    + + {relTime(s.created_at)} + + {#if s.message_count > 0} + + {s.message_count} msg{s.message_count !== 1 ? "s" : ""} + + {/if} +
    +
    + + + {#if !isEditing} + + {/if} +
    +
  • + {/each} +
+ {/if} + + +
+ + + {#if showArchive} + {#if archived.length === 0} +

+ {t("chat.sidebar.archiveEmpty")} +

+ {:else} +
    + {#each archived as s (s.id)} +
  • +
    onSelect(s.id)} + onkeydown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + onSelect(s.id); + } + }} + title={s.title || displayLabel(s)} + class="group w-full text-left flex items-start gap-0 px-3 py-1.5 transition-colors + hover:bg-muted dark:hover:bg-white/[0.04] + cursor-pointer relative opacity-60"> + +
    +

    + {shortLabel(s)} +

    +
    + + {relTime(s.created_at)} + + {#if s.message_count > 0} + + {s.message_count} msg{s.message_count !== 1 ? "s" : ""} + + {/if} +
    +
    + + +
    + + + + +
    +
    +
  • + {/each} +
+ {/if} + {/if} +
+
+
diff --git a/src/lib/chat/ChatToolCard.svelte b/src/lib/chat/ChatToolCard.svelte new file mode 100644 index 000000000..cc34fcfab --- /dev/null +++ b/src/lib/chat/ChatToolCard.svelte @@ -0,0 +1,391 @@ + + + + + +
+ +
+ + + + {#if tu.status === "calling"} + + {/if} +
+ + + {#if isDangerous && tu.status === 'calling' && dangerKey} +
+ + + + + + {t(dangerKey)} + +
+ {/if} + + + {#if tu.expanded && hasDetails} +
+ + {#if tu.tool === "bash" && bashCmd} +
+ + {t("chat.tools.commandLabel")} + +
{bashCmd}
+
+ + {:else if (tu.tool === "read_file" || tu.tool === "write_file" || tu.tool === "edit_file") && arg("path")} +
+ + {t("chat.tools.fileLabel")} + +
{arg("path")}
+ {#if tu.tool === "edit_file" && arg("old_text")} + + {t("chat.tools.editOldLabel")} + +
{arg("old_text")}
+ {/if} + {#if tu.tool === "edit_file" && arg("new_text")} + + {t("chat.tools.editNewLabel")} + +
{arg("new_text")}
+ {/if} + {#if tu.tool === "write_file" && arg("content")} + + {t("chat.tools.contentLabel")} + +
{arg("content")}
+ {/if} +
+ + {:else if tu.tool === "web_search" && arg("query")} +
+ + {t("chat.tools.queryLabel")} + +
{arg("query")}
+
+ + {#if sources.length} +
+ + {t("chat.tools.sourcesLabel")} + + {#each sources as src, si} + {@const expanded = expandedSources.has(si)} +
+ + + {#if expanded && src.preview} +
+ + {src.url} + +
{src.preview}
+
+ {/if} +
+ {/each} +
+ {/if} + + {:else if tu.tool === "web_fetch" && arg("url")} +
+ + URL + +
{arg("url")}
+
+ + {:else if hasNonEmptyArgs} +
+ + {t("chat.tools.argsLabel")} + +
{JSON.stringify(tu.args, null, 2)}
+
+ {/if} + + {#if tu.result} +
+ + {t("chat.tools.resultLabel")} + +
{#if typeof tu.result === "string"}{tu.result}{:else}{JSON.stringify(tu.result, null, 2)}{/if}
+
+ {/if} + + + {#if tu.status === "calling"} +
+ +
+ {/if} +
+ {/if} +
diff --git a/src/lib/chat/ChatToolsPanel.svelte b/src/lib/chat/ChatToolsPanel.svelte new file mode 100644 index 000000000..00d721646 --- /dev/null +++ b/src/lib/chat/ChatToolsPanel.svelte @@ -0,0 +1,213 @@ + + + + + +
+ +
+
+ + {t("chat.tools.label")} + + + {enabledToolCount}/9 + +
+
+ {#each [ + { key: "date" as const, icon: "🕐" }, + { key: "location" as const, icon: "📍" }, + { key: "web_search" as const, icon: "🔍" }, + { key: "web_fetch" as const, icon: "🌐" }, + { key: "bash" as const, icon: "💻" }, + { key: "read_file" as const, icon: "📄" }, + { key: "write_file" as const, icon: "✏️" }, + { key: "edit_file" as const, icon: "🔧" }, + { key: "skill_api" as const, icon: "🧠" }, + ] as tool} + + {/each} +
+ + + {#if toolConfig.bash} + + {/if} + + +
+
+ {t("chat.tools.executionMode")} +
+
+ {#each [ + { key: "parallel" as ToolExecutionMode, labelKey: "chat.tools.parallel" }, + { key: "sequential" as ToolExecutionMode, labelKey: "chat.tools.sequential" }, + ] as mode} + + {/each} +
+
+ + +
+
+ {t("llm.tools.maxRounds")} + {t("llm.tools.maxRoundsDesc")} +
+
+ {#each [1, 3, 5, 10] as val} + + {/each} +
+
+ + +
+
+ {t("llm.tools.maxCallsPerRound")} + {t("llm.tools.maxCallsPerRoundDesc")} +
+
+ {#each [1, 2, 4, 8] as val} + + {/each} +
+
+ + +
+
+ {t("llm.tools.contextCompression")} + {t("llm.tools.contextCompressionDesc")} +
+
+ {#each [ + { key: "off" as CompressionLevel, label: t("llm.tools.compressionOff") }, + { key: "normal" as CompressionLevel, label: t("llm.tools.compressionNormal") }, + { key: "aggressive" as CompressionLevel, label: t("llm.tools.compressionAggressive") }, + ] as opt} + + {/each} +
+
+ + +
+
+ {t("chat.tools.thinkingBudget")} + {t("chat.tools.thinkingBudgetDesc")} +
+
+ {#each TOOL_THINKING_LEVELS as lvl} + {@const isActive = toolConfig.thinking_budget === lvl.budget} + + {/each} +
+
+
+
diff --git a/src/lib/chat/ChatVoiceControls.svelte b/src/lib/chat/ChatVoiceControls.svelte new file mode 100644 index 000000000..a832384a9 --- /dev/null +++ b/src/lib/chat/ChatVoiceControls.svelte @@ -0,0 +1,201 @@ + + + + + +
+ + + + +
+ + +
+ + +
+ + +
+ + + {#if errorMsg} + + + + + {errorMsg} + + + {:else if phaseLabel} + + {#if phase === 'loading'} + + + + + {:else if phase === 'speaking'} + + {:else} + + + + + {/if} + {phaseLabel} + + {/if} +
diff --git a/src/lib/chat/PromptLibrary.svelte b/src/lib/chat/PromptLibrary.svelte new file mode 100644 index 000000000..408a6069a --- /dev/null +++ b/src/lib/chat/PromptLibrary.svelte @@ -0,0 +1,230 @@ + + + + + + + + + + + + + +{#if open} + +{/if} diff --git a/src/lib/chat/asr.ts b/src/lib/chat/asr.ts new file mode 100644 index 000000000..2d76d7723 --- /dev/null +++ b/src/lib/chat/asr.ts @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +/** + * Shared voice-input (ASR + VAD) types and helpers for the chat window. + * + * The daemon owns the engine: `asr_start` / `asr_stop` / `asr_status` / + * `asr_set_ptt` Tauri commands drive it, and lifecycle + transcript events + * arrive on the `/v1/events` WebSocket as `"asr"` events (see `onDaemonEvent`). + * + * `settings.asr` (the persisted defaults) is owned by the daemon: `fetchAsrDefaults` + * reads it via `get_asr_settings` and `saveAsrDefaults` writes it via + * `set_asr_settings` (both hit settings.json, the source of truth). A + * `localStorage` copy is kept only as an instant-first-paint cache — + * `loadAsrDefaults` reads it synchronously on mount, then the async daemon fetch + * overwrites it. `asr_start` is still called with the resolved mode, so the + * daemon stays the source of truth at runtime too. + */ + +import { invoke } from "@tauri-apps/api/core"; + +export type AsrTrigger = "continuous" | "push_to_talk"; +export type AsrRouting = "voice_loop" | "transcribe_only"; + +/** Per-session voice-mode selection passed to `asr_start`. */ +export interface AsrMode { + trigger: AsrTrigger; + routing: AsrRouting; + language: string; +} + +/** Chat-side `settings.asr` defaults (mirrors the daemon `AsrConfig`). */ +export interface AsrDefaults { + enabled: boolean; + default_trigger: AsrTrigger; + default_routing: AsrRouting; + language: string; + /** ASR engine backend (e.g. "whisper"). */ + engine: string; + /** HuggingFace repo id of the speech-recognition model. */ + model: string; +} + +export const ASR_DEFAULTS_FALLBACK: AsrDefaults = { + enabled: true, + default_trigger: "continuous", + default_routing: "voice_loop", + language: "en", + engine: "whisper", + model: "openai/whisper-base.en", +}; + +export const ASR_DEFAULTS_KEY = "chat.asrDefaults"; + +/** Normalise a partial/untrusted object into a full `AsrDefaults`. */ +function coerceAsrDefaults(parsed: Partial | null | undefined): AsrDefaults { + return { + enabled: parsed?.enabled ?? ASR_DEFAULTS_FALLBACK.enabled, + default_trigger: parsed?.default_trigger ?? ASR_DEFAULTS_FALLBACK.default_trigger, + default_routing: parsed?.default_routing ?? ASR_DEFAULTS_FALLBACK.default_routing, + language: parsed?.language ?? ASR_DEFAULTS_FALLBACK.language, + engine: parsed?.engine ?? ASR_DEFAULTS_FALLBACK.engine, + model: parsed?.model ?? ASR_DEFAULTS_FALLBACK.model, + }; +} + +/** + * Synchronously read the cached ASR defaults for instant first paint. + * + * This is only the `localStorage` cache — the daemon (settings.json) is the + * source of truth. Callers should follow up with `fetchAsrDefaults()` on mount + * to reconcile against the daemon. + */ +export function loadAsrDefaults(): AsrDefaults { + try { + const raw = localStorage.getItem(ASR_DEFAULTS_KEY); + if (!raw) return { ...ASR_DEFAULTS_FALLBACK }; + return coerceAsrDefaults(JSON.parse(raw) as Partial); + } catch { + return { ...ASR_DEFAULTS_FALLBACK }; + } +} + +/** Daemon `get_asr_settings` response shape. */ +interface GetAsrSettingsResponse { + ok: boolean; + asr?: Partial; +} + +/** + * Load the authoritative ASR defaults from the daemon (settings.json). + * + * Falls back to the built-in defaults on any error. On success the result is + * also written to the `localStorage` cache so the next first paint is correct. + */ +export async function fetchAsrDefaults(): Promise { + try { + const res = await invoke("get_asr_settings"); + const defaults = coerceAsrDefaults(res?.asr); + cacheAsrDefaults(defaults); + return defaults; + } catch { + return { ...ASR_DEFAULTS_FALLBACK }; + } +} + +/** Write the ASR defaults to the `localStorage` first-paint cache. */ +function cacheAsrDefaults(defaults: AsrDefaults): void { + try { + localStorage.setItem(ASR_DEFAULTS_KEY, JSON.stringify(defaults)); + } catch {} +} + +/** + * Persist the ASR defaults to the daemon (settings.json) and the local cache. + * + * Best-effort: the daemon write is fire-and-forget (errors are swallowed) and + * the cache write ignores storage errors. + */ +export function saveAsrDefaults(defaults: AsrDefaults): void { + cacheAsrDefaults(defaults); + invoke("set_asr_settings", { config: defaults }).catch(() => {}); +} + +// ── Engine event payload (daemon `"asr"` WebSocket events) ──────────────────── + +export type AsrEventKind = + | "loading" + | "download" + | "listening" + | "speech_start" + | "speech_end" + | "transcript" + | "error" + | "stopped" + | "assistant"; + +/** Payload of a daemon `"asr"` event (`DaemonEvent.payload`). */ +export interface AsrEventPayload { + kind: AsrEventKind; + // transcript / assistant + text?: string; + is_final?: boolean; + // error + message?: string; + // assistant + spoken?: boolean; + // download progress + label?: string; + downloaded?: number; + total?: number; +} + +/** Visual listening state derived from the event stream. */ +export type AsrPhase = "idle" | "loading" | "listening" | "speaking"; + +/** Status returned by the `asr_status` command. */ +export interface AsrStatus { + running: boolean; + available: boolean; + trigger: AsrTrigger | null; + routing: AsrRouting | null; + language: string | null; + ptt_active: boolean; +} diff --git a/src/lib/chat/chat-eeg.ts b/src/lib/chat/chat-eeg.ts new file mode 100644 index 000000000..280f60593 --- /dev/null +++ b/src/lib/chat/chat-eeg.ts @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +/** + * EEG context injection — builds a compact brain-state summary block + * suitable for injection into an LLM system prompt. + * + * Extracted from `routes/chat/+page.svelte`. + */ + +import type { BandPowers, BandSnapshot } from "$lib/chat/chat-types"; + +/** + * Build a compact EEG brain-state block suitable for injection into a + * system prompt. Averages relative band powers across all channels. + */ +export function buildEegBlock(b: BandSnapshot): string { + const n = b.channels.length || 1; + const avg = (key: keyof BandPowers) => b.channels.reduce((s, ch) => s + (ch[key] as number), 0) / n; + + const pct = (v: number) => `${(v * 100).toFixed(0)}%`; + const f1 = (v: number) => v.toFixed(1); + const f2 = (v: number) => (v >= 0 ? "+" : "") + v.toFixed(2); + + const rD = avg("rel_delta"); + const rT = avg("rel_theta"); + const rA = avg("rel_alpha"); + const rB = avg("rel_beta"); + const rG = avg("rel_gamma"); + + const dominant = b.channels[0]?.dominant ?? "—"; + + const lines: string[] = [ + "--- Live EEG Brain State (auto-updated) ---", + `Signal quality (SNR): ${f1(b.snr ?? 0)} dB | Dominant band: ${dominant}`, + `Relative band powers: δ=${pct(rD)} θ=${pct(rT)} α=${pct(rA)} β=${pct(rB)} γ=${pct(rG)}`, + `Mood: ${(b.mood ?? 0).toFixed(0)}/100 | FAA (approach): ${f2(b.faa)}`, + `TAR (θ/α — drowsiness): ${f1(b.tar ?? 0)} | BAR (β/α — focus/stress): ${f1(b.bar ?? 0)}`, + `Coherence (α sync): ${((b.coherence ?? 0) * 100).toFixed(0)}% | Consciousness: wakefulness=${(b.consciousness_wakefulness ?? 0).toFixed(0)} integration=${(b.consciousness_integration ?? 0).toFixed(0)}`, + ]; + + if (b.meditation != null) lines.push(`Meditation: ${b.meditation.toFixed(0)}/100`); + if (b.cognitive_load != null) lines.push(`Cognitive load: ${b.cognitive_load.toFixed(0)}/100`); + if (b.drowsiness != null) lines.push(`Drowsiness: ${b.drowsiness.toFixed(0)}/100`); + if (b.hr != null) + lines.push( + `Heart rate: ${b.hr.toFixed(0)} bpm${b.rmssd != null ? ` | HRV (RMSSD): ${b.rmssd.toFixed(1)} ms` : ""}`, + ); + if (b.stress_index != null) lines.push(`Stress index: ${b.stress_index.toFixed(1)}`); + if (b.respiratory_rate != null) lines.push(`Respiratory rate: ${b.respiratory_rate.toFixed(1)} breaths/min`); + + lines.push("--- End EEG State ---"); + return lines.join("\n"); +} diff --git a/src/lib/chat/chat-types.ts b/src/lib/chat/chat-types.ts new file mode 100644 index 000000000..801702ae8 --- /dev/null +++ b/src/lib/chat/chat-types.ts @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +/** + * Shared type definitions for the chat feature. + * + * Extracted from `routes/chat/+page.svelte` to reduce file size and + * allow reuse across chat sub-components. + */ + +// ── Core types ────────────────────────────────────────────────────────────── + +export type Role = "user" | "assistant" | "system"; +export type ServerStatus = "stopped" | "loading" | "running"; + +export interface ToolUseEvent { + tool: string; + status: string; // "calling" | "done" | "error" | "approval_required" | "cancelled" + detail?: string; + toolCallId?: string; + args?: Record; // structured arguments from tool_execution_start + result?: unknown; // structured result from tool_execution_end + expanded?: boolean; // UI toggle +} + +export interface Attachment { + dataUrl: string; + mimeType: string; + name: string; +} + +export interface UsageInfo { + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; + n_ctx: number; +} + +export interface Message { + id: number; + role: Role; + content: string; + /** Assistant text emitted before a block, shown as its own bubble */ + leadIn?: string; + /** Images attached to a user message */ + attachments?: Attachment[]; + /** Chain-of-thought text between … (stripped from content) */ + thinking?: string; + /** Whether the thinking block is expanded in the UI */ + thinkOpen?: boolean; + /** True while we're streaming tokens in */ + pending?: boolean; + /** This turn sent an image — show an "analyzing image" processing hint during + * the vision-encode/prefill lead-in (before the first token streams). */ + processingImage?: boolean; + /** Real backend generation phase during the lead-in ("vision" | "prefill"), + * set from `status` chunks; drives the phase-accurate processing label. */ + phase?: string; + /** ms taken for first token */ + ttft?: number; + /** ms for full response */ + elapsed?: number; + /** Token usage from the final SSE chunk */ + usage?: UsageInfo; + /** Tool calls made during this response */ + toolUses?: ToolUseEvent[]; +} + +// ── Server status payload ─────────────────────────────────────────────────── + +export interface ServerStatusPayload { + status: ServerStatus; + model_name: string; + model?: string; + supports_vision?: boolean; +} + +// ── IPC streaming types (mirror Rust ChatChunk) ───────────────────────────── + +export interface ChatChunkDelta { + type: "delta"; + content: string; +} +export interface ChatChunkToolUse { + type: "tool_use"; + tool: string; + status: string; + detail?: string; +} +export interface ChatChunkToolExecStart { + type: "tool_execution_start"; + tool_call_id: string; + tool_name: string; + args: Record; +} +export interface ChatChunkToolExecEnd { + type: "tool_execution_end"; + tool_call_id: string; + tool_name: string; + result: unknown; + is_error: boolean; +} +export interface ChatChunkToolCancelled { + type: "tool_cancelled"; + tool_call_id: string; + tool_name: string; +} +export interface ChatChunkDone { + type: "done"; + finish_reason: string; + prompt_tokens: number; + completion_tokens: number; + n_ctx: number; +} +export interface ChatChunkError { + type: "error"; + message: string; +} +/** Real generation-phase marker for the progress UI during the pre-token lead-in + * ("vision" while the image is encoded, "prefill" during the LM prefill). */ +export interface ChatChunkStatus { + type: "status"; + phase: string; +} +export type ChatChunk = + | ChatChunkDelta + | ChatChunkToolUse + | ChatChunkToolExecStart + | ChatChunkToolExecEnd + | ChatChunkToolCancelled + | ChatChunkDone + | ChatChunkError + | ChatChunkStatus; + +// ── Thinking budget ───────────────────────────────────────────────────────── + +export type ThinkingLevel = "minimal" | "normal" | "extended" | "unlimited"; + +export const THINKING_LEVELS: { labelKey: string; key: ThinkingLevel; budget: number | null }[] = [ + { labelKey: "chat.think.minimal", key: "minimal", budget: 512 }, + { labelKey: "chat.think.normal", key: "normal", budget: 2048 }, + { labelKey: "chat.think.extended", key: "extended", budget: 8192 }, + { labelKey: "chat.think.unlimited", key: "unlimited", budget: null }, +]; + +// ── Tool configuration ────────────────────────────────────────────────────── + +export type ToolExecutionMode = "sequential" | "parallel"; +export type CompressionLevel = "off" | "normal" | "aggressive"; + +export interface ToolContextCompression { + level: CompressionLevel; + max_search_results: number; + max_result_chars: number; +} + +export const DEFAULT_TOOL_CONTEXT_COMPRESSION: ToolContextCompression = { + level: "normal", + max_search_results: 0, + max_result_chars: 0, +}; + +export interface ToolConfig { + enabled: boolean; + date: boolean; + location: boolean; + web_search: boolean; + web_fetch: boolean; + bash: boolean; + require_bash_edit: boolean; + read_file: boolean; + write_file: boolean; + edit_file: boolean; + skill_api: boolean; + execution_mode: ToolExecutionMode; + max_rounds: number; + max_calls_per_round: number; + thinking_budget: number | null; + context_compression: ToolContextCompression; +} + +export type ToolThinkingLevel = "chat" | "none" | "minimal" | "normal" | "extended"; + +export const TOOL_THINKING_LEVELS: { labelKey: string; key: ToolThinkingLevel; budget: number | null }[] = [ + { labelKey: "chat.tools.thinkChat", key: "chat", budget: null }, + { labelKey: "chat.tools.thinkNone", key: "none", budget: 0 }, + { labelKey: "chat.tools.thinkMinimal", key: "minimal", budget: 256 }, + { labelKey: "chat.tools.thinkNormal", key: "normal", budget: 1024 }, + { labelKey: "chat.tools.thinkExtended", key: "extended", budget: 4096 }, +]; + +export const DEFAULT_TOOL_CONFIG: ToolConfig = { + enabled: true, + date: true, + location: true, + web_search: true, + web_fetch: true, + bash: false, + require_bash_edit: false, + read_file: false, + write_file: false, + edit_file: false, + skill_api: true, + execution_mode: "parallel", + max_rounds: 3, + max_calls_per_round: 4, + thinking_budget: null, + context_compression: { ...DEFAULT_TOOL_CONTEXT_COMPRESSION }, +}; + +// ── Stored-message type (mirrors Rust StoredMessage) ──────────────────────── + +export interface StoredToolCallRow { + id: number; + message_id: number; + tool: string; + status: string; + detail?: string | null; + tool_call_id?: string | null; + args?: Record; + result?: unknown; + created_at: number; +} + +export interface StoredMessage { + id: number; + session_id: number; + role: string; + content: string; + thinking: string | null; + created_at: number; + tool_calls: StoredToolCallRow[]; +} + +export interface ChatSessionResponse { + session_id: number; + messages: StoredMessage[]; +} + +// ── System prompt presets ─────────────────────────────────────────────────── + +export const SYSTEM_PROMPT_DEFAULT = "You are a helpful assistant."; +export const SYSTEM_PROMPT_KEY = "chat.systemPrompt"; + +export const SYSTEM_PROMPT_PRESETS: { key: string; icon: string; prompt: string }[] = [ + { + key: "default", + icon: "🤖", + prompt: "You are a helpful assistant.", + }, + { + key: "coach", + icon: "🧘", + prompt: + "You are a neurofeedback coach specialising in relaxation and stress reduction. " + + "Give practical, evidence-based advice grounded in the user's live EEG data. " + + "Be encouraging, clear, and concise.", + }, + { + key: "focus", + icon: "🎯", + prompt: + "You are a focus and cognitive-performance coach. " + + "Help the user optimise their attention, working memory, and mental endurance " + + "using insights from their EEG readings. Offer actionable protocols.", + }, + { + key: "educator", + icon: "📚", + prompt: + "You are a neuroscience educator. " + + "Explain brain-wave patterns and EEG metrics in plain, accessible language. " + + "Use analogies freely and avoid unnecessary jargon.", + }, + { + key: "sleep", + icon: "😴", + prompt: + "You are a sleep and recovery specialist. " + + "Interpret the user's EEG data to provide personalised guidance on improving " + + "sleep quality, recovery, and circadian regulation.", + }, + { + key: "mindfulness", + icon: "🌿", + prompt: + "You are a mindfulness and meditation guide. " + + "Use the user's real-time brainwave data to suggest meditation techniques, " + + "breathing exercises, and awareness practices tailored to their current state.", + }, +]; + +// ── EEG band snapshot — re-exported from the canonical BandChart module ───── +// The full BandPowers / BandSnapshot interfaces live in BandChart.svelte. +// Re-export here so chat-related code doesn't need to import from a UI component. +export type { BandPowers, BandSnapshot } from "$lib/charts/BandChart.svelte"; + +// ── Conversion helpers ────────────────────────────────────────────────────── + +export function storedToMessage(sm: StoredMessage, idCounter: { value: number }): Message { + const msg: Message = { + id: ++idCounter.value, + role: sm.role as Role, + content: sm.content, + thinking: sm.thinking ?? undefined, + thinkOpen: false, + pending: false, + }; + if (sm.tool_calls && sm.tool_calls.length > 0) { + msg.toolUses = sm.tool_calls.map( + (tc: StoredToolCallRow): ToolUseEvent => ({ + tool: tc.tool, + status: tc.status, + detail: tc.detail ?? undefined, + toolCallId: tc.tool_call_id ?? undefined, + args: tc.args ?? undefined, + result: tc.result ?? undefined, + expanded: false, + }), + ); + } + return msg; +} + +/** A text segment in a multi-part user message. */ +export interface TextContentPart { + type: "text"; + text: string; +} +/** An image segment in a multi-part user message. */ +export interface ImageContentPart { + type: "image_url"; + image_url: { url: string }; +} +/** Union of all multi-part content segments. */ +export type ContentPart = TextContentPart | ImageContentPart; + +/** + * Build the content field for a user message (plain string or parts array). + * If images are present, returns a multi-part content array for the API. + */ +export function buildUserContent(text: string, imgs: Attachment[]): string | ContentPart[] { + if (imgs.length === 0) return text; + const parts: ContentPart[] = []; + if (text.trim()) parts.push({ type: "text", text }); + for (const img of imgs) { + parts.push({ type: "image_url", image_url: { url: img.dataUrl } }); + } + return parts; +} + +/** + * Rough token estimate: ~4 chars per token, ~1 token overhead. + */ +export function estimateTokens(text: string): number { + return Math.ceil(text.length / 4) + 1; +} diff --git a/src/lib/chat/chat-utils.ts b/src/lib/chat/chat-utils.ts new file mode 100644 index 000000000..689758384 --- /dev/null +++ b/src/lib/chat/chat-utils.ts @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +/** + * Pure utility functions for the chat page — tool-call parsing, + * danger detection, and assistant output parsing. + * + * Extracted from `routes/chat/+page.svelte` to reduce file size and + * allow independent unit testing. + */ + +import type { TranslationKey } from "$lib/i18n/keys"; + +// ── Generation-phase progress labels ──────────────────────────────────────── + +/** Backend generation-phase identifiers, delivered on `status` chunks during + * the pre-token lead-in (see `ChatChunkStatus`). Kept here so the string + * literals live in exactly one place on the frontend. */ +export const GEN_PHASE = { + vision: "vision", + prefill: "prefill", + /** A built-in tool is executing (web_search, location, …). These can take + * tens of seconds, and before they were reported the client simply went + * quiet for the whole round-trip. */ + tool: "tool", +} as const; + +/** i18n key for the pre-token "processing" indicator: the real backend phase + * when known ("vision" → reading image, "prefill" → preparing response), + * otherwise the image/text fallback. */ +export function processingLabelKey(phase: string | undefined, hasImage: boolean | undefined): TranslationKey { + switch (phase) { + case GEN_PHASE.vision: + return "chat.phaseVision"; + case GEN_PHASE.prefill: + return "chat.phasePrefill"; + case GEN_PHASE.tool: + return "chat.phaseTool"; + default: + return hasImage ? "chat.analyzingImage" : "chat.processing"; + } +} + +// ── Tool-call stripping ───────────────────────────────────────────────────── + +/** Known built-in tool names — must stay in sync with KNOWN_TOOL_NAMES in tools.rs */ +const KNOWN_TOOLS = new Set([ + "date", + "location", + "web_search", + "web_fetch", + "bash", + "read_file", + "write_file", + "edit_file", + "search_output", +]); + +function isToolCallObject(v: Record): boolean { + if (("name" in v || "tool" in v || "tool_calls" in v) && ("parameters" in v || "arguments" in v || "tool_calls" in v)) + return true; + const keys = Object.keys(v); + return ( + keys.length > 0 && + keys.some((k) => KNOWN_TOOLS.has(k)) && + Object.values(v).every((val) => typeof val === "object" && val !== null) + ); +} + +function looksLikeToolCallJsonPrefix(s: string): boolean { + const trimmed = s.trimStart(); + if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return false; + + const probe = trimmed.slice(0, 320).toLowerCase(); + const isDictStyle = [...KNOWN_TOOLS].some((name) => probe.includes(`"${name}":`) || probe.includes(`"${name}": `)); + if (isDictStyle) return true; + + const mentionsToolName = + probe.includes('"name"') || + probe.includes('"tool"') || + probe.includes('"tool_calls"') || + probe.includes('"function"'); + const mentionsArgs = probe.includes('"parameter') || probe.includes('"argument') || probe.includes(""); + + return mentionsToolName && mentionsArgs; +} + +function isToolCallArray(v: unknown): boolean { + if (!Array.isArray(v)) return false; + return v.some( + (item) => + typeof item === "object" && + item !== null && + !Array.isArray(item) && + isToolCallObject(item as Record), + ); +} + +/** + * Strip tool-call JSON code fences that leaked into rawAcc. + * + * The streaming sanitizer on the Rust side holds back tool-call JSON, but + * it can only recognise a fence as a tool call once it has seen BOTH a + * "tool"/"name" key AND a "parameters"/"arguments" key. Tokens emitted + * before that threshold are already in rawAcc and need to be cleaned here. + */ +export function stripToolCallFences(raw: string): string { + // 1. Complete fenced blocks + let s = raw.replace(/```(?:json)?\n([\s\S]*?)\n?```/g, (match, body: string) => { + const trimmedBody = body.trim(); + try { + const v = JSON.parse(trimmedBody); + if (typeof v === "object" && v !== null && !Array.isArray(v) && isToolCallObject(v)) return ""; + if (isToolCallArray(v)) return ""; + } catch { + /* not JSON — keep */ + } + if (looksLikeToolCallJsonPrefix(trimmedBody)) return ""; + return match; + }); + + // 2a. Incomplete fence immediately before a tag. + s = s.replace(/```(?:json)?\n([\s\S]*?)(?=\n*)/g, (match, body: string) => + looksLikeToolCallJsonPrefix(body) ? "" : match, + ); + + // 2b. Incomplete fence at end of string (still streaming). + s = s.replace(/```(?:json)?\n([\s\S]*)$/g, (match, body: string) => (looksLikeToolCallJsonPrefix(body) ? "" : match)); + + // 3. Bare inline tool-call JSON (not fenced) + s = s.replace(/(?:^|\n)\s*[[{][\s\S]*$/gm, (match) => { + if (looksLikeToolCallJsonPrefix(match.trim())) return ""; + return match; + }); + + // 4. Complete [TOOL_CALL]…[/TOOL_CALL] blocks + s = s.replace(/\[TOOL_CALL\][\s\S]*?\[\/TOOL_CALL\]/g, ""); + + // 5. Incomplete [TOOL_CALL] at end of string (mid-stream) + s = s.replace(/\[TOOL_C[\s\S]*$/g, ""); + + return s; +} + +// ── Lead-in cleaning ──────────────────────────────────────────────────────── + +export function cleanAssistantLeadIn(raw: string): string { + return raw + .replace(/```[a-z]*\s*/gi, "") + .split("\n") + .filter((line) => !/^\s*(json|copy)\s*$/i.test(line)) + .join("\n") + .trim(); +} + +/** + * Clean lead-in text for display. When tool calls are active, aggressively + * strip ALL incomplete code fences and any JSON-like fragments. + */ +export function cleanLeadInForDisplay(raw: string, hasToolUses: boolean): string { + if (!raw.trim()) return ""; + + let s = stripToolCallFences(raw); + + if (hasToolUses) { + s = s.replace(/```[a-z]*\n[\s\S]*$/gi, ""); + s = s.replace(/\n\s*[[{][\s\S]*$/g, ""); + } + + return s.trim(); +} + +// ── Danger detection ──────────────────────────────────────────────────────── + +/** Bash patterns that indicate a potentially dangerous command (mirrored from Rust). */ +const DANGEROUS_BASH_PATTERNS = [ + "rm ", + "rm\t", + "rmdir", + "shred", + "mkfs", + "dd if=", + "dd of=", + "sudo ", + "su -", + "su\t", + "> /dev/", + "chmod", + "chown", + "kill ", + "killall", + "pkill", + "shutdown", + "reboot", + "halt", + "poweroff", + "systemctl stop", + "systemctl disable", + ":(){ :|:& };:", + "/etc/", + "/boot/", + "/usr/", + "/var/", + "/sys/", + "/proc/", +]; + +/** Sensitive path prefixes (mirrored from Rust). */ +const SENSITIVE_PATH_PREFIXES = [ + "/etc/", + "/boot/", + "/usr/", + "/var/", + "/sys/", + "/proc/", + "/bin/", + "/sbin/", + "/lib/", + "/opt/", +]; + +/** + * Check if a tool call looks dangerous based on its name and arguments. + * Returns a danger reason i18n key, or null if safe. + * + * Accepts any object with `tool` and optional `args` fields. + */ +export function detectToolDanger(tu: { tool: string; args?: Record }): string | null { + if (tu.tool === "bash" && typeof tu.args?.command === "string") { + const cmd = tu.args.command.toLowerCase(); + for (const pat of DANGEROUS_BASH_PATTERNS) { + if (cmd.includes(pat)) { + return `chat.tools.dangerBash`; + } + } + } + if (["write_file", "edit_file", "read_file"].includes(tu.tool) && typeof tu.args?.path === "string") { + const path = tu.args.path; + for (const prefix of SENSITIVE_PATH_PREFIXES) { + if (path.startsWith(prefix)) { + return `chat.tools.dangerPath`; + } + } + } + return null; +} + +// ── Assistant output parsing ──────────────────────────────────────────────── + +/** + * Split raw assistant output into three display zones: + * - `leadIn` = inter-think text segments (all except the last) + * - `thinking` = merged … blocks + * - `content` = the final answer (last non-empty segment) + */ +export function parseAssistantOutput(raw: string): { + leadIn: string; + thinking: string; + content: string; +} { + const s = stripToolCallFences(raw); + + if (!s.includes("")) return { leadIn: "", thinking: "", content: s }; + + const thinkingParts: string[] = []; + let withoutThink = s.replace(/([\s\S]*?)<\/think>/g, (_: string, inner: string) => { + thinkingParts.push(inner.trim()); + return "\x00"; + }); + + // Handle an unclosed at the end (still streaming) + const openIdx = withoutThink.indexOf(""); + if (openIdx !== -1) { + thinkingParts.push(withoutThink.slice(openIdx + 7).trim()); + withoutThink = withoutThink.slice(0, openIdx); + } + + const thinking = thinkingParts.join("\n\n"); + + const parts = withoutThink + .split("\x00") + .map((p: string) => p.trim()) + .filter(Boolean); + + if (parts.length === 0) return { leadIn: "", thinking, content: "" }; + + const content = parts[parts.length - 1]; + const leadIn = parts.slice(0, -1).map(cleanAssistantLeadIn).filter(Boolean).join("\n\n"); + + return { leadIn, thinking, content }; +} diff --git a/src/lib/chat/index.ts b/src/lib/chat/index.ts new file mode 100644 index 000000000..66ec9a217 --- /dev/null +++ b/src/lib/chat/index.ts @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 only. + +// Re-export chat types and utilities for convenient imports +export type { Attachment, Message, Role, UsageInfo } from "./chat-types"; diff --git a/src/lib/compare/compare-canvas.ts b/src/lib/compare/compare-canvas.ts new file mode 100644 index 000000000..f8552b905 --- /dev/null +++ b/src/lib/compare/compare-canvas.ts @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +/** + * Canvas drawing functions for the compare page — spectrum bars, diff chart, + * radar, heatmaps. + * + * Extracted from `routes/compare/+page.svelte`. + */ + +import { bandKeys, bandMeta, radarMetrics } from "$lib/compare/compare-types"; +import type { EpochRow, SessionMetrics } from "$lib/dashboard/SessionDetail.svelte"; +import { setupHiDpiCanvas } from "$lib/format"; + +// ── Heatmap definitions ────────────────────────────────────────────────────── + +export const HM_BANDS_DEF = [ + { + key: "rd" as const, + sym: "δ", + lo: [20, 20, 50] as [number, number, number], + hi: [99, 102, 241] as [number, number, number], + }, + { + key: "rt" as const, + sym: "θ", + lo: [25, 15, 55] as [number, number, number], + hi: [139, 92, 246] as [number, number, number], + }, + { + key: "ra" as const, + sym: "α", + lo: [10, 40, 20] as [number, number, number], + hi: [34, 197, 94] as [number, number, number], + }, + { + key: "rb" as const, + sym: "β", + lo: [10, 30, 60] as [number, number, number], + hi: [59, 130, 246] as [number, number, number], + }, + { + key: "rg" as const, + sym: "γ", + lo: [55, 45, 10] as [number, number, number], + hi: [245, 158, 11] as [number, number, number], + }, +] as const; + +export const HM_SCORES_DEF = [ + { + key: "relaxation" as const, + sym: "Rlx", + lo: [10, 45, 35] as [number, number, number], + hi: [16, 185, 129] as [number, number, number], + }, + { + key: "engagement" as const, + sym: "Eng", + lo: [55, 45, 10] as [number, number, number], + hi: [245, 158, 11] as [number, number, number], + }, + { + key: "med" as const, + sym: "Med", + lo: [35, 15, 70] as [number, number, number], + hi: [139, 92, 246] as [number, number, number], + }, + { + key: "cog" as const, + sym: "Cog", + lo: [10, 35, 55] as [number, number, number], + hi: [14, 165, 233] as [number, number, number], + }, + { + key: "drow" as const, + sym: "Drw", + lo: [55, 10, 10] as [number, number, number], + hi: [239, 68, 68] as [number, number, number], + }, +] as const; + +export const HEATMAP_ROW_H = 14; +export const HEATMAP_LABEL_W = 22; + +// ── Primitives ─────────────────────────────────────────────────────────────── + +export function lerpRgba(lo: [number, number, number], hi: [number, number, number], t: number, alpha: number): string { + const r = Math.round(lo[0] + (hi[0] - lo[0]) * t); + const g = Math.round(lo[1] + (hi[1] - lo[1]) * t); + const b = Math.round(lo[2] + (hi[2] - lo[2]) * t); + return `rgba(${r},${g},${b},${alpha})`; +} + +function roundedBar(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, r: number) { + if (h < r * 2) r = h / 2; + if (r < 0) r = 0; + ctx.beginPath(); + ctx.moveTo(x + r, y); + ctx.lineTo(x + w - r, y); + ctx.quadraticCurveTo(x + w, y, x + w, y + r); + ctx.lineTo(x + w, y + h); + ctx.lineTo(x, y + h); + ctx.lineTo(x, y + r); + ctx.quadraticCurveTo(x, y, x + r, y); + ctx.closePath(); +} + +function roundRect(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, r: number) { + ctx.moveTo(x + r, y); + ctx.lineTo(x + w - r, y); + ctx.quadraticCurveTo(x + w, y, x + w, y + r); + ctx.lineTo(x + w, y + h - r); + ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); + ctx.lineTo(x + r, y + h); + ctx.quadraticCurveTo(x, y + h, x, y + h - r); + ctx.lineTo(x, y + r); + ctx.quadraticCurveTo(x, y, x + r, y); + ctx.closePath(); +} + +// ── Spectrum bar ───────────────────────────────────────────────────────────── + +export function drawSpectrum(canvas: HTMLCanvasElement, m: SessionMetrics, _label: string) { + const w = canvas.clientWidth; + const h = canvas.clientHeight; + const ctx = setupHiDpiCanvas(canvas, w, h); + + const vals = bandKeys.map((k) => m[k] ?? 0); + let sum = vals.reduce((a, b) => a + b, 0); + if (sum < 1e-6) sum = 1; + + const barY = 0, + barH = h, + r = 8; + ctx.save(); + ctx.beginPath(); + roundRect(ctx, 0, barY, w, barH, r); + ctx.clip(); + + let x = 0; + for (let i = 0; i < 5; i++) { + const segW = (vals[i] / sum) * w; + ctx.fillStyle = bandMeta[i].color; + ctx.globalAlpha = 0.82; + ctx.fillRect(x, barY, segW + 0.5, barH); + x += segW; + } + + ctx.globalAlpha = 0.38; + ctx.fillStyle = "#000"; + ctx.fillRect(0, barY, w, barH); + ctx.globalAlpha = 1; + + x = 0; + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; + for (let i = 0; i < 5; i++) { + const segW = (vals[i] / sum) * w; + if (segW > 32) { + ctx.font = "bold 11px ui-sans-serif, system-ui, sans-serif"; + ctx.fillStyle = "#fff"; + ctx.globalAlpha = 0.95; + ctx.fillText(`${bandMeta[i].sym} ${Math.round((vals[i] / sum) * 100)}%`, x + segW / 2, barH / 2); + } + x += segW; + } + ctx.restore(); +} + +// ── Diff chart ─────────────────────────────────────────────────────────────── + +export function drawDiffChart(canvas: HTMLCanvasElement, a: SessionMetrics, b: SessionMetrics) { + const w = canvas.clientWidth; + const h = canvas.clientHeight; + const ctx = setupHiDpiCanvas(canvas, w, h); + + const ml = 4, + mr = 4, + mt = 14, + mb = 16; + const cw = w - ml - mr; + const ch = h - mt - mb; + + const valsA = bandKeys.map((k) => a[k] ?? 0); + const valsB = bandKeys.map((k) => b[k] ?? 0); + const maxVal = Math.max(...valsA, ...valsB, 0.01); + + const nBands = 5; + const groupW = cw / nBands; + const barW = groupW * 0.32; + const gap = groupW * 0.06; + + for (let i = 0; i < nBands; i++) { + const gx = ml + i * groupW; + + const hA = (valsA[i] / maxVal) * ch; + ctx.fillStyle = bandMeta[i].color; + ctx.globalAlpha = 0.9; + roundedBar(ctx, gx + groupW / 2 - barW - gap / 2, mt + ch - hA, barW, hA, 3); + ctx.fill(); + + const hB = (valsB[i] / maxVal) * ch; + ctx.globalAlpha = 0.45; + roundedBar(ctx, gx + groupW / 2 + gap / 2, mt + ch - hB, barW, hB, 3); + ctx.fill(); + + ctx.globalAlpha = 1; + ctx.font = "bold 9px ui-monospace, 'JetBrains Mono', monospace"; + ctx.fillStyle = bandMeta[i].color; + ctx.textAlign = "center"; + ctx.textBaseline = "top"; + ctx.fillText(bandMeta[i].sym, gx + groupW / 2, mt + ch + 4); + } + + ctx.font = "bold 8px ui-sans-serif, system-ui, sans-serif"; + ctx.textBaseline = "top"; + ctx.textAlign = "left"; + ctx.fillStyle = getComputedStyle(canvas).getPropertyValue("color") || "#888"; + ctx.globalAlpha = 0.7; + ctx.fillText("A ■ B ▪", ml + 2, 2); + ctx.globalAlpha = 1; +} + +// ── Radar chart ────────────────────────────────────────────────────────────── + +export function drawRadar(canvas: HTMLCanvasElement, a: SessionMetrics, b: SessionMetrics) { + const w = canvas.clientWidth; + const h = canvas.clientHeight; + const ctx = setupHiDpiCanvas(canvas, w, h); + + const cx = w / 2, + cy = h / 2; + const radius = Math.min(cx, cy) - 28; + const n = radarMetrics.length; + const angleStep = (Math.PI * 2) / n; + + // Grid rings + for (let ring = 1; ring <= 4; ring++) { + const r = (ring / 4) * radius; + ctx.beginPath(); + for (let i = 0; i <= n; i++) { + const angle = i * angleStep - Math.PI / 2; + const x = cx + Math.cos(angle) * r; + const y = cy + Math.sin(angle) * r; + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + } + ctx.strokeStyle = "rgba(128,128,128,0.12)"; + ctx.lineWidth = 0.5; + ctx.stroke(); + } + + // Axis lines + labels + ctx.font = "600 9px ui-sans-serif, system-ui, sans-serif"; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + for (let i = 0; i < n; i++) { + const angle = i * angleStep - Math.PI / 2; + const ex = cx + Math.cos(angle) * radius; + const ey = cy + Math.sin(angle) * radius; + ctx.beginPath(); + ctx.moveTo(cx, cy); + ctx.lineTo(ex, ey); + ctx.strokeStyle = "rgba(128,128,128,0.15)"; + ctx.lineWidth = 0.5; + ctx.stroke(); + + const lx = cx + Math.cos(angle) * (radius + 16); + const ly = cy + Math.sin(angle) * (radius + 16); + ctx.fillStyle = radarMetrics[i].color; + ctx.globalAlpha = 0.8; + ctx.fillText(radarMetrics[i].label, lx, ly); + ctx.globalAlpha = 1; + } + + function drawPoly(metrics: SessionMetrics, color: string, alpha: number) { + ctx.beginPath(); + for (let i = 0; i < n; i++) { + const angle = i * angleStep - Math.PI / 2; + const val = Number(metrics[radarMetrics[i].key]) || 0; + const r = (Math.min(val, radarMetrics[i].max) / radarMetrics[i].max) * radius; + const x = cx + Math.cos(angle) * r; + const y = cy + Math.sin(angle) * r; + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + } + ctx.closePath(); + ctx.fillStyle = color; + ctx.globalAlpha = alpha * 0.2; + ctx.fill(); + ctx.strokeStyle = color; + ctx.globalAlpha = alpha; + ctx.lineWidth = 1.5; + ctx.stroke(); + ctx.globalAlpha = 1; + + for (let i = 0; i < n; i++) { + const angle = i * angleStep - Math.PI / 2; + const val = Number(metrics[radarMetrics[i].key]) || 0; + const r = (Math.min(val, radarMetrics[i].max) / radarMetrics[i].max) * radius; + const x = cx + Math.cos(angle) * r; + const y = cy + Math.sin(angle) * r; + ctx.beginPath(); + ctx.arc(x, y, 2.5, 0, Math.PI * 2); + ctx.fillStyle = color; + ctx.globalAlpha = alpha; + ctx.fill(); + ctx.globalAlpha = 1; + } + } + + drawPoly(a, "#3b82f6", 0.9); + drawPoly(b, "#f59e0b", 0.65); +} + +// ── Heatmaps ───────────────────────────────────────────────────────────────── + +export function drawBandHeatmap(canvas: HTMLCanvasElement, ts: EpochRow[], dark: boolean) { + if (!canvas || ts.length < 2) return; + const rows = HM_BANDS_DEF; + const nRows = rows.length; + const cssH = HEATMAP_ROW_H * nRows; + const cssW = canvas.clientWidth; + if (cssW <= 0) return; + + const ctx = setupHiDpiCanvas(canvas, cssW, cssH); + ctx.fillStyle = dark ? "#0e0e1a" : "#f5f5fa"; + ctx.fillRect(0, 0, cssW, cssH); + + const plotW = cssW - HEATMAP_LABEL_W; + const nCols = ts.length; + const colW = plotW / nCols; + + for (let ri = 0; ri < nRows; ri++) { + const { key, sym, lo, hi } = rows[ri]; + const vals = ts.map((r) => r[key] as number); + const vMin = Math.min(...vals); + const vMax = Math.max(...vals); + const vRange = vMax - vMin || 1; + const y0 = ri * HEATMAP_ROW_H; + + ctx.font = "bold 8px ui-monospace, 'JetBrains Mono', monospace"; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillStyle = lerpRgba(lo, hi, 0.85, 0.9); + ctx.fillText(sym, HEATMAP_LABEL_W / 2, y0 + HEATMAP_ROW_H / 2); + + for (let ci = 0; ci < nCols; ci++) { + const t = Math.max(0, Math.min(1, (vals[ci] - vMin) / vRange)); + ctx.fillStyle = lerpRgba(lo, hi, t, 0.15 + t * 0.85); + ctx.fillRect(HEATMAP_LABEL_W + ci * colW, y0, colW + 0.5, HEATMAP_ROW_H); + } + + if (ri < nRows - 1) { + ctx.fillStyle = "rgba(0,0,0,0.35)"; + ctx.fillRect(HEATMAP_LABEL_W, y0 + HEATMAP_ROW_H - 0.5, plotW, 1); + } + } +} + +export function drawScoreHeatmap(canvas: HTMLCanvasElement, ts: EpochRow[], dark: boolean) { + if (!canvas || ts.length < 2) return; + const rows = HM_SCORES_DEF; + const nRows = rows.length; + const cssH = HEATMAP_ROW_H * nRows; + const cssW = canvas.clientWidth; + if (cssW <= 0) return; + + const ctx = setupHiDpiCanvas(canvas, cssW, cssH); + ctx.fillStyle = dark ? "#0e0e1a" : "#f5f5fa"; + ctx.fillRect(0, 0, cssW, cssH); + + const plotW = cssW - HEATMAP_LABEL_W; + const nCols = ts.length; + const colW = plotW / nCols; + + for (let ri = 0; ri < nRows; ri++) { + const { key, sym, lo, hi } = rows[ri]; + const vals = ts.map((r) => r[key] as number); + const vMin = Math.min(...vals); + const vMax = Math.max(...vals); + const vRange = vMax - vMin || 1; + const y0 = ri * HEATMAP_ROW_H; + + ctx.font = "bold 7px ui-monospace, 'JetBrains Mono', monospace"; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillStyle = lerpRgba(lo, hi, 0.85, 0.9); + ctx.fillText(sym, HEATMAP_LABEL_W / 2, y0 + HEATMAP_ROW_H / 2); + + for (let ci = 0; ci < nCols; ci++) { + const t = Math.max(0, Math.min(1, (vals[ci] - vMin) / vRange)); + ctx.fillStyle = lerpRgba(lo, hi, t, 0.15 + t * 0.85); + ctx.fillRect(HEATMAP_LABEL_W + ci * colW, y0, colW + 0.5, HEATMAP_ROW_H); + } + + if (ri < nRows - 1) { + ctx.fillStyle = "rgba(0,0,0,0.35)"; + ctx.fillRect(HEATMAP_LABEL_W, y0 + HEATMAP_ROW_H - 0.5, plotW, 1); + } + } +} + +export function drawBandDiffHeatmap(canvas: HTMLCanvasElement, tsA: EpochRow[], tsB: EpochRow[], dark: boolean) { + if (!canvas || tsA.length < 2 || tsB.length < 2) return; + const rows = HM_BANDS_DEF; + const nRows = rows.length; + const cssH = HEATMAP_ROW_H * nRows + 12; + const cssW = canvas.clientWidth; + if (cssW <= 0) return; + + const ctx = setupHiDpiCanvas(canvas, cssW, cssH); + ctx.fillStyle = dark ? "#0e0e1a" : "#f5f5fa"; + ctx.fillRect(0, 0, cssW, cssH); + + const nDisplay = Math.min(Math.max(tsA.length, tsB.length), 400); + const plotW = cssW - HEATMAP_LABEL_W; + const colW = plotW / nDisplay; + + function sampleIdx(tsLen: number, col: number): number { + return Math.min(Math.round((col / (nDisplay - 1)) * (tsLen - 1)), tsLen - 1); + } + + const BLUE_LO: [number, number, number] = [10, 50, 200]; + const BLUE_MID: [number, number, number] = [40, 80, 160]; + const NEUTRAL: [number, number, number] = dark ? [18, 18, 28] : [230, 230, 242]; + const RED_MID: [number, number, number] = [160, 40, 40]; + const RED_HI: [number, number, number] = [220, 30, 30]; + + function diffColor(d: number): string { + const absD = Math.abs(d); + const alpha = 0.12 + absD * 0.88; + if (d < 0) { + const t = Math.min(absD, 1); + return t < 0.5 ? lerpRgba(NEUTRAL, BLUE_MID, t * 2, alpha) : lerpRgba(BLUE_MID, BLUE_LO, (t - 0.5) * 2, alpha); + } else { + const t = Math.min(d, 1); + return t < 0.5 ? lerpRgba(NEUTRAL, RED_MID, t * 2, alpha) : lerpRgba(RED_MID, RED_HI, (t - 0.5) * 2, alpha); + } + } + + for (let ri = 0; ri < nRows; ri++) { + const { key, sym, lo, hi } = rows[ri]; + const y0 = ri * HEATMAP_ROW_H; + + const diffs: number[] = []; + for (let c = 0; c < nDisplay; c++) { + const iA = sampleIdx(tsA.length, c); + const iB = sampleIdx(tsB.length, c); + diffs.push((tsB[iB][key] as number) - (tsA[iA][key] as number)); + } + const maxAbsDiff = Math.max(...diffs.map(Math.abs), 0.001); + + ctx.font = "bold 8px ui-monospace, 'JetBrains Mono', monospace"; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillStyle = lerpRgba(lo, hi, 0.75, 0.8); + ctx.fillText(sym, HEATMAP_LABEL_W / 2, y0 + HEATMAP_ROW_H / 2); + + for (let c = 0; c < nDisplay; c++) { + const normD = diffs[c] / maxAbsDiff; + ctx.fillStyle = diffColor(normD); + ctx.fillRect(HEATMAP_LABEL_W + c * colW, y0, colW + 0.5, HEATMAP_ROW_H); + } + + if (ri < nRows - 1) { + ctx.fillStyle = "rgba(0,0,0,0.35)"; + ctx.fillRect(HEATMAP_LABEL_W, y0 + HEATMAP_ROW_H - 0.5, plotW, 1); + } + } + + // Legend bar + const legendY = nRows * HEATMAP_ROW_H + 2; + const legendH = 6; + const nStops = 80; + const sw = plotW / nStops; + for (let i = 0; i < nStops; i++) { + const d = (i / (nStops - 1)) * 2 - 1; + ctx.fillStyle = diffColor(d); + ctx.globalAlpha = 1; + ctx.fillRect(HEATMAP_LABEL_W + i * sw, legendY, sw + 0.5, legendH); + } + ctx.globalAlpha = 1; + + ctx.font = "7px ui-sans-serif, system-ui, sans-serif"; + ctx.textBaseline = "top"; + ctx.fillStyle = dark ? "rgba(100,120,200,0.9)" : "rgba(40,80,200,0.9)"; + ctx.textAlign = "left"; + ctx.fillText("A>B", HEATMAP_LABEL_W, legendY + legendH + 1); + ctx.fillStyle = dark ? "rgba(200,70,70,0.9)" : "rgba(180,30,30,0.9)"; + ctx.textAlign = "right"; + ctx.fillText("B>A", cssW, legendY + legendH + 1); +} diff --git a/src/lib/compare/compare-logic.ts b/src/lib/compare/compare-logic.ts new file mode 100644 index 000000000..5e786e546 --- /dev/null +++ b/src/lib/compare/compare-logic.ts @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +/** + * Compare page business logic — pure functions extracted from +page.svelte. + * + * Timeline helpers, date/time conversions, and range computations. + */ + +import type { EmbeddingSession } from "$lib/compare/compare-types"; +import { + dateToLocalKey, + fmtDateTime, + fmtDateTimeLocalInput, + fmtDayKey, + fmtDuration as fmtDurationSecs, + fromUnix, + parseDateTimeLocalInput, +} from "$lib/format"; + +// ── Date / time helpers ────────────────────────────────────────────────────── + +/** Local date string "YYYY-MM-DD" from a UTC unix-second timestamp. */ +export function localDateFromUtc(utc: number): string { + return dateToLocalKey(fromUnix(utc)); +} + +/** UTC seconds for local midnight of a "YYYY-MM-DD" date string. */ +export function localMidnight(dateStr: string): number { + const [y, mo, d] = dateStr.split("-").map(Number); + return Math.floor(new Date(y, mo - 1, d, 0, 0, 0).getTime() / 1000); +} + +/** "HH:MM" from a UTC unix-second timestamp (local time). */ +export function utcToTimeStr(utc: number): string { + const d = new Date(utc * 1000); + return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`; +} + +/** UTC seconds from a "YYYY-MM-DD" date and "HH:MM" time (local). */ +export function timeStrToUtc(dateStr: string, timeStr: string): number { + const [y, mo, d] = dateStr.split("-").map(Number); + const [h, mi] = timeStr.split(":").map(Number); + return Math.floor(new Date(y, mo - 1, d, h, mi, 0).getTime() / 1000); +} + +/** Human-readable date label for a "YYYY-MM-DD" string. */ +export function dayLabel(dateStr: string): string { + return fmtDayKey(dateStr); +} + +/** "YYYY-MM-DDThh:mm:ss" from a UTC unix-second timestamp (for datetime-local inputs). */ +export function utcToDateTimeLocal(utc: number): string { + return fmtDateTimeLocalInput(utc); +} + +/** UTC seconds from a "YYYY-MM-DDThh:mm" datetime-local string. */ +export function dateTimeLocalToUtc(dt: string): number { + return parseDateTimeLocalInput(dt); +} + +// ── Session helpers ────────────────────────────────────────────────────────── + +/** Sessions that overlap the half-open interval [startUtc, endUtc). */ +export function sessionsInRange(sessions: EmbeddingSession[], startUtc: number, endUtc: number): EmbeddingSession[] { + return sessions.filter((s) => s.end_utc > startUtc && s.start_utc < endUtc); +} + +/** Format a session as a human-readable label. */ +export function sessionLabel(s: EmbeddingSession): string { + const dt = fmtDateTime(s.start_utc); + const dur = fmtDurationSecs(s.end_utc - s.start_utc); + return `${dt} (${dur}, ${s.n_epochs} ep)`; +} + +/** Sorted unique day strings (newest first) that have recorded sessions. */ +export function sortedSessionDays(sessions: EmbeddingSession[]): string[] { + const s = new Set(); + for (const sess of sessions) { + const d = new Date( + fromUnix(sess.start_utc).getFullYear(), + fromUnix(sess.start_utc).getMonth(), + fromUnix(sess.start_utc).getDate(), + ); + const endD = fromUnix(sess.end_utc); + const endMid = new Date(endD.getFullYear(), endD.getMonth(), endD.getDate()); + while (d <= endMid) { + s.add(dateToLocalKey(d)); + d.setDate(d.getDate() + 1); + } + } + return [...s].sort().reverse(); // newest first +} + +// ── Range selection helpers ────────────────────────────────────────────────── + +/** Auto-select the range covering all sessions within the 48h window starting at anchorUtc. */ +export function autoSelectRange(sessions: EmbeddingSession[], anchorUtc: number): { start: number; end: number } { + const windowSess = sessions.filter((s) => s.end_utc > anchorUtc && s.start_utc < anchorUtc + 172800); + const rangeS = windowSess.length > 0 ? Math.min(...windowSess.map((s) => s.start_utc)) : anchorUtc; + const rangeE = Math.min( + windowSess.length > 0 ? Math.max(...windowSess.map((s) => s.end_utc)) : anchorUtc + 86400, + rangeS + 86400, + ); + return { start: rangeS, end: rangeE }; +} + +/** Snap a UTC timestamp to the nearest minute. */ +export function snapToMinute(utc: number): number { + return Math.round(utc / 60) * 60; +} + +/** Convert a pointer event's X position within an element to a UTC timestamp in a 48h window. */ +export function pointerToUtc(clientX: number, rect: DOMRect, anchorUtc: number): number { + const pct = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width)); + return Math.round(anchorUtc + pct * 172800); +} diff --git a/src/lib/compare/compare-types.ts b/src/lib/compare/compare-types.ts new file mode 100644 index 000000000..b1bfa5ae6 --- /dev/null +++ b/src/lib/compare/compare-types.ts @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +/** + * Shared type definitions and constants for the compare feature. + * + * Extracted from `routes/compare/+page.svelte`. + */ + +import { BANDS } from "$lib/constants"; +import type { SessionMetrics } from "$lib/dashboard/SessionDetail.svelte"; + +// ── Types ──────────────────────────────────────────────────────────────────── + +/** A contiguous recording range discovered from embedding timestamps. */ +export interface EmbeddingSession { + start_utc: number; + end_utc: number; + n_epochs: number; + day: string; +} + +/** Metric row descriptor for the advanced metrics table. */ +export interface MRow { + key: keyof SessionMetrics; + label: string; + unit: string; + fmt: (v: number) => string; +} + +export interface InsightDelta { + label: string; + key: keyof SessionMetrics; + a: number; + b: number; + delta: number; + pctChange: number; + direction: "improved" | "declined" | "stable"; +} + +export interface ClusterAnalysis { + separationScore: number; + interCluster: number; + intraSpreadA: number; + intraSpreadB: number; +} + +// ── Constants ──────────────────────────────────────────────────────────────── + +export { SESSION_COLORS } from "$lib/constants"; + +export const bandKeys = ["rel_delta", "rel_theta", "rel_alpha", "rel_beta", "rel_gamma"] as const; +export type BK = (typeof bandKeys)[number]; +export const bandMeta = BANDS.slice(0, 5); + +export const scoreKeys = [ + { key: "relaxation" as const, color: "#10b981", label: "dashboard.relaxation" }, + { key: "engagement" as const, color: "#f59e0b", label: "dashboard.engagement" }, +]; + +export const radarMetrics = [ + { key: "relaxation" as const, label: "Relax", max: 100, color: "#10b981" }, + { key: "engagement" as const, label: "Engage", max: 100, color: "#f59e0b" }, + { key: "meditation" as const, label: "Meditation", max: 100, color: "#8b5cf6" }, + { key: "cognitive_load" as const, label: "Cog Load", max: 100, color: "#0ea5e9" }, + { key: "drowsiness" as const, label: "Drowsiness", max: 100, color: "#ef4444" }, +]; + +const fmtF3 = (v: number) => v.toFixed(3); +const fmtF2 = (v: number) => v.toFixed(2); +const fmtF1 = (v: number) => v.toFixed(1); + +export const advancedMetrics: MRow[] = [ + { key: "tar", label: "compare.tar", unit: "", fmt: fmtF3 }, + { key: "bar", label: "compare.bar", unit: "", fmt: fmtF3 }, + { key: "dtr", label: "compare.dtr", unit: "", fmt: fmtF3 }, + { key: "tbr", label: "compare.tbr", unit: "", fmt: fmtF3 }, + { key: "pse", label: "compare.pse", unit: "", fmt: fmtF3 }, + { key: "apf", label: "compare.apf", unit: "Hz", fmt: fmtF2 }, + { key: "sef95", label: "compare.sef95", unit: "Hz", fmt: fmtF2 }, + { key: "spectral_centroid", label: "compare.spectralCentroid", unit: "Hz", fmt: fmtF2 }, + { key: "bps", label: "compare.bps", unit: "", fmt: fmtF3 }, + { key: "snr", label: "compare.snr", unit: "dB", fmt: fmtF1 }, + { key: "coherence", label: "compare.coherence", unit: "", fmt: fmtF3 }, + { key: "mu_suppression", label: "compare.muSuppression", unit: "", fmt: fmtF3 }, + { key: "mood", label: "compare.mood", unit: "", fmt: fmtF1 }, + { key: "hjorth_activity", label: "compare.hjorthActivity", unit: "µV²", fmt: fmtF3 }, + { key: "hjorth_mobility", label: "compare.hjorthMobility", unit: "", fmt: fmtF3 }, + { key: "hjorth_complexity", label: "compare.hjorthComplexity", unit: "", fmt: fmtF3 }, + { key: "permutation_entropy", label: "compare.permEntropy", unit: "", fmt: fmtF3 }, + { key: "higuchi_fd", label: "compare.higuchiFd", unit: "", fmt: fmtF3 }, + { key: "dfa_exponent", label: "compare.dfaExponent", unit: "", fmt: fmtF3 }, + { key: "sample_entropy", label: "compare.sampleEntropy", unit: "", fmt: fmtF3 }, + { key: "pac_theta_gamma", label: "compare.pacThetaGamma", unit: "", fmt: fmtF3 }, + { key: "laterality_index", label: "compare.lateralityIndex", unit: "", fmt: fmtF3 }, + { key: "echt", label: "compare.echt", unit: "", fmt: fmtF3 }, + { key: "hr", label: "compare.hr", unit: "bpm", fmt: fmtF1 }, + { key: "rmssd", label: "compare.rmssd", unit: "ms", fmt: fmtF1 }, + { key: "sdnn", label: "compare.sdnn", unit: "ms", fmt: fmtF1 }, + { key: "pnn50", label: "compare.pnn50", unit: "%", fmt: fmtF1 }, + { key: "lf_hf_ratio", label: "compare.lfHfRatio", unit: "", fmt: fmtF2 }, + { key: "respiratory_rate", label: "compare.respiratoryRate", unit: "bpm", fmt: fmtF1 }, + { key: "spo2_estimate", label: "compare.spo2", unit: "%", fmt: fmtF1 }, + { key: "perfusion_index", label: "compare.perfusionIndex", unit: "%", fmt: fmtF2 }, + { key: "stress_index", label: "compare.stressIndex", unit: "", fmt: fmtF1 }, + { key: "meditation", label: "compare.meditation", unit: "", fmt: fmtF1 }, + { key: "cognitive_load", label: "compare.cognitiveLoad", unit: "", fmt: fmtF1 }, + { key: "drowsiness", label: "compare.drowsiness", unit: "", fmt: fmtF1 }, + { key: "blink_count", label: "compare.blinkCount", unit: "", fmt: fmtF1 }, + { key: "blink_rate", label: "compare.blinkRate", unit: "/min", fmt: fmtF1 }, + { key: "head_pitch", label: "compare.headPitch", unit: "°", fmt: fmtF1 }, + { key: "head_roll", label: "compare.headRoll", unit: "°", fmt: fmtF1 }, + { key: "stillness", label: "compare.stillness", unit: "", fmt: fmtF1 }, + { key: "nod_count", label: "compare.nodCount", unit: "", fmt: fmtF1 }, + { key: "shake_count", label: "compare.shakeCount", unit: "", fmt: fmtF1 }, +]; + +/** Metrics where higher B value = improvement. */ +export const HIGHER_IS_BETTER = new Set([ + "relaxation", + "engagement", + "meditation", + "coherence", + "snr", + "mu_suppression", + "stillness", + "spo2_estimate", + "pnn50", + "rmssd", + "sdnn", + "echt", +]); + +/** Metrics where lower B value = improvement. */ +export const LOWER_IS_BETTER = new Set([ + "stress_index", + "cognitive_load", + "drowsiness", + "blink_rate", + "lf_hf_ratio", +]); + +export const insightMetrics: { key: keyof SessionMetrics; label: string }[] = [ + { key: "relaxation", label: "Relaxation" }, + { key: "engagement", label: "Engagement" }, + { key: "meditation", label: "Meditation" }, + { key: "cognitive_load", label: "Cog. Load" }, + { key: "drowsiness", label: "Drowsiness" }, + { key: "stress_index", label: "Stress" }, + { key: "coherence", label: "Coherence" }, + { key: "snr", label: "SNR" }, + { key: "mu_suppression", label: "μ Suppression" }, + { key: "hr", label: "Heart Rate" }, + { key: "rmssd", label: "RMSSD" }, + { key: "sdnn", label: "SDNN" }, + { key: "pnn50", label: "pNN50" }, + { key: "stillness", label: "Stillness" }, + { key: "blink_rate", label: "Blink Rate" }, + { key: "lf_hf_ratio", label: "LF/HF" }, +]; + +// ── Helpers ────────────────────────────────────────────────────────────────── + +export function bv(m: SessionMetrics | null, k: BK): number { + return m ? (m[k] ?? 0) : 0; +} +export function pct(v: number): string { + return (v * 100).toFixed(1); +} + +export function diff(a: number, b: number): string { + const d = a - b; + if (Math.abs(d) < 0.001) return "—"; + return `${d > 0 ? "+" : ""}${(d * 100).toFixed(1)}`; +} + +export function scoreDiff(a: number, b: number): string { + const d = a - b; + if (Math.abs(d) < 0.1) return "—"; + return `${d > 0 ? "+" : ""}${d.toFixed(1)}`; +} + +export function dc(a: number, b: number): string { + const d = a - b; + if (Math.abs(d) < 0.001) return "text-muted-foreground/40"; + return d > 0 ? "text-emerald-500" : "text-red-400"; +} + +export function sdc(a: number, b: number): string { + const d = a - b; + if (Math.abs(d) < 0.1) return "text-muted-foreground/40"; + return d > 0 ? "text-emerald-500" : "text-red-400"; +} + +// ── UMAP analysis ──────────────────────────────────────────────────────────── + +import type { UmapPoint, UmapResult } from "$lib/types"; +import { gauss } from "$lib/umap/umap-helpers"; + +export function analyzeUmapClusters(result: UmapResult): ClusterAnalysis | null { + const pts = result.points; + if (pts.length < 4) return null; + const ptsA = pts.filter((p) => p.session === 0); + const ptsB = pts.filter((p) => p.session === 1); + if (ptsA.length < 2 || ptsB.length < 2) return null; + + const centroid = (arr: UmapPoint[]) => ({ + x: arr.reduce((s, p) => s + p.x, 0) / arr.length, + y: arr.reduce((s, p) => s + p.y, 0) / arr.length, + z: arr.reduce((s, p) => s + (p.z ?? 0), 0) / arr.length, + }); + const dist = (a: { x: number; y: number; z: number }, b: { x: number; y: number; z: number }) => + Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2 + (a.z - b.z) ** 2); + + const cA = centroid(ptsA); + const cB = centroid(ptsB); + const interCluster = dist(cA, cB); + const intraSpreadA = Math.sqrt( + ptsA.reduce((s, p) => s + dist({ x: p.x, y: p.y, z: p.z ?? 0 }, cA) ** 2, 0) / ptsA.length, + ); + const intraSpreadB = Math.sqrt( + ptsB.reduce((s, p) => s + dist({ x: p.x, y: p.y, z: p.z ?? 0 }, cB) ** 2, 0) / ptsB.length, + ); + const avgIntra = (intraSpreadA + intraSpreadB) / 2; + const separationScore = avgIntra > 0 ? interCluster / avgIntra : 0; + + return { separationScore, interCluster, intraSpreadA, intraSpreadB }; +} + +/** Generate a random placeholder UMAP result for loading state. */ +export function generateUmapPlaceholder(nA: number, nB: number): UmapResult { + const points: UmapPoint[] = []; + for (let i = 0; i < nA; i++) { + points.push({ x: -3 + gauss() * 3, y: gauss() * 3, z: gauss() * 3, session: 0, utc: 0 }); + } + for (let i = 0; i < nB; i++) { + points.push({ x: 3 + gauss() * 3, y: gauss() * 3, z: gauss() * 3, session: 1, utc: 0 }); + } + return { points, n_a: nA, n_b: nB, dim: 0 }; +} + +// ── Insight computation ────────────────────────────────────────────────────── + +export function computeInsightDeltas(metricsA: SessionMetrics, metricsB: SessionMetrics): InsightDelta[] { + return insightMetrics.map((m) => { + const a = Number(metricsA[m.key]) || 0; + const b = Number(metricsB[m.key]) || 0; + const delta = b - a; + const pctChange = a !== 0 ? (delta / Math.abs(a)) * 100 : 0; + let direction: "improved" | "declined" | "stable" = "stable"; + if (Math.abs(pctChange) > 3) { + if (HIGHER_IS_BETTER.has(m.key)) direction = delta > 0 ? "improved" : "declined"; + else if (LOWER_IS_BETTER.has(m.key)) direction = delta < 0 ? "improved" : "declined"; + } + return { label: m.label, key: m.key, a, b, delta, pctChange, direction }; + }); +} diff --git a/src/lib/components/BrainStatusBar.svelte b/src/lib/components/BrainStatusBar.svelte new file mode 100644 index 000000000..7aab5380b --- /dev/null +++ b/src/lib/components/BrainStatusBar.svelte @@ -0,0 +1,75 @@ + + + + +
+ + + {#if flow?.in_flow} + + + + + + FLOW {fmtMins(flow.duration_secs)} + + {:else if flow && flow.score > 0} + + focus {flow.score.toFixed(0)} + + {/if} + + + {#if fatigue?.fatigued} + + TIRED {fatigue.continuous_work_mins}m + + {/if} + + + {#if streak && streak.current_streak_days > 0} + + {streak.current_streak_days}d streak + + {/if} + + + {#if flow && flow.edit_velocity > 0} + + {flow.edit_velocity.toFixed(1)} ln/min + + {/if} + + + {#if flow?.avg_focus != null} + + + {flow.avg_focus.toFixed(0)} + + {/if} + + + {#if streak && streak.today_deep_mins > 0} + + {streak.today_deep_mins}m deep + + {/if} +
diff --git a/src/lib/components/DaemonActivityPanel.svelte b/src/lib/components/DaemonActivityPanel.svelte new file mode 100644 index 000000000..48859c7b4 --- /dev/null +++ b/src/lib/components/DaemonActivityPanel.svelte @@ -0,0 +1,168 @@ + + + + + +
+ {t("daemonActivity.title")} + + + {#if loading} +

{t("daemonActivity.loading")}

+ {:else if error} +

{error}

+ {:else} +
    + {#each tasks as task (task.id)} +
  • +
    +
    + {task.name} + {#if task.state?.running} + + + {t("daemonActivity.running")} + + {:else if task.state && !task.state.running} + + {t("daemonActivity.idle")} + + {/if} +
    +
    + {intervalLabel(task.intervalSecs)} + + {costLabel(task.cost)} +
    +
    +

    {task.does}

    +

    + {t("daemonActivity.whyPrefix")} {task.why} +

    + {#if task.state?.detail} +

    {task.state.detail}

    + {/if} +

    + {lastRanLabel(task.heartbeat.lastTickUnixMs)} + {#if task.heartbeat.lastDurationMs > 0} + + {t("daemonActivity.tickDuration", { n: task.heartbeat.lastDurationMs })} + {/if} + {#if task.heartbeat.tickCount > 0} + + {t("daemonActivity.tickCount", { n: task.heartbeat.tickCount })} + {/if} +

    +
  • + {/each} +
+ {/if} +
+
+
diff --git a/src/lib/components/ui/badge/badge.svelte b/src/lib/components/ui/badge/badge.svelte index 70df2612c..85cf91ea2 100644 --- a/src/lib/components/ui/badge/badge.svelte +++ b/src/lib/components/ui/badge/badge.svelte @@ -5,27 +5,25 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. -->
diff --git a/src/lib/components/ui/card/card-description.svelte b/src/lib/components/ui/card/card-description.svelte index 108767e9c..fb57a9b31 100644 --- a/src/lib/components/ui/card/card-description.svelte +++ b/src/lib/components/ui/card/card-description.svelte @@ -5,15 +5,15 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. -->

+ + + + +
+ {#each items as item} + {@const active = selected === item} + + {/each} +
diff --git a/src/lib/components/ui/chip-group/index.ts b/src/lib/components/ui/chip-group/index.ts new file mode 100644 index 000000000..b37cc0366 --- /dev/null +++ b/src/lib/components/ui/chip-group/index.ts @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 only. +import ChipGroup from "./ChipGroup.svelte"; + +export { ChipGroup }; diff --git a/src/lib/components/ui/confirm-action/ConfirmAction.svelte b/src/lib/components/ui/confirm-action/ConfirmAction.svelte new file mode 100644 index 000000000..73530a661 --- /dev/null +++ b/src/lib/components/ui/confirm-action/ConfirmAction.svelte @@ -0,0 +1,52 @@ + + + + +
+ + {message} + + + + +
diff --git a/src/lib/components/ui/confirm-action/index.ts b/src/lib/components/ui/confirm-action/index.ts new file mode 100644 index 000000000..2f3836679 --- /dev/null +++ b/src/lib/components/ui/confirm-action/index.ts @@ -0,0 +1,2 @@ +// SPDX-License-Identifier: GPL-3.0-only +export { default as ConfirmAction } from "./ConfirmAction.svelte"; diff --git a/src/lib/components/ui/dialog/dialog-close.svelte b/src/lib/components/ui/dialog/dialog-close.svelte index 4b77ed95f..09d610b51 100644 --- a/src/lib/components/ui/dialog/dialog-close.svelte +++ b/src/lib/components/ui/dialog/dialog-close.svelte @@ -5,9 +5,9 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. --> diff --git a/src/lib/components/ui/dialog/dialog-content.svelte b/src/lib/components/ui/dialog/dialog-content.svelte index 48cbcd006..cfc03a7d6 100644 --- a/src/lib/components/ui/dialog/dialog-content.svelte +++ b/src/lib/components/ui/dialog/dialog-content.svelte @@ -5,25 +5,24 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. --> diff --git a/src/lib/components/ui/dialog/dialog-description.svelte b/src/lib/components/ui/dialog/dialog-description.svelte index 5259c1fe8..eee24ffb4 100644 --- a/src/lib/components/ui/dialog/dialog-description.svelte +++ b/src/lib/components/ui/dialog/dialog-description.svelte @@ -5,14 +5,10 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. -->
diff --git a/src/lib/components/ui/dialog/dialog-title.svelte b/src/lib/components/ui/dialog/dialog-title.svelte index fb68d16ec..64335b659 100644 --- a/src/lib/components/ui/dialog/dialog-title.svelte +++ b/src/lib/components/ui/dialog/dialog-title.svelte @@ -5,14 +5,10 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. --> diff --git a/src/lib/components/ui/dialog/dialog.svelte b/src/lib/components/ui/dialog/dialog.svelte index bb6168403..0bdb78bc9 100644 --- a/src/lib/components/ui/dialog/dialog.svelte +++ b/src/lib/components/ui/dialog/dialog.svelte @@ -5,9 +5,9 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. --> diff --git a/src/lib/components/ui/dialog/index.ts b/src/lib/components/ui/dialog/index.ts index 15751ce1e..f5101c6bf 100644 --- a/src/lib/components/ui/dialog/index.ts +++ b/src/lib/components/ui/dialog/index.ts @@ -4,13 +4,13 @@ // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3 only. -export { default as Root } from "./dialog.svelte"; -export { default as Content } from "./dialog-content.svelte"; -export { default as Close } from "./dialog-close.svelte"; +export { default as Root } from "./dialog.svelte"; +export { default as Close } from "./dialog-close.svelte"; +export { default as Content } from "./dialog-content.svelte"; export { default as Description } from "./dialog-description.svelte"; -export { default as Footer } from "./dialog-footer.svelte"; -export { default as Header } from "./dialog-header.svelte"; -export { default as Overlay } from "./dialog-overlay.svelte"; -export { default as Portal } from "./dialog-portal.svelte"; -export { default as Title } from "./dialog-title.svelte"; -export { default as Trigger } from "./dialog-trigger.svelte"; +export { default as Footer } from "./dialog-footer.svelte"; +export { default as Header } from "./dialog-header.svelte"; +export { default as Overlay } from "./dialog-overlay.svelte"; +export { default as Portal } from "./dialog-portal.svelte"; +export { default as Title } from "./dialog-title.svelte"; +export { default as Trigger } from "./dialog-trigger.svelte"; diff --git a/src/lib/components/ui/form-row/FormRow.svelte b/src/lib/components/ui/form-row/FormRow.svelte new file mode 100644 index 000000000..8d315a91e --- /dev/null +++ b/src/lib/components/ui/form-row/FormRow.svelte @@ -0,0 +1,48 @@ + + + + + +
+
+ {label} + {#if value} + {value} + {/if} +
+ {#if description} +

+ {description} +

+ {/if} + {#if children} + {@render children()} + {/if} +
diff --git a/src/lib/components/ui/form-row/index.ts b/src/lib/components/ui/form-row/index.ts new file mode 100644 index 000000000..75f8d42e3 --- /dev/null +++ b/src/lib/components/ui/form-row/index.ts @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 only. +import FormRow from "./FormRow.svelte"; + +export { FormRow }; diff --git a/src/lib/components/ui/icons/IconCheck.svelte b/src/lib/components/ui/icons/IconCheck.svelte new file mode 100644 index 000000000..19c15735c --- /dev/null +++ b/src/lib/components/ui/icons/IconCheck.svelte @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/lib/components/ui/icons/IconChevronRight.svelte b/src/lib/components/ui/icons/IconChevronRight.svelte new file mode 100644 index 000000000..de2ad96eb --- /dev/null +++ b/src/lib/components/ui/icons/IconChevronRight.svelte @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/lib/components/ui/icons/IconExternalLink.svelte b/src/lib/components/ui/icons/IconExternalLink.svelte new file mode 100644 index 000000000..03e6baf34 --- /dev/null +++ b/src/lib/components/ui/icons/IconExternalLink.svelte @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/src/lib/components/ui/icons/IconWarning.svelte b/src/lib/components/ui/icons/IconWarning.svelte new file mode 100644 index 000000000..de6e993d1 --- /dev/null +++ b/src/lib/components/ui/icons/IconWarning.svelte @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/lib/components/ui/icons/IconX.svelte b/src/lib/components/ui/icons/IconX.svelte new file mode 100644 index 000000000..e87b6b36f --- /dev/null +++ b/src/lib/components/ui/icons/IconX.svelte @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/lib/components/ui/icons/index.ts b/src/lib/components/ui/icons/index.ts new file mode 100644 index 000000000..58ed9aa37 --- /dev/null +++ b/src/lib/components/ui/icons/index.ts @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +export { default as IconCheck } from "./IconCheck.svelte"; +export { default as IconChevronRight } from "./IconChevronRight.svelte"; +export { default as IconExternalLink } from "./IconExternalLink.svelte"; +export { default as IconWarning } from "./IconWarning.svelte"; +export { default as IconX } from "./IconX.svelte"; diff --git a/src/lib/components/ui/progress/index.ts b/src/lib/components/ui/progress/index.ts index aba8ca76b..b45c1e8dd 100644 --- a/src/lib/components/ui/progress/index.ts +++ b/src/lib/components/ui/progress/index.ts @@ -7,7 +7,7 @@ import Root from "./progress.svelte"; export { - Root, - // - Root as Progress, + Root, + // + Root as Progress, }; diff --git a/src/lib/components/ui/progress/progress.svelte b/src/lib/components/ui/progress/progress.svelte index c99073c16..17ce7cc54 100644 --- a/src/lib/components/ui/progress/progress.svelte +++ b/src/lib/components/ui/progress/progress.svelte @@ -5,16 +5,16 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. --> + + + + + {@render children?.()} + diff --git a/src/lib/components/ui/section-header/index.ts b/src/lib/components/ui/section-header/index.ts new file mode 100644 index 000000000..a914dd1a6 --- /dev/null +++ b/src/lib/components/ui/section-header/index.ts @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 only. +import SectionHeader from "./SectionHeader.svelte"; + +export { SectionHeader }; diff --git a/src/lib/components/ui/separator/index.ts b/src/lib/components/ui/separator/index.ts index 88c1bc11f..d3a4fc78f 100644 --- a/src/lib/components/ui/separator/index.ts +++ b/src/lib/components/ui/separator/index.ts @@ -7,7 +7,7 @@ import Root from "./separator.svelte"; export { - Root, - // - Root as Separator, + Root, + // + Root as Separator, }; diff --git a/src/lib/components/ui/separator/separator.svelte b/src/lib/components/ui/separator/separator.svelte index 3c75ba8e2..e5a1adc88 100644 --- a/src/lib/components/ui/separator/separator.svelte +++ b/src/lib/components/ui/separator/separator.svelte @@ -5,15 +5,15 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. --> + + + + + + {@render children?.()} + diff --git a/src/lib/components/ui/settings-card/index.ts b/src/lib/components/ui/settings-card/index.ts new file mode 100644 index 000000000..7c623f39d --- /dev/null +++ b/src/lib/components/ui/settings-card/index.ts @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 only. +import SettingsCard from "./SettingsCard.svelte"; + +export { SettingsCard }; diff --git a/src/lib/components/ui/skeleton/index.ts b/src/lib/components/ui/skeleton/index.ts index 6e570330a..6616daa94 100644 --- a/src/lib/components/ui/skeleton/index.ts +++ b/src/lib/components/ui/skeleton/index.ts @@ -7,7 +7,7 @@ import Root from "./skeleton.svelte"; export { - Root, - // - Root as Skeleton, + Root, + // + Root as Skeleton, }; diff --git a/src/lib/components/ui/skeleton/skeleton.svelte b/src/lib/components/ui/skeleton/skeleton.svelte index ee0f73ab1..4619de3ad 100644 --- a/src/lib/components/ui/skeleton/skeleton.svelte +++ b/src/lib/components/ui/skeleton/skeleton.svelte @@ -5,14 +5,14 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. -->
+ + + + + + + + + {label} + diff --git a/src/lib/components/ui/status-badge/index.ts b/src/lib/components/ui/status-badge/index.ts new file mode 100644 index 000000000..7cddfd9cc --- /dev/null +++ b/src/lib/components/ui/status-badge/index.ts @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 only. +import StatusBadge, { type StatusLevel } from "./StatusBadge.svelte"; + +export { StatusBadge, type StatusLevel }; diff --git a/src/lib/components/ui/textarea/index.ts b/src/lib/components/ui/textarea/index.ts index 4d83b432a..bdd7cb3e0 100644 --- a/src/lib/components/ui/textarea/index.ts +++ b/src/lib/components/ui/textarea/index.ts @@ -6,7 +6,4 @@ // the Free Software Foundation, version 3 only. import Root from "./textarea.svelte"; -export { - Root, - Root as Textarea, -}; +export { Root, Root as Textarea }; diff --git a/src/lib/components/ui/textarea/textarea.svelte b/src/lib/components/ui/textarea/textarea.svelte index 8278a8abd..e32e8cd96 100644 --- a/src/lib/components/ui/textarea/textarea.svelte +++ b/src/lib/components/ui/textarea/textarea.svelte @@ -5,16 +5,16 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. --> +
+ +
+ {/if} +
+ + +
+ +
+ + + + + {/if} + + + {#if isNewEngine} +
+ {engineLabel} + + + + + {#if hasModelPicker} +
+ {t("chat.tts.modelLabel")} + +
+ {/if} + + {#if hasVoicePicker} +
+ {t("chat.tts.voiceLabel")} + +
+ {:else} +

{t("chat.tts.voiceDesc")}

+ {/if} + +
+
+
+ {/if} + + +
+ {t("ttsTab.testSection")} +

+ {t("ttsTab.testDesc")} +

+ +
+ + +
+ {t("ttsTab.apiSection")} +

+ {t("ttsTab.apiDesc")} +

+
+ {#each [ + ["WebSocket", `{"command":"say","text":"Eyes closed. Relax."}`], + ["HTTP (curl)", `curl -X POST http://localhost:/say \\ + -H 'Content-Type: application/json' \\ + -d '{"text":"Eyes closed. Relax."}'`], + ["websocat (CLI)", `echo '{"command":"say","text":"Eyes closed."}' | websocat ws://localhost:`], + ] as [label, code]} +
+ + {label} + +
{code}
+
+ {/each} +
+
+ + +
+ {t("ttsTab.startupSection")} + + + { + ttsPreload = !ttsPreload; + daemonInvoke("set_tts_preload", { preload: ttsPreload }).catch(e => console.warn("[tts] set_tts_preload failed:", e)); + }} + + showBadge={false} + /> + +
+ + +
+ {t("ttsTab.loggingSection")} + + + + +
+ +
diff --git a/src/lib/settings/UmapTab.svelte b/src/lib/settings/UmapTab.svelte new file mode 100644 index 000000000..19ddd00b8 --- /dev/null +++ b/src/lib/settings/UmapTab.svelte @@ -0,0 +1,452 @@ + + + + + +{#if !loaded} +
+ {t("common.loading")} +
+{:else} + + +{#if availableBackends.length > 0} +
+
+ {t("umapSettings.backend")} + {#if saving} + {t("common.saving")} + {/if} + {#if dirty} + + {/if} +
+ + + +

+ {t("umapSettings.backendDesc")} +

+ [b.toUpperCase(), b] as [string, string]), + ]} + selected={ + [["Auto", "auto"] as [string, string], + ...availableBackends.map(b => [b.toUpperCase(), b] as [string, string]), + ].find(p => p[1] === cfg.backend) ?? ["Auto", "auto"] + } + onselect={(p) => { cfg.backend = p[1]; markDirty(); }} + labelFn={(p) => p[0]} + /> +
+ + {@const activePrecisions = precisionsByBackend[cfg.backend === "auto" ? (availableBackends[0] ?? "gpu") : cfg.backend] ?? ["f32"]} + +
+ {t("umapSettings.precision")} + {cfg.precision.toUpperCase()} +
+

+ {t("umapSettings.precisionDesc")} +

+ [p.toUpperCase(), p] as [string, string])} + selected={activePrecisions.map(p => [p.toUpperCase(), p] as [string, string]).find(p => p[1] === cfg.precision) ?? [activePrecisions[0].toUpperCase(), activePrecisions[0]]} + onselect={(p) => { cfg.precision = p[1]; markDirty(); }} + labelFn={(p) => p[0]} + /> +
+
+
+{/if} + + +
+
+ {t("umapSettings.repulsion")} + {#if saving} + {t("common.saving")} + {/if} + {#if dirty} + + {/if} +
+ + + + + +
+
+ {t("umapSettings.repulsionStrength")} + {cfg.repulsion_strength.toFixed(1)} +
+

+ {t("umapSettings.repulsionDesc")} +

+ p[1] === cfg.repulsion_strength) ?? REPULSION_PRESETS[0]} + onselect={(p) => { cfg.repulsion_strength = p[1]; markDirty(); }} + labelFn={(p) => p[0]} + /> + +
+ + +
+
+ {t("umapSettings.negSampleRate")} + {cfg.neg_sample_rate} +
+

+ {t("umapSettings.negSampleRateDesc")} +

+ p[1] === cfg.neg_sample_rate) ?? NEG_SAMPLE_PRESETS[0]} + onselect={(p) => { cfg.neg_sample_rate = p[1]; markDirty(); }} + labelFn={(p) => p[0]} + /> +
+
+
+
+ + +
+ {t("umapSettings.graphOptimisation")} + + + + + +
+
+ {t("umapSettings.nNeighbors")} + {cfg.n_neighbors} +
+

+ {t("umapSettings.nNeighborsDesc")} +

+ p[1] === cfg.n_neighbors) ?? NEIGHBOR_PRESETS[0]} + onselect={(p) => { cfg.n_neighbors = p[1]; markDirty(); }} + labelFn={(p) => p[0]} + /> +
+ + +
+
+ {t("umapSettings.nEpochs")} + {cfg.n_epochs} +
+

+ {t("umapSettings.nEpochsDesc")} +

+ p[1] === cfg.n_epochs) ?? EPOCH_PRESETS[0]} + onselect={(p) => { cfg.n_epochs = p[1]; markDirty(); }} + labelFn={(p) => p[0]} + /> +
+
+
+
+ + +
+ {t("umapSettings.safetyPerformance")} + + + + + +
+
+ {t("umapSettings.timeout")} + {cfg.timeout_secs}s +
+

+ {t("umapSettings.timeoutDesc")} +

+ p[1] === cfg.timeout_secs) ?? TIMEOUT_PRESETS[0]} + onselect={(p) => { cfg.timeout_secs = p[1]; markDirty(); }} + labelFn={(p) => p[0]} + /> +
+ + +
+
+ {t("umapSettings.cooldownMs")} + + {cfg.cooldown_ms === 0 ? "0 ms" : cfg.cooldown_ms < 1000 ? `${cfg.cooldown_ms} ms` : `${(cfg.cooldown_ms / 1000).toFixed(1)}s`} + +
+

+ {t("umapSettings.cooldownMsDesc")} +

+ +
+ 0s5s10s +
+
+ + +
+ {t("umapSettings.pipeline")} + + repulsion {cfg.repulsion_strength.toFixed(1)} + + + neg×{cfg.neg_sample_rate} + + + k={cfg.n_neighbors} + + + {cfg.n_epochs} epochs + + + {cfg.timeout_secs}s timeout + + + {cfg.cooldown_ms}ms cooldown + + + {cfg.backend === "auto" ? "auto" : cfg.backend.toUpperCase()} · {cfg.precision} + + fast-umap 1.6.0 +
+
+
+
+ + +
+ + {#if dirty} + + {/if} +
+ +{/if} + + diff --git a/src/lib/settings/UpdatesTab.svelte b/src/lib/settings/UpdatesTab.svelte new file mode 100644 index 000000000..05cd239d9 --- /dev/null +++ b/src/lib/settings/UpdatesTab.svelte @@ -0,0 +1,716 @@ + + + + + +
+ + +
+
+ ⬆ +
+
+ {t("updates.title")} + + {t("updates.currentVersion", { version: appVersion })} + +
+ + {#if phase === "ready"} + + ✅ {t("updates.readyToRestart")} + + {:else if phase === "downloading"} + {progress}% + {:else if phase === "checking"} + + + + {/if} +
+ + + + + +
+ + {#if phase === "ready"} + +
+
+ ✅ +
+
+ + {t("updates.installed", { version: available?.version ?? "" })} + + + {t("updates.restartWhenReady")} + +
+
+ + +
+
+ + {#if sessionBlockWarning} +
+ ⚠ + + {t("updates.sessionLiveBlocked")} + +
+ {/if} + + {:else if phase === "downloading"} + +
+
+ + {t("updates.downloading", { version: available?.version ?? "" })} + + + {progress}% + +
+
+
+
+ {#if available?.body} +

{available.body}

+ {/if} +
+ + {:else} + +
+ {#if phase === "error" && available} + +
+ ⚠ +
+
+ + v{available.version} {t("updates.available")} + + + {t("updates.downloadFailed")} + +
+ {:else if phase === "idle" && available && !autoUpdateEnabled} + +
+ ⬆ +
+
+ + v{available.version} {t("updates.available")} + + + {t("updates.autoUpdateOffNotice")} + +
+ + {:else} +
+ + {phase === "checking" ? t("updates.checking") : t("updates.upToDate")} + + + {t("updates.lastChecked")}: {fmtLastChecked()} + +
+ {/if} + + + {#if !(phase === "idle" && available && !autoUpdateEnabled)} + + {/if} +
+ + + {#if phase === "error" && error} +
+ {error} +
+ {/if} + + {#if phase === "error" && available} +
+ +
+ {/if} + {/if} + +
+
+
+ + + + +
+
+
+ + {t("updates.checkInterval")} + + + {t("updates.checkIntervalDesc")} + +
+ {#if intervalSaving} + + + + {/if} +
+ + p[0] === checkIntervalSecs) ?? INTERVAL_OPTIONS[0]} + onselect={(p) => setCheckInterval(p[0])} + labelFn={(p) => t(p[1])} + /> + + {#if checkIntervalSecs === 0} +

+ {t("updates.intervalOffWarning")} +

+ {/if} +
+
+
+ + + + + + + {#if autoUpdateError} +
+ {autoUpdateError} +
+ {/if} +
+
+ + + + + + + {#if autostartError} +
+ {autostartError} +
+ {/if} +
+
+ + + + + + + {#if channelError} +
+ {channelError} +
+ {/if} +
+
+ + +
+ + {t("updates.footer")} + +
+ +
diff --git a/src/lib/settings/ValidationTab.svelte b/src/lib/settings/ValidationTab.svelte new file mode 100644 index 000000000..f9310839b --- /dev/null +++ b/src/lib/settings/ValidationTab.svelte @@ -0,0 +1,545 @@ + + + + + +
+ + + + +
+ + {t("validation.disclaimer")} +
+ + {#if loadError} +
+ Failed to load validation config: {loadError} +
+ {/if} + + {#if config} + + + +

{t("validation.master.title")}

+ + + +
+ + +
+

{t("validation.master.quietDesc")}

+
+
+ + + + +
+

{t("validation.kss.title")}

+

{t("validation.kss.desc")}

+
+ + + + {#if config.kss.enabled} +
+
+ + +
+ + + + + {#if config.kss.trigger_random} + + {/if} +
+ {/if} +
+
+ + + + +
+

{t("validation.tlx.title")}

+

{t("validation.tlx.desc")}

+
+ + + + {#if config.tlx.enabled} +
+
+ + +
+ +
+ +
+
+ {/if} +
+
+ + + + +
+

{t("validation.pvt.title")}

+

{t("validation.pvt.desc")}

+
+ + + + {#if config.pvt.enabled} +
+ +
+ {/if} + +
+ +
+
+
+ + + + +
+

{t("validation.eeg.title")}

+

{t("validation.eeg.desc")}

+
+ + + + {#if config.eeg_fatigue.enabled} +
+ +
+ {t("validation.eeg.current")} +
+ {fatigueIndex !== null ? fatigueIndex.toFixed(2) : t("validation.eeg.noHeadset")} +
+
+
+ {/if} +
+
+ + + + +
+

{t("validation.calibrationWeek.title")}

+

{t("validation.calibrationWeek.desc")}

+
+
+ +
+
+
+ + + + +

{t("validation.results.title")}

+ {#if recentKssCount > 0} +

+ {t("validation.results.kssCount", { 0: recentKssCount })} +

+ {:else} +

{t("validation.results.empty")}

+ {/if} +
+
+ + + {#if saveStatus !== "idle"} +
+ {#if saveStatus === "saving"}{t("validation.save.saving")}{/if} + {#if saveStatus === "saved"}{t("validation.save.saved")}{/if} + {#if saveStatus === "error"}{t("validation.save.failed", { 0: saveError })}{/if} +
+ {/if} + {/if} +
+ +{#if pvtOpen && config} + (pvtOpen = false)} /> +{/if} + +{#if tlxOpen && config} + (tlxOpen = false)} taskKind="manual" /> +{/if} diff --git a/src/lib/settings/VirtualEegTab.svelte b/src/lib/settings/VirtualEegTab.svelte new file mode 100644 index 000000000..f8b5cd25c --- /dev/null +++ b/src/lib/settings/VirtualEegTab.svelte @@ -0,0 +1,571 @@ + + + + + +
+ + +
+

{t("veeg.title")}

+

{t("veeg.desc")}

+
+ + + + +
+ + {#if running} + + + {:else} + + {/if} + + + {running ? t("veeg.running") : t("veeg.stopped")} + + {#if running} + + {config.channels}ch · {config.sampleRate} Hz + + {/if} +
+ +
+
+ + +
+ {t("veeg.template")} + + + {#each TEMPLATES as tmpl} + + {/each} + + {#if config.template === "file"} +
+ + + {config.fileName ?? t("veeg.noFile")} + +
+ {/if} +
+
+
+ + +
+ {t("veeg.channels")} + + +
+ {t("veeg.channelsDesc")} +
+ {#each CHANNEL_OPTIONS as n} + + {/each} +
+ {#if config.channels > 0} + + {channelLabels.join(", ")} + + {/if} +
+ + + +
+ {t("veeg.sampleRateDesc")} +
+ {#each RATE_OPTIONS as hz} + + {/each} +
+
+
+
+
+ + +
+ {t("veeg.quality")} + + + {t("veeg.qualityDesc")} +
+ {#each QUALITY_OPTIONS as q} + + {/each} +
+
+
+
+ + +
+ + + {#if showAdvanced} + + + + +
+
+ {t("veeg.amplitudeUv")} + {t("veeg.amplitudeDesc")} +
+ +
+ + + + +
+
+ {t("veeg.noiseUv")} + {t("veeg.noiseDesc")} +
+ +
+ + + + +
+
+ {t("veeg.lineNoise")} + {t("veeg.lineNoiseDesc")} +
+
+ {#each [["none", "veeg.lineNoiseNone"], ["50hz", "veeg.lineNoise50"], ["60hz", "veeg.lineNoise60"]] as [val, label]} + + {/each} +
+
+ + + + +
+
+ {t("veeg.dropoutProb")} + {t("veeg.dropoutDesc")} +
+ +
+ +
+
+ {/if} +
+ + +
+ {t("veeg.preview")} + + + +

{t("veeg.previewDesc")}

+
+
+
+ +
diff --git a/src/lib/settings/devices-logic.ts b/src/lib/settings/devices-logic.ts new file mode 100644 index 000000000..f3519eca4 --- /dev/null +++ b/src/lib/settings/devices-logic.ts @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +/** + * DevicesTab pure logic — extracted from DevicesTab.svelte. + * + * Fuzzy matching, device image resolution, channel labeling, and formatting. + */ + +// ── Virtual device detection ──────────────────────────────────────────────────── + +/** + * Return true when `dev` is a simulated/virtual device rather than real hardware. + * + * Detection rules (any match → virtual): + * - name contains "virtual" (case-insensitive) — covers "SkillVirtualEEG", + * "Virtual EEG", etc. + * - id contains "virtual" (case-insensitive) — covers daemon id "virtual-eeg" + */ +export function isVirtualDevice(dev: { id: string; name: string }): boolean { + const n = dev.name.toLowerCase(); + const id = dev.id.toLowerCase(); + return n.includes("virtual") || id.includes("virtual"); +} + +/** + * Stable-sort a device list so real hardware always appears above virtual + * devices. Within each group the original relative order is preserved. + */ +export function sortDevicesRealFirst(devs: T[]): T[] { + return [...devs].sort((a, b) => { + const av = isVirtualDevice(a) ? 1 : 0; + const bv = isVirtualDevice(b) ? 1 : 0; + return av - bv; // real (0) before virtual (1) + }); +} + +// ── Fuzzy search ────────────────────────────────────────────────────────────── + +/** Fuzzy substring + character-sequence match. */ +export function fuzzyMatch(haystack: string, needle: string): boolean { + if (!needle) return true; + const h = haystack.toLowerCase(); + const n = needle.toLowerCase(); + if (h.includes(n)) return true; + let hIdx = 0; + for (let i = 0; i < n.length; i++) { + hIdx = h.indexOf(n[i], hIdx); + if (hIdx === -1) return false; + hIdx++; + } + return true; +} + +// ── Device image resolution ─────────────────────────────────────────────────── + +/** Resolve the product image path for a Muse-family device. */ +export function museImage(name: string, _hw?: string | null): string | null { + const n = name.toLowerCase(); + // Athena (Muse S gen 2) advertises as "MuseS-XXXX" (no space before S). + // hardware_version is not set by the Muse adapter so we rely on the name only. + const isAthena = n.includes("muses"); + if (isAthena) return "/devices/muse-s-athena.jpg"; + if (n.includes("muse-s") || n.includes("muse s")) return "/devices/muse-s-gen1.jpg"; + if (n.includes("muse-2") || n.includes("muse2") || n.includes("muse 2")) return "/devices/muse-gen2.jpg"; + if (n.includes("muse")) return "/devices/muse-gen1.jpg"; + if (n.includes("mw75") || n.includes("neurable")) return "/devices/muse-mw75.jpg"; + return null; +} + +/** Resolve the product image path for any supported device. */ +export function deviceImage(name: string, hw?: string | null): string | null { + const muse = museImage(name, hw); + if (muse) return muse; + + const n = name.toLowerCase(); + if (n.includes("antneuro") || n.includes("eego")) return "/devices/antneuro-eego-mylab.webp"; + if (n.includes("idun") || n.includes("guardian") || n.startsWith("ige")) return "/devices/idun-guardian.png"; + if (n.includes("insight")) return "/devices/emotiv-insight.webp"; + if (n.includes("flex")) return "/devices/emotiv-flex-saline.webp"; + if (n.includes("mn8")) return "/devices/emotiv-mn8.webp"; + if (n.includes("epoc")) return "/devices/emotiv-epoc-x.webp"; + if (n.includes("ganglion")) return "/devices/openbci-ganglion.jpg"; + if (n.includes("cyton")) return "/devices/openbci-cyton.jpg"; + if (n.includes("hermes")) return "/devices/hermes.jpg"; + if (n.includes("mendi")) return "/devices/mendi-headband.png"; + if (n.includes("oura")) return "/devices/oura-ring.svg"; + if (n.includes("awear") || n.includes("luca")) return "/devices/awear-eeg.png"; + if (n.includes("vital")) return "/devices/vital-headphones.webp"; + if (n.includes("atu") || n.includes("attentivu")) return "/devices/attentivu-glasses.png"; + if (n.includes("quick-32") || n.includes("q32")) return "/devices/cgx-quick-32r.png"; + if (n.includes("quick-20r-v1") || n.includes("q20r-v1")) return "/devices/cgx-quick-20r-v1.png"; + if (n.includes("quick-20") || n.includes("q20") || n.includes("cognionics") || n.includes("cgx")) + return "/devices/cgx-quick-20r.png"; + if (n.includes("quick-8") || n.includes("q8")) return "/devices/cgx-quick-8r.png"; + if (n.includes("aim-2") || n.includes("aim2")) return "/devices/cgx-aim-2.png"; + return null; +} + +// ── OpenBCI channel labels ──────────────────────────────────────────────────── + +const OPENBCI_DEFAULT_LABELS = ["Fp1", "Fp2", "C3", "C4", "T5", "T6", "O1", "O2"]; + +/** Return the default 10-20 label for an OpenBCI channel index. */ +export function openbciChannelLabel(i: number): string { + return OPENBCI_DEFAULT_LABELS[i] ?? `Ch${i + 1}`; +} + +// ── Formatting ──────────────────────────────────────────────────────────────── + +/** Format a Unix timestamp as a relative "last seen" string. */ +export function fmtLastSeen(ts: number): string { + const now = Math.floor(Date.now() / 1000); + const diff = now - ts; + if (diff < 60) return "just now"; + if (diff < 3600) return `${Math.floor(diff / 60)}m ago`; + if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`; + return `${Math.floor(diff / 86400)}d ago`; +} + +// ── Types for merge/preferred helpers ───────────────────────────────────────── + +export interface DeviceBase { + id: string; + name: string; + last_seen: number; + last_rssi: number; + is_paired: boolean; + is_preferred: boolean; + transport?: string; +} + +export interface PairedInfo { + id: string; + name: string; + last_seen?: number; +} + +// ── Merge & preferred helpers ───────────────────────────────────────────────── + +/** + * Infer transport type from device ID prefix. + */ +function inferTransport(id: string): "ble" | "usb_serial" | "wifi" | undefined { + if (id.startsWith("ble:")) return "ble"; + if (id.startsWith("usb:")) return "usb_serial"; + if (id.startsWith("wifi:") || (id.includes(":") && id.includes("."))) return "wifi"; + return undefined; +} + +/** + * Merge paired devices from status into a base device list. + * Devices already in `base` are preserved as-is (including is_preferred). + * Devices in `paired` but missing from `base` are added with is_preferred=false. + */ +export function mergePairedIntoDevices(base: T[], paired: PairedInfo[]): T[] { + const out = [...base]; + const byId = new Set(out.map((d) => d.id)); + for (const p of paired) { + if (!byId.has(p.id)) { + out.push({ + id: p.id, + name: p.name, + last_seen: p.last_seen ?? 0, + last_rssi: 0, + is_paired: true, + is_preferred: false, + transport: inferTransport(p.id), + } as T); + byId.add(p.id); + } + } + return out; +} + +/** + * Apply a preferred device selection: sets is_preferred=true on the device + * matching `targetId`, and is_preferred=false on all others. + * If `targetId` is empty, clears all preferred flags. + */ +export function applyPreferred(devices: T[], targetId: string): T[] { + return devices.map((d) => ({ ...d, is_preferred: targetId !== "" && d.id === targetId })); +} diff --git a/src/lib/settings/goals-logic.ts b/src/lib/settings/goals-logic.ts new file mode 100644 index 000000000..34de47443 --- /dev/null +++ b/src/lib/settings/goals-logic.ts @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +/** + * GoalsTab pure logic — extracted from GoalsTab.svelte. + * + * Progress bar coloring and time formatting for the daily goal tracker. + */ + +/** Color for the daily goal progress bar based on minutes recorded. */ +export function barColor(mins: number, goalMin: number): string { + if (mins >= goalMin) return "#22c55e"; // green — goal met + if (mins >= goalMin * 0.5) return "#3b82f6"; // blue — halfway+ + if (mins === 0) return "transparent"; + return "#6366f1"; // indigo — some progress +} + +/** Format minutes as "1h 23m" or "45m" or "—" for zero. */ +export function fmtMins(m: number): string { + if (m === 0) return "\u2014"; + if (m < 60) return `${m}m`; + const remainder = m % 60; + return `${Math.floor(m / 60)}h${remainder > 0 ? ` ${remainder}m` : ""}`; +} diff --git a/src/lib/settings/hooks-logic.ts b/src/lib/settings/hooks-logic.ts new file mode 100644 index 000000000..5b6c1f8b0 --- /dev/null +++ b/src/lib/settings/hooks-logic.ts @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +/** + * HooksTab pure logic — extracted from HooksTab.svelte. + * + * Timestamp conversion and relative-time formatting for the hooks log. + */ + +/** + * Convert a UTC microsecond timestamp to Unix seconds. + * Returns 0 for invalid input. + */ +export function tsToUnix(tsUtc: number): number { + if (!tsUtc || Number.isNaN(tsUtc)) return 0; + // Detect microsecond vs second timestamps + return tsUtc > 1e15 ? Math.floor(tsUtc / 1_000_000) : tsUtc > 1e12 ? Math.floor(tsUtc / 1000) : Math.floor(tsUtc); +} + +/** + * Format a relative age string from a UTC timestamp. + * @param tsUtc - timestamp (microseconds, milliseconds, or seconds) + * @param nowSecs - current time in Unix seconds + * @param agoLabel - localized "ago" suffix + */ +export function relativeAge(tsUtc: number, nowSecs: number, agoLabel = "ago"): string { + const unix = tsToUnix(tsUtc); + if (!unix || Number.isNaN(unix)) return ""; + const diff = nowSecs - unix; + if (diff < 0) return ""; + if (diff < 60) return `${diff}s ${agoLabel}`; + if (diff < 3600) return `${Math.floor(diff / 60)}m ${agoLabel}`; + if (diff < 86400) return `${Math.floor(diff / 3600)}h ${agoLabel}`; + return `${Math.floor(diff / 86400)}d ${agoLabel}`; +} diff --git a/src/lib/settings/onboarding-logic.ts b/src/lib/settings/onboarding-logic.ts new file mode 100644 index 000000000..430149d4f --- /dev/null +++ b/src/lib/settings/onboarding-logic.ts @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +/** + * Onboarding model-selection logic — the single definition. + * + * This file used to be a COPY: `routes/onboarding/+page.svelte` carried its own + * `pickFamilyTarget` / `pickLlmTarget`, and those were what actually ran. The + * two drifted — the copy here still bootstrapped `lfm25-1.2b-instruct` long + * after the page moved to Qwen3.5 0.8B — so `tests/onboarding-logic.test.ts` + * was testing selection rules the app did not use. That is how two onboarding + * bugs shipped unnoticed: a card titled with a model we do not download, and a + * model chosen through the CLI being ignored in favour of the default. + * + * The page now imports from here. Keep it that way: these are pure functions + * precisely so they can be tested without a Svelte component. + */ + +/** Download state of a catalog entry, as the daemon reports it. */ +export type DownloadState = "not_downloaded" | "downloading" | "downloaded" | "failed" | "cancelled"; + +export interface LlmModelEntry { + repo?: string; + filename: string; + quant: string; + size_gb: number; + family_id: string; + family_name: string; + is_mmproj: boolean; + recommended: boolean; + state: DownloadState; + progress: number; + tags?: string[]; + local_path?: string | null; +} + +/** Whether an entry was discovered from another app's cache (tagged "discovered"). */ +export function isDiscoveredEntry(e: LlmModelEntry): boolean { + return (e.tags ?? []).includes("discovered"); +} + +/** + * Pick the best model within a given family by quantization preference. + * + * Priority: Q4_K_M > Q8_0 > Q4_0 > any Q4* > recommended > downloaded > first. + */ +export function pickFamilyTarget(entries: LlmModelEntry[], familyId: string, familyRe: RegExp): LlmModelEntry | null { + const family = entries.filter((e) => !e.is_mmproj && (e.family_id === familyId || familyRe.test(e.family_name))); + if (!family.length) return null; + const byQuant = (q: string) => family.find((e) => e.quant.toUpperCase() === q); + return ( + byQuant("Q4_K_M") ?? + byQuant("Q8_0") ?? + byQuant("Q4_0") ?? + family.find((e) => e.quant.toUpperCase().startsWith("Q4")) ?? + family.find((e) => e.recommended) ?? + family.find((e) => e.state === "downloaded") ?? + family[0] + ); +} + +/** + * Pick the default LLM to download during onboarding. + * + * Priority chain: + * 1. `activeModel` — whatever is already selected (`llm model set`, Models tab). + * 2. Already-downloaded model (any family) — skip the download. + * 3. Qwen3.5 0.8B — default bootstrap family. + * 4. Qwen3 0.6B — fallback tiny family. + * 5. Any recommended model, smallest first. + */ +export function pickLlmTarget(entries: LlmModelEntry[], activeModel = ""): LlmModelEntry | null { + // An already-chosen model wins. Substituting onboarding's own default here is + // how a model selected through the CLI ended up ignored, with Qwen3 0.6B + // fetched instead. + const active = activeModel ? entries.find((e) => !e.is_mmproj && e.filename === activeModel) : undefined; + if (active) return active; + + // Discovered (external-app) models are handled separately as an explicit + // reuse choice, so they don't silently satisfy the default card. + const downloaded = entries.find((e) => !e.is_mmproj && !isDiscoveredEntry(e) && e.state === "downloaded"); + if (downloaded) return downloaded; + + return ( + pickFamilyTarget(entries, "qwen35-0.8b", /qwen3\.?5\s*0\.8b/i) ?? + pickFamilyTarget(entries, "qwen3-0.6b", /qwen3\s*0\.6b/i) ?? + entries.filter((e) => !e.is_mmproj && e.recommended).sort((a, b) => a.size_gb - b.size_gb)[0] ?? + null + ); +} + +/** + * Title for onboarding's LLM card: derived from the model we will ACTUALLY + * fetch. A hard-coded title drifts — this card read "Qwen3.5 4B (Q4_K_M)" out + * of the i18n bundle in every locale while the download was Qwen3.5 0.8B, with + * the quant badge beside it already showing the truth. + */ +export function llmCardTitle(target: LlmModelEntry | null, fallback: string): string { + if (!target) return fallback; + return `${target.family_name || target.filename} (${target.quant})`; +} diff --git a/src/lib/settings/pvt-stats.ts b/src/lib/settings/pvt-stats.ts new file mode 100644 index 000000000..e2ba65bd5 --- /dev/null +++ b/src/lib/settings/pvt-stats.ts @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 only. +/** + * Pure helpers for the Psychomotor Vigilance Task (PVT) — extracted from + * PvtPanel.svelte so the statistics can be unit-tested without pulling in + * a Svelte renderer or the daemon HTTP layer. + * + * RT inputs are millisecond reaction times. Lapse threshold follows + * Dinges & Powell (1985) at 500 ms. `slowest10_rt_ms` is the mean of the + * worst 10 % of trials, which is the published anticipation-resistant + * summary statistic — slightly more robust than overall mean RT to a few + * outliers. + */ + +export interface PvtStats { + mean_rt_ms: number; + median_rt_ms: number; + slowest10_rt_ms: number; + lapse_count: number; + response_count: number; +} + +export const LAPSE_THRESHOLD_MS = 500; + +/** Arithmetic mean. Returns 0 for an empty input. */ +export function mean(xs: readonly number[]): number { + if (xs.length === 0) return 0; + let s = 0; + for (const x of xs) s += x; + return s / xs.length; +} + +/** + * Median of an unsorted array — picks the middle value (or the upper + * middle for even-length inputs, matching the Svelte component's + * implementation). Returns 0 for an empty input. + */ +export function median(xs: readonly number[]): number { + if (xs.length === 0) return 0; + const sorted = [...xs].sort((a, b) => a - b); + return sorted[Math.floor(sorted.length / 2)]; +} + +/** + * Mean of the slowest 10 % of trials. When there are fewer than 10 + * trials the slowest-bucket size rounds down to zero, in which case we + * fall back to the slowest single trial — that's the convention the + * literature uses for very short PVTs. Returns 0 for an empty input. + */ +export function slowest10Mean(xs: readonly number[]): number { + if (xs.length === 0) return 0; + const sorted = [...xs].sort((a, b) => a - b); + const cut = Math.floor(sorted.length * 0.9); + const tail = sorted.slice(cut); + if (tail.length === 0) return sorted[sorted.length - 1]; + return mean(tail); +} + +/** Trials with RT above the lapse threshold (Dinges & Powell 1985 = 500 ms). */ +export function lapseCount(xs: readonly number[], threshold: number = LAPSE_THRESHOLD_MS): number { + let n = 0; + for (const x of xs) if (x > threshold) n += 1; + return n; +} + +/** Convenience: compute every summary statistic in one pass. */ +export function computeStats(rts: readonly number[]): PvtStats { + return { + mean_rt_ms: mean(rts), + median_rt_ms: median(rts), + slowest10_rt_ms: slowest10Mean(rts), + lapse_count: lapseCount(rts), + response_count: rts.length, + }; +} diff --git a/src/lib/settings/sleep-analysis.ts b/src/lib/settings/sleep-analysis.ts new file mode 100644 index 000000000..47fcad84a --- /dev/null +++ b/src/lib/settings/sleep-analysis.ts @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Shared sleep-analysis helpers. + +import type { SleepStages } from "$lib/types"; + +export interface SleepAnalysis { + efficiency: number; // % of non-wake time + onsetLatencyMin: number; // minutes to first non-wake + remLatencyMin: number; // minutes to first REM (-1 if none) + totalMin: number; // total recording minutes + awakenings: number; // transitions from sleep → wake + stageMinutes: { + wake: number; + n1: number; + n2: number; + n3: number; + rem: number; + }; +} + +/** Derive sleep-analysis metrics from staged epochs. */ +export function analyzeSleep(sleep: SleepStages): SleepAnalysis { + const eps = sleep.epochs; + const epochSecs = sleep.summary.epoch_secs || 5; + const totalMin = (eps.length * epochSecs) / 60; + const wakeMin = (sleep.summary.wake_epochs * epochSecs) / 60; + const efficiency = totalMin > 0 ? ((totalMin - wakeMin) / totalMin) * 100 : 0; + + // Onset latency: time until first non-wake epoch + const onsetIdx = eps.findIndex((e) => e.stage !== 0); + const onsetLatencyMin = onsetIdx >= 0 ? (onsetIdx * epochSecs) / 60 : totalMin; + + // REM latency: time from sleep onset to first REM + const remIdx = eps.findIndex((e) => e.stage === 4); + const remLatencyMin = remIdx >= 0 && onsetIdx >= 0 ? ((remIdx - onsetIdx) * epochSecs) / 60 : -1; + + // Awakenings: transitions from sleep (stage 1-4) to wake (stage 0) + let awakenings = 0; + for (let i = 1; i < eps.length; i++) { + if (eps[i].stage === 0 && eps[i - 1].stage > 0) awakenings++; + } + + const m = (n: number) => (n * epochSecs) / 60; + const stageMinutes = { + wake: m(sleep.summary.wake_epochs), + n1: m(sleep.summary.n1_epochs), + n2: m(sleep.summary.n2_epochs), + n3: m(sleep.summary.n3_epochs), + rem: m(sleep.summary.rem_epochs), + }; + + return { efficiency, onsetLatencyMin, remLatencyMin, totalMin, awakenings, stageMinutes }; +} diff --git a/src/lib/app-name-store.svelte.ts b/src/lib/stores/app-name.svelte.ts similarity index 66% rename from src/lib/app-name-store.svelte.ts rename to src/lib/stores/app-name.svelte.ts index 16ee520e3..ca5a7244b 100644 --- a/src/lib/app-name-store.svelte.ts +++ b/src/lib/stores/app-name.svelte.ts @@ -15,7 +15,10 @@ import { invoke } from "@tauri-apps/api/core"; -let appName = $state("NeuroSkill™"); // sensible fallback while we fetch +const DEFAULT_APP_BASE_NAME = "NeuroSkill"; +const TRADEMARK_SUFFIX = "™"; + +let appName = $state(`${DEFAULT_APP_BASE_NAME}${TRADEMARK_SUFFIX}`); // sensible fallback while we fetch /** Reactive getter — use inside Svelte `$derived` / templates. */ export function getAppName(): string { @@ -27,11 +30,21 @@ function titleCase(s: string): string { return s.charAt(0).toUpperCase() + s.slice(1); } +/** Ensure UI-facing name always renders with a single trailing trademark sign. */ +function toDisplayName(raw: string): string { + const normalizedBase = raw + .replaceAll(TRADEMARK_SUFFIX, "") + .replace(/\(tm\)/gi, "") + .trim(); + const base = normalizedBase || DEFAULT_APP_BASE_NAME; + return `${titleCase(base)}${TRADEMARK_SUFFIX}`; +} + // Fetch from Rust on module init (runs once per window). (async () => { try { const raw = await invoke("get_app_name"); - if (raw) appName = titleCase(raw); + if (raw) appName = toDisplayName(raw); } catch { // keep fallback } diff --git a/src/lib/stores/brain.svelte.ts b/src/lib/stores/brain.svelte.ts new file mode 100644 index 000000000..c5d7c1e4d --- /dev/null +++ b/src/lib/stores/brain.svelte.ts @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-3.0-only +/** + * Reactive brain state store — polls daemon `/v1/brain/*` endpoints every 30s. + * Shared across all components: status bar, dashboard, activity tab, tray. + */ + +import { daemonGet, daemonPost } from "$lib/daemon/http"; + +export interface FlowState { + in_flow: boolean; + score: number; + duration_secs: number; + avg_focus: number | null; + file_switches: number; + edit_velocity: number; +} + +export interface FatigueState { + fatigued: boolean; + focus_decline_pct: number; + continuous_work_mins: number; + suggestion: string; +} + +export interface StreakState { + current_streak_days: number; + today_deep_mins: number; + today_qualifies: boolean; +} + +// ── Reactive state ─────────────────────────────────────────────────────────── + +let flow = $state(null); +let fatigue = $state(null); +let streak = $state(null); +let polling = false; +let timer: ReturnType | undefined; + +export function getFlow(): FlowState | null { + return flow; +} +export function getFatigue(): FatigueState | null { + return fatigue; +} +export function getStreak(): StreakState | null { + return streak; +} + +async function poll(): Promise { + try { + const [f, a, s] = await Promise.allSettled([ + daemonPost("/v1/brain/flow-state", { windowSecs: 300 }), + daemonGet("/v1/brain/fatigue"), + daemonPost("/v1/brain/streak", { minDeepWorkMins: 60 }), + ]); + if (f.status === "fulfilled") flow = f.value; + if (a.status === "fulfilled") fatigue = a.value; + if (s.status === "fulfilled") streak = s.value; + } catch { + // daemon offline + } +} + +let refCount = 0; + +export function startBrainPolling(): void { + refCount++; + if (polling) return; + polling = true; + poll(); // immediate first poll + timer = setInterval(poll, 30_000); +} + +export function stopBrainPolling(): void { + refCount = Math.max(0, refCount - 1); + if (refCount > 0) return; // other consumers still need it + polling = false; + if (timer) { + clearInterval(timer); + timer = undefined; + } +} diff --git a/src/lib/stores/bt-status.svelte.ts b/src/lib/stores/bt-status.svelte.ts new file mode 100644 index 000000000..49046b184 --- /dev/null +++ b/src/lib/stores/bt-status.svelte.ts @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 only. +/** + * Centralised reactive store for Bluetooth availability status. + * + * Set by the main dashboard page when the BLE adapter state changes; + * read by CustomTitleBar to tint the title bar red when BT is off. + */ + +let btOff = $state(false); + +/** Reactive getter — true when Bluetooth is unavailable. */ +export function isBtOff(): boolean { + return btOff; +} + +/** Update the Bluetooth-off flag from the dashboard. */ +export function setBtOff(off: boolean): void { + btOff = off; +} diff --git a/src/lib/chart-colors-store.svelte.ts b/src/lib/stores/chart-colors.svelte.ts similarity index 79% rename from src/lib/chart-colors-store.svelte.ts rename to src/lib/stores/chart-colors.svelte.ts index 4498ad301..2835d65e3 100644 --- a/src/lib/chart-colors-store.svelte.ts +++ b/src/lib/stores/chart-colors.svelte.ts @@ -37,7 +37,7 @@ export interface ChartScheme { delta: string; theta: string; alpha: string; - beta: string; + beta: string; gamma: string; } @@ -46,11 +46,15 @@ export interface ChartScheme { * Green / Blue / Purple / Orange channels with vivid band colors. */ const DEFAULT: ChartScheme = { - id: "default", + id: "default", labelKey: "chartScheme.default", - descKey: "chartScheme.defaultDesc", + descKey: "chartScheme.defaultDesc", channels: ["#22c55e", "#60a5fa", "#c084fc", "#fb923c"], - delta: "#6366f1", theta: "#22c55e", alpha: "#3b82f6", beta: "#f59e0b", gamma: "#ef4444", + delta: "#6366f1", + theta: "#22c55e", + alpha: "#3b82f6", + beta: "#f59e0b", + gamma: "#ef4444", }; /** @@ -58,11 +62,15 @@ const DEFAULT: ChartScheme = { * Cyan / Magenta / Lime / Gold channels. */ const NEON: ChartScheme = { - id: "neon", + id: "neon", labelKey: "chartScheme.neon", - descKey: "chartScheme.neonDesc", + descKey: "chartScheme.neonDesc", channels: ["#00fff7", "#ff00e5", "#b5ff00", "#ffd000"], - delta: "#7c3aed", theta: "#00fff7", alpha: "#3b82f6", beta: "#ffd000", gamma: "#ff006a", + delta: "#7c3aed", + theta: "#00fff7", + alpha: "#3b82f6", + beta: "#ffd000", + gamma: "#ff006a", }; /** @@ -70,11 +78,15 @@ const NEON: ChartScheme = { * White-to-gray channel differentiation for minimal distraction. */ const MONO: ChartScheme = { - id: "mono", + id: "mono", labelKey: "chartScheme.mono", - descKey: "chartScheme.monoDesc", + descKey: "chartScheme.monoDesc", channels: ["#e2e8f0", "#94a3b8", "#cbd5e1", "#64748b"], - delta: "#94a3b8", theta: "#a1a1aa", alpha: "#d4d4d8", beta: "#71717a", gamma: "#52525b", + delta: "#94a3b8", + theta: "#a1a1aa", + alpha: "#d4d4d8", + beta: "#71717a", + gamma: "#52525b", }; /** @@ -83,11 +95,15 @@ const MONO: ChartScheme = { * Based on the Wong (2011) colorblind-safe palette. */ const CB_DEUTAN: ChartScheme = { - id: "cb-deutan", + id: "cb-deutan", labelKey: "chartScheme.cbDeutan", - descKey: "chartScheme.cbDeutanDesc", + descKey: "chartScheme.cbDeutanDesc", channels: ["#0072b2", "#e69f00", "#f0e442", "#cc79a7"], - delta: "#0072b2", theta: "#e69f00", alpha: "#56b4e9", beta: "#f0e442", gamma: "#cc79a7", + delta: "#0072b2", + theta: "#e69f00", + alpha: "#56b4e9", + beta: "#f0e442", + gamma: "#cc79a7", }; /** @@ -95,11 +111,15 @@ const CB_DEUTAN: ChartScheme = { * Uses blue / yellow / teal / pink. */ const CB_PROTAN: ChartScheme = { - id: "cb-protan", + id: "cb-protan", labelKey: "chartScheme.cbProtan", - descKey: "chartScheme.cbProtanDesc", + descKey: "chartScheme.cbProtanDesc", channels: ["#2196f3", "#ffeb3b", "#009688", "#e91e63"], - delta: "#2196f3", theta: "#009688", alpha: "#03a9f4", beta: "#ffeb3b", gamma: "#e91e63", + delta: "#2196f3", + theta: "#009688", + alpha: "#03a9f4", + beta: "#ffeb3b", + gamma: "#e91e63", }; /** @@ -107,23 +127,20 @@ const CB_PROTAN: ChartScheme = { * Uses red / green / magenta / cyan. */ const CB_TRITAN: ChartScheme = { - id: "cb-tritan", + id: "cb-tritan", labelKey: "chartScheme.cbTritan", - descKey: "chartScheme.cbTritanDesc", + descKey: "chartScheme.cbTritanDesc", channels: ["#d32f2f", "#388e3c", "#e040fb", "#00bcd4"], - delta: "#d32f2f", theta: "#388e3c", alpha: "#e040fb", beta: "#00bcd4", gamma: "#ff7043", + delta: "#d32f2f", + theta: "#388e3c", + alpha: "#e040fb", + beta: "#00bcd4", + gamma: "#ff7043", }; // ── All schemes in display order ────────────────────────────────────────────── -export const CHART_SCHEMES: ChartScheme[] = [ - DEFAULT, - NEON, - MONO, - CB_DEUTAN, - CB_PROTAN, - CB_TRITAN, -]; +export const CHART_SCHEMES: ChartScheme[] = [DEFAULT, NEON, MONO, CB_DEUTAN, CB_PROTAN, CB_TRITAN]; // ── Reactive state ──────────────────────────────────────────────────────────── @@ -135,7 +152,7 @@ function load(): string { } function findScheme(id: string): ChartScheme { - return CHART_SCHEMES.find(s => s.id === id) ?? DEFAULT; + return CHART_SCHEMES.find((s) => s.id === id) ?? DEFAULT; } function apply() { @@ -158,9 +175,13 @@ function apply() { // ── Public API ──────────────────────────────────────────────────────────────── -export function getChartScheme(): string { return current; } +export function getChartScheme(): string { + return current; +} -export function getActiveScheme(): ChartScheme { return findScheme(current); } +export function getActiveScheme(): ChartScheme { + return findScheme(current); +} export function setChartScheme(id: string) { current = id; diff --git a/src/lib/stores/cmdk-history.svelte.ts b/src/lib/stores/cmdk-history.svelte.ts new file mode 100644 index 000000000..8c7207aa5 --- /dev/null +++ b/src/lib/stores/cmdk-history.svelte.ts @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +/** + * Cmd-K usage history — tracks which commands users run most often / recently. + * Persisted to localStorage so recency/frequency boosts survive reloads. + */ + +const STORAGE_KEY = "cmdk-history"; +const MAX_ENTRIES = 500; + +interface UsageEntry { + count: number; + lastUsed: number; +} + +let history = $state>(load()); + +function load(): Record { + if (typeof localStorage === "undefined") return {}; + try { + return JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}"); + } catch { + return {}; + } +} + +function save() { + if (typeof localStorage === "undefined") return; + // LRU eviction if too large + const entries = Object.entries(history); + if (entries.length > MAX_ENTRIES) { + entries.sort((a, b) => b[1].lastUsed - a[1].lastUsed); + history = Object.fromEntries(entries.slice(0, MAX_ENTRIES)); + } + localStorage.setItem(STORAGE_KEY, JSON.stringify(history)); +} + +export function recordUsage(id: string) { + const now = Date.now(); + const prev = history[id]; + history[id] = { count: (prev?.count ?? 0) + 1, lastUsed: now }; + save(); +} + +export function getUsageStats(): Record { + return history; +} + +/** + * Compute a boost score for a command based on usage frequency and recency. + * Returns 0 for never-used commands. + */ +export function usageBoost(id: string): number { + const entry = history[id]; + if (!entry) return 0; + const now = Date.now(); + const recency = Math.max(0, 1 - (now - entry.lastUsed) / (7 * 86_400_000)); // decays over 7 days + return Math.log2(entry.count + 1) * 3 + recency * 5; +} + +/** + * Get the N most recently used command IDs, sorted by last use time. + */ +export function getRecentIds(n = 5): string[] { + return Object.entries(history) + .sort((a, b) => b[1].lastUsed - a[1].lastUsed) + .slice(0, n) + .map(([id]) => id); +} diff --git a/src/lib/font-size-store.svelte.ts b/src/lib/stores/font-size.svelte.ts similarity index 82% rename from src/lib/font-size-store.svelte.ts rename to src/lib/stores/font-size.svelte.ts index 21dbe668d..c7dcc616f 100644 --- a/src/lib/font-size-store.svelte.ts +++ b/src/lib/stores/font-size.svelte.ts @@ -15,12 +15,14 @@ const STORAGE_KEY = "skill-font-size"; /** Available presets (percentage of base 16px). */ export const FONT_SIZE_PRESETS = [ - { label: "XS", value: 75 }, - { label: "S", value: 87 }, - { label: "M", value: 100 }, - { label: "L", value: 112 }, - { label: "XL", value: 125 }, + { label: "2XS", value: 50 }, + { label: "XS", value: 75 }, + { label: "S", value: 87 }, + { label: "M", value: 100 }, + { label: "L", value: 112 }, + { label: "XL", value: 125 }, { label: "XXL", value: 150 }, + { label: "3XL", value: 200 }, ] as const; export type FontSizeValue = (typeof FONT_SIZE_PRESETS)[number]["value"]; @@ -32,7 +34,7 @@ function load(): number { const v = localStorage.getItem(STORAGE_KEY); if (v) { const n = parseInt(v, 10); - if (FONT_SIZE_PRESETS.some(p => p.value === n)) return n; + if (FONT_SIZE_PRESETS.some((p) => p.value === n)) return n; } return 100; } @@ -46,7 +48,9 @@ function apply() { document.documentElement.style.setProperty("--font-scale", String(pct / 100)); } -export function getFontSize(): number { return current; } +export function getFontSize(): number { + return current; +} export function setFontSize(pct: number) { current = pct; diff --git a/src/lib/stores/theme.svelte.ts b/src/lib/stores/theme.svelte.ts new file mode 100644 index 000000000..7d74a90bd --- /dev/null +++ b/src/lib/stores/theme.svelte.ts @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 only. +/** + * Theme store — manages light / dark / system / high-contrast preference. + * Persists to settings.json via Tauri IPC (with localStorage fallback). + * Applies the "dark" and "high-contrast" classes on reactively. + */ + +import { invoke } from "@tauri-apps/api/core"; + +export type ThemeMode = "system" | "light" | "dark"; + +// ── Accent colour presets ───────────────────────────────────────────────────── +// +// Each preset carries a full `--color-violet-*` palette. At runtime we +// override Tailwind CSS custom properties on so accent-like utility +// classes across the app (`violet-*`, `blue-*`, `indigo-*`, `sky-*`) all +// adopt the selected hue — no template-wide refactor required. +// +// Values are taken directly from Tailwind v4's built-in color palette so +// each preset is a perfectly balanced, perceptually-uniform ramp. + +export interface AccentPreset { + id: string; + label: string; + /** oklch of the 600-shade — shown as the swatch colour in the picker. */ + swatch: string; + palette: Record; +} + +export const ACCENT_PRESETS: AccentPreset[] = [ + { + id: "violet", + label: "Violet", + swatch: "oklch(0.558 0.288 302.3)", + palette: { + "--color-violet-50": "oklch(0.969 0.016 272.3)", + "--color-violet-100": "oklch(0.943 0.029 294.6)", + "--color-violet-200": "oklch(0.895 0.058 296.8)", + "--color-violet-300": "oklch(0.842 0.117 293.1)", + "--color-violet-400": "oklch(0.749 0.194 293.2)", + "--color-violet-500": "oklch(0.627 0.265 292.7)", + "--color-violet-600": "oklch(0.558 0.288 302.3)", + "--color-violet-700": "oklch(0.491 0.270 292.0)", + "--color-violet-800": "oklch(0.432 0.232 292.0)", + "--color-violet-900": "oklch(0.380 0.189 293.0)", + "--color-violet-950": "oklch(0.283 0.141 291.1)", + }, + }, + { + id: "indigo", + label: "Indigo", + swatch: "oklch(0.511 0.262 276.9)", + palette: { + "--color-violet-50": "oklch(0.962 0.018 272.3)", + "--color-violet-100": "oklch(0.930 0.034 272.7)", + "--color-violet-200": "oklch(0.870 0.065 274.0)", + "--color-violet-300": "oklch(0.785 0.115 274.7)", + "--color-violet-400": "oklch(0.673 0.182 276.9)", + "--color-violet-500": "oklch(0.585 0.233 277.1)", + "--color-violet-600": "oklch(0.511 0.262 276.9)", + "--color-violet-700": "oklch(0.457 0.240 277.0)", + "--color-violet-800": "oklch(0.398 0.195 277.7)", + "--color-violet-900": "oklch(0.359 0.144 278.7)", + "--color-violet-950": "oklch(0.257 0.090 281.3)", + }, + }, + { + id: "blue", + label: "Blue", + swatch: "oklch(0.546 0.245 262.9)", + palette: { + "--color-violet-50": "oklch(0.970 0.014 254.6)", + "--color-violet-100": "oklch(0.932 0.032 255.6)", + "--color-violet-200": "oklch(0.882 0.059 254.1)", + "--color-violet-300": "oklch(0.809 0.105 251.8)", + "--color-violet-400": "oklch(0.707 0.165 254.6)", + "--color-violet-500": "oklch(0.623 0.214 259.1)", + "--color-violet-600": "oklch(0.546 0.245 262.9)", + "--color-violet-700": "oklch(0.488 0.243 264.4)", + "--color-violet-800": "oklch(0.424 0.199 265.6)", + "--color-violet-900": "oklch(0.379 0.146 265.1)", + "--color-violet-950": "oklch(0.282 0.091 267.9)", + }, + }, + { + id: "sky", + label: "Sky", + swatch: "oklch(0.588 0.158 241.9)", + palette: { + "--color-violet-50": "oklch(0.977 0.013 236.6)", + "--color-violet-100": "oklch(0.951 0.026 236.6)", + "--color-violet-200": "oklch(0.901 0.058 230.9)", + "--color-violet-300": "oklch(0.828 0.111 230.3)", + "--color-violet-400": "oklch(0.746 0.161 225.9)", + "--color-violet-500": "oklch(0.685 0.169 237.3)", + "--color-violet-600": "oklch(0.588 0.158 241.9)", + "--color-violet-700": "oklch(0.500 0.134 242.7)", + "--color-violet-800": "oklch(0.443 0.110 240.8)", + "--color-violet-900": "oklch(0.391 0.090 240.4)", + "--color-violet-950": "oklch(0.293 0.066 243.2)", + }, + }, + { + id: "teal", + label: "Teal", + swatch: "oklch(0.600 0.145 184.7)", + palette: { + "--color-violet-50": "oklch(0.984 0.014 180.7)", + "--color-violet-100": "oklch(0.963 0.023 180.8)", + "--color-violet-200": "oklch(0.910 0.048 180.4)", + "--color-violet-300": "oklch(0.855 0.099 180.4)", + "--color-violet-400": "oklch(0.777 0.152 180.6)", + "--color-violet-500": "oklch(0.705 0.157 187.3)", + "--color-violet-600": "oklch(0.600 0.145 184.7)", + "--color-violet-700": "oklch(0.510 0.122 184.8)", + "--color-violet-800": "oklch(0.442 0.095 185.9)", + "--color-violet-900": "oklch(0.391 0.077 188.7)", + "--color-violet-950": "oklch(0.277 0.056 192.6)", + }, + }, + { + id: "emerald", + label: "Emerald", + swatch: "oklch(0.596 0.145 163.0)", + palette: { + "--color-violet-50": "oklch(0.979 0.021 166.1)", + "--color-violet-100": "oklch(0.950 0.052 163.1)", + "--color-violet-200": "oklch(0.905 0.093 164.1)", + "--color-violet-300": "oklch(0.845 0.143 164.8)", + "--color-violet-400": "oklch(0.765 0.177 163.2)", + "--color-violet-500": "oklch(0.696 0.170 162.5)", + "--color-violet-600": "oklch(0.596 0.145 163.0)", + "--color-violet-700": "oklch(0.508 0.118 165.6)", + "--color-violet-800": "oklch(0.432 0.095 166.7)", + "--color-violet-900": "oklch(0.378 0.077 168.1)", + "--color-violet-950": "oklch(0.262 0.051 172.6)", + }, + }, + { + id: "rose", + label: "Rose", + swatch: "oklch(0.586 0.253 17.6)", + palette: { + "--color-violet-50": "oklch(0.969 0.015 12.4)", + "--color-violet-100": "oklch(0.941 0.030 12.6)", + "--color-violet-200": "oklch(0.892 0.058 10.0)", + "--color-violet-300": "oklch(0.811 0.111 8.6)", + "--color-violet-400": "oklch(0.712 0.194 13.4)", + "--color-violet-500": "oklch(0.645 0.246 16.4)", + "--color-violet-600": "oklch(0.586 0.253 17.6)", + "--color-violet-700": "oklch(0.514 0.222 16.9)", + "--color-violet-800": "oklch(0.455 0.188 13.1)", + "--color-violet-900": "oklch(0.408 0.153 2.4)", + "--color-violet-950": "oklch(0.271 0.105 12.8)", + }, + }, + { + id: "pink", + label: "Pink", + swatch: "oklch(0.592 0.249 0.6)", + palette: { + "--color-violet-50": "oklch(0.971 0.014 343.2)", + "--color-violet-100": "oklch(0.948 0.028 342.3)", + "--color-violet-200": "oklch(0.899 0.061 343.2)", + "--color-violet-300": "oklch(0.823 0.120 343.4)", + "--color-violet-400": "oklch(0.718 0.202 349.8)", + "--color-violet-500": "oklch(0.656 0.241 355.0)", + "--color-violet-600": "oklch(0.592 0.249 0.6)", + "--color-violet-700": "oklch(0.525 0.223 3.9)", + "--color-violet-800": "oklch(0.459 0.187 3.8)", + "--color-violet-900": "oklch(0.408 0.153 2.4)", + "--color-violet-950": "oklch(0.271 0.105 12.8)", + }, + }, +]; + +const STORAGE_KEY = "skill-theme"; +const HC_KEY = "skill-high-contrast"; +const ACCENT_KEY = "skill-accent"; + +let mode = $state(loadMode()); +let resolved = $state<"light" | "dark">(resolve(loadMode())); +let highContrast = $state(loadHC()); +let accentId = $state(loadAccentId()); + +function loadMode(): ThemeMode { + if (typeof localStorage === "undefined") return "system"; + const v = localStorage.getItem(STORAGE_KEY); + if (v === "light" || v === "dark") return v; + return "system"; +} + +function loadAccentId(): string { + if (typeof localStorage === "undefined") return "violet"; + return localStorage.getItem(ACCENT_KEY) ?? "violet"; +} + +/** Load persisted theme + accent from Tauri settings on startup. */ +export async function initFromSettings() { + try { + const [theme, _lang] = await invoke<[string, string]>("get_theme_and_language"); + if (theme === "light" || theme === "dark" || theme === "system") { + setTheme(theme as ThemeMode); + } + } catch { + /* not available (e.g. dev server without Tauri) */ + } + try { + const id = await invoke("get_accent_color"); + applyAccent(id); + } catch { + /* degrade gracefully */ + } +} + +function loadHC(): boolean { + if (typeof localStorage === "undefined") return false; + return localStorage.getItem(HC_KEY) === "true"; +} + +function resolve(m: ThemeMode): "light" | "dark" { + if (m !== "system") return m; + if (typeof window === "undefined") return "dark"; + return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; +} + +function apply() { + if (typeof document === "undefined") return; + resolved = resolve(mode); + document.documentElement.classList.toggle("dark", resolved === "dark"); + document.documentElement.classList.toggle("high-contrast", highContrast); +} + +/** Listen for OS theme changes (only matters in "system" mode). */ +if (typeof window !== "undefined") { + window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => { + if (mode === "system") apply(); + }); + // Also respond to OS high-contrast / forced-colors preference. + window.matchMedia("(prefers-contrast: more)").addEventListener("change", (e) => { + if (!localStorage.getItem(HC_KEY)) { + highContrast = e.matches; + apply(); + } + }); +} + +export function getTheme(): ThemeMode { + return mode; +} +export function getResolved(): "light" | "dark" { + return resolved; +} +export function getHighContrast(): boolean { + return highContrast; +} +export function getAccentId(): string { + return accentId; +} + +// ── Accent application ──────────────────────────────────────────────────────── + +/** + * Override the Tailwind `--color-violet-*` CSS custom properties on + * so every `violet-*` utility class adopts the new accent hue instantly. + * Falls back to the "violet" preset if `id` is unknown. + */ +export function applyAccent(id: string) { + const preset = ACCENT_PRESETS.find((p) => p.id === id) ?? ACCENT_PRESETS[0]; + accentId = preset.id; + if (typeof localStorage !== "undefined") { + if (preset.id === "violet") localStorage.removeItem(ACCENT_KEY); + else localStorage.setItem(ACCENT_KEY, preset.id); + } + if (typeof document === "undefined") return; + const style = document.documentElement.style; + const accentFamilies = ["violet", "blue", "indigo", "sky"] as const; + for (const [prop, val] of Object.entries(preset.palette)) { + style.setProperty(prop, val); + for (const family of accentFamilies) { + if (family === "violet") continue; + style.setProperty(prop.replace("--color-violet-", `--color-${family}-`), val); + } + } +} + +export function setAccent(id: string) { + applyAccent(id); + invoke("set_accent_color", { accent: id }).catch((_e) => {}); +} + +export function setTheme(m: ThemeMode) { + mode = m; + if (typeof localStorage !== "undefined") { + if (m === "system") localStorage.removeItem(STORAGE_KEY); + else localStorage.setItem(STORAGE_KEY, m); + } + apply(); + // Persist to settings.json via Tauri + invoke("set_theme", { theme: m }).catch((_e) => {}); +} + +export function setHighContrast(on: boolean) { + highContrast = on; + if (typeof localStorage !== "undefined") { + if (on) localStorage.setItem(HC_KEY, "true"); + else localStorage.removeItem(HC_KEY); + } + apply(); +} + +export function toggleHighContrast() { + setHighContrast(!highContrast); +} + +/** Toggle between light and dark (ignores system — just flips the resolved value). */ +export function toggleTheme() { + setTheme(resolved === "dark" ? "light" : "dark"); +} + +/** Cycle: system → light → dark → system */ +export function cycleTheme() { + const next: Record = { + system: "light", + light: "dark", + dark: "system", + }; + setTheme(next[mode]); +} + +// Apply on load +apply(); +applyAccent(loadAccentId()); diff --git a/src/lib/stores/titlebar.svelte.ts b/src/lib/stores/titlebar.svelte.ts new file mode 100644 index 000000000..3262377c3 --- /dev/null +++ b/src/lib/stores/titlebar.svelte.ts @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 only. +/** + * Titlebar reactive state — shared between page components (writers) and + * CustomTitleBar.svelte (reader). + * + * Consolidated from the former per-page titlebar files: + * titlebar-state.svelte.ts, chat-titlebar.svelte.ts, + * history-titlebar.svelte.ts, label-titlebar.svelte.ts, + * help-search-state.svelte.ts + */ + +// ── Factory helpers ─────────────────────────────────────────────────────────── + +/** + * Create a module-level `$state` store with a typed initial value. + * + * ```ts + * export const chatTitlebar = createTitlebarState({ modelName: "", status: "stopped" as const }); + * // page writes: chatTitlebar.modelName = "phi-3"; + * // titlebar reads: {chatTitlebar.modelName} + * ``` + */ +export function createTitlebarState>(initial: T): T { + const s: T = $state(initial) as T; + return s; +} + +/** + * Create a callback bag initialised with no-op functions. + * + * ```ts + * export const hCbs = createTitlebarCallbacks({ prev: () => {}, next: () => {} }); + * // page sets: hCbs.prev = () => navigatePrev(); + * // titlebar calls: hCbs.prev(); + * ``` + */ +// biome-ignore lint/suspicious/noExplicitAny: generic callback map — args are opaque +export function createTitlebarCallbacks void>>(defaults: T): T { + // Plain object — intentionally not reactive. The titlebar calls these + // synchronously; Svelte reactivity is not needed on the callback bag itself. + return { ...defaults }; +} + +// ── Chat titlebar ───────────────────────────────────────────────────────────── + +export type LlmStatus = "stopped" | "loading" | "running"; + +export const chatTitlebarState = createTitlebarState<{ + modelName: string; + status: LlmStatus; +}>({ modelName: "", status: "stopped" }); + +// ── History titlebar ────────────────────────────────────────────────────────── + +/** Calendar heatmap view granularity. */ +export type HistoryViewMode = "year" | "month" | "week" | "day"; + +/** Display data written by the history page, read by CustomTitleBar. */ +export const hBar = createTitlebarState({ + /** True once the history page has mounted and set up callbacks. */ + active: false, + /** Mirrors history page `daysLoading`. */ + daysLoading: true, + /** Total number of local calendar days with recordings. */ + dayCount: 0, + /** Index of the currently displayed day (0 = newest). */ + currentDayIdx: 0, + /** Pre-formatted label for the current day (e.g. "Mar 12, 2026"). */ + currentDayLabel: "", + /** Whether compare mode is active. */ + compareMode: false, + /** Number of sessions selected for comparison. */ + compareCount: 0, + /** Whether the labels panel is open. */ + showLabels: false, + /** Current history view mode (calendar granularity). */ + viewMode: "month" as HistoryViewMode, + /** Navigation label for calendar views (e.g. "March 2026"). */ + calendarLabel: "", +}); + +/** Callbacks set by the history page on mount. */ +export const hCbs = createTitlebarCallbacks({ + prev: () => {}, + next: () => {}, + toggleCompare: () => {}, + openCompare: () => {}, + toggleLabels: () => {}, + reload: () => {}, + setViewMode: (_m: HistoryViewMode) => {}, + calendarPrev: () => {}, + calendarNext: () => {}, +}); + +// ── Label titlebar ──────────────────────────────────────────────────────────── + +export const labelTitlebarState = createTitlebarState({ active: false, elapsed: "0s" }); + +// ── Help titlebar ───────────────────────────────────────────────────────────── + +export const helpTitlebarState = $state({ query: "", version: "…" }); diff --git a/src/lib/toast-store.svelte.ts b/src/lib/stores/toast.svelte.ts similarity index 90% rename from src/lib/toast-store.svelte.ts rename to src/lib/stores/toast.svelte.ts index 68e2a8288..52db8fdf4 100644 --- a/src/lib/toast-store.svelte.ts +++ b/src/lib/stores/toast.svelte.ts @@ -29,21 +29,16 @@ let nextId = 0; export const toasts = $state([]); const DEFAULT_DURATIONS: Record = { - info: 5_000, + info: 5_000, success: 4_000, warning: 6_000, - error: 8_000, + error: 8_000, }; /** * Add a toast notification. Returns the toast id (useful for programmatic dismissal). */ -export function addToast( - level: ToastLevel, - title: string, - message: string, - duration?: number, -): number { +export function addToast(level: ToastLevel, title: string, message: string, duration?: number): number { const id = nextId++; const dur = duration ?? DEFAULT_DURATIONS[level]; toasts.push({ id, level, title, message, duration: dur }); diff --git a/src/lib/window-title.svelte.ts b/src/lib/stores/window-title.svelte.ts similarity index 85% rename from src/lib/window-title.svelte.ts rename to src/lib/stores/window-title.svelte.ts index 84dfdace2..2bbc44a0f 100644 --- a/src/lib/window-title.svelte.ts +++ b/src/lib/stores/window-title.svelte.ts @@ -14,12 +14,14 @@ * * Usage — call once at the top level of a component's + +
+
+ {t("llm.tools.skillsSection")} + + {skills.filter((s) => s.enabled).length}/{skills.length} + +
+ + + +
+
+
+

+ {t("llm.tools.skillsSectionDesc")} +

+ {#if skillsLicense} + + {/if} +
+ {#if skills.length > 0} +
+ + +
+ {/if} +
+ + {#if skillsLicenseOpen && skillsLicense} +
+
{@html skillsLicense.replace(/AI100/g, 'AI100')}
+
+ {/if} +
+ +
+ {#if skillsLoading} +

{t("llm.tools.skillsLoading")}

+ {:else if skills.length === 0} +

{t("llm.tools.skillsNone")}

+ {:else} + {#each skills as skill} + +
(hoveredSkill = skill.name)} + onmouseleave={() => (hoveredSkill = null)}> +
+
+
+ {skill.name} + + {skill.source} + +
+ {@html inlineMd(skill.description)} +
+ +
+
+ {/each} + {/if} +
+
+
+
+ + diff --git a/src/lib/tools/ChatToolsSection.svelte b/src/lib/tools/ChatToolsSection.svelte new file mode 100644 index 000000000..e930078de --- /dev/null +++ b/src/lib/tools/ChatToolsSection.svelte @@ -0,0 +1,448 @@ + + + + +
+
+ {t("llm.tools.section")} + {config.tools.enabled ? toolRows.filter((r) => config.tools[r.key]).length + "/" + toolRows.length : "off"} + {#if configSaving}saving…{/if} +
+ + + +
+

{t("llm.tools.sectionDesc")}

+ +
+ +
+ {#each toolRows as tool} + +
(hoveredTool = tool.key)} + onmouseleave={() => (hoveredTool = null)}> +
+
+
+ {tool.label} + {#if tool.warn} + {t("llm.tools.advanced")} + {/if} +
+ {tool.desc} +
+ +
+ {#if hoveredTool === tool.key} +
+
+

{tool.hint}

+ {#if tool.warn}

{t("llm.tools.advancedHint")}

{/if} +
+ {/if} +
+ {/each} + + {#if config.tools.web_search} +
+
+ {t("llm.tools.searchProvider")} + {t("llm.tools.searchProviderDesc")} +
+ +
+ {#each [ + { key: "duckduckgo" as SearchBackend, label: "DuckDuckGo" }, + { key: "brave" as SearchBackend, label: "Brave" }, + { key: "searxng" as SearchBackend, label: "SearXNG" }, + ] as opt} + + {/each} +
+ + {#if config.tools.web_search_provider.backend === "brave"} +
+ + {t("llm.tools.braveApiKeyDesc")} + { + const val = (e.target as HTMLInputElement).value; + onConfigChange({ ...config, tools: { ...config.tools, web_search_provider: { ...config.tools.web_search_provider, brave_api_key: val } } }); + }} + class="mt-0.5 w-full rounded-lg border border-border/60 dark:border-white/[0.08] bg-surface-3 px-2.5 py-1.5 text-ui-md text-foreground placeholder:text-muted-foreground/50 outline-none focus:ring-1 focus:ring-blue-500/50" /> +
+ {/if} + + {#if config.tools.web_search_provider.backend === "searxng"} +
+ + {t("llm.tools.searxngUrlDesc")} + { + const val = (e.target as HTMLInputElement).value; + onConfigChange({ ...config, tools: { ...config.tools, web_search_provider: { ...config.tools.web_search_provider, searxng_url: val } } }); + }} + class="mt-0.5 w-full rounded-lg border border-border/60 dark:border-white/[0.08] bg-surface-3 px-2.5 py-1.5 text-ui-md text-foreground placeholder:text-muted-foreground/50 outline-none focus:ring-1 focus:ring-blue-500/50" /> +
+ {/if} +
+ {/if} + + {#if config.tools.web_search || config.tools.web_fetch} +
+
+
+ {t("llm.tools.webCache")} + {t("llm.tools.webCacheDesc")} +
+ +
+ + {#if config.tools.web_cache?.enabled ?? true} +
+
+ {t("llm.tools.webCacheSearchTtl")} +
+ {#each [{ secs: 300, label: "5" }, { secs: 900, label: "15" }, { secs: 1800, label: "30" }, { secs: 3600, label: "60" }] as opt} + + {/each} +
+
+
+ {t("llm.tools.webCacheFetchTtl")} +
+ {#each [{ secs: 1800, label: "30" }, { secs: 3600, label: "60" }, { secs: 7200, label: "120" }, { secs: 14400, label: "240" }] as opt} + + {/each} +
+
+
+ +
+
+ + {#if cacheStats.total_entries > 0} + {t("llm.tools.webCacheEntries").replace("{n}", String(cacheStats.total_entries))} + ({t("llm.tools.webCacheSize").replace("{size}", fmtBytes(cacheStats.total_bytes))}) + {:else} + {t("llm.tools.webCacheEmpty")} + {/if} + +
+ + {#if cacheStats.total_entries > 0} + + {/if} +
+
+ + {#if cacheEntries.length > 0} +
+ {#each cacheEntries as entry} +
+
+
+ + {entry.kind === "search" ? t("llm.tools.webCacheSearch") : t("llm.tools.webCacheFetch")} + + {entry.label} +
+
+ {entry.domain}{fmtBytes(entry.bytes)}{fmtAge(entry.created_at)} +
+
+
+ + +
+
+ {/each} +
+ {/if} +
+ {/if} +
+ {/if} +
+ +
+
+ {t("llm.tools.executionMode")} +
+ {#each [{ key: "parallel" as ToolExecutionMode, label: t("llm.tools.parallel") }, { key: "sequential" as ToolExecutionMode, label: t("llm.tools.sequential") }] as mode} + + {/each} +
+
+ +
+
+ {t("llm.tools.maxRounds")} + {t("llm.tools.maxRoundsDesc")} +
+
+ {#each [1, 3, 5, 10, 15] as val} + + {/each} +
+
+ +
+
+ {t("llm.tools.maxCallsPerRound")} + {t("llm.tools.maxCallsPerRoundDesc")} +
+
+ {#each [1, 2, 4, 8] as val} + + {/each} +
+
+ +
+
+ {t("llm.tools.contextCompression")} + {t("llm.tools.contextCompressionDesc")} +
+
+ {#each [ + { key: "off" as CompressionLevel, label: t("llm.tools.compressionOff") }, + { key: "normal" as CompressionLevel, label: t("llm.tools.compressionNormal") }, + { key: "aggressive" as CompressionLevel, label: t("llm.tools.compressionAggressive") }, + ] as opt} + + {/each} +
+ + {#if config.tools.context_compression.level !== "off"} +
+
+ + { + const val = parseInt((e.target as HTMLInputElement).value) || 0; + await onConfigChange({ + ...config, + tools: { + ...config.tools, + context_compression: { + ...config.tools.context_compression, + max_search_results: Math.max(0, Math.min(20, val)), + }, + }, + }); + }} + class="w-full rounded-lg border border-border/60 dark:border-white/[0.08] bg-surface-3 px-2.5 py-1.5 text-ui-md text-foreground placeholder:text-muted-foreground/50 outline-none focus:ring-1 focus:ring-blue-500/50" /> + {t("llm.tools.zeroAutoLabel")} +
+
+ + { + const val = parseInt((e.target as HTMLInputElement).value) || 0; + await onConfigChange({ + ...config, + tools: { + ...config.tools, + context_compression: { + ...config.tools.context_compression, + max_result_chars: Math.max(0, Math.min(32000, val)), + }, + }, + }); + }} + class="w-full rounded-lg border border-border/60 dark:border-white/[0.08] bg-surface-3 px-2.5 py-1.5 text-ui-md text-foreground placeholder:text-muted-foreground/50 outline-none focus:ring-1 focus:ring-blue-500/50" /> + {t("llm.tools.zeroAutoLabel")} +
+
+ {/if} +
+ +
+
+ {t("llm.tools.retrySection")} + {t("llm.tools.retrySectionDesc")} +
+ +
+
+ +
+ {#each [0, 1, 2, 3] as val} + + {/each} +
+ {t("llm.tools.retryMaxRetriesDesc")} +
+
+ +
+ {#each [500, 1000, 2000, 3000] as val} + + {/each} +
+ {t("llm.tools.retryBaseDelayDesc")} +
+
+
+
+
+
+
diff --git a/src/lib/tools/SkillsRefreshSection.svelte b/src/lib/tools/SkillsRefreshSection.svelte new file mode 100644 index 000000000..936770a9f --- /dev/null +++ b/src/lib/tools/SkillsRefreshSection.svelte @@ -0,0 +1,97 @@ + + + + +
+
+ {t("llm.tools.skillsRefresh")} +
+ + + +
+

+ {t("llm.tools.skillsRefreshDesc")} +

+
+ +
+
+
+ {t("llm.tools.skillsRefresh")} +
+
+ {#each [ + { secs: 0, label: t("llm.tools.skillsRefreshOff") }, + { secs: 43200, label: t("llm.tools.skillsRefresh12h") }, + { secs: 86400, label: t("llm.tools.skillsRefresh24h") }, + { secs: 604800, label: t("llm.tools.skillsRefresh7d") }, + ] as opt} + + {/each} +
+
+ + + +
+
+ + {t("llm.tools.skillsLastSync")}: {formatLastSync(skillsLastSync)} + +
+ +
+
+
+
+
diff --git a/src/lib/tools/SuggestSkillCta.svelte b/src/lib/tools/SuggestSkillCta.svelte new file mode 100644 index 000000000..c56d88ecc --- /dev/null +++ b/src/lib/tools/SuggestSkillCta.svelte @@ -0,0 +1,25 @@ + + + + +
+ + + + +
+ + {t("llm.tools.suggestSkill")} + + + {t("llm.tools.suggestSkillDesc")} + +
+ +
+
diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 000000000..47b0732e0 --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Shared TypeScript interfaces used across multiple pages/components. + +// ── Muse / BCI device status ───────────────────────────────────────────────── + +export interface PairedDevice { + id: string; + name: string; + last_seen: number; +} + +export interface DiscoveredDevice { + id: string; + name: string; + last_rssi: number; + is_paired: boolean; + is_preferred: boolean; +} + +export type PowerlineFreq = "Hz60" | "Hz50"; + +export interface FilterConfig { + sample_rate: number; + low_pass_hz: number | null; + high_pass_hz: number | null; + notch: PowerlineFreq | null; + notch_bandwidth_hz: number; +} + +export interface DeviceStatus { + state: "disconnected" | "scanning" | "connecting" | "connected" | "bt_off"; + device_name: string | null; + device_id: string | null; + /** Factory serial number from the headset ("sn" field), e.g. "AAAA-BBBB-CCCC". Arrives a few seconds after connect. */ + serial_number: string | null; + /** Hardware MAC address from the headset ("ma" field), e.g. "AA-BB-CC-DD-EE-FF". */ + mac_address: string | null; + csv_path: string | null; + sample_count: number; + battery: number; + eeg: number[]; + paired_devices: PairedDevice[]; + device_error: string | null; + target_name: string | null; + /** Canonical target id chosen by daemon for (re)connect attempts. */ + target_id?: string | null; + /** Human-readable target name resolved by daemon from paired metadata. */ + target_display_name?: string | null; + filter_config: FilterConfig; + /** Per-channel quality in electrode order [TP9, AF7, AF8, TP10]. + * Values: "good" | "fair" | "poor" | "no_signal" */ + channel_quality: string[]; + /** Current auto-retry attempt (0 = not retrying). */ + retry_attempt: number; + /** Seconds remaining until next auto-retry (0 = not counting down). */ + retry_countdown_secs: number; + /** Latest raw PPG values [ambient, infrared, red]. */ + ppg: number[]; + /** Total PPG samples received this session. */ + ppg_sample_count: number; + /** Total IMU samples received this session. */ + imu_sample_count: number; + /** Latest accelerometer reading [x, y, z] in g. */ + accel: [number, number, number]; + /** Latest gyroscope reading [x, y, z] in °/s. */ + gyro: [number, number, number]; + /** Battery fuel-gauge voltage in mV (Classic only, 0 on Athena). */ + fuel_gauge_mv: number; + /** Raw temperature ADC value (Classic only, 0 on Athena). */ + temperature_raw: number; + /** Which device family is connected (see `DeviceKind` in device.rs). */ + device_kind: string; + /** Hardware model code, e.g. "p21" = Muse 2. */ + hardware_version: string | null; + /** Firmware version string from the device, e.g. "3.4.5" (Muse). Arrives a few seconds after connect. */ + firmware_version?: string | null; + /** Bootloader version string from the device, if reported. */ + bootloader_version?: string | null; + /** Device has a PPG (heart-rate) sensor. */ + has_ppg: boolean; + has_vitals?: boolean; + /** Device has an IMU (accelerometer + gyroscope). */ + has_imu: boolean; + /** Device has electrodes at central scalp sites (C3/C4/Cz). */ + has_central_electrodes: boolean; + /** Device supports a full 10-20 montage (or superset). */ + has_full_montage: boolean; + /** EEG channel labels from the connected device (e.g. ["AF3","T7","Pz","T8","AF4"] for Insight). */ + channel_names?: string[]; + /** PPG channel labels from the connected device. */ + ppg_channel_names?: string[]; + /** IMU channel labels from the connected device. */ + imu_channel_names?: string[]; + /** fNIRS channel labels from the connected device. */ + fnirs_channel_names?: string[]; + /** fNIRS oxygenation proxy (0–100). */ + fnirs_oxygenation_pct?: number; + /** fNIRS workload proxy (0–100). */ + fnirs_workload?: number; + /** fNIRS left-vs-right lateralization proxy (-100..100). */ + fnirs_lateralization?: number; + /** fNIRS ΔHbO left proxy (a.u.). */ + fnirs_hbo_left?: number; + /** fNIRS ΔHbO right proxy (a.u.). */ + fnirs_hbo_right?: number; + /** fNIRS ΔHbR left proxy (a.u.). */ + fnirs_hbr_left?: number; + /** fNIRS ΔHbR right proxy (a.u.). */ + fnirs_hbr_right?: number; + /** fNIRS ΔHbT left proxy (a.u.). */ + fnirs_hbt_left?: number; + /** fNIRS ΔHbT right proxy (a.u.). */ + fnirs_hbt_right?: number; + /** fNIRS left-right connectivity proxy (Pearson r). */ + fnirs_connectivity?: number; + /** Hardware EEG channel count (e.g. 5 for Insight, 14 for EPOC X). */ + eeg_channel_count?: number; + /** Hardware EEG sample rate in Hz (e.g. 128 for Emotiv). */ + eeg_sample_rate_hz?: number; + /** Phone descriptor from the remote iOS client (model, OS, locale, etc.). */ + phone_info?: { + phone_model?: string; + phone_name?: string; + phone_marketing_name?: string; + os?: string; + os_version?: string; + app_version?: string; + battery_level?: number; + battery_state?: string; + iroh_endpoint_id?: string; + photo_url?: string; + avatar_url?: string; + image_url?: string; + photo_base64?: string; + avatar_base64?: string; + image_base64?: string; + [k: string]: unknown; + } | null; + /** Display name of the connected iroh client (from the auth store). */ + iroh_client_name?: string | null; + /** True when at least one iroh tunnel peer is currently online. */ + iroh_tunnel_online?: boolean; + /** Count of active iroh device-proxy peers. */ + iroh_connected_peers?: number; + /** True when iOS reports a remote BLE headset connected. */ + iroh_remote_device_connected?: boolean; + /** True when recent sensor chunks are actively flowing over iroh. */ + iroh_streaming_active?: boolean; + /** True when recent EEG-bearing chunks are actively flowing over iroh. */ + iroh_eeg_streaming_active?: boolean; + /** Catch-all for future fields not yet typed. */ + [k: string]: unknown; +} + +// ── Muse electrode constants ───────────────────────────────────────────────── + +export const MUSE_CHANNELS = ["TP9", "AF7", "AF8", "TP10"] as const; +export const MUSE_POSITIONS = ["Left ear", "Left forehead", "Right forehead", "Right ear"] as const; + +// ── Sleep types ────────────────────────────────────────────────────────────── + +export interface SleepEpoch { + utc: number; + stage: number; // 0=Wake, 1=N1, 2=N2, 3=N3, 5=REM + rel_delta: number; + rel_theta: number; + rel_alpha: number; + rel_beta: number; +} + +export interface SleepSummary { + total_epochs: number; + wake_epochs: number; + n1_epochs: number; + n2_epochs: number; + n3_epochs: number; + rem_epochs: number; + epoch_secs: number; +} + +export interface SleepStages { + epochs: SleepEpoch[]; + summary: SleepSummary; +} + +// ── UMAP types ─────────────────────────────────────────────────────────────── + +export interface UmapPoint { + x: number; + y: number; + z?: number; + session: number; + utc: number; + label?: string; + /** Semantic distance from the query anchor (used in kNN graph tooltips). */ + dist?: number; +} + +export interface UmapResult { + points: UmapPoint[]; + n_a: number; + n_b: number; + dim: number; + elapsed_ms?: number; +} + +export interface UmapProgress { + epoch: number; + total_epochs: number; + loss: number; + best_loss: number; + elapsed_secs: number; + epoch_ms: number; +} + +// ── Label types ────────────────────────────────────────────────────────────── + +export interface LabelRow { + id: number; + eeg_start: number; + eeg_end: number; + label_start: number; + label_end: number; + text: string; + context: string; + created_at: number; +} diff --git a/src/lib/UmapCanvas.svelte b/src/lib/umap/UmapCanvas.svelte similarity index 51% rename from src/lib/UmapCanvas.svelte rename to src/lib/umap/UmapCanvas.svelte index 59fc27297..87236f3ae 100644 --- a/src/lib/UmapCanvas.svelte +++ b/src/lib/umap/UmapCanvas.svelte @@ -9,26 +9,19 @@ the Free Software Foundation, version 3 only. --> This avoids svelte:component issues with Threlte's snippet children. --> diff --git a/src/lib/umap/UmapScene.svelte b/src/lib/umap/UmapScene.svelte new file mode 100644 index 000000000..5b74ce093 --- /dev/null +++ b/src/lib/umap/UmapScene.svelte @@ -0,0 +1,489 @@ + + + + + + + diff --git a/src/lib/umap/UmapViewer3D.svelte b/src/lib/umap/UmapViewer3D.svelte new file mode 100644 index 000000000..12bae5043 --- /dev/null +++ b/src/lib/umap/UmapViewer3D.svelte @@ -0,0 +1,1983 @@ + + + + + + +
+ + + {#if traceActive && traceTimeRange && traceTimeTicks.length > 0} + {@const prog = traceTotal > 0 ? traceProgress / traceTotal : 0} +
+
+ +
+ +
+
+ +
+ {#each traceTimeTicks as tick, i} + {@const align = i === 0 ? 'left-0' : i === traceTimeTicks.length - 1 ? 'right-0' : '-translate-x-1/2'} + 0 && i < traceTimeTicks.length - 1 ? `left:${tick.pct}%` : ''}> + {tick.label} + + {/each} +
+
+
+ {/if} + +
+ + + {#if sidebarOpen && uniqueLabels.length > 0} + {@const groups = [ + { key: "common", label: t("umap.common"), items: uniqueLabels.filter(l => l.inA && l.inB) }, + { key: "a-only", label: t("umap.sessionA"), items: uniqueLabels.filter(l => l.inA && !l.inB) }, + { key: "b-only", label: t("umap.sessionB"), items: uniqueLabels.filter(l => !l.inA && l.inB) }, + ].filter(g => g.items.length > 0)} +
+ +
+ + {t("umap.labels")} + + + + { sidebarOpen = false; selectedLabel = null; applyHighlight(); }}>✕ +
+ +
+ + + {t("umap.sessionA")} + + + + {t("umap.sessionB")} + +
+ + +
+ {#each groups as group} + +
+ {group.label} +
+
+ + {#each group.items as entry} + {@const isSelected = selectedLabel === entry.label} + {@const isProximate = proximateLabels.includes(entry.label)} + {@const hex = labelHex(entry.hue)} + + +
{ selectedLabel = selectedLabel === entry.label ? null : entry.label; applyHighlight(); }}> + +
+ {#if entry.inA && entry.inB} + + + + {:else if entry.inA} + + {:else} + + {/if} + {entry.label} +
+ {#if entry.inA} + A + {/if} + {#if entry.inB} + B + {/if} +
+
+ +
+ {#each entry.timestamps.slice(0, 6) as ts} + + {fmtUtcTime(ts.utc)} + + {/each} + {#if entry.timestamps.length > 6} + + +{entry.timestamps.length - 6} + + {/if} +
+
+ {/each} + {/each} +
+ + +
+
+ + {t("umap.proximity")} + + + {proximityDist.toFixed(1)} + +
+ +
+ {#if selectedLabel && !animating} + {#if proximateLabels.length > 0} + + {proximateLabels.length} {t("umap.nearbyLabels")} + + {:else} + {t("umap.noNearbyLabels")} + {/if} + {:else if animating} + {t("umap.computing")} + {:else} + {t("umap.selectLabelHint")} + {/if} +
+
+
+ {/if} + + +
+ + + {#if cmdHeld} +
+ ⌘ Pan +
+ {/if} + + {#if error} +
+ {t("umap.error")} +
{error}
+
+ {:else if !loaded} +
+
+ + {t("umap.loading")} +
+
+ {/if} + + + {#if computing} +
+
+
+
+ +
+ {t("umap.computing")} + {#if progress && progress.total_epochs > 0} + {@const pct = Math.round(progress.epoch / progress.total_epochs * 100)} + +
+
+
+
+ {pct}% +
+ + + epoch {progress.epoch}/{progress.total_epochs} · {progress.epoch_ms.toFixed(0)}ms/ep + + {:else} + {t("umap.placeholder")} + {/if} +
+
+ {/if} + + + {#if activeLabels.length > 0} +
+ {#each activeLabels as label} + {@const entry = linkGroups.get(label)} + {@const col = entry ? LINK_PALETTE[entry.colorIdx % LINK_PALETTE.length] : 0xffffff} +
+ + {label} + + + removeLinkGroup(label)}>✕ +
+ {/each} + {#if activeLabels.length > 1} + + +
clearAllLinks()}> + {t("umap.clearAll")} +
+ {/if} +
+ {/if} + + +
+ + + {#if timeGradient && gradientRange} + {@const span = gradientRange.maxUtc - gradientRange.minUtc} +
+ + Session {timeGradient} · time → + +
+ + {fmtGradientTs(gradientRange.minUtc, span)} + +
+
+ + {fmtGradientTs(gradientRange.maxUtc, span)} + +
+
+ {/if} + + + {#if !timeGradient && colorByDate && dateLegend.length > 0} +
+ {#each dateLegend as dl} +
+ + {dl.date} +
+ {/each} +
+ {/if} + + + {#if loaded && !error && uniqueLabels.length > 0} + {@const colAcss = `#${UMAP_COLOR_A.toString(16).padStart(6,"0")}`} + {@const colBcss = `#${UMAP_COLOR_B.toString(16).padStart(6,"0")}`} +
+ + +
+ + {t("umap.sessionA")} — {t("umap.shapeSphere")} +
+
+ + {t("umap.sessionB")} — {t("umap.shapeDiamond")} +
+ + + {#if selectedLabel} +
+
+ + {selectedLabel} + {t("umap.legendSelected")} +
+ {#if proximateLabels.length > 0} +
+ + {proximateLabels.length} {t("umap.legendNearby")} +
+ {:else} +
+ + {t("umap.noNearbyLabels")} +
+ {/if} +
+ + {t("umap.legendDimmed")} +
+ {/if} +
+ {/if} +
+ + + {#if loaded && !error && curPoints.length >= 2} +
+ + + +
+
{ timeGradient = timeGradient === 'A' ? null : 'A'; }}> + 🌈 A +
+
+
{ timeGradient = timeGradient === 'B' ? null : 'B'; }}> + 🌈 B +
+
+ + + + +
+ + {traceActive ? t("umap.traceStop") : t("umap.trace")} + + {#if traceActive && traceTotal > 0} + {traceProgress}/{traceTotal} + {/if} +
+ + + {#if uniqueLabels.length > 0} + + +
{ sidebarOpen = !sidebarOpen; if (!sidebarOpen) { selectedLabel = null; applyHighlight(); } }}> + + 🏷 {uniqueLabels.length} + +
+ {/if} + + + + +
+ +
+ {#if exportFlash === "png"} + + + + + {t("umap.exportedPng")} + {:else} + + + + + + {t("umap.exportPng")} + {/if} +
+ +
+ +
+ {#if exportFlash === "json"} + + + + {t("umap.exportedJson")} + {:else} + + + + + + {t("umap.exportJson")} + {/if} +
+
+
+ {/if} + + + {#if loaded && !error} +
+ {t("umap.nodeSize")} + + {pointScale.toFixed(1)}× +
+ {/if} + + + {#if tooltip} +
+ {tooltip.text} +
+ {/if} +
+
+
+ + diff --git a/src/lib/umap/umap-helpers.ts b/src/lib/umap/umap-helpers.ts new file mode 100644 index 000000000..31a15bf08 --- /dev/null +++ b/src/lib/umap/umap-helpers.ts @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +/** + * Pure math, color, and geometry helpers for the UMAP 3D viewer. + * + * Extracted from `UmapViewer3D.svelte` to reduce file size + * and enable independent testing. + */ + +import type { UmapPoint } from "$lib/types"; + +// ── Easing ─────────────────────────────────────────────────────────────────── + +export function easeOut(t: number): number { + return 1 - (1 - t) ** 3; +} + +export function gauss(): number { + return Math.sqrt(-2 * Math.log(Math.random() || 1e-10)) * Math.cos(Math.PI * 2 * Math.random()); +} + +// ── Color helpers ──────────────────────────────────────────────────────────── + +export function hslToRgb(h: number, s: number, l: number): [number, number, number] { + const a = s * Math.min(l, 1 - l); + const f = (n: number) => { + const k = (n + h / 30) % 12; + return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1)); + }; + return [f(0), f(8), f(4)]; +} + +export function labelHex(hue: number): string { + const [r, g, b] = hslToRgb(hue, 0.85, 0.55); + return `#${[r, g, b] + .map((v) => + Math.round(v * 255) + .toString(16) + .padStart(2, "0"), + ) + .join("")}`; +} + +/** Turbo colormap — returns raw [R, G, B] in 0–1 range. */ +export function turboRaw(t: number): [number, number, number] { + const c = Math.max(0, Math.min(1, t)); + const r = Math.max( + 0, + Math.min( + 1, + 0.13572138 + c * (4.6153926 + c * (-42.66032258 + c * (132.13108234 + c * (-152.54893924 + c * 59.28637943)))), + ), + ); + const g = Math.max( + 0, + Math.min( + 1, + 0.09140261 + c * (2.19418839 + c * (4.84296658 + c * (-14.18503333 + c * (4.27729857 + c * 2.82956604)))), + ), + ); + const b = Math.max( + 0, + Math.min( + 1, + 0.1066733 + c * (12.64194608 + c * (-60.58204836 + c * (110.36276771 + c * (-89.90310912 + c * 27.34824973)))), + ), + ); + return [r, g, b]; +} + +/** Jet colormap — returns raw [R, G, B] in 0–1 range. */ +export function jet(t: number): [number, number, number] { + const c = Math.max(0, Math.min(1, t)); + return [ + Math.min(1, Math.max(0, 1.5 - Math.abs(4 * c - 3))), + Math.min(1, Math.max(0, 1.5 - Math.abs(4 * c - 2))), + Math.min(1, Math.max(0, 1.5 - Math.abs(4 * c - 1))), + ]; +} + +/** Jet colormap → CSS hex string. */ +export function jetHex(t: number): string { + const [r, g, b] = jet(t); + return `#${[r, g, b] + .map((v) => + Math.round(v * 255) + .toString(16) + .padStart(2, "0"), + ) + .join("")}`; +} + +// ── Timestamp formatting ───────────────────────────────────────────────────── + +/** Format a UTC timestamp for the gradient legend, adapting precision to span. */ +export function fmtGradientTs(utc: number, span: number): string { + if (utc <= 0) return ""; + const d = new Date(utc * 1000); + if (span >= 172800) return d.toLocaleDateString(undefined, { month: "short", day: "numeric" }); + if (span >= 3600) return d.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" }); + return d.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit", second: "2-digit" }); +} + +/** Format a UTC timestamp as "HH:MM" local time. */ +export function fmtUtcTime(utc: number): string { + const d = new Date(utc * 1000); + return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`; +} + +/** Convert UTC seconds → local "YYYY-MM-DD" string. */ +export function utcToLocalDate(utc: number): string { + const d = new Date(utc * 1000); + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; +} + +// ── Geometry ───────────────────────────────────────────────────────────────── + +/** Normalise UMAP points into a [-1, 1]³ cube centered at the centroid. */ +export function normalise(pts: UmapPoint[]): Float32Array { + const n = pts.length; + const pos = new Float32Array(n * 3); + if (n === 0) return pos; + let mx = 0, + my = 0, + mz = 0; + for (const p of pts) { + mx += p.x; + my += p.y; + mz += p.z ?? 0; + } + mx /= n; + my /= n; + mz /= n; + let maxR = 0; + for (const p of pts) { + const dx = p.x - mx, + dy = p.y - my, + dz = (p.z ?? 0) - mz; + maxR = Math.max(maxR, Math.sqrt(dx * dx + dy * dy + dz * dz)); + } + if (maxR < 1e-8) maxR = 1; + for (let i = 0; i < n; i++) { + pos[i * 3] = (pts[i].x - mx) / maxR; + pos[i * 3 + 1] = (pts[i].y - my) / maxR; + pos[i * 3 + 2] = ((pts[i].z ?? 0) - mz) / maxR; + } + return pos; +} + +/** Generate random initial positions for animation. */ +export function randomPositions(pts: UmapPoint[]): Float32Array { + const n = pts.length; + const pos = new Float32Array(n * 3); + for (let i = 0; i < n; i++) { + pos[i * 3] = gauss() * 0.15; + pos[i * 3 + 1] = gauss() * 0.15; + pos[i * 3 + 2] = gauss() * 0.15; + } + return pos; +} + +/** + * Build tick positions for the trace gradient legend. + * Returns array of { t: number (0-1), label: string }. + */ +export function buildTraceTimeTicks(sorted: number[]): { t: number; label: string }[] { + if (sorted.length < 2) return []; + const tMin = sorted[0]; + const tMax = sorted[sorted.length - 1]; + const span = tMax - tMin; + if (span <= 0) return []; + + // Choose a "nice" tick interval + const TARGET_TICKS = 5; + const raw = span / TARGET_TICKS; + const niceIntervals = [60, 120, 300, 600, 900, 1800, 3600, 7200, 14400, 28800, 43200, 86400]; + const interval = niceIntervals.find((i) => i >= raw) ?? 86400; + + // Generate ticks at whole multiples of interval + const firstTick = Math.ceil(tMin / interval) * interval; + const ticks: { t: number; label: string }[] = []; + for (let ts = firstTick; ts <= tMax; ts += interval) { + const normT = (ts - tMin) / span; + if (normT < 0.02 || normT > 0.98) continue; // skip ticks too close to edges + ticks.push({ t: normT, label: fmtGradientTs(ts, span) }); + } + + // Always include endpoints if we have room + if (ticks.length === 0 || ticks[0].t > 0.1) { + ticks.unshift({ t: 0, label: fmtGradientTs(tMin, span) }); + } + if (ticks.length === 0 || ticks[ticks.length - 1].t < 0.9) { + ticks.push({ t: 1, label: fmtGradientTs(tMax, span) }); + } + return ticks; +} + +/** + * Build a date → color palette for multi-day UMAP coloring. + * Returns a Map from local date string to [r, g, b] tuple. + */ +export function buildDatePaletteRaw(pts: UmapPoint[]): Map { + const daySet = new Set(); + for (const p of pts) { + if (p.utc > 0) daySet.add(utcToLocalDate(p.utc)); + } + const days = [...daySet].sort(); + const palette = new Map(); + for (let i = 0; i < days.length; i++) { + const hue = (i / Math.max(days.length, 1)) * 360; + palette.set(days[i], hslToRgb(hue, 0.8, 0.55)); + } + return palette; +} diff --git a/src/lib/umap/umap-viewer-logic.ts b/src/lib/umap/umap-viewer-logic.ts new file mode 100644 index 000000000..14707d8ae --- /dev/null +++ b/src/lib/umap/umap-viewer-logic.ts @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +/** + * UmapViewer3D pure logic — extracted from UmapViewer3D.svelte. + * + * Geometry normalization, color mapping, and point cloud generation + * for the 3D UMAP scatter plot. No Three.js or Svelte dependencies. + */ + +import type { UmapPoint } from "$lib/types"; + +// ── Turbo colormap ──────────────────────────────────────────────────────────── + +/** + * Attempt to apply a Turbo-like RGB mapping for a parameter t in [0, 1]. + * Falls back to a simple blue→cyan→green→yellow→red gradient. + */ +export function turboApprox(t: number): [number, number, number] { + const tc = Math.max(0, Math.min(1, t)); + // Attempt a piecewise approximation of the Turbo colormap + const r = Math.max(0, Math.min(1, 1.5 - Math.abs(tc - 0.75) * 4)); + const g = Math.max(0, Math.min(1, 1.5 - Math.abs(tc - 0.5) * 4)); + const b = Math.max(0, Math.min(1, 1.5 - Math.abs(tc - 0.25) * 4)); + return [r, g, b]; +} + +/** Convert RGB [0-1] triple to a hex color string. */ +export function rgbToHex(r: number, g: number, b: number): string { + const hex = (v: number) => + Math.round(v * 255) + .toString(16) + .padStart(2, "0"); + return `#${hex(r)}${hex(g)}${hex(b)}`; +} + +// ── Point cloud geometry ────────────────────────────────────────────────────── + +/** + * Normalize UMAP points into a [-1, 1]^3 cube centered at origin. + * Returns a Float32Array of xyz triples (length = points.length * 3). + */ +export function normalisePoints(pts: UmapPoint[]): Float32Array { + const n = pts.length; + const out = new Float32Array(n * 3); + let mnX = Infinity, + mxX = -Infinity, + mnY = Infinity, + mxY = -Infinity, + mnZ = Infinity, + mxZ = -Infinity; + + for (const p of pts) { + const z = p.z ?? 0; + if (p.x < mnX) mnX = p.x; + if (p.x > mxX) mxX = p.x; + if (p.y < mnY) mnY = p.y; + if (p.y > mxY) mxY = p.y; + if (z < mnZ) mnZ = z; + if (z > mxZ) mxZ = z; + } + + const rX = mxX - mnX || 1; + const rY = mxY - mnY || 1; + const rZ = mxZ - mnZ || 1; + const scale = Math.max(rX, rY, rZ); + + for (let i = 0; i < n; i++) { + const p = pts[i]; + out[i * 3] = ((p.x - mnX) / scale) * 2 - 1; + out[i * 3 + 1] = ((p.y - mnY) / scale) * 2 - 1; + out[i * 3 + 2] = (((p.z ?? 0) - mnZ) / scale) * 2 - 1; + } + return out; +} + +/** + * Generate random xyz positions in [-1, 1]^3 for initial point-cloud animation. + */ +export function randomPositions(count: number, seed = 42): Float32Array { + const out = new Float32Array(count * 3); + // Simple deterministic pseudo-random (mulberry32) + let s = seed | 0; + const rand = () => { + s = (s + 0x6d2b79f5) | 0; + let t = Math.imul(s ^ (s >>> 15), 1 | s); + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; + for (let i = 0; i < out.length; i++) { + out[i] = rand() * 2 - 1; + } + return out; +} + +/** + * Compute per-point color array based on a normalized parameter (0-1 per point). + * Returns a Float32Array of rgb triples. + */ +export function buildColorArray(values: number[], isDark: boolean, dimFactor = 0.75): Float32Array { + const out = new Float32Array(values.length * 3); + for (let i = 0; i < values.length; i++) { + let [r, g, b] = turboApprox(values[i]); + if (!isDark) { + r *= dimFactor; + g *= dimFactor; + b *= dimFactor; + } + out[i * 3] = r; + out[i * 3 + 1] = g; + out[i * 3 + 2] = b; + } + return out; +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 43fd8f5d0..3004521c1 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -4,16 +4,16 @@ // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3 only. -import { clsx, type ClassValue } from "clsx"; +import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]): string { return twMerge(clsx(inputs)); } -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: structural type utility — any is intentional for conditional type matching export type WithoutChild = T extends { child?: any } ? Omit : T; export type WithoutChildrenOrChild = WithoutChildren>; -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: structural type utility — any is intentional for conditional type matching export type WithoutChildren = T extends { children?: any } ? Omit : T; export type WithElementRef = T & { ref?: U | null }; diff --git a/src/lib/utils/accelerator.ts b/src/lib/utils/accelerator.ts new file mode 100644 index 000000000..86982e8ed --- /dev/null +++ b/src/lib/utils/accelerator.ts @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 only. + +/** + * Pretty-print a Tauri accelerator string for display. + * "CmdOrCtrl+Shift+L" → "⌘⇧L" on Mac, "Ctrl+Shift+L" elsewhere. + */ +export function prettyAccelerator(accel: string): string { + if (!accel) return "—"; + const isMac = + typeof navigator !== "undefined" && (navigator.platform?.startsWith("Mac") || navigator.userAgent.includes("Mac")); + let s = accel; + if (isMac) { + s = s + .replace(/CmdOrCtrl/gi, "⌘") + .replace(/CommandOrControl/gi, "⌘") + .replace(/Ctrl/gi, "⌃") + .replace(/Cmd/gi, "⌘") + .replace(/Alt/gi, "⌥") + .replace(/Shift/gi, "⇧") + .replace(/\+/g, ""); + } else { + s = s.replace(/CmdOrCtrl/gi, "Ctrl").replace(/CommandOrControl/gi, "Ctrl"); + } + return s; +} diff --git a/src/lib/utils/error.ts b/src/lib/utils/error.ts new file mode 100644 index 000000000..d77fe9224 --- /dev/null +++ b/src/lib/utils/error.ts @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-3.0-only + +/** Convert unknown thrown values to a display-safe message. */ +export function toErrorMessage(err: unknown, fallback = "failed"): string { + if (err instanceof Error && err.message) return err.message; + if (typeof err === "string" && err.length > 0) return err; + if (err == null) return fallback; + return String(err); +} + +/** Build a localized prefixed error message (e.g. "Error: ..."). */ +export function formatPrefixedError(prefix: string, err: unknown, fallback = "failed"): string { + return `${prefix}: ${toErrorMessage(err, fallback)}`; +} diff --git a/src/lib/utils/fuzzy-utils.ts b/src/lib/utils/fuzzy-utils.ts new file mode 100644 index 000000000..fef8f7af2 --- /dev/null +++ b/src/lib/utils/fuzzy-utils.ts @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +/** + * Damerau-Levenshtein distance for typo tolerance in Cmd-K search. + */ + +/** + * Compute the Damerau-Levenshtein distance between two strings. + * Supports insertions, deletions, substitutions, and transpositions. + */ +export function damerauLevenshtein(a: string, b: string): number { + const la = a.length; + const lb = b.length; + if (la === 0) return lb; + if (lb === 0) return la; + + // Use flat array for speed + const d = new Uint16Array((la + 1) * (lb + 1)); + const w = lb + 1; + + for (let i = 0; i <= la; i++) d[i * w] = i; + for (let j = 0; j <= lb; j++) d[j] = j; + + for (let i = 1; i <= la; i++) { + for (let j = 1; j <= lb; j++) { + const cost = a[i - 1] === b[j - 1] ? 0 : 1; + d[i * w + j] = Math.min( + d[(i - 1) * w + j] + 1, // deletion + d[i * w + (j - 1)] + 1, // insertion + d[(i - 1) * w + (j - 1)] + cost, // substitution + ); + // transposition + if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) { + d[i * w + j] = Math.min(d[i * w + j], d[(i - 2) * w + (j - 2)] + cost); + } + } + } + return d[la * w + lb]; +} + +/** + * Check if query approximately matches any word in text. + * Returns a reduced score if a word-level edit distance <= maxDist, else null. + */ +export function typoMatch(query: string, text: string, maxDist = 2): number | null { + const q = query.toLowerCase(); + const words = text + .toLowerCase() + .split(/[\s\-_./›]+/) + .filter((w) => w.length > 0); + + let bestScore = Infinity; + for (const word of words) { + // Only compare if lengths are reasonably close + if (Math.abs(word.length - q.length) > maxDist) continue; + const dist = damerauLevenshtein(q, word); + if (dist <= maxDist && dist < bestScore) { + bestScore = dist; + } + } + + if (bestScore === Infinity) return null; + // Lower distance = higher score. Max score for dist=1 is ~5, dist=2 is ~2 + return Math.max(1, 8 - bestScore * 3); +} diff --git a/src/lib/utils/logger.ts b/src/lib/utils/logger.ts new file mode 100644 index 000000000..31a3999ba --- /dev/null +++ b/src/lib/utils/logger.ts @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Copyright (C) 2026 NeuroSkill.com +// +// Structured frontend logger. +// +// In production builds, console.log / console.debug are stripped by esbuild +// (see vite.config.js). This module provides a thin wrapper so call-sites +// are self-documenting and easy to grep. +// +// Usage: +// import { log } from "$lib/utils/logger"; +// log.info("device connected", { name: device.name }); +// log.warn("retrying BLE scan"); +// log.error("websocket closed", error); +// log.debug("raw EEG sample", sample); // stripped in production + +const isDev = import.meta.env.DEV; + +function timestamp(): string { + return new Date().toISOString(); +} + +/* biome-ignore lint/suspicious/noConsole: this IS the logger */ +const _debug = console.debug.bind(console); +/* biome-ignore lint/suspicious/noConsole: this IS the logger */ +const _log = console.log.bind(console); +/* biome-ignore lint/suspicious/noConsole: this IS the logger */ +const _warn = console.warn.bind(console); +/* biome-ignore lint/suspicious/noConsole: this IS the logger */ +const _error = console.error.bind(console); + +export const log = { + /** Debug-level: stripped from production builds. */ + debug(...args: unknown[]): void { + if (isDev) _debug(`[${timestamp()}] [DEBUG]`, ...args); + }, + + /** Informational: stripped from production builds. */ + info(...args: unknown[]): void { + if (isDev) _log(`[${timestamp()}] [INFO]`, ...args); + }, + + /** Warning: preserved in production. */ + warn(...args: unknown[]): void { + _warn(`[${timestamp()}] [WARN]`, ...args); + }, + + /** Error: preserved in production. */ + error(...args: unknown[]): void { + _error(`[${timestamp()}] [ERROR]`, ...args); + }, +}; diff --git a/src/lib/utils/markdown-normalize.ts b/src/lib/utils/markdown-normalize.ts new file mode 100644 index 000000000..56119b85f --- /dev/null +++ b/src/lib/utils/markdown-normalize.ts @@ -0,0 +1,116 @@ +export function normalizeMarkdown(raw: string): string { + const sanitized = stripLeadingOrphanJsonFence(raw); + const slots: string[] = []; + + const protectedText = sanitized + // Preserve fenced code blocks verbatim. + .replace(/```[\s\S]*?```/g, (block) => stash(block, slots)) + // Preserve inline code spans so emphasis repair does not rewrite examples. + .replace(/`[^`\n]*`/g, (code) => stash(code, slots)); + + const normalized = normalizeEmphasisRuns(normalizeEmphasisRuns(protectedText, "**", "strong"), "*", "em"); + + return normalized.replace(/\uE000(\d+)\uE001/g, (_, index) => slots[Number(index)] ?? ""); +} + +function stash(text: string, slots: string[]): string { + const id = slots.push(text) - 1; + return `\uE000${id}\uE001`; +} + +function stripLeadingOrphanJsonFence(raw: string): string { + const open = raw.match(/^\s*```(?:json)?\s*\n/i); + if (!open) return raw; + + const rest = raw.slice(open[0].length); + if (/^```|\n```/m.test(rest)) return raw; + + const lines = rest.split("\n"); + let idx = 0; + let sawJsonish = false; + + while (idx < lines.length) { + const line = lines[idx].trim(); + + if (!line) { + idx += 1; + if (sawJsonish) { + const next = lines.slice(idx).find((candidate) => candidate.trim()); + if (next && !looksLikeJsonFragmentLine(next.trim())) break; + } + continue; + } + + if (!looksLikeJsonFragmentLine(line)) break; + sawJsonish = true; + idx += 1; + } + + if (!sawJsonish) return raw; + + return lines.slice(idx).join("\n").trimStart(); +} + +function looksLikeJsonFragmentLine(line: string): boolean { + return ( + /^[{}[\],]+$/.test(line) || + /^"[^\n]*$/.test(line) || + /^[A-Za-z0-9_.$-]+"?\s*:\s*/.test(line) || + /^(true|false|null),?$/.test(line) || + /^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?,?$/.test(line) + ); +} + +function normalizeEmphasisRuns(text: string, delimiter: "**" | "*", tag: "strong" | "em"): string { + const pattern = + delimiter === "**" + ? /(? { + const trimmed = inner.trim(); + if (!trimmed) return match; + + const left = source[offset - 1] ?? ""; + const right = source[offset + match.length] ?? ""; + const first = trimmed[0] ?? ""; + const last = trimmed[trimmed.length - 1] ?? ""; + + const canOpen = delimiterCanOpen(left, first); + const canClose = delimiterCanClose(last, right); + + if (!canOpen || !canClose) { + return `<${tag}>${trimmed}`; + } + + return `${delimiter}${trimmed}${delimiter}`; + }); +} + +function delimiterCanOpen(prev: string, next: string): boolean { + const leftFlanking = isLeftFlanking(prev, next); + const rightFlanking = isRightFlanking(prev, next); + return leftFlanking && (!rightFlanking || isPunctuation(prev)); +} + +function delimiterCanClose(prev: string, next: string): boolean { + const leftFlanking = isLeftFlanking(prev, next); + const rightFlanking = isRightFlanking(prev, next); + return rightFlanking && (!leftFlanking || isPunctuation(next)); +} + +function isLeftFlanking(prev: string, next: string): boolean { + return !isWhitespace(next) && (!isPunctuation(next) || isWhitespace(prev) || isPunctuation(prev)); +} + +function isRightFlanking(prev: string, next: string): boolean { + return !isWhitespace(prev) && (!isPunctuation(prev) || isWhitespace(next) || isPunctuation(next)); +} + +function isWhitespace(char: string): boolean { + return char === "" || /\s/u.test(char); +} + +function isPunctuation(char: string): boolean { + return char !== "" && /[\p{P}\p{S}]/u.test(char); +} diff --git a/src/lib/virtual-eeg/generator.ts b/src/lib/virtual-eeg/generator.ts new file mode 100644 index 000000000..81e42eb40 --- /dev/null +++ b/src/lib/virtual-eeg/generator.ts @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Virtual EEG signal generator — produces synthetic multichannel EEG data. + +export type SignalTemplate = "sine" | "good_quality" | "bad_quality" | "interruptions" | "file"; +export type SignalQuality = "poor" | "fair" | "good" | "excellent"; +export type LineNoise = "none" | "50hz" | "60hz"; + +export interface VirtualEegConfig { + channels: number; + sampleRate: number; + template: SignalTemplate; + quality: SignalQuality; + amplitudeUv: number; + noiseUv: number; + lineNoise: LineNoise; + dropoutProb: number; + fileData: number[][] | null; // [channel][sample] + fileName: string | null; +} + +export const DEFAULT_CONFIG: VirtualEegConfig = { + channels: 4, + sampleRate: 256, + template: "good_quality", + quality: "good", + amplitudeUv: 50, + noiseUv: 5, + lineNoise: "none", + dropoutProb: 0, + fileData: null, + fileName: null, +}; + +export const QUALITY_SNR: Record = { + poor: 0.5, + fair: 2, + good: 5, + excellent: 20, +}; + +export const CHANNEL_PRESETS: Record = { + 1: ["Fp1"], + 2: ["Fp1", "Fp2"], + 4: ["TP9", "AF7", "AF8", "TP10"], + 8: ["Fp1", "Fp2", "F3", "F4", "C3", "C4", "O1", "O2"], + 16: ["Fp1", "Fp2", "F7", "F3", "Fz", "F4", "F8", "T3", "C3", "Cz", "C4", "T4", "P3", "Pz", "P4", "O1"], + 32: [ + "Fp1", + "Fp2", + "AF3", + "AF4", + "F7", + "F3", + "Fz", + "F4", + "F8", + "FT7", + "FC3", + "FCz", + "FC4", + "FT8", + "T7", + "C3", + "Cz", + "C4", + "T8", + "TP7", + "CP3", + "CPz", + "CP4", + "TP8", + "P7", + "P3", + "Pz", + "P4", + "P8", + "O1", + "Oz", + "O2", + ], +}; + +/** Get channel labels for a given channel count. */ +export function getChannelLabels(n: number): string[] { + if (n in CHANNEL_PRESETS) return CHANNEL_PRESETS[n]; + return Array.from({ length: n }, (_, i) => `Ch${i + 1}`); +} + +/** Gaussian random using Box-Muller transform. */ +function gaussianRandom(): number { + const u1 = Math.random(); + const u2 = Math.random(); + return Math.sqrt(-2 * Math.log(u1 || 1e-10)) * Math.cos(2 * Math.PI * u2); +} + +/** Generate a single sample buffer for all channels at a given time. */ +export function generateSamples(config: VirtualEegConfig, sampleIndex: number): number[] { + const { channels, sampleRate, template, amplitudeUv, noiseUv, lineNoise, dropoutProb } = config; + const snr = QUALITY_SNR[config.quality]; + const t = sampleIndex / sampleRate; + + // Check for dropout + if (dropoutProb > 0 && Math.random() < dropoutProb / sampleRate) { + return new Array(channels).fill(0); + } + + const samples = new Array(channels); + + for (let ch = 0; ch < channels; ch++) { + let value = 0; + const phaseOffset = ch * 0.3; + + switch (template) { + case "sine": { + // Standard EEG frequency bands + const delta = Math.sin(2 * Math.PI * 2 * t + phaseOffset) * 0.4; + const theta = Math.sin(2 * Math.PI * 6 * t + phaseOffset) * 0.3; + const alpha = Math.sin(2 * Math.PI * 10 * t + phaseOffset) * 0.5; + const beta = Math.sin(2 * Math.PI * 20 * t + phaseOffset) * 0.2; + const gamma = Math.sin(2 * Math.PI * 40 * t + phaseOffset) * 0.1; + value = (delta + theta + alpha + beta + gamma) * amplitudeUv; + break; + } + + case "good_quality": { + // Dominant alpha rhythm (8–13 Hz) + pink noise background + const alpha = Math.sin(2 * Math.PI * (10 + ch * 0.1) * t + phaseOffset) * 0.6; + const theta = Math.sin(2 * Math.PI * 5.5 * t + phaseOffset) * 0.15; + const beta = Math.sin(2 * Math.PI * 18 * t + phaseOffset) * 0.1; + // Pink noise approximation + const pink = (gaussianRandom() * 0.15) / (1 + Math.abs(gaussianRandom()) * 0.5); + value = ((alpha + theta + beta + pink) * amplitudeUv * snr) / 5; + break; + } + + case "bad_quality": { + // Muscle artefacts + electrode pops + const alpha = Math.sin(2 * Math.PI * 10 * t + phaseOffset) * 0.2; + const muscle = gaussianRandom() * 0.6; // High-frequency muscle + const pop = Math.random() < 0.005 ? gaussianRandom() * 5 : 0; // Electrode pops + const drift = Math.sin(2 * Math.PI * 0.1 * t + ch) * 2; // Slow drift + value = (alpha + muscle + pop + drift) * amplitudeUv * 0.3; + break; + } + + case "interruptions": { + // Good signal with periodic 1–3s dropouts every 8–15s + const cycleLen = 10 + ch * 0.5; + const dropoutLen = 1.5; + const inDropout = t % cycleLen > cycleLen - dropoutLen; + if (inDropout) { + value = gaussianRandom() * noiseUv * 0.1; + } else { + const alpha = Math.sin(2 * Math.PI * 10 * t + phaseOffset) * 0.5; + const theta = Math.sin(2 * Math.PI * 6 * t + phaseOffset) * 0.2; + value = ((alpha + theta) * amplitudeUv * snr) / 5; + } + break; + } + + case "file": { + if (config.fileData?.[ch]) { + const data = config.fileData[ch]; + value = data[sampleIndex % data.length] ?? 0; + } + break; + } + } + + // Add noise + if (template !== "file") { + value += gaussianRandom() * noiseUv; + } + + // Add line noise + if (lineNoise === "50hz") { + value += Math.sin(2 * Math.PI * 50 * t) * noiseUv * 0.3; + } else if (lineNoise === "60hz") { + value += Math.sin(2 * Math.PI * 60 * t) * noiseUv * 0.3; + } + + samples[ch] = value; + } + + return samples; +} + +// ── Runtime ──────────────────────────────────────────────────────────────── + +export interface VirtualEegRuntime { + config: VirtualEegConfig; + running: boolean; + sampleIndex: number; + timer: ReturnType | null; + onSamples: ((electrode: number, samples: number[], timestamp: number) => void) | null; +} + +export function createRuntime(config: VirtualEegConfig): VirtualEegRuntime { + return { + config: { ...config }, + running: false, + sampleIndex: 0, + timer: null, + onSamples: null, + }; +} + +const BATCH_SIZE = 8; // samples per callback + +export function startRuntime(rt: VirtualEegRuntime): void { + if (rt.running) return; + rt.running = true; + const intervalMs = (1000 * BATCH_SIZE) / rt.config.sampleRate; + + rt.timer = setInterval(() => { + const timestamp = Date.now() / 1000; + for (let ch = 0; ch < rt.config.channels; ch++) { + const samples: number[] = []; + for (let i = 0; i < BATCH_SIZE; i++) { + const allCh = generateSamples(rt.config, rt.sampleIndex + i); + samples.push(allCh[ch]); + } + rt.onSamples?.(ch, samples, timestamp); + } + rt.sampleIndex += BATCH_SIZE; + }, intervalMs); +} + +export function stopRuntime(rt: VirtualEegRuntime): void { + if (!rt.running) return; + rt.running = false; + if (rt.timer) { + clearInterval(rt.timer); + rt.timer = null; + } +} + +// ── Band power estimation (simple FFT-free approximation) ────────────────── + +/** Estimate band powers from recent samples using sine-component energy. */ +export function estimateBandPowers(config: VirtualEegConfig, sampleIndex: number): Record { + const t = sampleIndex / config.sampleRate; + const snr = QUALITY_SNR[config.quality]; + const amp = config.amplitudeUv; + + // Approximate band energies based on the template + let delta = 0, + theta = 0, + alpha = 0, + beta = 0, + gamma = 0; + + switch (config.template) { + case "sine": + delta = 0.4 * amp; + theta = 0.3 * amp; + alpha = 0.5 * amp; + beta = 0.2 * amp; + gamma = 0.1 * amp; + break; + case "good_quality": + delta = 0.1 * amp; + theta = 0.15 * amp; + alpha = (0.6 * amp * snr) / 5; + beta = 0.1 * amp; + gamma = 0.05 * amp; + break; + case "bad_quality": + delta = 0.3 * amp; + theta = 0.2 * amp; + alpha = 0.1 * amp; + beta = 0.5 * amp; + gamma = 0.4 * amp; // muscle artefacts + break; + case "interruptions": { + const cycleLen = 10; + const inDropout = t % cycleLen > cycleLen - 1.5; + if (inDropout) { + delta = theta = alpha = beta = gamma = 0.01 * amp; + } else { + delta = 0.1 * amp; + theta = 0.2 * amp; + alpha = 0.5 * amp; + beta = 0.1 * amp; + gamma = 0.05 * amp; + } + break; + } + default: + delta = theta = alpha = beta = gamma = 0.1 * amp; + } + + // Add some noise-driven variation + const jitter = () => 1 + (Math.random() - 0.5) * 0.2; + return { + delta: delta * jitter(), + theta: theta * jitter(), + alpha: alpha * jitter(), + beta: beta * jitter(), + gamma: gamma * jitter(), + }; +} diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte new file mode 100644 index 000000000..b4dc08d63 --- /dev/null +++ b/src/routes/+error.svelte @@ -0,0 +1,35 @@ + + + + + +
+
⚠
+

+ {$page.error?.message ?? t("common.error")} +

+

+ {t("error.description")} +

+
+ + {t("error.goHome")} + + +
+ {#if $page.status && $page.status !== 500} +

+ HTTP {$page.status} +

+ {/if} +
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index ae099a84e..601d275c7 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -5,57 +5,102 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. -->
+ -
- {@render children()} + + +
+ +
+ {@render children()} +
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 5af949e05..8f3595cef 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -5,738 +5,1771 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. --> -
- -
+
+ +
+ + {#if status.state === "connected" && (status.device_kind === "iroh-remote" || status.iroh_client_name || status.phone_info)} + {@const pi = status.phone_info} +
+
+ {#if phoneBannerImage((pi ?? null) as Record | null, status.device_name, status.device_kind)} + | null, status.device_name, status.device_kind) ?? ""} alt="remote client" + class="w-7 h-7 rounded-lg shrink-0 object-cover border border-indigo-400/30" /> + {:else} + +
+ + + + + +
+ {/if} + +
+
+ + {status.device_name ?? status.iroh_client_name ?? "Remote device"} + + + + + +
+ + streamed from {pi?.phone_marketing_name ?? pi?.phone_name ?? status.iroh_client_name ?? "remote client"} + {#if pi?.os} · {pi.os}{#if pi?.os_version} {pi.os_version}{/if}{/if} + {#if pi?.app_version} · v{pi.app_version}{/if} + {#if status.iroh_eeg_streaming_active} · EEG live{:else if status.iroh_streaming_active} · link live{/if} + +
+ + {#if pi?.battery_level != null && (pi?.battery_level ?? 0) > 0} +
+ 📱 + + {Math.round((pi?.battery_level ?? 0) * 100)}% + +
+ {/if} +
+
+ {/if} + {#if modelDlVisible} -
+
{#if modelDl.downloading_weights}
- + {t("model.downloading")} {#if modelDl.download_status_msg} - + {modelDl.download_status_msg} {/if}
@@ -767,21 +1800,21 @@ the Free Software Foundation, version 3 only. --> px-3 py-2.5">
- + {t("model.autoRetryIn", { secs: String(modelDl.download_retry_in_secs) })} · {t("model.autoRetryAttempt", { n: String(modelDl.download_retry_attempt + 1) })} {#if modelDl.download_status_msg && modelDl.download_status_msg !== "Download cancelled."} - + {modelDl.download_status_msg} {/if}
@@ -790,9 +1823,9 @@ the Free Software Foundation, version 3 only. -->
{/if} - + bg-surface-1"> {#if status.state === "scanning"}{t("dashboard.scanning")} + {:else if status.state === "connecting"}{t("dashboard.scanning")} {:else if status.state === "connected"}● {t("dashboard.connected")} {:else if status.state === "bt_off"}⚠ {t("dashboard.btOff")} {:else}{t("dashboard.disconnected")}{/if} @@ -826,33 +1860,28 @@ the Free Software Foundation, version 3 only. --> /> {/if} {#if status.device_name && status.state === "connected"} - {status.device_name} +
+
+ {status.device_name} + {#if sourceLabel} + {sourceLabel} + {/if} + {#if hasSecondary} + {t("dashboard.primary")} + +{secondarySessions.length} + {/if} +
+ {#if connectedDeviceId} +
{connectedDeviceId}
+ {/if} +
{:else} {/if} - +
- - - - -
{:else} - +
- - - -
+ -
+
@@ -915,10 +1922,10 @@ the Free Software Foundation, version 3 only. --> - {#if status.state === "scanning"} + {#if status.state === "scanning" || status.state === "connecting"} {t("dashboard.scanning")} {:else if status.state === "connected"} ● {t("dashboard.connected")} @@ -939,14 +1946,29 @@ the Free Software Foundation, version 3 only. --> {/if} {#if status.device_name && status.state === "connected"} -

{status.device_name}

- {#if status.serial_number || status.mac_address} +
+

+ {status.device_name} + {#if sourceLabel} + {sourceLabel} + {/if} + {#if hasSecondary} + {t("dashboard.primary")} + {/if} +

+ {#if connectedDeviceId} +

{connectedDeviceId}

+ {/if} +
+ {#if status.serial_number || status.mac_address || status.firmware_version}
{#if status.serial_number} @@ -954,14 +1976,74 @@ the Free Software Foundation, version 3 only. --> {#if status.mac_address} {/if} + {#if status.firmware_version} + + fw {status.firmware_version} + + {/if}
{/if} + + + {#if status.paired_devices.length > 1} + {#if showDeviceSwitcher} +
+ {#each status.paired_devices.filter(d => d.id !== status.device_id) as dev} + + {/each} + +
+ {:else} + + {/if} + {/if} + + + {/if}
{/if} @@ -972,6 +2054,16 @@ the Free Software Foundation, version 3 only. --> + {#if status.iroh_tunnel_online && status.state !== "connected"} +
+

+ iroh online ({status.iroh_connected_peers ?? 1} peer{(status.iroh_connected_peers ?? 1) !== 1 ? "s" : ""}) + {#if status.iroh_remote_device_connected} · iOS BLE connected{:else} · waiting for iOS BLE{/if} + {#if status.iroh_eeg_streaming_active} · EEG live{:else if status.iroh_streaming_active} · link live{:else} · idle{/if} +

+
+ {/if} + {#if status.state === "bt_off"}
-

+

{t("dashboard.bluetoothIsOff")}

-

+

{t("dashboard.turnOnBluetooth")}

@@ -1014,13 +2106,13 @@ the Free Software Foundation, version 3 only. -->
- {:else if status.bt_error && status.state === "disconnected"} - {#if status.bt_error === "NO_MUSE_NEARBY"} + {:else if status.device_error && status.state === "disconnected"} + {#if status.device_error === "NO_MUSE_NEARBY"}
-

+

{t("dashboard.noMuseNearbyTitle")}

-
    +
    • • {t("dashboard.noMuseNearbyHint1")}
    • • {t("dashboard.noMuseNearbyHint2")}
    • • {t("dashboard.noMuseNearbyHint3")}
    • @@ -1030,9 +2122,25 @@ the Free Software Foundation, version 3 only. -->
+ {:else if status.device_error.includes("EEG stream not available") || status.device_error.includes("-32230")} +
+

+ EEG Access Not Available +

+

+ Your Emotiv Cortex App does not have raw EEG data access enabled for this headset. + To stream EEG, enable the Raw EEG data stream in your Cortex App settings. +

+
+ + +
+
{:else}
-
{status.bt_error}
+
{status.device_error}
@@ -1041,7 +2149,7 @@ the Free Software Foundation, version 3 only. --> {/if} - {:else if status.state === "scanning"} + {:else if status.state === "scanning" || status.state === "connecting" || status.retry_countdown_secs > 0}
{#if status.retry_countdown_secs > 0} @@ -1061,11 +2169,11 @@ the Free Software Foundation, version 3 only. --> {status.retry_countdown_secs}
-

+

{t("dashboard.retryCountdown", { secs: String(status.retry_countdown_secs) })}

{#if status.retry_attempt > 0} -

+

{t("dashboard.retryAttempt", { n: String(status.retry_attempt) })}

{/if} @@ -1076,126 +2184,302 @@ the Free Software Foundation, version 3 only. --> {:else} -

- {status.target_name ? t("dashboard.connectingTo", { name: status.target_name }) : (isGanglion ? t("dashboard.lookingForGanglion") : t("dashboard.lookingForMuse"))} -

+ {#if connectingTarget} +
+

+ {t("dashboard.connectingTo", { name: connectingTarget.name })} +

+ {#if connectingTarget.id} +

{connectingTarget.id}

+ {/if} + {#if (status.target_id?.startsWith("peer:") || status.target_name?.startsWith("peer:") || status.iroh_tunnel_online) && !status.device_name} +

+ {#if status.iroh_tunnel_online} + iroh tunnel online{#if status.iroh_client_name} ({status.iroh_client_name}){/if} + {:else} + iroh tunnel offline + {/if} + {#if status.iroh_remote_device_connected} + · iOS BLE connected + {:else} + · waiting for iOS BLE + {/if} + {#if status.iroh_eeg_streaming_active} + · EEG live + {:else if status.iroh_streaming_active} + · link live, EEG paused + {:else} + · no live stream yet + {/if} +

+ {/if} +
+ {:else} +

+ {isGanglion ? t("dashboard.lookingForGanglion") : isEmotiv ? t("dashboard.connectingEmotiv") : isMw75 ? t("dashboard.connectingTo", { name: "MW75 Neuro" }) : isHermes ? t("dashboard.connectingTo", { name: "Hermes" }) : isIdun ? t("dashboard.connectingTo", { name: "IDUN Guardian" }) : isMendi ? t("dashboard.connectingTo", { name: "Mendi" }) : isVital ? t("dashboard.connectingTo", { name: "Vital Neuro" }) : t("dashboard.lookingForMuse")} +

+ {/if} + + + + {#if status.paired_devices.length > 1 || (status.paired_devices.length > 0 && !status.target_id)} +
+

+ {t("dashboard.connectDifferent")} +

+
+ {#each status.paired_devices.filter(d => d.id !== status.target_id) as dev} + + {/each} +
+
+ {/if} {/if}
{:else if status.state === "connected"} - +
+ + {#if hasBattery}
- {t("dashboard.battery")} + {t("dashboard.battery")} - + {(status.battery ?? 0).toFixed(0)}% {#if status.temperature_raw > 0} - + 🌡 {status.temperature_raw} {/if}
{/if} - - {#if isGanglion} + + {#if chLabels.length > 0 || sourceLabel} + {@const sr = (status.eeg_sample_rate_hz ?? 0) > 0 ? Math.round(status.eeg_sample_rate_hz ?? 0) : null}
- - OpenBCI Ganglion · 4ch · 200 Hz + + {chLabels.length}ch{#if sr} · {sr} Hz{/if}{#if sourceLabel} · {sourceLabel}{/if}
{/if} - -
- - {t("dashboard.signal")} - - {#each EEG_CH as ch, i} - {@const q = status.channel_quality[i] ?? 'no_signal'} -
- - - {#if q === 'fair' || q === 'poor'} - - {/if} - + {#if hasEeg} + + {@const visibleQ = status.channel_quality.slice(0, chLabels.length)} + {@const qGood = visibleQ.filter(q => q === 'good').length} + {@const qFair = visibleQ.filter(q => q === 'fair').length} + {@const qPoor = visibleQ.filter(q => q === 'poor').length} + {@const qNone = chLabels.length - qGood - qFair - qPoor} +
+ + {#if signalExpanded} +
4 && chLabels.length <= 8} class:grid-cols-4={chLabels.length > 8}> + {#each chLabels as ch, i} + {@const q = status.channel_quality[i] ?? 'no_signal'} +
+ + + {#if q === 'fair' || q === 'poor'} + + {/if} + + + {ch} + {qualityLabel(q)} +
+ {/each} +
+ {/if} +
+ + + + + {#if showElectrodes} +
+
- {/each} -
+ {/if} - - - - {#if showElectrodes} -
- + +
+ ℹ +

+ {t("disclaimer.exgPlacement")} +

+
+ {:else} +
+ Modalities + {#if hasFnirs && fnirsLabels.length > 0} +
fNIRS: {fnirsLabels.join(" · ")}
+
+
+
Oxy
+
{(status.fnirs_oxygenation_pct ?? 0).toFixed(1)}%
+
+
+
Workload
+
{(status.fnirs_workload ?? 0).toFixed(1)}
+
+
+
Lat
+
{(status.fnirs_lateralization ?? 0).toFixed(1)}
+
+
+
ΔHbO
+
{((((status.fnirs_hbo_left ?? 0) + (status.fnirs_hbo_right ?? 0)) / 2)).toFixed(3)}
+
+
+
ΔHbR
+
{((((status.fnirs_hbr_left ?? 0) + (status.fnirs_hbr_right ?? 0)) / 2)).toFixed(3)}
+
+
+
Conn
+
{(status.fnirs_connectivity ?? 0).toFixed(3)}
+
+
+ {/if} + {#if ppgLabels.length > 0} +
PPG: {ppgLabels.join(" · ")}
+ {/if} + {#if imuLabels.length > 0} +
IMU: {imuLabels.join(" · ")}
+ {/if}
+ {#if hasFnirs && liveView === "physiology"} +
+ +
+ {/if} {/if} - -
- ℹ -

- {t("disclaimer.exgPlacement")} -

-
- - - - - - + + {#if availableLiveViews.length > 1} +
+ {#each availableLiveViews as id (id)} + + {/each} +
+ {/if} - - + {#if hasEeg && liveView === "waveform"} +
+ +
+ {:else if hasEeg && liveView === "state"} + + + + + + + + + + +
+ +
- - + + - - + + - - + + + {/if} - + {#if liveView === "physiology"} + + {#if hasImuCap} + {/if} + + + {#if hasVitals} + + {/if} {#if hasPpg && (hrScore > 0 || status.ppg_sample_count > 0)} @@ -1204,10 +2488,10 @@ the Free Software Foundation, version 3 only. --> perfIdx={perfIdxScore} stressIdx={stressIdxScore} /> {/if} - + {#if hasPpg && (status.ppg_sample_count > 0 || status.state === "connected")}
+ bg-muted dark:bg-surface-2 px-3 py-2.5 flex flex-col gap-1.5"> @@ -1235,10 +2519,11 @@ the Free Software Foundation, version 3 only. -->
{/if} - - {@const hasImu = status.accel.some(v => v !== 0) || status.gyro.some(v => v !== 0)} + + {#if hasImuCap} + {@const hasImuData = status.accel.some(v => v !== 0) || status.gyro.some(v => v !== 0)}
+ bg-muted dark:bg-surface-2 px-3 py-2.5 flex flex-col gap-1.5"> {#if imuExpanded} {/if}
+ {/if} + {/if} - -
- - {#if eegChExpanded} -
- {#each EEG_CH as ch, i} -
- {ch} - {fmtEeg(status.eeg[i])} -
- {/each} -
- {/if} -
+ {#if hasEeg && liveView === "waveform"} + +
+ + {#if eegChExpanded} +
4 && chLabels.length <= 8} class:grid-cols-4={chLabels.length > 8}> + {#each chLabels as ch, i} +
+ {ch} + {fmtEeg(status.eeg[i])} +
+ {/each} +
+ {/if} +
+ {/if}
- + bg-muted dark:bg-surface-2 px-3 py-2 flex items-center gap-2.5"> + {t("dashboard.dailyGoal")}
- {Math.floor(todayTotalSecs / 60)}m / {dailyGoalMin}m {#if goalReached} - 🎯 + 🎯 {/if}
@@ -1322,9 +2611,9 @@ the Free Software Foundation, version 3 only. --> [t("dashboard.todayTotal"), fmtUptime(todayTotalSecs)], ] as [label, val]}
- {label} - {val} + bg-muted dark:bg-surface-2 px-2 py-1.5 flex flex-col gap-0.5"> + {label} + {val}
{/each}
@@ -1333,9 +2622,9 @@ the Free Software Foundation, version 3 only. --> {#if status.csv_path}
- {t("dashboard.rec")} - + bg-muted dark:bg-surface-3 px-3 py-2"> + {t("dashboard.rec")} + {csvName(status.csv_path)}
@@ -1343,14 +2632,14 @@ the Free Software Foundation, version 3 only. --> {#if recentLabel}
+ rounded-md border border-primary/25 bg-violet-500/8 dark:bg-violet-500/10"> + class="w-2.5 h-2.5 shrink-0 text-violet-600 dark:text-violet-400"> - + {recentLabel} +
+ { onboardDone = { devicePaired: true, calibrated: true, firstSession: true, goalSet: true, llmDownloaded: true, searchRun: true, dndConfigured: true, apiVisited: true }; saveOnboarding(); }} + />
{/if} +
+ {:else} {#if status.paired_devices.length > 0} -

{t("dashboard.pairedDevices")}

+ {t("dashboard.pairedDevices")}
- {#each status.paired_devices as dev} + {#each status.paired_devices as dev (dev.id)}
+ bg-muted dark:bg-surface-2 px-3 py-2">
- {dev.name} - {dev.id} + {dev.name} + {dev.id}
+ @@ -1452,74 +2716,214 @@ the Free Software Foundation, version 3 only. -->
{/each}
- + {#if dashboardUnpaired.length > 0} +
+ {t("devices.discoveredDevices")} + {#each dashboardUnpaired.slice(0, 4) as dev (dev.id)} +
+
+ {dev.name} + {dev.id} +
+ +
+ {/each} +
+ {/if} +
+ + +
{:else}
-

+

{t("dashboard.noDevicesPaired")}

- + + {#if dashboardUnpaired.length > 0} +
+

+ {t("devices.discoveredDevices")} +

+ {#each dashboardUnpaired.slice(0, 5) as dev (dev.id)} +
+
+ {dev.name} + {dev.id} +
+ +
+ {/each} +
+ {/if} + +
+ + +
{/if} {/if} - + + {#if status.state === "connected" && hasEeg && liveView === "waveform"}
-

- {t("dashboard.bandPowers")} -

- {#if status.state === "connected"} - ● - {/if} + {t("dashboard.bandPowers")} + ● +
+
+
-
-
-

{t("dashboard.eegWaveforms")}

- {#if status.state === "connected"} - ● - {/if} + {t("dashboard.eegWaveforms")} + ●
- +
+ {/if} + + {#if hasSecondary} +
+
+ + {t("dashboard.backgroundRecordings")} + +
+ {#each secondarySessions as sess (sess.id)} +
+ + + + + + + +
+ + {sess.device_name} + + + {sess.device_kind === "lsl" ? "LSL" : sess.device_kind === "lsl-iroh" ? "iroh" : sess.device_kind.toUpperCase()} + +
+ + + + {sess.channels}ch · {sess.sample_rate % 1 === 0 ? sess.sample_rate : sess.sample_rate.toFixed(1)} Hz + + + {sess.sample_count.toLocaleString()} + + + + +
+ {/each} +
+ {/if} + -

+

{#if status.state === "connected"} {t("dashboard.streamingCsv")} {:else if status.state === "scanning"} - {t("dashboard.cancelViaTray")} + {t("dashboard.scanningFooter")} {/if}

- {#if status.state === "connected"} - - {/if} - v{appVersion} + + e.key === 'Enter' && toggleEnginePopover()} role="button" tabindex="0"> + + {#if daemonStatus.state === "error" || daemonStatus.state === "disconnected"} + {#if daemonLaunching} + {t("daemon.starting")} + {:else if daemonCountdown > 0} + + {:else} + + {/if} + {:else} + + {engineState === "streaming" ? t("daemon.streaming") : engineState === "idle" ? t("daemon.idle") : t("daemon.stateConnecting")} + {#if daemonStatus.latencyMs != null} + {daemonStatus.latencyMs}ms + {/if} + + {/if} + + + {#if enginePopoverOpen} +
+
{t("daemon.popoverTitle")}
+
+ {t("daemon.uptime")} + {engineUptime()} + {#if daemonStatus.version} + {t("daemon.version")} + {daemonStatus.version} + {/if} + {t("daemon.latency")} + {daemonStatus.latencyMs != null ? `${daemonStatus.latencyMs}ms` : "—"} + {t("daemon.wsClients")} + {engineWsClients} +
+ +
+ {/if} +
+ v{appVersion}
+
+ +{@render children()} diff --git a/src/routes/chat/+page.svelte b/src/routes/chat/+page.svelte new file mode 100644 index 000000000..3bca38335 --- /dev/null +++ b/src/routes/chat/+page.svelte @@ -0,0 +1,1708 @@ + + + + + + +
+ + + {#if sidebarOpen} + + {/if} + + +
+ + sidebarOpen = !sidebarOpen} + onToggleSettings={() => { showSettings = !showSettings; if (showSettings) showTools = false; }} + onToggleTools={() => { showTools = !showTools; if (showTools) showSettings = false; }} + onStartServer={startServer} + onStopServer={stopServer} + onNewChat={newChat} + onToggleEeg={() => eegContext = !eegContext} + onToggleContextBreakdown={() => showContextBreakdown = !showContextBreakdown} + /> + + {#if startError} +
+ + + + {startError} + +
+ {/if} + + {#if showContextBreakdown && nCtx > 0} + 0} + onClose={() => showContextBreakdown = false} + onViewFull={() => { showContextBreakdown = false; showContextViewer = true; }} + /> + {/if} + + {#if showContextViewer} + showContextViewer = false} + /> + {/if} + + +
+ {#if showSettings} +
+ +
+ {/if} + + {#if showTools} +
+ +
+ {/if} + + + + {#if showVoice} +
+ asrError = ""} + /> +
+ {/if} + + +
+ +
+
diff --git a/src/routes/compare/+page.svelte b/src/routes/compare/+page.svelte index 20e5084d1..e719cbfdf 100644 --- a/src/routes/compare/+page.svelte +++ b/src/routes/compare/+page.svelte @@ -7,1596 +7,992 @@ the Free Software Foundation, version 3 only. --> -
- - -
- - - - - - {t("compare.title")} - - - - -
+
-
+
{#if loading}
-

{t("common.loading")}

+

{t("common.loading")}

{:else if sessions.length === 0} @@ -1607,8 +1003,8 @@ the Free Software Foundation, version 3 only. --> -

{t("compare.needSessions")}

-

+

{t("compare.needSessions")}

+

{t("compare.needSessionsHint")}

@@ -1704,7 +1122,7 @@ the Free Software Foundation, version 3 only. --> {/each} @@ -1807,9 +1225,9 @@ the Free Software Foundation, version 3 only. -->
{#if rw > 8} - {utcToTimeStr(rangeStart)} - {utcToTimeStr(rangeEnd)} {/if}
@@ -1820,25 +1238,27 @@ the Free Software Foundation, version 3 only. --> {#if anchorUtc !== null}
- {t("compare.timeFrom")} + {t("compare.timeFrom")} setRangeStart(side, (e.target as HTMLInputElement).value)} class="flex-1 min-w-0 rounded border border-border dark:border-white/[0.08] - bg-background dark:bg-[#14141e] px-1.5 py-0.5 - text-[0.6rem] text-foreground focus:outline-none focus:ring-1" + bg-background dark:bg-surface-1 px-1.5 py-0.5 + text-ui-sm text-foreground focus:outline-none focus:ring-1" style="--tw-ring-color:{accent}50"/> - – + – setRangeEnd(side, (e.target as HTMLInputElement).value)} class="flex-1 min-w-0 rounded border border-border dark:border-white/[0.08] - bg-background dark:bg-[#14141e] px-1.5 py-0.5 - text-[0.6rem] text-foreground focus:outline-none focus:ring-1" + bg-background dark:bg-surface-1 px-1.5 py-0.5 + text-ui-sm text-foreground focus:outline-none focus:ring-1" style="--tw-ring-color:{accent}50"/>
{/if} @@ -1849,7 +1269,7 @@ the Free Software Foundation, version 3 only. --> {#if aDayStr !== null && bDayStr !== null && aDayStr === bDayStr}
+ px-3 py-2 text-ui-sm text-amber-600 dark:text-amber-400"> @@ -1868,7 +1288,7 @@ the Free Software Foundation, version 3 only. -->
{#if metricsA && metricsB} - + {t("compare.epochsA", { n: metricsA.n_epochs })} · {t("compare.epochsB", { n: metricsB.n_epochs })} {/if} @@ -1913,12 +1331,22 @@ the Free Software Foundation, version 3 only. -->
+ {#if comparing && !metricsA && !metricsB} + + +
+ + + +

{t("compare.comparing")}

+
+ {/if} {#if metricsA && metricsB}
- + {t("compare.spectrum")} @@ -1927,7 +1355,7 @@ the Free Software Foundation, version 3 only. --> {#each bandMeta as band, i}
- {band.sym} {band.name} + {band.sym} {band.name}
{/each}
@@ -1935,37 +1363,37 @@ the Free Software Foundation, version 3 only. -->
- A + A
{#if metricsA.n_epochs === 0} - {t("compare.noEpochs")} + {t("compare.noEpochs")} {/if}
- B + B
{#if metricsB.n_epochs === 0} - {t("compare.noEpochs")} + {t("compare.noEpochs")} {/if}
- + {t("compare.bandPowers")}
+ bg-surface-1 overflow-hidden p-2"> @@ -1973,30 +1401,30 @@ the Free Software Foundation, version 3 only. -->
+ bg-surface-1 overflow-hidden">
- {t("compare.band")} - A - B - {t("compare.diff")} + {t("compare.band")} + A + B + {t("compare.diff")}
{#each bandKeys as key, i} {@const a = bv(metricsA, key)} {@const b = bv(metricsB, key)}
- {bandMeta[i].name} - {bandMeta[i].sym} + {bandMeta[i].name} + {bandMeta[i].sym}
- {pct(a)}% - {pct(b)}% - {diff(a, b)} + {pct(a)}% + {pct(b)}% + {diff(a, b)}
{/each}
@@ -2004,38 +1432,38 @@ the Free Software Foundation, version 3 only. -->
- + {t("compare.scores")}
- {#each scoreKeys as sk} + {#each scoreKeys as sk (sk)} {@const a = metricsA[sk.key]} {@const b = metricsB[sk.key]}
- + bg-surface-1 px-3 py-2.5 flex flex-col gap-2"> + {t(sk.label)}
- A + A
- {a.toFixed(0)} + {a.toFixed(0)}
- B + B
- {b.toFixed(0)} + {b.toFixed(0)}
- + {scoreDiff(a, b)}
@@ -2045,15 +1473,15 @@ the Free Software Foundation, version 3 only. -->
- + {t("compare.radarChart")}
+ bg-surface-1 overflow-hidden p-2"> -
+
A @@ -2069,20 +1497,20 @@ the Free Software Foundation, version 3 only. --> {#if improved.length > 0 || declined.length > 0}
- + {t("compare.insights")}
+ bg-surface-1 px-3.5 py-3 flex flex-col gap-2.5"> {#if improved.length > 0}
- ▲ Improved + ▲ Improved {#each improved as d} + text-ui-xs font-medium"> {d.label} - + {d.pctChange > 0 ? "+" : ""}{d.pctChange.toFixed(0)}% @@ -2091,20 +1519,20 @@ the Free Software Foundation, version 3 only. --> {/if} {#if declined.length > 0}
- ▼ Declined + ▼ Declined {#each declined as d} + text-ui-xs font-medium"> {d.label} - + {d.pctChange > 0 ? "+" : ""}{d.pctChange.toFixed(0)}% {/each}
{/if} -

+

Comparing session B vs A. Changes >3% shown.

@@ -2113,11 +1541,11 @@ the Free Software Foundation, version 3 only. -->
- + {t("dashboard.faa")}
+ bg-surface-1 px-3.5 py-3 flex flex-col gap-3"> {#each [ { label: "A", val: metricsA.faa }, @@ -2125,21 +1553,21 @@ the Free Software Foundation, version 3 only. --> ] as item}
- {item.label} + {item.label}
{#if item.val >= 0} -
{:else} -
{/if}
- + {item.val >= 0 ? "+" : ""}{item.val.toFixed(3)}
@@ -2147,12 +1575,12 @@ the Free Software Foundation, version 3 only. --> {/each}
-
+
{t("dashboard.faaWithdrawal")} {t("dashboard.faaFormula")} {t("dashboard.faaApproach")}
- + {(metricsA.faa - metricsB.faa) >= 0 ? "+" : ""}{(metricsA.faa - metricsB.faa).toFixed(3)}
@@ -2169,36 +1597,36 @@ the Free Software Foundation, version 3 only. --> transition-transform duration-150 shrink-0 {advExpanded ? 'rotate-90' : ''}"> - {t("compare.advancedMetrics")} {#if advExpanded}
+ bg-surface-1 overflow-hidden">
- {t("compare.metric")} - A - B - {t("compare.diff")} + {t("compare.metric")} + A + B + {t("compare.diff")}
{#each advancedMetrics as mr} {@const a = Number(metricsA[mr.key]) || 0} {@const b = Number(metricsB[mr.key]) || 0} {@const d = a - b}
- - {t(mr.label)}{#if mr.unit} ({mr.unit}){/if} + + {t(mr.label)}{#if mr.unit} ({mr.unit}){/if} - {mr.fmt(a)} - {mr.fmt(b)} - + {mr.fmt(a)} + {mr.fmt(b)} + {Math.abs(d) < 0.001 ? "—" : `${d > 0 ? "+" : ""}${mr.fmt(d)}`}
@@ -2211,10 +1639,10 @@ the Free Software Foundation, version 3 only. --> {#if tsA.length > 2 || tsB.length > 2}
- + {t("compare.heatmap")} - @@ -2225,16 +1653,16 @@ the Free Software Foundation, version 3 only. --> {#if tsA.length > 2}
- A - {t("compare.heatmapBands")} + A + {t("compare.heatmapBands")}
+ bg-surface-3 overflow-hidden">
- + {t("compare.heatmapRowNorm")} · {tsA.length} epochs
@@ -2244,16 +1672,16 @@ the Free Software Foundation, version 3 only. --> {#if tsB.length > 2}
- B - {t("compare.heatmapBands")} + B + {t("compare.heatmapBands")}
+ bg-surface-3 overflow-hidden">
- + {t("compare.heatmapRowNorm")} · {tsB.length} epochs
@@ -2262,14 +1690,14 @@ the Free Software Foundation, version 3 only. --> {#if tsA.length > 2 && tsB.length > 2}
- {t("compare.heatmapDiff")} + {t("compare.heatmapDiff")}
+ bg-surface-3 overflow-hidden">
- + {t("compare.heatmapDiffLegend")} · time-proportionally aligned
@@ -2279,11 +1707,11 @@ the Free Software Foundation, version 3 only. --> {#if tsA.length > 2}
- A - {t("compare.heatmapScores")} + A + {t("compare.heatmapScores")}
+ bg-surface-3 overflow-hidden"> @@ -2295,16 +1723,16 @@ the Free Software Foundation, version 3 only. --> {#if tsB.length > 2}
- B - {t("compare.heatmapScores")} + B + {t("compare.heatmapScores")}
+ bg-surface-3 overflow-hidden">
- + {t("compare.heatmapRowNorm")}
@@ -2317,10 +1745,10 @@ the Free Software Foundation, version 3 only. --> {#if tsA.length > 2 || tsB.length > 2}
- + {t("compare.timeSeries")} - @@ -2348,7 +1776,7 @@ the Free Software Foundation, version 3 only. --> {#if tsA.length > 2} - A — {t("compare.chartBands")} + A — {t("compare.chartBands")} ]} /> {/if} {#if tsB.length > 2} - B — {t("compare.chartBands")} + B — {t("compare.chartBands")} {#if tsA.length > 2} - A — {t("compare.chartScores")} + A — {t("compare.chartScores")} ]} /> {/if} {#if tsB.length > 2} - B — {t("compare.chartScores")} + B — {t("compare.chartScores")} {#if tsA.length > 2} - A — {t("compare.chartComposite")} + A — {t("compare.chartComposite")} ]} /> {/if} {#if tsB.length > 2} - B — {t("compare.chartComposite")} + B — {t("compare.chartComposite")} {#if tsA.some(r => r.hr > 0)} - A — {t("compare.chartPpg")} + A — {t("compare.chartPpg")} ]} /> {/if} {#if tsB.some(r => r.hr > 0)} - B — {t("compare.chartPpg")} + B — {t("compare.chartPpg")} {#if tsA.some(r => r.blink_r > 0)} - A — {t("compare.chartArtifacts")} + A — {t("compare.chartArtifacts")} ]} /> {/if} {#if tsB.some(r => r.blink_r > 0)} - B — {t("compare.chartArtifacts")} + B — {t("compare.chartArtifacts")} {#if tsA.some(r => r.still > 0)} - A — {t("compare.chartPose")} + A — {t("compare.chartPose")} ]} /> {/if} {#if tsB.some(r => r.still > 0)} - B — {t("compare.chartPose")} + B — {t("compare.chartPose")} {#if !umapRequested}
- + Brain Nebula™ - {t("compare.umap")} + {t("compare.umap")}
+ bg-surface-1 px-4 py-5 flex flex-col items-center gap-3"> @@ -2503,14 +1931,55 @@ the Free Software Foundation, version 3 only. --> -

+

{t("compare.umapDesc")}

+ {#if umapTotalEpochs > 0 && umapTotalEmb < 5} +

+ {t("compare.embeddingsNone")} +

+ {#if reembedRunning} + {@const eta = reembedEta()} +
+
+
+
+

+ {t("compare.embeddingsProcessing")} + {reembedStatus?.done ?? 0}/{reembedStatus?.total ?? 0} ({reembedPct}%) + {#if reembedStatus?.day} · {reembedStatus.day}{/if} + {#if eta} +
{eta.rate} embeddings/sec · ~{fmtDuration(eta.etaSecs)} remaining + {/if} +

+
+ {/if} + {:else if umapTotalEpochs > 0 && umapTotalEmb < umapTotalEpochs * 0.5} +

+ {umapTotalEmb}/{umapTotalEpochs} {t("compare.embeddingsReady")} ({Math.round(umapTotalEmb / umapTotalEpochs * 100)}%) +

+ {#if reembedRunning} + {@const eta = reembedEta()} +
+
+
+
+ {#if eta} + + {eta.rate}/sec · ~{fmtDuration(eta.etaSecs)} left + + {/if} +
+ {/if} + {/if}
{:else} @@ -2518,8 +1987,8 @@ the Free Software Foundation, version 3 only. --> style="min-height:calc(100vh - 8rem)">
- Brain Nebula™ - {t("compare.umap")} + Brain Nebula™ + {t("compare.umap")} {#if umapLoading} {#if umapQueuePosition !== null && umapQueuePosition > 0} @@ -2540,11 +2009,11 @@ the Free Software Foundation, version 3 only. -->
- + {umapQueuePosition} task{umapQueuePosition === 1 ? "" : "s"} running before yours - + {#if umapWaitSecs !== null && umapWaitSecs > 0} starts in ~{fmtSecs(umapWaitSecs)} {:else} @@ -2555,7 +2024,7 @@ the Free Software Foundation, version 3 only. -->
- + {fmtSecs(umapElapsed)} elapsed
@@ -2563,14 +2032,11 @@ the Free Software Foundation, version 3 only. --> {:else} - - computing 3D projection - {#if umapCountdown != null && umapCountdown > 0 && !umapProgress} - · ~{fmtSecs(umapCountdown)} remaining - {/if} - · {fmtSecs(umapElapsed)} elapsed + + computing 3D projection · {fmtSecs(umapElapsed)} elapsed {#if umapProgress && umapProgress.total_epochs > 0} + {@const pct = Math.round(umapProgress.epoch / umapProgress.total_epochs * 100)} {@const remEpochs = umapProgress.total_epochs - umapProgress.epoch} {@const remSecs = umapProgress.epoch_ms > 0 @@ -2581,30 +2047,45 @@ the Free Software Foundation, version 3 only. -->
- + {pct}%
{#if remSecs !== null} - + epoch {umapProgress.epoch}/{umapProgress.total_epochs} · {umapProgress.epoch_ms.toFixed(0)}ms/ep · ~{fmtSecs(remSecs)} left {/if}
+ {:else} + + {@const estSecs = umapOwnEstimateSecs > 0 ? umapOwnEstimateSecs : 15} + {@const estPct = Math.min(95, Math.round(umapElapsed / estSecs * 100))} +
+
+
+
+
+ + ~{fmtSecs(Math.max(0, estSecs - umapElapsed))} + +
+
{/if} {/if} {:else if umapResult} - + {umapResult.n_a} + {umapResult.n_b} {t("compare.umapPoints")} · dim={umapResult.dim} · 3D {#if umapComputeMs != null} · {umapComputeMs < 1000 ? `${umapComputeMs}ms` : `${(umapComputeMs / 1000).toFixed(1)}s`} compute {/if} {#if umapAnalysis} - = 2 ? 'bg-emerald-500/10 text-emerald-500' : umapAnalysis.separationScore >= 1 ? 'bg-yellow-500/10 text-yellow-600 dark:text-yellow-400' : 'bg-red-500/10 text-red-400'}"> @@ -2615,18 +2096,49 @@ the Free Software Foundation, version 3 only. -->
-
- -
+ {#if umapError && !umapLoading} +
+ + + + +

{umapError}

+ {#if reembedRunning} + {@const eta = reembedEta()} +
+
+
+
+

+ {t("compare.embeddingsProcessing")} + {reembedStatus?.done ?? 0}/{reembedStatus?.total ?? 0} ({reembedPct}%) + {#if eta} +
{eta.rate} embeddings/sec · ~{fmtDuration(eta.etaSecs)} remaining + {/if} +

+
+ {:else} + + {/if} +
+ {:else} +
+ +
+ {/if} -
+
- + A ({(umapResult ?? umapPlaceholder)?.n_a ?? 0})
- + B ({(umapResult ?? umapPlaceholder)?.n_b ?? 0})
{t("compare.umapDesc")} @@ -2651,7 +2163,7 @@ the Free Software Foundation, version 3 only. --> transition-transform duration-150 shrink-0 {sleepExpanded ? 'rotate-90' : ''}"> - {t("sleep.title")} @@ -2667,28 +2179,28 @@ the Free Software Foundation, version 3 only. --> ] as item} {#if item.sa}
- {item.label} + bg-surface-1 px-3 py-2 flex flex-col gap-1"> + {item.label}
- Efficiency - + Efficiency + {item.sa.efficiency.toFixed(0)}%
- Onset - {item.sa.onsetLatencyMin.toFixed(0)}m + Onset + {item.sa.onsetLatencyMin.toFixed(0)}m
{#if item.sa.remLatencyMin >= 0}
- → REM - {item.sa.remLatencyMin.toFixed(0)}m + → REM + {item.sa.remLatencyMin.toFixed(0)}m
{/if}
- Awakenings - {item.sa.awakenings} + Awakenings + {item.sa.awakenings}
@@ -2699,9 +2211,9 @@ the Free Software Foundation, version 3 only. --> {#if sleepA}
- A + A
+ bg-surface-1 p-2">
@@ -2710,9 +2222,9 @@ the Free Software Foundation, version 3 only. --> {#if sleepB}
- B + B
+ bg-surface-1 p-2">
@@ -2729,29 +2241,29 @@ the Free Software Foundation, version 3 only. --> { key: "sleep.n3", a: sleepA.summary.n3_epochs, b: sleepB.summary.n3_epochs, color: "#6366f1" }, ]}
+ bg-surface-1 overflow-hidden">
- {t("sleep.title")} - A - B - {t("compare.diff")} + {t("sleep.title")} + A + B + {t("compare.diff")}
{#each stages as st} {@const aPct = sleepA.summary.total_epochs > 0 ? (st.a / sleepA.summary.total_epochs * 100) : 0} {@const bPct = sleepB.summary.total_epochs > 0 ? (st.b / sleepB.summary.total_epochs * 100) : 0} {@const d = aPct - bPct}
- {t(st.key)} + {t(st.key)}
- {aPct.toFixed(0)}% - {bPct.toFixed(0)}% - + {aPct.toFixed(0)}% + {bPct.toFixed(0)}% + {Math.abs(d) < 0.5 ? "—" : `${d > 0 ? "+" : ""}${d.toFixed(0)}`}
diff --git a/src/routes/downloads/+page.svelte b/src/routes/downloads/+page.svelte new file mode 100644 index 000000000..d8ca6e4b0 --- /dev/null +++ b/src/routes/downloads/+page.svelte @@ -0,0 +1,160 @@ + + + + +
+
+
+ + Total download size · {items.length} {items.length === 1 ? "item" : "items"} + + + {loading ? t("downloads.loading") : fmtSize(totalDownloadsSizeGb)} + +
+
+ +
+ {#if loading} +

{t("downloads.loading")}

+ {:else if items.length === 0} +

{t("downloads.empty")}

+ {:else} +
+ {#each items as item (item.filename)} +
+
+
+

{item.filename}

+

{item.description}

+

+ {item.quant} · {fmtSize(item.size_gb)}{item.shard_count > 1 ? ` (${item.shard_count} parts)` : ""} · {statusLabel(item.state)}{item.shard_count > 1 && item.current_shard > 0 ? ` — part ${item.current_shard}/${item.shard_count}` : ""} +

+

{t("downloads.initiatedAt")}: {fmtInitiated(item.initiated_at_unix)}

+ {#if item.status_msg} +

{item.status_msg}

+ {/if} +
+ +
+ {#if item.state === "downloading"} + + + {:else if item.state === "paused"} + + + {:else} + + {/if} +
+
+ + {#if item.state === "downloading" || item.state === "paused"} +
+
+
+ {/if} +
+ {/each} +
+ {/if} +
+ +
diff --git a/src/routes/focus-timer/+page.svelte b/src/routes/focus-timer/+page.svelte index a14c0ec8b..3a9f0549c 100644 --- a/src/routes/focus-timer/+page.svelte +++ b/src/routes/focus-timer/+page.svelte @@ -8,297 +8,380 @@ the Free Software Foundation, version 3 only. --> Configurable work/break timer with automatic EEG labelling. --> -
- - -
- ⏱ - - {t("focusTimer.title")} - - {#if sessionsDone > 0} - - {t("focusTimer.sessionCount", { n: sessionsDone })} - - {/if} - - - -
+
-
+
-
{phaseLabel}
@@ -365,21 +448,21 @@ the Free Software Foundation, version 3 only. -->
{#if phase === "idle"} - {:else} - - {/if} @@ -390,7 +473,7 @@ the Free Software Foundation, version 3 only. --> onclick={openLabel} class="flex items-center gap-1.5 rounded-lg border border-border dark:border-white/[0.08] bg-muted/20 hover:bg-muted/40 - px-3.5 py-1.5 text-[0.72rem] font-medium text-muted-foreground + px-3.5 py-1.5 text-ui-md font-medium text-muted-foreground hover:text-foreground transition-colors" >
-

+

Presets

-
- {#each (["pomodoro", "deepWork", "shortFocus"] as const) as p} - - {/each} -
+ p === selectedPreset) ?? "pomodoro"} + onselect={(p) => applyPreset(p)} + labelFn={(p) => t(`focusTimer.preset.${p}`)} + class={phase !== "idle" ? "opacity-40 pointer-events-none" : ""} + />
-

+

{t("focusTimer.preset.custom")}

- + selectedPreset = "custom"} - class="w-full px-2 py-1 text-[0.78rem] rounded-md + class="w-full px-2 py-1 text-ui-lg rounded-md border border-border dark:border-white/[0.08] bg-background text-foreground - focus:outline-none focus:ring-1 focus:ring-blue-500/40 + focus:outline-none focus:ring-1 focus:ring-ring/40 disabled:opacity-40" />
- + selectedPreset = "custom"} - class="w-full px-2 py-1 text-[0.78rem] rounded-md + class="w-full px-2 py-1 text-ui-lg rounded-md border border-border dark:border-white/[0.08] bg-background text-foreground - focus:outline-none focus:ring-1 focus:ring-blue-500/40 + focus:outline-none focus:ring-1 focus:ring-ring/40 disabled:opacity-40" />
- + selectedPreset = "custom"} - class="w-full px-2 py-1 text-[0.78rem] rounded-md + class="w-full px-2 py-1 text-ui-lg rounded-md border border-border dark:border-white/[0.08] bg-background text-foreground - focus:outline-none focus:ring-1 focus:ring-blue-500/40 + focus:outline-none focus:ring-1 focus:ring-ring/40 disabled:opacity-40" />
-
@@ -498,18 +572,18 @@ the Free Software Foundation, version 3 only. -->
+ dark:border-white/[0.06] bg-muted/20 px-3 py-2.5">
-
@@ -517,18 +591,18 @@ the Free Software Foundation, version 3 only. -->
+ dark:border-white/[0.06] bg-muted/20 px-3 py-2.5">
-
@@ -538,12 +612,154 @@ the Free Software Foundation, version 3 only. --> {#if sessionsDone > 0 && phase === "idle"} {/if} + +
+ + + + + {#if logOpen} + + {#if cyclesDone > 0 || sessionLog.length > 0} +
+ +
+ + {t("focusTimer.log.focusTime")} + + + {fmtDuration(focusSecs)} + +
+ +
+ + {t("focusTimer.log.breakTime")} + + + {fmtDuration(breakSecs)} + +
+ +
+ + {t("focusTimer.log.totalTime")} + + + {fmtDuration(logTotalSecs)} + +
+
+ {/if} + + +
+ {#if sessionLog.length === 0} +

+ {t("focusTimer.log.empty")} +

+ {:else} + + {#each [...sessionLog].reverse() as entry, ri} + {@const isWork = entry.type === "work"} + {@const isLongBreak = entry.type === "longBreak"} +
+ + + + + + {isWork + ? t("focusTimer.log.work") + : isLongBreak + ? t("focusTimer.log.longBreak") + : t("focusTimer.log.break")} + + + + {fmtDuration(entry.durationSecs)} + + + + {fmtTime(entry.startUtc)} → {fmtTime(entry.completedAt)} + +
+ {/each} + {/if} +
+ + + {#if sessionLog.length > 0} +
+ +
+ {/if} + {/if} +
+
diff --git a/src/routes/help/+page.svelte b/src/routes/help/+page.svelte index 9c7b28b01..ff892eefe 100644 --- a/src/routes/help/+page.svelte +++ b/src/routes/help/+page.svelte @@ -4,399 +4,431 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. --> - + -
- - -
- - - - - {#if searchQuery} - - {/if} -
+ + +
- -
-
+ +
+ + + + + + + +
+ + {#if searchQuery.trim()} + + {#if searchResults.length === 0} +
+ + + +

+ {t("help.searchNoResults").replace("{query}", searchQuery.trim())} +

+
+ {:else} +
+

+ {searchResults.length} {searchResults.length === 1 ? "result" : "results"} +

+ {#each searchResults as item} + {@const tLabel = helpTabLabel(item.tab)} + + {/each} +
+ {/if} + {:else} + + {#if tab === "dashboard"} + + {:else if tab === "electrodes"} + + {:else if tab === "settings"} + + {:else if tab === "windows"} + + {:else if tab === "api"} + + {:else if tab === "tts"} + + {:else if tab === "llm"} + + {:else if tab === "hooks"} + + {:else if tab === "privacy"} + + {:else if tab === "references"} + + {:else} + + {/if} + {/if} + +
-
- - - - v{appVersion} - - - {t("settings.license")} - -
-
- - {#if searchQuery.trim()} - - {#if searchResults.length === 0} -
- - - -

- {t("help.searchNoResults").replace("{query}", searchQuery.trim())} -

-
- {:else} -
-

- {searchResults.length} {searchResults.length === 1 ? "result" : "results"} -

- {#each searchResults as item} - {@const tabLabel = helpTabLabel(item.tab)} - {@const title = t(item.titleKey as Parameters[0])} - {@const body = t(item.bodyKey as Parameters[0])} - - {/each} -
- {/if} - {:else} - - {#if tab === "dashboard"} - - {:else if tab === "electrodes"} - - {:else if tab === "settings"} - - {:else if tab === "windows"} - - {:else if tab === "api"} - - {:else if tab === "tts"} - - {:else if tab === "privacy"} - - {:else if tab === "references"} - - {:else} - - {/if} - {/if} - - +
diff --git a/src/routes/search/+page.svelte b/src/routes/search/+page.svelte index 345ec3bae..e50f3d916 100644 --- a/src/routes/search/+page.svelte +++ b/src/routes/search/+page.svelte @@ -5,721 +5,1476 @@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 only. --> -
- - -
- - - - - - {t("search.title")} - - -
- -
- -
- -
+
- -
+ +
+
{#if mode === "eeg" && result} - + {t("search.resultSummary", { queries: result.query_count, k: result.k, days: result.searched_days.length })} {:else if mode === "text" && textSearched} - + {textFiltered.length} / {textResults.length} {t("search.textResultsCount")} {:else if mode === "interactive" && ixSearched} - + {t("search.interactiveNodeCount", { n: ixNodes.length, e: ixEdges.length })} + {:else if mode === "images" && imgSearched} + + {imgResults.length} {t("search.imageResultsCount")} + {/if}
- - - {#if mode === "eeg" && searching && streamTotal > 0}
@@ -732,17 +1487,17 @@ the Free Software Foundation, version 3 only. -->
+ bg-muted/20 dark:bg-white/[0.02]"> {#if mode === "eeg"}
- + {t("search.last")} - {#each PRESETS as [label, mins]} + {#each PRESETS as [label, mins] (mins)}
@@ -765,35 +1522,52 @@ the Free Software Foundation, version 3 only. -->
- k + k
- ef + ef
+ {#if deviceList.length > 0} + + {/if} {#if error} - {error} + {error} {:else} {/if} {#if searching} - {/if} - +
+ + + + + {#if ixShowAdvanced} {#each [ { n: 2, color: "#3b82f6", title: "Text similarity", @@ -866,64 +1684,221 @@ the Free Software Foundation, version 3 only. --> min: 1, max: 60, val: ixReachMinutes, set: (v: number) => { ixReachMinutes = v; } }, ] as step}
{step.n} - {step.title} - {step.hint} + {step.title} + {step.hint}
step.set(Number((e.target as HTMLInputElement).value))} - class="w-9 rounded border border-border dark:border-white/[0.1] - bg-background px-0.5 py-0.5 text-[0.68rem] text-center font-mono + class="w-11 rounded border border-border dark:border-white/[0.1] + bg-background px-0.5 py-0.5 text-ui-base text-center font-mono focus:outline-none focus:ring-1 focus:ring-ring" />
{/each} - -
- {#if error} - {error} - {:else} - - {t("search.interactiveCmdEnter")} - - {/if} - + {/each} + {#if ixFilterStartUtc || ixFilterEndUtc} + {/if} - +
+ + +
+ 9 + {t("search.rankByLabel")} + {t("search.rankByHint")} + +
+ + + {#if ixPerf} +
+ {t("search.perfEmbed")} {ixPerf.embed_ms}ms + {t("search.perfGraph")} {ixPerf.graph_ms}ms + {t("search.perfTotal")} {ixPerf.total_ms}ms + {ixPerf.node_count} {t("search.perfNodes")} + {ixPerf.edge_count} {t("search.perfEdges")} + {t("search.perfCpu")} {ixPerf.cpu_usage_pct?.toFixed(0)}% + {t("search.perfMem")} {ixPerf.mem_used_mb}/{ixPerf.mem_total_mb}MB +
+ {/if} + + {/if} + + + {#if ixSessions.length > 0} +
+
+ {t("search.sessionsTitle")} + +
+ + {#if ixSessions.length > 1} + {@const maxEng = Math.max(...ixSessions.map(s => s.avg_engagement ?? 0), 0.01)} + {@const pts = ixSessions.map((s, i) => `${(i / (ixSessions.length - 1)) * 120},${40 - (s.avg_engagement / maxEng) * 36}`).join(" ")} + + + {#each ixSessions as s, i} + {@const x = (i / (ixSessions.length - 1)) * 120} + {@const y = 40 - (s.avg_engagement / maxEng) * 36} + + {/each} + + {/if} + + {#each ixSessions as s} + {@const engPct = Math.round(Math.min(1, s.avg_engagement ?? 0) * 100)} + {@const snrPct = Math.round(Math.min(1, (s.avg_snr ?? 0) / 20) * 100)} +
+ {#if s.best}★{/if} + {s.session_id} + {s.epoch_count}ep + {Math.round(s.duration_secs / 60)}m + +
+
+
+ {s.avg_engagement?.toFixed(2)} + +
+
+
+ {s.avg_snr?.toFixed(1)} +
+ {/each} +
+ {/if} + {/if}
- {:else} + {:else if mode === "text"}
-