pqcheck 0.16.12 → 0.16.13

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.
Files changed (3) hide show
  1. package/README.md +45 -11
  2. package/bin/pqcheck.js +202 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,12 +1,35 @@
1
1
  # pqcheck
2
2
 
3
- > **A deploy gate for AI coding agents.** Tell Claude Code / Cursor / Copilot whether the site you just deployed is safe to announce or whether your AI coworker should pause and ask you first.
3
+ > **Type `npx pqcheck stripe.com`** (or any HTTPS domain) to scan its public posture in seconds Decryption Blast Radius grade (0–10), letter (A–F), and findings ranked by severity. No signup, no API key, per-IP rate limited.
4
+ >
5
+ > **Or, for your own deploys:** wire `npx pqcheck deploy-check yourdomain.com --ai` into Claude Code / Cursor / Copilot. Your AI coder parses `ship_decision=pass|review|block` and decides whether to announce the deploy, ask you, or stop.
4
6
 
5
7
  [![npm version](https://img.shields.io/npm/v/pqcheck.svg?style=flat-square&color=06b6d4)](https://www.npmjs.com/package/pqcheck)
6
8
  [![npm downloads](https://img.shields.io/npm/dm/pqcheck.svg?style=flat-square&color=06b6d4)](https://www.npmjs.com/package/pqcheck)
7
9
  [![license](https://img.shields.io/npm/l/pqcheck.svg?style=flat-square&color=06b6d4)](./LICENSE)
8
10
 
9
- > **Latest: v0.16.12** — README rewritten to lead with the AI deploy gate (drift between deploys) as the product; DBR repositioned as the severity model the gate uses. No CLI behavior changes. [Full changelog →](./CHANGELOG.md)
11
+ > **Latest: v0.16.12** — ⚠️ **Existing GitHub Action users:** one-line fix required in `.github/workflows/cipherwake.yml` (`uses: cipherwakelabs/pqcheck@v3` → `cipherwakelabs/pqcheck/action@v3`). The old ref was broken since v0.15 and silently failed every CI run; today's end-to-end test caught it. Re-running `pqcheck onboard` also regenerates the workflow correctly. Plus: README rewritten to advertise both `pqcheck <domain>` and `pqcheck deploy-check --ai` equally, rate limits corrected. [Full changelog →](./CHANGELOG.md)
12
+
13
+ ## Two ways to use it
14
+
15
+ ### 1. Scan any domain — no signup, no API key, no per-account quota
16
+
17
+ ```bash
18
+ npx pqcheck stripe.com
19
+ ```
20
+
21
+ ```
22
+ ◆ Cipherwake · stripe.com DBR 2.3 B · 1 finding
23
+
24
+ Top finding:
25
+ [MEDIUM] Intermediate cert uses RSA — quantum-vulnerable chain link
26
+
27
+ Full report: https://cipherwake.io/r/stripe.com
28
+ ```
29
+
30
+ Anonymous. Per-IP rate limited (120 scans/hour) for cost protection — that's it. Use it to spot-check a vendor before signing, audit a competitor's HTTPS posture, or just satisfy "I wonder how `<domain>` grades."
31
+
32
+ ### 2. Gate your own deploys with your AI coder
10
33
 
11
34
  ```bash
12
35
  npx pqcheck deploy-check yourdomain.com --ai
@@ -31,24 +54,35 @@ The last block — `CIPHERWAKE_AI_GUARD_RESULT` — is what your AI coding agent
31
54
  - **review** — stop and ask you what to do (your HTTPS posture drifted vs. last scan)
32
55
  - **block** — refuse to announce until you investigate (something critical changed)
33
56
 
34
- Zero install. Works in any terminal with Node 18+. Free, no signup, no API key required for first deploys.
57
+ Zero install. Works in any terminal with Node 18+. Both modes are free — no signup, no API key required for first use of either.
35
58
 
36
59
  ## What pqcheck actually checks
37
60
 
38
- Each `pqcheck deploy-check <domain>` compares your site's public HTTPS surface *now* against your last scan and surfaces what changed:
61
+ ### Bare scan (`pqcheck <domain>`) current-state posture grade
62
+
63
+ Scans any public HTTPS surface and produces a **DBR score** (0–10), a **letter grade** (A–F), and a **findings list** ranked by severity. Same scanner that powers [cipherwake.io](https://cipherwake.io), the browser extension, and the GitHub Action — what it checks:
64
+
65
+ - **TLS posture** — ciphersuite class, hybrid PQC key agreement (`X25519MLKEM768`), forward secrecy
66
+ - **Certificate chain** — issuer, intermediate quality, key reuse across rotations (★ unique to pqcheck), wildcard / subdomain blast radius
67
+ - **Security headers** — HSTS (preload + max-age), CSP, X-Frame-Options, Referrer-Policy, Permissions-Policy, COOP, CORP
68
+ - **Email security** — SPF, DMARC, DKIM (~30 selectors probed including Resend/Mailgun/SES), BIMI
69
+ - **Supply chain** — every third-party script loaded by the page, graded for quantum risk
70
+ - **Subdomain takeover** — fingerprint scan against AWS S3, GitHub Pages, Heroku, Shopify, Fastly, etc.
71
+
72
+ ### Deploy gate (`pqcheck deploy-check --ai`) — drift since your last scan
73
+
74
+ Compares your site's public HTTPS surface *now* against your last scan and surfaces what changed:
39
75
 
40
76
  - **New third-party scripts** loading on the page that weren't there before (the Polyfill.io-class supply-chain risk)
41
77
  - **Header regressions** — CSP weakened, HSTS shortened or removed, X-Frame-Options dropped, permissive tokens (`'unsafe-inline'`, `*`) introduced
42
78
  - **Certificate / SPKI changes** — unexpected rotations, key reuse across renewals, intermediate-chain changes
43
- - **TLS posture changes** — ciphersuite shifts, hybrid PQC key-agreement (`X25519MLKEM768`) added or removed
79
+ - **TLS posture changes** — ciphersuite shifts, hybrid PQC key-agreement added or removed
44
80
  - **Vendor surface changes** — new email senders (SPF/DMARC), new CDN origins, new analytics endpoints
45
- - **Subdomain takeover exposure** — new dangling CNAMEs pointing at AWS S3 / GitHub Pages / Heroku / Fastly / etc.
46
-
47
- The gate routes each change through a severity model — the **Decryption Blast Radius** score (DBR, 0–10) — to decide `pass` / `review` / `block`. Cosmetic drift passes; high-severity drift (new script from an unknown origin, header regression that breaks defense-in-depth, cert SPKI change without a rotation event) triggers `review`; critical drift (expired cert served, takeover-vulnerable subdomain, malicious vendor injected) triggers `block`. DBR's full severity rubric is documented at [/methodology/decryption-blast-radius](https://cipherwake.io/methodology/decryption-blast-radius).
81
+ - **Subdomain takeover exposure** — new dangling CNAMEs
48
82
 
49
- On **first use** (no prior scan to diff against), `deploy-check` falls back to absolute-posture grading: it scores the current state and surfaces the highest-severity findings so you start with a baseline. Every subsequent run is drift-relative to the previous scan.
83
+ The gate routes each change through the same DBR severity model above to decide `pass` / `review` / `block`. Cosmetic drift passes; high-severity drift (new script from an unknown origin, header regression that breaks defense-in-depth, cert SPKI change without a rotation event) triggers `review`; critical drift (expired cert served, takeover-vulnerable subdomain, malicious vendor injected) triggers `block`. DBR's full severity rubric: [/methodology/decryption-blast-radius](https://cipherwake.io/methodology/decryption-blast-radius).
50
84
 
51
- You get the same scanner that powers [cipherwake.io](https://cipherwake.io), the browser extension, and the GitHub Action.
85
+ On **first use** with no prior scan to diff against, `deploy-check` falls back to the bare scan's absolute-posture grading to give you a baseline. Every subsequent run is drift-relative to the previous scan.
52
86
 
53
87
  ---
54
88
 
@@ -420,7 +454,7 @@ curl -s "https://www.cipherwake.io/api/scan?domain=stripe.com" | jq '.grade, .sc
420
454
 
421
455
  Full API reference at [cipherwake.io/api](https://cipherwake.io/api).
422
456
 
423
- **Rate limits:** 300 scans per hour per IP, 20 `--fresh` (force-refresh) scans per hour per IP. No API key required. Returns HTTP 429 if exceeded — back off and retry, or [let us know via the feedback form](https://cipherwake.io/feedback) if you need higher limits (we're prioritizing the API tier based on real demand).
457
+ **Rate limits:** 120 scans per hour per IP for anonymous CLI use, 20 `--fresh` (force-refresh) scans per hour per IP. **Authenticated paths bypass this:** GitHub Actions OIDC (Free = 100 calls/month per repo) and API key (Founder Pro = 5,000/month per account) each have their own per-account / per-repo quota with no per-IP cap. No API key required for the anonymous path. Returns HTTP 429 if exceeded — back off and retry, or [let us know via the feedback form](https://cipherwake.io/feedback) if you need higher limits.
424
458
 
425
459
  ## Methodology
426
460
 
package/bin/pqcheck.js CHANGED
@@ -24,9 +24,9 @@
24
24
  })();
25
25
 
26
26
  const API_BASE = process.env.PQCHECK_API_BASE || "https://cipherwake.io";
27
- const VERSION = "0.16.11";
27
+ const VERSION = "0.16.13";
28
28
 
29
- // API-key support — paid tiers (Starter $29 / Growth $79 / Scale $199) get
29
+ // API-key support — paid tier (Founder Pro $19.99/mo launch pricing, locked while sub active) gets
30
30
  // per-account monthly quotas instead of the per-IP rate limit. Set via:
31
31
  // export CIPHERWAKE_API_KEY=qpk_<32-hex>
32
32
  // Anonymous CLI use still works (no env var → falls back to IP rate limit).
@@ -95,6 +95,16 @@ async function main() {
95
95
  process.exit(0);
96
96
  }
97
97
 
98
+ // v0.16.13: opportunistic update-check banner. Reads cached registry
99
+ // result from disk and prints a one-line stderr banner if a newer
100
+ // version is published; refreshes the cache in the background for next
101
+ // time. Never blocks the command. See maybeShowVersionBanner() for the
102
+ // full rationale (TL;DR: prevents the npx-cached-stale-version trap
103
+ // that caused 0.7.0 to silently run for months on existing users).
104
+ // Awaited so the banner appears BEFORE the command output for visibility,
105
+ // but it only reads a tiny file (no network on hot path).
106
+ await maybeShowVersionBanner(args);
107
+
98
108
  // Subcommand dispatch.
99
109
  if (args[0] === "lock") {
100
110
  return runLockCommand(args.slice(1));
@@ -759,6 +769,169 @@ function formatAiFooterBlock(fields) {
759
769
  return color("dim", lines.join("\n"));
760
770
  }
761
771
 
772
+ // v0.16.13 — fail-loud AI guard for fetch/quota/server errors during
773
+ // deploy-check. Without this, a network blip during `pqcheck deploy-check
774
+ // --ai` would print a red error to stderr and exit 3 — but the AI Coder
775
+ // Protocol relies on the CIPHERWAKE_AI_GUARD_RESULT block. Missing block =
776
+ // AI agent assumes "no signal" and may continue shipping. The fix: in AI
777
+ // mode, always emit a block with ship_decision=review and a top_issue code
778
+ // the agent can route on. Behaviour in non-AI text mode is unchanged.
779
+ function emitAiGuardReviewAndExit(args, errorDetail) {
780
+ if (parseAiMode(args)) {
781
+ const positional = args.filter((a) => !a.startsWith("-"));
782
+ const domain = positional[0] || "";
783
+ const baseline = parseFlag(args, "--baseline") || "last-scan";
784
+ try {
785
+ console.log("");
786
+ console.log(formatBrandHeader(domain));
787
+ console.log("");
788
+ console.log(color("yellow", " ⚠ REVIEW — Cipherwake deploy-check could not complete"));
789
+ console.log(color("dim", ` ${errorDetail.message}`));
790
+ console.log(color("dim", " Treating as REVIEW per fail-safe policy. Do NOT announce the deploy until you manually verify or rerun the check successfully."));
791
+ console.log(formatAiFooterBlock({
792
+ status: "review",
793
+ domain,
794
+ kind: "trust-diff",
795
+ baseline,
796
+ verdict: "review",
797
+ delta_count: 0,
798
+ max_severity: "unknown",
799
+ ship_decision: "review",
800
+ top_issue: errorDetail.code,
801
+ top_issue_title: errorDetail.message,
802
+ dbr: "",
803
+ grade: "",
804
+ quota_used: "",
805
+ quota_limit: "",
806
+ scanned_at: new Date().toISOString(),
807
+ advisory_only: "true",
808
+ error: errorDetail.code,
809
+ }));
810
+ console.log("");
811
+ // Persist the review state so the IDE statusbar reflects the failure
812
+ // (otherwise the bar stays on the previous successful scan).
813
+ writeLastScanFile({
814
+ domain,
815
+ kind: "trust-diff",
816
+ score: null,
817
+ grade: null,
818
+ max_severity: "unknown",
819
+ ship_decision: "review",
820
+ baseline,
821
+ delta_count: 0,
822
+ top_issue: errorDetail.code,
823
+ error: errorDetail.code,
824
+ }).catch(() => { /* best-effort */ });
825
+ } catch {
826
+ // even error path must not throw — fall through to exit
827
+ }
828
+ }
829
+ process.exit(errorDetail.exitCode ?? 3);
830
+ }
831
+
832
+ // v0.16.13 — opportunistic version check. Reads a small cache file on cold
833
+ // start; if a newer version is in cache AND we haven't already banner'd
834
+ // today, prints a banner to stderr. Separately, if cache is >24h old, fires
835
+ // off a background fetch (non-blocking) whose result lands for the NEXT
836
+ // invocation. Zero network on hot path. Skipped in machine-readable formats
837
+ // (JSON/SARIF/github) so it never pollutes scripted output. The banner
838
+ // helps users who installed via `npx pqcheck` months ago and have a stale
839
+ // version cached in ~/.npm/_npx/ that they don't know is stale.
840
+ const VERSION_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
841
+ const VERSION_REGISTRY_URL = "https://registry.npmjs.org/pqcheck";
842
+
843
+ async function maybeShowVersionBanner(args) {
844
+ // Skip in machine-parsed output formats.
845
+ const format = parseFlag(args, "--format");
846
+ if (format === "json" || format === "sarif" || format === "github") return;
847
+ // Skip if explicitly disabled (env opt-out).
848
+ if (process.env.PQCHECK_NO_UPDATE_CHECK === "1") return;
849
+
850
+ try {
851
+ const os = await import("node:os");
852
+ const path = await import("node:path");
853
+ const fs = await import("node:fs/promises");
854
+
855
+ const cacheDir = path.join(os.homedir(), ".config", "cipherwake");
856
+ const cachePath = path.join(cacheDir, "version-check.json");
857
+
858
+ let cached = null;
859
+ try {
860
+ cached = JSON.parse(await fs.readFile(cachePath, "utf8"));
861
+ } catch {
862
+ // first run or unreadable — fall through
863
+ }
864
+
865
+ const now = Date.now();
866
+ const stale = !cached || !cached.checked_at || (now - cached.checked_at) > VERSION_CHECK_TTL_MS;
867
+
868
+ // 1. If cache has a known-newer version, print banner now (synchronous,
869
+ // cheap, no network).
870
+ if (cached && cached.latest && isNewerVersion(cached.latest, VERSION)) {
871
+ const lastShownAt = cached.last_shown_at || 0;
872
+ const showThrottleMs = 6 * 60 * 60 * 1000; // at most once per 6h
873
+ if (now - lastShownAt > showThrottleMs) {
874
+ process.stderr.write(
875
+ color("yellow", `\n ⬆ pqcheck ${cached.latest} is available `) +
876
+ color("dim", `(you have ${VERSION}) — run: `) +
877
+ color("bold", "npm i -g pqcheck@latest") +
878
+ color("dim", " (or: rm -rf ~/.npm/_npx && npx pqcheck@latest ...)\n\n")
879
+ );
880
+ // Persist that we just shown the banner so we don't spam.
881
+ await fs.mkdir(cacheDir, { recursive: true });
882
+ await fs.writeFile(cachePath, JSON.stringify({ ...cached, last_shown_at: now }, null, 2));
883
+ }
884
+ }
885
+
886
+ // 2. If cache is stale, fire-and-forget a registry fetch so the NEXT
887
+ // cold start has fresh data. Detached from the await chain so it
888
+ // never delays exit.
889
+ if (stale) {
890
+ void refreshVersionCacheInBackground(cachePath);
891
+ }
892
+ } catch {
893
+ // best-effort — never break the CLI for an update check
894
+ }
895
+ }
896
+
897
+ async function refreshVersionCacheInBackground(cachePath) {
898
+ try {
899
+ const controller = new AbortController();
900
+ const timeout = setTimeout(() => controller.abort(), 2500);
901
+ const resp = await fetch(VERSION_REGISTRY_URL, {
902
+ headers: { "Accept": "application/json", "User-Agent": `pqcheck-cli/${VERSION}` },
903
+ signal: controller.signal,
904
+ });
905
+ clearTimeout(timeout);
906
+ if (!resp.ok) return;
907
+ const body = await resp.json();
908
+ const latest = body?.["dist-tags"]?.latest;
909
+ if (!latest) return;
910
+ const fs = await import("node:fs/promises");
911
+ const path = await import("node:path");
912
+ await fs.mkdir(path.dirname(cachePath), { recursive: true });
913
+ // Preserve last_shown_at so we don't reset the throttle on every refresh.
914
+ let prior = {};
915
+ try { prior = JSON.parse(await fs.readFile(cachePath, "utf8")); } catch { /* none */ }
916
+ await fs.writeFile(cachePath, JSON.stringify({
917
+ ...prior,
918
+ latest,
919
+ checked_at: Date.now(),
920
+ }, null, 2));
921
+ } catch {
922
+ // best-effort
923
+ }
924
+ }
925
+
926
+ function isNewerVersion(remote, local) {
927
+ const parse = (v) => String(v).split(".").map((n) => parseInt(n, 10) || 0);
928
+ const [a, b, c] = parse(remote);
929
+ const [x, y, z] = parse(local);
930
+ if (a !== x) return a > x;
931
+ if (b !== y) return b > y;
932
+ return c > z;
933
+ }
934
+
762
935
  // v0.16.0 — high-yield output formatters.
763
936
  //
764
937
  // Design (per user direction 2026-05-22): every scan should deliver
@@ -1291,7 +1464,7 @@ function printMarkdown(r, multi) {
1291
1464
  // not the old "watch free / weekly digest"). PR-comment context plants
1292
1465
  // team-invitation idea for the eventual Growth tier upgrade.
1293
1466
  lines.push(`📌 **Monitor ${r.domain} continuously?** ${API_BASE}/watch/${encodeURIComponent(r.domain)}`);
1294
- lines.push(`Cipherwake Starter $29/mo · 5 domains · daily scans + email alerts on cert/script/posture changes. Invite your team on Growth ($79/mo) when ready.`);
1467
+ lines.push(`Cipherwake Founder Pro $19.99/mo (launch pricing, locked while subscription active) · 5 watched domains · daily scans · full CI Trust Gate · approved-vendor allowlist · webhook delivery (Slack incoming-webhook URLs work).`);
1295
1468
  if (multi) lines.push("\n---\n");
1296
1469
  console.log(lines.join("\n"));
1297
1470
  }
@@ -1421,7 +1594,7 @@ function printReport(r) {
1421
1594
  // 2026-05-14: copy must match current locked revenue plan ($29 Starter,
1422
1595
  // not the old "free 1-domain weekly digest").
1423
1596
  console.log(color("violet", ` 📌 Monitor ${r.domain} daily: ${API_BASE}/watch/${encodeURIComponent(r.domain)}`));
1424
- console.log(color("dim", ` Cipherwake Starter $29/mo · 5 watched domains · email alerts · cancel anytime`));
1597
+ console.log(color("dim", ` Cipherwake Founder Pro $19.99/mo · 5 watched domains · email alerts · launch pricing locked while subscription active · cancel anytime`));
1425
1598
  console.log("");
1426
1599
  console.log(color("dim", ` → Full report: ${API_BASE}/?check=${encodeURIComponent(r.domain)}`));
1427
1600
  console.log(color("dim", ` → Share this: ${API_BASE}/r/${encodeURIComponent(r.domain)}`));
@@ -3040,19 +3213,33 @@ async function runTrustDiffCommand(args) {
3040
3213
  });
3041
3214
  } catch (err) {
3042
3215
  console.error(color("red", `error: network failure calling /api/trust-diff: ${err.message}`));
3043
- process.exit(3);
3216
+ // v0.16.13: in AI mode, emit a ship_decision=review block so the
3217
+ // calling agent doesn't silently treat a fetch failure as "no signal".
3218
+ return emitAiGuardReviewAndExit(args, {
3219
+ code: "deploy_check_fetch_failed",
3220
+ message: `Network failure: ${err?.message || "fetch failed"}`,
3221
+ exitCode: 3,
3222
+ });
3044
3223
  }
3045
3224
 
3046
3225
  if (resp.status === 401 || resp.status === 403) {
3047
3226
  await handleAuthError(resp);
3048
- process.exit(3);
3227
+ return emitAiGuardReviewAndExit(args, {
3228
+ code: "deploy_check_auth_failed",
3229
+ message: `Authorization failed (${resp.status}). Check CIPHERWAKE_API_KEY or hit /account#api-keys.`,
3230
+ exitCode: 3,
3231
+ });
3049
3232
  }
3050
3233
  if (resp.status === 429) {
3051
3234
  const body = await safeJSON(resp);
3052
3235
  console.error(color("red", "error: Trust Diff API quota exceeded"));
3053
3236
  if (body?.message) console.error(color("dim", body.message));
3054
3237
  console.error(color("dim", "Higher quota via free API key (no card): https://cipherwake.io/account#api-keys"));
3055
- process.exit(3);
3238
+ return emitAiGuardReviewAndExit(args, {
3239
+ code: "deploy_check_quota_exceeded",
3240
+ message: body?.message || "Trust Diff monthly quota exceeded — upgrade or wait for monthly reset.",
3241
+ exitCode: 3,
3242
+ });
3056
3243
  }
3057
3244
  // R74-confirm friction fix #2 (GPT 2026-05-22): 404 on first-deploy is
3058
3245
  // expected — the domain has never been scanned, so there's no baseline to
@@ -3069,7 +3256,11 @@ async function runTrustDiffCommand(args) {
3069
3256
  console.error(color("red", `error: /api/trust-diff returned ${resp.status}`));
3070
3257
  if (body?.message) console.error(color("dim", body.message));
3071
3258
  if (body?.hint) console.error(color("dim", body.hint));
3072
- process.exit(3);
3259
+ return emitAiGuardReviewAndExit(args, {
3260
+ code: "deploy_check_server_error",
3261
+ message: body?.message || `Cipherwake server returned ${resp.status}.`,
3262
+ exitCode: 3,
3263
+ });
3073
3264
  }
3074
3265
 
3075
3266
  const result = await resp.json();
@@ -4232,7 +4423,7 @@ function renderReleaseChecklist(domain, opts = {}) {
4232
4423
  // `pqcheck init` — interactive workflow scaffold (habit-loop #4, locked 2026-05-16)
4233
4424
  // =============================================================================
4234
4425
  // Writes a ready-to-commit .github/workflows/cipherwake.yml that calls
4235
- // cipherwakelabs/pqcheck@v3 in trust-diff mode. Zero copy-paste docs friction.
4426
+ // cipherwakelabs/pqcheck/action@v3 in trust-diff mode. Zero copy-paste docs friction.
4236
4427
  //
4237
4428
  // Flags:
4238
4429
  // --domain <d> Skip the domain prompt
@@ -4409,7 +4600,7 @@ jobs:
4409
4600
  runs-on: ubuntu-latest
4410
4601
  steps:
4411
4602
  - name: Run Cipherwake Trust Diff
4412
- uses: cipherwakelabs/pqcheck@v3
4603
+ uses: cipherwakelabs/pqcheck/action@v3
4413
4604
  with:
4414
4605
  mode: trust-diff
4415
4606
  domain: ${domain}
@@ -4417,7 +4608,7 @@ jobs:
4417
4608
  fail-on: ${failOn}
4418
4609
  # No env/secrets needed for Free tier — the action uses the
4419
4610
  # workflow's id-token: write permission to fetch a GitHub-signed
4420
- # OIDC token and meters per repo (30 calls/mo, no setup).
4611
+ # OIDC token and meters per repo (100 calls/mo, no setup).
4421
4612
  # If you want higher limits, link this repo to a paid Cipherwake
4422
4613
  # account at https://cipherwake.io/account → Linked repos.
4423
4614
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pqcheck",
3
- "version": "0.16.12",
3
+ "version": "0.16.13",
4
4
  "description": "Deploy gate for AI-coded web apps. `pqcheck deploy-check --ai` returns ship_decision=pass|review|block for Claude Code / Cursor / Copilot / Aider to gate deploys before they ship. Anonymous, no signup, free for first use.",
5
5
  "keywords": [
6
6
  "ai-coder",