Compare commits
2 Commits
3a051c8012
...
f19dc7c26c
| Author | SHA1 | Date | |
|---|---|---|---|
| f19dc7c26c | |||
| b7de938919 |
492
DEV-CHEATSHEET.md
Normal file
492
DEV-CHEATSHEET.md
Normal file
@ -0,0 +1,492 @@
|
|||||||
|
# Dev Cheat Sheet
|
||||||
|
|
||||||
|
Concise command reference for day-to-day work across Git, Python, Rust, Node, Tauri, and .NET.
|
||||||
|
|
||||||
|
Use this as a practical command sheet, not as a full tutorial.
|
||||||
|
|
||||||
|
## Table of Contents 📑
|
||||||
|
|
||||||
|
- [Setup](#setup)
|
||||||
|
- [Git](#git)
|
||||||
|
- [PAQ-Next (Advanced Compression)](#paq-next-advanced-compression)
|
||||||
|
- [Python](#python)
|
||||||
|
- [Node and npm](#node-and-npm)
|
||||||
|
- [Rust](#rust)
|
||||||
|
- [Tauri v2](#tauri-v2)
|
||||||
|
- [Dotnet and CSharp](#dotnet-and-csharp)
|
||||||
|
- [Common Output Folder Pattern](#common-output-folder-pattern)
|
||||||
|
- [Docker](#docker)
|
||||||
|
- [Useful Cleanup Commands](#useful-cleanup-commands)
|
||||||
|
- [Recommended Habits](#recommended-habits)
|
||||||
|
- [Workflows and SDT DevTool](#workflows-and-sdt-devtool)
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### Setup w64devkit for C++ (PAQ-Next)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:PATH = "f:\w64devkit\w64devkit\bin;" + $env:PATH
|
||||||
|
```
|
||||||
|
|
||||||
|
Adds GCC/G++ and Unix tools to the current PowerShell session.
|
||||||
|
|
||||||
|
### Probe toolchain availability
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python scripts/diag.py probe --tool dotnet --json
|
||||||
|
python scripts/diag.py probe --tool python --json
|
||||||
|
python scripts/diag.py probe --tool node --json
|
||||||
|
python scripts/diag.py probe --tool npm --json
|
||||||
|
python scripts/diag.py probe --tool cargo --json
|
||||||
|
python scripts/diag.py probe --tool tauri --json
|
||||||
|
python scripts/diag.py probe --tool git --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Checks if required tools are in the current path.
|
||||||
|
|
||||||
|
### Shell bootstrap (doctor)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python scripts/dev_shell.py doctor
|
||||||
|
```
|
||||||
|
|
||||||
|
Analyzes the environment and reports missing components or configuration errors.
|
||||||
|
|
||||||
|
[Back to Top](#dev-cheat-sheet)
|
||||||
|
|
||||||
|
## Git
|
||||||
|
|
||||||
|
### Check where you are
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git status --short --branch
|
||||||
|
git remote -v
|
||||||
|
git branch -vv
|
||||||
|
|
||||||
|
# See which files are ignored by git
|
||||||
|
git clean -nd
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stashing
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git stash push -m "Description"
|
||||||
|
git stash pop
|
||||||
|
git stash drop # Discard
|
||||||
|
git stash clear # Wipe all stashes
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remotes & Pushing - THE FLAGS
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Safer 'force push' (fails if remote has moved)
|
||||||
|
git push --force-with-lease
|
||||||
|
|
||||||
|
# Push all branches and all tags at once
|
||||||
|
git push --all
|
||||||
|
git push --tags
|
||||||
|
|
||||||
|
# Delete a remote branch
|
||||||
|
git push origin --delete branch-name
|
||||||
|
|
||||||
|
# Set upstream for current branch (first push)
|
||||||
|
git push -u origin branch-name
|
||||||
|
```
|
||||||
|
|
||||||
|
### History & Analysis
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Graphical log in terminal
|
||||||
|
git log --graph --oneline --decorate --all
|
||||||
|
|
||||||
|
# Search commit messages
|
||||||
|
git log --grep="bug fix"
|
||||||
|
|
||||||
|
# Find which commit introduced a string
|
||||||
|
git log -S "secret_key"
|
||||||
|
|
||||||
|
# See changes per line (last change for every line)
|
||||||
|
git blame file_path.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Git Repository Cleanup
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Fast cleanup of untracked files
|
||||||
|
git clean -fd
|
||||||
|
|
||||||
|
# Prune stale remote tracking branches
|
||||||
|
git fetch origin --prune
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
[🔼 Back to Top](#dev-cheat-sheet)
|
||||||
|
|
||||||
|
## PAQ-Next (Advanced Compression)
|
||||||
|
|
||||||
|
### Compile PAQ-Next (C++)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Add toolchain first (if not in path)
|
||||||
|
$env:PATH = "f:\w64devkit\w64devkit\bin;" + $env:PATH
|
||||||
|
|
||||||
|
# Build from root of src-paq-next/
|
||||||
|
g++ -O3 -mavx2 -Iinclude cli/main.cpp src/archiver.cpp -o paq_next_cli.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
Compiles with AVX2 optimizations and include-path support.
|
||||||
|
|
||||||
|
### Run PAQ-Next Compression
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Compress a folder
|
||||||
|
.\paq_next_cli.exe c <source_folder>
|
||||||
|
|
||||||
|
# Compress a folder to a specific file
|
||||||
|
.\paq_next_cli.exe c <source_folder> <output_name>.paq
|
||||||
|
|
||||||
|
# Extract an archive
|
||||||
|
.\paq_next_cli.exe x <archive>.paq <destination_folder>
|
||||||
|
```
|
||||||
|
|
||||||
|
[Back to Top](#dev-cheat-sheet)
|
||||||
|
|
||||||
|
## Python
|
||||||
|
|
||||||
|
### Package Management - THE FLAGS
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Install from requirements with upgrades
|
||||||
|
pip install -r requirements.txt --upgrade
|
||||||
|
|
||||||
|
# Install without saving to cache (Great for CI/Low disk)
|
||||||
|
pip install -r requirements.txt --no-cache-dir
|
||||||
|
|
||||||
|
# Target a specific directory
|
||||||
|
pip install -t ./lib <package>
|
||||||
|
|
||||||
|
# Search local packages for a string
|
||||||
|
pip list | Select-String "crypto"
|
||||||
|
|
||||||
|
# See everything about a package
|
||||||
|
pip show <package-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Quality & Performance
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Parallel testing
|
||||||
|
pip install pytest-xdist
|
||||||
|
pytest -n auto # Use all CPU cores
|
||||||
|
|
||||||
|
# Benchmark a script
|
||||||
|
python -m cProfile -s time script.py
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
[🔼 Back to Top](#dev-cheat-sheet)
|
||||||
|
|
||||||
|
## Node and npm
|
||||||
|
|
||||||
|
### Dependency Management - THE FLAGS
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Force install (when versions conflict)
|
||||||
|
npm install --force
|
||||||
|
|
||||||
|
# Ignore peer dependency errors (Common in React)
|
||||||
|
npm install --legacy-peer-deps
|
||||||
|
|
||||||
|
# Production only (Skip devDependencies)
|
||||||
|
npm install --production
|
||||||
|
|
||||||
|
# Deep security audit and auto-fix
|
||||||
|
npm audit fix --force
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scripts & Performance
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Build with specific log level
|
||||||
|
npm run build --loglevel silent
|
||||||
|
|
||||||
|
# Pass custom flags directly to a script
|
||||||
|
npm run dev -- --host 0.0.0.0 --port 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
[🔼 Back to Top](#dev-cheat-sheet)
|
||||||
|
|
||||||
|
## Rust
|
||||||
|
|
||||||
|
### Essential Workflow - THE FLAGS
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Fast check (Skip code generation)
|
||||||
|
cargo check --all-targets --all-features
|
||||||
|
|
||||||
|
# Build with specific features enabled
|
||||||
|
cargo build --features "feature-a,feature-b"
|
||||||
|
|
||||||
|
# Build without default features
|
||||||
|
cargo build --no-default-features
|
||||||
|
|
||||||
|
# Cross-compilation (Requires target in toolchain)
|
||||||
|
cargo build --target x86_64-apple-darwin
|
||||||
|
cargo build --target aarch64-unknown-linux-gnu
|
||||||
|
```
|
||||||
|
|
||||||
|
### Diagnostics & Performance
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# See exactly why a build is slow
|
||||||
|
cargo build --timings
|
||||||
|
|
||||||
|
# Run specific test by name/substring
|
||||||
|
cargo test test_function_name
|
||||||
|
|
||||||
|
# Open local docs for all dependencies
|
||||||
|
cargo doc --open --no-deps
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
[🔼 Back to Top](#dev-cheat-sheet)
|
||||||
|
|
||||||
|
## Tauri v2
|
||||||
|
|
||||||
|
### Initialize Mobile (REQUIRED for first run)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# This creates the android/ios project folders in src-tauri/gen
|
||||||
|
npm run tauri android init
|
||||||
|
npm run tauri ios init
|
||||||
|
```
|
||||||
|
|
||||||
|
### Desktop Builds - THE FLAGS
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Fast build (Binary only)
|
||||||
|
npm run tauri build -- --no-bundle
|
||||||
|
|
||||||
|
# Explicit config file (Great for multi-env)
|
||||||
|
npm run tauri build -- --config path/to/config.json
|
||||||
|
|
||||||
|
# Verbose output for debugging packaging
|
||||||
|
npm run tauri build -- --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mobile Builds
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Debug mode on Android device (or Emulator)
|
||||||
|
npm run tauri android dev
|
||||||
|
|
||||||
|
# Production APK/AAB generation
|
||||||
|
npm run tauri android build -- --target aarch64
|
||||||
|
```
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# System diagnostic report
|
||||||
|
npm run tauri info
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
[🔼 Back to Top](#dev-cheat-sheet)
|
||||||
|
|
||||||
|
### Frontend-only build
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Builds the web frontend assets (the `dist/` folder) without launching or packaging the desktop app.
|
||||||
|
|
||||||
|
## Dotnet and CSharp
|
||||||
|
|
||||||
|
### Restore & Build - THE FLAGS
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Restore from specific NuGet source
|
||||||
|
dotnet restore --source https://api.nuget.org/v3/index.json
|
||||||
|
|
||||||
|
# Build without restoring (Great for build script repetition)
|
||||||
|
dotnet build --no-restore
|
||||||
|
|
||||||
|
# Build with minimal output (Keep it clean)
|
||||||
|
dotnet build --verbosity quiet # or minimal, normal, detailed, diagnostic
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run & Watch
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Run with custom environment variables
|
||||||
|
$env:ASPNETCORE_ENVIRONMENT="Development"; dotnet run
|
||||||
|
|
||||||
|
# Watch with specific project filter
|
||||||
|
dotnet watch --project Project.csproj
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Publishing
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Single-File for Linux (Self-Contained)
|
||||||
|
dotnet publish -c Release -r linux-x64 --self-contained true `
|
||||||
|
-p:PublishSingleFile=true `
|
||||||
|
-p:IncludeNativeLibrariesForSelfExtract=true `
|
||||||
|
-o ./output/linux-x64-single
|
||||||
|
|
||||||
|
# Ready-To-Run (R2R) - Fast startup at the cost of size
|
||||||
|
dotnet publish -c Release -o ./output -p:PublishReadyToRun=true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tools
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Install a dotnet tool globally
|
||||||
|
dotnet tool install -g <tool-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
[🔼 Back to Top](#dev-cheat-sheet)
|
||||||
|
|
||||||
|
## Common Output Folder Pattern
|
||||||
|
|
||||||
|
Use explicit output folders so results are easy to find and easy to clean up.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
./output/
|
||||||
|
./output/win-x64/
|
||||||
|
./output/win-x64-single/
|
||||||
|
./output/linux-x64/
|
||||||
|
./output/linux-x64-single/
|
||||||
|
```
|
||||||
|
|
||||||
|
[Back to Top](#dev-cheat-sheet)
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
### Build & Run
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
docker build -t app-name .
|
||||||
|
docker run -p 8080:80 app-name
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker System Cleanup
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
docker system prune -a # Wipe everything unused
|
||||||
|
docker container prune
|
||||||
|
docker image prune
|
||||||
|
```
|
||||||
|
|
||||||
|
[Back to Top](#dev-cheat-sheet)
|
||||||
|
|
||||||
|
## Useful Cleanup Commands
|
||||||
|
|
||||||
|
### Remove Node artifacts
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Remove-Item -Recurse -Force .\node_modules
|
||||||
|
Remove-Item -Recurse -Force .\dist
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remove Rust / Tauri artifacts
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Remove-Item -Recurse -Force .\src-tauri\target
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remove .NET artifacts (Deep clean)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Get-ChildItem -Include bin,obj,output -Recurse | Remove-Item -Recurse -Force
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remove Python artifacts
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Remove-Item -Recurse -Force .\.venv
|
||||||
|
Get-ChildItem -Include __pycache__ -Recurse | Remove-Item -Recurse -Force
|
||||||
|
```
|
||||||
|
|
||||||
|
[Back to Top](#dev-cheat-sheet)
|
||||||
|
|
||||||
|
## Recommended Habits
|
||||||
|
|
||||||
|
Before any risky Git action:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git status --short --branch
|
||||||
|
git branch backup/pre-risk
|
||||||
|
git tag pre-risk-YYYY-MM-DD
|
||||||
|
git fetch origin --prune
|
||||||
|
```
|
||||||
|
|
||||||
|
Before publishing builds:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm run build
|
||||||
|
cargo test
|
||||||
|
dotnet test
|
||||||
|
```
|
||||||
|
|
||||||
|
Adjust to the stack used by the current project.
|
||||||
|
|
||||||
|
[Back to Top](#dev-cheat-sheet)
|
||||||
|
|
||||||
|
## Workflows and SDT DevTool
|
||||||
|
|
||||||
|
### Build / Run SDT (DevTool)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Quick build
|
||||||
|
python scripts/dotnet-min.py build
|
||||||
|
|
||||||
|
# Standard run
|
||||||
|
dotnet run --project DevTool.csproj
|
||||||
|
```
|
||||||
|
|
||||||
|
### Route & Workflow Verification
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Static check
|
||||||
|
python scripts/verify-workflow-routes.py --project-root .
|
||||||
|
|
||||||
|
# Headless execution test
|
||||||
|
python scripts/verify-workflow-routes.py --project-root . --workflow build --workflow tauri --execute --env-profile dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build & Sync Output
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Publish web assets + app
|
||||||
|
python scripts/publish-app.py --target web
|
||||||
|
|
||||||
|
# Publish specific projects
|
||||||
|
python scripts/publish-sidecar.py --project path/to/sidecar.csproj
|
||||||
|
python scripts/publish-webgateway.py --project path/to/gateway.csproj
|
||||||
|
|
||||||
|
# Sync all results to central output/
|
||||||
|
python scripts/sync-output.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Migration Gate
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python scripts/migration-gate.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### NuGet / Node Cleanup
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# NuGet cache export
|
||||||
|
python scripts/nuget-export-cache.py --output-zip cache-export.zip
|
||||||
|
|
||||||
|
# Clean node_modules safely
|
||||||
|
python scripts/npm-clean.py --working-dir .
|
||||||
|
```
|
||||||
|
|
||||||
|
[Back to Top](#dev-cheat-sheet)
|
||||||
@ -1,6 +1,6 @@
|
|||||||
# Legacy Hardware Validation
|
# Legacy Hardware Validation
|
||||||
|
|
||||||
This report exists to close the remaining Phase 2 validation work against the target profile: older desktop hardware with limited RAM where Thisper must remain responsive and low-overhead.
|
This report exists to close the remaining release-candidate validation work against the target profile: older desktop hardware with limited RAM where Thisper must remain responsive and low-overhead.
|
||||||
|
|
||||||
## Validation Checklist
|
## Validation Checklist
|
||||||
|
|
||||||
@ -13,40 +13,46 @@ This report exists to close the remaining Phase 2 validation work against the ta
|
|||||||
|
|
||||||
### Repeated Cross-App Rewrite
|
### Repeated Cross-App Rewrite
|
||||||
|
|
||||||
- Scenario: Use `Ctrl + Alt + R` repeatedly across plain-text targets such as Notepad and browser text boxes.
|
- Scenario: Use `Ctrl + Alt + R` repeatedly across plain-text targets such as Notepad, browser text boxes, and lightweight game chat surfaces.
|
||||||
- Acceptance target: No destructive replacement on provider failure, no clipboard corruption after failures, stable repeated use without restart.
|
- Acceptance target: No destructive replacement on provider failure, no clipboard corruption after failures, stable repeated use without restart.
|
||||||
- Current evidence: Informal real-world usage is positive, but no structured pass is recorded yet.
|
- Current evidence: Informal real-world usage is positive across multiple programs and even some games. One known host-app incompatibility exists where the T3 Chat app steals focus on the hotkey path.
|
||||||
- Status: `remaining`
|
- Status: `partially validated`
|
||||||
|
|
||||||
### Long-Form Desktop Rewrite
|
### Long-Form Desktop Rewrite
|
||||||
|
|
||||||
- Scenario: Rewrite a multi-paragraph block through the main window and inspect diff/output toggles.
|
- Scenario: Rewrite a multi-paragraph block through the main window and inspect diff/output toggles.
|
||||||
- Acceptance target: UI remains responsive during rewrite and the diff view reflects only actual edits.
|
- Acceptance target: UI remains responsive during rewrite and the diff view reflects only actual edits.
|
||||||
- Current evidence: Main UI build path is working and has been used successfully, but no formal timing pass is logged yet.
|
- Current evidence: Main UI rewrite has been used successfully in normal writing flow, but no structured timing pass is logged yet.
|
||||||
- Status: `remaining`
|
- Status: `partially validated`
|
||||||
|
|
||||||
### Successive Rewrites Without Restart
|
### Successive Rewrites Without Restart
|
||||||
|
|
||||||
- Scenario: Perform multiple rewrites in sequence from both the main UI and the global shortcut flow.
|
- Scenario: Perform multiple rewrites in sequence from both the main UI and the global shortcut flow.
|
||||||
- Acceptance target: No cumulative instability, no stale model state, no stuck loading state.
|
- Acceptance target: No cumulative instability, no stale model state, no stuck loading state, and no credential-loss regression after restart.
|
||||||
- Current evidence: Runtime observability is now present to support this pass, but the pass itself is still pending.
|
- Current evidence: Runtime observability and secure key persistence are in place. Informal usage is positive, but the pass itself is not fully recorded yet.
|
||||||
- Status: `remaining`
|
- Status: `partially validated`
|
||||||
|
|
||||||
### Resource Footprint
|
### Resource Footprint
|
||||||
|
|
||||||
- Scenario: Observe memory use and responsiveness on constrained hardware during idle, active rewrite, and tray-hidden states.
|
- Scenario: Observe memory use and responsiveness on constrained hardware during idle, active rewrite, and tray-hidden states.
|
||||||
- Acceptance target: No runaway memory growth and acceptable perceived latency for short text.
|
- Acceptance target: No runaway memory growth and acceptable perceived latency for short text.
|
||||||
- Current evidence: Build/runtime artifacts are large in development, but that is mostly `target/`; release footprint still needs explicit validation.
|
- Current evidence: Development footprint is dominated by build artifacts in `target/`, so the meaningful missing data is runtime memory and release-build behavior on older hardware.
|
||||||
- Status: `remaining`
|
- Status: `remaining`
|
||||||
|
|
||||||
|
## Known Compatibility Notes
|
||||||
|
|
||||||
|
- `T3 Chat`: hotkey path can defocus the text box because the host app reacts to the shortcut first
|
||||||
|
- rich editors and secure fields: still best-effort and may not behave like plain-text targets
|
||||||
|
|
||||||
## What To Record During The Pass
|
## What To Record During The Pass
|
||||||
|
|
||||||
- idle memory use
|
- idle memory use
|
||||||
- active rewrite latency for short and medium inputs
|
- active rewrite latency for short and medium inputs
|
||||||
- whether the tray/hotkey path remains responsive after long idle time
|
- whether the tray/hotkey path remains responsive after long idle time
|
||||||
- whether clipboard restoration remains correct after both success and failure
|
- whether clipboard restoration remains correct after both success and failure
|
||||||
|
- whether secure credential persistence survives multiple app restarts
|
||||||
- any target app classes that consistently fail or behave inconsistently
|
- any target app classes that consistently fail or behave inconsistently
|
||||||
|
|
||||||
## Completion Rule
|
## Completion Rule
|
||||||
|
|
||||||
Phase 2 desktop validation is complete when each scenario above has an explicit observed result recorded here, including failures or caveats.
|
The desktop release candidate is fully validated when each scenario above has an explicit observed result recorded here, including failures or caveats.
|
||||||
|
|||||||
27
README.md
27
README.md
@ -2,12 +2,29 @@
|
|||||||
|
|
||||||
Thisper is a typing-first communication translator. It rewrites raw text into clearer text while staying as close as possible to the original meaning, voice, uncertainty, and tone.
|
Thisper is a typing-first communication translator. It rewrites raw text into clearer text while staying as close as possible to the original meaning, voice, uncertainty, and tone.
|
||||||
|
|
||||||
|
## Release Candidate Status
|
||||||
|
|
||||||
|
Thisper is currently a `desktop typed-workflow release candidate`.
|
||||||
|
|
||||||
|
What is considered in-scope and ready for release-candidate use:
|
||||||
|
|
||||||
|
- desktop text-to-text rewrite workflow
|
||||||
|
- cross-app selected-text rewrite via `Ctrl + Alt + R`
|
||||||
|
- tray/background operation
|
||||||
|
- native secure API key persistence
|
||||||
|
- protected URL preservation
|
||||||
|
|
||||||
|
What still remains before a broader general release:
|
||||||
|
|
||||||
|
- structured legacy hardware validation
|
||||||
|
- final commercial license allowlist decision
|
||||||
|
|
||||||
## Current Desktop Scope
|
## Current Desktop Scope
|
||||||
|
|
||||||
- Desktop text-to-text rewrite UI with diff review
|
- Desktop text-to-text rewrite UI with diff review
|
||||||
- Rewrite modes: `Preserve Voice`, `Clean`, `Readable`, `Formal`, `Concise`
|
- Rewrite modes: `Preserve Voice`, `Clean`, `Readable`, `Formal`, `Concise`
|
||||||
- Gemini-backed cloud rewrite provider with typed model selection
|
- Gemini-backed cloud rewrite provider with typed model selection
|
||||||
- Secure Gemini API key storage through the system credential store
|
- Secure Gemini API key storage through native platform protection
|
||||||
- Global cross-app rewrite shortcut: `Ctrl + Alt + R`
|
- Global cross-app rewrite shortcut: `Ctrl + Alt + R`
|
||||||
- System tray support with background operation
|
- System tray support with background operation
|
||||||
- Non-destructive clipboard replacement flow: selected text is only replaced after the full rewrite succeeds
|
- Non-destructive clipboard replacement flow: selected text is only replaced after the full rewrite succeeds
|
||||||
@ -50,6 +67,7 @@ If no API key is configured, Thisper brings the main window forward instead of a
|
|||||||
- Secure fields and some application surfaces may block selection, copy, paste, or synthetic key events.
|
- Secure fields and some application surfaces may block selection, copy, paste, or synthetic key events.
|
||||||
- Clipboard restoration currently preserves prior plain-text clipboard contents. Non-text clipboard formats are not yet restored.
|
- Clipboard restoration currently preserves prior plain-text clipboard contents. Non-text clipboard formats are not yet restored.
|
||||||
- If a rewrite attempts to drop a protected URL, Thisper now fails that rewrite instead of silently removing the link.
|
- If a rewrite attempts to drop a protected URL, Thisper now fails that rewrite instead of silently removing the link.
|
||||||
|
- Some host apps can intercept the hotkey before Thisper sees it. The known example so far is the T3 Chat app defocusing its own text box.
|
||||||
- Model fidelity still depends on prompt behavior. High-value detail protection is planned, but not part of the current Phase 2 completion scope.
|
- Model fidelity still depends on prompt behavior. High-value detail protection is planned, but not part of the current Phase 2 completion scope.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
@ -62,7 +80,10 @@ If no API key is configured, Thisper brings the main window forward instead of a
|
|||||||
|
|
||||||
### Credentials
|
### Credentials
|
||||||
|
|
||||||
Use the in-app Settings dialog to save the Gemini API key into your system credential store.
|
Use the in-app Settings dialog to save the Gemini API key using native platform protection.
|
||||||
|
|
||||||
|
- Windows: DPAPI-encrypted local app storage tied to your Windows user account
|
||||||
|
- Other supported desktop platforms: native credential store backend
|
||||||
|
|
||||||
For development, Thisper also accepts:
|
For development, Thisper also accepts:
|
||||||
|
|
||||||
@ -104,6 +125,8 @@ Current status:
|
|||||||
|
|
||||||
See:
|
See:
|
||||||
|
|
||||||
|
- `RELEASE_CANDIDATE.md` for the current desktop release-candidate lock
|
||||||
- `THISPER_STATUS.md` for current completion status
|
- `THISPER_STATUS.md` for current completion status
|
||||||
|
- `THISPER_IMPLEMENTATION_PLAN.md` for the release-candidate scope
|
||||||
- `LEGACY_HARDWARE_VALIDATION.md` for the Phase 2 validation checklist and report
|
- `LEGACY_HARDWARE_VALIDATION.md` for the Phase 2 validation checklist and report
|
||||||
- `PHASE3_SPEECH_PLAN.md` for the queued next-phase speech scope
|
- `PHASE3_SPEECH_PLAN.md` for the queued next-phase speech scope
|
||||||
|
|||||||
36
RELEASE_CANDIDATE.md
Normal file
36
RELEASE_CANDIDATE.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# Thisper Release Candidate
|
||||||
|
|
||||||
|
Current designation: `RC1 candidate`
|
||||||
|
|
||||||
|
This document locks the current desktop typed workflow into a release-candidate state.
|
||||||
|
|
||||||
|
## Included In RC1
|
||||||
|
|
||||||
|
- desktop text-to-text rewrite UI
|
||||||
|
- diff review
|
||||||
|
- explicit rewrite modes
|
||||||
|
- cross-app selected-text rewrite via `Ctrl + Alt + R`
|
||||||
|
- tray/background operation
|
||||||
|
- native secure API key persistence
|
||||||
|
- protected URL preservation
|
||||||
|
- local-only runtime observability
|
||||||
|
|
||||||
|
## Known Issues Accepted In RC1
|
||||||
|
|
||||||
|
- some host apps may intercept the hotkey before Thisper can act
|
||||||
|
- rich editors and secure fields remain best-effort
|
||||||
|
- non-text clipboard formats are not restored yet
|
||||||
|
- broader protected-term handling is not implemented yet
|
||||||
|
|
||||||
|
## Exit Criteria Before General Release
|
||||||
|
|
||||||
|
- legacy hardware validation report completed
|
||||||
|
- commercial license allowlist decision finalized
|
||||||
|
- Rust license audit policy aligned with that decision
|
||||||
|
|
||||||
|
## Primary Reference Docs
|
||||||
|
|
||||||
|
- `README.md`
|
||||||
|
- `THISPER_STATUS.md`
|
||||||
|
- `THISPER_IMPLEMENTATION_PLAN.md`
|
||||||
|
- `LEGACY_HARDWARE_VALIDATION.md`
|
||||||
@ -1,18 +1,41 @@
|
|||||||
# Thisper Implementation Plan
|
# Thisper Implementation Plan
|
||||||
|
|
||||||
This plan reflects the current repo state and the remaining work needed to close desktop Phase 2 before speech begins.
|
This plan reflects the current repo state and the work needed to move the desktop typed workflow from release candidate to general release.
|
||||||
|
|
||||||
## Completed Baseline
|
## Current State
|
||||||
|
|
||||||
- Desktop text-to-text MVP is complete.
|
- Desktop text-to-text MVP is complete.
|
||||||
- The provider boundary exists and Gemini is integrated.
|
- The provider boundary exists and Gemini is integrated.
|
||||||
- Secure credential storage is implemented.
|
- Native secure credential storage is implemented.
|
||||||
- Global shortcut and clipboard rewrite are implemented.
|
- Global shortcut and clipboard rewrite are implemented.
|
||||||
- Streaming rewrite is implemented.
|
|
||||||
- Tray and background lifecycle are implemented.
|
- Tray and background lifecycle are implemented.
|
||||||
- Runtime observability is implemented locally in-memory.
|
- Runtime observability is implemented locally in-memory.
|
||||||
|
- Protected URL preservation is implemented.
|
||||||
|
|
||||||
## Remaining To Close Phase 2
|
## Release Candidate Scope
|
||||||
|
|
||||||
|
The current release candidate covers the desktop typed workflow only.
|
||||||
|
|
||||||
|
### Included
|
||||||
|
|
||||||
|
- main rewrite UI
|
||||||
|
- explicit rewrite modes
|
||||||
|
- diff review
|
||||||
|
- copy output
|
||||||
|
- secure settings for the Gemini API key
|
||||||
|
- `Ctrl + Alt + R` selected-text rewrite
|
||||||
|
- tray/background operation
|
||||||
|
- native secure credential handling
|
||||||
|
- protected URL preservation
|
||||||
|
|
||||||
|
### Known Limits Accepted For The Release Candidate
|
||||||
|
|
||||||
|
- rich editors and some browser surfaces are still best-effort
|
||||||
|
- non-text clipboard formats are not restored yet
|
||||||
|
- host-app hotkey conflicts can still occur in specific apps
|
||||||
|
- broader high-value term protection is not implemented yet
|
||||||
|
|
||||||
|
## Remaining To Close Before General Release
|
||||||
|
|
||||||
### 1. Validation
|
### 1. Validation
|
||||||
|
|
||||||
@ -25,34 +48,10 @@ This plan reflects the current repo state and the remaining work needed to close
|
|||||||
- Decide the commercial allowlist policy.
|
- Decide the commercial allowlist policy.
|
||||||
- Update Rust audit configuration to match that policy.
|
- Update Rust audit configuration to match that policy.
|
||||||
|
|
||||||
## Current Desktop Contract
|
|
||||||
|
|
||||||
### Main UI
|
|
||||||
|
|
||||||
- typed input and output panes
|
|
||||||
- explicit rewrite modes
|
|
||||||
- diff review
|
|
||||||
- copy output
|
|
||||||
- secure settings for the Gemini API key
|
|
||||||
|
|
||||||
### Cross-App Utility
|
|
||||||
|
|
||||||
- `Ctrl + Alt + R` triggers selected-text rewrite
|
|
||||||
- default global mode is `Preserve Voice`
|
|
||||||
- selected text is only replaced after a complete rewrite succeeds
|
|
||||||
- app can remain hidden in the tray while the hotkey stays active
|
|
||||||
|
|
||||||
### Tray Lifecycle
|
|
||||||
|
|
||||||
- closing the main window does not quit by default
|
|
||||||
- first close shows a tray hint
|
|
||||||
- later closes hide to tray
|
|
||||||
- tray menu supports show, rewrite, and quit
|
|
||||||
|
|
||||||
## Queued Next
|
## Queued Next
|
||||||
|
|
||||||
After Phase 2 is closed:
|
After the desktop release candidate is validated:
|
||||||
|
|
||||||
1. start the speech phase from `PHASE3_SPEECH_PLAN.md`
|
1. start the speech phase from `PHASE3_SPEECH_PLAN.md`
|
||||||
2. queue trust refinements such as protected terms and high-value detail preservation
|
2. expand trust protections beyond URLs into protected terms and factual tokens
|
||||||
3. revisit optimization work only after validation data exists
|
3. revisit optimization work only after validation data exists
|
||||||
|
|||||||
@ -2,28 +2,32 @@
|
|||||||
|
|
||||||
This is the single source of truth for current delivery status.
|
This is the single source of truth for current delivery status.
|
||||||
|
|
||||||
|
Current release state: `desktop typed-workflow release candidate`
|
||||||
|
|
||||||
|
Phase 2 feature scope is implemented. The remaining work is validation and release-policy closure, not missing core desktop functionality.
|
||||||
|
|
||||||
## Completed
|
## Completed
|
||||||
|
|
||||||
### Desktop MVP Foundation
|
### Desktop MVP Foundation
|
||||||
|
|
||||||
- Status: `completed`
|
- Status: `completed`
|
||||||
- Rationale: The desktop app, main rewrite UI, mode selector, copy flow, and diff review are all implemented and working.
|
- Rationale: The desktop app, main rewrite UI, mode selector, copy flow, and diff review are implemented and in active use.
|
||||||
- Acceptance condition: User can paste text, rewrite it, inspect diff, and copy output without leaving the app.
|
- Acceptance condition: User can paste text, rewrite it, inspect diff, and copy output without leaving the app.
|
||||||
- Repo pointer: `src/`, `index.html`, `src/styles.css`, `src-tauri/src/lib.rs`
|
- Repo pointer: `src/`, `index.html`, `src/styles.css`, `src-tauri/src/lib.rs`
|
||||||
|
|
||||||
### Provider Abstraction And Gemini Integration
|
### Provider Abstraction And Gemini Integration
|
||||||
|
|
||||||
- Status: `completed`
|
- Status: `completed`
|
||||||
- Rationale: Rewrite logic is abstracted behind a provider trait and Gemini is implemented as the current backend, including streaming.
|
- Rationale: Rewrite logic is abstracted behind a provider trait and Gemini is implemented as the current backend.
|
||||||
- Acceptance condition: Main UI and cross-app rewrite both run through the provider boundary instead of hard-coded prompt logic in the UI.
|
- Acceptance condition: Main UI and cross-app rewrite both run through the provider boundary instead of hard-coded prompt logic in the UI.
|
||||||
- Repo pointer: `src-tauri/src/translator.rs`, `src-tauri/src/gemini.rs`, `src-tauri/src/lib.rs`
|
- Repo pointer: `src-tauri/src/translator.rs`, `src-tauri/src/gemini.rs`, `src-tauri/src/lib.rs`
|
||||||
|
|
||||||
### Secure Credential Storage
|
### Native Secure Credential Storage
|
||||||
|
|
||||||
- Status: `completed`
|
- Status: `completed`
|
||||||
- Rationale: The Gemini API key is stored in the system credential store rather than a plaintext app file.
|
- Rationale: API key storage now uses native platform protection instead of a misleading generic claim. On Windows this is DPAPI-encrypted app-local storage; other supported desktop platforms continue to use the native credential backend.
|
||||||
- Acceptance condition: Saving a key through Settings persists it securely across app restarts.
|
- Acceptance condition: Saving a key through Settings persists it securely across app restarts and the key is never written in plaintext.
|
||||||
- Repo pointer: `src-tauri/src/lib.rs`, `README.md`
|
- Repo pointer: `src-tauri/src/credentials.rs`, `src-tauri/src/lib.rs`, `README.md`
|
||||||
|
|
||||||
### Global Shortcut And Clipboard Rewrite
|
### Global Shortcut And Clipboard Rewrite
|
||||||
|
|
||||||
@ -35,17 +39,24 @@ This is the single source of truth for current delivery status.
|
|||||||
### Tray And Background Operation
|
### Tray And Background Operation
|
||||||
|
|
||||||
- Status: `completed`
|
- Status: `completed`
|
||||||
- Rationale: Thisper now supports tray-based lifecycle management and continued hotkey operation while the window is hidden.
|
- Rationale: Thisper supports tray-based lifecycle management and continued hotkey operation while the window is hidden.
|
||||||
- Acceptance condition: Closing the window hides to tray after the first hint, tray actions restore or quit correctly, and the hotkey remains active while hidden.
|
- Acceptance condition: Closing the window hides to tray after the first hint, tray actions restore or quit correctly, and the hotkey remains active while hidden.
|
||||||
- Repo pointer: `src-tauri/src/lib.rs`, `src/main.ts`, `index.html`, `src/styles.css`
|
- Repo pointer: `src-tauri/src/lib.rs`, `src/main.ts`, `index.html`, `src/styles.css`
|
||||||
|
|
||||||
### Runtime Observability
|
### Runtime Observability
|
||||||
|
|
||||||
- Status: `completed`
|
- Status: `completed`
|
||||||
- Rationale: In-memory runtime metrics now track rewrite attempts, success/failure counts, last error, last model, and last rewrite duration.
|
- Rationale: In-memory runtime metrics track rewrite attempts, success/failure counts, last error, last model, and last rewrite duration without storing raw user text.
|
||||||
- Acceptance condition: Frontend can query and display runtime status without storing raw user text.
|
- Acceptance condition: Frontend can query and display runtime status locally.
|
||||||
- Repo pointer: `src-tauri/src/lib.rs`, `src/main.ts`
|
- Repo pointer: `src-tauri/src/lib.rs`, `src/main.ts`
|
||||||
|
|
||||||
|
### Protected URL Preservation
|
||||||
|
|
||||||
|
- Status: `completed`
|
||||||
|
- Rationale: Links are now treated as protected content and may not be silently removed during rewrites.
|
||||||
|
- Acceptance condition: If a rewrite tries to drop a protected URL, it fails instead of returning altered text.
|
||||||
|
- Repo pointer: `src-tauri/src/gemini.rs`, `README.md`
|
||||||
|
|
||||||
### License Audit Automation
|
### License Audit Automation
|
||||||
|
|
||||||
- Status: `completed`
|
- Status: `completed`
|
||||||
@ -53,13 +64,13 @@ This is the single source of truth for current delivery status.
|
|||||||
- Acceptance condition: `licenses:npm`, `licenses:rust`, and `licenses:all` are available and documented.
|
- Acceptance condition: `licenses:npm`, `licenses:rust`, and `licenses:all` are available and documented.
|
||||||
- Repo pointer: `package.json`, `src-tauri/deny.toml`, `README.md`
|
- Repo pointer: `package.json`, `src-tauri/deny.toml`, `README.md`
|
||||||
|
|
||||||
## Remaining For Phase 2 Completion
|
## Remaining Before General Release
|
||||||
|
|
||||||
### Legacy Hardware Validation Pass
|
### Legacy Hardware Validation Pass
|
||||||
|
|
||||||
- Status: `remaining`
|
- Status: `remaining`
|
||||||
- Rationale: The implementation is in place, but the acceptance report still depends on a structured pass against the target hardware profile.
|
- Rationale: The implementation is in place, but the acceptance report still depends on a structured pass against the target hardware profile.
|
||||||
- Acceptance condition: The validation checklist is executed and the report is updated with actual observed results.
|
- Acceptance condition: The validation checklist is executed and the report is updated with explicit observed results.
|
||||||
- Repo pointer: `LEGACY_HARDWARE_VALIDATION.md`
|
- Repo pointer: `LEGACY_HARDWARE_VALIDATION.md`
|
||||||
|
|
||||||
### Commercial License Allowlist Decision
|
### Commercial License Allowlist Decision
|
||||||
@ -69,19 +80,19 @@ This is the single source of truth for current delivery status.
|
|||||||
- Acceptance condition: A written allowlist decision is made and the Rust audit configuration is aligned with that decision.
|
- Acceptance condition: A written allowlist decision is made and the Rust audit configuration is aligned with that decision.
|
||||||
- Repo pointer: `src-tauri/deny.toml`, `README.md`
|
- Repo pointer: `src-tauri/deny.toml`, `README.md`
|
||||||
|
|
||||||
## Queued After Phase 2
|
## Queued After Release Candidate
|
||||||
|
|
||||||
### Speech Input Phase
|
### Speech Input Phase
|
||||||
|
|
||||||
- Status: `queued`
|
- Status: `queued`
|
||||||
- Rationale: Speech is the next planned phase after desktop Phase 2 is closed, but it is not part of current completion criteria.
|
- Rationale: Speech is the next planned phase after the current desktop release candidate is validated.
|
||||||
- Acceptance condition: Phase 2 is marked complete and the speech scope starts from the queued plan.
|
- Acceptance condition: Release-candidate validation is closed and the speech scope starts from the queued plan.
|
||||||
- Repo pointer: `PHASE3_SPEECH_PLAN.md`
|
- Repo pointer: `PHASE3_SPEECH_PLAN.md`
|
||||||
|
|
||||||
### High-Value Detail Protection
|
### High-Value Detail Protection Expansion
|
||||||
|
|
||||||
- Status: `queued`
|
- Status: `queued`
|
||||||
- Rationale: Protected terms, factual token preservation, and drift flags are important trust refinements, but they are not blocking current Phase 2 completion.
|
- Rationale: URLs are protected now, but broader protected terms, factual token preservation, and drift flags are still future trust refinements.
|
||||||
- Acceptance condition: Add protected-term rules, token-preservation checks, and a stricter retry path without breaking current flows.
|
- Acceptance condition: Add protected-term rules, token-preservation checks, and a stricter retry path without breaking current flows.
|
||||||
- Repo pointer: `communication_translator_project_plan.md`
|
- Repo pointer: `communication_translator_project_plan.md`
|
||||||
|
|
||||||
@ -90,20 +101,20 @@ This is the single source of truth for current delivery status.
|
|||||||
### Mobile Platforms
|
### Mobile Platforms
|
||||||
|
|
||||||
- Status: `deferred`
|
- Status: `deferred`
|
||||||
- Rationale: Android and iOS are not part of current Phase 2 desktop completion.
|
- Rationale: Android and iOS are not part of the current desktop release candidate.
|
||||||
- Acceptance condition: Revisit after speech planning and desktop validation.
|
- Acceptance condition: Revisit after speech planning and desktop validation.
|
||||||
- Repo pointer: `PHASE3_SPEECH_PLAN.md`
|
- Repo pointer: `PHASE3_SPEECH_PLAN.md`
|
||||||
|
|
||||||
### Local Models And Multi-Provider Routing
|
### Local Models And Multi-Provider Routing
|
||||||
|
|
||||||
- Status: `deferred`
|
- Status: `deferred`
|
||||||
- Rationale: The provider boundary exists, but local execution and multi-provider fallback are not required to close the current desktop scope.
|
- Rationale: The provider boundary exists, but local execution and multi-provider fallback are not required for the current desktop release candidate.
|
||||||
- Acceptance condition: Re-enter scope only after current desktop workflow is fully validated.
|
- Acceptance condition: Re-enter scope only after the current desktop workflow is fully validated.
|
||||||
- Repo pointer: `src-tauri/src/translator.rs`
|
- Repo pointer: `src-tauri/src/translator.rs`
|
||||||
|
|
||||||
### Major UI Redesign
|
### Major UI Redesign
|
||||||
|
|
||||||
- Status: `deferred`
|
- Status: `deferred`
|
||||||
- Rationale: Current priority is lifecycle reliability and trust, not visual redesign.
|
- Rationale: Current priority is reliability, trust, and release readiness, not visual redesign.
|
||||||
- Acceptance condition: Only revisit after Phase 2 acceptance and trust refinements.
|
- Acceptance condition: Only revisit after release-candidate validation and trust refinements.
|
||||||
- Repo pointer: `src/`, `index.html`, `src/styles.css`
|
- Repo pointer: `src/`, `index.html`, `src/styles.css`
|
||||||
|
|||||||
@ -1,5 +1,14 @@
|
|||||||
# Communication Translator Project Plan
|
# Communication Translator Project Plan
|
||||||
|
|
||||||
|
This document is the long-term product vision and design philosophy.
|
||||||
|
|
||||||
|
For current implementation state and release readiness, use:
|
||||||
|
|
||||||
|
- `README.md`
|
||||||
|
- `RELEASE_CANDIDATE.md`
|
||||||
|
- `THISPER_STATUS.md`
|
||||||
|
- `THISPER_IMPLEMENTATION_PLAN.md`
|
||||||
|
|
||||||
## Working Name
|
## Working Name
|
||||||
|
|
||||||
Use a placeholder name until the product identity becomes obvious through use.
|
Use a placeholder name until the product identity becomes obvious through use.
|
||||||
|
|||||||
@ -49,7 +49,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="api-key-input">Gemini API Key</label>
|
<label for="api-key-input">Gemini API Key</label>
|
||||||
<input type="password" id="api-key-input" placeholder="Paste your API key here...">
|
<input type="password" id="api-key-input" placeholder="Paste your API key here...">
|
||||||
<p class="hint">Stored in your system credential store. Thisper never writes the key to a plaintext file.</p>
|
<p class="hint">Stored securely using native platform protection. On Windows, Thisper uses DPAPI-encrypted app data. The key is never written in plaintext.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
<button id="save-settings-btn" class="primary-btn">Save Settings</button>
|
<button id="save-settings-btn" class="primary-btn">Save Settings</button>
|
||||||
|
|||||||
12
src-tauri/Cargo.lock
generated
12
src-tauri/Cargo.lock
generated
@ -4529,6 +4529,7 @@ dependencies = [
|
|||||||
"tauri-plugin-clipboard-manager",
|
"tauri-plugin-clipboard-manager",
|
||||||
"tauri-plugin-global-shortcut",
|
"tauri-plugin-global-shortcut",
|
||||||
"tauri-plugin-single-instance",
|
"tauri-plugin-single-instance",
|
||||||
|
"windows-dpapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -5487,6 +5488,17 @@ dependencies = [
|
|||||||
"windows-strings 0.5.1",
|
"windows-strings 0.5.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-dpapi"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2981752d6f11bdcab4db52be8ad5c0e6a6d4d6d566764b3058cc1ee473e6479e"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"log",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-future"
|
name = "windows-future"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|||||||
@ -32,6 +32,9 @@ futures-util = "0.3.32"
|
|||||||
keyring = "3.6.3"
|
keyring = "3.6.3"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
windows-dpapi = "0.2.0"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
opt-level = "s"
|
opt-level = "s"
|
||||||
|
|||||||
134
src-tauri/src/credentials.rs
Normal file
134
src-tauri/src/credentials.rs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
use tauri::AppHandle;
|
||||||
|
|
||||||
|
const KEYRING_SERVICE: &str = "thisper";
|
||||||
|
const KEYRING_ACCOUNT: &str = "gemini_api_key";
|
||||||
|
|
||||||
|
pub fn load_api_key(app: &AppHandle) -> Result<Option<String>, String> {
|
||||||
|
backend::load_api_key(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_api_key(app: &AppHandle, key: &str) -> Result<(), String> {
|
||||||
|
backend::save_api_key(app, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn storage_description() -> &'static str {
|
||||||
|
backend::storage_description()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
mod backend {
|
||||||
|
use super::{KEYRING_ACCOUNT, KEYRING_SERVICE};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use tauri::{AppHandle, Manager};
|
||||||
|
use windows_dpapi::{Scope, decrypt_data, encrypt_data};
|
||||||
|
|
||||||
|
const API_KEY_FILE: &str = "gemini_api_key.dpapi";
|
||||||
|
|
||||||
|
fn encrypted_file_path(app: &AppHandle) -> Result<PathBuf, String> {
|
||||||
|
let dir = app
|
||||||
|
.path()
|
||||||
|
.app_local_data_dir()
|
||||||
|
.map_err(|err| format!("Failed to resolve local app data directory: {err}"))?;
|
||||||
|
Ok(dir.join("credentials").join(API_KEY_FILE))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_legacy_keyring_value() -> Result<Option<String>, String> {
|
||||||
|
let entry = keyring::Entry::new(KEYRING_SERVICE, KEYRING_ACCOUNT)
|
||||||
|
.map_err(|err| format!("Failed to access legacy credential store: {err}"))?;
|
||||||
|
|
||||||
|
match entry.get_password() {
|
||||||
|
Ok(password) if !password.trim().is_empty() => Ok(Some(password)),
|
||||||
|
Ok(_) => Ok(None),
|
||||||
|
Err(keyring::Error::NoEntry) => Ok(None),
|
||||||
|
Err(err) => Err(format!("Failed to read legacy credential store: {err}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_legacy_keyring_value() {
|
||||||
|
if let Ok(entry) = keyring::Entry::new(KEYRING_SERVICE, KEYRING_ACCOUNT) {
|
||||||
|
let _ = entry.delete_credential();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_api_key(app: &AppHandle) -> Result<Option<String>, String> {
|
||||||
|
let path = encrypted_file_path(app)?;
|
||||||
|
if path.exists() {
|
||||||
|
let encrypted = fs::read(&path)
|
||||||
|
.map_err(|err| format!("Failed to read encrypted API key: {err}"))?;
|
||||||
|
if encrypted.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let decrypted = decrypt_data(&encrypted, Scope::User, None)
|
||||||
|
.map_err(|err| format!("Failed to decrypt API key: {err}"))?;
|
||||||
|
let key = String::from_utf8(decrypted)
|
||||||
|
.map_err(|err| format!("Failed to decode decrypted API key: {err}"))?;
|
||||||
|
|
||||||
|
if key.trim().is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Some(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(legacy_key) = load_legacy_keyring_value()? {
|
||||||
|
save_api_key(app, &legacy_key)?;
|
||||||
|
delete_legacy_keyring_value();
|
||||||
|
return Ok(Some(legacy_key));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_api_key(app: &AppHandle, key: &str) -> Result<(), String> {
|
||||||
|
let path = encrypted_file_path(app)?;
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
fs::create_dir_all(parent)
|
||||||
|
.map_err(|err| format!("Failed to create credential directory: {err}"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let encrypted = encrypt_data(key.as_bytes(), Scope::User, None)
|
||||||
|
.map_err(|err| format!("Failed to encrypt API key with Windows DPAPI: {err}"))?;
|
||||||
|
|
||||||
|
fs::write(&path, encrypted)
|
||||||
|
.map_err(|err| format!("Failed to write encrypted API key: {err}"))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn storage_description() -> &'static str {
|
||||||
|
"Stored securely using Windows DPAPI in Thisper's local app data directory. The key is encrypted for your Windows user account and is never written in plaintext."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
mod backend {
|
||||||
|
use super::{KEYRING_ACCOUNT, KEYRING_SERVICE};
|
||||||
|
use tauri::AppHandle;
|
||||||
|
|
||||||
|
pub fn load_api_key(_app: &AppHandle) -> Result<Option<String>, String> {
|
||||||
|
let entry = keyring::Entry::new(KEYRING_SERVICE, KEYRING_ACCOUNT)
|
||||||
|
.map_err(|err| format!("Failed to access native credential storage: {err}"))?;
|
||||||
|
|
||||||
|
match entry.get_password() {
|
||||||
|
Ok(password) if !password.trim().is_empty() => Ok(Some(password)),
|
||||||
|
Ok(_) => Ok(None),
|
||||||
|
Err(keyring::Error::NoEntry) => Ok(None),
|
||||||
|
Err(err) => Err(format!("Failed to read native credential storage: {err}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_api_key(_app: &AppHandle, key: &str) -> Result<(), String> {
|
||||||
|
let entry = keyring::Entry::new(KEYRING_SERVICE, KEYRING_ACCOUNT)
|
||||||
|
.map_err(|err| format!("Failed to access native credential storage: {err}"))?;
|
||||||
|
entry
|
||||||
|
.set_password(key)
|
||||||
|
.map_err(|err| format!("Failed to save API key securely: {err}"))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn storage_description() -> &'static str {
|
||||||
|
"Stored securely using the native platform credential store. Thisper never writes the key in plaintext."
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
pub mod credentials;
|
||||||
pub mod gemini;
|
pub mod gemini;
|
||||||
pub mod translator;
|
pub mod translator;
|
||||||
|
|
||||||
@ -17,8 +18,6 @@ use tauri::{AppHandle, Emitter, Manager, State, WebviewWindow, WindowEvent};
|
|||||||
use tauri_plugin_clipboard_manager::ClipboardExt;
|
use tauri_plugin_clipboard_manager::ClipboardExt;
|
||||||
use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut};
|
use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut};
|
||||||
|
|
||||||
const KEYRING_SERVICE: &str = "thisper";
|
|
||||||
const KEYRING_ACCOUNT: &str = "gemini_api_key";
|
|
||||||
const MAIN_WINDOW_LABEL: &str = "main";
|
const MAIN_WINDOW_LABEL: &str = "main";
|
||||||
const TRAY_ID: &str = "main-tray";
|
const TRAY_ID: &str = "main-tray";
|
||||||
const MENU_SHOW_ID: &str = "show";
|
const MENU_SHOW_ID: &str = "show";
|
||||||
@ -69,17 +68,6 @@ struct RuntimeState {
|
|||||||
last_metrics: Option<RewriteMetrics>,
|
last_metrics: Option<RewriteMetrics>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_saved_api_key() -> Result<Option<String>, String> {
|
|
||||||
let entry = keyring::Entry::new(KEYRING_SERVICE, KEYRING_ACCOUNT)
|
|
||||||
.map_err(|err| format!("Failed to access credential store: {err}"))?;
|
|
||||||
match entry.get_password() {
|
|
||||||
Ok(password) if !password.trim().is_empty() => Ok(Some(password)),
|
|
||||||
Ok(_) => Ok(None),
|
|
||||||
Err(keyring::Error::NoEntry) => Ok(None),
|
|
||||||
Err(err) => Err(format!("Failed to read credential store: {err}")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn api_key_configured(state: &AppState) -> bool {
|
fn api_key_configured(state: &AppState) -> bool {
|
||||||
let key = state.api_key.lock().unwrap();
|
let key = state.api_key.lock().unwrap();
|
||||||
!key.trim().is_empty() || std::env::var("GEMINI_API_KEY").is_ok()
|
!key.trim().is_empty() || std::env::var("GEMINI_API_KEY").is_ok()
|
||||||
@ -332,11 +320,7 @@ async fn save_api_key(
|
|||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let entry = keyring::Entry::new(KEYRING_SERVICE, KEYRING_ACCOUNT)
|
credentials::save_api_key(&app, &key)?;
|
||||||
.map_err(|err| format!("Failed to access credential store: {err}"))?;
|
|
||||||
entry
|
|
||||||
.set_password(&key)
|
|
||||||
.map_err(|err| format!("Failed to save API key securely: {err}"))?;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut key_state = state.api_key.lock().unwrap();
|
let mut key_state = state.api_key.lock().unwrap();
|
||||||
@ -423,11 +407,7 @@ async fn run_clipboard_rewrite(app: AppHandle) -> Result<(), String> {
|
|||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
let initial_key = load_saved_api_key()
|
let initial_key = std::env::var("GEMINI_API_KEY").unwrap_or_default();
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
.or_else(|| std::env::var("GEMINI_API_KEY").ok())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let provider =
|
let provider =
|
||||||
Box::new(gemini::GeminiProvider::new().expect("Gemini provider initialization failed"))
|
Box::new(gemini::GeminiProvider::new().expect("Gemini provider initialization failed"))
|
||||||
@ -474,6 +454,21 @@ pub fn run() {
|
|||||||
attach_window_lifecycle(&app.handle()).map_err(std::io::Error::other)?;
|
attach_window_lifecycle(&app.handle()).map_err(std::io::Error::other)?;
|
||||||
app.global_shortcut().register(shortcut_clone.clone())?;
|
app.global_shortcut().register(shortcut_clone.clone())?;
|
||||||
|
|
||||||
|
match credentials::load_api_key(&app.handle()) {
|
||||||
|
Ok(Some(saved_key)) => {
|
||||||
|
let state: State<'_, AppState> = app.state();
|
||||||
|
let mut api_key = state.api_key.lock().unwrap();
|
||||||
|
*api_key = saved_key;
|
||||||
|
}
|
||||||
|
Ok(None) => {}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Credential storage load error: {err}");
|
||||||
|
let state: State<'_, AppState> = app.state();
|
||||||
|
let mut runtime = state.runtime.lock().unwrap();
|
||||||
|
runtime.last_error = Some(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let state: State<'_, AppState> = app.state();
|
let state: State<'_, AppState> = app.state();
|
||||||
let mut runtime = state.runtime.lock().unwrap();
|
let mut runtime = state.runtime.lock().unwrap();
|
||||||
|
|||||||
@ -161,7 +161,7 @@ saveSettingsBtn.addEventListener("click", async () => {
|
|||||||
settingsModal.classList.add("hidden");
|
settingsModal.classList.add("hidden");
|
||||||
apiKeyInput.value = "";
|
apiKeyInput.value = "";
|
||||||
await refreshRuntimeStatus();
|
await refreshRuntimeStatus();
|
||||||
alert("API key saved to your system credential store.");
|
alert("API key saved using native platform protection.");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert("Error saving API key: " + err);
|
alert("Error saving API key: " + err);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user