@canonry/canonry 4.177.1 → 4.177.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/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +92 -57
- package/dist/{chunk-GJE334DA.js → chunk-5EH666EO.js} +67 -34
- package/dist/{chunk-XFAPF2YX.js → chunk-7ABFIVKO.js} +2 -2
- package/dist/cli.js +2 -2
- package/dist/index.js +2 -2
- package/dist/{intelligence-service-C57RIB37.js → intelligence-service-CHZGHFGS.js} +1 -1
- package/package.json +9 -9
|
@@ -72,21 +72,17 @@ sync flow does NOT echo the private key back in any response.
|
|
|
72
72
|
## Connecting a WordPress source
|
|
73
73
|
|
|
74
74
|
The WordPress adapter pulls events from the **Canonry Traffic Logger**
|
|
75
|
-
WordPress plugin, which captures
|
|
76
|
-
|
|
77
|
-
Application Password.
|
|
78
|
-
|
|
79
|
-
> **Cache blind spot.**
|
|
80
|
-
>
|
|
81
|
-
>
|
|
82
|
-
>
|
|
83
|
-
>
|
|
84
|
-
>
|
|
85
|
-
>
|
|
86
|
-
> views go uncounted. Exclude AI user-agents from the cache (and any
|
|
87
|
-
> CDN), or capture from access/edge logs instead. The
|
|
88
|
-
> `traffic.source.cache-blindspot` doctor check warns whenever a
|
|
89
|
-
> WordPress source is connected.
|
|
75
|
+
WordPress plugin, which is PHP-only: it captures non-admin GET page-loads
|
|
76
|
+
**that reach PHP** and exposes a paginated REST endpoint protected by an
|
|
77
|
+
Application Password. It has no browser-side capture path.
|
|
78
|
+
|
|
79
|
+
> **Cache blind spot.** Cache-served requests never execute PHP, so they
|
|
80
|
+
> produce no plugin event. A full-page cache (LiteSpeed, WP Rocket, W3 Total
|
|
81
|
+
> Cache, WP Super Cache) or any CDN can therefore make the source look active
|
|
82
|
+
> while real page views, AI crawlers, and live AI user-fetches such as
|
|
83
|
+
> `Claude-User` and `ChatGPT-User` go uncounted. To use this source for
|
|
84
|
+
> AI-agent traffic, bypass **every** cache layer for the selected AI user
|
|
85
|
+
> agents, or capture from access/edge logs instead.
|
|
90
86
|
|
|
91
87
|
**Which user-agents to exclude from the cache** (one per line in
|
|
92
88
|
LiteSpeed's "Do Not Cache User Agents", WP Rocket's
|
|
@@ -94,23 +90,61 @@ LiteSpeed's "Do Not Cache User Agents", WP Rocket's
|
|
|
94
90
|
Agents"):
|
|
95
91
|
|
|
96
92
|
```
|
|
97
|
-
Claude-User
|
|
98
|
-
ClaudeBot
|
|
99
|
-
ChatGPT-User
|
|
100
|
-
OAI-SearchBot
|
|
101
93
|
GPTBot
|
|
94
|
+
OAI-SearchBot
|
|
95
|
+
OAI-AdsBot
|
|
96
|
+
ChatGPT-User
|
|
97
|
+
openai-mcp
|
|
98
|
+
ClaudeBot
|
|
99
|
+
Claude-
|
|
100
|
+
anthropic-ai
|
|
102
101
|
PerplexityBot
|
|
103
102
|
Perplexity-User
|
|
103
|
+
ShapBot
|
|
104
|
+
Shap-User
|
|
105
|
+
Google-Agent
|
|
106
|
+
Google-GeminiNotebook
|
|
107
|
+
Google-NotebookLM
|
|
108
|
+
Google-CloudVertexBot
|
|
109
|
+
Bytespider
|
|
110
|
+
Applebot
|
|
111
|
+
meta-externalagent
|
|
112
|
+
CCBot
|
|
113
|
+
cohere-ai
|
|
114
|
+
Diffbot
|
|
115
|
+
MistralAI-User
|
|
116
|
+
MistralAI-Index
|
|
117
|
+
MistralAI-Training
|
|
118
|
+
MistralBot
|
|
119
|
+
DeepSeekBot
|
|
120
|
+
xAI-Bot
|
|
121
|
+
Grok-Bot
|
|
122
|
+
GrokBot
|
|
123
|
+
YouBot
|
|
124
|
+
DuckAssistBot
|
|
125
|
+
Amazonbot
|
|
126
|
+
Amzn-SearchBot
|
|
127
|
+
Amzn-User
|
|
104
128
|
```
|
|
105
129
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
130
|
+
This list mirrors Canonry's current classifier: answer-engine user-fetch,
|
|
131
|
+
crawl, search, and training agents for which WordPress has no cache-independent
|
|
132
|
+
measurement surface. `ClaudeBot` covers Anthropic's unhyphenated core crawler;
|
|
133
|
+
`Claude-` is a separate family rule so newly named `Claude-*Bot` variants
|
|
134
|
+
inherit the bypass. Do NOT add traditional search agents
|
|
135
|
+
`Googlebot`, `bingbot`, `DuckDuckBot`, `YandexBot`, or `Baiduspider`: caching
|
|
136
|
+
helps them crawl efficiently, and the important Google/Bing crawl evidence is
|
|
137
|
+
available through their webmaster tools.
|
|
138
|
+
|
|
139
|
+
> **1.1.0 -> 1.1.1 measurement boundary.** Plugin 1.1.0 briefly added a
|
|
140
|
+
> JavaScript beacon to recover browser/referral page views served from cache.
|
|
141
|
+
> Plugin 1.1.1 removes it and returns to PHP-only capture. This intentionally
|
|
142
|
+
> means cache-served browser referrals can again be absent even when GA4
|
|
143
|
+
> reports sessions; neither version made cache-served crawler requests visible
|
|
144
|
+
> to PHP. Annotate the upgrade time, do not interpret a trend spanning it as
|
|
145
|
+
> like-for-like traffic, and purge HTML from both the WordPress cache and every
|
|
146
|
+
> outer CDN. A temporary `/wp-json/canonry/v1/pv` compatibility route returns
|
|
147
|
+
> `204` for scripts stranded in old cached HTML, but never records an event.
|
|
114
148
|
|
|
115
149
|
```bash
|
|
116
150
|
# 1. Install the plugin. Download the latest release zip from the
|
|
@@ -978,7 +1012,7 @@ The doctor checks are adapter-agnostic. When they fail or warn:
|
|
|
978
1012
|
| `traffic.source.queue-backlog` | `traffic.queue-backlog.within-drain-budget` | 1–1,000 Queue messages remain. If no new messages arrive, the next scheduled sync can drain them. Run a manual sync to accelerate. |
|
|
979
1013
|
| `traffic.source.queue-backlog` | `traffic.queue-backlog.remaining` | More than 1,000 Queue messages remain. Run a manual sync. If the backlog persists, shorten the traffic-sync schedule. |
|
|
980
1014
|
| `traffic.source.credentials` | `traffic.credentials.resolve-failed` | Reconnect from the host that owns the source credentials. Queue pull requires a non-empty Account Queues Edit token paired by source ID. |
|
|
981
|
-
| `traffic.source.cache-blindspot` | `traffic.cache-blindspot.wordpress-plugin` |
|
|
1015
|
+
| `traffic.source.cache-blindspot` | `traffic.cache-blindspot.wordpress-plugin` | Cache-served requests never execute PHP and produce no event, so a source can look active while real page views go uncounted. Exclude selected AI user agents from every page-cache and CDN layer, or switch to a log/edge source. Warns only, not a failure. |
|
|
982
1016
|
| `traffic.source.worker-version` | `traffic.worker-version.waiting-for-first-event` | Send a smoke-test request through the Worker. For Queue delivery, run a sync to ingest it. Then run the doctor again. |
|
|
983
1017
|
| `traffic.source.worker-version` | `traffic.worker-version.stale` | Regenerate and redeploy the Worker from the credential-owning host with the source's existing delivery mode, then verify the route or Queue binding. |
|
|
984
1018
|
|
|
@@ -1043,21 +1077,18 @@ domains, or PII are surfaced.
|
|
|
1043
1077
|
|
|
1044
1078
|
## Limits & caveats
|
|
1045
1079
|
|
|
1046
|
-
- **The WordPress plugin is
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
`
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
every request that reaches the service, missing only what a CDN placed
|
|
1059
|
-
in front of Cloud Run serves from its own edge cache. Only the
|
|
1060
|
-
hook-based `wordpress` adapter has the always-present blind spot.
|
|
1080
|
+
- **The WordPress plugin is PHP-only.** The `wordpress` adapter records only
|
|
1081
|
+
requests that reach PHP. Cache-served page requests, including AI crawlers
|
|
1082
|
+
and live AI user-fetches, go uncounted even while crawls of uncached
|
|
1083
|
+
sitemaps, assets, or cache misses appear. On a cached WordPress site, treat
|
|
1084
|
+
this source as a floor unless the selected AI user agents bypass every
|
|
1085
|
+
page-cache and CDN layer. The static doctor warning surfaces this limitation
|
|
1086
|
+
without requesting the origin. Otherwise, use a cache-independent
|
|
1087
|
+
`cloud-run`, `vercel`, or edge-log source. Adapter coverage
|
|
1088
|
+
differs: `vercel` ingests edge request logs so cache hits are captured (it
|
|
1089
|
+
records the `cache` HIT/MISS label), and `cloud-run` logs every request that
|
|
1090
|
+
reaches the service, missing only what a CDN ahead of Cloud Run serves at its
|
|
1091
|
+
own edge.
|
|
1061
1092
|
- **Path-level citation cross-reference is not implemented yet.** The
|
|
1062
1093
|
citation store is domain-grain (`query_snapshots.cited_domains`). A
|
|
1063
1094
|
future iteration that lands URL-grain citation evidence will extend
|
|
@@ -1078,17 +1109,21 @@ domains, or PII are surfaced.
|
|
|
1078
1109
|
`traffic_sources.source_type` and a `TrafficSourceValidator`
|
|
1079
1110
|
registration.
|
|
1080
1111
|
|
|
1081
|
-
### Page caches and
|
|
1082
|
-
|
|
1083
|
-
A page cache (LiteSpeed, WP Rocket, Super Cache, any
|
|
1084
|
-
drop-in)
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1112
|
+
### Page caches and PHP-only coverage
|
|
1113
|
+
|
|
1114
|
+
A page cache (LiteSpeed, WP Rocket, Super Cache, or any
|
|
1115
|
+
`advanced-cache.php` drop-in) can serve a request before WordPress boots. The
|
|
1116
|
+
plugin therefore has one capture lane only: PHP. It cannot recover a
|
|
1117
|
+
cache-served crawler, user fetch, referral, or browser page view after the
|
|
1118
|
+
fact.
|
|
1119
|
+
|
|
1120
|
+
For the AI agents measured by this source, configure cache bypasses at every
|
|
1121
|
+
layer that can answer the public request: the WordPress cache, a host/CDN cache,
|
|
1122
|
+
and any edge cache. A LiteSpeed exclusion alone is insufficient when an outer
|
|
1123
|
+
CDN can return a cached response first.
|
|
1124
|
+
|
|
1125
|
+
Cache exclusions for the selected AI agents can recover crawler and user-fetch
|
|
1126
|
+
coverage, but they do not recover ordinary browser referrals served from cache.
|
|
1127
|
+
Use GA4 for landed browser visits or edge/access logs for cache-independent
|
|
1128
|
+
request coverage. The `traffic.source.cache-blindspot` doctor check is a static
|
|
1129
|
+
warning; it does not request or impersonate traffic against the public origin.
|
|
@@ -633,7 +633,7 @@ import {
|
|
|
633
633
|
} from "./chunk-PQT3ECM3.js";
|
|
634
634
|
|
|
635
635
|
// src/intelligence-service.ts
|
|
636
|
-
import { eq as eq61, desc as desc27, asc as asc11, and as and50, ne as ne8, or as or14, inArray as inArray21, gte as gte14, lte as lte12 } from "drizzle-orm";
|
|
636
|
+
import { eq as eq61, desc as desc27, asc as asc11, and as and50, ne as ne8, or as or14, inArray as inArray21, gte as gte14, lte as lte12, isNull as isNull8, sql as sql25, exists } from "drizzle-orm";
|
|
637
637
|
|
|
638
638
|
// ../db/src/client.ts
|
|
639
639
|
import { mkdirSync } from "fs";
|
|
@@ -7208,6 +7208,13 @@ function detectRegressions(currentRun, previousRun) {
|
|
|
7208
7208
|
return regressions;
|
|
7209
7209
|
}
|
|
7210
7210
|
|
|
7211
|
+
// ../intelligence/src/observation-coverage.ts
|
|
7212
|
+
function observedKeys(run, keyOf) {
|
|
7213
|
+
const observed2 = /* @__PURE__ */ new Set();
|
|
7214
|
+
for (const snap of run.snapshots) observed2.add(keyOf(snap));
|
|
7215
|
+
return observed2;
|
|
7216
|
+
}
|
|
7217
|
+
|
|
7211
7218
|
// ../intelligence/src/gains.ts
|
|
7212
7219
|
function snapshotKey2(snap) {
|
|
7213
7220
|
const loc = snap.location ?? "__none__";
|
|
@@ -7218,6 +7225,7 @@ function detectGains(currentRun, previousRun) {
|
|
|
7218
7225
|
return [];
|
|
7219
7226
|
}
|
|
7220
7227
|
const gains = [];
|
|
7228
|
+
const previousObserved = observedKeys(previousRun, snapshotKey2);
|
|
7221
7229
|
const previousCited = /* @__PURE__ */ new Set();
|
|
7222
7230
|
for (const snap of previousRun.snapshots) {
|
|
7223
7231
|
if (snap.cited) {
|
|
@@ -7226,7 +7234,7 @@ function detectGains(currentRun, previousRun) {
|
|
|
7226
7234
|
}
|
|
7227
7235
|
for (const snap of currentRun.snapshots) {
|
|
7228
7236
|
const key = snapshotKey2(snap);
|
|
7229
|
-
if (snap.cited && !previousCited.has(key)) {
|
|
7237
|
+
if (snap.cited && previousObserved.has(key) && !previousCited.has(key)) {
|
|
7230
7238
|
gains.push({
|
|
7231
7239
|
query: snap.query,
|
|
7232
7240
|
provider: snap.provider,
|
|
@@ -7452,6 +7460,7 @@ function generateInsights(input) {
|
|
|
7452
7460
|
|
|
7453
7461
|
// ../intelligence/src/first-citations.ts
|
|
7454
7462
|
function detectFirstCitations(currentRun, previousRun) {
|
|
7463
|
+
const previousObservedQueries = observedKeys(previousRun, (snap) => snap.query);
|
|
7455
7464
|
const previousCitedQueries = /* @__PURE__ */ new Set();
|
|
7456
7465
|
for (const snap of previousRun.snapshots) {
|
|
7457
7466
|
if (snap.cited) previousCitedQueries.add(snap.query);
|
|
@@ -7460,6 +7469,7 @@ function detectFirstCitations(currentRun, previousRun) {
|
|
|
7460
7469
|
const seen = /* @__PURE__ */ new Set();
|
|
7461
7470
|
for (const snap of currentRun.snapshots) {
|
|
7462
7471
|
if (!snap.cited) continue;
|
|
7472
|
+
if (!previousObservedQueries.has(snap.query)) continue;
|
|
7463
7473
|
if (previousCitedQueries.has(snap.query)) continue;
|
|
7464
7474
|
const key = `${snap.query}:${snap.provider}`;
|
|
7465
7475
|
if (seen.has(key)) continue;
|
|
@@ -7477,6 +7487,7 @@ function detectFirstCitations(currentRun, previousRun) {
|
|
|
7477
7487
|
|
|
7478
7488
|
// ../intelligence/src/provider-pickups.ts
|
|
7479
7489
|
function detectProviderPickups(currentRun, previousRun) {
|
|
7490
|
+
const previousObservedPairs = observedKeys(previousRun, (snap) => `${snap.query}:${snap.provider}`);
|
|
7480
7491
|
const previousCitedQueries = /* @__PURE__ */ new Set();
|
|
7481
7492
|
const previousCitedPairs = /* @__PURE__ */ new Set();
|
|
7482
7493
|
for (const snap of previousRun.snapshots) {
|
|
@@ -7490,6 +7501,7 @@ function detectProviderPickups(currentRun, previousRun) {
|
|
|
7490
7501
|
if (!snap.cited) continue;
|
|
7491
7502
|
if (!previousCitedQueries.has(snap.query)) continue;
|
|
7492
7503
|
const key = `${snap.query}:${snap.provider}`;
|
|
7504
|
+
if (!previousObservedPairs.has(key)) continue;
|
|
7493
7505
|
if (previousCitedPairs.has(key)) continue;
|
|
7494
7506
|
if (seen.has(key)) continue;
|
|
7495
7507
|
seen.add(key);
|
|
@@ -7551,11 +7563,13 @@ function detectCompetitorGains(currentRun, previousRun, opts) {
|
|
|
7551
7563
|
if (tracked.size === 0) return [];
|
|
7552
7564
|
const currentMap = buildCompetitorQueryMap(currentRun, tracked);
|
|
7553
7565
|
const previousMap = buildCompetitorQueryMap(previousRun, tracked);
|
|
7566
|
+
const previousObservedQueries = observedKeys(previousRun, (snap) => snap.query);
|
|
7554
7567
|
const result = [];
|
|
7555
7568
|
for (const competitorDomain of tracked) {
|
|
7556
7569
|
const currentQs = currentMap.get(competitorDomain) ?? /* @__PURE__ */ new Set();
|
|
7557
7570
|
const previousQs = previousMap.get(competitorDomain) ?? /* @__PURE__ */ new Set();
|
|
7558
7571
|
for (const query of currentQs) {
|
|
7572
|
+
if (!previousObservedQueries.has(query)) continue;
|
|
7559
7573
|
if (previousQs.has(query)) continue;
|
|
7560
7574
|
result.push({ query, competitorDomain });
|
|
7561
7575
|
}
|
|
@@ -7567,11 +7581,13 @@ function detectCompetitorLosses(currentRun, previousRun, opts) {
|
|
|
7567
7581
|
if (tracked.size === 0) return [];
|
|
7568
7582
|
const currentMap = buildCompetitorQueryMap(currentRun, tracked);
|
|
7569
7583
|
const previousMap = buildCompetitorQueryMap(previousRun, tracked);
|
|
7584
|
+
const currentObservedQueries = observedKeys(currentRun, (snap) => snap.query);
|
|
7570
7585
|
const result = [];
|
|
7571
7586
|
for (const competitorDomain of tracked) {
|
|
7572
7587
|
const currentQs = currentMap.get(competitorDomain) ?? /* @__PURE__ */ new Set();
|
|
7573
7588
|
const previousQs = previousMap.get(competitorDomain) ?? /* @__PURE__ */ new Set();
|
|
7574
7589
|
for (const query of previousQs) {
|
|
7590
|
+
if (!currentObservedQueries.has(query)) continue;
|
|
7575
7591
|
if (currentQs.has(query)) continue;
|
|
7576
7592
|
result.push({ query, competitorDomain });
|
|
7577
7593
|
}
|
|
@@ -51119,7 +51135,7 @@ async function queryBacklinks(opts) {
|
|
|
51119
51135
|
const reversed = opts.targets.map(reverseDomain);
|
|
51120
51136
|
const targetList = reversed.map(quote).join(", ");
|
|
51121
51137
|
const limitClause = opts.limitPerTarget ? `QUALIFY row_number() OVER (PARTITION BY t.target_rev_domain ORDER BY v.num_hosts DESC) <= ${Math.floor(opts.limitPerTarget)}` : "";
|
|
51122
|
-
const
|
|
51138
|
+
const sql26 = `
|
|
51123
51139
|
WITH vertices AS (
|
|
51124
51140
|
SELECT * FROM read_csv(
|
|
51125
51141
|
${quote(opts.vertexPath)},
|
|
@@ -51155,7 +51171,7 @@ async function queryBacklinks(opts) {
|
|
|
51155
51171
|
const conn = await instance.connect();
|
|
51156
51172
|
let rows;
|
|
51157
51173
|
try {
|
|
51158
|
-
const reader = await conn.runAndReadAll(
|
|
51174
|
+
const reader = await conn.runAndReadAll(sql26);
|
|
51159
51175
|
rows = reader.getRowObjects();
|
|
51160
51176
|
} finally {
|
|
51161
51177
|
conn.disconnectSync?.();
|
|
@@ -77833,6 +77849,19 @@ function matchesBrandAlias(candidate, aliases) {
|
|
|
77833
77849
|
var RECURRENCE_LOOKBACK_RUNS = 5;
|
|
77834
77850
|
var HISTORY_WINDOW_RUNS = Math.max(PERSISTENT_GAP_THRESHOLD, 5);
|
|
77835
77851
|
var log = createLogger("IntelligenceService");
|
|
77852
|
+
var runChronologyKey = sql25`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
|
|
77853
|
+
function analyzableRunPredicates(projectId) {
|
|
77854
|
+
return [
|
|
77855
|
+
eq61(runs.projectId, projectId),
|
|
77856
|
+
// Only this kind writes query_snapshots.
|
|
77857
|
+
eq61(runs.kind, RunKinds["answer-visibility"]),
|
|
77858
|
+
or14(eq61(runs.status, RunStatuses.completed), eq61(runs.status, RunStatuses.partial)),
|
|
77859
|
+
// Defensive: RunCoordinator already skips probes before analyzeAndPersist
|
|
77860
|
+
// is called, but a future call site invoking it directly for a probe must
|
|
77861
|
+
// still not pollute the intelligence window.
|
|
77862
|
+
ne8(runs.trigger, RunTriggers.probe)
|
|
77863
|
+
];
|
|
77864
|
+
}
|
|
77836
77865
|
function readStoredGroundingSources(rawResponse) {
|
|
77837
77866
|
const parsed = parseJsonColumn(rawResponse, {});
|
|
77838
77867
|
const sources = parsed.groundingSources;
|
|
@@ -77865,26 +77894,27 @@ var IntelligenceService = class {
|
|
|
77865
77894
|
* Returns the analysis result for the coordinator to inspect (e.g. for webhook dispatch).
|
|
77866
77895
|
*/
|
|
77867
77896
|
analyzeAndPersist(runId, projectId) {
|
|
77897
|
+
const currentRunRecord = this.db.select().from(runs).where(and50(eq61(runs.id, runId), ...analyzableRunPredicates(projectId))).get();
|
|
77898
|
+
if (!currentRunRecord) {
|
|
77899
|
+
log.info("intelligence.skip", { runId, reason: "run not eligible for analysis" });
|
|
77900
|
+
return null;
|
|
77901
|
+
}
|
|
77902
|
+
const currentLocation = currentRunRecord.location ?? null;
|
|
77868
77903
|
const recentRuns = this.db.select().from(runs).where(
|
|
77869
77904
|
and50(
|
|
77870
|
-
|
|
77871
|
-
|
|
77872
|
-
//
|
|
77873
|
-
//
|
|
77874
|
-
//
|
|
77875
|
-
|
|
77876
|
-
|
|
77905
|
+
...analyzableRunPredicates(projectId),
|
|
77906
|
+
currentLocation === null ? isNull8(runs.location) : eq61(runs.location, currentLocation),
|
|
77907
|
+
// Never look forward. The window is this run's own past, so
|
|
77908
|
+
// re-analyzing a historical run compares it against its true
|
|
77909
|
+
// predecessor rather than against sweeps that came after it.
|
|
77910
|
+
lte12(runChronologyKey, currentRunRecord.finishedAt ?? currentRunRecord.createdAt),
|
|
77911
|
+
// MEASUREMENT: only runs that actually wrote snapshots — see above.
|
|
77912
|
+
// Index seek on idx_snapshots_run, short-circuited on the first row.
|
|
77913
|
+
exists(
|
|
77914
|
+
this.db.select({ one: sql25`1` }).from(querySnapshots).where(eq61(querySnapshots.runId, runs.id))
|
|
77915
|
+
)
|
|
77877
77916
|
)
|
|
77878
77917
|
).orderBy(desc27(runs.finishedAt), desc27(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
|
|
77879
|
-
if (recentRuns.length === 0) {
|
|
77880
|
-
log.info("intelligence.skip", { runId, reason: "no completed runs" });
|
|
77881
|
-
return null;
|
|
77882
|
-
}
|
|
77883
|
-
const currentRunRecord = recentRuns.find((r) => r.id === runId);
|
|
77884
|
-
if (!currentRunRecord) {
|
|
77885
|
-
log.info("intelligence.skip", { runId, reason: "run not in recent completed list" });
|
|
77886
|
-
return null;
|
|
77887
|
-
}
|
|
77888
77918
|
const trackedCompetitors = this.loadTrackedCompetitors(projectId);
|
|
77889
77919
|
const currentRun = this.buildRunData(
|
|
77890
77920
|
runId,
|
|
@@ -77898,10 +77928,8 @@ var IntelligenceService = class {
|
|
|
77898
77928
|
return null;
|
|
77899
77929
|
}
|
|
77900
77930
|
const orderedRecent = [...recentRuns].reverse();
|
|
77901
|
-
const
|
|
77902
|
-
const
|
|
77903
|
-
const currentLocIdx = sameLocationOrdered.findIndex((r) => r.id === runId);
|
|
77904
|
-
const previousRunRecord = currentLocIdx > 0 ? sameLocationOrdered[currentLocIdx - 1] : null;
|
|
77931
|
+
const currentIdx = orderedRecent.findIndex((r) => r.id === runId);
|
|
77932
|
+
const previousRunRecord = currentIdx > 0 ? orderedRecent[currentIdx - 1] : null;
|
|
77905
77933
|
const previousRun = previousRunRecord ? this.buildRunData(
|
|
77906
77934
|
previousRunRecord.id,
|
|
77907
77935
|
projectId,
|
|
@@ -77909,7 +77937,7 @@ var IntelligenceService = class {
|
|
|
77909
77937
|
previousRunRecord.location ?? null,
|
|
77910
77938
|
trackedCompetitors
|
|
77911
77939
|
) : null;
|
|
77912
|
-
const history =
|
|
77940
|
+
const history = orderedRecent.slice(0, currentIdx + 1).map((r) => r.id === runId ? currentRun : this.buildRunData(
|
|
77913
77941
|
r.id,
|
|
77914
77942
|
projectId,
|
|
77915
77943
|
r.finishedAt ?? r.createdAt,
|
|
@@ -78184,14 +78212,9 @@ var IntelligenceService = class {
|
|
|
78184
78212
|
}
|
|
78185
78213
|
sinceTimestamp = parsed;
|
|
78186
78214
|
}
|
|
78187
|
-
const allRuns = this.db.select().from(runs).where(
|
|
78188
|
-
|
|
78189
|
-
|
|
78190
|
-
or14(eq61(runs.status, "completed"), eq61(runs.status, "partial")),
|
|
78191
|
-
// Backfill must not replay probe runs as if they were real sweeps.
|
|
78192
|
-
ne8(runs.trigger, RunTriggers.probe)
|
|
78193
|
-
)
|
|
78194
|
-
).orderBy(asc11(runs.finishedAt)).all();
|
|
78215
|
+
const allRuns = this.db.select().from(runs).where(and50(...analyzableRunPredicates(project.id))).orderBy(asc11(runs.finishedAt)).all();
|
|
78216
|
+
const measuredRunIds = this.runIdsWithSnapshots(allRuns.map((r) => r.id));
|
|
78217
|
+
const measuredRuns = allRuns.filter((r) => measuredRunIds.has(r.id));
|
|
78195
78218
|
let startIdx = 0;
|
|
78196
78219
|
let endIdx = allRuns.length;
|
|
78197
78220
|
if (opts?.fromRunId) {
|
|
@@ -78229,7 +78252,7 @@ var IntelligenceService = class {
|
|
|
78229
78252
|
for (let i = 0; i < targetRuns.length; i++) {
|
|
78230
78253
|
const run = targetRuns[i];
|
|
78231
78254
|
const runLocation = run.location ?? null;
|
|
78232
|
-
const sameLocationRuns =
|
|
78255
|
+
const sameLocationRuns = measuredRuns.filter((r) => (r.location ?? null) === runLocation);
|
|
78233
78256
|
const sameLocIdx = sameLocationRuns.indexOf(run);
|
|
78234
78257
|
const previousRun = sameLocIdx > 0 ? sameLocationRuns[sameLocIdx - 1] : null;
|
|
78235
78258
|
const historyStart = Math.max(0, sameLocIdx - (HISTORY_WINDOW_RUNS - 1));
|
|
@@ -78268,6 +78291,16 @@ var IntelligenceService = class {
|
|
|
78268
78291
|
loadTrackedCompetitors(projectId) {
|
|
78269
78292
|
return this.db.select({ domain: competitors.domain }).from(competitors).where(eq61(competitors.projectId, projectId)).all().map((r) => r.domain);
|
|
78270
78293
|
}
|
|
78294
|
+
/**
|
|
78295
|
+
* Which of `runIds` actually wrote query snapshots. One grouped read, so
|
|
78296
|
+
* callers can drop the runs that measured nothing before paying to build
|
|
78297
|
+
* RunData for them. An empty input returns an empty set without querying.
|
|
78298
|
+
*/
|
|
78299
|
+
runIdsWithSnapshots(runIds) {
|
|
78300
|
+
if (runIds.length === 0) return /* @__PURE__ */ new Set();
|
|
78301
|
+
const rows = this.db.select({ runId: querySnapshots.runId }).from(querySnapshots).where(inArray21(querySnapshots.runId, [...runIds])).groupBy(querySnapshots.runId).all();
|
|
78302
|
+
return new Set(rows.map((r) => r.runId));
|
|
78303
|
+
}
|
|
78271
78304
|
/**
|
|
78272
78305
|
* Wipe transition signals from an analysis result while keeping health.
|
|
78273
78306
|
* Used when there's no baseline (first run) to avoid emitting false transitions.
|
|
@@ -145,7 +145,7 @@ import {
|
|
|
145
145
|
siteCrawlSnapshots,
|
|
146
146
|
toAlertView,
|
|
147
147
|
usageCounters
|
|
148
|
-
} from "./chunk-
|
|
148
|
+
} from "./chunk-5EH666EO.js";
|
|
149
149
|
import {
|
|
150
150
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
151
151
|
AGENT_PROVIDER_IDS,
|
|
@@ -11839,7 +11839,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
11839
11839
|
return result;
|
|
11840
11840
|
}
|
|
11841
11841
|
async function backfillInsightsCommand(project, opts) {
|
|
11842
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
11842
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-CHZGHFGS.js");
|
|
11843
11843
|
const config = loadConfig();
|
|
11844
11844
|
const db = createClient(config.database);
|
|
11845
11845
|
migrate(db);
|
package/dist/cli.js
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
showFirstRunNotice,
|
|
27
27
|
trackCliCommandFinished,
|
|
28
28
|
trackEvent
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-7ABFIVKO.js";
|
|
30
30
|
import {
|
|
31
31
|
autoSyncSkills,
|
|
32
32
|
formatAutoSyncNotice
|
|
@@ -69,7 +69,7 @@ import {
|
|
|
69
69
|
projects,
|
|
70
70
|
queries,
|
|
71
71
|
renderReportHtml
|
|
72
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-5EH666EO.js";
|
|
73
73
|
import {
|
|
74
74
|
AdsDeliverySnapshotStatuses,
|
|
75
75
|
AdsHistoricalCampaignRollupStatuses,
|
package/dist/index.js
CHANGED
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
createGoogleMarketingCredentialStore,
|
|
4
4
|
createGoogleMarketingRuntime,
|
|
5
5
|
createServer
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-7ABFIVKO.js";
|
|
7
7
|
import {
|
|
8
8
|
loadConfig
|
|
9
9
|
} from "./chunk-6XUWDSLN.js";
|
|
10
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-5EH666EO.js";
|
|
11
11
|
import "./chunk-PQT3ECM3.js";
|
|
12
12
|
export {
|
|
13
13
|
GoogleMarketingRuntimeError,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonry/canonry",
|
|
3
|
-
"version": "4.177.
|
|
3
|
+
"version": "4.177.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Self-hosted AI visibility (AEO) platform: track how ChatGPT, Claude, Gemini, and Perplexity cite your domain, join it with Search Console, GA4, server-side traffic, and paid media, and fix what you find through agent tools (CLI, REST, MCP). Local SQLite.",
|
|
6
6
|
"license": "FSL-1.1-ALv2",
|
|
@@ -70,27 +70,27 @@
|
|
|
70
70
|
"@types/node-cron": "^3.0.11",
|
|
71
71
|
"tsup": "^8.5.1",
|
|
72
72
|
"tsx": "^4.19.0",
|
|
73
|
+
"@ainyc/canonry-api-client": "0.0.0",
|
|
73
74
|
"@ainyc/canonry-api-routes": "0.0.0",
|
|
74
|
-
"@ainyc/canonry-contracts": "0.0.0",
|
|
75
75
|
"@ainyc/canonry-config": "0.0.0",
|
|
76
|
-
"@ainyc/canonry-api-client": "0.0.0",
|
|
77
76
|
"@ainyc/canonry-db": "0.0.0",
|
|
77
|
+
"@ainyc/canonry-contracts": "0.0.0",
|
|
78
78
|
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
79
79
|
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
80
80
|
"@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
|
|
81
81
|
"@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
|
|
82
82
|
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
83
|
-
"@ainyc/canonry-integration-google": "0.0.0",
|
|
84
83
|
"@ainyc/canonry-integration-google-ads": "0.0.0",
|
|
85
|
-
"@ainyc/canonry-integration-google-places": "0.0.0",
|
|
86
84
|
"@ainyc/canonry-integration-google-business-profile": "0.0.0",
|
|
85
|
+
"@ainyc/canonry-integration-google": "0.0.0",
|
|
86
|
+
"@ainyc/canonry-integration-google-places": "0.0.0",
|
|
87
87
|
"@ainyc/canonry-integration-google-tag-manager": "0.0.0",
|
|
88
|
-
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
89
|
-
"@ainyc/canonry-integration-openai-ads": "0.0.0",
|
|
90
88
|
"@ainyc/canonry-integration-traffic": "0.0.0",
|
|
91
|
-
"@ainyc/canonry-
|
|
92
|
-
"@ainyc/canonry-
|
|
89
|
+
"@ainyc/canonry-integration-openai-ads": "0.0.0",
|
|
90
|
+
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
93
91
|
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
92
|
+
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
93
|
+
"@ainyc/canonry-intelligence": "0.0.0",
|
|
94
94
|
"@ainyc/canonry-provider-local": "0.0.0",
|
|
95
95
|
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
96
96
|
"@ainyc/canonry-provider-claude": "0.0.0",
|