@canonry/canonry 7.17.1 → 7.18.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.
Files changed (36) hide show
  1. package/assets/agent-workspace/skills/aero/references/portfolio-analysis.md +8 -6
  2. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +1 -1
  3. package/assets/assets/{AuditHistoryPanel-CzjskfMX.js → AuditHistoryPanel-CgKpjgpq.js} +1 -1
  4. package/assets/assets/BacklinksPage-BBqbRSeG.js +1 -0
  5. package/assets/assets/Hint-DICI5Jq9.js +1 -0
  6. package/assets/assets/{HistoryPage-BZgVoYGm.js → HistoryPage-D8nQ8E8_.js} +1 -1
  7. package/assets/assets/{MeasurementPropertyPage-EBI5sn-D.js → MeasurementPropertyPage-CQ8lLoeA.js} +1 -1
  8. package/assets/assets/{ProjectPage-lGGjM_qj.js → ProjectPage-CP4fvluh.js} +6 -6
  9. package/assets/assets/{RunsPage-CuHi8241.js → RunsPage-D0qkTwas.js} +1 -1
  10. package/assets/assets/{SettingsPage-D0fgAoVu.js → SettingsPage-DaqR1wKC.js} +1 -1
  11. package/assets/assets/SiteHealthSection-dGXxZ8IR.js +4 -0
  12. package/assets/assets/{TrafficPage-DD65RqeR.js → TrafficPage-DodOmnVv.js} +1 -1
  13. package/assets/assets/{TrafficSourceDetailPage-CFVCj13J.js → TrafficSourceDetailPage-C7b-BcKF.js} +1 -1
  14. package/assets/assets/{extract-error-message-D118msoE.js → extract-error-message-BYvzTsML.js} +1 -1
  15. package/assets/assets/index-BVzozur0.js +86 -0
  16. package/assets/assets/{react-sigma_core.esm.min-DdlHLMEz.js → react-sigma_core.esm.min-DXcq1O0r.js} +1 -1
  17. package/assets/assets/{v2-overview-adapter-CoCb2aB0.js → v2-overview-adapter-1MHnrdnr.js} +1 -1
  18. package/assets/assets/{vendor-lucide-N6vOpZmh.js → vendor-lucide-Y6jANGfG.js} +1 -1
  19. package/assets/index.html +2 -2
  20. package/dist/{chunk-ORA5R7QM.js → chunk-5M2RY5WU.js} +132 -104
  21. package/dist/{chunk-ZQ7C6IFA.js → chunk-COQOQYRZ.js} +749 -614
  22. package/dist/{chunk-FK7AWV57.js → chunk-EAAM47H6.js} +24 -9
  23. package/dist/{chunk-PLS4EM2Y.js → chunk-JMC7CEJB.js} +4 -4
  24. package/dist/{chunk-OPJCFDNR.js → chunk-JUOFNJLV.js} +3 -3
  25. package/dist/{chunk-C6GWKYGE.js → chunk-U7D7LNVJ.js} +5 -4
  26. package/dist/{chunk-MHB23E3N.js → chunk-X2S7D3MN.js} +123 -82
  27. package/dist/cli.js +30 -22
  28. package/dist/{demo-server-WQDHQG6P.js → demo-server-XWRIXME5.js} +6 -5
  29. package/dist/index.d.ts +7 -5
  30. package/dist/index.js +6 -6
  31. package/dist/{intelligence-service-JWZRZ6JZ.js → intelligence-service-332S7D7Y.js} +2 -2
  32. package/dist/mcp.js +4 -4
  33. package/package.json +12 -12
  34. package/assets/assets/BacklinksPage-ToENGBdp.js +0 -1
  35. package/assets/assets/SiteHealthSection-DhLxldgs.js +0 -4
  36. package/assets/assets/index-DxFd1t4G.js +0 -86
package/dist/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import {
3
3
  AERO_TOOL_PROFILES,
4
4
  OPENAI_RETRIEVAL_CONTRACT,
5
+ allowsKeylessFirstRunSetup,
5
6
  checkLatestVersionForCli,
6
7
  cliRuntimeContext,
7
8
  coerceAgentProvider,
@@ -33,11 +34,11 @@ import {
33
34
  trackCliCommandFinished,
34
35
  trackEvent,
35
36
  waitForServerRuntimeStartup
36
- } from "./chunk-ORA5R7QM.js";
37
+ } from "./chunk-5M2RY5WU.js";
37
38
  import {
38
39
  autoSyncSkills,
39
40
  formatAutoSyncNotice
40
- } from "./chunk-OPJCFDNR.js";
41
+ } from "./chunk-JUOFNJLV.js";
41
42
  import {
42
43
  configExists,
43
44
  createApiClient,
@@ -54,13 +55,13 @@ import {
54
55
  saveConfig,
55
56
  saveConfigPatch,
56
57
  shouldPrintServeSkillsNudge
57
- } from "./chunk-PLS4EM2Y.js";
58
+ } from "./chunk-JMC7CEJB.js";
58
59
  import {
59
60
  DEFAULT_DEMO_TRUSTED_PROXIES,
60
61
  trustedProxiesProblem,
61
62
  trustedProxyProblem
62
63
  } from "./chunk-RB2F4D3Z.js";
