@canonry/canonry 7.99.0 → 7.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +18 -16
  2. package/assets/agent-workspace/AGENTS.md +13 -6
  3. package/assets/agent-workspace/skills/aero/SKILL.md +45 -26
  4. package/assets/agent-workspace/skills/aero/references/agent-operations.md +47 -12
  5. package/assets/agent-workspace/skills/aero/references/memory-patterns.md +1 -1
  6. package/assets/agent-workspace/skills/aero/references/orchestration.md +9 -9
  7. package/assets/agent-workspace/skills/aero/references/reporting.md +8 -4
  8. package/assets/agent-workspace/skills/aero/references/seo-analysis.md +2 -2
  9. package/assets/agent-workspace/skills/aero/references/site-health.md +23 -22
  10. package/assets/agent-workspace/skills/aero/soul.md +2 -2
  11. package/assets/agent-workspace/skills/canonry/SKILL.md +37 -7
  12. package/assets/agent-workspace/skills/canonry/references/aeo-analysis.md +14 -11
  13. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +122 -53
  14. package/assets/assets/AccessControls-DzO5Y8v9.js +2 -0
  15. package/assets/assets/AgentSetupPage-DWWovaCw.js +36 -0
  16. package/assets/assets/AuditHistoryPanel-BG-n1i8c.js +1 -0
  17. package/assets/assets/BacklinksPage-D6wdOMXt.js +1 -0
  18. package/assets/assets/ChartPrimitives-CtKdkn64.js +3 -0
  19. package/assets/assets/Hint-3TTi0MgX.js +1 -0
  20. package/assets/assets/{HistoryPage-DNE76LNh.js → HistoryPage-B_ReWHbL.js} +1 -1
  21. package/assets/assets/InfoTooltip-D29JcNMW.js +1 -0
  22. package/assets/assets/MeasurementPropertyPage-zdyLL-kl.js +4 -0
  23. package/assets/assets/ProjectPage-KZNKXR4w.js +15 -0
  24. package/assets/assets/RunsPage-DxN0xXQh.js +1 -0
  25. package/assets/assets/ServerTrafficSection-CvISj5pf.js +40 -0
  26. package/assets/assets/SettingsPage-CpRpBlMV.js +1 -0
  27. package/assets/assets/ToneBadge-DcwPruHr.js +60 -0
  28. package/assets/assets/TrafficPage-zk6zMZyz.js +1 -0
  29. package/assets/assets/TrafficSourceDetailPage-DJtsI06Z.js +1 -0
  30. package/assets/assets/{account-context-j3asaevn.js → account-context-CoJVqEXZ.js} +1 -1
  31. package/assets/assets/{agent-trust-copy--U_DFYQE.js → agent-trust-copy-BIXDMtBm.js} +1 -1
  32. package/assets/assets/{card-CQAQ-c3g.js → card-Dwmc7Srx.js} +1 -1
  33. package/assets/assets/index-fh1T5JQY.css +1 -0
  34. package/assets/assets/index-mqc2mWUR.js +42 -0
  35. package/assets/assets/query-client-Tg1fZu4i.js +1 -0
  36. package/assets/assets/react-query-BqcEEfEO.js +1 -0
  37. package/assets/assets/{react-sigma_core.esm.min-BAq_-iP1.js → react-sigma_core.esm.min-D1q0DR_j.js} +3 -3
  38. package/assets/assets/{server-traffic-jKXLZyeC.js → server-traffic-Djnh_9f0.js} +1 -1
  39. package/assets/assets/service-BQjBdN4O.js +1 -0
  40. package/assets/assets/use-tooltip-position-BOzm-6Wy.js +1 -0
  41. package/assets/assets/vendor-lucide-iE5MEh2-.js +1 -0
  42. package/assets/assets/{vendor-markdown-BDQ1GKGP.js → vendor-markdown-BBGbNklj.js} +1 -1
  43. package/assets/assets/{vendor-radix-BGYlvS2l.js → vendor-radix-DKCt5Z_h.js} +1 -1
  44. package/assets/assets/{vendor-recharts-DhEHcK3G.js → vendor-recharts-D3MLFU3q.js} +1 -1
  45. package/assets/assets/{vendor-tanstack-BNuZ37rE.js → vendor-tanstack-B90HW50P.js} +2 -2
  46. package/assets/index.html +16 -15
  47. package/dist/{ads-EYDUTG4U.js → ads-FVPZ6R6Z.js} +3 -3
  48. package/dist/{agent-KTK2IDB3.js → agent-2LPU2E5I.js} +7 -7
  49. package/dist/{agent-ask-KPZOH6RY.js → agent-ask-5ODREK6D.js} +3 -3
  50. package/dist/{agent-conversations-BCWA4JJL.js → agent-conversations-SNLOOJEM.js} +3 -3
  51. package/dist/{agent-journeys-UBVLQ7JV.js → agent-journeys-XDXVL6PK.js} +3 -3
  52. package/dist/{agent-memory-24SYEGEX.js → agent-memory-NKAI2S3U.js} +3 -3
  53. package/dist/{agent-providers-E7EWFE5P.js → agent-providers-R5L7A72X.js} +3 -3
  54. package/dist/{agent-transcript-QGJ45NW5.js → agent-transcript-C5WAGYVC.js} +3 -3
  55. package/dist/{analytics-GHVG5CCT.js → analytics-EJVOLIQL.js} +33 -13
  56. package/dist/{apply-7YLB2DPH.js → apply-I6JOM247.js} +3 -3
  57. package/dist/{backfill-WNQ6O5WP.js → backfill-UVUR36IB.js} +10 -10
  58. package/dist/{backlinks-ZBZR4D27.js → backlinks-3VMVACWN.js} +3 -3
  59. package/dist/{bing-4VBSKPSX.js → bing-R5RL3PW2.js} +3 -3
  60. package/dist/{bootstrap-7ESCLP2G.js → bootstrap-GMMSDTNO.js} +12 -12
  61. package/dist/{cdp-WBL6FZTL.js → cdp-FJKUXHEY.js} +7 -7
  62. package/dist/{chunk-L53ZSQN5.js → chunk-2DV7F6XA.js} +91 -2
  63. package/dist/{chunk-MTW37GXF.js → chunk-2FWYWGIR.js} +2 -2
  64. package/dist/{chunk-DMPSA7ZS.js → chunk-56VVRWJM.js} +14 -5
  65. package/dist/{chunk-YACKJGY4.js → chunk-7BEGCVVB.js} +1 -1
  66. package/dist/{chunk-YMHN4UIT.js → chunk-7BH53MOB.js} +27 -2
  67. package/dist/{chunk-XPIMVXAI.js → chunk-7J2RIIYU.js} +1366 -1033
  68. package/dist/{chunk-UY6SKAKW.js → chunk-7SNMIXEQ.js} +2 -2
  69. package/dist/{chunk-C55P75G4.js → chunk-AMGM55CP.js} +2 -2
  70. package/dist/{chunk-ZN2TEBBX.js → chunk-AYKTVQF7.js} +13 -5
  71. package/dist/{chunk-74V5EPZH.js → chunk-BKDRT4VS.js} +7 -7
  72. package/dist/{chunk-O6RXNSNW.js → chunk-COHSL6KW.js} +1 -1
  73. package/dist/{chunk-3WDQ5GPO.js → chunk-DGDLLRS4.js} +1 -1
  74. package/dist/{chunk-VADVPARO.js → chunk-DHIB7EIA.js} +4 -4
  75. package/dist/{chunk-3MFYM2IS.js → chunk-EE3QTCLS.js} +18 -6
  76. package/dist/{chunk-DLOWRO5R.js → chunk-F46TBLXK.js} +204 -94
  77. package/dist/{chunk-P764TJKQ.js → chunk-FBO25TA3.js} +1 -1
  78. package/dist/{chunk-SY3CWJR4.js → chunk-FZ3MUTU3.js} +2 -2
  79. package/dist/{chunk-WA6APXI6.js → chunk-G6QN4VFS.js} +16 -10
  80. package/dist/{chunk-YBWATBX4.js → chunk-HBHLYWKE.js} +9 -6
  81. package/dist/{chunk-5ZOM5BJS.js → chunk-HL2LGRTI.js} +8 -6
  82. package/dist/chunk-J6536OL6.js +31 -0
  83. package/dist/{chunk-3MKJ4LP6.js → chunk-KVRX7YIH.js} +20 -10
  84. package/dist/{chunk-3VPBRGPN.js → chunk-LJO2BKWU.js} +1 -1
  85. package/dist/{chunk-YEUULP44.js → chunk-LNVFERI4.js} +1 -1
  86. package/dist/{chunk-WXAF7AMC.js → chunk-NQYJHKML.js} +39 -7
  87. package/dist/{chunk-UPHHKO3L.js → chunk-O5UFAXUI.js} +646 -588
  88. package/dist/{chunk-XQP3XHLJ.js → chunk-O6APBEJE.js} +2 -2
  89. package/dist/{chunk-47X24IJG.js → chunk-O7S7OHLE.js} +9393 -8394
  90. package/dist/{chunk-2RW5D55K.js → chunk-QS5DWJDI.js} +2 -2
  91. package/dist/{chunk-TVBOK4P2.js → chunk-RBG2QXQM.js} +4 -4
  92. package/dist/{chunk-LM7HTNUO.js → chunk-SWZXHHAG.js} +16 -8
  93. package/dist/{chunk-AY7SDX6G.js → chunk-T7UDY7W3.js} +26 -11
  94. package/dist/{chunk-AJD252SX.js → chunk-UGO2FO6V.js} +119 -55
  95. package/dist/{chunk-PT5HNVCM.js → chunk-VHULVUDL.js} +1 -1
  96. package/dist/{chunk-ZGBLNKFN.js → chunk-YGAQQSCL.js} +56303 -54933
  97. package/dist/{chunk-RRXLJPBN.js → chunk-YIIRS7V7.js} +98 -65
  98. package/dist/{chunk-VOVT2H25.js → chunk-YU2WX7RD.js} +3 -3
  99. package/dist/{citations-N5MGNKJN.js → citations-LOEQRDUB.js} +21 -10
  100. package/dist/{cli-catalog-FFU7PHW7.js → cli-catalog-5RXNUFBS.js} +3 -3
  101. package/dist/{cli-commands-AMIDEUO2.js → cli-commands-HE2BQY3Y.js} +7 -7
  102. package/dist/cli.js +22 -17
  103. package/dist/{competitor-GRA2GMZR.js → competitor-PIX2BHJW.js} +3 -3
  104. package/dist/{content-YVAPTIV4.js → content-PJGW4WZF.js} +3 -3
  105. package/dist/{dashboard-33ACBR5A.js → dashboard-7OKBD4MG.js} +6 -6
  106. package/dist/{demo-AGXWBMR6.js → demo-SCOFAWFM.js} +1 -1
  107. package/dist/{demo-server-W4YAC7GT.js → demo-server-SJPI6RSL.js} +38 -36
  108. package/dist/{discover-BH33TO4Z.js → discover-I3WL2QUN.js} +10 -8
  109. package/dist/{discover-eval-3CEA6DEE.js → discover-eval-YRECOIP5.js} +3 -3
  110. package/dist/{doctor-ZAYTD5PC.js → doctor-F4J6LILX.js} +3 -3
  111. package/dist/{evidence-247GL7UK.js → evidence-MYQTCY4V.js} +25 -10
  112. package/dist/{export-cmd-HLKWFI5Q.js → export-cmd-3SUFOJM6.js} +3 -3
  113. package/dist/{ga-BMING57Z.js → ga-VA6FYK4L.js} +3 -3
  114. package/dist/{gbp-MIB5CXSD.js → gbp-DI6ZVFHL.js} +3 -3
  115. package/dist/{google-5JP6RYAW.js → google-UVKWJCD2.js} +4 -4
  116. package/dist/{google-marketing-25OBMPS6.js → google-marketing-S63HN733.js} +1 -1
  117. package/dist/{health-cmd-D5XSEL7H.js → health-cmd-DOZXGI46.js} +16 -9
  118. package/dist/{history-6MDRFPQ3.js → history-UTJTUDYK.js} +3 -3
  119. package/dist/index.d.ts +211 -19
  120. package/dist/index.js +26 -26
  121. package/dist/{init-RDR75N2Y.js → init-IVJSS33J.js} +19 -19
  122. package/dist/{insights-QZVMBGVM.js → insights-UHCVCZ76.js} +3 -3
  123. package/dist/intelligence-service-3EENJ5DS.js +16 -0
  124. package/dist/{journeys-TABTZDFZ.js → journeys-XIT7WDM2.js} +3 -3
  125. package/dist/{keys-SL5YF7NQ.js → keys-MQKW4JUI.js} +3 -3
  126. package/dist/{keyword-OCDEXRSW.js → keyword-EASMKYZ3.js} +3 -3
  127. package/dist/{logs-CSC7O364.js → logs-ZHZ6XM55.js} +3 -3
  128. package/dist/{mcp-P725OHAJ.js → mcp-ECVT2YGU.js} +4 -4
  129. package/dist/mcp.js +10 -10
  130. package/dist/{measurement-plan-Z42GBJBO.js → measurement-plan-7FQKUOMR.js} +5 -5
  131. package/dist/{notify-E5JBES4I.js → notify-TC7ARC6W.js} +3 -3
  132. package/dist/{organic-evidence-CUA3T64O.js → organic-evidence-M34MWJXE.js} +7 -4
  133. package/dist/overview-P6BRMXJN.js +314 -0
  134. package/dist/{project-2ULM64TV.js → project-WZ2YXFPJ.js} +4 -4
  135. package/dist/project-setup-WV6SPGIV.js +176 -0
  136. package/dist/{query-Y47IEFKJ.js → query-WAFJKDHG.js} +3 -3
  137. package/dist/{research-DPFQZ3TE.js → research-B5ZPQX7R.js} +3 -3
  138. package/dist/{results-export-KBHOBQAH.js → results-export-NIZV6R3K.js} +3 -3
  139. package/dist/{run-4CYHVIIU.js → run-ICLPZ7QT.js} +7 -4
  140. package/dist/{schedule-GY4P3RON.js → schedule-4C44LQ7R.js} +22 -3
  141. package/dist/{search-AGVNZMJQ.js → search-UJM7KII3.js} +11 -4
  142. package/dist/{serve-5LFKIXNL.js → serve-A6UIO6WP.js} +30 -29
  143. package/dist/{session-YAYEDM3P.js → session-RTE22GQT.js} +14 -14
  144. package/dist/settings-BOF5Z7ML.js +211 -0
  145. package/dist/{sitemaps-APTY7U6H.js → sitemaps-BM7S76H2.js} +4 -4
  146. package/dist/{skills-4BUONEIZ.js → skills-X5SR4HRO.js} +2 -2
  147. package/dist/{snapshot-BXCWSF5P.js → snapshot-KLKE37VY.js} +4 -4
  148. package/dist/{src-R7XZUX3D.js → src-EKLG5A6O.js} +4 -2
  149. package/dist/{status-SB6HPBYJ.js → status-OYG57DWU.js} +6 -5
  150. package/dist/{technical-aeo-G7UEMWZD.js → technical-aeo-KWVWOOTI.js} +23 -23
  151. package/dist/{telemetry-H4S4Y7YV.js → telemetry-KGE6FF3Y.js} +4 -4
  152. package/dist/{tools-46XX6I4R.js → tools-4R33AZQ3.js} +11 -11
  153. package/dist/{traffic-I6WRJN3W.js → traffic-SVMOKCSS.js} +7 -11
  154. package/dist/{trends-AQK3GLWF.js → trends-RGAKGX3L.js} +4 -4
  155. package/dist/{users-SNTVY6OB.js → users-47WKA2TO.js} +3 -3
  156. package/dist/{visibility-stats-44QAD2XS.js → visibility-stats-DTGQJPNV.js} +9 -5
  157. package/dist/{wordpress-N7LTOW46.js → wordpress-2CNUX4UE.js} +3 -3
  158. package/package.json +1 -1
  159. package/assets/assets/AgentSetupPage-yOqSVJ-e.js +0 -18
  160. package/assets/assets/AuditHistoryPanel-CiTzFgbw.js +0 -1
  161. package/assets/assets/BacklinksPage-DmRdQsFQ.js +0 -1
  162. package/assets/assets/ChartPrimitives-CvK6CQrj.js +0 -1
  163. package/assets/assets/Hint-c4a4W87X.js +0 -1
  164. package/assets/assets/InfoTooltip-Drnst4ML.js +0 -1
  165. package/assets/assets/MeasurementPropertyPage-Cx64Fnmz.js +0 -4
  166. package/assets/assets/ProjectPage-DNfgC82V.js +0 -11
  167. package/assets/assets/RunsPage-BmPKBKKr.js +0 -1
  168. package/assets/assets/SettingsPage-Dl0TNeVK.js +0 -1
  169. package/assets/assets/SiteHealthSection-DxQPaR--.js +0 -2
  170. package/assets/assets/ToneBadge-BhjTBHi-.js +0 -60
  171. package/assets/assets/TrafficPage-CJp0pS01.js +0 -40
  172. package/assets/assets/TrafficSourceDetailPage-DKE8tSw5.js +0 -1
  173. package/assets/assets/format-helpers-BPugHeIX.js +0 -3
  174. package/assets/assets/index-BAxHW-1G.css +0 -1
  175. package/assets/assets/index-xlDLvgRC.js +0 -60
  176. package/assets/assets/query-client-CwZpfiiD.js +0 -1
  177. package/assets/assets/react-query-CBpNFR4t.js +0 -1
  178. package/assets/assets/service-DFumlmXt.js +0 -1
  179. package/assets/assets/vendor-lucide-DuOM7wVi.js +0 -1
  180. package/dist/intelligence-service-KQE7RO5I.js +0 -16
  181. package/dist/overview-UKQ52I72.js +0 -213
  182. package/dist/settings-HUKMWRHY.js +0 -119
@@ -39,12 +39,12 @@ cnry dashboard reset-password # clear a forgotten dashboard pas
39
39
  cnry --version
40
40
  ```
41
41
 
42
- `cnry bootstrap` is the provider-free Page Health path. It stores state under
42
+ `cnry bootstrap` is the provider-free Agent Readiness path. It stores state under
43
43
  `$CANONRY_CONFIG_DIR` when set, otherwise `~/.canonry`, and prints a new API key
44
44
  once. With no provider, its `--format json` output lists `nextSteps` for adding
45
- one, and `technical-aeo run|score|crawl` / `site-health overview` print a
45
+ one, and `agent-readiness run|score|overview` print a
46
46
  `NO_PROVIDER` notice on stderr (one `{"notice":{...}}` line for JSON): AI
47
- Visibility is the next step after Page Health. The operator adds the key in the
47
+ Visibility is the next step after Agent Readiness. The operator adds the key in the
48
48
  dashboard or in their own terminal; never ask for it in chat. `cnry settings
