@dreamdata_io/verify-tracking 0.1.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/LICENSE +21 -0
- package/README.md +756 -0
- package/dist/ai/checker.js +222 -0
- package/dist/ai/checker.js.map +1 -0
- package/dist/ai/checklist.js +190 -0
- package/dist/ai/checklist.js.map +1 -0
- package/dist/ai/cli-presets.js +27 -0
- package/dist/ai/cli-presets.js.map +1 -0
- package/dist/ai/evidence-view.js +143 -0
- package/dist/ai/evidence-view.js.map +1 -0
- package/dist/ai/local-cli.js +58 -0
- package/dist/ai/local-cli.js.map +1 -0
- package/dist/ai/openai-compatible.js +78 -0
- package/dist/ai/openai-compatible.js.map +1 -0
- package/dist/ai/response-parser.js +105 -0
- package/dist/ai/response-parser.js.map +1 -0
- package/dist/ai/types.js +2 -0
- package/dist/ai/types.js.map +1 -0
- package/dist/analysis/consent-mode.js +225 -0
- package/dist/analysis/consent-mode.js.map +1 -0
- package/dist/analysis/deployment.js +2658 -0
- package/dist/analysis/deployment.js.map +1 -0
- package/dist/analysis/diagnosis/copy.en.js +303 -0
- package/dist/analysis/diagnosis/copy.en.js.map +1 -0
- package/dist/analysis/diagnosis/diagnose.js +172 -0
- package/dist/analysis/diagnosis/diagnose.js.map +1 -0
- package/dist/analysis/diagnosis/docs.js +118 -0
- package/dist/analysis/diagnosis/docs.js.map +1 -0
- package/dist/analysis/diagnosis/index.js +5 -0
- package/dist/analysis/diagnosis/index.js.map +1 -0
- package/dist/analysis/diagnosis/rules.js +656 -0
- package/dist/analysis/diagnosis/rules.js.map +1 -0
- package/dist/analysis/diagnosis/types.js +2 -0
- package/dist/analysis/diagnosis/types.js.map +1 -0
- package/dist/cli.js +344 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/env.js +100 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/preflight.js +167 -0
- package/dist/config/preflight.js.map +1 -0
- package/dist/config/user-config.js +79 -0
- package/dist/config/user-config.js.map +1 -0
- package/dist/evidence/collect.js +527 -0
- package/dist/evidence/collect.js.map +1 -0
- package/dist/evidence/consent-accept.js +377 -0
- package/dist/evidence/consent-accept.js.map +1 -0
- package/dist/evidence/consent.js +340 -0
- package/dist/evidence/consent.js.map +1 -0
- package/dist/evidence/constants.js +615 -0
- package/dist/evidence/constants.js.map +1 -0
- package/dist/evidence/globals.js +132 -0
- package/dist/evidence/globals.js.map +1 -0
- package/dist/evidence/gtm-container.js +432 -0
- package/dist/evidence/gtm-container.js.map +1 -0
- package/dist/evidence/heuristic-banner.js +107 -0
- package/dist/evidence/heuristic-banner.js.map +1 -0
- package/dist/evidence/http-probe.js +139 -0
- package/dist/evidence/http-probe.js.map +1 -0
- package/dist/evidence/init-script.js +112 -0
- package/dist/evidence/init-script.js.map +1 -0
- package/dist/evidence/network.js +365 -0
- package/dist/evidence/network.js.map +1 -0
- package/dist/evidence/preference-center.js +368 -0
- package/dist/evidence/preference-center.js.map +1 -0
- package/dist/evidence/script.js +425 -0
- package/dist/evidence/script.js.map +1 -0
- package/dist/evidence/second-page.js +332 -0
- package/dist/evidence/second-page.js.map +1 -0
- package/dist/evidence/selector-probe.js +53 -0
- package/dist/evidence/selector-probe.js.map +1 -0
- package/dist/evidence/stealth.js +21 -0
- package/dist/evidence/stealth.js.map +1 -0
- package/dist/evidence/types.js +14 -0
- package/dist/evidence/types.js.map +1 -0
- package/dist/interactive/prompt.js +31 -0
- package/dist/interactive/prompt.js.map +1 -0
- package/dist/mcp/server.js +308 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/summary.js +41 -0
- package/dist/mcp/summary.js.map +1 -0
- package/dist/report/context.js +184 -0
- package/dist/report/context.js.map +1 -0
- package/dist/report/projection.js +125 -0
- package/dist/report/projection.js.map +1 -0
- package/dist/report/quote-check.js +90 -0
- package/dist/report/quote-check.js.map +1 -0
- package/dist/report/render.js +178 -0
- package/dist/report/render.js.map +1 -0
- package/dist/report/serializers.js +124 -0
- package/dist/report/serializers.js.map +1 -0
- package/dist/report/store.js +139 -0
- package/dist/report/store.js.map +1 -0
- package/dist/run/verify.js +77 -0
- package/dist/run/verify.js.map +1 -0
- package/dist/utils/duration.js +10 -0
- package/dist/utils/duration.js.map +1 -0
- package/dist/utils/errors.js +17 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.js +59 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/runTimingsLog.js +27 -0
- package/dist/utils/runTimingsLog.js.map +1 -0
- package/dist/utils/slug.js +27 -0
- package/dist/utils/slug.js.map +1 -0
- package/dist/version.js +10 -0
- package/dist/version.js.map +1 -0
- package/package.json +57 -0
- package/scripts/install.sh +314 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where reports live on disk, and how to find one by slug.
|
|
3
|
+
*
|
|
4
|
+
* Runs have not stayed flat: the golden set accumulated into dated subfolders
|
|
5
|
+
* (`phase_1/`, `new_script/`), and a replayed run files itself under
|
|
6
|
+
* `local_ai/` rather than beside the baseline it was replayed from. A slug
|
|
7
|
+
* naming a report in one of those is exactly as valid as one at the top
|
|
8
|
+
* level, so every reader walks the tree — and they all walk it the same way,
|
|
9
|
+
* here, rather than each growing its own copy.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
12
|
+
import { readFile } from "node:fs/promises";
|
|
13
|
+
import { join, relative, resolve } from "node:path";
|
|
14
|
+
import { outputRoot } from "../config/user-config.js";
|
|
15
|
+
/**
|
|
16
|
+
* Functions, not constants: the root they sit under is overridable via the
|
|
17
|
+
* user config, which is loaded inside main() — after this module's imports
|
|
18
|
+
* have already been evaluated. A `const` here would capture the default and
|
|
19
|
+
* make the override silently do nothing.
|
|
20
|
+
*/
|
|
21
|
+
export function reportsDir() {
|
|
22
|
+
return join(outputRoot(), "reports");
|
|
23
|
+
}
|
|
24
|
+
export function evidenceDir() {
|
|
25
|
+
return join(outputRoot(), "evidence");
|
|
26
|
+
}
|
|
27
|
+
const REPORT_SUFFIX = "_report.json";
|
|
28
|
+
function reportsRoot() {
|
|
29
|
+
return resolve(process.cwd(), reportsDir());
|
|
30
|
+
}
|
|
31
|
+
function walk(dir) {
|
|
32
|
+
if (!existsSync(dir))
|
|
33
|
+
return [];
|
|
34
|
+
const found = [];
|
|
35
|
+
const stack = [dir];
|
|
36
|
+
while (stack.length > 0) {
|
|
37
|
+
const current = stack.pop();
|
|
38
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
39
|
+
const full = join(current, entry.name);
|
|
40
|
+
if (entry.isDirectory())
|
|
41
|
+
stack.push(full);
|
|
42
|
+
else if (entry.name.endsWith(REPORT_SUFFIX))
|
|
43
|
+
found.push(full);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return found;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Every file that could answer to `slug`, the canonical one first.
|
|
50
|
+
*
|
|
51
|
+
* A run written straight to `verification/reports/` outranks any subfolder
|
|
52
|
+
* copy of the same slug: the subfolders hold archived golden-set runs and
|
|
53
|
+
* replayed (`local_ai/`) ones, and neither should shadow the current
|
|
54
|
+
* ground-truth report just by being newer.
|
|
55
|
+
*/
|
|
56
|
+
function copiesOf(slug, all) {
|
|
57
|
+
const flat = join(reportsRoot(), `${slug}${REPORT_SUFFIX}`);
|
|
58
|
+
const others = all.filter((path) => path !== flat).sort();
|
|
59
|
+
return existsSync(flat) ? [flat, ...others] : others;
|
|
60
|
+
}
|
|
61
|
+
/** The report for `slug`, repo-relative, preferring the flat path over any subfolder copy. */
|
|
62
|
+
export function findReportPath(slug) {
|
|
63
|
+
const matches = walk(reportsRoot()).filter((path) => path.endsWith(`/${slug}${REPORT_SUFFIX}`));
|
|
64
|
+
const [canonical] = copiesOf(slug, matches);
|
|
65
|
+
return canonical ? relative(process.cwd(), canonical) : undefined;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* One entry per slug — the same file `findReportPath` would open, so the two
|
|
69
|
+
* can never name different files for the same slug — newest first.
|
|
70
|
+
*/
|
|
71
|
+
export function listReports() {
|
|
72
|
+
const bySlug = new Map();
|
|
73
|
+
for (const path of walk(reportsRoot())) {
|
|
74
|
+
const slug = path.slice(0, -REPORT_SUFFIX.length).split("/").pop();
|
|
75
|
+
bySlug.set(slug, [...(bySlug.get(slug) ?? []), path]);
|
|
76
|
+
}
|
|
77
|
+
return [...bySlug.entries()]
|
|
78
|
+
.map(([slug, matches]) => {
|
|
79
|
+
const [canonical, ...others] = copiesOf(slug, matches);
|
|
80
|
+
return {
|
|
81
|
+
slug,
|
|
82
|
+
path: relative(process.cwd(), canonical),
|
|
83
|
+
modifiedAt: statSync(canonical).mtime.toISOString(),
|
|
84
|
+
otherCopies: others.map((path) => relative(process.cwd(), path)),
|
|
85
|
+
};
|
|
86
|
+
})
|
|
87
|
+
.sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The untrimmed evidence each run archives beside its report.
|
|
91
|
+
*
|
|
92
|
+
* A report has the request bodies and cookie values taken back out of it
|
|
93
|
+
* (projectReport), and those are not decoration: `page-view-counted-once`
|
|
94
|
+
* reads the page events out of the bodies, so re-analysing a report reaches a
|
|
95
|
+
* different verdict than the run did. Anything replaying a run wants this
|
|
96
|
+
* file, and the report only as a fallback.
|
|
97
|
+
*/
|
|
98
|
+
export function findEvidencePath(slug) {
|
|
99
|
+
const path = join(resolve(process.cwd(), evidenceDir()), `${slug}_evidence.json`);
|
|
100
|
+
return existsSync(path) ? relative(process.cwd(), path) : undefined;
|
|
101
|
+
}
|
|
102
|
+
export async function readReport(path) {
|
|
103
|
+
return JSON.parse(await readFile(path, "utf-8"));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The value at a dotted/bracketed path, e.g. `grant.network.normalRequestCount`,
|
|
107
|
+
* `phases[0].script.present`, or `deployment.checks[page-view-counted-once].status`.
|
|
108
|
+
*
|
|
109
|
+
* That last form — a bracket holding a name rather than an index — is how both
|
|
110
|
+
* the model and applyDeterministicChecks cite a cross-phase check, and the
|
|
111
|
+
* checks are an array, so the name is matched against each element's `id`.
|
|
112
|
+
* Without it such a citation resolves to the whole array, which technically
|
|
113
|
+
* "exists" while telling a reader nothing about the check that was named.
|
|
114
|
+
*
|
|
115
|
+
* Returns undefined for a path that does not resolve — which is the answer a
|
|
116
|
+
* caller asking "does the model's quoted field actually exist?" needs, so a
|
|
117
|
+
* miss is not an error.
|
|
118
|
+
*/
|
|
119
|
+
export function valueAtPath(root, path) {
|
|
120
|
+
if (!path)
|
|
121
|
+
return undefined;
|
|
122
|
+
// `[0]`, `[some-id]` and `['some-id']` all become one path segment; the
|
|
123
|
+
// quotes are how a model tends to write a name-keyed citation.
|
|
124
|
+
const keys = path.replace(/\[['"]?([^\]'"]+)['"]?\]/g, ".$1").split(".");
|
|
125
|
+
let current = root;
|
|
126
|
+
for (const key of keys) {
|
|
127
|
+
if (current === null || current === undefined || typeof current !== "object")
|
|
128
|
+
return undefined;
|
|
129
|
+
if (Array.isArray(current) && !/^\d+$/.test(key)) {
|
|
130
|
+
current = current.find((item) => item?.id === key);
|
|
131
|
+
if (current === undefined)
|
|
132
|
+
return undefined;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
current = current[key];
|
|
136
|
+
}
|
|
137
|
+
return current;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/report/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One verification run, without a terminal around it.
|
|
3
|
+
*
|
|
4
|
+
* cli.ts owns flags, a spinner and the exit code; the MCP server owns a tool
|
|
5
|
+
* schema and a JSON reply. Between the two sits the same three steps in the
|
|
6
|
+
* same order — collect the evidence, analyse it deterministically, then ask
|
|
7
|
+
* the model — and that middle is what lives here, so neither caller can drift
|
|
8
|
+
* from the other on what a run actually is.
|
|
9
|
+
*/
|
|
10
|
+
import { analyzeDeployment } from "../analysis/deployment.js";
|
|
11
|
+
import { checkWithAI } from "../ai/checker.js";
|
|
12
|
+
import { BATCH_FLUSH_WAIT_MS } from "../config/env.js";
|
|
13
|
+
import { collectEvidence } from "../evidence/collect.js";
|
|
14
|
+
import { evidenceDir, reportsDir } from "../report/store.js";
|
|
15
|
+
import { serializeEvidenceToJson, serializeToJson } from "../report/serializers.js";
|
|
16
|
+
import { appendRunTimings } from "../utils/runTimingsLog.js";
|
|
17
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
18
|
+
import { dirname } from "node:path";
|
|
19
|
+
/** Every output path this CLI writes may name a directory that isn't there yet. */
|
|
20
|
+
export async function writeEnsuringDir(path, contents) {
|
|
21
|
+
await mkdir(dirname(path), { recursive: true });
|
|
22
|
+
await writeFile(path, contents, "utf-8");
|
|
23
|
+
}
|
|
24
|
+
export async function runVerification(options, onProgress) {
|
|
25
|
+
const runStart = Date.now();
|
|
26
|
+
const collectEvidenceStart = Date.now();
|
|
27
|
+
const evidence = await collectEvidence({
|
|
28
|
+
url: options.url,
|
|
29
|
+
headless: options.headless,
|
|
30
|
+
timeoutMs: options.timeoutMs,
|
|
31
|
+
batchFlushWaitMs: BATCH_FLUSH_WAIT_MS,
|
|
32
|
+
trackingHosts: options.trackingHosts,
|
|
33
|
+
phases: options.phases,
|
|
34
|
+
scanTagManager: options.scanTagManager,
|
|
35
|
+
probeSecondPage: options.probeSecondPage,
|
|
36
|
+
probeOverHttp: options.probeOverHttp,
|
|
37
|
+
}, (phase, index, total) => onProgress?.({ step: "collect", phase, index, total }));
|
|
38
|
+
const collectEvidenceMs = Date.now() - collectEvidenceStart;
|
|
39
|
+
onProgress?.({ step: "analyze" });
|
|
40
|
+
const analyzeDeploymentStart = Date.now();
|
|
41
|
+
const analysis = analyzeDeployment(evidence, options.phases);
|
|
42
|
+
const analyzeDeploymentMs = Date.now() - analyzeDeploymentStart;
|
|
43
|
+
onProgress?.({ step: "ai-check" });
|
|
44
|
+
const checkWithAIStart = Date.now();
|
|
45
|
+
const result = await checkWithAI(evidence, analysis, options.backend);
|
|
46
|
+
const checkWithAIMs = Date.now() - checkWithAIStart;
|
|
47
|
+
const timings = {
|
|
48
|
+
collectEvidenceMs,
|
|
49
|
+
analyzeDeploymentMs,
|
|
50
|
+
checkWithAIMs,
|
|
51
|
+
totalMs: Date.now() - runStart,
|
|
52
|
+
};
|
|
53
|
+
await appendRunTimings({
|
|
54
|
+
timestamp: new Date().toISOString(),
|
|
55
|
+
kind: "run",
|
|
56
|
+
model: options.backend.model,
|
|
57
|
+
url: options.url,
|
|
58
|
+
slug: options.slug,
|
|
59
|
+
phases: options.phases,
|
|
60
|
+
durationsMs: timings,
|
|
61
|
+
});
|
|
62
|
+
return { evidence, analysis, result, timings };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The default two-file write: the trimmed report a human (or the review
|
|
66
|
+
* dashboard) reads, and the untrimmed evidence archive beside it. `--out`
|
|
67
|
+
* takes a run out of this layout and is handled by the caller that offers it.
|
|
68
|
+
*/
|
|
69
|
+
export async function writeDefaultArtifacts(run, slug) {
|
|
70
|
+
const reportPath = `${reportsDir()}/${slug}_report.json`;
|
|
71
|
+
const evidencePath = `${evidenceDir()}/${slug}_evidence.json`;
|
|
72
|
+
const meta = { slug, timings: run.timings };
|
|
73
|
+
await writeEnsuringDir(reportPath, serializeToJson(run.evidence, run.analysis, run.result, meta));
|
|
74
|
+
await writeEnsuringDir(evidencePath, serializeEvidenceToJson(run.evidence, run.analysis, run.result, meta));
|
|
75
|
+
return { reportPath, evidencePath };
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/run/verify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** `90000` -> `"1m 30s"`. Always shows both units so a report never implies a step ran in under a minute when it didn't. */
|
|
2
|
+
export function formatDuration(ms) {
|
|
3
|
+
if (ms === null)
|
|
4
|
+
return "not run (replayed from an archive)";
|
|
5
|
+
const totalSeconds = Math.round(ms / 1000);
|
|
6
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
7
|
+
const seconds = totalSeconds % 60;
|
|
8
|
+
return `${minutes}m ${seconds}s`;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=duration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duration.js","sourceRoot":"","sources":["../../src/utils/duration.ts"],"names":[],"mappings":"AAcA,4HAA4H;AAC5H,MAAM,UAAU,cAAc,CAAC,EAAiB;IAC9C,IAAI,EAAE,KAAK,IAAI;QAAE,OAAO,oCAAoC,CAAC;IAC7D,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,EAAE,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,YAAY,GAAG,EAAE,CAAC;IAClC,OAAO,GAAG,OAAO,KAAK,OAAO,GAAG,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class EvidenceCollectionError extends Error {
|
|
2
|
+
cause;
|
|
3
|
+
constructor(message, cause) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.cause = cause;
|
|
6
|
+
this.name = "EvidenceCollectionError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class AIError extends Error {
|
|
10
|
+
cause;
|
|
11
|
+
constructor(message, cause) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.cause = cause;
|
|
14
|
+
this.name = "AIError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IACV;IAAtC,YAAY,OAAe,EAAW,KAAe;QACnD,KAAK,CAAC,OAAO,CAAC,CAAC;QADqB,UAAK,GAAL,KAAK,CAAU;QAEnD,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAED,MAAM,OAAO,OAAQ,SAAQ,KAAK;IACM;IAAtC,YAAY,OAAe,EAAW,KAAe;QACnD,KAAK,CAAC,OAAO,CAAC,CAAC;QADqB,UAAK,GAAL,KAAK,CAAU;QAEnD,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;IACxB,CAAC;CACF"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const SECRET_KEY_PATTERN = /api[-_]?key|authorization|bearer/i;
|
|
2
|
+
function redact(value) {
|
|
3
|
+
if (typeof value === "string")
|
|
4
|
+
return value;
|
|
5
|
+
if (Array.isArray(value))
|
|
6
|
+
return value.map(redact);
|
|
7
|
+
if (value && typeof value === "object") {
|
|
8
|
+
return Object.fromEntries(Object.entries(value).map(([key, val]) => SECRET_KEY_PATTERN.test(key) ? [key, "[redacted]"] : [key, redact(val)]));
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Where `info` goes.
|
|
14
|
+
*
|
|
15
|
+
* A terminal wants it on stdout next to the report. The MCP server cannot
|
|
16
|
+
* have it there at all: stdout is the JSON-RPC channel, and one stray line of
|
|
17
|
+
* prose in it is a protocol parse error, not a cosmetic problem. Everything
|
|
18
|
+
* the collector logs mid-run (`logger.info`, `warn`, `debug`) would land in
|
|
19
|
+
* that channel, so the server flips this once at startup and every existing
|
|
20
|
+
* call site follows without knowing about it. `warn` and `error` are already
|
|
21
|
+
* safe (console.warn/error are stderr), but `debug` is not — console.debug is
|
|
22
|
+
* an alias for console.log, i.e. stdout — so it is switched here as well.
|
|
23
|
+
*/
|
|
24
|
+
let infoStream = "stdout";
|
|
25
|
+
/** Send `logger.info` to stderr — for any caller that owns stdout itself. */
|
|
26
|
+
export function sendLogsToStderr() {
|
|
27
|
+
infoStream = "stderr";
|
|
28
|
+
}
|
|
29
|
+
function write(message) {
|
|
30
|
+
if (infoStream === "stderr")
|
|
31
|
+
console.error(message);
|
|
32
|
+
else
|
|
33
|
+
console.log(message);
|
|
34
|
+
}
|
|
35
|
+
export const logger = {
|
|
36
|
+
debugEnabled: process.env.DEBUG === "1" || process.env.DEBUG === "true",
|
|
37
|
+
info(message) {
|
|
38
|
+
write(message);
|
|
39
|
+
},
|
|
40
|
+
warn(message) {
|
|
41
|
+
console.warn(message);
|
|
42
|
+
},
|
|
43
|
+
error(message, cause) {
|
|
44
|
+
console.error(message);
|
|
45
|
+
if (cause instanceof Error) {
|
|
46
|
+
console.error(cause.message);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
debug(message, data) {
|
|
50
|
+
if (!this.debugEnabled)
|
|
51
|
+
return;
|
|
52
|
+
if (data === undefined) {
|
|
53
|
+
write(`[debug] ${message}`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
write(`[debug] ${message} ${JSON.stringify(redact(data))}`);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,MAAM,kBAAkB,GAAG,mCAAmC,CAAC;AAE/D,SAAS,MAAM,CAAC,KAAc;IAC5B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACnD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CAClE,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CACxE,CACF,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;GAWG;AACH,IAAI,UAAU,GAAwB,QAAQ,CAAC;AAE/C,6EAA6E;AAC7E,MAAM,UAAU,gBAAgB;IAC9B,UAAU,GAAG,QAAQ,CAAC;AACxB,CAAC;AAED,SAAS,KAAK,CAAC,OAAe;IAC5B,IAAI,UAAU,KAAK,QAAQ;QAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;;QAC/C,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,MAAM;IAEvE,IAAI,CAAC,OAAe;QAClB,KAAK,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;IAED,IAAI,CAAC,OAAe;QAClB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,KAAe;QACpC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACvB,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAAc;QACnC,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,OAAO;QAC/B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,KAAK,CAAC,WAAW,OAAO,EAAE,CAAC,CAAC;YAC5B,OAAO;QACT,CAAC;QACD,KAAK,CAAC,WAAW,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9D,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { mkdir, appendFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { outputRoot } from "../config/user-config.js";
|
|
4
|
+
/**
|
|
5
|
+
* One line per run, appended and never rewritten — so a failure mid-run can
|
|
6
|
+
* never lose an earlier run's timing, and the file never needs to be read back
|
|
7
|
+
* in just to add to it. JSON Lines rather than a single JSON array for exactly
|
|
8
|
+
* that reason: an array would require reading and rewriting the whole file on
|
|
9
|
+
* every run.
|
|
10
|
+
*
|
|
11
|
+
* It sits under the same root as the reports rather than in the working
|
|
12
|
+
* directory. This is a development record with no reader outside the repo, and
|
|
13
|
+
* an installed CLI is run from wherever its operator happens to be — dropping
|
|
14
|
+
* a stray file there alongside the report directory is one piece of litter
|
|
15
|
+
* too many, and one setting should govern everything a run writes.
|
|
16
|
+
*/
|
|
17
|
+
function runTimingsPath() {
|
|
18
|
+
return join(outputRoot(), "run-timings.jsonl");
|
|
19
|
+
}
|
|
20
|
+
export async function appendRunTimings(record) {
|
|
21
|
+
// The root may not exist yet: a run that saves no report writes this and
|
|
22
|
+
// nothing else, so it cannot rely on the report writer having made it.
|
|
23
|
+
const path = runTimingsPath();
|
|
24
|
+
await mkdir(dirname(path), { recursive: true });
|
|
25
|
+
await appendFile(path, `${JSON.stringify(record)}\n`, "utf-8");
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=runTimingsLog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runTimingsLog.js","sourceRoot":"","sources":["../../src/utils/runTimingsLog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAGtD;;;;;;;;;;;;GAYG;AACH,SAAS,cAAc;IACrB,OAAO,IAAI,CAAC,UAAU,EAAE,EAAE,mBAAmB,CAAC,CAAC;AACjD,CAAC;AAmBD,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAwB;IAC7D,yEAAyE;IACzE,uEAAuE;IACvE,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9B,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,UAAU,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AACjE,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The account slug: what a run is filed under, and — because it goes straight
|
|
3
|
+
* into a report path — the one caller-supplied string that could climb out of
|
|
4
|
+
* `verification/reports/`. Both the CLI and the MCP server take a slug from
|
|
5
|
+
* outside, so the rule lives here rather than in either one of them.
|
|
6
|
+
*/
|
|
7
|
+
/** Matches the `<domain-with-underscores>` convention `verification/slugs.txt` already uses. */
|
|
8
|
+
export const SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?$/i;
|
|
9
|
+
/**
|
|
10
|
+
* Anything that could escape the reports directory — a separator, a leading
|
|
11
|
+
* dot — is rejected rather than sanitised: silently rewriting it would file
|
|
12
|
+
* the report under a name the caller never asked for and cannot predict.
|
|
13
|
+
*/
|
|
14
|
+
export function isSafeSlug(slug) {
|
|
15
|
+
return SLUG_PATTERN.test(slug);
|
|
16
|
+
}
|
|
17
|
+
export function slugFromUrl(url) {
|
|
18
|
+
let hostname;
|
|
19
|
+
try {
|
|
20
|
+
hostname = new URL(url).hostname;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
hostname = url;
|
|
24
|
+
}
|
|
25
|
+
return hostname.replace(/^www\./, "").replace(/\./g, "_");
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=slug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slug.js","sourceRoot":"","sources":["../../src/utils/slug.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,gGAAgG;AAChG,MAAM,CAAC,MAAM,YAAY,GAAG,qCAAqC,CAAC;AAElE;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,IAAI,QAAgB,CAAC;IACrB,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,QAAQ,GAAG,GAAG,CAAC;IACjB,CAAC;IACD,OAAO,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAC5D,CAAC"}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The published version, in one place.
|
|
3
|
+
*
|
|
4
|
+
* Not read from package.json at run time: `rootDir: src` keeps it out of the
|
|
5
|
+
* compiled tree, and counting directories back up to it from dist/mcp/server.js
|
|
6
|
+
* breaks the moment the output layout changes. A literal that the release
|
|
7
|
+
* workflow checks against the git tag is the cheaper guarantee.
|
|
8
|
+
*/
|
|
9
|
+
export const VERSION = "0.1.0";
|
|
10
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dreamdata_io/verify-tracking",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scrapes a live website with Playwright and asks an AI model whether the Dreamdata Analytics Script is correctly installed.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/dreamdata-io/tracking-verification-cli.git"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"dreamdata",
|
|
13
|
+
"analytics",
|
|
14
|
+
"tracking",
|
|
15
|
+
"verification",
|
|
16
|
+
"consent-mode",
|
|
17
|
+
"playwright"
|
|
18
|
+
],
|
|
19
|
+
"bin": {
|
|
20
|
+
"verify-tracking": "./dist/cli.js",
|
|
21
|
+
"verify-tracking-mcp": "./dist/mcp/server.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"scripts/install.sh",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc -p tsconfig.json",
|
|
34
|
+
"typecheck": "tsc -p tsconfig.check.json",
|
|
35
|
+
"cli": "bun run src/cli.ts",
|
|
36
|
+
"test": "bun test",
|
|
37
|
+
"test:watch": "bun test --watch",
|
|
38
|
+
"mcp": "bun run src/mcp/server.ts",
|
|
39
|
+
"prepublishOnly": "bun run build && bun run typecheck && bun test"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=20",
|
|
43
|
+
"bun": ">=1.4.0"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@clack/prompts": "^0.9.1",
|
|
47
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
48
|
+
"openai": "^4.77.0",
|
|
49
|
+
"playwright": "^1.49.1",
|
|
50
|
+
"zod": "^4.5.4"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "^22.20.1",
|
|
54
|
+
"bun-types": "^1.4.0",
|
|
55
|
+
"typescript": "^5.7.3"
|
|
56
|
+
}
|
|
57
|
+
}
|