A URL that parses is not a URL that is safe
url-utils gives your agent the two things it cannot improvise: the patterns for clean, GA4-correct links, and three scripts that answer PASS or FAIL instead of guessing. Guidance-only, so there is no backend to call and nothing to 404.
Three failures your parser will never report
Mixed-script homographs
A Cyrillic а inside paypal.com parses cleanly and resolves to a completely different domain. The defense is UTS #46 and UTS #39 mixed-script restriction, not a regex. Single-script domains like münchen.de and 日本.jp stay legitimate and still pass.
Double percent-encoding
%2541 decodes to %41, then to A. One decode pass leaves a residual payload behind, which is both a filter-evasion signal and a way to break deduplication. The verifier flags it rather than silently normalizing it away.
Auto-tag override
Adding manual utm_source and utm_medium to a Google Ads URL overrides the gclid and breaks GA4 cost attribution. Nothing errors. This is the most expensive mistake, because it corrupts the channel carrying the most spend.

Two verdicts, and the arrow between them does not hold.
Three verdicts, not three opinions
Each script prints PASS or FAIL with counts, so the answer is the same every run. Audit mode reports what would change without changing it.
Is this scraped URL safe to store, click, or forward?
node scripts/verify-url.mjs <url>
FAIL MIXED_SCRIPT_HOMOGRAPH
What would stripping actually remove? (changes nothing)
node scripts/strip-tracking.mjs --mode=audit
42/57 URLs cleaned, avg 3.2 params each
Is this campaign link GA4-correct?
node scripts/lint-utm.mjs <url>
FAIL invalid medium 'smm'
What you install, instead of what you re-derive
The first version of this skill was a manual. My agent re-read it on every call, reasoned through hex codes and UTM conventions from scratch, and landed somewhere slightly different each time. It scored 100/100 on security and I still called it not useful.
So the shape changed. url-utils now ships 22 registry assets an agent installs once: eight UTM channel templates covering LinkedIn, X, Facebook, Instagram, Threads, email and both paid networks; six per-platform tracking-parameter sets that catch the whole click-ID family rather than just utm_*; four CMS canonical patterns for WordPress, Shopify, Webflow and Next.js; plus fixtures and a homograph allowlist.
One rule outranks everything else. No parameter you have declared attribution-authoritative is ever stripped. Over-stripping is silent and it destroys a client's campaign measurement, so audit mode exists to show you the diff before anything moves.
Questions I get about it
Guidance-only means what, exactly?
There is no API behind this skill and no typed method to call. Every operation is pure logic your agent's own runtime already performs, so it ships the patterns, the assets and the scripts instead of an endpoint. An earlier version declared a backend that did not exist, and a typed call against it would have returned a 404. This shape is the honest one.
Does it reject international domains?
No, and that distinction is the whole point. Rejecting everything non-ASCII would break real customers. münchen.de is Latin with German diacritics and 日本.jp is Han. Both single-script, both legitimate, both pass. What fails is a label that mixes scripts without being on an allowlist.
Why not just strip every utm_ parameter?
Because utm_-only stripping is not URL hygiene. It misses gclid, gbraid, fbclid, msclkid, ttclid, li_fat_id and yclid entirely. The per-platform configs close that gap, and they are snapshot-dated so you know when a set was last checked against the live rulesets.
Do the network operations work in a sandbox?
Expanding a short link and tracing a redirect chain both need outbound HTTP. In a sandboxed runtime they degrade to a cannot-resolve finding rather than crashing, so a locked-down environment loses those two operations and keeps the other eleven.
Stop letting your agent improvise URLs
Install it from the OPVS marketplace, then ask your agent to clean a batch of links, build a campaign URL, or check one before it trusts it.
Install @di-atomic/url-utils