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- Apple Silicon Mac (M1+)
- macOS 14+
- Swift 6.2+ / Xcode 16+
- Network on first model download (cached afterwards)
| 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/
| 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/...).
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).
- 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)
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.txtimport 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)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)
swift test
swift build -c release --product watermark-purgeDeterministic path (no model download) for CI:
.build/debug/watermark-purge --unicode-only sample.txt -o /tmp/out.txt- 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.
MIT — see LICENSE.