49
49
  provider` registers a provider on the running server immediately. `cnry init` is the optional interactive path when provider/OAuth setup is
50
50
  wanted at the same time. An agent must ask the operator to run either
@@ -132,8 +132,9 @@ pm2 restart canonry
132
132
 
133
133
  ```bash
134
134
  cnry project list # list all projects
135
- cnry project create <name> --domain <url> --country US --language en
135
+ cnry project create <name> --domain <url> --country US --language en # brand name from the domain unless --display-name
136
136
  cnry project show <name> # project detail
137
+ cnry project setup <name> # first-run setup: Agent Readiness audit scan, AI Visibility check, next step
137
138
  cnry project update <name> # update project settings
138
139
  cnry project update <name> --add-lead-event book_demo --remove-lead-event old_event
139
140
  cnry project update <name> --no-count-key-events # count only the lead events above, not the GA4 property's key events (--count-key-events turns it back on)
@@ -143,7 +144,7 @@ cnry project update <name> --display-name "Sample Brand" --expect-revision 4
143
144
  cnry project update <name> --dispatch-mode claude=batch # scheduled sweeps batch this provider (repeatable; see Batch mode below)
144
145
  cnry project update <name> --clear-dispatch-mode claude # back to sync
145
146
  cnry project update <name> --negative-review-max-stars 2 # review.negative for 1-2 star Google reviews only (1-4, or "default" for 3)
146
- cnry project update <name> --site-audit-max-pages 2500 # Site Health page budget for scans that set none (1-50000, or "full" for the full site); create takes it too, spec.siteAuditMaxPages in apply
147
+ cnry project update <name> --site-audit-max-pages 2500 # Agent Readiness page budget for scans that set none (1-50000, or "full" for the full site); create takes it too, spec.siteAuditMaxPages in apply
147
148
  cnry project update <name> --competitor-auto-aliases apply # store answer-derived competitor names after each sweep (off | preview | apply; default preview logs only); spec.competitorAutoAliases in apply
148
149
  cnry project update <name> --add-qualified-alias "Acme NYC" # sentiment treats this existing alias as the brand's own name (see Brand aliases)
149
150
  cnry project update <name> --remove-qualified-alias "Acme NYC" # repeatable; create takes --qualified-alias
@@ -153,6 +154,8 @@ cnry status <project> # stored run, integration and hea
153
154
  cnry status --all [--problems] # stored status for every project and connected integration
154
155
  ```
155
156
 
157
+ `project setup` reads `GET /projects/<name>/setup` from stored state only: no provider call, crawl or write. Each track is the latest non-probe run of its kind (`not_started` when there is none): the Agent Readiness audit scan (`site-audit`) and the AI Visibility check (`answer-visibility`). A check that measured every location is one sweep of sibling runs (`aiVisibility.runIds`, newest first), read as one check: running while any run is, partial when only some saved results. Neither gates the other. A queued or running track reports stored progress (scan pages found and checked against its page budget; check answers saved against the answers it planned), a completed or partial one its result (scan score and pages scored; check answers, engines, and query counts with non-brand and branded kept apart, and text output labels it `Baseline` or `Latest check`). `aiVisibility.baselineRunId` names the baseline, the first check to finish with a result, in every state (null before any); the latest check is the baseline when `runId` equals it, and a failed one its reason; a failed check also lists each engine's failure kind (`failedEngines[].code`, such as `PROVIDER_BILLING` for an account out of credit), and text output adds the step that fixes it. `aiVisibility.blockedBy` is the run preview's readiness (`no_engine`, `no_queries`, `plan_unpublished`, `engines_failing`; Simple and Advanced). `nextAction` is the server's next step: `run_agent_readiness`, `retry_agent_readiness`, `connect_engine`, `add_queries`, `publish_plan`, `run_ai_visibility`, `repair_ai_visibility`, `wait` or `open_project`; text output prints it as a `Next:` command. It names a step and grants nothing: the scan and the check (which spends the operator's AI engine accounts) each need approval. `--format json` is the API object; MCP `canonry_project_setup` returns the same.
158
+
156
159
  Project updates use PATCH and preserve omitted settings and config source. Measurement
157
160
  flags are repeatable: `--lead-event`, `--brand-term`, and `--marketing-host` replace
158
161
  only their named list; `--add-*` and `--remove-*` adjust it. Lead event matching is
@@ -222,6 +225,7 @@ cnry snapshot "Acme Corp" --domain acme.example.com --provider-mode api # all c
222
225
  cnry snapshot "Acme Corp" --domain acme.example.com --provider-mode browser --queries "best widget suppliers"
223
226
 
224
227
  cnry run <project> # sweep all configured providers
228
+ cnry run preview <project> --format json # stored-only workload/admission for the next default full manual sweep
225
229
  cnry run <project> --provider gemini # single provider only
226
230
  cnry run <project> --query "alpha" --query "beta" # scope sweep to a subset of tracked queries (repeatable)
227
231
  cnry run <project> --wait # block until complete (a batch run: until batch-pending, then exit 0); exit 2 if the run failed
@@ -250,6 +254,8 @@ The newest limited rows retain chronological order. Traffic sync rows include
250
254
  JSON is the API array; JSONL emits each run with its project. With no second
251
255
  positional, `run list` retains the sweep shorthand for a project named `list`.
252
256
 
257
+ `run preview` reports query classes, engines/models, locations and Advanced published revision/assignments. `expectedRequestCount` counts planned answer slots; `requestCount` excludes unavailable engines and held provider accounts. It makes no provider calls or writes, reserves no work, and does not preview scoped, forced, or all-location overrides. A draft-only Advanced project must be published before an ordinary sweep.
258
+
253
259
  Run statuses: `queued` → `running` → `completed` / `failed` / `partial`
254
260
 
255
261
  `partial` = some providers failed (usually rate limits) — successful snapshots are still saved.
@@ -340,8 +346,9 @@ because generation spends provider quota.
340
346
  Two independent signals per (query × provider): **mention** (`answerMentioned` — brand named in the answer text; the **primary** read) and **citation** (`cited`/`citedDomains` — domain in the grounding sources; **secondary**). Read mention first. Never compute one from the other; never coerce `answerMentioned` null → false (null = "not checked").
341
347
 
342
348
  ```bash
343
- cnry evidence <project> # per-query [C/c][M/m] cell + Mentioned: X / Y / Cited: X / Y
344
- cnry evidence <project> --format json # JSON output
349
+ cnry citations visibility <project> # two-glyph [citation][mention] cell per (query × engine) + Cite/Ment counts
350
+ cnry evidence <project> # latest citation per query (✓ cited / ✗ not-cited / · not measured) + Cited: X / Y
351
+ cnry evidence <project> --format json # JSON output: each query's runs with citation and mention state
345
352
  cnry history <project> # audit trail
346
353
  cnry history <project> --entity-type competitor --format json
347
354
  cnry history <project> --action-prefix competitors. --exclude-entity-type traffic_source --since 2026-10-01T00:00:00Z --until 2026-10-02T00:00:00Z
@@ -369,18 +376,21 @@ Project exports redact notification webhook URLs by default as `redacted://<host
369
376
  Applying that export preserves the existing notification ID, signing secret, source, enabled state and creation time. The HTTPS `/redacted` placeholder from notification listings also preserves the matched stored destination. A real URL change appears in preview and audit as a URL-change marker without exposing its path or secret.
370
377
  Use `--include-secrets` only when the credential has administrator authority. The CLI prints a redaction note to stderr when URLs remain redacted.
371
378
 
372
- Output uses a two-glyph cell per (query × provider): `[C/c][M/m]` — uppercase = present, lowercase = absent, `–` = no snapshot. **C/c = cited** (secondary), **M/m = mentioned** (primary). Always print the legend before the table; never collapse the two signals into one cell.
379
+ `cnry citations visibility` prints a two-glyph cell per (query × engine): citation first, mention second. Uppercase = present, lowercase = absent, `·` = citation not measured (the engine returns no sources, such as a local model), `–` = no snapshot. **C/c = cited** (secondary), **M/m = mentioned** (primary). The legend prints above the table; never collapse the two signals into one cell.
373
380
 
374
381
  ```
375
- Legend: [C/c][M/m] C=cited c=not-cited M=mentioned m=not-mentioned –=no snapshot
376
-
377
- [C][M] acme corp ny ← mentioned AND cited
378
- [c][M] best crm for smb ← mentioned, not cited (mention win, citation gap)
379
- [C][m] crm pricing ← cited, not mentioned (citation without share of voice)
380
- [c][m] free crm tools ← neither
382
+ (cell = [citation][mention]; C=cited c=not ·=not measured, M=mentioned m=not, –=no data)
383
+ Query gemini local openai Cite Ment
384
+ acme corp ny CM ·M CM 2/2 3/3 ← mentioned AND cited
385
+ best crm for smb cM ·M cM 0/2 3/3 ← mentioned, not cited (mention win, citation gap)
386
+ crm pricing Cm ·m cm 1/2 0/3 ← cited, not mentioned (citation without share of voice)
387
+ free crm tools cm ·m cm 0/2 0/3 ← neither
388
+ crm for agencies – ·M – · 1/1 ← mentioned; citation not measured (no sources), never a citation gap
381
389
  ```
382
390
 
383
- Summary: `Mentioned: X / Y` (primary) and `Cited: X / Y` (secondary) are reported independently — a query can be one, both, or neither.
391
+ `Cite` is the engines that cited the query over those whose sources were checked (`·` when none were); `Ment` is the engines that mentioned it over those that answered. `Cited in sources: X/Y engines` and `Mentioned in answers: X/Y engines` above the table report the two signals independently — a query can be one, both, or neither.
392
+
393
+ `cnry evidence` prints each query's latest citation instead: `✓ cited`, `✗ not-cited` or `· not measured (no sources)`, with a transition such as `(lost)`. Its summary is `Cited: X / Y`, where `Y` counts only queries whose latest answers carried sources, and `Not measured: N` lists the rest apart. It prints no mention line; `--format json` carries each run's `mentionState` and `mentionTransition`.
384
394
 
385
395
  ## Results Export (historical observations)
386
396
 
@@ -395,7 +405,7 @@ cnry results export <project> --include-probes # opt IN to probe runs (e
395
405
  Bulk download of every persisted answer-visibility query × provider observation — one record per snapshot, ordered by run then snapshot. This is the raw-history primitive; use `visibility-stats` for aggregates and `visibility-compare` for month-over-month claims.
396
406
 
397
407
  Behavior to know:
398
- - **Citation and mention stay independent** per record: `citationState`/`cited` describe source-list attribution; `answerMentioned`/`mentionState` describe answer-text presence. `answerMentioned: null` means "not evaluated" (legacy snapshot), never not-mentioned.
408
+ - **Citation and mention stay independent** per record: `citationState`/`cited` describe source-list attribution; `answerMentioned`/`mentionState` describe answer-text presence. `answerMentioned: null` means "not evaluated" (legacy snapshot), never not-mentioned. `citationState: not-measured` with `cited: null` (an empty CSV cell) means the engine returns no sources, never not-cited; an older row of such an engine that stored `not-cited` exports as `not-measured`.
399
409
  - `query` is the snapshot-time text, so removed tracked queries keep their history; `queryId` is null for them.
400
410
  - Includes completed, partial, and failed answer-visibility runs. Probe runs and other run kinds (site-audit etc.) are excluded unless `--include-probes`.
401
411
  - CSV cells are spreadsheet-injection-safe (leading `=`, `+`, `-`, `@` neutralized) and JSON-array columns (`cited_domains_json`, …) are embedded JSON strings.
@@ -408,7 +418,7 @@ Behavior to know:
408
418
  ```bash
409
419
  cnry analytics <project> # default analytics view
410
420
  cnry analytics <project> --feature metrics # mention + citation rate trends (BrandMetricsDto: mentionTrend primary, trend secondary)
411
- cnry analytics <project> --feature gaps # brand gap analysis — mention buckets (mentionedQueries[]/mentionGap[]/notMentioned[]) primary, cited buckets (cited[]/gap[]/uncited[]) secondary
421
+ cnry analytics <project> --feature gaps # brand gap analysis — mention buckets (mentionedQueries[]/mentionGap[]/notMentioned[]) primary, cited buckets (cited[]/gap[]/uncited[], plus citationNotMeasured[] for queries whose engines return no sources) secondary
412
422
  cnry analytics <project> --feature sources # source breakdown by category
413
423
  cnry analytics <project> --window 7d # time window: 7d, 30d, 90d, all
414
424
  cnry analytics <project> --feature metrics --query-class non-brand # branded|non-brand answers only (metrics and sources); omitted = all, which pools both
@@ -444,7 +454,7 @@ cnry bing submit-sitemap <project> <url> # ask Bing to read a sitemap (adds a
444
454
  cnry google submit-sitemap <project> <url> # the same for Google
445
455
  ```
446
456
 
447
- Canonry compares three stored lists: the sitemap files the latest Site Health scan read pages from (`site.sitemaps`), Search Console's list (stored by every Google sync, files inside each index included) and Bing's (stored by `bing sync`). For each engine, `missing` holds sitemaps the site declares, or someone submitted to the other engine, that this engine lacks (not offered from the other engine: a sitemap it only found by itself, one it reports errors for, or an index on a host where this engine already has every sitemap the site declares); a file inside a missing index is left out, since adding the index covers it. Google is offered only sitemaps inside the connected property, Bing only its site's host and subdomains. An engine is `null` until its list has been read once; after that an empty list means it has none. `submittedAt: null` on a top-level row means the engine found the sitemap itself; on a file inside an index it came through the index. A new submission shows in the stored list after that engine's next sync.
457
+ Canonry compares three stored lists: the sitemap files the latest Agent Readiness audit scan read pages from (`site.sitemaps`), Search Console's list (stored by every Google sync, files inside each index included) and Bing's (stored by `bing sync`). For each engine, `missing` holds sitemaps the site declares, or someone submitted to the other engine, that this engine lacks (not offered from the other engine: a sitemap it only found by itself, one it reports errors for, or an index on a host where this engine already has every sitemap the site declares); a file inside a missing index is left out, since adding the index covers it. Google is offered only sitemaps inside the connected property, Bing only its site's host and subdomains. An engine is `null` until its list has been read once; after that an empty list means it has none. `submittedAt: null` on a top-level row means the engine found the sitemap itself; on a file inside an index it came through the index. A new submission shows in the stored list after that engine's next sync.
448
458
 
449
459
  ### Stored trend figures (`cnry trends`)
450
460
 
@@ -452,7 +462,7 @@ The figures a dashboard tab's trend card leads with, from stored data only: each
452
462
 
453
463
  ```bash
454
464
  cnry trends <project> --surface index-coverage # Google and Bing indexed / not indexed pages, last 90 days by default
455
- cnry trends <project> --surface site-health # site score and pages checked, one reading per scan, all history by default
465
+ cnry trends <project> --surface site-health # Agent Readiness score and pages checked, one reading per scan, all history by default
456
466
  cnry trends <project> --surface backlinks --window 90d # referring domains, linking hosts and top-10 concentration per Common Crawl release
457
467
  cnry trends <project> --surface index-coverage --start 2026-08-01 --end 2026-08-31 # your own calendar range instead of a window
458
468
  cnry trends <project> --surface backlinks --format json # TrendsResponse; jsonl degrades to the json document
@@ -469,7 +479,7 @@ cnry trends <project> --surface backlinks --format json # TrendsResponse; jsonl
469
479
  Per-query mention (answer-text) and citation (source-list) **counts with a sample size**, pooled across many answer-visibility runs — the data to compute a confidence-aware proportion (e.g. Wilson) or detect drift without fetching every run. Backed by `GET /api/v1/projects/<name>/visibility-stats` and the `canonry_visibility_stats` MCP tool. Probe runs and non-`answer-visibility` runs are excluded; only completed/partial runs count. `--share-of-voice` adds a head-to-head vs tracked competitors (`project / (project + competitor)` brand mentions) — the primitive for a month-over-month client report: call it with `--month <this>` and `--month <last>` and compare. **It is scoped to NON-BRAND queries by default and the response echoes `queryClass`.** A branded query hands the model your name, so you are mentioned on ~all of them and a competitor structurally cannot be; a pooled figure therefore reports brand recall as category placement (measured: one basket read 42% / rank 1 pooled and 3% / rank LAST non-brand). Pass `--query-class branded` for the recall figure. There is no "all" — the two never share a denominator. `--query-class` scopes share of voice only, never the counts: `TOTAL` (JSON `totals`) pools both classes, and the lines under it (JSON `byQueryClass`, plus `queryClass` on each query) split mention and citation counts into non-brand, branded and unclassified. Read the non-brand line for competitive placement.
470
480
 