63
- import "./chunk-C6GWKYGE.js";
64
+ import "./chunk-U7D7LNVJ.js";
64
65
  import {
65
66
  CliError,
66
67
  EXIT_SYSTEM_ERROR,
@@ -72,7 +73,7 @@ import {
72
73
  runMeasurementDraftAction,
73
74
  systemError,
74
75
  usageError
75
- } from "./chunk-FK7AWV57.js";
76
+ } from "./chunk-EAAM47H6.js";
76
77
  import {
77
78
  CLOUDFLARE_WORKER_BINDINGS,
78
79
  CLOUDFLARE_WORKER_GENERATED_MARKER,
@@ -104,7 +105,7 @@ import {
104
105
  rawEventSamples,
105
106
  readMarketCompetitorNames,
106
107
  runs
107
- } from "./chunk-ZQ7C6IFA.js";
108
+ } from "./chunk-COQOQYRZ.js";
108
109
  import {
109
110
  AdsDeliverySnapshotStatuses,
110
111
  AdsHistoricalCampaignRollupStatuses,
@@ -237,7 +238,7 @@ import {
237
238
  visibilityCompareSelectionSchema,
238
239
  visibilityReportRequestSchema,
239
240
  winnabilityClassSchema
240
- } from "./chunk-MHB23E3N.js";
241
+ } from "./chunk-X2S7D3MN.js";
241
242
 
242
243
  // src/cli.ts
243
244
  import { pathToFileURL } from "url";
