# 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 <YOUR_HWID>
```

- Paste the returned token into the activation UI.
- Delete the `license.lic` file from your local AppData directory:
  - **Path:** `C:\Users\<user>\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 <private-key-hex> <device-hwid> [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": "<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_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/`
