@ainyc/canonry 4.99.0 → 4.99.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/assets/{BacklinksPage-CuWs7I1D.js → BacklinksPage-v2RC8wlg.js} +1 -1
- package/assets/assets/{ChartPrimitives-bm37tprp.js → ChartPrimitives-B74e5pqn.js} +1 -1
- package/assets/assets/ProjectPage-BCzS7TQn.js +6 -0
- package/assets/assets/{RunRow-DzhQl87g.js → RunRow-BmVPjO-m.js} +1 -1
- package/assets/assets/{RunsPage-wOaewhbF.js → RunsPage-CK5atXvL.js} +1 -1
- package/assets/assets/{SettingsPage-CJdhhkkE.js → SettingsPage-DarPFUPl.js} +1 -1
- package/assets/assets/{TrafficPage-BJBeN5Ph.js → TrafficPage-Ddnn_WqU.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-D3PmH0B1.js → TrafficSourceDetailPage-g08SKnwZ.js} +1 -1
- package/assets/assets/{arrow-left-BmITMI6O.js → arrow-left-D39Vxasb.js} +1 -1
- package/assets/assets/{extract-error-message-BGwnyAfP.js → extract-error-message-BiNWqBeh.js} +1 -1
- package/assets/assets/{index-BsZNCwg8.css → index-BCuWbxUj.css} +1 -1
- package/assets/assets/index-Dd4J1VhB.js +210 -0
- package/assets/assets/{trash-2-Bd2mkP_w.js → trash-2-xh6gsnap.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-CEHD33NQ.js → chunk-4Q3LBD7A.js} +251 -111
- package/dist/{chunk-VQN74WWA.js → chunk-IDI6LMYL.js} +1 -1
- package/dist/{chunk-CWFTMFEU.js → chunk-JF7EXHGA.js} +12 -3
- package/dist/{chunk-DJVUNURL.js → chunk-YBIT5O62.js} +36 -29
- package/dist/cli.js +4 -4
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-QHBXW4KI.js → intelligence-service-TWCEKVKZ.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +8 -8
- package/assets/assets/ProjectPage-BksYe8FP.js +0 -6
- package/assets/assets/index-BSX6r6ju.js +0 -210
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
loadConfig,
|
|
10
10
|
loadConfigRaw,
|
|
11
11
|
saveConfigPatch
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-IDI6LMYL.js";
|
|
13
13
|
import {
|
|
14
14
|
CC_CACHE_DIR,
|
|
15
15
|
DUCKDB_SPEC,
|
|
@@ -111,7 +111,7 @@ import {
|
|
|
111
111
|
siteAuditPages,
|
|
112
112
|
siteAuditSnapshots,
|
|
113
113
|
usageCounters
|
|
114
|
-
} from "./chunk-
|
|
114
|
+
} from "./chunk-YBIT5O62.js";
|
|
115
115
|
import {
|
|
116
116
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
117
117
|
AGENT_PROVIDER_IDS,
|
|
@@ -171,7 +171,7 @@ import {
|
|
|
171
171
|
validationError,
|
|
172
172
|
winnabilityClassLabel,
|
|
173
173
|
withRetry
|
|
174
|
-
} from "./chunk-
|
|
174
|
+
} from "./chunk-JF7EXHGA.js";
|
|
175
175
|
|
|
176
176
|
// src/telemetry.ts
|
|
177
177
|
import crypto from "crypto";
|
|
@@ -1473,7 +1473,8 @@ function extractWebSearchToolErrors(rawResponse) {
|
|
|
1473
1473
|
if (!content) return [];
|
|
1474
1474
|
for (const block of content) {
|
|
1475
1475
|
if (block.type !== "web_search_tool_result") continue;
|
|
1476
|
-
if (block.content === null || typeof block.content !== "object" || Array.isArray(block.content))
|
|
1476
|
+
if (block.content === null || typeof block.content !== "object" || Array.isArray(block.content))
|
|
1477
|
+
continue;
|
|
1477
1478
|
const errorCode = block.content.error_code;
|
|
1478
1479
|
if (typeof errorCode === "string" && errorCode.length > 0) {
|
|
1479
1480
|
errors.add(errorCode);
|
|
@@ -6402,7 +6403,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
6402
6403
|
return result;
|
|
6403
6404
|
}
|
|
6404
6405
|
async function backfillInsightsCommand(project, opts) {
|
|
6405
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
6406
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-TWCEKVKZ.js");
|
|
6406
6407
|
const config = loadConfig();
|
|
6407
6408
|
const db = createClient(config.database);
|
|
6408
6409
|
migrate(db);
|
|
@@ -10425,9 +10426,7 @@ var API_ADAPTERS = [
|
|
|
10425
10426
|
localAdapter,
|
|
10426
10427
|
perplexityAdapter
|
|
10427
10428
|
];
|
|
10428
|
-
var BROWSER_ADAPTERS = [
|
|
10429
|
-
cdpChatgptAdapter
|
|
10430
|
-
];
|
|
10429
|
+
var BROWSER_ADAPTERS = [cdpChatgptAdapter];
|
|
10431
10430
|
var adapterMap = Object.fromEntries(
|
|
10432
10431
|
API_ADAPTERS.map((a) => [a.name, a])
|
|
10433
10432
|
);
|
|
@@ -10477,13 +10476,18 @@ function verifyDashboardPassword(password, storedHash) {
|
|
|
10477
10476
|
N: DASHBOARD_SCRYPT_COST,
|
|
10478
10477
|
maxmem: DASHBOARD_SCRYPT_MAXMEM
|
|
10479
10478
|
});
|
|
10480
|
-
if (derived.length !== expected.length)
|
|
10481
|
-
|
|
10479
|
+
if (derived.length !== expected.length)
|
|
10480
|
+
return { ok: false, needsRehash: false };
|
|
10481
|
+
return {
|
|
10482
|
+
ok: crypto20.timingSafeEqual(derived, expected),
|
|
10483
|
+
needsRehash: false
|
|
10484
|
+
};
|
|
10482
10485
|
}
|
|
10483
10486
|
if (/^[a-f0-9]{64}$/i.test(storedHash)) {
|
|
10484
10487
|
const candidate = Buffer.from(hashApiKey(password), "hex");
|
|
10485
10488
|
const expected = Buffer.from(storedHash, "hex");
|
|
10486
|
-
if (candidate.length !== expected.length)
|
|
10489
|
+
if (candidate.length !== expected.length)
|
|
10490
|
+
return { ok: false, needsRehash: false };
|
|
10487
10491
|
const ok = crypto20.timingSafeEqual(candidate, expected);
|
|
10488
10492
|
return { ok, needsRehash: ok };
|
|
10489
10493
|
}
|
|
@@ -10525,7 +10529,11 @@ function serializeSessionCookie(opts) {
|
|
|
10525
10529
|
function applyLegacyCredentials(rows, config) {
|
|
10526
10530
|
let migratedGoogle = 0;
|
|
10527
10531
|
for (const row of rows.google) {
|
|
10528
|
-
const existing = getGoogleConnection(
|
|
10532
|
+
const existing = getGoogleConnection(
|
|
10533
|
+
config,
|
|
10534
|
+
row.domain,
|
|
10535
|
+
row.connectionType
|
|
10536
|
+
);
|
|
10529
10537
|
if (existing?.refreshToken) continue;
|
|
10530
10538
|
upsertGoogleConnection(config, {
|
|
10531
10539
|
domain: row.domain,
|
|
@@ -10604,10 +10612,12 @@ async function createServer(opts) {
|
|
|
10604
10612
|
error: err instanceof Error ? err.message : String(err)
|
|
10605
10613
|
});
|
|
10606
10614
|
}
|
|
10607
|
-
log21.info("providers.configured", {
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10615
|
+
log21.info("providers.configured", {
|
|
10616
|
+
providers: Object.keys(providers).filter((k) => {
|
|
10617
|
+
const p = providers[k];
|
|
10618
|
+
return p?.apiKey || p?.baseUrl || p?.vertexProject;
|
|
10619
|
+
})
|
|
10620
|
+
});
|
|
10611
10621
|
for (const adapter of API_ADAPTERS) {
|
|
10612
10622
|
const entry = providers[adapter.name];
|
|
10613
10623
|
if (!entry) continue;
|
|
@@ -10627,7 +10637,11 @@ async function createServer(opts) {
|
|
|
10627
10637
|
}
|
|
10628
10638
|
const cdpConfig = opts.config.cdp;
|
|
10629
10639
|
if (cdpConfig?.host || cdpConfig?.port) {
|
|
10630
|
-
const CDP_DEFAULT_QUOTA = {
|
|
10640
|
+
const CDP_DEFAULT_QUOTA = {
|
|
10641
|
+
maxConcurrency: 1,
|
|
10642
|
+
maxRequestsPerMinute: 4,
|
|
10643
|
+
maxRequestsPerDay: 200
|
|
10644
|
+
};
|
|
10631
10645
|
const cdpEndpoint = `ws://${cdpConfig.host ?? "localhost"}:${cdpConfig.port ?? 9222}`;
|
|
10632
10646
|
registry.register(cdpChatgptAdapter, {
|
|
10633
10647
|
provider: "cdp:chatgpt",
|
|
@@ -10641,7 +10655,9 @@ async function createServer(opts) {
|
|
|
10641
10655
|
jobRunner.recoverStaleRuns();
|
|
10642
10656
|
const notifier = new Notifier(opts.db, serverUrl);
|
|
10643
10657
|
const intelligenceService = new IntelligenceService(opts.db);
|
|
10644
|
-
const aeroClient = new ApiClient(opts.config.apiUrl, opts.config.apiKey, {
|
|
10658
|
+
const aeroClient = new ApiClient(opts.config.apiUrl, opts.config.apiKey, {
|
|
10659
|
+
skipProbe: true
|
|
10660
|
+
});
|
|
10645
10661
|
const agentEnabled = resolveAgentEnabled(process.env, opts.config);
|
|
10646
10662
|
const sessionRegistry = agentEnabled ? new SessionRegistry({
|
|
10647
10663
|
db: opts.db,
|
|
@@ -10688,7 +10704,9 @@ async function createServer(opts) {
|
|
|
10688
10704
|
const runGbpSync = (runId, projectId, syncOpts) => {
|
|
10689
10705
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getGoogleAuthConfig(opts.config);
|
|
10690
10706
|
if (!googleClientId || !googleClientSecret) {
|
|
10691
|
-
app.log.error(
|
|
10707
|
+
app.log.error(
|
|
10708
|
+
"GBP sync requested but Google OAuth credentials are not configured in the local config"
|
|
10709
|
+
);
|
|
10692
10710
|
return;
|
|
10693
10711
|
}
|
|
10694
10712
|
executeGbpSync(opts.db, runId, projectId, {
|
|
@@ -10741,7 +10759,14 @@ async function createServer(opts) {
|
|
|
10741
10759
|
},
|
|
10742
10760
|
onTrafficSyncRequested: (projectName, sourceId) => {
|
|
10743
10761
|
aeroClient.trafficSync(projectName, sourceId).catch((err) => {
|
|
10744
|
-
app.log.error(
|
|
10762
|
+
app.log.error(
|
|
10763
|
+
{
|
|
10764
|
+
projectName,
|
|
10765
|
+
sourceId,
|
|
10766
|
+
err: err instanceof Error ? err.message : String(err)
|
|
10767
|
+
},
|
|
10768
|
+
"Scheduled traffic sync failed"
|
|
10769
|
+
);
|
|
10745
10770
|
});
|
|
10746
10771
|
},
|
|
10747
10772
|
onGbpSyncRequested: (runId, projectId) => {
|
|
@@ -10756,21 +10781,33 @@ async function createServer(opts) {
|
|
|
10756
10781
|
onBacklinksSyncRequested: (projectName) => {
|
|
10757
10782
|
void (async () => {
|
|
10758
10783
|
const probed = await probeLatestRelease().catch((err) => {
|
|
10759
|
-
app.log.warn(
|
|
10784
|
+
app.log.warn(
|
|
10785
|
+
{ projectName, err },
|
|
10786
|
+
"Scheduled backlinks sync: latest-release probe failed"
|
|
10787
|
+
);
|
|
10760
10788
|
return null;
|
|
10761
10789
|
});
|
|
10762
10790
|
if (!probed) return;
|
|
10763
|
-
const alreadySynced = opts.db.select().from(ccReleaseSyncs).where(
|
|
10764
|
-
|
|
10765
|
-
|
|
10766
|
-
|
|
10791
|
+
const alreadySynced = opts.db.select().from(ccReleaseSyncs).where(
|
|
10792
|
+
and14(
|
|
10793
|
+
eq20(ccReleaseSyncs.release, probed.release),
|
|
10794
|
+
eq20(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)
|
|
10795
|
+
)
|
|
10796
|
+
).limit(1).get();
|
|
10767
10797
|
if (alreadySynced) {
|
|
10768
|
-
app.log.info(
|
|
10798
|
+
app.log.info(
|
|
10799
|
+
{ projectName, release: probed.release },
|
|
10800
|
+
"Scheduled backlinks sync: already up to date, skipping"
|
|
10801
|
+
);
|
|
10769
10802
|
return;
|
|
10770
10803
|
}
|
|
10771
10804
|
aeroClient.backlinksTriggerSync(probed.release).catch((err) => {
|
|
10772
10805
|
app.log.error(
|
|
10773
|
-
{
|
|
10806
|
+
{
|
|
10807
|
+
projectName,
|
|
10808
|
+
release: probed.release,
|
|
10809
|
+
err: err instanceof Error ? err.message : String(err)
|
|
10810
|
+
},
|
|
10774
10811
|
"Scheduled backlinks sync failed"
|
|
10775
10812
|
);
|
|
10776
10813
|
});
|
|
@@ -10779,7 +10816,10 @@ async function createServer(opts) {
|
|
|
10779
10816
|
if (project && bingConnectionStore.getConnection(project.canonicalDomain)) {
|
|
10780
10817
|
aeroClient.backlinksBingSync(projectName).catch((err) => {
|
|
10781
10818
|
app.log.error(
|
|
10782
|
-
{
|
|
10819
|
+
{
|
|
10820
|
+
projectName,
|
|
10821
|
+
err: err instanceof Error ? err.message : String(err)
|
|
10822
|
+
},
|
|
10783
10823
|
"Scheduled Bing backlinks sync failed"
|
|
10784
10824
|
);
|
|
10785
10825
|
});
|
|
@@ -10801,7 +10841,9 @@ async function createServer(opts) {
|
|
|
10801
10841
|
vertexConfigured: adapter.name === "gemini" ? !!opts.config.providers?.gemini?.vertexProject : void 0
|
|
10802
10842
|
}));
|
|
10803
10843
|
const googleSettingsSummary = {
|
|
10804
|
-
configured: Boolean(
|
|
10844
|
+
configured: Boolean(
|
|
10845
|
+
opts.config.google?.clientId && opts.config.google?.clientSecret
|
|
10846
|
+
)
|
|
10805
10847
|
};
|
|
10806
10848
|
const bingSettingsSummary = {
|
|
10807
10849
|
// Treat Bing as configured if there is at least one connection with an API key,
|
|
@@ -10818,8 +10860,13 @@ async function createServer(opts) {
|
|
|
10818
10860
|
upsertConnection: (connection) => {
|
|
10819
10861
|
if (!opts.config.bing) opts.config.bing = {};
|
|
10820
10862
|
if (!opts.config.bing.connections) opts.config.bing.connections = [];
|
|
10821
|
-
const idx = opts.config.bing.connections.findIndex(
|
|
10822
|
-
|
|
10863
|
+
const idx = opts.config.bing.connections.findIndex(
|
|
10864
|
+
(c) => c.domain === connection.domain
|
|
10865
|
+
);
|
|
10866
|
+
const normalized = {
|
|
10867
|
+
...connection,
|
|
10868
|
+
createdByProjectId: connection.createdByProjectId ?? null
|
|
10869
|
+
};
|
|
10823
10870
|
if (idx >= 0) {
|
|
10824
10871
|
opts.config.bing.connections[idx] = normalized;
|
|
10825
10872
|
} else {
|
|
@@ -10829,7 +10876,9 @@ async function createServer(opts) {
|
|
|
10829
10876
|
return normalized;
|
|
10830
10877
|
},
|
|
10831
10878
|
updateConnection: (domain, patch) => {
|
|
10832
|
-
const conn = opts.config.bing?.connections?.find(
|
|
10879
|
+
const conn = opts.config.bing?.connections?.find(
|
|
10880
|
+
(c) => c.domain === domain
|
|
10881
|
+
);
|
|
10833
10882
|
if (!conn) return void 0;
|
|
10834
10883
|
Object.assign(conn, patch);
|
|
10835
10884
|
saveConfigPatch(opts.config);
|
|
@@ -10837,7 +10886,9 @@ async function createServer(opts) {
|
|
|
10837
10886
|
},
|
|
10838
10887
|
deleteConnection: (domain) => {
|
|
10839
10888
|
if (!opts.config.bing?.connections) return false;
|
|
10840
|
-
const idx = opts.config.bing.connections.findIndex(
|
|
10889
|
+
const idx = opts.config.bing.connections.findIndex(
|
|
10890
|
+
(c) => c.domain === domain
|
|
10891
|
+
);
|
|
10841
10892
|
if (idx < 0) return false;
|
|
10842
10893
|
opts.config.bing.connections.splice(idx, 1);
|
|
10843
10894
|
saveConfigPatch(opts.config);
|
|
@@ -10884,7 +10935,10 @@ async function createServer(opts) {
|
|
|
10884
10935
|
return updated;
|
|
10885
10936
|
},
|
|
10886
10937
|
deleteConnection: (projectName) => {
|
|
10887
|
-
const removed = removeWordpressTrafficConnection(
|
|
10938
|
+
const removed = removeWordpressTrafficConnection(
|
|
10939
|
+
opts.config,
|
|
10940
|
+
projectName
|
|
10941
|
+
);
|
|
10888
10942
|
if (removed) saveConfigPatch(opts.config);
|
|
10889
10943
|
return removed;
|
|
10890
10944
|
}
|
|
@@ -10914,12 +10968,21 @@ async function createServer(opts) {
|
|
|
10914
10968
|
return updated;
|
|
10915
10969
|
},
|
|
10916
10970
|
updateConnection: (domain, connectionType, patch) => {
|
|
10917
|
-
const updated = patchGoogleConnection(
|
|
10971
|
+
const updated = patchGoogleConnection(
|
|
10972
|
+
opts.config,
|
|
10973
|
+
domain,
|
|
10974
|
+
connectionType,
|
|
10975
|
+
patch
|
|
10976
|
+
);
|
|
10918
10977
|
if (updated) saveConfigPatch(opts.config);
|
|
10919
10978
|
return updated;
|
|
10920
10979
|
},
|
|
10921
10980
|
deleteConnection: (domain, connectionType) => {
|
|
10922
|
-
const removed = removeGoogleConnection(
|
|
10981
|
+
const removed = removeGoogleConnection(
|
|
10982
|
+
opts.config,
|
|
10983
|
+
domain,
|
|
10984
|
+
connectionType
|
|
10985
|
+
);
|
|
10923
10986
|
if (removed) saveConfigPatch(opts.config);
|
|
10924
10987
|
return removed;
|
|
10925
10988
|
}
|
|
@@ -11010,13 +11073,16 @@ async function createServer(opts) {
|
|
|
11010
11073
|
const key = getDefaultApiKey();
|
|
11011
11074
|
if (!key || key.revokedAt) return false;
|
|
11012
11075
|
const sessionId = createSession(key.id);
|
|
11013
|
-
reply.header(
|
|
11014
|
-
|
|
11015
|
-
|
|
11016
|
-
|
|
11017
|
-
|
|
11018
|
-
|
|
11019
|
-
|
|
11076
|
+
reply.header(
|
|
11077
|
+
"set-cookie",
|
|
11078
|
+
serializeSessionCookie({
|
|
11079
|
+
name: SESSION_COOKIE_NAME,
|
|
11080
|
+
value: sessionId,
|
|
11081
|
+
path: sessionCookiePath,
|
|
11082
|
+
secure: sessionCookieSecure,
|
|
11083
|
+
ttlMs: SESSION_TTL_MS
|
|
11084
|
+
})
|
|
11085
|
+
);
|
|
11020
11086
|
return true;
|
|
11021
11087
|
};
|
|
11022
11088
|
const boundToLoopback = isLoopbackBindHost(opts.host);
|
|
@@ -11037,7 +11103,9 @@ async function createServer(opts) {
|
|
|
11037
11103
|
});
|
|
11038
11104
|
app.post(apiPrefix + "/session/setup", async (request, reply) => {
|
|
11039
11105
|
if (!boundToLoopback && !requestHasValidApiKey(request)) {
|
|
11040
|
-
const err = authRequired(
|
|
11106
|
+
const err = authRequired(
|
|
11107
|
+
"This server is network-reachable; setting the dashboard password requires a valid API key."
|
|
11108
|
+
);
|
|
11041
11109
|
return reply.status(err.statusCode).send(err.toJSON());
|
|
11042
11110
|
}
|
|
11043
11111
|
if (opts.config.dashboardPasswordHash) {
|
|
@@ -11062,19 +11130,31 @@ async function createServer(opts) {
|
|
|
11062
11130
|
const apiKey = request.body?.apiKey?.trim();
|
|
11063
11131
|
if (password) {
|
|
11064
11132
|
if (!opts.config.dashboardPasswordHash) {
|
|
11065
|
-
const err2 = validationError(
|
|
11133
|
+
const err2 = validationError(
|
|
11134
|
+
"No dashboard password configured \u2014 use /session/setup first"
|
|
11135
|
+
);
|
|
11066
11136
|
return reply.status(err2.statusCode).send(err2.toJSON());
|
|
11067
11137
|
}
|
|
11068
|
-
const verification = verifyDashboardPassword(
|
|
11138
|
+
const verification = verifyDashboardPassword(
|
|
11139
|
+
password,
|
|
11140
|
+
opts.config.dashboardPasswordHash
|
|
11141
|
+
);
|
|
11069
11142
|
if (!verification.ok) {
|
|
11070
|
-
return reply.status(401).send({
|
|
11143
|
+
return reply.status(401).send({
|
|
11144
|
+
error: { code: "AUTH_INVALID", message: "Incorrect password" }
|
|
11145
|
+
});
|
|
11071
11146
|
}
|
|
11072
11147
|
if (verification.needsRehash) {
|
|
11073
11148
|
opts.config.dashboardPasswordHash = hashDashboardPassword(password);
|
|
11074
11149
|
saveConfigPatch(opts.config);
|
|
11075
11150
|
}
|
|
11076
11151
|
if (!createPasswordSession(reply)) {
|
|
11077
|
-
return reply.status(401).send({
|
|
11152
|
+
return reply.status(401).send({
|
|
11153
|
+
error: {
|
|
11154
|
+
code: "AUTH_INVALID",
|
|
11155
|
+
message: "Server API key not found \u2014 re-run canonry init"
|
|
11156
|
+
}
|
|
11157
|
+
});
|
|
11078
11158
|
}
|
|
11079
11159
|
return reply.send({ authenticated: true });
|
|
11080
11160
|
}
|
|
@@ -11086,13 +11166,16 @@ async function createServer(opts) {
|
|
|
11086
11166
|
}
|
|
11087
11167
|
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq20(apiKeys.id, key.id)).run();
|
|
11088
11168
|
const sessionId = createSession(key.id);
|
|
11089
|
-
reply.header(
|
|
11090
|
-
|
|
11091
|
-
|
|
11092
|
-
|
|
11093
|
-
|
|
11094
|
-
|
|
11095
|
-
|
|
11169
|
+
reply.header(
|
|
11170
|
+
"set-cookie",
|
|
11171
|
+
serializeSessionCookie({
|
|
11172
|
+
name: SESSION_COOKIE_NAME,
|
|
11173
|
+
value: sessionId,
|
|
11174
|
+
path: sessionCookiePath,
|
|
11175
|
+
secure: sessionCookieSecure,
|
|
11176
|
+
ttlMs: SESSION_TTL_MS
|
|
11177
|
+
})
|
|
11178
|
+
);
|
|
11096
11179
|
return reply.send({ authenticated: true });
|
|
11097
11180
|
}
|
|
11098
11181
|
const err = validationError("Either password or apiKey is required");
|
|
@@ -11101,13 +11184,16 @@ async function createServer(opts) {
|
|
|
11101
11184
|
app.delete(apiPrefix + "/session", async (request, reply) => {
|
|
11102
11185
|
const sessionId = parseCookies(request.headers.cookie)[SESSION_COOKIE_NAME];
|
|
11103
11186
|
clearSession(sessionId);
|
|
11104
|
-
reply.header(
|
|
11105
|
-
|
|
11106
|
-
|
|
11107
|
-
|
|
11108
|
-
|
|
11109
|
-
|
|
11110
|
-
|
|
11187
|
+
reply.header(
|
|
11188
|
+
"set-cookie",
|
|
11189
|
+
serializeSessionCookie({
|
|
11190
|
+
name: SESSION_COOKIE_NAME,
|
|
11191
|
+
value: null,
|
|
11192
|
+
path: sessionCookiePath,
|
|
11193
|
+
secure: sessionCookieSecure,
|
|
11194
|
+
ttlMs: SESSION_TTL_MS
|
|
11195
|
+
})
|
|
11196
|
+
);
|
|
11111
11197
|
return reply.status(204).send();
|
|
11112
11198
|
});
|
|
11113
11199
|
const LATEST_RELEASE_TTL_MS = 5 * 60 * 1e3;
|
|
@@ -11132,8 +11218,12 @@ async function createServer(opts) {
|
|
|
11132
11218
|
latestReleaseCache = { value, expiresAt: now + LATEST_RELEASE_TTL_MS };
|
|
11133
11219
|
return value;
|
|
11134
11220
|
};
|
|
11135
|
-
const explainContentRecommendation = createRecommendationExplainer({
|
|
11136
|
-
|
|
11221
|
+
const explainContentRecommendation = createRecommendationExplainer({
|
|
11222
|
+
config: opts.config
|
|
11223
|
+
});
|
|
11224
|
+
const briefContentRecommendation = createRecommendationBriefSynthesizer({
|
|
11225
|
+
config: opts.config
|
|
11226
|
+
});
|
|
11137
11227
|
await app.register(apiRoutes, {
|
|
11138
11228
|
db: opts.db,
|
|
11139
11229
|
routePrefix: apiPrefix,
|
|
@@ -11195,7 +11285,9 @@ async function createServer(opts) {
|
|
|
11195
11285
|
onGscSyncRequested: (runId, projectId, syncOpts) => {
|
|
11196
11286
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getGoogleAuthConfig(opts.config);
|
|
11197
11287
|
if (!googleClientId || !googleClientSecret) {
|
|
11198
|
-
app.log.error(
|
|
11288
|
+
app.log.error(
|
|
11289
|
+
"GSC sync requested but Google OAuth credentials are not configured in the local config"
|
|
11290
|
+
);
|
|
11199
11291
|
return;
|
|
11200
11292
|
}
|
|
11201
11293
|
executeGscSync(opts.db, runId, projectId, {
|
|
@@ -11208,7 +11300,9 @@ async function createServer(opts) {
|
|
|
11208
11300
|
onInspectSitemapRequested: (runId, projectId, inspectOpts) => {
|
|
11209
11301
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getGoogleAuthConfig(opts.config);
|
|
11210
11302
|
if (!googleClientId || !googleClientSecret) {
|
|
11211
|
-
app.log.error(
|
|
11303
|
+
app.log.error(
|
|
11304
|
+
"Inspect sitemap requested but Google OAuth credentials are not configured"
|
|
11305
|
+
);
|
|
11212
11306
|
return;
|
|
11213
11307
|
}
|
|
11214
11308
|
executeInspectSitemap(opts.db, runId, projectId, {
|
|
@@ -11233,7 +11327,9 @@ async function createServer(opts) {
|
|
|
11233
11327
|
pluginDir: PLUGIN_DIR
|
|
11234
11328
|
}),
|
|
11235
11329
|
onInstallBacklinks: async () => {
|
|
11236
|
-
const result = await installDuckdb({
|
|
11330
|
+
const result = await installDuckdb({
|
|
11331
|
+
onLog: (line) => app.log.info({ line }, "duckdb install")
|
|
11332
|
+
});
|
|
11237
11333
|
return {
|
|
11238
11334
|
installed: true,
|
|
11239
11335
|
version: result.version,
|
|
@@ -11256,8 +11352,13 @@ async function createServer(opts) {
|
|
|
11256
11352
|
trigger: RunTriggers.scheduled,
|
|
11257
11353
|
createdAt: now
|
|
11258
11354
|
}).run();
|
|
11259
|
-
executeBacklinkExtract(opts.db, runId, projectId, {
|
|
11260
|
-
|
|
11355
|
+
executeBacklinkExtract(opts.db, runId, projectId, {
|
|
11356
|
+
release: r
|
|
11357
|
+
}).catch((err) => {
|
|
11358
|
+
app.log.error(
|
|
11359
|
+
{ runId, projectId, err },
|
|
11360
|
+
"Auto backlink extract failed"
|
|
11361
|
+
);
|
|
11261
11362
|
});
|
|
11262
11363
|
}
|
|
11263
11364
|
}
|
|
@@ -11266,9 +11367,11 @@ async function createServer(opts) {
|
|
|
11266
11367
|
});
|
|
11267
11368
|
},
|
|
11268
11369
|
onBacklinkExtractRequested: (runId, projectId, release) => {
|
|
11269
|
-
executeBacklinkExtract(opts.db, runId, projectId, { release }).catch(
|
|
11270
|
-
|
|
11271
|
-
|
|
11370
|
+
executeBacklinkExtract(opts.db, runId, projectId, { release }).catch(
|
|
11371
|
+
(err) => {
|
|
11372
|
+
app.log.error({ runId, err }, "Backlink extract failed");
|
|
11373
|
+
}
|
|
11374
|
+
);
|
|
11272
11375
|
},
|
|
11273
11376
|
onBingBacklinkSyncRequested: (runId, projectId) => {
|
|
11274
11377
|
executeBingBacklinkSync(opts.db, runId, projectId, {
|
|
@@ -11303,9 +11406,16 @@ async function createServer(opts) {
|
|
|
11303
11406
|
embedQueries: (queriesToEmbed) => {
|
|
11304
11407
|
const cfg = registry.get("gemini")?.config;
|
|
11305
11408
|
if (!cfg?.apiKey) {
|
|
11306
|
-
return Promise.reject(
|
|
11409
|
+
return Promise.reject(
|
|
11410
|
+
new Error(
|
|
11411
|
+
"Gemini API key not configured; harvest semantic novelty unavailable"
|
|
11412
|
+
)
|
|
11413
|
+
);
|
|
11307
11414
|
}
|
|
11308
|
-
return embedQueries(queriesToEmbed, {
|
|
11415
|
+
return embedQueries(queriesToEmbed, {
|
|
11416
|
+
apiKey: cfg.apiKey,
|
|
11417
|
+
baseUrl: cfg.baseUrl
|
|
11418
|
+
});
|
|
11309
11419
|
},
|
|
11310
11420
|
onSiteAuditRequested: (runId, projectId, auditOpts) => {
|
|
11311
11421
|
runSiteAudit(runId, projectId, auditOpts);
|
|
@@ -11321,7 +11431,10 @@ async function createServer(opts) {
|
|
|
11321
11431
|
const cached = listCachedReleases();
|
|
11322
11432
|
const syncByRelease = /* @__PURE__ */ new Map();
|
|
11323
11433
|
for (const row of opts.db.select().from(ccReleaseSyncs).all()) {
|
|
11324
|
-
syncByRelease.set(row.release, {
|
|
11434
|
+
syncByRelease.set(row.release, {
|
|
11435
|
+
status: row.status,
|
|
11436
|
+
updatedAt: row.updatedAt
|
|
11437
|
+
});
|
|
11325
11438
|
}
|
|
11326
11439
|
return cached.map((entry) => {
|
|
11327
11440
|
const sync = syncByRelease.get(entry.release);
|
|
@@ -11370,16 +11483,20 @@ async function createServer(opts) {
|
|
|
11370
11483
|
wordpressTrafficCredentialStore,
|
|
11371
11484
|
vercelTrafficCredentialStore,
|
|
11372
11485
|
onTrafficSynced: (event) => {
|
|
11373
|
-
trackEvent(
|
|
11374
|
-
|
|
11375
|
-
|
|
11376
|
-
|
|
11377
|
-
|
|
11378
|
-
|
|
11379
|
-
|
|
11380
|
-
|
|
11381
|
-
|
|
11382
|
-
|
|
11486
|
+
trackEvent(
|
|
11487
|
+
"traffic.synced",
|
|
11488
|
+
{
|
|
11489
|
+
status: event.status,
|
|
11490
|
+
sourceType: event.sourceType,
|
|
11491
|
+
sourceId: event.sourceId,
|
|
11492
|
+
pulledEvents: event.pulledEvents,
|
|
11493
|
+
selfTrafficExcluded: event.selfTrafficExcluded,
|
|
11494
|
+
crawlerHits: event.crawlerHits,
|
|
11495
|
+
aiReferralHits: event.aiReferralHits,
|
|
11496
|
+
durationMs: event.durationMs
|
|
11497
|
+
},
|
|
11498
|
+
event.errorCode ? { errorCode: event.errorCode } : void 0
|
|
11499
|
+
);
|
|
11383
11500
|
},
|
|
11384
11501
|
onRunCreated: (runId, projectId, providers2, location) => {
|
|
11385
11502
|
jobRunner.executeRun(runId, projectId, providers2, location).catch((err) => {
|
|
@@ -11442,16 +11559,18 @@ async function createServer(opts) {
|
|
|
11442
11559
|
}).map((project) => project.id);
|
|
11443
11560
|
const targetProjectIds = affectedProjectIds.length > 0 ? affectedProjectIds : [null];
|
|
11444
11561
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11445
|
-
opts.db.insert(auditLog).values(
|
|
11446
|
-
|
|
11447
|
-
|
|
11448
|
-
|
|
11449
|
-
|
|
11450
|
-
|
|
11451
|
-
|
|
11452
|
-
|
|
11453
|
-
|
|
11454
|
-
|
|
11562
|
+
opts.db.insert(auditLog).values(
|
|
11563
|
+
targetProjectIds.map((projectId) => ({
|
|
11564
|
+
id: crypto20.randomUUID(),
|
|
11565
|
+
projectId,
|
|
11566
|
+
actor: "api",
|
|
11567
|
+
action: existing ? "provider.updated" : "provider.created",
|
|
11568
|
+
entityType: "provider",
|
|
11569
|
+
entityId: name,
|
|
11570
|
+
diff,
|
|
11571
|
+
createdAt
|
|
11572
|
+
}))
|
|
11573
|
+
).run();
|
|
11455
11574
|
}
|
|
11456
11575
|
return {
|
|
11457
11576
|
name,
|
|
@@ -11499,7 +11618,10 @@ async function createServer(opts) {
|
|
|
11499
11618
|
"aliases changed \u2014 recomputed mention fields on historical snapshots"
|
|
11500
11619
|
);
|
|
11501
11620
|
} catch (err) {
|
|
11502
|
-
app.log.error(
|
|
11621
|
+
app.log.error(
|
|
11622
|
+
{ err, projectId, projectName },
|
|
11623
|
+
"alias-triggered backfill failed"
|
|
11624
|
+
);
|
|
11503
11625
|
}
|
|
11504
11626
|
});
|
|
11505
11627
|
},
|
|
@@ -11528,7 +11650,11 @@ async function createServer(opts) {
|
|
|
11528
11650
|
app.log.error({ err }, "Failed to save CDP config");
|
|
11529
11651
|
throw err;
|
|
11530
11652
|
}
|
|
11531
|
-
const CDP_DEFAULT_QUOTA = {
|
|
11653
|
+
const CDP_DEFAULT_QUOTA = {
|
|
11654
|
+
maxConcurrency: 1,
|
|
11655
|
+
maxRequestsPerMinute: 4,
|
|
11656
|
+
maxRequestsPerDay: 200
|
|
11657
|
+
};
|
|
11532
11658
|
registry.register(cdpChatgptAdapter, {
|
|
11533
11659
|
provider: "cdp:chatgpt",
|
|
11534
11660
|
cdpEndpoint: `ws://${host}:${port2}`,
|
|
@@ -11560,16 +11686,19 @@ async function createServer(opts) {
|
|
|
11560
11686
|
conn.config
|
|
11561
11687
|
);
|
|
11562
11688
|
const raw = result.rawResponse;
|
|
11563
|
-
return [
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
|
|
11568
|
-
|
|
11689
|
+
return [
|
|
11690
|
+
{
|
|
11691
|
+
target: targets?.[0] ?? "chatgpt",
|
|
11692
|
+
screenshotPath: result.screenshotPath ?? "",
|
|
11693
|
+
answerText: raw.answerText ?? "",
|
|
11694
|
+
citations: raw.groundingSources ?? []
|
|
11695
|
+
}
|
|
11696
|
+
];
|
|
11569
11697
|
},
|
|
11570
11698
|
onGenerateQueries: async (providerName, count, project) => {
|
|
11571
11699
|
const provider = registry.get(providerName);
|
|
11572
|
-
if (!provider)
|
|
11700
|
+
if (!provider)
|
|
11701
|
+
throw new Error(`Provider "${providerName}" is not configured`);
|
|
11573
11702
|
const siteText = await fetchSiteText(project.domain);
|
|
11574
11703
|
const prompt = buildQueryGenerationPrompt({
|
|
11575
11704
|
domain: project.domain,
|
|
@@ -11622,11 +11751,11 @@ async function createServer(opts) {
|
|
|
11622
11751
|
// Without this, the browser hits the server for every JS chunk on
|
|
11623
11752
|
// every page load, defeating most of the dashboard's first-paint
|
|
11624
11753
|
// budget.
|
|
11625
|
-
setHeaders: (
|
|
11626
|
-
if (
|
|
11627
|
-
|
|
11754
|
+
setHeaders: (res, path10) => {
|
|
11755
|
+
if (path10.endsWith(".html")) {
|
|
11756
|
+
res.setHeader("Cache-Control", "no-cache, must-revalidate");
|
|
11628
11757
|
} else {
|
|
11629
|
-
|
|
11758
|
+
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
11630
11759
|
}
|
|
11631
11760
|
}
|
|
11632
11761
|
});
|
|
@@ -11690,10 +11819,18 @@ function buildQueryGenerationPrompt(ctx) {
|
|
|
11690
11819
|
lines.push(`Country: ${ctx.country}`);
|
|
11691
11820
|
lines.push(`Language: ${ctx.language}`);
|
|
11692
11821
|
if (ctx.siteText) {
|
|
11693
|
-
lines.push(
|
|
11822
|
+
lines.push(
|
|
11823
|
+
"",
|
|
11824
|
+
"--- Site Content ---",
|
|
11825
|
+
ctx.siteText,
|
|
11826
|
+
"--- End Site Content ---"
|
|
11827
|
+
);
|
|
11694
11828
|
}
|
|
11695
11829
|
if (ctx.existingQueries.length > 0) {
|
|
11696
|
-
lines.push(
|
|
11830
|
+
lines.push(
|
|
11831
|
+
"",
|
|
11832
|
+
`Already tracking (do NOT duplicate): ${ctx.existingQueries.join(", ")}`
|
|
11833
|
+
);
|
|
11697
11834
|
}
|
|
11698
11835
|
lines.push(
|
|
11699
11836
|
"",
|
|
@@ -11715,7 +11852,10 @@ function parseQueryResponse(raw, count) {
|
|
|
11715
11852
|
let cleaned = line.replace(/^\s*(?:\d+[.)]\s*|[-*•]\s*)/, "").trim();
|
|
11716
11853
|
cleaned = cleaned.replace(/^["']|["']$/g, "").trim();
|
|
11717
11854
|
if (!cleaned) continue;
|
|
11718
|
-
if (/^(?:here are|sure|certainly|of course|i['’]ve|these are|below are)/i.test(
|
|
11855
|
+
if (/^(?:here are|sure|certainly|of course|i['’]ve|these are|below are)/i.test(
|
|
11856
|
+
cleaned
|
|
11857
|
+
))
|
|
11858
|
+
continue;
|
|
11719
11859
|
if (cleaned.split(/\s+/).length > 8) continue;
|
|
11720
11860
|
const key = cleaned.toLowerCase();
|
|
11721
11861
|
if (seen.has(key)) continue;
|