@ainyc/canonry 4.98.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.
Files changed (26) hide show
  1. package/assets/assets/{BacklinksPage-CuWs7I1D.js → BacklinksPage-v2RC8wlg.js} +1 -1
  2. package/assets/assets/{ChartPrimitives-bm37tprp.js → ChartPrimitives-B74e5pqn.js} +1 -1
  3. package/assets/assets/ProjectPage-BCzS7TQn.js +6 -0
  4. package/assets/assets/{RunRow-DzhQl87g.js → RunRow-BmVPjO-m.js} +1 -1
  5. package/assets/assets/{RunsPage-wOaewhbF.js → RunsPage-CK5atXvL.js} +1 -1
  6. package/assets/assets/{SettingsPage-CJdhhkkE.js → SettingsPage-DarPFUPl.js} +1 -1
  7. package/assets/assets/{TrafficPage-BJBeN5Ph.js → TrafficPage-Ddnn_WqU.js} +1 -1
  8. package/assets/assets/{TrafficSourceDetailPage-D3PmH0B1.js → TrafficSourceDetailPage-g08SKnwZ.js} +1 -1
  9. package/assets/assets/{arrow-left-BmITMI6O.js → arrow-left-D39Vxasb.js} +1 -1
  10. package/assets/assets/{extract-error-message-BGwnyAfP.js → extract-error-message-BiNWqBeh.js} +1 -1
  11. package/assets/assets/{index-BsZNCwg8.css → index-BCuWbxUj.css} +1 -1
  12. package/assets/assets/index-Dd4J1VhB.js +210 -0
  13. package/assets/assets/{trash-2-Bd2mkP_w.js → trash-2-xh6gsnap.js} +1 -1
  14. package/assets/index.html +2 -2
  15. package/dist/{chunk-7COFPNMH.js → chunk-4Q3LBD7A.js} +470 -131
  16. package/dist/{chunk-3TH54726.js → chunk-IDI6LMYL.js} +27 -1
  17. package/dist/{chunk-CWFTMFEU.js → chunk-JF7EXHGA.js} +12 -3
  18. package/dist/{chunk-DJVUNURL.js → chunk-YBIT5O62.js} +36 -29
  19. package/dist/cli.js +4 -4
  20. package/dist/index.d.ts +16 -0
  21. package/dist/index.js +4 -4
  22. package/dist/{intelligence-service-QHBXW4KI.js → intelligence-service-TWCEKVKZ.js} +2 -2
  23. package/dist/mcp.js +2 -2
  24. package/package.json +7 -7
  25. package/assets/assets/ProjectPage-BksYe8FP.js +0 -6
  26. 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-3TH54726.js";
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-DJVUNURL.js";
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-CWFTMFEU.js";
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)) continue;
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-QHBXW4KI.js");
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);
@@ -8331,13 +8332,63 @@ function buildSkillDocTools() {
8331
8332
  // src/agent/mcp-to-agent-tool.ts
8332
8333
  import { Type as Type2 } from "@sinclair/typebox";
8333
8334
  var MAX_TOOL_RESULT_CHARS = 2e4;
8334
- function truncate(json) {
8335
- if (json.length <= MAX_TOOL_RESULT_CHARS) return json;
8336
- return json.slice(0, MAX_TOOL_RESULT_CHARS) + "\n... (truncated \u2014 result too large)";
8335
+ var TRUNCATION_NOTE = "... (truncated \u2014 result too large)";
8336
+ function serializeResult(value) {
8337
+ return JSON.stringify(value, null, 2);
8338
+ }
8339
+ function largestArrayKey(obj) {
8340
+ let best;
8341
+ let bestLen = -1;
8342
+ for (const [k, v] of Object.entries(obj)) {
8343
+ if (!Array.isArray(v)) continue;
8344
+ const len = serializeResult(v).length;
8345
+ if (len > bestLen) {
8346
+ bestLen = len;
8347
+ best = k;
8348
+ }
8349
+ }
8350
+ return best;
8351
+ }
8352
+ function trimRowsToFit(rows, render) {
8353
+ let kept = rows.slice();
8354
+ while (kept.length > 0 && render(kept).length > MAX_TOOL_RESULT_CHARS) {
8355
+ kept = kept.slice(0, -1);
8356
+ }
8357
+ return kept;
8358
+ }
8359
+ function truncateToolResult(details) {
8360
+ const full = serializeResult(details);
8361
+ if (full.length <= MAX_TOOL_RESULT_CHARS) return full;
8362
+ if (Array.isArray(details)) {
8363
+ const kept = trimRowsToFit(
8364
+ details,
8365
+ (rows) => serializeResult({ items: rows, __truncated: true, __omittedRows: details.length - rows.length })
8366
+ );
8367
+ return serializeResult({ items: kept, __truncated: true, __omittedRows: details.length - kept.length });
8368
+ }
8369
+ if (details && typeof details === "object") {
8370
+ const obj = details;
8371
+ const arrayKey = largestArrayKey(obj);
8372
+ if (arrayKey) {
8373
+ const rows = obj[arrayKey];
8374
+ const kept = trimRowsToFit(
8375
+ rows,
8376
+ (r) => serializeResult({ ...obj, [arrayKey]: r, __truncated: true, __omittedRows: rows.length - r.length })
8377
+ );
8378
+ const out = serializeResult({
8379
+ ...obj,
8380
+ [arrayKey]: kept,
8381
+ __truncated: true,
8382
+ __omittedRows: rows.length - kept.length
8383
+ });
8384
+ if (out.length <= MAX_TOOL_RESULT_CHARS) return out;
8385
+ }
8386
+ }
8387
+ return full.slice(0, MAX_TOOL_RESULT_CHARS) + "\n" + TRUNCATION_NOTE;
8337
8388
  }
8338
8389
  function textResult2(details) {
8339
8390
  return {
8340
- content: [{ type: "text", text: truncate(JSON.stringify(details, null, 2)) }],
8391
+ content: [{ type: "text", text: truncateToolResult(details) }],
8341
8392
  details
8342
8393
  };
8343
8394
  }
@@ -8404,13 +8455,31 @@ function loadAeroSystemPrompt(pkgDir) {
8404
8455
  const skillDir = resolveAeroSkillDir(pkgDir);
8405
8456
  const skillBody = fs7.readFileSync(path8.join(skillDir, "SKILL.md"), "utf-8");
8406
8457
  const soulPath = path8.join(skillDir, "soul.md");
8407
- if (!fs7.existsSync(soulPath)) return skillBody;
8408
- const soulBody = fs7.readFileSync(soulPath, "utf-8");
8409
- return `${soulBody.trimEnd()}
8458
+ const base = fs7.existsSync(soulPath) ? `${fs7.readFileSync(soulPath, "utf-8").trimEnd()}
8410
8459
 
8411
8460
  ---
8412
8461
 
8413
- ${skillBody}`;
8462
+ ${skillBody}` : skillBody;
8463
+ return appendSystemPromptExtras(base);
8464
+ }
8465
+ function appendSystemPromptExtras(base, env = process.env) {
8466
+ const inline = env.AERO_SYSTEM_PROMPT_APPEND?.trim();
8467
+ let fileBody = "";
8468
+ const filePath = env.AERO_SYSTEM_PROMPT_FILE?.trim();
8469
+ if (filePath) {
8470
+ try {
8471
+ fileBody = fs7.readFileSync(filePath, "utf-8").trim();
8472
+ } catch {
8473
+ fileBody = "";
8474
+ }
8475
+ }
8476
+ const extras = [inline, fileBody].filter((s) => !!s && s.length > 0);
8477
+ if (extras.length === 0) return base;
8478
+ return `${base.trimEnd()}
8479
+
8480
+ ---
8481
+
8482
+ ${extras.join("\n\n")}`;
8414
8483
  }
8415
8484
  function missingProviderMessage() {
8416
8485
  const configHints = agentProvidersByPriority().join(", ");
@@ -8461,6 +8530,93 @@ function resolveSessionProviderAndModel(config, opts) {
8461
8530
  return { provider, modelId };
8462
8531
  }
8463
8532
 
8533
+ // src/agent/remote-mcp.ts
8534
+ import { Type as Type3 } from "@sinclair/typebox";
8535
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
8536
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
8537
+ var log18 = createLogger("RemoteMcp");
8538
+ var AERO_EXCLUDED_MCP_TOOLS2 = /* @__PURE__ */ new Set([]);
8539
+ var MAX_TOOL_RESULT_CHARS2 = 2e4;
8540
+ function truncate(text) {
8541
+ if (text.length <= MAX_TOOL_RESULT_CHARS2) return text;
8542
+ return text.slice(0, MAX_TOOL_RESULT_CHARS2) + "\n... (truncated, result too large)";
8543
+ }
8544
+ async function connectStreamableHttp(server) {
8545
+ const transport = new StreamableHTTPClientTransport(new URL(server.url), {
8546
+ requestInit: {
8547
+ headers: { Authorization: `Bearer ${server.token}` }
8548
+ }
8549
+ });
8550
+ const client = new Client(
8551
+ { name: "canonry-aero", version: "1.0.0" },
8552
+ { capabilities: {} }
8553
+ );
8554
+ await client.connect(transport);
8555
+ return client;
8556
+ }
8557
+ function isReadOnly(tool) {
8558
+ return tool.annotations?.readOnlyHint === true;
8559
+ }
8560
+ function adaptRemoteTool(client, tool) {
8561
+ const parameters = Type3.Unsafe(
8562
+ tool.inputSchema ?? { type: "object", properties: {} }
8563
+ );
8564
+ const execute = async (_toolCallId, params) => {
8565
+ const result = await client.callTool({ name: tool.name, arguments: params });
8566
+ return {
8567
+ content: [{ type: "text", text: truncate(JSON.stringify(result, null, 2)) }],
8568
+ details: result
8569
+ };
8570
+ };
8571
+ return {
8572
+ name: tool.name,
8573
+ label: tool.annotations?.title ?? tool.name,
8574
+ description: tool.description ?? "",
8575
+ parameters,
8576
+ execute
8577
+ };
8578
+ }
8579
+ async function loadExternalMcpTools(servers, opts = {}) {
8580
+ if (!servers || servers.length === 0) return [];
8581
+ const connect = opts.connect ?? connectStreamableHttp;
8582
+ const excluded = opts.excluded ?? AERO_EXCLUDED_MCP_TOOLS2;
8583
+ const tools = [];
8584
+ for (const server of servers) {
8585
+ const label = server.label ?? server.url;
8586
+ let client;
8587
+ try {
8588
+ client = await connect(server);
8589
+ } catch (err) {
8590
+ log18.error("external-mcp.connect-failed", {
8591
+ label,
8592
+ error: err instanceof Error ? err.message : String(err)
8593
+ });
8594
+ continue;
8595
+ }
8596
+ let listed;
8597
+ try {
8598
+ listed = await client.listTools();
8599
+ } catch (err) {
8600
+ log18.error("external-mcp.list-failed", {
8601
+ label,
8602
+ error: err instanceof Error ? err.message : String(err)
8603
+ });
8604
+ continue;
8605
+ }
8606
+ for (const tool of listed.tools) {
8607
+ if (excluded.has(tool.name)) continue;
8608
+ if (!isReadOnly(tool)) continue;
8609
+ tools.push(adaptRemoteTool(client, tool));
8610
+ }
8611
+ log18.info("external-mcp.loaded", {
8612
+ label,
8613
+ discovered: listed.tools.length,
8614
+ adopted: tools.length
8615
+ });
8616
+ }
8617
+ return tools;
8618
+ }
8619
+
8464
8620
  // src/agent/memory-store.ts
8465
8621
  import crypto18 from "crypto";
8466
8622
  import { and as and13, desc as desc6, eq as eq17, like, sql as sql5 } from "drizzle-orm";
@@ -8684,7 +8840,7 @@ async function compactMessages(args) {
8684
8840
  }
8685
8841
 
8686
8842
  // src/agent/session-registry.ts
8687
- var log18 = createLogger("SessionRegistry");
8843
+ var log19 = createLogger("SessionRegistry");
8688
8844
  var MAX_HYDRATE_NOTES = 20;
8689
8845
  var MAX_HYDRATE_BYTES = 32 * 1024;
8690
8846
  function escapeMemoryFragment(value) {
@@ -8703,10 +8859,53 @@ var SessionRegistry = class {
8703
8859
  * awaits the same promise instead of kicking off a duplicate LLM call.
8704
8860
  */
8705
8861
  compactions = /* @__PURE__ */ new Map();
8862
+ /**
8863
+ * Read-only tools loaded once from the injected remote MCP servers (OSS-A).
8864
+ * The server set is static config, so we connect + adapt a single time and
8865
+ * cache the promise; every session merges the same AgentTool list. Resolves
8866
+ * to `[]` when no servers are configured or all fail to connect (the load is
8867
+ * fail-soft and never throws).
8868
+ */
8869
+ externalToolsPromise;
8706
8870
  opts;
8707
8871
  constructor(opts) {
8708
8872
  this.opts = opts;
8709
8873
  }
8874
+ /**
8875
+ * Lazily load + cache the injected remote MCP tools. The seam OSS-A exposes:
8876
+ * `loadExternalMcpTools` connects to each configured server over the frozen
8877
+ * bearer-gated Streamable HTTP transport, lists tools, and adapts the
8878
+ * read-only, non-excluded ones into AgentTools. Cached so we connect once per
8879
+ * registry lifetime regardless of how many projects or turns run.
8880
+ */
8881
+ loadExternalTools() {
8882
+ if (!this.externalToolsPromise) {
8883
+ this.externalToolsPromise = loadExternalMcpTools(this.opts.config.externalMcpServers).catch(
8884
+ (err) => {
8885
+ log19.error("external-mcp.load-failed", {
8886
+ error: err instanceof Error ? err.message : String(err)
8887
+ });
8888
+ return [];
8889
+ }
8890
+ );
8891
+ }
8892
+ return this.externalToolsPromise;
8893
+ }
8894
+ /**
8895
+ * Append the cached external MCP tools to the live agent's tool list, idempotently.
8896
+ * Called after scope/model alignment in `acquireForTurn` (which rebuilds
8897
+ * `state.tools` from the local registry), so the remote read-only tools ride
8898
+ * alongside the local ones for the upcoming turn. No-op when none are configured.
8899
+ */
8900
+ async mergeExternalTools(agent) {
8901
+ const external = await this.loadExternalTools();
8902
+ if (external.length === 0) return;
8903
+ const current = agent.state.tools;
8904
+ const present = new Set(current.map((t) => t.name));
8905
+ const additions = external.filter((t) => !present.has(t.name));
8906
+ if (additions.length === 0) return;
8907
+ agent.state.tools = [...current, ...additions];
8908
+ }
8710
8909
  /** Read-only access to the config snapshot the registry was built with. */
8711
8910
  getConfig() {
8712
8911
  return this.opts.config;
@@ -8865,6 +9064,7 @@ ${lines.join("\n")}
8865
9064
  if (preferences?.provider || preferences?.modelId) {
8866
9065
  this.alignModel(projectName, agent, preferences);
8867
9066
  }
9067
+ await this.mergeExternalTools(agent);
8868
9068
  await this.maybeCompact(projectName, agent);
8869
9069
  return agent;
8870
9070
  }
@@ -8911,13 +9111,13 @@ ${lines.join("\n")}
8911
9111
  agent.state.messages = result.messages;
8912
9112
  agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, row.systemPrompt);
8913
9113
  this.save(projectName);
8914
- log18.info("compaction.completed", {
9114
+ log19.info("compaction.completed", {
8915
9115
  projectName,
8916
9116
  removedCount: result.removedCount,
8917
9117
  summaryBytes: Buffer.byteLength(result.summary, "utf8")
8918
9118
  });
8919
9119
  } catch (err) {
8920
- log18.error("compaction.failed", {
9120
+ log19.error("compaction.failed", {
8921
9121
  projectName,
8922
9122
  error: err instanceof Error ? err.message : String(err)
8923
9123
  });
@@ -9014,7 +9214,7 @@ ${lines.join("\n")}
9014
9214
  await agent.prompt(msgs);
9015
9215
  this.save(projectName);
9016
9216
  } catch (err) {
9017
- log18.error("drain.failed", {
9217
+ log19.error("drain.failed", {
9018
9218
  projectName,
9019
9219
  error: err instanceof Error ? err.message : String(err)
9020
9220
  });
@@ -9612,7 +9812,7 @@ function formatAuditFactorScore(factor) {
9612
9812
  }
9613
9813
 
9614
9814
  // src/snapshot-service.ts
9615
- var log19 = createLogger("Snapshot");
9815
+ var log20 = createLogger("Snapshot");
9616
9816
  var ANALYSIS_PROVIDER_PRIORITY = ["openai", "claude", "gemini", "perplexity", "local"];
9617
9817
  var SNAPSHOT_QUERY_COUNT = 6;
9618
9818
  var ProviderExecutionGate2 = class {
@@ -9758,7 +9958,7 @@ var SnapshotService = class {
9758
9958
  return mapAuditReport(report);
9759
9959
  } catch (err) {
9760
9960
  const message = err instanceof Error ? err.message : String(err);
9761
- log19.warn("audit.failed", { homepageUrl, error: message });
9961
+ log20.warn("audit.failed", { homepageUrl, error: message });
9762
9962
  return {
9763
9963
  url: homepageUrl,
9764
9964
  finalUrl: homepageUrl,
@@ -9787,7 +9987,7 @@ var SnapshotService = class {
9787
9987
  queries: parsedQueries
9788
9988
  };
9789
9989
  } catch (err) {
9790
- log19.warn("profile.generation-failed", {
9990
+ log20.warn("profile.generation-failed", {
9791
9991
  domain: ctx.domain,
9792
9992
  provider: ctx.analysisProvider.adapter.name,
9793
9993
  error: err instanceof Error ? err.message : String(err)
@@ -9929,7 +10129,7 @@ var SnapshotService = class {
9929
10129
  recommendedActions: uniqueStrings(parsed.recommendedActions ?? []).slice(0, 4)
9930
10130
  };
9931
10131
  } catch (err) {
9932
- log19.warn("response.analysis-failed", {
10132
+ log20.warn("response.analysis-failed", {
9933
10133
  provider: ctx.analysisProvider.adapter.name,
9934
10134
  error: err instanceof Error ? err.message : String(err)
9935
10135
  });
@@ -10211,7 +10411,7 @@ function clipText(value, length) {
10211
10411
  // src/server.ts
10212
10412
  var _require3 = createRequire3(import.meta.url);
10213
10413
  var { version: PKG_VERSION2 } = _require3("../package.json");
10214
- var log20 = createLogger("Server");
10414
+ var log21 = createLogger("Server");
10215
10415
  var DEFAULT_QUOTA = {
10216
10416
  maxConcurrency: 2,
10217
10417
  maxRequestsPerMinute: 10,
@@ -10226,9 +10426,7 @@ var API_ADAPTERS = [
10226
10426
  localAdapter,
10227
10427
  perplexityAdapter
10228
10428
  ];
10229
- var BROWSER_ADAPTERS = [
10230
- cdpChatgptAdapter
10231
- ];
10429
+ var BROWSER_ADAPTERS = [cdpChatgptAdapter];
10232
10430
  var adapterMap = Object.fromEntries(
10233
10431
  API_ADAPTERS.map((a) => [a.name, a])
10234
10432
  );
@@ -10278,13 +10476,18 @@ function verifyDashboardPassword(password, storedHash) {
10278
10476
  N: DASHBOARD_SCRYPT_COST,
10279
10477
  maxmem: DASHBOARD_SCRYPT_MAXMEM
10280
10478
  });
10281
- if (derived.length !== expected.length) return { ok: false, needsRehash: false };
10282
- return { ok: crypto20.timingSafeEqual(derived, expected), needsRehash: false };
10479
+ if (derived.length !== expected.length)
10480
+ return { ok: false, needsRehash: false };
10481
+ return {
10482
+ ok: crypto20.timingSafeEqual(derived, expected),
10483
+ needsRehash: false
10484
+ };
10283
10485
  }
10284
10486
  if (/^[a-f0-9]{64}$/i.test(storedHash)) {
10285
10487
  const candidate = Buffer.from(hashApiKey(password), "hex");
10286
10488
  const expected = Buffer.from(storedHash, "hex");
10287
- if (candidate.length !== expected.length) return { ok: false, needsRehash: false };
10489
+ if (candidate.length !== expected.length)
10490
+ return { ok: false, needsRehash: false };
10288
10491
  const ok = crypto20.timingSafeEqual(candidate, expected);
10289
10492
  return { ok, needsRehash: ok };
10290
10493
  }
@@ -10326,7 +10529,11 @@ function serializeSessionCookie(opts) {
10326
10529
  function applyLegacyCredentials(rows, config) {
10327
10530
  let migratedGoogle = 0;
10328
10531
  for (const row of rows.google) {
10329
- const existing = getGoogleConnection(config, row.domain, row.connectionType);
10532
+ const existing = getGoogleConnection(
10533
+ config,
10534
+ row.domain,
10535
+ row.connectionType
10536
+ );
10330
10537
  if (existing?.refreshToken) continue;
10331
10538
  upsertGoogleConnection(config, {
10332
10539
  domain: row.domain,
@@ -10344,7 +10551,7 @@ function applyLegacyCredentials(rows, config) {
10344
10551
  }
10345
10552
  if (migratedGoogle > 0) {
10346
10553
  saveConfigPatch({ google: config.google });
10347
- log20.info("credentials.migrated", { type: "google", count: migratedGoogle });
10554
+ log21.info("credentials.migrated", { type: "google", count: migratedGoogle });
10348
10555
  }
10349
10556
  let migratedGa4 = 0;
10350
10557
  for (const row of rows.ga4) {
@@ -10362,7 +10569,7 @@ function applyLegacyCredentials(rows, config) {
10362
10569
  }
10363
10570
  if (migratedGa4 > 0) {
10364
10571
  saveConfigPatch({ ga4: config.ga4 });
10365
- log20.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
10572
+ log21.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
10366
10573
  }
10367
10574
  }
10368
10575
  function isLoopbackBindHost(host) {
@@ -10401,14 +10608,16 @@ async function createServer(opts) {
10401
10608
  applyLegacyCredentials(legacyRows, opts.config);
10402
10609
  dropLegacyCredentialColumns(opts.db);
10403
10610
  } catch (err) {
10404
- log20.warn("credentials.migration.failed", {
10611
+ log21.warn("credentials.migration.failed", {
10405
10612
  error: err instanceof Error ? err.message : String(err)
10406
10613
  });
10407
10614
  }
10408
- log20.info("providers.configured", { providers: Object.keys(providers).filter((k) => {
10409
- const p = providers[k];
10410
- return p?.apiKey || p?.baseUrl || p?.vertexProject;
10411
- }) });
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
+ });
10412
10621
  for (const adapter of API_ADAPTERS) {
10413
10622
  const entry = providers[adapter.name];
10414
10623
  if (!entry) continue;
@@ -10428,7 +10637,11 @@ async function createServer(opts) {
10428
10637
  }
10429
10638
  const cdpConfig = opts.config.cdp;
10430
10639
  if (cdpConfig?.host || cdpConfig?.port) {
10431
- const CDP_DEFAULT_QUOTA = { maxConcurrency: 1, maxRequestsPerMinute: 4, maxRequestsPerDay: 200 };
10640
+ const CDP_DEFAULT_QUOTA = {
10641
+ maxConcurrency: 1,
10642
+ maxRequestsPerMinute: 4,
10643
+ maxRequestsPerDay: 200
10644
+ };
10432
10645
  const cdpEndpoint = `ws://${cdpConfig.host ?? "localhost"}:${cdpConfig.port ?? 9222}`;
10433
10646
  registry.register(cdpChatgptAdapter, {
10434
10647
  provider: "cdp:chatgpt",
@@ -10442,7 +10655,9 @@ async function createServer(opts) {
10442
10655
  jobRunner.recoverStaleRuns();
10443
10656
  const notifier = new Notifier(opts.db, serverUrl);
10444
10657
  const intelligenceService = new IntelligenceService(opts.db);
10445
- const aeroClient = new ApiClient(opts.config.apiUrl, opts.config.apiKey, { skipProbe: true });
10658
+ const aeroClient = new ApiClient(opts.config.apiUrl, opts.config.apiKey, {
10659
+ skipProbe: true
10660
+ });
10446
10661
  const agentEnabled = resolveAgentEnabled(process.env, opts.config);
10447
10662
  const sessionRegistry = agentEnabled ? new SessionRegistry({
10448
10663
  db: opts.db,
@@ -10489,7 +10704,9 @@ async function createServer(opts) {
10489
10704
  const runGbpSync = (runId, projectId, syncOpts) => {
10490
10705
  const { clientId: googleClientId, clientSecret: googleClientSecret } = getGoogleAuthConfig(opts.config);
10491
10706
  if (!googleClientId || !googleClientSecret) {
10492
- app.log.error("GBP sync requested but Google OAuth credentials are not configured in the local config");
10707
+ app.log.error(
10708
+ "GBP sync requested but Google OAuth credentials are not configured in the local config"
10709
+ );
10493
10710
  return;
10494
10711
  }
10495
10712
  executeGbpSync(opts.db, runId, projectId, {
@@ -10542,7 +10759,14 @@ async function createServer(opts) {
10542
10759
  },
10543
10760
  onTrafficSyncRequested: (projectName, sourceId) => {
10544
10761
  aeroClient.trafficSync(projectName, sourceId).catch((err) => {
10545
- app.log.error({ projectName, sourceId, err: err instanceof Error ? err.message : String(err) }, "Scheduled traffic sync failed");
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
+ );
10546
10770
  });
10547
10771
  },
10548
10772
  onGbpSyncRequested: (runId, projectId) => {
@@ -10557,21 +10781,33 @@ async function createServer(opts) {
10557
10781
  onBacklinksSyncRequested: (projectName) => {
10558
10782
  void (async () => {
10559
10783
  const probed = await probeLatestRelease().catch((err) => {
10560
- app.log.warn({ projectName, err }, "Scheduled backlinks sync: latest-release probe failed");
10784
+ app.log.warn(
10785
+ { projectName, err },
10786
+ "Scheduled backlinks sync: latest-release probe failed"
10787
+ );
10561
10788
  return null;
10562
10789
  });
10563
10790
  if (!probed) return;
10564
- const alreadySynced = opts.db.select().from(ccReleaseSyncs).where(and14(
10565
- eq20(ccReleaseSyncs.release, probed.release),
10566
- eq20(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)
10567
- )).limit(1).get();
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();
10568
10797
  if (alreadySynced) {
10569
- app.log.info({ projectName, release: probed.release }, "Scheduled backlinks sync: already up to date, skipping");
10798
+ app.log.info(
10799
+ { projectName, release: probed.release },
10800
+ "Scheduled backlinks sync: already up to date, skipping"
10801
+ );
10570
10802
  return;
10571
10803
  }
10572
10804
  aeroClient.backlinksTriggerSync(probed.release).catch((err) => {
10573
10805
  app.log.error(
10574
- { projectName, release: probed.release, err: err instanceof Error ? err.message : String(err) },
10806
+ {
10807
+ projectName,
10808
+ release: probed.release,
10809
+ err: err instanceof Error ? err.message : String(err)
10810
+ },
10575
10811
  "Scheduled backlinks sync failed"
10576
10812
  );
10577
10813
  });
@@ -10580,7 +10816,10 @@ async function createServer(opts) {
10580
10816
  if (project && bingConnectionStore.getConnection(project.canonicalDomain)) {
10581
10817
  aeroClient.backlinksBingSync(projectName).catch((err) => {
10582
10818
  app.log.error(
10583
- { projectName, err: err instanceof Error ? err.message : String(err) },
10819
+ {
10820
+ projectName,
10821
+ err: err instanceof Error ? err.message : String(err)
10822
+ },
10584
10823
  "Scheduled Bing backlinks sync failed"
10585
10824
  );
10586
10825
  });
@@ -10602,7 +10841,9 @@ async function createServer(opts) {
10602
10841
  vertexConfigured: adapter.name === "gemini" ? !!opts.config.providers?.gemini?.vertexProject : void 0
10603
10842
  }));
10604
10843
  const googleSettingsSummary = {
10605
- configured: Boolean(opts.config.google?.clientId && opts.config.google?.clientSecret)
10844
+ configured: Boolean(
10845
+ opts.config.google?.clientId && opts.config.google?.clientSecret
10846
+ )
10606
10847
  };
10607
10848
  const bingSettingsSummary = {
10608
10849
  // Treat Bing as configured if there is at least one connection with an API key,
@@ -10619,8 +10860,13 @@ async function createServer(opts) {
10619
10860
  upsertConnection: (connection) => {
10620
10861
  if (!opts.config.bing) opts.config.bing = {};
10621
10862
  if (!opts.config.bing.connections) opts.config.bing.connections = [];
10622
- const idx = opts.config.bing.connections.findIndex((c) => c.domain === connection.domain);
10623
- const normalized = { ...connection, createdByProjectId: connection.createdByProjectId ?? null };
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
+ };
10624
10870
  if (idx >= 0) {
10625
10871
  opts.config.bing.connections[idx] = normalized;
10626
10872
  } else {
@@ -10630,7 +10876,9 @@ async function createServer(opts) {
10630
10876
  return normalized;
10631
10877
  },
10632
10878
  updateConnection: (domain, patch) => {
10633
- const conn = opts.config.bing?.connections?.find((c) => c.domain === domain);
10879
+ const conn = opts.config.bing?.connections?.find(
10880
+ (c) => c.domain === domain
10881
+ );
10634
10882
  if (!conn) return void 0;
10635
10883
  Object.assign(conn, patch);
10636
10884
  saveConfigPatch(opts.config);
@@ -10638,7 +10886,9 @@ async function createServer(opts) {
10638
10886
  },
10639
10887
  deleteConnection: (domain) => {
10640
10888
  if (!opts.config.bing?.connections) return false;
10641
- const idx = opts.config.bing.connections.findIndex((c) => c.domain === domain);
10889
+ const idx = opts.config.bing.connections.findIndex(
10890
+ (c) => c.domain === domain
10891
+ );
10642
10892
  if (idx < 0) return false;
10643
10893
  opts.config.bing.connections.splice(idx, 1);
10644
10894
  saveConfigPatch(opts.config);
@@ -10685,7 +10935,10 @@ async function createServer(opts) {
10685
10935
  return updated;
10686
10936
  },
10687
10937
  deleteConnection: (projectName) => {
10688
- const removed = removeWordpressTrafficConnection(opts.config, projectName);
10938
+ const removed = removeWordpressTrafficConnection(
10939
+ opts.config,
10940
+ projectName
10941
+ );
10689
10942
  if (removed) saveConfigPatch(opts.config);
10690
10943
  return removed;
10691
10944
  }
@@ -10715,12 +10968,21 @@ async function createServer(opts) {
10715
10968
  return updated;
10716
10969
  },
10717
10970
  updateConnection: (domain, connectionType, patch) => {
10718
- const updated = patchGoogleConnection(opts.config, domain, connectionType, patch);
10971
+ const updated = patchGoogleConnection(
10972
+ opts.config,
10973
+ domain,
10974
+ connectionType,
10975
+ patch
10976
+ );
10719
10977
  if (updated) saveConfigPatch(opts.config);
10720
10978
  return updated;
10721
10979
  },
10722
10980
  deleteConnection: (domain, connectionType) => {
10723
- const removed = removeGoogleConnection(opts.config, domain, connectionType);
10981
+ const removed = removeGoogleConnection(
10982
+ opts.config,
10983
+ domain,
10984
+ connectionType
10985
+ );
10724
10986
  if (removed) saveConfigPatch(opts.config);
10725
10987
  return removed;
10726
10988
  }
@@ -10811,13 +11073,16 @@ async function createServer(opts) {
10811
11073
  const key = getDefaultApiKey();
10812
11074
  if (!key || key.revokedAt) return false;
10813
11075
  const sessionId = createSession(key.id);
10814
- reply.header("set-cookie", serializeSessionCookie({
10815
- name: SESSION_COOKIE_NAME,
10816
- value: sessionId,
10817
- path: sessionCookiePath,
10818
- secure: sessionCookieSecure,
10819
- ttlMs: SESSION_TTL_MS
10820
- }));
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
+ );
10821
11086
  return true;
10822
11087
  };
10823
11088
  const boundToLoopback = isLoopbackBindHost(opts.host);
@@ -10838,7 +11103,9 @@ async function createServer(opts) {
10838
11103
  });
10839
11104
  app.post(apiPrefix + "/session/setup", async (request, reply) => {
10840
11105
  if (!boundToLoopback && !requestHasValidApiKey(request)) {
10841
- const err = authRequired("This server is network-reachable; setting the dashboard password requires a valid API key.");
11106
+ const err = authRequired(
11107
+ "This server is network-reachable; setting the dashboard password requires a valid API key."
11108
+ );
10842
11109
  return reply.status(err.statusCode).send(err.toJSON());
10843
11110
  }
10844
11111
  if (opts.config.dashboardPasswordHash) {
@@ -10863,19 +11130,31 @@ async function createServer(opts) {
10863
11130
  const apiKey = request.body?.apiKey?.trim();
10864
11131
  if (password) {
10865
11132
  if (!opts.config.dashboardPasswordHash) {
10866
- const err2 = validationError("No dashboard password configured \u2014 use /session/setup first");
11133
+ const err2 = validationError(
11134
+ "No dashboard password configured \u2014 use /session/setup first"
11135
+ );
10867
11136
  return reply.status(err2.statusCode).send(err2.toJSON());
10868
11137
  }
10869
- const verification = verifyDashboardPassword(password, opts.config.dashboardPasswordHash);
11138
+ const verification = verifyDashboardPassword(
11139
+ password,
11140
+ opts.config.dashboardPasswordHash
11141
+ );
10870
11142
  if (!verification.ok) {
10871
- return reply.status(401).send({ error: { code: "AUTH_INVALID", message: "Incorrect password" } });
11143
+ return reply.status(401).send({
11144
+ error: { code: "AUTH_INVALID", message: "Incorrect password" }
11145
+ });
10872
11146
  }
10873
11147
  if (verification.needsRehash) {
10874
11148
  opts.config.dashboardPasswordHash = hashDashboardPassword(password);
10875
11149
  saveConfigPatch(opts.config);
10876
11150
  }
10877
11151
  if (!createPasswordSession(reply)) {
10878
- return reply.status(401).send({ error: { code: "AUTH_INVALID", message: "Server API key not found \u2014 re-run canonry init" } });
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
+ });
10879
11158
  }
10880
11159
  return reply.send({ authenticated: true });
10881
11160
  }
@@ -10887,13 +11166,16 @@ async function createServer(opts) {
10887
11166
  }
10888
11167
  opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq20(apiKeys.id, key.id)).run();
10889
11168
  const sessionId = createSession(key.id);
10890
- reply.header("set-cookie", serializeSessionCookie({
10891
- name: SESSION_COOKIE_NAME,
10892
- value: sessionId,
10893
- path: sessionCookiePath,
10894
- secure: sessionCookieSecure,
10895
- ttlMs: SESSION_TTL_MS
10896
- }));
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
+ );
10897
11179
  return reply.send({ authenticated: true });
10898
11180
  }
10899
11181
  const err = validationError("Either password or apiKey is required");
@@ -10902,13 +11184,16 @@ async function createServer(opts) {
10902
11184
  app.delete(apiPrefix + "/session", async (request, reply) => {
10903
11185
  const sessionId = parseCookies(request.headers.cookie)[SESSION_COOKIE_NAME];
10904
11186
  clearSession(sessionId);
10905
- reply.header("set-cookie", serializeSessionCookie({
10906
- name: SESSION_COOKIE_NAME,
10907
- value: null,
10908
- path: sessionCookiePath,
10909
- secure: sessionCookieSecure,
10910
- ttlMs: SESSION_TTL_MS
10911
- }));
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
+ );
10912
11197
  return reply.status(204).send();
10913
11198
  });
10914
11199
  const LATEST_RELEASE_TTL_MS = 5 * 60 * 1e3;
@@ -10933,8 +11218,12 @@ async function createServer(opts) {
10933
11218
  latestReleaseCache = { value, expiresAt: now + LATEST_RELEASE_TTL_MS };
10934
11219
  return value;
10935
11220
  };
10936
- const explainContentRecommendation = createRecommendationExplainer({ config: opts.config });
10937
- const briefContentRecommendation = createRecommendationBriefSynthesizer({ config: opts.config });
11221
+ const explainContentRecommendation = createRecommendationExplainer({
11222
+ config: opts.config
11223
+ });
11224
+ const briefContentRecommendation = createRecommendationBriefSynthesizer({
11225
+ config: opts.config
11226
+ });
10938
11227
  await app.register(apiRoutes, {
10939
11228
  db: opts.db,
10940
11229
  routePrefix: apiPrefix,
@@ -10996,7 +11285,9 @@ async function createServer(opts) {
10996
11285
  onGscSyncRequested: (runId, projectId, syncOpts) => {
10997
11286
  const { clientId: googleClientId, clientSecret: googleClientSecret } = getGoogleAuthConfig(opts.config);
10998
11287
  if (!googleClientId || !googleClientSecret) {
10999
- app.log.error("GSC sync requested but Google OAuth credentials are not configured in the local config");
11288
+ app.log.error(
11289
+ "GSC sync requested but Google OAuth credentials are not configured in the local config"
11290
+ );
11000
11291
  return;
11001
11292
  }
11002
11293
  executeGscSync(opts.db, runId, projectId, {
@@ -11009,7 +11300,9 @@ async function createServer(opts) {
11009
11300
  onInspectSitemapRequested: (runId, projectId, inspectOpts) => {
11010
11301
  const { clientId: googleClientId, clientSecret: googleClientSecret } = getGoogleAuthConfig(opts.config);
11011
11302
  if (!googleClientId || !googleClientSecret) {
11012
- app.log.error("Inspect sitemap requested but Google OAuth credentials are not configured");
11303
+ app.log.error(
11304
+ "Inspect sitemap requested but Google OAuth credentials are not configured"
11305
+ );
11013
11306
  return;
11014
11307
  }
11015
11308
  executeInspectSitemap(opts.db, runId, projectId, {
@@ -11034,7 +11327,9 @@ async function createServer(opts) {
11034
11327
  pluginDir: PLUGIN_DIR
11035
11328
  }),
11036
11329
  onInstallBacklinks: async () => {
11037
- const result = await installDuckdb({ onLog: (line) => app.log.info({ line }, "duckdb install") });
11330
+ const result = await installDuckdb({
11331
+ onLog: (line) => app.log.info({ line }, "duckdb install")
11332
+ });
11038
11333
  return {
11039
11334
  installed: true,
11040
11335
  version: result.version,
@@ -11057,8 +11352,13 @@ async function createServer(opts) {
11057
11352
  trigger: RunTriggers.scheduled,
11058
11353
  createdAt: now
11059
11354
  }).run();
11060
- executeBacklinkExtract(opts.db, runId, projectId, { release: r }).catch((err) => {
11061
- app.log.error({ runId, projectId, err }, "Auto backlink extract failed");
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
+ );
11062
11362
  });
11063
11363
  }
11064
11364
  }
@@ -11067,9 +11367,11 @@ async function createServer(opts) {
11067
11367
  });
11068
11368
  },
11069
11369
  onBacklinkExtractRequested: (runId, projectId, release) => {
11070
- executeBacklinkExtract(opts.db, runId, projectId, { release }).catch((err) => {
11071
- app.log.error({ runId, err }, "Backlink extract failed");
11072
- });
11370
+ executeBacklinkExtract(opts.db, runId, projectId, { release }).catch(
11371
+ (err) => {
11372
+ app.log.error({ runId, err }, "Backlink extract failed");
11373
+ }
11374
+ );
11073
11375
  },
11074
11376
  onBingBacklinkSyncRequested: (runId, projectId) => {
11075
11377
  executeBingBacklinkSync(opts.db, runId, projectId, {
@@ -11104,9 +11406,16 @@ async function createServer(opts) {
11104
11406
  embedQueries: (queriesToEmbed) => {
11105
11407
  const cfg = registry.get("gemini")?.config;
11106
11408
  if (!cfg?.apiKey) {
11107
- return Promise.reject(new Error("Gemini API key not configured; harvest semantic novelty unavailable"));
11409
+ return Promise.reject(
11410
+ new Error(
11411
+ "Gemini API key not configured; harvest semantic novelty unavailable"
11412
+ )
11413
+ );
11108
11414
  }
11109
- return embedQueries(queriesToEmbed, { apiKey: cfg.apiKey, baseUrl: cfg.baseUrl });
11415
+ return embedQueries(queriesToEmbed, {
11416
+ apiKey: cfg.apiKey,
11417
+ baseUrl: cfg.baseUrl
11418
+ });
11110
11419
  },
11111
11420
  onSiteAuditRequested: (runId, projectId, auditOpts) => {
11112
11421
  runSiteAudit(runId, projectId, auditOpts);
@@ -11122,7 +11431,10 @@ async function createServer(opts) {
11122
11431
  const cached = listCachedReleases();
11123
11432
  const syncByRelease = /* @__PURE__ */ new Map();
11124
11433
  for (const row of opts.db.select().from(ccReleaseSyncs).all()) {
11125
- syncByRelease.set(row.release, { status: row.status, updatedAt: row.updatedAt });
11434
+ syncByRelease.set(row.release, {
11435
+ status: row.status,
11436
+ updatedAt: row.updatedAt
11437
+ });
11126
11438
  }
11127
11439
  return cached.map((entry) => {
11128
11440
  const sync = syncByRelease.get(entry.release);
@@ -11171,16 +11483,20 @@ async function createServer(opts) {
11171
11483
  wordpressTrafficCredentialStore,
11172
11484
  vercelTrafficCredentialStore,
11173
11485
  onTrafficSynced: (event) => {
11174
- trackEvent("traffic.synced", {
11175
- status: event.status,
11176
- sourceType: event.sourceType,
11177
- sourceId: event.sourceId,
11178
- pulledEvents: event.pulledEvents,
11179
- selfTrafficExcluded: event.selfTrafficExcluded,
11180
- crawlerHits: event.crawlerHits,
11181
- aiReferralHits: event.aiReferralHits,
11182
- durationMs: event.durationMs
11183
- }, event.errorCode ? { errorCode: event.errorCode } : void 0);
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
+ );
11184
11500
  },
11185
11501
  onRunCreated: (runId, projectId, providers2, location) => {
11186
11502
  jobRunner.executeRun(runId, projectId, providers2, location).catch((err) => {
@@ -11243,16 +11559,18 @@ async function createServer(opts) {
11243
11559
  }).map((project) => project.id);
11244
11560
  const targetProjectIds = affectedProjectIds.length > 0 ? affectedProjectIds : [null];
11245
11561
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
11246
- opts.db.insert(auditLog).values(targetProjectIds.map((projectId) => ({
11247
- id: crypto20.randomUUID(),
11248
- projectId,
11249
- actor: "api",
11250
- action: existing ? "provider.updated" : "provider.created",
11251
- entityType: "provider",
11252
- entityId: name,
11253
- diff,
11254
- createdAt
11255
- }))).run();
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();
11256
11574
  }
11257
11575
  return {
11258
11576
  name,
@@ -11300,7 +11618,10 @@ async function createServer(opts) {
11300
11618
  "aliases changed \u2014 recomputed mention fields on historical snapshots"
11301
11619
  );
11302
11620
  } catch (err) {
11303
- app.log.error({ err, projectId, projectName }, "alias-triggered backfill failed");
11621
+ app.log.error(
11622
+ { err, projectId, projectName },
11623
+ "alias-triggered backfill failed"
11624
+ );
11304
11625
  }
11305
11626
  });
11306
11627
  },
@@ -11329,7 +11650,11 @@ async function createServer(opts) {
11329
11650
  app.log.error({ err }, "Failed to save CDP config");
11330
11651
  throw err;
11331
11652
  }
11332
- const CDP_DEFAULT_QUOTA = { maxConcurrency: 1, maxRequestsPerMinute: 4, maxRequestsPerDay: 200 };
11653
+ const CDP_DEFAULT_QUOTA = {
11654
+ maxConcurrency: 1,
11655
+ maxRequestsPerMinute: 4,
11656
+ maxRequestsPerDay: 200
11657
+ };
11333
11658
  registry.register(cdpChatgptAdapter, {
11334
11659
  provider: "cdp:chatgpt",
11335
11660
  cdpEndpoint: `ws://${host}:${port2}`,
@@ -11361,16 +11686,19 @@ async function createServer(opts) {
11361
11686
  conn.config
11362
11687
  );
11363
11688
  const raw = result.rawResponse;
11364
- return [{
11365
- target: targets?.[0] ?? "chatgpt",
11366
- screenshotPath: result.screenshotPath ?? "",
11367
- answerText: raw.answerText ?? "",
11368
- citations: raw.groundingSources ?? []
11369
- }];
11689
+ return [
11690
+ {
11691
+ target: targets?.[0] ?? "chatgpt",
11692
+ screenshotPath: result.screenshotPath ?? "",
11693
+ answerText: raw.answerText ?? "",
11694
+ citations: raw.groundingSources ?? []
11695
+ }
11696
+ ];
11370
11697
  },
11371
11698
  onGenerateQueries: async (providerName, count, project) => {
11372
11699
  const provider = registry.get(providerName);
11373
- if (!provider) throw new Error(`Provider "${providerName}" is not configured`);
11700
+ if (!provider)
11701
+ throw new Error(`Provider "${providerName}" is not configured`);
11374
11702
  const siteText = await fetchSiteText(project.domain);
11375
11703
  const prompt = buildQueryGenerationPrompt({
11376
11704
  domain: project.domain,
@@ -11423,11 +11751,11 @@ async function createServer(opts) {
11423
11751
  // Without this, the browser hits the server for every JS chunk on
11424
11752
  // every page load, defeating most of the dashboard's first-paint
11425
11753
  // budget.
11426
- setHeaders: (reply, filePath) => {
11427
- if (filePath.endsWith(".html")) {
11428
- reply.setHeader("Cache-Control", "no-cache, must-revalidate");
11754
+ setHeaders: (res, path10) => {
11755
+ if (path10.endsWith(".html")) {
11756
+ res.setHeader("Cache-Control", "no-cache, must-revalidate");
11429
11757
  } else {
11430
- reply.setHeader("Cache-Control", "public, max-age=31536000, immutable");
11758
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
11431
11759
  }
11432
11760
  }
11433
11761
  });
@@ -11491,10 +11819,18 @@ function buildQueryGenerationPrompt(ctx) {
11491
11819
  lines.push(`Country: ${ctx.country}`);
11492
11820
  lines.push(`Language: ${ctx.language}`);
11493
11821
  if (ctx.siteText) {
11494
- lines.push("", "--- Site Content ---", ctx.siteText, "--- End Site Content ---");
11822
+ lines.push(
11823
+ "",
11824
+ "--- Site Content ---",
11825
+ ctx.siteText,
11826
+ "--- End Site Content ---"
11827
+ );
11495
11828
  }
11496
11829
  if (ctx.existingQueries.length > 0) {
11497
- lines.push("", `Already tracking (do NOT duplicate): ${ctx.existingQueries.join(", ")}`);
11830
+ lines.push(
11831
+ "",
11832
+ `Already tracking (do NOT duplicate): ${ctx.existingQueries.join(", ")}`
11833
+ );
11498
11834
  }
11499
11835
  lines.push(
11500
11836
  "",
@@ -11516,7 +11852,10 @@ function parseQueryResponse(raw, count) {
11516
11852
  let cleaned = line.replace(/^\s*(?:\d+[.)]\s*|[-*•]\s*)/, "").trim();
11517
11853
  cleaned = cleaned.replace(/^["']|["']$/g, "").trim();
11518
11854
  if (!cleaned) continue;
11519
- if (/^(?:here are|sure|certainly|of course|i['’]ve|these are|below are)/i.test(cleaned)) continue;
11855
+ if (/^(?:here are|sure|certainly|of course|i['’]ve|these are|below are)/i.test(
11856
+ cleaned
11857
+ ))
11858
+ continue;
11520
11859
  if (cleaned.split(/\s+/).length > 8) continue;
11521
11860
  const key = cleaned.toLowerCase();
11522
11861
  if (seen.has(key)) continue;