@ainyc/canonry 1.40.1 → 1.41.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/assets/assets/{index-DU7KOHur.js → index-iy29Cmx8.js} +127 -127
- package/assets/index.html +1 -1
- package/dist/{chunk-FOWWBLXD.js → chunk-EUBC5EGC.js} +4 -1
- package/dist/{chunk-FXHVGU5S.js → chunk-TKMBOLZB.js} +193 -87
- package/dist/cli.js +3 -3
- package/dist/index.js +2 -2
- package/dist/{intelligence-service-PDZOIB7L.js → intelligence-service-DXGTIRF5.js} +1 -1
- package/package.json +7 -7
package/assets/index.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
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-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-iy29Cmx8.js"></script>
|
|
16
16
|
<link rel="stylesheet" crossorigin href="./assets/index-Djm1st6N.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
@@ -110,7 +110,10 @@ var querySnapshots = sqliteTable("query_snapshots", {
|
|
|
110
110
|
createdAt: text("created_at").notNull()
|
|
111
111
|
}, (table) => [
|
|
112
112
|
index("idx_snapshots_run").on(table.runId),
|
|
113
|
-
index("idx_snapshots_keyword").on(table.keywordId)
|
|
113
|
+
index("idx_snapshots_keyword").on(table.keywordId),
|
|
114
|
+
index("idx_snapshots_citation_state").on(table.citationState),
|
|
115
|
+
index("idx_snapshots_provider_model").on(table.provider, table.model),
|
|
116
|
+
index("idx_snapshots_location").on(table.location)
|
|
114
117
|
]);
|
|
115
118
|
var auditLog = sqliteTable("audit_log", {
|
|
116
119
|
id: text("id").primaryKey(),
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
runs,
|
|
22
22
|
schedules,
|
|
23
23
|
usageCounters
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-EUBC5EGC.js";
|
|
25
25
|
|
|
26
26
|
// src/config.ts
|
|
27
27
|
import fs from "fs";
|
|
@@ -978,6 +978,11 @@ var scheduleUpsertRequestSchema = z10.object({
|
|
|
978
978
|
{ message: 'Exactly one of "preset" or "cron" must be provided' }
|
|
979
979
|
);
|
|
980
980
|
|
|
981
|
+
// ../contracts/src/analytics.ts
|
|
982
|
+
import { z as z11 } from "zod";
|
|
983
|
+
var visibilityMetricModeSchema = z11.enum(["answer", "citation"]);
|
|
984
|
+
var VisibilityMetricModes = visibilityMetricModeSchema.enum;
|
|
985
|
+
|
|
981
986
|
// ../contracts/src/source-categories.ts
|
|
982
987
|
var SOURCE_CATEGORY_RULES = [
|
|
983
988
|
// Forums
|
|
@@ -1071,62 +1076,62 @@ function categoryLabel(category) {
|
|
|
1071
1076
|
}
|
|
1072
1077
|
|
|
1073
1078
|
// ../contracts/src/ga.ts
|
|
1074
|
-
import { z as
|
|
1075
|
-
var ga4ConnectionDtoSchema =
|
|
1076
|
-
id:
|
|
1077
|
-
projectId:
|
|
1078
|
-
propertyId:
|
|
1079
|
-
clientEmail:
|
|
1080
|
-
connected:
|
|
1081
|
-
createdAt:
|
|
1082
|
-
updatedAt:
|
|
1079
|
+
import { z as z12 } from "zod";
|
|
1080
|
+
var ga4ConnectionDtoSchema = z12.object({
|
|
1081
|
+
id: z12.string(),
|
|
1082
|
+
projectId: z12.string(),
|
|
1083
|
+
propertyId: z12.string(),
|
|
1084
|
+
clientEmail: z12.string(),
|
|
1085
|
+
connected: z12.boolean(),
|
|
1086
|
+
createdAt: z12.string(),
|
|
1087
|
+
updatedAt: z12.string()
|
|
1083
1088
|
});
|
|
1084
|
-
var ga4TrafficSnapshotDtoSchema =
|
|
1085
|
-
date:
|
|
1086
|
-
landingPage:
|
|
1087
|
-
sessions:
|
|
1088
|
-
organicSessions:
|
|
1089
|
-
users:
|
|
1089
|
+
var ga4TrafficSnapshotDtoSchema = z12.object({
|
|
1090
|
+
date: z12.string(),
|
|
1091
|
+
landingPage: z12.string(),
|
|
1092
|
+
sessions: z12.number(),
|
|
1093
|
+
organicSessions: z12.number(),
|
|
1094
|
+
users: z12.number()
|
|
1090
1095
|
});
|
|
1091
|
-
var ga4SourceDimensionSchema =
|
|
1092
|
-
var ga4AiReferralDtoSchema =
|
|
1093
|
-
source:
|
|
1094
|
-
medium:
|
|
1095
|
-
sessions:
|
|
1096
|
-
users:
|
|
1096
|
+
var ga4SourceDimensionSchema = z12.enum(["session", "first_user", "manual_utm"]);
|
|
1097
|
+
var ga4AiReferralDtoSchema = z12.object({
|
|
1098
|
+
source: z12.string(),
|
|
1099
|
+
medium: z12.string(),
|
|
1100
|
+
sessions: z12.number(),
|
|
1101
|
+
users: z12.number(),
|
|
1097
1102
|
sourceDimension: ga4SourceDimensionSchema
|
|
1098
1103
|
});
|
|
1099
|
-
var ga4TrafficSummaryDtoSchema =
|
|
1100
|
-
totalSessions:
|
|
1101
|
-
totalOrganicSessions:
|
|
1102
|
-
totalUsers:
|
|
1103
|
-
topPages:
|
|
1104
|
-
landingPage:
|
|
1105
|
-
sessions:
|
|
1106
|
-
organicSessions:
|
|
1107
|
-
users:
|
|
1104
|
+
var ga4TrafficSummaryDtoSchema = z12.object({
|
|
1105
|
+
totalSessions: z12.number(),
|
|
1106
|
+
totalOrganicSessions: z12.number(),
|
|
1107
|
+
totalUsers: z12.number(),
|
|
1108
|
+
topPages: z12.array(z12.object({
|
|
1109
|
+
landingPage: z12.string(),
|
|
1110
|
+
sessions: z12.number(),
|
|
1111
|
+
organicSessions: z12.number(),
|
|
1112
|
+
users: z12.number()
|
|
1108
1113
|
})),
|
|
1109
|
-
aiReferrals:
|
|
1114
|
+
aiReferrals: z12.array(ga4AiReferralDtoSchema),
|
|
1110
1115
|
/** Deduped AI session total: MAX(sessions) per date+source+medium across attribution dimensions, then summed. */
|
|
1111
|
-
aiSessionsDeduped:
|
|
1116
|
+
aiSessionsDeduped: z12.number(),
|
|
1112
1117
|
/** Deduped AI user total: MAX(users) per date+source+medium across attribution dimensions, then summed. */
|
|
1113
|
-
aiUsersDeduped:
|
|
1114
|
-
lastSyncedAt:
|
|
1118
|
+
aiUsersDeduped: z12.number(),
|
|
1119
|
+
lastSyncedAt: z12.string().nullable()
|
|
1115
1120
|
});
|
|
1116
|
-
var ga4AiReferralHistoryEntrySchema =
|
|
1117
|
-
date:
|
|
1118
|
-
source:
|
|
1119
|
-
medium:
|
|
1120
|
-
sessions:
|
|
1121
|
-
users:
|
|
1121
|
+
var ga4AiReferralHistoryEntrySchema = z12.object({
|
|
1122
|
+
date: z12.string(),
|
|
1123
|
+
source: z12.string(),
|
|
1124
|
+
medium: z12.string(),
|
|
1125
|
+
sessions: z12.number(),
|
|
1126
|
+
users: z12.number(),
|
|
1122
1127
|
/** Which GA4 dimension this row came from: session (sessionSource), first_user (firstUserSource), or manual_utm (utm_source parameter) */
|
|
1123
1128
|
sourceDimension: ga4SourceDimensionSchema
|
|
1124
1129
|
});
|
|
1125
|
-
var ga4SessionHistoryEntrySchema =
|
|
1126
|
-
date:
|
|
1127
|
-
sessions:
|
|
1128
|
-
organicSessions:
|
|
1129
|
-
users:
|
|
1130
|
+
var ga4SessionHistoryEntrySchema = z12.object({
|
|
1131
|
+
date: z12.string(),
|
|
1132
|
+
sessions: z12.number(),
|
|
1133
|
+
organicSessions: z12.number(),
|
|
1134
|
+
users: z12.number()
|
|
1130
1135
|
});
|
|
1131
1136
|
|
|
1132
1137
|
// ../contracts/src/answer-visibility.ts
|
|
@@ -2877,20 +2882,27 @@ async function analyticsRoutes(app) {
|
|
|
2877
2882
|
return reply.send({
|
|
2878
2883
|
window,
|
|
2879
2884
|
buckets: [],
|
|
2880
|
-
overall: { citationRate: 0, cited: 0, total: 0 },
|
|
2885
|
+
overall: { citationRate: 0, cited: 0, total: 0, answerRate: 0, answerMentionedCount: 0 },
|
|
2881
2886
|
byProvider: {},
|
|
2882
2887
|
trend: "stable",
|
|
2888
|
+
answerTrend: "stable",
|
|
2883
2889
|
keywordChanges: []
|
|
2884
2890
|
});
|
|
2885
2891
|
}
|
|
2886
2892
|
const runIds = projectRuns.map((r) => r.id);
|
|
2887
|
-
const
|
|
2893
|
+
const rawSnapshots = app.db.select({
|
|
2888
2894
|
runId: querySnapshots.runId,
|
|
2889
2895
|
keywordId: querySnapshots.keywordId,
|
|
2890
2896
|
provider: querySnapshots.provider,
|
|
2891
2897
|
citationState: querySnapshots.citationState,
|
|
2898
|
+
answerMentioned: querySnapshots.answerMentioned,
|
|
2899
|
+
answerText: querySnapshots.answerText,
|
|
2892
2900
|
createdAt: querySnapshots.createdAt
|
|
2893
2901
|
}).from(querySnapshots).where(inArray2(querySnapshots.runId, runIds)).all();
|
|
2902
|
+
const allSnapshots = rawSnapshots.map((s) => ({
|
|
2903
|
+
...s,
|
|
2904
|
+
resolvedMentioned: resolveSnapshotAnswerMentioned(s, project)
|
|
2905
|
+
}));
|
|
2894
2906
|
const projectKeywords = app.db.select({ id: keywords.id, createdAt: keywords.createdAt }).from(keywords).where(eq10(keywords.projectId, project.id)).all();
|
|
2895
2907
|
const keywordCreatedAt = new Map(projectKeywords.map((k) => [k.id, k.createdAt]));
|
|
2896
2908
|
const overall = computeProviderMetric(allSnapshots);
|
|
@@ -2904,9 +2916,10 @@ async function analyticsRoutes(app) {
|
|
|
2904
2916
|
const spanDays = Math.max(1, Math.ceil((latest.getTime() - earliest.getTime()) / 864e5));
|
|
2905
2917
|
const bucketSize = bucketSizeForSpan(spanDays);
|
|
2906
2918
|
const buckets = computeBuckets(allSnapshots, projectRuns, bucketSize, keywordCreatedAt);
|
|
2907
|
-
const trend = computeTrend(buckets);
|
|
2919
|
+
const trend = computeTrend(buckets, "citationRate");
|
|
2920
|
+
const answerTrend = computeTrend(buckets, "answerRate");
|
|
2908
2921
|
const keywordChanges = computeKeywordChanges(projectKeywords, cutoff);
|
|
2909
|
-
return reply.send({ window, buckets, overall, byProvider, trend, keywordChanges });
|
|
2922
|
+
return reply.send({ window, buckets, overall, byProvider, trend, answerTrend, keywordChanges });
|
|
2910
2923
|
});
|
|
2911
2924
|
app.get("/projects/:name/analytics/gaps", async (request, reply) => {
|
|
2912
2925
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -2914,7 +2927,7 @@ async function analyticsRoutes(app) {
|
|
|
2914
2927
|
const cutoff = windowCutoff(window);
|
|
2915
2928
|
const latestRun = app.db.select().from(runs).where(eq10(runs.projectId, project.id)).orderBy(desc3(runs.createdAt)).all().find((r) => r.status === "completed" || r.status === "partial");
|
|
2916
2929
|
if (!latestRun) {
|
|
2917
|
-
return reply.send({ cited: [], gap: [], uncited: [], runId: "", window });
|
|
2930
|
+
return reply.send({ cited: [], gap: [], uncited: [], mentionedKeywords: [], mentionGap: [], notMentioned: [], runId: "", window });
|
|
2918
2931
|
}
|
|
2919
2932
|
const windowRuns = app.db.select().from(runs).where(eq10(runs.projectId, project.id)).orderBy(runs.createdAt).all().filter((r) => r.status === "completed" || r.status === "partial").filter((r) => !cutoff || r.createdAt >= cutoff);
|
|
2920
2933
|
const windowRunIds = windowRuns.map((r) => r.id);
|
|
@@ -2923,25 +2936,34 @@ async function analyticsRoutes(app) {
|
|
|
2923
2936
|
const allWindowSnaps = app.db.select({
|
|
2924
2937
|
keywordId: querySnapshots.keywordId,
|
|
2925
2938
|
runId: querySnapshots.runId,
|
|
2926
|
-
citationState: querySnapshots.citationState
|
|
2939
|
+
citationState: querySnapshots.citationState,
|
|
2940
|
+
answerMentioned: querySnapshots.answerMentioned,
|
|
2941
|
+
answerText: querySnapshots.answerText
|
|
2927
2942
|
}).from(querySnapshots).where(inArray2(querySnapshots.runId, windowRunIds)).all();
|
|
2928
2943
|
for (const s of allWindowSnaps) {
|
|
2929
2944
|
let entry = consistencyMap.get(s.keywordId);
|
|
2930
2945
|
if (!entry) {
|
|
2931
|
-
entry = { citedRuns: /* @__PURE__ */ new Set(), totalRuns: /* @__PURE__ */ new Set() };
|
|
2946
|
+
entry = { citedRuns: /* @__PURE__ */ new Set(), totalRuns: /* @__PURE__ */ new Set(), mentionedRuns: /* @__PURE__ */ new Set() };
|
|
2932
2947
|
consistencyMap.set(s.keywordId, entry);
|
|
2933
2948
|
}
|
|
2934
2949
|
entry.totalRuns.add(s.runId);
|
|
2935
2950
|
if (s.citationState === "cited") entry.citedRuns.add(s.runId);
|
|
2951
|
+
if (resolveSnapshotAnswerMentioned(s, project)) entry.mentionedRuns.add(s.runId);
|
|
2936
2952
|
}
|
|
2937
2953
|
}
|
|
2938
|
-
const
|
|
2954
|
+
const rawSnapshots = app.db.select({
|
|
2939
2955
|
keywordId: querySnapshots.keywordId,
|
|
2940
2956
|
keyword: keywords.keyword,
|
|
2941
2957
|
provider: querySnapshots.provider,
|
|
2942
2958
|
citationState: querySnapshots.citationState,
|
|
2959
|
+
answerMentioned: querySnapshots.answerMentioned,
|
|
2960
|
+
answerText: querySnapshots.answerText,
|
|
2943
2961
|
competitorOverlap: querySnapshots.competitorOverlap
|
|
2944
2962
|
}).from(querySnapshots).leftJoin(keywords, eq10(querySnapshots.keywordId, keywords.id)).where(eq10(querySnapshots.runId, latestRun.id)).all();
|
|
2963
|
+
const snapshots = rawSnapshots.map((s) => ({
|
|
2964
|
+
...s,
|
|
2965
|
+
resolvedMentioned: resolveSnapshotAnswerMentioned(s, project)
|
|
2966
|
+
}));
|
|
2945
2967
|
const byKeyword = /* @__PURE__ */ new Map();
|
|
2946
2968
|
for (const s of snapshots) {
|
|
2947
2969
|
const key = s.keywordId;
|
|
@@ -2952,14 +2974,24 @@ async function analyticsRoutes(app) {
|
|
|
2952
2974
|
const cited = [];
|
|
2953
2975
|
const gap = [];
|
|
2954
2976
|
const uncited = [];
|
|
2977
|
+
const mentionedKeywords = [];
|
|
2978
|
+
const mentionGap = [];
|
|
2979
|
+
const notMentioned = [];
|
|
2955
2980
|
for (const [keywordId, kwSnapshots] of byKeyword) {
|
|
2956
2981
|
const keyword = kwSnapshots[0]?.keyword ?? "";
|
|
2957
2982
|
const citedProviders = kwSnapshots.filter((s) => s.citationState === "cited").map((s) => s.provider);
|
|
2983
|
+
const mentionedProviders = kwSnapshots.filter((s) => s.resolvedMentioned).map((s) => s.provider);
|
|
2958
2984
|
const competitorsCiting = /* @__PURE__ */ new Set();
|
|
2959
2985
|
for (const s of kwSnapshots) {
|
|
2960
2986
|
const overlap = parseJsonColumn(s.competitorOverlap, []);
|
|
2961
2987
|
for (const c of overlap) competitorsCiting.add(c);
|
|
2962
2988
|
}
|
|
2989
|
+
const cons = consistencyMap.get(keywordId);
|
|
2990
|
+
const consistency = {
|
|
2991
|
+
citedRuns: cons?.citedRuns.size ?? 0,
|
|
2992
|
+
totalRuns: cons?.totalRuns.size ?? 0,
|
|
2993
|
+
mentionedRuns: cons?.mentionedRuns.size ?? 0
|
|
2994
|
+
};
|
|
2963
2995
|
let category;
|
|
2964
2996
|
if (citedProviders.length > 0) {
|
|
2965
2997
|
category = "cited";
|
|
@@ -2968,26 +3000,44 @@ async function analyticsRoutes(app) {
|
|
|
2968
3000
|
} else {
|
|
2969
3001
|
category = "uncited";
|
|
2970
3002
|
}
|
|
2971
|
-
const
|
|
2972
|
-
const entry = {
|
|
3003
|
+
const citationEntry = {
|
|
2973
3004
|
keyword,
|
|
2974
3005
|
keywordId,
|
|
2975
3006
|
category,
|
|
2976
3007
|
providers: citedProviders,
|
|
2977
3008
|
competitorsCiting: [...competitorsCiting],
|
|
2978
|
-
consistency
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
3009
|
+
consistency
|
|
3010
|
+
};
|
|
3011
|
+
if (category === "cited") cited.push(citationEntry);
|
|
3012
|
+
else if (category === "gap") gap.push(citationEntry);
|
|
3013
|
+
else uncited.push(citationEntry);
|
|
3014
|
+
let mentionCategory;
|
|
3015
|
+
if (mentionedProviders.length > 0) {
|
|
3016
|
+
mentionCategory = "cited";
|
|
3017
|
+
} else if (competitorsCiting.size > 0) {
|
|
3018
|
+
mentionCategory = "gap";
|
|
3019
|
+
} else {
|
|
3020
|
+
mentionCategory = "uncited";
|
|
3021
|
+
}
|
|
3022
|
+
const mentionEntry = {
|
|
3023
|
+
keyword,
|
|
3024
|
+
keywordId,
|
|
3025
|
+
category: mentionCategory,
|
|
3026
|
+
providers: mentionedProviders,
|
|
3027
|
+
competitorsCiting: [...competitorsCiting],
|
|
3028
|
+
consistency
|
|
2982
3029
|
};
|
|
2983
|
-
if (
|
|
2984
|
-
else if (
|
|
2985
|
-
else
|
|
3030
|
+
if (mentionCategory === "cited") mentionedKeywords.push(mentionEntry);
|
|
3031
|
+
else if (mentionCategory === "gap") mentionGap.push(mentionEntry);
|
|
3032
|
+
else notMentioned.push(mentionEntry);
|
|
2986
3033
|
}
|
|
2987
3034
|
gap.sort((a, b) => b.competitorsCiting.length - a.competitorsCiting.length);
|
|
2988
3035
|
cited.sort((a, b) => a.keyword.localeCompare(b.keyword));
|
|
2989
3036
|
uncited.sort((a, b) => a.keyword.localeCompare(b.keyword));
|
|
2990
|
-
|
|
3037
|
+
mentionGap.sort((a, b) => b.competitorsCiting.length - a.competitorsCiting.length);
|
|
3038
|
+
mentionedKeywords.sort((a, b) => a.keyword.localeCompare(b.keyword));
|
|
3039
|
+
notMentioned.sort((a, b) => a.keyword.localeCompare(b.keyword));
|
|
3040
|
+
return reply.send({ cited, gap, uncited, mentionedKeywords, mentionGap, notMentioned, runId: latestRun.id, window });
|
|
2991
3041
|
});
|
|
2992
3042
|
app.get("/projects/:name/analytics/sources", async (request, reply) => {
|
|
2993
3043
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -3070,10 +3120,13 @@ function bucketSizeForSpan(spanDays) {
|
|
|
3070
3120
|
function computeProviderMetric(snapshots) {
|
|
3071
3121
|
const total = snapshots.length;
|
|
3072
3122
|
const cited = snapshots.filter((s) => s.citationState === "cited").length;
|
|
3123
|
+
const answerMentionedCount = snapshots.filter((s) => s.resolvedMentioned).length;
|
|
3073
3124
|
return {
|
|
3074
3125
|
citationRate: total > 0 ? Math.round(cited / total * 1e4) / 1e4 : 0,
|
|
3075
3126
|
cited,
|
|
3076
|
-
total
|
|
3127
|
+
total,
|
|
3128
|
+
answerRate: total > 0 ? Math.round(answerMentionedCount / total * 1e4) / 1e4 : 0,
|
|
3129
|
+
answerMentionedCount
|
|
3077
3130
|
};
|
|
3078
3131
|
}
|
|
3079
3132
|
function computeBuckets(snapshots, projectRuns, bucketDays, keywordCreatedAt) {
|
|
@@ -3106,7 +3159,9 @@ function computeBuckets(snapshots, projectRuns, bucketDays, keywordCreatedAt) {
|
|
|
3106
3159
|
citationRate: metric.citationRate,
|
|
3107
3160
|
cited: metric.cited,
|
|
3108
3161
|
total: metric.total,
|
|
3109
|
-
keywordCount
|
|
3162
|
+
keywordCount,
|
|
3163
|
+
answerRate: metric.answerRate,
|
|
3164
|
+
answerMentionedCount: metric.answerMentionedCount
|
|
3110
3165
|
});
|
|
3111
3166
|
}
|
|
3112
3167
|
start = end;
|
|
@@ -3128,14 +3183,14 @@ function computeKeywordChanges(projectKeywords, cutoff) {
|
|
|
3128
3183
|
label: `+${count} kp`
|
|
3129
3184
|
}));
|
|
3130
3185
|
}
|
|
3131
|
-
function computeTrend(buckets) {
|
|
3186
|
+
function computeTrend(buckets, rateKey) {
|
|
3132
3187
|
const nonEmpty = buckets.filter((b) => b.total > 0);
|
|
3133
3188
|
if (nonEmpty.length < 2) return "stable";
|
|
3134
3189
|
const mid = Math.floor(nonEmpty.length / 2);
|
|
3135
3190
|
const firstHalf = nonEmpty.slice(0, mid);
|
|
3136
3191
|
const secondHalf = nonEmpty.slice(mid);
|
|
3137
|
-
const avgFirst = firstHalf.reduce((s, b) => s + b
|
|
3138
|
-
const avgSecond = secondHalf.reduce((s, b) => s + b
|
|
3192
|
+
const avgFirst = firstHalf.reduce((s, b) => s + b[rateKey], 0) / firstHalf.length;
|
|
3193
|
+
const avgSecond = secondHalf.reduce((s, b) => s + b[rateKey], 0) / secondHalf.length;
|
|
3139
3194
|
const diff = avgSecond - avgFirst;
|
|
3140
3195
|
if (diff > 0.05) return "improving";
|
|
3141
3196
|
if (diff < -0.05) return "declining";
|
|
@@ -6020,18 +6075,16 @@ async function gscFetch(accessToken, url, opts) {
|
|
|
6020
6075
|
signal: AbortSignal.timeout(GOOGLE_REQUEST_TIMEOUT_MS)
|
|
6021
6076
|
});
|
|
6022
6077
|
if (res.status === 401) {
|
|
6023
|
-
|
|
6024
|
-
gscClientLog("error", "http.auth-expired", { url, method, httpStatus: 401, responseBody: body });
|
|
6078
|
+
gscClientLog("error", "http.auth-expired", { url, method, httpStatus: 401 });
|
|
6025
6079
|
throw new GoogleApiError("Access token expired or revoked", 401);
|
|
6026
6080
|
}
|
|
6027
6081
|
if (res.status === 429) {
|
|
6028
|
-
|
|
6029
|
-
gscClientLog("error", "http.rate-limited", { url, method, httpStatus: 429, responseBody: body });
|
|
6082
|
+
gscClientLog("error", "http.rate-limited", { url, method, httpStatus: 429 });
|
|
6030
6083
|
throw new GoogleApiError("Google API rate limit exceeded", 429);
|
|
6031
6084
|
}
|
|
6032
6085
|
if (!res.ok) {
|
|
6033
6086
|
const body = await res.text();
|
|
6034
|
-
gscClientLog("error", "http.error", { url, method, httpStatus: res.status
|
|
6087
|
+
gscClientLog("error", "http.error", { url, method, httpStatus: res.status });
|
|
6035
6088
|
throw new GoogleApiError(`GSC API error (${res.status}): ${body}`, res.status);
|
|
6036
6089
|
}
|
|
6037
6090
|
return await res.json();
|
|
@@ -6214,7 +6267,7 @@ async function getAccessToken(clientEmail, privateKey) {
|
|
|
6214
6267
|
});
|
|
6215
6268
|
if (!res.ok) {
|
|
6216
6269
|
const body = await res.text().catch(() => "");
|
|
6217
|
-
ga4Log("error", "token.failed", { httpStatus: res.status
|
|
6270
|
+
ga4Log("error", "token.failed", { httpStatus: res.status });
|
|
6218
6271
|
throw new GA4ApiError(`Failed to get access token: ${body}`, res.status);
|
|
6219
6272
|
}
|
|
6220
6273
|
const data = await res.json();
|
|
@@ -6244,7 +6297,7 @@ async function runReport(accessToken, propertyId, request) {
|
|
|
6244
6297
|
} catch {
|
|
6245
6298
|
if (body.length < 200) detail = ` ${body}`;
|
|
6246
6299
|
}
|
|
6247
|
-
ga4Log("error", "report.auth-failed", { propertyId, httpStatus: res.status
|
|
6300
|
+
ga4Log("error", "report.auth-failed", { propertyId, httpStatus: res.status });
|
|
6248
6301
|
throw new GA4ApiError(
|
|
6249
6302
|
`GA4 API authentication failed \u2014 check service account permissions.${detail}`,
|
|
6250
6303
|
res.status
|
|
@@ -6256,7 +6309,7 @@ async function runReport(accessToken, propertyId, request) {
|
|
|
6256
6309
|
}
|
|
6257
6310
|
if (!res.ok) {
|
|
6258
6311
|
const body = await res.text();
|
|
6259
|
-
ga4Log("error", "report.error", { propertyId, httpStatus: res.status
|
|
6312
|
+
ga4Log("error", "report.error", { propertyId, httpStatus: res.status });
|
|
6260
6313
|
throw new GA4ApiError(`GA4 API error (${res.status}): ${body}`, res.status);
|
|
6261
6314
|
}
|
|
6262
6315
|
return await res.json();
|
|
@@ -7317,18 +7370,16 @@ async function bingFetch(apiKey, endpoint, opts) {
|
|
|
7317
7370
|
signal: AbortSignal.timeout(BING_REQUEST_TIMEOUT_MS)
|
|
7318
7371
|
});
|
|
7319
7372
|
if (res.status === 401 || res.status === 403) {
|
|
7320
|
-
|
|
7321
|
-
bingClientLog("error", "http.auth-failed", { endpoint, method, httpStatus: res.status, responseBody: body });
|
|
7373
|
+
bingClientLog("error", "http.auth-failed", { endpoint, method, httpStatus: res.status });
|
|
7322
7374
|
throw new BingApiError("Bing API key is invalid or unauthorized", res.status);
|
|
7323
7375
|
}
|
|
7324
7376
|
if (res.status === 429) {
|
|
7325
|
-
|
|
7326
|
-
bingClientLog("error", "http.rate-limited", { endpoint, method, httpStatus: 429, responseBody: body });
|
|
7377
|
+
bingClientLog("error", "http.rate-limited", { endpoint, method, httpStatus: 429 });
|
|
7327
7378
|
throw new BingApiError("Bing API rate limit exceeded", 429);
|
|
7328
7379
|
}
|
|
7329
7380
|
if (!res.ok) {
|
|
7330
7381
|
const body = await res.text();
|
|
7331
|
-
bingClientLog("error", "http.error", { endpoint, method, httpStatus: res.status
|
|
7382
|
+
bingClientLog("error", "http.error", { endpoint, method, httpStatus: res.status });
|
|
7332
7383
|
throw new BingApiError(`Bing API error (${res.status}): ${body}`, res.status);
|
|
7333
7384
|
}
|
|
7334
7385
|
const text = await res.text();
|
|
@@ -10137,6 +10188,15 @@ async function apiRoutes(app, opts) {
|
|
|
10137
10188
|
import { GoogleGenAI } from "@google/genai";
|
|
10138
10189
|
|
|
10139
10190
|
// ../provider-gemini/src/utils.ts
|
|
10191
|
+
function isRetryableError(err) {
|
|
10192
|
+
if (err != null && typeof err === "object" && "status" in err) {
|
|
10193
|
+
const status = err.status;
|
|
10194
|
+
if (typeof status === "number") {
|
|
10195
|
+
return status >= 500 || status === 429;
|
|
10196
|
+
}
|
|
10197
|
+
}
|
|
10198
|
+
return true;
|
|
10199
|
+
}
|
|
10140
10200
|
async function withRetry(fn, options = {}) {
|
|
10141
10201
|
const { maxRetries = 3, initialDelay = 1e3 } = options;
|
|
10142
10202
|
let lastError;
|
|
@@ -10145,10 +10205,12 @@ async function withRetry(fn, options = {}) {
|
|
|
10145
10205
|
return await fn();
|
|
10146
10206
|
} catch (err) {
|
|
10147
10207
|
lastError = err;
|
|
10148
|
-
if (attempt < maxRetries) {
|
|
10208
|
+
if (attempt < maxRetries && isRetryableError(err)) {
|
|
10149
10209
|
const delay = initialDelay * Math.pow(2, attempt);
|
|
10150
10210
|
console.warn(`[provider] Attempt ${attempt + 1} failed, retrying in ${delay}ms...`, err instanceof Error ? err.message : String(err));
|
|
10151
10211
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
10212
|
+
} else {
|
|
10213
|
+
throw err;
|
|
10152
10214
|
}
|
|
10153
10215
|
}
|
|
10154
10216
|
}
|
|
@@ -10510,6 +10572,15 @@ var geminiAdapter = {
|
|
|
10510
10572
|
import OpenAI from "openai";
|
|
10511
10573
|
|
|
10512
10574
|
// ../provider-openai/src/utils.ts
|
|
10575
|
+
function isRetryableError2(err) {
|
|
10576
|
+
if (err != null && typeof err === "object" && "status" in err) {
|
|
10577
|
+
const status = err.status;
|
|
10578
|
+
if (typeof status === "number") {
|
|
10579
|
+
return status >= 500 || status === 429;
|
|
10580
|
+
}
|
|
10581
|
+
}
|
|
10582
|
+
return true;
|
|
10583
|
+
}
|
|
10513
10584
|
async function withRetry2(fn, options = {}) {
|
|
10514
10585
|
const { maxRetries = 3, initialDelay = 1e3 } = options;
|
|
10515
10586
|
let lastError;
|
|
@@ -10518,10 +10589,12 @@ async function withRetry2(fn, options = {}) {
|
|
|
10518
10589
|
return await fn();
|
|
10519
10590
|
} catch (err) {
|
|
10520
10591
|
lastError = err;
|
|
10521
|
-
if (attempt < maxRetries) {
|
|
10592
|
+
if (attempt < maxRetries && isRetryableError2(err)) {
|
|
10522
10593
|
const delay = initialDelay * Math.pow(2, attempt);
|
|
10523
10594
|
console.warn(`[provider] Attempt ${attempt + 1} failed, retrying in ${delay}ms...`, err instanceof Error ? err.message : String(err));
|
|
10524
10595
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
10596
|
+
} else {
|
|
10597
|
+
throw err;
|
|
10525
10598
|
}
|
|
10526
10599
|
}
|
|
10527
10600
|
}
|
|
@@ -10843,6 +10916,15 @@ var openaiAdapter = {
|
|
|
10843
10916
|
import Anthropic from "@anthropic-ai/sdk";
|
|
10844
10917
|
|
|
10845
10918
|
// ../provider-claude/src/utils.ts
|
|
10919
|
+
function isRetryableError3(err) {
|
|
10920
|
+
if (err != null && typeof err === "object" && "status" in err) {
|
|
10921
|
+
const status = err.status;
|
|
10922
|
+
if (typeof status === "number") {
|
|
10923
|
+
return status >= 500 || status === 429;
|
|
10924
|
+
}
|
|
10925
|
+
}
|
|
10926
|
+
return true;
|
|
10927
|
+
}
|
|
10846
10928
|
async function withRetry3(fn, options = {}) {
|
|
10847
10929
|
const { maxRetries = 3, initialDelay = 1e3 } = options;
|
|
10848
10930
|
let lastError;
|
|
@@ -10851,10 +10933,12 @@ async function withRetry3(fn, options = {}) {
|
|
|
10851
10933
|
return await fn();
|
|
10852
10934
|
} catch (err) {
|
|
10853
10935
|
lastError = err;
|
|
10854
|
-
if (attempt < maxRetries) {
|
|
10936
|
+
if (attempt < maxRetries && isRetryableError3(err)) {
|
|
10855
10937
|
const delay = initialDelay * Math.pow(2, attempt);
|
|
10856
10938
|
console.warn(`[provider] Attempt ${attempt + 1} failed, retrying in ${delay}ms...`, err instanceof Error ? err.message : String(err));
|
|
10857
10939
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
10940
|
+
} else {
|
|
10941
|
+
throw err;
|
|
10858
10942
|
}
|
|
10859
10943
|
}
|
|
10860
10944
|
}
|
|
@@ -11199,6 +11283,15 @@ var claudeAdapter = {
|
|
|
11199
11283
|
import OpenAI2 from "openai";
|
|
11200
11284
|
|
|
11201
11285
|
// ../provider-local/src/utils.ts
|
|
11286
|
+
function isRetryableError4(err) {
|
|
11287
|
+
if (err != null && typeof err === "object" && "status" in err) {
|
|
11288
|
+
const status = err.status;
|
|
11289
|
+
if (typeof status === "number") {
|
|
11290
|
+
return status >= 500 || status === 429;
|
|
11291
|
+
}
|
|
11292
|
+
}
|
|
11293
|
+
return true;
|
|
11294
|
+
}
|
|
11202
11295
|
async function withRetry4(fn, options = {}) {
|
|
11203
11296
|
const { maxRetries = 3, initialDelay = 1e3 } = options;
|
|
11204
11297
|
let lastError;
|
|
@@ -11207,10 +11300,12 @@ async function withRetry4(fn, options = {}) {
|
|
|
11207
11300
|
return await fn();
|
|
11208
11301
|
} catch (err) {
|
|
11209
11302
|
lastError = err;
|
|
11210
|
-
if (attempt < maxRetries) {
|
|
11303
|
+
if (attempt < maxRetries && isRetryableError4(err)) {
|
|
11211
11304
|
const delay = initialDelay * Math.pow(2, attempt);
|
|
11212
11305
|
console.warn(`[provider] Attempt ${attempt + 1} failed, retrying in ${delay}ms...`, err instanceof Error ? err.message : String(err));
|
|
11213
11306
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
11307
|
+
} else {
|
|
11308
|
+
throw err;
|
|
11214
11309
|
}
|
|
11215
11310
|
}
|
|
11216
11311
|
}
|
|
@@ -11994,6 +12089,15 @@ var cdpChatgptAdapter = {
|
|
|
11994
12089
|
import OpenAI3 from "openai";
|
|
11995
12090
|
|
|
11996
12091
|
// ../provider-perplexity/src/utils.ts
|
|
12092
|
+
function isRetryableError5(err) {
|
|
12093
|
+
if (err != null && typeof err === "object" && "status" in err) {
|
|
12094
|
+
const status = err.status;
|
|
12095
|
+
if (typeof status === "number") {
|
|
12096
|
+
return status >= 500 || status === 429;
|
|
12097
|
+
}
|
|
12098
|
+
}
|
|
12099
|
+
return true;
|
|
12100
|
+
}
|
|
11997
12101
|
async function withRetry5(fn, options = {}) {
|
|
11998
12102
|
const { maxRetries = 3, initialDelay = 1e3 } = options;
|
|
11999
12103
|
let lastError;
|
|
@@ -12002,10 +12106,12 @@ async function withRetry5(fn, options = {}) {
|
|
|
12002
12106
|
return await fn();
|
|
12003
12107
|
} catch (err) {
|
|
12004
12108
|
lastError = err;
|
|
12005
|
-
if (attempt < maxRetries) {
|
|
12109
|
+
if (attempt < maxRetries && isRetryableError5(err)) {
|
|
12006
12110
|
const delay = initialDelay * Math.pow(2, attempt);
|
|
12007
12111
|
console.warn(`[provider] Attempt ${attempt + 1} failed, retrying in ${delay}ms...`, err instanceof Error ? err.message : String(err));
|
|
12008
12112
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
12113
|
+
} else {
|
|
12114
|
+
throw err;
|
|
12009
12115
|
}
|
|
12010
12116
|
}
|
|
12011
12117
|
}
|
package/dist/cli.js
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
setGoogleAuthConfig,
|
|
29
29
|
showFirstRunNotice,
|
|
30
30
|
trackEvent
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-TKMBOLZB.js";
|
|
32
32
|
import {
|
|
33
33
|
apiKeys,
|
|
34
34
|
competitors,
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
projects,
|
|
39
39
|
querySnapshots,
|
|
40
40
|
runs
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-EUBC5EGC.js";
|
|
42
42
|
|
|
43
43
|
// src/cli.ts
|
|
44
44
|
import { pathToFileURL } from "url";
|
|
@@ -355,7 +355,7 @@ async function backfillAnswerVisibilityCommand(opts) {
|
|
|
355
355
|
console.log(` Errors: ${providerErrors}`);
|
|
356
356
|
}
|
|
357
357
|
async function backfillInsightsCommand(project, opts) {
|
|
358
|
-
const { IntelligenceService } = await import("./intelligence-service-
|
|
358
|
+
const { IntelligenceService } = await import("./intelligence-service-DXGTIRF5.js");
|
|
359
359
|
const config = loadConfig();
|
|
360
360
|
const db = createClient(config.database);
|
|
361
361
|
migrate(db);
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainyc/canonry",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.41.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The ultimate open-source AEO monitoring tool - track how answer engines cite your domain",
|
|
6
6
|
"license": "FSL-1.1-ALv2",
|
|
@@ -57,17 +57,17 @@
|
|
|
57
57
|
"@ainyc/canonry-api-routes": "0.0.0",
|
|
58
58
|
"@ainyc/canonry-config": "0.0.0",
|
|
59
59
|
"@ainyc/canonry-contracts": "0.0.0",
|
|
60
|
-
"@ainyc/canonry-db": "0.0.0",
|
|
61
60
|
"@ainyc/canonry-intelligence": "0.0.0",
|
|
62
|
-
"@ainyc/canonry-integration-
|
|
61
|
+
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
63
62
|
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
63
|
+
"@ainyc/canonry-db": "0.0.0",
|
|
64
64
|
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
65
|
-
"@ainyc/canonry-integration-
|
|
66
|
-
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
65
|
+
"@ainyc/canonry-integration-google": "0.0.0",
|
|
67
66
|
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
68
|
-
"@ainyc/canonry-provider-
|
|
67
|
+
"@ainyc/canonry-provider-local": "0.0.0",
|
|
68
|
+
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
69
69
|
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
70
|
-
"@ainyc/canonry-provider-
|
|
70
|
+
"@ainyc/canonry-provider-perplexity": "0.0.0"
|
|
71
71
|
},
|
|
72
72
|
"scripts": {
|
|
73
73
|
"build": "tsup && tsx build-web.ts",
|