@ainyc/canonry 4.180.8 → 4.183.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 (38) hide show
  1. package/README.md +166 -100
  2. package/assets/agent-workspace/skills/aero/references/orchestration.md +1 -1
  3. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +1 -1
  4. package/assets/assets/{AuditHistoryPanel-qiuqNGvG.js → AuditHistoryPanel-ClwYugEs.js} +1 -1
  5. package/assets/assets/{BacklinksPage-D-g5f0Xv.js → BacklinksPage-DaEn1w9_.js} +1 -1
  6. package/assets/assets/{ChartPrimitives-DG35TyhH.js → ChartPrimitives-DLEICrrL.js} +1 -1
  7. package/assets/assets/{HistoryPage-q8_pCuOs.js → HistoryPage-B4WWIXmx.js} +1 -1
  8. package/assets/assets/{MeasurementPropertyPage-C_wMjUZe.js → MeasurementPropertyPage-DFvelKfS.js} +1 -1
  9. package/assets/assets/ProjectPage-D3AuGcau.js +9 -0
  10. package/assets/assets/{RunRow-D5hatdlY.js → RunRow-0HuBq5KN.js} +1 -1
  11. package/assets/assets/{RunsPage-Bj2snMM3.js → RunsPage-DhdjoIdK.js} +1 -1
  12. package/assets/assets/{SettingsPage-Cf5YgyJv.js → SettingsPage-XzHCmYz4.js} +1 -1
  13. package/assets/assets/{SiteHealthSection-D8RLvMAE.js → SiteHealthSection-BODUx0bX.js} +3 -3
  14. package/assets/assets/{TrafficPage-CotJ14le.js → TrafficPage-2AKICLH_.js} +1 -1
  15. package/assets/assets/{TrafficSourceDetailPage-BcxYX-uq.js → TrafficSourceDetailPage-CYVG7qZ0.js} +1 -1
  16. package/assets/assets/extract-error-message-CtGLEBMO.js +1 -0
  17. package/assets/assets/index-B64WXjg8.js +86 -0
  18. package/assets/assets/{index-DXCQf5s1.css → index-DwdK2e4e.css} +1 -1
  19. package/assets/assets/{react-sigma_core.esm.min-DxZINEmi.js → react-sigma_core.esm.min-D5Honb13.js} +1 -1
  20. package/assets/assets/{vendor-lucide-nYKv-BB7.js → vendor-lucide-XjCNFK0x.js} +1 -1
  21. package/assets/assets/{vendor-markdown-Bn7fRwrx.js → vendor-markdown-9tTeP8v7.js} +1 -1
  22. package/assets/assets/{vendor-radix-4UOaVWza.js → vendor-radix-BpmvLvpp.js} +1 -1
  23. package/assets/assets/{vendor-recharts-D8rt5lKn.js → vendor-recharts-FA0uySU7.js} +1 -1
  24. package/assets/assets/{vendor-tanstack-Dock0eH1.js → vendor-tanstack-CYBjbqVr.js} +1 -1
  25. package/assets/index.html +7 -7
  26. package/dist/{chunk-ZJ2VGTYU.js → chunk-ISONU3FA.js} +4276 -3389
  27. package/dist/{chunk-GFVZOVG3.js → chunk-OF3PKBW4.js} +72 -6
  28. package/dist/{chunk-G7M2KM7Q.js → chunk-RQSSKN5F.js} +2 -2
  29. package/dist/{chunk-IM4HFNGF.js → chunk-VFQPW2VX.js} +43 -3
  30. package/dist/{chunk-GN2G7VFK.js → chunk-Z2HTLTQV.js} +162 -4
  31. package/dist/cli.js +141 -8
  32. package/dist/index.js +4 -4
  33. package/dist/{intelligence-service-IURLYRB3.js → intelligence-service-4XQBUFBU.js} +2 -2
  34. package/dist/mcp.js +3 -3
  35. package/package.json +8 -8
  36. package/assets/assets/ProjectPage-33THfDtC.js +0 -9
  37. package/assets/assets/extract-error-message-Bxt42scd.js +0 -1
  38. package/assets/assets/index-BlILyZJ5.js +0 -86
