@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 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 test # parsers, checks, each AI provider and the MCP server, end to end against the demo shop
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 test # parsers, checks, each AI provider and the MCP server, end to end against the demo shop
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.1",
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
- 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 });
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
- const firstHitStep = run.hits.length ? Math.min(...run.hits.map((h) => h.step)) : null;
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 (firstHitStep !== null && defaultEntry.step > firstHitStep) {
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");