# CleanCopy CleanCopy is a lightweight, headless Windows utility that sits silently in the system tray, automatically sanitizing your clipboard text on trigger. Designed with a strict focus on privacy, developer ergonomics, and performance, CleanCopy operates with a minimal footprint and compiles to a native installer. ## Features - Automatically removes marketing/analytics trackers (e.g., `utm_*`, `gclid`, `fbclid`, `si`, `msclkid`) from copied URLs while keeping functional query arguments. Users can also define **custom tracking parameters** via a comma-separated input in the Rules tab. - Converts curly quotes (`"`/`"`/`'`/`'`), en-dashes (`–`), and em-dashes (`—`) to standard straight quotes (`"`, `'`) and double hyphens (`--`). - Joins single-newline wraps (frequently introduced by chat apps like WhatsApp or Messenger) into continuous lines while preserving double-newline paragraphs and Markdown layout structure. Also handles **hyphenated line breaks** (e.g., `"inno-\nvation"` → `"innovation"`). - Removes zero-width spaces, null bytes, and other invisible Unicode characters that can cause issues when pasting. - Plays a system beep and shows a Windows notification each time the clipboard is sanitized. - Optional autostart toggle via `tauri-plugin-autostart`. - A dark-themed settings panel styled around the VS Code _Lavender Dimmed_ color scheme, using the local _CaskaydiaCove Nerd Font_. - Integrates secure Ed25519-signature-based offline licensing tied directly to a hardware-fingerprinted machine ID (HWID). ## Getting Started ### Prerequisites 1. **Node.js** (v18+) 2. **Rust & Cargo** (latest stable release) 3. **Windows C++ Build Tools** (Visual Studio Build Tools with C++ desktop workload enabled) ### Installation Clone the repository and install npm packages from the app directory: ```bash cd app npm install ``` ### Running in Development Start the development server (starts Vite and launches the Tauri background tray app): ```bash npm run tauri dev ``` ## Testing & Code Quality ### Running Frontend Tests Run the Vitest test suite covering React components, state management, and UI behavior: ```bash npm run test ``` ### Linting Frontend Code Run ESLint across all frontend TypeScript/TSX files: ```bash npm run lint ``` To auto-fix fixable issues: ```bash npm run lint:fix ``` ### Formatting Frontend Code Run Prettier to format all frontend files: ```bash npm run format ``` To check formatting without writing (useful for CI): ```bash npm run format:check ``` ### Running Rust Tests Run unit tests for clipboard cleansing rules and cryptographic license validation: ```bash cargo test --workspace ``` ### Running Rust Linter Verify code quality and check for warnings using Cargo Clippy: ```bash cargo clippy --workspace -- -D warnings ``` ### Building Frontend Verify that the TypeScript compilation and Vite assets packager complete without warning: ```bash npm run build ``` ## Architecture Notes ### Clipboard Simulation When the global hotkey is pressed (`Alt + Shift + C` by default), CleanCopy uses the Windows `keybd_event` API to simulate a `Ctrl+C` keystroke. It releases the Alt/Shift modifiers first to avoid polluting the copied selection, then waits 150ms for the OS clipboard to update before running the sanitization pipeline. ### Profile-Based Licensing The file `licensing_profiles.json` is compiled directly into the Rust binary via `include_str!`. Profile selection at runtime: | Build mode | Profile | Activation URL | | ----------------------- | ------------- | --------------------------------------------------- | | `cargo test` | `testing` | `http://localhost:3000/api/activate` | | `cargo build` (debug) | `development` | `http://localhost:3000/api/activate` | | `cargo build` (release) | `production` | `https://cleancopy.stevanfreeborn.com/api/activate` | ### License Enforcement When no valid license is found, CleanCopy locks the UI to the License tab only — the General and Rules tabs are disabled. If the global hotkey is pressed while unlicensed, it opens the settings window to the License tab instead of cleaning the clipboard. ## Licensing System CleanCopy utilizes a unified, database-free Stripe licensing proxy designed to support a suite of multiple desktop applications. ### The Activation Flow 1. The user buys CleanCopy on your landing page via Stripe Checkout. 2. The user copies their Stripe Payment ID (e.g. `pi_3MtgK...`) or Charge ID (e.g. `ch_3MtgK...`) from their Stripe receipt. 3. The user pastes the Stripe ID into CleanCopy's License tab. 4. CleanCopy pings server. The server: - Verifies the purchase with Stripe's API. - Checks that the payment is for the correct `product_id`. - Signs the user's Hardware ID (HWID) using a private Ed25519 signing key. - Returns a signed activation token. 5. CleanCopy verifies the signature using the embedded public key, saving it to `license.lic`. On subsequent startups, CleanCopy validates this file offline with zero network requests. ### Local Development Testing To test the activation flow locally without deploying a live server: #### Option A: Run the Local Mock Server (Recommended) 1. Start the mock licensing server: ```bash cd app/src-tauri cargo run --bin mock_license_server ``` - CleanCopy detects debug mode and automatically redirects its requests to `http://localhost:3000`. - In the Settings -> License UI, enter any key (e.g. `pi_test_12345`) and click **Activate**. - To test purchase failures, enter a key starting with `pi_invalid` (e.g. `pi_invalid_999`). #### Option B: Offline Sign & Bypass - Copy the HWID displayed in Settings -> License. - Run the license tool to sign it manually with the private key: ```bash cd app/src-tauri cargo run --bin license_tool issue 04a0664dfa85f71119e9f4fc971a953ad49f82b752087deab6bee816c1d37d6d ``` - Paste the returned token into the activation UI. - Delete the `license.lic` file from your local AppData directory: - **Path:** `C:\Users\\AppData\Roaming\com.cleancopy.app\license.lic` ### The CLI License Tool (`license_tool`) CleanCopy includes a built-in Rust command-line utility `license_tool.rs` for key generation and offline license issuance. #### Generating Keypairs (`generate-keys`) Generate fresh, cryptographically secure Ed25519 public/private key pairs. The tool uses `getrandom` to extract secure entropy from the host OS (Windows, macOS, or Linux). Run the tool with an optional profile name (prefix) and optional target folder: ```bash cd app/src-tauri cargo run --bin license_tool generate-keys [profile_name] [output_dir] ``` Example: ```bash cargo run --bin license_tool generate-keys development ``` - Generates keys and automatically persists them as `licensing/development.priv` and `licensing/development.pub` under your `.gitignored` project root folder. - Outputs the key hex values directly to the console. #### Issuing Offline Activation Keys (`issue`) Issue signed license tokens manually for air-gapped users, secure enterprise environments, or local automated virtual-machine testing. Run the tool specifying the profile's private key and the user's Hardware ID (HWID): ```bash cd app/src-tauri cargo run --bin license_tool issue [product_id] [license_key] ``` Example: ```bash cargo run --bin license_tool issue 04a0664dfa85f71119e9f4fc971a953ad49f82b752087deab6bee816c1d37d6d 9a0b396c42da0860b246aaa3b8e0cafb86789e062a8947957bb88a46f08477b6 ``` - Outputs a period-separated activation token: `PAYLOAD_B64.SIGNATURE_B64`. The customer pastes this token into their Settings panel, which is verified offline against the compiled public key. #### Purging License Status (`deactivate`) Easily reset your development machine back to the "Unregistered" state to verify and test the activation UI/flow repeatedly without manually locating files. Run the tool with the `deactivate` subcommand: ```bash cd app/src-tauri cargo run --bin license_tool deactivate ``` - Locates the local AppData directory, checks if `license.lic` is present, and deletes it. #### Diagnostics: Manually Querying a User's HWID If a customer is offline and cannot run the app to copy their Hardware ID from the License tab, can instruct them to run this single line in **PowerShell**: ```powershell $guid = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Cryptography').MachineGuid; $serial = (Get-CimInstance -ClassName Win32_BaseBoard).SerialNumber; $hasher = [System.Security.Cryptography.HashAlgorithm]::Create('SHA256'); $bytes = [System.Text.Encoding]::UTF8.GetBytes("$guid$serial"); $hash = $hasher.ComputeHash($bytes); -join ($hash | ForEach-Object { "{0:x2}" -f $_ }) ``` This queries the registry and bios serial, SHA-256 hashes them, and prints the matching HWID. ### Key Rotation If your signing key is compromised or you need to rotate for any reason, CleanCopy supports seamless key rotation via the `deprecated_public_keys` field in `licensing_profiles.json`. #### How It Works Each profile contains: - `public_key_hex`: The current signing key (used for all new activations) - `deprecated_public_keys`: Array of previous keys that remain valid for existing users When verifying a license, CleanCopy tries the current key first, then falls back to each deprecated key. Users with tokens signed by an old key continue to work without re-activation. #### Rotation Steps - Generate a new keypair: ```bash cd app/src-tauri cargo run --bin license_tool generate-keys production ``` - Update `licensing_profiles.json`: ```json { "production": { "activation_url": "https://cleancopy.stevanfreeborn.com/api/activate", "public_key_hex": "", "deprecated_public_keys": [""] } } ``` - Update the signing key on your activation server to use the new private key. - Rebuild and distribute the updated app. Existing users' licenses validate against the deprecated key. New activations use the current key. - After the migration period, remove the old key from `deprecated_public_keys` and rebuild. ## Logging and Diagnostics CleanCopy uses the `tauri-plugin-log` crate for structured logging at the `Info` level. ### Log Output Logs are written to two destinations simultaneously: | Destination | Purpose | | ------------ | -------------------------------------------------------- | | **Stdout** | Visible in the terminal when running `npm run tauri dev` | | **Log file** | Persistent file for production builds | ### Log File Location The log file is named `app.log` and stored in the Tauri log directory: | Platform | Path | | -------- | ------------------------------------------ | | Windows | `%APPDATA%/com.cleancopy.app/logs/app.log` | ### What Gets Logged - **Startup**: Config loading, license validation, hotkey registration - **Clipboard events**: Each clean operation with input/output text - **License activation**: Proxy requests, token verification results - **Errors**: File I/O failures, signature verification failures, network errors ### Viewing Logs **Real-time (development):** ```bash npm run tauri dev # Logs appear in the terminal ``` **Log file (production):** ```powershell # Windows PowerShell Get-Content "$env:APPDATA\com.cleancopy.app\logs\app.log" -Tail 50 -Wait ``` ### Log Level The log level is hardcoded to `Info`. To change it for debugging, modify the `.level()` call in `app/src-tauri/src/lib.rs`: ```rust .level(log::LevelFilter::Debug) .level(log::LevelFilter::Warn) ``` ## Production Distribution To bundle a production-ready MSI installer and release binary: ```bash npm run tauri build ``` The compiled assets will be bundled in: `app/src-tauri/target/release/bundle/msi/`