@canonry/canonry 4.104.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 (52) hide show
  1. package/LICENSE +105 -0
  2. package/README.md +117 -0
  3. package/assets/agent-workspace/AGENTS.md +88 -0
  4. package/assets/agent-workspace/USER.md +23 -0
  5. package/assets/agent-workspace/skills/aero/SKILL.md +72 -0
  6. package/assets/agent-workspace/skills/aero/references/aeo-discovery.md +161 -0
  7. package/assets/agent-workspace/skills/aero/references/memory-patterns.md +66 -0
  8. package/assets/agent-workspace/skills/aero/references/orchestration.md +67 -0
  9. package/assets/agent-workspace/skills/aero/references/regression-playbook.md +58 -0
  10. package/assets/agent-workspace/skills/aero/references/reporting.md +98 -0
  11. package/assets/agent-workspace/skills/aero/references/wordpress-elementor-mcp.md +223 -0
  12. package/assets/agent-workspace/skills/aero/soul.md +30 -0
  13. package/assets/agent-workspace/skills/canonry/SKILL.md +152 -0
  14. package/assets/agent-workspace/skills/canonry/references/aeo-analysis.md +200 -0
  15. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +714 -0
  16. package/assets/agent-workspace/skills/canonry/references/google-business-profile.md +300 -0
  17. package/assets/agent-workspace/skills/canonry/references/indexing.md +163 -0
  18. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +372 -0
  19. package/assets/agent-workspace/skills/canonry/references/wordpress-integration.md +61 -0
  20. package/assets/apple-touch-icon.png +0 -0
  21. package/assets/assets/BacklinksPage-CixBvw--.js +1 -0
  22. package/assets/assets/ChartPrimitives-CjX4mQCy.js +1 -0
  23. package/assets/assets/ProjectPage-C09hcd6v.js +7 -0
  24. package/assets/assets/RunRow-Bel7mWgu.js +1 -0
  25. package/assets/assets/RunsPage-wqKWA2Ei.js +1 -0
  26. package/assets/assets/SettingsPage-esOqUkW5.js +1 -0
  27. package/assets/assets/TrafficPage-V0iCV3l4.js +1 -0
  28. package/assets/assets/TrafficSourceDetailPage-DUeuOh2v.js +1 -0
  29. package/assets/assets/arrow-left-CVZPxpW6.js +1 -0
  30. package/assets/assets/extract-error-message-BWAlA7ea.js +1 -0
  31. package/assets/assets/index-LsyRFLMG.css +1 -0
  32. package/assets/assets/index-avJiga1P.js +210 -0
  33. package/assets/assets/trash-2-CA_L6uGC.js +1 -0
  34. package/assets/assets/vendor-markdown-6dwjPOKK.js +14 -0
  35. package/assets/assets/vendor-radix-CQ77EO2y.js +45 -0
  36. package/assets/assets/vendor-recharts-C9EZkgbP.js +36 -0
  37. package/assets/assets/vendor-tanstack-Be8JdV5M.js +1 -0
  38. package/assets/favicon-32.png +0 -0
  39. package/assets/favicon.svg +24 -0
  40. package/assets/index.html +25 -0
  41. package/bin/canonry-mcp.mjs +2 -0
  42. package/bin/canonry.mjs +2 -0
  43. package/dist/chunk-23DCY7DL.js +7030 -0
  44. package/dist/chunk-GIKNEXFE.js +12414 -0
  45. package/dist/chunk-GNMZTR6Y.js +35673 -0
  46. package/dist/chunk-Q7FFDQU4.js +5743 -0
  47. package/dist/cli.js +13344 -0
  48. package/dist/index.d.ts +260 -0
  49. package/dist/index.js +12 -0
  50. package/dist/intelligence-service-GM3RXE2F.js +7 -0
  51. package/dist/mcp.js +442 -0
  52. package/package.json +97 -0
