@ainyc/canonry 1.45.1 → 1.45.3

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.
@@ -23,7 +23,7 @@ import {
23
23
  runs,
24
24
  schedules,
25
25
  usageCounters
26
- } from "./chunk-WBV2D7FB.js";
26
+ } from "./chunk-HO22LHTY.js";
27
27
 
28
28
  // src/config.ts
29
29
  import fs from "fs";
@@ -817,7 +817,14 @@ var wordpressDiffDtoSchema = z7.object({
817
817
  import { z as z8 } from "zod";
818
818
  var runStatusSchema = z8.enum(["queued", "running", "completed", "partial", "failed", "cancelled"]);
819
819
  var RunStatuses = runStatusSchema.enum;
820
- var runKindSchema = z8.enum(["answer-visibility", "site-audit", "gsc-sync", "inspect-sitemap"]);
820
+ var runKindSchema = z8.enum([
821
+ "answer-visibility",
822
+ "site-audit",
823
+ "gsc-sync",
824
+ "inspect-sitemap",
825
+ "ga-sync",
826
+ "bing-inspect"
827
+ ]);
821
828
  var RunKinds = runKindSchema.enum;
822
829
  var runTriggerSchema = z8.enum(["manual", "scheduled", "config-apply"]);
823
830
  var RunTriggers = runTriggerSchema.enum;
@@ -6171,7 +6178,14 @@ function validateDate(date, label) {
6171
6178
  }
6172
6179
  }
6173
6180
  function gscClientLog(level, action, ctx) {
6174
- const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GscClient", action, ...ctx };
6181
+ const entry = {
6182
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
6183
+ level,
6184
+ module: "GscClient",
6185
+ action,
6186
+ ...ctx
6187
+ };
6188
+ if (entry.accessToken) entry.accessToken = "***";
6175
6189
  const stream = level === "error" ? process.stderr : process.stdout;
6176
6190
  stream.write(JSON.stringify(entry) + "\n");
6177
6191
  }
@@ -6348,7 +6362,15 @@ function validateScope(scope) {
6348
6362
  }
6349
6363
  }
6350
6364
  function ga4Log(level, action, ctx) {
6351
- const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Client", action, ...ctx };
6365
+ const entry = {
6366
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
6367
+ level,
6368
+ module: "GA4Client",
6369
+ action,
6370
+ ...ctx
6371
+ };
6372
+ if (entry.accessToken) entry.accessToken = "***";
6373
+ if (entry.privateKey) entry.privateKey = "***";
6352
6374
  const stream = level === "error" ? process.stderr : process.stdout;
6353
6375
  stream.write(JSON.stringify(entry) + "\n");
6354
6376
  }
@@ -7542,7 +7564,15 @@ function validateUrls(urls) {
7542
7564
  }
7543
7565
  }
7544
7566
  function bingClientLog(level, action, ctx) {
7545
- const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "BingClient", action, ...ctx };
7567
+ const entry = {
7568
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
7569
+ level,
7570
+ module: "BingClient",
7571
+ action,
7572
+ ...ctx
7573
+ };
7574
+ if (entry.apiKey) entry.apiKey = "***";
7575
+ if (entry.apikey) entry.apikey = "***";
7546
7576
  const stream = level === "error" ? process.stderr : process.stdout;
7547
7577
  stream.write(JSON.stringify(entry) + "\n");
7548
7578
  }
