clean-user-facing-text
Clean and finalize authorized natural-language text intended for readers by auditing suspicious invisible Unicode and rewriting…
Remove multi-vendor AI provenance marks: invisible Unicode (Layer A), statistical text watermarks via rewrite (Layer B, always offer), and C2PA/EXIF/XMP/container metadata on PNG/JPEG/WebP/SVG/PDF/DOCX/ODT/HTML/MD/TEX. Covers Claude, Gemini/SynthID-class, OpenAI provenance, and
$ npx -y skills add guillaumemeyer/watermarks-remover --skill remove-ai-marks --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/remove-ai-marksContext preview
The summary Claude sees to decide when to auto-load this skill.
Remove multi-vendor AI provenance marks: invisible Unicode (Layer A), statistical text watermarks via rewrite (Layer B, always offer), and C2PA/EXIF/XMP/container metadata on PNG/JPEG/WebP/SVG/PDF/DOCX/ODT/HTML/MD/TEX. Covers Claude, Gemini/SynthID-class, OpenAI provenance, and
name: remove-ai-marks description: > Remove multi-vendor AI provenance marks: invisible Unicode (Layer A), statistical text watermarks via rewrite (Layer B, always offer), and C2PA/EXIF/XMP/container metadata on PNG/JPEG/WebP/SVG/PDF/DOCX/ODT/HTML/MD/TEX. Covers Claude, Gemini/SynthID-class, OpenAI provenance, and open-LLM sampling marks. Use when the user asks to strip watermarks, remove C2PA/Content Credentials, clean AI metadata, remove invisible Unicode, anti-detect clean AI output, or runs /remove-ai-marks (aliases: /remove-claude-marks).
Multi-vendor anti-detection hygiene for **text** (Unicode + statistical rewrite) and **files** (C2PA / AI metadata across common containers).
Read if needed:
This skill is a **thin client**. All deterministic cleaning machinery runs in a separate HTTP service (this repo's `service/`), so the agent host needs no Python, venvs, or cleaning tools. Call the service with `curl`; never run cleaning scripts directly.
Base URL comes from `WATERMARKS_SERVICE_URL`, default `http://127.0.0.1:8765`:
WM="${WATERMARKS_SERVICE_URL:-http://127.0.0.1:8765}"The service is started either by the operator (`docker compose up -d`, or a published GHCR image) or locally (`make serve`). **Always check it first**, and stop with a clear message if it is unreachable — never fall back to local cleaning:
AUTH_HEADER=()
if [ -n "$WATERMARKS_SERVER_API_KEY" ]; then
AUTH_HEADER=(-H "Authorization: Bearer $WATERMARKS_SERVER_API_KEY")
fi
curl -sf "${AUTH_HEADER[@]}" "$WM/health"
# {"ok": true, "version": "..."}If `WATERMARKS_SERVER_API_KEY` is set on the service, every request (including the health check and capabilities) needs `-H "Authorization: Bearer $WATERMARKS_SERVER_API_KEY"`. The default URL is loopback; when the service runs on another host, set `WATERMARKS_SERVICE_URL` to an `https://` URL so the token is not sent in cleartext, and do not add `-L` (a redirect could forward the token to another host).
curl -s "${AUTH_HEADER[@]}" "$WM/capabilities"Reports which optional tools are available server-side (`c2patool`, `exiftool`, `qpdf`, `ghostscript`), scorers present (`scorers.stylometry`, `scorers.synthid`, `scorers.synthid_http`), text-watermark detectors (`text_detectors.markllm`, `text_detectors.claude-text`), and which heavy backends are configured (`pixel_backends.ctrlregen`, `pixel_backends.diffusion`, `harnesses.markllm`). **Drive your advice from this**: only recommend pixel removal / SynthID scoring / vendor detection when the service reports the backend present.
Payloads are JSON with the file as **base64**. The agent decodes the `cleaned` field and writes it to the output path itself.
| Method | Path | Body | Returns | | --- | --- | --- | --- | | GET | `/health` | — | `{"ok": true, "version": ...}` | | GET | `/capabilities` | — | optional tools / backends present | | GET | `/openapi.json` | — | dynamically generated OpenAPI 3.0.3 spec | | POST | `/inspect` | `{"file": "<base64>", "name": "notes.md"}` | `{"ok", "kind", "suspicious", "report"}` | | POST | `/detect` | `{"file": "<base64>", "name": "notes.txt"}` | `{"ok", "kind", "detections": [...]}` | | POST | `/clean` | `{"file": "<base64>", "name": "notes.md", "options": {...}}` | `{"ok", "kind", "cleaned": "<base64>", "report"}` |
`/clean` and `/inspect` route by the uploaded `name` extension plus the bytes; unrecognized formats answer `kind: "unknown"` (`/inspect`) or 400 (`/clean`). When writing a temp file for pasted text, keep a known extension (`.txt` / `.md`) in the `name` you send.
The machine-readable contract lives at `$WM/openapi.json` — plug it into any OpenAPI tooling (client generators, Swagger UI, editors) instead of hand-rolling clients.
`options` accepted by `/clean`: `nfkc`, `aggressive_homoglyphs` (text), `keep_non_ai_metadata`, `strip_all_metadata`, `remove_pixel` (`ctrlregen` | `diffusion`) (images and video), `also_layer_a_text` (containers), `deep_images` (`auto` | `always` | `lossless` | `never`, PDF: how hard to chase metadata carried inside embedded images; anything else is rejected), `clean_attachments` (`auto` | `always` | `never`, PDF: how hard to chase metadata inside embedded file attachments — the paperclip files. `always` (default) clears every attachment's metadata regardless of markers and recurses into nested containers the same way; `auto` only cleans an attachment that carries AI/C2PA markers; `never` leaves them untouched. Needs `qpdf`. Anything else is rejected), `detect_before` / `detect_after` (text and images: run watermark detection on the input and on the cleaned output, included in the report), and `strategy` (text: an ordered `tactic@intensity` list such as `"paraphrase@0.8,mlm@0.2"` that runs the Layer B rewrite after Layer A; when omitted the default from `config/clean_strategy.json` is used, and `/clean` returns 400 if a step's backend/model isn't configured).
**Inspect first** (decide, don't guess):
curl -s -X POST "${AUTH_HEADER[@]}" "$WM/inspect" -H 'Content-Type: application/json' \
-d "{\"file\": \"$(base64 < notes.md | tr -d '\n')\", \"name\": \"notes.md\"}"**Clean** (text / image / container are auto-detected by name + bytes):
curl -s -X POST "${AUTH_HEADER[@]}" "$WM/clean" -H 'Content-Type: application/json' \
-d "{\"file\": \"$(base64 < notes.md | tr -d '\n')\", \"name\": \"notes.md\"}"Decode the returned `cleaned` base64 into the output file (`*.cleaned.*` by default unless the
Agent skill + stdlib Python service to strip multi-vendor AI provenance marks from text and files — for privacy and hygiene on content you own. The skill is a thin client: it drives the machinery over HTTP, so the agent host needs no Python.
Clean and finalize authorized natural-language text intended for readers by auditing suspicious invisible Unicode and rewriting…