12 KiB
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
- Node.js (v18+)
- Rust & Cargo (latest stable release)
- 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:
cd app
npm install
Running in Development
Start the development server (starts Vite and launches the Tauri background tray app):
npm run tauri dev
Testing & Code Quality
Running Frontend Tests
Run the Vitest test suite covering React components, state management, and UI behavior:
npm run test
Linting Frontend Code
Run ESLint across all frontend TypeScript/TSX files:
npm run lint
To auto-fix fixable issues:
npm run lint:fix
Formatting Frontend Code
Run Prettier to format all frontend files:
npm run format
To check formatting without writing (useful for CI):
npm run format:check
Running Rust Tests
Run unit tests for clipboard cleansing rules and cryptographic license validation:
cargo test --workspace
Running Rust Linter
Verify code quality and check for warnings using Cargo Clippy:
cargo clippy --workspace -- -D warnings
Building Frontend
Verify that the TypeScript compilation and Vite assets packager complete without warning:
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
- The user buys CleanCopy on your landing page via Stripe Checkout.
- The user copies their Stripe Payment ID (e.g.
pi_3MtgK...) or Charge ID (e.g.ch_3MtgK...) from their Stripe receipt. - The user pastes the Stripe ID into CleanCopy's License tab.
- 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.
- 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)
- Start the mock licensing server:
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).
- To test purchase failures, enter a key starting with
Option B: Offline Sign & Bypass
- Copy the HWID displayed in Settings -> License.
- Run the license tool to sign it manually with the private key:
cd app/src-tauri
cargo run --bin license_tool issue 04a0664dfa85f71119e9f4fc971a953ad49f82b752087deab6bee816c1d37d6d <YOUR_HWID>
- Paste the returned token into the activation UI.
- Delete the
license.licfile from your local AppData directory:- Path:
C:\Users\<user>\AppData\Roaming\com.cleancopy.app\license.lic
- Path:
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:
cd app/src-tauri
cargo run --bin license_tool generate-keys [profile_name] [output_dir]
Example:
cargo run --bin license_tool generate-keys development
- Generates keys and automatically persists them as
licensing/development.privandlicensing/development.pubunder your.gitignoredproject 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):
cd app/src-tauri
cargo run --bin license_tool issue <private-key-hex> <device-hwid> [product_id] [license_key]
Example:
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:
cd app/src-tauri
cargo run --bin license_tool deactivate
- Locates the local AppData directory, checks if
license.licis 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:
$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:
cd app/src-tauri
cargo run --bin license_tool generate-keys production
- Update
licensing_profiles.json:
{
"production": {
"activation_url": "https://cleancopy.stevanfreeborn.com/api/activate",
"public_key_hex": "<new key>",
"deprecated_public_keys": ["<old key>"]
}
}
- 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_keysand 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):
npm run tauri dev
# Logs appear in the terminal
Log file (production):
# 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:
.level(log::LevelFilter::Debug)
.level(log::LevelFilter::Warn)
Production Distribution
To bundle a production-ready MSI installer and release binary:
npm run tauri build
The compiled assets will be bundled in:
app/src-tauri/target/release/bundle/msi/