WatermarkPurge

1.0.0

On-device multi-pass text watermark remover for Apple Silicon (Swift + MLX)
asaptf/swift-watermark-remover

What's New

1.0.0

2026-08-12T07:41:00Z

WatermarkPurge 1.0.0

First public release: fully on-device multi-pass text watermark disruption for Apple Silicon.

Highlights

  • Library WatermarkPurgeCore — embed the purge pipeline in your Swift app
  • CLI watermark-purge — one-command rewrite from the terminal
  • Multi-pass pipeline: Unicode hygiene → progressive rewrite → optional polish → normalize
  • Structure-aware chunking with protected regions (code fences, block quotes)
  • Local quality metrics (n-gram disruption + meaning preservation)
  • Auto model selection by unified memory (MLX, 4-bit)

Requirements

  • Apple Silicon Mac (M1+)
  • macOS 14+
  • Swift 6.2+ / Xcode 16+

Install (SPM)

.package(url: "https://github.com/asaptf/swift-watermark-remover.git", from: "1.0.0")

CLI

./run.sh document.md -o cleaned.md
# or after bootstrap:
watermark-purge input.md -o cleaned.md --intensity aggressive --stats

Library

import WatermarkPurgeCore

let service = PurgeService()
var config = PurgeConfig(intensity: .balanced, preferredModel: "auto")
let result = try await service.purge(text: source, config: config)

MIT License.

WatermarkPurge

One-command, fully on-device multi-pass text rewriting for Apple Silicon (MLX).

Built for disrupting statistical / sampling-based text watermarks (token-distribution schemes, invisible Unicode steganography, and similar patterns) without sending text to the cloud and without re-stamping via Claude-class models.

# One click: build (first time) + download model (first generative run) + process
./run.sh document.md -o cleaned.md

# Or explicit bootstrap once, then reuse the binary
./scripts/bootstrap.sh
./run.sh notes.txt --intensity aggressive --stats

Requirements

  • Apple Silicon Mac (M1+)
  • macOS 14+
  • Swift 6.2+ / Xcode 16+
  • Network on first model download (cached afterwards)

One-command UX

Command What it does
./run.sh input.txt -o out.txt Build if needed, auto-download model, run full pipeline
./run.sh --download-model Only download/cache the default model
./run.sh --list-models Show curated aliases + auto pick for your RAM
./run.sh --inspect input.txt Unicode / structure report, no generation
./run.sh input.txt --unicode-only Deterministic hygiene only (no model)
make bootstrap Resolve deps, release build, download model
make purge FILE=in.txt OUT=out.txt Convenience wrapper

Models are cached under:

~/Library/Application Support/WatermarkPurge/Hub/

Default model by memory

Unified memory Auto model
≤16 GB llama3.2-3b (4-bit)
~32 GB qwen3-4b (4-bit)
~64 GB qwen2.5-7b (4-bit)
64 GB+ qwen3-8b (4-bit)

Override with --model qwen3-4b or a full HF id (mlx-community/...).

Multi-pass pipeline

Pass 0  Unicode hygiene          (always, deterministic)
Pass 1…N Progressive rewrite     (paraphrase / style-shift / outline / back-translation)
Pass N+1 Optional polish
Final   Typography normalize     (always, deterministic)
Intensity Generative passes Character
light 1 light paraphrase
balanced 2 paraphrase + style-shift (+ polish)
aggressive 3 + outline regenerate
maximum 4 + simulated back-translation

Never uses Claude / Anthropic models for rewrite (avoids re-stamping).

Strength features

  • Structure-aware chunking (headings, lists)
  • Protected regions: fenced code, block quotes
  • Smart stitch with before/after context in prompts
  • Adaptive intensity for short/long docs
  • Local quality metrics: n-gram disruption + meaning preservation
  • Retry chunk on low quality
  • Intermediate pass logging (--log-intermediates)
  • Streaming cancellation via Swift concurrency (Task.cancel)

CLI

watermark-purge input.md -o cleaned.md \
  --intensity aggressive \
  --passes 4 \
  --model qwen3-4b \
  --stats

watermark-purge --inspect input.txt
watermark-purge --pipeline balanced input.txt
watermark-purge --download-model --model llama3.2-3b
watermark-purge --unicode-only dirty.txt -o clean.txt

Library

import WatermarkPurgeCore

let service = PurgeService()
var config = PurgeConfig(intensity: .balanced, preferredModel: "auto")
let result = try await service.purge(text: source, config: config) { event in
    // progress…
}
print(result.cleaned)
print(result.quality.disruptionScore, result.quality.preservationScore)

Project layout

Sources/WatermarkPurgeCore/
  Config/          PurgeConfig, intensity
  Passes/          Unicode, paraphrase, polish, normalize
  Pipeline/        orchestrator
  MLX/             model catalog + ModelManager
  Chunking/        structure-aware chunk/stitch
  Quality/         n-gram disruption + preservation
  Prompts/         multi-intensity templates
  Text/            Unicode hygiene
Sources/watermark-purge/   CLI
Tests/                     unit tests (unicode, metrics, chunker, pipeline)

Build & test

swift test
swift build -c release --product watermark-purge

Deterministic path (no model download) for CI:

.build/debug/watermark-purge --unicode-only sample.txt -o /tmp/out.txt

Limitations

  • On-device quality depends on the local model size; 1–3B is fast but weaker on long technical prose.
  • Statistical watermark disruption is heuristic — no public detector guarantees 100% removal.
  • Meaning preservation is scored with cheap local metrics (numbers, entities, length), not an entailment model.
  • Homoglyph cleanup is conservative; full UTS #39 skeletons are not rewritten into the document.

License

MIT — see LICENSE.

Description

  • Swift Tools 6.2.0
View More Packages from this Author

Dependencies

Last updated: Mon Aug 31 2026 10:39:38 GMT-0900 (Hawaii-Aleutian Daylight Time)