471
481
  ```bash
472
- cnry visibility-stats <project> # all runs; per-query cited/total + mentioned/checked + pooled TOTAL
482
+ cnry visibility-stats <project> # all runs; per-query cited/measured (n/m: no sources) + mentioned/checked + pooled TOTAL
473
483
  cnry visibility-stats <project> --last-runs 10 # most recent 10 runs (mutually exclusive with --since/--until/--month)
474
484
  cnry visibility-stats <project> --since 2026-06-01 --until 2026-06-30 # ISO date/time window on run createdAt
475
485
  cnry visibility-stats <project> --month 2026-06 # a whole calendar month (YYYY-MM), expanded to that month's UTC bounds
@@ -488,52 +498,61 @@ cnry visibility-compare <project> --from 2026-05 --to 2026-06 --scope property -
488
498
  - **Use `visibility-compare` for any month-over-month AEO claim.** It leads with **share of voice** (less exposed to an engine's broad naming propensity than an absolute rate, `driftRobust: true`), pools rates per-snapshot (invariant to sweep count), restricts to the query/provider PAIRS present in BOTH months, then to providers with one known, identical configured model id in both months, and attaches a Wilson 95% interval + a `verdict` to every metric. Verdicts: **`within-noise`** = NO confirmed change (never report it as a decline); **`moved`** = a real directional move; **`model-discontinuous` / `model-unknown`** = the engine's configured model changed, was mixed within a month, or is unrecorded, so no directional call is made (do not attribute the swing to the site). Read `continuity` (`status` + per-provider evidence) for what was excluded — `continuity` is the gate, `modelChanges` is advisory. A silent upstream version bump under an unchanged configured id remains undetectable. `lowRunCount` flags a month under 5 sweeps, where intervals are too wide to resolve a move.
489
499
  - **Class rates:** `mention-rate-branded`, `cited-rate-branded`, `mention-rate-non-brand` and `cited-rate-non-brand` never pool the classes; a missing split reads `classification-unavailable`, and `excludedUnknown` counts rows without that metric's own signal. On an Advanced project without `--scope`/`--market-key`, the four original metrics keep the project frame while the class rates come from the frozen frame in **`classComparison`**: read `classComparison.continuity` (and its `basket`/run counts) as the gate for those four rates, not the top-level `continuity`. `--scope`/`--market-key` answer entirely from the frozen frame at the top level and are refused when a month holds unreconstructable schema-v1 runs.
490
500
 
491
- - **Tri-state aware:** `checked` counts only snapshots where `answerMentioned` was recorded — `null` ("not checked") is **excluded**, never counted as not-mentioned. So `checked` is the correct `n` for a mention proportion. `mentionRate = mentioned/checked`; `citedRate = cited/total` (citation_state is always populated, so the citation `n` is `total`). Both rates are `null` when their denominator is 0 (undefined over no samples).
501
+ - **Tri-state aware:** `checked` counts only snapshots where `answerMentioned` was recorded — `null` ("not checked") is **excluded**, never counted as not-mentioned. So `checked` is the correct `n` for a mention proportion. `mentionRate = mentioned/checked`; `citedRate = cited/(total - citationNotMeasured)`, where `citationNotMeasured` counts answers whose engine returns no sources (never a miss). Both rates are `null` when their denominator is 0 (undefined over no samples).
492
502
  - **Date-only window:** `--since`/`--until` accept a full ISO instant or a bare `YYYY-MM-DD`. A date-only `--until 2026-06-30` covers the **whole** UTC day (through 23:59:59.999), so same-day runs are included; a date-only `--since` is that day's start.
493
503
  - **Unbounded by default:** with no `--since`/`--until`/`--last-runs`, every completed/partial run is pooled (`window.runCount` reports how many). For a recent sample, bound it with `--last-runs N`.
494
504
  - **`groupBy` in the payload:** present (`"provider"`) only with `--by-provider`; omitted otherwise (absent = no breakdown) — the generated SDK types it `groupBy?: 'provider'`.
495
505
  - **mention vs cited stay independent** — a model can do either, both, or neither. Don't read one from the other.
496
506
 
497
- ## Site Health (technical-aeo compatibility)
507
+ <a id="site-health-technical-aeo-compatibility"></a>
508
+
509
+ ## Agent Readiness
498
510
 
499
- Site-wide technical audit (structured data, AI-readable content, AI-crawler access, content depth/freshness/extractability, …) powered by `@canonry/agent-readiness`'s `runSiteCrawl`. Runs as the `site-audit` run kind — discovers in-scope URLs from the project root, sitemaps, and internal links; stores the URL/link graph; audits eligible HTML pages; and rolls the results into one 0–100 site score. Pure HTTP, no LLM cost; a large site can take minutes, so it runs in the background. `site-health` is the operator-facing CLI name; `technical-aeo` remains compatible.
511
+ Site-wide Agent Readiness audit scan (structured data, AI-readable content, AI-crawler access, content depth/freshness/extractability, agent integrations, …) powered by `@canonry/agent-readiness`'s `runSiteCrawl`. Runs as the `site-audit` run kind — discovers in-scope URLs from the project root, sitemaps, and internal links; stores the URL/link graph; audits eligible HTML pages; and rolls the results into one 0–100 Agent Readiness score. Pure HTTP, no LLM cost; a large site can take minutes, so it runs in the background. The API serves the same data under `/api/v1/projects/<name>/agent-readiness/…`, and the MCP tools are `canonry_agent_readiness_*`.
500
512
 
501
513
  ```bash
502
- cnry technical-aeo run <project> --wait # full crawl + audit; without --max-pages uses the project's saved page budget, else the full site (up to 50,000 pages); edges derived from the page count unless --max-edges is set; --wait polls up to 15 minutes, so follow a longer scan with technical-aeo progress
503
- cnry technical-aeo run <project> --sitemap-url <url> --max-pages 5000 --max-edges 250000 --max-depth 12 # optional crawl seeds and custom budgets; hard caps are 50,000 pages / 1,000,000 edges
504
- cnry technical-aeo run <project> --check-dead-links --wait # opt in to dead-link checks; they are off by default
505
- cnry technical-aeo progress <project> --run-id <id> [--format json] # exact durable phase and pages found / checked / failed counters; never a synthesized percentage
506
- cnry technical-aeo crawl <project> [--run-id <id>] [--format json] # crawl metadata, budgets, completeness, and termination
507
- cnry site-health overview <project> [--run-id <id>] [--format json] # operator-facing alias for crawl metadata
508
- cnry site-health issues <project> [--run-id <id>] [--cursor <cursor>] [--limit <n>] [--format json|jsonl] # stored findings including passing-score issues, affected-page counts and example URLs
509
- cnry site-health issue-pages <project> --issue-id <id> [--run-id <id>] [--cursor <cursor>] [--limit <n>] [--format json|jsonl] # affected URLs for one issue; pin the run from the issue list
510
- cnry site-health page-audit <project> (--node-key <key>|--url <url>) [--run-id <id>] [--format json] # exact audit score, factor findings, recommendations, and crawl provenance for one graph page
511
- cnry site-health subgraph <project> [--node-key <key>|--url <url>] [--hops <n>] [--max-nodes <n>] [--max-edges <n>] [--format json] # focused semantic graph (MCP defaults to 25 nodes / 50 edges)
512
- cnry site-health path <project> (--to-node-key <key>|--to-url <url>) [--from-node-key <key>|--from-url <url>] [--max-depth <n>] [--format json] # directed shortest followable-link path
513
- cnry site-health changes <project> [--from-run-id <id>] [--to-run-id <id>] [--scope all|pages|links] [--change all|added|removed|changed] [--cursor <cursor>] [--limit <n>] [--format json|jsonl] # canonical scan diff; either scan ID is optional; JSONL begins with scan/cursor/filter metadata
514
- cnry technical-aeo changes <project> [--from-run-id <id>] [--to-run-id <id>] [--scope all|pages|links] [--change all|added|removed|changed] [--cursor <cursor>] [--limit <n>] [--format json|jsonl] # compatibility alias with the same independently optional scan IDs
515
- cnry technical-aeo crawl-pages <project> [--fetch-state <state>] [--indexability-state <state>] [--url-versions include|exclude|only] [--sort url|path|score-asc|score-desc] [--cursor <cursor>] [--limit <n>] [--format json|jsonl] # bounded URL inventory with depth and link score; --url-versions exclude lists pages only, as the dashboard does
516
- cnry technical-aeo page-audit <project> (--node-key <key>|--url <url>) [--run-id <id>] [--format json] # compatibility alias for one page's exact audit evidence
517
- cnry technical-aeo structure <project> [--parent-path <path>] [--cursor <cursor>] [--limit <n>] [--format json|jsonl] # one level of the path hierarchy
518
- cnry technical-aeo links <project> [--source-url <url>] [--target-url <url>] [--followable|--nofollow] [--cursor <cursor>] [--limit <n>] [--format json|jsonl] # bounded internal-link edges
519
- cnry technical-aeo links neighbors <project> (--node-key <key>|--url <url>) [--limit <n>] [--format json] # bounded inbound/outbound neighborhood
520
- cnry technical-aeo dead-links <project> [--cursor <cursor>] [--limit <n>] [--format json|jsonl] # disabled unless the run opted in
521
- cnry technical-aeo score <project> [--format json] # site score + per-factor scorecard (avg + pass/partial/fail per page) + delta vs the previous audit
522
- cnry technical-aeo pages <project> [--status error] [--sort score-asc|score-desc|url] [--format json|jsonl] # audited-page compatibility view (worst-first by default)
523
- cnry technical-aeo trend <project> [--format json|jsonl] # aggregate-score history across past audits
514
+ # Scan
515
+ cnry agent-readiness run <project> [--sitemap-url <url>] [--max-pages <n>] [--max-edges <n>] [--max-depth <n>] [--check-dead-links] [--include-geo] [--no-agent-skills] [--wait] [--format json]
516
+ cnry agent-readiness run <project> --wait # full crawl + audit; without --max-pages uses the project's saved page budget, else the full site (up to 50,000 pages); edges derived from the page count unless --max-edges is set; --wait polls up to 15 minutes, so follow a longer scan with agent-readiness progress
517
+ cnry agent-readiness run <project> --sitemap-url <url> --max-pages 5000 --max-edges 250000 --max-depth 12 # optional crawl seeds and custom budgets; hard caps are 50,000 pages / 1,000,000 edges
518
+ cnry agent-readiness run <project> --check-dead-links --wait # opt in to dead-link checks; they are off by default
519
+ cnry agent-readiness progress <project> --run-id <id> [--format json] # exact durable phase and pages found / checked / failed counters; never a synthesized percentage
524
520
  cnry schedule set <project> --kind site-audit --preset weekly # keep it fresh
525
521
  cnry schedule set <project> --kind site-audit --check-dead-links --include-geo # make every scheduled scan a deep scan (keeps the timing)
522
+
523
+ # Score
524
+ cnry agent-readiness score <project> [--run-id <id>] [--format json] # Agent Readiness score + per-factor scorecard (avg + pass/partial/fail per page) + delta vs the previous scan
525
+ cnry agent-readiness page-scores <project> [--run-id <id>] [--status success|error] [--sort score-asc|score-desc|url] [--limit <n>] [--format json|jsonl] # per-page scores of audited pages (worst-first by default); --status error lists unreachable pages
526
+ cnry agent-readiness trend <project> [--limit <n>] [--format json|jsonl] # Agent Readiness score history across past scans
527
+
528
+ # Scan overview, pages, and findings
529
+ cnry agent-readiness overview <project> [--run-id <id> | --date YYYY-MM-DD] [--format json] # crawl metadata, budgets, completeness, and termination
530
+ cnry agent-readiness pages <project> [--run-id <id> | --date YYYY-MM-DD] [--inventory-eligible true|false] [--fetch-state <state>] [--indexability-state <state>] [--audit-state <state>] [--url-versions include|exclude|only] [--sort url|path|score-asc|score-desc] [--cursor <cursor>] [--limit <n>] [--format json|jsonl] # bounded URL inventory with depth and link score; --url-versions exclude lists pages only, as the dashboard does
531
+ cnry agent-readiness page-audit <project> (--node-key <key>|--url <url>) [--run-id <id>] [--format json] # exact audit score, factor findings, recommendations, and crawl provenance for one graph page
532
+ cnry agent-readiness issues <project> [--run-id <id>] [--cursor <cursor>] [--limit <n>] [--format json|jsonl] # stored findings including passing-score issues, affected-page counts and example URLs
533
+ cnry agent-readiness issue-pages <project> --issue-id <id> [--run-id <id>] [--cursor <cursor>] [--limit <n>] [--format json|jsonl] # affected URLs for one issue; pin the run from the issue list
534
+
535
+ # Site structure and links
536
+ cnry agent-readiness structure <project> [--run-id <id>] [--parent-path <path>] [--cursor <cursor>] [--limit <n>] [--format json|jsonl] # one level of the path hierarchy
537
+ cnry agent-readiness links <project> [--run-id <id>] [--source-url <url>] [--target-url <url>] [--followable|--nofollow] [--cursor <cursor>] [--limit <n>] [--format json|jsonl] # bounded internal-link edges
538
+ cnry agent-readiness neighbors <project> (--node-key <key>|--url <url>) [--run-id <id>] [--limit <n>] [--format json] # bounded inbound/outbound neighborhood of one page
539
+ cnry agent-readiness dead-links <project> [--run-id <id>] [--cursor <cursor>] [--limit <n>] [--format json|jsonl] # disabled unless the run opted in
540
+ cnry agent-readiness subgraph <project> [--run-id <id>] [--node-key <key>|--url <url>] [--hops <n>] [--max-nodes <n>] [--max-edges <n>] [--format json] # focused semantic graph (MCP defaults to 25 nodes / 50 edges)
541
+ cnry agent-readiness path <project> (--to-node-key <key>|--to-url <url>) [--run-id <id>] [--from-node-key <key>|--from-url <url>] [--max-depth <n>] [--format json] # directed shortest followable-link path
542
+ cnry agent-readiness changes <project> [--from-run-id <id>] [--to-run-id <id>] [--scope all|pages|links] [--change all|added|removed|changed] [--cursor <cursor>] [--limit <n>] [--format json|jsonl] # canonical scan diff; either scan ID is optional; JSONL begins with scan/cursor/filter metadata
526
543
  ```
527
544
 
545
+ **Deprecated names.** The older `technical-aeo …` and `site-health …` commands still work as aliases, with the same flags, output, JSON, and exit codes; they will be removed only in a future major version. Four subcommands have new names: `technical-aeo crawl` and `site-health overview` are `agent-readiness overview`; `technical-aeo crawl-pages` and `site-health pages` are `agent-readiness pages`; `technical-aeo pages` is `agent-readiness page-scores`; and `technical-aeo links neighbors` and `site-health neighbors` are `agent-readiness neighbors`. Every other subcommand keeps its name.
546
+
528
547
  Agent integration checks run by default, inspecting discoverable actions, forms, and tool links. Use `--no-agent-skills` to disable them. Geographic checks are off by default; `--include-geo` adds location and service-area signals. Both selections change scan identity. Lighthouse is not exposed by Canonry.
529
548
 
530
549
  A scheduled scan always covers the full site (or the project's saved page budget). Which checks it runs belongs to the schedule: `schedule set --kind site-audit` takes the same `--check-dead-links`, `--include-geo` and `--no-agent-skills` flags as a manual scan, and any of them sets the whole list of checks. `--default-checks` goes back to the defaults (no dead links, no location checks, agent checks on). Without these flags the schedule keeps its checks, so moving, pausing or resuming it never drops them; check flags alone keep the current timing. `schedule show` prints them on a `Checks:` line, and the API returns them as `siteAudit` (PUT `siteAudit`: omit to keep, `null` to reset).
531
550
 
532
- For agent site readiness, begin with `cnry technical-aeo score <project> --format json`. Use `cnry site-health overview <project>` only to add crawl metadata; it never replaces the score. Use `cnry site-health page-audit` (MCP: `canonry_site_health_page_audit`) to tie a selected graph page's audit score to exact findings and fixes. Link score is importance, not an audit verdict. Then request a focused neighborhood, a shortest path, or scan-to-scan changes. Do not ask an agent to materialize the interactive graph: it can exceed the MCP tool-result limit. The matching traversal tools are `canonry_site_health_subgraph`, `canonry_site_health_path`, and `canonry_site_health_changes`; the subgraph tool defaults to a small focused result and should be expanded only when needed.
551
+ To assess Agent Readiness, begin with `cnry agent-readiness score <project> --format json`. Use `cnry agent-readiness overview <project>` only to add crawl metadata; it never replaces the score. Use `cnry agent-readiness page-audit` (MCP: `canonry_agent_readiness_page_audit`) to tie a selected graph page's audit score to exact findings and fixes. Link score is importance, not an audit verdict. Then request a focused neighborhood, a shortest path, or scan-to-scan changes. Do not ask an agent to materialize the interactive graph: it can exceed the MCP tool-result limit. The matching traversal tools are `canonry_agent_readiness_subgraph`, `canonry_agent_readiness_path`, and `canonry_agent_readiness_changes`; the subgraph tool defaults to a small focused result and should be expanded only when needed.
533
552
 
534
- - The score is only available after at least one audit runs — `score` returns `hasData: false` until then.
535
- - URL versions are not scored. A parameter address (a filter, view or cache setting) whose canonical names the same path is a copy of that page: the audit fetches it to learn that, then leaves it out of the score, the factor counts, `technical-aeo pages` and the fix lists. `inventorySummary.urlVersions` counts them, each one's `auditState` is `url-version` with `urlVersionOf` naming its page, and `crawl-pages --url-versions only` lists them. Once 3 addresses with the same parameter pattern (site section plus parameter names) prove to be copies, the crawl stops fetching that pattern, unless one of them turns out to be a page of its own; those copies have no rows, and `inventorySummary.urlVersionsNotFetched` counts them. The URL versions a scan found are `urlVersions + urlVersionsNotFetched`, which is the number the dashboard and `technical-aeo crawl` show. A parameter page with no such canonical (`?page=2`, `?id=123`) is scored like any page. Scans made before this rule scored every address.
536
- - `issues` and `issue-pages` read stored evidence only; `technical-aeo` aliases expose the same commands. JSONL begins with provenance, coverage, total and continuation metadata, including for an empty result. An unavailable cross-page source in an older scan is not a measured zero, and a partial scan cannot establish site-wide absence. Follow the cursor before claiming a complete list. Issue dispositions guide review; advisory metadata findings do not automatically justify an edit or a blog post.
553
+ - The score is only available after at least one scan runs — `score` returns `hasData: false` until then.
554
+ - URL versions are not scored. A parameter address (a filter, view or cache setting) whose canonical names the same path is a copy of that page: the audit fetches it to learn that, then leaves it out of the score, the factor counts, `agent-readiness page-scores` and the fix lists. `inventorySummary.urlVersions` counts them, each one's `auditState` is `url-version` with `urlVersionOf` naming its page, and `agent-readiness pages --url-versions only` lists them. Once 3 addresses with the same parameter pattern (site section plus parameter names) prove to be copies, the crawl stops fetching that pattern, unless one of them turns out to be a page of its own; those copies have no rows, and `inventorySummary.urlVersionsNotFetched` counts them. The URL versions a scan found are `urlVersions + urlVersionsNotFetched`, which is the number the dashboard and `agent-readiness overview` show. A parameter page with no such canonical (`?page=2`, `?id=123`) is scored like any page. Scans made before this rule scored every address.
555
+ - `issues` and `issue-pages` read stored evidence only. JSONL begins with provenance, coverage, total and continuation metadata, including for an empty result. An unavailable cross-page source in an older scan is not a measured zero, and a partial scan cannot establish site-wide absence. Follow the cursor before claiming a complete list. Issue dispositions guide review; advisory metadata findings do not automatically justify an edit or a blog post.
537
556
  - Reads without `--run-id` use the newest scan that published a crawl, including one that stopped at its page budget (`complete: false`, `termination: max-pages`). A failed, cancelled, or still-running scan never replaces it. `changes` compares complete crawls only.
538
557
  - Graph reads are server-paged and bounded. Use `--run-id` to inspect a specific retained run.
539
558
  - A subgraph with `countAccuracy: "lower-bound"` hit a traversal cap: totals and omissions are minimums, not site-wide counts. Its `complete` and `termination` fields apply to every observation.
@@ -721,7 +740,7 @@ cnry schedule set <project> --cron "0 9 * * *" --timezone America/New_York
721
740
  cnry schedule set <project> --kind data-refresh --preset daily # refresh all connected GSC/Bing (coverage + full data sync)/GA/GBP integrations (no --source)
722
741
  cnry schedule set <project> --kind backlinks-sync --preset weekly # re-probe Common Crawl; sync only when a newer rolling window is published (no --source/--provider)
723
742
  cnry doctor --project <project> --check site.reachability # probe the homepage now; the server also probes every 10 min and pages health.degraded after two failed passes
724
- cnry schedule set <project> --kind site-audit --preset weekly # Technical AEO: bounded full-site crawl and audit (no --source/--provider)
743
+ cnry schedule set <project> --kind site-audit --preset weekly # Agent Readiness audit scan: bounded full-site crawl and audit (no --source/--provider)
725
744
  cnry schedule set <project> --kind site-audit --check-dead-links --include-geo # weekly deep scan: dead links + location checks on every scheduled scan
726
745
  cnry schedule set <project> --kind seo-sync --cron '0 7 2 * *' # paid SEO refresh of every saved profile, monthly (needs seo.run; no --source/--provider)
727
746
  cnry schedule show <project>
@@ -757,6 +776,16 @@ A webhook that lists either event receives **only the health events it lists**,
757
776
 
758
777
  > **No mention events yet.** Notification events cover the citation signal only — there are **no** `mention.lost` / `mention.gained` events today. For mention-first monitoring, read `scores.mentionCoverage` / `scores.mentionShare` via `cnry overview` (or the `insight.*` events, which can be driven by mention-side insights); do not wire automation to mention events that aren't emitted.
759
778
 
779
+ **What changed.** `cnry overview <project>` prints the dashboard's "What changed" rows: the queries that lost a mention and those that lost a citation in the latest check (each query once, the two signals counted apart, with the engines, locations, and for Advanced the Properties and markets that lost it) and the runs in progress. `cnry overview --all` prints the portfolio's rows under its table. JSON carries them as `whatChanged`: `lostMentions` / `lostCitations` (`queryCount`, at most 25 `queries`, `truncated`), `activeRuns`, the rows as `items`, and `comparison`. Simple compares each engine's answer with its previous check of the same query and location; Advanced compares each Property's answer with the comparable previous sweep.
780
+
781
+ `comparison` is `{ "state": "available", "previousRunIds" }` when the latest check was compared. Otherwise it is `{ "state": "unavailable", "reason" }`, and the lost counts are 0 because nothing was compared, not because nothing was lost:
782
+
783
+ - `not-measured`: the project has no completed check yet. It says nothing about any answer's citation.
784
+ - `no-previous-check`: the latest check is the first, so there is nothing earlier to compare it with.
785
+ - Advanced only: `definition-changed` (the previous sweep measured another plan definition), `model-changed` (it asked other engines or models), `partial-run` (the latest or previous sweep is partial), `legacy-plan` (a schema v1 plan freezes no Property definition; republish setup) and `unreadable` (the previous sweep can't be read).
786
+
787
+ A project whose overview `cnry overview --all` can't read keeps its table row, marked "Couldn't be loaded" with the reason, and What changed names it instead of counting it as clear, as the dashboard does. In JSON it is `{ "project", "error": { "code", "message" } }` in its place. Once everything is printed the command exits with `OVERVIEW_INCOMPLETE` (`details.projects` names them): `2` when a retry may help (a server or network failure), otherwise `1`. Exit `0` means every element is a full overview.
788
+
760
789
  ## Provider Settings & Quotas
761
790
 
762
791
  ```bash
