@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 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 mcp # find_text, redact, and verify as MCP tools over stdio
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
 
@@ -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.1.0";
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.1.0",
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",