@@ -7793,6 +7823,7 @@ async function bingRoutes(app, opts) {
7793
7823
  const notIndexedUrls = [];
7794
7824
  const unknownUrls = [];
7795
7825
  let lastInspectedAt = null;
7826
+ let snapshotRunId = null;
7796
7827
  for (const [, row] of latestByUrl) {
7797
7828
  if (row.inIndex === 1) {
7798
7829
  indexedUrls.push(row);
@@ -7803,6 +7834,7 @@ async function bingRoutes(app, opts) {
7803
7834
  }
7804
7835
  if (!lastInspectedAt || row.inspectedAt > lastInspectedAt) {
7805
7836
  lastInspectedAt = row.inspectedAt;
7837
+ snapshotRunId = row.syncRunId ?? null;
7806
7838
  }
7807
7839
  }
7808
7840
  const indexed = indexedUrls.length;
@@ -7827,6 +7859,7 @@ async function bingRoutes(app, opts) {
7827
7859
  app.db.insert(bingCoverageSnapshots).values({
7828
7860
  id: crypto15.randomUUID(),
7829
7861
  projectId: project.id,
7862
+ syncRunId: snapshotRunId,
7830
7863
  date: snapshotDate,
7831
7864
  indexed,
7832
7865
  notIndexed,
@@ -7834,7 +7867,7 @@ async function bingRoutes(app, opts) {
7834
7867
  createdAt: now
7835
7868
  }).onConflictDoUpdate({
7836
7869
  target: [bingCoverageSnapshots.projectId, bingCoverageSnapshots.date],
7837
- set: { indexed, notIndexed, unknown, createdAt: now }
7870
+ set: { indexed, notIndexed, unknown, createdAt: now, syncRunId: snapshotRunId }
7838
7871
  }).run();
7839
7872
  }
7840
7873
  return {
@@ -7900,9 +7933,19 @@ async function bingRoutes(app, opts) {
7900
7933
  const err = validationError("url is required");
7901
7934
  return reply.status(err.statusCode).send(err.toJSON());
7902
7935
  }
7903
- let result;
7936
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7937
+ const runId = crypto15.randomUUID();
7938
+ app.db.insert(runs).values({
7939
+ id: runId,
7940
+ projectId: project.id,
7941
+ kind: RunKinds["bing-inspect"],
7942
+ status: RunStatuses.running,
7943
+ trigger: RunTriggers.manual,
7944
+ startedAt,
7945
+ createdAt: startedAt
7946
+ }).run();
7904
7947
  try {
7905
- result = await getUrlInfo(conn.apiKey, conn.siteUrl, url);
7948
+ const result = await getUrlInfo(conn.apiKey, conn.siteUrl, url);
7906
7949
  bingLog("info", "inspect-url.result", {
7907
7950
  domain: project.canonicalDomain,
7908
7951
  url,
@@ -7911,49 +7954,52 @@ async function bingRoutes(app, opts) {
7911
7954
  documentSize: result.DocumentSize ?? null,
7912
7955
  lastCrawledDate: result.LastCrawledDate ?? null
7913
7956
  });
7957
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7958
+ const id = crypto15.randomUUID();
7959
+ const httpCode = result.HttpStatus ?? result.HttpCode ?? null;
7960
+ let derivedInIndex = null;
7961
+ if (result.InIndex != null) {
7962
+ derivedInIndex = result.InIndex;
7963
+ } else if (result.DocumentSize != null && result.DocumentSize > 0) {
7964
+ derivedInIndex = true;
7965
+ }
7966
+ const lastCrawledDate = parseBingDate(result.LastCrawledDate);
7967
+ const inIndexDate = parseBingDate(result.InIndexDate);
7968
+ const discoveryDate = parseBingDate(result.DiscoveryDate);
7969
+ app.db.insert(bingUrlInspections).values({
7970
+ id,
7971
+ projectId: project.id,
7972
+ url,
7973
+ httpCode,
7974
+ inIndex: derivedInIndex === true ? 1 : derivedInIndex === false ? 0 : null,
7975
+ lastCrawledDate,
7976
+ inIndexDate,
7977
+ inspectedAt: now,
7978
+ syncRunId: runId,
7979
+ createdAt: now,
7980
+ documentSize: result.DocumentSize ?? null,
7981
+ anchorCount: result.AnchorCount ?? null,
7982
+ discoveryDate
7983
+ }).run();
7984
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq15(runs.id, runId)).run();
7985
+ return {
7986
+ id,
7987
+ url,
7988
+ httpCode,
7989
+ inIndex: derivedInIndex,
7990
+ lastCrawledDate,
7991
+ inIndexDate,
7992
+ inspectedAt: now,
7993
+ documentSize: result.DocumentSize ?? null,
7994
+ anchorCount: result.AnchorCount ?? null,
7995
+ discoveryDate
7996
+ };
7914
7997
  } catch (e) {
7915
7998
  const msg = e instanceof Error ? e.message : String(e);
7916
7999
  bingLog("error", "inspect-url.failed", { domain: project.canonicalDomain, url, error: msg });
8000
+ app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq15(runs.id, runId)).run();
7917
8001
  throw e;
7918
8002
  }
7919
- const now = (/* @__PURE__ */ new Date()).toISOString();
7920
- const id = crypto15.randomUUID();
7921
- const httpCode = result.HttpStatus ?? result.HttpCode ?? null;
7922
- let derivedInIndex = null;
7923
- if (result.InIndex != null) {
7924
- derivedInIndex = result.InIndex;
7925
- } else if (result.DocumentSize != null && result.DocumentSize > 0) {
7926
- derivedInIndex = true;
7927
- }
7928
- const lastCrawledDate = parseBingDate(result.LastCrawledDate);
7929
- const inIndexDate = parseBingDate(result.InIndexDate);
7930
- const discoveryDate = parseBingDate(result.DiscoveryDate);
7931
- app.db.insert(bingUrlInspections).values({
7932
- id,
7933
- projectId: project.id,
7934
- url,
7935
- httpCode,
7936
- inIndex: derivedInIndex === true ? 1 : derivedInIndex === false ? 0 : null,
7937
- lastCrawledDate,
7938
- inIndexDate,
7939
- inspectedAt: now,
7940
- createdAt: now,
7941
- documentSize: result.DocumentSize ?? null,
7942
- anchorCount: result.AnchorCount ?? null,
7943
- discoveryDate
7944
- }).run();
7945
- return {
7946
- id,
7947
- url,
7948
- httpCode,
7949
- inIndex: derivedInIndex,
7950
- lastCrawledDate,
7951
- inIndexDate,
7952
- inspectedAt: now,
7953
- documentSize: result.DocumentSize ?? null,
7954
- anchorCount: result.AnchorCount ?? null,
7955
- discoveryDate
7956
- };
7957
8003
  });
7958
8004
  app.post("/projects/:name/bing/request-indexing", async (request, reply) => {
7959
8005
  const store = requireConnectionStore(reply);
@@ -8434,18 +8480,28 @@ async function ga4Routes(app, opts) {
8434
8480
  const syncAi = !only || only === "ai";
8435
8481
  const syncSocial = !only || only === "social";
8436
8482
  const syncSummary = !only;
8437
- const { accessToken, propertyId } = await resolveGa4AccessToken(opts, project.name, project.canonicalDomain);
8438
- let rows = [];
8439
- let summary;
8440
- let aiReferrals = [];
8441
- let socialReferrals = [];
8483
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
8484
+ const runId = crypto16.randomUUID();
8485
+ app.db.insert(runs).values({
8486
+ id: runId,
8487
+ projectId: project.id,
8488
+ kind: RunKinds["ga-sync"],
8489
+ status: RunStatuses.running,
8490
+ trigger: RunTriggers.manual,
8491
+ startedAt,
8492
+ createdAt: startedAt
8493
+ }).run();
8442
8494
  try {
8495
+ const { accessToken, propertyId } = await resolveGa4AccessToken(opts, project.name, project.canonicalDomain);
8496
+ let rows = [];
8497
+ let aiReferrals = [];
8498
+ let socialReferrals = [];
8443
8499
  const fetches = [fetchAggregateSummary(accessToken, propertyId, days)];
8444
8500
  if (syncTraffic) fetches.push(fetchTrafficByLandingPage(accessToken, propertyId, days));
8445
8501
  if (syncAi) fetches.push(fetchAiReferrals(accessToken, propertyId, days));
8446
8502
  if (syncSocial) fetches.push(fetchSocialReferrals(accessToken, propertyId, days));
8447
8503
  const results = await Promise.all(fetches);
8448
- summary = results[0];
8504
+ const summary = results[0];
8449
8505
  let idx = 1;
8450
8506
  if (syncTraffic) {
8451
8507
  rows = results[idx++];
@@ -8456,111 +8512,118 @@ async function ga4Routes(app, opts) {
8456
8512
  if (syncSocial) {
8457
8513
  socialReferrals = results[idx++];
8458
8514
  }
8459
- } catch (e) {
8460
- const msg = e instanceof Error ? e.message : String(e);
8461
- gaLog("error", "sync.fetch-failed", { projectId: project.id, error: msg });
8462
- throw e;
8463
- }
8464
- const now = (/* @__PURE__ */ new Date()).toISOString();
8465
- app.db.transaction((tx) => {
8466
- if (syncTraffic) {
8467
- tx.delete(gaTrafficSnapshots).where(
8468
- and6(
8469
- eq17(gaTrafficSnapshots.projectId, project.id),
8470
- sql3`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
8471
- sql3`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
8472
- )
8473
- ).run();
8474
- for (const row of rows) {
8475
- tx.insert(gaTrafficSnapshots).values({
8476
- id: crypto16.randomUUID(),
8477
- projectId: project.id,
8478
- date: row.date,
8479
- landingPage: row.landingPage,
8480
- sessions: row.sessions,
8481
- organicSessions: row.organicSessions,
8482
- users: row.users,
8483
- syncedAt: now
8484
- }).run();
8515
+ const now = (/* @__PURE__ */ new Date()).toISOString();
8516
+ app.db.transaction((tx) => {
8517
+ if (syncTraffic) {
8518
+ tx.delete(gaTrafficSnapshots).where(
8519
+ and6(
8520
+ eq17(gaTrafficSnapshots.projectId, project.id),
8521
+ sql3`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
8522
+ sql3`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
8523
+ )
8524
+ ).run();
8525
+ for (const row of rows) {
8526
+ tx.insert(gaTrafficSnapshots).values({
8527
+ id: crypto16.randomUUID(),
8528
+ projectId: project.id,
8529
+ date: row.date,
8530
+ landingPage: row.landingPage,
8531
+ sessions: row.sessions,
8532
+ organicSessions: row.organicSessions,
8533
+ users: row.users,
8534
+ syncedAt: now,
8535
+ syncRunId: runId
8536
+ }).run();
8537
+ }
8485
8538
  }
8486
- }
8487
- if (syncAi) {
8488
- tx.delete(gaAiReferrals).where(
8489
- and6(
8490
- eq17(gaAiReferrals.projectId, project.id),
8491
- sql3`${gaAiReferrals.date} >= ${summary.periodStart}`,
8492
- sql3`${gaAiReferrals.date} <= ${summary.periodEnd}`
8493
- )
8494
- ).run();
8495
- for (const row of aiReferrals) {
8496
- tx.insert(gaAiReferrals).values({
8497
- id: crypto16.randomUUID(),
8498
- projectId: project.id,
8499
- date: row.date,
8500
- source: row.source,
8501
- medium: row.medium,
8502
- sourceDimension: row.sourceDimension,
8503
- sessions: row.sessions,
8504
- users: row.users,
8505
- syncedAt: now
8506
- }).run();
8539
+ if (syncAi) {
8540
+ tx.delete(gaAiReferrals).where(
8541
+ and6(
8542
+ eq17(gaAiReferrals.projectId, project.id),
8543
+ sql3`${gaAiReferrals.date} >= ${summary.periodStart}`,
8544
+ sql3`${gaAiReferrals.date} <= ${summary.periodEnd}`
8545
+ )
8546
+ ).run();
8547
+ for (const row of aiReferrals) {
8548
+ tx.insert(gaAiReferrals).values({
8549
+ id: crypto16.randomUUID(),
8550
+ projectId: project.id,
8551
+ date: row.date,
8552
+ source: row.source,
8553
+ medium: row.medium,
8554
+ sourceDimension: row.sourceDimension,
8555
+ sessions: row.sessions,
8556
+ users: row.users,
8557
+ syncedAt: now,
8558
+ syncRunId: runId
8559
+ }).run();
8560
+ }
8507
8561
  }
8508
- }
8509
- if (syncSocial) {
8510
- tx.delete(gaSocialReferrals).where(
8511
- and6(
8512
- eq17(gaSocialReferrals.projectId, project.id),
8513
- sql3`${gaSocialReferrals.date} >= ${summary.periodStart}`,
8514
- sql3`${gaSocialReferrals.date} <= ${summary.periodEnd}`
8515
- )
8516
- ).run();
8517
- for (const row of socialReferrals) {
8518
- tx.insert(gaSocialReferrals).values({
8562
+ if (syncSocial) {
8563
+ tx.delete(gaSocialReferrals).where(
8564
+ and6(
8565
+ eq17(gaSocialReferrals.projectId, project.id),
8566
+ sql3`${gaSocialReferrals.date} >= ${summary.periodStart}`,
8567
+ sql3`${gaSocialReferrals.date} <= ${summary.periodEnd}`
8568
+ )
8569
+ ).run();
8570
+ for (const row of socialReferrals) {
8571
+ tx.insert(gaSocialReferrals).values({
8572
+ id: crypto16.randomUUID(),
8573
+ projectId: project.id,
8574
+ date: row.date,
8575
+ source: row.source,
8576
+ medium: row.medium,
8577
+ channelGroup: row.channelGroup,
8578
+ sessions: row.sessions,
8579
+ users: row.users,
8580
+ syncedAt: now,
8581
+ syncRunId: runId
8582
+ }).run();
8583
+ }
8584
+ }
8585
+ if (syncSummary) {
8586
+ tx.delete(gaTrafficSummaries).where(eq17(gaTrafficSummaries.projectId, project.id)).run();
8587
+ tx.insert(gaTrafficSummaries).values({
8519
8588
  id: crypto16.randomUUID(),
8520
8589
  projectId: project.id,
8521
- date: row.date,
8522
- source: row.source,
8523
- medium: row.medium,
8524
- channelGroup: row.channelGroup,
8525
- sessions: row.sessions,
8526
- users: row.users,
8527
- syncedAt: now
8590
+ periodStart: summary.periodStart,
8591
+ periodEnd: summary.periodEnd,
8592
+ totalSessions: summary.totalSessions,
8593
+ totalOrganicSessions: summary.totalOrganicSessions,
8594
+ totalUsers: summary.totalUsers,
8595
+ syncedAt: now,
8596
+ syncRunId: runId
8528
8597
  }).run();
8529
8598
  }
8530
- }
8531
- if (syncSummary) {
8532
- tx.delete(gaTrafficSummaries).where(eq17(gaTrafficSummaries.projectId, project.id)).run();
8533
- tx.insert(gaTrafficSummaries).values({
8534
- id: crypto16.randomUUID(),
8535
- projectId: project.id,
8536
- periodStart: summary.periodStart,
8537
- periodEnd: summary.periodEnd,
8538
- totalSessions: summary.totalSessions,
8539
- totalOrganicSessions: summary.totalOrganicSessions,
8540
- totalUsers: summary.totalUsers,
8541
- syncedAt: now
8542
- }).run();
8543
- }
8544
- });
8545
- const syncedComponents = only ? [only, ...only !== "social" && only !== "ai" && only !== "traffic" ? [] : []] : void 0;
8546
- gaLog("info", "sync.complete", {
8547
- projectId: project.id,
8548
- rowCount: rows.length,
8549
- aiReferralCount: aiReferrals.length,
8550
- socialReferralCount: socialReferrals.length,
8551
- days,
8552
- totalUsers: summary.totalUsers,
8553
- ...only ? { only } : {}
8554
- });
8555
- return {
8556
- synced: true,
8557
- rowCount: rows.length,
8558
- aiReferralCount: aiReferrals.length,
8559
- socialReferralCount: socialReferrals.length,
8560
- days,
8561
- syncedAt: now,
8562
- ...syncedComponents ? { syncedComponents } : {}
8563
- };
8599
+ });
8600
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq17(runs.id, runId)).run();
8601
+ const syncedComponents = only ? [only, ...only !== "social" && only !== "ai" && only !== "traffic" ? [] : []] : void 0;
8602
+ gaLog("info", "sync.complete", {
8603
+ projectId: project.id,
8604
+ runId,
8605
+ rowCount: rows.length,
8606
+ aiReferralCount: aiReferrals.length,
8607
+ socialReferralCount: socialReferrals.length,
8608
+ days,
8609
+ totalUsers: summary.totalUsers,
8610
+ ...only ? { only } : {}
8611
+ });
8612
+ return {
8613
+ synced: true,
8614
+ rowCount: rows.length,
8615
+ aiReferralCount: aiReferrals.length,
8616
+ socialReferralCount: socialReferrals.length,
8617
+ days,
8618
+ syncedAt: now,
8619
+ ...syncedComponents ? { syncedComponents } : {}
8620
+ };
8621
+ } catch (e) {
8622
+ const msg = e instanceof Error ? e.message : String(e);
8623
+ gaLog("error", "sync.fetch-failed", { projectId: project.id, runId, error: msg });
8624
+ app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq17(runs.id, runId)).run();
8625
+ throw e;
8626
+ }
8564
8627
  });
8565
8628
  app.get("/projects/:name/ga/traffic", async (request, _reply) => {
8566
8629
  const project = resolveProject(app.db, request.params.name);
@@ -9049,7 +9112,8 @@ async function fetchJson(connection, siteUrl, path7, init) {
9049
9112
  });
9050
9113
  if (res.status === 401 || res.status === 403) {
9051
9114
  const text = await res.text().catch(() => "");
9052
- throw new WordpressApiError("AUTH_INVALID", buildAuthErrorMessage(res, text), res.status);
9115
+ const errorMessage = buildAuthErrorMessage(res, text);
9116
+ throw new WordpressApiError("AUTH_INVALID", errorMessage, res.status);
9053
9117
  }
9054
9118
  if (res.status === 404) {
9055
9119
  throw new WordpressApiError("NOT_FOUND", "WordPress endpoint not found", 404);
@@ -12250,56 +12314,63 @@ var chatgptTarget = {
12250
12314
  }
12251
12315
  return text;
12252
12316
  },
12253
- async extractCitations(client) {
12254
- const { result } = await client.Runtime.evaluate({
12255
- expression: `(() => {
12256
- const sources = [];
12257
- // ChatGPT shows citation pills as links within the response
12258
- // Multiple possible structures:
12259
- // 1. Inline citation links [1], [2] etc.
12260
- // 2. Source pills at the bottom of the response
12261
- // 3. "Sources" section with expandable links
12317
+ extractCitations(client) {
12318
+ return (async () => {
12319
+ const { result } = await client.Runtime.evaluate({
12320
+ expression: `(() => {
12321
+ const sources = [];
12322
+ const seen = new Set();
12323
+ const turns = document.querySelectorAll('[data-message-author-role="assistant"]');
12324
+ const last = turns.length ? turns[turns.length - 1] : null;
12325
+ if (!last) return [];
12262
12326
 
12263
- // Collect external links from the last assistant message.
12264
- // :last-of-type does not work with attribute selectors (it selects
12265
- // by tag type), so we use JS to grab the actual last element.
12266
- const seen = new Set();
12267
- const turns = document.querySelectorAll('[data-message-author-role="assistant"]');
12268
- const last = turns.length ? turns[turns.length - 1] : null;
12269
- const links = last ? last.querySelectorAll('a[href]') : [];
12270
- for (const link of links) {
12271
- const href = link.getAttribute('href');
12272
- // ChatGPT appends ?utm_source=chatgpt.com to external links,
12273
- // so string-includes would false-positive on every citation.
12274
- // Parse the URL and compare the hostname instead.
12275
- if (!href) continue;
12276
- let hostname = '';
12277
- try { hostname = new URL(href).hostname.replace(/^www\\./, ''); } catch {}
12278
- if (!seen.has(href) && hostname !== 'chatgpt.com' && hostname !== 'openai.com') {
12279
- seen.add(href);
12280
- sources.push({
12281
- uri: href,
12282
- title: hostname || href,
12283
- });
12327
+ const links = last.querySelectorAll('a[href]');
12328
+ for (const link of links) {
12329
+ const href = link.getAttribute('href');
12330
+ if (!href) continue;
12331
+ let hostname = '';
12332
+ try {
12333
+ const url = new URL(href);
12334
+ hostname = url.hostname.replace(/^www\\./, '');
12335
+ } catch {
12336
+ // Check for relative links or weird protocols
12337
+ if (href.startsWith('/') || href.startsWith('http')) {
12338
+ hostname = href;
12339
+ } else {
12340
+ continue;
12341
+ }
12342
+ }
12343
+
12344
+ if (!seen.has(href) && hostname !== 'chatgpt.com' && hostname !== 'openai.com') {
12345
+ seen.add(href);
12346
+ sources.push({
12347
+ uri: href,
12348
+ title: hostname || href,
12349
+ });
12350
+ }
12284
12351
  }
12285
- }
12286
12352
 
12287
- // Also check for citation superscripts that may reference sources
12288
- const citeButtons = document.querySelectorAll('[data-testid="citation-button"], .citation-button');
12289
- for (const btn of citeButtons) {
12290
- const href = btn.getAttribute('data-href') || btn.closest('a')?.getAttribute('href');
12291
- const title = btn.getAttribute('data-title') || btn.textContent?.trim();
12292
- if (href && !seen.has(href)) {
12293
- seen.add(href);
12294
- sources.push({ uri: href, title: title || href });
12353
+ // Also check for citation superscripts that may reference sources
12354
+ const citeButtons = last.querySelectorAll('[data-testid="citation-button"], .citation-button');
12355
+ for (const btn of citeButtons) {
12356
+ const href = btn.getAttribute('data-href') || btn.closest('a')?.getAttribute('href');
12357
+ const title = btn.getAttribute('data-title') || btn.textContent?.trim();
12358
+ if (href && !seen.has(href)) {
12359
+ let hostname = '';
12360
+ try { hostname = new URL(href).hostname.replace(/^www\\./, ''); } catch {}
12361
+ if (hostname !== 'chatgpt.com' && hostname !== 'openai.com') {
12362
+ seen.add(href);
12363
+ sources.push({ uri: href, title: title || hostname || href });
12364
+ }
12365
+ }
12295
12366
  }
12296
- }
12297
12367
 
12298
- return sources;
12299
- })()`,
12300
- returnByValue: true
12301
- });
12302
- return result.value ?? [];
12368
+ return sources;
12369
+ })()`,
12370
+ returnByValue: true
12371
+ });
12372
+ return result.value ?? [];
12373
+ })();
12303
12374
  }
12304
12375
  };
12305
12376
  async function waitForElement(client, selector, timeoutMs) {
@@ -12460,10 +12531,11 @@ var cdpChatgptAdapter = {
12460
12531
  const conn = getConnection(config);
12461
12532
  const health = await conn.healthcheck();
12462
12533
  if (!health.connected) {
12534
+ const port = config.cdpEndpoint?.split(":").pop() || "9222";
12463
12535
  return {
12464
12536
  ok: false,
12465
12537
  provider: "cdp:chatgpt",
12466
- message: `Chrome not reachable at ${config.cdpEndpoint}`
12538
+ message: `Chrome not reachable at ${config.cdpEndpoint}. Ensure Chrome is running with --remote-debugging-port=${port}`
12467
12539
  };
12468
12540
  }
12469
12541
  return {