@@ -765,6 +794,12 @@ cnry settings --format json
765
794
  cnry settings reload-providers --format json # apply the server's saved provider config
766
795
  cnry settings provider gemini --api-key <KEY> --model gemini-flash-latest
767
796
  cnry settings provider openai --max-per-day 1000 --max-per-minute 20
797
+ cnry settings provider-info openai --format json # saved config, defaults and project impact
798
+ cnry settings provider test openai --format json # check the saved key with the provider now
799
+ cnry settings provider openai --reset-model --reset-limits
800
+ cnry settings provider local --remove-key # remove an optional Local key
801
+ cnry settings disconnect-provider openai # inspect impact; makes no changes
802
+ cnry settings disconnect-provider openai --confirm --if-impact-revision <REVISION>
768
803
  cnry settings provider perplexity --api-key <KEY> --model fast
769
804
  cnry settings provider muse --api-key <KEY> --model muse-spark-1.3
770
805
  cnry settings provider openrouter --api-key <KEY>
@@ -772,7 +807,40 @@ cnry settings provider openrouter --api-key <KEY>
772
807
 
773
808
  Perplexity runs on its Agent API. `--model` takes a preset (`fast` default, `low`, `medium`, `high`, `xhigh`) or a `vendor/model` slug such as `perplexity/sonar`. Retired Sonar names still work and run as their replacement (`sonar` → `fast`, `sonar-pro` → `low`).
774
809
 
775
- Quota flags: `--max-concurrent`, `--max-per-minute`, `--max-per-day`
810
+ Quota flags: `--max-concurrent`, `--max-per-minute`, `--max-per-day`.
811
+ Use `--reset-limits` to inherit all connection limits, or repeat
812
+ `--reset-quota maxConcurrency|maxRequestsPerMinute|maxRequestsPerDay` for individual
813
+ fields. `--reset-model` removes the connection override. `--reset-base-url` restores a built-in endpoint (Local requires an explicit endpoint). Project overrides remain
814
+ independent. `--if-revision <revision>` from `provider-info` rejects stale updates.
815
+
816
+ `provider-info` is a stored read: it exposes no credentials and makes no vendor
817
+ calls. It returns editable capabilities, effective/default/overridden limits,
818
+ host-managed Vertex/batch/pricing settings, active-work blockers, and projects
819
+ that reference the connection through defaults, schedules or Advanced plans/drafts.
820
+ Disconnect previews use the same response. Without `--confirm` no state changes;
821
+ with it, the connection is removed from the connected server's saved config and
822
+ runtime. Project selections and result history remain available, and the key is
823
+ not revoked at its vendor. Reconnect before running those engine selections again.
824
+ Pass the preview's `impactRevision` with `--if-impact-revision` to reject changed
825
+ project impact. Resets and disconnect require install administrator authority and
826
+ `settings.write`; host-managed credentials must be changed on the host.
827
+
828
+ `provider test <name>` checks a saved connection with the provider now, using
829
+ the server's active settings, and changes no settings. Most answer engines answer one
830
+ tiny prompt on the provider account; Local lists its models; OpenRouter reads its
831
+ key; a search answer service with no free key check makes no request. `status` is
832
+ `connected`, `failed` (with the provider's redacted reason; no answer within 30
833
+ seconds also fails) or `not-verified` (saved, but the first real request is the
834
+ first proof). Exit 0 for `connected` and `not-verified`, 2 for `failed` after the
835
+ result is printed, 1 for an unknown or unconfigured provider, or one whose
836
+ previous test is still running. It requires install administrator authority and
837
+ `settings.write` (a project-scoped or narrower key is refused) and refuses
838
+ read-only and `--stored-only` use. A passing test proves the key and endpoint,
839
+ not the account's quota. The server keeps the latest result as the connection's
840
+ `lastTest`, shown by `settings` and `provider-info` (JSON and text), until the
841
+ key, endpoint, model, Vertex project, region or credentials, or CDP endpoint
842
+ changes, or the connection is disconnected; request-limit, batch and pricing
843
+ edits keep it. Read it before testing again.
776
844
 
777
845
  `reload-providers` rereads the connected server's config file: provider keys,
778
846
  models, Vertex AI settings, batch settings, pricing, quotas, and CDP. Any config
