residoo 0.3.8 → 0.3.9

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
@@ -121,13 +121,18 @@ won't be built into the tool that writes it.
121
121
  - Redacts everything in its own output. You get a shape and a first/last-4
122
122
  preview, never the real value, including in `--json` mode. A decoded or
123
123
  rejoined secret is redacted exactly like a plain one.
124
- - Every report opens with the exact version and timestamp it was run with
125
- (`residoo v0.3.8 · scanned 2026-01-01 12:00`; `--json` carries the same
126
- as `residooVersion`/`scannedAt`), so a report pasted or screenshotted
127
- later never leaves you guessing which build produced it. On an
128
- interactive terminal, a lightweight spinner shows scan progress on
129
- stderr; it is a complete no-op when stdout/stderr are piped, redirected,
130
- or run in CI, so it can never interleave with `--json`/`--sarif` output.
124
+ - On an interactive terminal, prints who it is and where it lives before
125
+ scanning starts (`residoo v0.3.9 · find secrets your AI coding agent left
126
+ on disk` plus the repo URL), then a live spinner naming the current file
127
+ as it scans. Every report also opens with the exact version and timestamp
128
+ it was run with (`residoo v0.3.9 · scanned 2026-01-01 12:00`; `--json`
129
+ carries the same as `residooVersion`/`scannedAt`), so a report pasted or
130
+ screenshotted later never leaves you guessing which build produced it.
131
+ When there are findings, the report closes with a "Next steps" pointer to
132
+ `--json` and `--seal`. All of the interactive chatter (the intro, the
133
+ spinner) goes to stderr only and is a complete no-op when stdout/stderr
134
+ are piped, redirected, or run in CI, so none of it can ever interleave
135
+ with `--json`/`--sarif` output.
131
136
  - `--sarif` emits SARIF 2.1.0 for GitHub code scanning's Security tab and
132
137
  inline pull-request annotations, the same format gitleaks/trufflehog/
133
138
  agentsweep already speak, so residoo's own Action and pre-commit hook plug
@@ -324,7 +329,7 @@ As a GitHub Action (this repository doubles as a composite action):
324
329
  ```yaml
325
330
  steps:
326
331
  - uses: actions/checkout@v4
327
- - uses: dandovdub/residoo@v0.3.8
332
+ - uses: dandovdub/residoo@v0.3.9
328
333
  ```
329
334
 
330
335
  As a pre-commit hook:
@@ -332,7 +337,7 @@ As a pre-commit hook:
332
337
  ```yaml
333
338
  repos:
334
339
  - repo: https://github.com/dandovdub/residoo
335
- rev: v0.3.8
340
+ rev: v0.3.9
336
341
  hooks:
337
342
  - id: residoo
338
343
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "residoo",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "Find secrets leaking through your AI coding agent's session history. Zero network calls in the scan path, zero dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "CloudRoam (https://cloudroam.io)",
package/src/cli.js CHANGED
@@ -5,7 +5,7 @@ const fs = require("fs");
5
5
  const crypto = require("crypto");
6
6
  const { availableSources, ALL_SOURCES } = require("./sources");
7
7
  const { scan, emptyResult } = require("./scan");
8
- const { render, renderIntegrity, renderJson, renderSarif, makeProgressReporter } = require("./report");
8
+ const { render, renderIntegrity, renderJson, renderSarif, makeProgressReporter, printIntro } = require("./report");
9
9
  const { checkIntegrity } = require("./integrity");
10
10
  const {
11
11
  ROTATION_GUIDANCE, guidanceFor, loadAcks, ackFinding, renderRotation,
@@ -404,6 +404,7 @@ async function main(argv) {
404
404
  // mutated env var would leak past this one invocation and silently kill
405
405
  // color for a later call that never asked for that.
406
406
  const noColor = args.includes("--no-color");
407
+ printIntro(noColor);
407
408
 
408
409
  // Integrity runs by default: a scan that reports "no secrets leaked" while
409
410
  // a planted SessionStart hook sits ready to re-leak them next session is
@@ -494,7 +495,7 @@ async function main(argv) {
494
495
  return failOnFind && integrityWarnCount(integrity) > 0 ? 1 : 0;
495
496
  }
496
497
 
497
- const progress = makeProgressReporter();
498
+ const progress = makeProgressReporter(noColor);
498
499
  const result = await scan({ sources, includeNoisy, includeSuppressed, onProgress: progress.onProgress });
499
500
  progress.stop();
500
501
  const integrity = wantsIntegrity ? runIntegrity() : null;
package/src/report.js CHANGED
@@ -32,35 +32,58 @@ function ageDays(mtimeMs) {
32
32
  const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
33
33
 
34
34
  /**
35
- * A minimal progress indicator for the scan phase, wired to scan()'s own
35
+ * Printed once, before scanning starts: what this is, what version, where
36
+ * it lives. Answers exactly the question a first-time (or every-time)
37
+ * reader has before results ever appear, without waiting for the report.
38
+ * Same TTY gate and same stream as the progress spinner below and for the
39
+ * same reason: stderr only, so a --json/--sarif consumer's stdout is
40
+ * untouched, and a complete no-op under redirection, piping, or CI.
41
+ */
42
+ function printIntro(noColor) {
43
+ if (!process.stderr.isTTY) return;
44
+ const paint = makePaint(noColor);
45
+ const { version } = require("../package.json");
46
+ process.stderr.write(
47
+ paint(c.bold + c.cyan, `residoo v${version}`) +
48
+ paint(c.dim, " · find secrets your AI coding agent left on disk\n") +
49
+ paint(c.dim, "https://github.com/dandovdub/residoo\n\n")
50
+ );
51
+ }
52
+
53
+ /**
54
+ * A progress indicator for the scan phase, wired to scan()'s own
36
55
  * onProgress callback. Writes to STDERR only, never stdout: --json/--sarif
37
56
  * consumers pipe stdout into a parser, and a spinner corrupting that would
38
57
  * be a much worse bug than not having one. Gated on stderr actually being a
39
58
  * TTY, so it is a complete no-op under redirection, piping, or CI, exactly
40
59
  * the contexts where carriage-return spam in a captured log would be
41
- * useless or actively annoying, not merely invisible. `stop()` clears the
42
- * line so whatever prints next (the report, on stdout, is unaffected
43
- * either way since this never touched stdout, but a plain-text stderr
44
- * reader watching live should not see a stale line lingering) starts
60
+ * useless or actively annoying, not merely invisible. Shows the actual
61
+ * current file (basename only, through the same safeBasename() every other
62
+ * displayed path in this report goes through control bytes stripped,
63
+ * invisible code points made visible), not just a running count: real
64
+ * signal, not just motion, and genuinely useful if a scan stalls on one
65
+ * huge file. `stop()` clears the line so whatever prints next starts
45
66
  * clean.
46
67
  */
47
- function makeProgressReporter() {
68
+ function makeProgressReporter(noColor) {
48
69
  if (!process.stderr.isTTY) return { onProgress: null, stop() {} };
70
+ const paint = makePaint(noColor);
49
71
  let count = 0;
50
72
  let lastWriteMs = 0;
51
73
  let lastLineLen = 0;
52
74
  let frame = 0;
53
- const write = (s) => {
75
+ const write = (s, visibleLen) => {
54
76
  process.stderr.write("\r" + " ".repeat(lastLineLen) + "\r" + s);
55
- lastLineLen = s.length;
77
+ lastLineLen = visibleLen;
56
78
  };
57
- const onProgress = ({ source }) => {
79
+ const onProgress = ({ source, file }) => {
58
80
  count++;
59
81
  const now = Date.now();
60
82
  if (now - lastWriteMs < 80) return; // throttled: avoid flicker on a fast scan
61
83
  lastWriteMs = now;
62
84
  frame = (frame + 1) % SPINNER_FRAMES.length;
63
- write(`${SPINNER_FRAMES[frame]} scanning… ${count} file${count === 1 ? "" : "s"} checked (${source})`);
85
+ const label = `scanning ${source}… ${count} file${count === 1 ? "" : "s"} ${safeBasename(file)}`;
86
+ write(paint(c.bold + c.cyan, SPINNER_FRAMES[frame]) + " " + paint(c.dim, label), 2 + label.length);
64
87
  };
65
88
  const stop = () => { if (lastLineLen > 0) process.stderr.write("\r" + " ".repeat(lastLineLen) + "\r"); };
66
89
  return { onProgress, stop };
@@ -304,9 +327,13 @@ function render({ findings, filesScanned, sourcesScanned, bytesScanned, suppress
304
327
  push(renderIntegrity(integrity, { noColor }));
305
328
  }
306
329
 
330
+ push();
331
+ push(paint(c.bold, "Next steps:"));
332
+ push(` residoo scan --json ${paint(c.dim, "machine-readable output, full detail")}`);
333
+ push(` residoo scan --seal ${paint(c.dim, "encrypt the affected files into a local vault (originals untouched)")}`);
307
334
  push();
308
335
  push(paint(c.dim, "Values are redacted in this report (first/last 4 characters only). Nothing scanned"));
309
- push(paint(c.dim, "here left your machine; residoo makes no network calls. Run with --json for full detail."));
336
+ push(paint(c.dim, "here left your machine; residoo makes no network calls."));
310
337
 
311
338
  return lines.join("\n");
312
339
  }
@@ -450,4 +477,4 @@ function renderSarif(result) {
450
477
  }, null, 2);
451
478
  }
452
479
 
453
- module.exports = { render, renderIntegrity, renderRotationSection, renderJson, renderSarif, makeProgressReporter };
480
+ module.exports = { render, renderIntegrity, renderRotationSection, renderJson, renderSarif, makeProgressReporter, printIntro };