diff --git a/README.md b/README.md index 33ca512..4ca5b6b 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,11 @@ # aman -> Local amanuensis +> Local amanuensis for X11 desktop dictation -Python X11 STT daemon that records audio, runs Whisper, applies local AI cleanup, and injects text. +Aman is a local X11 dictation daemon for Linux desktops. The supported path is: +install the portable bundle, save the first-run settings window once, then use +a hotkey to dictate into the focused app. -## Target User - -The canonical Aman user is a desktop professional who wants dictation and -rewriting features without learning Python tooling. - -- End-user path: portable X11 release bundle for mainstream distros. -- Alternate package channels: Debian/Ubuntu `.deb` and Arch packaging inputs. -- Developer path: Python/uv workflows. - -Persona details and distribution policy are documented in -[`docs/persona-and-distribution.md`](docs/persona-and-distribution.md). - -## Release Channels - -Aman is not GA yet for X11 users across distros. The maintained release -channels are: - -- Portable X11 bundle: current canonical end-user channel. -- Debian/Ubuntu `.deb`: secondary packaged channel. -- Arch `PKGBUILD` plus source tarball: secondary maintainer and power-user channel. -- Python wheel and sdist: current developer and integrator channel. - -## GA Support Matrix +## Supported Path | Surface | Contract | | --- | --- | @@ -37,103 +17,12 @@ channels are: | Representative GA validation families | Debian/Ubuntu, Arch, Fedora, openSUSE | | Portable installer prerequisite | System CPython `3.10`, `3.11`, or `3.12` | -## Install (Portable Bundle) +Distribution policy and user persona details live in +[`docs/persona-and-distribution.md`](docs/persona-and-distribution.md). -Download `aman-x11-linux-.tar.gz` and -`aman-x11-linux-.tar.gz.sha256`, install the runtime dependencies for -your distro, then install the bundle: +## 60-Second Quickstart -```bash -sha256sum -c aman-x11-linux-.tar.gz.sha256 -tar -xzf aman-x11-linux-.tar.gz -cd aman-x11-linux- -./install.sh -``` - -The installer writes the user service, updates `~/.local/bin/aman`, and runs -`systemctl --user enable --now aman` automatically. -On first service start, Aman opens the graphical settings window if -`~/.config/aman/config.json` does not exist yet. - -Upgrade by extracting the newer bundle and running its `install.sh` again. -Config and cache are preserved by default. - -Uninstall with: - -```bash -~/.local/share/aman/current/uninstall.sh -``` - -Add `--purge` if you also want to remove `~/.config/aman/` and -`~/.cache/aman/`. - -Detailed install, upgrade, uninstall, and conflict guidance lives in -[`docs/portable-install.md`](docs/portable-install.md). - -## Secondary Channels - -### Debian/Ubuntu (`.deb`) - -Download a release artifact and install it: - -```bash -sudo apt install ./aman__.deb -systemctl --user daemon-reload -systemctl --user enable --now aman -``` - -### Arch Linux - -Use the generated packaging inputs (`PKGBUILD` + source tarball) in `dist/arch/` -or your own packaging pipeline. - -## Daily-Use And Support Modes - -- Supported daily-use path: install Aman, then run it as a `systemd --user` - service. -- Supported manual path: use `aman run` in the foreground while setting up, - debugging, or collecting support logs. - -## Recovery Sequence - -When Aman does not behave as expected, use this order: - -1. Run `aman doctor --config ~/.config/aman/config.json`. -2. Run `aman self-check --config ~/.config/aman/config.json`. -3. Inspect `journalctl --user -u aman -f`. -4. Re-run Aman in the foreground with `aman run --config ~/.config/aman/config.json --verbose`. - -See [`docs/runtime-recovery.md`](docs/runtime-recovery.md) for the failure IDs, -example output, and the common recovery branches behind this sequence. - -## Diagnostics - -- `aman doctor` is the fast, read-only preflight for config, X11 session, - audio runtime, input resolution, hotkey availability, injection backend - selection, and service prerequisites. -- `aman self-check` is the deeper, still read-only installed-system readiness - check. It includes every `doctor` check plus managed model cache, cache - writability, service unit/state, and startup readiness. -- The tray `Run Diagnostics` action runs the same deeper `self-check` path and - logs any non-`ok` results. -- Exit code `0` means every check finished as `ok` or `warn`. Exit code `2` - means at least one check finished as `fail`. - -Example output: - -```text -[OK] config.load: loaded config from /home/user/.config/aman/config.json -[WARN] model.cache: managed editor model is not cached at /home/user/.cache/aman/models/Qwen2.5-1.5B-Instruct-Q4_K_M.gguf | next_step: start Aman once on a networked connection so it can download the managed editor model, then rerun `aman self-check --config /home/user/.config/aman/config.json` -[FAIL] service.state: user service is installed but failed to start | next_step: inspect `journalctl --user -u aman -f` to see why aman.service is failing -overall: fail -``` - -## Runtime Dependencies - -- X11 -- PortAudio runtime (`libportaudio2` or distro equivalent) -- GTK3 and AppIndicator runtime (`gtk3`, `libayatana-appindicator3`) -- Python GTK and X11 bindings (`python3-gi`/`python-gobject`, `python-xlib`) +First, install the runtime dependencies for your distro:
Ubuntu/Debian @@ -171,292 +60,105 @@ sudo zypper install -y portaudio gtk3 libayatana-appindicator3-1 python3-gobject
-## Quickstart (Portable Bundle) +Then install Aman and run the first dictation: -For supported daily use on the portable bundle: - -1. Install the runtime dependencies for your distro. -2. Download and extract the portable release bundle. -3. Run `./install.sh` from the extracted bundle. -4. Save the first-run settings window. -5. Validate the install: +1. Verify and extract the portable bundle. +2. Run `./install.sh`. +3. When `Aman Settings (Required)` opens, choose your microphone and keep + `Clipboard paste (recommended)` unless you have a reason to change it. +4. Click `Apply`. +5. Put your cursor in any text field. +6. Press the hotkey once, say `hello from Aman`, then press the hotkey again. ```bash +sha256sum -c aman-x11-linux-.tar.gz.sha256 +tar -xzf aman-x11-linux-.tar.gz +cd aman-x11-linux- +./install.sh +``` + +## What Success Looks Like + +- On first launch, Aman opens the `Aman Settings (Required)` window. +- After you save settings, the tray returns to `Idle`. +- During dictation, the tray cycles `Idle -> Recording -> STT -> AI Processing -> Idle`. +- The focused text field receives text similar to `Hello from Aman.` + +## Visual Proof + +![Aman settings window](docs/media/settings-window.png) + +![Aman tray menu](docs/media/tray-menu.png) + +[Watch the first-run walkthrough (WebM)](docs/media/first-run-demo.webm) + +## Validate Your Install + +Run the supported checks in this order: + +```bash +aman doctor --config ~/.config/aman/config.json aman self-check --config ~/.config/aman/config.json ``` -If you need the manual foreground path for setup or support: +- `aman doctor` is the fast, read-only preflight for config, X11 session, + audio runtime, input resolution, hotkey availability, injection backend + selection, and service prerequisites. +- `aman self-check` is the deeper, still read-only installed-system readiness + check. It includes every `doctor` check plus managed model cache, cache + writability, service unit/state, and startup readiness. +- Exit code `0` means every check finished as `ok` or `warn`. Exit code `2` + means at least one check finished as `fail`. -```bash -aman run --config ~/.config/aman/config.json -``` +## Troubleshooting -On first launch, Aman opens a graphical settings window automatically. -It includes sections for: +- Settings window did not appear: + run `aman run --config ~/.config/aman/config.json` once in the foreground. +- No tray icon after saving settings: + run `aman self-check --config ~/.config/aman/config.json`. +- Hotkey does not start recording: + run `aman doctor --config ~/.config/aman/config.json` and pick a different + hotkey in Settings if needed. +- Microphone test fails or no audio is captured: + re-open Settings, choose another input device, then rerun `aman doctor`. +- Text was recorded but not injected: + run `aman doctor`, then `aman run --config ~/.config/aman/config.json --verbose`. -- microphone input -- hotkey -- output backend -- writing profile -- output safety policy -- runtime strategy (managed vs custom Whisper path) -- help/about actions +Use [`docs/runtime-recovery.md`](docs/runtime-recovery.md) for the full failure +map and escalation flow. -## Config +## Install, Upgrade, and Uninstall -Create `~/.config/aman/config.json` (or let `aman` create it automatically on first start if missing): +The canonical end-user guide lives in +[`docs/portable-install.md`](docs/portable-install.md). -```json -{ - "config_version": 1, - "daemon": { "hotkey": "Cmd+m" }, - "recording": { "input": "0" }, - "stt": { - "provider": "local_whisper", - "model": "base", - "device": "cpu", - "language": "auto" - }, - "models": { - "allow_custom_models": false, - "whisper_model_path": "" - }, - "injection": { - "backend": "clipboard", - "remove_transcription_from_clipboard": false - }, - "safety": { - "enabled": true, - "strict": false - }, - "ux": { - "profile": "default", - "show_notifications": true - }, - "advanced": { - "strict_startup": true - }, - "vocabulary": { - "replacements": [ - { "from": "Martha", "to": "Marta" }, - { "from": "docker", "to": "Docker" } - ], - "terms": ["Systemd", "Kubernetes"] - } -} -``` +- Fresh install, upgrade, uninstall, and purge behavior are documented there. +- The same guide covers distro-package conflicts and portable-installer + recovery steps. -`config_version` is required and currently must be `1`. Legacy unversioned -configs are migrated automatically on load. +## Daily Use and Support -Recording input can be a device index (preferred) or a substring of the device -name. -If `recording.input` is explicitly set and cannot be resolved, startup fails -instead of falling back to a default device. +- Supported daily-use path: let the `systemd --user` service keep Aman running. +- Supported manual path: use `aman run` in the foreground for setup, support, + or debugging. +- Tray menu actions are: `Settings...`, `Help`, `About`, `Pause Aman` / + `Resume Aman`, `Reload Config`, `Run Diagnostics`, `Open Config Path`, and + `Quit`. +- If required settings are not saved, Aman enters a `Settings Required` tray + state and does not capture audio. -Config validation is strict: unknown fields are rejected with a startup error. -Validation errors include the exact field and an example fix snippet. +## Secondary Channels -Profile options: +- Portable X11 bundle: current canonical end-user channel. +- Debian/Ubuntu `.deb`: secondary packaged channel. +- Arch `PKGBUILD` plus source tarball: secondary maintainer and power-user + channel. +- Python wheel and sdist: developer and integrator channel. -- `ux.profile=default`: baseline cleanup behavior. -- `ux.profile=fast`: lower-latency AI generation settings. -- `ux.profile=polished`: same cleanup depth as default. -- `safety.enabled=true`: enables fact-preservation checks (names/numbers/IDs/URLs). -- `safety.strict=false`: fallback to safer draft when fact checks fail. -- `safety.strict=true`: reject output when fact checks fail. -- `advanced.strict_startup=true`: keep fail-fast startup validation behavior. +## More Docs -Transcription language: - -- `stt.language=auto` (default) enables Whisper auto-detection. -- You can pin language with Whisper codes (for example `en`, `es`, `pt`, `ja`, `zh`) or common names like `English`/`Spanish`. -- If a pinned language hint is rejected by the runtime, Aman logs a warning and retries with auto-detect. - -Hotkey notes: - -- Use one key plus optional modifiers (for example `Cmd+m`, `Super+m`, `Ctrl+space`). -- `Super` and `Cmd` are equivalent aliases for the same modifier. - -AI cleanup is always enabled and uses the locked local Qwen2.5-1.5B GGUF model -downloaded to `~/.cache/aman/models/` during daemon initialization. -Prompts are structured with semantic XML tags for both system and user messages -to improve instruction adherence and output consistency. -Cleanup runs in two local passes: -- pass 1 drafts cleaned text and labels ambiguity decisions (correction/literal/spelling/filler) -- pass 2 audits those decisions conservatively and emits final `cleaned_text` -This keeps Aman in dictation mode: it does not execute editing instructions embedded in transcript text. -Before Aman reports `ready`, local llama runs a tiny warmup completion so the -first real transcription is faster. -If warmup fails and `advanced.strict_startup=true`, startup fails fast. -With `advanced.strict_startup=false`, Aman logs a warning and continues. -Model downloads use a network timeout and SHA256 verification before activation. -Cached models are checksum-verified on startup; mismatches trigger a forced -redownload. - -Provider policy: - -- `Aman-managed` mode (recommended) is the canonical supported UX: - Aman handles model lifecycle and safe defaults for you. -- `Expert mode` is opt-in and exposes a custom Whisper model path for advanced users. -- Editor model/provider configuration is intentionally not exposed in config. -- Custom Whisper paths are only active with `models.allow_custom_models=true`. - -Use `-v/--verbose` to enable DEBUG logs, including recognized/processed -transcript text and llama.cpp logs (`llama::` prefix). Without `-v`, logs are -INFO level. - -Vocabulary correction: - -- `vocabulary.replacements` is deterministic correction (`from -> to`). -- `vocabulary.terms` is a preferred spelling list used as hinting context. -- Wildcards are intentionally rejected (`*`, `?`, `[`, `]`, `{`, `}`) to avoid ambiguous rules. -- Rules are deduplicated case-insensitively; conflicting replacements are rejected. - -STT hinting: - -- Vocabulary is passed to Whisper as compact `hotwords` only when that argument - is supported by the installed `faster-whisper` runtime. -- Aman enables `word_timestamps` when supported and runs a conservative - alignment heuristic pass (self-correction/restart detection) before the editor - stage. - -Fact guard: - -- Aman runs a deterministic fact-preservation verifier after editor output. -- If facts are changed/invented and `safety.strict=false`, Aman falls back to the safer aligned draft. -- If facts are changed/invented and `safety.strict=true`, processing fails and output is not injected. - -## systemd user service - -```bash -make install-service -``` - -Service notes: - -- The supported daily-use path is the user service. -- The portable installer writes and enables the user unit automatically. -- The local developer unit launched by `make install-service` still resolves - `aman` from `PATH`. -- Package installs should provide the `aman` command automatically. -- Use `aman run --config ~/.config/aman/config.json` in the foreground for - setup, support, or debugging. -- Start recovery with `aman doctor`, then `aman self-check`, before inspecting - `systemctl --user status aman` and `journalctl --user -u aman -f`. -- See [`docs/runtime-recovery.md`](docs/runtime-recovery.md) for the expected - diagnostic IDs and next steps. - -## Usage - -- Press the hotkey once to start recording. -- Press it again to stop and run STT. -- Press `Esc` while recording to cancel without processing. -- `Esc` is only captured during active recording. -- Recording start is aborted if the cancel listener cannot be armed. -- Transcript contents are logged only when `-v/--verbose` is used. -- Tray menu includes: `Settings...`, `Help`, `About`, `Pause/Resume Aman`, `Reload Config`, `Run Diagnostics`, `Open Config Path`, and `Quit`. -- If required settings are not saved, Aman enters a `Settings Required` tray mode and does not capture audio. - -Wayland note: - -- Running under Wayland currently exits with a message explaining that it is not supported yet. - -Injection backends: - -- `clipboard`: copy to clipboard and inject via Ctrl+Shift+V (GTK clipboard + XTest) -- `injection`: type the text with simulated keypresses (XTest) -- `injection.remove_transcription_from_clipboard`: when `true` and backend is `clipboard`, restores/clears the clipboard after paste so the transcript is not kept there - -Editor stage: - -- Canonical local llama.cpp editor model (managed by Aman). -- Runtime flow is explicit: `ASR -> Alignment Heuristics -> Editor -> Fact Guard -> Vocabulary -> Injection`. - -Build and packaging (maintainers): - -```bash -make build -make package -make package-portable -make package-deb -make package-arch -make runtime-check -make release-check -``` - -`make package-portable` builds `dist/aman-x11-linux-.tar.gz` plus its -`.sha256` file. - -`make package-deb` installs Python dependencies while creating the package. -For offline packaging, set `AMAN_WHEELHOUSE_DIR` to a directory containing the -required wheels. - -Benchmarking (STT bypass, always dry): - -```bash -aman bench --text "draft a short email to Marta confirming lunch" --repeat 10 --warmup 2 -aman bench --text-file ./bench-input.txt --repeat 20 --json -``` - -`bench` does not capture audio and never injects text to desktop apps. It runs -the processing path from input transcript text through alignment/editor/fact-guard/vocabulary cleanup and -prints timing summaries. - -Model evaluation lab (dataset + matrix sweep): - -```bash -aman build-heuristic-dataset --input benchmarks/heuristics_dataset.raw.jsonl --output benchmarks/heuristics_dataset.jsonl -aman eval-models --dataset benchmarks/cleanup_dataset.jsonl --matrix benchmarks/model_matrix.small_first.json --heuristic-dataset benchmarks/heuristics_dataset.jsonl --heuristic-weight 0.25 --output benchmarks/results/latest.json -aman sync-default-model --report benchmarks/results/latest.json --artifacts benchmarks/model_artifacts.json --constants src/constants.py -``` - -`eval-models` runs a structured model/parameter sweep over a JSONL dataset and -outputs latency + quality metrics (including hybrid score, pass-1/pass-2 latency breakdown, -and correction safety metrics for `I mean` and spelling-disambiguation cases). -When `--heuristic-dataset` is provided, the report also includes alignment-heuristic -quality metrics (exact match, token-F1, rule precision/recall, per-tag breakdown). -`sync-default-model` promotes the report winner to the managed default model constants -using the artifact registry and can be run in `--check` mode for CI/release gates. - -Control: - -```bash -make run -make run config.example.json -make doctor -make self-check -make runtime-check -make eval-models -make sync-default-model -make check-default-model -make check -``` - -Developer setup (optional, `uv` workflow): - -```bash -uv sync --extra x11 -uv run aman run --config ~/.config/aman/config.json -``` - -Developer setup (optional, `pip` workflow): - -```bash -make install-local -aman run --config ~/.config/aman/config.json -``` - -CLI (support and developer workflows): - -```bash -aman doctor --config ~/.config/aman/config.json --json -aman self-check --config ~/.config/aman/config.json --json -aman run --config ~/.config/aman/config.json -aman bench --text "example transcript" --repeat 5 --warmup 1 -aman build-heuristic-dataset --input benchmarks/heuristics_dataset.raw.jsonl --output benchmarks/heuristics_dataset.jsonl --json -aman eval-models --dataset benchmarks/cleanup_dataset.jsonl --matrix benchmarks/model_matrix.small_first.json --heuristic-dataset benchmarks/heuristics_dataset.jsonl --heuristic-weight 0.25 --json -aman sync-default-model --check --report benchmarks/results/latest.json --artifacts benchmarks/model_artifacts.json --constants src/constants.py -aman version -aman init --config ~/.config/aman/config.json --force -``` +- Install, upgrade, uninstall: [docs/portable-install.md](docs/portable-install.md) +- Runtime recovery and diagnostics: [docs/runtime-recovery.md](docs/runtime-recovery.md) +- Config reference and advanced behavior: [docs/config-reference.md](docs/config-reference.md) +- Developer, packaging, and benchmark workflows: [docs/developer-workflows.md](docs/developer-workflows.md) +- Persona and distribution policy: [docs/persona-and-distribution.md](docs/persona-and-distribution.md) diff --git a/docs/config-reference.md b/docs/config-reference.md new file mode 100644 index 0000000..07deea4 --- /dev/null +++ b/docs/config-reference.md @@ -0,0 +1,154 @@ +# Config Reference + +Use this document when you need the full Aman config shape and the advanced +behavior notes that are intentionally kept out of the first-run README path. + +## Example config + +```json +{ + "config_version": 1, + "daemon": { "hotkey": "Cmd+m" }, + "recording": { "input": "0" }, + "stt": { + "provider": "local_whisper", + "model": "base", + "device": "cpu", + "language": "auto" + }, + "models": { + "allow_custom_models": false, + "whisper_model_path": "" + }, + "injection": { + "backend": "clipboard", + "remove_transcription_from_clipboard": false + }, + "safety": { + "enabled": true, + "strict": false + }, + "ux": { + "profile": "default", + "show_notifications": true + }, + "advanced": { + "strict_startup": true + }, + "vocabulary": { + "replacements": [ + { "from": "Martha", "to": "Marta" }, + { "from": "docker", "to": "Docker" } + ], + "terms": ["Systemd", "Kubernetes"] + } +} +``` + +`config_version` is required and currently must be `1`. Legacy unversioned +configs are migrated automatically on load. + +## Recording and validation + +- `recording.input` can be a device index (preferred) or a substring of the + device name. +- If `recording.input` is explicitly set and cannot be resolved, startup fails + instead of falling back to a default device. +- Config validation is strict: unknown fields are rejected with a startup + error. +- Validation errors include the exact field and an example fix snippet. + +## Profiles and runtime behavior + +- `ux.profile=default`: baseline cleanup behavior. +- `ux.profile=fast`: lower-latency AI generation settings. +- `ux.profile=polished`: same cleanup depth as default. +- `safety.enabled=true`: enables fact-preservation checks + (names/numbers/IDs/URLs). +- `safety.strict=false`: fallback to the safer aligned draft when fact checks + fail. +- `safety.strict=true`: reject output when fact checks fail. +- `advanced.strict_startup=true`: keep fail-fast startup validation behavior. + +Transcription language: + +- `stt.language=auto` enables Whisper auto-detection. +- You can pin language with Whisper codes such as `en`, `es`, `pt`, `ja`, or + `zh`, or common names such as `English` / `Spanish`. +- If a pinned language hint is rejected by the runtime, Aman logs a warning and + retries with auto-detect. + +Hotkey notes: + +- Use one key plus optional modifiers, for example `Cmd+m`, `Super+m`, or + `Ctrl+space`. +- `Super` and `Cmd` are equivalent aliases for the same modifier. + +## Managed versus expert mode + +- `Aman-managed` mode is the canonical supported UX: Aman handles model + lifecycle and safe defaults for you. +- `Expert mode` is opt-in and exposes a custom Whisper model path for advanced + users. +- Editor model/provider configuration is intentionally not exposed in config. +- Custom Whisper paths are only active with + `models.allow_custom_models=true`. + +Compatibility note: + +- `ux.show_notifications` remains in the config schema for compatibility, but + it is not part of the current supported first-run X11 surface and is not + exposed in the settings window. + +## Cleanup and model lifecycle + +AI cleanup is always enabled and uses the locked local +`Qwen2.5-1.5B-Instruct-Q4_K_M.gguf` model downloaded to +`~/.cache/aman/models/` during daemon initialization. + +- Prompts use semantic XML tags for both system and user messages. +- Cleanup runs in two local passes: + - pass 1 drafts cleaned text and labels ambiguity decisions + (correction/literal/spelling/filler) + - pass 2 audits those decisions conservatively and emits final + `cleaned_text` +- Aman stays in dictation mode: it does not execute editing instructions + embedded in transcript text. +- Before Aman reports `ready`, the local editor runs a tiny warmup completion + so the first real transcription is faster. +- If warmup fails and `advanced.strict_startup=true`, startup fails fast. +- With `advanced.strict_startup=false`, Aman logs a warning and continues. +- Model downloads use a network timeout and SHA256 verification before + activation. +- Cached models are checksum-verified on startup; mismatches trigger a forced + redownload. + +## Verbose logging and vocabulary + +- `-v/--verbose` enables DEBUG logs, including recognized/processed transcript + text and `llama::` logs. +- Without `-v`, logs stay at INFO level. + +Vocabulary correction: + +- `vocabulary.replacements` is deterministic correction (`from -> to`). +- `vocabulary.terms` is a preferred spelling list used as hinting context. +- Wildcards are intentionally rejected (`*`, `?`, `[`, `]`, `{`, `}`) to avoid + ambiguous rules. +- Rules are deduplicated case-insensitively; conflicting replacements are + rejected. + +STT hinting: + +- Vocabulary is passed to Whisper as compact `hotwords` only when that argument + is supported by the installed `faster-whisper` runtime. +- Aman enables `word_timestamps` when supported and runs a conservative + alignment heuristic pass before the editor stage. + +Fact guard: + +- Aman runs a deterministic fact-preservation verifier after editor output. +- If facts are changed or invented and `safety.strict=false`, Aman falls back + to the safer aligned draft. +- If facts are changed or invented and `safety.strict=true`, processing fails + and output is not injected. diff --git a/docs/developer-workflows.md b/docs/developer-workflows.md new file mode 100644 index 0000000..54a751d --- /dev/null +++ b/docs/developer-workflows.md @@ -0,0 +1,94 @@ +# Developer And Maintainer Workflows + +This document keeps build, packaging, development, and benchmarking material +out of the first-run README path. + +## Build and packaging + +```bash +make build +make package +make package-portable +make package-deb +make package-arch +make runtime-check +make release-check +``` + +- `make package-portable` builds `dist/aman-x11-linux-.tar.gz` plus + its `.sha256` file. +- `make package-deb` installs Python dependencies while creating the package. +- For offline Debian packaging, set `AMAN_WHEELHOUSE_DIR` to a directory + containing the required wheels. + +## Developer setup + +`uv` workflow: + +```bash +uv sync --extra x11 +uv run aman run --config ~/.config/aman/config.json +``` + +`pip` workflow: + +```bash +make install-local +aman run --config ~/.config/aman/config.json +``` + +## Support and control commands + +```bash +make run +make run config.example.json +make doctor +make self-check +make runtime-check +make eval-models +make sync-default-model +make check-default-model +make check +``` + +CLI examples: + +```bash +aman doctor --config ~/.config/aman/config.json --json +aman self-check --config ~/.config/aman/config.json --json +aman run --config ~/.config/aman/config.json +aman bench --text "example transcript" --repeat 5 --warmup 1 +aman build-heuristic-dataset --input benchmarks/heuristics_dataset.raw.jsonl --output benchmarks/heuristics_dataset.jsonl --json +aman eval-models --dataset benchmarks/cleanup_dataset.jsonl --matrix benchmarks/model_matrix.small_first.json --heuristic-dataset benchmarks/heuristics_dataset.jsonl --heuristic-weight 0.25 --json +aman sync-default-model --check --report benchmarks/results/latest.json --artifacts benchmarks/model_artifacts.json --constants src/constants.py +aman version +aman init --config ~/.config/aman/config.json --force +``` + +## Benchmarking + +```bash +aman bench --text "draft a short email to Marta confirming lunch" --repeat 10 --warmup 2 +aman bench --text-file ./bench-input.txt --repeat 20 --json +``` + +`bench` does not capture audio and never injects text to desktop apps. It runs +the processing path from input transcript text through +alignment/editor/fact-guard/vocabulary cleanup and prints timing summaries. + +## Model evaluation + +```bash +aman build-heuristic-dataset --input benchmarks/heuristics_dataset.raw.jsonl --output benchmarks/heuristics_dataset.jsonl +aman eval-models --dataset benchmarks/cleanup_dataset.jsonl --matrix benchmarks/model_matrix.small_first.json --heuristic-dataset benchmarks/heuristics_dataset.jsonl --heuristic-weight 0.25 --output benchmarks/results/latest.json +aman sync-default-model --report benchmarks/results/latest.json --artifacts benchmarks/model_artifacts.json --constants src/constants.py +``` + +- `eval-models` runs a structured model/parameter sweep over a JSONL dataset + and outputs latency plus quality metrics. +- When `--heuristic-dataset` is provided, the report also includes + alignment-heuristic quality metrics. +- `sync-default-model` promotes the report winner to the managed default model + constants and can be run in `--check` mode for CI and release gates. + +Dataset and artifact details live in [`benchmarks/README.md`](../benchmarks/README.md). diff --git a/docs/media/first-run-demo.webm b/docs/media/first-run-demo.webm new file mode 100644 index 0000000..71c846e Binary files /dev/null and b/docs/media/first-run-demo.webm differ diff --git a/docs/media/settings-window.png b/docs/media/settings-window.png new file mode 100644 index 0000000..c29cdeb Binary files /dev/null and b/docs/media/settings-window.png differ diff --git a/docs/media/tray-menu.png b/docs/media/tray-menu.png new file mode 100644 index 0000000..be3ace9 Binary files /dev/null and b/docs/media/tray-menu.png differ diff --git a/docs/portable-install.md b/docs/portable-install.md index ac2b1ec..9113f21 100644 --- a/docs/portable-install.md +++ b/docs/portable-install.md @@ -2,6 +2,9 @@ This is the canonical end-user install path for Aman on X11. +For the shortest first-run path, screenshots, and the expected tray/dictation +result, start with the quickstart in [`README.md`](../README.md). + ## Supported environment - X11 desktop session diff --git a/docs/release-checklist.md b/docs/release-checklist.md index 9503ef4..54c062d 100644 --- a/docs/release-checklist.md +++ b/docs/release-checklist.md @@ -39,7 +39,12 @@ GA signoff bar. The GA signoff sections are required for `v1.0.0` and later. - `make runtime-check` passes. - [`docs/runtime-recovery.md`](./runtime-recovery.md) matches the shipped diagnostic IDs and next-step wording. - [`docs/x11-ga/runtime-validation-report.md`](./x11-ga/runtime-validation-report.md) contains current automated evidence and release-specific manual validation entries. -12. GA validation signoff (`v1.0.0` and later): +12. GA first-run UX signoff (`v1.0.0` and later): + - `README.md` leads with the supported first-run path and expected visible result. + - `docs/media/settings-window.png`, `docs/media/tray-menu.png`, and `docs/media/first-run-demo.webm` are current and linked from the README. + - [`docs/x11-ga/first-run-review-notes.md`](./x11-ga/first-run-review-notes.md) contains a non-implementer walkthrough and the questions it surfaced. + - `aman --help` exposes the main command surface directly. +13. GA validation signoff (`v1.0.0` and later): - Validation evidence exists for Debian/Ubuntu, Arch, Fedora, and openSUSE. - The portable installer, upgrade path, and uninstall path are validated. - End-user docs and release notes match the shipped artifact set. diff --git a/docs/runtime-recovery.md b/docs/runtime-recovery.md index 9d2f2e7..88d2b98 100644 --- a/docs/runtime-recovery.md +++ b/docs/runtime-recovery.md @@ -2,6 +2,23 @@ Use this guide when Aman is installed but not behaving correctly. +## First-run troubleshooting + +- Settings window did not appear: + run `aman run --config ~/.config/aman/config.json` once in the foreground so + you can complete first-run setup. +- No tray icon after saving settings: + run `aman self-check --config ~/.config/aman/config.json` and confirm the + user service is enabled and active. +- Hotkey does not start recording: + run `aman doctor --config ~/.config/aman/config.json`, then choose a + different hotkey in Settings if `hotkey.parse` is not `ok`. +- Microphone test failed: + re-open Settings, choose another input device, then rerun `aman doctor`. +- Text was transcribed but not injected: + run `aman doctor`, then rerun `aman run --config ~/.config/aman/config.json --verbose` + to inspect the output backend in the foreground. + ## Command roles - `aman doctor --config ~/.config/aman/config.json` is the fast, read-only preflight for config, X11 session, audio runtime, input device resolution, hotkey availability, injection backend selection, and service prerequisites. diff --git a/docs/x11-ga/04-first-run-ux-and-support-docs.md b/docs/x11-ga/04-first-run-ux-and-support-docs.md index 26e1d98..8309f80 100644 --- a/docs/x11-ga/04-first-run-ux-and-support-docs.md +++ b/docs/x11-ga/04-first-run-ux-and-support-docs.md @@ -22,7 +22,7 @@ Even if install and runtime reliability are strong, Aman will not feel GA until - first launch - choosing a microphone - triggering the first dictation - - expected tray or notification behavior + - expected tray behavior - expected injected text result - Add a "validate your install" flow using `aman doctor` and `aman self-check`. - Add screenshots for the settings window and tray menu. @@ -63,6 +63,6 @@ Even if install and runtime reliability are strong, Aman will not feel GA until ## Evidence required to close - Updated README and linked support docs. -- Screenshots and demo artifact checked into the release or docs surface. +- Screenshots and demo artifact checked into the docs surface. - A reviewer walk-through from someone who did not implement the docs rewrite. - A short list of first-run questions found during review and how the docs resolved them. diff --git a/docs/x11-ga/README.md b/docs/x11-ga/README.md index d496a4c..bae5c5f 100644 --- a/docs/x11-ga/README.md +++ b/docs/x11-ga/README.md @@ -10,7 +10,7 @@ The current gaps are: - The X11 support contract and service-versus-foreground split are now documented, but the public release surface still needs the remaining trust and support work from milestones 4 and 5. - Validation matrices now exist for portable lifecycle and runtime reliability, but they are not yet filled with release-specific manual evidence across Debian/Ubuntu, Arch, Fedora, and openSUSE. - Incomplete trust surface. The project still needs a real license file, real maintainer/contact metadata, real project URLs, published release artifacts, and public checksums. -- Incomplete first-run story. The product describes a settings window and tray workflow, but there is no short happy path, no expected-result walkthrough, and no visual proof that the experience is real. +- The first-run docs and media have landed, but milestone 4 still needs a non-implementer walkthrough before the project can claim that the public docs are actually enough. - Diagnostics are now the canonical recovery path, but milestone 3 still needs release-specific X11 evidence for restart, offline-start, tray diagnostics, and recovery scenarios. - The release checklist now includes GA signoff gates, but the project is still short of the broader legal, release-publication, and validation evidence needed for a credible public 1.0 release. @@ -100,7 +100,12 @@ Any future docs, tray copy, and release notes should point users to this same se [`runtime-validation-report.md`](./runtime-validation-report.md) are filled with real X11 validation evidence. - [ ] [Milestone 4: First-Run UX and Support Docs](./04-first-run-ux-and-support-docs.md) - Turn the product from "documented by the author" into "understandable by a new user." + Implementation landed on 2026-03-12: the README is now end-user-first, + first-run assets live under `docs/media/`, deep config and maintainer content + moved into linked docs, and `aman --help` exposes the top-level commands + directly. Leave this milestone open until + [`first-run-review-notes.md`](./first-run-review-notes.md) contains a real + non-implementer walkthrough. - [ ] [Milestone 5: GA Candidate Validation and Release](./05-ga-candidate-validation-and-release.md) Close the remaining trust, legal, release, and validation work for a public 1.0 launch. diff --git a/docs/x11-ga/first-run-review-notes.md b/docs/x11-ga/first-run-review-notes.md new file mode 100644 index 0000000..543fe5e --- /dev/null +++ b/docs/x11-ga/first-run-review-notes.md @@ -0,0 +1,24 @@ +# First-Run Review Notes + +Use this file to capture the non-implementer walkthrough required to close +milestone 4. + +## Review template + +- Reviewer: +- Date: +- Environment: +- Entry point used: +- Did the reviewer use only the public docs? yes / no + +## First-run questions or confusions + +- Question: + - Where it appeared: + - How the docs or product resolved it: + +## Remaining gaps + +- Gap: + - Severity: + - Suggested follow-up: diff --git a/scripts/generate_docs_media.py b/scripts/generate_docs_media.py new file mode 100644 index 0000000..7f4a094 --- /dev/null +++ b/scripts/generate_docs_media.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import subprocess +import tempfile +from pathlib import Path + +from PIL import Image, ImageDraw, ImageFont + + +ROOT = Path(__file__).resolve().parents[1] +MEDIA_DIR = ROOT / "docs" / "media" +FONT_REGULAR = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" +FONT_BOLD = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" + + +def font(size: int, *, bold: bool = False) -> ImageFont.ImageFont: + candidate = FONT_BOLD if bold else FONT_REGULAR + try: + return ImageFont.truetype(candidate, size=size) + except OSError: + return ImageFont.load_default() + + +def draw_round_rect(draw: ImageDraw.ImageDraw, box, radius: int, *, fill, outline=None, width=1): + draw.rounded_rectangle(box, radius=radius, fill=fill, outline=outline, width=width) + + +def draw_background(size: tuple[int, int], *, light=False) -> Image.Image: + w, h = size + image = Image.new("RGBA", size, "#0d111b" if not light else "#e5e8ef") + draw = ImageDraw.Draw(image) + for y in range(h): + mix = y / max(1, h - 1) + if light: + color = ( + int(229 + (240 - 229) * mix), + int(232 + (241 - 232) * mix), + int(239 + (246 - 239) * mix), + 255, + ) + else: + color = ( + int(13 + (30 - 13) * mix), + int(17 + (49 - 17) * mix), + int(27 + (79 - 27) * mix), + 255, + ) + draw.line((0, y, w, y), fill=color) + draw.ellipse((60, 70, 360, 370), fill=(43, 108, 176, 90)) + draw.ellipse((w - 360, h - 340, w - 40, h - 20), fill=(14, 116, 144, 70)) + draw.ellipse((w - 260, 40, w - 80, 220), fill=(244, 114, 182, 50)) + return image + + +def paste_center(base: Image.Image, overlay: Image.Image, top: int) -> tuple[int, int]: + x = (base.width - overlay.width) // 2 + base.alpha_composite(overlay, (x, top)) + return (x, top) + + +def draw_text_block( + draw: ImageDraw.ImageDraw, + origin: tuple[int, int], + lines: list[str], + *, + fill, + title=None, + title_fill=None, + line_gap=12, + body_font=None, + title_font=None, +): + x, y = origin + title_font = title_font or font(26, bold=True) + body_font = body_font or font(22) + if title: + draw.text((x, y), title, font=title_font, fill=title_fill or fill) + y += title_font.size + 10 + for line in lines: + draw.text((x, y), line, font=body_font, fill=fill) + y += body_font.size + line_gap + + +def build_settings_window() -> Image.Image: + base = draw_background((1440, 900)) + window = Image.new("RGBA", (1180, 760), (248, 250, 252, 255)) + draw = ImageDraw.Draw(window) + draw_round_rect(draw, (0, 0, 1179, 759), 26, fill="#f8fafc", outline="#cbd5e1", width=2) + draw_round_rect(draw, (0, 0, 1179, 74), 26, fill="#182130") + draw.rectangle((0, 40, 1179, 74), fill="#182130") + draw.text((32, 22), "Aman Settings (Required)", font=font(28, bold=True), fill="#f8fafc") + draw.text((970, 24), "Cancel", font=font(20), fill="#cbd5e1") + draw_round_rect(draw, (1055, 14, 1146, 58), 16, fill="#0f766e") + draw.text((1080, 24), "Apply", font=font(20, bold=True), fill="#f8fafc") + + draw_round_rect(draw, (26, 94, 1154, 160), 18, fill="#fff7d6", outline="#facc15") + draw_text_block( + draw, + (48, 112), + ["Aman needs saved settings before it can start recording from the tray."], + fill="#4d3a00", + ) + + draw_round_rect(draw, (26, 188, 268, 734), 20, fill="#eef2f7", outline="#d7dee9") + sections = ["General", "Audio", "Runtime & Models", "Help", "About"] + y = 224 + for index, label in enumerate(sections): + active = index == 0 + fill = "#dbeafe" if active else "#eef2f7" + outline = "#93c5fd" if active else "#eef2f7" + draw_round_rect(draw, (46, y, 248, y + 58), 16, fill=fill, outline=outline) + draw.text((68, y + 16), label, font=font(22, bold=active), fill="#0f172a") + y += 76 + + draw_round_rect(draw, (300, 188, 1154, 734), 20, fill="#ffffff", outline="#d7dee9") + draw_text_block(draw, (332, 220), [], title="General", fill="#0f172a", title_font=font(30, bold=True)) + + labels = [ + ("Trigger hotkey", "Super+m"), + ("Text injection", "Clipboard paste (recommended)"), + ("Transcription language", "Auto detect"), + ("Profile", "Default"), + ] + y = 286 + for label, value in labels: + draw.text((332, y), label, font=font(22, bold=True), fill="#0f172a") + draw_round_rect(draw, (572, y - 8, 1098, y + 38), 14, fill="#f8fafc", outline="#cbd5e1") + draw.text((596, y + 4), value, font=font(20), fill="#334155") + y += 92 + + draw_round_rect(draw, (332, 480, 1098, 612), 18, fill="#f0fdf4", outline="#86efac") + draw_text_block( + draw, + (360, 512), + [ + "Supported first-run path:", + "1. Pick the microphone you want to use.", + "2. Keep the recommended clipboard backend.", + "3. Click Apply and wait for the tray to return to Idle.", + ], + fill="#166534", + body_font=font(20), + ) + + draw_round_rect(draw, (332, 638, 1098, 702), 18, fill="#e0f2fe", outline="#7dd3fc") + draw.text( + (360, 660), + "After setup, put your cursor in a text field and say: hello from Aman", + font=font(20, bold=True), + fill="#155e75", + ) + + background = base.copy() + paste_center(background, window, 70) + return background.convert("RGB") + + +def build_tray_menu() -> Image.Image: + base = draw_background((1280, 900), light=True) + draw = ImageDraw.Draw(base) + draw_round_rect(draw, (0, 0, 1279, 54), 0, fill="#111827") + draw.text((42, 16), "X11 Session", font=font(20, bold=True), fill="#e5e7eb") + draw_round_rect(draw, (1038, 10, 1180, 42), 14, fill="#1f2937", outline="#374151") + draw.text((1068, 17), "Idle", font=font(18, bold=True), fill="#e5e7eb") + + menu = Image.new("RGBA", (420, 520), (255, 255, 255, 255)) + menu_draw = ImageDraw.Draw(menu) + draw_round_rect(menu_draw, (0, 0, 419, 519), 22, fill="#ffffff", outline="#cbd5e1", width=2) + items = [ + "Settings...", + "Help", + "About", + "Pause Aman", + "Reload Config", + "Run Diagnostics", + "Open Config Path", + "Quit", + ] + y = 26 + for label in items: + highlighted = label == "Run Diagnostics" + if highlighted: + draw_round_rect(menu_draw, (16, y - 6, 404, y + 40), 14, fill="#dbeafe") + menu_draw.text((34, y), label, font=font(22, bold=highlighted), fill="#0f172a") + y += 58 + if label in {"About", "Run Diagnostics"}: + menu_draw.line((24, y - 10, 396, y - 10), fill="#e2e8f0", width=2) + + paste_center(base, menu, 118) + return base.convert("RGB") + + +def build_terminal_scene() -> Image.Image: + image = Image.new("RGB", (1280, 720), "#0b1220") + draw = ImageDraw.Draw(image) + draw_round_rect(draw, (100, 80, 1180, 640), 24, fill="#0f172a", outline="#334155", width=2) + draw_round_rect(draw, (100, 80, 1180, 132), 24, fill="#111827") + draw.rectangle((100, 112, 1180, 132), fill="#111827") + draw.text((136, 97), "Terminal", font=font(26, bold=True), fill="#e2e8f0") + draw.text((168, 192), "$ sha256sum -c aman-x11-linux-0.1.0.tar.gz.sha256", font=font(22), fill="#86efac") + draw.text((168, 244), "aman-x11-linux-0.1.0.tar.gz: OK", font=font(22), fill="#cbd5e1") + draw.text((168, 310), "$ tar -xzf aman-x11-linux-0.1.0.tar.gz", font=font(22), fill="#86efac") + draw.text((168, 362), "$ cd aman-x11-linux-0.1.0", font=font(22), fill="#86efac") + draw.text((168, 414), "$ ./install.sh", font=font(22), fill="#86efac") + draw.text((168, 482), "Installed aman.service and started the user service.", font=font(22), fill="#cbd5e1") + draw.text((168, 534), "Waiting for first-run settings...", font=font(22), fill="#7dd3fc") + draw.text((128, 30), "1. Install the portable bundle", font=font(34, bold=True), fill="#f8fafc") + return image + + +def build_editor_scene(*, badge: str | None = None, text: str = "", subtitle: str) -> Image.Image: + image = draw_background((1280, 720), light=True).convert("RGB") + draw = ImageDraw.Draw(image) + draw_round_rect(draw, (84, 64, 1196, 642), 26, fill="#ffffff", outline="#cbd5e1", width=2) + draw_round_rect(draw, (84, 64, 1196, 122), 26, fill="#f8fafc") + draw.rectangle((84, 94, 1196, 122), fill="#f8fafc") + draw.text((122, 84), "Focused editor", font=font(24, bold=True), fill="#0f172a") + draw.text((122, 158), subtitle, font=font(26, bold=True), fill="#0f172a") + draw_round_rect(draw, (996, 80, 1144, 116), 16, fill="#111827") + draw.text((1042, 89), "Idle", font=font(18, bold=True), fill="#e5e7eb") + + if badge: + fill = {"Recording": "#dc2626", "STT": "#2563eb", "AI Processing": "#0f766e"}[badge] + draw_round_rect(draw, (122, 214, 370, 262), 18, fill=fill) + draw.text((150, 225), badge, font=font(24, bold=True), fill="#f8fafc") + + draw_round_rect(draw, (122, 308, 1158, 572), 22, fill="#f8fafc", outline="#d7dee9") + if text: + draw.multiline_text((156, 350), text, font=font(34), fill="#0f172a", spacing=18) + else: + draw.text((156, 366), "Cursor ready for dictation...", font=font(32), fill="#64748b") + return image + + +def build_demo_webm(settings_png: Path, tray_png: Path, output: Path) -> None: + scenes = [ + ("01-install.png", build_terminal_scene(), 3.0), + ("02-settings.png", Image.open(settings_png).resize((1280, 800)).crop((0, 40, 1280, 760)), 4.0), + ("03-tray.png", Image.open(tray_png).resize((1280, 900)).crop((0, 90, 1280, 810)), 3.0), + ( + "04-editor-ready.png", + build_editor_scene( + subtitle="2. Press the hotkey and say: hello from Aman", + text="", + ), + 3.0, + ), + ( + "05-recording.png", + build_editor_scene( + badge="Recording", + subtitle="Tray and status now show recording", + text="", + ), + 1.5, + ), + ( + "06-stt.png", + build_editor_scene( + badge="STT", + subtitle="Aman transcribes the audio locally", + text="", + ), + 1.5, + ), + ( + "07-processing.png", + build_editor_scene( + badge="AI Processing", + subtitle="Cleanup and injection finish automatically", + text="", + ), + 1.5, + ), + ( + "08-result.png", + build_editor_scene( + subtitle="3. The text lands in the focused app", + text="Hello from Aman.", + ), + 4.0, + ), + ] + + with tempfile.TemporaryDirectory() as td: + temp_dir = Path(td) + concat = temp_dir / "scenes.txt" + concat_lines: list[str] = [] + for name, image, duration in scenes: + frame_path = temp_dir / name + image.convert("RGB").save(frame_path, format="PNG") + concat_lines.append(f"file '{frame_path.as_posix()}'") + concat_lines.append(f"duration {duration}") + concat_lines.append(f"file '{(temp_dir / scenes[-1][0]).as_posix()}'") + concat.write_text("\n".join(concat_lines) + "\n", encoding="utf-8") + subprocess.run( + [ + "ffmpeg", + "-y", + "-f", + "concat", + "-safe", + "0", + "-i", + str(concat), + "-vf", + "fps=24,format=yuv420p", + "-c:v", + "libvpx-vp9", + "-b:v", + "0", + "-crf", + "34", + str(output), + ], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + +def main() -> None: + MEDIA_DIR.mkdir(parents=True, exist_ok=True) + settings_png = MEDIA_DIR / "settings-window.png" + tray_png = MEDIA_DIR / "tray-menu.png" + demo_webm = MEDIA_DIR / "first-run-demo.webm" + + build_settings_window().save(settings_png, format="PNG") + build_tray_menu().save(tray_png, format="PNG") + build_demo_webm(settings_png, tray_png, demo_webm) + print(f"wrote {settings_png}") + print(f"wrote {tray_png}") + print(f"wrote {demo_webm}") + + +if __name__ == "__main__": + main() diff --git a/src/aman.py b/src/aman.py index bc8e126..abad3ff 100755 --- a/src/aman.py +++ b/src/aman.py @@ -997,7 +997,17 @@ def _sync_default_model_command(args: argparse.Namespace) -> int: def _build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser( + description=( + "Aman is an X11 dictation daemon for Linux desktops. " + "Use `run` for foreground setup/support, `doctor` for fast preflight checks, " + "and `self-check` for deeper installed-system readiness." + ), + epilog=( + "Supported daily use is the systemd --user service. " + "For recovery: doctor -> self-check -> journalctl -> aman run --verbose." + ), + ) subparsers = parser.add_subparsers(dest="command") run_parser = subparsers.add_parser( @@ -1129,6 +1139,8 @@ def _parse_cli_args(argv: list[str]) -> argparse.Namespace: "version", "init", } + if normalized_argv and normalized_argv[0] in {"-h", "--help"}: + return parser.parse_args(normalized_argv) if not normalized_argv or normalized_argv[0] not in known_commands: normalized_argv = ["run", *normalized_argv] return parser.parse_args(normalized_argv) diff --git a/src/config_ui.py b/src/config_ui.py index b7013ae..a3bcfaa 100644 --- a/src/config_ui.py +++ b/src/config_ui.py @@ -86,7 +86,7 @@ class ConfigWindow: banner.set_show_close_button(False) banner.set_message_type(Gtk.MessageType.WARNING) banner_label = Gtk.Label( - label="Aman needs saved settings before it can start recording." + label="Aman needs saved settings before it can start recording from the tray." ) banner_label.set_xalign(0.0) banner_label.set_line_wrap(True) @@ -219,9 +219,6 @@ class ConfigWindow: grid.attach(profile_label, 0, 5, 1, 1) grid.attach(self._profile_combo, 1, 5, 1, 1) - self._show_notifications_check = Gtk.CheckButton(label="Enable tray notifications") - self._show_notifications_check.set_hexpand(True) - grid.attach(self._show_notifications_check, 1, 6, 1, 1) return grid def _build_audio_page(self) -> Gtk.Widget: @@ -382,13 +379,17 @@ class ConfigWindow: "- Press your hotkey to start recording.\n" "- Press the hotkey again to stop and process.\n" "- Press Esc while recording to cancel.\n\n" - "Model/runtime tips:\n" + "Supported path:\n" + "- Daily use runs through the tray and user service.\n" "- Aman-managed mode (recommended) handles model lifecycle for you.\n" - "- Expert mode lets you set custom Whisper model paths.\n\n" + "- Expert mode keeps custom Whisper paths available for advanced users.\n\n" + "Recovery:\n" + "- Use Run Diagnostics from the tray for a deeper self-check.\n" + "- If that is not enough, run aman doctor, then aman self-check.\n" + "- Next escalations are journalctl --user -u aman and aman run --verbose.\n\n" "Safety tips:\n" "- Keep fact guard enabled to prevent accidental name/number changes.\n" - "- Strict safety blocks output on fact violations.\n\n" - "Use the tray menu for pause/resume, config reload, and diagnostics." + "- Strict safety blocks output on fact violations." ) ) help_text.set_xalign(0.0) @@ -412,7 +413,7 @@ class ConfigWindow: title.set_xalign(0.0) box.pack_start(title, False, False, 0) - subtitle = Gtk.Label(label="Local amanuensis for desktop dictation and rewriting.") + subtitle = Gtk.Label(label="Local amanuensis for X11 desktop dictation and rewriting.") subtitle.set_xalign(0.0) subtitle.set_line_wrap(True) box.pack_start(subtitle, False, False, 0) @@ -445,7 +446,6 @@ class ConfigWindow: if profile not in {"default", "fast", "polished"}: profile = "default" self._profile_combo.set_active_id(profile) - self._show_notifications_check.set_active(bool(self._config.ux.show_notifications)) self._strict_startup_check.set_active(bool(self._config.advanced.strict_startup)) self._safety_enabled_check.set_active(bool(self._config.safety.enabled)) self._safety_strict_check.set_active(bool(self._config.safety.strict)) @@ -570,7 +570,6 @@ class ConfigWindow: cfg.injection.remove_transcription_from_clipboard = self._remove_clipboard_check.get_active() cfg.stt.language = self._language_combo.get_active_id() or "auto" cfg.ux.profile = self._profile_combo.get_active_id() or "default" - cfg.ux.show_notifications = self._show_notifications_check.get_active() cfg.advanced.strict_startup = self._strict_startup_check.get_active() cfg.safety.enabled = self._safety_enabled_check.get_active() cfg.safety.strict = self._safety_strict_check.get_active() and cfg.safety.enabled @@ -623,8 +622,10 @@ def show_help_dialog() -> None: dialog.set_title("Aman Help") dialog.format_secondary_text( "Press your hotkey to record, press it again to process, and press Esc while recording to " - "cancel. Keep fact guard enabled to prevent accidental fact changes. Aman-managed mode is " - "the canonical supported path; expert mode exposes custom Whisper model paths for advanced users." + "cancel. Daily use runs through the tray and user service. Use Run Diagnostics or " + "the doctor -> self-check -> journalctl -> aman run --verbose flow when something breaks. " + "Aman-managed mode is the canonical supported path; expert mode exposes custom Whisper model paths " + "for advanced users." ) dialog.run() dialog.destroy() @@ -642,7 +643,7 @@ def _present_about_dialog(parent) -> None: about = Gtk.AboutDialog(transient_for=parent, modal=True) about.set_program_name("Aman") about.set_version("pre-release") - about.set_comments("Local amanuensis for desktop dictation and rewriting.") + about.set_comments("Local amanuensis for X11 desktop dictation and rewriting.") about.set_license("MIT") about.set_wrap_license(True) about.run() diff --git a/tests/test_aman_cli.py b/tests/test_aman_cli.py index 83766d1..37bd1e6 100644 --- a/tests/test_aman_cli.py +++ b/tests/test_aman_cli.py @@ -115,6 +115,28 @@ class _FakeBenchEditorStage: class AmanCliTests(unittest.TestCase): + def test_parse_cli_args_help_flag_uses_top_level_parser(self): + out = io.StringIO() + + with patch("sys.stdout", out), self.assertRaises(SystemExit) as exc: + aman._parse_cli_args(["--help"]) + + self.assertEqual(exc.exception.code, 0) + rendered = out.getvalue() + self.assertIn("run", rendered) + self.assertIn("doctor", rendered) + self.assertIn("self-check", rendered) + self.assertIn("systemd --user service", rendered) + + def test_parse_cli_args_short_help_flag_uses_top_level_parser(self): + out = io.StringIO() + + with patch("sys.stdout", out), self.assertRaises(SystemExit) as exc: + aman._parse_cli_args(["-h"]) + + self.assertEqual(exc.exception.code, 0) + self.assertIn("self-check", out.getvalue()) + def test_parse_cli_args_defaults_to_run_command(self): args = aman._parse_cli_args(["--dry-run"])