@@ -787,7 +788,7 @@ function parseDemoTrustedProxies(values) {
787
788
  async function demoCommand(options) {
788
789
  const { host, port } = parseDemoListenOptions(options);
789
790
  const trustProxy = parseDemoTrustedProxies(options.trustProxy);
790
- const { createDemoServer } = await import("./demo-server-WQDHQG6P.js");
791
+ const { createDemoServer } = await import("./demo-server-XWRIXME5.js");
791
792
  const app = await createDemoServer({ trustProxy });
792
793
  let stopping = false;
793
794
  const stop = () => {
@@ -1201,7 +1202,7 @@ No DB writes performed. Re-run without --dry-run to apply.`);
1201
1202
  }
1202
1203
  }
1203
1204
  async function backfillInsightsCommand(project2, opts) {
1204
- const { IntelligenceService } = await import("./intelligence-service-JWZRZ6JZ.js");
1205
+ const { IntelligenceService } = await import("./intelligence-service-332S7D7Y.js");
1205
1206
  const config = loadConfig();
1206
1207
  const db = createClient(config.database);
1207
1208
  migrate(db);
@@ -12246,8 +12247,18 @@ async function discoverMeasurementTargets(project2, sitemapUrl, ruleSource, maxU
12246
12247
  });
12247
12248
  console.log(JSON.stringify(await createApiClient().discoverMeasurementTargets(project2, request), null, 2));
12248
12249
  }
12249
- async function showMeasurementReport(project2, revision) {
12250
- console.log(JSON.stringify(await createApiClient().getMeasurementReport(project2, revision), null, 2));
12250
+ async function showMeasurementReport(project2, revision, queryClass) {
12251
+ console.log(JSON.stringify(await createApiClient().getMeasurementReport(project2, revision, void 0, queryClass), null, 2));
12252
+ }
12253
+ function queryClassText(queryClass) {
12254
+ switch (queryClass) {
12255
+ case "non-brand":
12256
+ return "non-brand queries";
12257
+ case "branded":
12258
+ return "branded queries";
12259
+ case "all":
12260
+ return "branded and non-brand queries pooled";
12261
+ }
12251
12262
  }
12252
12263
  var METRIC_REASONS = {
12253
12264
  plan_v1: "not measured (setup update required)",
@@ -12289,7 +12300,7 @@ async function showMeasurementProperty(project2, opts) {
12289
12300
  function printMeasurementProperty(response) {
12290
12301
  const row = response.properties.items.at(0);
12291
12302
  const lines = [];
12292
- lines.push(`${response.scope.label} \u2014 ${response.queryClass} questions`);
12303
+ lines.push(`${response.scope.label} \xB7 ${queryClassText(response.queryClass)}`);
12293
12304
  lines.push(`Measurement: ${response.measurement.state}${response.measurement.displayedRunId ? ` \xB7 run ${response.measurement.displayedRunId}` : ""}`);
12294
12305
  lines.push("");
12295
12306
  const mention = row ? row.mentionCoverage : response.metrics.mentionCoverage;
@@ -17983,7 +17994,7 @@ function buildNextSteps() {
17983
17994
  "",
17984
17995
  " Map your public site and capture a persisted Page Health baseline.",
17985
17996
  " AI Visibility is optional and can be configured after Page Health.",
17986
- " For remote/exposed hosts, complete dashboard password setup from loopback first.",
17997
+ " On a remote or proxied host, dashboard password setup asks for the root API key (apiKey in config.yaml).",
17987
17998
  "",
17988
17999
  "Prefer the terminal? The same flow as CLI commands:",
17989
18000
  "",
@@ -18239,9 +18250,6 @@ function detectCanonryAgentPlugin(opts = {}) {
18239
18250
  }
18240
18251
 
18241
18252
  // src/commands/serve.ts
18242
- function shouldWarnAboutRemoteSetup(host) {
18243
- return !isLoopbackBindHost(host);
18244
- }
18245
18253
  function readServeOpenState(db) {
18246
18254
  try {
18247
18255
  const rows = db.select({
@@ -18345,8 +18353,8 @@ Received ${signal}, stopping server...`);
18345
18353
  console.log(`
18346
18354
  Canonry server running at ${url}`);
18347
18355
  console.log(buildServeOpenLine({ url, ...readServeOpenState(db) }));
18348
- if (shouldWarnAboutRemoteSetup(host)) {
18349
- console.log("First-run dashboard password setup is unauthenticated only on loopback; complete setup from this machine first or use a bearer cnry_... key.");
18356
+ if (!allowsKeylessFirstRunSetup(app)) {
18357
+ console.log(isLoopbackBindHost(host) ? "This server is configured to be reached through a proxy or external URL (publicUrl, apiUrl, basePath, or CANONRY_TRUST_PROXY), so first-run dashboard password setup requires the root API key (apiKey in config.yaml)." : "This server is not bound to loopback, so first-run dashboard password setup requires the root API key (apiKey in config.yaml).");
18350
18358
  }
18351
18359
  console.log("Press Ctrl+C to stop.\n");
18352
18360
  const nudge = getMissingUserSkillsNudge(process.env.HOME, getAgentPluginState());
@@ -21947,8 +21955,8 @@ Usage: ${usage}`);
21947
21955
  },
21948
21956
  {
21949
21957
  path: ["measurement-plan", "report"],
21950
- usage: "canonry measurement-plan report <project> --revision N [--format json]",
21951
- options: { revision: stringOption() },
21958
+ usage: "canonry measurement-plan report <project> --revision N [--query-class non-brand|branded|all] [--format json] (schema v2 revision: default non-brand, all pools both classes; schema v1 revision: reads every answer, refuses non-brand and branded)",
21959
+ options: { revision: stringOption(), "query-class": stringOption() },
21952
21960
  run: (input) => {
21953
21961
  const project2 = requireProject(input, "measurement-plan.report", "canonry measurement-plan report <project> --revision N");
21954
21962
  const value = getString(input.values, "revision");
@@ -21956,12 +21964,12 @@ Usage: ${usage}`);
21956
21964
  if (revision === void 0 || !Number.isInteger(revision) || revision <= 0) {
21957
21965
  throw usageError("--revision must be a positive integer");
21958
21966
  }
21959
- return showMeasurementReport(project2, revision);
21967
+ return showMeasurementReport(project2, revision, queryClassOption(input));
21960
21968
  }
21961
21969
  },
