@analitikisler/ai-tag-debugger 0.11.1 → 0.11.3
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 +10 -0
- package/README.md +11 -0
- package/README.tr.md +11 -0
- package/package.json +1 -1
- package/src/ask.js +8 -6
- package/src/audit.js +2 -2
- package/src/help.js +3 -2
- package/src/providers.js +53 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.11.3 (2026-10-16)
|
|
4
|
+
|
|
5
|
+
- `aitd` and `aitd --help` end with the AI a run would use: `AI: anthropic (claude-opus-5-5) via Cloudflare AI Gateway · also set: gemini (pick with --provider)`, or `AI: no key set`. The first line of a run names the other keys too.
|
|
6
|
+
- Vercel AI Gateway: set `AI_GATEWAY_API_KEY` and pick any model it offers with `--model <provider>/<model>`, e.g. `google/gemini-3.6-flash`. The default is `anthropic/claude-opus-5.5`.
|
|
7
|
+
|
|
8
|
+
## 0.11.2 (2026-10-16)
|
|
9
|
+
|
|
10
|
+
- Fix sentence requests (`aitd "check my GA tags"`) failing with a 400 "Invalid schema" error from the Anthropic API. The request schema now writes optional values in a form Anthropic, OpenAI and Gemini all accept.
|
|
11
|
+
- When `AI_GATEWAY_URL` is set, the first line of a run says so: `Using anthropic (claude-opus-5-5) via Cloudflare AI Gateway`.
|
|
12
|
+
|
|
3
13
|
## 0.11.1 (2026-10-16)
|
|
4
14
|
|
|
5
15
|
- Set `AI_GATEWAY_URL` (and `AI_GATEWAY_TOKEN` if the gateway has authentication on) to send Anthropic, OpenAI and Gemini calls through Cloudflare AI Gateway, for logs and cost tracking. Unset, nothing changes.
|
package/README.md
CHANGED
|
@@ -99,6 +99,17 @@ ai-tag-debugger run --site https://yourshop.com --journey add_to_cart \
|
|
|
99
99
|
|
|
100
100
|
Use the model id from your provider's documentation. You can also set `OPENAI_COMPATIBLE_BASE_URL` and `OPENAI_COMPATIBLE_MODEL` instead of the flags, or `"provider"`, `"base_url"` and `"model"` in the config. When the API can't return strict JSON schemas, the explanations fall back to plain JSON mode.
|
|
101
101
|
|
|
102
|
+
### Vercel AI Gateway
|
|
103
|
+
|
|
104
|
+
One key for many models. Set `AI_GATEWAY_API_KEY` from your Vercel dashboard and pick a model as `<provider>/<model>`:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
export AI_GATEWAY_API_KEY=...
|
|
108
|
+
aitd run --site https://yourshop.com --journey purchase --model google/gemini-3.6-flash
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Without `--model` it uses `anthropic/claude-opus-5.5`. Usage and costs show in Vercel's AI Gateway dashboard and are billed by Vercel. To see which AI a run will use, run `aitd`: the last line says, for example, `AI: vercel (anthropic/claude-opus-5.5) · also set: anthropic (pick with --provider)`.
|
|
112
|
+
|
|
102
113
|
### Cloudflare AI Gateway
|
|
103
114
|
|
|
104
115
|
To see logs, costs and rate limits for every AI call, send them through [Cloudflare AI Gateway](https://developers.cloudflare.com/ai-gateway/). Your provider key stays the same:
|
package/README.tr.md
CHANGED
|
@@ -99,6 +99,17 @@ ai-tag-debugger run --site https://yourshop.com --journey add_to_cart \
|
|
|
99
99
|
|
|
100
100
|
Model kimliğini sağlayıcınızın dokümantasyonundan alın. Bayrakların yerine `OPENAI_COMPATIBLE_BASE_URL` ve `OPENAI_COMPATIBLE_MODEL` değişkenlerini ya da ayar dosyasında `"provider"`, `"base_url"` ve `"model"` alanlarını da kullanabilirsiniz. API katı JSON şemalarını döndüremezse açıklamalar düz JSON moduna geçer.
|
|
101
101
|
|
|
102
|
+
### Vercel AI Gateway
|
|
103
|
+
|
|
104
|
+
Tek anahtarla birçok model. Vercel panelinizden `AI_GATEWAY_API_KEY` alın ve modeli `<sağlayıcı>/<model>` olarak seçin:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
export AI_GATEWAY_API_KEY=...
|
|
108
|
+
aitd run --site https://yourshop.com --journey purchase --model google/gemini-3.6-flash
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`--model` verilmezse `anthropic/claude-opus-5.5` kullanılır. Kullanım ve maliyet Vercel'in AI Gateway panelinde görünür ve Vercel tarafından faturalandırılır. Hangi yapay zekânın kullanılacağını görmek için `aitd` çalıştırın: son satır örneğin `AI: vercel (anthropic/claude-opus-5.5) · also set: anthropic (pick with --provider)` der.
|
|
112
|
+
|
|
102
113
|
### Cloudflare AI Gateway
|
|
103
114
|
|
|
104
115
|
Her AI çağrısının kayıtlarını, maliyetini ve limitlerini görmek için çağrıları [Cloudflare AI Gateway](https://developers.cloudflare.com/ai-gateway/) üzerinden gönderin. Sağlayıcı anahtarınız aynı kalır:
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@analitikisler/ai-tag-debugger",
|
|
3
3
|
"mcpName": "io.github.analitikisler/ai-tag-debugger",
|
|
4
|
-
"version": "0.11.
|
|
4
|
+
"version": "0.11.3",
|
|
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": {
|
package/src/ask.js
CHANGED
|
@@ -7,19 +7,21 @@ const PLATFORM_KEYS = Object.keys(PLATFORMS);
|
|
|
7
7
|
// The same values as plan --topic (TOPICS in discover.js, kept in step by a test).
|
|
8
8
|
export const TOPIC_KEYS = ["custom-events", "ecommerce", "forms", "consent"];
|
|
9
9
|
|
|
10
|
+
// Optional values use anyOf with a null branch, not type: ["string", "null"]: the Anthropic API
|
|
11
|
+
// rejects an enum next to a type list, and anyOf works for OpenAI and Gemini too.
|
|
10
12
|
export const INTENT_SCHEMA = {
|
|
11
13
|
type: "object",
|
|
12
14
|
additionalProperties: false,
|
|
13
15
|
required: ["action", "site", "against", "platforms", "goal", "journey", "topic", "question"],
|
|
14
16
|
properties: {
|
|
15
17
|
action: { type: "string", enum: ["plan", "run", "report", "unclear"], description: "plan drafts what a site should track; run audits what the site sends; report re-reads an existing report; unclear when you cannot tell plan from run." },
|
|
16
|
-
site: { type:
|
|
17
|
-
against: { type:
|
|
18
|
+
site: { anyOf: [{ type: "string" }, { type: "null" }], description: "The site's domain or URL if the sentence names one." },
|
|
19
|
+
against: { anyOf: [{ type: "string", enum: ["plan", "latest"] }, { type: "null" }], description: "run only: compare with the saved plan, or with the latest run (\"what changed\", \"since the last run\", \"after the deploy\")." },
|
|
18
20
|
platforms: { type: "array", items: { type: "string", enum: PLATFORM_KEYS }, description: "Only the tracking platforms the sentence names; empty for all." },
|
|
19
|
-
goal: { type:
|
|
20
|
-
journey: { type:
|
|
21
|
-
topic: { type:
|
|
22
|
-
question: { type:
|
|
21
|
+
goal: { anyOf: [{ type: "string" }, { type: "null" }], description: "run only: a short user journey to try, in plain words, e.g. \"submit the contact form\"." },
|
|
22
|
+
journey: { anyOf: [{ type: "string", enum: ["purchase"] }, { type: "null" }], description: "run only: purchase when the sentence is about the purchase event or revenue." },
|
|
23
|
+
topic: { anyOf: [{ type: "string", enum: TOPIC_KEYS }, { type: "null" }], description: "plan only: limit the plan to one kind of event when the sentence asks about it." },
|
|
24
|
+
question: { anyOf: [{ type: "string" }, { type: "null" }], description: "One short question to ask back when the sentence is ambiguous; otherwise null." },
|
|
23
25
|
},
|
|
24
26
|
};
|
|
25
27
|
|
package/src/audit.js
CHANGED
|
@@ -9,7 +9,7 @@ import { VIEWPORTS } from "./browser.js";
|
|
|
9
9
|
import { checkRun, groupFindings, planCheck, summarize } from "./checks.js";
|
|
10
10
|
import { explainFindings, renderCsv, renderHtml, renderMarkdown } from "./report.js";
|
|
11
11
|
import { LANGUAGES, messages } from "./i18n.js";
|
|
12
|
-
import { createProvider } from "./providers.js";
|
|
12
|
+
import { createProvider, alsoSet } from "./providers.js";
|
|
13
13
|
import { filterFindings, JOURNEY_PRESETS, planRun } from "./journeys.js";
|
|
14
14
|
import { resolveProviders } from "./parsers.js";
|
|
15
15
|
import { SAVED_SITE_FILE, writeSavedSite } from "./replay.js";
|
|
@@ -102,7 +102,7 @@ export async function runAudit(opts) {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
const provider = useAi ? await createProvider({ name: opts.provider ?? config.provider, model: opts.model ?? config.model, baseUrl: opts.baseUrl ?? config.base_url }) : null;
|
|
105
|
-
if (provider) log(`Using ${provider.name} (${provider.model})`);
|
|
105
|
+
if (provider) log(`Using ${provider.name} (${provider.model})${provider.via ? ` via ${provider.via}` : ""}${alsoSet(provider.key)}`);
|
|
106
106
|
|
|
107
107
|
if (opts.saveSite) await mkdir(opts.saveSite, { recursive: true });
|
|
108
108
|
const runs = [];
|
package/src/help.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LANGUAGES } from "./i18n.js";
|
|
2
|
-
import { PROVIDERS } from "./providers.js";
|
|
2
|
+
import { PROVIDERS, aiStatus } from "./providers.js";
|
|
3
3
|
import { JOURNEY_PRESETS } from "./journeys.js";
|
|
4
4
|
|
|
5
5
|
const journeys = Object.keys(JOURNEY_PRESETS).join(", ");
|
|
@@ -103,5 +103,6 @@ const HELP = { run: RUN, plan: PLAN, replay: REPLAY, mcp: MCP };
|
|
|
103
103
|
|
|
104
104
|
/** Help text for a command, or the top-level help when the command is unknown or missing. */
|
|
105
105
|
export function helpText(command) {
|
|
106
|
-
|
|
106
|
+
// The main help ends with which AI a run would use, so a mix of exported keys is never a surprise.
|
|
107
|
+
return HELP[command] ?? `${MAIN}\n\n${aiStatus()}`;
|
|
107
108
|
}
|
package/src/providers.js
CHANGED
|
@@ -11,6 +11,8 @@ export const PROVIDERS = {
|
|
|
11
11
|
anthropic: { label: "Anthropic (Claude)", env: "ANTHROPIC_API_KEY", defaultModel: "claude-opus-5-5" },
|
|
12
12
|
openai: { label: "OpenAI", env: "OPENAI_API_KEY", defaultModel: "gpt-5.5" },
|
|
13
13
|
gemini: { label: "Google Gemini", env: "GEMINI_API_KEY", defaultModel: "gemini-pro-latest" },
|
|
14
|
+
// Vercel AI Gateway: one key, any model as "<provider>/<model>". OpenAI-compatible API.
|
|
15
|
+
vercel: { label: "Vercel AI Gateway", env: "AI_GATEWAY_API_KEY", defaultModel: "anthropic/claude-opus-5.5" },
|
|
14
16
|
// Any API that speaks the OpenAI chat completions protocol. Needs a base URL and a model.
|
|
15
17
|
"openai-compatible": { label: "OpenAI-compatible API", env: "OPENAI_COMPATIBLE_API_KEY", defaultModel: null },
|
|
16
18
|
};
|
|
@@ -22,16 +24,17 @@ export function resolveProviderName(name, env = process.env) {
|
|
|
22
24
|
return name;
|
|
23
25
|
}
|
|
24
26
|
if (env.OPENAI_COMPATIBLE_BASE_URL) return "openai-compatible";
|
|
27
|
+
if (env.AI_GATEWAY_API_KEY) return "vercel";
|
|
25
28
|
if (env.ANTHROPIC_API_KEY) return "anthropic";
|
|
26
29
|
if (env.OPENAI_API_KEY) return "openai";
|
|
27
30
|
if (env.GEMINI_API_KEY || env.GOOGLE_API_KEY) return "gemini";
|
|
28
31
|
return "anthropic";
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
const NO_API_KEY = `No AI provider key found. Set one of ANTHROPIC_API_KEY, OPENAI_API_KEY or
|
|
34
|
+
const NO_API_KEY = `No AI provider key found. Set one of ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY or AI_GATEWAY_API_KEY for Vercel AI Gateway (or OPENAI_COMPATIBLE_BASE_URL for an OpenAI-compatible API), or pick one with --provider. To run without any AI model, replay saved steps with --no-ai.`;
|
|
32
35
|
|
|
33
36
|
function hasApiKey(env) {
|
|
34
|
-
return Boolean(env.OPENAI_COMPATIBLE_BASE_URL || env.ANTHROPIC_API_KEY || env.ANTHROPIC_AUTH_TOKEN || env.OPENAI_API_KEY || env.GEMINI_API_KEY || env.GOOGLE_API_KEY);
|
|
37
|
+
return Boolean(env.OPENAI_COMPATIBLE_BASE_URL || env.AI_GATEWAY_API_KEY || env.ANTHROPIC_API_KEY || env.ANTHROPIC_AUTH_TOKEN || env.OPENAI_API_KEY || env.GEMINI_API_KEY || env.GOOGLE_API_KEY);
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
/**
|
|
@@ -42,6 +45,12 @@ function hasApiKey(env) {
|
|
|
42
45
|
export async function createProvider(opts = {}, env = process.env) {
|
|
43
46
|
// A bare client (tests, embedding) is an Anthropic client unless named otherwise.
|
|
44
47
|
const name = opts.client && !opts.name ? "anthropic" : opts.baseUrl && !opts.name ? "openai-compatible" : resolveProviderName(opts.name, env);
|
|
48
|
+
const provider = await buildProvider(name, opts, env);
|
|
49
|
+
provider.key = name; // The PROVIDERS key, for status lines.
|
|
50
|
+
return provider;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function buildProvider(name, opts, env) {
|
|
45
54
|
// With no provider named and no key set, the SDK would only fail at the first step with an
|
|
46
55
|
// unclear auth error, after the browser has started. A named provider skips this check, since
|
|
47
56
|
// its SDK may find credentials elsewhere (an Anthropic profile, Google Cloud credentials).
|
|
@@ -56,14 +65,53 @@ export async function createProvider(opts = {}, env = process.env) {
|
|
|
56
65
|
const client = opts.client ?? new (await import("openai")).default({ baseURL, apiKey });
|
|
57
66
|
return openaiProvider(client, model, { compatible: true, label: new URL(baseURL).host });
|
|
58
67
|
}
|
|
68
|
+
if (name === "vercel") {
|
|
69
|
+
const client = opts.client ?? new (await import("openai")).default({ baseURL: VERCEL_GATEWAY, apiKey: env.AI_GATEWAY_API_KEY });
|
|
70
|
+
return { ...openaiProvider(client, opts.model ?? PROVIDERS.vercel.defaultModel, { compatible: true }), name: "vercel" };
|
|
71
|
+
}
|
|
59
72
|
const model = opts.model ?? PROVIDERS[name].defaultModel;
|
|
60
73
|
const gateway = gatewayOptions(name, env);
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
const provider = await builtInProvider(name, model, gateway, opts.client, env);
|
|
75
|
+
// Shown in the "Using ..." line so a run says whether its calls went through the gateway.
|
|
76
|
+
if (gateway.baseURL && !opts.client) provider.via = "Cloudflare AI Gateway";
|
|
77
|
+
return provider;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function builtInProvider(name, model, gateway, client, env) {
|
|
81
|
+
if (name === "anthropic") return anthropicProvider(client ?? new (await import("@anthropic-ai/sdk")).default(gateway), model);
|
|
82
|
+
if (name === "openai") return openaiProvider(client ?? new (await import("openai")).default(gateway), model);
|
|
63
83
|
const { GoogleGenAI } = await import("@google/genai");
|
|
64
84
|
const apiKey = env.GEMINI_API_KEY ?? env.GOOGLE_API_KEY;
|
|
65
85
|
const httpOptions = gateway.baseURL ? { httpOptions: { baseUrl: gateway.baseURL, headers: gateway.defaultHeaders } } : {};
|
|
66
|
-
return geminiProvider(
|
|
86
|
+
return geminiProvider(client ?? new GoogleGenAI({ ...(apiKey ? { apiKey } : {}), ...httpOptions }), model);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const VERCEL_GATEWAY = "https://ai-gateway.vercel.sh/v1";
|
|
90
|
+
|
|
91
|
+
const KEY_PROVIDERS = [
|
|
92
|
+
["vercel", (env) => env.AI_GATEWAY_API_KEY],
|
|
93
|
+
["anthropic", (env) => env.ANTHROPIC_API_KEY || env.ANTHROPIC_AUTH_TOKEN],
|
|
94
|
+
["openai", (env) => env.OPENAI_API_KEY],
|
|
95
|
+
["gemini", (env) => env.GEMINI_API_KEY || env.GOOGLE_API_KEY],
|
|
96
|
+
["openai-compatible", (env) => env.OPENAI_COMPATIBLE_BASE_URL],
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* One line saying which AI aitd would use and why, e.g.
|
|
101
|
+
* "AI: anthropic (claude-opus-5-5) via Cloudflare AI Gateway · also set: gemini (pick with --provider)".
|
|
102
|
+
*/
|
|
103
|
+
export function aiStatus(opts = {}, env = process.env) {
|
|
104
|
+
if (!opts.name && !KEY_PROVIDERS.some(([, has]) => has(env))) return "AI: no key set (ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY or AI_GATEWAY_API_KEY)";
|
|
105
|
+
const name = resolveProviderName(opts.name, env);
|
|
106
|
+
const model = opts.model ?? (name === "openai-compatible" ? env.OPENAI_COMPATIBLE_MODEL : PROVIDERS[name].defaultModel) ?? "no model set";
|
|
107
|
+
const via = name !== "vercel" && gatewayOptions(name, env).baseURL ? " via Cloudflare AI Gateway" : "";
|
|
108
|
+
return `AI: ${name} (${model})${via}${alsoSet(name, env)}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** " · also set: gemini (pick with --provider)" when keys for other providers are set too. */
|
|
112
|
+
export function alsoSet(name, env = process.env) {
|
|
113
|
+
const others = KEY_PROVIDERS.filter(([n, has]) => n !== name && has(env)).map(([n]) => n);
|
|
114
|
+
return others.length ? ` · also set: ${others.join(", ")} (pick with --provider)` : "";
|
|
67
115
|
}
|
|
68
116
|
|
|
69
117
|
// Cloudflare AI Gateway path for each provider, appended to AI_GATEWAY_URL.
|