@analitikisler/ai-tag-debugger 0.8.1 → 0.8.2
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/CHANGELOG.md +9 -0
- package/README.md +8 -1
- package/README.tr.md +8 -1
- package/package.json +4 -2
- package/src/agent.js +15 -0
- package/src/audit.js +9 -2
- package/src/checks.js +8 -2
- package/src/cli.js +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.2 (2026-10-15)
|
|
4
|
+
|
|
5
|
+
- A journey that fails with an error, such as the AI provider's API refusing a request or the browser crashing, no longer ends the whole audit. That journey is marked unfinished with the error as its note, what it captured until then is kept, the other journeys still run, and the report is written. This applies to the CLI and to `run_audit` over MCP.
|
|
6
|
+
- A Consent Mode default pushed after the first Google hit is now reported as late (`consent_default_late`) when both happen on the same step, such as the start page. The check used to compare only step numbers, so it missed this. Only Google hits count, since Consent Mode only governs Google tags.
|
|
7
|
+
- An unknown viewport, given with `--viewport` or in the config's `viewports`, now stops the audit at once with a message listing the allowed values (desktop, mobile). Before, it failed only when the browser started for that journey, after the model was set up and earlier journeys had run.
|
|
8
|
+
- `ai-tag-debugger --version` (or `aitd -v`) prints the installed version.
|
|
9
|
+
- New short command `aitd`, an alias for `ai-tag-debugger` installed alongside it.
|
|
10
|
+
- For contributors: `npm run setup` checks the Node version, installs the dependencies and Chromium, and says whether an AI provider key is set. A dev container (`.devcontainer/devcontainer.json`) runs it, so VS Code and Codespaces open the repository ready to run.
|
|
11
|
+
|
|
3
12
|
## 0.8.1 (2026-10-15)
|
|
4
13
|
|
|
5
14
|
- A run with no AI provider key set now stops at once with a message naming the keys to set and the `--scripted --no-ai` option, instead of starting the browser and failing at the first step with the SDK's "Could not resolve authentication method".
|
package/README.md
CHANGED
|
@@ -110,6 +110,8 @@ export ANTHROPIC_API_KEY=... # or OPENAI_API_KEY, or GEMINI_API_K
|
|
|
110
110
|
ai-tag-debugger run --site https://staging.yourshop.com --journey add_to_cart --headed
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
+
The global install also adds `aitd`, a short alias: `aitd run --site … --journey add_to_cart` does the same.
|
|
114
|
+
|
|
113
115
|
Or run it without installing: `npx -y @analitikisler/ai-tag-debugger run --site … --journey add_to_cart`.
|
|
114
116
|
|
|
115
117
|
Run `ai-tag-debugger --help` to see every command and option, such as `--providers`, `--scripted` and `--no-ai`.
|
|
@@ -252,6 +254,8 @@ ai-tag-debugger mcp [--http] [--port <n>] [--host <addr>] [--token <secret>] [--
|
|
|
252
254
|
[--allow-private-network]
|
|
253
255
|
```
|
|
254
256
|
|
|
257
|
+
`aitd` is a short alias for `ai-tag-debugger` and takes the same commands and options.
|
|
258
|
+
|
|
255
259
|
The exit code is `1` when findings reach `--fail-on` (default `broken`), so it can gate a deploy. See [`examples/github-actions/ai-tag-debugger.yml`](examples/github-actions/ai-tag-debugger.yml) for running it after every deployment.
|
|
256
260
|
|
|
257
261
|
Output:
|
|
@@ -358,9 +362,12 @@ A hosted server, so you don't need a tunnel, is part of the [Analitik İşler](h
|
|
|
358
362
|
## Development
|
|
359
363
|
|
|
360
364
|
```bash
|
|
361
|
-
npm
|
|
365
|
+
npm run setup # checks Node, installs the dependencies and Chromium, says whether an AI provider key is set
|
|
366
|
+
npm test # parsers, checks, each AI provider and the MCP server, end to end against the demo shop
|
|
362
367
|
```
|
|
363
368
|
|
|
369
|
+
The repository also has a dev container (`.devcontainer/devcontainer.json`). Open it in VS Code with the Dev Containers extension, or in GitHub Codespaces, and it runs `npm run setup` for you, Chromium's system libraries included. To use an AI model there, add your key as a Codespaces secret or export it in the container's terminal.
|
|
370
|
+
|
|
364
371
|
The end-to-end tests use fake model clients, so they run without an API key.
|
|
365
372
|
|
|
366
373
|
## Hosted version
|
package/README.tr.md
CHANGED
|
@@ -110,6 +110,8 @@ export ANTHROPIC_API_KEY=... # or OPENAI_API_KEY, or GEMINI_API_K
|
|
|
110
110
|
ai-tag-debugger run --site https://staging.yourshop.com --journey add_to_cart --headed
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
+
Global kurulum kısa bir takma ad olan `aitd` komutunu da ekler: `aitd run --site … --journey add_to_cart` aynı işi yapar.
|
|
114
|
+
|
|
113
115
|
Ya da kurmadan çalıştırın: `npx -y @analitikisler/ai-tag-debugger run --site … --journey add_to_cart`.
|
|
114
116
|
|
|
115
117
|
Tüm komutları ve seçenekleri (`--providers`, `--scripted`, `--no-ai` gibi) görmek için `ai-tag-debugger --help` çalıştırın.
|
|
@@ -252,6 +254,8 @@ ai-tag-debugger mcp [--http] [--port <n>] [--host <addr>] [--token <secret>] [--
|
|
|
252
254
|
[--allow-private-network]
|
|
253
255
|
```
|
|
254
256
|
|
|
257
|
+
`aitd`, `ai-tag-debugger` için kısa bir takma addır ve aynı komut ve seçenekleri alır.
|
|
258
|
+
|
|
255
259
|
Bulgular `--fail-on` seviyesine (varsayılan `broken`) ulaştığında çıkış kodu `1` olur, böylece bir deploy'u durdurabilir. Her deploy sonrası çalıştırmak için [`examples/github-actions/ai-tag-debugger.yml`](examples/github-actions/ai-tag-debugger.yml) örneğine bakın.
|
|
256
260
|
|
|
257
261
|
Çıktılar:
|
|
@@ -358,9 +362,12 @@ Tünele gerek kalmayan, hazır bir adresle barındırılan sunucu, [Analitik İ
|
|
|
358
362
|
## Geliştirme
|
|
359
363
|
|
|
360
364
|
```bash
|
|
361
|
-
npm
|
|
365
|
+
npm run setup # checks Node, installs the dependencies and Chromium, says whether an AI provider key is set
|
|
366
|
+
npm test # parsers, checks, each AI provider and the MCP server, end to end against the demo shop
|
|
362
367
|
```
|
|
363
368
|
|
|
369
|
+
Depoda bir dev container (`.devcontainer/devcontainer.json`) da var. Depoyu VS Code'da Dev Containers eklentisiyle ya da GitHub Codespaces'te açtığınızda `npm run setup` sizin için çalışır ve Chromium'un sistem kütüphanelerini de kurar. Orada bir yapay zekâ modeli kullanmak için anahtarınızı Codespaces secret olarak ekleyin ya da container'ın terminalinde export edin.
|
|
370
|
+
|
|
364
371
|
Uçtan uca testler sahte model istemcileri kullanır, bu yüzden API anahtarı olmadan çalışır.
|
|
365
372
|
|
|
366
373
|
## Barındırılan sürüm
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@analitikisler/ai-tag-debugger",
|
|
3
3
|
"mcpName": "io.github.analitikisler/ai-tag-debugger",
|
|
4
|
-
"version": "0.8.
|
|
4
|
+
"version": "0.8.2",
|
|
5
5
|
"description": "Open-source AI agent that QA-tests your website's analytics, ad and consent tracking (GA4, GTM, Google Ads, Meta, TikTok). Bring your own model (Claude, OpenAI, Gemini) or use it from your chat app over MCP.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"ai-tag-debugger": "src/cli.js"
|
|
8
|
+
"ai-tag-debugger": "src/cli.js",
|
|
9
|
+
"aitd": "src/cli.js"
|
|
9
10
|
},
|
|
10
11
|
"files": [
|
|
11
12
|
"src",
|
|
@@ -14,6 +15,7 @@
|
|
|
14
15
|
"CHANGELOG.md"
|
|
15
16
|
],
|
|
16
17
|
"scripts": {
|
|
18
|
+
"setup": "node scripts/setup.js",
|
|
17
19
|
"test": "node --test test/*.test.js",
|
|
18
20
|
"demo:serve": "node examples/serve.js",
|
|
19
21
|
"demo": "node src/cli.js run --config examples/demo.config.json --scripted --no-ai --out reports/demo"
|
package/src/agent.js
CHANGED
|
@@ -203,12 +203,27 @@ export async function runJourney(config, journey, viewport, opts = {}) {
|
|
|
203
203
|
throw err;
|
|
204
204
|
});
|
|
205
205
|
return { ...collect(session, journey, viewport), ...outcome, cookies: await session.cookies() };
|
|
206
|
+
} catch (err) {
|
|
207
|
+
// The model's API, the browser or the site can fail in many ways. That ends this journey, not the run,
|
|
208
|
+
// and what was captured until then is kept.
|
|
209
|
+
return { ...collect(session, journey, viewport), completed: false, note: journeyError(err) };
|
|
206
210
|
} finally {
|
|
207
211
|
if (opts.saveSite) opts.log?.(" Writing the recording…");
|
|
208
212
|
await session.close();
|
|
209
213
|
}
|
|
210
214
|
}
|
|
211
215
|
|
|
216
|
+
/** The note of a journey that ended with an error. */
|
|
217
|
+
export const journeyError = (err) => `The journey stopped with an error: ${err?.message ?? err}`;
|
|
218
|
+
|
|
219
|
+
/** A journey that failed before anything was captured, so the report can still list it. */
|
|
220
|
+
export function failedRun(journey, viewport, err, providers) {
|
|
221
|
+
return {
|
|
222
|
+
journeyId: journey.id, viewport, completed: false, note: journeyError(err), hits: [], steps: [], consentStep: null, rejectStep: null,
|
|
223
|
+
cookieLog: [], dataLayerLog: [], network: [], providers, skipped: {}, bodyBudgetStep: null, startedAt: Date.now(), pageErrors: [],
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
212
227
|
export function collect(session, journey, viewport) {
|
|
213
228
|
return {
|
|
214
229
|
journeyId: journey.id,
|
package/src/audit.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// Shared by the CLI and the MCP server's run_audit tool.
|
|
3
3
|
import { readFile, mkdir, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { runJourney } from "./agent.js";
|
|
5
|
+
import { failedRun, runJourney } from "./agent.js";
|
|
6
|
+
import { VIEWPORTS } from "./browser.js";
|
|
6
7
|
import { checkRun, groupFindings, summarize } from "./checks.js";
|
|
7
8
|
import { explainFindings, renderCsv, renderHtml, renderMarkdown } from "./report.js";
|
|
8
9
|
import { LANGUAGES, messages } from "./i18n.js";
|
|
@@ -70,6 +71,10 @@ export async function runAudit(opts) {
|
|
|
70
71
|
const { journeys, plan } = planRun({ config, plan: opts.plan, picks: opts.journeys, goals: opts.goals, events });
|
|
71
72
|
config = { ...config, providers: resolveProviders(opts.providers ?? config.providers, plan) };
|
|
72
73
|
const viewports = opts.viewports?.length ? opts.viewports : config.viewports ?? ["desktop"];
|
|
74
|
+
const allowed = Object.keys(VIEWPORTS).join(", ");
|
|
75
|
+
if (!Array.isArray(viewports)) throw new Error(`"viewports" must be a list, e.g. ["desktop", "mobile"]. Allowed values: ${allowed}.`);
|
|
76
|
+
const unknown = viewports.filter((v) => !Object.hasOwn(VIEWPORTS, v));
|
|
77
|
+
if (unknown.length) throw new Error(`Unknown viewport ${unknown.map((v) => `"${v}"`).join(", ")}. Use: ${allowed}.`);
|
|
73
78
|
if (!journeys.length) throw new Error("No journeys to run. Pick one with --journey (e.g. purchase) or describe one with --goal.");
|
|
74
79
|
if (opts.scripted) {
|
|
75
80
|
const unscripted = journeys.filter((j) => !j.steps?.length && !j.incomplete).map((j) => j.id);
|
|
@@ -84,7 +89,9 @@ export async function runAudit(opts) {
|
|
|
84
89
|
for (const journey of journeys) {
|
|
85
90
|
for (const viewport of viewports) {
|
|
86
91
|
log(`\n▶ ${journey.id} (${viewport})`);
|
|
87
|
-
|
|
92
|
+
// runJourney keeps its own errors in the run. This catches the rest, such as a browser that won't close,
|
|
93
|
+
// so one journey can't cost the report of the others.
|
|
94
|
+
const run = await runJourney(config, journey, viewport, { scripted: opts.scripted, provider, maxSteps: opts.maxSteps, headed: opts.headed, lang, log, saveSite: opts.saveSite, replaySite: opts.replaySite }).catch((err) => failedRun(journey, viewport, err, config.providers));
|
|
88
95
|
log(` ${run.completed ? "✓" : "✗"} ${run.note} · ${run.hits.length} tracking hits captured`);
|
|
89
96
|
runs.push(run);
|
|
90
97
|
}
|
package/src/checks.js
CHANGED
|
@@ -5,6 +5,11 @@ import { messages } from "./i18n.js";
|
|
|
5
5
|
import { isOwnAddress } from "./redact.js";
|
|
6
6
|
|
|
7
7
|
const NON_GOOGLE_AD_PLATFORMS = new Set(["meta", "tiktok"]);
|
|
8
|
+
const GOOGLE_PLATFORMS = new Set(["ga4", "google_ads"]);
|
|
9
|
+
|
|
10
|
+
/** Whether a happened before b: an earlier step, or the same step and an earlier time when both have one. */
|
|
11
|
+
const sentBefore = (a, b) => a.step < b.step || (a.step === b.step && a.t != null && b.t != null && a.t < b.t);
|
|
12
|
+
|
|
8
13
|
const EMAIL = /[A-Z0-9._%+-]+(@|%40)[A-Z0-9.-]+\.[A-Z]{2,}/gi;
|
|
9
14
|
// Consent Mode v2 added two signals to ad_storage and analytics_storage. Google requires them for EEA/UK traffic.
|
|
10
15
|
const V2_SIGNALS = ["ad_user_data", "ad_personalization"];
|
|
@@ -222,12 +227,13 @@ export function checkRun(plan, run, { lang = "en" } = {}) {
|
|
|
222
227
|
|
|
223
228
|
// Consent.
|
|
224
229
|
const granted = consentGrantedStep(run);
|
|
225
|
-
|
|
230
|
+
// Consent Mode only governs Google tags. Within one step, the push and hit times decide which came first.
|
|
231
|
+
const firstGoogleHit = run.hits.filter((h) => GOOGLE_PLATFORMS.has(h.platform)).reduce((a, h) => (!a || sentBefore(h, a) ? h : a), null);
|
|
226
232
|
if (plan.consent_mode) {
|
|
227
233
|
const defaultEntry = run.dataLayerLog.find((e) => isConsentCommand(e, "default"));
|
|
228
234
|
if (!defaultEntry) {
|
|
229
235
|
findings.push({ ...base, severity: "risk", check: "consent_default_missing", detail: d.consentDefaultMissing() });
|
|
230
|
-
} else if (
|
|
236
|
+
} else if (firstGoogleHit && sentBefore(firstGoogleHit, defaultEntry)) {
|
|
231
237
|
findings.push({ ...base, severity: "risk", check: "consent_default_late", detail: d.consentDefaultLate() });
|
|
232
238
|
}
|
|
233
239
|
}
|
package/src/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { parseArgs } from "node:util";
|
|
4
5
|
import { loadConfig, runAudit } from "./audit.js";
|
|
@@ -8,6 +9,8 @@ import { JOURNEY_PRESETS } from "./journeys.js";
|
|
|
8
9
|
import { PROVIDERS as TRACKING_PROVIDERS, DEFAULT_PROVIDERS } from "./parsers.js";
|
|
9
10
|
import { replaySavedSite } from "./replay.js";
|
|
10
11
|
|
|
12
|
+
const VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
13
|
+
|
|
11
14
|
const USAGE = `ai-tag-debugger: an open-source AI agent that QA-tests your analytics and ad tracking.
|
|
12
15
|
|
|
13
16
|
Usage:
|
|
@@ -61,6 +64,9 @@ MCP options:
|
|
|
61
64
|
--public-url <url> The HTTPS address your tunnel or host gives this server, so audits can link to their reports.
|
|
62
65
|
|
|
63
66
|
-h, --help Show this help.
|
|
67
|
+
-v, --version Print the version.
|
|
68
|
+
|
|
69
|
+
Short alias: aitd (aitd run ... is the same as ai-tag-debugger run ...).
|
|
64
70
|
|
|
65
71
|
API keys: set one of ${Object.values(PROVIDERS).map((p) => p.env).join(", ")}.
|
|
66
72
|
Any OpenAI-compatible API: --provider openai-compatible --base-url <url> --model <id>, with the key in OPENAI_COMPATIBLE_API_KEY.`;
|
|
@@ -99,9 +105,14 @@ async function main() {
|
|
|
99
105
|
"save-site": { type: "string" },
|
|
100
106
|
update: { type: "boolean", default: false },
|
|
101
107
|
help: { type: "boolean", short: "h", default: false },
|
|
108
|
+
version: { type: "boolean", short: "v", default: false },
|
|
102
109
|
},
|
|
103
110
|
});
|
|
104
111
|
const command = positionals[0];
|
|
112
|
+
if (values.version) {
|
|
113
|
+
console.log(VERSION);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
105
116
|
|
|
106
117
|
if (command === "mcp" && !values.help) {
|
|
107
118
|
const { startMcp } = await import("./mcp.js");
|