@ainyc/canonry 4.99.0 → 4.100.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/canonry/references/canonry-cli.md +13 -1
- package/assets/assets/{BacklinksPage-CuWs7I1D.js → BacklinksPage-DWZdWEm3.js} +1 -1
- package/assets/assets/{ChartPrimitives-bm37tprp.js → ChartPrimitives-C-ky5206.js} +1 -1
- package/assets/assets/ProjectPage-CpbFpi6J.js +6 -0
- package/assets/assets/{RunRow-DzhQl87g.js → RunRow-Bj-D4kZe.js} +1 -1
- package/assets/assets/{RunsPage-wOaewhbF.js → RunsPage-GktitsAV.js} +1 -1
- package/assets/assets/{SettingsPage-CJdhhkkE.js → SettingsPage-AsFLFDS3.js} +1 -1
- package/assets/assets/{TrafficPage-BJBeN5Ph.js → TrafficPage-Cf3uCkxs.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-D3PmH0B1.js → TrafficSourceDetailPage-B5t7nf9C.js} +1 -1
- package/assets/assets/{arrow-left-BmITMI6O.js → arrow-left-CZFQuQO_.js} +1 -1
- package/assets/assets/{extract-error-message-BGwnyAfP.js → extract-error-message--bieZ2IX.js} +1 -1
- package/assets/assets/index-9hI0ODQU.css +1 -0
- package/assets/assets/index-Cb2h7KkD.js +210 -0
- package/assets/assets/{trash-2-Bd2mkP_w.js → trash-2-sKO0k75f.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-CEHD33NQ.js → chunk-74SOIAIY.js} +262 -119
- package/dist/{chunk-CWFTMFEU.js → chunk-ADLS64PG.js} +117 -3
- package/dist/{chunk-DJVUNURL.js → chunk-SNHYRSUF.js} +401 -40
- package/dist/{chunk-VQN74WWA.js → chunk-UVIHWF45.js} +27 -1
- package/dist/cli.js +110 -4
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-QHBXW4KI.js → intelligence-service-3CRNJLUZ.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +10 -10
- package/assets/assets/ProjectPage-BksYe8FP.js +0 -6
- package/assets/assets/index-BSX6r6ju.js +0 -210
- package/assets/assets/index-BsZNCwg8.css +0 -1
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
loadConfig,
|
|
10
10
|
loadConfigRaw,
|
|
11
11
|
saveConfigPatch
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-UVIHWF45.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-SNHYRSUF.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-ADLS64PG.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-3CRNJLUZ.js");
|
|
6406
6407
|
const config = loadConfig();
|
|
6407
6408
|
const db = createClient(config.database);
|
|
6408
6409
|
migrate(db);
|
|
@@ -8019,12 +8020,13 @@ var PROVIDER_MODELS = {
|
|
|
8019
8020
|
[LlmCapabilities.classify]: "glm-5-turbo"
|
|
8020
8021
|
},
|
|
8021
8022
|
[AgentProviderIds.deepinfra]: {
|
|
8022
|
-
// DeepInfra `org/Model` slugs. GLM-5.2
|
|
8023
|
-
//
|
|
8024
|
-
// custom openai-completions model, not pi-ai's
|
|
8023
|
+
// DeepInfra `org/Model` slugs. GLM-5.2 (Sonnet-class) on all three tiers —
|
|
8024
|
+
// it undercuts the Claude tiers it replaces, so the cheaper tiers take no
|
|
8025
|
+
// quality hit. Resolved into a custom openai-completions model, not pi-ai's
|
|
8026
|
+
// catalog.
|
|
8025
8027
|
[LlmCapabilities.agent]: "zai-org/GLM-5.2",
|
|
8026
|
-
[LlmCapabilities.analyze]: "
|
|
8027
|
-
[LlmCapabilities.classify]: "
|
|
8028
|
+
[LlmCapabilities.analyze]: "zai-org/GLM-5.2",
|
|
8029
|
+
[LlmCapabilities.classify]: "zai-org/GLM-5.2"
|
|
8028
8030
|
}
|
|
8029
8031
|
};
|
|
8030
8032
|
var AGENT_PROVIDERS = {
|
|
@@ -8129,7 +8131,8 @@ function resolveModelForCapability(provider, capability, modelIdOverride) {
|
|
|
8129
8131
|
const entry = AGENT_PROVIDERS[provider];
|
|
8130
8132
|
const id = modelIdOverride ?? PROVIDER_MODELS[provider][capability];
|
|
8131
8133
|
if (entry.openaiCompatible) {
|
|
8132
|
-
|
|
8134
|
+
const suppressThinking = capability !== LlmCapabilities.agent;
|
|
8135
|
+
return buildOpenAiCompatibleModel(entry, id, suppressThinking);
|
|
8133
8136
|
}
|
|
8134
8137
|
const model = getModel(entry.piAiProvider, id);
|
|
8135
8138
|
if (!model) {
|
|
@@ -8139,13 +8142,14 @@ function resolveModelForCapability(provider, capability, modelIdOverride) {
|
|
|
8139
8142
|
}
|
|
8140
8143
|
return model;
|
|
8141
8144
|
}
|
|
8142
|
-
function buildOpenAiCompatibleModel(entry, id) {
|
|
8145
|
+
function buildOpenAiCompatibleModel(entry, id, suppressThinking = false) {
|
|
8143
8146
|
const host = entry.openaiCompatible;
|
|
8144
8147
|
if (!host) {
|
|
8145
8148
|
throw new Error(`buildOpenAiCompatibleModel called for non-custom provider '${entry.piAiProvider}'`);
|
|
8146
8149
|
}
|
|
8147
8150
|
const meta = host.knownModels[id] ?? host.defaultModelMeta;
|
|
8148
8151
|
const baseUrl = (host.baseUrlEnvVar ? process.env[host.baseUrlEnvVar] : void 0) || host.baseUrl;
|
|
8152
|
+
const compat = suppressThinking ? { ...host.compat, thinkingFormat: "qwen-chat-template" } : host.compat;
|
|
8149
8153
|
return {
|
|
8150
8154
|
id,
|
|
8151
8155
|
name: id,
|
|
@@ -8157,7 +8161,7 @@ function buildOpenAiCompatibleModel(entry, id) {
|
|
|
8157
8161
|
cost: meta.cost,
|
|
8158
8162
|
contextWindow: meta.contextWindow,
|
|
8159
8163
|
maxTokens: meta.maxTokens,
|
|
8160
|
-
...
|
|
8164
|
+
...compat ? { compat } : {}
|
|
8161
8165
|
};
|
|
8162
8166
|
}
|
|
8163
8167
|
function validateAgentProviderRegistry() {
|
|
@@ -10425,9 +10429,7 @@ var API_ADAPTERS = [
|
|
|
10425
10429
|
localAdapter,
|
|
10426
10430
|
perplexityAdapter
|
|
10427
10431
|
];
|
|
10428
|
-
var BROWSER_ADAPTERS = [
|
|
10429
|
-
cdpChatgptAdapter
|
|
10430
|
-
];
|
|
10432
|
+
var BROWSER_ADAPTERS = [cdpChatgptAdapter];
|
|
10431
10433
|
var adapterMap = Object.fromEntries(
|
|
10432
10434
|
API_ADAPTERS.map((a) => [a.name, a])
|
|
10433
10435
|
);
|
|
@@ -10477,13 +10479,18 @@ function verifyDashboardPassword(password, storedHash) {
|
|
|
10477
10479
|
N: DASHBOARD_SCRYPT_COST,
|
|
10478
10480
|
maxmem: DASHBOARD_SCRYPT_MAXMEM
|
|
10479
10481
|
});
|
|
10480
|
-
if (derived.length !== expected.length)
|
|
10481
|
-
|
|
10482
|
+
if (derived.length !== expected.length)
|
|
10483
|
+
return { ok: false, needsRehash: false };
|
|
10484
|
+
return {
|
|
10485
|
+
ok: crypto20.timingSafeEqual(derived, expected),
|
|
10486
|
+
needsRehash: false
|
|
10487
|
+
};
|
|
10482
10488
|
}
|
|
10483
10489
|
if (/^[a-f0-9]{64}$/i.test(storedHash)) {
|
|
10484
10490
|
const candidate = Buffer.from(hashApiKey(password), "hex");
|
|
10485
10491
|
const expected = Buffer.from(storedHash, "hex");
|
|
10486
|
-
if (candidate.length !== expected.length)
|
|
10492
|
+
if (candidate.length !== expected.length)
|
|
10493
|
+
return { ok: false, needsRehash: false };
|
|
10487
10494
|
const ok = crypto20.timingSafeEqual(candidate, expected);
|
|
10488
10495
|
return { ok, needsRehash: ok };
|
|
10489
10496
|
}
|
|
@@ -10525,7 +10532,11 @@ function serializeSessionCookie(opts) {
|
|
|
10525
10532
|
function applyLegacyCredentials(rows, config) {
|
|
10526
10533
|
let migratedGoogle = 0;
|
|
10527
10534
|
for (const row of rows.google) {
|
|
10528
|
-
const existing = getGoogleConnection(
|
|
10535
|
+
const existing = getGoogleConnection(
|
|
10536
|
+
config,
|
|
10537
|
+
row.domain,
|
|
10538
|
+
row.connectionType
|
|
10539
|
+
);
|
|
10529
10540
|
if (existing?.refreshToken) continue;
|
|
10530
10541
|
upsertGoogleConnection(config, {
|
|
10531
10542
|
domain: row.domain,
|
|
@@ -10604,10 +10615,12 @@ async function createServer(opts) {
|
|
|
10604
10615
|
error: err instanceof Error ? err.message : String(err)
|
|
10605
10616
|
});
|
|
10606
10617
|
}
|
|
10607
|
-
log21.info("providers.configured", {
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10618
|
+
log21.info("providers.configured", {
|
|
10619
|
+
providers: Object.keys(providers).filter((k) => {
|
|
10620
|
+
const p = providers[k];
|
|
10621
|
+
return p?.apiKey || p?.baseUrl || p?.vertexProject;
|
|
10622
|
+
})
|
|
10623
|
+
});
|
|
10611
10624
|
for (const adapter of API_ADAPTERS) {
|
|
10612
10625
|
const entry = providers[adapter.name];
|
|
10613
10626
|
if (!entry) continue;
|
|
@@ -10627,7 +10640,11 @@ async function createServer(opts) {
|
|
|
10627
10640
|
}
|
|
10628
10641
|
const cdpConfig = opts.config.cdp;
|
|
10629
10642
|
if (cdpConfig?.host || cdpConfig?.port) {
|
|
10630
|
-
const CDP_DEFAULT_QUOTA = {
|
|
10643
|
+
const CDP_DEFAULT_QUOTA = {
|
|
10644
|
+
maxConcurrency: 1,
|
|
10645
|
+
maxRequestsPerMinute: 4,
|
|
10646
|
+
maxRequestsPerDay: 200
|
|
10647
|
+
};
|
|
10631
10648
|
const cdpEndpoint = `ws://${cdpConfig.host ?? "localhost"}:${cdpConfig.port ?? 9222}`;
|
|
10632
10649
|
registry.register(cdpChatgptAdapter, {
|
|
10633
10650
|
provider: "cdp:chatgpt",
|
|
@@ -10641,7 +10658,9 @@ async function createServer(opts) {
|
|
|
10641
10658
|
jobRunner.recoverStaleRuns();
|
|
10642
10659
|
const notifier = new Notifier(opts.db, serverUrl);
|
|
10643
10660
|
const intelligenceService = new IntelligenceService(opts.db);
|
|
10644
|
-
const aeroClient = new ApiClient(opts.config.apiUrl, opts.config.apiKey, {
|
|
10661
|
+
const aeroClient = new ApiClient(opts.config.apiUrl, opts.config.apiKey, {
|
|
10662
|
+
skipProbe: true
|
|
10663
|
+
});
|
|
10645
10664
|
const agentEnabled = resolveAgentEnabled(process.env, opts.config);
|
|
10646
10665
|
const sessionRegistry = agentEnabled ? new SessionRegistry({
|
|
10647
10666
|
db: opts.db,
|
|
@@ -10688,7 +10707,9 @@ async function createServer(opts) {
|
|
|
10688
10707
|
const runGbpSync = (runId, projectId, syncOpts) => {
|
|
10689
10708
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getGoogleAuthConfig(opts.config);
|
|
10690
10709
|
if (!googleClientId || !googleClientSecret) {
|
|
10691
|
-
app.log.error(
|
|
10710
|
+
app.log.error(
|
|
10711
|
+
"GBP sync requested but Google OAuth credentials are not configured in the local config"
|
|
10712
|
+
);
|
|
10692
10713
|
return;
|
|
10693
10714
|
}
|
|
10694
10715
|
executeGbpSync(opts.db, runId, projectId, {
|
|
@@ -10741,7 +10762,14 @@ async function createServer(opts) {
|
|
|
10741
10762
|
},
|
|
10742
10763
|
onTrafficSyncRequested: (projectName, sourceId) => {
|
|
10743
10764
|
aeroClient.trafficSync(projectName, sourceId).catch((err) => {
|
|
10744
|
-
app.log.error(
|
|
10765
|
+
app.log.error(
|
|
10766
|
+
{
|
|
10767
|
+
projectName,
|
|
10768
|
+
sourceId,
|
|
10769
|
+
err: err instanceof Error ? err.message : String(err)
|
|
10770
|
+
},
|
|
10771
|
+
"Scheduled traffic sync failed"
|
|
10772
|
+
);
|
|
10745
10773
|
});
|
|
10746
10774
|
},
|
|
10747
10775
|
onGbpSyncRequested: (runId, projectId) => {
|
|
@@ -10756,21 +10784,33 @@ async function createServer(opts) {
|
|
|
10756
10784
|
onBacklinksSyncRequested: (projectName) => {
|
|
10757
10785
|
void (async () => {
|
|
10758
10786
|
const probed = await probeLatestRelease().catch((err) => {
|
|
10759
|
-
app.log.warn(
|
|
10787
|
+
app.log.warn(
|
|
10788
|
+
{ projectName, err },
|
|
10789
|
+
"Scheduled backlinks sync: latest-release probe failed"
|
|
10790
|
+
);
|
|
10760
10791
|
return null;
|
|
10761
10792
|
});
|
|
10762
10793
|
if (!probed) return;
|
|
10763
|
-
const alreadySynced = opts.db.select().from(ccReleaseSyncs).where(
|
|
10764
|
-
|
|
10765
|
-
|
|
10766
|
-
|
|
10794
|
+
const alreadySynced = opts.db.select().from(ccReleaseSyncs).where(
|
|
10795
|
+
and14(
|
|
10796
|
+
eq20(ccReleaseSyncs.release, probed.release),
|
|
10797
|
+
eq20(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)
|
|
10798
|
+
)
|
|
10799
|
+
).limit(1).get();
|
|
10767
10800
|
if (alreadySynced) {
|
|
10768
|
-
app.log.info(
|
|
10801
|
+
app.log.info(
|
|
10802
|
+
{ projectName, release: probed.release },
|
|
10803
|
+
"Scheduled backlinks sync: already up to date, skipping"
|
|
10804
|
+
);
|
|
10769
10805
|
return;
|
|
10770
10806
|
}
|
|
10771
10807
|
aeroClient.backlinksTriggerSync(probed.release).catch((err) => {
|
|
10772
10808
|
app.log.error(
|
|
10773
|
-
{
|
|
10809
|
+
{
|
|
10810
|
+
projectName,
|
|
10811
|
+
release: probed.release,
|
|
10812
|
+
err: err instanceof Error ? err.message : String(err)
|
|
10813
|
+
},
|
|
10774
10814
|
"Scheduled backlinks sync failed"
|
|
10775
10815
|
);
|
|
10776
10816
|
});
|
|
@@ -10779,7 +10819,10 @@ async function createServer(opts) {
|
|
|
10779
10819
|
if (project && bingConnectionStore.getConnection(project.canonicalDomain)) {
|
|
10780
10820
|
aeroClient.backlinksBingSync(projectName).catch((err) => {
|
|
10781
10821
|
app.log.error(
|
|
10782
|
-
{
|
|
10822
|
+
{
|
|
10823
|
+
projectName,
|
|
10824
|
+
err: err instanceof Error ? err.message : String(err)
|
|
10825
|
+
},
|
|
10783
10826
|
"Scheduled Bing backlinks sync failed"
|
|
10784
10827
|
);
|
|
10785
10828
|
});
|
|
@@ -10801,7 +10844,9 @@ async function createServer(opts) {
|
|
|
10801
10844
|
vertexConfigured: adapter.name === "gemini" ? !!opts.config.providers?.gemini?.vertexProject : void 0
|
|
10802
10845
|
}));
|
|
10803
10846
|
const googleSettingsSummary = {
|
|
10804
|
-
configured: Boolean(
|
|
10847
|
+
configured: Boolean(
|
|
10848
|
+
opts.config.google?.clientId && opts.config.google?.clientSecret
|
|
10849
|
+
)
|
|
10805
10850
|
};
|
|
10806
10851
|
const bingSettingsSummary = {
|
|
10807
10852
|
// Treat Bing as configured if there is at least one connection with an API key,
|
|
@@ -10818,8 +10863,13 @@ async function createServer(opts) {
|
|
|
10818
10863
|
upsertConnection: (connection) => {
|
|
10819
10864
|
if (!opts.config.bing) opts.config.bing = {};
|
|
10820
10865
|
if (!opts.config.bing.connections) opts.config.bing.connections = [];
|
|
10821
|
-
const idx = opts.config.bing.connections.findIndex(
|
|
10822
|
-
|
|
10866
|
+
const idx = opts.config.bing.connections.findIndex(
|
|
10867
|
+
(c) => c.domain === connection.domain
|
|
10868
|
+
);
|
|
10869
|
+
const normalized = {
|
|
10870
|
+
...connection,
|
|
10871
|
+
createdByProjectId: connection.createdByProjectId ?? null
|
|
10872
|
+
};
|
|
10823
10873
|
if (idx >= 0) {
|
|
10824
10874
|
opts.config.bing.connections[idx] = normalized;
|
|
10825
10875
|
} else {
|
|
@@ -10829,7 +10879,9 @@ async function createServer(opts) {
|
|
|
10829
10879
|
return normalized;
|
|
10830
10880
|
},
|
|
10831
10881
|
updateConnection: (domain, patch) => {
|
|
10832
|
-
const conn = opts.config.bing?.connections?.find(
|
|
10882
|
+
const conn = opts.config.bing?.connections?.find(
|
|
10883
|
+
(c) => c.domain === domain
|
|
10884
|
+
);
|
|
10833
10885
|
if (!conn) return void 0;
|
|
10834
10886
|
Object.assign(conn, patch);
|
|
10835
10887
|
saveConfigPatch(opts.config);
|
|
@@ -10837,7 +10889,9 @@ async function createServer(opts) {
|
|
|
10837
10889
|
},
|
|
10838
10890
|
deleteConnection: (domain) => {
|
|
10839
10891
|
if (!opts.config.bing?.connections) return false;
|
|
10840
|
-
const idx = opts.config.bing.connections.findIndex(
|
|
10892
|
+
const idx = opts.config.bing.connections.findIndex(
|
|
10893
|
+
(c) => c.domain === domain
|
|
10894
|
+
);
|
|
10841
10895
|
if (idx < 0) return false;
|
|
10842
10896
|
opts.config.bing.connections.splice(idx, 1);
|
|
10843
10897
|
saveConfigPatch(opts.config);
|
|
@@ -10884,7 +10938,10 @@ async function createServer(opts) {
|
|
|
10884
10938
|
return updated;
|
|
10885
10939
|
},
|
|
10886
10940
|
deleteConnection: (projectName) => {
|
|
10887
|
-
const removed = removeWordpressTrafficConnection(
|
|
10941
|
+
const removed = removeWordpressTrafficConnection(
|
|
10942
|
+
opts.config,
|
|
10943
|
+
projectName
|
|
10944
|
+
);
|
|
10888
10945
|
if (removed) saveConfigPatch(opts.config);
|
|
10889
10946
|
return removed;
|
|
10890
10947
|
}
|
|
@@ -10914,12 +10971,21 @@ async function createServer(opts) {
|
|
|
10914
10971
|
return updated;
|
|
10915
10972
|
},
|
|
10916
10973
|
updateConnection: (domain, connectionType, patch) => {
|
|
10917
|
-
const updated = patchGoogleConnection(
|
|
10974
|
+
const updated = patchGoogleConnection(
|
|
10975
|
+
opts.config,
|
|
10976
|
+
domain,
|
|
10977
|
+
connectionType,
|
|
10978
|
+
patch
|
|
10979
|
+
);
|
|
10918
10980
|
if (updated) saveConfigPatch(opts.config);
|
|
10919
10981
|
return updated;
|
|
10920
10982
|
},
|
|
10921
10983
|
deleteConnection: (domain, connectionType) => {
|
|
10922
|
-
const removed = removeGoogleConnection(
|
|
10984
|
+
const removed = removeGoogleConnection(
|
|
10985
|
+
opts.config,
|
|
10986
|
+
domain,
|
|
10987
|
+
connectionType
|
|
10988
|
+
);
|
|
10923
10989
|
if (removed) saveConfigPatch(opts.config);
|
|
10924
10990
|
return removed;
|
|
10925
10991
|
}
|
|
@@ -11010,13 +11076,16 @@ async function createServer(opts) {
|
|
|
11010
11076
|
const key = getDefaultApiKey();
|
|
11011
11077
|
if (!key || key.revokedAt) return false;
|
|
11012
11078
|
const sessionId = createSession(key.id);
|
|
11013
|
-
reply.header(
|
|
11014
|
-
|
|
11015
|
-
|
|
11016
|
-
|
|
11017
|
-
|
|
11018
|
-
|
|
11019
|
-
|
|
11079
|
+
reply.header(
|
|
11080
|
+
"set-cookie",
|
|
11081
|
+
serializeSessionCookie({
|
|
11082
|
+
name: SESSION_COOKIE_NAME,
|
|
11083
|
+
value: sessionId,
|
|
11084
|
+
path: sessionCookiePath,
|
|
11085
|
+
secure: sessionCookieSecure,
|
|
11086
|
+
ttlMs: SESSION_TTL_MS
|
|
11087
|
+
})
|
|
11088
|
+
);
|
|
11020
11089
|
return true;
|
|
11021
11090
|
};
|
|
11022
11091
|
const boundToLoopback = isLoopbackBindHost(opts.host);
|
|
@@ -11037,7 +11106,9 @@ async function createServer(opts) {
|
|
|
11037
11106
|
});
|
|
11038
11107
|
app.post(apiPrefix + "/session/setup", async (request, reply) => {
|
|
11039
11108
|
if (!boundToLoopback && !requestHasValidApiKey(request)) {
|
|
11040
|
-
const err = authRequired(
|
|
11109
|
+
const err = authRequired(
|
|
11110
|
+
"This server is network-reachable; setting the dashboard password requires a valid API key."
|
|
11111
|
+
);
|
|
11041
11112
|
return reply.status(err.statusCode).send(err.toJSON());
|
|
11042
11113
|
}
|
|
11043
11114
|
if (opts.config.dashboardPasswordHash) {
|
|
@@ -11062,19 +11133,31 @@ async function createServer(opts) {
|
|
|
11062
11133
|
const apiKey = request.body?.apiKey?.trim();
|
|
11063
11134
|
if (password) {
|
|
11064
11135
|
if (!opts.config.dashboardPasswordHash) {
|
|
11065
|
-
const err2 = validationError(
|
|
11136
|
+
const err2 = validationError(
|
|
11137
|
+
"No dashboard password configured \u2014 use /session/setup first"
|
|
11138
|
+
);
|
|
11066
11139
|
return reply.status(err2.statusCode).send(err2.toJSON());
|
|
11067
11140
|
}
|
|
11068
|
-
const verification = verifyDashboardPassword(
|
|
11141
|
+
const verification = verifyDashboardPassword(
|
|
11142
|
+
password,
|
|
11143
|
+
opts.config.dashboardPasswordHash
|
|
11144
|
+
);
|
|
11069
11145
|
if (!verification.ok) {
|
|
11070
|
-
return reply.status(401).send({
|
|
11146
|
+
return reply.status(401).send({
|
|
11147
|
+
error: { code: "AUTH_INVALID", message: "Incorrect password" }
|
|
11148
|
+
});
|
|
11071
11149
|
}
|
|
11072
11150
|
if (verification.needsRehash) {
|
|
11073
11151
|
opts.config.dashboardPasswordHash = hashDashboardPassword(password);
|
|
11074
11152
|
saveConfigPatch(opts.config);
|
|
11075
11153
|
}
|
|
11076
11154
|
if (!createPasswordSession(reply)) {
|
|
11077
|
-
return reply.status(401).send({
|
|
11155
|
+
return reply.status(401).send({
|
|
11156
|
+
error: {
|
|
11157
|
+
code: "AUTH_INVALID",
|
|
11158
|
+
message: "Server API key not found \u2014 re-run canonry init"
|
|
11159
|
+
}
|
|
11160
|
+
});
|
|
11078
11161
|
}
|
|
11079
11162
|
return reply.send({ authenticated: true });
|
|
11080
11163
|
}
|
|
@@ -11086,13 +11169,16 @@ async function createServer(opts) {
|
|
|
11086
11169
|
}
|
|
11087
11170
|
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq20(apiKeys.id, key.id)).run();
|
|
11088
11171
|
const sessionId = createSession(key.id);
|
|
11089
|
-
reply.header(
|
|
11090
|
-
|
|
11091
|
-
|
|
11092
|
-
|
|
11093
|
-
|
|
11094
|
-
|
|
11095
|
-
|
|
11172
|
+
reply.header(
|
|
11173
|
+
"set-cookie",
|
|
11174
|
+
serializeSessionCookie({
|
|
11175
|
+
name: SESSION_COOKIE_NAME,
|
|
11176
|
+
value: sessionId,
|
|
11177
|
+
path: sessionCookiePath,
|
|
11178
|
+
secure: sessionCookieSecure,
|
|
11179
|
+
ttlMs: SESSION_TTL_MS
|
|
11180
|
+
})
|
|
11181
|
+
);
|
|
11096
11182
|
return reply.send({ authenticated: true });
|
|
11097
11183
|
}
|
|
11098
11184
|
const err = validationError("Either password or apiKey is required");
|
|
@@ -11101,13 +11187,16 @@ async function createServer(opts) {
|
|
|
11101
11187
|
app.delete(apiPrefix + "/session", async (request, reply) => {
|
|
11102
11188
|
const sessionId = parseCookies(request.headers.cookie)[SESSION_COOKIE_NAME];
|
|
11103
11189
|
clearSession(sessionId);
|
|
11104
|
-
reply.header(
|
|
11105
|
-
|
|
11106
|
-
|
|
11107
|
-
|
|
11108
|
-
|
|
11109
|
-
|
|
11110
|
-
|
|
11190
|
+
reply.header(
|
|
11191
|
+
"set-cookie",
|
|
11192
|
+
serializeSessionCookie({
|
|
11193
|
+
name: SESSION_COOKIE_NAME,
|
|
11194
|
+
value: null,
|
|
11195
|
+
path: sessionCookiePath,
|
|
11196
|
+
secure: sessionCookieSecure,
|
|
11197
|
+
ttlMs: SESSION_TTL_MS
|
|
11198
|
+
})
|
|
11199
|
+
);
|
|
11111
11200
|
return reply.status(204).send();
|
|
11112
11201
|
});
|
|
11113
11202
|
const LATEST_RELEASE_TTL_MS = 5 * 60 * 1e3;
|
|
@@ -11132,8 +11221,12 @@ async function createServer(opts) {
|
|
|
11132
11221
|
latestReleaseCache = { value, expiresAt: now + LATEST_RELEASE_TTL_MS };
|
|
11133
11222
|
return value;
|
|
11134
11223
|
};
|
|
11135
|
-
const explainContentRecommendation = createRecommendationExplainer({
|
|
11136
|
-
|
|
11224
|
+
const explainContentRecommendation = createRecommendationExplainer({
|
|
11225
|
+
config: opts.config
|
|
11226
|
+
});
|
|
11227
|
+
const briefContentRecommendation = createRecommendationBriefSynthesizer({
|
|
11228
|
+
config: opts.config
|
|
11229
|
+
});
|
|
11137
11230
|
await app.register(apiRoutes, {
|
|
11138
11231
|
db: opts.db,
|
|
11139
11232
|
routePrefix: apiPrefix,
|
|
@@ -11195,7 +11288,9 @@ async function createServer(opts) {
|
|
|
11195
11288
|
onGscSyncRequested: (runId, projectId, syncOpts) => {
|
|
11196
11289
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getGoogleAuthConfig(opts.config);
|
|
11197
11290
|
if (!googleClientId || !googleClientSecret) {
|
|
11198
|
-
app.log.error(
|
|
11291
|
+
app.log.error(
|
|
11292
|
+
"GSC sync requested but Google OAuth credentials are not configured in the local config"
|
|
11293
|
+
);
|
|
11199
11294
|
return;
|
|
11200
11295
|
}
|
|
11201
11296
|
executeGscSync(opts.db, runId, projectId, {
|
|
@@ -11208,7 +11303,9 @@ async function createServer(opts) {
|
|
|
11208
11303
|
onInspectSitemapRequested: (runId, projectId, inspectOpts) => {
|
|
11209
11304
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getGoogleAuthConfig(opts.config);
|
|
11210
11305
|
if (!googleClientId || !googleClientSecret) {
|
|
11211
|
-
app.log.error(
|
|
11306
|
+
app.log.error(
|
|
11307
|
+
"Inspect sitemap requested but Google OAuth credentials are not configured"
|
|
11308
|
+
);
|
|
11212
11309
|
return;
|
|
11213
11310
|
}
|
|
11214
11311
|
executeInspectSitemap(opts.db, runId, projectId, {
|
|
@@ -11233,7 +11330,9 @@ async function createServer(opts) {
|
|
|
11233
11330
|
pluginDir: PLUGIN_DIR
|
|
11234
11331
|
}),
|
|
11235
11332
|
onInstallBacklinks: async () => {
|
|
11236
|
-
const result = await installDuckdb({
|
|
11333
|
+
const result = await installDuckdb({
|
|
11334
|
+
onLog: (line) => app.log.info({ line }, "duckdb install")
|
|
11335
|
+
});
|
|
11237
11336
|
return {
|
|
11238
11337
|
installed: true,
|
|
11239
11338
|
version: result.version,
|
|
@@ -11256,8 +11355,13 @@ async function createServer(opts) {
|
|
|
11256
11355
|
trigger: RunTriggers.scheduled,
|
|
11257
11356
|
createdAt: now
|
|
11258
11357
|
}).run();
|
|
11259
|
-
executeBacklinkExtract(opts.db, runId, projectId, {
|
|
11260
|
-
|
|
11358
|
+
executeBacklinkExtract(opts.db, runId, projectId, {
|
|
11359
|
+
release: r
|
|
11360
|
+
}).catch((err) => {
|
|
11361
|
+
app.log.error(
|
|
11362
|
+
{ runId, projectId, err },
|
|
11363
|
+
"Auto backlink extract failed"
|
|
11364
|
+
);
|
|
11261
11365
|
});
|
|
11262
11366
|
}
|
|
11263
11367
|
}
|
|
@@ -11266,9 +11370,11 @@ async function createServer(opts) {
|
|
|
11266
11370
|
});
|
|
11267
11371
|
},
|
|
11268
11372
|
onBacklinkExtractRequested: (runId, projectId, release) => {
|
|
11269
|
-
executeBacklinkExtract(opts.db, runId, projectId, { release }).catch(
|
|
11270
|
-
|
|
11271
|
-
|
|
11373
|
+
executeBacklinkExtract(opts.db, runId, projectId, { release }).catch(
|
|
11374
|
+
(err) => {
|
|
11375
|
+
app.log.error({ runId, err }, "Backlink extract failed");
|
|
11376
|
+
}
|
|
11377
|
+
);
|
|
11272
11378
|
},
|
|
11273
11379
|
onBingBacklinkSyncRequested: (runId, projectId) => {
|
|
11274
11380
|
executeBingBacklinkSync(opts.db, runId, projectId, {
|
|
@@ -11303,9 +11409,16 @@ async function createServer(opts) {
|
|
|
11303
11409
|
embedQueries: (queriesToEmbed) => {
|
|
11304
11410
|
const cfg = registry.get("gemini")?.config;
|
|
11305
11411
|
if (!cfg?.apiKey) {
|
|
11306
|
-
return Promise.reject(
|
|
11412
|
+
return Promise.reject(
|
|
11413
|
+
new Error(
|
|
11414
|
+
"Gemini API key not configured; harvest semantic novelty unavailable"
|
|
11415
|
+
)
|
|
11416
|
+
);
|
|
11307
11417
|
}
|
|
11308
|
-
return embedQueries(queriesToEmbed, {
|
|
11418
|
+
return embedQueries(queriesToEmbed, {
|
|
11419
|
+
apiKey: cfg.apiKey,
|
|
11420
|
+
baseUrl: cfg.baseUrl
|
|
11421
|
+
});
|
|
11309
11422
|
},
|
|
11310
11423
|
onSiteAuditRequested: (runId, projectId, auditOpts) => {
|
|
11311
11424
|
runSiteAudit(runId, projectId, auditOpts);
|
|
@@ -11321,7 +11434,10 @@ async function createServer(opts) {
|
|
|
11321
11434
|
const cached = listCachedReleases();
|
|
11322
11435
|
const syncByRelease = /* @__PURE__ */ new Map();
|
|
11323
11436
|
for (const row of opts.db.select().from(ccReleaseSyncs).all()) {
|
|
11324
|
-
syncByRelease.set(row.release, {
|
|
11437
|
+
syncByRelease.set(row.release, {
|
|
11438
|
+
status: row.status,
|
|
11439
|
+
updatedAt: row.updatedAt
|
|
11440
|
+
});
|
|
11325
11441
|
}
|
|
11326
11442
|
return cached.map((entry) => {
|
|
11327
11443
|
const sync = syncByRelease.get(entry.release);
|
|
@@ -11370,16 +11486,20 @@ async function createServer(opts) {
|
|
|
11370
11486
|
wordpressTrafficCredentialStore,
|
|
11371
11487
|
vercelTrafficCredentialStore,
|
|
11372
11488
|
onTrafficSynced: (event) => {
|
|
11373
|
-
trackEvent(
|
|
11374
|
-
|
|
11375
|
-
|
|
11376
|
-
|
|
11377
|
-
|
|
11378
|
-
|
|
11379
|
-
|
|
11380
|
-
|
|
11381
|
-
|
|
11382
|
-
|
|
11489
|
+
trackEvent(
|
|
11490
|
+
"traffic.synced",
|
|
11491
|
+
{
|
|
11492
|
+
status: event.status,
|
|
11493
|
+
sourceType: event.sourceType,
|
|
11494
|
+
sourceId: event.sourceId,
|
|
11495
|
+
pulledEvents: event.pulledEvents,
|
|
11496
|
+
selfTrafficExcluded: event.selfTrafficExcluded,
|
|
11497
|
+
crawlerHits: event.crawlerHits,
|
|
11498
|
+
aiReferralHits: event.aiReferralHits,
|
|
11499
|
+
durationMs: event.durationMs
|
|
11500
|
+
},
|
|
11501
|
+
event.errorCode ? { errorCode: event.errorCode } : void 0
|
|
11502
|
+
);
|
|
11383
11503
|
},
|
|
11384
11504
|
onRunCreated: (runId, projectId, providers2, location) => {
|
|
11385
11505
|
jobRunner.executeRun(runId, projectId, providers2, location).catch((err) => {
|
|
@@ -11442,16 +11562,18 @@ async function createServer(opts) {
|
|
|
11442
11562
|
}).map((project) => project.id);
|
|
11443
11563
|
const targetProjectIds = affectedProjectIds.length > 0 ? affectedProjectIds : [null];
|
|
11444
11564
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11445
|
-
opts.db.insert(auditLog).values(
|
|
11446
|
-
|
|
11447
|
-
|
|
11448
|
-
|
|
11449
|
-
|
|
11450
|
-
|
|
11451
|
-
|
|
11452
|
-
|
|
11453
|
-
|
|
11454
|
-
|
|
11565
|
+
opts.db.insert(auditLog).values(
|
|
11566
|
+
targetProjectIds.map((projectId) => ({
|
|
11567
|
+
id: crypto20.randomUUID(),
|
|
11568
|
+
projectId,
|
|
11569
|
+
actor: "api",
|
|
11570
|
+
action: existing ? "provider.updated" : "provider.created",
|
|
11571
|
+
entityType: "provider",
|
|
11572
|
+
entityId: name,
|
|
11573
|
+
diff,
|
|
11574
|
+
createdAt
|
|
11575
|
+
}))
|
|
11576
|
+
).run();
|
|
11455
11577
|
}
|
|
11456
11578
|
return {
|
|
11457
11579
|
name,
|
|
@@ -11499,7 +11621,10 @@ async function createServer(opts) {
|
|
|
11499
11621
|
"aliases changed \u2014 recomputed mention fields on historical snapshots"
|
|
11500
11622
|
);
|
|
11501
11623
|
} catch (err) {
|
|
11502
|
-
app.log.error(
|
|
11624
|
+
app.log.error(
|
|
11625
|
+
{ err, projectId, projectName },
|
|
11626
|
+
"alias-triggered backfill failed"
|
|
11627
|
+
);
|
|
11503
11628
|
}
|
|
11504
11629
|
});
|
|
11505
11630
|
},
|
|
@@ -11528,7 +11653,11 @@ async function createServer(opts) {
|
|
|
11528
11653
|
app.log.error({ err }, "Failed to save CDP config");
|
|
11529
11654
|
throw err;
|
|
11530
11655
|
}
|
|
11531
|
-
const CDP_DEFAULT_QUOTA = {
|
|
11656
|
+
const CDP_DEFAULT_QUOTA = {
|
|
11657
|
+
maxConcurrency: 1,
|
|
11658
|
+
maxRequestsPerMinute: 4,
|
|
11659
|
+
maxRequestsPerDay: 200
|
|
11660
|
+
};
|
|
11532
11661
|
registry.register(cdpChatgptAdapter, {
|
|
11533
11662
|
provider: "cdp:chatgpt",
|
|
11534
11663
|
cdpEndpoint: `ws://${host}:${port2}`,
|
|
@@ -11560,16 +11689,19 @@ async function createServer(opts) {
|
|
|
11560
11689
|
conn.config
|
|
11561
11690
|
);
|
|
11562
11691
|
const raw = result.rawResponse;
|
|
11563
|
-
return [
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
|
|
11568
|
-
|
|
11692
|
+
return [
|
|
11693
|
+
{
|
|
11694
|
+
target: targets?.[0] ?? "chatgpt",
|
|
11695
|
+
screenshotPath: result.screenshotPath ?? "",
|
|
11696
|
+
answerText: raw.answerText ?? "",
|
|
11697
|
+
citations: raw.groundingSources ?? []
|
|
11698
|
+
}
|
|
11699
|
+
];
|
|
11569
11700
|
},
|
|
11570
11701
|
onGenerateQueries: async (providerName, count, project) => {
|
|
11571
11702
|
const provider = registry.get(providerName);
|
|
11572
|
-
if (!provider)
|
|
11703
|
+
if (!provider)
|
|
11704
|
+
throw new Error(`Provider "${providerName}" is not configured`);
|
|
11573
11705
|
const siteText = await fetchSiteText(project.domain);
|
|
11574
11706
|
const prompt = buildQueryGenerationPrompt({
|
|
11575
11707
|
domain: project.domain,
|
|
@@ -11622,11 +11754,11 @@ async function createServer(opts) {
|
|
|
11622
11754
|
// Without this, the browser hits the server for every JS chunk on
|
|
11623
11755
|
// every page load, defeating most of the dashboard's first-paint
|
|
11624
11756
|
// budget.
|
|
11625
|
-
setHeaders: (
|
|
11626
|
-
if (
|
|
11627
|
-
|
|
11757
|
+
setHeaders: (res, path10) => {
|
|
11758
|
+
if (path10.endsWith(".html")) {
|
|
11759
|
+
res.setHeader("Cache-Control", "no-cache, must-revalidate");
|
|
11628
11760
|
} else {
|
|
11629
|
-
|
|
11761
|
+
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
11630
11762
|
}
|
|
11631
11763
|
}
|
|
11632
11764
|
});
|
|
@@ -11690,10 +11822,18 @@ function buildQueryGenerationPrompt(ctx) {
|
|
|
11690
11822
|
lines.push(`Country: ${ctx.country}`);
|
|
11691
11823
|
lines.push(`Language: ${ctx.language}`);
|
|
11692
11824
|
if (ctx.siteText) {
|
|
11693
|
-
lines.push(
|
|
11825
|
+
lines.push(
|
|
11826
|
+
"",
|
|
11827
|
+
"--- Site Content ---",
|
|
11828
|
+
ctx.siteText,
|
|
11829
|
+
"--- End Site Content ---"
|
|
11830
|
+
);
|
|
11694
11831
|
}
|
|
11695
11832
|
if (ctx.existingQueries.length > 0) {
|
|
11696
|
-
lines.push(
|
|
11833
|
+
lines.push(
|
|
11834
|
+
"",
|
|
11835
|
+
`Already tracking (do NOT duplicate): ${ctx.existingQueries.join(", ")}`
|
|
11836
|
+
);
|
|
11697
11837
|
}
|
|
11698
11838
|
lines.push(
|
|
11699
11839
|
"",
|
|
@@ -11715,7 +11855,10 @@ function parseQueryResponse(raw, count) {
|
|
|
11715
11855
|
let cleaned = line.replace(/^\s*(?:\d+[.)]\s*|[-*•]\s*)/, "").trim();
|
|
11716
11856
|
cleaned = cleaned.replace(/^["']|["']$/g, "").trim();
|
|
11717
11857
|
if (!cleaned) continue;
|
|
11718
|
-
if (/^(?:here are|sure|certainly|of course|i['’]ve|these are|below are)/i.test(
|
|
11858
|
+
if (/^(?:here are|sure|certainly|of course|i['’]ve|these are|below are)/i.test(
|
|
11859
|
+
cleaned
|
|
11860
|
+
))
|
|
11861
|
+
continue;
|
|
11719
11862
|
if (cleaned.split(/\s+/).length > 8) continue;
|
|
11720
11863
|
const key = cleaned.toLowerCase();
|
|
11721
11864
|
if (seen.has(key)) continue;
|