pqcheck 0.16.18 → 0.16.19

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
@@ -8,7 +8,7 @@
8
8
  [![npm downloads](https://img.shields.io/npm/dm/pqcheck.svg?style=flat-square&color=06b6d4)](https://www.npmjs.com/package/pqcheck)
9
9
  [![license](https://img.shields.io/npm/l/pqcheck.svg?style=flat-square&color=06b6d4)](./LICENSE)
10
10
 
11
- > **Latest: v0.16.18** — `--version` now reads dynamically from `package.json` instead of a hardcoded constant. Prior releases (0.16.16, 0.16.17) shipped with the constant left at `0.16.15`, so AI agents citing "I ran pqcheck X.Y.Z" were citing the wrong version. Single source of truth from here on; locked by a unit test against future drift. [Full changelog →](./CHANGELOG.md)
11
+ > **Latest: v0.16.19** — `pqcheck setup` now COMPOSES with an existing Claude Code `statusLine.command` (e.g., PinnedAI's) via a new `--prepend=<cmd>` flag on `cipherwake-statusline`. Both tools' badges render in the single statusLine slot. Previously, Cipherwake politely skipped if any prior statusLine existed which meant you'd only ever see one tool's badge. Now: composed wrap with 5s timeout + graceful degradation on prepend failure + idempotent re-runs. [Full changelog →](./CHANGELOG.md)
12
12
 
13
13
  ## Two ways to use it
14
14
 
@@ -21,10 +21,56 @@
21
21
  import { readFileSync, existsSync } from "node:fs";
22
22
  import { join, dirname } from "node:path";
23
23
  import { homedir } from "node:os";
24
+ import { execSync } from "node:child_process";
24
25
 
25
26
  const GLOBAL_STATE_FILE = join(homedir(), ".config", "cipherwake", "last-scan.json");
26
27
  const STALE_THRESHOLD_HOURS = 24;
27
28
 
29
+ // v0.16.19 — `--prepend=<command>` flag for statusLine composition. Claude
30
+ // Code's `statusLine.command` only supports ONE command, which means two
31
+ // tools (e.g. Cipherwake + PinnedAI) both writing their own statusLine
32
+ // silently clobber each other depending on install order. When a customer
33
+ // runs both, they only see whichever installed last. With --prepend, the
34
+ // Cipherwake binary runs the other tool's command first, joins its output
35
+ // with a separator, and renders Cipherwake's own line after. Result:
36
+ // `[pinned output] · ◆ Cipherwake · domain ✓ PASS · 5m ago` — both
37
+ // surfaces visible in the single statusLine slot.
38
+ //
39
+ // pqcheck setup detects an existing statusLine.command at install time
40
+ // and writes the wrapper form automatically:
41
+ // "command": "npx --package=pqcheck@latest cipherwake-statusline --prepend='<prior-command>'"
42
+ //
43
+ // If the prior command errors (or the tool was uninstalled), this binary
44
+ // swallows the failure and just renders Cipherwake's part — never causes
45
+ // the whole statusLine to break.
46
+ const PREPEND_SEPARATOR = " · ";
47
+ const prependArg = process.argv.find((a) => a.startsWith("--prepend="));
48
+ if (prependArg) {
49
+ const prependCmd = prependArg.slice("--prepend=".length);
50
+ if (prependCmd) {
51
+ try {
52
+ // 5s soft timeout — statusLine rendering must stay snappy. If the
53
+ // other tool hangs, we cut it off and continue with Cipherwake's part.
54
+ // stdio: ignore on stderr so a noisy prior tool doesn't pollute the
55
+ // bar; we want only its rendered stdout output.
56
+ const out = execSync(prependCmd, {
57
+ encoding: "utf8",
58
+ timeout: 5_000,
59
+ stdio: ["ignore", "pipe", "ignore"],
60
+ env: process.env,
61
+ }).trim();
62
+ if (out) {
63
+ process.stdout.write(out);
64
+ process.stdout.write(PREPEND_SEPARATOR);
65
+ }
66
+ } catch {
67
+ // Prepend command failed (uninstalled, timeout, errored). Skip
68
+ // silently — never break the whole statusLine because of a
69
+ // composition partner.
70
+ }
71
+ }
72
+ }
73
+
28
74
  // v0.16.6 — project-aware state lookup. Walk up from CWD looking for a
29
75
  // repo-local `.cipherwake/last-scan.json`. This way each project shows
30
76
  // its own last scan, and switching projects doesn't bleed the previous
package/bin/pqcheck.js CHANGED
@@ -6317,19 +6317,54 @@ async function runSetupCommand(args) {
6317
6317
  settings = JSON.parse(raw);
6318
6318
  existed = true;
6319
6319
  } catch { /* will create */ }
6320
- if (existed && settings.statusLine && typeof settings.statusLine === "object") {
6321
- // Already has a statusLine config don't overwrite.
6322
- console.log(color("dim", ` ⊝ ${displayPath} already has a statusLine entry leaving alone`));
6323
- console.log(color("dim", ` To use the Cipherwake statusline instead, set: "command": "npx --package=pqcheck@latest cipherwake-statusline"`));
6324
- installSummary.push({ component: "Claude Code statusLine", path: settingsPath, status: "skipped-existing-config" });
6320
+ // v0.16.19 statusLine composition. Previous behavior was: if any
6321
+ // statusLine.command already existed, SKIP. That was safe (never
6322
+ // clobbered other tools) but invisible a user with PinnedAI's
6323
+ // statusline installed would never see Cipherwake's after running
6324
+ // `pqcheck setup`. Now: detect the prior command and wrap it via
6325
+ // `cipherwake-statusline --prepend=<prior>` so both render in the
6326
+ // single statusLine slot. The prepend logic in cipherwake-statusline.js
6327
+ // swallows prepend-command failures silently, so this composition
6328
+ // survives the user later uninstalling the partner tool.
6329
+ //
6330
+ // Skip the wrap when the existing command IS already our wrapper
6331
+ // (idempotent re-install — don't recursively nest).
6332
+ const CIPHERWAKE_CMD_PREFIX = "npx --package=pqcheck@latest cipherwake-statusline";
6333
+ const priorCmd = (existed && settings.statusLine && typeof settings.statusLine === "object")
6334
+ ? String(settings.statusLine.command || "").trim()
6335
+ : "";
6336
+ const priorIsOurs = priorCmd.startsWith("npx --package=pqcheck") && priorCmd.includes("cipherwake-statusline");
6337
+
6338
+ if (priorIsOurs) {
6339
+ console.log(color("dim", ` ⊝ ${displayPath} statusLine already points at cipherwake-statusline — leaving alone`));
6340
+ installSummary.push({ component: "Claude Code statusLine", path: settingsPath, status: "skipped-already-ours" });
6325
6341
  } else {
6326
6342
  const backupPath = existed ? await backupSettingsJson(settingsPath) : null;
6327
6343
  if (backupPath) console.log(color("dim", ` backup: ${backupPath}`));
6328
- settings.statusLine = { type: "command", command: "npx --package=pqcheck@latest cipherwake-statusline" };
6344
+ let command;
6345
+ if (priorCmd) {
6346
+ // Compose: wrap the existing command via --prepend.
6347
+ // Single-quote the prior command + escape any single quotes
6348
+ // (rare in practice but defensive).
6349
+ const safe = priorCmd.replace(/'/g, `'\\''`);
6350
+ command = `${CIPHERWAKE_CMD_PREFIX} --prepend='${safe}'`;
6351
+ console.log(color("green", ` ✓ composed statusLine: existing command wrapped via --prepend → ${displayPath}`));
6352
+ console.log(color("dim", ` prior command: ${priorCmd.slice(0, 80)}${priorCmd.length > 80 ? "..." : ""}`));
6353
+ console.log(color("dim", ` both surfaces now render in the single statusLine slot`));
6354
+ } else {
6355
+ // Fresh install — no prior command to compose with.
6356
+ command = CIPHERWAKE_CMD_PREFIX;
6357
+ console.log(color("green", ` ✓ added statusLine config → ${displayPath}`));
6358
+ }
6359
+ settings.statusLine = { type: "command", command };
6329
6360
  await fs.mkdir(path.dirname(settingsPath), { recursive: true });
6330
6361
  await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
6331
- console.log(color("green", ` ✓ added statusLine config → ${displayPath}`));
6332
- installSummary.push({ component: "Claude Code statusLine", path: settingsPath, status: existed ? "installed-updated" : "installed-created", backup: backupPath });
6362
+ installSummary.push({
6363
+ component: "Claude Code statusLine",
6364
+ path: settingsPath,
6365
+ status: existed && priorCmd ? "installed-composed" : (existed ? "installed-updated" : "installed-created"),
6366
+ backup: backupPath,
6367
+ });
6333
6368
  }
6334
6369
  } catch (err) {
6335
6370
  console.log(color("red", ` ✗ statusLine config install failed: ${err.message}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pqcheck",
3
- "version": "0.16.18",
3
+ "version": "0.16.19",
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",