@canonry/canonry 7.61.2 → 7.62.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/agent-workspace/skills/aero/SKILL.md +9 -2
- package/assets/agent-workspace/skills/aero/references/ai-traffic-tracking.md +152 -0
- package/assets/agent-workspace/skills/canonry/SKILL.md +1 -0
- package/assets/agent-workspace/skills/canonry/references/ai-traffic-tracking.md +145 -0
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +8 -4
- package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +14 -3
- package/assets/assets/{AgentSetupPage-CCMNQwRE.js → AgentSetupPage-DtPnVaeF.js} +1 -1
- package/assets/assets/{AuditHistoryPanel-B8mIRI3I.js → AuditHistoryPanel-Ce_oy4OV.js} +1 -1
- package/assets/assets/{BacklinksPage-BNLyIR97.js → BacklinksPage-CrqPWRd8.js} +1 -1
- package/assets/assets/{HistoryPage-DSr9KE16.js → HistoryPage-5v0JM7DN.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-BTg-SFW7.js → MeasurementPropertyPage-Bm5Mrd5a.js} +1 -1
- package/assets/assets/{ProjectPage-SqE0fkEq.js → ProjectPage-DaWUh6iw.js} +1 -1
- package/assets/assets/{RunsPage-gJR17ho1.js → RunsPage-CfdAR2v2.js} +1 -1
- package/assets/assets/{SettingsPage-DY5f_oN4.js → SettingsPage-BFooA7JG.js} +1 -1
- package/assets/assets/{SiteHealthSection-BYx91hP-.js → SiteHealthSection-CerUQgAp.js} +2 -2
- package/assets/assets/{ToneBadge-C3S7aVf3.js → ToneBadge-DKvZcONW.js} +1 -1
- package/assets/assets/{TrafficPage-CCXH5nhh.js → TrafficPage-C5fhZGJ2.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-DTjCsu4-.js → TrafficSourceDetailPage-BoqARHYz.js} +1 -1
- package/assets/assets/{account-context-DozTQM44.js → account-context-D_CDYrs8.js} +1 -1
- package/assets/assets/{card-D8MU7RYV.js → card-C6z0XyOo.js} +1 -1
- package/assets/assets/{extract-error-message-CsVYPTyt.js → extract-error-message-By_wcU8g.js} +1 -1
- package/assets/assets/{format-helpers-CWBooMJJ.js → format-helpers-D3Ubm8vk.js} +1 -1
- package/assets/assets/{index-D5o-caTl.js → index-DSVFZqv6.js} +4 -4
- package/assets/assets/{query-client-DbxlHfUm.js → query-client-BETh6SO9.js} +1 -1
- package/assets/assets/{react-query-D4ZSMg7h.js → react-query-DzDr4w41.js} +1 -1
- package/assets/assets/{react-sigma_core.esm.min-CSRp4hew.js → react-sigma_core.esm.min-3kgtOWbQ.js} +1 -1
- package/assets/assets/{service-CYzJmCXU.js → service-BUaUaize.js} +1 -1
- package/assets/index.html +7 -7
- package/dist/{ads-Z2SNR2F5.js → ads-7WUY5CVC.js} +3 -3
- package/dist/{agent-UUDYXXRD.js → agent-NOA33LVM.js} +7 -7
- package/dist/{agent-ask-KCZKPBY7.js → agent-ask-PKQDFJ7K.js} +3 -3
- package/dist/{agent-conversations-CDQA7HOU.js → agent-conversations-7HVWPSFB.js} +3 -3
- package/dist/{agent-memory-MER75L4L.js → agent-memory-Y4M2EWKA.js} +3 -3
- package/dist/{agent-providers-RH7W5HYA.js → agent-providers-P67FWELV.js} +3 -3
- package/dist/{agent-transcript-VU2VHSZ6.js → agent-transcript-C2BYWPRT.js} +3 -3
- package/dist/{analytics-BDVOFX6O.js → analytics-XHYWNNW7.js} +3 -3
- package/dist/{apply-XJULCLGT.js → apply-6OIWDXQP.js} +3 -3
- package/dist/{backfill-7I5BMA6R.js → backfill-KQPXCUUJ.js} +9 -9
- package/dist/{backlinks-HCSL6KKT.js → backlinks-ASYIZFLQ.js} +3 -3
- package/dist/{bing-AEUPBLV6.js → bing-ZLKJ3MIL.js} +3 -3
- package/dist/{bootstrap-J73OTQWI.js → bootstrap-KMVQY5JD.js} +10 -10
- package/dist/{cdp-2UQER2VR.js → cdp-V2HX4ZLE.js} +7 -7
- package/dist/{chunk-5STJ5LXF.js → chunk-2HNTNKFC.js} +157 -57
- package/dist/{chunk-CZSHCZHW.js → chunk-2LY4FY4N.js} +1 -1
- package/dist/{chunk-COUDJ44G.js → chunk-3B2GBWIA.js} +5 -5
- package/dist/{chunk-YHTU4XBR.js → chunk-5NYS3ABL.js} +2 -2
- package/dist/{chunk-H6QJN6RQ.js → chunk-7EWDQUNH.js} +8 -8
- package/dist/{chunk-XEM6TO4X.js → chunk-AO4P75DI.js} +5 -5
- package/dist/{chunk-GAA7JGB7.js → chunk-APQ5EAL2.js} +2 -2
- package/dist/{chunk-VHHFAAMI.js → chunk-B5CSCGVD.js} +1 -1
- package/dist/{chunk-SQ6AOE57.js → chunk-CJC4AQ2M.js} +2 -2
- package/dist/{chunk-VCSJHXW6.js → chunk-DAKJBAUN.js} +1 -1
- package/dist/{chunk-B5KK54RE.js → chunk-GSHHYR24.js} +3 -3
- package/dist/{chunk-KLB3CTOQ.js → chunk-I3CMCTNE.js} +4 -4
- package/dist/{chunk-P5DZ56CR.js → chunk-IALK4DQJ.js} +1 -1
- package/dist/{chunk-FWRHDY4R.js → chunk-IVZMXHXE.js} +1 -1
- package/dist/{chunk-EKY6GHJ4.js → chunk-KLGHY67V.js} +4 -4
- package/dist/{chunk-XCA546YC.js → chunk-KTQVRQDM.js} +1 -1
- package/dist/{chunk-KX5IRN55.js → chunk-LAILXR2F.js} +1 -1
- package/dist/{chunk-RWHKTH75.js → chunk-LFXKB5FZ.js} +333 -333
- package/dist/{chunk-MRNMDQRB.js → chunk-MMRJUOE2.js} +1 -1
- package/dist/{chunk-OWEIOEOY.js → chunk-O6VSPM5S.js} +20 -20
- package/dist/{chunk-5PYPNDIE.js → chunk-OJVXKPIN.js} +3 -3
- package/dist/{chunk-7VEXNHRX.js → chunk-Q7PTBHNG.js} +1 -1
- package/dist/{chunk-HD7AZIOP.js → chunk-RGD5SDT3.js} +17 -1
- package/dist/{chunk-SZXYGJMI.js → chunk-SDZKHJ6W.js} +2 -2
- package/dist/{chunk-34HGKT6I.js → chunk-SYQLUOR7.js} +1 -1
- package/dist/{chunk-G3ZNUINX.js → chunk-U2WPZZPQ.js} +1 -1
- package/dist/{chunk-IG6ISQYD.js → chunk-U6CNJC2Y.js} +6310 -6261
- package/dist/{chunk-ZB3YVCBL.js → chunk-UUGT7UMF.js} +4 -4
- package/dist/{chunk-SA4CG775.js → chunk-UVDBUMYV.js} +2 -2
- package/dist/{chunk-W5FR6AJP.js → chunk-WKZZILBI.js} +2 -2
- package/dist/{chunk-3A7LI5KI.js → chunk-X3GWBK7N.js} +3 -3
- package/dist/{chunk-5AV2OUCJ.js → chunk-YBDUZBGU.js} +1 -1
- package/dist/{chunk-TYWTIYYM.js → chunk-YJAN2ZDV.js} +3 -3
- package/dist/{chunk-RL7LPPFU.js → chunk-YQGVA64Q.js} +3 -3
- package/dist/{citations-3X5EJNJ3.js → citations-NOCVQPPU.js} +3 -3
- package/dist/{cli-catalog-F3IOEPJK.js → cli-catalog-WOWYZ7Q2.js} +3 -3
- package/dist/{cli-commands-NVNMM4IE.js → cli-commands-VSS42S5I.js} +7 -7
- package/dist/cli.js +13 -13
- package/dist/{competitor-CUPGN4NZ.js → competitor-KQK66W3M.js} +3 -3
- package/dist/{content-FYQT7V5A.js → content-OHOQO5LP.js} +3 -3
- package/dist/{dashboard-4AEKE4XK.js → dashboard-W6MII62Z.js} +6 -6
- package/dist/{demo-GLYZQOTJ.js → demo-23H25IOO.js} +1 -1
- package/dist/{demo-server-24RESPMP.js → demo-server-WYYWTH4H.js} +11 -11
- package/dist/{discover-4IGXLRRE.js → discover-557WRGEK.js} +3 -3
- package/dist/{discover-eval-DGLZO5MD.js → discover-eval-GGRZZHHI.js} +3 -3
- package/dist/{doctor-3MFTUIEU.js → doctor-D5JNJKVE.js} +3 -3
- package/dist/{evidence-ANE4SFP5.js → evidence-DHYYVPWW.js} +3 -3
- package/dist/{export-cmd-X6S5F463.js → export-cmd-M24S3YBY.js} +3 -3
- package/dist/{ga-2UKFAVDJ.js → ga-XM6WXZNP.js} +3 -3
- package/dist/{gbp-Q3V3SQMV.js → gbp-M3S5XYPG.js} +3 -3
- package/dist/{google-D4O4JCM4.js → google-PULCSUOU.js} +4 -4
- package/dist/{google-marketing-6L5327YK.js → google-marketing-AA625HA4.js} +1 -1
- package/dist/{health-cmd-7TPQHGKK.js → health-cmd-LVEW6OBD.js} +3 -3
- package/dist/{history-NNUOPNA7.js → history-32ZYGZGQ.js} +3 -3
- package/dist/index.d.ts +56 -9
- package/dist/index.js +24 -24
- package/dist/{init-U7MG23AF.js → init-VJDDRCBV.js} +11 -11
- package/dist/{insights-DDAP3MRC.js → insights-RK3MJ4UM.js} +3 -3
- package/dist/intelligence-service-UNKFCG5N.js +15 -0
- package/dist/{journeys-I7H25PPF.js → journeys-S4CPNZYU.js} +3 -3
- package/dist/{keys-YUJNNNV3.js → keys-WCXE7WXV.js} +3 -3
- package/dist/{keyword-25DDYT45.js → keyword-HHVLC4HI.js} +3 -3
- package/dist/{logs-FKPJ4L5R.js → logs-VYESWGAN.js} +3 -3
- package/dist/{mcp-ZPLVNLL3.js → mcp-4LRFIOLC.js} +2 -2
- package/dist/mcp.js +9 -9
- package/dist/{measurement-plan-CDY63CLU.js → measurement-plan-AL3EHWRK.js} +5 -5
- package/dist/{notify-67IIWOSF.js → notify-LUDG5PWU.js} +3 -3
- package/dist/{organic-evidence-P2BQK2BV.js → organic-evidence-CIOHMKLD.js} +3 -3
- package/dist/{overview-WHSTSNS2.js → overview-NAZJ6PJG.js} +4 -4
- package/dist/{project-7AMXFZMD.js → project-N3MU5V6T.js} +3 -3
- package/dist/{query-RLKE6JHY.js → query-XMI2FV6G.js} +3 -3
- package/dist/{research-PDOE77HZ.js → research-MQ6QA5TV.js} +3 -3
- package/dist/{results-export-5VKTQLOR.js → results-export-OFEZXCJK.js} +3 -3
- package/dist/{run-QXCB4AWO.js → run-YQS3LCFT.js} +4 -4
- package/dist/{schedule-NDM2GBBW.js → schedule-DF6HCMXN.js} +3 -3
- package/dist/{search-DDRSJUWZ.js → search-MRDJKVOL.js} +3 -3
- package/dist/{serve-U6ZKXTKN.js → serve-OCAFOFS2.js} +25 -25
- package/dist/{session-PPYDP5UU.js → session-5OXLS7NP.js} +12 -12
- package/dist/{settings-Q2ZOEA7H.js → settings-NIF34QPH.js} +3 -3
- package/dist/{skills-PKSEX44J.js → skills-JOSNWG7B.js} +2 -2
- package/dist/{snapshot-URNBVDTA.js → snapshot-32DLVRIJ.js} +3 -3
- package/dist/{src-MAMJVGSJ.js → src-F63SHN5T.js} +2 -2
- package/dist/{status-DDXVX4KE.js → status-IERHHP4C.js} +4 -4
- package/dist/{technical-aeo-OS6KQXAP.js → technical-aeo-D7CA3CK6.js} +3 -3
- package/dist/{telemetry-G7GX2LIY.js → telemetry-DRURFCCJ.js} +4 -4
- package/dist/{tools-OOAF75Y5.js → tools-3IZMSHSY.js} +11 -11
- package/dist/{traffic-A4QW7455.js → traffic-AME5NDMN.js} +27 -5
- package/dist/{users-4WPDDCCR.js → users-4CQ6ATAG.js} +3 -3
- package/dist/{visibility-stats-5BY7GVUV.js → visibility-stats-R5JBISBX.js} +3 -3
- package/dist/{wordpress-T5EYN2WX.js → wordpress-GFK4GR3O.js} +3 -3
- package/package.json +1 -1
- package/dist/intelligence-service-ZJ5X6J3A.js +0 -15
|
@@ -177,12 +177,18 @@ estimate; a candidate burst is not confirmed automation. Read the Cloudflare
|
|
|
177
177
|
`traffic sync`. Queue pull freshness requires an enabled `traffic-sync`
|
|
178
178
|
schedule. Run the `traffic.source.*` doctor checks. Inspect
|
|
179
179
|
`traffic.source.queue-backlog` before you quote current Queue data. If more than
|
|
180
|
-
1,000 messages remain, report
|
|
181
|
-
|
|
180
|
+
1,000 messages remain, report the cause the check names from the last sync's
|
|
181
|
+
stop reason (`cnry runs <project> --kind traffic-sync --format json`).
|
|
182
182
|
Get approval before you run a manual sync or change the schedule. The full
|
|
183
183
|
command reference is in the co-installed
|
|
184
184
|
`canonry/references/canonry-cli.md`.
|
|
185
185
|
|
|
186
|
+
When someone asks whether AI search sent visitors, whether they came back, or
|
|
187
|
+
whether they bought, booked or became leads, read `references/ai-traffic-tracking.md`
|
|
188
|
+
first. Report crawled, read, clicked, came back and converted as separate
|
|
189
|
+
numbers, and never report zero conversions until you have checked that the
|
|
190
|
+
conversion is tracked and reaches GA4.
|
|
191
|
+
|
|
186
192
|
**Diagnosing a stuck Vercel/Cloud Run source:** if `cnry traffic status` shows `status=error` with a recent `lastError` of `refusing to advance` or `ExceedsBillingLimitError`, the source's `lastSyncedAt` has aged past the upstream retention boundary and every sync now throws. Recovery: `cnry traffic reset <project> --source <id> --advance-to-now`. This advances `lastSyncedAt` to NOW and resumes going-forward syncs — historical events in the gap are unrecoverable from the sync path; run `cnry traffic backfill --days N` separately if any of that history is needed (capped at retention).
|
|
187
193
|
|
|
188
194
|
## Judgment Rules
|
|
@@ -260,6 +266,7 @@ Detailed playbooks live alongside this file. Read them on demand when the task m
|
|
|
260
266
|
| `references/aeo-discovery.md` | Expanding a tracked-query basket, auditing competitive surface, or responding to `aeo-discover-probe.completed` |
|
|
261
267
|
| `references/memory-patterns.md` | Deciding whether to remember a fact in agent memory or re-query canonry |
|
|
262
268
|
| `references/reporting.md` | Producing a client-facing weekly or monthly summary |
|
|
269
|
+
| `references/ai-traffic-tracking.md` | Answering whether AI traffic led to visits, return visits, leads, bookings or sales, or whether tracking is broken; generated from the Canonry reference |
|
|
263
270
|
| `references/wordpress-elementor-mcp.md` | Editing WordPress pages with the Elementor MCP integration |
|
|
264
271
|
|
|
265
272
|
Aero (canonry's built-in agent) exposes `list_skill_docs` / `read_skill_doc` tools that walk this directory programmatically. External agents (Claude Code, Codex) can read the files directly.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ai-traffic-tracking
|
|
3
|
+
description: "Crawled, read, clicked, came back, converted: which instrument measures each step, the checks to run before reporting a zero, attribution limits, and user stories such as whether anyone from AI search bought or booked. Read when asked about AI traffic, returning AI visitors, conversions, or broken tracking."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- Generated from skills/canonry/references/ai-traffic-tracking.md by pnpm guide:sync. Do not edit. -->
|
|
7
|
+
|
|
8
|
+
# Tracking AI traffic to visits and conversions
|
|
9
|
+
|
|
10
|
+
Read this before you answer whether AI search sent visitors, whether those
|
|
11
|
+
visitors came back, or whether they bought, booked or became leads. Read it too
|
|
12
|
+
when someone says "our tracking is broken" or "AI referrals dropped".
|
|
13
|
+
|
|
14
|
+
Everything here is a stored read unless it says otherwise. Changing a setting,
|
|
15
|
+
connecting a source, a backfill or a GA sync each need approval for that exact
|
|
16
|
+
action.
|
|
17
|
+
|
|
18
|
+
## The funnel and what measures each step
|
|
19
|
+
|
|
20
|
+
Each step is usually 10 to 100 times smaller than the one above it. Most AI
|
|
21
|
+
answers are read inside the assistant, so a site can be read often and clicked
|
|
22
|
+
rarely. Report every step separately; never pool them.
|
|
23
|
+
|
|
24
|
+
| Step | What it is | Instrument | Read with |
|
|
25
|
+
|---|---|---|---|
|
|
26
|
+
| Crawled | AI and search bots fetch pages to index them | Server-side traffic, `crawler` | `cnry traffic events <p> --kind crawler` (`canonry_traffic_events`) |
|
|
27
|
+
| Read for a user | An assistant fetches a page live while answering someone (ChatGPT-User, Claude-User, Perplexity-User) | Server-side traffic, `ai-user-fetch` | `cnry traffic events <p> --kind ai-user-fetch` |
|
|
28
|
+
| Clicked | A person follows a link from an AI answer | Server-side `ai-referral` hits; GA4 AI sessions | `cnry traffic events <p> --kind ai-referral`; `cnry ga ai-referral-history <p>` (`canonry_ga_ai_referral_history`) |
|
|
29
|
+
| Came back | That person's later visits, from any channel | AI visitor journeys (server logs) | `cnry journeys summary\|list\|show <p>` (`canonry_journeys_summary`, `canonry_journeys_list`, `canonry_journey_get`) |
|
|
30
|
+
| Converted | Lead, booking or purchase | GA4 events named in `measurement.leadEventNames` | `cnry ga measurement-analysis <p> --window 90d --host-scope all` (`canonry_ga_measurement_analysis`) |
|
|
31
|
+
|
|
32
|
+
Server-side hits are requests and GA4 counts sessions, and each instrument
|
|
33
|
+
misses some visits the other sees, so the two never match exactly. Compare
|
|
34
|
+
their trend, not their totals.
|
|
35
|
+
|
|
36
|
+
## Check the instrument before you report a zero
|
|
37
|
+
|
|
38
|
+
A zero means nothing until you know the instrument could have seen it. Work
|
|
39
|
+
through these in order and say which one applies.
|
|
40
|
+
|
|
41
|
+
1. **Is the conversion tracked at all?** Read the project's
|
|
42
|
+
`measurement.leadEventNames` (`cnry project show <p> --format json`). The
|
|
43
|
+
default is only `generate_lead`. Online stores report sales as `purchase`,
|
|
44
|
+
and booking engines often do too. If lead events are zero across every
|
|
45
|
+
channel, not only AI, the instrument is blind: report "conversions are not
|
|
46
|
+
measured", never "AI drove no conversions". Adding an event is a setting
|
|
47
|
+
change (`cnry project update <p> --add-lead-event purchase`) and needs
|
|
48
|
+
approval; the daily refresh then fills about 30 days.
|
|
49
|
+
2. **Does GA4 receive the event?** Read the stored GTM snapshot
|
|
50
|
+
(`cnry gtm snapshots <p>`, then `cnry gtm snapshot <p> <id>`;
|
|
51
|
+
`canonry_gtm_snapshot_get`). If the booking or purchase trigger fires only
|
|
52
|
+
Google Ads tags (`awct`) and no GA4 event tag, GA4 records no conversions.
|
|
53
|
+
The fix is a GA4 Event tag on the same trigger, made by someone with
|
|
54
|
+
publish rights in the site's GTM container. Canonry's GTM access is
|
|
55
|
+
read-only.
|
|
56
|
+
3. **Do two instruments agree?** Compare Canonry's GA4 AI sessions with GA4's
|
|
57
|
+
own `AI Assistant` channel (`cnry ga acquisition <p> --window 30d
|
|
58
|
+
--group-by channel`), and with server-side `ai-referral` hits. Agreement
|
|
59
|
+
between independent instruments rules out an ingestion fault. GA4 may file
|
|
60
|
+
AI shopping links under another channel when they carry a different
|
|
61
|
+
`utm_medium` (for example `feed`); the source still reads `chatgpt.com`.
|
|
62
|
+
4. **Paid or organic?** An AI-referral spike can be an ads test. GA4 rows carry
|
|
63
|
+
`trafficClass`, and server-side referrals split paid from organic. Never
|
|
64
|
+
compare a paid month with organic months, and say "the ads ended" rather
|
|
65
|
+
than "AI traffic dropped" when the paid line is what fell.
|
|
66
|
+
5. **Is the source reading the right site?** Confirm the traffic source's
|
|
67
|
+
service or host actually serves the domain (for example, check which backend
|
|
68
|
+
a load balancer routes the domain to) before you claim a wiring gap.
|
|
69
|
+
6. **Is the source partial?**
|
|
70
|
+
- The default Cloudflare Worker forwards only AI-referred and bot requests,
|
|
71
|
+
so journeys miss later visits until the source is reconnected with
|
|
72
|
+
`--forward-page-views` and the Worker redeployed. Journey reads list such
|
|
73
|
+
sources in `partialSources`.
|
|
74
|
+
- The WordPress logger runs inside WordPress, so it cannot see page views a
|
|
75
|
+
cache serves before WordPress runs.
|
|
76
|
+
- Vercel logs carry no IP, so journey keys use the user agent only
|
|
77
|
+
(`keyBasis`); people with the same device and browser version merge,
|
|
78
|
+
which undercounts people and overstates return rates.
|
|
79
|
+
- A hosted checkout or booking engine on another domain is outside the
|
|
80
|
+
site's server logs.
|
|
81
|
+
7. **Is a Queue backlog hiding recent traffic?** For Cloudflare Queue pull,
|
|
82
|
+
read `cnry traffic status <p>` and the last sync's `syncSummary`
|
|
83
|
+
(`cnry runs <p> --kind traffic-sync --format json`) before quoting recent
|
|
84
|
+
days. Recent days look low while a backlog drains.
|
|
85
|
+
8. **Is it noise?** Unverified "AI user" fetches of `/.env`, admin paths or
|
|
86
|
+
404s are scanners using AI bot names. Quote verified fetches
|
|
87
|
+
(`verificationStatus: verified`) and pages that returned 200.
|
|
88
|
+
|
|
89
|
+
## What attribution can and cannot say
|
|
90
|
+
|
|
91
|
+
- GA4 credits a conversion to the session it happened in. A person who first
|
|
92
|
+
came from ChatGPT and books days later from Google counts as Google. Journeys
|
|
93
|
+
keep the earlier AI touch for 30 days, but they see a conversion only when
|
|
94
|
+
its page (for example a booking confirmation URL) appears in the server logs.
|
|
95
|
+
- A return from an outside payment or booking step can take the credit in GA4
|
|
96
|
+
unless that domain is listed under unwanted referrals in the GA4 web stream
|
|
97
|
+
settings. That is a GA4 admin change for the site owner.
|
|
98
|
+
- A visitor key is a keyed hash of IP and user agent, scoped to the project.
|
|
99
|
+
Never print or ask for visitor IPs.
|
|
100
|
+
- Since iOS 26, Safari freezes the OS version in its user agent; device labels
|
|
101
|
+
use Safari's own version instead, so treat the iOS version as approximate.
|
|
102
|
+
|
|
103
|
+
## User stories
|
|
104
|
+
|
|
105
|
+
**"Did anyone from AI search buy, book or become a lead?"**
|
|
106
|
+
1. Run the instrument checks above, steps 1 and 2 first.
|
|
107
|
+
2. If conversions are measured: `cnry ga measurement-analysis <p> --window 90d
|
|
108
|
+
--host-scope all` and read `leads.aiEngines` per engine and period. Quote
|
|
109
|
+
event counts with their sessions; the lead rate is null when the evidence is
|
|
110
|
+
mixed.
|
|
111
|
+
3. Add journeys for the cross-session view: `cnry journeys list <p> --returned
|
|
112
|
+
true`, then `show` for visitors whose later visits reach a conversion page.
|
|
113
|
+
4. Say what is unmeasured. Example: "ChatGPT sent 120 sessions in 30 days.
|
|
114
|
+
Bookings are not sent to GA4, so we cannot say how many booked."
|
|
115
|
+
|
|
116
|
+
**"A customer says they found us through ChatGPT. Can we trace them?"**
|
|
117
|
+
1. Get the date and time of their purchase, booking or form, and the device if
|
|
118
|
+
known.
|
|
119
|
+
2. `cnry journeys list <p> --engine ChatGPT --since <a few days before>` and
|
|
120
|
+
`cnry journeys show <p> <visitor-key>` for visitors active near that time.
|
|
121
|
+
3. A user-agent match is "likely", never proof, unless the key also includes
|
|
122
|
+
the IP. Reading a provider's raw logs beyond what Canonry stored is a live
|
|
123
|
+
read and needs approval.
|
|
124
|
+
|
|
125
|
+
**"AI referrals dropped. Is our tracking broken?"**
|
|
126
|
+
1. Pull the monthly series from both instruments (server-side `ai-referral` and
|
|
127
|
+
GA4 AI sessions) and split paid from organic.
|
|
128
|
+
2. If both fell together, the traffic changed, not the tracking. Check for an
|
|
129
|
+
ended ads test, a lost citation or mention (`references/aeo-analysis.md`), or
|
|
130
|
+
a site change.
|
|
131
|
+
3. If only one fell, check that instrument: sync errors and stop reasons
|
|
132
|
+
(`cnry traffic status`, `cnry runs <p> --kind traffic-sync`), the GA4 tag,
|
|
133
|
+
or the source's service or host.
|
|
134
|
+
|
|
135
|
+
**"Which of our pages does AI read?"**
|
|
136
|
+
`cnry traffic events <p> --kind ai-user-fetch --since <date>`: keep verified
|
|
137
|
+
fetches with status 200, group by path, and leave out `robots.txt`. These are
|
|
138
|
+
the pages assistants open while answering people, even when nobody clicks.
|
|
139
|
+
|
|
140
|
+
**"Do AI visitors come back?"**
|
|
141
|
+
`cnry journeys summary <p>`: `returnRate`, `returnedVia` and
|
|
142
|
+
`medianDaysToReturn`. Report `keyBasis` and `partialSources` with the numbers;
|
|
143
|
+
a user-agent-only or partial source changes what the rate means.
|
|
144
|
+
|
|
145
|
+
## Reporting language
|
|
146
|
+
|
|
147
|
+
- Lead with the step, the count and the window: "Assistants read the site 300
|
|
148
|
+
times in 30 days (verified); 10 people clicked through; conversions are not
|
|
149
|
+
measured."
|
|
150
|
+
- Keep crawled, read, clicked, came back and converted as separate numbers.
|
|
151
|
+
- Name the blind spot whenever a number is zero or low because an instrument
|
|
152
|
+
cannot see it.
|
|
@@ -258,3 +258,4 @@ Read only references relevant to the requested task. They are not required for M
|
|
|
258
258
|
- [Server-side traffic setup and diagnosis](references/server-side-traffic.md)
|
|
259
259
|
- [Google Business Profile prerequisites](references/google-business-profile.md)
|
|
260
260
|
- [Google Ads and GTM evidence boundaries](references/google-marketing.md)
|
|
261
|
+
- [Tracking AI traffic to visits and conversions](references/ai-traffic-tracking.md)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Tracking AI traffic to visits and conversions
|
|
2
|
+
|
|
3
|
+
Read this before you answer whether AI search sent visitors, whether those
|
|
4
|
+
visitors came back, or whether they bought, booked or became leads. Read it too
|
|
5
|
+
when someone says "our tracking is broken" or "AI referrals dropped".
|
|
6
|
+
|
|
7
|
+
Everything here is a stored read unless it says otherwise. Changing a setting,
|
|
8
|
+
connecting a source, a backfill or a GA sync each need approval for that exact
|
|
9
|
+
action.
|
|
10
|
+
|
|
11
|
+
## The funnel and what measures each step
|
|
12
|
+
|
|
13
|
+
Each step is usually 10 to 100 times smaller than the one above it. Most AI
|
|
14
|
+
answers are read inside the assistant, so a site can be read often and clicked
|
|
15
|
+
rarely. Report every step separately; never pool them.
|
|
16
|
+
|
|
17
|
+
| Step | What it is | Instrument | Read with |
|
|
18
|
+
|---|---|---|---|
|
|
19
|
+
| Crawled | AI and search bots fetch pages to index them | Server-side traffic, `crawler` | `cnry traffic events <p> --kind crawler` (`canonry_traffic_events`) |
|
|
20
|
+
| Read for a user | An assistant fetches a page live while answering someone (ChatGPT-User, Claude-User, Perplexity-User) | Server-side traffic, `ai-user-fetch` | `cnry traffic events <p> --kind ai-user-fetch` |
|
|
21
|
+
| Clicked | A person follows a link from an AI answer | Server-side `ai-referral` hits; GA4 AI sessions | `cnry traffic events <p> --kind ai-referral`; `cnry ga ai-referral-history <p>` (`canonry_ga_ai_referral_history`) |
|
|
22
|
+
| Came back | That person's later visits, from any channel | AI visitor journeys (server logs) | `cnry journeys summary\|list\|show <p>` (`canonry_journeys_summary`, `canonry_journeys_list`, `canonry_journey_get`) |
|
|
23
|
+
| Converted | Lead, booking or purchase | GA4 events named in `measurement.leadEventNames` | `cnry ga measurement-analysis <p> --window 90d --host-scope all` (`canonry_ga_measurement_analysis`) |
|
|
24
|
+
|
|
25
|
+
Server-side hits are requests and GA4 counts sessions, and each instrument
|
|
26
|
+
misses some visits the other sees, so the two never match exactly. Compare
|
|
27
|
+
their trend, not their totals.
|
|
28
|
+
|
|
29
|
+
## Check the instrument before you report a zero
|
|
30
|
+
|
|
31
|
+
A zero means nothing until you know the instrument could have seen it. Work
|
|
32
|
+
through these in order and say which one applies.
|
|
33
|
+
|
|
34
|
+
1. **Is the conversion tracked at all?** Read the project's
|
|
35
|
+
`measurement.leadEventNames` (`cnry project show <p> --format json`). The
|
|
36
|
+
default is only `generate_lead`. Online stores report sales as `purchase`,
|
|
37
|
+
and booking engines often do too. If lead events are zero across every
|
|
38
|
+
channel, not only AI, the instrument is blind: report "conversions are not
|
|
39
|
+
measured", never "AI drove no conversions". Adding an event is a setting
|
|
40
|
+
change (`cnry project update <p> --add-lead-event purchase`) and needs
|
|
41
|
+
approval; the daily refresh then fills about 30 days.
|
|
42
|
+
2. **Does GA4 receive the event?** Read the stored GTM snapshot
|
|
43
|
+
(`cnry gtm snapshots <p>`, then `cnry gtm snapshot <p> <id>`;
|
|
44
|
+
`canonry_gtm_snapshot_get`). If the booking or purchase trigger fires only
|
|
45
|
+
Google Ads tags (`awct`) and no GA4 event tag, GA4 records no conversions.
|
|
46
|
+
The fix is a GA4 Event tag on the same trigger, made by someone with
|
|
47
|
+
publish rights in the site's GTM container. Canonry's GTM access is
|
|
48
|
+
read-only.
|
|
49
|
+
3. **Do two instruments agree?** Compare Canonry's GA4 AI sessions with GA4's
|
|
50
|
+
own `AI Assistant` channel (`cnry ga acquisition <p> --window 30d
|
|
51
|
+
--group-by channel`), and with server-side `ai-referral` hits. Agreement
|
|
52
|
+
between independent instruments rules out an ingestion fault. GA4 may file
|
|
53
|
+
AI shopping links under another channel when they carry a different
|
|
54
|
+
`utm_medium` (for example `feed`); the source still reads `chatgpt.com`.
|
|
55
|
+
4. **Paid or organic?** An AI-referral spike can be an ads test. GA4 rows carry
|
|
56
|
+
`trafficClass`, and server-side referrals split paid from organic. Never
|
|
57
|
+
compare a paid month with organic months, and say "the ads ended" rather
|
|
58
|
+
than "AI traffic dropped" when the paid line is what fell.
|
|
59
|
+
5. **Is the source reading the right site?** Confirm the traffic source's
|
|
60
|
+
service or host actually serves the domain (for example, check which backend
|
|
61
|
+
a load balancer routes the domain to) before you claim a wiring gap.
|
|
62
|
+
6. **Is the source partial?**
|
|
63
|
+
- The default Cloudflare Worker forwards only AI-referred and bot requests,
|
|
64
|
+
so journeys miss later visits until the source is reconnected with
|
|
65
|
+
`--forward-page-views` and the Worker redeployed. Journey reads list such
|
|
66
|
+
sources in `partialSources`.
|
|
67
|
+
- The WordPress logger runs inside WordPress, so it cannot see page views a
|
|
68
|
+
cache serves before WordPress runs.
|
|
69
|
+
- Vercel logs carry no IP, so journey keys use the user agent only
|
|
70
|
+
(`keyBasis`); people with the same device and browser version merge,
|
|
71
|
+
which undercounts people and overstates return rates.
|
|
72
|
+
- A hosted checkout or booking engine on another domain is outside the
|
|
73
|
+
site's server logs.
|
|
74
|
+
7. **Is a Queue backlog hiding recent traffic?** For Cloudflare Queue pull,
|
|
75
|
+
read `cnry traffic status <p>` and the last sync's `syncSummary`
|
|
76
|
+
(`cnry runs <p> --kind traffic-sync --format json`) before quoting recent
|
|
77
|
+
days. Recent days look low while a backlog drains.
|
|
78
|
+
8. **Is it noise?** Unverified "AI user" fetches of `/.env`, admin paths or
|
|
79
|
+
404s are scanners using AI bot names. Quote verified fetches
|
|
80
|
+
(`verificationStatus: verified`) and pages that returned 200.
|
|
81
|
+
|
|
82
|
+
## What attribution can and cannot say
|
|
83
|
+
|
|
84
|
+
- GA4 credits a conversion to the session it happened in. A person who first
|
|
85
|
+
came from ChatGPT and books days later from Google counts as Google. Journeys
|
|
86
|
+
keep the earlier AI touch for 30 days, but they see a conversion only when
|
|
87
|
+
its page (for example a booking confirmation URL) appears in the server logs.
|
|
88
|
+
- A return from an outside payment or booking step can take the credit in GA4
|
|
89
|
+
unless that domain is listed under unwanted referrals in the GA4 web stream
|
|
90
|
+
settings. That is a GA4 admin change for the site owner.
|
|
91
|
+
- A visitor key is a keyed hash of IP and user agent, scoped to the project.
|
|
92
|
+
Never print or ask for visitor IPs.
|
|
93
|
+
- Since iOS 26, Safari freezes the OS version in its user agent; device labels
|
|
94
|
+
use Safari's own version instead, so treat the iOS version as approximate.
|
|
95
|
+
|
|
96
|
+
## User stories
|
|
97
|
+
|
|
98
|
+
**"Did anyone from AI search buy, book or become a lead?"**
|
|
99
|
+
1. Run the instrument checks above, steps 1 and 2 first.
|
|
100
|
+
2. If conversions are measured: `cnry ga measurement-analysis <p> --window 90d
|
|
101
|
+
--host-scope all` and read `leads.aiEngines` per engine and period. Quote
|
|
102
|
+
event counts with their sessions; the lead rate is null when the evidence is
|
|
103
|
+
mixed.
|
|
104
|
+
3. Add journeys for the cross-session view: `cnry journeys list <p> --returned
|
|
105
|
+
true`, then `show` for visitors whose later visits reach a conversion page.
|
|
106
|
+
4. Say what is unmeasured. Example: "ChatGPT sent 120 sessions in 30 days.
|
|
107
|
+
Bookings are not sent to GA4, so we cannot say how many booked."
|
|
108
|
+
|
|
109
|
+
**"A customer says they found us through ChatGPT. Can we trace them?"**
|
|
110
|
+
1. Get the date and time of their purchase, booking or form, and the device if
|
|
111
|
+
known.
|
|
112
|
+
2. `cnry journeys list <p> --engine ChatGPT --since <a few days before>` and
|
|
113
|
+
`cnry journeys show <p> <visitor-key>` for visitors active near that time.
|
|
114
|
+
3. A user-agent match is "likely", never proof, unless the key also includes
|
|
115
|
+
the IP. Reading a provider's raw logs beyond what Canonry stored is a live
|
|
116
|
+
read and needs approval.
|
|
117
|
+
|
|
118
|
+
**"AI referrals dropped. Is our tracking broken?"**
|
|
119
|
+
1. Pull the monthly series from both instruments (server-side `ai-referral` and
|
|
120
|
+
GA4 AI sessions) and split paid from organic.
|
|
121
|
+
2. If both fell together, the traffic changed, not the tracking. Check for an
|
|
122
|
+
ended ads test, a lost citation or mention (`references/aeo-analysis.md`), or
|
|
123
|
+
a site change.
|
|
124
|
+
3. If only one fell, check that instrument: sync errors and stop reasons
|
|
125
|
+
(`cnry traffic status`, `cnry runs <p> --kind traffic-sync`), the GA4 tag,
|
|
126
|
+
or the source's service or host.
|
|
127
|
+
|
|
128
|
+
**"Which of our pages does AI read?"**
|
|
129
|
+
`cnry traffic events <p> --kind ai-user-fetch --since <date>`: keep verified
|
|
130
|
+
fetches with status 200, group by path, and leave out `robots.txt`. These are
|
|
131
|
+
the pages assistants open while answering people, even when nobody clicks.
|
|
132
|
+
|
|
133
|
+
**"Do AI visitors come back?"**
|
|
134
|
+
`cnry journeys summary <p>`: `returnRate`, `returnedVia` and
|
|
135
|
+
`medianDaysToReturn`. Report `keyBasis` and `partialSources` with the numbers;
|
|
136
|
+
a user-agent-only or partial source changes what the rate means.
|
|
137
|
+
|
|
138
|
+
## Reporting language
|
|
139
|
+
|
|
140
|
+
- Lead with the step, the count and the window: "Assistants read the site 300
|
|
141
|
+
times in 30 days (verified); 10 people clicked through; conversions are not
|
|
142
|
+
measured."
|
|
143
|
+
- Keep crawled, read, clicked, came back and converted as separate numbers.
|
|
144
|
+
- Name the blind spot whenever a number is zero or low because an instrument
|
|
145
|
+
cannot see it.
|
|
@@ -1006,10 +1006,14 @@ the explicit activation command. Activation pauses sibling sources and moves the
|
|
|
1006
1006
|
one `traffic-sync` schedule for the project. Direct push rejects `traffic sync` and
|
|
1007
1007
|
does not use this schedule.
|
|
1008
1008
|
|
|
1009
|
-
Queue pull drains for up to 4 minutes in one sync.
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1009
|
+
Queue pull drains for up to 4 minutes in one sync. Each run records why it
|
|
1010
|
+
stopped: read `syncSummary.stopReason` with
|
|
1011
|
+
`cnry runs <project> --kind traffic-sync --format json`. The doctor warns when
|
|
1012
|
+
more than 1,000 messages remain afterwards and names the cause. After
|
|
1013
|
+
`deadline` or `batch-cap` the drain hit its limit: if the operator approves a
|
|
1014
|
+
manual drain, run a manual sync, and if the backlog recurs, get approval to
|
|
1015
|
+
shorten the schedule interval or raise the time limit. After `empty-pulls`
|
|
1016
|
+
Cloudflare still counted messages it would not return; later syncs pull them.
|
|
1013
1017
|
|
|
1014
1018
|
Read the [server-side traffic guide](server-side-traffic.md) for token safety,
|
|
1015
1019
|
route checks, activation order, smoke tests, rollback, and troubleshooting.
|
|
@@ -932,10 +932,21 @@ Cloudflare reports a backlog, up to a 4-minute time limit per sync
|
|
|
932
932
|
while a backlog waits, so a sync stops only when Cloudflare reports no backlog,
|
|
933
933
|
two pulls in a row come back empty, or the limit is reached. If a pull fails
|
|
934
934
|
after earlier batches were saved, the run ends `partial` and keeps them.
|
|
935
|
+
|
|
936
|
+
Each sync run records what it did and why it stopped in `syncSummary`: pulls,
|
|
937
|
+
empty pulls, messages pulled and acknowledged, redelivered (duplicate) events,
|
|
938
|
+
the backlog Cloudflare reported on the first and last pull, and `stopReason`
|
|
939
|
+
(`backlog-empty`, `empty-pulls`, `unleased-only`, `deadline`, `batch-cap` or
|
|
940
|
+
`error`). Read it with `cnry runs <project> --kind traffic-sync --format json`;
|
|
941
|
+
`cnry traffic status` shows the latest one.
|
|
942
|
+
|
|
935
943
|
Doctor treats a residual of up to 1,000 messages as normal (they arrived during
|
|
936
|
-
the drain) and warns above that
|
|
937
|
-
|
|
938
|
-
|
|
944
|
+
the drain) and warns above that, with the cause taken from the last stop
|
|
945
|
+
reason. After `deadline` or `batch-cap` the drain hit its limit: shorten the
|
|
946
|
+
`traffic-sync` interval or raise the time limit, or messages will pass the
|
|
947
|
+
Queue's retention and be dropped. After `empty-pulls` or `unleased-only`
|
|
948
|
+
Cloudflare still counted messages it would not return (leased or not yet
|
|
949
|
+
visible); later syncs pull them, and raising the time limit does not help.
|
|
939
950
|
|
|
940
951
|
## Syncing data
|
|
941
952
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{r as e}from"./rolldown-runtime-XKyaHtl9.js";import{I as t,K as n,R as r,rt as i}from"./vendor-lucide-4KkDquAu.js";import{r as a}from"./vendor-markdown-9SSeLNHZ.js";import{Ct as o,En as s,Xt as c,Zt as l,_r as u,r as d,st as f,t as p,vr as m,wt as h,yr as g}from"./ToneBadge-
|
|
1
|
+
import{r as e}from"./rolldown-runtime-XKyaHtl9.js";import{I as t,K as n,R as r,rt as i}from"./vendor-lucide-4KkDquAu.js";import{r as a}from"./vendor-markdown-9SSeLNHZ.js";import{Ct as o,En as s,Xt as c,Zt as l,_r as u,r as d,st as f,t as p,vr as m,wt as h,yr as g}from"./ToneBadge-DKvZcONW.js";import{r as _}from"./account-context-D_CDYrs8.js";import{t as v}from"./async-handler-Bj3TnIRw.js";import{X as y,et as b}from"./index-DSVFZqv6.js";var x=e(i(),1),S=a(),C=`https://github.com/Canonry/canonry/blob/main/docs/mcp.md`,w=`https://github.com/Canonry/canonry/blob/main/docs/plugins.md`,T=`https://help.openai.com/en/articles/12584461-connecting-custom-mcp-servers-in-chatgpt`,E=`https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp`,D={[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.`},O={codex:{label:`Codex`,command:`codex plugin marketplace add Canonry/canonry
|
|
2
2
|
codex plugin add canonry@canonry`},claude:{label:`Claude Code`,command:`claude plugin marketplace add Canonry/canonry
|
|
3
3
|
claude plugin install canonry@canonry`},other:{label:`Other MCP clients`,command:`canonry-mcp`}};function k({label:e,value:t,showText:i=!1,secondary:a=!1,action:o=`agent_setup.copy`}){let c=(0,x.useId)(),[l,u]=(0,x.useState)(!1),[f,p]=(0,x.useState)(!1);async function m(){try{await navigator.clipboard.writeText(t),s(o),u(!0),p(!1)}catch{u(!1),p(!0)}}return(0,S.jsxs)(`div`,{className:`min-w-0 space-y-3`,children:[i?(0,S.jsx)(`pre`,{className:`overflow-x-auto rounded-md border border-default bg-bg p-3 text-sm text-heading`,children:(0,S.jsx)(`code`,{children:t})}):null,(0,S.jsxs)(d,{type:`button`,className:`min-h-11`,variant:a?`outline`:`default`,onClick:v(m),children:[l?(0,S.jsx)(n,{className:`size-4`,"aria-hidden":`true`}):(0,S.jsx)(r,{className:`size-4`,"aria-hidden":`true`}),e]}),l?(0,S.jsx)(`p`,{role:`status`,className:`text-sm text-secondary`,children:`Copied.`}):null,!i||f?(0,S.jsxs)(`details`,{open:f||void 0,className:`text-sm text-secondary`,children:[(0,S.jsx)(`summary`,{className:`min-h-11 w-fit cursor-pointer py-3`,children:f?`Copy unavailable. Select the text below.`:`Preview text`}),(0,S.jsxs)(`label`,{htmlFor:c,className:`sr-only`,children:[e,` text`]}),(0,S.jsx)(`textarea`,{id:c,readOnly:!0,rows:f&&i?3:8,value:t,className:`mt-2 w-full rounded-md border border-default bg-bg p-3 text-sm leading-6 text-heading`})]}):null]})}function A(){return o()||h()?(0,S.jsxs)(`div`,{className:`page-container`,children:[(0,S.jsx)(`h1`,{className:`page-title`,children:`Agent setup`}),(0,S.jsx)(`p`,{className:`mt-3 text-sm text-secondary`,children:`Open Agent setup in your own Canonry instance to connect your agent.`}),l()?(0,S.jsx)(`a`,{className:`mt-4 inline-block text-sm underline`,href:C,target:`_blank`,rel:`noopener noreferrer`,children:`Read the connection guide`}):null]}):(0,S.jsx)(j,{})}function j(){let e=b(),{isAdmin:n}=_(),r=c(),i=l(),a=y(!e,e?.health),o=a.data?.apiStatus,s=o?.state===`ok`?o.mcp:void 0,d=s?.status===g.available&&s.hosted?.status===u.ready?s.hosted:void 0,m=s?.hosted?.status===u.unavailable?s.hosted.reason:void 0,h=`${window.location.origin}${f()}`,v=`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?v=`Checking the instance’s hosted connection settings.`:m?v=n&&r?D[m]:`Ask your Canonry administrator to finish hosted connection setup for this instance.`:o?.state===`error`?v=`The instance’s connection settings could not be checked. Try again after the service is available.`:s?.status===g[`not-supported`]&&(v=`This deployment does not provide hosted MCP connections. Ask your administrator for a supported connection.`);let x=d?`Help me connect this existing Canonry instance to ChatGPT or Claude Desktop using its remote MCP URL: ${d.url}
|
|
4
4
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as e}from"./rolldown-runtime-XKyaHtl9.js";import{rt as t}from"./vendor-lucide-4KkDquAu.js";import{r as n}from"./vendor-markdown-9SSeLNHZ.js";import{b as r}from"./vendor-tanstack-pEsPGDvT.js";import{pt as i,r as a,t as o}from"./ToneBadge-
|
|
1
|
+
import{r as e}from"./rolldown-runtime-XKyaHtl9.js";import{rt as t}from"./vendor-lucide-4KkDquAu.js";import{r as n}from"./vendor-markdown-9SSeLNHZ.js";import{b as r}from"./vendor-tanstack-pEsPGDvT.js";import{pt as i,r as a,t as o}from"./ToneBadge-DKvZcONW.js";import{Jt as s,Y as c,i as l}from"./react-query-DzDr4w41.js";import{t as u}from"./card-C6z0XyOo.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({...l({client:i,query:T}),enabled:!e}),O=r({...s({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.jsxs)(`div`,{children:[(0,f.jsx)(`p`,{className:`eyebrow eyebrow-soft`,children:`Audit trail`}),(0,f.jsx)(`h2`,{id:`change-history-heading`,children:`Change history`}),(0,f.jsx)(`p`,{className:`supporting-copy mt-1 max-w-2xl`,children:`Configuration and operator actions. Execution outcomes remain in Runs.`})]}),(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-sm text-strong placeholder-mono-600 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-sm 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-sm 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 font-mono text-[11px] 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 font-mono text-[11px] 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-sm 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-xs 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-xs 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};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as e}from"./rolldown-runtime-XKyaHtl9.js";import{I as t,L as n,S as r,o as i,rt as a,s as o}from"./vendor-lucide-4KkDquAu.js";import{r as s}from"./vendor-markdown-9SSeLNHZ.js";import{At as c,Ct as l,H as u,I as d,L as f,U as p,_t as m,a as h,ln as g,q as _,r as v,t as y}from"./ToneBadge-C3S7aVf3.js";import{t as b}from"./async-handler-Bj3TnIRw.js";import{t as x}from"./card-D8MU7RYV.js";import{t as S}from"./Hint-C0TmIzHX.js";var C=e(a(),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,a]=(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,i]=await Promise.all([d(),p().catch(()=>null),_().catch(()=>[]),f().catch(()=>[]),u().catch(()=>null)]);a(e),k(t),j(n),N(r),F(i)}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 m();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 g(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 c(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 border-negative-800/60`,children:(0,w.jsx)(`p`,{className:`text-sm text-negative`,children:K})}),J&&(0,w.jsx)(x,{className:`surface-card p-4 mb-4 border-positive-800/60`,children:(0,w.jsx)(`p`,{className:`text-sm text-positive`,children:J})}),(0,w.jsxs)(`section`,{className:`page-section-divider`,children:[(0,w.jsxs)(`div`,{className:`section-head section-head-inline`,children:[(0,w.jsxs)(`div`,{children:[(0,w.jsx)(`p`,{className:`eyebrow eyebrow-soft`,children:`Readiness`}),(0,w.jsx)(`h2`,{children:`Backlink source`})]}),e?.duckdbInstalled?(0,w.jsx)(y,{tone:`positive`,children:`Installed`}):(0,w.jsx)(y,{tone:`caution`,children:`Not installed`})]}),(0,w.jsx)(x,{className:`surface-card p-4`,children:I?(0,w.jsx)(`p`,{className:`text-sm text-muted`,children:`Checking…`}):e?.duckdbInstalled?(0,w.jsx)(`p`,{className:`text-sm 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-sm text-secondary`,children:`Install the local query engine to enable release syncs.`}),!l()&&(0,w.jsxs)(v,{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-divider`,children:[(0,w.jsxs)(`div`,{className:`section-head section-head-inline`,children:[(0,w.jsxs)(`div`,{children:[(0,w.jsx)(`p`,{className:`eyebrow eyebrow-soft`,children:`Latest sync`}),(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)(y,{tone:O(s.status),children:s.status})]}),(0,w.jsxs)(x,{className:`surface-card p-5`,children:[s?(0,w.jsxs)(`div`,{className:`space-y-2 text-sm`,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-xs text-muted pt-2`,children:[(0,w.jsxs)(`div`,{children:[(0,w.jsx)(`p`,{className:`text-faint uppercase tracking-wide`,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 uppercase tracking-wide 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 uppercase tracking-wide`,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 uppercase tracking-wide`,children:`Finished`}),(0,w.jsx)(`p`,{className:`text-neutral mt-0.5`,children:D(s.queryFinishedAt)})]})]}),s.error&&(0,w.jsx)(`p`,{className:`text-sm text-negative-400 pt-2`,children:s.error})]}):(0,w.jsx)(`p`,{className:`text-sm 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)(i,{className:`h-4 w-4 text-caution-400 shrink-0 mt-0.5`,"aria-hidden":!0}),(0,w.jsxs)(`div`,{className:`text-sm text-neutral leading-relaxed`,children:[(0,w.jsx)(`p`,{className:`font-medium 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:`mt-4 rounded border border-base bg-bg-elevated/40 p-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-[10px] uppercase tracking-wide text-muted`,children:`Auto-detected release`}),P?(0,w.jsxs)(`p`,{className:`text-sm text-strong mt-0.5`,children:[(0,w.jsx)(`code`,{className:`text-heading`,children:P.release}),(0,w.jsxs)(`span`,{className:`ml-2 text-xs text-muted`,children:[`Vertex `,E(P.vertexBytes),`, edges `,E(P.edgesBytes)]})]}):(0,w.jsx)(`p`,{className:`text-sm 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-xs text-secondary hover:text-strong focus:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded`,children:[`Browse all Common Crawl web-graph releases`,(0,w.jsx)(t,{className:`h-3 w-3`,"aria-hidden":!0})]})]}),!l()&&(0,w.jsxs)(`div`,{className:`flex items-center gap-2 shrink-0`,children:[(0,w.jsxs)(v,{type:`button`,size:`sm`,disabled:B||!e?.duckdbInstalled||!P&&!H.trim(),onClick:b(Q),children:[(0,w.jsx)(r,{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).`]})]})]})]}),l()?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-sm text-strong placeholder-mono-600 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-sm 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-sm 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-sm 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-sm font-medium text-secondary hover:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded`,children:`Technical details and cache`}),(0,w.jsxs)(`div`,{className:`mt-4`,children:[(0,w.jsx)(`p`,{className:`text-sm text-secondary mb-3`,children:`Cached releases support project re-extracts. Pruning a release does not remove saved backlink results.`}),(0,w.jsx)(x,{className:`surface-card overflow-hidden`,children:(0,w.jsxs)(`table`,{className:`w-full text-sm`,children:[(0,w.jsx)(`thead`,{children:(0,w.jsxs)(`tr`,{className:`border-b border-base text-left text-xs uppercase tracking-wide text-faint`,children:[(0,w.jsx)(`th`,{className:`px-4 py-2 font-medium`,children:`Release`}),(0,w.jsx)(`th`,{className:`px-4 py-2 font-medium`,children:`Sync status`}),(0,w.jsx)(`th`,{className:`px-4 py-2 text-right font-medium`,children:`Size`}),(0,w.jsx)(`th`,{className:`px-4 py-2 font-medium`,children:`Last used`}),!l()&&(0,w.jsx)(`th`,{className:`px-4 py-2 font-medium 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)(y,{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)}),!l()&&(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)(v,{type:`button`,variant:`outline`,size:`sm`,onClick:()=>{$(e.release)},children:[(0,w.jsx)(o,{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-sm text-muted`,colSpan:l()?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-xs 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-divider`,children:[(0,w.jsx)(`div`,{className:`section-head section-head-inline`,children:(0,w.jsxs)(`div`,{children:[(0,w.jsx)(`p`,{className:`eyebrow eyebrow-soft`,children:`History`}),(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-sm`,children:[(0,w.jsx)(`thead`,{children:(0,w.jsxs)(`tr`,{className:`border-b border-base text-left text-xs uppercase tracking-wide text-faint`,children:[(0,w.jsx)(`th`,{className:`px-4 py-2 font-medium`,children:`Release`}),(0,w.jsx)(`th`,{className:`px-4 py-2 font-medium`,children:`Status`}),(0,w.jsx)(`th`,{className:`px-4 py-2 text-right font-medium`,children:`Projects`}),(0,w.jsx)(`th`,{className:`px-4 py-2 text-right font-medium`,children:`Rows`}),(0,w.jsx)(`th`,{className:`px-4 py-2 font-medium`,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)(y,{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};
|
|
1
|
+
import{r as e}from"./rolldown-runtime-XKyaHtl9.js";import{I as t,L as n,S as r,o as i,rt as a,s as o}from"./vendor-lucide-4KkDquAu.js";import{r as s}from"./vendor-markdown-9SSeLNHZ.js";import{At as c,Ct as l,H as u,I as d,L as f,U as p,_t as m,a as h,ln as g,q as _,r as v,t as y}from"./ToneBadge-DKvZcONW.js";import{t as b}from"./async-handler-Bj3TnIRw.js";import{t as x}from"./card-C6z0XyOo.js";import{t as S}from"./Hint-C0TmIzHX.js";var C=e(a(),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,a]=(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,i]=await Promise.all([d(),p().catch(()=>null),_().catch(()=>[]),f().catch(()=>[]),u().catch(()=>null)]);a(e),k(t),j(n),N(r),F(i)}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 m();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 g(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 c(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 border-negative-800/60`,children:(0,w.jsx)(`p`,{className:`text-sm text-negative`,children:K})}),J&&(0,w.jsx)(x,{className:`surface-card p-4 mb-4 border-positive-800/60`,children:(0,w.jsx)(`p`,{className:`text-sm text-positive`,children:J})}),(0,w.jsxs)(`section`,{className:`page-section-divider`,children:[(0,w.jsxs)(`div`,{className:`section-head section-head-inline`,children:[(0,w.jsxs)(`div`,{children:[(0,w.jsx)(`p`,{className:`eyebrow eyebrow-soft`,children:`Readiness`}),(0,w.jsx)(`h2`,{children:`Backlink source`})]}),e?.duckdbInstalled?(0,w.jsx)(y,{tone:`positive`,children:`Installed`}):(0,w.jsx)(y,{tone:`caution`,children:`Not installed`})]}),(0,w.jsx)(x,{className:`surface-card p-4`,children:I?(0,w.jsx)(`p`,{className:`text-sm text-muted`,children:`Checking…`}):e?.duckdbInstalled?(0,w.jsx)(`p`,{className:`text-sm 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-sm text-secondary`,children:`Install the local query engine to enable release syncs.`}),!l()&&(0,w.jsxs)(v,{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-divider`,children:[(0,w.jsxs)(`div`,{className:`section-head section-head-inline`,children:[(0,w.jsxs)(`div`,{children:[(0,w.jsx)(`p`,{className:`eyebrow eyebrow-soft`,children:`Latest sync`}),(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)(y,{tone:O(s.status),children:s.status})]}),(0,w.jsxs)(x,{className:`surface-card p-5`,children:[s?(0,w.jsxs)(`div`,{className:`space-y-2 text-sm`,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-xs text-muted pt-2`,children:[(0,w.jsxs)(`div`,{children:[(0,w.jsx)(`p`,{className:`text-faint uppercase tracking-wide`,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 uppercase tracking-wide 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 uppercase tracking-wide`,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 uppercase tracking-wide`,children:`Finished`}),(0,w.jsx)(`p`,{className:`text-neutral mt-0.5`,children:D(s.queryFinishedAt)})]})]}),s.error&&(0,w.jsx)(`p`,{className:`text-sm text-negative-400 pt-2`,children:s.error})]}):(0,w.jsx)(`p`,{className:`text-sm 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)(i,{className:`h-4 w-4 text-caution-400 shrink-0 mt-0.5`,"aria-hidden":!0}),(0,w.jsxs)(`div`,{className:`text-sm text-neutral leading-relaxed`,children:[(0,w.jsx)(`p`,{className:`font-medium 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:`mt-4 rounded border border-base bg-bg-elevated/40 p-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-[10px] uppercase tracking-wide text-muted`,children:`Auto-detected release`}),P?(0,w.jsxs)(`p`,{className:`text-sm text-strong mt-0.5`,children:[(0,w.jsx)(`code`,{className:`text-heading`,children:P.release}),(0,w.jsxs)(`span`,{className:`ml-2 text-xs text-muted`,children:[`Vertex `,E(P.vertexBytes),`, edges `,E(P.edgesBytes)]})]}):(0,w.jsx)(`p`,{className:`text-sm 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-xs text-secondary hover:text-strong focus:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded`,children:[`Browse all Common Crawl web-graph releases`,(0,w.jsx)(t,{className:`h-3 w-3`,"aria-hidden":!0})]})]}),!l()&&(0,w.jsxs)(`div`,{className:`flex items-center gap-2 shrink-0`,children:[(0,w.jsxs)(v,{type:`button`,size:`sm`,disabled:B||!e?.duckdbInstalled||!P&&!H.trim(),onClick:b(Q),children:[(0,w.jsx)(r,{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).`]})]})]})]}),l()?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-sm text-strong placeholder-mono-600 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-sm 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-sm 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-sm 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-sm font-medium text-secondary hover:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded`,children:`Technical details and cache`}),(0,w.jsxs)(`div`,{className:`mt-4`,children:[(0,w.jsx)(`p`,{className:`text-sm text-secondary mb-3`,children:`Cached releases support project re-extracts. Pruning a release does not remove saved backlink results.`}),(0,w.jsx)(x,{className:`surface-card overflow-hidden`,children:(0,w.jsxs)(`table`,{className:`w-full text-sm`,children:[(0,w.jsx)(`thead`,{children:(0,w.jsxs)(`tr`,{className:`border-b border-base text-left text-xs uppercase tracking-wide text-faint`,children:[(0,w.jsx)(`th`,{className:`px-4 py-2 font-medium`,children:`Release`}),(0,w.jsx)(`th`,{className:`px-4 py-2 font-medium`,children:`Sync status`}),(0,w.jsx)(`th`,{className:`px-4 py-2 text-right font-medium`,children:`Size`}),(0,w.jsx)(`th`,{className:`px-4 py-2 font-medium`,children:`Last used`}),!l()&&(0,w.jsx)(`th`,{className:`px-4 py-2 font-medium 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)(y,{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)}),!l()&&(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)(v,{type:`button`,variant:`outline`,size:`sm`,onClick:()=>{$(e.release)},children:[(0,w.jsx)(o,{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-sm text-muted`,colSpan:l()?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-xs 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-divider`,children:[(0,w.jsx)(`div`,{className:`section-head section-head-inline`,children:(0,w.jsxs)(`div`,{children:[(0,w.jsx)(`p`,{className:`eyebrow eyebrow-soft`,children:`History`}),(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-sm`,children:[(0,w.jsx)(`thead`,{children:(0,w.jsxs)(`tr`,{className:`border-b border-base text-left text-xs uppercase tracking-wide text-faint`,children:[(0,w.jsx)(`th`,{className:`px-4 py-2 font-medium`,children:`Release`}),(0,w.jsx)(`th`,{className:`px-4 py-2 font-medium`,children:`Status`}),(0,w.jsx)(`th`,{className:`px-4 py-2 text-right font-medium`,children:`Projects`}),(0,w.jsx)(`th`,{className:`px-4 py-2 text-right font-medium`,children:`Rows`}),(0,w.jsx)(`th`,{className:`px-4 py-2 font-medium`,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)(y,{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};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as e}from"./vendor-markdown-9SSeLNHZ.js";import{t}from"./AuditHistoryPanel-
|
|
1
|
+
import{r as e}from"./vendor-markdown-9SSeLNHZ.js";import{t}from"./AuditHistoryPanel-Ce_oy4OV.js";var n=e();function r(){return(0,n.jsxs)(`div`,{className:`page-container`,children:[(0,n.jsx)(`div`,{className:`page-header`,children:(0,n.jsxs)(`div`,{className:`page-header-left`,children:[(0,n.jsx)(`h1`,{className:`page-title`,children:`History`}),(0,n.jsx)(`p`,{className:`page-subtitle`,children:`Changes to projects and settings.`})]})}),(0,n.jsx)(t,{})]})}export{r as HistoryPage};
|