package/README.md CHANGED
@@ -1,46 +1,56 @@
1
1
  # Canonry <img src="https://raw.githubusercontent.com/Canonry/canonry/main/apps/web/public/favicon-32.png" alt="Canonry canary icon" width="24" />
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@canonry/canonry)](https://www.npmjs.com/package/@canonry/canonry) [![Node.js >= 22.14](https://img.shields.io/badge/node-%3E%3D22.14-brightgreen)](https://nodejs.org)
3
+ [![npm version](https://img.shields.io/npm/v/@canonry/canonry)](https://www.npmjs.com/package/@canonry/canonry)
4
4
 
5
- Your entire AEO/GEO/AI Visibility, technical SEO + web analytics stack. **Agent-first. Self-hosted. Local.**
5
+ Canonry is an **agent-first, open-source AEO operating platform.** Track AI visibility over time, investigate changes across search and traffic, and measure progress after your agent acts.
6
6
 
7
- Think PostHog, for AI search visibility. Canonry tracks what ChatGPT, Claude, Gemini, and Perplexity say about you, joins it with your search, server side traffic data and paid media, and gives your agent the tools + skills to fix what it finds.
7
+ **Self-hosted, with your own provider keys.** Your dashboard and agent review the same project evidence.
8
8
 
9
- `npm install -g @canonry/canonry`
9
+ [Quick start](#quick-start) · [Evidence](#explore-the-evidence) · [Actions](#act-on-the-evidence) · [Integrations](#integrations) · [Docs](#documentation)
10
10
 
11
- ![Canonry AI Visibility mention share trend across answer engines](https://raw.githubusercontent.com/Canonry/canonry/main/docs/images/ai-visibility-trend.png)
11
+ <p align="center">
12
+ <a href="https://raw.githubusercontent.com/Canonry/canonry/main/docs/images/measure-act.svg">
13
+ <img src="https://raw.githubusercontent.com/Canonry/canonry/main/docs/images/measure-act.svg" alt="AI, search, analytics, and traffic sources feed Canonry on schedules or on demand. Historical evidence across multiple domains and portfolios runs from baseline through the latest checks. Your agent reads evidence and sends commands to Canonry tools, while using its own tools for code and content. Canonry publishes to WordPress, submits to Google and Bing, and supplies the dashboard, reports, and webhooks. Site changes loop back into measurement." width="100%" />
14
+ </a>
15
+ </p>
12
16
 
13
- *Track your share of answer-engine brand mentions over time.*
17
+ ## Quick start
14
18
 
15
- **Measure → diagnose → approve action → measure change**
19
+ <a id="or-use-any-shell-capable-coding-agent"></a>
16
20
 
17
- | Phase | What Canonry does |
18
- |---|---|
19
- | **Measure** | Track mentions and citations across Gemini, ChatGPT, Claude, Perplexity, and local models. Join them with GSC, GA4, Bing, server side traffic, Business Profile, and backlink data. |
20
- | **Diagnose** | Crawl the site, score Page Health, inspect evidence, compare competitors, and explain regressions. |
21
- | **Act** | Give your agent the context + skills to make surgical on-site technical SEO changes and coordinate content, indexing, analytics, and paid media. |
22
- | **Operate** | Create automation workflows, schedule checks, sync data, send webhooks, and generate client-ready reports. |
21
+ ### Start with your agent
23
22
 
24
- ### Built-in integrations and workflows
23
+ Connect the [Agent Plugin](docs/plugins.md) or [MCP adapter](docs/mcp.md) to your agent. When enabled, built-in [Aero](skills/aero/SKILL.md) reviews evidence and wakes after completed runs.
25
24
 
26
- - **Answer engines:** Canonry measures Gemini, ChatGPT, Claude, Perplexity, and OpenAI-compatible local models.
27
- - **Agent workflows:** Use the [MCP adapter](docs/mcp.md), [Agent Plugin](docs/plugins.md), [external webhooks](skills/canonry/references/canonry-cli.md#agent), or built-in [Aero](skills/aero/SKILL.md).
28
- - **Search and analytics:** Connect [Google Search Console](docs/google-search-console-setup.md), [Google Analytics 4](docs/google-analytics-setup.md), and [Bing Webmaster Tools](docs/bing-webmaster-setup.md).
29
- - **Server traffic:** Capture events from [Cloudflare, Cloud Run, Vercel, and WordPress](skills/canonry/references/server-side-traffic.md).
30
- - **Backlinks:** Query [Common Crawl hyperlink releases](skills/canonry/references/canonry-cli.md#backlinks-common-crawl) locally with DuckDB, and sync new releases on a schedule.
31
- - **Paid media:** Connect [OpenAI Ads Manager](docs/mcp.md#tool-surface) for account, conversion, campaign, and performance workflows.
32
- - **Conversion measurement:** Audit [Google Ads and Google Tag Manager](docs/google-marketing.md) with read-only snapshots and declared conversion contracts.
33
- - **Local presence:** Connect [Google Business Profile](skills/canonry/references/google-business-profile.md) for search terms, performance, lodging data, and booking actions.
34
- - **Publishing and indexing:** Publish through [WordPress](docs/wordpress-setup.md), generate JSON-LD, and [submit sitemaps or URLs for indexing](skills/canonry/references/indexing.md).
35
- - **Client reporting:** Automate [scheduled checks and data syncs](skills/canonry/references/canonry-cli.md#scheduling--notifications), send webhook alerts, and generate [client-ready HTML reports](skills/canonry/references/canonry-cli.md#reports).
25
+ For a new installation, give your agent this setup request:
36
26
 
37
- The dashboard, CLI, and agent tools share the same project API.
27
+ <details>
28
+ <summary>Copy the first-time setup request</summary>
38
29
 
39
- ## Get a Page Health baseline
30
+ ```text
31
+ Help me set up Canonry for my public site.
40
32
 
41
- ![Canonry Site Map graph](https://raw.githubusercontent.com/Canonry/canonry/main/docs/images/dashboard.png)
33
+ Use the official Canonry docs:
34
+ - Agent quickstart: https://github.com/Canonry/canonry#or-use-any-shell-capable-coding-agent
35
+ - CLI reference: https://github.com/Canonry/canonry/blob/main/skills/canonry/references/canonry-cli.md
36
+ - Plugin setup: https://github.com/Canonry/canonry/blob/main/docs/plugins.md
37
+ - MCP setup: https://github.com/Canonry/canonry/blob/main/docs/mcp.md
38
+
39
+ If a Canonry installation or connected plugin/MCP is available, use it. Do not create a duplicate. Choose the connected tools or the shell path, not both. The `cnry` and `canonry` commands are interchangeable.
42
40
 
43
- *Map crawlable pages and the internal links connecting them.*
41
+ 1. Ask for my public domain, country, and language. Do not create or scan anything yet.
42
+ 2. If connected tools are available, use them for the remaining steps. For the shell path, make sure that `cnry` is on PATH. Then run `cnry --version`. If Canonry is missing, propose `npm install -g @canonry/canonry` and wait for approval. If configuration is missing, tell me to run `cnry bootstrap` in my private terminal and wait. Never ask me to paste passwords, API keys, OAuth credentials, or command output.
43
+ 3. Make sure that the API or connected tool is reachable. If the shell API is unavailable, propose `cnry start`. Wait for approval. List the projects with the connected project tool or `cnry project list --format json`. Reuse a project with the same domain. Make sure that the proposed name is not assigned to a different domain. If no match exists, show the exact create operation and wait for approval.
44
+ 4. Propose a bounded Site Health scan. Include `--max-pages` and the state of dead-link checking. Show the connected operation or exact `cnry technical-aeo run ... --wait --format json` command. Wait for separate approval before scanning.
45
+ 5. If the run status is `completed` or `partial`, read its score and worst pages with run-pinned connected tools. For the shell path, use `cnry technical-aeo score <project> --run-id <run-id> --format json` and `cnry technical-aeo pages <project> --run-id <run-id> --sort score-asc --limit 10 --format jsonl`. If the run failed or was cancelled, inspect the run error and stop. Summarize completed evidence and propose AI Visibility setup.
46
+ 6. Ask before you add queries, connect providers, start a provider-backed or quota-consuming run, edit files, or publish.
47
+ ```
48
+
49
+ </details>
50
+
51
+ <a id="get-a-page-health-baseline"></a>
52
+
53
+ ### Start locally
44
54
 
45
55
  1. Install Canonry.
46
56
 
@@ -48,79 +58,67 @@ The dashboard, CLI, and agent tools share the same project API.
48
58
  npm install -g @canonry/canonry
49
59
  ```
50
60
 
51
- 2. Create the local configuration, SQLite database, and full-instance API key.
61
+ 2. Initialize Canonry.
52
62
 
53
63
  ```bash
54
64
  cnry bootstrap
55
65
  ```
56
66
 
57
- Keep the output private. Provider credentials are optional. Bootstrap imports supported variables that are already in your environment.
58
-
59
- Canonry stores `config.yaml` and `data.db` in `~/.canonry` by default. Set
60
- `CANONRY_CONFIG_DIR` before `bootstrap`, `serve`, and later CLI commands to
61
- keep an install in another private directory; use the same value every time.
62
-
63
67
  3. Start Canonry.
64
68
 
65
69
  ```bash
66
70
  cnry serve
67
71
  ```
68
72
 
69
- 4. Open [http://127.0.0.1:4100/setup](http://127.0.0.1:4100/setup). If prompted, create a dashboard password.
73
+ 4. Open [http://127.0.0.1:4100/setup](http://127.0.0.1:4100/setup) and follow the setup to scan your site.
70
74
 
71
- 5. Enter your domain and approve the public-site crawl. This crawl creates a persisted Page Health baseline. AI Visibility is optional.
75
+ The crawl saves a Page Health baseline. AI Visibility is optional and has a separate setup.
72
76
 
73
- 6. To use the terminal instead, keep `cnry serve` running. Open a second terminal and run:
77
+ <details>
78
+ <summary>Scan and read results from the terminal</summary>
74
79
 
75
- ```bash
76
- cnry project create my-site --domain example.com --country US --language en
77
- cnry technical-aeo run my-site --max-pages 100 --wait --format json
78
- ```
80
+ Keep `cnry serve` active. In a second terminal, create a project and start a bounded scan:
79
81
 
80
- 7. Read the run ID and status from the output. If the status is `completed` or `partial`, read evidence from that run:
82
+ ```bash
83
+ cnry project create my-site --domain example.com --country US --language en
84
+ cnry technical-aeo run my-site --max-pages 100 --wait --format json
85
+ ```
81
86
 
82
- ```bash
83
- cnry technical-aeo score my-site --run-id <run-id> --format json
84
- cnry technical-aeo pages my-site --run-id <run-id> --sort score-asc --limit 10 --format jsonl
85
- ```
87
+ Read the run ID and status from the output. If the status is `completed` or `partial`, read evidence from that run:
86
88
 
87
- `--wait` polls for up to 15 minutes. If the scan remains active, use the progress command below. If it fails or is cancelled, inspect it with `cnry run show <run-id> --format json`.
89
+ ```bash
90
+ cnry technical-aeo score my-site --run-id <run-id> --format json
91
+ cnry technical-aeo pages my-site --run-id <run-id> --sort score-asc --limit 10 --format jsonl
92
+ ```
88
93
 
89
- ## Or use any shell-capable coding agent
94
+ `--wait` polls for up to 15 minutes. If the scan remains active, use `cnry technical-aeo progress <project> --run-id <id> --format json`.
95
+ If the scan fails or is cancelled, read the error with `cnry run show <run-id> --format json`.
90
96
 
91
- If your client supports the [Agent Plugin](docs/plugins.md) or [MCP adapter](docs/mcp.md), use that integration. Otherwise, paste this request into any shell-capable agent.
97
+ </details>
92
98
 
93
- <details>
94
- <summary>Copy the Site Health-first setup request</summary>
99
+ ## Explore the evidence
95
100
 
96
- <br />
101
+ Your agent and dashboard use the same project API. The dashboard makes trends, exact answers, and site findings available for human review.
97
102
 
98
- ```text
99
- Help me set up Canonry for my public site.
103
+ <a id="add-ai-visibility-when-you-need-it"></a>
100
104
 
101
- Use the official Canonry docs:
102
- - Agent quickstart: https://github.com/Canonry/canonry#or-use-any-shell-capable-coding-agent
103
- - CLI reference: https://github.com/Canonry/canonry/blob/main/skills/canonry/references/canonry-cli.md
104
- - Plugin setup: https://github.com/Canonry/canonry/blob/main/docs/plugins.md
105
- - MCP setup: https://github.com/Canonry/canonry/blob/main/docs/mcp.md
105
+ ### AI visibility
106
106
 
107
- If a Canonry installation or connected plugin/MCP is available, use it. Do not create a duplicate. Choose the connected tools or the shell path, not both. The `cnry` and `canonry` commands are interchangeable.
107
+ Track brand mentions and citations over time, by query and answer engine. Find answers that cite competitors but omit your site.
108
108
 
109
- 1. Ask for my public domain, country, and language. Do not create or scan anything yet.
110
- 2. If connected tools are available, use them for the remaining steps. For the shell path, make sure that `cnry` is on PATH. Then run `cnry --version`. If Canonry is missing, propose `npm install -g @canonry/canonry` and wait for approval. If configuration is missing, tell me to run `cnry bootstrap` in my private terminal and wait. Never ask me to paste passwords, API keys, OAuth credentials, or command output.
111
- 3. Make sure that the API or connected tool is reachable. If the shell API is unavailable, propose `cnry start`. Wait for approval. List the projects with the connected project tool or `cnry project list --format json`. Reuse a project with the same domain. Make sure that the proposed name is not assigned to a different domain. If no match exists, show the exact create operation and wait for approval.
112
- 4. Propose a bounded Site Health scan. Include `--max-pages` and the state of dead-link checking. Show the connected operation or exact `cnry technical-aeo run ... --wait --format json` command. Wait for separate approval before scanning.
113
- 5. If the run status is `completed` or `partial`, read its score and worst pages with run-pinned connected tools. For the shell path, use `cnry technical-aeo score <project> --run-id <run-id> --format json` and `cnry technical-aeo pages <project> --run-id <run-id> --sort score-asc --limit 10 --format jsonl`. If the run failed or was cancelled, inspect the run error and stop. Summarize completed evidence and propose AI Visibility setup.
114
- 6. Ask before you add queries, connect providers, start a provider-backed or quota-consuming run, edit files, or publish.
115
- ```
116
-
117
- </details>
109
+ ![Canonry AI Visibility mention share trend across answer engines](https://raw.githubusercontent.com/Canonry/canonry/main/docs/images/ai-visibility-trend.png)
118
110
 
119
- ## Add AI Visibility when you need it
111
+ *Track your share of answer-engine brand mentions over time.*
120
112
 
121
113
  ![Canonry AI Visibility citation map across queries and answer engines](https://raw.githubusercontent.com/Canonry/canonry/main/docs/images/ai-visibility-diagnostics.png)
122
114
 
123
- *Map citation and answer-mention coverage across every tracked query and engine.*
115
+ The citation map shows mention and citation coverage across queries and engines.
116
+ [Rank cited domains and pages](skills/canonry/references/canonry-cli.md#cited-source-rankings-cnry-sources), then inspect the exact answers and URLs behind each result.
117
+
118
+ <details>
119
+ <summary>Configure a provider and run visibility checks</summary>
120
+
121
+ For an existing project, configure a provider with your own key.
124
122
 
125
123
  | Provider | Key source | Environment variable |
126
124
  |---|---|---|
@@ -130,7 +128,7 @@ If a Canonry installation or connected plugin/MCP is available, use it. Do not c
130
128
  | Perplexity | [Perplexity settings](https://www.perplexity.ai/settings/api) | `PERPLEXITY_API_KEY` |
131
129
  | Local model | Any OpenAI-compatible endpoint | `LOCAL_BASE_URL` |
132
130
 
133
- Then add the queries that matter and run a measured sweep:
131
+ Add the queries that matter to your project:
134
132
 
135
133
  ```bash
136
134
  cnry query add my-site "your first query" "your second query"
@@ -138,45 +136,113 @@ cnry run my-site --wait
138
136
  cnry visibility-stats my-site --by-provider
139
137
  ```
140
138
 
141
- ## Technical surface
139
+ </details>
140
+
141
+ <a id="why-we-use-model-providers-apis-directly-not-a-router"></a>
142
+
143
+ <details>
144
+ <summary>Why Canonry uses direct provider APIs</summary>
145
+
146
+ Canonry uses direct provider APIs to capture answers, citations, and available
147
+ search details that model routers can omit. This richer data helps us measure
148
+ brand mentions and website citations accurately and explain changes in your
149
+ visibility.
150
+ [Read more about accuracy and why we chose models and direct provider API adapters individually](docs/providers/model-selection.md).
151
+
152
+ </details>
153
+
154
+ <details>
155
+ <summary>Discover questions or run one-off research</summary>
156
+
157
+ **Expand your tracked questions.** Run `cnry discover run <project> --icp "..."`. This does not change the tracked questions.
158
+ Preview a completed session with `cnry discover promote preview <project> <session-id>`. Promote only after approval.
159
+
160
+ **Research without changing tracking.** Run `cnry research run <project> "query one" "query two" --wait`.
161
+ Research saves the answers without adding queries to the tracked questions.
162
+
163
+ </details>
164
+
165
+ <a id="compare-competitors-and-measure-complex-portfolios"></a>
166
+
167
+ ### Portfolios and competitors
168
+
169
+ Use [versioned measurement plans](docs/mcp.md#tool-surface) for portfolios of locations, products, or site sections.
142
170
 
143
- | Surface | Use it for |
171
+ - **Properties:** Assign queries, engines, models, and locations to each property. Keep branded and non-brand coverage separate.
172
+ - **Market groups:** Organize overlapping sets of properties, each with its own competitors.
173
+ - **Comparisons:** Compare properties, groups, engines, and locations. Open the exact answers and cited URLs behind each result.
174
+
175
+ ### Search, traffic, and site health
176
+
177
+ Investigate visibility changes with evidence from search and local performance, crawler visits, AI page fetches, referrals, and conversions.
178
+ Site audits show technical findings alongside that evidence.
179
+
180
+ ![Canonry Site Map graph](https://raw.githubusercontent.com/Canonry/canonry/main/docs/images/dashboard.png)
181
+
182
+ *Map crawlable pages and the internal links that connect them.*
183
+
184
+ ## Act on the evidence
185
+
186
+ Your agent coordinates the work through Canonry and its own tools.
187
+
188
+ - **Improve the site:** Combine your agent's coding tools with Canonry's content, [WordPress publishing](docs/wordpress-setup.md), JSON-LD, and [indexing workflows](skills/canonry/references/indexing.md).
189
+ - **Measure after changes:** Configure [independent schedules](skills/canonry/references/canonry-cli.md#scheduling--notifications) for data syncs and checks. Compare new results with earlier evidence.
190
+ - **Report and follow up:** Generate [HTML reports or JSON evidence](skills/canonry/references/canonry-cli.md#reports). Send [webhook alerts](skills/canonry/references/canonry-cli.md#agent) to Discord, Slack, or your systems.
191
+
192
+ **An example job for your connected agent:**
193
+
194
+ > Find where competitors get cited and we don't. Use search data and the site audit to prepare a pull request or WordPress draft for review. After it ships, rerun checks and report what changed.
195
+
196
+ <a id="built-in-integrations-and-workflows"></a>
197
+
198
+ ## Integrations
199
+
200
+ Connect the sources you use. Each integration adds evidence or tools to the same project workflow.
201
+
202
+ | Area | Supported integrations |
144
203
  |---|---|
145
- | **CLI and REST API** | Script project measurements, diagnoses, actions, reports, and schedules. OpenAPI is available at `GET /api/v1/openapi.json`. |
146
- | **MCP and Agent Plugin** | Give Codex, Claude, Cursor, or a custom agent a typed, task-shaped tool surface. |
147
- | **Aero** | When enabled and configured, use the built-in analyst that reviews evidence and wakes after completed runs. |
148
- | **Dashboard** | Approve work, inspect evidence, and observe the same project record used by agents. |
204
+ | **AI answers** | ChatGPT · Claude · Gemini · Perplexity · OpenAI-compatible local models |
205
+ | **Search and local** | [Google Search Console](docs/google-search-console-setup.md) · [Bing Webmaster Tools](docs/bing-webmaster-setup.md) · [Google Business Profile](skills/canonry/references/google-business-profile.md) |
206
+ | **Analytics** | [Google Analytics 4](docs/google-analytics-setup.md) |
207
+ | **Conversion measurement** | [Google Ads + Google Tag Manager](docs/google-marketing.md): read-only snapshots and declared conversion contracts |
208
+ | **Server traffic** | [Cloudflare · Vercel · Cloud Run · WordPress](skills/canonry/references/server-side-traffic.md) |
209
+ | **Backlinks** | [Common Crawl](skills/canonry/references/canonry-cli.md#backlinks-common-crawl) hyperlink releases, queried locally with DuckDB |
210
+ | **Publishing and indexing** | [WordPress](docs/wordpress-setup.md) · JSON-LD · [sitemap and URL submissions](skills/canonry/references/indexing.md) |
211
+ | **ChatGPT Ads** | [Campaign measurement, paused campaign editing, approved activation, and operation reconciliation](docs/mcp.md#tool-surface) |
212
+
213
+ <a id="deployment-and-trust-boundary"></a>
214
+ <a id="technical-surface"></a>
215
+
216
+ ## Self-hosting and API
149
217
 
150
- ## Deployment and trust boundary
218
+ Canonry is single-tenant. Run one instance for one operator or team. Keep unrelated teams on separate instances.
151
219
 
152
- Canonry is self-hosted and single-tenant. Run one instance for one operator or team, and isolate unrelated teams on separate instances.
220
+ The CLI and REST API are the primary interfaces. They expose measurements, diagnoses, actions, reports, and schedules.
221
+ OpenAPI is available at `GET /api/v1/openapi.json`.
153
222
 
154
223
  See the [deployment guide](docs/deployment.md) for reverse proxies, daemon mode, Docker, systemd, and Tailscale.
155
224
 
156
- ## If you get stuck
225
+ ## Documentation
226
+
227
+ - **Operate Canonry:** [CLI reference](skills/canonry/references/canonry-cli.md) · [Agent Plugin](docs/plugins.md) · [MCP](docs/mcp.md)
228
+ - **Use built-in Aero:** [Setup and analysis workflows](skills/aero/SKILL.md)
229
+ - **Understand the system:** [Architecture](docs/architecture.md) · [Data model](docs/data-model.md)
230
+ - **Install standalone agent skills:** `cnry skills install` ([guide](skills/canonry/SKILL.md))
231
+ - **Browse all guides:** [Documentation index](docs/README.md)
232
+
233
+ <a id="if-you-get-stuck"></a>
157
234
 
158
- | Problem | Fix |
235
+ <details>
236
+ <summary>Troubleshoot installation, scans, and visibility results</summary>
237
+
238
+ | Problem | Next step |
159
239
  |---|---|
160
- | Site scan is still running | Read exact counters with `cnry technical-aeo progress <project> --run-id <id> --format json`. |
161
- | Site scan failed | Read the error with `cnry run show <run-id> --format json`. Read the last phase and counters with the progress command above. |
240
+ | Site scan is still active | Read exact counters with `cnry technical-aeo progress <project> --run-id <id> --format json`. |
241
+ | Site scan failed | Read the error with `cnry run show <run-id> --format json`. Read the last phase and counters with the progress command. |
162
242
  | No visibility results | Inspect existing work with `cnry runs <project> --format json`, then `cnry run show <run-id> --format json`. This does not start another paid run. |
163
- | Need more query candidates | Run `cnry discover run <project> --icp "..."`. This does not change the basket. Preview a completed session with `cnry discover promote preview <project> <session-id>`. Promote only after approval. |
164
- | Need one-off research | Run `cnry research run <project> "query one" "query two" --wait`. Research does not change the tracked basket. |
165
243
  | `npm install` fails on `node-gyp` | Install build tools for `better-sqlite3` ([guide](https://github.com/WiseLibs/better-sqlite3/blob/master/docs/troubleshooting.md)). |
166
244
 
167
- ## Documentation
168
-
169
- | | |
170
- |---|---|
171
- | **Architecture & data model** | [docs/architecture.md](docs/architecture.md) · [docs/data-model.md](docs/data-model.md) |
172
- | **Aero — built-in agent** | [skills/aero/SKILL.md](skills/aero/SKILL.md) |
173
- | **Agent Plugin — portable core + Codex / Claude adapters** | [docs/plugins.md](docs/plugins.md) |
174
- | **MCP — Claude Desktop / Cursor / Codex** | [docs/mcp.md](docs/mcp.md) |
175
- | **Integrations** | [GSC](docs/google-search-console-setup.md) · [GA4](docs/google-analytics-setup.md) · [Google Ads + GTM](docs/google-marketing.md) · [Bing](docs/bing-webmaster-setup.md) · [Google Business Profile](skills/canonry/references/google-business-profile.md) · [WordPress](docs/wordpress-setup.md) · [Server-side traffic (Cloudflare direct push or Queue pull, Cloud Run, Vercel, WordPress)](skills/canonry/references/server-side-traffic.md) |
176
- | **Deployment** — reverse proxies, Docker, systemd, Tailscale | [docs/deployment.md](docs/deployment.md) |
177
- | **API** | `GET /api/v1/openapi.json` |
178
- | **Standalone skills bundle** for Claude Code / Codex | `cnry skills install` ([details](skills/canonry/SKILL.md)) |
179
- | **All docs** | [docs/README.md](docs/README.md) |
245
+ </details>
180
246
 
181
247
  ## Contributing
182
248
 
@@ -14,7 +14,7 @@ Trigger: First sweep completes for a new project
14
14
  Steps:
15
15
  1. `cnry overview <project> --format json` → mention read first: `queryCounts.mentionRate`, `scores.mention`, `scores.mentionShare`. Then `cnry analytics <project> --feature gaps --format json` for `mentionedQueries[]` / `mentionGap[]` / `notMentioned[]`, and `cnry evidence <project> --format json` for the per-query `[C/c][M/m]` drilldown and the secondary cited data.
16
16
  2. Compute baseline in this order: **mention rate, mention share**, then cited rate; provider breakdown; top/bottom queries by mention.
17
- 3. With explicit operator approval for the crawl and persisted run, `cnry technical-aeo run <project> --wait`, then `cnry technical-aeo score <project> --format json` for site readiness. Use `cnry site-health overview <project> --format json` only to add crawl metadata (root, completeness, budgets, and termination); it never replaces the score. The crawl discovers the in-scope URL inventory from the root, sitemaps, and internal links. The default budgets are 1,000 pages and 100,000 edges; use `--max-pages`, `--max-edges`, or `--max-depth` to tighten them. Dead-link checks remain off unless `--check-dead-links` is explicit. For architecture investigation, use bounded Site Health subgraph/path/changes reads rather than attempting to load the visualization graph. Treat `countAccuracy: "lower-bound"` subgraph counts as minimums, and qualify an incomplete path's unreachable/truncated result with `complete: false` plus `termination`; neither is a site-wide conclusion. Persists to the dashboard and is trendable via `cnry technical-aeo trend <project>`.
17
+ 3. With explicit operator approval for the crawl and persisted run, `cnry technical-aeo run <project> --wait`, then `cnry technical-aeo score <project> --format json` for site readiness. Use `cnry site-health overview <project> --format json` only to add crawl metadata (root, completeness, budgets, and termination); it never replaces the score. The crawl discovers the in-scope URL inventory from the root, sitemaps, and internal links. The default page budget is 1,000; the edge budget is unset by default and the crawl engine derives it from the page count, so `--max-edges` sets a ceiling rather than lifting one. Use `--max-pages`, `--max-edges`, or `--max-depth` to tighten them. Dead-link checks remain off unless `--check-dead-links` is explicit. For architecture investigation, use bounded Site Health subgraph/path/changes reads rather than attempting to load the visualization graph. Treat `countAccuracy: "lower-bound"` subgraph counts as minimums, and qualify an incomplete path's unreachable/truncated result with `complete: false` plus `termination`; neither is a site-wide conclusion. Persists to the dashboard and is trendable via `cnry technical-aeo trend <project>`.
18
18
  4. Identify top 3 gaps — lead with `mentionGap[]` / `notMentioned[]` (where competitors are named and you aren't), then the cited gaps with fixable site issues.
19
19
  5. Generate onboarding report with baseline + action plan
20
20
  6. Store baseline metrics in memory (include mention rate + mention share, not just cited rate)
@@ -257,7 +257,7 @@ cnry visibility-compare <project> --from 2026-05 --to 2026-06 --format json
257
257
  Site-wide technical audit (structured data, AI-readable content, AI-crawler access, content depth/freshness/extractability, …) powered by `@canonry/aeo-audit`'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.
258
258
 
259
259
  ```bash
260
- cnry technical-aeo run <project> --wait # full crawl + audit; defaults to 1,000 pages / 100,000 edges; waits for terminal state
260
+ cnry technical-aeo run <project> --wait # full crawl + audit; defaults to 1,000 pages, edges derived from the page count unless --max-edges is set; waits for terminal state
261
261
  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
262
262
  cnry technical-aeo run <project> --check-dead-links --wait # opt in to dead-link checks; they are off by default
263
263
  cnry technical-aeo progress <project> --run-id <id> [--format json] # exact durable phase and pages found / checked / failed counters; never a synthesized percentage
@@ -1 +1 @@
1
- import{r as a,u as h,j as e}from"./vendor-tanstack-Dock0eH1.js";import{cE as L,cF as E,bX as P,i as u,B as x,f as v,T as M}from"./index-BlILyZJ5.js";const o=100;function k(s){if(s==="all")return;const n=Number.parseInt(s,10);return new Date(Date.now()-n*24*60*60*1e3).toISOString()}function O(s){const n=s.userAgent?.toLowerCase()??"";return n.includes("canonry-mcp")?"MCP":n.includes("canonry-cli")?"CLI":n.includes("mozilla/")?"Dashboard":s.actor==="scheduler"?"Scheduler":s.actor==="agent"?"Agent":s.actor==="mcp"?"MCP":s.actor==="cli"?"CLI":s.actor}function B(s){return/delete|remove|revoke|disconnect|failed/.test(s)?"negative":/create|add|connect|completed/.test(s)?"positive":/update|replace|sync|apply/.test(s)?"caution":"neutral"}function D(s){return[s.action,s.actor,s.entityType,s.entityId,s.actorSession,s.userAgent,s.diff==null?"":JSON.stringify(s.diff)].filter(Boolean).join(" ").toLowerCase()}function H({projectName:s}){const[n,l]=a.useState(0),[c,y]=a.useState("90d"),[m,N]=a.useState(""),[p,w]=a.useState(""),S=a.useMemo(()=>k(c),[c]),g={limit:o,offset:n*o,since:S,actor:m||void 0},C=h({...L({client:u,path:{name:s??""},query:g}),enabled:!!s}),A=h({...E({client:u,query:g}),enabled:!s}),f=h({...P({client:u}),enabled:!s}),i=s?C:A,r=i.data??[],I=a.useMemo(()=>new Map((f.data??[]).map(t=>[t.id,t.displayName||t.name])),[f.data]),d=p.trim().toLowerCase(),j=a.useMemo(()=>d?r.filter(t=>D(t).includes(d)):r,[r,d]),b=t=>{l(0),t()};return i.isLoading?e.jsx("p",{className:"supporting-copy mt-6",role:"status",children:"Loading change history…"}):e.jsxs("section",{className:"mt-6","aria-labelledby":"change-history-heading",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Audit trail"}),e.jsx("h2",{id:"change-history-heading",children:"Change history"}),e.jsx("p",{className:"supporting-copy mt-1 max-w-2xl",children:"Configuration and operator actions. Execution outcomes remain in Runs."})]}),e.jsx(x,{type:"button",variant:"outline",size:"sm",onClick:()=>{i.refetch()},disabled:i.isFetching,children:i.isFetching?"Refreshing…":"Refresh"})]}),e.jsxs("div",{className:"mt-4 grid gap-2 sm:grid-cols-3",children:[e.jsx("input",{type:"search","aria-label":"Search change history",value:p,onChange:t=>w(t.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"}),e.jsxs("select",{"aria-label":"Filter change history by actor",value:m,onChange:t=>b(()=>N(t.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:[e.jsx("option",{value:"",children:"All actors"}),e.jsx("option",{value:"api",children:"API and dashboard"}),e.jsx("option",{value:"cli",children:"CLI"}),e.jsx("option",{value:"scheduler",children:"Scheduler"}),e.jsx("option",{value:"agent",children:"Agent"}),e.jsx("option",{value:"mcp",children:"MCP"})]}),e.jsxs("select",{"aria-label":"Filter change history by date range",value:c,onChange:t=>b(()=>y(t.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:[e.jsx("option",{value:"30d",children:"Last 30 days"}),e.jsx("option",{value:"90d",children:"Last 90 days"}),e.jsx("option",{value:"365d",children:"Last year"}),e.jsx("option",{value:"all",children:"All retained history"})]})]}),i.isError?e.jsxs(v,{className:"surface-card empty-card mt-4",children:[e.jsx("h3",{children:"Change history unavailable"}),e.jsx("p",{children:i.error instanceof Error?i.error.message:"Could not load the audit trail."})]}):j.length===0?e.jsxs(v,{className:"surface-card empty-card mt-4",children:[e.jsx("h3",{children:"No changes match these filters"}),e.jsx("p",{children:"Widen the date range or clear the actor and search filters."})]}):e.jsx("div",{className:"evidence-table-wrap mt-4",children:e.jsxs("table",{className:"evidence-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"When"}),s?null:e.jsx("th",{children:"Scope"}),e.jsx("th",{children:"Action"}),e.jsx("th",{children:"Entity"}),e.jsx("th",{children:"Origin"}),e.jsx("th",{children:"Details"})]})}),e.jsx("tbody",{children:j.map(t=>e.jsxs("tr",{children:[e.jsx("td",{className:"whitespace-nowrap tabular-nums text-muted",children:new Date(t.createdAt).toLocaleString()}),s?null:e.jsx("td",{className:"text-secondary",children:t.projectId?I.get(t.projectId)??`Deleted project · ${t.projectId}`:"Instance settings"}),e.jsx("td",{children:e.jsx(M,{tone:B(t.action),children:t.action})}),e.jsxs("td",{children:[e.jsx("span",{className:"text-strong",children:t.entityType}),t.entityId?e.jsx("span",{className:"mt-0.5 block max-w-48 truncate font-mono text-[11px] text-faint",title:t.entityId,children:t.entityId}):null]}),e.jsxs("td",{children:[e.jsx("span",{className:"text-secondary",children:O(t)}),t.actorSession?e.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]}),e.jsx("td",{children:t.diff==null?e.jsx("span",{className:"text-faint",children:"No field diff"}):e.jsxs("details",{children:[e.jsx("summary",{className:"min-h-11 cursor-pointer py-3 text-sm text-secondary hover:text-strong",children:"View diff"}),e.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))})]})}),!i.isError&&(n>0||r.length===o)?e.jsxs("div",{className:"mt-4 flex items-center justify-between gap-3",children:[e.jsx(x,{type:"button",variant:"outline",size:"sm",disabled:n===0,onClick:()=>l(t=>Math.max(0,t-1)),children:"Previous"}),e.jsxs("span",{className:"text-xs tabular-nums text-muted",children:["Page ",n+1]}),e.jsx(x,{type:"button",variant:"outline",size:"sm",disabled:r.length<o,onClick:()=>l(t=>t+1),children:"Next"})]}):null]})}export{H as A};
1
+ import{r as a,u as h,j as e}from"./vendor-tanstack-CYBjbqVr.js";import{cG as L,cH as P,bW as E,f as u,B as x,e as v,T as M}from"./index-B64WXjg8.js";const o=100;function k(s){if(s==="all")return;const n=Number.parseInt(s,10);return new Date(Date.now()-n*24*60*60*1e3).toISOString()}function O(s){const n=s.userAgent?.toLowerCase()??"";return n.includes("canonry-mcp")?"MCP":n.includes("canonry-cli")?"CLI":n.includes("mozilla/")?"Dashboard":s.actor==="scheduler"?"Scheduler":s.actor==="agent"?"Agent":s.actor==="mcp"?"MCP":s.actor==="cli"?"CLI":s.actor}function B(s){return/delete|remove|revoke|disconnect|failed/.test(s)?"negative":/create|add|connect|completed/.test(s)?"positive":/update|replace|sync|apply/.test(s)?"caution":"neutral"}function D(s){return[s.action,s.actor,s.entityType,s.entityId,s.actorSession,s.userAgent,s.diff==null?"":JSON.stringify(s.diff)].filter(Boolean).join(" ").toLowerCase()}function F({projectName:s}){const[n,l]=a.useState(0),[c,y]=a.useState("90d"),[m,N]=a.useState(""),[p,w]=a.useState(""),S=a.useMemo(()=>k(c),[c]),g={limit:o,offset:n*o,since:S,actor:m||void 0},C=h({...L({client:u,path:{name:s??""},query:g}),enabled:!!s}),A=h({...P({client:u,query:g}),enabled:!s}),f=h({...E({client:u}),enabled:!s}),i=s?C:A,r=i.data??[],I=a.useMemo(()=>new Map((f.data??[]).map(t=>[t.id,t.displayName||t.name])),[f.data]),d=p.trim().toLowerCase(),j=a.useMemo(()=>d?r.filter(t=>D(t).includes(d)):r,[r,d]),b=t=>{l(0),t()};return i.isLoading?e.jsx("p",{className:"supporting-copy mt-6",role:"status",children:"Loading change history…"}):e.jsxs("section",{className:"mt-6","aria-labelledby":"change-history-heading",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Audit trail"}),e.jsx("h2",{id:"change-history-heading",children:"Change history"}),e.jsx("p",{className:"supporting-copy mt-1 max-w-2xl",children:"Configuration and operator actions. Execution outcomes remain in Runs."})]}),e.jsx(x,{type:"button",variant:"outline",size:"sm",onClick:()=>{i.refetch()},disabled:i.isFetching,children:i.isFetching?"Refreshing…":"Refresh"})]}),e.jsxs("div",{className:"mt-4 grid gap-2 sm:grid-cols-3",children:[e.jsx("input",{type:"search","aria-label":"Search change history",value:p,onChange:t=>w(t.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"}),e.jsxs("select",{"aria-label":"Filter change history by actor",value:m,onChange:t=>b(()=>N(t.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:[e.jsx("option",{value:"",children:"All actors"}),e.jsx("option",{value:"api",children:"API and dashboard"}),e.jsx("option",{value:"cli",children:"CLI"}),e.jsx("option",{value:"scheduler",children:"Scheduler"}),e.jsx("option",{value:"agent",children:"Agent"}),e.jsx("option",{value:"mcp",children:"MCP"})]}),e.jsxs("select",{"aria-label":"Filter change history by date range",value:c,onChange:t=>b(()=>y(t.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:[e.jsx("option",{value:"30d",children:"Last 30 days"}),e.jsx("option",{value:"90d",children:"Last 90 days"}),e.jsx("option",{value:"365d",children:"Last year"}),e.jsx("option",{value:"all",children:"All retained history"})]})]}),i.isError?e.jsxs(v,{className:"surface-card empty-card mt-4",children:[e.jsx("h3",{children:"Change history unavailable"}),e.jsx("p",{children:i.error instanceof Error?i.error.message:"Could not load the audit trail."})]}):j.length===0?e.jsxs(v,{className:"surface-card empty-card mt-4",children:[e.jsx("h3",{children:"No changes match these filters"}),e.jsx("p",{children:"Widen the date range or clear the actor and search filters."})]}):e.jsx("div",{className:"evidence-table-wrap mt-4",children:e.jsxs("table",{className:"evidence-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"When"}),s?null:e.jsx("th",{children:"Scope"}),e.jsx("th",{children:"Action"}),e.jsx("th",{children:"Entity"}),e.jsx("th",{children:"Origin"}),e.jsx("th",{children:"Details"})]})}),e.jsx("tbody",{children:j.map(t=>e.jsxs("tr",{children:[e.jsx("td",{className:"whitespace-nowrap tabular-nums text-muted",children:new Date(t.createdAt).toLocaleString()}),s?null:e.jsx("td",{className:"text-secondary",children:t.projectId?I.get(t.projectId)??`Deleted project · ${t.projectId}`:"Instance settings"}),e.jsx("td",{children:e.jsx(M,{tone:B(t.action),children:t.action})}),e.jsxs("td",{children:[e.jsx("span",{className:"text-strong",children:t.entityType}),t.entityId?e.jsx("span",{className:"mt-0.5 block max-w-48 truncate font-mono text-[11px] text-faint",title:t.entityId,children:t.entityId}):null]}),e.jsxs("td",{children:[e.jsx("span",{className:"text-secondary",children:O(t)}),t.actorSession?e.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]}),e.jsx("td",{children:t.diff==null?e.jsx("span",{className:"text-faint",children:"No field diff"}):e.jsxs("details",{children:[e.jsx("summary",{className:"min-h-11 cursor-pointer py-3 text-sm text-secondary hover:text-strong",children:"View diff"}),e.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))})]})}),!i.isError&&(n>0||r.length===o)?e.jsxs("div",{className:"mt-4 flex items-center justify-between gap-3",children:[e.jsx(x,{type:"button",variant:"outline",size:"sm",disabled:n===0,onClick:()=>l(t=>Math.max(0,t-1)),children:"Previous"}),e.jsxs("span",{className:"text-xs tabular-nums text-muted",children:["Page ",n+1]}),e.jsx(x,{type:"button",variant:"outline",size:"sm",disabled:r.length<o,onClick:()=>l(t=>t+1),children:"Next"})]}):null]})}export{F as A};
@@ -1 +1 @@
1
- import{r as n,j as e}from"./vendor-tanstack-Dock0eH1.js";import{cT as _,ah as H,cU as U,cV as V,cW as Y,f as x,T as j,j as u,B as w,l as F,cX as K,cY as X,ak as Z,cZ as J}from"./index-BlILyZJ5.js";import{D as ee,T as se,E as te,P as ae,v as ne,t as re}from"./vendor-lucide-nYKv-BB7.js";import"./vendor-radix-4UOaVWza.js";import"./vendor-recharts-D8rt5lKn.js";import"./vendor-markdown-Bn7fRwrx.js";import"./vendor-yaml-GWJz5H2G.js";function g({children:t,label:h="More info",placement:a="top",className:i}){const d=n.useId(),[y,c]=n.useState(!1);return e.jsxs("span",{className:`relative inline-flex ${i??""}`,children:[e.jsx("button",{type:"button","aria-label":h,"aria-describedby":y?d:void 0,className:"inline-flex h-4 w-4 items-center justify-center rounded-full text-muted hover:text-strong focus:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500",onMouseEnter:()=>c(!0),onMouseLeave:()=>c(!1),onFocus:()=>c(!0),onBlur:()=>c(!1),children:e.jsx(re,{className:"h-3.5 w-3.5","aria-hidden":!0})}),y&&e.jsx("span",{id:d,role:"tooltip",className:`absolute z-50 w-64 rounded border border-strong bg-bg-elevated px-3 py-2 text-xs font-normal leading-relaxed text-strong shadow-lg ${a==="top"?"bottom-full mb-2":"top-full mt-2"} left-1/2 -translate-x-1/2 whitespace-normal`,children:t})]})}const le="https://commoncrawl.org/web-graphs";function S(t){return t==null?"—":t>=1e12?`${(t/1e12).toFixed(1)} TB`:t>=1e9?`${(t/1e9).toFixed(1)} GB`:t>=1e6?`${(t/1e6).toFixed(1)} MB`:t>=1e3?`${(t/1e3).toFixed(1)} KB`:`${t} B`}function b(t){if(!t)return"—";const h=Date.now()-new Date(t).getTime(),a=Math.floor(h/6e4);if(a<1)return"just now";if(a<60)return`${a}m ago`;const i=Math.floor(a/60);return i<24?`${i}h ago`:`${Math.floor(i/24)}d ago`}function C(t){switch(t){case"ready":return"positive";case"failed":return"negative";case"downloading":case"querying":case"queued":return"caution"}}function me(){const[t,h]=n.useState(null),[a,i]=n.useState(null),[d,y]=n.useState([]),[c,$]=n.useState([]),[m,L]=n.useState(null),[B,D]=n.useState(!0),[R,E]=n.useState(!1),[p,I]=n.useState(!1),[N,v]=n.useState(""),[T,k]=n.useState(!1),[A,l]=n.useState(null),[P,o]=n.useState(null),f=n.useCallback(async()=>{D(!0),l(null);try{const[s,r,W,z,O]=await Promise.all([_(),H().catch(()=>null),U().catch(()=>[]),V().catch(()=>[]),Y().catch(()=>null)]);h(s),i(r),y(W),$(z),L(O)}catch(s){l(s instanceof Error?s.message:"Failed to load backlinks status")}finally{D(!1)}},[]);n.useEffect(()=>{f()},[f]);async function M(){E(!0),l(null),o(null);try{const s=await K();o(s.alreadyPresent?`DuckDB already installed (${s.version}).`:`Installed DuckDB ${s.version}.`),await f()}catch(s){l(s instanceof Error?s.message:"Failed to install DuckDB")}finally{E(!1)}}async function q(){const s=N.trim()||void 0;I(!0),l(null),o(null);try{const r=await X(s);o(s?`Queued sync for ${r.release}. Download + query runs in the background.`:`Queued sync for auto-discovered release ${r.release}. Download + query runs in the background.`),v(""),k(!1),await f()}catch(r){r instanceof Z&&r.code==="MISSING_DEPENDENCY"?l("DuckDB is not installed. Install it first."):l(r instanceof Error?r.message:"Failed to trigger sync")}finally{I(!1)}}async function G(s){l(null),o(null);try{await J(s),o(`Pruned cached release ${s}.`),await f()}catch(r){l(r instanceof Error?r.message:"Failed to prune release")}}const Q=a?.status==="ready"&&c.every(s=>s.release!==a.release);return e.jsxs("div",{className:"page-container",children:[e.jsx("div",{className:"page-header",children:e.jsxs("div",{className:"page-header-left",children:[e.jsx("h1",{className:"page-title",children:"Backlink data"}),e.jsx("p",{className:"page-subtitle",children:"Manage the shared backlink source and review its latest data."})]})}),A&&e.jsx(x,{className:"surface-card p-4 mb-4 border-negative-800/60",children:e.jsx("p",{className:"text-sm text-negative",children:A})}),P&&e.jsx(x,{className:"surface-card p-4 mb-4 border-positive-800/60",children:e.jsx("p",{className:"text-sm text-positive",children:P})}),e.jsxs("section",{className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Readiness"}),e.jsx("h2",{children:"Backlink source"})]}),t?.duckdbInstalled?e.jsx(j,{tone:"positive",children:"Installed"}):e.jsx(j,{tone:"caution",children:"Not installed"})]}),e.jsx(x,{className:"surface-card p-4",children:B?e.jsx("p",{className:"text-sm text-muted",children:"Checking…"}):t?.duckdbInstalled?e.jsx("p",{className:"text-sm text-secondary",children:"Ready to sync a Common Crawl release."}):e.jsxs("div",{className:"flex items-center justify-between gap-4",children:[e.jsx("p",{className:"text-sm text-secondary",children:"Install the local query engine to enable release syncs."}),!u()&&e.jsxs(w,{type:"button",size:"sm",disabled:R,onClick:F(M),children:[e.jsx(ee,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),R?"Installing…":"Install DuckDB"]})]})})]}),e.jsxs("section",{className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Latest sync"}),e.jsxs("h2",{className:"flex items-center gap-2",children:["Release sync",e.jsx(g,{label:"What is a release sync?",children:"A release sync downloads one Common Crawl dump (~16 GB) and extracts backlinks for every project in this workspace in one pass. This is the heavy job — subsequent per-project re-runs skip the download and just re-query the cached files."})]})]}),a&&e.jsx(j,{tone:C(a.status),children:a.status})]}),e.jsxs(x,{className:"surface-card p-5",children:[a?e.jsxs("div",{className:"space-y-2 text-sm",children:[e.jsxs("p",{className:"text-strong",children:["Release ",e.jsx("code",{className:"text-neutral",children:a.release})]}),a.phaseDetail&&e.jsx("p",{className:"text-secondary",children:a.phaseDetail}),e.jsxs("div",{className:"grid grid-cols-2 md:grid-cols-4 gap-4 text-xs text-muted pt-2",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Projects"}),e.jsx("p",{className:"text-neutral mt-0.5",children:a.projectsProcessed??"—"})]}),e.jsxs("div",{children:[e.jsxs("p",{className:"text-faint uppercase tracking-wide flex items-center gap-1",children:["Rows",e.jsx(g,{label:"What are rows?",children:"Total number of (project, referring domain) pairs persisted in SQLite from this sync, across every project in the workspace."})]}),e.jsx("p",{className:"text-neutral mt-0.5",children:a.domainsDiscovered??"—"})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Started"}),e.jsx("p",{className:"text-neutral mt-0.5",children:b(a.downloadStartedAt??a.createdAt)})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Finished"}),e.jsx("p",{className:"text-neutral mt-0.5",children:b(a.queryFinishedAt)})]})]}),a.error&&e.jsx("p",{className:"text-sm text-negative-400 pt-2",children:a.error})]}):e.jsx("p",{className:"text-sm text-muted",children:"No release sync has run in this workspace yet."}),Q&&e.jsx("div",{className:"mt-4 rounded border border-caution-800/60 bg-caution-950/20 p-3",children:e.jsxs("div",{className:"flex items-start gap-2",children:[e.jsx(se,{className:"h-4 w-4 text-caution-400 shrink-0 mt-0.5","aria-hidden":!0}),e.jsxs("div",{className:"text-sm text-neutral leading-relaxed",children:[e.jsx("p",{className:"font-medium text-caution-200",children:"Cached files for this release are missing."}),e.jsx("p",{className:"mt-1 text-secondary",children:"Sync this release again, or sync the newest release, before refreshing individual projects."})]})]})}),e.jsxs("div",{className:"mt-4 rounded border border-base bg-bg-elevated/40 p-3",children:[e.jsxs("div",{className:"flex items-start justify-between gap-3 mb-3",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-[10px] uppercase tracking-wide text-muted",children:"Auto-detected release"}),m?e.jsxs("p",{className:"text-sm text-strong mt-0.5",children:[e.jsx("code",{className:"text-heading",children:m.release}),e.jsxs("span",{className:"ml-2 text-xs text-muted",children:["Vertex ",S(m.vertexBytes),", edges ",S(m.edgesBytes)]})]}):e.jsx("p",{className:"text-sm text-muted mt-0.5",children:B?"Checking Common Crawl…":"Could not find a release. Choose one below."}),e.jsxs("a",{href:le,target:"_blank",rel:"noopener noreferrer",className:"mt-1 inline-flex items-center gap-1 text-xs text-secondary hover:text-strong focus:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",children:["Browse all Common Crawl web-graph releases",e.jsx(te,{className:"h-3 w-3","aria-hidden":!0})]})]}),!u()&&e.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[e.jsxs(w,{type:"button",size:"sm",disabled:p||!t?.duckdbInstalled||!m&&!N.trim(),onClick:F(q),children:[e.jsx(ae,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),p?"Queuing…":"Run sync"]}),e.jsxs(g,{label:"What does Run sync do?",children:[e.jsxs("span",{className:"block",children:["Downloads the auto-detected (or chosen) Common Crawl release (~16 GB) to"," ",e.jsx("code",{className:"text-neutral",children:"~/.canonry/cache/commoncrawl/"}),", then runs a single DuckDB query that extracts referring domains for every project in this workspace."]}),e.jsxs("span",{className:"mt-2 block text-secondary",children:["First time for a release: ",e.jsx("span",{className:"text-strong",children:"~10–20 min download + ~5 min query"}),". Re-running the same release later: ",e.jsx("span",{className:"text-strong",children:"skips download, just re-queries"})," (~5 min)."]})]})]})]}),u()?null:T?e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("input",{type:"text",className:"flex-1 min-w-[240px] rounded border border-strong bg-transparent px-2.5 py-1.5 text-sm text-strong placeholder-mono-600 focus:border-mono-500 focus:outline-none",placeholder:"cc-main-2026-jan-feb-mar",value:N,onChange:s=>v(s.target.value),disabled:p,autoFocus:!0}),e.jsx("button",{type:"button",className:"text-sm text-secondary hover:text-neutral focus:text-neutral focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",onClick:()=>{v(""),k(!1)},disabled:p,children:"Cancel"})]}):e.jsx("button",{type:"button",className:"text-sm text-secondary hover:text-neutral focus:text-neutral focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",onClick:()=>k(!0),disabled:p,children:"Use a different release →"})]}),!t?.duckdbInstalled&&e.jsx("p",{className:"text-sm text-secondary mt-2",children:"Install DuckDB first to enable sync."})]})]}),e.jsxs("details",{className:"page-section-divider group",children:[e.jsx("summary",{className:"cursor-pointer text-sm font-medium text-secondary hover:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",children:"Technical details and cache"}),e.jsxs("div",{className:"mt-4",children:[e.jsx("p",{className:"text-sm text-secondary mb-3",children:"Cached releases support project re-extracts. Pruning a release does not remove saved backlink results."}),e.jsx(x,{className:"surface-card overflow-hidden",children:e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-base text-left text-xs uppercase tracking-wide text-faint",children:[e.jsx("th",{className:"px-4 py-2 font-medium",children:"Release"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Sync status"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Size"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Last used"}),!u()&&e.jsx("th",{className:"px-4 py-2 font-medium sr-only",children:"Actions"})]})}),e.jsxs("tbody",{children:[c.map(s=>e.jsxs("tr",{className:"border-b border-mono-900 last:border-0",children:[e.jsx("td",{className:"px-4 py-2 text-strong",children:e.jsx("code",{children:s.release})}),e.jsx("td",{className:"px-4 py-2",children:s.syncStatus?e.jsx(j,{tone:C(s.syncStatus),children:s.syncStatus}):e.jsx("span",{className:"text-faint",children:"—"})}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:S(s.bytes)}),e.jsx("td",{className:"px-4 py-2 text-secondary",children:b(s.lastUsedAt)}),!u()&&e.jsx("td",{className:"px-4 py-2 text-right",children:e.jsxs("div",{className:"inline-flex items-center gap-1",children:[e.jsxs(w,{type:"button",variant:"outline",size:"sm",onClick:()=>{G(s.release)},children:[e.jsx(ne,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),"Prune"]}),e.jsx(g,{label:"What does Prune do?",placement:"top",children:"Deletes the ~16 GB cache for this release from disk. Backlink results already in SQLite remain untouched. To re-run extracts against this release, you’d have to sync it again (another ~16 GB download)."})]})})]},s.release)),c.length===0&&e.jsx("tr",{children:e.jsx("td",{className:"px-4 py-4 text-sm text-muted",colSpan:u()?4:5,children:"No cached releases on this machine. If you ran a sync from a different machine (or deleted the cache), the backlink data is still in the database — but you’ll need to re-sync a release to run new extracts."})})]})]})}),e.jsx("p",{className:"mt-3 text-xs text-muted",children:"Common Crawl files are stored locally. DuckDB reads them; Canonry stores results in its normal database."})]})]}),d.length>1&&e.jsxs("section",{className:"page-section-divider",children:[e.jsx("div",{className:"section-head section-head-inline",children:e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"History"}),e.jsx("h2",{children:"Past release syncs"})]})}),e.jsx(x,{className:"surface-card overflow-hidden",children:e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-base text-left text-xs uppercase tracking-wide text-faint",children:[e.jsx("th",{className:"px-4 py-2 font-medium",children:"Release"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Status"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Projects"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Rows"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Finished"})]})}),e.jsx("tbody",{children:d.map(s=>e.jsxs("tr",{className:"border-b border-mono-900 last:border-0",children:[e.jsx("td",{className:"px-4 py-2 text-strong",children:e.jsx("code",{children:s.release})}),e.jsx("td",{className:"px-4 py-2",children:e.jsx(j,{tone:C(s.status),children:s.status})}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:s.projectsProcessed??"—"}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:s.domainsDiscovered??"—"}),e.jsx("td",{className:"px-4 py-2 text-secondary",children:b(s.queryFinishedAt??s.updatedAt)})]},s.id))})]})})]})]})}export{me as BacklinksPage};
1
+ import{r as n,j as e}from"./vendor-tanstack-CYBjbqVr.js";import{cV as O,ag as H,cW as U,cX as V,cY as Y,e as x,T as j,i as u,B as w,k as $,cZ as K,c_ as X,aj as Z,c$ as J}from"./index-B64WXjg8.js";import{D as ee,T as se,E as te,P as ae,v as ne,t as re}from"./vendor-lucide-XjCNFK0x.js";import"./vendor-radix-BpmvLvpp.js";import"./vendor-recharts-FA0uySU7.js";import"./vendor-markdown-9tTeP8v7.js";import"./vendor-yaml-GWJz5H2G.js";function g({children:t,label:h="More info",placement:a="top",className:i}){const d=n.useId(),[y,c]=n.useState(!1);return e.jsxs("span",{className:`relative inline-flex ${i??""}`,children:[e.jsx("button",{type:"button","aria-label":h,"aria-describedby":y?d:void 0,className:"inline-flex h-4 w-4 items-center justify-center rounded-full text-muted hover:text-strong focus:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500",onMouseEnter:()=>c(!0),onMouseLeave:()=>c(!1),onFocus:()=>c(!0),onBlur:()=>c(!1),children:e.jsx(re,{className:"h-3.5 w-3.5","aria-hidden":!0})}),y&&e.jsx("span",{id:d,role:"tooltip",className:`absolute z-50 w-64 rounded border border-strong bg-bg-elevated px-3 py-2 text-xs font-normal leading-relaxed text-strong shadow-lg ${a==="top"?"bottom-full mb-2":"top-full mt-2"} left-1/2 -translate-x-1/2 whitespace-normal`,children:t})]})}const le="https://commoncrawl.org/web-graphs";function S(t){return t==null?"—":t>=1e12?`${(t/1e12).toFixed(1)} TB`:t>=1e9?`${(t/1e9).toFixed(1)} GB`:t>=1e6?`${(t/1e6).toFixed(1)} MB`:t>=1e3?`${(t/1e3).toFixed(1)} KB`:`${t} B`}function b(t){if(!t)return"—";const h=Date.now()-new Date(t).getTime(),a=Math.floor(h/6e4);if(a<1)return"just now";if(a<60)return`${a}m ago`;const i=Math.floor(a/60);return i<24?`${i}h ago`:`${Math.floor(i/24)}d ago`}function C(t){switch(t){case"ready":return"positive";case"failed":return"negative";case"downloading":case"querying":case"queued":return"caution"}}function me(){const[t,h]=n.useState(null),[a,i]=n.useState(null),[d,y]=n.useState([]),[c,F]=n.useState([]),[m,L]=n.useState(null),[B,D]=n.useState(!0),[R,E]=n.useState(!1),[p,I]=n.useState(!1),[N,v]=n.useState(""),[M,k]=n.useState(!1),[A,l]=n.useState(null),[P,o]=n.useState(null),f=n.useCallback(async()=>{D(!0),l(null);try{const[s,r,W,_,z]=await Promise.all([O(),H().catch(()=>null),U().catch(()=>[]),V().catch(()=>[]),Y().catch(()=>null)]);h(s),i(r),y(W),F(_),L(z)}catch(s){l(s instanceof Error?s.message:"Failed to load backlinks status")}finally{D(!1)}},[]);n.useEffect(()=>{f()},[f]);async function T(){E(!0),l(null),o(null);try{const s=await K();o(s.alreadyPresent?`DuckDB already installed (${s.version}).`:`Installed DuckDB ${s.version}.`),await f()}catch(s){l(s instanceof Error?s.message:"Failed to install DuckDB")}finally{E(!1)}}async function q(){const s=N.trim()||void 0;I(!0),l(null),o(null);try{const r=await X(s);o(s?`Queued sync for ${r.release}. Download + query runs in the background.`:`Queued sync for auto-discovered release ${r.release}. Download + query runs in the background.`),v(""),k(!1),await f()}catch(r){r instanceof Z&&r.code==="MISSING_DEPENDENCY"?l("DuckDB is not installed. Install it first."):l(r instanceof Error?r.message:"Failed to trigger sync")}finally{I(!1)}}async function G(s){l(null),o(null);try{await J(s),o(`Pruned cached release ${s}.`),await f()}catch(r){l(r instanceof Error?r.message:"Failed to prune release")}}const Q=a?.status==="ready"&&c.every(s=>s.release!==a.release);return e.jsxs("div",{className:"page-container",children:[e.jsx("div",{className:"page-header",children:e.jsxs("div",{className:"page-header-left",children:[e.jsx("h1",{className:"page-title",children:"Backlink data"}),e.jsx("p",{className:"page-subtitle",children:"Manage the shared backlink source and review its latest data."})]})}),A&&e.jsx(x,{className:"surface-card p-4 mb-4 border-negative-800/60",children:e.jsx("p",{className:"text-sm text-negative",children:A})}),P&&e.jsx(x,{className:"surface-card p-4 mb-4 border-positive-800/60",children:e.jsx("p",{className:"text-sm text-positive",children:P})}),e.jsxs("section",{className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Readiness"}),e.jsx("h2",{children:"Backlink source"})]}),t?.duckdbInstalled?e.jsx(j,{tone:"positive",children:"Installed"}):e.jsx(j,{tone:"caution",children:"Not installed"})]}),e.jsx(x,{className:"surface-card p-4",children:B?e.jsx("p",{className:"text-sm text-muted",children:"Checking…"}):t?.duckdbInstalled?e.jsx("p",{className:"text-sm text-secondary",children:"Ready to sync a Common Crawl release."}):e.jsxs("div",{className:"flex items-center justify-between gap-4",children:[e.jsx("p",{className:"text-sm text-secondary",children:"Install the local query engine to enable release syncs."}),!u()&&e.jsxs(w,{type:"button",size:"sm",disabled:R,onClick:$(T),children:[e.jsx(ee,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),R?"Installing…":"Install DuckDB"]})]})})]}),e.jsxs("section",{className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Latest sync"}),e.jsxs("h2",{className:"flex items-center gap-2",children:["Release sync",e.jsx(g,{label:"What is a release sync?",children:"A release sync downloads one Common Crawl dump (~16 GB) and extracts backlinks for every project in this workspace in one pass. This is the heavy job — subsequent per-project re-runs skip the download and just re-query the cached files."})]})]}),a&&e.jsx(j,{tone:C(a.status),children:a.status})]}),e.jsxs(x,{className:"surface-card p-5",children:[a?e.jsxs("div",{className:"space-y-2 text-sm",children:[e.jsxs("p",{className:"text-strong",children:["Release ",e.jsx("code",{className:"text-neutral",children:a.release})]}),a.phaseDetail&&e.jsx("p",{className:"text-secondary",children:a.phaseDetail}),e.jsxs("div",{className:"grid grid-cols-2 md:grid-cols-4 gap-4 text-xs text-muted pt-2",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Projects"}),e.jsx("p",{className:"text-neutral mt-0.5",children:a.projectsProcessed??"—"})]}),e.jsxs("div",{children:[e.jsxs("p",{className:"text-faint uppercase tracking-wide flex items-center gap-1",children:["Rows",e.jsx(g,{label:"What are rows?",children:"Total number of (project, referring domain) pairs persisted in SQLite from this sync, across every project in the workspace."})]}),e.jsx("p",{className:"text-neutral mt-0.5",children:a.domainsDiscovered??"—"})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Started"}),e.jsx("p",{className:"text-neutral mt-0.5",children:b(a.downloadStartedAt??a.createdAt)})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Finished"}),e.jsx("p",{className:"text-neutral mt-0.5",children:b(a.queryFinishedAt)})]})]}),a.error&&e.jsx("p",{className:"text-sm text-negative-400 pt-2",children:a.error})]}):e.jsx("p",{className:"text-sm text-muted",children:"No release sync has run in this workspace yet."}),Q&&e.jsx("div",{className:"mt-4 rounded border border-caution-800/60 bg-caution-950/20 p-3",children:e.jsxs("div",{className:"flex items-start gap-2",children:[e.jsx(se,{className:"h-4 w-4 text-caution-400 shrink-0 mt-0.5","aria-hidden":!0}),e.jsxs("div",{className:"text-sm text-neutral leading-relaxed",children:[e.jsx("p",{className:"font-medium text-caution-200",children:"Cached files for this release are missing."}),e.jsx("p",{className:"mt-1 text-secondary",children:"Sync this release again, or sync the newest release, before refreshing individual projects."})]})]})}),e.jsxs("div",{className:"mt-4 rounded border border-base bg-bg-elevated/40 p-3",children:[e.jsxs("div",{className:"flex items-start justify-between gap-3 mb-3",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-[10px] uppercase tracking-wide text-muted",children:"Auto-detected release"}),m?e.jsxs("p",{className:"text-sm text-strong mt-0.5",children:[e.jsx("code",{className:"text-heading",children:m.release}),e.jsxs("span",{className:"ml-2 text-xs text-muted",children:["Vertex ",S(m.vertexBytes),", edges ",S(m.edgesBytes)]})]}):e.jsx("p",{className:"text-sm text-muted mt-0.5",children:B?"Checking Common Crawl…":"Could not find a release. Choose one below."}),e.jsxs("a",{href:le,target:"_blank",rel:"noopener noreferrer",className:"mt-1 inline-flex items-center gap-1 text-xs text-secondary hover:text-strong focus:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",children:["Browse all Common Crawl web-graph releases",e.jsx(te,{className:"h-3 w-3","aria-hidden":!0})]})]}),!u()&&e.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[e.jsxs(w,{type:"button",size:"sm",disabled:p||!t?.duckdbInstalled||!m&&!N.trim(),onClick:$(q),children:[e.jsx(ae,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),p?"Queuing…":"Run sync"]}),e.jsxs(g,{label:"What does Run sync do?",children:[e.jsxs("span",{className:"block",children:["Downloads the auto-detected (or chosen) Common Crawl release (~16 GB) to"," ",e.jsx("code",{className:"text-neutral",children:"~/.canonry/cache/commoncrawl/"}),", then runs a single DuckDB query that extracts referring domains for every project in this workspace."]}),e.jsxs("span",{className:"mt-2 block text-secondary",children:["First time for a release: ",e.jsx("span",{className:"text-strong",children:"~10–20 min download + ~5 min query"}),". Re-running the same release later: ",e.jsx("span",{className:"text-strong",children:"skips download, just re-queries"})," (~5 min)."]})]})]})]}),u()?null:M?e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("input",{type:"text",className:"flex-1 min-w-[240px] rounded border border-strong bg-transparent px-2.5 py-1.5 text-sm text-strong placeholder-mono-600 focus:border-mono-500 focus:outline-none",placeholder:"cc-main-2026-jan-feb-mar",value:N,onChange:s=>v(s.target.value),disabled:p,autoFocus:!0}),e.jsx("button",{type:"button",className:"text-sm text-secondary hover:text-neutral focus:text-neutral focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",onClick:()=>{v(""),k(!1)},disabled:p,children:"Cancel"})]}):e.jsx("button",{type:"button",className:"text-sm text-secondary hover:text-neutral focus:text-neutral focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",onClick:()=>k(!0),disabled:p,children:"Use a different release →"})]}),!t?.duckdbInstalled&&e.jsx("p",{className:"text-sm text-secondary mt-2",children:"Install DuckDB first to enable sync."})]})]}),e.jsxs("details",{className:"page-section-divider group",children:[e.jsx("summary",{className:"cursor-pointer text-sm font-medium text-secondary hover:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",children:"Technical details and cache"}),e.jsxs("div",{className:"mt-4",children:[e.jsx("p",{className:"text-sm text-secondary mb-3",children:"Cached releases support project re-extracts. Pruning a release does not remove saved backlink results."}),e.jsx(x,{className:"surface-card overflow-hidden",children:e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-base text-left text-xs uppercase tracking-wide text-faint",children:[e.jsx("th",{className:"px-4 py-2 font-medium",children:"Release"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Sync status"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Size"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Last used"}),!u()&&e.jsx("th",{className:"px-4 py-2 font-medium sr-only",children:"Actions"})]})}),e.jsxs("tbody",{children:[c.map(s=>e.jsxs("tr",{className:"border-b border-mono-900 last:border-0",children:[e.jsx("td",{className:"px-4 py-2 text-strong",children:e.jsx("code",{children:s.release})}),e.jsx("td",{className:"px-4 py-2",children:s.syncStatus?e.jsx(j,{tone:C(s.syncStatus),children:s.syncStatus}):e.jsx("span",{className:"text-faint",children:"—"})}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:S(s.bytes)}),e.jsx("td",{className:"px-4 py-2 text-secondary",children:b(s.lastUsedAt)}),!u()&&e.jsx("td",{className:"px-4 py-2 text-right",children:e.jsxs("div",{className:"inline-flex items-center gap-1",children:[e.jsxs(w,{type:"button",variant:"outline",size:"sm",onClick:()=>{G(s.release)},children:[e.jsx(ne,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),"Prune"]}),e.jsx(g,{label:"What does Prune do?",placement:"top",children:"Deletes the ~16 GB cache for this release from disk. Backlink results already in SQLite remain untouched. To re-run extracts against this release, you’d have to sync it again (another ~16 GB download)."})]})})]},s.release)),c.length===0&&e.jsx("tr",{children:e.jsx("td",{className:"px-4 py-4 text-sm text-muted",colSpan:u()?4:5,children:"No cached releases on this machine. If you ran a sync from a different machine (or deleted the cache), the backlink data is still in the database — but you’ll need to re-sync a release to run new extracts."})})]})]})}),e.jsx("p",{className:"mt-3 text-xs text-muted",children:"Common Crawl files are stored locally. DuckDB reads them; Canonry stores results in its normal database."})]})]}),d.length>1&&e.jsxs("section",{className:"page-section-divider",children:[e.jsx("div",{className:"section-head section-head-inline",children:e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"History"}),e.jsx("h2",{children:"Past release syncs"})]})}),e.jsx(x,{className:"surface-card overflow-hidden",children:e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-base text-left text-xs uppercase tracking-wide text-faint",children:[e.jsx("th",{className:"px-4 py-2 font-medium",children:"Release"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Status"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Projects"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Rows"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Finished"})]})}),e.jsx("tbody",{children:d.map(s=>e.jsxs("tr",{className:"border-b border-mono-900 last:border-0",children:[e.jsx("td",{className:"px-4 py-2 text-strong",children:e.jsx("code",{children:s.release})}),e.jsx("td",{className:"px-4 py-2",children:e.jsx(j,{tone:C(s.status),children:s.status})}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:s.projectsProcessed??"—"}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:s.domainsDiscovered??"—"}),e.jsx("td",{className:"px-4 py-2 text-secondary",children:b(s.queryFinishedAt??s.updatedAt)})]},s.id))})]})})]})]})}export{me as BacklinksPage};
@@ -1 +1 @@
1
- import{j as i}from"./vendor-tanstack-Dock0eH1.js";import{R as k,C,a as R,e as A,X as I,Y as _,T as D,d as m}from"./vendor-recharts-D8rt5lKn.js";const L={contentStyle:{backgroundColor:"var(--chart-tooltip-bg, #18181b)",border:"1px solid var(--chart-tooltip-border, #3f3f46)",borderRadius:8,fontSize:12},labelStyle:{color:"var(--chart-tooltip-label, #e4e4e7)"},itemStyle:{color:"var(--chart-tooltip-item, #a1a1aa)"}},v={fill:"var(--chart-neutral-text-dim, #8b8b93)",fontSize:11},O="var(--chart-grid, #27272a)",b="var(--chart-axis, #27272a)",p=["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)"],j={gemini:"#60a5fa",openai:"#34d399",claude:"#fb923c",perplexity:"#22d3ee",local:"#a78bfa"};function $(r,a=0){return j[r]??p[a%p.length]}const K={text:"var(--chart-neutral-text, #a1a1aa)",textDim:"var(--chart-neutral-text-dim, #8b8b93)",textFaint:"var(--chart-neutral-text-faint, #52525b)",surface:"var(--chart-neutral-surface, #27272a)",trackSubtle:"var(--chart-neutral-track-subtle, rgb(255 255 255 / 0.04))"},H={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)"};function N(r){return r}function y(r){const a=/^(\d{4})-(\d{2})-(\d{2})/.exec(r);return a?{year:Number(a[1]),month:Number(a[2]),day:Number(a[3])}:null}function M(r){const a=String(r),e=y(a);return e?new Date(Date.UTC(e.year,e.month-1,e.day)).toLocaleDateString(void 0,{timeZone:"UTC",month:"short",day:"numeric",year:"numeric"}):a}function F(r){const a=y(String(r));return a?`${a.month}/${a.day}`:String(r)}function G(r){return new Date(r).toLocaleDateString(void 0,{month:"short",day:"numeric",year:"numeric"})}function P(r){const a=new Date(r);return`${a.getMonth()+1}/${a.getDate()}`}function U({data:r,xKey:a,series:e,height:x=260,xTickFormatter:g,labelFormatter:f,onSelectX:d,selectedX:u}){const h=[...new Set(e.map(t=>t.axisId))],S=new Map(e.map(t=>[t.label,t.formatValue])),T=r.map((t,s)=>{const n={...t};for(const o of e){if(!o.trend)continue;const c=o.trend.startIndex??0,l=o.trend.endIndex??r.length-1;if(s<c||s>l){n[`${o.dataKey}__trend`]=null;continue}n[`${o.dataKey}__trend`]=l<=c?o.trend.start:o.trend.start+(o.trend.end-o.trend.start)*(s-c)/(l-c)}return n});return i.jsx(k,{width:"100%",height:x,children:i.jsxs(C,{data:T,margin:{top:8,right:8,bottom:4,left:0},onClick:d?t=>{t?.activeLabel!==void 0&&d(String(t.activeLabel))}:void 0,style:d?{cursor:"pointer"}:void 0,children:[i.jsx(R,{stroke:O,strokeDasharray:"3 3",vertical:!1}),u!=null&&i.jsx(A,{x:u,yAxisId:h[0],stroke:K.text,strokeDasharray:"2 3",strokeWidth:1}),i.jsx(I,{dataKey:a,tick:v,stroke:b,tickFormatter:g,minTickGap:24}),h.map((t,s)=>{const n=e.find(o=>o.axisId===t);return i.jsx(_,{yAxisId:t,orientation:s===1?"right":"left",hide:s>1,reversed:n.inverted??!1,domain:n.inverted?[1,"auto"]:[0,"auto"],tick:v,stroke:b,width:48,tickFormatter:n.formatValue},t)}),i.jsx(D,{...L,labelFormatter:t=>f?f(String(t)):String(t),formatter:(t,s)=>{const n=String(s??"");return typeof t!="number"?[String(t??""),n]:[S.get(n)?.(t)??t.toLocaleString(),n]}}),e.filter(t=>t.trend).map(t=>i.jsx(m,{yAxisId:t.axisId,dataKey:`${t.dataKey}__trend`,name:`${t.label} trend`,stroke:t.color,strokeWidth:1.5,strokeDasharray:"5 4",strokeOpacity:.55,dot:!1,activeDot:!1,isAnimationActive:!1,legendType:"none",tooltipType:"none"},`${t.dataKey}-trend`)),e.map(t=>i.jsx(m,{yAxisId:t.axisId,dataKey:t.dataKey,name:t.label,stroke:t.color,strokeWidth:2,dot:!1,activeDot:{r:3},connectNulls:!1,isAnimationActive:!1},t.dataKey))]})})}export{O as C,U as M,b as a,v as b,M as c,L as d,p as e,F as f,K as g,H as h,P as i,G as j,N as o,$ as p};
1
+ import{j as i}from"./vendor-tanstack-CYBjbqVr.js";import{R as k,C,a as R,e as A,X as I,Y as _,T as D,d as m}from"./vendor-recharts-FA0uySU7.js";const L={contentStyle:{backgroundColor:"var(--chart-tooltip-bg, #18181b)",border:"1px solid var(--chart-tooltip-border, #3f3f46)",borderRadius:8,fontSize:12},labelStyle:{color:"var(--chart-tooltip-label, #e4e4e7)"},itemStyle:{color:"var(--chart-tooltip-item, #a1a1aa)"}},v={fill:"var(--chart-neutral-text-dim, #8b8b93)",fontSize:11},O="var(--chart-grid, #27272a)",b="var(--chart-axis, #27272a)",p=["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)"],j={gemini:"#60a5fa",openai:"#34d399",claude:"#fb923c",perplexity:"#22d3ee",local:"#a78bfa"};function $(r,a=0){return j[r]??p[a%p.length]}const K={text:"var(--chart-neutral-text, #a1a1aa)",textDim:"var(--chart-neutral-text-dim, #8b8b93)",textFaint:"var(--chart-neutral-text-faint, #52525b)",surface:"var(--chart-neutral-surface, #27272a)",trackSubtle:"var(--chart-neutral-track-subtle, rgb(255 255 255 / 0.04))"},H={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)"};function N(r){return r}function y(r){const a=/^(\d{4})-(\d{2})-(\d{2})/.exec(r);return a?{year:Number(a[1]),month:Number(a[2]),day:Number(a[3])}:null}function M(r){const a=String(r),e=y(a);return e?new Date(Date.UTC(e.year,e.month-1,e.day)).toLocaleDateString(void 0,{timeZone:"UTC",month:"short",day:"numeric",year:"numeric"}):a}function F(r){const a=y(String(r));return a?`${a.month}/${a.day}`:String(r)}function G(r){return new Date(r).toLocaleDateString(void 0,{month:"short",day:"numeric",year:"numeric"})}function P(r){const a=new Date(r);return`${a.getMonth()+1}/${a.getDate()}`}function U({data:r,xKey:a,series:e,height:x=260,xTickFormatter:g,labelFormatter:f,onSelectX:d,selectedX:u}){const h=[...new Set(e.map(t=>t.axisId))],S=new Map(e.map(t=>[t.label,t.formatValue])),T=r.map((t,s)=>{const n={...t};for(const o of e){if(!o.trend)continue;const c=o.trend.startIndex??0,l=o.trend.endIndex??r.length-1;if(s<c||s>l){n[`${o.dataKey}__trend`]=null;continue}n[`${o.dataKey}__trend`]=l<=c?o.trend.start:o.trend.start+(o.trend.end-o.trend.start)*(s-c)/(l-c)}return n});return i.jsx(k,{width:"100%",height:x,children:i.jsxs(C,{data:T,margin:{top:8,right:8,bottom:4,left:0},onClick:d?t=>{t?.activeLabel!==void 0&&d(String(t.activeLabel))}:void 0,style:d?{cursor:"pointer"}:void 0,children:[i.jsx(R,{stroke:O,strokeDasharray:"3 3",vertical:!1}),u!=null&&i.jsx(A,{x:u,yAxisId:h[0],stroke:K.text,strokeDasharray:"2 3",strokeWidth:1}),i.jsx(I,{dataKey:a,tick:v,stroke:b,tickFormatter:g,minTickGap:24}),h.map((t,s)=>{const n=e.find(o=>o.axisId===t);return i.jsx(_,{yAxisId:t,orientation:s===1?"right":"left",hide:s>1,reversed:n.inverted??!1,domain:n.inverted?[1,"auto"]:[0,"auto"],tick:v,stroke:b,width:48,tickFormatter:n.formatValue},t)}),i.jsx(D,{...L,labelFormatter:t=>f?f(String(t)):String(t),formatter:(t,s)=>{const n=String(s??"");return typeof t!="number"?[String(t??""),n]:[S.get(n)?.(t)??t.toLocaleString(),n]}}),e.filter(t=>t.trend).map(t=>i.jsx(m,{yAxisId:t.axisId,dataKey:`${t.dataKey}__trend`,name:`${t.label} trend`,stroke:t.color,strokeWidth:1.5,strokeDasharray:"5 4",strokeOpacity:.55,dot:!1,activeDot:!1,isAnimationActive:!1,legendType:"none",tooltipType:"none"},`${t.dataKey}-trend`)),e.map(t=>i.jsx(m,{yAxisId:t.axisId,dataKey:t.dataKey,name:t.label,stroke:t.color,strokeWidth:2,dot:!1,activeDot:{r:3},connectNulls:!1,isAnimationActive:!1},t.dataKey))]})})}export{O as C,U as M,b as a,v as b,M as c,L as d,p as e,F as f,K as g,H as h,P as i,G as j,N as o,$ as p};
@@ -1 +1 @@
1
- import{j as e}from"./vendor-tanstack-Dock0eH1.js";import{A as s}from"./AuditHistoryPanel-qiuqNGvG.js";import"./index-BlILyZJ5.js";import"./vendor-radix-4UOaVWza.js";import"./vendor-recharts-D8rt5lKn.js";import"./vendor-lucide-nYKv-BB7.js";import"./vendor-markdown-Bn7fRwrx.js";import"./vendor-yaml-GWJz5H2G.js";function l(){return e.jsxs("div",{className:"page-container",children:[e.jsx("div",{className:"page-header",children:e.jsxs("div",{className:"page-header-left",children:[e.jsx("h1",{className:"page-title",children:"History"}),e.jsx("p",{className:"page-subtitle",children:"Changes to projects and settings."})]})}),e.jsx(s,{})]})}export{l as HistoryPage};
1
+ import{j as e}from"./vendor-tanstack-CYBjbqVr.js";import{A as s}from"./AuditHistoryPanel-ClwYugEs.js";import"./index-B64WXjg8.js";import"./vendor-radix-BpmvLvpp.js";import"./vendor-recharts-FA0uySU7.js";import"./vendor-lucide-XjCNFK0x.js";import"./vendor-markdown-9tTeP8v7.js";import"./vendor-yaml-GWJz5H2G.js";function l(){return e.jsxs("div",{className:"page-container",children:[e.jsx("div",{className:"page-header",children:e.jsxs("div",{className:"page-header-left",children:[e.jsx("h1",{className:"page-title",children:"History"}),e.jsx("p",{className:"page-subtitle",children:"Changes to projects and settings."})]})}),e.jsx(s,{})]})}export{l as HistoryPage};