package/LICENSE ADDED
@@ -0,0 +1,105 @@
1
+ # Functional Source License, Version 1.1, ALv2 Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-ALv2
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 Arber X
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the Apache License, Version 2.0 that is effective on the second anniversary of
91
+ the date we make the Software available. On or after that date, you may use the
92
+ Software under the Apache License, Version 2.0, in which case the following
93
+ will apply:
94
+
95
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
96
+ this file except in compliance with the License.
97
+
98
+ You may obtain a copy of the License at
99
+
100
+ http://www.apache.org/licenses/LICENSE-2.0
101
+
102
+ Unless required by applicable law or agreed to in writing, software distributed
103
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
104
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
105
+ specific language governing permissions and limitations under the License.
package/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # Canonry <img src="https://raw.githubusercontent.com/Canonry/canonry/main/apps/web/public/favicon-32.png" alt="Canonry canary icon" width="24" />
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)
4
+
5
+ **Agent-first AEO operating platform. Open source. Self-hosted.**
6
+
7
+ - Track citations across Gemini, ChatGPT, Claude, Perplexity, and local LLMs
8
+ - Watch AI engines crawl and refer traffic via [server-log ingestion](skills/canonry/references/server-side-traffic.md) — Cloud Run, Vercel, and the WordPress Traffic Logger plugin today
9
+ - Diagnose against real traffic with built-in [GSC](docs/google-search-console-setup.md), [GA4](docs/google-analytics-setup.md), and [Bing Webmaster](docs/bing-webmaster-setup.md)
10
+ - Track local AEO via [Google Business Profile](skills/canonry/references/google-business-profile.md) — search-term impressions, performance metrics, and hotel lodging + booking-CTA gaps
11
+ - Discover who links to you with [Common Crawl backlinks](skills/canonry/references/canonry-cli.md#backlinks-common-crawl) — follows Common Crawl's rolling monthly hyperlink graph, auto-syncing each new window on a schedule, queried locally with DuckDB
12
+ - Execute fixes via [WordPress](docs/wordpress-setup.md), JSON-LD schema, and indexing submissions
13
+ - Manage many clients declaratively — config-as-code YAML + `cnry apply`
14
+ - Schedule recurring visibility checks, traffic syncs, and Business Profile syncs, with webhook alerts on regressions
15
+ - Generate client-ready HTML reports — `cnry report <project>`
16
+ - Drive from your own agent via the [67-tool MCP adapter](docs/mcp.md) or webhooks
17
+ - Or use **Aero** — Canonry's built-in agent that wakes up after every run
18
+
19
+ Every dashboard view has a matching CLI command and API endpoint. The CLI is the surface; the UI consumes the same API your agent does.
20
+
21
+ ![Canonry Dashboard](https://raw.githubusercontent.com/Canonry/canonry/main/docs/images/dashboard.png)
22
+
23
+ ## Run your first AI visibility check in 5 minutes
24
+
25
+ ```bash
26
+ npm install -g @canonry/canonry
27
+ cnry init
28
+ cnry serve
29
+ ```
30
+
31
+ The CLI installs as `cnry` (short form) and `canonry` — the two are interchangeable.
32
+ The legacy package name `@ainyc/canonry` is still published at the same versions for compatibility, but new installs should use `@canonry/canonry`.
33
+
34
+ Open [http://localhost:4100/setup](http://localhost:4100/setup). A guided wizard walks you through provider keys, project setup, queries, and your first visibility check.
35
+
36
+ If you serve Canonry on `0.0.0.0` or a LAN address, complete first-run dashboard password setup from loopback first, or authenticate with a `cnry_...` bearer key. The unauthenticated setup path is loopback-only by design.
37
+
38
+ Prefer the terminal?
39
+
40
+ ```bash
41
+ cnry project create my-site --domain example.com
42
+ cnry query add my-site "your first query" "second query"
43
+ cnry run my-site --wait
44
+ cnry evidence my-site
45
+ cnry insights my-site
46
+ ```
47
+
48
+ ## Or set it up with your AI coding agent
49
+
50
+ Drop this into Claude Code, Codex, or any shell-capable agent. It installs canonry, runs your first sweep, audits your site for AEO readiness, and stops for your sign-off before taking any action on your behalf:
51
+
52
+ ```text
53
+ Set up canonry for me. Canonry is an open-source platform that tracks how AI answer engines (Gemini, ChatGPT, Claude, Perplexity) cite my site.
54
+
55
+ 1. Ask me for: my domain, 3–5 queries I want to track, and which provider I want to start with (gemini / openai / claude / perplexity). Wait for my answers before proceeding.
56
+ 2. Run `npm install -g @canonry/canonry`.
57
+ 3. Run `cnry init` in this directory. This scaffolds config and installs the canonry skills into `.claude/skills/canonry/`, `.claude/skills/aero/`, `.codex/skills/canonry/`, and `.codex/skills/aero/`. If the skills aren't there afterwards, run `cnry skills install`.
58
+ 4. Read the operator playbook at `.claude/skills/canonry/SKILL.md` and follow it end-to-end: create the project with my domain and queries, wire up the provider key I chose, and trigger the first sweep.
59
+ 5. Open my browser to the dashboard so I can see the run results.
60
+ 6. Switch to the analyst playbook at `.claude/skills/aero/SKILL.md` and run a baseline AEO audit on my behalf. Read citation evidence with `cnry evidence <project> --format json`, then run `cnry technical-aeo run <project> --wait` followed by `cnry technical-aeo score <project> --format json` for a site-readiness score. This crawls every page in my sitemap (not just the homepage), scores the whole site 0–100, and saves the result so it appears in the dashboard and can be re-audited on a schedule.
61
+ 7. Summarize what you found: my mention and citation rates per provider, the top 3 queries I'm not yet cited on, and the highest-impact site issues from the audit. Ask me for permission before taking any further action, such as drafting content, submitting URLs for indexing, editing files, or anything else that changes my site.
62
+ ```
63
+
64
+ One-click copy at [canonry.ai](https://canonry.ai).
65
+
66
+ ## If you get stuck
67
+
68
+ | Problem | Fix |
69
+ |---------|-----|
70
+ | No provider key configured | Open `/setup`, or grab a free [Gemini key](https://aistudio.google.com/apikey), set `GEMINI_API_KEY`, and restart `cnry serve`. |
71
+ | Why did my first audit fail? | Run `cnry doctor`, then reopen `/setup`; it checks provider keys and setup blockers before the first sweep. |
72
+ | No results after a run | Visibility checks are async — check the Runs tab or use `cnry run <project> --wait`. |
73
+ | Not sure what queries to test | Setup wizard auto-generates them; expand the basket later with `cnry discover run <project> --icp "..."` — see the [discovery methodology](skills/aero/references/aeo-discovery.md). |
74
+ | `npm install` fails on `node-gyp` | Install build tools for `better-sqlite3` ([guide](https://github.com/WiseLibs/better-sqlite3/blob/master/docs/troubleshooting.md)). |
75
+
76
+ ## Provider keys
77
+
78
+ | Provider | Key source | Env var |
79
+ |----------|-----------|---------|
80
+ | Gemini | [aistudio.google.com](https://aistudio.google.com/apikey) | `GEMINI_API_KEY` |
81
+ | OpenAI | [platform.openai.com](https://platform.openai.com/api-keys) | `OPENAI_API_KEY` |
82
+ | Claude | [console.anthropic.com](https://console.anthropic.com/settings/keys) | `ANTHROPIC_API_KEY` |
83
+ | Perplexity | [perplexity.ai/settings/api](https://www.perplexity.ai/settings/api) | `PERPLEXITY_API_KEY` |
84
+ | Local LLMs | Any OpenAI-compatible endpoint | `LOCAL_LLM_URL` |
85
+
86
+ Configure during `cnry init`, in the dashboard `/settings`, or as env vars.
87
+
88
+ ## Documentation
89
+
90
+ | | |
91
+ |---|---|
92
+ | **Architecture & data model** | [docs/architecture.md](docs/architecture.md) · [docs/data-model.md](docs/data-model.md) |
93
+ | **Aero — built-in agent** | [skills/aero/SKILL.md](skills/aero/SKILL.md) |
94
+ | **MCP — Claude Desktop / Cursor / Codex** | [docs/mcp.md](docs/mcp.md) |
95
+ | **Integrations** | [GSC](docs/google-search-console-setup.md) · [GA4](docs/google-analytics-setup.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 (Cloud Run + Vercel + WordPress logs)](skills/canonry/references/server-side-traffic.md) |
96
+ | **Deployment** — Docker, Railway, Render, systemd, Tailscale | [docs/deployment.md](docs/deployment.md) |
97
+ | **API** — 118+ endpoints | `GET /api/v1/openapi.json` (no auth) |
98
+ | **Skills bundle** for Claude Code / Codex | `cnry skills install` ([details](skills/canonry/SKILL.md)) |
99
+ | **Roadmap & ADRs** | [docs/roadmap.md](docs/roadmap.md) · [docs/adr/](docs/adr/) |
100
+ | **All docs** | [docs/README.md](docs/README.md) |
101
+
102
+ ## Requirements
103
+
104
+ Node.js ≥ 22.14.0. At least one provider API key.
105
+
106
+ ## Contributing
107
+
108
+ ```bash
109
+ git clone https://github.com/Canonry/canonry.git && cd canonry
110
+ pnpm install && pnpm run typecheck && pnpm run test && pnpm run lint
111
+ ```
112
+
113
+ See [CONTRIBUTING.md](./CONTRIBUTING.md).
114
+
115
+ ## License
116
+
117
+ [FSL-1.1-ALv2](./LICENSE). Free to use, modify, and self-host. Each version converts to Apache 2.0 after two years.
@@ -0,0 +1,88 @@
1
+ # Aero Agent -- Operational Guidelines
2
+
3
+ ## Data Access
4
+
5
+ All data access goes through the canonry CLI. Never read the SQLite database directly.
6
+
7
+ ```bash
8
+ # Always use --format json for structured output
9
+ canonry <command> --format json
10
+ ```
11
+
12
+ The canonry server must be running for most commands. Verify by hitting the health endpoint (`GET /health`) or by listing projects:
13
+
14
+ ```bash
15
+ canonry project list --format json
16
+ ```
17
+
18
+ If the server isn't running, start it with `canonry serve`.
19
+
20
+ ## Key Commands
21
+
22
+ ### Monitoring
23
+
24
+ | Command | Purpose |
25
+ |---------|---------|
26
+ | `canonry run <project>` | Trigger a visibility sweep across all configured providers |
27
+ | `canonry run <project> --provider gemini` | Single-provider sweep |
28
+ | `canonry status <project>` | Current project status and latest run summary |
29
+ | `canonry evidence <project>` | Raw citation evidence from sweeps |
30
+ | `canonry insights <project>` | AI-generated insights and findings |
31
+ | `canonry health <project>` | Health snapshot with visibility scores |
32
+ | `canonry timeline <project>` | Per-query citation history over time |
33
+ | `canonry export <project>` | Full project data export |
34
+
35
+ ### Auditing
36
+
37
+ ```bash
38
+ # Run a technical AEO audit on a URL
39
+ npx @ainyc/aeo-audit <url> --format json
40
+ ```
41
+
42
+ ### Project Management
43
+
44
+ | Command | Purpose |
45
+ |---------|---------|
46
+ | `canonry project list` | List all projects |
47
+ | `canonry project create <name> --domain <domain>` | Create a new project |
48
+ | `canonry query add <project> <query>...` | Add queries to track |
49
+ | `canonry query list <project>` | List tracked queries |
50
+
51
+ ## Workflow Patterns
52
+
53
+ ### Daily monitoring sweep
54
+
55
+ 1. Check project status: `canonry status <project> --format json`
56
+ 2. Run sweep if stale: `canonry run <project>`
57
+ 3. Review insights: `canonry insights <project> --format json`
58
+ 4. Escalate critical/high severity findings to the operator
59
+
60
+ ### Investigation workflow
61
+
62
+ 1. Identify affected queries from insights
63
+ 2. Pull evidence: `canonry evidence <project> --format json`
64
+ 3. Check timeline for trends: `canonry timeline <project> --format json`
65
+ 4. If structural issues suspected, run audit: `npx @ainyc/aeo-audit <url> --format json`
66
+ 5. Compile findings with evidence and recommended actions
67
+
68
+ ## Quota Awareness
69
+
70
+ Provider APIs have rate limits. Follow these guidelines:
71
+
72
+ - Don't run full sweeps more than necessary. Check `canonry status` first to see when the last run completed.
73
+ - Use `--provider <name>` for targeted single-provider checks when investigating a specific engine.
74
+ - If a run returns `partial` status, some providers failed -- check the run details before retrying.
75
+ - Space out consecutive sweeps. Back-to-back runs waste quota without new data.
76
+
77
+ ## Skills
78
+
79
+ Reference skills are available in `skills/` for domain-specific guidance:
80
+
81
+ - `skills/canonry/` -- Canonry operator playbook
82
+
83
+ ## Error Handling
84
+
85
+ - Exit code `0` = success, `1` = user error, `2` = system error.
86
+ - On exit code `2` (system error), check server status and retry once before escalating.
87
+ - On exit code `1` (user error), review the error message -- don't retry the same command.
88
+ - Parse stderr for structured error JSON: `{ "error": { "code": "...", "message": "..." } }`.
@@ -0,0 +1,23 @@
1
+ # Client Context
2
+
3
+ This file stores client-specific context accumulated over time. Update it as you learn about the client's domain, priorities, and competitive landscape.
4
+
5
+ ## Client Info
6
+
7
+ <!-- Domain, industry, target audience, key products/services -->
8
+
9
+ ## Projects
10
+
11
+ <!-- Active canonry projects and their purpose -->
12
+
13
+ ## Key Findings
14
+
15
+ <!-- Important discoveries from sweeps and audits, with dates -->
16
+
17
+ ## Watchlist
18
+
19
+ <!-- Queries, competitors, or trends to monitor closely -->
20
+
21
+ ## Notes
22
+
23
+ <!-- Any other relevant context -->
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: aero
3
+ slug: aero
4
+ description: AEO analyst orchestration — coordinates canonry sweeps and aeo-audit analysis with persistent memory and proactive regression response.
5
+ homepage: https://canonry.ai
6
+ repository: https://github.com/AINYC/aero
7
+ ---
8
+
9
+ # Aero Orchestration Skill
10
+
11
+ You coordinate across two tools to deliver comprehensive AEO monitoring:
12
+ - **canonry** — the source of truth for project state (runs, snapshots, timelines, insights, audit log, **GA4 traffic + AI/social referrals**, **server-side crawler + referral events**). Query it with `cnry <command> --format json` (the CLI is also installed as `canonry` — the two are interchangeable); never maintain a parallel copy in agent memory. For a specific scalar use `cnry get <project> <path>` instead of pulling a full payload.
13
+ - **aeo-audit** — on-demand site analysis and fix generation.
14
+
15
+ Persist only *user-scoped* context (operator preferences, communication style) in your platform's native memory. Project-scoped facts live in canonry and must be read back, not remembered.
16
+
17
+ **Two signals, not one.** Every (query × provider) snapshot tracks **mentioned** (brand in answer text) and **cited** (domain in source links) independently. Lead with **Mention Coverage** when narrating health — it is the primary gauge — and report **Citation Coverage** as the secondary signal. Never compute one from the other, and never collapse them into a single "visibility" headline. The downloadable report (`cnry report`) and the dashboard hero both honor this split.
18
+
19
+ When a project has GA4 connected, traffic is a first-class signal alongside mentions and citations. Use `cnry ga traffic` / `cnry ga attribution --trend` for the current snapshot, `cnry ga ai-referral-history` and `cnry ga social-referral-history` for daily series. Reads query a local DB synced by `cnry ga sync` — confirm `cnry ga status` shows a recent `lastSyncedAt` before quoting numbers; if stale, re-sync first. When the project has a server-side traffic source attached (Cloud Run / WordPress / Vercel), `cnry traffic status` and `cnry traffic events` surface crawler + AI-referral evidence the GA4 layer can miss. Full command reference and return shapes live in the co-installed `canonry/references/canonry-cli.md`.
20
+
21
+ **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).
22
+
23
+ ## Judgment Rules
24
+
25
+ ### What to Prioritize
26
+
27
+ Mention is the primary gauge (see "Two signals, not one" above); citation is the secondary signal on the same query. Rank work accordingly:
28
+
29
+ 1. **Branded-term mention loss** — the engine no longer MENTIONING your brand by name is the most urgent regression. Losing the citation for your own name is the secondary signal on the same query: report it, but the mention is what moved share.
30
+ 2. **Mention-share losses** — a competitor took mention share on a query where yours fell. Rank by share swing first, then by any lost citation on the same query.
31
+ 3. **Neither mentioned nor cited** — new queries where you are absent on both signals (not mentioned and not cited). Mention gap leads; the missing citation is the trailing clause.
32
+ 4. **Indexing issues** — pages not indexed can't be cited, and a weak/unindexed page also starves the engine of reasons to mention you. Keep this on the list; it feeds both signals.
33
+ 5. **Content optimization** — improve mention rate first (give the answer a reason to name you), then cited rate on partially-covered queries.
34
+
35
+ ### What NOT to Do
36
+ - Don't promise fixes will appear in the next sweep (AEO changes take weeks/months)
37
+ - Don't give generic SEO advice — always ground recommendations in mention and citation data, leading with the mention signal
38
+ - Don't run sweeps without user confirmation (they consume API quota)
39
+ - Don't edit client's code without showing diffs and getting approval
40
+ - Don't conflate "not mentioned" with "page doesn't exist" — and don't conflate "not cited" with "not mentioned" either; check first. The two signals are independent (see "Two signals, not one") and are never computed from each other.
41
+ - Don't coerce `answerMentioned` null → false. Null means "not checked," not "not mentioned" — treat it as missing data, never as a negative.
42
+
43
+ ### When to use `--probe` runs
44
+ When you need to **verify** something on your own initiative — "did the OpenAI provider migration land cleanly?", "is the regression still reproducible after the WP fix?", "does this query actually surface us when I think it should?" — use `cnry run <project> --probe --provider <p> --query "..."`. Probe runs:
45
+ - Still cost provider API quota (same wire call)
46
+ - Write a snapshot you can inspect via `cnry runs get <id>`
47
+ - Are EXCLUDED from dashboard, analytics, intelligence, insights, and notifications
48
+ - Won't wake you up again via the post-run hook (no recursive analysis loops)
49
+
50
+ Use probes whenever the run is for **your** investigation, not for the user's metrics. The two May-17 ainyc probes that broke the dashboard before this convention existed are the canonical example of why this matters — a 1-snapshot test masqueraded as "the latest sweep" and zeroed the headline.
51
+
52
+ A real (non-probe) sweep is appropriate when the user explicitly asks to refresh data ("run it again", "get the latest", "trigger a sweep").
53
+
54
+ ### How to Communicate
55
+ - Data first: show the numbers before the interpretation
56
+ - Lead with the mention transition, keep citation as a trailing clause: "ChatGPT stopped mentioning you for 'roof repair phoenix' between Mar 28-Apr 2, and your mention share fell from 50% to 0% as a competitor took the slot" — then, second, note that you also lost the citation for that query. Not "your visibility decreased."
57
+ - Action-oriented: every observation ends with a recommended next step
58
+
59
+ ## References
60
+
61
+ Detailed playbooks live alongside this file. Read them on demand when the task matches:
62
+
63
+ | File | Read when |
64
+ |---|---|
65
+ | `references/orchestration.md` | Planning a multi-step or recurring workflow (baseline, weekly review, content-gap analysis) |
66
+ | `references/regression-playbook.md` | A query lost a mention (primary) or a citation (secondary) and you need to triage and respond |
67
+ | `references/aeo-discovery.md` | Expanding a tracked-query basket, auditing competitive surface, or responding to `aeo-discover-probe.completed` |
68
+ | `references/memory-patterns.md` | Deciding whether to remember a fact in agent memory or re-query canonry |
69
+ | `references/reporting.md` | Producing a client-facing weekly or monthly summary |
70
+ | `references/wordpress-elementor-mcp.md` | Editing WordPress pages with the Elementor MCP integration |
71
+
72
+ Aero (canonry's built-in agent) additionally exposes `list_skill_docs` / `read_skill_doc` MCP tools that walk this directory programmatically. External agents (Claude Code, Codex) should `Read` the files directly.
@@ -0,0 +1,161 @@
1
+ ---
2
+ name: aeo-discovery
3
+ description: How to operate the tracked-basket discovery pipeline. Read when an operator asks to expand a project's basket, audit its competitive surface, or you wake unprompted on `aeo-discover-probe.completed`.
4
+ ---
5
+
6
+ # AEO Discovery (Tracked-Basket Expansion)
7
+
8
+ Discovery turns a free-text ICP description into a deduped basket of representative queries, probes each against Gemini grounding, and classifies the results into three buckets. Read each bucket as **mention-or-citation** presence — the brand named in the answer text (`answerMentioned`, the primary signal) OR the domain in the grounding sources (`cited`, the secondary signal) — not citation alone:
9
+
10
+ - **cited** — the project shows up for the query: its brand is mentioned in the answer text, or its canonical/owned domain appears in the grounding sources (mention-or-citation present)
11
+ - **wasted-surface** — a tracked competitor shows up (mentioned or cited) but the project does not
12
+ - **aspirational** — neither the project nor a tracked competitor shows up on either signal (greenfield)
13
+
14
+ Plus a competitor map: every non-canonical domain (and named competitor brand) that shows up across probes, ranked by hit count and classified by type, so the operator can spot recurring competitors that aren't yet on the watchlist. Read the headline competitor signal as mention-or-citation, not citation alone — a competitor the engine keeps NAMING is taking your share of voice even when no domain is cited.
15
+
16
+ > **Mention data on probes (engine PR #707, merged 2026-06-17).** Discovery probes now carry `answerMentioned` per probe, so mention-based bucketing and competitor reads are available. Sessions that pre-date #707 have `answerMentioned = null` on their probes — read null as "not checked," never as not-mentioned, and fall back to the cited signal for those older sessions.
17
+
18
+ After probing, one Gemini call classifies every recurring cited domain into a `competitorType`:
19
+
20
+ - **direct-competitor** — a business competing for the same customers (another hotel, another tool in the category). The only type promoted by default.
21
+ - **ota-aggregator** — OTAs, marketplaces, directories, review aggregators (expedia.com, booking.com, g2.com). Suppressed from competitor tracking.
22
+ - **editorial-media** — news, blogs, "best of" round-ups (timeout.com, a personal blog). A *channel* to earn placement in, not a competitor — suppressed by default.
23
+ - **other** — government sites, social platforms, off-topic domains. Suppressed.
24
+ - **unknown** — classification failed, or a session that pre-dates classification. Excluded from the default promote.
25
+
26
+ ## When discovery is the right move
27
+
28
+ - Operator says "expand my tracked queries", "audit my basket", "what am I missing", "find competitors I should track".
29
+ - Recurring `wasted-surface` shows up in regression analysis — the project keeps losing on queries adjacent to its tracked basket.
30
+ - A new ICP is being onboarded and the operator only has a domain + tagline, no curated query list.
31
+
32
+ ## Triggering a session
33
+
34
+ The operator runs:
35
+
36
+ ```bash
37
+ cnry discover run <project> --icp "..." --wait
38
+ ```
39
+
40
+ Or the MCP equivalent: `canonry_discover_run_start` with `{ project, request: { icpDescription, dedupThreshold?, maxProbes? } }`. The endpoint returns `{ runId, sessionId, status: "running" }` immediately and finishes the work in the background. Poll `canonry_discover_session_get` until `status` is `completed` or `failed`.
41
+
42
+ ICP fallback: if the request omits `icpDescription`, the route uses `projects.icp_description` if set. Surface a clear "needs an ICP" prompt if neither is available.
43
+
44
+ ## Cost + budget
45
+
46
+ Per session: ~$1 at the default probe budget (100 queries × 1 Gemini grounded call each, plus a single batched embed call for ~$0.0002). Hard cap: 500 probes per session, enforced both client-side (Zod) and server-side. Recommend the default (100) unless the operator has a specific reason.
47
+
48
+ ## Reading the result
49
+
50
+ `canonry_discover_session_get` returns:
51
+
52
+ - Session-level: `seedCountRaw` vs `seedCount` (proves embedding dedup did real work), bucket counts, `competitorMap` (top recurring non-tracked domains, each with `hits` and `competitorType`).
53
+ - Per-probe: query, bucket, citation state, the cited domains list.
54
+
55
+ Things to call out without being asked:
56
+
57
+ - **High wasted-surface ratio** (≥ 40% of probes, or > cited count at ≥ 20%) → the project is missing from its own competitive space. The auto-written `discovery.basket-divergence` insight flags this as `high` severity.
58
+ - **Recurring `direct-competitor` domains** in `competitorMap` that aren't already in the project's tracked competitor list → `cnry discover promote` adopts `direct-competitor` domains with at least 2 hits automatically alongside the queries; or add them à la carte with `cnry competitor add <project> <domain>`. Domains classified `ota-aggregator` / `editorial-media` / `other` are surfaced but **not** promoted by default — flag a recurring `editorial-media` site as a placement opportunity, not a competitor.
59
+ - **Aspirational greenfield** queries with no tracked competitor and no canonical cite → low-friction content opportunities.
60
+
61
+ ## Promoting a session into the tracked basket
62
+
63
+ Once a session is `completed`, preview first unless the operator has already approved the write:
64
+
65
+ ```bash
66
+ cnry discover promote preview <project> <session-id>
67
+ ```
68
+
69
+ Or the MCP equivalent: `canonry_discover_promote_preview` with `{ project, sessionId }`.
70
+
71
+ The preview returns every bucket so you can explain the tradeoff:
72
+
73
+ - `cited` — already grounded to the project, safe to track.
74
+ - `aspirational` — greenfield ICP-fit opportunities, safe to track as a growth basket.
75
+ - `wasted-surface` — competitor-cited but project-missing. Treat as content-planning evidence first; do not add it to the weekly tracked basket unless the operator explicitly wants those off-ICP competitor gaps tracked.
76
+ - `suggestedCompetitors` — recurring domains (≥ 2 hits) not already tracked, of **every** classified type, each tagged with `competitorType`. The default promote adopts only the `direct-competitor` ones; the others are shown so you can recommend a `--competitor-types` widening.
77
+
78
+ Promote with one of these paths:
79
+
80
+ ```bash
81
+ cnry discover promote <project> <session-id> # cited + aspirational buckets + direct-competitor domains
82
+ cnry discover promote <project> <session-id> --bucket aspirational # scope to a bucket subset (repeatable / comma-separated)
83
+ cnry discover promote <project> <session-id> --bucket wasted-surface # explicitly track off-ICP competitor gaps
84
+ cnry discover promote <project> <session-id> --competitor-types direct-competitor,editorial-media # widen the competitor merge to other classified types
85
+ cnry discover promote <project> <session-id> --no-competitors # queries only, skip the competitor merge
86
+ ```
87
+
88
+ Or the MCP equivalent:
89
+
90
+ ```json
91
+ { "project": "<project>", "sessionId": "<session-id>" }
92
+ ```
93
+
94
+ That default request promotes `cited` + `aspirational` queries and `direct-competitor` domains. For scoped writes, pass `request`:
95
+
96
+ ```json
97
+ { "project": "<project>", "sessionId": "<session-id>", "request": { "buckets": ["aspirational"], "competitorTypes": ["direct-competitor", "editorial-media"], "includeCompetitors": false } }
98
+ ```
99
+
100
+ - **Default is cited + aspirational.** `wasted-surface` queries are off-ICP competitor gaps; promote them only when the operator explicitly wants those tracked in the weekly basket.
101
+ - **Competitor promotion requires recurrence + a promotable type.** The default competitor merge ignores one-off domains (< 2 hits) and adopts only domains classified `direct-competitor`. Pass `competitorTypes` (CLI: `--competitor-types`) to also adopt `editorial-media` channels, or `competitorTypes: ["unknown"]` to recover a legacy session promoted before classification existed.
102
+ - **Add-only and idempotent.** Queries and competitor domains already tracked are returned under `skipped`, never inserted twice. Re-running a promote is safe.
103
+ - **Completed sessions only.** Promoting a `queued`/`seeding`/`probing`/`failed` session is rejected — the buckets aren't final.
104
+ - Promoted rows carry `provenance="discovery:<sessionId>"`, so a tracked query can always be traced back to the session that surfaced it.
105
+
106
+ ## When you wake on `aeo-discover-probe.completed`
107
+
108
+ The follow-up payload `RunCoordinator` queues for you includes:
109
+
110
+ ```
111
+ [system] Discovery run <runId> completed for project <name> (session <sessionId>).
112
+ Buckets — cited:<n>, wasted-surface:<n>, aspirational:<n> (<probeCount> probes; seed provider: gemini).
113
+ Top recurring competitor domains: <domain1>(<hits>), <domain2>(<hits>), …
114
+ ```
115
+
116
+ Respond with:
117
+
118
+ 1. A one-line headline naming the dominant bucket.
119
+ 2. The top 2-3 wasted-surface queries (call `canonry_discover_session_get` to fetch them — don't guess).
120
+ 3. The top 1-2 recurring `direct-competitor` domains worth tracking, ignoring one-hit domains unless the operator asks for the full long tail. If a recurring `editorial-media` domain stands out, mention it separately as a placement opportunity — not a competitor.
121
+ 4. A single recommended next step. Examples: "preview and promote cited + aspirational findings (`cnry discover promote preview`, then `cnry discover promote`)", "the wasted-surface set warrants a content plan around X before tracking", "the aspirational set is greenfield — pick the 3 with highest commercial intent and write content".
122
+
123
+ Do not recommend "promote everything" as the default. The safe path is: inspect session detail, preview promotion candidates, then promote the default cited + aspirational set. Escalate `wasted-surface` to tracking only when the operator deliberately chooses that tradeoff.
124
+
125
+ Keep it tight. The operator wakes to a short, decision-ready summary, not a full report.
126
+
127
+ ## What discovery does NOT do (yet)
128
+
129
+ - **No multi-provider amplification.** v1 probes Gemini only. v2 will probe across Gemini + ChatGPT + Claude in one session (the schema is already shaped for it — `discovery_probes` has no `UNIQUE(session_id, query)` exactly because of this).
130
+ - **No re-run drift.** Each session is independent. Comparing sessions over time is on the PR 4 / PR 5 roadmap.
131
+
132
+ ## Failure modes
133
+
134
+ - **Gemini not configured** → orchestrator throws early; `runs.status='failed'` with `Gemini provider is not configured.` Surface as "configure Gemini before running discovery" — link to `cnry init` or `~/.canonry/config.yaml`.
135
+ - **Vertex-only Gemini** → embeddings step throws (Vertex embeddings deferred). Same surface, "use a Gemini API key for now."
136
+ - **ICP missing** → route returns 400 with `VALIDATION_ERROR`. Ask the operator for the ICP description in plain language.
137
+ - **Seed collapse (hyperlocal/niche businesses)** → 40 raw seeds collapse to 1-2 canonical queries after embedding+clustering, even at low dedup thresholds. This happens when Gemini generates seed queries that all live in the same semantic pocket (e.g. all variants of "boutique hotel Venice Beach"). The embedding model sees them as near-identical, so clustering produces one representative.
138
+
139
+ **Diagnostic signal:** `seedCountRaw / seedCount > 10:1` (e.g. 40 raw → 1 selected).
140
+
141
+ **Remediation:** break the ICP into 3-5 distinct purchase-intent angles and run one session per angle via `--icp-angle`:
142
+
143
+ ```bash
144
+ cnry discover run <project> \
145
+ --icp-angle "romantic anniversary stay in Venice Beach" \
146
+ --icp-angle "best rooftop bars and dining hotels LA" \
147
+ --icp-angle "walkable Venice Beach hotels near Abbot Kinney" \
148
+ --icp-angle "design-forward boutique hotels for creative professionals" \
149
+ --wait
150
+ ```
151
+
152
+ Each angle generates its own 40-seed cluster independently, so aggregate coverage grows while per-session dedup stays clean. The `--wait` output prints a combined summary with per-session session IDs and a `promote` command for each. Promote the sessions individually after reviewing previews.
153
+
154
+ ## Memory hygiene
155
+
156
+ After a discovery session, store a one-liner in `agent_memory` if the operator validates a non-obvious call. Examples:
157
+
158
+ - `discovery:icp-style` — phrasing they responded well to
159
+ - `discovery:competitor-watchlist` — domains they explicitly accepted/rejected from the suggested list
160
+
161
+ Skip routine results — only memory-worthy material is what would help a future session avoid re-asking the same question.
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: memory-patterns
3
+ description: When to remember vs. re-query — project state lives in canonry, only durable user-scoped facts go in Aero memory. Read when unsure whether to call canonry_memory_set or look up.
4
+ ---
5
+
6
+ # Memory Patterns
7
+
8
+ Canonry is the source of truth for project state. Do **not** maintain a parallel copy of project facts in Aero memory — it will drift from the DB and mislead the next session.
9
+
10
+ Aero ships with a built-in durable notes store — the `canonry_memory_set`, `canonry_memory_forget`, and `canonry_memory_list` tools — backed by the `agent_memory` table. The N most-recently-updated notes are injected into the system prompt at every session start, so you usually see relevant memory without calling `canonry_memory_list`.
11
+
12
+ ## What belongs where
13
+
14
+ | Scope | Examples | Home |
15
+ |---|---|---|
16
+ | **Project state** | Baselines (**mention rate + mention share** as the primary KPI, then cited rate), historical regressions, mention + citation rates per query/provider, recent insights, sweep history, audit trail | Canonry DB — query via CLI / API / read tools |
17
+ | **Operator facts** | Personal preferences, non-observable context ("content lead is Sarah", "migrating off Webflow next quarter"), tone/voice preferences the operator confirmed | Aero memory (`canonry_memory_set`) |
18
+ | **Session scratch** | "I just tried X and it failed", intermediate reasoning, turn-local state | Nowhere — let it die with the session |
19
+
20
+ ## How to read project state from canonry
21
+
22
+ Prefer Aero's read tools (`canonry_project_overview`, `canonry_health_latest`, `canonry_timeline_get`, `canonry_insights_list`, `canonry_queries_list`, `canonry_competitors_list`, `canonry_run_get`) over shelling out, but the CLI exists for operators too:
23
+
24
+ ```bash
25
+ cnry status <project> --format json
26
+ cnry health <project> --format json
27
+ cnry timeline <project> --since <YYYY-MM-DD> --format json
28
+ cnry insights <project> --format json
29
+ cnry evidence <project> --format json
30
+ cnry audit <project> --format json
31
+ ```
32
+
33
+ If the data you need isn't reachable with a single read tool or CLI call, that's a bug in canonry's API surface — file it rather than working around it in memory.
34
+
35
+ ## Regenerate, don't remember
36
+
37
+ Derived interpretations (trend summaries, correlations between events) are cheap to recompute from the underlying DB rows. Prefer running the analysis again on fresh data over recalling what you concluded last session — conclusions age, the data doesn't.
38
+
39
+ ## Using `canonry_memory_set` / `canonry_memory_forget` / `canonry_memory_list`
40
+
41
+ - `canonry_memory_set({ key, value })` — upsert a project-scoped note. Capped at 2 KB per value. Same key replaces the prior value, so use stable keys (e.g. `operator-pref.reporting-tone`, not `note-2026-04-17`).
42
+ - `canonry_memory_forget({ key })` — remove a single note. Returns `status: missing` when the key never existed (non-fatal).
43
+ - `canonry_memory_list()` — read notes newest-first. Usually unnecessary — the top 20 are already in the system prompt under `<memory>`. Reach for it when you need older context or the full value of a note that's been summarized.
44
+
45
+ **Reserved prefix.** Keys starting with `compaction:` are reserved for LLM-summarized transcript slices. `canonry_memory_set` and `canonry_memory_forget` both reject them. Compaction notes are pruned automatically — you can list them but never write or delete them by hand.
46
+
47
+ **CLI parity.** Operators can manage memory without talking to you:
48
+
49
+ ```bash
50
+ cnry agent memory list <project> --format json
51
+ cnry agent memory set <project> --key <k> --value <v>
52
+ cnry agent memory forget <project> --key <k>
53
+ ```
54
+
55
+ ## Good remember candidates
56
+
57
+ - Operator-confirmed facts canonry can't observe (team names, migration plans, vendor lock-in, upcoming content bets).
58
+ - Stable preferences the operator has validated at least once ("report weekly", "prefer Claude over GPT for prose", "never auto-dismiss insights").
59
+ - Non-obvious decisions made mid-investigation that a future turn would re-derive wastefully ("confirmed competitor X is out of scope").
60
+
61
+ ## Bad remember candidates
62
+
63
+ - Anything canonry already tracks (runs, insights, citation rates, schedules). Query it.
64
+ - Turn-local state that's useful for one follow-up and then noise ("user just asked about query Y").
65
+ - Raw evidence or long transcripts — persist a conclusion, not a dump.
66
+ - Unvalidated guesses. Memory isn't a place to think aloud; it's a place to record things you're willing to act on next session.