@ainyc/canonry 1.36.0 → 1.37.1
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/assets/assets/{index-CborW-lk.css → index-CM92zXYn.css} +1 -1
- package/assets/assets/index-Du9ZvTq9.js +281 -0
- package/assets/index.html +2 -2
- package/dist/{chunk-PZKK53EX.js → chunk-U2ZNKIWK.js} +176 -102
- package/dist/cli.js +20 -8
- package/dist/index.js +1 -1
- package/package.json +7 -7
- package/assets/assets/index-Du_w835k.js +0 -281
package/assets/index.html
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
|
|
13
13
|
<link rel="apple-touch-icon" href="./apple-touch-icon.png" />
|
|
14
14
|
<title>Canonry</title>
|
|
15
|
-
<script type="module" crossorigin src="./assets/index-
|
|
16
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-Du9ZvTq9.js"></script>
|
|
16
|
+
<link rel="stylesheet" crossorigin href="./assets/index-CM92zXYn.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
19
19
|
<div id="root"></div>
|
|
@@ -1055,11 +1055,13 @@ var ga4TrafficSnapshotDtoSchema = z11.object({
|
|
|
1055
1055
|
organicSessions: z11.number(),
|
|
1056
1056
|
users: z11.number()
|
|
1057
1057
|
});
|
|
1058
|
+
var ga4SourceDimensionSchema = z11.enum(["session", "first_user", "manual_utm"]);
|
|
1058
1059
|
var ga4AiReferralDtoSchema = z11.object({
|
|
1059
1060
|
source: z11.string(),
|
|
1060
1061
|
medium: z11.string(),
|
|
1061
1062
|
sessions: z11.number(),
|
|
1062
|
-
users: z11.number()
|
|
1063
|
+
users: z11.number(),
|
|
1064
|
+
sourceDimension: ga4SourceDimensionSchema
|
|
1063
1065
|
});
|
|
1064
1066
|
var ga4TrafficSummaryDtoSchema = z11.object({
|
|
1065
1067
|
totalSessions: z11.number(),
|
|
@@ -1072,6 +1074,10 @@ var ga4TrafficSummaryDtoSchema = z11.object({
|
|
|
1072
1074
|
users: z11.number()
|
|
1073
1075
|
})),
|
|
1074
1076
|
aiReferrals: z11.array(ga4AiReferralDtoSchema),
|
|
1077
|
+
/** Deduped AI session total: MAX(sessions) per date+source+medium across attribution dimensions, then summed. */
|
|
1078
|
+
aiSessionsDeduped: z11.number(),
|
|
1079
|
+
/** Deduped AI user total: MAX(users) per date+source+medium across attribution dimensions, then summed. */
|
|
1080
|
+
aiUsersDeduped: z11.number(),
|
|
1075
1081
|
lastSyncedAt: z11.string().nullable()
|
|
1076
1082
|
});
|
|
1077
1083
|
var ga4AiReferralHistoryEntrySchema = z11.object({
|
|
@@ -1079,7 +1085,9 @@ var ga4AiReferralHistoryEntrySchema = z11.object({
|
|
|
1079
1085
|
source: z11.string(),
|
|
1080
1086
|
medium: z11.string(),
|
|
1081
1087
|
sessions: z11.number(),
|
|
1082
|
-
users: z11.number()
|
|
1088
|
+
users: z11.number(),
|
|
1089
|
+
/** Which GA4 dimension this row came from: session (sessionSource), first_user (firstUserSource), or manual_utm (utm_source parameter) */
|
|
1090
|
+
sourceDimension: ga4SourceDimensionSchema
|
|
1083
1091
|
});
|
|
1084
1092
|
var ga4SessionHistoryEntrySchema = z11.object({
|
|
1085
1093
|
date: z11.string(),
|
|
@@ -1487,13 +1495,15 @@ var gaAiReferrals = sqliteTable("ga_ai_referrals", {
|
|
|
1487
1495
|
date: text("date").notNull(),
|
|
1488
1496
|
source: text("source").notNull(),
|
|
1489
1497
|
medium: text("medium").notNull(),
|
|
1498
|
+
/** Which GA4 dimension produced this row: 'session' | 'first_user' | 'manual_utm' */
|
|
1499
|
+
sourceDimension: text("source_dimension").notNull().default("session"),
|
|
1490
1500
|
sessions: integer("sessions").notNull().default(0),
|
|
1491
1501
|
users: integer("users").notNull().default(0),
|
|
1492
1502
|
syncedAt: text("synced_at").notNull()
|
|
1493
1503
|
}, (table) => [
|
|
1494
1504
|
index("idx_ga_ai_ref_project_date").on(table.projectId, table.date),
|
|
1495
1505
|
index("idx_ga_ai_ref_source").on(table.source),
|
|
1496
|
-
uniqueIndex("
|
|
1506
|
+
uniqueIndex("idx_ga_ai_ref_unique_v2").on(table.projectId, table.date, table.source, table.medium, table.sourceDimension)
|
|
1497
1507
|
]);
|
|
1498
1508
|
var gaTrafficSummaries = sqliteTable("ga_traffic_summaries", {
|
|
1499
1509
|
id: text("id").primaryKey(),
|
|
@@ -1860,7 +1870,13 @@ var MIGRATIONS = [
|
|
|
1860
1870
|
`CREATE UNIQUE INDEX IF NOT EXISTS idx_schedules_project ON schedules(project_id)`,
|
|
1861
1871
|
`CREATE UNIQUE INDEX IF NOT EXISTS idx_usage_scope_period_metric ON usage_counters(scope, period, metric)`,
|
|
1862
1872
|
`ALTER TABLE projects ADD COLUMN config_source TEXT NOT NULL DEFAULT 'cli'`,
|
|
1863
|
-
`ALTER TABLE projects ADD COLUMN config_revision INTEGER NOT NULL DEFAULT 1
|
|
1873
|
+
`ALTER TABLE projects ADD COLUMN config_revision INTEGER NOT NULL DEFAULT 1`,
|
|
1874
|
+
// v20: Track which GA4 dimension produced each AI referral row
|
|
1875
|
+
// Values: 'session' (sessionSource), 'first_user' (firstUserSource), 'manual_utm' (manualSource/utm_source)
|
|
1876
|
+
`ALTER TABLE ga_ai_referrals ADD COLUMN source_dimension TEXT NOT NULL DEFAULT 'session'`,
|
|
1877
|
+
// Replace old unique index with one that includes source_dimension
|
|
1878
|
+
`DROP INDEX IF EXISTS idx_ga_ai_ref_unique`,
|
|
1879
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_ga_ai_ref_unique_v2 ON ga_ai_referrals(project_id, date, source, medium, source_dimension)`
|
|
1864
1880
|
];
|
|
1865
1881
|
function migrate(db) {
|
|
1866
1882
|
const statements = MIGRATION_SQL.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
@@ -6634,7 +6650,10 @@ var AI_REFERRAL_SOURCE_FILTERS = [
|
|
|
6634
6650
|
{ matchType: "CONTAINS", value: "openai" },
|
|
6635
6651
|
{ matchType: "CONTAINS", value: "claude" },
|
|
6636
6652
|
{ matchType: "CONTAINS", value: "anthropic" },
|
|
6637
|
-
{ matchType: "CONTAINS", value: "copilot" }
|
|
6653
|
+
{ matchType: "CONTAINS", value: "copilot" },
|
|
6654
|
+
{ matchType: "CONTAINS", value: "phind" },
|
|
6655
|
+
{ matchType: "EXACT", value: "you.com" },
|
|
6656
|
+
{ matchType: "CONTAINS", value: "meta.ai" }
|
|
6638
6657
|
];
|
|
6639
6658
|
async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
|
|
6640
6659
|
const syncDays = Math.min(Math.max(1, days ?? GA4_DEFAULT_SYNC_DAYS), GA4_MAX_SYNC_DAYS);
|
|
@@ -6773,52 +6792,75 @@ async function fetchAiReferrals(accessToken, propertyId, days) {
|
|
|
6773
6792
|
ga4Log("info", "fetch-ai-referrals.start", { propertyId, days: syncDays });
|
|
6774
6793
|
const PAGE_SIZE = 1e3;
|
|
6775
6794
|
const rows = [];
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6795
|
+
const dimensionPairs = [
|
|
6796
|
+
["sessionSource", "sessionMedium", "session"],
|
|
6797
|
+
["firstUserSource", "firstUserMedium", "first_user"],
|
|
6798
|
+
["manualSource", "manualMedium", "manual_utm"]
|
|
6799
|
+
];
|
|
6800
|
+
for (const [sourceDim, mediumDim, dimLabel] of dimensionPairs) {
|
|
6801
|
+
let offset = 0;
|
|
6802
|
+
while (true) {
|
|
6803
|
+
const request = {
|
|
6804
|
+
dateRanges: [{ startDate: formatDate(startDate), endDate: formatDate(endDate) }],
|
|
6805
|
+
dimensions: [
|
|
6806
|
+
{ name: "date" },
|
|
6807
|
+
{ name: sourceDim },
|
|
6808
|
+
{ name: mediumDim }
|
|
6809
|
+
],
|
|
6810
|
+
metrics: [
|
|
6811
|
+
{ name: "sessions" },
|
|
6812
|
+
{ name: "totalUsers" }
|
|
6813
|
+
],
|
|
6814
|
+
dimensionFilter: {
|
|
6815
|
+
orGroup: {
|
|
6816
|
+
expressions: AI_REFERRAL_SOURCE_FILTERS.map(({ matchType, value }) => ({
|
|
6817
|
+
filter: {
|
|
6818
|
+
fieldName: sourceDim,
|
|
6819
|
+
stringFilter: { matchType, value }
|
|
6820
|
+
}
|
|
6821
|
+
}))
|
|
6822
|
+
}
|
|
6823
|
+
},
|
|
6824
|
+
limit: PAGE_SIZE,
|
|
6825
|
+
offset
|
|
6826
|
+
};
|
|
6827
|
+
const response = await runReport(accessToken, propertyId, request);
|
|
6828
|
+
const pageRows = (response.rows ?? []).map((row) => ({
|
|
6829
|
+
date: row.dimensionValues[0].value,
|
|
6830
|
+
source: row.dimensionValues[1].value,
|
|
6831
|
+
medium: row.dimensionValues[2].value,
|
|
6832
|
+
sessions: parseInt(row.metricValues[0].value, 10) || 0,
|
|
6833
|
+
users: parseInt(row.metricValues[1].value, 10) || 0,
|
|
6834
|
+
sourceDimension: dimLabel
|
|
6835
|
+
}));
|
|
6836
|
+
rows.push(...pageRows);
|
|
6837
|
+
const totalRows = response.rowCount ?? 0;
|
|
6838
|
+
offset += pageRows.length;
|
|
6839
|
+
if (pageRows.length < PAGE_SIZE || offset >= totalRows) break;
|
|
6840
|
+
}
|
|
6814
6841
|
}
|
|
6842
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
6815
6843
|
for (const row of rows) {
|
|
6844
|
+
const key = `${row.date}::${row.source}::${row.medium}::${row.sourceDimension}`;
|
|
6845
|
+
const existing = deduped.get(key);
|
|
6846
|
+
if (!existing) {
|
|
6847
|
+
deduped.set(key, row);
|
|
6848
|
+
} else {
|
|
6849
|
+
deduped.set(key, {
|
|
6850
|
+
...existing,
|
|
6851
|
+
sessions: Math.max(existing.sessions, row.sessions),
|
|
6852
|
+
users: Math.max(existing.users, row.users)
|
|
6853
|
+
});
|
|
6854
|
+
}
|
|
6855
|
+
}
|
|
6856
|
+
const dedupedRows = [...deduped.values()];
|
|
6857
|
+
for (const row of dedupedRows) {
|
|
6816
6858
|
if (row.date.length === 8 && !row.date.includes("-")) {
|
|
6817
6859
|
row.date = `${row.date.slice(0, 4)}-${row.date.slice(4, 6)}-${row.date.slice(6, 8)}`;
|
|
6818
6860
|
}
|
|
6819
6861
|
}
|
|
6820
|
-
ga4Log("info", "fetch-ai-referrals.done", { propertyId, rowCount:
|
|
6821
|
-
return
|
|
6862
|
+
ga4Log("info", "fetch-ai-referrals.done", { propertyId, rowCount: dedupedRows.length });
|
|
6863
|
+
return dedupedRows;
|
|
6822
6864
|
}
|
|
6823
6865
|
|
|
6824
6866
|
// ../api-routes/src/google.ts
|
|
@@ -8444,6 +8486,7 @@ async function ga4Routes(app, opts) {
|
|
|
8444
8486
|
date: row.date,
|
|
8445
8487
|
source: row.source,
|
|
8446
8488
|
medium: row.medium,
|
|
8489
|
+
sourceDimension: row.sourceDimension,
|
|
8447
8490
|
sessions: row.sessions,
|
|
8448
8491
|
users: row.users,
|
|
8449
8492
|
syncedAt: now
|
|
@@ -8495,9 +8538,23 @@ async function ga4Routes(app, opts) {
|
|
|
8495
8538
|
const aiReferrals = app.db.select({
|
|
8496
8539
|
source: gaAiReferrals.source,
|
|
8497
8540
|
medium: gaAiReferrals.medium,
|
|
8541
|
+
sourceDimension: gaAiReferrals.sourceDimension,
|
|
8498
8542
|
sessions: sql4`SUM(${gaAiReferrals.sessions})`,
|
|
8499
8543
|
users: sql4`SUM(${gaAiReferrals.users})`
|
|
8500
|
-
}).from(gaAiReferrals).where(eq16(gaAiReferrals.projectId, project.id)).groupBy(gaAiReferrals.source, gaAiReferrals.medium).orderBy(sql4`SUM(${gaAiReferrals.sessions}) DESC`).all();
|
|
8544
|
+
}).from(gaAiReferrals).where(eq16(gaAiReferrals.projectId, project.id)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).orderBy(sql4`SUM(${gaAiReferrals.sessions}) DESC`).all();
|
|
8545
|
+
const aiDeduped = app.db.select({
|
|
8546
|
+
sessions: sql4`SUM(max_sessions)`,
|
|
8547
|
+
users: sql4`SUM(max_users)`
|
|
8548
|
+
}).from(
|
|
8549
|
+
sql4`(
|
|
8550
|
+
SELECT date, source, medium,
|
|
8551
|
+
MAX(sessions) AS max_sessions,
|
|
8552
|
+
MAX(users) AS max_users
|
|
8553
|
+
FROM ga_ai_referrals
|
|
8554
|
+
WHERE project_id = ${project.id}
|
|
8555
|
+
GROUP BY date, source, medium
|
|
8556
|
+
)`
|
|
8557
|
+
).get();
|
|
8501
8558
|
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq16(gaTrafficSummaries.projectId, project.id)).orderBy(desc6(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
8502
8559
|
return {
|
|
8503
8560
|
totalSessions: summary?.totalSessions ?? 0,
|
|
@@ -8512,9 +8569,12 @@ async function ga4Routes(app, opts) {
|
|
|
8512
8569
|
aiReferrals: aiReferrals.map((r) => ({
|
|
8513
8570
|
source: r.source,
|
|
8514
8571
|
medium: r.medium,
|
|
8572
|
+
sourceDimension: r.sourceDimension,
|
|
8515
8573
|
sessions: r.sessions ?? 0,
|
|
8516
8574
|
users: r.users ?? 0
|
|
8517
8575
|
})),
|
|
8576
|
+
aiSessionsDeduped: aiDeduped?.sessions ?? 0,
|
|
8577
|
+
aiUsersDeduped: aiDeduped?.users ?? 0,
|
|
8518
8578
|
lastSyncedAt: latestSync?.syncedAt ?? null
|
|
8519
8579
|
};
|
|
8520
8580
|
});
|
|
@@ -8525,6 +8585,7 @@ async function ga4Routes(app, opts) {
|
|
|
8525
8585
|
date: gaAiReferrals.date,
|
|
8526
8586
|
source: gaAiReferrals.source,
|
|
8527
8587
|
medium: gaAiReferrals.medium,
|
|
8588
|
+
sourceDimension: gaAiReferrals.sourceDimension,
|
|
8528
8589
|
sessions: gaAiReferrals.sessions,
|
|
8529
8590
|
users: gaAiReferrals.users
|
|
8530
8591
|
}).from(gaAiReferrals).where(eq16(gaAiReferrals.projectId, project.id)).orderBy(gaAiReferrals.date).all();
|
|
@@ -10326,19 +10387,11 @@ async function apiRoutes(app, opts) {
|
|
|
10326
10387
|
// ../provider-gemini/src/normalize.ts
|
|
10327
10388
|
import { GoogleGenAI } from "@google/genai";
|
|
10328
10389
|
var DEFAULT_MODEL = "gemini-3-flash";
|
|
10329
|
-
var VALIDATION_PATTERN = /^gemini-/;
|
|
10330
10390
|
function isVertexConfig(config) {
|
|
10331
10391
|
return !!config.vertexProject;
|
|
10332
10392
|
}
|
|
10333
10393
|
function resolveModel(config) {
|
|
10334
|
-
|
|
10335
|
-
if (!m) return DEFAULT_MODEL;
|
|
10336
|
-
if (VALIDATION_PATTERN.test(m)) return m;
|
|
10337
|
-
const backend = isVertexConfig(config) ? "Vertex AI" : "AI Studio";
|
|
10338
|
-
console.warn(
|
|
10339
|
-
`[provider-gemini] Invalid model name "${m}" \u2014 this provider uses the Gemini ${backend} API which only accepts "gemini-*" model names. Falling back to ${DEFAULT_MODEL}.`
|
|
10340
|
-
);
|
|
10341
|
-
return DEFAULT_MODEL;
|
|
10394
|
+
return config.model || DEFAULT_MODEL;
|
|
10342
10395
|
}
|
|
10343
10396
|
function createClient2(config) {
|
|
10344
10397
|
if (isVertexConfig(config)) {
|
|
@@ -10352,6 +10405,9 @@ function createClient2(config) {
|
|
|
10352
10405
|
return new GoogleGenAI({ apiKey: config.apiKey });
|
|
10353
10406
|
}
|
|
10354
10407
|
function validateConfig(config) {
|
|
10408
|
+
if ("vertexProject" in config && config.vertexProject !== void 0 && config.vertexProject.trim().length === 0) {
|
|
10409
|
+
return { ok: false, provider: "gemini", message: "missing Vertex AI project ID" };
|
|
10410
|
+
}
|
|
10355
10411
|
if (isVertexConfig(config)) {
|
|
10356
10412
|
const model2 = resolveModel(config);
|
|
10357
10413
|
return {
|
|
@@ -10365,11 +10421,10 @@ function validateConfig(config) {
|
|
|
10365
10421
|
return { ok: false, provider: "gemini", message: "missing api key" };
|
|
10366
10422
|
}
|
|
10367
10423
|
const model = resolveModel(config);
|
|
10368
|
-
const warning = config.model && !VALIDATION_PATTERN.test(config.model) ? ` (invalid model "${config.model}" replaced with default)` : "";
|
|
10369
10424
|
return {
|
|
10370
10425
|
ok: true,
|
|
10371
10426
|
provider: "gemini",
|
|
10372
|
-
message:
|
|
10427
|
+
message: "config valid",
|
|
10373
10428
|
model
|
|
10374
10429
|
};
|
|
10375
10430
|
}
|
|
@@ -10562,12 +10617,14 @@ var geminiAdapter = {
|
|
|
10562
10617
|
displayName: "Gemini",
|
|
10563
10618
|
mode: "api",
|
|
10564
10619
|
keyUrl: "https://aistudio.google.com/apikey",
|
|
10620
|
+
// Upstream model list: https://ai.google.dev/gemini-api/docs/models
|
|
10565
10621
|
modelRegistry: {
|
|
10566
10622
|
defaultModel: "gemini-3-flash",
|
|
10567
|
-
validationPattern:
|
|
10568
|
-
validationHint:
|
|
10623
|
+
validationPattern: /./,
|
|
10624
|
+
validationHint: "any valid Google model name (e.g. gemini-3-flash, learnlm-1.5-pro-experimental)",
|
|
10569
10625
|
knownModels: [
|
|
10570
10626
|
{ id: "gemini-3.1-pro-preview", displayName: "Gemini 3.1 Pro (Preview)", tier: "flagship" },
|
|
10627
|
+
{ id: "gemini-3-flash", displayName: "Gemini 3 Flash", tier: "standard" },
|
|
10571
10628
|
{ id: "gemini-3-flash-preview", displayName: "Gemini 3 Flash (Preview)", tier: "standard" },
|
|
10572
10629
|
{ id: "gemini-3.1-flash-lite-preview", displayName: "Gemini 3.1 Flash-Lite (Preview)", tier: "economy" },
|
|
10573
10630
|
{ id: "gemini-2.5-flash", displayName: "Gemini 2.5 Flash", tier: "standard" }
|
|
@@ -10730,13 +10787,15 @@ function extractResponseText(response) {
|
|
|
10730
10787
|
}
|
|
10731
10788
|
function extractGroundingSources(response) {
|
|
10732
10789
|
const sources = [];
|
|
10790
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10733
10791
|
try {
|
|
10734
10792
|
for (const item of response.output) {
|
|
10735
10793
|
if (item.type === "message") {
|
|
10736
10794
|
for (const content of item.content) {
|
|
10737
10795
|
if (content.type === "output_text" && content.annotations) {
|
|
10738
10796
|
for (const annotation of content.annotations) {
|
|
10739
|
-
if (annotation.type === "url_citation") {
|
|
10797
|
+
if (annotation.type === "url_citation" && !seen.has(annotation.url)) {
|
|
10798
|
+
seen.add(annotation.url);
|
|
10740
10799
|
sources.push({
|
|
10741
10800
|
uri: annotation.url,
|
|
10742
10801
|
title: annotation.title ?? ""
|
|
@@ -10756,7 +10815,10 @@ function extractSearchQueries2(response) {
|
|
|
10756
10815
|
try {
|
|
10757
10816
|
for (const item of response.output) {
|
|
10758
10817
|
if (item.type === "web_search_call" && "query" in item) {
|
|
10759
|
-
|
|
10818
|
+
const query = item.query;
|
|
10819
|
+
if (typeof query === "string" && query.length > 0) {
|
|
10820
|
+
queries.push(query);
|
|
10821
|
+
}
|
|
10760
10822
|
}
|
|
10761
10823
|
}
|
|
10762
10824
|
} catch {
|
|
@@ -10828,10 +10890,11 @@ var openaiAdapter = {
|
|
|
10828
10890
|
displayName: "OpenAI",
|
|
10829
10891
|
mode: "api",
|
|
10830
10892
|
keyUrl: "https://platform.openai.com/api-keys",
|
|
10893
|
+
// Upstream model list: https://platform.openai.com/docs/models
|
|
10831
10894
|
modelRegistry: {
|
|
10832
10895
|
defaultModel: "gpt-5.4",
|
|
10833
|
-
validationPattern:
|
|
10834
|
-
validationHint: "
|
|
10896
|
+
validationPattern: /./,
|
|
10897
|
+
validationHint: "any valid OpenAI model name (e.g. gpt-5.4, o3, chatgpt-4o-latest)",
|
|
10835
10898
|
knownModels: [
|
|
10836
10899
|
{ id: "gpt-5.4", displayName: "GPT-5.4", tier: "flagship" },
|
|
10837
10900
|
{ id: "gpt-5.4-pro", displayName: "GPT-5.4 Pro", tier: "flagship" },
|
|
@@ -10900,24 +10963,37 @@ var openaiAdapter = {
|
|
|
10900
10963
|
// ../provider-claude/src/normalize.ts
|
|
10901
10964
|
import Anthropic from "@anthropic-ai/sdk";
|
|
10902
10965
|
var DEFAULT_MODEL3 = "claude-sonnet-4-6";
|
|
10966
|
+
var VALIDATION_PATTERN = /^claude-/;
|
|
10967
|
+
function resolveModel2(config) {
|
|
10968
|
+
const m = config.model;
|
|
10969
|
+
if (!m) return DEFAULT_MODEL3;
|
|
10970
|
+
if (VALIDATION_PATTERN.test(m)) return m;
|
|
10971
|
+
console.warn(
|
|
10972
|
+
`[provider-claude] Invalid model name "${m}" \u2014 this provider uses the Anthropic API which only accepts "claude-*" model names. Falling back to ${DEFAULT_MODEL3}.`
|
|
10973
|
+
);
|
|
10974
|
+
return DEFAULT_MODEL3;
|
|
10975
|
+
}
|
|
10903
10976
|
function validateConfig3(config) {
|
|
10904
10977
|
if (!config.apiKey || config.apiKey.length === 0) {
|
|
10905
10978
|
return { ok: false, provider: "claude", message: "missing api key" };
|
|
10906
10979
|
}
|
|
10980
|
+
const model = resolveModel2(config);
|
|
10981
|
+
const warning = config.model && !VALIDATION_PATTERN.test(config.model) ? ` (invalid model "${config.model}" replaced with default)` : "";
|
|
10907
10982
|
return {
|
|
10908
10983
|
ok: true,
|
|
10909
10984
|
provider: "claude",
|
|
10910
|
-
message:
|
|
10911
|
-
model
|
|
10985
|
+
message: `config valid${warning}`,
|
|
10986
|
+
model
|
|
10912
10987
|
};
|
|
10913
10988
|
}
|
|
10914
10989
|
async function healthcheck3(config) {
|
|
10915
10990
|
const validation = validateConfig3(config);
|
|
10916
10991
|
if (!validation.ok) return validation;
|
|
10917
10992
|
try {
|
|
10993
|
+
const model = resolveModel2(config);
|
|
10918
10994
|
const client = new Anthropic({ apiKey: config.apiKey });
|
|
10919
10995
|
const response = await client.messages.create({
|
|
10920
|
-
model
|
|
10996
|
+
model,
|
|
10921
10997
|
max_tokens: 32,
|
|
10922
10998
|
messages: [{ role: "user", content: 'Say "ok"' }]
|
|
10923
10999
|
});
|
|
@@ -10926,19 +11002,19 @@ async function healthcheck3(config) {
|
|
|
10926
11002
|
ok: text2.length > 0,
|
|
10927
11003
|
provider: "claude",
|
|
10928
11004
|
message: text2.length > 0 ? "claude api key verified" : "empty response from claude",
|
|
10929
|
-
model
|
|
11005
|
+
model
|
|
10930
11006
|
};
|
|
10931
11007
|
} catch (err) {
|
|
10932
11008
|
return {
|
|
10933
11009
|
ok: false,
|
|
10934
11010
|
provider: "claude",
|
|
10935
11011
|
message: err instanceof Error ? err.message : String(err),
|
|
10936
|
-
model: config
|
|
11012
|
+
model: resolveModel2(config)
|
|
10937
11013
|
};
|
|
10938
11014
|
}
|
|
10939
11015
|
}
|
|
10940
11016
|
async function executeTrackedQuery3(input) {
|
|
10941
|
-
const model = input.config
|
|
11017
|
+
const model = resolveModel2(input.config);
|
|
10942
11018
|
const client = new Anthropic({ apiKey: input.config.apiKey });
|
|
10943
11019
|
const webSearchTool = {
|
|
10944
11020
|
type: "web_search_20250305",
|
|
@@ -11060,7 +11136,7 @@ function extractDomainFromUri3(uri) {
|
|
|
11060
11136
|
}
|
|
11061
11137
|
}
|
|
11062
11138
|
async function generateText3(prompt, config) {
|
|
11063
|
-
const model = config
|
|
11139
|
+
const model = resolveModel2(config);
|
|
11064
11140
|
const client = new Anthropic({ apiKey: config.apiKey });
|
|
11065
11141
|
const response = await client.messages.create({
|
|
11066
11142
|
model,
|
|
@@ -11090,6 +11166,7 @@ var claudeAdapter = {
|
|
|
11090
11166
|
displayName: "Claude",
|
|
11091
11167
|
mode: "api",
|
|
11092
11168
|
keyUrl: "https://platform.claude.com/settings/keys",
|
|
11169
|
+
// Upstream model list: https://platform.claude.com/docs/en/about-claude/models/overview
|
|
11093
11170
|
modelRegistry: {
|
|
11094
11171
|
defaultModel: "claude-sonnet-4-6",
|
|
11095
11172
|
validationPattern: /^claude-/,
|
|
@@ -11799,7 +11876,7 @@ function normalizeResult5(raw) {
|
|
|
11799
11876
|
}
|
|
11800
11877
|
|
|
11801
11878
|
// ../provider-cdp/src/adapter.ts
|
|
11802
|
-
var
|
|
11879
|
+
var connectionPool = /* @__PURE__ */ new Map();
|
|
11803
11880
|
function getConnection(config) {
|
|
11804
11881
|
if (!config.cdpEndpoint) {
|
|
11805
11882
|
throw new CDPProviderError("CDP_CONNECTION_REFUSED", "CDP endpoint not configured");
|
|
@@ -11810,14 +11887,13 @@ function getConnection(config) {
|
|
|
11810
11887
|
const parts = endpoint.split(":");
|
|
11811
11888
|
if (parts.length >= 1 && parts[0]) host = parts[0];
|
|
11812
11889
|
if (parts.length >= 2 && parts[1]) port = parseInt(parts[1], 10) || 9222;
|
|
11813
|
-
|
|
11814
|
-
|
|
11815
|
-
|
|
11816
|
-
|
|
11817
|
-
|
|
11818
|
-
sharedConnection = new CDPConnectionManager(host, port);
|
|
11890
|
+
const key = `${host}:${port}`;
|
|
11891
|
+
let conn = connectionPool.get(key);
|
|
11892
|
+
if (!conn) {
|
|
11893
|
+
conn = new CDPConnectionManager(host, port);
|
|
11894
|
+
connectionPool.set(key, conn);
|
|
11819
11895
|
}
|
|
11820
|
-
return
|
|
11896
|
+
return conn;
|
|
11821
11897
|
}
|
|
11822
11898
|
function getScreenshotDir2() {
|
|
11823
11899
|
return path4.join(os3.homedir(), ".canonry", "screenshots");
|
|
@@ -12063,6 +12139,7 @@ var perplexityAdapter = {
|
|
|
12063
12139
|
displayName: "Perplexity",
|
|
12064
12140
|
mode: "api",
|
|
12065
12141
|
keyUrl: "https://www.perplexity.ai/settings/api",
|
|
12142
|
+
// Upstream model list: https://docs.perplexity.ai/guides/model-cards
|
|
12066
12143
|
modelRegistry: {
|
|
12067
12144
|
defaultModel: "sonar",
|
|
12068
12145
|
validationPattern: /^sonar/,
|
|
@@ -12291,7 +12368,7 @@ import crypto18 from "crypto";
|
|
|
12291
12368
|
import fs4 from "fs";
|
|
12292
12369
|
import path5 from "path";
|
|
12293
12370
|
import os4 from "os";
|
|
12294
|
-
import { and as and6, eq as eq17, inArray as inArray3 } from "drizzle-orm";
|
|
12371
|
+
import { and as and6, eq as eq17, inArray as inArray3, sql as sql5 } from "drizzle-orm";
|
|
12295
12372
|
|
|
12296
12373
|
// src/logger.ts
|
|
12297
12374
|
var IS_TTY = process.stdout.isTTY === true;
|
|
@@ -12453,12 +12530,12 @@ var JobRunner = class {
|
|
|
12453
12530
|
} else if (locationOverride) {
|
|
12454
12531
|
runLocation = locationOverride;
|
|
12455
12532
|
} else {
|
|
12456
|
-
const projectLocations =
|
|
12533
|
+
const projectLocations = parseJsonColumn(project.locations, []);
|
|
12457
12534
|
if (project.defaultLocation && projectLocations.length > 0) {
|
|
12458
12535
|
runLocation = projectLocations.find((l) => l.label === project.defaultLocation);
|
|
12459
12536
|
}
|
|
12460
12537
|
}
|
|
12461
|
-
const projectProviders = providerOverride ??
|
|
12538
|
+
const projectProviders = providerOverride ?? parseJsonColumn(project.providers, []);
|
|
12462
12539
|
activeProviders = this.registry.getForProject(projectProviders);
|
|
12463
12540
|
if (activeProviders.length === 0) {
|
|
12464
12541
|
throw new Error("No providers configured. Add at least one provider API key.");
|
|
@@ -12469,7 +12546,7 @@ var JobRunner = class {
|
|
|
12469
12546
|
const competitorDomains = projectCompetitors.map((c) => c.domain);
|
|
12470
12547
|
const allDomains = effectiveDomains({
|
|
12471
12548
|
canonicalDomain: project.canonicalDomain,
|
|
12472
|
-
ownedDomains:
|
|
12549
|
+
ownedDomains: parseJsonColumn(project.ownedDomains, [])
|
|
12473
12550
|
});
|
|
12474
12551
|
const executionContext = {
|
|
12475
12552
|
providerCount: activeProviders.length,
|
|
@@ -12680,22 +12757,19 @@ var JobRunner = class {
|
|
|
12680
12757
|
}
|
|
12681
12758
|
}
|
|
12682
12759
|
incrementUsage(scope, metric, count) {
|
|
12683
|
-
const now = /* @__PURE__ */ new Date();
|
|
12684
|
-
const period = now.
|
|
12685
|
-
|
|
12686
|
-
|
|
12687
|
-
|
|
12688
|
-
|
|
12689
|
-
|
|
12690
|
-
|
|
12691
|
-
|
|
12692
|
-
|
|
12693
|
-
|
|
12694
|
-
|
|
12695
|
-
|
|
12696
|
-
updatedAt: now.toISOString()
|
|
12697
|
-
}).run();
|
|
12698
|
-
}
|
|
12760
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12761
|
+
const period = now.slice(0, 10);
|
|
12762
|
+
this.db.insert(usageCounters).values({
|
|
12763
|
+
id: crypto18.randomUUID(),
|
|
12764
|
+
scope,
|
|
12765
|
+
period,
|
|
12766
|
+
metric,
|
|
12767
|
+
count,
|
|
12768
|
+
updatedAt: now
|
|
12769
|
+
}).onConflictDoUpdate({
|
|
12770
|
+
target: [usageCounters.scope, usageCounters.period, usageCounters.metric],
|
|
12771
|
+
set: { count: sql5`${usageCounters.count} + ${count}`, updatedAt: now }
|
|
12772
|
+
}).run();
|
|
12699
12773
|
}
|
|
12700
12774
|
flushProviderUsage(projectId, providerDispatchCounts) {
|
|
12701
12775
|
for (const [providerName, count] of providerDispatchCounts.entries()) {
|
|
@@ -12896,7 +12970,7 @@ function matchesBrandKey(candidateKey, brandKeys) {
|
|
|
12896
12970
|
|
|
12897
12971
|
// src/gsc-sync.ts
|
|
12898
12972
|
import crypto19 from "crypto";
|
|
12899
|
-
import { eq as eq18, and as and7, sql as
|
|
12973
|
+
import { eq as eq18, and as and7, sql as sql6 } from "drizzle-orm";
|
|
12900
12974
|
var log2 = createLogger("GscSync");
|
|
12901
12975
|
function formatDate2(d) {
|
|
12902
12976
|
return d.toISOString().split("T")[0];
|
|
@@ -12950,8 +13024,8 @@ async function executeGscSync(db, runId, projectId, opts) {
|
|
|
12950
13024
|
db.delete(gscSearchData).where(
|
|
12951
13025
|
and7(
|
|
12952
13026
|
eq18(gscSearchData.projectId, projectId),
|
|
12953
|
-
|
|
12954
|
-
|
|
13027
|
+
sql6`${gscSearchData.date} >= ${startDate}`,
|
|
13028
|
+
sql6`${gscSearchData.date} <= ${endDate}`
|
|
12955
13029
|
)
|
|
12956
13030
|
).run();
|
|
12957
13031
|
const batchSize = 500;
|
|
@@ -13410,7 +13484,7 @@ var Scheduler = class {
|
|
|
13410
13484
|
nextRunAt,
|
|
13411
13485
|
updatedAt: now
|
|
13412
13486
|
}).where(eq20(schedules.id, currentSchedule.id)).run();
|
|
13413
|
-
const scheduleProviders =
|
|
13487
|
+
const scheduleProviders = parseJsonColumn(currentSchedule.providers, []);
|
|
13414
13488
|
const providers = scheduleProviders.length > 0 ? scheduleProviders : void 0;
|
|
13415
13489
|
log4.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
|
|
13416
13490
|
this.callbacks.onRunCreated(runId, projectId, providers);
|
package/dist/cli.js
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
setGoogleAuthConfig,
|
|
27
27
|
showFirstRunNotice,
|
|
28
28
|
trackEvent
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-U2ZNKIWK.js";
|
|
30
30
|
|
|
31
31
|
// src/cli.ts
|
|
32
32
|
import { pathToFileURL } from "url";
|
|
@@ -1493,14 +1493,20 @@ async function gaTraffic(project, opts) {
|
|
|
1493
1493
|
console.log(` Total Sessions: ${result.totalSessions}`);
|
|
1494
1494
|
console.log(` Organic Sessions: ${result.totalOrganicSessions}`);
|
|
1495
1495
|
console.log(` Total Users: ${result.totalUsers}`);
|
|
1496
|
+
if (result.aiSessionsDeduped > 0) {
|
|
1497
|
+
const share = result.totalSessions > 0 ? Math.round(result.aiSessionsDeduped / result.totalSessions * 100) : 0;
|
|
1498
|
+
console.log(` AI Sessions (deduped): ${result.aiSessionsDeduped} (${share}% of total)`);
|
|
1499
|
+
}
|
|
1496
1500
|
console.log();
|
|
1497
1501
|
if (result.aiReferrals.length > 0) {
|
|
1502
|
+
const attrWidth = 12;
|
|
1498
1503
|
console.log(" AI REFERRAL SOURCES");
|
|
1499
|
-
console.log(` ${"SOURCE".padEnd(25)} ${"MEDIUM".padEnd(15)} ${"SESSIONS".padEnd(10)}${"USERS".padEnd(8)}`);
|
|
1500
|
-
console.log(` ${"\u2500".repeat(25)} ${"\u2500".repeat(15)} ${"\u2500".repeat(10)}${"\u2500".repeat(8)}`);
|
|
1504
|
+
console.log(` ${"SOURCE".padEnd(25)} ${"MEDIUM".padEnd(15)} ${"ATTRIBUTION".padEnd(attrWidth)} ${"SESSIONS".padEnd(10)}${"USERS".padEnd(8)}`);
|
|
1505
|
+
console.log(` ${"\u2500".repeat(25)} ${"\u2500".repeat(15)} ${"\u2500".repeat(attrWidth)} ${"\u2500".repeat(10)}${"\u2500".repeat(8)}`);
|
|
1501
1506
|
for (const ref of result.aiReferrals) {
|
|
1507
|
+
const dimLabel = ref.sourceDimension === "first_user" ? "first-visit" : ref.sourceDimension === "manual_utm" ? "utm" : "session";
|
|
1502
1508
|
console.log(
|
|
1503
|
-
` ${ref.source.padEnd(25)} ${ref.medium.padEnd(15)} ${String(ref.sessions).padEnd(10)}${String(ref.users).padEnd(8)}`
|
|
1509
|
+
` ${ref.source.padEnd(25)} ${ref.medium.padEnd(15)} ${dimLabel.padEnd(attrWidth)} ${String(ref.sessions).padEnd(10)}${String(ref.users).padEnd(8)}`
|
|
1504
1510
|
);
|
|
1505
1511
|
}
|
|
1506
1512
|
console.log();
|
|
@@ -1535,13 +1541,15 @@ async function gaAiReferralHistory(project, format) {
|
|
|
1535
1541
|
}
|
|
1536
1542
|
const dateWidth = 12;
|
|
1537
1543
|
const sourceWidth = Math.min(30, Math.max(10, ...result.map((r) => r.source.length)));
|
|
1544
|
+
const attrWidth = 12;
|
|
1538
1545
|
console.log(`GA4 AI Referral History for "${project}":
|
|
1539
1546
|
`);
|
|
1540
|
-
console.log(` ${"DATE".padEnd(dateWidth)} ${"SOURCE".padEnd(sourceWidth)} ${"SESSIONS".padEnd(10)}${"USERS".padEnd(8)}`);
|
|
1541
|
-
console.log(` ${"\u2500".repeat(dateWidth)} ${"\u2500".repeat(sourceWidth)} ${"\u2500".repeat(10)}${"\u2500".repeat(8)}`);
|
|
1547
|
+
console.log(` ${"DATE".padEnd(dateWidth)} ${"SOURCE".padEnd(sourceWidth)} ${"ATTRIBUTION".padEnd(attrWidth)} ${"SESSIONS".padEnd(10)}${"USERS".padEnd(8)}`);
|
|
1548
|
+
console.log(` ${"\u2500".repeat(dateWidth)} ${"\u2500".repeat(sourceWidth)} ${"\u2500".repeat(attrWidth)} ${"\u2500".repeat(10)}${"\u2500".repeat(8)}`);
|
|
1542
1549
|
for (const row of result) {
|
|
1550
|
+
const dimLabel = row.sourceDimension === "first_user" ? "first-visit" : row.sourceDimension === "manual_utm" ? "utm" : "session";
|
|
1543
1551
|
console.log(
|
|
1544
|
-
` ${row.date.padEnd(dateWidth)} ${row.source.padEnd(sourceWidth)} ${String(row.sessions).padEnd(10)}${String(row.users).padEnd(8)}`
|
|
1552
|
+
` ${row.date.padEnd(dateWidth)} ${row.source.padEnd(sourceWidth)} ${dimLabel.padEnd(attrWidth)} ${String(row.sessions).padEnd(10)}${String(row.users).padEnd(8)}`
|
|
1545
1553
|
);
|
|
1546
1554
|
}
|
|
1547
1555
|
}
|
|
@@ -1690,7 +1698,11 @@ async function addCompetitors(project, domains, format) {
|
|
|
1690
1698
|
}, null, 2));
|
|
1691
1699
|
return;
|
|
1692
1700
|
}
|
|
1693
|
-
|
|
1701
|
+
if (addedDomains.length === 0) {
|
|
1702
|
+
console.log(`No new competitors added to "${project}" (all already tracked).`);
|
|
1703
|
+
} else {
|
|
1704
|
+
console.log(`Added ${addedDomains.length} competitor(s) to "${project}".`);
|
|
1705
|
+
}
|
|
1694
1706
|
}
|
|
1695
1707
|
async function listCompetitors(project, format) {
|
|
1696
1708
|
const client = getClient4();
|