21962
21970
  {
21963
21971
  path: ["measurement-plan", "property"],
21964
- usage: "canonry measurement-plan property <project> --target-key <key> [--query-class all|branded|non-brand] [--provider <p>] [--location <l>] [--run-id <id>] [--format json]",
21972
+ usage: "canonry measurement-plan property <project> --target-key <key> [--query-class non-brand|branded|all] [--provider <p>] [--location <l>] [--run-id <id>] [--format json] (default non-brand; all pools both classes)",
21965
21973
  options: PROPERTY_SCOPE_OPTIONS,
21966
21974
  run: (input) => {
21967
21975
  const usage = "canonry measurement-plan property <project> --target-key <key>";
@@ -5,11 +5,11 @@ import {
5
5
  computeFactorAverages,
6
6
  layoutSiteCrawlGraphInput,
7
7
  persistSiteCrawlGraphLayout
8
- } from "./chunk-C6GWKYGE.js";
8
+ } from "./chunk-U7D7LNVJ.js";
9
9
  import {
10
10
  PACKAGE_VERSION,
11
11
  canonryMcpTools
12
- } from "./chunk-FK7AWV57.js";
12
+ } from "./chunk-EAAM47H6.js";
13
13
  import {
14
14
  HISTORY_WINDOW_RUNS,
15
15
  IntelligenceService,
@@ -85,7 +85,7 @@ import {
85
85
  siteCrawlPages,
86
86
  siteCrawlSnapshots,
87
87
  trafficSources
88
- } from "./chunk-ZQ7C6IFA.js";
88
+ } from "./chunk-COQOQYRZ.js";
89
89
  import {
90
90
  RunKinds,
91
91
  SiteCrawlFetchStates,
@@ -98,6 +98,7 @@ import {
98
98
  canonicalMeasurementExecutionIdentityJson,
99
99
  canonicalMeasurementPlanV2Json,
100
100
  canonicalSimpleMeasurementDefinitionJson,
101
+ compareText,
101
102
  computeCompetitorOverlap,
102
103
  deriveSiteHealthState,
103
104
  determineAnswerMentioned,
@@ -114,7 +115,7 @@ import {
114
115
  roundPreservingTotal,
115
116
  siteAuditPageFactorSchema,
116
117
  wilsonInterval
117
- } from "./chunk-MHB23E3N.js";
118
+ } from "./chunk-X2S7D3MN.js";
118
119
 
119
120
  // src/demo-server.ts
120
121
  import { dirname, join as join2 } from "path";
@@ -273,7 +274,7 @@ function engineOrder(left, right) {
273
274
  const index = ENGINE_ORDER.indexOf(provider);
274
275
  return index === -1 ? ENGINE_ORDER.length : index;
275
276
  };
276
- return rank(left) - rank(right) || (left < right ? -1 : left > right ? 1 : 0);
277
+ return rank(left) - rank(right) || compareText(left, right);
277
278
  }
278
279
  function tally(answers) {
279
280
  return {
package/dist/index.d.ts CHANGED
@@ -462,11 +462,13 @@ declare function createServer(opts: {
462
462
  logger?: boolean;
463
463
  /**
464
464
  * The network interface the server will bind to (from `canonry serve`).
465
- * Used to gate the unauthenticated first-run dashboard password setup: on a
466
- * loopback bind only local processes can reach `/session/setup`, so claiming
467
- * the initial password without the API key is safe. On a non-loopback bind
468
- * (`0.0.0.0`, a LAN IP) the setup endpoint additionally requires a valid
469
- * bearer key so a remote first-visitor cannot mint a full-access session.
465
+ * Used to gate the first-run dashboard password setup: only on a loopback
466
+ * bind, and only while config names no other way in
467
+ * (`configuresExternalAccess`), can a direct local request (see
468
+ * `isDirectLocalRequest`) claim the initial password without the root API
469
+ * key. On a non-loopback bind (`0.0.0.0`, a LAN IP) every setup request must
470
+ * present the root key, so a remote first visitor cannot mint a standing
471
+ * full-access credential.
470
472
  * Defaults to loopback when unset (programmatic/test callers).
471
473
  */
472
474
  host?: string;
package/dist/index.js CHANGED
@@ -3,14 +3,14 @@ import {
3
3
  createGoogleMarketingCredentialStore,
4
4
  createGoogleMarketingRuntime,
5
5
  createServer
6
- } from "./chunk-ORA5R7QM.js";
6
+ } from "./chunk-5M2RY5WU.js";
7
7
  import {
8
8
  loadConfig
9
- } from "./chunk-PLS4EM2Y.js";
10
- import "./chunk-C6GWKYGE.js";
11
- import "./chunk-FK7AWV57.js";
12
- import "./chunk-ZQ7C6IFA.js";
13
- import "./chunk-MHB23E3N.js";
9
+ } from "./chunk-JMC7CEJB.js";
10
+ import "./chunk-U7D7LNVJ.js";
11
+ import "./chunk-EAAM47H6.js";
12
+ import "./chunk-COQOQYRZ.js";
13
+ import "./chunk-X2S7D3MN.js";
14
14
  export {
15
15
  GoogleMarketingRuntimeError,
16
16
  createGoogleMarketingCredentialStore,
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  HISTORY_WINDOW_RUNS,
3
3
  IntelligenceService
4
- } from "./chunk-ZQ7C6IFA.js";
5
- import "./chunk-MHB23E3N.js";
4
+ } from "./chunk-COQOQYRZ.js";
5
+ import "./chunk-X2S7D3MN.js";
6
6
  export {
7
7
  HISTORY_WINDOW_RUNS,
8
8
  IntelligenceService
package/dist/mcp.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  autoSyncSkills
3
- } from "./chunk-OPJCFDNR.js";
3
+ } from "./chunk-JUOFNJLV.js";
4
4
  import {
5
5
  createApiClient,
6
6
  createCanonryMcpServer
7
- } from "./chunk-PLS4EM2Y.js";
8
- import "./chunk-FK7AWV57.js";
7
+ } from "./chunk-JMC7CEJB.js";
8
+ import "./chunk-EAAM47H6.js";
9
9
  import {
10
10
  isReadOnlyKey,
11
11
  updateCheckEnvOptOut
12
- } from "./chunk-MHB23E3N.js";
12
+ } from "./chunk-X2S7D3MN.js";
13
13
 
