@canonry/canonry 7.77.2 → 7.78.2
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 +13 -2
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +4 -2
- package/assets/assets/{AgentSetupPage-BZXWC3x6.js → AgentSetupPage-BGdvyQFL.js} +4 -4
- package/assets/assets/{AuditHistoryPanel-DcrUuwRu.js → AuditHistoryPanel-Bb5wq-Bk.js} +1 -1
- package/assets/assets/{BacklinksPage-BSpHJBiy.js → BacklinksPage-i1cfxTDj.js} +1 -1
- package/assets/assets/{HistoryPage-0a0CK9zF.js → HistoryPage-C2NRdNrn.js} +1 -1
- package/assets/assets/MeasurementPropertyPage-CwPuJMBy.js +4 -0
- package/assets/assets/ProjectPage-Bbnbg7c9.js +11 -0
- package/assets/assets/{RunsPage-D0vbz1Ho.js → RunsPage-CJHJ7kHk.js} +1 -1
- package/assets/assets/{SettingsPage-DjRm5cmn.js → SettingsPage-r8VrtFyJ.js} +1 -1
- package/assets/assets/SiteHealthSection-D1oSuo9O.js +2 -0
- package/assets/assets/ToneBadge-CUe-WrzN.js +60 -0
- package/assets/assets/{TrafficPage-Z8Z_z0k9.js → TrafficPage-DY2ykwH1.js} +1 -1
- package/assets/assets/TrafficSourceDetailPage-D3KHgRvF.js +1 -0
- package/assets/assets/{account-context-zaSxLBst.js → account-context-B50QqDEA.js} +1 -1
- package/assets/assets/{card-COqALGzB.js → card-BE2fqwVq.js} +1 -1
- package/assets/assets/{extract-error-message-DKve0SNr.js → extract-error-message-xuN_xeRJ.js} +1 -1
- package/assets/assets/{format-helpers-DKrHlaV7.js → format-helpers-CiC6gALY.js} +1 -1
- package/assets/assets/{index-CKZK95pI.css → index-CHHZDc6d.css} +1 -1
- package/assets/assets/{index-fzqtwr6m.js → index-Dfm8z5cG.js} +23 -23
- package/assets/assets/{query-client-COM9uzxL.js → query-client-C5GhDMY7.js} +1 -1
- package/assets/assets/react-query-CmHyfYFq.js +1 -0
- package/assets/assets/{react-sigma_core.esm.min-e91U3Myi.js → react-sigma_core.esm.min-Bjdtl0TQ.js} +1 -1
- package/assets/assets/{service-BgFTYqJA.js → service-B2i_ZzWP.js} +1 -1
- package/assets/index.html +8 -8
- package/dist/{ads-5K5IQDCD.js → ads-MNMND5VH.js} +3 -3
- package/dist/{agent-Q4PKMBEJ.js → agent-4KNDL3D3.js} +7 -7
- package/dist/{agent-ask-JEPVVW2J.js → agent-ask-32RTOL2M.js} +3 -3
- package/dist/{agent-conversations-EHE7QEBC.js → agent-conversations-BXVWHUZC.js} +3 -3
- package/dist/{agent-memory-AB72YQET.js → agent-memory-7R7UVFS5.js} +3 -3
- package/dist/{agent-providers-NQ7HJYAQ.js → agent-providers-QGVLCCH4.js} +3 -3
- package/dist/{agent-transcript-PRTWIQ2A.js → agent-transcript-7Y6BJCIT.js} +3 -3
- package/dist/{analytics-IY3J4VZE.js → analytics-YOGEHW6J.js} +3 -3
- package/dist/{apply-HNOG674K.js → apply-TB3MF3PH.js} +3 -3
- package/dist/{backfill-TO7TPPO6.js → backfill-IRVLT6VT.js} +9 -9
- package/dist/{backlinks-3NY5GNT4.js → backlinks-6GTL324T.js} +3 -3
- package/dist/{bing-Z7W7U2CT.js → bing-OMQACSDY.js} +3 -3
- package/dist/{bootstrap-DXXS3LRH.js → bootstrap-WZ6YDQIG.js} +10 -10
- package/dist/{cdp-CKPRS6VE.js → cdp-3WBLAWOL.js} +7 -7
- package/dist/{chunk-G26YB6JK.js → chunk-2EWZ3DJ4.js} +3 -3
- package/dist/{chunk-JPOIDGGF.js → chunk-3MGM3U6V.js} +1 -1
- package/dist/{chunk-7JC4Z6FQ.js → chunk-3QJWOF2B.js} +4 -3
- package/dist/{chunk-2BDWXHYF.js → chunk-45RGH7XI.js} +3 -3
- package/dist/{chunk-6KPBYCPE.js → chunk-4GEEYY3N.js} +2 -2
- package/dist/{chunk-C3UFOS7J.js → chunk-5NBXPO4G.js} +2 -2
- package/dist/{chunk-RPJJOBKN.js → chunk-6ACWXQNZ.js} +3 -1
- package/dist/{chunk-4XVXP6VR.js → chunk-72YPSNCG.js} +2 -2
- package/dist/{chunk-5QR7D4RQ.js → chunk-7HB6QLX4.js} +1 -1
- package/dist/{chunk-EM2HAM2U.js → chunk-7HE4LAJY.js} +8 -8
- package/dist/{chunk-JRVKW4OO.js → chunk-7OAXEMOF.js} +26 -2
- package/dist/{chunk-7UVCZZQJ.js → chunk-AI44ZCMK.js} +1 -1
- package/dist/{chunk-LKEFPJQ2.js → chunk-BKOG3GEI.js} +4 -4
- package/dist/{chunk-CMS4YM5M.js → chunk-BLZCNL3B.js} +3 -3
- package/dist/{chunk-HD2VVIPD.js → chunk-BTMLE3EN.js} +5 -5
- package/dist/{chunk-CHTKK43G.js → chunk-COUM462G.js} +152 -24
- package/dist/{chunk-2WBT6TEJ.js → chunk-FCYGY4FT.js} +1 -1
- package/dist/{chunk-JMCURBZG.js → chunk-GEHRGKQT.js} +4 -4
- package/dist/{chunk-ORZBHCZR.js → chunk-IYMTQHPD.js} +4 -4
- package/dist/{chunk-XYYWKCJQ.js → chunk-JERATA5R.js} +1 -1
- package/dist/{chunk-AAUMUHUI.js → chunk-KF7I7FBI.js} +2 -2
- package/dist/{chunk-PSNTEGNY.js → chunk-KZB6HLFT.js} +1 -1
- package/dist/{chunk-46KV7CZU.js → chunk-LBNUTWPK.js} +2 -2
- package/dist/{chunk-4QHTXT4A.js → chunk-MUYTDD35.js} +40 -4
- package/dist/{chunk-TESDUMVK.js → chunk-O4WBYEGV.js} +1 -1
- package/dist/{chunk-5LKK2MCJ.js → chunk-OES6PSQK.js} +7 -7
- package/dist/{chunk-D5XPB7MM.js → chunk-PH4AESRE.js} +3 -1
- package/dist/{chunk-JEXBP7CR.js → chunk-PLLDJUG2.js} +4 -4
- package/dist/{chunk-FCGYYZHL.js → chunk-PM2DB3HE.js} +164 -10
- package/dist/{chunk-GDKMVOZI.js → chunk-PRCBHOVR.js} +146 -19
- package/dist/{chunk-JZ4UFSVR.js → chunk-R26CF4WT.js} +2 -2
- package/dist/{chunk-Z2FY6AGZ.js → chunk-TZRYVW75.js} +2 -2
- package/dist/{chunk-WFFZAQRG.js → chunk-UHCOJCT3.js} +371 -340
- package/dist/{chunk-ZXYCPRNY.js → chunk-WSWHLKKJ.js} +1 -1
- package/dist/{chunk-NDTU7FGL.js → chunk-WVCDUKJ2.js} +1 -1
- package/dist/{chunk-JSCO223C.js → chunk-ZHFE6J3A.js} +1 -1
- package/dist/{citations-4AV6HLUZ.js → citations-PGG7JEIN.js} +3 -3
- package/dist/{cli-catalog-ZVFLJXID.js → cli-catalog-KC6JYHSW.js} +3 -3
- package/dist/{cli-commands-NJDUBQ3R.js → cli-commands-NGXSLPIA.js} +7 -7
- package/dist/cli.js +13 -13
- package/dist/{competitor-SG4N37UD.js → competitor-B3CZOKZU.js} +3 -3
- package/dist/{content-EDLZAPUV.js → content-Z4XJMZB4.js} +3 -3
- package/dist/{dashboard-5ZFVJPHX.js → dashboard-6MMYA2GG.js} +6 -6
- package/dist/{demo-5CU3JDHC.js → demo-3R2SHQPO.js} +1 -1
- package/dist/{demo-server-43QV3UKM.js → demo-server-ISJOEJN5.js} +12 -12
- package/dist/{discover-7X2WMNFV.js → discover-OT3K7KQK.js} +3 -3
- package/dist/{discover-eval-ECOPSJJ4.js → discover-eval-DN4J4KD5.js} +3 -3
- package/dist/{doctor-NMFBKNYR.js → doctor-UDF6OCSR.js} +3 -3
- package/dist/{evidence-C52ZPRYB.js → evidence-Z364UFOD.js} +3 -3
- package/dist/{export-cmd-2WSEETUC.js → export-cmd-K27XN4G5.js} +3 -3
- package/dist/{ga-YG4O2FT5.js → ga-Q2CELDLG.js} +3 -3
- package/dist/{gbp-BDGV6F74.js → gbp-V2APGSIQ.js} +3 -3
- package/dist/{google-P2AX6CUF.js → google-WFHYA3QT.js} +4 -4
- package/dist/{google-marketing-XPE6CVQI.js → google-marketing-5ROIKBC7.js} +1 -1
- package/dist/{health-cmd-MIQT2JNL.js → health-cmd-RGFAJ4YP.js} +3 -3
- package/dist/{history-3XDDJDAV.js → history-RUSU66BZ.js} +3 -3
- package/dist/index.d.ts +208 -13
- package/dist/index.js +26 -26
- package/dist/{init-GT3QM7FC.js → init-SMBVJSP6.js} +12 -12
- package/dist/{insights-ZJUDCBBP.js → insights-4IFQLPS3.js} +3 -3
- package/dist/intelligence-service-MPNONLCW.js +16 -0
- package/dist/{journeys-L2CXQXRP.js → journeys-PTC2QY57.js} +3 -3
- package/dist/{keys-ODRGIQS7.js → keys-JASXI2SR.js} +3 -3
- package/dist/{keyword-LE3CAXO2.js → keyword-SK2HJXCJ.js} +3 -3
- package/dist/{logs-OXK43XN2.js → logs-AS5I5V2A.js} +3 -3
- package/dist/{mcp-6OTPCDGW.js → mcp-BQSOYHEX.js} +4 -4
- package/dist/mcp.js +10 -10
- package/dist/{measurement-plan-NXRCDUYL.js → measurement-plan-LZYYH3OD.js} +5 -5
- package/dist/{notify-VNNJTNC6.js → notify-JM6252F6.js} +3 -3
- package/dist/{organic-evidence-LYDB7IID.js → organic-evidence-L3Y6RZIH.js} +3 -3
- package/dist/{overview-2GPPQ6JP.js → overview-VIF2GXYW.js} +4 -4
- package/dist/{project-CQJPXC66.js → project-FQEPFPRD.js} +3 -3
- package/dist/{query-PPE4EHMY.js → query-VKRESS6B.js} +3 -3
- package/dist/{research-GDYZKQY2.js → research-6A6QWKN3.js} +3 -3
- package/dist/{results-export-OAJHDEIQ.js → results-export-VUB2ZD5B.js} +3 -3
- package/dist/{run-XDPDQS44.js → run-PG5GYZT7.js} +4 -4
- package/dist/{schedule-C6SYV54I.js → schedule-EBGOLCQZ.js} +3 -3
- package/dist/{search-DONTDHQK.js → search-NS4XPZRL.js} +3 -3
- package/dist/{serve-PJXATOSY.js → serve-J7WR3J6I.js} +27 -27
- package/dist/{session-PMZV27GX.js → session-FBHHWILQ.js} +14 -14
- package/dist/{settings-PMATG7GG.js → settings-LNCAYCDF.js} +3 -3
- package/dist/{skills-CYHWYN42.js → skills-DWXA55QA.js} +2 -2
- package/dist/{snapshot-E4GHH6DD.js → snapshot-LX7OR7SO.js} +3 -3
- package/dist/{src-SXB3MOP3.js → src-AQ5MHQGQ.js} +2 -2
- package/dist/{status-MEGXEEW6.js → status-DQ6FNWUT.js} +4 -4
- package/dist/{technical-aeo-GE6KOFKT.js → technical-aeo-5GU2IY6W.js} +3 -3
- package/dist/{telemetry-Y63F4XFX.js → telemetry-QK2GPII3.js} +4 -4
- package/dist/{tools-V546ALG7.js → tools-3ACR2D2V.js} +11 -11
- package/dist/{traffic-5ELSKXBM.js → traffic-2JFQKVAX.js} +5 -5
- package/dist/{users-7YK7XYWY.js → users-7OJ2RGV3.js} +3 -3
- package/dist/{visibility-stats-GYPUKXVS.js → visibility-stats-VR25XMMY.js} +3 -3
- package/dist/{wordpress-WUGJT5NN.js → wordpress-JYXTF4PL.js} +3 -3
- package/package.json +1 -1
- package/assets/assets/MeasurementPropertyPage-DbfTFN3F.js +0 -4
- package/assets/assets/ProjectPage-D0m-Xpp3.js +0 -9
- package/assets/assets/SiteHealthSection-BCdPVFEJ.js +0 -2
- package/assets/assets/ToneBadge-DSJSzqI6.js +0 -60
- package/assets/assets/TrafficSourceDetailPage-DJyDO527.js +0 -1
- package/assets/assets/react-query-BVRWpXWq.js +0 -1
- package/dist/intelligence-service-NXZYQJBH.js +0 -16
|
@@ -9,14 +9,14 @@ import {
|
|
|
9
9
|
normalizeCloudflareWorkerEvent,
|
|
10
10
|
sessionizeJourneyRequests,
|
|
11
11
|
verifyRequestSignature
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-O4WBYEGV.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-3MGM3U6V.js";
|
|
20
20
|
import {
|
|
21
21
|
SentimentIdempotencyConflict,
|
|
22
22
|
SentimentRepository,
|
|
@@ -157,7 +157,7 @@ import {
|
|
|
157
157
|
userSessions,
|
|
158
158
|
users,
|
|
159
159
|
writeNegativeReviewMaxStars
|
|
160
|
-
} from "./chunk-
|
|
160
|
+
} from "./chunk-PH4AESRE.js";
|
|
161
161
|
import {
|
|
162
162
|
ADS_ACTIVATE_SCOPE,
|
|
163
163
|
ADS_ACTIVATION_ABSOLUTE_MAX_ENTITIES,
|
|
@@ -371,6 +371,8 @@ import {
|
|
|
371
371
|
SeoAdapterOutcomes,
|
|
372
372
|
SeoAvailability,
|
|
373
373
|
SeoCapabilities,
|
|
374
|
+
SeoKeywordGapKinds,
|
|
375
|
+
SeoKeywordGapModes,
|
|
374
376
|
SeoMarketRankingSearchStatuses,
|
|
375
377
|
SeoMarketRankingSorts,
|
|
376
378
|
SeoRunStatuses,
|
|
@@ -534,6 +536,8 @@ import {
|
|
|
534
536
|
compactDateToIso,
|
|
535
537
|
compareMarketRankingsBy,
|
|
536
538
|
compareSemver,
|
|
539
|
+
compareSeoCompetitorsBy,
|
|
540
|
+
compareSeoKeywordGapsBy,
|
|
537
541
|
compareText,
|
|
538
542
|
competitorAliasBlockRequestSchema,
|
|
539
543
|
competitorAliasProjectIdentity,
|
|
@@ -1058,9 +1062,16 @@ import {
|
|
|
1058
1062
|
sentimentSettingsUpdateSchema,
|
|
1059
1063
|
sentimentSummaryRequestSchema,
|
|
1060
1064
|
sentimentSummarySchema,
|
|
1065
|
+
seoCompetitorListSchema,
|
|
1066
|
+
seoCompetitorQuerySchema,
|
|
1067
|
+
seoCompetitorSortSchema,
|
|
1061
1068
|
seoContextSchema,
|
|
1062
1069
|
seoDomainIntelligenceListSchema,
|
|
1063
1070
|
seoEvidenceQuerySchema,
|
|
1071
|
+
seoKeywordGapKindSchema,
|
|
1072
|
+
seoKeywordGapListSchema,
|
|
1073
|
+
seoKeywordGapQuerySchema,
|
|
1074
|
+
seoKeywordGapSortSchema,
|
|
1064
1075
|
seoKeywordMetricsListSchema,
|
|
1065
1076
|
seoMarketRankingFilterSchema,
|
|
1066
1077
|
seoMarketRankingListSchema,
|
|
@@ -1196,7 +1207,7 @@ import {
|
|
|
1196
1207
|
wordpressSchemaDeployResultDtoSchema,
|
|
1197
1208
|
wordpressSchemaStatusResultDtoSchema,
|
|
1198
1209
|
wordpressStatusDtoSchema
|
|
1199
|
-
} from "./chunk-
|
|
1210
|
+
} from "./chunk-PM2DB3HE.js";
|
|
1200
1211
|
|
|
1201
1212
|
// ../api-routes/src/user-session.ts
|
|
1202
1213
|
import crypto2 from "crypto";
|
|
@@ -7946,6 +7957,8 @@ h1 { margin: 0; font-size: 24px; line-height: 32px; font-weight: 600; letter-spa
|
|
|
7946
7957
|
p { margin: 0; overflow-wrap: anywhere; }
|
|
7947
7958
|
.intro { color: var(--secondary); margin-top: 8px; }
|
|
7948
7959
|
strong { color: var(--text); font-weight: 550; }
|
|
7960
|
+
a { color: var(--text); text-decoration: underline; text-decoration-color: var(--border-strong); text-underline-offset: 3px; }
|
|
7961
|
+
a:hover { text-decoration-color: var(--text); }
|
|
7949
7962
|
.err { margin-top: 16px; color: var(--negative); }
|
|
7950
7963
|
.warn { margin-top: 16px; padding: 12px; border: 1px solid var(--caution-border); border-radius: 6px; background: var(--caution-surface); color: var(--caution); font-size: 13px; line-height: 20px; }
|
|
7951
7964
|
.grant { margin: 24px 0 0; padding: 16px 0 20px; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); font-size: 13px; line-height: 20px; }
|
|
@@ -7968,7 +7981,7 @@ button { min-height: 36px; border: 1px solid transparent; border-radius: 6px; pa
|
|
|
7968
7981
|
.secondary { border-color: var(--border); background: var(--surface); color: var(--text); }
|
|
7969
7982
|
.secondary:hover { background: var(--hover); border-color: var(--border-strong); }
|
|
7970
7983
|
.secondary:active { background: var(--border); }
|
|
7971
|
-
:where(input, button):focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
|
|
7984
|
+
:where(input, button, a):focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
|
|
7972
7985
|
@media (max-width: 480px) {
|
|
7973
7986
|
main { padding: 32px 16px; align-items: flex-start; }
|
|
7974
7987
|
.panel { padding: 24px; }
|
|
@@ -7993,11 +8006,11 @@ function fontFace(fontUrl) {
|
|
|
7993
8006
|
src: url('${fontUrl}') format('woff2');
|
|
7994
8007
|
}`;
|
|
7995
8008
|
}
|
|
7996
|
-
function gatePage(title, body, fontUrl) {
|
|
8009
|
+
function gatePage(title, body, fontUrl, head = "") {
|
|
7997
8010
|
return `<!doctype html>
|
|
7998
8011
|
<html lang="en"><head>
|
|
7999
8012
|
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8000
|
-
<meta name="color-scheme" content="dark"><meta name="theme-color" content="#0a0a0a"
|
|
8013
|
+
<meta name="color-scheme" content="dark"><meta name="theme-color" content="#0a0a0a">${head}
|
|
8001
8014
|
<title>${title} \xB7 Canonry</title>
|
|
8002
8015
|
<style>${fontFace(fontUrl)}${styles}</style>
|
|
8003
8016
|
</head><body><main><div class="gate">
|
|
@@ -8032,6 +8045,18 @@ ${o.scope.split(/\s+/).includes(RESEARCH_RUN_SCOPE) ? `<p class="warn">Run resea
|
|
|
8032
8045
|
<div class="actions"><button class="primary" type="submit" name="approve" value="yes">Allow</button><button class="secondary" type="submit" name="approve" value="no">Deny</button></div>
|
|
8033
8046
|
</form>`, o.fontUrl);
|
|
8034
8047
|
}
|
|
8048
|
+
function renderAuthorizationHandoff(o) {
|
|
8049
|
+
const target = escapeHtml(o.target);
|
|
8050
|
+
const title = o.approved ? "Access allowed" : "Access denied";
|
|
8051
|
+
return gatePage(
|
|
8052
|
+
title,
|
|
8053
|
+
`<h1 id="gate-title">${title}</h1>
|
|
8054
|
+
<p class="intro">Returning you to <strong>${escapeHtml(o.clientName)}</strong>. If nothing happens, <a href="${target}" rel="noreferrer">continue</a>.</p>`,
|
|
8055
|
+
o.fontUrl,
|
|
8056
|
+
`
|
|
8057
|
+
<meta http-equiv="refresh" content="0;url=${target}">`
|
|
8058
|
+
);
|
|
8059
|
+
}
|
|
8035
8060
|
|
|
8036
8061
|
// ../api-routes/src/oauth.ts
|
|
8037
8062
|
var CODE_TTL_MS = 6e4;
|
|
@@ -8089,6 +8114,12 @@ function sendGatePage(reply, html) {
|
|
|
8089
8114
|
"frame-ancestors 'none'; default-src 'none'; style-src 'unsafe-inline'; font-src 'self'; form-action 'self'"
|
|
8090
8115
|
).header("x-frame-options", "DENY").header("referrer-policy", "same-origin").header("cache-control", "no-store").type("text/html").send(html);
|
|
8091
8116
|
}
|
|
8117
|
+
function sendHandoffPage(reply, html) {
|
|
8118
|
+
return reply.header(
|
|
8119
|
+
"content-security-policy",
|
|
8120
|
+
"frame-ancestors 'none'; default-src 'none'; style-src 'unsafe-inline'; font-src 'self'; form-action 'none'; base-uri 'none'"
|
|
8121
|
+
).header("x-frame-options", "DENY").header("referrer-policy", "no-referrer").header("cache-control", "no-store").type("text/html").send(html);
|
|
8122
|
+
}
|
|
8092
8123
|
var CONSENT_SECRET = crypto10.randomBytes(32);
|
|
8093
8124
|
function registerOAuthRoutes(app, opts) {
|
|
8094
8125
|
void app.register(async (scope) => {
|
|
@@ -8115,6 +8146,12 @@ function registerOAuthRoutesIn(app, opts) {
|
|
|
8115
8146
|
function consentToken(userId, clientId, redirectUri, scope) {
|
|
8116
8147
|
return crypto10.createHmac("sha256", CONSENT_SECRET).update([userId, clientId, redirectUri, scope].join("\0")).digest("base64url");
|
|
8117
8148
|
}
|
|
8149
|
+
function selfRegistered(client) {
|
|
8150
|
+
if (client.registration !== "dynamic") return false;
|
|
8151
|
+
const gateway = opts.gatewayClient;
|
|
8152
|
+
const vouched = gateway !== void 0 && client.id === gateway.id && client.redirectUris.length > 0 && client.redirectUris.every((uri) => URL.canParse(uri) && new URL(uri).origin === gateway.origin);
|
|
8153
|
+
return !vouched;
|
|
8154
|
+
}
|
|
8118
8155
|
function authorizationServerMetadata() {
|
|
8119
8156
|
return {
|
|
8120
8157
|
issuer,
|
|
@@ -8190,7 +8227,7 @@ function registerOAuthRoutesIn(app, opts) {
|
|
|
8190
8227
|
return sendGatePage(reply, renderConsentPage({
|
|
8191
8228
|
action: request.url,
|
|
8192
8229
|
clientName: client.name,
|
|
8193
|
-
selfRegistered: client
|
|
8230
|
+
selfRegistered: selfRegistered(client),
|
|
8194
8231
|
redirectUri,
|
|
8195
8232
|
scope: grantedScope,
|
|
8196
8233
|
userName: user.name,
|
|
@@ -8228,7 +8265,12 @@ function registerOAuthRoutesIn(app, opts) {
|
|
|
8228
8265
|
const denied = new URL(redirectUri);
|
|
8229
8266
|
denied.searchParams.set("error", "access_denied");
|
|
8230
8267
|
if (q.state) denied.searchParams.set("state", q.state);
|
|
8231
|
-
return reply
|
|
8268
|
+
return sendHandoffPage(reply, renderAuthorizationHandoff({
|
|
8269
|
+
target: denied.toString(),
|
|
8270
|
+
approved: false,
|
|
8271
|
+
clientName: client.name,
|
|
8272
|
+
fontUrl: opts.fontUrl
|
|
8273
|
+
}));
|
|
8232
8274
|
}
|
|
8233
8275
|
const code = newToken();
|
|
8234
8276
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -8247,7 +8289,12 @@ function registerOAuthRoutesIn(app, opts) {
|
|
|
8247
8289
|
target.searchParams.set("code", code);
|
|
8248
8290
|
if (q.state) target.searchParams.set("state", q.state);
|
|
8249
8291
|
target.searchParams.set("iss", issuer);
|
|
8250
|
-
return reply
|
|
8292
|
+
return sendHandoffPage(reply, renderAuthorizationHandoff({
|
|
8293
|
+
target: target.toString(),
|
|
8294
|
+
approved: true,
|
|
8295
|
+
clientName: client.name,
|
|
8296
|
+
fontUrl: opts.fontUrl
|
|
8297
|
+
}));
|
|
8251
8298
|
});
|
|
8252
8299
|
const registrations = /* @__PURE__ */ new Map();
|
|
8253
8300
|
let registrationsThisWindow = [];
|
|
@@ -15435,7 +15482,9 @@ function resultRows(tasks) {
|
|
|
15435
15482
|
keywordMetrics: kept.flatMap((task) => task.results.keywordMetrics),
|
|
15436
15483
|
domainIntelligence: kept.flatMap((task) => task.results.domainIntelligence),
|
|
15437
15484
|
rankedKeywords: kept.flatMap((task) => task.results.rankedKeywords),
|
|
15438
|
-
marketRankings: tasks.flatMap(marketRankingRows)
|
|
15485
|
+
marketRankings: tasks.flatMap(marketRankingRows),
|
|
15486
|
+
competitors: kept.flatMap((task) => task.results.competitors ?? []),
|
|
15487
|
+
keywordGaps: kept.flatMap((task) => task.results.keywordGaps ?? [])
|
|
15439
15488
|
};
|
|
15440
15489
|
}
|
|
15441
15490
|
function marketRankingRows(task) {
|
|
@@ -15471,14 +15520,14 @@ function scopedPlanTargets(plan, scope) {
|
|
|
15471
15520
|
return { targets: plan.targets.filter((target) => targetKeys.includes(target.stableKey)), market, targetKeys };
|
|
15472
15521
|
}
|
|
15473
15522
|
function scopeCapabilities(kind, targets) {
|
|
15474
|
-
if (kind === SeoScopeKinds.project) return
|
|
15523
|
+
if (kind === SeoScopeKinds.project) return Object.values(SeoCapabilities);
|
|
15475
15524
|
const exactHosts = targets.length > 0 && targets.every((target) => target.urlMatchers.length > 0 && target.urlMatchers.every((matcher) => matcher.kind === "host"));
|
|
15476
15525
|
const markets = kind === SeoScopeKinds.group || kind === SeoScopeKinds.market ? [SeoCapabilities["market-rankings"]] : [];
|
|
15477
15526
|
return exactHosts ? [SeoCapabilities["keyword-metrics"], SeoCapabilities["ranked-keywords"], ...markets] : [SeoCapabilities["keyword-metrics"], ...markets];
|
|
15478
15527
|
}
|
|
15479
15528
|
function scopeReason(capabilities) {
|
|
15480
15529
|
const markets = capabilities.includes(SeoCapabilities["market-rankings"]);
|
|
15481
|
-
return capabilities.includes(SeoCapabilities["ranked-keywords"]) ? `Supports explicit keyword metrics${markets ? ", market rankings" : ""} and exact-host ranked keywords.
|
|
15530
|
+
return capabilities.includes(SeoCapabilities["ranked-keywords"]) ? `Supports explicit keyword metrics${markets ? ", market rankings" : ""} and exact-host ranked keywords. Use project scope for domain intelligence, competitor discovery and keyword gaps.` : `Supports explicit keyword metrics${markets ? " and market rankings" : " only"}. Use project scope for domain research; path and entity Properties cannot be replaced by whole-domain data.`;
|
|
15482
15531
|
}
|
|
15483
15532
|
function cancelSeoRefresh(db, projectId, id) {
|
|
15484
15533
|
db.transaction((tx) => {
|
|
@@ -15503,6 +15552,7 @@ function resolveScope2(db, projectId, input) {
|
|
|
15503
15552
|
const { market, targetKeys, targets } = scopedPlanTargets(plan, input.scope);
|
|
15504
15553
|
const supported = scopeCapabilities(input.scope.kind, targets);
|
|
15505
15554
|
if (input.capabilities.some((capability) => !supported.includes(capability))) {
|
|
15555
|
+
if (input.capabilities.includes(SeoCapabilities["competitor-discovery"]) || input.capabilities.includes(SeoCapabilities["keyword-gaps"])) throw validationError("Competitor discovery and keyword gaps compare whole domains. Select project scope with an explicit domain Target; Property, group and market scope cannot be broadened automatically.");
|
|
15506
15556
|
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.");
|
|
15507
15557
|
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.");
|
|
15508
15558
|
}
|
|
@@ -15515,13 +15565,24 @@ function resolveScope2(db, projectId, input) {
|
|
|
15515
15565
|
}
|
|
15516
15566
|
function normalizeProfile(input) {
|
|
15517
15567
|
if (new Set(input.capabilities).size !== input.capabilities.length) throw validationError("Select each SEO capability once.");
|
|
15518
|
-
const
|
|
15568
|
+
const normalized = [...input.targets, ...input.competitors ?? []].map((target) => {
|
|
15519
15569
|
const host = normalizeHostname(target.value);
|
|
15520
15570
|
if (!host) throw validationError("SEO Targets must be plain hostnames without paths or ports.");
|
|
15521
15571
|
if (target.kind === SeoTargetKinds.host && target.includeSubdomains) throw validationError("Exact host Targets cannot include subdomains.");
|
|
15522
15572
|
return { ...target, value: host };
|
|
15523
15573
|
});
|
|
15574
|
+
const targets = normalized.slice(0, input.targets.length);
|
|
15575
|
+
const comparisonTargets = normalized.slice(input.targets.length);
|
|
15524
15576
|
if (new Set(targets.map((target) => `${target.kind}:${target.value}:${target.includeSubdomains}`)).size !== targets.length) throw validationError("SEO Targets must be distinct.");
|
|
15577
|
+
const competitive = input.capabilities.includes(SeoCapabilities["competitor-discovery"]) || input.capabilities.includes(SeoCapabilities["keyword-gaps"]);
|
|
15578
|
+
if (competitive && [...targets, ...comparisonTargets].some((target) => target.kind !== SeoTargetKinds.domain || !target.includeSubdomains || registrableDomain(target.value) !== target.value)) throw validationError("Competitor discovery and keyword gaps require root domain Targets with subdomains included. Exact host comparisons are not supported by this source.");
|
|
15579
|
+
if (input.capabilities.includes(SeoCapabilities["keyword-gaps"])) {
|
|
15580
|
+
if (!comparisonTargets.length) throw validationError("Keyword gaps requires at least one explicit competitor domain.");
|
|
15581
|
+
if (new Set(comparisonTargets.map((target) => target.value)).size !== comparisonTargets.length) throw validationError("Competitor domains must be distinct.");
|
|
15582
|
+
if (comparisonTargets.some((competitor) => targets.some((target) => target.value === competitor.value))) throw validationError("A keyword-gap competitor must differ from every Target domain.");
|
|
15583
|
+
} else if (input.competitors !== void 0) {
|
|
15584
|
+
throw validationError("Comparison competitors apply only to keyword gaps.");
|
|
15585
|
+
}
|
|
15525
15586
|
const keywords = [...new Set(input.keywords)];
|
|
15526
15587
|
if (input.capabilities.includes(SeoCapabilities["keyword-metrics"]) && !keywords.length) throw validationError("Keyword metrics requires at least one explicit keyword.");
|
|
15527
15588
|
if (input.capabilities.some((capability) => capability !== SeoCapabilities["keyword-metrics"]) && !targets.length) throw validationError("Domain capabilities require at least one explicit Target.");
|
|
@@ -15535,7 +15596,7 @@ function normalizeProfile(input) {
|
|
|
15535
15596
|
} else if (input.rankings) {
|
|
15536
15597
|
throw validationError("Search settings apply only to market rankings.");
|
|
15537
15598
|
}
|
|
15538
|
-
return { ...input, targets, keywords };
|
|
15599
|
+
return { ...input, targets, keywords, ...input.competitors === void 0 ? {} : { competitors: comparisonTargets } };
|
|
15539
15600
|
}
|
|
15540
15601
|
var NO_MARKET_RANKINGS = { searches: [] };
|
|
15541
15602
|
function advancedMarketQueries(plan, scope) {
|
|
@@ -15630,6 +15691,18 @@ function marketRankingPlan(db, projectId, input) {
|
|
|
15630
15691
|
}
|
|
15631
15692
|
function taskRequests(profile, rankings) {
|
|
15632
15693
|
return profile.capabilities.flatMap((capability) => {
|
|
15694
|
+
if (capability === SeoCapabilities["keyword-gaps"]) {
|
|
15695
|
+
return profile.targets.flatMap((target) => (profile.competitors ?? []).flatMap((competitor) => Object.values(SeoKeywordGapModes).map((mode) => ({
|
|
15696
|
+
capability,
|
|
15697
|
+
target,
|
|
15698
|
+
keywords: [],
|
|
15699
|
+
locationCode: profile.locationCode,
|
|
15700
|
+
languageCode: profile.languageCode,
|
|
15701
|
+
limit: profile.limit,
|
|
15702
|
+
tag: "",
|
|
15703
|
+
comparison: { competitor, mode }
|
|
15704
|
+
}))));
|
|
15705
|
+
}
|
|
15633
15706
|
if (capability === SeoCapabilities["market-rankings"]) {
|
|
15634
15707
|
const settings = profile.rankings;
|
|
15635
15708
|
return rankings.searches.map((search) => ({
|
|
@@ -15891,7 +15964,7 @@ async function seoRoutes(app, opts) {
|
|
|
15891
15964
|
const row = job(resolveProject(app.db, request.params.name).id, request.params.id);
|
|
15892
15965
|
const rows = tasks(row.id);
|
|
15893
15966
|
const results = resultRows(rows);
|
|
15894
|
-
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, ...taskSearch(task), retried: task.retryOf !== 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) };
|
|
15967
|
+
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, comparison: task.request.comparison ?? null, status: task.status, error: task.error, remoteTaskId: task.receipts[0]?.remoteTaskId ?? null, ...taskSearch(task), retried: task.retryOf !== 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), competitors: results.competitors.slice(0, 20), keywordGaps: results.keywordGaps.slice(0, 20) }, resultCounts: { keywordMetrics: results.keywordMetrics.length, domainIntelligence: results.domainIntelligence.length, rankedKeywords: results.rankedKeywords.length, marketRankings: results.marketRankings.length, competitors: results.competitors.length, keywordGaps: results.keywordGaps.length }, resultsTruncated: Object.values(results).some((items) => items.length > 20) };
|
|
15895
15968
|
});
|
|
15896
15969
|
app.post("/projects/:name/seo/runs/:id/cancel", { config: { paidRead: true, writeScope: SEO_RUN_SCOPE } }, async (request) => {
|
|
15897
15970
|
requireScope(request, SEO_RUN_SCOPE);
|
|
@@ -15910,7 +15983,7 @@ async function seoRoutes(app, opts) {
|
|
|
15910
15983
|
const results = resultRows(rows);
|
|
15911
15984
|
const primary = row?.profile.targets[0];
|
|
15912
15985
|
const domain = primary ? results.domainIntelligence.find((result) => result.target.kind === primary.kind && result.target.value === primary.value && result.target.includeSubdomains === primary.includeSubdomains) : null;
|
|
15913
|
-
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 };
|
|
15986
|
+
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, competitorCount: results.competitors.length, keywordGapCount: results.keywordGaps.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 };
|
|
15914
15987
|
});
|
|
15915
15988
|
for (const [path8, key] of [["keyword-metrics", "keywordMetrics"], ["domain-intelligence", "domainIntelligence"], ["ranked-keywords", "rankedKeywords"]]) app.get(`/projects/:name/seo/${path8}`, async (request) => {
|
|
15916
15989
|
const query = queryInput(request.query);
|
|
@@ -15920,6 +15993,30 @@ async function seoRoutes(app, opts) {
|
|
|
15920
15993
|
const offset = query.offset ?? 0;
|
|
15921
15994
|
return { runId: row?.id ?? null, items: items.slice(offset, offset + limit), total: items.length, limit, offset };
|
|
15922
15995
|
});
|
|
15996
|
+
app.get("/projects/:name/seo/competitors", async (request) => {
|
|
15997
|
+
const raw = request.query;
|
|
15998
|
+
const parsed = seoCompetitorQuerySchema.safeParse({ ...raw, ...raw.limit === void 0 ? {} : { limit: Number(raw.limit) }, ...raw.offset === void 0 ? {} : { offset: Number(raw.offset) } });
|
|
15999
|
+
if (!parsed.success) throw validationError("Invalid SEO competitor selection.");
|
|
16000
|
+
const query = parsed.data;
|
|
16001
|
+
const row = selected(resolveProject(app.db, request.params.name).id, query, true);
|
|
16002
|
+
const items = row ? resultRows(tasks(row.id)).competitors : [];
|
|
16003
|
+
if (query.sort) items.sort(compareSeoCompetitorsBy(query.sort));
|
|
16004
|
+
const limit = query.limit ?? 50;
|
|
16005
|
+
const offset = query.offset ?? 0;
|
|
16006
|
+
return { runId: row?.id ?? null, items: items.slice(offset, offset + limit), total: items.length, limit, offset };
|
|
16007
|
+
});
|
|
16008
|
+
app.get("/projects/:name/seo/keyword-gaps", async (request) => {
|
|
16009
|
+
const raw = request.query;
|
|
16010
|
+
const parsed = seoKeywordGapQuerySchema.safeParse({ ...raw, ...raw.limit === void 0 ? {} : { limit: Number(raw.limit) }, ...raw.offset === void 0 ? {} : { offset: Number(raw.offset) } });
|
|
16011
|
+
if (!parsed.success) throw validationError("Invalid SEO keyword-gap selection.");
|
|
16012
|
+
const query = parsed.data;
|
|
16013
|
+
const row = selected(resolveProject(app.db, request.params.name).id, query, true);
|
|
16014
|
+
const items = (row ? resultRows(tasks(row.id)).keywordGaps : []).filter((item2) => !query.kind || (query.kind === SeoKeywordGapKinds.shared ? item2.kind !== SeoKeywordGapKinds.missing : item2.kind === query.kind));
|
|
16015
|
+
if (query.sort) items.sort(compareSeoKeywordGapsBy(query.sort));
|
|
16016
|
+
const limit = query.limit ?? 50;
|
|
16017
|
+
const offset = query.offset ?? 0;
|
|
16018
|
+
return { runId: row?.id ?? null, items: items.slice(offset, offset + limit), total: items.length, limit, offset };
|
|
16019
|
+
});
|
|
15923
16020
|
app.get("/projects/:name/seo/market-rankings", async (request) => {
|
|
15924
16021
|
const raw = request.query ?? {};
|
|
15925
16022
|
const parsed = seoMarketRankingQuerySchema.safeParse({ ...raw, ...raw.limit === void 0 ? {} : { limit: Number(raw.limit) }, ...raw.offset === void 0 ? {} : { offset: Number(raw.offset) } });
|
|
@@ -34192,6 +34289,8 @@ var SCHEMA_TABLE = {
|
|
|
34192
34289
|
SeoRunList: seoRunListSchema,
|
|
34193
34290
|
SeoOverview: seoOverviewSchema,
|
|
34194
34291
|
SeoKeywordMetricsList: seoKeywordMetricsListSchema,
|
|
34292
|
+
SeoCompetitorList: seoCompetitorListSchema,
|
|
34293
|
+
SeoKeywordGapList: seoKeywordGapListSchema,
|
|
34195
34294
|
SeoDomainIntelligenceList: seoDomainIntelligenceListSchema,
|
|
34196
34295
|
SeoRankedKeywordList: seoRankedKeywordListSchema,
|
|
34197
34296
|
SeoMarketRankingList: seoMarketRankingListSchema,
|
|
@@ -35425,12 +35524,40 @@ var routeCatalog = [
|
|
|
35425
35524
|
409: errorResponse("Selection, intent or execution identity changed.")
|
|
35426
35525
|
}
|
|
35427
35526
|
},
|
|
35527
|
+
{
|
|
35528
|
+
method: "get",
|
|
35529
|
+
path: "/api/v1/projects/{name}/seo/competitors",
|
|
35530
|
+
summary: "Read stored organic search competitors",
|
|
35531
|
+
tags: ["seo"],
|
|
35532
|
+
description: "Stored whole-domain organic competitor discovery only; never calls a provider. Available for Simple and Advanced project profiles. Each row names its target and source locale. Results are bounded provider database observations, not an exhaustive competitor inventory or a market/Property measurement.",
|
|
35533
|
+
parameters: [nameParameter, ...seoEvidenceParameters, { name: "sort", in: "query", description: "Sort the complete saved result before pagination: domain, shared searches, average position or estimated traffic. Prefix with - for descending. Missing values stay last in both directions. Omitted preserves stored order.", schema: { type: "string", enum: [...seoCompetitorSortSchema.options] } }],
|
|
35534
|
+
responses: {
|
|
35535
|
+
200: jsonResponse("Saved organic competitor evidence.", "SeoCompetitorList"),
|
|
35536
|
+
400: errorResponse("Invalid scope or input."),
|
|
35537
|
+
403: errorResponse("Insufficient authority."),
|
|
35538
|
+
404: errorResponse("Project or selected evidence not found.")
|
|
35539
|
+
}
|
|
35540
|
+
},
|
|
35541
|
+
{
|
|
35542
|
+
method: "get",
|
|
35543
|
+
path: "/api/v1/projects/{name}/seo/keyword-gaps",
|
|
35544
|
+
summary: "Read stored organic keyword comparisons",
|
|
35545
|
+
tags: ["seo"],
|
|
35546
|
+
description: "Stored whole-domain comparisons only; never calls a provider. Missing means the source database has a competitor ranking and no target ranking, not absence from Google. Shared includes outranked searches where both domains rank; outranked selects only those with the competitor ahead. Filters apply before pagination; total counts saved rows, not the full provider keyword universe. A signed rankGap is target position minus competitor position and is null when the target is missing.",
|
|
35547
|
+
parameters: [nameParameter, ...seoEvidenceParameters, { name: "kind", in: "query", description: "shared includes outranked; missing and outranked select that exact kind. Omitted returns all saved comparisons.", schema: { type: "string", enum: [...seoKeywordGapKindSchema.options] } }, { name: "sort", in: "query", description: "Sort after filtering and before pagination: keyword, kind, target rank, competitor rank, rank gap or monthly search volume. Prefix with - for descending. Missing values stay last in both directions. Omitted preserves stored order.", schema: { type: "string", enum: [...seoKeywordGapSortSchema.options] } }],
|
|
35548
|
+
responses: {
|
|
35549
|
+
200: jsonResponse("Saved keyword comparison evidence.", "SeoKeywordGapList"),
|
|
35550
|
+
400: errorResponse("Invalid scope or input."),
|
|
35551
|
+
403: errorResponse("Insufficient authority."),
|
|
35552
|
+
404: errorResponse("Project or selected evidence not found.")
|
|
35553
|
+
}
|
|
35554
|
+
},
|
|
35428
35555
|
{
|
|
35429
35556
|
method: "get",
|
|
35430
35557
|
path: "/api/v1/projects/{name}/seo/market-rankings",
|
|
35431
35558
|
summary: "Read stored market rankings",
|
|
35432
35559
|
tags: ["seo"],
|
|
35433
|
-
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. searchStatus says how each search ended: in a completed or partial row a null rank means the site was not in the checked results (depth, which is lower when only part of the results came back); a failed or cancelled search keeps its rows with no rank. outcome says where each row landed (a partial search without the site is partly-checked, never not-ranked) and rivalAhead whether a tracked competitor ranks above the site.",
|
|
35560
|
+
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. searchStatus says how each search ended: in a completed or partial row a null rank means the site was not in the checked results (depth, which is lower when only part of the results came back); a failed or cancelled search keeps its rows with no rank. outcome says where each row landed (a partial search without the site is partly-checked, never not-ranked) and rivalAhead whether a tracked competitor ranks above the site. topCompetitor is the best tracked rival, and rankGap is the target position minus that rival position, null when either rank is unavailable.",
|
|
35434
35561
|
parameters: [
|
|
35435
35562
|
nameParameter,
|
|
35436
35563
|
...seoEvidenceParameters,
|
|
@@ -35474,7 +35601,7 @@ var routeCatalog = [
|
|
|
35474
35601
|
path: "/api/v1/projects/{name}/seo/profiles/{id}",
|
|
35475
35602
|
summary: "Save an SEO profile",
|
|
35476
35603
|
tags: ["seo"],
|
|
35477
|
-
description: "Requires seo.write and an administrator session. Saving never starts provider work. Scope, locale, targets, capabilities, keywords and row limit are identity parameters.",
|
|
35604
|
+
description: "Requires seo.write and an administrator session. Saving never starts provider work. Scope, locale, targets, comparison competitors, capabilities, keywords and row limit are identity parameters. Keyword gaps requires one to five explicit competitor root domains; shared and missing datasets are reserved separately for each target/competitor pair. Competitor discovery and keyword gaps support project scope with whole-domain targets only, including Advanced projects.",
|
|
35478
35605
|
parameters: [nameParameter, { name: "id", in: "path", required: true, description: "Exact project-scoped identifier.", schema: stringSchema }],
|
|
35479
35606
|
requestBody: { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/SeoProfileInput" } } } },
|
|
35480
35607
|
responses: {
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
} from "./chunk-VJWDBFSL.js";
|
|
5
5
|
import {
|
|
6
6
|
createApiClient
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-7OAXEMOF.js";
|
|
8
8
|
import {
|
|
9
9
|
CliError,
|
|
10
10
|
isMachineFormat
|
|
11
11
|
} from "./chunk-HGMYEDAQ.js";
|
|
12
12
|
import {
|
|
13
13
|
describeError
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-PM2DB3HE.js";
|
|
15
15
|
|
|
16
16
|
// src/commands/mcp.ts
|
|
17
17
|
import fs from "fs";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getRequestContext
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-3MGM3U6V.js";
|
|
4
4
|
import {
|
|
5
5
|
diagnosticIdentity,
|
|
6
6
|
redactLogString,
|
|
7
7
|
redactLogValue
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-PM2DB3HE.js";
|
|
9
9
|
|
|
10
10
|
// ../api-routes/src/runtime-logger.ts
|
|
11
11
|
import { format } from "util";
|