@canonry/canonry 7.65.0 → 7.66.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/agent-workspace/skills/aero/references/seo-analysis.md +15 -1
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +1 -1
- package/assets/assets/{AgentSetupPage-DPHRo4OR.js → AgentSetupPage-DXFxOJ1W.js} +1 -1
- package/assets/assets/{AuditHistoryPanel-qhIB8KRK.js → AuditHistoryPanel-CS8UfTHc.js} +1 -1
- package/assets/assets/{BacklinksPage-iWrZ0-Pp.js → BacklinksPage-D5IA2tNY.js} +1 -1
- package/assets/assets/{HistoryPage-BP1_GGlT.js → HistoryPage-DsQAUkgR.js} +1 -1
- package/assets/assets/MeasurementPropertyPage-CdCnjCaS.js +4 -0
- package/assets/assets/ProjectPage-D3oFKnBG.js +9 -0
- package/assets/assets/{RunsPage-Cc98Px3T.js → RunsPage-BgjN1N3t.js} +1 -1
- package/assets/assets/{SettingsPage-CMyCE-H2.js → SettingsPage-BQuqnEvm.js} +1 -1
- package/assets/assets/SiteHealthSection-C6QE9xNk.js +2 -0
- package/assets/assets/ToneBadge-DNva5_AM.js +60 -0
- package/assets/assets/{TrafficPage-DAef3d2A.js → TrafficPage-BBqpvwl5.js} +2 -2
- package/assets/assets/{TrafficSourceDetailPage-CdoL5Dt9.js → TrafficSourceDetailPage-uxd0ojry.js} +1 -1
- package/assets/assets/{account-context-CWRd3KeS.js → account-context-wRthfm6K.js} +1 -1
- package/assets/assets/{card-Cb_fM36G.js → card-Bq64-UB5.js} +1 -1
- package/assets/assets/{extract-error-message-DSIAoHTR.js → extract-error-message-BRDc8As2.js} +1 -1
- package/assets/assets/{format-helpers-CxNDa1oa.js → format-helpers-D5wFtgg2.js} +1 -1
- package/assets/assets/{index-Da8t1_vu.css → index-C6U4u18w.css} +1 -1
- package/assets/assets/index-Ttz5_bL3.js +60 -0
- package/assets/assets/{query-client-DLdAMlsX.js → query-client-IopmAOx0.js} +1 -1
- package/assets/assets/react-query-CISHjSch.js +1 -0
- package/assets/assets/{react-sigma_core.esm.min-DgK5sRqv.js → react-sigma_core.esm.min-d72rX0Uo.js} +1 -1
- package/assets/assets/{service-Dv9FDyFY.js → service-DsDxSQuN.js} +1 -1
- package/assets/index.html +8 -8
- package/dist/{ads-D4WQOT5K.js → ads-IFKZU7SC.js} +3 -3
- package/dist/{agent-E3IYC7OJ.js → agent-3KMENHL3.js} +7 -7
- package/dist/{agent-ask-ZSKRPB2Z.js → agent-ask-7WVQLLD5.js} +3 -3
- package/dist/{agent-conversations-YHPYPQE2.js → agent-conversations-5LR67TBD.js} +3 -3
- package/dist/{agent-memory-6OWOOD5O.js → agent-memory-GKUCCKVX.js} +3 -3
- package/dist/{agent-providers-7KZK37UO.js → agent-providers-TVCRM3YZ.js} +3 -3
- package/dist/{agent-transcript-3O7GPML6.js → agent-transcript-MUTCOOWG.js} +3 -3
- package/dist/{analytics-P2LLIUYK.js → analytics-2IXSG5ML.js} +3 -3
- package/dist/{apply-VBNNJOCT.js → apply-7RIYK4VC.js} +3 -3
- package/dist/{backfill-F5SSURCH.js → backfill-VU3FV6DQ.js} +9 -9
- package/dist/{backlinks-4RE3TNNA.js → backlinks-DLSRYEX2.js} +3 -3
- package/dist/{bing-V6UN7D44.js → bing-4CXTZUH4.js} +3 -3
- package/dist/{bootstrap-SHTSPAI5.js → bootstrap-OOHSBSDQ.js} +10 -10
- package/dist/{cdp-GDL2VFI3.js → cdp-Y53SRSRV.js} +7 -7
- package/dist/{chunk-ZWZOQNLD.js → chunk-2YBEI6LE.js} +1 -1
- package/dist/{chunk-SL3Y7IOX.js → chunk-6ASEI24F.js} +1 -1
- package/dist/{chunk-HI2HCYLB.js → chunk-743NYXIK.js} +2 -2
- package/dist/{chunk-BV6NXSAH.js → chunk-ALQEGGYM.js} +21 -3
- package/dist/{chunk-GQXSZE2Z.js → chunk-AVQQRTMD.js} +146 -39
- package/dist/{chunk-Q2WZAD5W.js → chunk-BCRRAH5C.js} +1 -1
- package/dist/{chunk-ZPRJCSSR.js → chunk-CKTA5374.js} +1 -1
- package/dist/{chunk-GZPGDAAF.js → chunk-DE2UI37M.js} +2 -2
- package/dist/{chunk-2Y7RN5HG.js → chunk-DH5QRKFW.js} +3 -3
- package/dist/{chunk-CRKOGLKJ.js → chunk-DVYS2RMM.js} +4 -4
- package/dist/{chunk-F7NZEUXP.js → chunk-EIMXB6UH.js} +14 -2
- package/dist/{chunk-C2PYKUD3.js → chunk-EYWC24VC.js} +1 -1
- package/dist/{chunk-2AROR73F.js → chunk-FG7KMVA6.js} +5 -5
- package/dist/{chunk-QNRMYDK6.js → chunk-FNI3QSVD.js} +1 -1
- package/dist/{chunk-H6HIJAIU.js → chunk-H37CDAZX.js} +2 -2
- package/dist/{chunk-XMKXXWMO.js → chunk-IHOVPHUW.js} +2 -2
- package/dist/{chunk-M3OZL6PQ.js → chunk-IPF2S5G2.js} +4 -4
- package/dist/{chunk-3QKKHU7D.js → chunk-J5YZXRD5.js} +8 -8
- package/dist/{chunk-XOMUSIM7.js → chunk-JALKQREN.js} +1 -1
- package/dist/{chunk-Z2EENWM3.js → chunk-JYOMHNRP.js} +182 -20
- package/dist/{chunk-66DPEVLJ.js → chunk-K2HDTMRI.js} +3 -3
- package/dist/{chunk-O3INLME6.js → chunk-L7DQYID4.js} +4 -4
- package/dist/{chunk-HO5W6QC7.js → chunk-LBRG6RKI.js} +2 -1
- package/dist/{chunk-HPUZMBS6.js → chunk-OSF27DCY.js} +2 -2
- package/dist/{chunk-737TOG2F.js → chunk-PFKBQTYD.js} +1 -1
- package/dist/{chunk-S4VCXCRQ.js → chunk-QRBZBJ4U.js} +1 -1
- package/dist/{chunk-X4AHYJCE.js → chunk-SGZWOUIZ.js} +5 -5
- package/dist/{chunk-AE6HJZBU.js → chunk-TJZLICMB.js} +3 -3
- package/dist/{chunk-GM5ZOWBK.js → chunk-VGUZPC3I.js} +4 -4
- package/dist/{chunk-DQVJB6CZ.js → chunk-WNCOBSPN.js} +82 -8
- package/dist/{chunk-LTX3TJ3E.js → chunk-XC44Q5XR.js} +1 -1
- package/dist/{chunk-IYB3EGSD.js → chunk-YJYLFIVT.js} +1 -1
- package/dist/{chunk-IJKKK7F4.js → chunk-YRX5UOM3.js} +1 -1
- package/dist/{chunk-6FN2OQKL.js → chunk-ZRSNMEB3.js} +364 -336
- package/dist/{citations-GO4SPZYS.js → citations-ZTQIYYDB.js} +3 -3
- package/dist/{cli-catalog-6XYQY3PI.js → cli-catalog-IZJXAHFQ.js} +3 -3
- package/dist/{cli-commands-AMURHC27.js → cli-commands-XYM2VMAK.js} +7 -7
- package/dist/cli.js +13 -13
- package/dist/{competitor-JBTKRJ6Y.js → competitor-SIXAPHOM.js} +3 -3
- package/dist/{content-LKXXQ7X4.js → content-XPS3D3Y6.js} +3 -3
- package/dist/{dashboard-LUA7UX6B.js → dashboard-FMJDM5PA.js} +6 -6
- package/dist/{demo-I7JDZJUW.js → demo-QQKSHTPO.js} +1 -1
- package/dist/{demo-server-GNGGWERV.js → demo-server-LQ3QXUAS.js} +71 -11
- package/dist/{discover-TODAYYJ2.js → discover-K5HFEU2T.js} +3 -3
- package/dist/{discover-eval-FWQESEYB.js → discover-eval-6536Z2YN.js} +3 -3
- package/dist/{doctor-AXNJT2UC.js → doctor-STNH3PHH.js} +3 -3
- package/dist/{evidence-2XGEY4ZC.js → evidence-W2EWKBAV.js} +3 -3
- package/dist/{export-cmd-7RMYBIBZ.js → export-cmd-EGHMY3M6.js} +3 -3
- package/dist/{ga-HZDIZGCO.js → ga-DVIG3PVW.js} +3 -3
- package/dist/{gbp-SMRBWJEL.js → gbp-SNS4XZBO.js} +3 -3
- package/dist/{google-2CN345XS.js → google-KGPHZG6E.js} +4 -4
- package/dist/{google-marketing-W7T2VTVW.js → google-marketing-VZR6W3L6.js} +1 -1
- package/dist/{health-cmd-W7OWZ4ZI.js → health-cmd-2KWO2GOM.js} +3 -3
- package/dist/{history-UVNCBHTE.js → history-ZY27B7JE.js} +3 -3
- package/dist/index.d.ts +236 -161
- package/dist/index.js +24 -24
- package/dist/{init-MDGOTK2L.js → init-5TXIKII2.js} +11 -11
- package/dist/{insights-PQ52G5LU.js → insights-MUYEOSOG.js} +3 -3
- package/dist/intelligence-service-U4NSGZUH.js +15 -0
- package/dist/{journeys-3PP5GYVJ.js → journeys-YO54IRVC.js} +3 -3
- package/dist/{keys-CWZUX7IQ.js → keys-M4QZGYVJ.js} +3 -3
- package/dist/{keyword-5OMMULLY.js → keyword-MU62MGWT.js} +3 -3
- package/dist/{logs-3FTE42E3.js → logs-BAQM334Q.js} +3 -3
- package/dist/{mcp-FK5DTI7I.js → mcp-LYS5P3PB.js} +2 -2
- package/dist/mcp.js +9 -9
- package/dist/{measurement-plan-ICO2BQNT.js → measurement-plan-HVSTZWTW.js} +5 -5
- package/dist/{notify-ML4PYD3L.js → notify-XSP5JXU3.js} +3 -3
- package/dist/{organic-evidence-4U4H3DCR.js → organic-evidence-4HYW4PVO.js} +3 -3
- package/dist/{overview-NF427VFO.js → overview-XH72WTZT.js} +4 -4
- package/dist/{project-AAO65OF7.js → project-UKH7FTQX.js} +3 -3
- package/dist/{query-7ZOUQNF5.js → query-RRD5M4QV.js} +3 -3
- package/dist/{research-UQ7AF5N3.js → research-V2IGDUKL.js} +3 -3
- package/dist/{results-export-CZCLC4ZH.js → results-export-5FC7ZT6O.js} +3 -3
- package/dist/{run-4XJZW52R.js → run-B2A4LMWU.js} +4 -4
- package/dist/{schedule-D3E5FDBE.js → schedule-PLY6CHQT.js} +3 -3
- package/dist/{search-DYET2IL4.js → search-ACVK3WH5.js} +3 -3
- package/dist/{serve-R2CI3RWQ.js → serve-2LTQZPEK.js} +25 -25
- package/dist/{session-7CW2V5EH.js → session-IHXMPETX.js} +12 -12
- package/dist/{settings-ZISHACFX.js → settings-ZHBNX2I5.js} +3 -3
- package/dist/{skills-WU6IN3NL.js → skills-KWRTCXTF.js} +2 -2
- package/dist/{snapshot-KCKQDJ2W.js → snapshot-GBDCCV6L.js} +3 -3
- package/dist/{src-WIOU5G4V.js → src-NOTQWGYV.js} +2 -2
- package/dist/{status-CYZFVMYA.js → status-FGDDGBMJ.js} +4 -4
- package/dist/{technical-aeo-P5CS22AR.js → technical-aeo-AK4QI63Q.js} +3 -3
- package/dist/{telemetry-LPK5F2WU.js → telemetry-6QPWYMCR.js} +4 -4
- package/dist/{tools-E3B2GZ6Q.js → tools-PBNTVVN7.js} +11 -11
- package/dist/{traffic-5KZYXXIQ.js → traffic-AS4PIOQL.js} +5 -5
- package/dist/{users-2F346HIW.js → users-BNYKF35J.js} +3 -3
- package/dist/{visibility-stats-ITA6DNF3.js → visibility-stats-TWL3GJMD.js} +3 -3
- package/dist/{wordpress-ZHCFT6V3.js → wordpress-XBWXAACD.js} +3 -3
- package/package.json +1 -1
- package/assets/assets/MeasurementPropertyPage-DfPHZNGo.js +0 -4
- package/assets/assets/ProjectPage-D4n2oqPP.js +0 -9
- package/assets/assets/SiteHealthSection-CGKKUtqQ.js +0 -2
- package/assets/assets/ToneBadge-CJ6ObMRe.js +0 -60
- package/assets/assets/index-DEXKV7TW.js +0 -60
- package/assets/assets/react-query-BktZZtSC.js +0 -1
- package/dist/intelligence-service-IAUGKMDO.js +0 -15
|
@@ -2,13 +2,13 @@ import {
|
|
|
2
2
|
bucketCliCommandDuration,
|
|
3
3
|
isTelemetryEnabled,
|
|
4
4
|
trackEvent
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-IHOVPHUW.js";
|
|
6
6
|
import {
|
|
7
7
|
getConfigDir
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-EYWC24VC.js";
|
|
9
9
|
import {
|
|
10
10
|
currentOutcomeAttribution
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-YRX5UOM3.js";
|
|
12
12
|
import {
|
|
13
13
|
AGENT_NONE,
|
|
14
14
|
OutcomeSurfaces,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
integrationConnectionPropertiesSchema,
|
|
20
20
|
normalizeAgentSlug,
|
|
21
21
|
usageSurfaceSchema
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-WNCOBSPN.js";
|
|
23
23
|
|
|
24
24
|
// src/outcome-telemetry.ts
|
|
25
25
|
import fs from "fs";
|
|
@@ -7,27 +7,27 @@ import {
|
|
|
7
7
|
reparseStoredResult4,
|
|
8
8
|
reparseStoredResult5,
|
|
9
9
|
resolveStoredApiResponse
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-OSF27DCY.js";
|
|
11
11
|
import {
|
|
12
12
|
loadConfig
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-EYWC24VC.js";
|
|
14
14
|
import {
|
|
15
15
|
isMachineFormat
|
|
16
16
|
} from "./chunk-HGMYEDAQ.js";
|
|
17
17
|
import {
|
|
18
18
|
determineCitationState
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-QRBZBJ4U.js";
|
|
20
20
|
import {
|
|
21
21
|
backfillProjectAnswerMentions,
|
|
22
22
|
competitorIdentityColumns,
|
|
23
23
|
createRunCompetitorResolver,
|
|
24
24
|
readMarketCompetitorNames,
|
|
25
25
|
refreshSnapshotMentionIdentity
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-JYOMHNRP.js";
|
|
27
27
|
import {
|
|
28
28
|
classifyAiUserFetch,
|
|
29
29
|
classifyCrawler
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-BCRRAH5C.js";
|
|
31
31
|
import {
|
|
32
32
|
aiUserFetchEventsHourly,
|
|
33
33
|
auditLog,
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
querySnapshots,
|
|
43
43
|
rawEventSamples,
|
|
44
44
|
runs
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-JALKQREN.js";
|
|
46
46
|
import {
|
|
47
47
|
ProviderNames,
|
|
48
48
|
RetrievalContracts,
|
|
@@ -59,7 +59,7 @@ import {
|
|
|
59
59
|
extractRecommendedCompetitors,
|
|
60
60
|
normalizeUrlPath,
|
|
61
61
|
openRouterRouteModel
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-WNCOBSPN.js";
|
|
63
63
|
|
|
64
64
|
// src/commands/backfill.ts
|
|
65
65
|
import { setImmediate as yieldToEventLoop } from "timers/promises";
|
|
@@ -437,7 +437,7 @@ No DB writes performed. Re-run without --dry-run to apply.`);
|
|
|
437
437
|
}
|
|
438
438
|
}
|
|
439
439
|
async function backfillInsightsCommand(project, opts) {
|
|
440
|
-
const { IntelligenceService } = await import("./intelligence-service-
|
|
440
|
+
const { IntelligenceService } = await import("./intelligence-service-U4NSGZUH.js");
|
|
441
441
|
const config = loadConfig();
|
|
442
442
|
const db = createClient(config.database);
|
|
443
443
|
migrate(db);
|
|
@@ -9,14 +9,14 @@ import {
|
|
|
9
9
|
normalizeCloudflareWorkerEvent,
|
|
10
10
|
sessionizeJourneyRequests,
|
|
11
11
|
verifyRequestSignature
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-BCRRAH5C.js";
|
|
13
13
|
import {
|
|
14
14
|
currentOutcomeAttribution,
|
|
15
15
|
getRequestContext,
|
|
16
16
|
registerRequestContext,
|
|
17
17
|
requestAuditIdentity,
|
|
18
18
|
sanitizeRequestContext
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-YRX5UOM3.js";
|
|
20
20
|
import {
|
|
21
21
|
SentimentIdempotencyConflict,
|
|
22
22
|
SentimentRepository,
|
|
@@ -154,7 +154,7 @@ import {
|
|
|
154
154
|
userSessions,
|
|
155
155
|
users,
|
|
156
156
|
writeNegativeReviewMaxStars
|
|
157
|
-
} from "./chunk-
|
|
157
|
+
} from "./chunk-JALKQREN.js";
|
|
158
158
|
import {
|
|
159
159
|
ADS_ACTIVATE_SCOPE,
|
|
160
160
|
ADS_ACTIVATION_ABSOLUTE_MAX_ENTITIES,
|
|
@@ -338,6 +338,8 @@ import {
|
|
|
338
338
|
SENTIMENT_ATTEMPT_PAGE_DEFAULT,
|
|
339
339
|
SENTIMENT_INTERVAL_LIMITATION,
|
|
340
340
|
SENTIMENT_QUERY_PAGE_DEFAULT,
|
|
341
|
+
SEO_MARKET_RANKING_MAX_SEARCHES,
|
|
342
|
+
SEO_PROJECT_MARKET_KEY,
|
|
341
343
|
SEO_RUN_SCOPE,
|
|
342
344
|
SEO_WRITE_SCOPE,
|
|
343
345
|
SITE_AUDIT_ISSUES_DEFAULT_LIMIT,
|
|
@@ -949,6 +951,7 @@ import {
|
|
|
949
951
|
providersFailing,
|
|
950
952
|
qualifiedNameIncludesName,
|
|
951
953
|
queryClassFilterSchema,
|
|
954
|
+
queryClassSchema,
|
|
952
955
|
queryDtoSchema,
|
|
953
956
|
queryGenerateRequestSchema,
|
|
954
957
|
queryGenerateResponseSchema,
|
|
@@ -1042,6 +1045,8 @@ import {
|
|
|
1042
1045
|
seoDomainIntelligenceListSchema,
|
|
1043
1046
|
seoEvidenceQuerySchema,
|
|
1044
1047
|
seoKeywordMetricsListSchema,
|
|
1048
|
+
seoMarketRankingListSchema,
|
|
1049
|
+
seoMarketRankingQuerySchema,
|
|
1045
1050
|
seoOperatorTaskSchema,
|
|
1046
1051
|
seoOverviewSchema,
|
|
1047
1052
|
seoProfileInputSchema,
|
|
@@ -1055,6 +1060,7 @@ import {
|
|
|
1055
1060
|
seoRunDetailSchema,
|
|
1056
1061
|
seoRunListSchema,
|
|
1057
1062
|
seoRunSchema,
|
|
1063
|
+
seoSearchTextForQuery,
|
|
1058
1064
|
seoStatusSchema,
|
|
1059
1065
|
seoSyncInputSchema,
|
|
1060
1066
|
serializeRunError,
|
|
@@ -1167,7 +1173,7 @@ import {
|
|
|
1167
1173
|
wordpressSchemaDeployResultDtoSchema,
|
|
1168
1174
|
wordpressSchemaStatusResultDtoSchema,
|
|
1169
1175
|
wordpressStatusDtoSchema
|
|
1170
|
-
} from "./chunk-
|
|
1176
|
+
} from "./chunk-WNCOBSPN.js";
|
|
1171
1177
|
|
|
1172
1178
|
// ../api-routes/src/user-session.ts
|
|
1173
1179
|
import crypto2 from "crypto";
|
|
@@ -15214,7 +15220,10 @@ function runDto(job, counts2) {
|
|
|
15214
15220
|
return { id: job.id, projectId: job.projectKey, profileId: job.profileId, profileRevision: job.profileRevision, locationCode: job.profile.locationCode, languageCode: job.profile.languageCode, cancelRequested: job.cancelRequested, status: job.status, capabilities: job.profile.capabilities, scope: job.profile.scope, taskCount: counts2.reduce((sum, group) => sum + group.count, 0), completedTasks: counts2.filter((group) => group.status === SeoRunStatuses.completed || group.status === SeoRunStatuses.partial).reduce((sum, group) => sum + group.count, 0), failedTasks: counts2.filter((group) => group.status === SeoRunStatuses.failed).reduce((sum, group) => sum + group.count, 0), createdAt: job.createdAt, startedAt: job.startedAt, finishedAt: job.finishedAt, error: job.error };
|
|
15215
15221
|
}
|
|
15216
15222
|
function resultRows(tasks) {
|
|
15217
|
-
return tasks.filter((task) => task.reconciliation?.outcome !== SeoAdapterOutcomes.failed).reduce((results, task) => ({ keywordMetrics: results.keywordMetrics.concat(task.results.keywordMetrics), domainIntelligence: results.domainIntelligence.concat(task.results.domainIntelligence), rankedKeywords: results.rankedKeywords.concat(task.results.rankedKeywords) }), emptySeoResults());
|
|
15223
|
+
return tasks.filter((task) => task.reconciliation?.outcome !== SeoAdapterOutcomes.failed).reduce((results, task) => ({ keywordMetrics: results.keywordMetrics.concat(task.results.keywordMetrics), domainIntelligence: results.domainIntelligence.concat(task.results.domainIntelligence), rankedKeywords: results.rankedKeywords.concat(task.results.rankedKeywords), marketRankings: results.marketRankings.concat(task.results.marketRankings ?? []) }), emptySeoResults());
|
|
15224
|
+
}
|
|
15225
|
+
function compareMarketRankings(a, b) {
|
|
15226
|
+
return compareText(a.regionLabel ?? "", b.regionLabel ?? "") || compareText(a.marketLabel, b.marketLabel) || compareText(a.searchText, b.searchText) || compareText(a.queryText, b.queryText);
|
|
15218
15227
|
}
|
|
15219
15228
|
function scopedPlanTargets(plan, scope) {
|
|
15220
15229
|
const market = scope.kind === SeoScopeKinds.market ? plan.reportingScopes?.find((market2) => market2.stableKey === scope.key) : void 0;
|
|
@@ -15222,9 +15231,14 @@ function scopedPlanTargets(plan, scope) {
|
|
|
15222
15231
|
return { targets: plan.targets.filter((target) => targetKeys.includes(target.stableKey)), market, targetKeys };
|
|
15223
15232
|
}
|
|
15224
15233
|
function scopeCapabilities(kind, targets) {
|
|
15225
|
-
if (kind === SeoScopeKinds.project) return [SeoCapabilities["keyword-metrics"], SeoCapabilities["domain-intelligence"], SeoCapabilities["ranked-keywords"]];
|
|
15234
|
+
if (kind === SeoScopeKinds.project) return [SeoCapabilities["keyword-metrics"], SeoCapabilities["domain-intelligence"], SeoCapabilities["ranked-keywords"], SeoCapabilities["market-rankings"]];
|
|
15226
15235
|
const exactHosts = targets.length > 0 && targets.every((target) => target.urlMatchers.length > 0 && target.urlMatchers.every((matcher) => matcher.kind === "host"));
|
|
15227
|
-
|
|
15236
|
+
const markets = kind === SeoScopeKinds.group || kind === SeoScopeKinds.market ? [SeoCapabilities["market-rankings"]] : [];
|
|
15237
|
+
return exactHosts ? [SeoCapabilities["keyword-metrics"], SeoCapabilities["ranked-keywords"], ...markets] : [SeoCapabilities["keyword-metrics"], ...markets];
|
|
15238
|
+
}
|
|
15239
|
+
function scopeReason(capabilities) {
|
|
15240
|
+
const markets = capabilities.includes(SeoCapabilities["market-rankings"]);
|
|
15241
|
+
return capabilities.includes(SeoCapabilities["ranked-keywords"]) ? `Supports explicit keyword metrics${markets ? ", market rankings" : ""} and exact-host ranked keywords. Whole-domain intelligence requires project scope.` : `Supports explicit keyword metrics${markets ? " and market rankings" : " only"}. Path and entity Properties cannot be replaced by whole-domain data.`;
|
|
15228
15242
|
}
|
|
15229
15243
|
function cancelSeoRefresh(db, projectId, id) {
|
|
15230
15244
|
db.transaction((tx) => {
|
|
@@ -15252,7 +15266,7 @@ function resolveScope2(db, projectId, input) {
|
|
|
15252
15266
|
if (supported.includes(SeoCapabilities["ranked-keywords"])) throw validationError("Domain intelligence requires project scope with a whole-domain Target. This Advanced scope supports exact-host ranked keywords and explicit keyword metrics.");
|
|
15253
15267
|
throw validationError("This Advanced scope contains path or entity Properties and supports explicit keyword metrics only. Select a host-only scope for ranked keywords, or project scope for whole-domain intelligence.");
|
|
15254
15268
|
}
|
|
15255
|
-
if (input.scope.kind !== SeoScopeKinds.project && input.capabilities.
|
|
15269
|
+
if (input.scope.kind !== SeoScopeKinds.project && input.capabilities.includes(SeoCapabilities["ranked-keywords"])) {
|
|
15256
15270
|
const matchers = targets.flatMap((target) => target.urlMatchers).filter((matcher) => matcher.kind === "host");
|
|
15257
15271
|
if (input.targets.some((target) => target.kind !== SeoTargetKinds.host || target.includeSubdomains || !matchers.some((matcher) => matchesMeasurementTargetUrl(`https://${target.value}/`, matcher)))) throw validationError("Ranked keywords in this Advanced scope require an exact host Target belonging to a selected Property. Other hosts and broader domains are not supported.");
|
|
15258
15272
|
}
|
|
@@ -15271,12 +15285,128 @@ function normalizeProfile(input) {
|
|
|
15271
15285
|
const keywords = [...new Set(input.keywords)];
|
|
15272
15286
|
if (input.capabilities.includes(SeoCapabilities["keyword-metrics"]) && !keywords.length) throw validationError("Keyword metrics requires at least one explicit keyword.");
|
|
15273
15287
|
if (input.capabilities.some((capability) => capability !== SeoCapabilities["keyword-metrics"]) && !targets.length) throw validationError("Domain capabilities require at least one explicit Target.");
|
|
15288
|
+
if (input.capabilities.includes(SeoCapabilities["market-rankings"])) {
|
|
15289
|
+
if (input.capabilities.length !== 1) throw validationError("Keep market rankings in their own profile: they run one search for each tracked query and market.");
|
|
15290
|
+
if (!input.rankings) throw validationError("Market rankings need search settings: market locations, result depth, device and search text.");
|
|
15291
|
+
if (targets.length !== 1) throw validationError("Market rankings need exactly one Target: the site whose position they report.");
|
|
15292
|
+
if (keywords.length) throw validationError("Market rankings search the project's tracked queries. Leave keywords empty.");
|
|
15293
|
+
const keys = input.rankings.locations.map((location) => location.key);
|
|
15294
|
+
if (new Set(keys).size !== keys.length) throw validationError("Set each market location once.");
|
|
15295
|
+
} else if (input.rankings) {
|
|
15296
|
+
throw validationError("Search settings apply only to market rankings.");
|
|
15297
|
+
}
|
|
15274
15298
|
return { ...input, targets, keywords };
|
|
15275
15299
|
}
|
|
15276
|
-
|
|
15277
|
-
|
|
15300
|
+
var NO_MARKET_RANKINGS = { searches: [] };
|
|
15301
|
+
function advancedMarketQueries(plan, scope) {
|
|
15302
|
+
const groups = new Map(plan.groups.map((group) => [group.stableKey, group]));
|
|
15303
|
+
const lineage = (groupKey) => {
|
|
15304
|
+
const keys = [];
|
|
15305
|
+
for (let key = groupKey; key && !keys.includes(key); key = groups.get(key)?.parentGroupKey) keys.push(key);
|
|
15306
|
+
return keys;
|
|
15307
|
+
};
|
|
15308
|
+
const nonBrandEdge = new Set(plan.assignments.filter((assignment) => assignment.queryClass === queryClassSchema.enum["non-brand"]).map((assignment) => `${assignment.targetKey}\0${assignment.queryId}`));
|
|
15309
|
+
const queryText = new Map(plan.querySnapshots.map((snapshot) => [snapshot.queryId, snapshot.queryText]));
|
|
15310
|
+
const queriesFor = (edges) => {
|
|
15311
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
15312
|
+
for (const edge of edges) nodes.set(edge.queryId, (nodes.get(edge.queryId) ?? /* @__PURE__ */ new Set()).add(edge.executionNodeKey));
|
|
15313
|
+
return [...nodes].flatMap(([id, keys]) => {
|
|
15314
|
+
const text = queryText.get(id);
|
|
15315
|
+
return text ? [{ id, text, executionNodeKeys: [...keys] }] : [];
|
|
15316
|
+
});
|
|
15317
|
+
};
|
|
15318
|
+
const markets = plan.reportingScopes ?? [];
|
|
15319
|
+
if (!markets.length) {
|
|
15320
|
+
const { targetKeys } = scopedPlanTargets(plan, scope);
|
|
15321
|
+
return [{ context: { marketKey: SEO_PROJECT_MARKET_KEY, marketLabel: "Project", regionKey: null, regionLabel: null }, locationKeys: [SEO_PROJECT_MARKET_KEY], queries: queriesFor(plan.assignments.filter((assignment) => targetKeys.includes(assignment.targetKey) && nonBrandEdge.has(`${assignment.targetKey}\0${assignment.queryId}`))) }];
|
|
15322
|
+
}
|
|
15323
|
+
return markets.flatMap((market) => {
|
|
15324
|
+
const chain = lineage(market.groupKey);
|
|
15325
|
+
const inScope = scope.kind === SeoScopeKinds.project || (scope.kind === SeoScopeKinds.market ? market.stableKey === scope.key : scope.kind === SeoScopeKinds.group && chain.includes(scope.key));
|
|
15326
|
+
if (!inScope) return [];
|
|
15327
|
+
const region = chain.at(-1);
|
|
15328
|
+
return [{
|
|
15329
|
+
context: { marketKey: market.stableKey, marketLabel: market.label, regionKey: region ?? null, regionLabel: region ? groups.get(region)?.label ?? region : null },
|
|
15330
|
+
locationKeys: [market.stableKey, ...chain],
|
|
15331
|
+
queries: queriesFor(market.usageEdges.filter((edge) => nonBrandEdge.has(`${edge.targetKey}\0${edge.queryId}`)))
|
|
15332
|
+
}];
|
|
15333
|
+
});
|
|
15278
15334
|
}
|
|
15279
|
-
function
|
|
15335
|
+
function simpleMarketQueries(db, project) {
|
|
15336
|
+
const rows = db.select({ id: queries.id, query: queries.query }).from(queries).where(eq32(queries.projectId, project.id)).all();
|
|
15337
|
+
const classes = readQueryClasses(db, project, rows);
|
|
15338
|
+
if (rows.length > 0 && rows.every((row) => classes.get(row.id) == null)) {
|
|
15339
|
+
throw validationError("Canonry cannot tell this project's branded queries apart without a brand name. Add one before setting up market rankings.");
|
|
15340
|
+
}
|
|
15341
|
+
const nonBrand = rows.filter((row) => classes.get(row.id) === queryClassSchema.enum["non-brand"]).map((row) => ({ id: row.id, text: row.query, executionNodeKeys: [] }));
|
|
15342
|
+
const locations = project.locations.length > 0 ? project.locations.map((location) => ({ key: location.label, label: location.label })) : [{ key: SEO_PROJECT_MARKET_KEY, label: "Project" }];
|
|
15343
|
+
return locations.map((location) => ({ context: { marketKey: location.key, marketLabel: location.label, regionKey: null, regionLabel: null }, locationKeys: [location.key], queries: nonBrand }));
|
|
15344
|
+
}
|
|
15345
|
+
function marketRankingPlan(db, projectId, input) {
|
|
15346
|
+
if (!input.capabilities.includes(SeoCapabilities["market-rankings"]) || !input.rankings) return NO_MARKET_RANKINGS;
|
|
15347
|
+
const settings = input.rankings;
|
|
15348
|
+
const project = db.select().from(projects).where(eq32(projects.id, projectId)).get();
|
|
15349
|
+
if (!project) throw notFound("Project", projectId);
|
|
15350
|
+
const own = new Set(effectiveDomains({ canonicalDomain: project.canonicalDomain, ownedDomains: project.ownedDomains }).map((domain) => registrableDomain(domain)).filter(Boolean));
|
|
15351
|
+
const target = input.targets[0];
|
|
15352
|
+
if (!target || !own.has(registrableDomain(target.value))) throw validationError("The market-rankings Target must be one of this project's own domains.");
|
|
15353
|
+
const active = activeMeasurementPlan(db, projectId);
|
|
15354
|
+
const advanced = active?.plan.schemaVersion === 2 ? { versionId: active.version.id, plan: active.plan } : null;
|
|
15355
|
+
const markets = advanced ? advancedMarketQueries(advanced.plan, input.scope) : simpleMarketQueries(db, project);
|
|
15356
|
+
const rival = (domain) => {
|
|
15357
|
+
const registrable = registrableDomain(normalizeHostname(domain) ?? domain);
|
|
15358
|
+
return registrable && !own.has(registrable) ? [registrable] : [];
|
|
15359
|
+
};
|
|
15360
|
+
const projectCompetitors = db.select({ domain: competitors.domain }).from(competitors).where(eq32(competitors.projectId, projectId)).all().flatMap((row) => rival(row.domain));
|
|
15361
|
+
const pinnedFor = createPlanPinLookup(db);
|
|
15362
|
+
const competitorsFor = (query) => [.../* @__PURE__ */ new Set([...projectCompetitors, ...query.executionNodeKeys.flatMap((key) => pinnedFor(advanced?.versionId, key)).flatMap(rival)])].sort(compareText);
|
|
15363
|
+
const locations = new Map(settings.locations.map((location) => [location.key, location]));
|
|
15364
|
+
const searches = /* @__PURE__ */ new Map();
|
|
15365
|
+
const unmapped = [];
|
|
15366
|
+
for (const market of markets) {
|
|
15367
|
+
const mapped = market.locationKeys.map((key) => locations.get(key)).find((location2) => location2 !== void 0);
|
|
15368
|
+
const location = mapped ?? (market.context.marketKey === SEO_PROJECT_MARKET_KEY ? { locationCode: input.locationCode, label: `Location ${input.locationCode}` } : void 0);
|
|
15369
|
+
if (!location) {
|
|
15370
|
+
unmapped.push(market.context.marketLabel);
|
|
15371
|
+
continue;
|
|
15372
|
+
}
|
|
15373
|
+
for (const query of market.queries) {
|
|
15374
|
+
const searchText = seoSearchTextForQuery(query.text, market.context.marketKey === SEO_PROJECT_MARKET_KEY ? null : market.context.marketLabel, settings.searchText);
|
|
15375
|
+
if (!searchText) continue;
|
|
15376
|
+
const answers = {
|
|
15377
|
+
context: { ...market.context, queryId: query.id, queryText: query.text, queryClass: queryClassSchema.enum["non-brand"] },
|
|
15378
|
+
competitors: competitorsFor(query)
|
|
15379
|
+
};
|
|
15380
|
+
const key = `${location.locationCode}\0${searchText.toLowerCase()}`;
|
|
15381
|
+
const existing = searches.get(key);
|
|
15382
|
+
if (existing) existing.markets.push(answers);
|
|
15383
|
+
else searches.set(key, { searchText, locationCode: location.locationCode, locationLabel: location.label, markets: [answers] });
|
|
15384
|
+
}
|
|
15385
|
+
}
|
|
15386
|
+
if (unmapped.length) throw validationError(`Set a search location for ${unmapped.length === 1 ? "1 market" : `${unmapped.length} markets`} or their groups: ${unmapped.slice(0, 5).join("; ")}${unmapped.length > 5 ? "; \u2026" : ""}.`);
|
|
15387
|
+
if (!searches.size) throw validationError("There are no non-brand tracked queries to search in this scope.");
|
|
15388
|
+
if (searches.size > SEO_MARKET_RANKING_MAX_SEARCHES) throw validationError(`One market-rankings refresh runs at most ${SEO_MARKET_RANKING_MAX_SEARCHES} searches; this scope needs ${searches.size}. Select a narrower scope.`);
|
|
15389
|
+
return { searches: [...searches.values()].sort((a, b) => compareText(a.locationLabel, b.locationLabel) || compareText(a.searchText, b.searchText)) };
|
|
15390
|
+
}
|
|
15391
|
+
function taskRequests(profile, rankings) {
|
|
15392
|
+
return profile.capabilities.flatMap((capability) => {
|
|
15393
|
+
if (capability === SeoCapabilities["market-rankings"]) {
|
|
15394
|
+
const settings = profile.rankings;
|
|
15395
|
+
return rankings.searches.map((search) => ({
|
|
15396
|
+
capability,
|
|
15397
|
+
target: profile.targets[0],
|
|
15398
|
+
keywords: [search.searchText],
|
|
15399
|
+
locationCode: search.locationCode,
|
|
15400
|
+
languageCode: profile.languageCode,
|
|
15401
|
+
limit: profile.limit,
|
|
15402
|
+
tag: "",
|
|
15403
|
+
search: { device: settings.device, depth: settings.depth, locationLabel: search.locationLabel, markets: search.markets }
|
|
15404
|
+
}));
|
|
15405
|
+
}
|
|
15406
|
+
return (capability === SeoCapabilities["keyword-metrics"] ? [null] : profile.targets).map((target) => ({ capability, target, keywords: capability === SeoCapabilities["keyword-metrics"] ? profile.keywords : [], locationCode: profile.locationCode, languageCode: profile.languageCode, limit: profile.limit, tag: "" }));
|
|
15407
|
+
});
|
|
15408
|
+
}
|
|
15409
|
+
function planSeoTasks(opts, input, rankings = NO_MARKET_RANKINGS) {
|
|
15280
15410
|
const runtime = opts.getSeoRuntime?.() ?? null;
|
|
15281
15411
|
const connection = runtime?.connections.find((connection2) => connection2.id === input.connectionId);
|
|
15282
15412
|
if (!runtime || !opts.onSeoRunRequested) throw missingDependency("SEO execution is not configured on this host.");
|
|
@@ -15284,7 +15414,7 @@ function planSeoTasks(opts, input) {
|
|
|
15284
15414
|
if (input.capabilities.some((capability) => !connection.adapter.capabilities.includes(capability))) throw validationError("The selected SEO connection does not support every requested capability. Select capabilities listed for this connection in SEO context.");
|
|
15285
15415
|
let planned;
|
|
15286
15416
|
try {
|
|
15287
|
-
planned = taskRequests(input).map((request) => {
|
|
15417
|
+
planned = taskRequests(input, rankings).map((request) => {
|
|
15288
15418
|
const estimate = connection.adapter.estimate(request);
|
|
15289
15419
|
return { request, reservedMicros: estimate.maxCostMicros, catalogRevision: estimate.catalogRevision };
|
|
15290
15420
|
});
|
|
@@ -15322,7 +15452,7 @@ function startScheduledSeoRefreshes(db, opts, input) {
|
|
|
15322
15452
|
if (prior?.runId) return { profileId: profile.id, status: "replayed", runId: prior.runId };
|
|
15323
15453
|
try {
|
|
15324
15454
|
const scopeSnapshot = resolveScope2(db, input.projectId, profile);
|
|
15325
|
-
const runtime = planSeoTasks(opts, profile);
|
|
15455
|
+
const runtime = planSeoTasks(opts, profile, marketRankingPlan(db, input.projectId, profile));
|
|
15326
15456
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15327
15457
|
const account = runtime.connection.adapter.accountKey;
|
|
15328
15458
|
const requestedId = crypto17.randomUUID();
|
|
@@ -15394,7 +15524,7 @@ async function seoRoutes(app, opts) {
|
|
|
15394
15524
|
const available = !!runtime?.connections.length && !!opts.onSeoRunRequested;
|
|
15395
15525
|
return { available, reason: available ? null : "SEO execution is not configured on this host.", capabilities: [...new Set(runtime?.connections.flatMap((connection) => [...connection.adapter.capabilities]) ?? [])], profileCount: profiles(projectId).length, latestRunId: latest?.id ?? null, latestRunStatus: latest?.status ?? null, lastCompletedAt: last?.finishedAt ?? null };
|
|
15396
15526
|
};
|
|
15397
|
-
const planTasks = (input) => planSeoTasks(opts, input);
|
|
15527
|
+
const planTasks = (projectId, input) => planSeoTasks(opts, input, marketRankingPlan(app.db, projectId, input));
|
|
15398
15528
|
app.get("/seo/operator/tasks/:id", async (request) => {
|
|
15399
15529
|
requireOperator(request);
|
|
15400
15530
|
const task = app.db.select().from(seoTasks).where(eq32(seoTasks.id, request.params.id)).get();
|
|
@@ -15422,7 +15552,7 @@ async function seoRoutes(app, opts) {
|
|
|
15422
15552
|
const plan = active?.plan.schemaVersion === 2 ? active.plan : null;
|
|
15423
15553
|
const scopes = plan ? planScopeOptions(plan, { marketLinks: true }).map((option) => {
|
|
15424
15554
|
const capabilities = scopeCapabilities(option.kind, scopedPlanTargets(plan, { kind: option.kind, ...option.kind === SeoScopeKinds.project ? {} : { key: option.id } }).targets).filter((capability) => current.capabilities.includes(capability));
|
|
15425
|
-
return { kind: option.kind, key: option.kind === SeoScopeKinds.project ? null : option.id, label: option.label, planRevision: active.version.revision, capabilities, supported: capabilities.length > 0, reason: !capabilities.length ? current.reason ?? "No configured connection supports this scope." : option.kind === SeoScopeKinds.project ? null : capabilities
|
|
15555
|
+
return { kind: option.kind, key: option.kind === SeoScopeKinds.project ? null : option.id, label: option.label, planRevision: active.version.revision, capabilities, supported: capabilities.length > 0, reason: !capabilities.length ? current.reason ?? "No configured connection supports this scope." : option.kind === SeoScopeKinds.project ? null : scopeReason(capabilities) };
|
|
15426
15556
|
}) : [{ kind: SeoScopeKinds.project, key: null, label: "Project", capabilities: active ? [] : current.capabilities, supported: !active && current.capabilities.length > 0, reason: active ? "Publish a current Advanced Measurement plan first." : current.reason }];
|
|
15427
15557
|
return { available: current.available, reason: current.reason, connections: getRuntime()?.connections.map((connection) => ({ id: connection.id, label: connection.label, provider: connection.adapter.provider, capabilities: [...connection.adapter.capabilities] })) ?? [], scopes, access: { canRun: can(request, SEO_RUN_SCOPE), canWrite: can(request, SEO_WRITE_SCOPE) } };
|
|
15428
15558
|
});
|
|
@@ -15438,7 +15568,7 @@ async function seoRoutes(app, opts) {
|
|
|
15438
15568
|
if (!parsed.success) throw validationError("Invalid SEO profile.", { issues: parsed.error.issues });
|
|
15439
15569
|
const input = normalizeProfile(parsed.data);
|
|
15440
15570
|
resolveScope2(app.db, project.id, input);
|
|
15441
|
-
planTasks(input);
|
|
15571
|
+
planTasks(project.id, input);
|
|
15442
15572
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15443
15573
|
app.db.transaction((tx) => {
|
|
15444
15574
|
const current = tx.select().from(seoProfiles).where(and24(eq32(seoProfiles.projectId, project.id), eq32(seoProfiles.id, request.params.id))).get();
|
|
@@ -15454,7 +15584,7 @@ async function seoRoutes(app, opts) {
|
|
|
15454
15584
|
if (!parsed.success) throw validationError("Select an SEO profile.");
|
|
15455
15585
|
const saved = profile(project.id, parsed.data.profileId);
|
|
15456
15586
|
const scope = resolveScope2(app.db, project.id, saved);
|
|
15457
|
-
const planned = planTasks(saved);
|
|
15587
|
+
const planned = planTasks(project.id, saved);
|
|
15458
15588
|
const expiresAt = new Date(Date.now() + 5 * 6e4).toISOString();
|
|
15459
15589
|
const payload = Buffer.from(JSON.stringify({ projectId: project.id, actor: actor(request), profileId: saved.id, revision: saved.revision, scopeHash: hash2(JSON.stringify(scope)), intentHash: planned.intentHash, nonce: crypto17.randomUUID(), expiresAt })).toString("base64url");
|
|
15460
15590
|
const signature = crypto17.createHmac("sha256", intentSecret).update(payload).digest("base64url");
|
|
@@ -15491,7 +15621,7 @@ async function seoRoutes(app, opts) {
|
|
|
15491
15621
|
const saved = profile(project.id, claims.profileId);
|
|
15492
15622
|
const scopeSnapshot = resolveScope2(app.db, project.id, saved);
|
|
15493
15623
|
if (saved.revision !== claims.revision || hash2(JSON.stringify(scopeSnapshot)) !== claims.scopeHash) throw validationError("This SEO profile or measurement scope changed. Create a new refresh intent.");
|
|
15494
|
-
const runtime = planTasks(saved);
|
|
15624
|
+
const runtime = planTasks(project.id, saved);
|
|
15495
15625
|
if (runtime.intentHash !== claims.intentHash) throw validationError("The SEO connection or host policy changed. Create a new refresh intent.");
|
|
15496
15626
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15497
15627
|
const account = runtime.connection.adapter.accountKey;
|
|
@@ -15515,7 +15645,7 @@ async function seoRoutes(app, opts) {
|
|
|
15515
15645
|
const row = job(resolveProject(app.db, request.params.name).id, request.params.id);
|
|
15516
15646
|
const rows = tasks(row.id);
|
|
15517
15647
|
const results = resultRows(rows);
|
|
15518
|
-
return { ...runDto(row, rows.map((task) => ({ status: task.status, count: 1 }))), tasks: rows.map((task) => ({ id: task.id, capability: task.request.capability, target: task.request.target, status: task.status, error: task.error, remoteTaskId: task.receipts[0]?.remoteTaskId ?? null })), results: { keywordMetrics: results.keywordMetrics.slice(0, 20), domainIntelligence: results.domainIntelligence.slice(0, 20), rankedKeywords: results.rankedKeywords.slice(0, 20) }, resultCounts: { keywordMetrics: results.keywordMetrics.length, domainIntelligence: results.domainIntelligence.length, rankedKeywords: results.rankedKeywords.length }, resultsTruncated: Object.values(results).some((items) => items.length > 20) };
|
|
15648
|
+
return { ...runDto(row, rows.map((task) => ({ status: task.status, count: 1 }))), tasks: rows.map((task) => ({ id: task.id, capability: task.request.capability, target: task.request.target, status: task.status, error: task.error, remoteTaskId: task.receipts[0]?.remoteTaskId ?? null })), results: { keywordMetrics: results.keywordMetrics.slice(0, 20), domainIntelligence: results.domainIntelligence.slice(0, 20), rankedKeywords: results.rankedKeywords.slice(0, 20), marketRankings: [...results.marketRankings].sort(compareMarketRankings).slice(0, 20) }, resultCounts: { keywordMetrics: results.keywordMetrics.length, domainIntelligence: results.domainIntelligence.length, rankedKeywords: results.rankedKeywords.length, marketRankings: results.marketRankings.length }, resultsTruncated: Object.values(results).some((items) => items.length > 20) };
|
|
15519
15649
|
});
|
|
15520
15650
|
app.post("/projects/:name/seo/runs/:id/cancel", { config: { paidRead: true, writeScope: SEO_RUN_SCOPE } }, async (request) => {
|
|
15521
15651
|
requireScope(request, SEO_RUN_SCOPE);
|
|
@@ -15534,7 +15664,7 @@ async function seoRoutes(app, opts) {
|
|
|
15534
15664
|
const results = resultRows(rows);
|
|
15535
15665
|
const primary = row?.profile.targets[0];
|
|
15536
15666
|
const domain = primary ? results.domainIntelligence.find((result) => result.target.kind === primary.kind && result.target.value === primary.value && result.target.includeSubdomains === primary.includeSubdomains) : null;
|
|
15537
|
-
return { status: status(project.id), profiles: profiles(project.id), latestRun: latest ? runSummary(latest) : null, evidenceRunId: row?.id ?? null, evidenceRun: row ? runDto(row, rows.map((task) => ({ status: task.status, count: 1 }))) : null, keywordMetricsCount: results.keywordMetrics.length, domainIntelligenceCount: results.domainIntelligence.length, rankedKeywordCount: results.rankedKeywords.length, summary: domain ? { target: domain.target, organicKeywords: domain.organicKeywords, inTop10: domain.top1 !== null && domain.top2To3 !== null && domain.top4To10 !== null ? domain.top1 + domain.top2To3 + domain.top4To10 : null, estimatedOrganicTraffic: domain.estimatedOrganicTraffic, provenance: domain.provenance } : null };
|
|
15667
|
+
return { status: status(project.id), profiles: profiles(project.id), latestRun: latest ? runSummary(latest) : null, evidenceRunId: row?.id ?? null, evidenceRun: row ? runDto(row, rows.map((task) => ({ status: task.status, count: 1 }))) : null, keywordMetricsCount: results.keywordMetrics.length, domainIntelligenceCount: results.domainIntelligence.length, rankedKeywordCount: results.rankedKeywords.length, marketRankingCount: results.marketRankings.length, summary: domain ? { target: domain.target, organicKeywords: domain.organicKeywords, inTop10: domain.top1 !== null && domain.top2To3 !== null && domain.top4To10 !== null ? domain.top1 + domain.top2To3 + domain.top4To10 : null, estimatedOrganicTraffic: domain.estimatedOrganicTraffic, provenance: domain.provenance } : null };
|
|
15538
15668
|
});
|
|
15539
15669
|
for (const [path8, key] of [["keyword-metrics", "keywordMetrics"], ["domain-intelligence", "domainIntelligence"], ["ranked-keywords", "rankedKeywords"]]) app.get(`/projects/:name/seo/${path8}`, async (request) => {
|
|
15540
15670
|
const query = queryInput(request.query);
|
|
@@ -15544,6 +15674,17 @@ async function seoRoutes(app, opts) {
|
|
|
15544
15674
|
const offset = query.offset ?? 0;
|
|
15545
15675
|
return { runId: row?.id ?? null, items: items.slice(offset, offset + limit), total: items.length, limit, offset };
|
|
15546
15676
|
});
|
|
15677
|
+
app.get("/projects/:name/seo/market-rankings", async (request) => {
|
|
15678
|
+
const raw = request.query ?? {};
|
|
15679
|
+
const parsed = seoMarketRankingQuerySchema.safeParse({ ...raw, ...raw.limit === void 0 ? {} : { limit: Number(raw.limit) }, ...raw.offset === void 0 ? {} : { offset: Number(raw.offset) } });
|
|
15680
|
+
if (!parsed.success) throw validationError("Invalid SEO evidence selection.");
|
|
15681
|
+
const query = parsed.data;
|
|
15682
|
+
const row = selected(resolveProject(app.db, request.params.name).id, query, true);
|
|
15683
|
+
const items = (row ? resultRows(tasks(row.id)).marketRankings : []).filter((item2) => (query.marketKey === void 0 || item2.marketKey === query.marketKey) && (query.regionKey === void 0 || item2.regionKey === query.regionKey)).sort(compareMarketRankings);
|
|
15684
|
+
const limit = query.limit ?? 50;
|
|
15685
|
+
const offset = query.offset ?? 0;
|
|
15686
|
+
return { runId: row?.id ?? null, items: items.slice(offset, offset + limit), total: items.length, limit, offset };
|
|
15687
|
+
});
|
|
15547
15688
|
}
|
|
15548
15689
|
|
|
15549
15690
|
// ../api-routes/src/runs.ts
|
|
@@ -33750,6 +33891,7 @@ var SCHEMA_TABLE = {
|
|
|
33750
33891
|
SeoKeywordMetricsList: seoKeywordMetricsListSchema,
|
|
33751
33892
|
SeoDomainIntelligenceList: seoDomainIntelligenceListSchema,
|
|
33752
33893
|
SeoRankedKeywordList: seoRankedKeywordListSchema,
|
|
33894
|
+
SeoMarketRankingList: seoMarketRankingListSchema,
|
|
33753
33895
|
SentimentSummary: sentimentSummarySchema,
|
|
33754
33896
|
SentimentSettings: sentimentSettingsSchema,
|
|
33755
33897
|
SentimentSettingsUpdate: sentimentSettingsUpdateSchema,
|
|
@@ -34977,6 +35119,26 @@ var routeCatalog = [
|
|
|
34977
35119
|
409: errorResponse("Selection, intent or execution identity changed.")
|
|
34978
35120
|
}
|
|
34979
35121
|
},
|
|
35122
|
+
{
|
|
35123
|
+
method: "get",
|
|
35124
|
+
path: "/api/v1/projects/{name}/seo/market-rankings",
|
|
35125
|
+
summary: "Read stored market rankings",
|
|
35126
|
+
tags: ["seo"],
|
|
35127
|
+
description: "Stored evidence only; never calls a provider. The project's position for each tracked non-brand query, searched on Google from its market's location, ordered by region, market and search text. A null rank means the site was not in the checked results.",
|
|
35128
|
+
parameters: [
|
|
35129
|
+
nameParameter,
|
|
35130
|
+
...seoEvidenceParameters,
|
|
35131
|
+
{ name: "marketKey", in: "query", description: "Only rows for this market (an Advanced market key or a Simple location label).", schema: stringSchema },
|
|
35132
|
+
{ name: "regionKey", in: "query", description: "Only rows whose market sits under this top-level group.", schema: stringSchema }
|
|
35133
|
+
],
|
|
35134
|
+
responses: {
|
|
35135
|
+
200: jsonResponse("SEO response.", "SeoMarketRankingList"),
|
|
35136
|
+
400: errorResponse("Invalid scope or input."),
|
|
35137
|
+
403: errorResponse("Insufficient authority."),
|
|
35138
|
+
404: errorResponse("Project or selected evidence not found."),
|
|
35139
|
+
409: errorResponse("Selection, intent or execution identity changed.")
|
|
35140
|
+
}
|
|
35141
|
+
},
|
|
34980
35142
|
{
|
|
34981
35143
|
method: "put",
|
|
34982
35144
|
path: "/api/v1/projects/{name}/seo/profiles/{id}",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createApiClient
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EIMXB6UH.js";
|
|
4
4
|
import {
|
|
5
5
|
emitJsonl
|
|
6
6
|
} from "./chunk-Z7IUAJOF.js";
|
|
7
7
|
import {
|
|
8
8
|
measurementDraftOperationSchema,
|
|
9
9
|
runMeasurementDraftAction
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-CKTA5374.js";
|
|
11
11
|
import {
|
|
12
12
|
isMachineFormat,
|
|
13
13
|
systemError
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
unattributedAnswersLabel,
|
|
38
38
|
uncheckedSourcesLabel,
|
|
39
39
|
visibilityReportRequestSchema
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-WNCOBSPN.js";
|
|
41
41
|
|
|
42
42
|
// src/commands/measurement-plan.ts
|
|
43
43
|
import crypto from "crypto";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
registeredProviderNames
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-YJYLFIVT.js";
|
|
4
4
|
import {
|
|
5
5
|
configExists,
|
|
6
6
|
loadConfigRaw
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-EYWC24VC.js";
|
|
8
8
|
|
|
9
9
|
// src/setup-state.ts
|
|
10
10
|
import fs from "fs";
|
|
@@ -28,7 +28,7 @@ async function buildSetupState() {
|
|
|
28
28
|
}
|
|
29
29
|
if (dbPath && fs.existsSync(dbPath)) {
|
|
30
30
|
try {
|
|
31
|
-
const { createClient, projects, queries: queriesTable } = await import("./src-
|
|
31
|
+
const { createClient, projects, queries: queriesTable } = await import("./src-NOTQWGYV.js");
|
|
32
32
|
const db = createClient(dbPath);
|
|
33
33
|
project_count = db.select({ id: projects.id }).from(projects).all().length;
|
|
34
34
|
const firstQuery = db.select({ id: queriesTable.id }).from(queriesTable).limit(1).all();
|
|
@@ -47,7 +47,7 @@ function queueServeHandoff() {
|
|
|
47
47
|
async function runPendingServeHandoff(format) {
|
|
48
48
|
if (!pendingServeHandoff) return;
|
|
49
49
|
pendingServeHandoff = false;
|
|
50
|
-
const { serveCommand } = await import("./serve-
|
|
50
|
+
const { serveCommand } = await import("./serve-2LTQZPEK.js");
|
|
51
51
|
await serveCommand(format);
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
AppError,
|
|
6
6
|
parseWindowIn,
|
|
7
7
|
resolveDateRange
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-WNCOBSPN.js";
|
|
9
9
|
|
|
10
10
|
// src/cli-access.ts
|
|
11
11
|
var CLI_ACCESS_BY_ID = Object.freeze({
|
|
@@ -331,6 +331,7 @@ var CLI_ACCESS_BY_ID = Object.freeze({
|
|
|
331
331
|
"seo.context": "stored",
|
|
332
332
|
"seo.domain-intelligence": "stored",
|
|
333
333
|
"seo.keyword-metrics": "stored",
|
|
334
|
+
"seo.market-rankings": "stored",
|
|
334
335
|
"seo.operator.reconcile": "write",
|
|
335
336
|
"seo.operator.task": "stored",
|
|
336
337
|
"seo.overview": "stored",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseJsonColumn
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-JALKQREN.js";
|
|
4
4
|
import {
|
|
5
5
|
AI_ENGINE_DOMAINS,
|
|
6
6
|
AI_ENGINE_SELF_DOMAINS,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
retryAfterDelayMs,
|
|
27
27
|
usageCount,
|
|
28
28
|
withRetry
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-WNCOBSPN.js";
|
|
30
30
|
|
|
31
31
|
// ../provider-openai/src/normalize.ts
|
|
32
32
|
import OpenAI from "openai";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
pickProjectCitedDomain
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-QRBZBJ4U.js";
|
|
4
4
|
import {
|
|
5
5
|
GBP_INSIGHT_PROVIDER,
|
|
6
6
|
PERSISTENT_GAP_THRESHOLD,
|
|
@@ -14,10 +14,10 @@ import {
|
|
|
14
14
|
mergeGscQueryTotalsWithFallback,
|
|
15
15
|
readGscQueryDailyRows,
|
|
16
16
|
readLatestGscDataDate
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-JYOMHNRP.js";
|
|
18
18
|
import {
|
|
19
19
|
getRequestContext
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-YRX5UOM3.js";
|
|
21
21
|
import {
|
|
22
22
|
competitors,
|
|
23
23
|
gbpDailyMetrics,
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
queries,
|
|
36
36
|
querySnapshots,
|
|
37
37
|
runs
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-JALKQREN.js";
|
|
39
39
|
import {
|
|
40
40
|
CitationStates,
|
|
41
41
|
RunKinds,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
effectiveDomains,
|
|
46
46
|
redactLogString,
|
|
47
47
|
redactLogValue
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-WNCOBSPN.js";
|
|
49
49
|
|
|
50
50
|
// src/intelligence-service.ts
|
|
51
51
|
import { eq, desc, asc, and, ne, or, inArray, gte, lte, isNull, sql, exists } from "drizzle-orm";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
resolveCliAccess,
|
|
4
4
|
resolveCliAccessForInvocation,
|
|
5
5
|
windowOptions
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-LBRG6RKI.js";
|
|
7
7
|
import {
|
|
8
8
|
emitJsonl
|
|
9
9
|
} from "./chunk-Z7IUAJOF.js";
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "./chunk-HGMYEDAQ.js";
|
|
15
15
|
import {
|
|
16
16
|
OperationAccesses
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-WNCOBSPN.js";
|
|
18
18
|
|
|
19
19
|
// src/cli-catalog.ts
|
|
20
20
|
import { createRequire } from "module";
|
|
@@ -111,7 +111,7 @@ async function dispatchRegisteredCommand(args, fallbackFormat, specs) {
|
|
|
111
111
|
const accessValue = accessFilterFromArgs(argsWithoutHelp);
|
|
112
112
|
validateAccessFilter(accessValue);
|
|
113
113
|
if (fallbackFormat !== "text") {
|
|
114
|
-
const { printCliCatalog: printCliCatalog2 } = await import("./cli-catalog-
|
|
114
|
+
const { printCliCatalog: printCliCatalog2 } = await import("./cli-catalog-IZJXAHFQ.js");
|
|
115
115
|
const group2 = argsWithoutHelp.find((arg) => !arg.startsWith("--"));
|
|
116
116
|
printCliCatalog2(specs, fallbackFormat, group2, accessValue);
|
|
117
117
|
return true;
|