Chores customize install #23
@@ -138,10 +138,15 @@ during setup).
|
||||
On first run WA detects your hardware and picks the best transcription backend
|
||||
(**NPU → NVIDIA → AMD → Intel → CPU**). It works on your **CPU or GPU** (GPU via Vulkan) out of the
|
||||
box; to use an Intel **NPU**, open **Settings ▸ Hardware** and download the one-time NPU
|
||||
acceleration package. The app runs without admin rights, never adds itself to startup, and keeps all
|
||||
data under `%LOCALAPPDATA%\WhispAssist`.
|
||||
acceleration package. The app runs without admin rights, does not add itself to startup unless you
|
||||
opt in (**Settings ▸ Recording ▸ Launch at login**), and keeps all data under
|
||||
`%LOCALAPPDATA%\WhispAssist`.
|
||||
|
||||
Prefer the NSIS installer? Grab **`WhispAssist_<version>_x64-setup.exe`** from the same page.
|
||||
Prefer the NSIS installer? Grab **`WhispAssist_<version>_x64-setup.exe`** from the same page — it
|
||||
lets you choose a **current-user** (no admin) or **all-users** install.
|
||||
|
||||
**Deploying to many machines?** See [`docs/enterprise-deployment.md`](docs/enterprise-deployment.md)
|
||||
for silent install, custom install location, and presetting defaults with a `wa-defaults.ini` file.
|
||||
|
||||
## Optional dependencies
|
||||
|
||||
@@ -155,7 +160,7 @@ If you do not have these installed, WhispAssist will still work, but some featur
|
||||
|
||||
WhispAssist is a **Tauri 2** application: a small Rust core with a compiled **Svelte + TypeScript**
|
||||
frontend rendered through the OS WebView2 (no bundled browser → low idle memory). Every decision
|
||||
and its alternatives are recorded in [`docs/adr/`](docs/adr/) (ADR-0001–0011).
|
||||
and its alternatives are recorded in [`docs/adr/`](docs/adr/) (ADR-0001–0012).
|
||||
|
||||
- **Shell / IPC:** Tauri 2 (Rust ⇄ WebView2)
|
||||
- **Audio capture:** WASAPI loopback
|
||||
@@ -176,7 +181,7 @@ and its alternatives are recorded in [`docs/adr/`](docs/adr/) (ADR-0001–0011).
|
||||
WhispAssist/
|
||||
├── docs/ # The engineering plan (read this first)
|
||||
│ ├── 00-overview.md … 07-research-findings.md
|
||||
│ └── adr/ Architecture Decision Records (0001–0011)
|
||||
│ └── adr/ Architecture Decision Records (0001–0012)
|
||||
├── src-tauri/ # Rust core — implemented service modules:
|
||||
│ └── src/{audio,transcription,diarization,storage,llm,calendar,
|
||||
│ hardware,notes,sync,mcp,vault}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# WhispAssist v0.7.0
|
||||
|
||||
**Privacy-first, Windows-native meeting assistant — everything on-device, nothing leaves unless you say so.**
|
||||
|
||||
This release is about **control over install and startup** — for a single user and for admins rolling
|
||||
WhispAssist out across a fleet. You can now launch WhispAssist at login, choose a per-user (no-admin)
|
||||
or all-users install, preset every default with a deployment file, and re-scan your hardware without
|
||||
restarting. No feature here changes the privacy posture: everything optional stays off-by-default and
|
||||
local-first.
|
||||
|
||||
One universal installer (**MSI** and **NSIS**) covers every machine: **Vulkan** for all GPUs
|
||||
(NVIDIA/AMD/Intel), the Intel **NPU** (OpenVINO), a **DirectML** fallback, and **CPU**.
|
||||
|
||||
---
|
||||
|
||||
## ✨ New
|
||||
|
||||
### Launch at login (opt-in)
|
||||
A new **Settings ▸ Recording ▸ Launch WhispAssist at login** toggle starts WhispAssist when you sign
|
||||
in to Windows. It's **off by default**, needs **no admin** (a per-user startup entry), and does **not**
|
||||
begin recording on its own.
|
||||
|
||||
### Choose a per-user or all-users install
|
||||
The NSIS setup (`WhispAssist_0.7.0_x64-setup.exe`) now lets you install for **just yourself**
|
||||
(no admin rights required) or for **all users**. The MSI remains the per-machine, all-users installer.
|
||||
|
||||
### Customize deployments with a file (`wa-defaults.ini`)
|
||||
Admins can preset WhispAssist's defaults for every machine using native Windows tooling (Group Policy,
|
||||
SCCM, Intune, `msiexec`) — no management console. Drop a `wa-defaults.ini` next to the installer or in
|
||||
`%PROGRAMDATA%\WhispAssist\`, and each machine's **first launch** seeds its settings from it: record-
|
||||
by-default, preferred backend, a model to auto-download, retention, storage location, AI provider, and
|
||||
more. You can also set a **custom install location** with standard `msiexec INSTALLDIR=…` / NSIS `/D=`
|
||||
flags. **Secrets are never read from this file** — API keys and tokens stay in the OS credential store.
|
||||
Full key reference and silent-install examples in
|
||||
[`docs/enterprise-deployment.md`](docs/enterprise-deployment.md).
|
||||
|
||||
### Refresh hardware
|
||||
A **Refresh** button in **Settings ▸ Hardware** re-detects your GPU/NPU on the spot — handy after
|
||||
installing a driver or plugging in an eGPU — no restart needed.
|
||||
|
||||
---
|
||||
|
||||
## 📦 Install
|
||||
|
||||
**Requirements:** Windows 10 or 11 (x64). WhispAssist needs **WebView2** (preinstalled on Windows 11;
|
||||
the installer fetches it on Windows 10). Importing from a file/URL additionally needs **`ffmpeg`**
|
||||
(and **`yt-dlp`** for URLs) on your PATH — install those separately.
|
||||
|
||||
1. Download **`WhispAssist_0.7.0_x64_en-US.msi`** (or the NSIS **`WhispAssist_0.7.0_x64-setup.exe`**).
|
||||
2. Run it and accept the UAC prompt. If SmartScreen appears, choose **More info → Run anyway**.
|
||||
3. Launch **WhispAssist** from the Start menu.
|
||||
|
||||
On first run WA picks the best transcription backend (**NPU → NVIDIA → AMD → Intel → CPU**). It runs
|
||||
without admin rights, does **not** add itself to startup unless you opt in, and keeps all data under
|
||||
`%LOCALAPPDATA%\WhispAssist`.
|
||||
|
||||
Deploying to many machines? See [`docs/enterprise-deployment.md`](docs/enterprise-deployment.md).
|
||||
|
||||
## 🔐 Checksums (SHA-256)
|
||||
|
||||
```
|
||||
730676580dbef5c4bb2c155f46a73a5791722139ab144a3fc0286e3a4449c432 WhispAssist_0.7.0_x64_en-US.msi
|
||||
6a34c16eb6f876fc92c7a79d37414b4fdd2c09f4f40873008149db36cd0a30aa WhispAssist_0.7.0_x64-setup.exe
|
||||
```
|
||||
|
||||
Verify after download:
|
||||
|
||||
```powershell
|
||||
Get-FileHash .\WhispAssist_0.7.0_x64_en-US.msi -Algorithm SHA256
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Privacy, unchanged
|
||||
Everything optional is **off by default**. With nothing configured, WhispAssist makes **no content
|
||||
egress at all**. Recording is opt-in; sync/AI credentials live only in the OS credential store; the
|
||||
MCP server is loopback-only and adds no egress; the deployment file never carries secrets. The
|
||||
reachable-host allowlist is derived from your settings and enforced in the core.
|
||||
@@ -382,6 +382,10 @@ segment (the M1 grounding invariant, asserted by the golden-transcript test).
|
||||
"expose_recordings": false, // never serve .wav unless explicitly true (FR-MCP-3)
|
||||
},
|
||||
"privacy": { "encrypt_at_rest": false },
|
||||
// Launch WhispAssist at login (NFR-RES-4). Opt-in, OFF by default. Toggling this
|
||||
// via `set_auto_start` also writes a per-user `HKCU\...\Run` entry (no admin);
|
||||
// startup reconciles the OS entry to this flag (e.g. after a reinstall).
|
||||
"auto_start": false,
|
||||
// Optional MS Graph calendar source (M4.4, T8.9, FR-CAL-6). Opt-in, explicit consent via OAuth
|
||||
// PKCE — OFF by default. `credential_ref` points into the OS credential store; the token itself
|
||||
// is never written here (same invariant as sync credentials, FR-SYNC-6).
|
||||
@@ -389,6 +393,20 @@ segment (the M1 grounding invariant, asserted by the golden-transcript test).
|
||||
}
|
||||
```
|
||||
|
||||
## First-run deploy seeding (`wa-defaults.ini`)
|
||||
|
||||
For enterprise mass-deployment, the **first** launch on a machine (before `settings.json` exists)
|
||||
optionally seeds its defaults from an admin-supplied INI. First file found wins:
|
||||
|
||||
1. `%PROGRAMDATA%\WhispAssist\wa-defaults.ini` — machine-wide (GPO / SCCM / Intune file copy).
|
||||
2. `<install dir>\wa-defaults.ini` — the bundled template (shipped fully commented → no-op).
|
||||
|
||||
Keys are flat `key = value` matching `settings.json` field names (bools/ints coerced), plus the
|
||||
special `auto_download_model = true` which fetches the configured `whisper_model` in the background.
|
||||
**No secrets** — any key containing `key`/`token`/`secret`/`credential`/`password` is ignored; those
|
||||
live only in the OS credential store. After first run the file is never read again. See
|
||||
`docs/enterprise-deployment.md` and `src-tauri/src/deploy.rs`.
|
||||
|
||||
## Retention & recovery semantics
|
||||
|
||||
- **Retention** (FR-STORE-2): a background job deletes whole meeting folders + rows once a meeting
|
||||
|
||||
@@ -36,8 +36,10 @@ update_live_notes(input: { meetingId: MeetingId; markdown: string }): void
|
||||
set_segment_note(input: { meetingId: MeetingId; anchorMs: number; text: string }): void
|
||||
|
||||
// ---- Hardware ----
|
||||
hardware_status(): { backends: BackendInfo[]; active: BackendId; modelSize: string; estRtf: number }
|
||||
hardware_status(): { backends: BackendInfo[]; active: BackendId; modelSize: string; estRtf: number } // re-detects fresh each call — backs the Settings ▸ Hardware "Refresh" button
|
||||
set_preferred_backend(input: { backend: BackendId | "auto" }): void
|
||||
// Launch-at-login (NFR-RES-4). Writes/removes a per-user OS Run entry (no admin) and persists auto_start. Opt-in, off by default.
|
||||
set_auto_start(input: { enabled: boolean }): void
|
||||
|
||||
// ---- Transcription / models ----
|
||||
// language (T8.7, M4.2): omitted reuses the meeting's current language rather than resetting it.
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# ADR-0012 — Launch-at-login & enterprise deployment defaults
|
||||
|
||||
- **Status:** Accepted
|
||||
- **Date:** 2026-07-14
|
||||
- **Context source:** User request (2026-07-14) — auto-start on boot; customize an installation
|
||||
(install location, per-user/all-users, default settings) with native Windows tooling for
|
||||
mass-deployment.
|
||||
|
||||
## Context
|
||||
|
||||
Two related needs. (1) Users want WhispAssist to **launch automatically at login**. NFR-RES-4
|
||||
forbids adding WA to OS startup without explicit consent, so this must be opt-in. (2) An admin
|
||||
mass-deploying WA to many machines wants to **customize the deployment** — install location, whether
|
||||
it installs per-user (no admin) or all-users, and the app's default settings (record-by-default,
|
||||
preferred backend, a model to pre-download, retention, AI provider) — using **native Windows tools**
|
||||
(GPO / SCCM / Intune / `msiexec` / silent NSIS), not a bespoke management console.
|
||||
|
||||
## Decision
|
||||
|
||||
1. **Launch-at-login is opt-in, off by default.** A `set_auto_start` command uses
|
||||
`tauri-plugin-autostart` to write a **per-user** `HKCU\...\Run` entry (no admin) and persists an
|
||||
`auto_start` setting. Startup reconciles the OS entry to that flag (restores it after a reinstall).
|
||||
Nothing runs on a timer — this is a registry entry, not a background process (NFR-RES-1).
|
||||
2. **Install location & scope are native, no app code.**
|
||||
- Location: `msiexec INSTALLDIR=…` (MSI) / NSIS `/D=…` (silent).
|
||||
- Scope: NSIS `installMode: "both"` — the `.exe` setup lets the user choose **current-user
|
||||
(no admin)** or **all-users (admin)**. The MSI stays per-machine as the enterprise all-users
|
||||
artifact.
|
||||
3. **Default settings via a first-run `wa-defaults.ini`.** On a machine's **first** launch (before
|
||||
`settings.json` exists) WA reads an admin-supplied INI — `%PROGRAMDATA%\WhispAssist\wa-defaults.ini`
|
||||
first, else the bundled `<install dir>\wa-defaults.ini` — and seeds `settings.json` from it, with an
|
||||
optional `auto_download_model` to pre-fetch the model. The shipped template is fully commented, so
|
||||
a normal install is unaffected. This is deployable purely by copying a file with existing Windows
|
||||
management tooling; no WiX custom actions.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Positive:** opt-in startup honors NFR-RES-4 with zero idle cost; install location/scope reuse the
|
||||
installers' native behavior (no custom code to maintain); one small INI + a first-run guard covers
|
||||
the whole deployment-customization surface and works for MSI, NSIS, and portable copies alike.
|
||||
- **Guardrail — no secrets in the deploy file (CLAUDE.md):** the INI must never carry credentials.
|
||||
`deploy.rs` drops any key containing `key`/`token`/`secret`/`credential`/`password` as defense in
|
||||
depth; API keys, OAuth tokens and sync passwords remain in the OS credential store only. Seeding a
|
||||
provider (e.g. `llm_provider=anthropic`) still requires the admin/user to provision its key
|
||||
separately — no new egress path is created by the file.
|
||||
- **Negative / care:** the seed runs only when `settings.json` is absent (truly first run); it does
|
||||
**not** re-apply on upgrade, matching "the user's own settings win thereafter." Array config merges
|
||||
in Tauri **replace** rather than append, so `wa-defaults.ini` must be listed in both
|
||||
`tauri.conf.json` and `tauri.vulkan.conf.json` bundle resources (the shipped build uses the latter).
|
||||
|
||||
## Revisit if
|
||||
|
||||
Admins need per-machine policy that **overrides** user settings on every launch (not just seeds
|
||||
first-run), or a signed/locked-down enterprise policy channel beyond a plain INI.
|
||||
@@ -0,0 +1,101 @@
|
||||
# Enterprise deployment
|
||||
|
||||
How to mass-deploy WhispAssist and preset its defaults with native Windows tooling (Group Policy,
|
||||
SCCM, Intune, `msiexec`, silent NSIS). No management console, no phone-home. See ADR-0012.
|
||||
|
||||
WhispAssist ships two bundles:
|
||||
|
||||
| Bundle | Scope | Admin? |
|
||||
|---|---|---|
|
||||
| `WhispAssist_<ver>_x64_en-US.msi` | Per-machine (all users) | Yes |
|
||||
| `WhispAssist_<ver>_x64-setup.exe` (NSIS) | Current-user **or** all-users (prompts) | Only for all-users |
|
||||
|
||||
## Install location
|
||||
|
||||
- **MSI:** `msiexec /i WhispAssist_<ver>_x64_en-US.msi INSTALLDIR="D:\Apps\WhispAssist" /qn`
|
||||
(`INSTALLDIR` is Tauri's WiX install-dir property; confirm against the generated `.wxs` if a build
|
||||
changes it.)
|
||||
- **NSIS:** `WhispAssist_<ver>_x64-setup.exe /S /D=D:\Apps\WhispAssist`
|
||||
(`/S` = silent, `/D=` = install dir; `/D=` must be **last** and unquoted per NSIS.)
|
||||
|
||||
## Install scope (per-user vs all-users)
|
||||
|
||||
The NSIS `.exe` shows a "current user / all users" page. **Current user needs no admin** and installs
|
||||
under the user profile; **all users** requires elevation. Silent all-users:
|
||||
`WhispAssist_<ver>_x64-setup.exe /S`. The MSI is always per-machine (all-users) and requires admin.
|
||||
|
||||
## Auto-start at login
|
||||
|
||||
Off by default (NFR-RES-4). Turn it on for the user either in-app (Settings ▸ Recording ▸ *Launch
|
||||
WhispAssist at login*) or by presetting `auto_start = true` in `wa-defaults.ini` (below). It installs
|
||||
a **per-user** `HKCU\Software\Microsoft\Windows\CurrentVersion\Run` entry — no admin, and it does
|
||||
**not** start recording on its own.
|
||||
|
||||
## Preset default settings — `wa-defaults.ini`
|
||||
|
||||
On a machine's **first** launch (before `settings.json` exists), WhispAssist reads an admin-supplied
|
||||
INI and seeds that user's `settings.json`. After that the user's own settings win and the file is
|
||||
ignored. First location found wins:
|
||||
|
||||
1. `%PROGRAMDATA%\WhispAssist\wa-defaults.ini` — machine-wide. Deploy with a GPO/SCCM/Intune file copy.
|
||||
2. `<install dir>\wa-defaults.ini` — the template shipped next to the executable.
|
||||
|
||||
The shipped template is fully commented out, so a default install behaves as if it were absent.
|
||||
Uncomment and edit the keys you want to preset.
|
||||
|
||||
### Format
|
||||
|
||||
Flat `key = value`, one per line. `;` and `#` comment lines and `[section]` headers are ignored.
|
||||
`true`/`false` become switches, plain numbers become numbers, everything else is text. Unknown or
|
||||
misspelled keys are ignored.
|
||||
|
||||
> **Never put secrets in this file.** API keys, OAuth tokens and sync passwords live only in the OS
|
||||
> credential store. Any key containing `key`, `token`, `secret`, `credential` or `password` is
|
||||
> dropped on read. Presetting `llm_provider = anthropic` still requires the key to be provisioned
|
||||
> separately — the file adds no egress path.
|
||||
|
||||
### Keys
|
||||
|
||||
| Key | Values | Meaning |
|
||||
|---|---|---|
|
||||
| `default_record` | true/false | Record every meeting by default (consent notice still applies). |
|
||||
| `preferred_backend` | auto\|npu\|nvidia\|amd\|intel\|cpu | Transcription backend. |
|
||||
| `whisper_model` | catalog id (e.g. `base.en-q5_1`) | Default transcription model. |
|
||||
| `auto_download_model` | true/false | Fetch `whisper_model` in the background on first launch. |
|
||||
| `whisper_language` | auto\|ISO-639-1 | Default language (multilingual model only). |
|
||||
| `low_overhead` | true/false | CPU + smallest model preset. |
|
||||
| `storage_root` | path | Where meetings are stored. |
|
||||
| `retention_max_age_days` | number | Delete meetings older than N days. |
|
||||
| `retention_max_size_gb` | number | Cap total storage at N GB. |
|
||||
| `llm_provider` | ollama\|custom\|anthropic\|off | Summary provider (key provisioned separately). |
|
||||
| `llm_endpoint` | url | LLM endpoint. |
|
||||
| `llm_model` | text | LLM model name. |
|
||||
| `microphone_enabled` | true/false | Capture the user's mic into the transcript. |
|
||||
| `auto_record_calendar` | true/false | Auto-start recording on calendar events (app open only). |
|
||||
| `theme` | system\|light\|dark | UI theme. |
|
||||
| `auto_start` | true/false | Launch WhispAssist at login (per-user Run entry). |
|
||||
| `sync_enabled` | true/false | Sync master switch (targets/credentials configured in-app). |
|
||||
|
||||
### Example
|
||||
|
||||
```ini
|
||||
default_record = true
|
||||
preferred_backend = npu
|
||||
whisper_model = small.en-q5_1
|
||||
auto_download_model = true
|
||||
retention_max_age_days = 90
|
||||
auto_start = true
|
||||
```
|
||||
|
||||
## Silent end-to-end example
|
||||
|
||||
```bat
|
||||
:: 1. Push machine-wide defaults (as SYSTEM via GPO/SCCM)
|
||||
mkdir "%ProgramData%\WhispAssist"
|
||||
copy wa-defaults.ini "%ProgramData%\WhispAssist\wa-defaults.ini"
|
||||
|
||||
:: 2. Install per-machine, custom location, no UI
|
||||
msiexec /i WhispAssist_<ver>_x64_en-US.msi INSTALLDIR="C:\Program Files\WhispAssist" /qn
|
||||
```
|
||||
|
||||
Each user's first launch then seeds their `settings.json` from the machine-wide file.
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "whispassist",
|
||||
"private": true,
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"type": "module",
|
||||
"description": "Privacy-first, fully local Windows meeting assistant.",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
|
||||
Generated
+57
-2
@@ -124,6 +124,17 @@ version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "auto-launch"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f012b8cc0c850f34117ec8252a44418f2e34a2cf501de89e29b241ae5f79471"
|
||||
dependencies = [
|
||||
"dirs 4.0.0",
|
||||
"thiserror 1.0.69",
|
||||
"winreg 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
@@ -848,6 +859,15 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
|
||||
dependencies = [
|
||||
"dirs-sys 0.3.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "5.0.1"
|
||||
@@ -866,6 +886,17 @@ dependencies = [
|
||||
"dirs-sys 0.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users 0.4.6",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.1"
|
||||
@@ -1043,7 +1074,7 @@ dependencies = [
|
||||
"rustc_version",
|
||||
"toml 1.1.2+spec-1.1.0",
|
||||
"vswhom",
|
||||
"winreg",
|
||||
"winreg 0.55.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4935,6 +4966,20 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-autostart"
|
||||
version = "2.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "459383cebc193cdd03d1ba4acc40f2c408a7abce419d64bdcd2d745bc2886f70"
|
||||
dependencies = [
|
||||
"auto-launch",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-dialog"
|
||||
version = "2.7.1"
|
||||
@@ -6043,7 +6088,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "whispassist"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"async-trait",
|
||||
@@ -6073,6 +6118,7 @@ dependencies = [
|
||||
"sqlx",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-autostart",
|
||||
"tauri-plugin-dialog",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
@@ -6767,6 +6813,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.55.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "whispassist"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
description = "Privacy-first, fully local Windows meeting assistant"
|
||||
authors = ["WhispAssist contributors"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -81,6 +81,7 @@ ort = { version = "=2.0.0-rc.10", optional = true, default-features = false, fea
|
||||
rustfft = { version = "6", optional = true }
|
||||
sherpa-rs = { version = "0.6", optional = true, default-features = false, features = ["download-binaries"] } # sherpa-onnx bindings (Phase 4, ADR-0005)
|
||||
tauri-plugin-dialog = "2" # native Save/choose-folder (Phase 2 export)
|
||||
tauri-plugin-autostart = "2" # opt-in launch-on-login (per-user HKCU\Run, no admin; NFR-RES-4)
|
||||
|
||||
# notes export (Phase 8, FR-NOTE-4) — pure-Rust, no external binary/cloud
|
||||
# conversion service, consistent with the fully-local invariant.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -176,6 +176,48 @@
|
||||
"Identifier": {
|
||||
"description": "Permission identifier",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "This permission set configures if your\napplication can enable or disable auto\nstarting the application on boot.\n\n#### Granted Permissions\n\nIt allows all to check, enable and\ndisable the automatic start on boot.\n\n\n#### This default permission set includes:\n\n- `allow-enable`\n- `allow-disable`\n- `allow-is-enabled`",
|
||||
"type": "string",
|
||||
"const": "autostart:default",
|
||||
"markdownDescription": "This permission set configures if your\napplication can enable or disable auto\nstarting the application on boot.\n\n#### Granted Permissions\n\nIt allows all to check, enable and\ndisable the automatic start on boot.\n\n\n#### This default permission set includes:\n\n- `allow-enable`\n- `allow-disable`\n- `allow-is-enabled`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the disable command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "autostart:allow-disable",
|
||||
"markdownDescription": "Enables the disable command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the enable command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "autostart:allow-enable",
|
||||
"markdownDescription": "Enables the enable command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the is_enabled command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "autostart:allow-is-enabled",
|
||||
"markdownDescription": "Enables the is_enabled command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the disable command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "autostart:deny-disable",
|
||||
"markdownDescription": "Denies the disable command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the enable command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "autostart:deny-enable",
|
||||
"markdownDescription": "Denies the enable command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the is_enabled command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "autostart:deny-is-enabled",
|
||||
"markdownDescription": "Denies the is_enabled command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`",
|
||||
"type": "string",
|
||||
|
||||
@@ -176,6 +176,48 @@
|
||||
"Identifier": {
|
||||
"description": "Permission identifier",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "This permission set configures if your\napplication can enable or disable auto\nstarting the application on boot.\n\n#### Granted Permissions\n\nIt allows all to check, enable and\ndisable the automatic start on boot.\n\n\n#### This default permission set includes:\n\n- `allow-enable`\n- `allow-disable`\n- `allow-is-enabled`",
|
||||
"type": "string",
|
||||
"const": "autostart:default",
|
||||
"markdownDescription": "This permission set configures if your\napplication can enable or disable auto\nstarting the application on boot.\n\n#### Granted Permissions\n\nIt allows all to check, enable and\ndisable the automatic start on boot.\n\n\n#### This default permission set includes:\n\n- `allow-enable`\n- `allow-disable`\n- `allow-is-enabled`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the disable command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "autostart:allow-disable",
|
||||
"markdownDescription": "Enables the disable command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the enable command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "autostart:allow-enable",
|
||||
"markdownDescription": "Enables the enable command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the is_enabled command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "autostart:allow-is-enabled",
|
||||
"markdownDescription": "Enables the is_enabled command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the disable command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "autostart:deny-disable",
|
||||
"markdownDescription": "Denies the disable command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the enable command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "autostart:deny-enable",
|
||||
"markdownDescription": "Denies the enable command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the is_enabled command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "autostart:deny-is-enabled",
|
||||
"markdownDescription": "Denies the is_enabled command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`",
|
||||
"type": "string",
|
||||
|
||||
@@ -55,7 +55,7 @@ pub struct StartRecordingArgs {
|
||||
// `settings.json` per `docs/03-data-model.md`; meeting rows/files go through
|
||||
// `AppState.store` (Phase 2, `storage::SqliteStore`).
|
||||
|
||||
fn default_settings() -> Settings {
|
||||
pub(crate) fn default_settings() -> Settings {
|
||||
Settings {
|
||||
theme: "system".into(),
|
||||
storage_root: wa_root().display().to_string(),
|
||||
@@ -87,6 +87,7 @@ fn default_settings() -> Settings {
|
||||
mcp_port: 4849,
|
||||
mcp_expose: "none".into(),
|
||||
mcp_expose_recordings: false,
|
||||
auto_start: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +98,7 @@ pub(crate) fn load_settings() -> Settings {
|
||||
.unwrap_or_else(default_settings)
|
||||
}
|
||||
|
||||
fn save_settings(settings: &Settings) -> Result<(), WaError> {
|
||||
pub(crate) fn save_settings(settings: &Settings) -> Result<(), WaError> {
|
||||
let path = settings_path();
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).map_err(|e| WaError::new("settings", e.to_string()))?;
|
||||
@@ -1444,6 +1445,26 @@ pub async fn set_preferred_backend(args: SetPreferredBackendArgs) -> WaResult<()
|
||||
save_settings(&settings)
|
||||
}
|
||||
|
||||
/// Enable/disable launch-at-login (NFR-RES-4). Writes a per-user `HKCU\...\Run`
|
||||
/// entry via `tauri-plugin-autostart` (no admin) and persists the choice so the
|
||||
/// startup reconcile in `lib.rs` keeps the OS entry in sync after a reinstall.
|
||||
/// Opt-in only: nothing calls this unless the user toggles it (or an enterprise
|
||||
/// deploy file set `auto_start=true`).
|
||||
#[tauri::command]
|
||||
pub async fn set_auto_start(app: AppHandle, enabled: bool) -> WaResult<()> {
|
||||
use tauri_plugin_autostart::ManagerExt;
|
||||
let manager = app.autolaunch();
|
||||
let res = if enabled {
|
||||
manager.enable()
|
||||
} else {
|
||||
manager.disable()
|
||||
};
|
||||
res.map_err(|e| WaError::new("autostart", e.to_string()))?;
|
||||
let mut settings = load_settings();
|
||||
settings.auto_start = enabled;
|
||||
save_settings(&settings)
|
||||
}
|
||||
|
||||
/// True when the NPU ONNX Whisper model is downloaded (false on non-NPU builds).
|
||||
fn npu_model_installed() -> bool {
|
||||
#[cfg(feature = "npu")]
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
//! Enterprise deployment: seed default settings from an admin-supplied `.ini`
|
||||
//! on **first run only** (before any `settings.json` exists).
|
||||
//!
|
||||
//! An admin mass-deploying WhispAssist (GPO / SCCM / Intune) drops a
|
||||
//! `wa-defaults.ini` and every fresh install picks it up once, seeding
|
||||
//! `settings.json` with their chosen defaults (record-by-default, preferred
|
||||
//! backend, retention, model to auto-download, …) — all via native Windows file
|
||||
//! deployment, no WiX custom actions. See `docs/enterprise-deployment.md`.
|
||||
//!
|
||||
//! **Guardrail (CLAUDE.md):** the file must never carry secrets. Keys that look
|
||||
//! like credentials are ignored here as defense in depth — API keys / OAuth
|
||||
//! tokens live only in the OS credential store.
|
||||
|
||||
use crate::models::Settings;
|
||||
use serde_json::{Map, Value};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Special (non-`Settings`) INI key: when truthy, the first-run seed also fetches
|
||||
/// the configured `whisper_model` in the background so the machine is ready
|
||||
/// offline. Stripped before the settings merge.
|
||||
const AUTO_DOWNLOAD_KEY: &str = "auto_download_model";
|
||||
|
||||
/// Candidate locations, first found wins:
|
||||
/// 1. `%PROGRAMDATA%\WhispAssist\wa-defaults.ini` — machine-wide enterprise path.
|
||||
/// 2. `<exe dir>\wa-defaults.ini` — the bundled template / per-install override.
|
||||
fn candidate_paths() -> Vec<PathBuf> {
|
||||
let mut paths = Vec::new();
|
||||
if let Ok(program_data) = std::env::var("ProgramData") {
|
||||
paths.push(PathBuf::from(program_data).join("WhispAssist").join("wa-defaults.ini"));
|
||||
}
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(dir) = exe.parent() {
|
||||
paths.push(dir.join("wa-defaults.ini"));
|
||||
}
|
||||
}
|
||||
paths
|
||||
}
|
||||
|
||||
/// Reads the first existing defaults file and produces the seeded settings plus
|
||||
/// the whisper model id to auto-download (if `auto_download_model` was set).
|
||||
/// `None` when no file exists or it contains no overrides (the shipped template
|
||||
/// is fully commented, so normal installs get exactly today's behavior).
|
||||
pub fn seed_settings_from_defaults() -> Option<(Settings, Option<String>)> {
|
||||
let text = candidate_paths()
|
||||
.into_iter()
|
||||
.find_map(|p| std::fs::read_to_string(p).ok())?;
|
||||
seed_from_ini(&text)
|
||||
}
|
||||
|
||||
/// Testable core: parse INI text → merge onto the built-in defaults.
|
||||
fn seed_from_ini(text: &str) -> Option<(Settings, Option<String>)> {
|
||||
let mut overrides = parse_ini(text);
|
||||
if overrides.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Pull the non-Settings auto-download flag out before the merge.
|
||||
let auto_download = overrides
|
||||
.remove(AUTO_DOWNLOAD_KEY)
|
||||
.map(|v| truthy(&v))
|
||||
.unwrap_or(false);
|
||||
|
||||
// Merge overrides onto the default settings' JSON form, then deserialize.
|
||||
// Unknown keys (typos) are ignored — `Settings` has no deny_unknown_fields.
|
||||
let mut base = match serde_json::to_value(crate::commands::default_settings()) {
|
||||
Ok(Value::Object(map)) => map,
|
||||
_ => return None,
|
||||
};
|
||||
for (k, v) in overrides {
|
||||
base.insert(k, v);
|
||||
}
|
||||
|
||||
let settings: Settings = serde_json::from_value(Value::Object(base)).ok()?;
|
||||
let model = if auto_download {
|
||||
Some(settings.whisper_model.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Some((settings, model))
|
||||
}
|
||||
|
||||
/// Minimal INI reader: skips blanks, `;`/`#` comments and `[section]` headers;
|
||||
/// splits each `key = value` on the first `=`; coerces values to bool / integer /
|
||||
/// string so serde lands them on the typed `Settings` fields. Silently drops any
|
||||
/// key that looks like a secret (guardrail — no credentials in the deploy file).
|
||||
fn parse_ini(text: &str) -> Map<String, Value> {
|
||||
let mut map = Map::new();
|
||||
for line in text.lines() {
|
||||
let line = line.trim();
|
||||
if line.is_empty()
|
||||
|| line.starts_with(';')
|
||||
|| line.starts_with('#')
|
||||
|| line.starts_with('[')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let Some((key, value)) = line.split_once('=') else {
|
||||
continue;
|
||||
};
|
||||
let key = key.trim().to_string();
|
||||
let value = value.trim();
|
||||
if key.is_empty() || looks_like_secret(&key) {
|
||||
continue;
|
||||
}
|
||||
map.insert(key, coerce(value));
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
/// `true`/`false` → bool, all-integer → number, everything else → string.
|
||||
fn coerce(value: &str) -> Value {
|
||||
match value.to_ascii_lowercase().as_str() {
|
||||
"true" => return Value::Bool(true),
|
||||
"false" => return Value::Bool(false),
|
||||
_ => {}
|
||||
}
|
||||
if let Ok(n) = value.parse::<i64>() {
|
||||
return Value::Number(n.into());
|
||||
}
|
||||
Value::String(value.to_string())
|
||||
}
|
||||
|
||||
fn truthy(v: &Value) -> bool {
|
||||
matches!(v, Value::Bool(true)) || matches!(v, Value::String(s) if s.eq_ignore_ascii_case("true"))
|
||||
}
|
||||
|
||||
/// Defense in depth: never seed anything that smells like a credential.
|
||||
fn looks_like_secret(key: &str) -> bool {
|
||||
let k = key.to_ascii_lowercase();
|
||||
["key", "token", "secret", "credential", "password"]
|
||||
.iter()
|
||||
.any(|needle| k.contains(needle))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fully_commented_file_is_a_noop() {
|
||||
let ini = "; default_record = true\n# preferred_backend = cpu\n[general]\n\n";
|
||||
assert!(seed_from_ini(ini).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coerces_bool_int_and_string_fields() {
|
||||
let ini = "default_record = true\nretention_max_age_days = 90\npreferred_backend = cpu\n";
|
||||
let (settings, model) = seed_from_ini(ini).expect("overrides present");
|
||||
assert!(settings.default_record);
|
||||
assert_eq!(settings.retention_max_age_days, Some(90));
|
||||
assert_eq!(settings.preferred_backend, "cpu");
|
||||
assert!(model.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_download_returns_the_configured_model() {
|
||||
let ini = "whisper_model = base.en-q5_1\nauto_download_model = true\n";
|
||||
let (_settings, model) = seed_from_ini(ini).expect("overrides present");
|
||||
assert_eq!(model.as_deref(), Some("base.en-q5_1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unset_fields_keep_their_defaults() {
|
||||
let ini = "default_record = true\n";
|
||||
let (settings, _) = seed_from_ini(ini).unwrap();
|
||||
// microphone stays on, auto_start stays off — only the named key changed.
|
||||
assert!(settings.microphone_enabled);
|
||||
assert!(!settings.auto_start);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn secret_keys_are_ignored() {
|
||||
let ini = "anthropic_api_key = sk-should-be-dropped\ndefault_record = true\n";
|
||||
let map = parse_ini(ini);
|
||||
assert!(!map.contains_key("anthropic_api_key"));
|
||||
assert!(map.contains_key("default_record"));
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ pub mod audio;
|
||||
pub mod briefs;
|
||||
pub mod calendar;
|
||||
pub mod commands;
|
||||
pub mod deploy;
|
||||
pub mod diarization;
|
||||
pub mod error;
|
||||
pub mod hardware;
|
||||
@@ -155,6 +156,14 @@ pub fn run() {
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
// Opt-in launch-at-login (NFR-RES-4). The macOS launcher arg is required
|
||||
// by the signature but unused on Windows, where enable/disable writes a
|
||||
// per-user HKCU\...\Run entry (no admin). Off until the user (or an
|
||||
// enterprise deploy file) turns `auto_start` on.
|
||||
.plugin(tauri_plugin_autostart::init(
|
||||
tauri_plugin_autostart::MacosLauncher::LaunchAgent,
|
||||
None,
|
||||
))
|
||||
// In-memory streaming of recordings for the player (FR-REC-5): decrypts
|
||||
// on the fly so no plaintext audio is ever written to disk.
|
||||
.register_uri_scheme_protocol("waaudio", |_ctx, request| {
|
||||
@@ -172,6 +181,53 @@ pub fn run() {
|
||||
.build(app)?;
|
||||
app.manage(TrayHandle(tray));
|
||||
|
||||
// First-run enterprise deploy seeding (deploy.rs): if no settings.json
|
||||
// exists yet and an admin dropped a wa-defaults.ini, seed settings once
|
||||
// and optionally fetch the configured model in the background. One-shot
|
||||
// — guarded by the settings file's absence, so it never re-runs and adds
|
||||
// nothing to idle cost (NFR-RES-1).
|
||||
if !crate::paths::settings_path().exists() {
|
||||
if let Some((seeded, model_to_download)) = deploy::seed_settings_from_defaults() {
|
||||
match commands::save_settings(&seeded) {
|
||||
Ok(()) => {
|
||||
tracing::info!("seeded settings.json from wa-defaults.ini");
|
||||
if let Some(id) = model_to_download {
|
||||
let app_handle = app.handle().clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
if let Err(e) = commands::download_model(
|
||||
app_handle,
|
||||
commands::DownloadModelArgs {
|
||||
kind: "whisper".into(),
|
||||
id,
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::warn!("deploy auto-download of model failed: {e:?}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("first-run deploy seeding failed to write settings: {e:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reconcile launch-at-login with the persisted preference (NFR-RES-4):
|
||||
// if the user opted in but the OS entry is missing (e.g. after a
|
||||
// reinstall or a deploy file that set auto_start), restore it. One-shot.
|
||||
{
|
||||
use tauri_plugin_autostart::ManagerExt;
|
||||
let manager = app.autolaunch();
|
||||
if commands::load_settings().auto_start && !manager.is_enabled().unwrap_or(false) {
|
||||
if let Err(e) = manager.enable() {
|
||||
tracing::warn!("failed to restore auto-start entry: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reminders (Phase 8, T8.6, FR-CAL-5) are Windows-scheduled toasts, not
|
||||
// an app-side timer — Windows itself is what's "polling", so this stays
|
||||
// within NFR-RES-1. init() just registers the AppUserModelID.
|
||||
@@ -283,6 +339,7 @@ pub fn run() {
|
||||
commands::list_audio_devices,
|
||||
commands::list_input_devices,
|
||||
commands::set_preferred_backend,
|
||||
commands::set_auto_start,
|
||||
commands::list_models,
|
||||
commands::list_whisper_languages,
|
||||
commands::download_npu_package,
|
||||
|
||||
@@ -354,6 +354,12 @@ pub struct Settings {
|
||||
pub mcp_expose: String, // none|selected|all
|
||||
#[serde(default)]
|
||||
pub mcp_expose_recordings: bool,
|
||||
/// Launch WhispAssist automatically at login (opt-in, NFR-RES-4). OFF by
|
||||
/// default; toggled via `set_auto_start`, which writes a per-user
|
||||
/// `HKCU\...\Run` entry through `tauri-plugin-autostart` (no admin). An
|
||||
/// enterprise deploy file may set this to `true` (see `deploy.rs`).
|
||||
#[serde(default)]
|
||||
pub auto_start: bool,
|
||||
}
|
||||
|
||||
fn default_mcp_transport() -> String {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "WhispAssist",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"identifier": "bet.dou.whispassist",
|
||||
"build": {
|
||||
"frontendDist": "../dist",
|
||||
@@ -32,8 +32,12 @@
|
||||
"active": true,
|
||||
"targets": ["msi", "nsis"],
|
||||
"icon": ["icons/icon.ico"],
|
||||
"resources": ["wa-defaults.ini"],
|
||||
"windows": {
|
||||
"webviewInstallMode": { "type": "downloadBootstrapper" }
|
||||
"webviewInstallMode": { "type": "downloadBootstrapper" },
|
||||
"nsis": {
|
||||
"installMode": "both"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "gen/schemas/desktop-schema.json",
|
||||
"bundle": {
|
||||
"resources": ["vulkan-1.dll"]
|
||||
"resources": ["vulkan-1.dll", "wa-defaults.ini"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
; ============================================================================
|
||||
; WhispAssist enterprise deployment defaults (wa-defaults.ini)
|
||||
; ============================================================================
|
||||
; Read ONCE, on a machine's FIRST launch (before settings.json exists), to
|
||||
; seed the app's default settings. After that the user's own settings.json
|
||||
; wins and this file is ignored. Deploy it with native Windows tooling
|
||||
; (Group Policy / SCCM / Intune file copy) to either location — the first
|
||||
; one found wins:
|
||||
;
|
||||
; 1. %PROGRAMDATA%\WhispAssist\wa-defaults.ini (machine-wide)
|
||||
; 2. <install dir>\wa-defaults.ini (this bundled template)
|
||||
;
|
||||
; As shipped, every setting below is COMMENTED OUT, so a normal install
|
||||
; behaves exactly as if this file were absent. Uncomment and edit the lines
|
||||
; you want to preset. Format is flat "key = value" — no sections required;
|
||||
; [section] headers, ';' and '#' comment lines are ignored.
|
||||
;
|
||||
; Values: true / false for switches; a plain number for numeric fields;
|
||||
; text otherwise. Unknown / misspelled keys are ignored.
|
||||
;
|
||||
; SECURITY: never put secrets here. API keys, OAuth tokens and sync
|
||||
; passwords live only in the OS credential store; any key containing
|
||||
; "key", "token", "secret", "credential" or "password" is dropped on read.
|
||||
; ----------------------------------------------------------------------------
|
||||
|
||||
; ---- Recording (ADR-0009) --------------------------------------------------
|
||||
; Record every meeting to disk by default (consent notice still applies).
|
||||
; default_record = false
|
||||
|
||||
; ---- Transcription backend & model -----------------------------------------
|
||||
; preferred_backend = auto ; auto | npu | nvidia | amd | intel | cpu
|
||||
; whisper_model = base.en-q5_1 ; a catalog model id
|
||||
; whisper_language = auto ; auto | ISO-639-1 code (multilingual model only)
|
||||
; low_overhead = false
|
||||
|
||||
; Download whisper_model in the background on first launch so the machine is
|
||||
; ready offline. Requires network at first run.
|
||||
; auto_download_model = false
|
||||
|
||||
; ---- Storage & retention (FR-STORE-2) --------------------------------------
|
||||
; storage_root = C:\ProgramData\WhispAssist\data
|
||||
; retention_max_age_days = 90
|
||||
; retention_max_size_gb = 20
|
||||
|
||||
; ---- Local LLM / summaries (ADR-0007) --------------------------------------
|
||||
; llm_provider = ollama ; ollama | custom | anthropic | off
|
||||
; llm_endpoint = http://localhost:11434
|
||||
; llm_model = llama3
|
||||
|
||||
; ---- Capture & UX ----------------------------------------------------------
|
||||
; microphone_enabled = true
|
||||
; auto_record_calendar = false
|
||||
; theme = system ; system | light | dark
|
||||
|
||||
; ---- Startup (NFR-RES-4) ---------------------------------------------------
|
||||
; Launch WhispAssist automatically at login for the user.
|
||||
; auto_start = false
|
||||
|
||||
; ---- Sync master switch (ADR-0010; targets/creds configured in-app) --------
|
||||
; sync_enabled = false
|
||||
@@ -336,6 +336,9 @@ export interface AppSettings {
|
||||
audio_output_device: string | null;
|
||||
microphone_enabled: boolean;
|
||||
audio_input_device: string | null;
|
||||
/** Launch WhispAssist at login (opt-in, off by default; NFR-RES-4). Toggled
|
||||
* via setAutoStart, which writes a per-user Run entry (no admin). */
|
||||
auto_start: boolean;
|
||||
}
|
||||
|
||||
// Feature brief — agent-ready spec distilled from a meeting (ADR-0011).
|
||||
@@ -417,6 +420,7 @@ export const api = {
|
||||
listInputDevices: () => invoke<AudioDeviceInfo[]>("list_input_devices"),
|
||||
setPreferredBackend: (backend: BackendId | "auto") =>
|
||||
invoke<void>("set_preferred_backend", { args: { backend } }),
|
||||
setAutoStart: (enabled: boolean) => invoke<void>("set_auto_start", { enabled }),
|
||||
downloadNpuPackage: () => invoke<void>("download_npu_package"),
|
||||
downloadDirectmlPackage: () => invoke<void>("download_directml_package"),
|
||||
listModels: () => invoke<ModelInfo[]>("list_models"),
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"settings.transcription.switch_multilingual": "Switch to a multilingual model above to choose a language.",
|
||||
|
||||
"settings.hardware.title": "Hardware",
|
||||
"settings.hardware.refresh": "Refresh",
|
||||
"settings.hardware.active_backend": "Active backend",
|
||||
"settings.hardware.model_meta": "· model {size}",
|
||||
"settings.hardware.preferred_backend": "Preferred backend",
|
||||
@@ -527,5 +528,7 @@
|
||||
"settings.recording.consent_ack": "acknowledged",
|
||||
"settings.recording.consent_not": "not yet acknowledged",
|
||||
"settings.recording.auto_label": "Auto-start recording when a calendar event begins",
|
||||
"settings.recording.auto_hint": "Only while WhispAssist is open. When an imported calendar event's start time arrives, a recording begins automatically (using your default retention setting above). Nothing runs in the background — the timer is armed only while the app is running. Import events under Settings → Calendar."
|
||||
"settings.recording.auto_hint": "Only while WhispAssist is open. When an imported calendar event's start time arrives, a recording begins automatically (using your default retention setting above). Nothing runs in the background — the timer is armed only while the app is running. Import events under Settings → Calendar.",
|
||||
"settings.recording.autostart_label": "Launch WhispAssist at login",
|
||||
"settings.recording.autostart_hint": "Starts WhispAssist automatically when you sign in to Windows. Off by default; installs a per-user startup entry (no admin required) and does not begin recording on its own."
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ const DEFAULT_SETTINGS: AppSettings = {
|
||||
audio_output_device: null, // system default render device (FR-CAP-1)
|
||||
microphone_enabled: true, // capture the user's mic into the transcript (FR-CAP-7)
|
||||
audio_input_device: null, // system default capture device
|
||||
auto_start: false, // launch at login — opt-in, off by default (NFR-RES-4)
|
||||
};
|
||||
|
||||
class SettingsStore {
|
||||
@@ -383,6 +384,18 @@ class SettingsStore {
|
||||
return this.patch({ default_record: on });
|
||||
}
|
||||
|
||||
/** Launch-at-login toggle (NFR-RES-4). Goes through its own command (not
|
||||
* patch) since the backend also writes the per-user OS Run entry; that
|
||||
* command persists auto_start itself, so we just mirror it locally. */
|
||||
async setAutoStart(on: boolean) {
|
||||
this.settings = { ...this.settings, auto_start: on };
|
||||
try {
|
||||
await api.setAutoStart(on);
|
||||
} catch {
|
||||
this.backendStub = true;
|
||||
}
|
||||
}
|
||||
|
||||
setRetentionPolicy(maxAgeDays: number | null, maxSizeGb: number | null) {
|
||||
return this.patch({ retention_max_age_days: maxAgeDays, retention_max_size_gb: maxSizeGb });
|
||||
}
|
||||
|
||||
@@ -754,13 +754,29 @@
|
||||
</label>
|
||||
<p class="muted">{t("settings.recording.auto_hint")}</p>
|
||||
|
||||
<label class="row">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={settings.settings.auto_start}
|
||||
onchange={(e) => settings.setAutoStart((e.target as HTMLInputElement).checked)}
|
||||
/>
|
||||
<span>{t("settings.recording.autostart_label")}</span>
|
||||
</label>
|
||||
<p class="muted">{t("settings.recording.autostart_hint")}</p>
|
||||
|
||||
{#if showConsent}
|
||||
<ConsentNotice onAccept={acceptConsent} onCancel={() => (showConsent = false)} />
|
||||
{/if}
|
||||
</section>
|
||||
{:else if section === "hardware"}
|
||||
<section>
|
||||
<h3>{t("settings.hardware.title")}</h3>
|
||||
<div class="actions">
|
||||
<h3>{t("settings.hardware.title")}</h3>
|
||||
<button class="ghost" onclick={() => settings.loadHardware()}>
|
||||
<RefreshCw size={14} aria-hidden="true" />
|
||||
{t("settings.hardware.refresh")}
|
||||
</button>
|
||||
</div>
|
||||
{#if settings.hardware}
|
||||
<div class="row">
|
||||
{t("settings.hardware.active_backend")}<code>{settings.hardware.active}</code>
|
||||
|
||||
Reference in New Issue
Block a user