14
14
  // src/mcp/cli.ts
15
15
  import { randomUUID } from "crypto";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonry/canonry",
3
- "version": "7.17.1",
3
+ "version": "7.18.0",
4
4
  "type": "module",
5
5
  "description": "Self-hosted AI visibility (AEO) platform: track how ChatGPT, Claude, Gemini, and Perplexity cite your domain, join it with Search Console, GA4, server-side traffic, and paid media, and fix what you find through agent tools (CLI, REST, MCP). Local SQLite.",
6
6
  "keywords": [
@@ -86,31 +86,31 @@
86
86
  "tsup": "^8.5.1",
87
87
  "tsx": "^4.19.0",
88
88
  "@ainyc/canonry-web": "0.0.0",
89
+ "@ainyc/canonry-api-client": "0.0.0",
89
90
  "@ainyc/canonry-api-routes": "0.0.0",
90
91
  "@ainyc/canonry-config": "0.0.0",
91
- "@ainyc/canonry-contracts": "0.0.0",
92
- "@ainyc/canonry-api-client": "0.0.0",
93
92
  "@ainyc/canonry-db": "0.0.0",
93
+ "@ainyc/canonry-contracts": "0.0.0",
94
94
  "@ainyc/canonry-integration-bing": "0.0.0",
95
+ "@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
96
+ "@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
95
97
  "@ainyc/canonry-integration-cloud-run": "0.0.0",
96
98
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
97
- "@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
98
- "@ainyc/canonry-integration-google": "0.0.0",
99
99
  "@ainyc/canonry-integration-google-ads": "0.0.0",
100
- "@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
101
- "@ainyc/canonry-integration-google-places": "0.0.0",
102
100
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
101
+ "@ainyc/canonry-integration-google-places": "0.0.0",
102
+ "@ainyc/canonry-integration-google": "0.0.0",
103
103
  "@ainyc/canonry-integration-google-tag-manager": "0.0.0",
104
104
  "@ainyc/canonry-integration-openai-ads": "0.0.0",
105
105
  "@ainyc/canonry-integration-traffic": "0.0.0",
106
- "@ainyc/canonry-intelligence": "0.0.0",
107
- "@ainyc/canonry-provider-cdp": "0.0.0",
108
106
  "@ainyc/canonry-integration-wordpress": "0.0.0",
107
+ "@ainyc/canonry-provider-cdp": "0.0.0",
109
108
  "@ainyc/canonry-provider-claude": "0.0.0",
110
- "@ainyc/canonry-provider-muse": "0.0.0",
111
- "@ainyc/canonry-provider-gemini": "0.0.0",
112
- "@ainyc/canonry-provider-openai": "0.0.0",
109
+ "@ainyc/canonry-intelligence": "0.0.0",
113
110
  "@ainyc/canonry-provider-local": "0.0.0",
111
+ "@ainyc/canonry-provider-openai": "0.0.0",
112
+ "@ainyc/canonry-provider-gemini": "0.0.0",
113
+ "@ainyc/canonry-provider-muse": "0.0.0",
114
114
  "@ainyc/canonry-provider-perplexity": "0.0.0",
115
115
  "@ainyc/canonry-integration-typesafe": "0.0.0"
116
116
  },
