Prepare the 1.0.0 GA release surface
Add the repo-side pieces for milestone 5: MIT licensing, real maintainer and forge metadata, a public support doc, 1.0.0 release notes, release-prep tooling, and CI uploads for the full candidate artifact set. Keep source-tree version surfaces honest by reading the local project version in the CLI and About dialog, and cover the new release-prep plus version-fallback behavior with focused tests. Document where raw validation evidence belongs, add the GA validation rollup, and archive the latest readiness review. Milestone 5 remains open until the forge release page is published and the milestone 2 and 3 matrices are filled with linked manual evidence. Validation: PYTHONPATH=src python3 -m unittest discover -s tests -p 'test_*.py'; PYTHONPATH=src python3 -m unittest tests.test_release_prep tests.test_portable_bundle tests.test_aman_cli tests.test_config_ui; python3 -m py_compile src/*.py tests/*.py; PYTHONPATH=src python3 -m aman version
This commit is contained in:
parent
acfc376845
commit
31a1e069b3
28 changed files with 591 additions and 33 deletions
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
|
|
@ -17,12 +17,8 @@ jobs:
|
|||
python -m pip install --upgrade pip
|
||||
python -m pip install uv build
|
||||
uv sync --extra x11
|
||||
- name: Release quality checks
|
||||
run: make release-check
|
||||
- name: Build Debian package
|
||||
run: make package-deb
|
||||
- name: Build Arch package inputs
|
||||
run: make package-arch
|
||||
- name: Prepare release candidate artifacts
|
||||
run: make release-prep
|
||||
- name: Upload packaging artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
|
@ -30,5 +26,8 @@ jobs:
|
|||
path: |
|
||||
dist/*.whl
|
||||
dist/*.tar.gz
|
||||
dist/*.sha256
|
||||
dist/SHA256SUMS
|
||||
dist/*.deb
|
||||
dist/arch/PKGBUILD
|
||||
dist/arch/*.tar.gz
|
||||
|
|
|
|||
15
CHANGELOG.md
15
CHANGELOG.md
|
|
@ -6,14 +6,19 @@ The format is based on Keep a Changelog and this project follows Semantic Versio
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.0.0] - 2026-03-12
|
||||
|
||||
### Added
|
||||
- Packaging scripts and templates for Debian (`.deb`) and Arch (`PKGBUILD` + source tarball).
|
||||
- Make targets for build/package/release-check workflows.
|
||||
- Persona and distribution policy documentation.
|
||||
- Portable X11 bundle install, upgrade, uninstall, and purge lifecycle.
|
||||
- Distinct `doctor` and `self-check` diagnostics plus a runtime recovery guide.
|
||||
- End-user-first first-run docs, screenshots, demo media, release notes, and a public support document.
|
||||
- `make release-prep` plus `dist/SHA256SUMS` for the GA release artifact set.
|
||||
- X11 GA validation matrices and a final GA validation report surface.
|
||||
|
||||
### Changed
|
||||
- README now documents package-first installation for non-technical users.
|
||||
- Release checklist now includes packaging artifacts.
|
||||
- Project metadata now uses the real maintainer, release URLs, and MIT license.
|
||||
- Packaging templates now point at the public Aman forge location instead of placeholders.
|
||||
- CI now prepares the full release-candidate artifact set instead of only Debian and Arch packaging outputs.
|
||||
|
||||
## [0.1.0] - 2026-02-26
|
||||
|
||||
|
|
|
|||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2026 Thales Maciel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
7
Makefile
7
Makefile
|
|
@ -6,7 +6,7 @@ BUILD_DIR := $(CURDIR)/build
|
|||
RUN_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
|
||||
RUN_CONFIG := $(if $(RUN_ARGS),$(abspath $(firstword $(RUN_ARGS))),$(CONFIG))
|
||||
|
||||
.PHONY: run doctor self-check runtime-check eval-models build-heuristic-dataset sync-default-model check-default-model sync test check build package package-deb package-arch package-portable release-check install-local install-service install clean-dist clean-build clean
|
||||
.PHONY: run doctor self-check runtime-check eval-models build-heuristic-dataset sync-default-model check-default-model sync test check build package package-deb package-arch package-portable release-check release-prep install-local install-service install clean-dist clean-build clean
|
||||
EVAL_DATASET ?= $(CURDIR)/benchmarks/cleanup_dataset.jsonl
|
||||
EVAL_MATRIX ?= $(CURDIR)/benchmarks/model_matrix.small_first.json
|
||||
EVAL_OUTPUT ?= $(CURDIR)/benchmarks/results/latest.json
|
||||
|
|
@ -77,6 +77,11 @@ release-check:
|
|||
$(MAKE) test
|
||||
$(MAKE) build
|
||||
|
||||
release-prep:
|
||||
$(MAKE) release-check
|
||||
$(MAKE) package
|
||||
./scripts/prepare_release.sh
|
||||
|
||||
install-local:
|
||||
$(PYTHON) -m pip install --user ".[x11]"
|
||||
|
||||
|
|
|
|||
11
README.md
11
README.md
|
|
@ -5,6 +5,11 @@ 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.
|
||||
|
||||
Published bundles, checksums, and release notes live on the
|
||||
[`git.thaloco.com` releases page](https://git.thaloco.com/thaloco/aman/releases).
|
||||
Support requests and bug reports go to
|
||||
[`SUPPORT.md`](SUPPORT.md) or `thales@thalesmaciel.com`.
|
||||
|
||||
## Supported Path
|
||||
|
||||
| Surface | Contract |
|
||||
|
|
@ -62,7 +67,7 @@ sudo zypper install -y portaudio gtk3 libayatana-appindicator3-1 python3-gobject
|
|||
|
||||
Then install Aman and run the first dictation:
|
||||
|
||||
1. Verify and extract the portable bundle.
|
||||
1. Download, verify, and extract the portable bundle from the releases page.
|
||||
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.
|
||||
|
|
@ -138,6 +143,8 @@ The canonical end-user guide lives in
|
|||
- Fresh install, upgrade, uninstall, and purge behavior are documented there.
|
||||
- The same guide covers distro-package conflicts and portable-installer
|
||||
recovery steps.
|
||||
- Release-specific notes for `1.0.0` live in
|
||||
[`docs/releases/1.0.0.md`](docs/releases/1.0.0.md).
|
||||
|
||||
## Daily Use and Support
|
||||
|
||||
|
|
@ -162,6 +169,8 @@ The canonical end-user guide lives in
|
|||
|
||||
- Install, upgrade, uninstall: [docs/portable-install.md](docs/portable-install.md)
|
||||
- Runtime recovery and diagnostics: [docs/runtime-recovery.md](docs/runtime-recovery.md)
|
||||
- Release notes: [docs/releases/1.0.0.md](docs/releases/1.0.0.md)
|
||||
- Support and issue reporting: [SUPPORT.md](SUPPORT.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)
|
||||
|
|
|
|||
35
SUPPORT.md
Normal file
35
SUPPORT.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# Support
|
||||
|
||||
Aman supports X11 desktop sessions on mainstream Linux distros with the
|
||||
documented runtime dependencies and `systemd --user`.
|
||||
|
||||
For support, bug reports, or packaging issues, email:
|
||||
|
||||
- `thales@thalesmaciel.com`
|
||||
|
||||
## Include this information
|
||||
|
||||
To make support requests actionable, include:
|
||||
|
||||
- distro and version
|
||||
- whether the session is X11
|
||||
- how Aman was installed: portable bundle, `.deb`, Arch package inputs, or
|
||||
developer install
|
||||
- the Aman version you installed
|
||||
- the output of `aman doctor --config ~/.config/aman/config.json`
|
||||
- the output of `aman self-check --config ~/.config/aman/config.json`
|
||||
- the first relevant lines from `journalctl --user -u aman`
|
||||
- whether the problem still reproduces with
|
||||
`aman run --config ~/.config/aman/config.json --verbose`
|
||||
|
||||
## Supported escalation path
|
||||
|
||||
Use the supported recovery order before emailing:
|
||||
|
||||
1. `aman doctor --config ~/.config/aman/config.json`
|
||||
2. `aman self-check --config ~/.config/aman/config.json`
|
||||
3. `journalctl --user -u aman`
|
||||
4. `aman run --config ~/.config/aman/config.json --verbose`
|
||||
|
||||
The diagnostic IDs and common remediation steps are documented in
|
||||
[`docs/runtime-recovery.md`](docs/runtime-recovery.md).
|
||||
|
|
@ -13,14 +13,21 @@ make package-deb
|
|||
make package-arch
|
||||
make runtime-check
|
||||
make release-check
|
||||
make release-prep
|
||||
```
|
||||
|
||||
- `make package-portable` builds `dist/aman-x11-linux-<version>.tar.gz` plus
|
||||
its `.sha256` file.
|
||||
- `make release-prep` runs `make release-check`, builds the packaged artifacts,
|
||||
and writes `dist/SHA256SUMS` for the release page upload set.
|
||||
- `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.
|
||||
|
||||
For `1.0.0`, the manual publication target is the forge release page at
|
||||
`https://git.thaloco.com/thaloco/aman/releases`, using
|
||||
[`docs/releases/1.0.0.md`](./releases/1.0.0.md) as the release-notes source.
|
||||
|
||||
## Developer setup
|
||||
|
||||
`uv` workflow:
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ Design implications:
|
|||
|
||||
The current release channels are:
|
||||
|
||||
1. Current canonical end-user channel: portable X11 bundle (`aman-x11-linux-<version>.tar.gz`).
|
||||
1. Current canonical end-user channel: portable X11 bundle (`aman-x11-linux-<version>.tar.gz`) published on `https://git.thaloco.com/thaloco/aman/releases`.
|
||||
2. Secondary packaged channel: Debian package (`.deb`) for Ubuntu/Debian users.
|
||||
3. Secondary maintainer channel: Arch package inputs (`PKGBUILD` + source tarball).
|
||||
4. Developer: wheel and sdist from `python -m build`.
|
||||
|
|
@ -75,7 +75,7 @@ variant.
|
|||
|
||||
## Release and Support Policy
|
||||
|
||||
- App versioning follows SemVer (`0.y.z` until API/UX stabilizes).
|
||||
- App versioning follows SemVer starting with `1.0.0` for the X11 GA release.
|
||||
- Config schema versioning is independent (`config_version` in config).
|
||||
- Docs must always separate:
|
||||
- Current release channels
|
||||
|
|
@ -86,5 +86,7 @@ variant.
|
|||
- Daily-use service mode versus manual foreground mode
|
||||
- Canonical recovery sequence
|
||||
- Representative validation families
|
||||
- Public support and issue reporting currently use email only:
|
||||
`thales@thalesmaciel.com`
|
||||
- GA means the support contract, validation evidence, and release surface are
|
||||
consistent. It does not require a native package for every distro.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,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).
|
||||
|
||||
Download published bundles, checksums, and release notes from
|
||||
`https://git.thaloco.com/thaloco/aman/releases`.
|
||||
|
||||
## Supported environment
|
||||
|
||||
- X11 desktop session
|
||||
|
|
@ -42,7 +45,7 @@ sudo zypper install -y portaudio gtk3 libayatana-appindicator3-1 python3-gobject
|
|||
|
||||
## Fresh install
|
||||
|
||||
1. Download `aman-x11-linux-<version>.tar.gz` and `aman-x11-linux-<version>.tar.gz.sha256`.
|
||||
1. Download `aman-x11-linux-<version>.tar.gz` and `aman-x11-linux-<version>.tar.gz.sha256` from the releases page.
|
||||
2. Verify the checksum.
|
||||
3. Extract the bundle.
|
||||
4. Run `install.sh`.
|
||||
|
|
@ -150,3 +153,6 @@ If installation succeeds but runtime behavior is wrong, use the supported recove
|
|||
|
||||
The failure IDs and example outputs for this flow are documented in
|
||||
[`docs/runtime-recovery.md`](./runtime-recovery.md).
|
||||
|
||||
Public support and issue reporting instructions live in
|
||||
[`SUPPORT.md`](../SUPPORT.md).
|
||||
|
|
|
|||
|
|
@ -5,26 +5,27 @@ GA signoff bar. The GA signoff sections are required for `v1.0.0` and later.
|
|||
|
||||
1. Update `CHANGELOG.md` with final release notes.
|
||||
2. Bump `project.version` in `pyproject.toml`.
|
||||
3. Run quality and build gates:
|
||||
- `make release-check`
|
||||
- `make runtime-check`
|
||||
- `make check-default-model`
|
||||
4. Ensure model promotion artifacts are current:
|
||||
3. Ensure model promotion artifacts are current:
|
||||
- `benchmarks/results/latest.json` has the latest `winner_recommendation.name`
|
||||
- `benchmarks/model_artifacts.json` contains that winner with URL + SHA256
|
||||
- `make sync-default-model` (if constants drifted)
|
||||
5. Build packaging artifacts:
|
||||
- `make package`
|
||||
6. Verify artifacts:
|
||||
4. Prepare the release candidate:
|
||||
- `make release-prep`
|
||||
5. Verify artifacts:
|
||||
- `dist/*.whl`
|
||||
- `dist/aman-x11-linux-<version>.tar.gz`
|
||||
- `dist/aman-x11-linux-<version>.tar.gz.sha256`
|
||||
- `dist/SHA256SUMS`
|
||||
- `dist/*.deb`
|
||||
- `dist/arch/PKGBUILD`
|
||||
6. Verify checksums:
|
||||
- `sha256sum -c dist/SHA256SUMS`
|
||||
7. Tag release:
|
||||
- `git tag vX.Y.Z`
|
||||
- `git push origin vX.Y.Z`
|
||||
8. Publish release and upload package artifacts from `dist/`.
|
||||
8. Publish `vX.Y.Z` on `https://git.thaloco.com/thaloco/aman/releases` and upload package artifacts from `dist/`.
|
||||
- Use [`docs/releases/1.0.0.md`](./releases/1.0.0.md) as the release-notes source for the GA release.
|
||||
- Include `dist/SHA256SUMS` with the uploaded artifacts.
|
||||
9. Portable bundle release signoff:
|
||||
- `README.md` points end users to the portable bundle first.
|
||||
- [`docs/portable-install.md`](./portable-install.md) matches the shipped install, upgrade, uninstall, and purge behavior.
|
||||
|
|
@ -49,3 +50,4 @@ GA signoff bar. The GA signoff sections are required for `v1.0.0` and later.
|
|||
- The portable installer, upgrade path, and uninstall path are validated.
|
||||
- End-user docs and release notes match the shipped artifact set.
|
||||
- Public metadata, checksums, and support/reporting surfaces are complete.
|
||||
- [`docs/x11-ga/ga-validation-report.md`](./x11-ga/ga-validation-report.md) links the release page, matrices, and raw evidence files.
|
||||
|
|
|
|||
69
docs/releases/1.0.0.md
Normal file
69
docs/releases/1.0.0.md
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# Aman 1.0.0
|
||||
|
||||
This is the first GA-targeted X11 release for Aman.
|
||||
|
||||
- Canonical release page:
|
||||
`https://git.thaloco.com/thaloco/aman/releases/tag/v1.0.0`
|
||||
- Canonical release index:
|
||||
`https://git.thaloco.com/thaloco/aman/releases`
|
||||
- Support and issue reporting:
|
||||
`thales@thalesmaciel.com`
|
||||
|
||||
## Supported environment
|
||||
|
||||
- X11 desktop sessions only
|
||||
- `systemd --user` for supported daily use
|
||||
- System CPython `3.10`, `3.11`, or `3.12` for the portable installer
|
||||
- Runtime dependencies installed from the distro package manager
|
||||
- Representative validation families: Debian/Ubuntu, Arch, Fedora, openSUSE
|
||||
|
||||
## Artifacts
|
||||
|
||||
The release page should publish:
|
||||
|
||||
- `aman-x11-linux-1.0.0.tar.gz`
|
||||
- `aman-x11-linux-1.0.0.tar.gz.sha256`
|
||||
- `SHA256SUMS`
|
||||
- wheel artifact from `dist/*.whl`
|
||||
- Debian package from `dist/*.deb`
|
||||
- Arch package inputs from `dist/arch/PKGBUILD` and `dist/arch/*.tar.gz`
|
||||
|
||||
## Install, update, and uninstall
|
||||
|
||||
- Install: download the portable bundle and checksum from the release page,
|
||||
verify the checksum, extract the bundle, then run `./install.sh`
|
||||
- Update: extract the newer bundle and run its `./install.sh`
|
||||
- Uninstall: run `~/.local/share/aman/current/uninstall.sh`
|
||||
- Purge uninstall: run `~/.local/share/aman/current/uninstall.sh --purge`
|
||||
|
||||
The full end-user lifecycle is documented in
|
||||
[`docs/portable-install.md`](../portable-install.md).
|
||||
|
||||
## Recovery path
|
||||
|
||||
If the supported path fails, use:
|
||||
|
||||
1. `aman doctor --config ~/.config/aman/config.json`
|
||||
2. `aman self-check --config ~/.config/aman/config.json`
|
||||
3. `journalctl --user -u aman`
|
||||
4. `aman run --config ~/.config/aman/config.json --verbose`
|
||||
|
||||
Reference diagnostics and failure IDs live in
|
||||
[`docs/runtime-recovery.md`](../runtime-recovery.md).
|
||||
|
||||
## Support
|
||||
|
||||
Email `thales@thalesmaciel.com` with:
|
||||
|
||||
- distro and version
|
||||
- X11 confirmation
|
||||
- install channel and Aman version
|
||||
- `aman doctor` output
|
||||
- `aman self-check` output
|
||||
- relevant `journalctl --user -u aman` lines
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Wayland support
|
||||
- Flatpak or snap as the canonical GA path
|
||||
- Native-package parity across every Linux distro
|
||||
|
|
@ -58,3 +58,4 @@ The final step to GA is not more feature work. It is proving that Aman has a rea
|
|||
- Completed validation report for the representative distro families.
|
||||
- Updated release checklist with signed-off GA criteria.
|
||||
- Public support/reporting instructions that match the shipped product.
|
||||
- Raw validation evidence stored in `user-readiness/<linux-timestamp>.md` and linked from the validation matrices.
|
||||
|
|
|
|||
|
|
@ -106,7 +106,12 @@ Any future docs, tray copy, and release notes should point users to this same se
|
|||
[`first-run-review-notes.md`](./first-run-review-notes.md) plus
|
||||
[`user-readiness/1773352170.md`](../../user-readiness/1773352170.md).
|
||||
- [ ] [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.
|
||||
Implementation landed on 2026-03-12: repo metadata now uses the real
|
||||
maintainer and forge URLs, `LICENSE`, `SUPPORT.md`, `docs/releases/1.0.0.md`,
|
||||
`make release-prep`, and [`ga-validation-report.md`](./ga-validation-report.md)
|
||||
now exist. Leave this milestone open until the release page is published and
|
||||
the milestone 2 and 3 validation matrices are filled with linked raw
|
||||
evidence.
|
||||
|
||||
## Cross-milestone acceptance scenarios
|
||||
|
||||
|
|
|
|||
54
docs/x11-ga/ga-validation-report.md
Normal file
54
docs/x11-ga/ga-validation-report.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# GA Validation Report
|
||||
|
||||
This document is the final rollup for the X11 GA release. It does not replace
|
||||
the underlying evidence sources. It links them and records the final signoff
|
||||
state.
|
||||
|
||||
## Where to put validation evidence
|
||||
|
||||
- Put raw manual validation notes in `user-readiness/<linux-timestamp>.md`.
|
||||
- Use one timestamped file per validation session, distro pass, or reviewer
|
||||
handoff.
|
||||
- In the raw evidence file, record:
|
||||
- distro and version
|
||||
- reviewer
|
||||
- date
|
||||
- release artifact version
|
||||
- commands run
|
||||
- pass/fail results
|
||||
- failure details and recovery outcome
|
||||
- Reference those timestamped files from the `Notes` columns in:
|
||||
- [`portable-validation-matrix.md`](./portable-validation-matrix.md)
|
||||
- [`runtime-validation-report.md`](./runtime-validation-report.md)
|
||||
|
||||
## Release metadata
|
||||
|
||||
- Release version: `1.0.0`
|
||||
- Release page:
|
||||
`https://git.thaloco.com/thaloco/aman/releases/tag/v1.0.0`
|
||||
- Support channel: `thales@thalesmaciel.com`
|
||||
- License: MIT
|
||||
|
||||
## Evidence sources
|
||||
|
||||
- Portable lifecycle matrix:
|
||||
[`portable-validation-matrix.md`](./portable-validation-matrix.md)
|
||||
- Runtime reliability matrix:
|
||||
[`runtime-validation-report.md`](./runtime-validation-report.md)
|
||||
- First-run review:
|
||||
[`first-run-review-notes.md`](./first-run-review-notes.md)
|
||||
- Raw evidence archive:
|
||||
[`user-readiness/README.md`](../../user-readiness/README.md)
|
||||
- Release notes:
|
||||
[`docs/releases/1.0.0.md`](../releases/1.0.0.md)
|
||||
|
||||
## Final signoff status
|
||||
|
||||
| Area | Status | Evidence |
|
||||
| --- | --- | --- |
|
||||
| Milestone 2 portable lifecycle | Pending | Fill `portable-validation-matrix.md` and link raw timestamped evidence |
|
||||
| Milestone 3 runtime reliability | Pending | Fill `runtime-validation-report.md` and link raw timestamped evidence |
|
||||
| Milestone 4 first-run UX/docs | Complete | `first-run-review-notes.md` and `user-readiness/1773352170.md` |
|
||||
| Release metadata and support surface | Repo-complete | `LICENSE`, `SUPPORT.md`, `pyproject.toml`, packaging templates |
|
||||
| Release artifacts and checksums | Repo-complete | `make release-prep`, `dist/SHA256SUMS`, `docs/releases/1.0.0.md` |
|
||||
| Published release page | Pending | Publish `v1.0.0` on the forge release page and attach the prepared artifacts |
|
||||
|
|
@ -20,6 +20,9 @@ Completed on 2026-03-12:
|
|||
These rows must be filled with real results before milestone 2 can be closed as
|
||||
fully complete for GA evidence.
|
||||
|
||||
Store raw evidence for each distro pass in `user-readiness/<linux-timestamp>.md`
|
||||
and reference that file in the `Notes` column.
|
||||
|
||||
| Distro family | Fresh install | First service start | Upgrade | Uninstall | Reinstall | Reboot or service restart | Missing dependency recovery | Conflict with prior package install | Reviewer | Status | Notes |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| Debian/Ubuntu | Pending | Pending | Pending | Pending | Pending | Pending | Pending | Pending | Pending | Pending | |
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ Completed on 2026-03-12:
|
|||
These rows must be filled with release-specific evidence before milestone 3 can
|
||||
be closed as complete for GA signoff.
|
||||
|
||||
Store raw evidence for each runtime validation pass in
|
||||
`user-readiness/<linux-timestamp>.md` and reference that file in the `Notes`
|
||||
column.
|
||||
|
||||
| Scenario | Debian/Ubuntu | Arch | Fedora | openSUSE | Reviewer | Status | Notes |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| Service restart after a successful install | Pending | Pending | Pending | Pending | Pending | Pending | Verify `systemctl --user restart aman` returns to the tray/ready state |
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
# Maintainer: Aman Maintainers <maintainers@example.com>
|
||||
# Maintainer: Thales Maciel <thales@thalesmaciel.com>
|
||||
pkgname=aman
|
||||
pkgver=__VERSION__
|
||||
pkgrel=1
|
||||
pkgdesc="Local amanuensis daemon for X11 desktops"
|
||||
arch=('x86_64')
|
||||
url="https://github.com/example/aman"
|
||||
url="https://git.thaloco.com/thaloco/aman"
|
||||
license=('MIT')
|
||||
depends=('python' 'python-pip' 'python-setuptools' 'portaudio' 'gtk3' 'libayatana-appindicator' 'python-gobject' 'python-xlib')
|
||||
makedepends=('python-build' 'python-installer' 'python-wheel')
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Version: __VERSION__
|
|||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: __ARCH__
|
||||
Maintainer: Aman Maintainers <maintainers@example.com>
|
||||
Maintainer: Thales Maciel <thales@thalesmaciel.com>
|
||||
Depends: python3, python3-venv, python3-gi, python3-xlib, libportaudio2, gir1.2-gtk-3.0, libayatana-appindicator3-1
|
||||
Description: Aman local amanuensis daemon for X11 desktops
|
||||
Aman records microphone input, transcribes speech, optionally rewrites output,
|
||||
|
|
|
|||
|
|
@ -4,10 +4,26 @@ build-backend = "setuptools.build_meta"
|
|||
|
||||
[project]
|
||||
name = "aman"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0"
|
||||
description = "X11 STT daemon with faster-whisper and optional AI cleanup"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
license = { file = "LICENSE" }
|
||||
authors = [
|
||||
{ name = "Thales Maciel", email = "thales@thalesmaciel.com" },
|
||||
]
|
||||
maintainers = [
|
||||
{ name = "Thales Maciel", email = "thales@thalesmaciel.com" },
|
||||
]
|
||||
classifiers = [
|
||||
"Environment :: X11 Applications",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: POSIX :: Linux",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
]
|
||||
dependencies = [
|
||||
"faster-whisper",
|
||||
"llama-cpp-python",
|
||||
|
|
@ -26,6 +42,12 @@ x11 = [
|
|||
]
|
||||
wayland = []
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://git.thaloco.com/thaloco/aman"
|
||||
Source = "https://git.thaloco.com/thaloco/aman"
|
||||
Releases = "https://git.thaloco.com/thaloco/aman/releases"
|
||||
Support = "https://git.thaloco.com/thaloco/aman"
|
||||
|
||||
[tool.setuptools]
|
||||
package-dir = {"" = "src"}
|
||||
packages = ["engine", "stages"]
|
||||
|
|
|
|||
63
scripts/prepare_release.sh
Executable file
63
scripts/prepare_release.sh
Executable file
|
|
@ -0,0 +1,63 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
source "${SCRIPT_DIR}/package_common.sh"
|
||||
|
||||
require_command sha256sum
|
||||
|
||||
VERSION="$(project_version)"
|
||||
PACKAGE_NAME="$(project_name)"
|
||||
DIST_DIR="${DIST_DIR:-${ROOT_DIR}/dist}"
|
||||
ARCH_DIST_DIR="${DIST_DIR}/arch"
|
||||
PORTABLE_TARBALL="${DIST_DIR}/${PACKAGE_NAME}-x11-linux-${VERSION}.tar.gz"
|
||||
PORTABLE_CHECKSUM="${PORTABLE_TARBALL}.sha256"
|
||||
ARCH_TARBALL="${ARCH_DIST_DIR}/${PACKAGE_NAME}-${VERSION}.tar.gz"
|
||||
ARCH_PKGBUILD="${ARCH_DIST_DIR}/PKGBUILD"
|
||||
SHA256SUMS_PATH="${DIST_DIR}/SHA256SUMS"
|
||||
|
||||
require_file() {
|
||||
local path="$1"
|
||||
if [[ -f "${path}" ]]; then
|
||||
return
|
||||
fi
|
||||
echo "missing required release artifact: ${path}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_file "${PORTABLE_TARBALL}"
|
||||
require_file "${PORTABLE_CHECKSUM}"
|
||||
require_file "${ARCH_TARBALL}"
|
||||
require_file "${ARCH_PKGBUILD}"
|
||||
|
||||
shopt -s nullglob
|
||||
wheels=("${DIST_DIR}/${PACKAGE_NAME//-/_}-${VERSION}-"*.whl)
|
||||
debs=("${DIST_DIR}/${PACKAGE_NAME}_${VERSION}_"*.deb)
|
||||
shopt -u nullglob
|
||||
|
||||
if [[ "${#wheels[@]}" -eq 0 ]]; then
|
||||
echo "missing required release artifact: wheel for ${PACKAGE_NAME} ${VERSION}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${#debs[@]}" -eq 0 ]]; then
|
||||
echo "missing required release artifact: deb for ${PACKAGE_NAME} ${VERSION}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mapfile -t published_files < <(
|
||||
cd "${DIST_DIR}" && find . -type f ! -name "SHA256SUMS" -print | LC_ALL=C sort
|
||||
)
|
||||
|
||||
if [[ "${#published_files[@]}" -eq 0 ]]; then
|
||||
echo "no published files found in ${DIST_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(
|
||||
cd "${DIST_DIR}"
|
||||
rm -f "SHA256SUMS"
|
||||
sha256sum "${published_files[@]}" >"SHA256SUMS"
|
||||
)
|
||||
|
||||
echo "generated ${SHA256SUMS_PATH}"
|
||||
14
src/aman.py
14
src/aman.py
|
|
@ -770,7 +770,21 @@ def _build_editor_stage(cfg: Config, *, verbose: bool) -> LlamaEditorStage:
|
|||
)
|
||||
|
||||
|
||||
def _local_project_version() -> str | None:
|
||||
pyproject_path = Path(__file__).resolve().parents[1] / "pyproject.toml"
|
||||
if not pyproject_path.exists():
|
||||
return None
|
||||
for line in pyproject_path.read_text(encoding="utf-8").splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped.startswith('version = "'):
|
||||
return stripped.split('"')[1]
|
||||
return None
|
||||
|
||||
|
||||
def _app_version() -> str:
|
||||
local_version = _local_project_version()
|
||||
if local_version:
|
||||
return local_version
|
||||
try:
|
||||
return importlib.metadata.version("aman")
|
||||
except importlib.metadata.PackageNotFoundError:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import importlib.metadata
|
||||
import logging
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
|
|
@ -642,9 +643,22 @@ def show_about_dialog() -> None:
|
|||
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_version(_app_version())
|
||||
about.set_comments("Local amanuensis for X11 desktop dictation and rewriting.")
|
||||
about.set_license("MIT")
|
||||
about.set_wrap_license(True)
|
||||
about.run()
|
||||
about.destroy()
|
||||
|
||||
|
||||
def _app_version() -> str:
|
||||
pyproject_path = Path(__file__).resolve().parents[1] / "pyproject.toml"
|
||||
if pyproject_path.exists():
|
||||
for line in pyproject_path.read_text(encoding="utf-8").splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped.startswith('version = "'):
|
||||
return stripped.split('"')[1]
|
||||
try:
|
||||
return importlib.metadata.version("aman")
|
||||
except importlib.metadata.PackageNotFoundError:
|
||||
return "unknown"
|
||||
|
|
|
|||
|
|
@ -242,6 +242,14 @@ class AmanCliTests(unittest.TestCase):
|
|||
self.assertEqual(exit_code, 0)
|
||||
self.assertEqual(out.getvalue().strip(), "1.2.3")
|
||||
|
||||
def test_app_version_prefers_local_pyproject_version(self):
|
||||
pyproject_text = '[project]\nversion = "9.9.9"\n'
|
||||
|
||||
with patch.object(aman.Path, "exists", return_value=True), patch.object(
|
||||
aman.Path, "read_text", return_value=pyproject_text
|
||||
), patch("aman.importlib.metadata.version", return_value="1.0.0"):
|
||||
self.assertEqual(aman._app_version(), "9.9.9")
|
||||
|
||||
def test_doctor_command_json_output_and_exit_code(self):
|
||||
report = DiagnosticReport(
|
||||
checks=[DiagnosticCheck(id="config.load", status="ok", message="ok", next_step="")]
|
||||
|
|
|
|||
|
|
@ -11,9 +11,11 @@ from config import Config
|
|||
from config_ui import (
|
||||
RUNTIME_MODE_EXPERT,
|
||||
RUNTIME_MODE_MANAGED,
|
||||
_app_version,
|
||||
apply_canonical_runtime_defaults,
|
||||
infer_runtime_mode,
|
||||
)
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
class ConfigUiRuntimeModeTests(unittest.TestCase):
|
||||
|
|
@ -38,6 +40,14 @@ class ConfigUiRuntimeModeTests(unittest.TestCase):
|
|||
self.assertFalse(cfg.models.allow_custom_models)
|
||||
self.assertEqual(cfg.models.whisper_model_path, "")
|
||||
|
||||
def test_app_version_prefers_local_pyproject_version(self):
|
||||
pyproject_text = '[project]\nversion = "9.9.9"\n'
|
||||
|
||||
with patch("config_ui.Path.exists", return_value=True), patch(
|
||||
"config_ui.Path.read_text", return_value=pyproject_text
|
||||
), patch("config_ui.importlib.metadata.version", return_value="1.0.0"):
|
||||
self.assertEqual(_app_version(), "9.9.9")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
88
tests/test_release_prep.py
Normal file
88
tests/test_release_prep.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def _project_version() -> str:
|
||||
for line in (ROOT / "pyproject.toml").read_text(encoding="utf-8").splitlines():
|
||||
if line.startswith('version = "'):
|
||||
return line.split('"')[1]
|
||||
raise RuntimeError("project version not found")
|
||||
|
||||
|
||||
def _write_file(path: Path, content: str) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content, encoding="utf-8")
|
||||
|
||||
|
||||
class ReleasePrepScriptTests(unittest.TestCase):
|
||||
def test_prepare_release_writes_sha256sums_for_expected_artifacts(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
dist_dir = tmp_path / "dist"
|
||||
arch_dir = dist_dir / "arch"
|
||||
version = _project_version()
|
||||
|
||||
_write_file(dist_dir / f"aman-{version}-py3-none-any.whl", "wheel\n")
|
||||
_write_file(dist_dir / f"aman-x11-linux-{version}.tar.gz", "portable\n")
|
||||
_write_file(dist_dir / f"aman-x11-linux-{version}.tar.gz.sha256", "checksum\n")
|
||||
_write_file(dist_dir / f"aman_{version}_amd64.deb", "deb\n")
|
||||
_write_file(arch_dir / "PKGBUILD", "pkgbuild\n")
|
||||
_write_file(arch_dir / f"aman-{version}.tar.gz", "arch-src\n")
|
||||
|
||||
env = os.environ.copy()
|
||||
env["DIST_DIR"] = str(dist_dir)
|
||||
|
||||
subprocess.run(
|
||||
["bash", "./scripts/prepare_release.sh"],
|
||||
cwd=ROOT,
|
||||
env=env,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
sha256sums = (dist_dir / "SHA256SUMS").read_text(encoding="utf-8")
|
||||
self.assertIn(f"./aman-{version}-py3-none-any.whl", sha256sums)
|
||||
self.assertIn(f"./aman-x11-linux-{version}.tar.gz", sha256sums)
|
||||
self.assertIn(f"./aman-x11-linux-{version}.tar.gz.sha256", sha256sums)
|
||||
self.assertIn(f"./aman_{version}_amd64.deb", sha256sums)
|
||||
self.assertIn(f"./arch/PKGBUILD", sha256sums)
|
||||
self.assertIn(f"./arch/aman-{version}.tar.gz", sha256sums)
|
||||
|
||||
def test_prepare_release_fails_when_expected_artifact_is_missing(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
dist_dir = tmp_path / "dist"
|
||||
arch_dir = dist_dir / "arch"
|
||||
version = _project_version()
|
||||
|
||||
_write_file(dist_dir / f"aman-{version}-py3-none-any.whl", "wheel\n")
|
||||
_write_file(dist_dir / f"aman-x11-linux-{version}.tar.gz", "portable\n")
|
||||
_write_file(dist_dir / f"aman-x11-linux-{version}.tar.gz.sha256", "checksum\n")
|
||||
_write_file(arch_dir / "PKGBUILD", "pkgbuild\n")
|
||||
_write_file(arch_dir / f"aman-{version}.tar.gz", "arch-src\n")
|
||||
|
||||
env = os.environ.copy()
|
||||
env["DIST_DIR"] = str(dist_dir)
|
||||
|
||||
result = subprocess.run(
|
||||
["bash", "./scripts/prepare_release.sh"],
|
||||
cwd=ROOT,
|
||||
env=env,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("missing required release artifact", result.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
105
user-readiness/1773354709.md
Normal file
105
user-readiness/1773354709.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# User Readiness Review
|
||||
|
||||
- Date: 2026-03-12
|
||||
- Reviewer: Codex
|
||||
- Scope: documentation, packaged artifacts, and CLI help surface
|
||||
- Live run status: documentation-and-artifact based plus `python3 -m aman --help`; I did not launch the GTK daemon in a live X11 session
|
||||
|
||||
## Verdict
|
||||
|
||||
A new X11 user can now tell what Aman is for, how to install it, what success
|
||||
looks like, and what recovery path to follow when the first run goes wrong.
|
||||
That is a real improvement over an internal-looking project surface.
|
||||
|
||||
It still does not feel fully distribution-ready. The first-contact and
|
||||
onboarding story are strong, but the public release and validation story still
|
||||
looks in-progress rather than complete.
|
||||
|
||||
## What A New User Would Experience
|
||||
|
||||
A new user lands on a README that immediately states the product, the supported
|
||||
environment, the install path, the expected first dictation result, and the
|
||||
recovery flow. The quickstart is concrete, with distro-specific dependency
|
||||
commands, screenshots, demo media, and a plain-language description of what the
|
||||
tray and injected text should do. The install and support docs stay aligned
|
||||
with that same path, which keeps the project from feeling like it requires
|
||||
author hand-holding.
|
||||
|
||||
Confidence drops once the user looks for proof that the release is actually
|
||||
published and validated. The repo-visible evidence still shows pending GA
|
||||
publication work and pending manual distro validation, so the project reads as
|
||||
"nearly ready" instead of "safe to recommend."
|
||||
|
||||
## Top Blockers
|
||||
|
||||
1. The public release trust surface is still incomplete. The supported install
|
||||
path depends on a published release page, but
|
||||
`docs/x11-ga/ga-validation-report.md` still marks `Published release page`
|
||||
as `Pending`.
|
||||
2. The artifact story still reads as pre-release. `docs/releases/1.0.0.md`
|
||||
says the release page "should publish" the artifacts, and local `dist/`
|
||||
contents are still `0.1.0` wheel and tarball outputs rather than a visible
|
||||
`1.0.0` portable bundle plus checksum set.
|
||||
3. Supported-distro validation is still promise, not proof.
|
||||
`docs/x11-ga/portable-validation-matrix.md` and
|
||||
`docs/x11-ga/runtime-validation-report.md` show good automated coverage, but
|
||||
every manual Debian/Ubuntu, Arch, Fedora, and openSUSE row is still
|
||||
`Pending`.
|
||||
4. The top-level CLI help still mixes end-user and maintainer workflows.
|
||||
Commands like `bench`, `eval-models`, `build-heuristic-dataset`, and
|
||||
`sync-default-model` make the help surface feel more internal than a focused
|
||||
desktop product when a user checks `--help`.
|
||||
|
||||
## What Is Already Working
|
||||
|
||||
- A new user can tell what Aman is and who it is for from `README.md`.
|
||||
- A new user can follow one obvious install path without being pushed into
|
||||
developer tooling.
|
||||
- A new user can see screenshots, demo media, expected tray states, and a
|
||||
sample dictated phrase before installing.
|
||||
- A new user gets a coherent support and recovery story through `doctor`,
|
||||
`self-check`, `journalctl`, and `aman run --verbose`.
|
||||
- The repo now has visible trust signals such as a real `LICENSE`,
|
||||
maintainer/contact metadata, and a public support document.
|
||||
|
||||
## Quick Wins
|
||||
|
||||
- Publish the `1.0.0` release page with the portable bundle, checksum files,
|
||||
and final release notes, then replace every `Pending` or "should publish"
|
||||
wording with completed wording.
|
||||
- Make the local artifact story match the docs by generating or checking in the
|
||||
expected `1.0.0` release outputs referenced by the release documentation.
|
||||
- Fill at least one full manual validation pass per supported distro family and
|
||||
link each timestamped evidence file into the two GA matrices.
|
||||
- Narrow the top-level CLI help to the supported user commands, or clearly
|
||||
label maintainer-only commands so the main recovery path stays prominent.
|
||||
|
||||
## What Would Make It Distribution-Ready
|
||||
|
||||
Before broader distribution, it needs a real published `1.0.0` release page,
|
||||
artifact and checksum evidence that matches the docs, linked manual validation
|
||||
results across the supported distro families, and a slightly cleaner user-facing
|
||||
CLI surface. Once those land, the project will look like a maintained product
|
||||
rather than a well-documented release candidate.
|
||||
|
||||
## Evidence
|
||||
|
||||
### Commands Run
|
||||
|
||||
- `bash /home/thales/projects/personal/skills-exploration/.agents/skills/user-readiness-review/scripts/collect_readiness_context.sh`
|
||||
- `PYTHONPATH=src python3 -m aman --help`
|
||||
- `find docs/media -maxdepth 1 -type f | sort`
|
||||
- `ls -la dist`
|
||||
|
||||
### Files Reviewed
|
||||
|
||||
- `README.md`
|
||||
- `docs/portable-install.md`
|
||||
- `SUPPORT.md`
|
||||
- `pyproject.toml`
|
||||
- `CHANGELOG.md`
|
||||
- `docs/releases/1.0.0.md`
|
||||
- `docs/persona-and-distribution.md`
|
||||
- `docs/x11-ga/ga-validation-report.md`
|
||||
- `docs/x11-ga/portable-validation-matrix.md`
|
||||
- `docs/x11-ga/runtime-validation-report.md`
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# User Readiness Reports
|
||||
# User Readiness Reports And Validation Evidence
|
||||
|
||||
Each Markdown file in this directory is a user readiness report for the
|
||||
project.
|
||||
|
|
@ -6,3 +6,10 @@ project.
|
|||
The filename title is a Linux timestamp. In practice, a report named
|
||||
`1773333303.md` corresponds to a report generated at Unix timestamp
|
||||
`1773333303`.
|
||||
|
||||
This directory also stores raw manual validation evidence for GA signoff.
|
||||
Use one timestamped file per validation session and reference those files from:
|
||||
|
||||
- `docs/x11-ga/portable-validation-matrix.md`
|
||||
- `docs/x11-ga/runtime-validation-report.md`
|
||||
- `docs/x11-ga/ga-validation-report.md`
|
||||
|
|
|
|||
2
uv.lock
generated
2
uv.lock
generated
|
|
@ -8,7 +8,7 @@ resolution-markers = [
|
|||
|
||||
[[package]]
|
||||
name = "aman"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "faster-whisper" },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue