@ggalmazor/decoy 1.1.0 → 1.3.0
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/README.md +3 -2
- package/dist-cli/decoy.mjs +84 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,10 +40,11 @@ decoy find invoice.pdf -m 'ES\d{2}( ?\d{4}){5}' # only the matches
|
|
|
40
40
|
decoy redact invoice.pdf -m 'ES\d{2}( ?\d{4}){5}' -m '[\w.+-]+@[\w-]+\.[\w.]+' --mood corporate
|
|
41
41
|
decoy redact screenshot.png -b 120,340,260,32 # images have no text layer: boxes are x,y,w,h[@page]
|
|
42
42
|
decoy verify invoice-decoyed.pdf -m 'ES\d{2}( ?\d{4}){5}' --original invoice.pdf
|
|
43
|
-
decoy
|
|
43
|
+
decoy update # is there a newer version? The only network call decoy makes
|
|
44
|
+
decoy mcp # find_text, redact, verify, and check_update as MCP tools over stdio
|
|
44
45
|
```
|
|
45
46
|
|
|
46
|
-
`redact` covers every match of every `-m` pattern (a JavaScript regular expression, matched per line so a number split at spaces still matches) and every `-b` box with a decoy, then writes `<name>-decoyed.pdf` or `.png` next to the input. `--mood`, `--clean`, `--cell`, `--seed`, `--text`, and `--watermark` are the panel's controls; `--json` on any command returns machine-readable output. `verify` proves what an agent can prove about a file it did not watch being made: a PDF has no text layer and no metadata, no pattern is still readable, and, given `--original`, only the matched boxes changed.
|
|
47
|
+
`redact` covers every match of every `-m` pattern (a JavaScript regular expression, matched per line so a number split at spaces still matches) and every `-b` box with a decoy, then writes `<name>-decoyed.pdf` or `.png` next to the input. `--mood`, `--clean`, `--cell`, `--seed`, `--text`, and `--watermark` are the panel's controls; `--json` on any command returns machine-readable output. `verify` proves what an agent can prove about a file it did not watch being made: a PDF has no text layer and no metadata, no pattern is still readable, and, given `--original`, only the matched boxes changed. `update` is the one command that touches the network: a single request to the npm registry for the latest version, made only when you (or an agent, through the `check_update` tool) ask for it.
|
|
47
48
|
|
|
48
49
|
For agents, `skills/decoy-redact/SKILL.md` is a ready-made skill (copy it into `.claude/skills/` or `~/.claude/skills/`), and the MCP server plugs into any client:
|
|
49
50
|
|
package/dist-cli/decoy.mjs
CHANGED
|
@@ -1460,6 +1460,72 @@ function round(rect) {
|
|
|
1460
1460
|
};
|
|
1461
1461
|
}
|
|
1462
1462
|
//#endregion
|
|
1463
|
+
//#region src/cli/update.ts
|
|
1464
|
+
var PACKAGE_NAME = "@ggalmazor/decoy";
|
|
1465
|
+
var REGISTRY = "https://registry.npmjs.org";
|
|
1466
|
+
function parts(version) {
|
|
1467
|
+
const [core, prerelease = null] = version.replace(/^v/, "").split("-", 2);
|
|
1468
|
+
return {
|
|
1469
|
+
numbers: core.split(".").map((n) => Number(n) || 0),
|
|
1470
|
+
prerelease
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
function compareVersions(a, b) {
|
|
1474
|
+
const pa = parts(a);
|
|
1475
|
+
const pb = parts(b);
|
|
1476
|
+
for (let i = 0; i < 3; i++) {
|
|
1477
|
+
const d = (pa.numbers[i] ?? 0) - (pb.numbers[i] ?? 0);
|
|
1478
|
+
if (d !== 0) return Math.sign(d);
|
|
1479
|
+
}
|
|
1480
|
+
if (pa.prerelease && !pb.prerelease) return -1;
|
|
1481
|
+
if (!pa.prerelease && pb.prerelease) return 1;
|
|
1482
|
+
return 0;
|
|
1483
|
+
}
|
|
1484
|
+
async function checkForUpdate(installed, fetchImpl = fetch, timeoutMs = 8e3) {
|
|
1485
|
+
const base = {
|
|
1486
|
+
package: PACKAGE_NAME,
|
|
1487
|
+
installed,
|
|
1488
|
+
command: `npm install -g ${PACKAGE_NAME}@latest`,
|
|
1489
|
+
registry: REGISTRY
|
|
1490
|
+
};
|
|
1491
|
+
try {
|
|
1492
|
+
const res = await fetchImpl(`${REGISTRY}/${encodeURIComponent(PACKAGE_NAME)}/latest`, {
|
|
1493
|
+
headers: { accept: "application/json" },
|
|
1494
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
1495
|
+
});
|
|
1496
|
+
if (!res.ok) return {
|
|
1497
|
+
...base,
|
|
1498
|
+
latest: null,
|
|
1499
|
+
updateAvailable: false,
|
|
1500
|
+
error: `registry answered ${res.status}`
|
|
1501
|
+
};
|
|
1502
|
+
const body = await res.json();
|
|
1503
|
+
if (typeof body.version !== "string") return {
|
|
1504
|
+
...base,
|
|
1505
|
+
latest: null,
|
|
1506
|
+
updateAvailable: false,
|
|
1507
|
+
error: "registry answer had no version"
|
|
1508
|
+
};
|
|
1509
|
+
return {
|
|
1510
|
+
...base,
|
|
1511
|
+
latest: body.version,
|
|
1512
|
+
updateAvailable: compareVersions(body.version, installed) > 0
|
|
1513
|
+
};
|
|
1514
|
+
} catch (error) {
|
|
1515
|
+
return {
|
|
1516
|
+
...base,
|
|
1517
|
+
latest: null,
|
|
1518
|
+
updateAvailable: false,
|
|
1519
|
+
error: error.message
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
function describeUpdate(report) {
|
|
1524
|
+
if (report.error) return `${report.package} ${report.installed} installed; could not reach ${report.registry}: ${report.error}`;
|
|
1525
|
+
if (report.updateAvailable) return `${report.package} ${report.installed} installed, ${report.latest} available. Update with: ${report.command}`;
|
|
1526
|
+
return `${report.package} ${report.installed} is the latest version.`;
|
|
1527
|
+
}
|
|
1528
|
+
//#endregion
|
|
1463
1529
|
//#region src/cli/verify.ts
|
|
1464
1530
|
function inside(rects, x, y) {
|
|
1465
1531
|
return rects.some((r) => x >= r.x && x < r.x + r.w && y >= r.y && y < r.y + r.h);
|
|
@@ -1692,11 +1758,22 @@ async function serveMcp(version) {
|
|
|
1692
1758
|
return failure(error);
|
|
1693
1759
|
}
|
|
1694
1760
|
});
|
|
1761
|
+
server.registerTool("check_update", {
|
|
1762
|
+
title: "Check for a newer decoy",
|
|
1763
|
+
description: "Asks registry.npmjs.org for the latest published version of @ggalmazor/decoy and compares it with the running one. This is the only network request decoy ever makes, and it happens only when this tool is called; nothing about the user or their files is sent. Returns installed, latest, updateAvailable, and the install command.",
|
|
1764
|
+
inputSchema: {}
|
|
1765
|
+
}, async () => {
|
|
1766
|
+
try {
|
|
1767
|
+
return json(await checkForUpdate(version));
|
|
1768
|
+
} catch (error) {
|
|
1769
|
+
return failure(error);
|
|
1770
|
+
}
|
|
1771
|
+
});
|
|
1695
1772
|
await server.connect(new StdioServerTransport());
|
|
1696
1773
|
}
|
|
1697
1774
|
//#endregion
|
|
1698
1775
|
//#region src/cli/main.ts
|
|
1699
|
-
var VERSION = "1.
|
|
1776
|
+
var VERSION = "1.3.0";
|
|
1700
1777
|
function collect(value, previous) {
|
|
1701
1778
|
return [...previous, value];
|
|
1702
1779
|
}
|
|
@@ -1778,6 +1855,12 @@ program.command("verify").description("check a redacted file: no text layer, no
|
|
|
1778
1855
|
fail(error);
|
|
1779
1856
|
}
|
|
1780
1857
|
});
|
|
1858
|
+
program.command("update").description("ask the npm registry whether a newer version exists (the only network request decoy ever makes)").action(async () => {
|
|
1859
|
+
const report = await checkForUpdate(VERSION);
|
|
1860
|
+
if (program.opts().json) console.log(JSON.stringify(report, null, 2));
|
|
1861
|
+
else console.log(describeUpdate(report));
|
|
1862
|
+
process.exit(report.error ? 1 : 0);
|
|
1863
|
+
});
|
|
1781
1864
|
program.command("mcp").description("serve find_text, redact, and verify as MCP tools over stdio").action(async () => {
|
|
1782
1865
|
try {
|
|
1783
1866
|
await serveMcp(VERSION);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggalmazor/decoy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Redaction that paints a pixelated decoy phrase over the secret instead of pixelating the secret. Web app, CLI, and MCP server; everything runs on your machine.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"redaction",
|