@@ -1 +0,0 @@
1
- import{r as n,j as e}from"./vendor-tanstack-ZXXkndVj.js";import{dY as W,aF as H,dZ as U,d_ as Y,d$ as J,v as x,T as j,y as u,B as w,D as $,e0 as K,e1 as V,aI as Z,e2 as X}from"./index-DxFd1t4G.js";import{I as ee,T as se,E as te,j as ae,O as ne,J as re}from"./vendor-lucide-N6vOpZmh.js";import"./vendor-radix-twS1M3FF.js";import"./vendor-recharts-D-cUuLZo.js";import"./vendor-markdown-B4giJcws.js";import"./vendor-yaml-GWJz5H2G.js";function g({children:t,label:h="More info",placement:a="top",className:i}){const d=n.useId(),[y,c]=n.useState(!1);return e.jsxs("span",{className:`relative inline-flex ${i??""}`,children:[e.jsx("button",{type:"button","aria-label":h,"aria-describedby":y?d:void 0,className:"inline-flex h-4 w-4 items-center justify-center rounded-full text-muted hover:text-strong focus:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500",onMouseEnter:()=>c(!0),onMouseLeave:()=>c(!1),onFocus:()=>c(!0),onBlur:()=>c(!1),children:e.jsx(re,{className:"h-3.5 w-3.5","aria-hidden":!0})}),y&&e.jsx("span",{id:d,role:"tooltip",className:`absolute z-50 w-64 rounded border border-strong bg-bg-elevated px-3 py-2 text-xs font-normal leading-relaxed text-strong shadow-lg ${a==="top"?"bottom-full mb-2":"top-full mt-2"} left-1/2 -translate-x-1/2 whitespace-normal`,children:t})]})}const le="https://commoncrawl.org/web-graphs";function S(t){return t==null?"—":t>=1e12?`${(t/1e12).toFixed(1)} TB`:t>=1e9?`${(t/1e9).toFixed(1)} GB`:t>=1e6?`${(t/1e6).toFixed(1)} MB`:t>=1e3?`${(t/1e3).toFixed(1)} KB`:`${t} B`}function b(t){if(!t)return"—";const h=Date.now()-new Date(t).getTime(),a=Math.floor(h/6e4);if(a<1)return"just now";if(a<60)return`${a}m ago`;const i=Math.floor(a/60);return i<24?`${i}h ago`:`${Math.floor(i/24)}d ago`}function C(t){switch(t){case"ready":return"positive";case"failed":return"negative";case"downloading":case"querying":case"queued":return"caution"}}function me(){const[t,h]=n.useState(null),[a,i]=n.useState(null),[d,y]=n.useState([]),[c,P]=n.useState([]),[m,L]=n.useState(null),[B,D]=n.useState(!0),[R,I]=n.useState(!1),[p,E]=n.useState(!1),[N,v]=n.useState(""),[M,k]=n.useState(!1),[A,l]=n.useState(null),[F,o]=n.useState(null),f=n.useCallback(async()=>{D(!0),l(null);try{const[s,r,Q,_,z]=await Promise.all([W(),H().catch(()=>null),U().catch(()=>[]),Y().catch(()=>[]),J().catch(()=>null)]);h(s),i(r),y(Q),P(_),L(z)}catch(s){l(s instanceof Error?s.message:"Failed to load backlinks status")}finally{D(!1)}},[]);n.useEffect(()=>{f()},[f]);async function T(){I(!0),l(null),o(null);try{const s=await K();o(s.alreadyPresent?`DuckDB already installed (${s.version}).`:`Installed DuckDB ${s.version}.`),await f()}catch(s){l(s instanceof Error?s.message:"Failed to install DuckDB")}finally{I(!1)}}async function q(){const s=N.trim()||void 0;E(!0),l(null),o(null);try{const r=await V(s);o(s?`Queued sync for ${r.release}. Download + query runs in the background.`:`Queued sync for auto-discovered release ${r.release}. Download + query runs in the background.`),v(""),k(!1),await f()}catch(r){r instanceof Z&&r.code==="MISSING_DEPENDENCY"?l("DuckDB is not installed. Install it first."):l(r instanceof Error?r.message:"Failed to trigger sync")}finally{E(!1)}}async function G(s){l(null),o(null);try{await X(s),o(`Pruned cached release ${s}.`),await f()}catch(r){l(r instanceof Error?r.message:"Failed to prune release")}}const O=a?.status==="ready"&&c.every(s=>s.release!==a.release);return e.jsxs("div",{className:"page-container",children:[e.jsx("div",{className:"page-header",children:e.jsxs("div",{className:"page-header-left",children:[e.jsx("h1",{className:"page-title",children:"Backlink data"}),e.jsx("p",{className:"page-subtitle",children:"Manage the shared backlink source and review its latest data."})]})}),A&&e.jsx(x,{className:"surface-card p-4 mb-4 border-negative-800/60",children:e.jsx("p",{className:"text-sm text-negative",children:A})}),F&&e.jsx(x,{className:"surface-card p-4 mb-4 border-positive-800/60",children:e.jsx("p",{className:"text-sm text-positive",children:F})}),e.jsxs("section",{className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Readiness"}),e.jsx("h2",{children:"Backlink source"})]}),t?.duckdbInstalled?e.jsx(j,{tone:"positive",children:"Installed"}):e.jsx(j,{tone:"caution",children:"Not installed"})]}),e.jsx(x,{className:"surface-card p-4",children:B?e.jsx("p",{className:"text-sm text-muted",children:"Checking…"}):t?.duckdbInstalled?e.jsx("p",{className:"text-sm text-secondary",children:"Ready to sync a Common Crawl release."}):e.jsxs("div",{className:"flex items-center justify-between gap-4",children:[e.jsx("p",{className:"text-sm text-secondary",children:"Install the local query engine to enable release syncs."}),!u()&&e.jsxs(w,{type:"button",size:"sm",disabled:R,onClick:$(T),children:[e.jsx(ee,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),R?"Installing…":"Install DuckDB"]})]})})]}),e.jsxs("section",{className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Latest sync"}),e.jsxs("h2",{className:"flex items-center gap-2",children:["Release sync",e.jsx(g,{label:"What is a release sync?",children:"A release sync downloads one Common Crawl dump (~16 GB) and extracts backlinks for every project in this workspace in one pass. This is the heavy job — subsequent per-project re-runs skip the download and just re-query the cached files."})]})]}),a&&e.jsx(j,{tone:C(a.status),children:a.status})]}),e.jsxs(x,{className:"surface-card p-5",children:[a?e.jsxs("div",{className:"space-y-2 text-sm",children:[e.jsxs("p",{className:"text-strong",children:["Release ",e.jsx("code",{className:"text-neutral",children:a.release})]}),a.phaseDetail&&e.jsx("p",{className:"text-secondary",children:a.phaseDetail}),e.jsxs("div",{className:"grid grid-cols-2 md:grid-cols-4 gap-4 text-xs text-muted pt-2",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Projects"}),e.jsx("p",{className:"text-neutral mt-0.5",children:a.projectsProcessed??"—"})]}),e.jsxs("div",{children:[e.jsxs("p",{className:"text-faint uppercase tracking-wide flex items-center gap-1",children:["Rows",e.jsx(g,{label:"What are rows?",children:"Total number of (project, referring domain) pairs persisted in SQLite from this sync, across every project in the workspace."})]}),e.jsx("p",{className:"text-neutral mt-0.5",children:a.domainsDiscovered??"—"})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Started"}),e.jsx("p",{className:"text-neutral mt-0.5",children:b(a.downloadStartedAt??a.createdAt)})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Finished"}),e.jsx("p",{className:"text-neutral mt-0.5",children:b(a.queryFinishedAt)})]})]}),a.error&&e.jsx("p",{className:"text-sm text-negative-400 pt-2",children:a.error})]}):e.jsx("p",{className:"text-sm text-muted",children:"No release sync has run in this workspace yet."}),O&&e.jsx("div",{className:"mt-4 rounded border border-caution-800/60 bg-caution-950/20 p-3",children:e.jsxs("div",{className:"flex items-start gap-2",children:[e.jsx(se,{className:"h-4 w-4 text-caution-400 shrink-0 mt-0.5","aria-hidden":!0}),e.jsxs("div",{className:"text-sm text-neutral leading-relaxed",children:[e.jsx("p",{className:"font-medium text-caution-200",children:"Cached files for this release are missing."}),e.jsx("p",{className:"mt-1 text-secondary",children:"Sync this release again, or sync the newest release, before refreshing individual projects."})]})]})}),e.jsxs("div",{className:"mt-4 rounded border border-base bg-bg-elevated/40 p-3",children:[e.jsxs("div",{className:"flex items-start justify-between gap-3 mb-3",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-[10px] uppercase tracking-wide text-muted",children:"Auto-detected release"}),m?e.jsxs("p",{className:"text-sm text-strong mt-0.5",children:[e.jsx("code",{className:"text-heading",children:m.release}),e.jsxs("span",{className:"ml-2 text-xs text-muted",children:["Vertex ",S(m.vertexBytes),", edges ",S(m.edgesBytes)]})]}):e.jsx("p",{className:"text-sm text-muted mt-0.5",children:B?"Checking Common Crawl…":"Could not find a release. Choose one below."}),e.jsxs("a",{href:le,target:"_blank",rel:"noopener noreferrer",className:"mt-1 inline-flex items-center gap-1 text-xs text-secondary hover:text-strong focus:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",children:["Browse all Common Crawl web-graph releases",e.jsx(te,{className:"h-3 w-3","aria-hidden":!0})]})]}),!u()&&e.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[e.jsxs(w,{type:"button",size:"sm",disabled:p||!t?.duckdbInstalled||!m&&!N.trim(),onClick:$(q),children:[e.jsx(ae,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),p?"Queuing…":"Run sync"]}),e.jsxs(g,{label:"What does Run sync do?",children:[e.jsxs("span",{className:"block",children:["Downloads the auto-detected (or chosen) Common Crawl release (~16 GB) to"," ",e.jsx("code",{className:"text-neutral",children:"~/.canonry/cache/commoncrawl/"}),", then runs a single DuckDB query that extracts referring domains for every project in this workspace."]}),e.jsxs("span",{className:"mt-2 block text-secondary",children:["First time for a release: ",e.jsx("span",{className:"text-strong",children:"~10–20 min download + ~5 min query"}),". Re-running the same release later: ",e.jsx("span",{className:"text-strong",children:"skips download, just re-queries"})," (~5 min)."]})]})]})]}),u()?null:M?e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("input",{type:"text",className:"flex-1 min-w-[240px] rounded border border-strong bg-transparent px-2.5 py-1.5 text-sm text-strong placeholder-mono-600 focus:border-mono-500 focus:outline-none",placeholder:"cc-main-2026-jan-feb-mar",value:N,onChange:s=>v(s.target.value),disabled:p,autoFocus:!0}),e.jsx("button",{type:"button",className:"text-sm text-secondary hover:text-neutral focus:text-neutral focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",onClick:()=>{v(""),k(!1)},disabled:p,children:"Cancel"})]}):e.jsx("button",{type:"button",className:"text-sm text-secondary hover:text-neutral focus:text-neutral focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",onClick:()=>k(!0),disabled:p,children:"Use a different release →"})]}),!t?.duckdbInstalled&&e.jsx("p",{className:"text-sm text-secondary mt-2",children:"Install DuckDB first to enable sync."})]})]}),e.jsxs("details",{className:"page-section-divider group",children:[e.jsx("summary",{className:"cursor-pointer text-sm font-medium text-secondary hover:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",children:"Technical details and cache"}),e.jsxs("div",{className:"mt-4",children:[e.jsx("p",{className:"text-sm text-secondary mb-3",children:"Cached releases support project re-extracts. Pruning a release does not remove saved backlink results."}),e.jsx(x,{className:"surface-card overflow-hidden",children:e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-base text-left text-xs uppercase tracking-wide text-faint",children:[e.jsx("th",{className:"px-4 py-2 font-medium",children:"Release"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Sync status"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Size"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Last used"}),!u()&&e.jsx("th",{className:"px-4 py-2 font-medium sr-only",children:"Actions"})]})}),e.jsxs("tbody",{children:[c.map(s=>e.jsxs("tr",{className:"border-b border-mono-900 last:border-0",children:[e.jsx("td",{className:"px-4 py-2 text-strong",children:e.jsx("code",{children:s.release})}),e.jsx("td",{className:"px-4 py-2",children:s.syncStatus?e.jsx(j,{tone:C(s.syncStatus),children:s.syncStatus}):e.jsx("span",{className:"text-faint",children:"—"})}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:S(s.bytes)}),e.jsx("td",{className:"px-4 py-2 text-secondary",children:b(s.lastUsedAt)}),!u()&&e.jsx("td",{className:"px-4 py-2 text-right",children:e.jsxs("div",{className:"inline-flex items-center gap-1",children:[e.jsxs(w,{type:"button",variant:"outline",size:"sm",onClick:()=>{G(s.release)},children:[e.jsx(ne,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),"Prune"]}),e.jsx(g,{label:"What does Prune do?",placement:"top",children:"Deletes the ~16 GB cache for this release from disk. Backlink results already in SQLite remain untouched. To re-run extracts against this release, you’d have to sync it again (another ~16 GB download)."})]})})]},s.release)),c.length===0&&e.jsx("tr",{children:e.jsx("td",{className:"px-4 py-4 text-sm text-muted",colSpan:u()?4:5,children:"No cached releases on this machine. If you ran a sync from a different machine (or deleted the cache), the backlink data is still in the database — but you’ll need to re-sync a release to run new extracts."})})]})]})}),e.jsx("p",{className:"mt-3 text-xs text-muted",children:"Common Crawl files are stored locally. DuckDB reads them; Canonry stores results in its normal database."})]})]}),d.length>1&&e.jsxs("section",{className:"page-section-divider",children:[e.jsx("div",{className:"section-head section-head-inline",children:e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"History"}),e.jsx("h2",{children:"Past release syncs"})]})}),e.jsx(x,{className:"surface-card overflow-hidden",children:e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-base text-left text-xs uppercase tracking-wide text-faint",children:[e.jsx("th",{className:"px-4 py-2 font-medium",children:"Release"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Status"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Projects"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Rows"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Finished"})]})}),e.jsx("tbody",{children:d.map(s=>e.jsxs("tr",{className:"border-b border-mono-900 last:border-0",children:[e.jsx("td",{className:"px-4 py-2 text-strong",children:e.jsx("code",{children:s.release})}),e.jsx("td",{className:"px-4 py-2",children:e.jsx(j,{tone:C(s.status),children:s.status})}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:s.projectsProcessed??"—"}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:s.domainsDiscovered??"—"}),e.jsx("td",{className:"px-4 py-2 text-secondary",children:b(s.queryFinishedAt??s.updatedAt)})]},s.id))})]})})]})]})}export{me as BacklinksPage};