@@ -1884,11 +1952,12 @@ Compact reference for the composite / keyed commands agents read most (shapes ca
1884
1952
 
1885
1953
  | Command | JSON output shape (top-level keys → DTO) | `jsonl` |
1886
1954
  |---|---|---|
1955
+ | `cnry project setup <p>` | `ProjectSetupDto` @ `contracts/project-setup.ts`: `{ project{name,displayName,canonicalDomain}, complete, firstResultAt, nextAction, tracks{ agentReadiness{state,runId,startedAt,finishedAt,progress{phase,pagesFound,pagesChecked,pageLimit,linksFound,pagesFailed}\|null,result{score,pagesScored}\|null,error}, aiVisibility{state,runId,runIds[],startedAt,finishedAt,baselineRunId,mode,blockedBy,engines[],queries{total,nonBrand,branded,unclassified},progress{answersSaved,answersPlanned}\|null,providerFailures,result{first,answers,engines,queryCounts,queryCountsByClass}\|null,error,failedEngines[{provider,code,message,skipped}]\|null} } }`. `complete` = each track has produced a result (a completed or partial non-probe run) at least once; a newer run in progress does not undo it. | → degrades to the `json` document |
1887
1956
  | `cnry status --all [--problems]` | `InstanceStatusDto` @ `contracts/status.ts`: `{ generatedAt, summary{projects,ok,failing,warning,stale,neverSynced}, projects[{project,displayName,state,health,integrations[]}] }`. Stored reads only; a successful read exits 0 even when rows are failing. | One integration per line as `{project, health, ...row}`; a project without integration rows emits `{project,state,health}`. Single-project `status <p>` keeps its JSON envelope and emits one compact record for JSONL. Stored health failures and warnings affect project state, summary and `--problems`; explicit project reads always retain health. |
1888
1957
  | `cnry doctor [--project p] [--all] [--report-month YYYY-MM]` | `{ scope, project, reportMonths?, generatedAt, durationMs, summary{total,ok,warn,fail,skipped}, checks[] }` — `DoctorReportDto` @ `contracts/doctor.ts`. `checks[]` = `CheckResultDto{ id, category, scope, title, status(ok\|warn\|fail\|skipped), notificationPolicy?(health\|silent), code, summary, remediation?, details?, durationMs }`. `report.*` checks are silent report-readiness advisories: they never page or change health state. `--report-month` picks the month they grade (default: the current month, plus the closed month through day 3, when only the closed month is graded). With `--all`: an object keyed by `__global__` + each project name, each value a full report. | ✅ one check / line as `{project, …check}`; still exits non-zero if any `fail` |
1889
- | `cnry analytics <p> [--feature metrics\|gaps\|sources] [--window 7d\|30d\|90d\|all]` | Object **keyed by feature**: `{ metrics?, gaps?, sources? }` (all three present with no `--feature`; one with `--feature X`). `metrics`=`BrandMetricsDto{ window, buckets[], overall, byProvider, trend, mentionTrend, queryChanges[] }`; `gaps`=`GapAnalysisDto{ cited[], gap[], uncited[], mentionedQueries[], mentionGap[], notMentioned[], runId, window }` (each `[]`=`GapQuery`); `sources`=`SourceBreakdownDto` (same shape as `cnry sources`, below). @ `contracts/analytics.ts` | → degrades to the `json` document |
1958
+ | `cnry analytics <p> [--feature metrics\|gaps\|sources] [--window 7d\|30d\|90d\|all]` | Object **keyed by feature**: `{ metrics?, gaps?, sources? }` (all three present with no `--feature`; one with `--feature X`). `metrics`=`BrandMetricsDto{ window, buckets[], overall, byProvider, trend, mentionTrend, queryChanges[] }` (each metric's `citationRate` is over `citedTotal`, the answers whose sources were checked, and null when no answer carries sources; the human output prints "not measured"); `gaps`=`GapAnalysisDto{ cited[], gap[], uncited[], citationNotMeasured[], mentionedQueries[], mentionGap[], notMentioned[], runId, window }` (each `[]`=`GapQuery`; `citationNotMeasured` holds queries whose latest answers all lack sources; `consistency.citedRuns` is out of `consistency.citedTotalRuns`); `sources`=`SourceBreakdownDto` (same shape as `cnry sources`, below). @ `contracts/analytics.ts` | → degrades to the `json` document |
1890
1959
  | `cnry sources <p> [--rank] [--limit N] [--by-provider] [--window …]` | `SourceBreakdownDto{ overall[], byQuery, ranked, byProvider, runId, window, limit }` @ `contracts/analytics.ts`. `ranked`/each `byProvider[name]` = `RankedSourceList{ totalCitedSlots, domainTotal, entries[], truncatedDomainCount, truncatedCitedSlots, bySurfaceClass[] }`; `entries[]`=`SourceRankEntry{ domain, count, percentage, category, label, surfaceClass }`; `bySurfaceClass[]`=`SurfaceClassCount{ surfaceClass, label, count, percentage, domainCount }`. `surfaceClass` ∈ own \| direct-competitor \| ota-aggregator \| editorial-media \| other. | ✅ streams `ranked.entries` one / line as `{project, …entry}` |
1891
- | `cnry visibility-stats <p> [--since <iso>] [--until <iso>] [--month <YYYY-MM>] [--last-runs N] [--by-provider] [--share-of-voice] [--query-class branded\|non-brand]` | `VisibilityStatsDto{ project, groupBy, window{since,until,lastRuns,runCount}, totals, byProvider?[], queries[], shareOfVoice? }` @ `contracts/visibility-stats.ts`. Each query / provider / totals entry = `{ total, checked, mentioned, cited, mentionRate, citedRate }` (+ `query`/`queryId`/`firstObserved`/`lastObserved` on queries, + `provider`/observed on provider entries). `checked`=snapshots with non-null `answerMentioned` (tri-state n for mention); `mentionRate=mentioned/checked`, `citedRate=cited/total`, both `null` on a 0 denominator. `byProvider`/per-query `providers` present only with `--by-provider`; counts sum to pooled. `--month YYYY-MM` echoes the resolved `window.since`/`until`. `shareOfVoice` present only with `--share-of-voice` = `{ queryClass, percent, projectMentions, competitorMentions, snapshotsWithAnswerText, perCompetitor[{domain,mentions}] }`; `percent` (0-100) = `projectMentions/(projectMentions+competitorMentions)`, `null` when no competitors configured. `queryClass` is what was actually served: `non-brand` (the default), `branded` (via `--query-class`), or `pooled` — which appears ONLY when the project has no usable brand alias to split by, never as a default. Branded and non-brand never share a denominator. | ✅ streams `queries` one / line as `{project, runCount, …query}` (envelope-only `shareOfVoice` not in the jsonl rows) |
1960
+ | `cnry visibility-stats <p> [--since <iso>] [--until <iso>] [--month <YYYY-MM>] [--last-runs N] [--by-provider] [--share-of-voice] [--query-class branded\|non-brand]` | `VisibilityStatsDto{ project, groupBy, window{since,until,lastRuns,runCount}, totals, byProvider?[], queries[], shareOfVoice? }` @ `contracts/visibility-stats.ts`. Each query / provider / totals entry = `{ total, citationNotMeasured, checked, mentioned, cited, mentionRate, citedRate }` (+ `query`/`queryId`/`firstObserved`/`lastObserved` on queries, + `provider`/observed on provider entries). `checked`=snapshots with non-null `answerMentioned` (tri-state n for mention); `citationNotMeasured`=snapshots whose engine returns no sources (never a miss); `mentionRate=mentioned/checked`, `citedRate=cited/(total-citationNotMeasured)`, both `null` on a 0 denominator. `byProvider`/per-query `providers` present only with `--by-provider`; counts sum to pooled. `--month YYYY-MM` echoes the resolved `window.since`/`until`. `shareOfVoice` present only with `--share-of-voice` = `{ queryClass, percent, projectMentions, competitorMentions, snapshotsWithAnswerText, perCompetitor[{domain,mentions}] }`; `percent` (0-100) = `projectMentions/(projectMentions+competitorMentions)`, `null` when no competitors configured. `queryClass` is what was actually served: `non-brand` (the default), `branded` (via `--query-class`), or `pooled` — which appears ONLY when the project has no usable brand alias to split by, never as a default. Branded and non-brand never share a denominator. | ✅ streams `queries` one / line as `{project, runCount, …query}` (envelope-only `shareOfVoice` not in the jsonl rows) |
1892
1961
  | `cnry trends <p> --surface index-coverage\|site-health\|backlinks [--window 7d\|30d\|90d\|all \| --start YYYY-MM-DD --end YYYY-MM-DD]` | `TrendsResponse{ project, surface, window (null for a date range), startDate, endDate, groups[] }` @ `contracts/trends.ts`. Each group = `{ key, label, figures[] }`; each figure = `TrendFigure{ key, label, unit(count\|score\|fraction), better(higher\|lower\|neither), value, asOf, change{ difference, ratio, tone(better\|worse\|neutral), basis{ type: first-in-window, date, observedAt, label } }\|null, series[{ date, observedAt, label, value }], recordedFrom }`. A group that never recorded a reading is omitted; an empty surface has no groups and null dates. | → degrades to the `json` document |
1893
1962
  | `cnry google coverage <p>` (index coverage) | `{ summary{total,indexed,notIndexed,deindexed,percentage}, lastInspectedAt, lastSyncedAt, indexed[], notIndexed[], deindexed[], reasonGroups[] }` — `GscCoverageSummaryDto` @ `contracts/google.ts`. `indexed[]`/`notIndexed[]`=`GscUrlInspectionDto`, `deindexed[]`=`GscDeindexedRowDto`. | → degrades to the `json` document. The single-array reads `google inspections` / `coverage-history` / `deindexed` **stream** `jsonl`. |
1894
1963
  | `cnry google query-totals <p> [--start …] [--end …] [--window …] [--limit N] [--offset N]` | `GscQueryTotalsDto{ rows[], totalMatching, truncated, window }` @ `contracts/google.ts`. Each row = `{ query, clicks, impressions, ctr, position, days, source(google\|page-summed\|mixed) }`; `window` = `GscWindowRange{ startDate, endDate, latestDataDate, daysSinceLatestData }` plus `earliestDataDate` (first stored per-query date, `null` with none; a window starting before it is only partly covered). Named queries only: the rows do not add up to the property total. | Streams one line per query, each tagged with `project` and `window`. |
@@ -0,0 +1,2 @@
1
+ import{r as e}from"./vendor-markdown-BBGbNklj.js";import{Aa as t,Na as n,r}from"./ToneBadge-DcwPruHr.js";import{D as i,E as a,h as o}from"./ChartPrimitives-CtKdkn64.js";import{n as s,r as c}from"./account-context-CoJVqEXZ.js";import{a as l,i as u,n as d,r as f,t as ee}from"./card-Dwmc7Srx.js";function p(e,t=[]){let n=e.trim().split(`/`),r=n.length>1?n.shift():``,i=n.join(`/`),a=r===`google`&&/^gemini(?:[-_]|$)/i.test(i)?`Google`:r===`anthropic`&&/^claude(?:[-_]|$)/i.test(i)?`Anthropic`:r===`openai`&&/^(?:gpt(?:[-_]|$)|o\d)/i.test(i)?`OpenAI`:``,o=t.find(t=>t.id===e)?.displayName.trim();if(o){let e=`${a}:`;return a&&o.startsWith(e)&&o.slice(e.length).trim()||o}let s=e=>e.replace(/(?<!\d)[-_]|[-_](?!\d)/g,` `).replace(/:\s*/g,`: `).replace(/\b[a-z]+/gi,e=>e.toLowerCase()===`gpt`?`GPT`:e.toLowerCase()===`ai`?`AI`:e===`o`?e:e.charAt(0).toUpperCase()+e.slice(1)).replace(/\bGPT (?=\d)/g,`GPT-`),c=s(i);return r&&!a?`${s(r)}: ${c}`:c}var m=`__cited__`,h=`__mentioned__`,g=`__mentionShare__`;function _(e){let t=e,{dataStartDate:n,dataEndDate:r}=t;return typeof n!=`string`||typeof r!=`string`?null:{start:o(n),end:o(r),sweepCount:t.sweepCount??1}}function v(e,t=new Date){return i(e,t)}function y(e,t=new Date){let n=_(e);if(!n)return null;let r=v(n.start,t),i=v(n.end,t);return r===i?r:`${r} to ${i}`}function b(e,t=new Date){let n=_(e),r=y(e,t);return!n||!r?`Check date unavailable`:`${r} · ${n.sweepCount} ${n.sweepCount===1?`check`:`checks`}`}function x(e){let t=_(e);return t?a(t.start):``}function S(e){return e.trim().toLowerCase()}function C(e,t={}){return e.map(e=>{let n=e.trim().toLowerCase(),r=(t[e]??[]).map(e=>e.trim().toLowerCase()).filter(Boolean).sort();return n&&r.length>0?`${n}=${r.join(`|`)}`:n}).filter(Boolean).sort().join(`
2
+ `)}function te(e,r){let i=n(e);return i?`${p(i,r?.find(e=>e.name===`openrouter`)?.knownModels??r?.find(t=>t.name===e)?.knownModels)} via OpenRouter`:r?.find(t=>t.name===e)?.displayName?.trim()||t(e)}function w(e){return Math.round(e*1e3)/10}function T(e){return e===null?null:w(e)}function E(e){return e.byProvider??{}}function D(e,t,n){let r=e.buckets.length>0,i=e.buckets.length===1;if(n===`overall`){let n=t===`cited`?m:h,a=t===`cited`?`citationRate`:`mentionRate`;return{rows:e.buckets.map(e=>({date:e.startDate,[n]:T(e[a])})),series:[n],hasData:r,singleBucket:i}}let a=t===`mentioned`?`mentionRate`:`citationRate`,o=[...new Set(e.buckets.flatMap(e=>Object.keys(E(e))))].sort();return{rows:e.buckets.map(e=>{let t={date:e.startDate},n=E(e);for(let e of o){let r=n[e];t[e]=r?T(r[a]):null}return t}),series:o,hasData:r,singleBucket:i}}function O(e){let t=e;return Object.hasOwn(t,`modelEvidenceByProvider`)?t.modelEvidenceByProvider??{}:null}function ne(e){let t=e;return Object.hasOwn(t,`modelAttribution`)?t.modelAttribution??{}:null}function re(e){return e.servedModelAttribution??{}}function ie(e){return e.modelServiceMismatch??{}}function ae(e){return e.modelPointerChanges??{}}function k(e){return e.length<=1?e[0]??``:`${e.slice(0,-1).join(`, `)} and ${e[e.length-1]}`}function A(e){switch(e.status){case`known`:return e.model.includes(`/`)?p(e.model):e.model;case`unknown`:return`Unknown model`;case`mixed`:return e.models.length===0?`Unknown model`:`${e.models.map(e=>e.includes(`/`)?p(e):e).join(`, `)}${e.includesUnknown?` and an unknown model`:``}`}}function j(e){return k([...e.models.map(e=>e.includes(`/`)?p(e):e),...e.includesUnknown?[`an unknown model`]:[]])}function M(e){let t=new Map;for(let[n,r]of Object.entries(e))for(let e of r.events){let r=t.get(e.bucketStartDate)??[];r.push({provider:n,event:e}),t.set(e.bucketStartDate,r)}return[...t.entries()].sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>({bucketStartDate:e,events:[...t].sort((e,t)=>e.provider.localeCompare(t.provider)||e.event.observedAt.localeCompare(t.event.observedAt))}))}function N(e){let t={},n=[];for(let[r,i]of Object.entries(e)){let e=i.events.filter(e=>!e.fromPreWindowAnchor);for(let e of i.events)e.fromPreWindowAnchor&&n.push({provider:r,event:e});e.length>0&&(t[r]={...i,events:e})}return{buckets:M(t),beforeWindow:n.sort((e,t)=>e.event.observedAt.localeCompare(t.event.observedAt)||e.provider.localeCompare(t.provider))}}function P(e){let t=[];for(let[n,r]of Object.entries(e)){let e=r.eventTotal??r.events.length;e>r.events.length&&t.push({provider:n,shown:r.events.length,total:e})}return t.sort((e,t)=>e.provider.localeCompare(t.provider))}function F(e){let t=e.buckets.map(e=>{let t=e.mentionShare;return{date:e.startDate,[g]:t?.rate==null?null:w(t.rate)}}),n=t.map(e=>e[g]).filter(e=>typeof e==`number`&&Number.isFinite(e));return{rows:t,series:[g],hasData:n.length>0,singleBucket:n.length===1}}function I(e,t,n){return t===`mentionShare`?F(e):D(e,t,n)}function L(e,t){if(t===`mentionShare`)return e.buckets.flatMap(e=>{let t=e.mentionShare?.rate;return typeof t==`number`&&Number.isFinite(t)?[t]:[]});let n=t===`cited`?`citationRate`:`mentionRate`;return e.buckets.flatMap(e=>{let t=e[n];return t===null?[]:[t]})}function R(e){return e.buckets.some(e=>e.citationRate!==null)}function z(e,t){let n=e.buckets.flatMap(e=>{let n=E(e)[t];return n?[n]:[]});return n.length>0&&n.every(e=>e.citationRate===null)}function B(e,t){let n=e.windowChange;if(!n)return null;switch(t){case`cited`:return n.citationRate;case`mentioned`:return n.mentionRate;case`mentionShare`:return n.mentionShare}}function V(e,t,n){let r=n===`mentioned`?`mentionRate`:`citationRate`;for(let n=e.buckets.length-1;n>=0;n--){let i=E(e.buckets[n])[t]?.[r];if(i!=null)return i}return null}function H(e){return Date.parse(e)}function U(e,t){let n=_(e);if(!n)return!1;let r=H(t);return r>=H(n.start)&&r<=H(n.end)}function W(e){return e.basketChanges??[]}function oe(e,t){return t.filter(t=>U(e,t.at)).sort((e,t)=>H(e.at)-H(t.at))}function G(e,t){return S(e)===`perplexity`&&t.status===`known`&&!t.model.includes(`/`)}function K(e){return Object.entries(e).filter(([e,t])=>!G(e,t.configured)).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>({provider:e,mismatch:t}))}function q(e){return[...W(e)].sort((e,t)=>H(t.at)-H(e.at)).map(e=>({at:o(e.at),added:e.added,removed:e.removed}))}function se(e,t){let n=[...e.events].sort((e,t)=>H(e.observedAt)-H(t.observedAt)),r=n[0]?.from??e.latestObservation.state;for(let e of n)H(e.observedAt)<=H(t)&&(r=e.to);return r}function J(e,t,n,r){return{provider:e,at:o(t.observedAt),onOrBefore:t.fromPreWindowAnchor===!0,anchorAt:t.anchorObservedAt?o(t.anchorObservedAt):null,from:t.from,to:t.to,served:n,reroute:r}}function ce(e,t){let n=[];for(let[r,i]of Object.entries(e)){let e=t[r]?.events??[],a=new Set(i.events.map(e=>e.observedAt));for(let t of i.events){let i=G(r,t.to)?e.find(e=>e.observedAt===t.observedAt):void 0;n.push(J(r,t,i?{from:i.from,to:i.to}:null,!1))}for(let t of e)a.has(t.observedAt)||G(r,se(i,t.observedAt))&&n.push(J(r,t,null,!0))}return n.sort((e,t)=>H(t.at)-H(e.at)||e.provider.localeCompare(t.provider))}function le({latest:e,queryChanges:t,modelRows:n,now:r=new Date}){let i=[...t.map(e=>e.at),...n.map(e=>e.at)];if(i.length===0)return null;let a=t=>e!==void 0&&U(e,t),o=t.filter(e=>a(e.at)),s=o.reduce((e,t)=>e+t.added.length,0),c=o.reduce((e,t)=>e+t.removed.length,0),l=n.filter(e=>a(e.at)).length,u=e?y(e,r):null;if(u===null||s+c+l===0)return`No changes since ${v(i.reduce((e,t)=>H(t)>H(e)?t:e),r)}`;let d=[u];return s>0&&d.push(`${s} ${s===1?`query`:`queries`} added`),c>0&&d.push(`${c} ${c===1?`query`:`queries`} removed`),l>0&&d.push(`${l} new ${l===1?`model`:`models`}`),d.join(` · `)}function ue(e,t,n,r=null){let i=H(e);if(!Number.isFinite(i))return null;let a=t.filter(e=>H(e)<i);if(a.length>0)return o(a.reduce((e,t)=>H(t)>H(e)?t:e));let s=n.map(e=>_(e)).filter(e=>e!==null).sort((e,t)=>H(e.start)-H(t.start)),c=s.findIndex(e=>H(e.start)<=i&&i<=H(e.end));if(c===-1)return null;let l=s[c];return H(l.start)===i?c>0?s[c-1].end:r&&H(r)<i?o(r):null:l.sweepCount===2&&H(l.end)===i?l.start:null}function de(e){let t=new Map,n=(e,n)=>{let r=t.get(e);r?r.push(n):t.set(e,[n])};for(let t of[...e].sort((e,t)=>e.revision-t.revision)){let e=H(t.at);for(let r of t.removed)n(r,{at:e,member:!1});for(let r of t.added)n(r,{at:e,member:!0})}return new Map([...t].filter(([,e])=>e.at(-1).member))}function Y(e,t){let n=!e[0].member;for(let r of e)r.at<=t&&(n=r.member);return n}function fe(e,t,n){return e.some(e=>e.at>t&&e.at<=n)}function X(e,t){let n=H(t.start),r=H(t.end);return t.sweepCount>2?fe(e,n,r):Y(e,n)!==Y(e,r)}function Z(e,t,n,r){let i=H(t),a=H(n),o={added:[],removed:[],removedAndAddedBack:[],addedAndRemovedAgain:[]},s=new Set;for(let[t,n]of de(e)){if(!r(n))continue;s.add(t);let e=Y(n,i),c=Y(n,a);e===c?(e?o.removedAndAddedBack:o.addedAndRemovedAgain).push(t):(c?o.added:o.removed).push(t)}if(s.size===0)return null;let c=e=>[...e.added,...e.removed].some(e=>s.has(e)),l=e.filter(e=>H(e.at)>i&&H(e.at)<=a&&c(e)).sort((e,t)=>H(e.at)-H(t.at));return{changes:l,keys:[...new Set(l.flatMap(e=>[...e.added,...e.removed]))].filter(e=>s.has(e)),moves:o}}function pe(e,t){if(e.length<2)return null;let n=e[0],r=e.at(-1),i=_(n),a=_(r),o=i?.start??n.startDate,s=a?.end??r.endDate,c=Z(t,o,s,e=>Y(e,H(o))!==Y(e,H(s))||i!==null&&X(e,i)||a!==null&&X(e,a));return!c&&n.queryCount===r.queryCount?null:{...c??{changes:[],keys:[],moves:{added:[],removed:[],removedAndAddedBack:[],addedAndRemovedAgain:[]}},firstCount:n.queryCount,latestCount:r.queryCount}}function me(e,t){let n=_(e);if(!n)return null;let r=Z(t,n.start,n.end,e=>X(e,n));return r?{...r,firstCount:e.queryCount,latestCount:e.queryCount}:null}function Q(e){return`${e} ${e===1?`query`:`queries`}`}function he(e){let t=[],n=e=>t.length===0?Q(e):String(e);return e.added.length>0&&t.push(`${n(e.added.length)} added`),e.removed.length>0&&t.push(`${n(e.removed.length)} removed`),e.removedAndAddedBack.length>0&&t.push(`${Q(e.removedAndAddedBack.length)} removed and added back`),e.addedAndRemovedAgain.length>0&&t.push(`${Q(e.addedAndRemovedAgain.length)} added and removed again`),k(t)}function ge(e,t,n,r){return e?t!==`mentionShare`||n!==`non-brand`||!r?!1:e.keys.length>0&&e.keys.every(e=>r(e)===`branded`):!0}var $=e();function _e({disabled:e,title:t,...n}){let{canWrite:i}=c();return i?(0,$.jsx)(r,{disabled:e,title:t,...n}):(0,$.jsx)(r,{...n,disabled:!0,title:s})}function ve({title:e,children:t}){let{isAdmin:n}=c();return n?(0,$.jsx)($.Fragment,{children:t}):(0,$.jsxs)(ee,{className:`surface-card mx-auto mt-8 max-w-lg`,children:[(0,$.jsxs)(u,{children:[(0,$.jsxs)(l,{children:[e,` is for administrators`]}),(0,$.jsx)(f,{children:`Your account has view-only access. You can read everything this install has measured; changing how it is set up needs an administrator account.`})]}),(0,$.jsx)(d,{children:(0,$.jsx)(`p`,{className:`supporting-copy`,children:`If you need to make a change here, ask an administrator on this install.`})})]})}export{W as A,P as B,L as C,q as D,te as E,ie as F,p as H,re as I,ge as L,_ as M,ne as N,he as O,ae as P,K as R,N as S,z as T,le as V,k as _,g as a,ce as b,R as c,x as d,y as f,G as g,v as h,h as i,O as j,pe as k,C as l,A as m,_e as n,oe as o,j as p,m as r,I as s,ve as t,b as u,V as v,me as w,S as x,B as y,ue as z};
@@ -0,0 +1,36 @@
1
+ import{r as e}from"./rolldown-runtime-XKyaHtl9.js";import{H as t,W as n,ft as r,tt as i}from"./vendor-lucide-iE5MEh2-.js";import{r as a}from"./vendor-markdown-BBGbNklj.js";import{Dt as o,Et as s,Fn as c,Ot as l,ii as u,ni as d,nn as f,r as p,ri as m,rn as h,t as g,ut as _}from"./ToneBadge-DcwPruHr.js";import{r as v}from"./account-context-CoJVqEXZ.js";import{t as y}from"./async-handler-Bj3TnIRw.js";import{rt as b,st as x}from"./index-mqc2mWUR.js";var S=e(r(),1),C=`Use the official Canonry docs:
2
+ - Agent quickstart: https://github.com/Canonry/canonry#or-use-any-shell-capable-coding-agent
3
+ - CLI reference: https://github.com/Canonry/canonry/blob/main/skills/canonry/references/canonry-cli.md
4
+ - Plugin setup: https://github.com/Canonry/canonry/blob/main/docs/plugins.md
5
+ - MCP setup: https://github.com/Canonry/canonry/blob/main/docs/mcp.md
6
+
7
+ `,w=`Help me set up Canonry for my public site.
8
+
9
+ ${C}Pick one path and stay on it. Use connected Canonry tools (plugin or MCP) only if you have them and I have not pointed you at a specific install. A connected tool runs in its own process and never sees \`CANONRY_CONFIG_DIR\`, \`CANONRY_PORT\`, or anything else you export in a shell, so it always acts on my default install. If I named a config directory, a port, or a sandbox, that is the shell path: use \`cnry\` and tell me which path you chose. Never mix the two in one run, and never create a duplicate project. The \`cnry\` and \`canonry\` commands are interchangeable.
10
+
11
+ 1. Ask for my public domain, country, and language. Do not create or scan anything yet. Country and language are only applied when a project is created, so if you reuse an existing project, report its country and language instead of changing them.
12
+ 2. Shell path only: confirm \`cnry\` is on PATH, then run \`cnry --version\`. If Canonry is missing, propose \`npm install -g @canonry/canonry\` and wait for approval. Then run \`cnry doctor --format json\`, which is the command that says whether config, database, and server are in place. \`cnry --version\` does not read config and succeeds on a completely unconfigured install, so it cannot answer this. If config is missing, run \`cnry bootstrap\` yourself: it is not interactive, takes about a second, and is safe to rerun. Do not hand it to me and wait. The interactive command is \`cnry init\`, which is optional provider and OAuth setup that Agent Readiness does not need. Bootstrap prints an API key, so do not repeat its output back to me, and never ask me to paste passwords, API keys, OAuth credentials, or command output.
13
+ 3. Confirm the API is reachable. \`cnry doctor --format json\` reports it, and any project read exits non-zero with \`CONNECTION_ERROR\` when it is not. If it is unreachable, propose \`cnry start\` and wait for approval. Use \`cnry start\`, which is the background daemon, and not \`cnry serve\`, which runs in the foreground and will block you until I stop it, even though some error messages suggest it. Stop anything you started with \`cnry stop\`.
14
+ 4. List projects with the connected project tool or \`cnry project list --format json\`, and reuse one whose domain matches. Confirm the proposed name is not already assigned to a different domain. To find out whether a project has already been scanned, run \`cnry agent-readiness score <project> --format json\` with no \`--run-id\`, which reports the latest run. Read the \`hasData\` field, not the score: this command exits 0 and reports \`aggregateScore: 0\` for a project that has never been scanned, so reading the score alone would have you tell me my site scored zero. If \`hasData\` is true and \`runStatus\` is \`completed\` or \`partial\`, read that scan instead of starting a new one. If no project matches, show the exact create operation and wait for approval.
15
+ 5. Propose a bounded Agent Readiness audit scan: \`--max-pages 100\` for a first look, plus whether dead-link checking is on (it is off unless you pass \`--check-dead-links\`). Show the connected operation or the exact \`cnry agent-readiness run <project> --max-pages 100 --wait --format json\` command with the project name filled in, and wait for separate approval before scanning. \`--wait\` polls for up to 15 minutes and returns only the run id and status. If the status it returns is still \`queued\` or \`running\`, the scan has not finished: do not rerun it or report results, poll \`cnry agent-readiness progress <project> --run-id <run-id> --format json\` until it is terminal. If \`--wait\` outruns your own tool timeout first, recover the run id with \`cnry runs <project> --kind site-audit --limit 1 --format json\`, which returns the newest scan whatever its status, and poll the same way.
16
+ 6. When the run is \`completed\` or \`partial\`, read \`cnry agent-readiness overview <project> --run-id <run-id> --format json\` first. It is the only one of these commands that carries \`termination\` and \`complete\`; the score and page-scores commands do not. Then read \`cnry agent-readiness score <project> --run-id <run-id> --format json\` and \`cnry agent-readiness page-scores <project> --run-id <run-id> --sort score-asc --limit 10 --format jsonl\`. Tell me the termination reason in plain words and whether the scan covered the whole site or stopped at a page, link, depth, or time limit. A \`partial\` run scored the pages it reached and not my site, so never present it as a full-site result. If it stopped early, the fix depends on the reason, so say which: a page or depth limit needs a larger budget, a time limit needs a smaller scan (a lower \`--max-pages\` or \`--max-depth\`). If the run failed or was cancelled, inspect the run error and stop.
17
+ 7. Summarize only completed evidence, then propose AI Visibility setup. Ask before you add queries, connect providers, start a provider-backed or quota-consuming run, edit files, or publish.`;function T(e){return e?w:w.replace(C,``)}var E=a(),D=`https://github.com/Canonry/canonry/blob/main/docs/mcp.md`,O=`https://github.com/Canonry/canonry/blob/main/docs/plugins.md`,k=`https://help.openai.com/en/articles/12584461-connecting-custom-mcp-servers-in-chatgpt`,A=`https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp`,j=`codex plugin marketplace add Canonry/canonry
18
+ codex plugin add canonry-remote@canonry`,M={[m[`public-url-required`]]:`Configure this instance’s public URL before connecting a hosted client.`,[m[`https-required`]]:`Configure an HTTPS public URL for this instance.`,[m[`public-host-required`]]:`Use a public HTTPS address that your client can reach. Localhost and private-network addresses do not work for hosted connectors.`,[m[`public-path-mismatch`]]:`Make the publicUrl path match basePath, and preserve that prefix when the proxy forwards requests for hosted OAuth.`,[m[`oauth-unavailable`]]:`The sign-in routes are unavailable. Check this instance’s OAuth configuration.`,[m[`account-required`]]:`Create a named Canonry account on this instance’s server with canonry user create. A shared dashboard password does not provide an OAuth account.`,[m[`transport-unavailable`]]:`Restore the instance’s HTTP MCP service before connecting a hosted client.`},N=[{client:`Claude Code`,command:`claude plugin marketplace add Canonry/canonry
19
+ claude plugin install canonry@canonry`},{client:`Codex`,command:`codex plugin marketplace add Canonry/canonry
20
+ codex plugin add canonry@canonry`}],P=`canonry-mcp`;function F({label:e,value:t,showText:r=!1,secondary:a=!1,action:o=`agent_setup.copy`}){let s=(0,S.useId)(),[l,u]=(0,S.useState)(!1),[d,f]=(0,S.useState)(!1);async function m(){try{await navigator.clipboard.writeText(t),c(o),u(!0),f(!1)}catch{u(!1),f(!0)}}return(0,E.jsxs)(`div`,{className:`min-w-0 space-y-3`,children:[r?(0,E.jsx)(`pre`,{className:`overflow-x-auto material-base p-3 text-copy-14 text-heading`,children:(0,E.jsx)(`code`,{children:t})}):null,(0,E.jsxs)(p,{type:`button`,className:`pointer-coarse:min-h-11`,variant:a?`outline`:`default`,onClick:y(m),children:[l?(0,E.jsx)(i,{className:`size-4`,"aria-hidden":`true`}):(0,E.jsx)(n,{className:`size-4`,"aria-hidden":`true`}),e]}),l?(0,E.jsx)(`p`,{role:`status`,className:`text-copy-14 text-secondary`,children:`Copied.`}):null,!r||d?(0,E.jsxs)(`details`,{open:d||void 0,className:`text-label-14 text-secondary`,children:[(0,E.jsx)(`summary`,{className:`min-h-11 w-fit cursor-pointer py-3`,children:d?`Copy unavailable. Select the text below.`:`Preview text`}),(0,E.jsxs)(`label`,{htmlFor:s,className:`sr-only`,children:[e,` text`]}),(0,E.jsx)(`textarea`,{id:s,readOnly:!0,rows:d&&r?3:8,value:t,className:`mt-2 w-full rounded-md border border-default bg-bg p-3 text-copy-14 text-heading`})]}):null]})}function I(){return s()||l()?(0,E.jsxs)(`div`,{className:`page-container`,children:[(0,E.jsx)(`h1`,{className:`page-title`,children:`Agent setup`}),(0,E.jsx)(`p`,{className:`mt-3 text-copy-14 text-secondary`,children:`Open Agent setup in your own Canonry instance to connect your agent.`}),h()?(0,E.jsx)(`a`,{className:`mt-4 inline-block text-label-14 underline`,href:D,target:`_blank`,rel:`noopener noreferrer`,children:`Read the connection guide`}):null]}):(0,E.jsx)(L,{})}function L(){let e=x(),{isAdmin:n}=v(),r=f(),i=h(),a=b(!e,e?.health),s=a.data?.apiStatus,c=s?.state===`ok`?s.mcp:void 0,l=c?.status===u.available&&c.hosted?.status===d.ready?c.hosted:void 0,p=c?.hosted?.status===d.unavailable?c.hosted.reason:void 0,m=`${window.location.origin}${_()}`,y=`This server has not confirmed a public URL and OAuth sign-in. Ask your administrator to check the connection configuration or update Canonry.`;a.isPending?y=`Checking the instance’s hosted connection settings.`:p?y=n&&r?M[p]:`Ask your Canonry administrator to finish hosted connection setup for this instance.`:s?.state===`error`?y=`The instance’s connection settings could not be checked. Try again after the service is available.`:c?.status===u[`not-supported`]&&(y=`This deployment does not provide hosted MCP connections. Ask your administrator for a supported connection.`);let S=l?`Help me connect this existing Canonry instance to ChatGPT or Claude Desktop using its remote MCP URL: ${l.url}
21
+
22
+ Use the client's remote MCP connection flow with OAuth sign-in. For Claude, set the OAuth client option to Register automatically; Claude's published identity is not supported here, and on Team or Enterprise plans an Owner adds custom connectors. Have me sign in to Canonry in the browser and review the requested access before approving it. Keep passwords, API keys, browser sessions, and other credentials out of chat. Do not install software, run local commands, create another instance, or broaden access.
23
+
24
+ ChatGPT guide: ${k}
25
+ Claude guide: ${A}
26
+
27
+ After authorization, enable the connection in this conversation. Call canonry_help with intent "status", follow its available tool guidance, and list accessible projects using stored reads only. Confirm the target instance and report whether the read succeeded; an empty list can still mean success. Do not run scans, sweeps, research, live provider checks, or other writes.`:void 0,C=r&&!l&&o(window.location.hostname),w=(0,E.jsx)(g,{tone:l?`positive`:p||s?.state===`error`?`caution`:`neutral`,children:l?`Hosted setup configured`:a.isPending?`Checking hosted setup`:p?`Hosted setup needs attention`:`Hosted setup not confirmed`});return(0,E.jsxs)(`div`,{className:`page-container`,children:[(0,E.jsx)(`div`,{className:`page-header`,children:(0,E.jsxs)(`div`,{className:`page-header-left`,children:[(0,E.jsx)(`h1`,{className:`page-title`,children:`Agent setup`}),(0,E.jsx)(`p`,{className:`page-subtitle`,children:r?`Connect Claude Code, Codex, ChatGPT, Claude Desktop, or another agent to this instance.`:`Connect ChatGPT or Claude Desktop to this instance.`})]})}),(0,E.jsxs)(`div`,{className:`max-w-3xl`,children:[(0,E.jsxs)(`div`,{className:`flex flex-wrap items-center justify-between gap-3 border-b border-default pb-5`,children:[(0,E.jsxs)(`div`,{className:`min-w-0`,children:[(0,E.jsx)(`p`,{className:`text-copy-14-strong text-heading`,children:`Your Canonry instance`}),(0,E.jsx)(`p`,{className:`mt-1 break-all text-copy-14 text-secondary`,children:m})]}),C?null:w]}),C?(0,E.jsx)(V,{instanceUrl:m,showResourceLinks:i,open:!0}):null,l&&S?l.plugin?(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(R,{plugin:l.plugin}),(0,E.jsxs)(`details`,{className:`border-t border-default py-3 text-label-14`,children:[(0,E.jsx)(`summary`,{className:`min-h-11 w-fit cursor-pointer py-3 text-label-14-strong text-heading`,children:`Connect directly (Claude and other MCP clients)`}),(0,E.jsx)(z,{url:l.url,setupPrompt:S})]})]}):(0,E.jsx)(z,{url:l.url,setupPrompt:S}):(0,E.jsxs)(`section`,{className:C?`border-t border-default py-6`:`py-6`,"aria-labelledby":`agent-requirements-title`,children:[(0,E.jsxs)(`div`,{className:`flex flex-wrap items-center gap-3`,children:[(0,E.jsx)(`h2`,{id:`agent-requirements-title`,className:`text-heading-20 text-heading`,children:C?`Connect ChatGPT or Claude Desktop`:`Before you connect a hosted client`}),C?w:null]}),(0,E.jsx)(`p`,{className:`mt-2 max-w-prose text-copy-14 text-secondary`,children:y}),i?(0,E.jsxs)(`a`,{href:D,target:`_blank`,rel:`noopener noreferrer`,className:`mt-3 inline-flex min-h-11 items-center gap-2 text-label-14 underline`,children:[`Read the connection guide `,(0,E.jsx)(t,{className:`size-3.5`,"aria-hidden":`true`})]}):null]}),r&&!C?(0,E.jsx)(V,{instanceUrl:m,showResourceLinks:i,open:!1}):null]})]})}function R({plugin:e}){return(0,E.jsxs)(`section`,{className:`py-6`,"aria-labelledby":`agent-plugin-title`,children:[(0,E.jsx)(`h2`,{id:`agent-plugin-title`,className:`text-heading-20 text-heading`,children:`Use the Canonry plugin`}),(0,E.jsx)(`p`,{className:`mt-2 max-w-prose text-copy-14 text-secondary`,children:`For the ChatGPT desktop app and Codex. The plugin opens this workspace with your Canonry sign-in and reads stored results only.`}),(0,E.jsxs)(`ol`,{className:`mt-5 space-y-6`,children:[(0,E.jsxs)(`li`,{className:`space-y-3`,children:[(0,E.jsx)(`h3`,{className:`text-label-14-strong text-heading`,children:`1. Install the plugin`}),(0,E.jsx)(`p`,{className:`max-w-prose text-copy-14 text-secondary`,children:`Add the Canonry marketplace, then install Canonry. In Codex, run:`}),(0,E.jsx)(F,{label:`Copy plugin install commands`,value:j,showText:!0,secondary:!0})]}),(0,E.jsxs)(`li`,{className:`space-y-3`,children:[(0,E.jsx)(`h3`,{className:`text-label-14-strong text-heading`,children:`2. Enter this workspace`}),(0,E.jsx)(`p`,{className:`max-w-prose text-copy-14 text-secondary`,children:`When Canonry asks which workspace to open, enter:`}),(0,E.jsx)(F,{label:`Copy workspace`,value:e.workspace,showText:!0,secondary:!0},e.workspace)]}),(0,E.jsxs)(`li`,{className:`space-y-2`,children:[(0,E.jsx)(`h3`,{className:`text-label-14-strong text-heading`,children:`3. Sign in and approve access`}),(0,E.jsx)(`p`,{className:`max-w-prose text-copy-14 text-secondary`,children:`Sign in with your Canonry account for this workspace and approve read access. The plugin cannot start sweeps or change settings.`})]})]})]})}function z({url:e,setupPrompt:t}){return(0,E.jsxs)(E.Fragment,{children:[(0,E.jsxs)(`section`,{className:`py-6`,"aria-labelledby":`agent-connect-title`,children:[(0,E.jsx)(`h2`,{id:`agent-connect-title`,className:`text-heading-20 text-heading`,children:`1. Add Canonry to your client`}),(0,E.jsx)(`p`,{className:`mt-2 mb-4 max-w-prose text-copy-14 text-secondary`,children:`Add this URL to your client’s remote connector settings. No local installation is needed.`}),(0,E.jsx)(F,{label:`Copy MCP URL`,value:e,showText:!0},e),(0,E.jsxs)(`div`,{className:`mt-4 flex flex-wrap gap-x-5 gap-y-2 text-label-14 text-secondary`,children:[(0,E.jsx)(`a`,{href:k,target:`_blank`,rel:`noopener noreferrer`,className:`underline`,children:`ChatGPT setup guide`}),(0,E.jsx)(`a`,{href:A,target:`_blank`,rel:`noopener noreferrer`,className:`underline`,children:`Claude Desktop setup guide`})]}),(0,E.jsx)(`p`,{className:`mt-2 max-w-prose text-copy-14 text-secondary`,children:`Choose OAuth. In Claude, set the OAuth client to “Register automatically”. On Claude Team and Enterprise plans, an Owner adds the connector first.`}),(0,E.jsx)(`div`,{className:`mt-4`,children:(0,E.jsx)(F,{label:`Copy setup prompt`,value:t,secondary:!0},t)})]}),(0,E.jsxs)(`section`,{className:`border-t border-default py-6`,"aria-labelledby":`agent-authorize-title`,children:[(0,E.jsx)(`h2`,{id:`agent-authorize-title`,className:`text-heading-20 text-heading`,children:`2. Sign in and approve access`}),(0,E.jsx)(`p`,{className:`mt-2 max-w-prose text-copy-14 text-secondary`,children:`Sign in with your Canonry account and review the requested access. After approving it, return to your client and enable the connection for your conversation.`})]}),(0,E.jsxs)(`section`,{className:`border-t border-default py-6`,"aria-labelledby":`agent-verify-title`,children:[(0,E.jsx)(`h2`,{id:`agent-verify-title`,className:`text-heading-20 text-heading`,children:`3. Verify the connection`}),(0,E.jsx)(`p`,{className:`mt-2 mb-4 max-w-prose text-copy-14 text-secondary`,children:`Server configuration is ready. Use this prompt to confirm your agent can read your projects without starting work or using provider quota.`}),(0,E.jsx)(B,{target:e})]})]})}function B({target:e,local:t=!1}){let n=`Verify my existing Canonry connection targets ${e}. Call canonry_help with intent "status", follow its available tool guidance, then list accessible projects using stored reads only. Report the instance, connection method, and whether the project read succeeded. An empty project list can still be a successful connection. If the target cannot be confirmed, stop and explain. Do not install anything, change configuration, create projects, run scans, sweeps, research, or live provider checks.`;return(0,E.jsx)(F,{label:t?`Copy local verification prompt`:`Copy verification prompt`,value:n,secondary:!0,action:`agent_setup.verify_copy`},e)}function V({instanceUrl:e,showResourceLinks:n,open:r}){let i=`Connect my local agent to this existing Canonry instance: ${e}
28
+
29
+ Use an installed Canonry runtime whose private configuration already points to this exact instance. A local plugin or canonry-mcp process does not inherit the browser connection, and shell environment changes do not retarget an already-connected tool. Check the saved API URL before using it.
30
+
31
+ ${n?`Official connection guide: ${D}
32
+ Optional plugin guide for Codex or Claude Code: ${O}
33
+
34
+ `:``}Preserve existing client connections, credentials, and access. Do not create another Canonry instance, bootstrap a config, or create projects. Keep secrets and configuration contents out of chat. Ask before changing client configuration or installing software. Reload the client if needed, then verify with stored reads only. Do not run scans, sweeps, research, live provider checks, or other writes.`,a=`${T(n)}
35
+
36
+ This Canonry dashboard is already running at ${e}. Work only with the install that serves it. If \`cnry doctor\` reports no config, or you cannot confirm your config belongs to this dashboard, stop and ask me which config folder it uses, then pass that folder as \`CANONRY_CONFIG_DIR\` on every \`cnry\` command. Do not bootstrap, start, or create another install.`,o=(0,E.jsxs)(`div`,{className:`space-y-5`,children:[N.map(({client:e,command:t})=>(0,E.jsxs)(`div`,{className:`space-y-2`,children:[(0,E.jsx)(`h3`,{className:`text-label-14-strong text-heading`,children:e}),(0,E.jsx)(F,{label:`Copy ${e} commands`,value:t,showText:!0,secondary:!0})]},e)),(0,E.jsxs)(`div`,{className:`space-y-2`,children:[(0,E.jsx)(`h3`,{className:`text-label-14-strong text-heading`,children:`Other MCP clients`}),(0,E.jsx)(`p`,{className:`max-w-prose text-copy-14 text-secondary`,children:`Add this executable as a stdio MCP server in your client.`}),(0,E.jsx)(F,{label:`Copy MCP command`,value:P,showText:!0,secondary:!0})]}),(0,E.jsx)(`p`,{className:`max-w-prose text-copy-14 text-secondary`,children:`Reload your client after installing the plugin. Keep one Canonry connection per instance.`}),n?(0,E.jsxs)(`a`,{href:O,target:`_blank`,rel:`noopener noreferrer`,className:`inline-flex min-h-11 items-center gap-2 text-label-14 underline`,children:[`Runtime and plugin instructions `,(0,E.jsx)(t,{className:`size-3.5`,"aria-hidden":`true`})]}):null,(0,E.jsx)(B,{target:e,local:!0})]});return r?(0,E.jsxs)(E.Fragment,{children:[(0,E.jsxs)(`section`,{className:`py-6`,"aria-labelledby":`agent-local-title`,children:[(0,E.jsx)(`h2`,{id:`agent-local-title`,className:`text-heading-20 text-heading`,children:`Set up with your agent`}),(0,E.jsx)(`p`,{className:`mt-2 mb-4 max-w-prose text-copy-14 text-secondary`,children:`Paste this into Claude Code, Codex, or any shell-capable coding agent.`}),(0,E.jsx)(F,{label:`Copy setup request`,value:a})]}),(0,E.jsxs)(`section`,{className:`border-t border-default py-6`,"aria-labelledby":`agent-local-plugin-title`,children:[(0,E.jsx)(`h2`,{id:`agent-local-plugin-title`,className:`text-heading-20 text-heading`,children:`Or use the plugin`}),(0,E.jsx)(`p`,{className:`mt-2 mb-5 max-w-prose text-copy-14 text-secondary`,children:`The plugin runs the Canonry install on your computer, not your browser session, so that install’s configuration must point to this instance.`}),o]})]}):(0,E.jsxs)(`details`,{className:`border-t border-default py-3 text-label-14`,children:[(0,E.jsx)(`summary`,{className:`min-h-11 w-fit cursor-pointer py-3 text-label-14-strong text-heading`,children:`Local plugins and other MCP clients`}),(0,E.jsxs)(`div`,{className:`space-y-6 py-4`,children:[(0,E.jsxs)(`div`,{className:`space-y-4`,children:[(0,E.jsx)(`p`,{className:`max-w-prose text-copy-14 text-secondary`,children:`For an agent running on your computer, use a Canonry runtime whose private configuration already points to this instance. The plugin does not inherit your browser connection.`}),(0,E.jsx)(F,{label:`Copy local setup prompt`,value:i,secondary:!0})]}),o]})]})}export{I as AgentSetupPage};
@@ -0,0 +1 @@
1
+ import{r as e}from"./rolldown-runtime-XKyaHtl9.js";import{ft as t}from"./vendor-lucide-iE5MEh2-.js";import{r as n}from"./vendor-markdown-BBGbNklj.js";import{x as r}from"./vendor-tanstack-B90HW50P.js";import{gt as i,r as a,t as o}from"./ToneBadge-DcwPruHr.js";import{i as s,mt as c,pn as l}from"./react-query-BqcEEfEO.js";import{t as u}from"./card-Dwmc7Srx.js";var d=e(t(),1),f=n(),p=100;function m(e){if(e===`all`)return;let t=Number.parseInt(e,10);return new Date(Date.now()-t*24*60*60*1e3).toISOString()}function h(e){let t=e.userAgent?.toLowerCase()??``;return t.includes(`canonry-mcp`)?`MCP`:t.includes(`canonry-cli`)?`CLI`:t.includes(`mozilla/`)?`Dashboard`:e.actor===`scheduler`?`Scheduler`:e.actor===`agent`?`Agent`:e.actor===`mcp`?`MCP`:e.actor===`cli`?`CLI`:e.actor}function g(e){return/delete|remove|revoke|disconnect|failed|refused/.test(e)?`negative`:/create|add|connect|completed/.test(e)?`positive`:/update|replace|sync|apply/.test(e)?`caution`:`neutral`}function _(e){return[e.action,e.actor,e.entityType,e.entityId,e.actorSession,e.userAgent,e.diff==null?``:JSON.stringify(e.diff)].filter(Boolean).join(` `).toLowerCase()}function v({projectName:e}){let[t,n]=(0,d.useState)(0),[v,y]=(0,d.useState)(`90d`),[b,x]=(0,d.useState)(``),[S,C]=(0,d.useState)(``),w=(0,d.useMemo)(()=>m(v),[v]),T={limit:p,offset:t*p,since:w,actor:b||void 0},E=r({...c({client:i,path:{name:e??``},query:T}),enabled:!!e}),D=r({...s({client:i,query:T}),enabled:!e}),O=r({...l({client:i}),enabled:!e}),k=e?E:D,A=k.data??[],j=(0,d.useMemo)(()=>new Map((O.data??[]).map(e=>[e.id,e.displayName||e.name])),[O.data]),M=S.trim().toLowerCase(),N=(0,d.useMemo)(()=>M?A.filter(e=>_(e).includes(M)):A,[A,M]),P=e=>{n(0),e()};return k.isLoading?(0,f.jsx)(`p`,{className:`supporting-copy mt-6`,role:`status`,children:`Loading change history…`}):(0,f.jsxs)(`section`,{className:`mt-6`,"aria-labelledby":`change-history-heading`,children:[(0,f.jsxs)(`div`,{className:`section-head section-head-inline`,children:[(0,f.jsx)(`div`,{children:(0,f.jsx)(`h2`,{id:`change-history-heading`,className:e?void 0:`sr-only`,children:`Change history`})}),(0,f.jsx)(a,{type:`button`,variant:`outline`,size:`sm`,onClick:()=>void k.refetch(),disabled:k.isFetching,children:k.isFetching?`Refreshing…`:`Refresh`})]}),(0,f.jsxs)(`div`,{className:`mt-4 grid gap-2 sm:grid-cols-3`,children:[(0,f.jsx)(`input`,{type:`search`,"aria-label":`Search change history`,value:S,onChange:e=>C(e.target.value),placeholder:`Search actions, entities, or details`,className:`min-h-11 rounded-md border border-base bg-bg px-3 text-label-14 text-strong placeholder-mono-400 focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600`}),(0,f.jsxs)(`select`,{"aria-label":`Filter change history by actor`,value:b,onChange:e=>P(()=>x(e.target.value)),className:`min-h-11 rounded-md border border-base bg-bg px-3 text-label-14 text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600`,children:[(0,f.jsx)(`option`,{value:``,children:`All actors`}),(0,f.jsx)(`option`,{value:`api`,children:`API and dashboard`}),(0,f.jsx)(`option`,{value:`cli`,children:`CLI`}),(0,f.jsx)(`option`,{value:`scheduler`,children:`Scheduler`}),(0,f.jsx)(`option`,{value:`agent`,children:`Agent`}),(0,f.jsx)(`option`,{value:`mcp`,children:`MCP`})]}),(0,f.jsxs)(`select`,{"aria-label":`Filter change history by date range`,value:v,onChange:e=>P(()=>y(e.target.value)),className:`min-h-11 rounded-md border border-base bg-bg px-3 text-label-14 text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600`,children:[(0,f.jsx)(`option`,{value:`30d`,children:`Last 30 days`}),(0,f.jsx)(`option`,{value:`90d`,children:`Last 90 days`}),(0,f.jsx)(`option`,{value:`365d`,children:`Last year`}),(0,f.jsx)(`option`,{value:`all`,children:`All retained history`})]})]}),k.isError?(0,f.jsxs)(u,{className:`surface-card empty-card mt-4`,children:[(0,f.jsx)(`h3`,{children:`Change history unavailable`}),(0,f.jsx)(`p`,{children:k.error instanceof Error?k.error.message:`Could not load the audit trail.`})]}):N.length===0?(0,f.jsxs)(u,{className:`surface-card empty-card mt-4`,children:[(0,f.jsx)(`h3`,{children:`No changes match these filters`}),(0,f.jsx)(`p`,{children:`Widen the date range or clear the actor and search filters.`})]}):(0,f.jsx)(`div`,{className:`evidence-table-wrap mt-4`,children:(0,f.jsxs)(`table`,{className:`evidence-table`,children:[(0,f.jsx)(`thead`,{children:(0,f.jsxs)(`tr`,{children:[(0,f.jsx)(`th`,{children:`When`}),e?null:(0,f.jsx)(`th`,{children:`Scope`}),(0,f.jsx)(`th`,{children:`Action`}),(0,f.jsx)(`th`,{children:`Entity`}),(0,f.jsx)(`th`,{children:`Origin`}),(0,f.jsx)(`th`,{children:`Details`})]})}),(0,f.jsx)(`tbody`,{children:N.map(t=>(0,f.jsxs)(`tr`,{children:[(0,f.jsx)(`td`,{className:`whitespace-nowrap tabular-nums text-muted`,children:new Date(t.createdAt).toLocaleString()}),e?null:(0,f.jsx)(`td`,{className:`text-secondary`,children:t.projectId?j.get(t.projectId)??`Deleted project · ${t.projectId}`:`Instance settings`}),(0,f.jsx)(`td`,{children:(0,f.jsx)(o,{tone:g(t.action),children:t.action})}),(0,f.jsxs)(`td`,{children:[(0,f.jsx)(`span`,{className:`text-strong`,children:t.entityType}),t.entityId?(0,f.jsx)(`span`,{className:`mt-0.5 block max-w-48 truncate text-label-12-mono text-faint`,title:t.entityId,children:t.entityId}):null]}),(0,f.jsxs)(`td`,{children:[(0,f.jsx)(`span`,{className:`text-secondary`,children:h(t)}),t.actorSession?(0,f.jsxs)(`span`,{className:`mt-0.5 block max-w-40 truncate text-label-12-mono text-faint`,title:t.actorSession,children:[`Session `,t.actorSession]}):null]}),(0,f.jsx)(`td`,{children:t.diff==null?(0,f.jsx)(`span`,{className:`text-faint`,children:`No field diff`}):(0,f.jsxs)(`details`,{children:[(0,f.jsx)(`summary`,{className:`min-h-11 cursor-pointer py-3 text-label-14 text-secondary hover:text-strong`,children:`View diff`}),(0,f.jsx)(`pre`,{className:`max-h-72 max-w-xl overflow-auto whitespace-pre-wrap break-words rounded-md bg-bg-elevated p-3 text-copy-13 text-secondary`,children:JSON.stringify(t.diff,null,2)})]})})]},t.id))})]})}),!k.isError&&(t>0||A.length===p)?(0,f.jsxs)(`div`,{className:`mt-4 flex items-center justify-between gap-3`,children:[(0,f.jsx)(a,{type:`button`,variant:`outline`,size:`sm`,disabled:t===0,onClick:()=>n(e=>Math.max(0,e-1)),children:`Previous`}),(0,f.jsxs)(`span`,{className:`text-label-12 tabular-nums text-muted`,children:[`Page `,t+1]}),(0,f.jsx)(a,{type:`button`,variant:`outline`,size:`sm`,disabled:A.length<p,onClick:()=>n(e=>e+1),children:`Next`})]}):null]})}export{v as t};
@@ -0,0 +1 @@
1
+ import{r as e}from"./rolldown-runtime-XKyaHtl9.js";import{H as t,U as n,c as r,ft as i,s as a,w as o}from"./vendor-lucide-iE5MEh2-.js";import{r as s}from"./vendor-markdown-BBGbNklj.js";import{Et as c,Ft as l,R as u,U as d,W as f,Y as p,_n as m,a as h,bt as g,r as _,t as v,z as y}from"./ToneBadge-DcwPruHr.js";import{t as b}from"./async-handler-Bj3TnIRw.js";import{t as x}from"./card-Dwmc7Srx.js";import{t as S}from"./Hint-3TTi0MgX.js";var C=e(i(),1),w=s(),T=`https://commoncrawl.org/web-graphs`;function E(e){return e==null?`—`:e>=0xe8d4a51000?`${(e/0xe8d4a51000).toFixed(1)} TB`:e>=1e9?`${(e/1e9).toFixed(1)} GB`:e>=1e6?`${(e/1e6).toFixed(1)} MB`:e>=1e3?`${(e/1e3).toFixed(1)} KB`:`${e} B`}function D(e){if(!e)return`—`;let t=Date.now()-new Date(e).getTime(),n=Math.floor(t/6e4);if(n<1)return`just now`;if(n<60)return`${n}m ago`;let r=Math.floor(n/60);return r<24?`${r}h ago`:`${Math.floor(r/24)}d ago`}function O(e){switch(e){case`ready`:return`positive`;case`failed`:return`negative`;case`downloading`:case`querying`:case`queued`:return`caution`}}function k(){let[e,i]=(0,C.useState)(null),[s,k]=(0,C.useState)(null),[A,j]=(0,C.useState)([]),[M,N]=(0,C.useState)([]),[P,F]=(0,C.useState)(null),[I,L]=(0,C.useState)(!0),[R,z]=(0,C.useState)(!1),[B,V]=(0,C.useState)(!1),[H,U]=(0,C.useState)(``),[W,G]=(0,C.useState)(!1),[K,q]=(0,C.useState)(null),[J,Y]=(0,C.useState)(null),X=(0,C.useCallback)(async()=>{L(!0),q(null);try{let[e,t,n,r,a]=await Promise.all([u(),f().catch(()=>null),p().catch(()=>[]),y().catch(()=>[]),d().catch(()=>null)]);i(e),k(t),j(n),N(r),F(a)}catch(e){q(e instanceof Error?e.message:`Failed to load backlinks status`)}finally{L(!1)}},[]);(0,C.useEffect)(()=>{X()},[X]);async function Z(){z(!0),q(null),Y(null);try{let e=await g();Y(e.alreadyPresent?`DuckDB already installed (${e.version}).`:`Installed DuckDB ${e.version}.`),await X()}catch(e){q(e instanceof Error?e.message:`Failed to install DuckDB`)}finally{z(!1)}}async function Q(){let e=H.trim()||void 0;V(!0),q(null),Y(null);try{let t=await m(e);Y(e?`Queued sync for ${t.release}. Download + query runs in the background.`:`Queued sync for auto-discovered release ${t.release}. Download + query runs in the background.`),U(``),G(!1),await X()}catch(e){e instanceof h&&e.code===`MISSING_DEPENDENCY`?q(`DuckDB is not installed. Install it first.`):q(e instanceof Error?e.message:`Failed to trigger sync`)}finally{V(!1)}}async function $(e){q(null),Y(null);try{await l(e),Y(`Pruned cached release ${e}.`),await X()}catch(e){q(e instanceof Error?e.message:`Failed to prune release`)}}let ee=s?.status===`ready`&&M.every(e=>e.release!==s.release);return(0,w.jsxs)(`div`,{className:`page-container`,children:[(0,w.jsx)(`div`,{className:`page-header`,children:(0,w.jsxs)(`div`,{className:`page-header-left`,children:[(0,w.jsx)(`h1`,{className:`page-title`,children:`Backlink data`}),(0,w.jsx)(`p`,{className:`page-subtitle`,children:`Manage the shared backlink source and review its latest data.`})]})}),K&&(0,w.jsx)(x,{className:`surface-card p-4 mb-4 bg-negative-soft`,children:(0,w.jsx)(`p`,{className:`text-copy-14 text-negative`,children:K})}),J&&(0,w.jsx)(x,{className:`surface-card p-4 mb-4 bg-positive-soft`,children:(0,w.jsx)(`p`,{className:`text-copy-14 text-positive`,children:J})}),(0,w.jsxs)(`section`,{className:`page-section`,children:[(0,w.jsxs)(`div`,{className:`section-head section-head-inline`,children:[(0,w.jsx)(`div`,{children:(0,w.jsx)(`h2`,{children:`Backlink source`})}),e?.duckdbInstalled?(0,w.jsx)(v,{tone:`positive`,children:`Installed`}):(0,w.jsx)(v,{tone:`caution`,children:`Not installed`})]}),(0,w.jsx)(x,{className:`surface-card p-4`,children:I?(0,w.jsx)(`p`,{className:`text-copy-14 text-muted`,children:`Checking…`}):e?.duckdbInstalled?(0,w.jsx)(`p`,{className:`text-copy-14 text-secondary`,children:`Ready to sync a Common Crawl release.`}):(0,w.jsxs)(`div`,{className:`flex items-center justify-between gap-4`,children:[(0,w.jsx)(`p`,{className:`text-copy-14 text-secondary`,children:`Install the local query engine to enable release syncs.`}),!c()&&(0,w.jsxs)(_,{type:`button`,size:`sm`,disabled:R,onClick:b(Z),children:[(0,w.jsx)(n,{className:`h-4 w-4 mr-1.5`,"aria-hidden":!0}),R?`Installing…`:`Install DuckDB`]})]})})]}),(0,w.jsxs)(`section`,{className:`page-section`,children:[(0,w.jsxs)(`div`,{className:`section-head section-head-inline`,children:[(0,w.jsx)(`div`,{children:(0,w.jsxs)(`h2`,{className:`flex items-center gap-2`,children:[`Release sync`,(0,w.jsx)(S,{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.`})]})}),s&&(0,w.jsx)(v,{tone:O(s.status),children:s.status})]}),(0,w.jsxs)(`div`,{className:`space-y-4`,children:[s?(0,w.jsxs)(`div`,{className:`space-y-2 text-label-14`,children:[(0,w.jsxs)(`p`,{className:`text-strong`,children:[`Release `,(0,w.jsx)(`code`,{className:`text-neutral`,children:s.release})]}),s.phaseDetail&&(0,w.jsx)(`p`,{className:`text-secondary`,children:s.phaseDetail}),(0,w.jsxs)(`div`,{className:`grid grid-cols-2 md:grid-cols-4 gap-4 text-label-12 text-muted pt-2`,children:[(0,w.jsxs)(`div`,{children:[(0,w.jsx)(`p`,{className:`text-faint normal-case text-label-12`,children:`Projects`}),(0,w.jsx)(`p`,{className:`text-neutral mt-0.5`,children:s.projectsProcessed??`—`})]}),(0,w.jsxs)(`div`,{children:[(0,w.jsxs)(`p`,{className:`text-faint normal-case text-label-12 flex items-center gap-1`,children:[`Rows`,(0,w.jsx)(S,{label:`What are rows?`,children:`Total number of (project, referring domain) pairs persisted in SQLite from this sync, across every project in the workspace.`})]}),(0,w.jsx)(`p`,{className:`text-neutral mt-0.5`,children:s.domainsDiscovered??`—`})]}),(0,w.jsxs)(`div`,{children:[(0,w.jsx)(`p`,{className:`text-faint normal-case text-label-12`,children:`Started`}),(0,w.jsx)(`p`,{className:`text-neutral mt-0.5`,children:D(s.downloadStartedAt??s.createdAt)})]}),(0,w.jsxs)(`div`,{children:[(0,w.jsx)(`p`,{className:`text-faint normal-case text-label-12`,children:`Finished`}),(0,w.jsx)(`p`,{className:`text-neutral mt-0.5`,children:D(s.queryFinishedAt)})]})]}),s.error&&(0,w.jsx)(`p`,{className:`text-copy-14 text-negative-400 pt-2`,children:s.error})]}):(0,w.jsx)(`p`,{className:`text-copy-14 text-muted`,children:`No release sync has run in this workspace yet.`}),ee&&(0,w.jsx)(`div`,{className:`mt-4 rounded border border-caution-800/60 bg-caution-950/20 p-3`,children:(0,w.jsxs)(`div`,{className:`flex items-start gap-2`,children:[(0,w.jsx)(a,{className:`h-4 w-4 text-caution-400 shrink-0 mt-0.5`,"aria-hidden":!0}),(0,w.jsxs)(`div`,{className:`text-label-14 text-neutral`,children:[(0,w.jsx)(`p`,{className:`text-copy-14-strong text-caution-200`,children:`Cached files for this release are missing.`}),(0,w.jsx)(`p`,{className:`mt-1 text-secondary`,children:`Sync this release again, or sync the newest release, before refreshing individual projects.`})]})]})}),(0,w.jsxs)(`div`,{className:`space-y-3`,children:[(0,w.jsxs)(`div`,{className:`flex items-start justify-between gap-3 mb-3`,children:[(0,w.jsxs)(`div`,{children:[(0,w.jsx)(`p`,{className:`text-label-12 normal-case text-muted`,children:`Auto-detected release`}),P?(0,w.jsxs)(`p`,{className:`text-copy-14 text-strong mt-0.5`,children:[(0,w.jsx)(`code`,{className:`text-heading`,children:P.release}),(0,w.jsxs)(`span`,{className:`ml-2 text-label-12 text-muted`,children:[`Vertex `,E(P.vertexBytes),`, edges `,E(P.edgesBytes)]})]}):(0,w.jsx)(`p`,{className:`text-copy-14 text-muted mt-0.5`,children:I?`Checking Common Crawl…`:`Could not find a release. Choose one below.`}),(0,w.jsxs)(`a`,{href:T,target:`_blank`,rel:`noopener noreferrer`,className:`mt-1 inline-flex items-center gap-1 text-label-12 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`,(0,w.jsx)(t,{className:`h-3 w-3`,"aria-hidden":!0})]})]}),!c()&&(0,w.jsxs)(`div`,{className:`flex items-center gap-2 shrink-0`,children:[(0,w.jsxs)(_,{type:`button`,size:`sm`,disabled:B||!e?.duckdbInstalled||!P&&!H.trim(),onClick:b(Q),children:[(0,w.jsx)(o,{className:`h-4 w-4 mr-1.5`,"aria-hidden":!0}),B?`Queuing…`:`Run sync`]}),(0,w.jsxs)(S,{label:`What does Run sync do?`,children:[(0,w.jsxs)(`span`,{className:`block`,children:[`Downloads the auto-detected (or chosen) Common Crawl release (~16 GB) to`,` `,(0,w.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.`]}),(0,w.jsxs)(`span`,{className:`mt-2 block text-secondary`,children:[`First time for a release: `,(0,w.jsx)(`span`,{className:`text-strong`,children:`~10–20 min download + ~5 min query`}),`. Re-running the same release later: `,(0,w.jsx)(`span`,{className:`text-strong`,children:`skips download, just re-queries`}),` (~5 min).`]})]})]})]}),c()?null:W?(0,w.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,w.jsx)(`input`,{type:`text`,className:`flex-1 min-w-[240px] rounded border border-strong bg-transparent px-2.5 py-1.5 text-label-14 text-strong placeholder-mono-400 focus:border-mono-500 focus:outline-none`,placeholder:`cc-main-2026-jan-feb-mar`,value:H,onChange:e=>U(e.target.value),disabled:B,autoFocus:!0}),(0,w.jsx)(`button`,{type:`button`,className:`text-button-14 text-secondary hover:text-neutral focus:text-neutral focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded`,onClick:()=>{U(``),G(!1)},disabled:B,children:`Cancel`})]}):(0,w.jsx)(`button`,{type:`button`,className:`text-button-14 text-secondary hover:text-neutral focus:text-neutral focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded`,onClick:()=>G(!0),disabled:B,children:`Use a different release →`})]}),!e?.duckdbInstalled&&(0,w.jsx)(`p`,{className:`text-copy-14 text-secondary mt-2`,children:`Install DuckDB first to enable sync.`})]})]}),(0,w.jsxs)(`details`,{className:`page-section-divider group`,children:[(0,w.jsx)(`summary`,{className:`cursor-pointer text-label-14-strong text-secondary hover:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded`,children:`Technical details and cache`}),(0,w.jsxs)(`div`,{className:`mt-4`,children:[(0,w.jsx)(`p`,{className:`text-copy-14 text-secondary mb-3`,children:`Cached releases support project re-extracts. Pruning a release does not remove saved backlink results.`}),(0,w.jsx)(`div`,{className:`overflow-x-auto`,children:(0,w.jsxs)(`table`,{className:`w-full text-label-14`,children:[(0,w.jsx)(`thead`,{children:(0,w.jsxs)(`tr`,{className:`border-b border-base text-left text-label-12 normal-case text-faint`,children:[(0,w.jsx)(`th`,{className:`px-4 py-2 text-label-12-strong`,children:`Release`}),(0,w.jsx)(`th`,{className:`px-4 py-2 text-label-12-strong`,children:`Sync status`}),(0,w.jsx)(`th`,{className:`px-4 py-2 text-right text-label-12-strong`,children:`Size`}),(0,w.jsx)(`th`,{className:`px-4 py-2 text-label-12-strong`,children:`Last used`}),!c()&&(0,w.jsx)(`th`,{className:`px-4 py-2 text-label-12-strong sr-only`,children:`Actions`})]})}),(0,w.jsxs)(`tbody`,{children:[M.map(e=>(0,w.jsxs)(`tr`,{className:`border-b border-mono-900 last:border-0`,children:[(0,w.jsx)(`td`,{className:`px-4 py-2 text-strong`,children:(0,w.jsx)(`code`,{children:e.release})}),(0,w.jsx)(`td`,{className:`px-4 py-2`,children:e.syncStatus?(0,w.jsx)(v,{tone:O(e.syncStatus),children:e.syncStatus}):(0,w.jsx)(`span`,{className:`text-faint`,children:`—`})}),(0,w.jsx)(`td`,{className:`px-4 py-2 text-right text-secondary tabular-nums`,children:E(e.bytes)}),(0,w.jsx)(`td`,{className:`px-4 py-2 text-secondary`,children:D(e.lastUsedAt)}),!c()&&(0,w.jsx)(`td`,{className:`px-4 py-2 text-right`,children:(0,w.jsxs)(`div`,{className:`inline-flex items-center gap-1`,children:[(0,w.jsxs)(_,{type:`button`,variant:`outline`,size:`sm`,onClick:()=>{$(e.release)},children:[(0,w.jsx)(r,{className:`h-4 w-4 mr-1.5`,"aria-hidden":!0}),`Prune`]}),(0,w.jsx)(S,{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).`})]})})]},e.release)),M.length===0&&(0,w.jsx)(`tr`,{children:(0,w.jsx)(`td`,{className:`px-4 py-4 text-label-14 text-muted`,colSpan:c()?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.`})})]})]})}),(0,w.jsx)(`p`,{className:`mt-3 text-copy-13 text-muted`,children:`Common Crawl files are stored locally. DuckDB reads them; Canonry stores results in its normal database.`})]})]}),A.length>1&&(0,w.jsxs)(`section`,{className:`page-section`,children:[(0,w.jsx)(`div`,{className:`section-head section-head-inline`,children:(0,w.jsx)(`div`,{children:(0,w.jsx)(`h2`,{children:`Past release syncs`})})}),(0,w.jsx)(x,{className:`surface-card overflow-hidden`,children:(0,w.jsxs)(`table`,{className:`w-full text-label-14`,children:[(0,w.jsx)(`thead`,{children:(0,w.jsxs)(`tr`,{className:`border-b border-base text-left text-label-12 normal-case text-faint`,children:[(0,w.jsx)(`th`,{className:`px-4 py-2 text-label-12-strong`,children:`Release`}),(0,w.jsx)(`th`,{className:`px-4 py-2 text-label-12-strong`,children:`Status`}),(0,w.jsx)(`th`,{className:`px-4 py-2 text-right text-label-12-strong`,children:`Projects`}),(0,w.jsx)(`th`,{className:`px-4 py-2 text-right text-label-12-strong`,children:`Rows`}),(0,w.jsx)(`th`,{className:`px-4 py-2 text-label-12-strong`,children:`Finished`})]})}),(0,w.jsx)(`tbody`,{children:A.map(e=>(0,w.jsxs)(`tr`,{className:`border-b border-mono-900 last:border-0`,children:[(0,w.jsx)(`td`,{className:`px-4 py-2 text-strong`,children:(0,w.jsx)(`code`,{children:e.release})}),(0,w.jsx)(`td`,{className:`px-4 py-2`,children:(0,w.jsx)(v,{tone:O(e.status),children:e.status})}),(0,w.jsx)(`td`,{className:`px-4 py-2 text-right text-secondary tabular-nums`,children:e.projectsProcessed??`—`}),(0,w.jsx)(`td`,{className:`px-4 py-2 text-right text-secondary tabular-nums`,children:e.domainsDiscovered??`—`}),(0,w.jsx)(`td`,{className:`px-4 py-2 text-secondary`,children:D(e.queryFinishedAt??e.updatedAt)})]},e.id))})]})})]})]})}export{k as BacklinksPage};
@@ -0,0 +1,3 @@
1
+ import{r as e}from"./vendor-markdown-BBGbNklj.js";import{c as t,f as n,i as r,m as i,r as a,s as o,t as s,u as c}from"./vendor-recharts-D3MLFU3q.js";import{ea as l,wa as u}from"./ToneBadge-DcwPruHr.js";var d={ambiguous:`Property identity unverified`},f=`No answer could be tied to one property`;function p(e){return e.denominator===null||e.denominator===void 0||e.unattributed===void 0||e.unattributed<=0?null:`${e.unattributed} of ${e.denominator+e.unattributed} answers could not be tied to one property`}function m(e){if(e.denominator===null||e.denominator===void 0||e.unchecked===void 0||e.unchecked<=0)return null;let t=e.denominator+e.unchecked+(e.citationNotMeasured??0);return`${e.unchecked} of ${t} ${t===1?`answer`:`answers`} had sources that could not be checked`}var h=`The engine returns no sources, so whether it cites you is not measured.`;function g(e){if(e.denominator===null||e.denominator===void 0||e.citationNotMeasured===void 0||e.citationNotMeasured<=0)return null;let t=e.denominator+(e.unchecked??0)+e.citationNotMeasured;return`${e.citationNotMeasured} of ${t} ${t===1?`answer`:`answers`} came from engines that return no sources`}var _={label:`Not measured`,signal:`Citation not measured`,timeline:`citation not measured`,sources:h},v=function(e){return e.Clicks=`clicks`,e.Impressions=`impressions`,e.CTR=`ctr`,e.Position=`position`,e}({}),y={clicks:`Clicks`,impressions:`Impressions`,ctr:`CTR`,position:`Position`},b=l;function x(e){return e.endsWith(`%`)?{figure:e.slice(0,-1),sign:`%`}:{figure:e,sign:``}}function S(e,t){if(t!==`cited`)return p(e);let n=[m(e),g(e)].filter(e=>e!==null);return n.length===0?null:n.join(`; `)}function C(e,t){let n=S(e,t);if(n===null)return null;let r=t===`cited`?[...e.unchecked?[`${e.unchecked} unchecked`]:[],...e.citationNotMeasured?[`${e.citationNotMeasured} not measured`]:[]]:e.unattributed?[`${e.unattributed} unattributed`]:[],i=t===`cited`?(e.unchecked??0)+(e.citationNotMeasured??0):e.unattributed??0;if(r.length===0)return null;let a=r.join(`, `);return{label:a,text:`${a}. ${n}, so this rate leaves ${i===1?`it`:`them`} out.`}}function w(e,t){let n=u(e);switch(t){case`cited`:return`${n} cited (any page)`;case`emerging`:return`${n} newly cited (any page)`;case`lost`:return`${n} citation lost (any page)`;case`not-cited`:return`${n} not cited (any page)`;case`not-measured`:return`${n} citation not measured`;case`pending`:return`Pending`}}var T={cited:`Cited`,"not-cited":`Not cited`,"not-measured":_.label};function E(e){let t=[];if(e.message&&t.push(e.message),e.providers)for(let[n,r]of Object.entries(e.providers)){let e=`[${n}] ${r.message}`;r.raw===void 0?t.push(e):t.push(`${e}\n\n${JSON.stringify(r.raw,null,2)}`)}return t.length>0?t.join(`
2
+
3
+ `):`Run failed.`}function D(e){return e.split(`-`).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(` `)}function O(e,t,n=new Date){let r=new Date(e);if(Number.isNaN(r.getTime()))return e;let i=r.toLocaleTimeString(`en-US`,{hour:`numeric`,minute:`2-digit`});return t&&new Date(t).toDateString()===r.toDateString()?i:`${k(e,n)}, ${i}`}function k(e,t=new Date){let n=new Date(e);return Number.isNaN(n.getTime())?e:n.getFullYear()===t.getFullYear()?A(e):n.toLocaleDateString(`en-US`,{month:`short`,day:`numeric`,year:`numeric`})}function A(e){let t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleDateString(`en-US`,{month:`short`,day:`numeric`})}function j(e){if(!e)return`—`;try{return new Date(e).toLocaleString()}catch{return e}}function M(e){return e===null?`Unknown`:e?`Pass`:`Fail`}function N(e){return e===0?`12:00 AM`:e<12?`${e}:00 AM`:e===12?`12:00 PM`:`${e-12}:00 PM`}function P(e,t){return e===`twice-daily`?`twice-daily`:e.startsWith(`weekly@`)?`${e}@${t}`:`daily@${t}`}function F(e,t){if(!e)return{freq:`custom`,hour:6,customCron:t};if(e===`twice-daily`)return{freq:`twice-daily`,hour:6,customCron:``};let n=e.match(/^daily(?:@(\d+))?$/);if(n)return{freq:`daily`,hour:n[1]?parseInt(n[1]):6,customCron:``};let r=e.match(/^(weekly@(?:mon|tue|wed|thu|fri|sat|sun))(?:@(\d+))?$/);return r?{freq:r[1],hour:r[2]?parseInt(r[2]):6,customCron:``}:{freq:`custom`,hour:6,customCron:t}}function I(e,t,n){let r=n===`UTC`?`UTC`:n.split(`/`).pop()?.replace(/_/g,` `)??n;if(!e)return`Custom: ${t} · ${r}`;if(e===`twice-daily`)return`Twice a day (6am & 6pm) · ${r}`;let i=e.match(/^daily(?:@(\d+))?$/);if(i)return`Every day at ${N(i[1]?parseInt(i[1]):6)} · ${r}`;let a=e.match(/^weekly@(mon|tue|wed|thu|fri|sat|sun)(?:@(\d+))?$/);if(a){let e={mon:`Monday`,tue:`Tuesday`,wed:`Wednesday`,thu:`Thursday`,fri:`Friday`,sat:`Saturday`,sun:`Sunday`},t=a[2]?parseInt(a[2]):6;return`Every ${e[a[1]]} at ${N(t)} · ${r}`}return`${e} · ${r}`}function L(e,t){return t&&t!==e?`${e} · ${t}`:e}function R(){let e=Intl.DateTimeFormat().resolvedOptions().timeZone,t=new Intl.DateTimeFormat(`en-US`,{timeZoneName:`short`}).formatToParts().find(e=>e.type===`timeZoneName`)?.value;return L(e,t)}var z=e(),B={contentStyle:{backgroundColor:`var(--chart-tooltip-bg, #0a0a0a)`,border:`none`,borderRadius:`var(--radius-material-tooltip)`,boxShadow:`var(--shadow-material-tooltip)`,fontSize:13,lineHeight:`18px`,fontWeight:400},labelStyle:{color:`var(--chart-tooltip-label, #ededed)`},itemStyle:{color:`var(--chart-tooltip-item, #a1a1a1)`}},V={fill:`var(--chart-neutral-text-dim, #a1a1a1)`,fontSize:12},H=`var(--chart-grid, #2e2e2e)`,U=`var(--chart-axis, #2e2e2e)`,W=[`var(--chart-series-1, #34d399)`,`var(--chart-series-2, #60a5fa)`,`var(--chart-series-3, #f472b6)`,`var(--chart-series-4, #facc15)`,`var(--chart-series-5, #a78bfa)`,`var(--chart-series-6, #fb923c)`,`var(--chart-series-7, #22d3ee)`,`var(--chart-series-8, #f87171)`],G={gemini:`#60a5fa`,openai:`#34d399`,claude:`#fb923c`,perplexity:`#22d3ee`,muse:`#818cf8`,local:`#a78bfa`};function K(e,t=0){return G[e]??W[t%W.length]}var q={text:`var(--chart-neutral-text, #a1a1a1)`,textDim:`var(--chart-neutral-text-dim, #a1a1a1)`,textFaint:`var(--chart-neutral-text-faint, #7d7d7d)`,surface:`var(--chart-neutral-surface, #2e2e2e)`,trackSubtle:`var(--chart-neutral-track-subtle, rgb(255 255 255 / 0.04))`,gridLine:`var(--chart-neutral-grid-line, rgb(255 255 255 / 0.06))`},J={positive:`var(--chart-tone-positive, #34d399)`,positiveDeep:`var(--chart-tone-positive-deep, #10b981)`,caution:`var(--chart-tone-caution, #fbbf24)`,negative:`var(--chart-tone-negative, #fb7185)`,neutral:`var(--chart-tone-neutral, #a1a1a1)`};function Y(e){return e}function X(e){let t=/^(\d{4})-(\d{2})-(\d{2})/.exec(e);return t?{year:Number(t[1]),month:Number(t[2]),day:Number(t[3])}:null}function Z(e){let t=String(e),n=X(t);return n?new Date(Date.UTC(n.year,n.month-1,n.day)).toLocaleDateString(void 0,{timeZone:`UTC`,month:`short`,day:`numeric`,year:`numeric`}):t}function Q(e){let t=X(String(e));return t?`${t.month}/${t.day}`:String(e)}function $(e){let t=X(String(e));return t?new Date(Date.UTC(t.year,t.month-1,t.day)).toLocaleDateString(void 0,{timeZone:`UTC`,month:`short`,day:`numeric`}):String(e)}function ee(e){return new Date(e).toLocaleDateString(void 0,{month:`short`,day:`numeric`,year:`numeric`})}function te(e){return new Date(e).toLocaleDateString(void 0,{month:`short`,day:`numeric`})}function ne(e){return new Date(e).getFullYear()}function re(e){let t=new Date(e);return`${t.getMonth()+1}/${t.getDate()}`}function ie({data:e,xKey:l,series:u,height:d=260,xTickFormatter:f,labelFormatter:p,onSelectX:m,selectedX:h,accessibilityLayer:g=!0,ariaLabel:_}){let v=[...new Set(u.map(e=>e.axisId))],y=new Map(u.map(e=>[e.label,e.formatValue])),b=e.map((t,n)=>{let r={...t};for(let t of u){if(!t.trend)continue;let i=t.trend.startIndex??0,a=t.trend.endIndex??e.length-1;n<i||n>a?r[`${t.dataKey}__trend`]=null:r[`${t.dataKey}__trend`]=a<=i?t.trend.start:t.trend.start+(t.trend.end-t.trend.start)*(n-i)/(a-i)}return r});return(0,z.jsx)(i,{width:`100%`,height:d,children:(0,z.jsxs)(s,{data:b,accessibilityLayer:g,"aria-label":_,margin:{top:8,right:8,bottom:4,left:0},onClick:m?e=>{e?.activeLabel!==void 0&&m(String(e.activeLabel))}:void 0,style:m?{cursor:`pointer`}:void 0,children:[(0,z.jsx)(t,{stroke:H,strokeDasharray:`3 3`,vertical:!1}),h!=null&&(0,z.jsx)(c,{x:h,yAxisId:v[0],stroke:q.text,strokeDasharray:`2 3`,strokeWidth:1}),(0,z.jsx)(r,{dataKey:l,tick:V,stroke:U,tickFormatter:f,minTickGap:24}),v.map((e,t)=>{let n=u.find(t=>t.axisId===e);return(0,z.jsx)(a,{yAxisId:e,orientation:t===1?`right`:`left`,hide:t>1,reversed:n.inverted??!1,domain:n.inverted?[1,`auto`]:[0,`auto`],tick:V,stroke:U,width:48,tickFormatter:n.formatValue},e)}),(0,z.jsx)(n,{...B,labelFormatter:e=>{let t=typeof e==`string`||typeof e==`number`?String(e):``;return p?p(t):t},formatter:(e,t)=>{let n=String(t??``);return typeof e==`number`?[y.get(n)?.(e)??e.toLocaleString(),n]:[String(e??``),n]}}),u.filter(e=>e.trend).map(e=>(0,z.jsx)(o,{yAxisId:e.axisId,dataKey:`${e.dataKey}__trend`,name:`${e.label} trend`,stroke:e.color,strokeWidth:1.5,strokeDasharray:`5 4`,strokeOpacity:.55,dot:!1,activeDot:!1,isAnimationActive:!1,legendType:`none`,tooltipType:`none`},`${e.dataKey}-trend`)),u.map(e=>(0,z.jsx)(o,{yAxisId:e.axisId,dataKey:e.dataKey,name:e.label,stroke:e.color,strokeWidth:2,dot:!1,activeDot:{r:3},connectNulls:e.connectNulls??!1,isAnimationActive:!1},e.dataKey))]})})}export{R as A,d as B,M as C,k as D,A as E,b as F,D as I,_ as L,I as M,w as N,O,x as P,h as R,C as S,N as T,K as _,W as a,v as b,ie as c,Q as d,ee as f,ne as g,Y as h,q as i,F as j,j as k,Z as l,re as m,V as n,J as o,te as p,H as r,B as s,U as t,$ as u,T as v,E as w,P as x,y,f as z};