agentlinkops 0.6.13 → 0.6.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +2 -2
- package/CHANGELOG.md +25 -2
- package/PACK-ARTIFACT.json +16 -0
- package/PACKS.json +591 -0
- package/README.md +34 -48
- package/cli/adapters/suppliers.js +1 -1
- package/cli/agent-setup.js +101 -17
- package/cli/campaign-bridge.js +325 -0
- package/cli/campaign-outcomes.js +107 -0
- package/cli/catalog.js +45702 -18674
- package/cli/check.js +40 -10
- package/cli/client.js +2 -1
- package/cli/commands.js +17 -8
- package/cli/connection.js +92 -18
- package/cli/contact-adoption.js +91 -0
- package/cli/context.js +13 -2
- package/cli/doctor.js +15 -4
- package/cli/import.js +7 -1
- package/cli/key-store.js +115 -0
- package/cli/ledger.js +1 -0
- package/cli/local-result.js +41 -6
- package/cli/main.js +51 -12
- package/cli/mcp-client.js +2 -2
- package/cli/migration.js +308 -0
- package/cli/prompt.js +25 -0
- package/cli/public-http.js +168 -0
- package/cli/state.js +14 -0
- package/cli/sync.js +1 -1
- package/package.json +7 -8
- package/references/citation-panels.md +2 -0
- package/references/cli.md +2 -2
- package/references/disavow.md +1 -1
- package/references/discovery-handoff.md +1 -1
- package/references/local-crm-draft-handoff.md +1 -1
- package/references/portable-ledger-handoff.md +11 -10
- package/references/recipes/catalog.json +263 -7
- package/references/recipes/command-notes.md +5 -3
- package/references/recipes/internal-link-improvement.md +25 -0
- package/references/recipes/placement-reconciliation.md +53 -1
- package/scripts/portable-handoff.py +17 -4
- package/shared/admission-contract.js +3 -2
- package/shared/admission-report-contract.js +3 -2
- package/shared/analytics-history-contract.js +15 -0
- package/shared/campaign-outcome-contract.js +12 -0
- package/shared/campaign-prospect-contract.js +23 -0
- package/shared/classification-contract.js +54 -0
- package/shared/contact-record-contract.js +27 -0
- package/shared/disavow-contract.js +37 -9
- package/shared/index-observation-contract.js +5 -4
- package/shared/internal-links-contract.js +143 -0
- package/shared/link-category-contract.js +35 -0
- package/shared/link-category.js +138 -0
- package/shared/link-labels.js +26 -0
- package/shared/link-summary-contract.js +17 -0
- package/shared/observation-event.js +1 -1
- package/shared/onboarding.js +20 -0
- package/shared/response-contract.js +38 -5
- package/shared/search-connection-contract.js +2 -1
- package/shared/table-page-contract.js +13 -0
- package/skills/agentlinkops-assets/SKILL-DEPS.json +290 -0
- package/skills/agentlinkops-assets/SKILL.md +3 -1
- package/skills/agentlinkops-assets/agents/openai.yaml +1 -1
- package/skills/agentlinkops-assets/references/portable/references/cli.md +220 -0
- package/skills/agentlinkops-assets/references/portable/references/disavow.md +73 -0
- package/skills/agentlinkops-assets/references/portable/references/reports.md +53 -0
- package/skills/agentlinkops-assets/references/portable/scripts/agentlinkops.py +676 -0
- package/skills/agentlinkops-assets/references/portable/scripts/disavow.py +207 -0
- package/skills/agentlinkops-assets/references/portable/scripts/migrations/001_crm.sql +45 -0
- package/skills/agentlinkops-assets/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
- package/skills/agentlinkops-assets/references/portable/scripts/migrations/003_disavow.sql +20 -0
- package/skills/agentlinkops-assets/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
- package/skills/agentlinkops-assets/references/portable/scripts/report.py +113 -0
- package/skills/agentlinkops-assets/references/portable/templates/campaigns.v1.json +65 -0
- package/skills/agentlinkops-campaigns/SKILL-DEPS.json +336 -0
- package/skills/agentlinkops-campaigns/SKILL.md +7 -5
- package/skills/agentlinkops-campaigns/agents/openai.yaml +1 -1
- package/skills/agentlinkops-campaigns/references/first-campaign.md +31 -0
- package/skills/agentlinkops-campaigns/references/mailbox-campaign.md +46 -0
- package/skills/agentlinkops-campaigns/references/portable/references/cli.md +220 -0
- package/skills/agentlinkops-campaigns/references/portable/references/disavow.md +73 -0
- package/skills/agentlinkops-campaigns/references/portable/references/reports.md +53 -0
- package/skills/agentlinkops-campaigns/references/portable/scripts/agentlinkops.py +676 -0
- package/skills/agentlinkops-campaigns/references/portable/scripts/disavow.py +207 -0
- package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/001_crm.sql +45 -0
- package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
- package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/003_disavow.sql +20 -0
- package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
- package/skills/agentlinkops-campaigns/references/portable/scripts/report.py +113 -0
- package/skills/agentlinkops-campaigns/references/portable/templates/campaigns.v1.json +65 -0
- package/skills/agentlinkops-connect/SKILL-DEPS.json +104 -0
- package/skills/agentlinkops-connect/SKILL.md +2 -0
- package/skills/agentlinkops-connect/agents/openai.yaml +1 -1
- package/skills/agentlinkops-connect/references/agentlinkops.md +164 -62
- package/skills/agentlinkops-connect/references/placement-workflows.md +19 -0
- package/skills/agentlinkops-crm/SKILL-DEPS.json +381 -0
- package/skills/agentlinkops-crm/SKILL.md +26 -9
- package/skills/agentlinkops-crm/agents/openai.yaml +1 -1
- package/skills/agentlinkops-crm/references/portable/references/cli.md +220 -0
- package/skills/agentlinkops-crm/references/portable/references/disavow.md +73 -0
- package/skills/agentlinkops-crm/references/portable/references/local-crm-draft-handoff.md +76 -0
- package/skills/agentlinkops-crm/references/portable/references/portable-ledger-handoff.md +51 -0
- package/skills/agentlinkops-crm/references/portable/references/reports.md +53 -0
- package/skills/agentlinkops-crm/references/portable/scripts/agentlinkops.py +676 -0
- package/skills/agentlinkops-crm/references/portable/scripts/disavow.py +207 -0
- package/skills/agentlinkops-crm/references/portable/scripts/migrations/001_crm.sql +45 -0
- package/skills/agentlinkops-crm/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
- package/skills/agentlinkops-crm/references/portable/scripts/migrations/003_disavow.sql +20 -0
- package/skills/agentlinkops-crm/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
- package/skills/agentlinkops-crm/references/portable/scripts/outreach-crm-draft.py +121 -0
- package/skills/agentlinkops-crm/references/portable/scripts/portable-handoff.py +197 -0
- package/skills/agentlinkops-crm/references/portable/scripts/report.py +113 -0
- package/skills/agentlinkops-crm/references/portable/templates/campaigns.v1.json +65 -0
- package/skills/agentlinkops-discovery/SKILL-DEPS.json +301 -0
- package/skills/agentlinkops-discovery/SKILL.md +4 -2
- package/skills/agentlinkops-discovery/references/portable/references/cli.md +220 -0
- package/skills/agentlinkops-discovery/references/portable/references/disavow.md +73 -0
- package/skills/agentlinkops-discovery/references/portable/references/discovery-handoff.md +32 -0
- package/skills/agentlinkops-discovery/references/portable/references/reports.md +53 -0
- package/skills/agentlinkops-discovery/references/portable/scripts/agentlinkops.py +676 -0
- package/skills/agentlinkops-discovery/references/portable/scripts/disavow.py +207 -0
- package/skills/agentlinkops-discovery/references/portable/scripts/migrations/001_crm.sql +45 -0
- package/skills/agentlinkops-discovery/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
- package/skills/agentlinkops-discovery/references/portable/scripts/migrations/003_disavow.sql +20 -0
- package/skills/agentlinkops-discovery/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
- package/skills/agentlinkops-discovery/references/portable/scripts/report.py +113 -0
- package/skills/agentlinkops-discovery/references/portable/templates/campaigns.v1.json +65 -0
- package/skills/agentlinkops-discovery/references/saved-backlink-workflows.md +23 -0
- package/src/competitors/contract.js +17 -1
- package/src/context/site-profile.js +34 -121
- package/src/corpus/acquire.js +68 -32
- package/src/corpus/extract.js +147 -0
- package/src/corpus/frontier.js +151 -0
- package/src/discovery/contract.js +8 -0
- package/src/discovery/import.js +13 -1
- package/src/mentions.js +193 -0
- package/src/page-acquisition/acquire.js +50 -0
- package/src/page-acquisition/bytes.js +138 -0
- package/src/page-acquisition/capture.js +11 -0
- package/src/page-acquisition/contracts.js +67 -0
- package/src/page-acquisition/documents.js +166 -0
- package/src/page-acquisition/http.js +307 -0
- package/src/page-acquisition/policy.js +20 -0
- package/src/page-acquisition/recovery.js +164 -0
- package/src/page-acquisition/render-failure.js +82 -0
- package/src/page-extraction/html.js +359 -0
- package/src/page-extraction/index.js +30 -0
- package/src/page-extraction/metadata.js +16 -0
- package/src/response-shaping.js +4 -3
- package/src/tool-views.js +22 -12
- package/src/verifier/fetch.js +4 -339
- package/src/verifier/index.js +81 -23
- package/src/verifier/page-quality.js +14 -0
- package/src/verifier/parser.js +2 -241
- package/src/verifier/readiness.js +64 -13
- package/src/verifier/render-evidence.js +25 -2
- package/src/verifier/target.js +201 -0
- package/src/verifier/transition.js +18 -3
- package/src/verifier/url.js +31 -0
- package/src/watch-crawlability.js +144 -0
- package/skills/content-defingerprinting/SKILL.md +0 -29
- package/skills/content-defingerprinting/scripts/watermarks/README.md +0 -102
- package/skills/content-defingerprinting/scripts/watermarks/UPSTREAM.json +0 -26
- package/skills/content-defingerprinting/scripts/watermarks/revendor.sh +0 -52
- package/skills/content-defingerprinting/scripts/watermarks/vendor/LICENSE.upstream +0 -21
- package/skills/content-defingerprinting/scripts/watermarks/vendor/clean_text.py +0 -96
- package/skills/content-defingerprinting/scripts/watermarks/vendor/common.py +0 -432
- package/skills/content-defingerprinting/scripts/watermarks/vendor/inspect_text.py +0 -86
- package/skills/content-defingerprinting/scripts/watermarks/vendor/score_stylometry.py +0 -460
- package/skills/content-defingerprinting/scripts/watermarks/vendor/text_unicode.py +0 -651
- package/skills/content-defingerprinting/scripts/watermarks/verify-vendor.py +0 -27
- package/skills/content-defingerprinting/scripts/watermarks/wm-scan.py +0 -212
- package/skills/internal-linking-optimizer/SKILL.md +0 -56
- package/skills/internal-linking-optimizer/references/source-prompt.md +0 -76
- package/skills/pre-publish-review/SKILL.md +0 -179
- package/skills/source-cited-content-builder/SKILL.md +0 -56
- package/skills/source-cited-content-builder/references/source-prompt.md +0 -19
- package/skills/source-cited-humanizer/SKILL.md +0 -20
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# Stage 2b — the invisible-character scan
|
|
2
|
-
|
|
3
|
-
**Required on every page, every time, before it ships.** It is part of stage 2
|
|
4
|
-
(humanize), and it is the half of stage 2 that a human reviewer physically cannot do.
|
|
5
|
-
|
|
6
|
-
## What it is
|
|
7
|
-
|
|
8
|
-
A vendored copy of the Layer A engine from
|
|
9
|
-
[watermarks-remover](https://github.com/guillaumemeyer/watermarks-remover) (MIT),
|
|
10
|
-
pinned in `UPSTREAM.json`, plus `wm-scan.py`, our wrapper that walks content roots
|
|
11
|
-
and reports line numbers.
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
python3 wm-scan.py <path> [<path> ...] # report, exit 1 on findings
|
|
15
|
-
python3 wm-scan.py --fix <path> # clean in place, then re-verify
|
|
16
|
-
python3 wm-scan.py --json <path> # for a gate
|
|
17
|
-
python3 verify-vendor.py # prove the engine is the pinned upstream one
|
|
18
|
-
./revendor.sh [ref] # refresh from upstream and re-pin
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Why it is required rather than recommended
|
|
22
|
-
|
|
23
|
-
Because it catches a defect class that survives every other check we have. These
|
|
24
|
-
characters are invisible in the editor, invisible in review, invisible in the
|
|
25
|
-
rendered page, and they ride in on ordinary copy-paste from PDFs, Word and model
|
|
26
|
-
output. What they break is everything that reads our prose as text:
|
|
27
|
-
|
|
28
|
-
**A zero-width space inside "affidavit" means the banned-phrase scan, the citation
|
|
29
|
-
audits and every `grep` in the repo stop seeing the word.** The page looks fine and
|
|
30
|
-
the checks that are supposed to guard it have quietly stopped applying.
|
|
31
|
-
|
|
32
|
-
It is not a theoretical risk. First run across the estate, tax-sale, trucking,
|
|
33
|
-
coverage and proxy repos, 2026-08-18:
|
|
34
|
-
|
|
35
|
-
| repo | finding |
|
|
36
|
-
|---|---|
|
|
37
|
-
| settled | a soft hyphen inside `affi[U+00AD]davit` in rendered prose, plus 4 more in Missouri state data |
|
|
38
|
-
| Tax Sale Atlas | Wingdings/PUA glyphs (`U+F0B7`, `U+F073`) in 5 counties' `sale.json`, including inside an `evidence` string |
|
|
39
|
-
| covered-weight | 1,200+ PUA glyphs across the archived payer-policy extractions |
|
|
40
|
-
| haulhandbook, proxy-compare | clean |
|
|
41
|
-
|
|
42
|
-
## The archive rule, which is not negotiable
|
|
43
|
-
|
|
44
|
-
**`--fix` refuses to touch anything under `sources/`, `snapshots/`, `evidence/`,
|
|
45
|
-
`_sources/`, `price-history/` or `raw/`.**
|
|
46
|
-
|
|
47
|
-
That tree is the evidence every `_source` and `verified_quote` resolves against. The
|
|
48
|
-
marks in it are glyph artifacts of a document *somebody else published*, and they are
|
|
49
|
-
part of what we recorded. Rewriting the archive so a scan goes green is precisely the
|
|
50
|
-
tampering the provenance chain exists to detect — one edited file and one re-forged
|
|
51
|
-
hash is the whole covered-weight story.
|
|
52
|
-
|
|
53
|
-
So: **the archive is reported and left alone. The QUOTE lifted out of it is the thing
|
|
54
|
-
to check.** A hit inside a quote field is a provenance question before it is a
|
|
55
|
-
formatting one: open the source, see what the character is, and decide. A PDF bullet
|
|
56
|
-
glyph rendered into a quote is noise the reader never saw and can be dropped; a real
|
|
57
|
-
character cannot.
|
|
58
|
-
|
|
59
|
-
## The load-bearing rule (found by auditing, 2026-08-18)
|
|
60
|
-
|
|
61
|
-
**`--fix` also holds back any file where the mark sits on a line carrying regex-replacement
|
|
62
|
-
syntax**, tagging it `[LOAD-BEARING? regex literal on that line - review by hand]`.
|
|
63
|
-
|
|
64
|
-
The estate-wide audit found three of these, and every one is a **sanitizer written with the
|
|
65
|
-
literal characters it strips**:
|
|
66
|
-
|
|
67
|
-
```js
|
|
68
|
-
.replace(/[<zwsp><zwnj><zwj><bom>]+/g, '') covered-weight extract-ehb-chart-rows.mjs:127
|
|
69
|
-
.replace(/ | |<zwsp>/g, " ") proxy-compare scraper/vendors/proxyscrape.ts:112
|
|
70
|
-
.replace(/<nbsp>/g, " ") proxy-compare scraper/vendors/rayobyte.ts:67
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Auto-cleaning those empties the character class and silently breaks the guard, letting through
|
|
74
|
-
exactly the contamination it existed to catch. **A blanket `--fix` across the estate would have
|
|
75
|
-
broken three sanitizers in two repos** — the scanner would have disabled its own counterparts.
|
|
76
|
-
|
|
77
|
-
This is the argument for classify-before-fix in one example. A hit is not a defect; a hit is a
|
|
78
|
-
question. The three answers that mean "do not touch" are: it is archived evidence, it is a
|
|
79
|
-
sourced quote, or it is load-bearing.
|
|
80
|
-
|
|
81
|
-
## What we deliberately do NOT run
|
|
82
|
-
|
|
83
|
-
The upstream project does more than this, and the rest of it is off by decision, not
|
|
84
|
-
by oversight.
|
|
85
|
-
|
|
86
|
-
| layer | status | why |
|
|
87
|
-
|---|---|---|
|
|
88
|
-
| **A** — invisible Unicode, exotic spaces | **required** | pure contamination, deterministic, zero judgement needed |
|
|
89
|
-
| **Files** — EXIF / XMP on our own generated covers | available | harmless hygiene; our covers are generated by our own script, so there is little to strip |
|
|
90
|
-
| **C2PA manifests** | **detect, never strip** | C2PA is a provenance record. On one of our own covers it is a signal that something upstream generated an image with AI — on sites that say "never an AI image", that is a question to answer, not noise to delete |
|
|
91
|
-
| **B** — rewriting text to defeat statistical watermarks | **opt-in, off** | our humanize stage already rewrites AI-pattern prose on quality grounds. Making "defeat provenance watermarking" an explicit standing step across five YMYL properties is a decision for the owner, not a default |
|
|
92
|
-
| **Pixels** — CtrlRegen image watermark removal | **not installed** | needs a model backend, and it has no use on sites that generate their own covers. Its actual use is stripping watermarks off images we did not make |
|
|
93
|
-
|
|
94
|
-
If any of those need turning on, that is a conversation, and the reason belongs in
|
|
95
|
-
this file next to the change.
|
|
96
|
-
|
|
97
|
-
## Keeping it honest
|
|
98
|
-
|
|
99
|
-
`vendor/` is byte-identical to upstream and must stay that way — `verify-vendor.py`
|
|
100
|
-
proves it, and `revendor.sh` is how it changes. Do not edit it in place. The last time
|
|
101
|
-
a copied source of truth got edited locally in this estate it became a house rule
|
|
102
|
-
nobody agreed to, sitting in a file that looked authoritative.
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"upstream": "https://github.com/guillaumemeyer/watermarks-remover",
|
|
3
|
-
"license": "MIT",
|
|
4
|
-
"pinned_commit": "8318d4df79de18a8c18c21eecb4b796192f34805",
|
|
5
|
-
"pinned_commit_date": "2026-08-18T18:28:19-07:00",
|
|
6
|
-
"vendored_on": "2026-08-18",
|
|
7
|
-
"vendored_from": "service/scripts/",
|
|
8
|
-
"modified": false,
|
|
9
|
-
"layers_vendored": [
|
|
10
|
-
"A: invisible Unicode / space homoglyphs (deterministic)"
|
|
11
|
-
],
|
|
12
|
-
"layers_deliberately_not_vendored": [
|
|
13
|
-
"B: statistical token-sampling watermark removal by rewriting (opt-in, needs a decision)",
|
|
14
|
-
"Files: C2PA / EXIF / XMP stripping (we DETECT and report C2PA, we do not strip it)",
|
|
15
|
-
"Pixels: CtrlRegen image watermark removal (not used; our covers are our own generated assets)"
|
|
16
|
-
],
|
|
17
|
-
"note": "Files are byte-identical to upstream. Re-vendor with revendor.sh; never edit in place.",
|
|
18
|
-
"sha256": {
|
|
19
|
-
"LICENSE.upstream": "c70d55b5722484a6be261f755b5bdfd5335244d0389436eb116aa8357243347c",
|
|
20
|
-
"clean_text.py": "8cde7a3c75c06687f7403f85364eda3380ace4c0ab5e5eb7d1f89f9e0c50ce04",
|
|
21
|
-
"common.py": "311d78fb23351d56b9a2601a7af3b8d5bf834e2e55289796e1f18184a3b9f49d",
|
|
22
|
-
"inspect_text.py": "a61e5c524f72de8122750114f09973cec826641d798f417d811c7e5649218479",
|
|
23
|
-
"score_stylometry.py": "cd3dae134d5f641120218fd525afd523d4a0bfb52d4d73c0e92dc04e3bcc6d59",
|
|
24
|
-
"text_unicode.py": "cd21c5700184a6bb4a43089f546531a2692bf140413954ea9b93f4a67f1f3a5e"
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Re-vendor the watermarks-remover Layer A engine from upstream.
|
|
3
|
-
#
|
|
4
|
-
# ./revendor.sh [ref] # default: main
|
|
5
|
-
#
|
|
6
|
-
# The vendored files are byte-identical to upstream and MUST stay that way. If a
|
|
7
|
-
# behaviour needs changing, change it in wm-scan.py (ours) or send a PR upstream.
|
|
8
|
-
# Editing vendor/ in place produces a house rule nobody agreed to, in a file that
|
|
9
|
-
# looks like somebody else's source of truth. That has happened here before with a
|
|
10
|
-
# banned-phrase list.
|
|
11
|
-
set -euo pipefail
|
|
12
|
-
|
|
13
|
-
REF="${1:-main}"
|
|
14
|
-
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
-
REPO="https://github.com/guillaumemeyer/watermarks-remover"
|
|
16
|
-
FILES=(common.py text_unicode.py inspect_text.py clean_text.py score_stylometry.py)
|
|
17
|
-
TMP="$(mktemp -d)"
|
|
18
|
-
trap 'rm -rf "$TMP"' EXIT
|
|
19
|
-
|
|
20
|
-
echo "Fetching $REPO @ $REF ..."
|
|
21
|
-
git clone --quiet --depth 1 --branch "$REF" "$REPO" "$TMP/src" 2>/dev/null \
|
|
22
|
-
|| git clone --quiet "$REPO" "$TMP/src"
|
|
23
|
-
git -C "$TMP/src" checkout --quiet "$REF" 2>/dev/null || true
|
|
24
|
-
|
|
25
|
-
for f in "${FILES[@]}"; do
|
|
26
|
-
cp "$TMP/src/service/scripts/$f" "$HERE/vendor/$f"
|
|
27
|
-
done
|
|
28
|
-
cp "$TMP/src/LICENSE" "$HERE/vendor/LICENSE.upstream"
|
|
29
|
-
|
|
30
|
-
SHA="$(git -C "$TMP/src" rev-parse HEAD)"
|
|
31
|
-
DATE="$(git -C "$TMP/src" log -1 --format=%cI)"
|
|
32
|
-
TODAY="$(date +%Y-%m-%d)"
|
|
33
|
-
|
|
34
|
-
python3 - "$HERE" "$SHA" "$DATE" "$TODAY" <<'PY'
|
|
35
|
-
import hashlib, io, json, os, sys
|
|
36
|
-
here, sha, date, today = sys.argv[1:5]
|
|
37
|
-
m = json.load(io.open(os.path.join(here, 'UPSTREAM.json'), encoding='utf-8'))
|
|
38
|
-
files = {}
|
|
39
|
-
for f in sorted(os.listdir(os.path.join(here, 'vendor'))):
|
|
40
|
-
if f.startswith('._'): # AppleDouble shadows on the exFAT volumes
|
|
41
|
-
continue
|
|
42
|
-
files[f] = hashlib.sha256(io.open(os.path.join(here, 'vendor', f), 'rb').read()).hexdigest()
|
|
43
|
-
changed = [f for f, h in files.items() if m['sha256'].get(f) != h]
|
|
44
|
-
m.update(pinned_commit=sha, pinned_commit_date=date, vendored_on=today, sha256=files)
|
|
45
|
-
io.open(os.path.join(here, 'UPSTREAM.json'), 'w', encoding='utf-8').write(json.dumps(m, indent=2) + "\n")
|
|
46
|
-
print("pinned %s (%s)" % (sha[:12], date))
|
|
47
|
-
print("changed: %s" % (", ".join(changed) if changed else "nothing"))
|
|
48
|
-
PY
|
|
49
|
-
|
|
50
|
-
echo
|
|
51
|
-
echo "Now re-run the scan on a repo you know the answer for, before trusting it:"
|
|
52
|
-
echo " python3 $HERE/wm-scan.py <a path with a known hit>"
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 watermarks-remover contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Strip invisible Unicode / normalize space homoglyphs (Layer A)."""
|
|
3
|
-
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
import argparse
|
|
7
|
-
import json
|
|
8
|
-
import sys
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
|
|
11
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
12
|
-
|
|
13
|
-
from common import (
|
|
14
|
-
backup_path,
|
|
15
|
-
cleaned_path,
|
|
16
|
-
eprint,
|
|
17
|
-
read_text_input,
|
|
18
|
-
write_text_output,
|
|
19
|
-
)
|
|
20
|
-
from text_unicode import clean_text
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def main() -> int:
|
|
24
|
-
p = argparse.ArgumentParser(description=__doc__)
|
|
25
|
-
p.add_argument("path", nargs="?", default="-", help="Input text file, or - for stdin")
|
|
26
|
-
p.add_argument("-o", "--output", help="Output path (default: stdout or *.cleaned.*)")
|
|
27
|
-
p.add_argument("--nfkc", action="store_true", help="Apply Unicode NFKC after scrub")
|
|
28
|
-
p.add_argument(
|
|
29
|
-
"--aggressive-homoglyphs",
|
|
30
|
-
action="store_true",
|
|
31
|
-
help="Map Cyrillic/fullwidth Latin confusables to ASCII Latin",
|
|
32
|
-
)
|
|
33
|
-
p.add_argument(
|
|
34
|
-
"--no-normalize-spaces",
|
|
35
|
-
action="store_true",
|
|
36
|
-
help="Do not rewrite exotic spaces to U+0020",
|
|
37
|
-
)
|
|
38
|
-
p.add_argument(
|
|
39
|
-
"--strip-emoji-glue",
|
|
40
|
-
action="store_true",
|
|
41
|
-
help="Paranoid: strip all load-bearing invisibles too (emoji glue, script joiners, flag tags, same-script fillers/selectors, orthographic Cf)",
|
|
42
|
-
)
|
|
43
|
-
p.add_argument(
|
|
44
|
-
"--strip-bidi",
|
|
45
|
-
action="store_true",
|
|
46
|
-
help="Also strip legitimate RTL/LTR directional marks and isolates",
|
|
47
|
-
)
|
|
48
|
-
p.add_argument("--stats", action="store_true", help="Print stats JSON to stderr")
|
|
49
|
-
p.add_argument(
|
|
50
|
-
"--force-text",
|
|
51
|
-
action="store_true",
|
|
52
|
-
help="Clean even when the input looks like a binary container "
|
|
53
|
-
"(this rewrites the bytes and will corrupt the file)",
|
|
54
|
-
)
|
|
55
|
-
p.add_argument(
|
|
56
|
-
"--in-place",
|
|
57
|
-
action="store_true",
|
|
58
|
-
help="Overwrite input file (creates .bak backup)",
|
|
59
|
-
)
|
|
60
|
-
args = p.parse_args()
|
|
61
|
-
|
|
62
|
-
text = read_text_input(args.path, allow_binary=args.force_text)
|
|
63
|
-
cleaned, stats = clean_text(
|
|
64
|
-
text,
|
|
65
|
-
nfkc=args.nfkc,
|
|
66
|
-
aggressive_homoglyphs=args.aggressive_homoglyphs,
|
|
67
|
-
normalize_spaces=not args.no_normalize_spaces,
|
|
68
|
-
strip_emoji_glue=args.strip_emoji_glue,
|
|
69
|
-
strip_bidi=args.strip_bidi,
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
out = args.output
|
|
73
|
-
if args.in_place:
|
|
74
|
-
if args.path in (None, "-"):
|
|
75
|
-
eprint("--in-place requires a file path")
|
|
76
|
-
return 2
|
|
77
|
-
src = Path(args.path)
|
|
78
|
-
backup_path(src) # side effect: keep a .bak of the original before overwriting
|
|
79
|
-
out = str(src)
|
|
80
|
-
elif out is None and args.path not in (None, "-"):
|
|
81
|
-
out = str(cleaned_path(Path(args.path)))
|
|
82
|
-
|
|
83
|
-
write_text_output(cleaned, out)
|
|
84
|
-
|
|
85
|
-
if args.stats:
|
|
86
|
-
eprint(json.dumps(stats, indent=2, ensure_ascii=False))
|
|
87
|
-
else:
|
|
88
|
-
eprint(
|
|
89
|
-
f"removed={stats['removed_count']} replaced={stats['replaced_count']} "
|
|
90
|
-
f"len {stats['input_length']}->{stats['output_length']}"
|
|
91
|
-
)
|
|
92
|
-
return 0
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if __name__ == "__main__":
|
|
96
|
-
raise SystemExit(main())
|