@canonry/canonry 7.25.0 → 7.43.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/assets/agent-workspace/skills/canonry/references/aeo-analysis.md +24 -0
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +219 -6
- package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +14 -2
- package/assets/assets/{AuditHistoryPanel-B-9DLSmD.js → AuditHistoryPanel-C5oZsvsR.js} +1 -1
- package/assets/assets/{BacklinksPage-DPssWU5-.js → BacklinksPage-CF5sGSTQ.js} +1 -1
- package/assets/assets/{HistoryPage-BAPUoHjI.js → HistoryPage-DumL6cl5.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-aTs5l1OV.js → MeasurementPropertyPage-DCegOp_w.js} +3 -3
- package/assets/assets/ProjectPage-CWDk48Y6.js +8 -0
- package/assets/assets/RunsPage-Bb1VMWYu.js +1 -0
- package/assets/assets/{SettingsPage-Oo861eRm.js → SettingsPage-CHGh169l.js} +1 -1
- package/assets/assets/{SiteHealthSection-BMdk3Och.js → SiteHealthSection-bGzH9m9-.js} +3 -3
- package/assets/assets/{TrafficPage-Ciot_0lN.js → TrafficPage-cfnEZ2Mu.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-D7tWadfJ.js → TrafficSourceDetailPage-BQS0MAXE.js} +1 -1
- package/assets/assets/{extract-error-message-Bb4azvlK.js → extract-error-message-IWhACkO_.js} +1 -1
- package/assets/assets/index-BDY3-V1S.js +86 -0
- package/assets/assets/{react-sigma_core.esm.min-C-Y9Z74U.js → react-sigma_core.esm.min-BQt189AI.js} +1 -1
- package/assets/assets/{v2-overview-adapter-D2S25ivE.js → v2-overview-adapter-ZC3VuoTT.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-PKQF7BV2.js → chunk-65BHDVYO.js} +5650 -4002
- package/dist/{chunk-NOA7KJDB.js → chunk-BPFRGC7K.js} +3 -3
- package/dist/{chunk-I3546GTK.js → chunk-BWR5MNL5.js} +2 -2
- package/dist/chunk-C2I7M3B2.js +22948 -0
- package/dist/{chunk-PFEQWGJN.js → chunk-E43JM3QV.js} +11444 -10397
- package/dist/chunk-HGMYEDAQ.js +79 -0
- package/dist/{chunk-BMBHREYM.js → chunk-KOIOZ7QW.js} +195 -20
- package/dist/{chunk-6AOIFHC6.js → chunk-LYKDNJXR.js} +163 -107
- package/dist/chunk-WRUKKEGR.js +562 -0
- package/dist/chunk-WU2BRWXY.js +546 -0
- package/dist/{chunk-NX257AKC.js → chunk-WXX3NL4C.js} +108 -41
- package/dist/cli-catalog-J3O6W726.js +15 -0
- package/dist/cli-commands-QST2CPDG.js +15 -0
- package/dist/cli.js +136 -22502
- package/dist/{demo-server-RUZWYXSW.js → demo-server-KA7DWTY6.js} +5 -4
- package/dist/index.js +7 -6
- package/dist/{intelligence-service-TYSVXO3S.js → intelligence-service-LHZZTJPH.js} +2 -2
- package/dist/mcp.js +30 -15
- package/package.json +6 -6
- package/assets/assets/ProjectPage-sVY1ETmt.js +0 -8
- package/assets/assets/RunsPage-D0q7ifK3.js +0 -1
- package/assets/assets/index-ClA5Nf2V.js +0 -86
package/README.md
CHANGED
|
@@ -255,10 +255,11 @@ Join the [Canonry Discord](https://discord.gg/8K9yY7GywJ) for support and questi
|
|
|
255
255
|
|
|
256
256
|
```bash
|
|
257
257
|
git clone https://github.com/Canonry/canonry.git && cd canonry
|
|
258
|
-
pnpm install
|
|
258
|
+
pnpm install
|
|
259
259
|
```
|
|
260
260
|
|
|
261
|
-
|
|
261
|
+
For local agent work, run tests only after changes are finalized and ready for push or review.
|
|
262
|
+
`pnpm verify` is optional locally. See [CONTRIBUTING.md](./CONTRIBUTING.md) and the [testing guide](docs/testing.md).
|
|
262
263
|
|
|
263
264
|
## License
|
|
264
265
|
|
|
@@ -102,6 +102,23 @@ Before anything else, separate the two signals. `cnry analytics <project> --feat
|
|
|
102
102
|
|
|
103
103
|
> **Known gap:** `cnry health` is citation-only today (no mention dimension). For the mention-first read, use `cnry overview` / `cnry get <project> scores.mentionCoverage.value` / `cnry get <project> scores.mentionShare.value` until health is extended.
|
|
104
104
|
|
|
105
|
+
### Stored competitor suggestions
|
|
106
|
+
|
|
107
|
+
`competitor suggest` and the competitor suggestions API read stored answer and
|
|
108
|
+
citation evidence only. They do not run a provider sweep. Treat `namingAnswers`
|
|
109
|
+
and `citingAnswers` as separate answer counts, and use `pairedAnswers`
|
|
110
|
+
only when the name and source occur in the same answer. Probe and non-terminal
|
|
111
|
+
runs are reported as excluded evidence and do not enter candidate counts. An
|
|
112
|
+
exact suggestion read refuses with `VALIDATION_ERROR` above 10,000 eligible
|
|
113
|
+
answer rows instead of sampling; narrow the window or select one Advanced
|
|
114
|
+
market with `groupKey`.
|
|
115
|
+
|
|
116
|
+
Suggestions preserve the selected query class and Advanced market scope. Keep
|
|
117
|
+
branded and non-brand results separate; never pool their shares into one
|
|
118
|
+
percentage. Review `unresolvedNames` and answer samples before adding an alias
|
|
119
|
+
because a recommended name can be a partner, place, service, or unrelated
|
|
120
|
+
entity.
|
|
121
|
+
|
|
105
122
|
### Step 1: Check indexing
|
|
106
123
|
Not mentioned / not cited ≠ bad content. Often the page isn't indexed yet, which starves both signals.
|
|
107
124
|
```bash
|
|
@@ -223,3 +240,10 @@ coverage, frozen scope, and per-query source evidence before interpreting a scor
|
|
|
223
240
|
The feature is default-off, has no theme setup, and has not passed independent
|
|
224
241
|
human evaluation for either query class. Native Aero reads these scores but
|
|
225
242
|
cannot enable sentiment or submit a backfill; client reports do not consume them.
|
|
243
|
+
|
|
244
|
+
Competitor impact previews use the latest stored landscape and its exact run
|
|
245
|
+
IDs, query population and tracked basis. Compare project and competitor mention
|
|
246
|
+
counts before comparing percentages. A competitor change restates every stored
|
|
247
|
+
period; an apply-mode add may move again after alias detection. Use an explicit
|
|
248
|
+
preview before a separate curated write, and do not substitute its result for
|
|
249
|
+
the overview card's separate instrument.
|
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
The CLI is invoked as `cnry` (short form) or `canonry` — both ship with the `@canonry/canonry` npm package and behave identically. This reference uses `cnry`.
|
|
4
4
|
|
|
5
|
+
## Find a command
|
|
6
|
+
|
|
7
|
+
`cnry help` lists every command group in this binary. Use `cnry help --format json`
|
|
8
|
+
for the complete catalog, or `cnry help ga --format jsonl` for one command per line
|
|
9
|
+
in a group. `cnry --help --format json` and `cnry ga --help --format json` provide
|
|
10
|
+
the same discovery output. The catalog includes access, flags, dry-run support and
|
|
11
|
+
supported rolling windows. Contextual help prints `Access: stored`, `Access: live`
|
|
12
|
+
or `Access: write`; live means the command calls a provider now, while write means
|
|
13
|
+
it changes server or local state. `--trace` reports the response access header for
|
|
14
|
+
API calls. Help is local and does not contact the server.
|
|
15
|
+
|
|
5
16
|
## Server Management
|
|
6
17
|
|
|
7
18
|
```bash
|
|
@@ -42,6 +53,34 @@ install's server, or restart it, to apply the saved providers. Any other
|
|
|
42
53
|
rejected reload reports `failed` and exits nonzero; the saved config remains
|
|
43
54
|
available. Bootstrap never reloads a remote server automatically.
|
|
44
55
|
|
|
56
|
+
### Stored-only mode
|
|
57
|
+
|
|
58
|
+
Use `cnry --stored-only <command>` or set `CANONRY_STORED_ONLY=1` when the
|
|
59
|
+
workflow may read saved evidence but must not perform provider work or change
|
|
60
|
+
Canonry operational, configuration, or business state. The guard runs before
|
|
61
|
+
telemetry, anonymous first-run setup, skills synchronization, update checks,
|
|
62
|
+
configuration writes, or API access. A refused command exits 1 with
|
|
63
|
+
`STORED_ONLY_REFUSED`; `doctor` remains a live diagnostic and is refused unless
|
|
64
|
+
run outside stored-only mode. The local client refuses live and write operations
|
|
65
|
+
before any network call on older servers; newer servers also honor
|
|
66
|
+
`x-canonry-stored-only: 1` before their handlers run.
|
|
67
|
+
|
|
68
|
+
Access follows the invocation: competitor change previews, alias reads, and
|
|
69
|
+
alias detection without `--apply` work in stored-only mode. The corresponding
|
|
70
|
+
writes remain refused. `get --from doctor` is a live diagnostic; other stored
|
|
71
|
+
evidence sources and the settings read are allowed. Use `cnry help <group>
|
|
72
|
+
--access stored` or `<group> --help --access stored` to filter help. A known
|
|
73
|
+
group with no matching commands returns an empty result; `--access` is only a
|
|
74
|
+
help/catalog option.
|
|
75
|
+
|
|
76
|
+
Results exports are allowed because they read stored evidence; writing the
|
|
77
|
+
requested export artifact is not a Canonry state mutation. Ordinary server
|
|
78
|
+
request and authentication accounting remains separate from caller business
|
|
79
|
+
mutations. The MCP adapter has the equivalent `--stored-only`,
|
|
80
|
+
`--scope=stored-only`, `CANONRY_MCP_SCOPE=stored-only`, and
|
|
81
|
+
`CANONRY_STORED_ONLY=1` forms, and a later read-only or all-scope option cannot
|
|
82
|
+
widen a stored-only request.
|
|
83
|
+
|
|
45
84
|
`cnry dashboard reset-password` clears a forgotten dashboard password. It edits
|
|
46
85
|
`config.yaml` under `$CANONRY_CONFIG_DIR` on this machine and never calls the
|
|
47
86
|
server, so run it where the server's config lives. A missing file is a
|
|
@@ -84,6 +123,10 @@ cnry project list # list all projects
|
|
|
84
123
|
cnry project create <name> --domain <url> --country US --language en
|
|
85
124
|
cnry project show <name> # project detail
|
|
86
125
|
cnry project update <name> # update project settings
|
|
126
|
+
cnry project update <name> --add-lead-event book_demo --remove-lead-event old_event
|
|
127
|
+
cnry project update <name> --brand-term "Sample Brand" --add-brand-term "Sample Pro"
|
|
128
|
+
cnry project update <name> --marketing-host offers.example --remove-marketing-host old.example
|
|
129
|
+
cnry project update <name> --display-name "Sample Brand" --expect-revision 4
|
|
87
130
|
cnry project update <name> --dispatch-mode claude=batch # scheduled sweeps batch this provider (repeatable; see Batch mode below)
|
|
88
131
|
cnry project update <name> --clear-dispatch-mode claude # back to sync
|
|
89
132
|
cnry project update <name> --negative-review-max-stars 2 # review.negative for 1-2 star Google reviews only (1-4, or "default" for 3)
|
|
@@ -93,9 +136,23 @@ cnry project update <name> --add-qualified-alias "Acme NYC" # sentiment tre
|
|
|
93
136
|
cnry project update <name> --remove-qualified-alias "Acme NYC" # repeatable; create takes --qualified-alias
|
|
94
137
|
cnry project delete <name> # delete a project
|
|
95
138
|
cnry project delete <name> --dry-run # preview cascade impact (GET /delete-preview) without writing
|
|
96
|
-
cnry status <project> #
|
|
139
|
+
cnry status <project> # stored run, integration and health status for one project
|
|
140
|
+
cnry status --all [--problems] # stored status for every project and connected integration
|
|
97
141
|
```
|
|
98
142
|
|
|
143
|
+
Project updates use PATCH and preserve omitted settings and config source. Measurement
|
|
144
|
+
flags are repeatable: `--lead-event`, `--brand-term`, and `--marketing-host` replace
|
|
145
|
+
only their named list; `--add-*` and `--remove-*` adjust it. Lead event matching is
|
|
146
|
+
case-sensitive; brand-term and host removals ignore case. Values use the shared
|
|
147
|
+
measurement validation. `project show` includes these lists in human output.
|
|
148
|
+
|
|
149
|
+
The CLI sends the revision it read, or validates an explicit positive
|
|
150
|
+
`--expect-revision`. A stale revision returns `PROJECT_REVISION_CONFLICT` without
|
|
151
|
+
writing. A server whose PATCH route is unavailable uses the legacy full PUT after
|
|
152
|
+
a second read confirms the revision is unchanged. That old-server fallback cannot
|
|
153
|
+
provide atomic compare-and-swap protection; upgrade the server for that guarantee.
|
|
154
|
+
The native MCP tool `canonry_project_update` accepts the same partial request.
|
|
155
|
+
|
|
99
156
|
### Brand aliases
|
|
100
157
|
|
|
101
158
|
`spec.aliases: string[]` on the project (set via `cnry apply`, `cnry project update <name> --add-alias <name>`, or the dashboard) widens the mention detector. Use it when the answer text says "Meta" but the canonical brand is "Facebook", or for a product or former name ("AcmeCloud Pro"). Matching ignores case, spacing and punctuation, so "AcmeCloud" already matches "Acme Cloud"; it is the same answer-text scan that powers `answerMentioned`.
|
|
@@ -154,10 +211,20 @@ cnry run cancel <project> [run-id] # force-cancel stuck runs
|
|
|
154
211
|
cnry run completeness <run-id> # answered vs missing answers per provider, and whether a fill is allowed
|
|
155
212
|
cnry run fill <run-id> --dry-run # what a fill would record, without spending quota
|
|
156
213
|
cnry run fill <run-id> [--provider claude] --wait # record a partial run's missing answers under the SAME run id
|
|
157
|
-
cnry runs <project> --limit 10 # list recent runs
|
|
214
|
+
cnry runs <project> --limit 10 # list recent runs, including probes
|
|
215
|
+
cnry run list <project> --kind traffic-sync --status failed --since 2026-10-01T00:00:00Z --limit 20 --format json
|
|
158
216
|
cnry run show <id> # show run details
|
|
159
217
|
```
|
|
160
218
|
|
|
219
|
+
`run list <project>` and `runs <project>` read the same stored history. Filters
|
|
220
|
+
include `--kind`, `--status`, `--since`, `--exclude-kind`, and repeatable or
|
|
221
|
+
comma-separated `--exclude-kinds`. The inclusive `--since` timestamp accepts
|
|
222
|
+
UTC or an offset. Omit it for all dates; omit `--limit` for all matching runs.
|
|
223
|
+
The newest limited rows retain chronological order. Traffic sync rows include
|
|
224
|
+
`sourceId` when stored, and human output shows failed and partial errors.
|
|
225
|
+
JSON is the API array; JSONL emits each run with its project. With no second
|
|
226
|
+
positional, `run list` retains the sweep shorthand for a project named `list`.
|
|
227
|
+
|
|
161
228
|
Run statuses: `queued` → `running` → `completed` / `failed` / `partial`
|
|
162
229
|
|
|
163
230
|
`partial` = some providers failed (usually rate limits) — successful snapshots are still saved.
|
|
@@ -251,7 +318,10 @@ Two independent signals per (query × provider): **mention** (`answerMentioned`
|
|
|
251
318
|
cnry evidence <project> # per-query [C/c][M/m] cell + Mentioned: X / Y / Cited: X / Y
|
|
252
319
|
cnry evidence <project> --format json # JSON output
|
|
253
320
|
cnry history <project> # audit trail
|
|
321
|
+
cnry history <project> --entity-type competitor --format json
|
|
322
|
+
cnry history <project> --action-prefix competitors. --exclude-entity-type traffic_source --since 2026-10-01T00:00:00Z --until 2026-10-02T00:00:00Z
|
|
254
323
|
cnry export <project> --include-results # export as YAML
|
|
324
|
+
cnry export <project> --include-secrets # include webhook URLs; requires administrator authority or a wildcard key
|
|
255
325
|
cnry backfill answer-mentions # recompute answerMentioned (primary) from stored answers (honors aliases)
|
|
256
326
|
cnry backfill answer-mentions --dry-run
|
|
257
327
|
cnry backfill answer-visibility # recompute citationState (secondary) from stored answers
|
|
@@ -260,6 +330,19 @@ cnry backfill insights <project> # recompute insights for complete
|
|
|
260
330
|
cnry backfill insights <project> --since 2026-04-01 --dry-run
|
|
261
331
|
```
|
|
262
332
|
|
|
333
|
+
History filters compose on project reads and `history --all`. `--action` remains
|
|
334
|
+
exact; `--action-prefix` matches a literal prefix. Repeat `--exclude-entity-type`
|
|
335
|
+
or supply a comma list to remove noisy entity types before paging. `--entity-id`
|
|
336
|
+
selects one entity. Time bounds are inclusive, normalize offsets to UTC, and
|
|
337
|
+
reject invalid values. `--actor api` includes legacy API, API-key, and user
|
|
338
|
+
actors; other actors match exactly. Empty filters are rejected. Human output shows actor and client origin; instance-wide rows include the project name. Select either a project or `--all`.
|
|
339
|
+
New competitor replacement records include `added`, `removed`, and any discarded
|
|
340
|
+
aliases beside the resulting `competitors` list. Older records remain unchanged.
|
|
341
|
+
|
|
342
|
+
Project exports redact notification webhook URLs by default as `redacted://<host>/<notification-id>`.
|
|
343
|
+
Applying that export preserves the existing notification ID, signing secret, source, enabled state and creation time. The HTTPS `/redacted` placeholder from notification listings also preserves the matched stored destination. A real URL change appears in preview and audit as a URL-change marker without exposing its path or secret.
|
|
344
|
+
Use `--include-secrets` only when the credential has administrator authority. The CLI prints a redaction note to stderr when URLs remain redacted.
|
|
345
|
+
|
|
263
346
|
Output uses a two-glyph cell per (query × provider): `[C/c][M/m]` — uppercase = present, lowercase = absent, `–` = no snapshot. **C/c = cited** (secondary), **M/m = mentioned** (primary). Always print the legend before the table; never collapse the two signals into one cell.
|
|
264
347
|
|
|
265
348
|
```
|
|
@@ -440,34 +523,54 @@ approval; reading a recommendation does not authorize either action.
|
|
|
440
523
|
## Queries & Competitors
|
|
441
524
|
|
|
442
525
|
```bash
|
|
443
|
-
cnry query add <project> "phrase one" "phrase two"
|
|
526
|
+
cnry query add <project> "phrase one" "phrase two" # prints each query's class; JSON adds `queryClasses`
|
|
444
527
|
cnry query replace <project> "phrase one" "phrase two" # set the basket to exactly this list
|
|
445
528
|
cnry query replace <project> "..." --dry-run # preview adds/removes via /queries/replace-preview
|
|
446
529
|
cnry query remove <project> "phrase"
|
|
447
|
-
cnry query list <project>
|
|
530
|
+
cnry query list <project> # CLASS column; JSON and JSONL rows carry `queryClass`
|
|
448
531
|
cnry query import <project> queries.txt
|
|
449
532
|
cnry query generate <project> --provider gemini --count 10 --save
|
|
450
533
|
|
|
451
534
|
cnry competitor add <project> competitor1.com competitor2.com
|
|
535
|
+
cnry competitor add <project> rival.example --dry-run --format json # preview latest stored-evidence impact without writing
|
|
452
536
|
cnry competitor add <project> spoketuneworks.example --alias TuneSpoke # one domain; repeat --alias for more names
|
|
537
|
+
cnry competitor add <project> competitor1.com competitor2.com --acknowledge-bulk-add # operator acknowledgement for measured growth over 5 domains in 24 hours
|
|
453
538
|
cnry competitor list <project> # text shows each competitor's aliases and auto names; JSON rows carry `aliases`, `autoAliases`, `blockedAliases`
|
|
454
539
|
cnry competitor aliases <project> qvx.example # read one competitor's curated, auto-detected (with evidence) and blocked names
|
|
455
540
|
cnry competitor aliases <project> qvx.example --set QVX # exact list (repeat --set); --clear empties it
|
|
541
|
+
cnry competitor aliases <project> qvx.example --set QVX --dry-run --format json # preview an alias identity change
|
|
456
542
|
cnry competitor aliases <project> qvx.example --add "QVX Stores" # appends server-side in one call
|
|
457
543
|
cnry competitor aliases <project> qvx.example --remove qvx # read, edit, write back (a concurrent edit can be overwritten)
|
|
458
544
|
cnry competitor aliases <project> spoketuneworks.example --block "Spoke Crew" # never auto-apply this name (removes it if auto)
|
|
459
545
|
cnry competitor aliases <project> spoketuneworks.example --unblock "Spoke Crew" # release it to detection again
|
|
460
|
-
cnry competitor aliases detect <project> --format json # dry run: names the stored answers tie to each site, with evidence
|
|
546
|
+
cnry competitor aliases detect <project> --format json # dry run: names the stored answers tie to each site, with evidence and impact
|
|
461
547
|
cnry competitor aliases detect <project> --apply # run detection now and store it, in any mode (after each sweep the project's mode decides)
|
|
462
548
|
cnry competitor landscape <project> --query-class non-brand --window 30d --format json
|
|
549
|
+
cnry competitor suggest <project> --window 30d --min-answers 3 --query-class non-brand --format json
|
|
463
550
|
```
|
|
464
551
|
|
|
552
|
+
`competitor suggest` reads stored answer and citation evidence and ranks possible competitors. It never starts a sweep or writes data. Use `--format jsonl` for one candidate per line. The response keeps naming counts separate from citation counts and includes the query class, answer floor, run IDs, sample URLs, exclusions, and count units. An exact suggestion read refuses with `VALIDATION_ERROR` above 10,000 eligible answer rows instead of sampling; narrow `--window` or use `--group-key` for one Advanced market. A Simple project without a usable brand alias uses the pooled `all` class unless an explicit class filter is requested. Exact reads also refuse more than 200,000 characters in one answer, 20 million evidence characters, or 200,000 source references with narrowing guidance. Scans yield so other requests remain responsive. Unknown untracked candidates with only unpaired prose matches are excluded as `unpaired-name`; mention counts stay unchanged for comparison with a bare-add preview. Human output shows both-answer and paired-answer counts and exclusion reasons.
|
|
553
|
+
|
|
554
|
+
### Query class
|
|
555
|
+
|
|
556
|
+
Every tracked query is `branded` (it names the project) or `non-brand`, and the two are never counted together. The server decides the class; read it, never work it out from the query text.
|
|
557
|
+
|
|
558
|
+
- `cnry query list <project>` prints a `CLASS` column. `--format json` and `--format jsonl` rows carry `queryClass`: `"branded"`, `"non-brand"` or `null`.
|
|
559
|
+
- `cnry query add <project> ...` prints one line per query with its class after the count line. `--format json` adds `queryClasses: [{ query, queryClass }]`, one entry per distinct query in the order given. A query that was already tracked is listed with its class too.
|
|
560
|
+
- A project without an advanced measurement plan is classed from the query text against the project's brand names: display name, aliases and owned-domain labels. A legacy schema v1 plan stores no class, so its queries are classed the same way.
|
|
561
|
+
- A project with an active advanced measurement plan uses the class stored on the query's assignments in that plan, which an operator may have set by hand, so it can differ from what the text suggests.
|
|
562
|
+
- A dash in text output, `null` in JSON, means there is no single class: the project has no usable brand name, or the plan holds no single class for the query (it has no assignment, or it is assigned under both classes). A dash is never a `non-brand`. `cnry query workspace <project> --format json` lists the class of each assignment, one per Property.
|
|
563
|
+
- Against a server older than this field the JSON rows and `queryClasses` entries have no `queryClass` key, and text output shows a dash with the note "class not reported". That means unknown, not `null`.
|
|
564
|
+
|
|
565
|
+
MCP: `canonry_queries_list` rows carry the same `queryClass`, and `canonry_queries_add` returns `queryClasses`.
|
|
566
|
+
|
|
465
567
|
### Competitor aliases
|
|
466
568
|
|
|
467
569
|
A competitor is stored as its registrable domain, and every competitor mention matcher derives a name from the domain label (only when that label has 4 or more letters or digits). A competitor whose answers name it differently ("TuneSpoke" for `spoketuneworks.example`, "Ravenwood Cycling" for `ravenwoodbikeinc.example`) or whose brand is 3 letters (`QVX` for `qvx.example`) is never counted until you give it curated aliases. Without them, mention share overstates the project.
|
|
468
570
|
|
|
469
571
|
- Rules: trimmed, deduplicated case-insensitively (first spelling kept), at most 10 per competitor, 80 characters each, at least 3 letters or digits. Rejected with a 400 naming each alias and the name it overlaps: a brand key (letters and digits, case and punctuation folded) that contains, or sits inside, one of the project's own names or written hosts ("Acme", "Cycles", `acmecycles.example`, `shop.acmecycles.example` or "Acme Cycles Outlet" for the project "AcmeCycles" at `acmecycles.example`), or a name another tracked competitor answers to (its aliases, domain label or written host). Answers split and join the words of a name freely, so "Tune" cannot belong to one competitor while another answers to "Tune Spoke", "TuneSpoke" or `tunespoke.example`: an answer writing "Tune Spoke" would count both. Joined-word lookalikes ("Tune" and "Tuner") are refused too; pick a longer alias.
|
|
470
572
|
- A new competitor whose domain name overlaps another competitor's alias that way (adding `tunespoke.example` or `spoketuneworks.example` while another competitor carries "Tune", or `tune.example` while one carries "Tune Spoke") is rejected on `competitor add` and apply until you remove or restate that alias; discovery promote leaves it out with reason `claimed-by-alias`. No surface starts tracking the project's own site, a subdomain of it, or a parent of an owned domain. A project identity change that claims a competitor alias drops it, audited as `droppedCompetitorAliases`.
|
|
573
|
+
- On a measured project, a competitor add that would put more than 5 new or recently added domains inside the trailing 24 hours is refused with `bulk-add-unacknowledged`. Review stored evidence with `competitor suggest` or `competitor add --dry-run`, then pass `--acknowledge-bulk-add` for an operator-approved write. Projects without a completed or partial non-probe sweep are not guarded. The rule covers REST, CLI, MCP and dashboard writes; declarative apply and discovery promote remain outside this guard.
|
|
471
574
|
- Advanced markets: a competitor an active or draft market pins counts as another competitor, and so does one a superseded revision pins when the project's runs were measured under that revision (`supersededRevision`), since those runs are still scored with it. An alias overlapping its names is rejected (`market-competitor`, naming the `markets`), a stored one is dropped and audited, and a new competitor whose domain name a pin's label or curated alias claims is rejected (`claimed-by-alias`, naming the `markets`) until you remove that name from the pin. The other way round, pinning a competitor (or giving a pin a new name) fails with `claimed-by-alias` when a curated alias already answers to it, and with `other-competitor` when its label or alias overlaps a tracked competitor's own domain label or host; `pin-competitor`, `upsert-competitor`, `upsert-group` and draft publish all check the pins they add or rename.
|
|
472
575
|
- Not checked: two names that only share a part ("Tune Spoke" and "Spoke Works" both match the phrase "Tune Spoke Works"), and an alias equal to a subdomain label of another competitor's host. Avoid such pairs by hand.
|
|
473
576
|
- Any spelling of a tracked domain (`www.`, a subdomain, or a row an older build's discovery promote stored as a subdomain) finds its stored row; discovery promote now stores the registrable domain. A competitor stored as two rows refuses alias edits; the error names both rows and the aliases to restate: `competitor remove` (which removes every row of it), then `competitor add <project> <domain> --alias ...`.
|
|
@@ -551,7 +654,7 @@ cnry schedule set <project> --preset daily # or: weekly, twice-daily, daily@
|
|
|
551
654
|
cnry schedule set <project> --cron "0 9 * * *" --timezone America/New_York
|
|
552
655
|
cnry schedule set <project> --kind data-refresh --preset daily # refresh all connected GSC/Bing/GA/GBP integrations (no --source)
|
|
553
656
|
cnry schedule set <project> --kind backlinks-sync --preset weekly # re-probe Common Crawl; sync only when a newer rolling window is published (no --source/--provider)
|
|
554
|
-
cnry doctor <project> --check site.reachability # probe the homepage now; the server also probes every 10 min and pages health.degraded after two failed passes
|
|
657
|
+
cnry doctor --project <project> --check site.reachability # probe the homepage now; the server also probes every 10 min and pages health.degraded after two failed passes
|
|
555
658
|
cnry schedule set <project> --kind site-audit --preset weekly # Technical AEO: bounded full-site crawl and audit (no --source/--provider)
|
|
556
659
|
cnry schedule show <project>
|
|
557
660
|
cnry schedule list <project> --format json # all configured schedule kinds
|
|
@@ -1601,6 +1704,7 @@ Compact reference for the composite / keyed commands agents read most (shapes ca
|
|
|
1601
1704
|
|
|
1602
1705
|
| Command | JSON output shape (top-level keys → DTO) | `jsonl` |
|
|
1603
1706
|
|---|---|---|
|
|
1707
|
+
| `cnry status --all [--problems]` | `InstanceStatusDto` @ `contracts/status.ts`: `{ generatedAt, summary{projects,ok,failing,warning,stale,neverSynced}, projects[{project,displayName,state,health,integrations[]}] }`. Stored reads only; a successful read exits 0 even when rows are failing. | One integration per line as `{project, health, ...row}`; a project without integration rows emits `{project,state,health}`. Single-project `status <p>` keeps its JSON envelope and emits one compact record for JSONL. Stored health failures and warnings affect project state, summary and `--problems`; explicit project reads always retain health. |
|
|
1604
1708
|
| `cnry doctor [--project p] [--all] [--report-month YYYY-MM]` | `{ scope, project, reportMonths?, generatedAt, durationMs, summary{total,ok,warn,fail,skipped}, checks[] }` — `DoctorReportDto` @ `contracts/doctor.ts`. `checks[]` = `CheckResultDto{ id, category, scope, title, status(ok\|warn\|fail\|skipped), notificationPolicy?(health\|silent), code, summary, remediation?, details?, durationMs }`. `report.*` checks are silent report-readiness advisories: they never page or change health state. `--report-month` picks the month they grade (default: the current month, plus the closed month through day 3, when only the closed month is graded). With `--all`: an object keyed by `__global__` + each project name, each value a full report. | ✅ one check / line as `{project, …check}`; still exits non-zero if any `fail` |
|
|
1605
1709
|
| `cnry analytics <p> [--feature metrics\|gaps\|sources] [--window 7d\|30d\|90d\|all]` | Object **keyed by feature**: `{ metrics?, gaps?, sources? }` (all three present with no `--feature`; one with `--feature X`). `metrics`=`BrandMetricsDto{ window, buckets[], overall, byProvider, trend, mentionTrend, queryChanges[] }`; `gaps`=`GapAnalysisDto{ cited[], gap[], uncited[], mentionedQueries[], mentionGap[], notMentioned[], runId, window }` (each `[]`=`GapQuery`); `sources`=`SourceBreakdownDto` (same shape as `cnry sources`, below). @ `contracts/analytics.ts` | → degrades to the `json` document |
|
|
1606
1710
|
| `cnry sources <p> [--rank] [--limit N] [--by-provider] [--window …]` | `SourceBreakdownDto{ overall[], byQuery, ranked, byProvider, runId, window, limit }` @ `contracts/analytics.ts`. `ranked`/each `byProvider[name]` = `RankedSourceList{ totalCitedSlots, domainTotal, entries[], truncatedDomainCount, truncatedCitedSlots, bySurfaceClass[] }`; `entries[]`=`SourceRankEntry{ domain, count, percentage, category, label, surfaceClass }`; `bySurfaceClass[]`=`SurfaceClassCount{ surfaceClass, label, count, percentage, domainCount }`. `surfaceClass` ∈ own \| direct-competitor \| ota-aggregator \| editorial-media \| other. | ✅ streams `ranked.entries` one / line as `{project, …entry}` |
|
|
@@ -1683,3 +1787,112 @@ breakdown keys, most mixed plus unfavorable first). Text output lists them under
|
|
|
1683
1787
|
`Most criticized properties: N of total` with favorable, mixed and unfavorable
|
|
1684
1788
|
counts; read one with `--scope property --scope-key <key> --outcome mixed,unfavorable`
|
|
1685
1789
|
(in a market view, pass that market as `--market-key`).
|
|
1790
|
+
|
|
1791
|
+
### Stored traffic samples
|
|
1792
|
+
|
|
1793
|
+
```bash
|
|
1794
|
+
cnry traffic samples <project> --kind ai-referral --since 2026-10-10T00:00:00Z --until 2026-10-11T00:00:00Z --format json
|
|
1795
|
+
cnry traffic samples <project> --source <id> --path /blog --limit 100 --offset 100 --format jsonl
|
|
1796
|
+
```
|
|
1797
|
+
|
|
1798
|
+
A stored sampled tail with 30-day retention, not visits or sessions. Use `traffic
|
|
1799
|
+
events` for totals. Default `--kind all` includes crawler, AI user-fetch, and AI
|
|
1800
|
+
referral samples; unknown requests require `--kind unknown`. Time bounds are
|
|
1801
|
+
inclusive ISO timestamps or `YYYY-MM-DD` dates (midnight UTC), defaulting to the last 24 hours. `--since-minutes`
|
|
1802
|
+
selects a positive relative duration and cannot be combined with `--since`. Path
|
|
1803
|
+
prefixes are literal. Limit is 1 to 500; offset starts at zero. Project/source
|
|
1804
|
+
attribution applies equally to Simple and Advanced portfolios; Property, Target,
|
|
1805
|
+
and market attribution is unavailable. No provider call or write. Signed-in
|
|
1806
|
+
viewers and embeds cannot read samples; API read keys retain project scope.
|
|
1807
|
+
|
|
1808
|
+
### Stored GA acquisition
|
|
1809
|
+
|
|
1810
|
+
```bash
|
|
1811
|
+
cnry ga acquisition <project> --start 2026-09-01 --end 2026-09-30 --channel "Organic Search" --landing-page /pricing --format json
|
|
1812
|
+
cnry ga acquisition <project> --group-by source,medium --limit 100 --offset 100 --format jsonl
|
|
1813
|
+
```
|
|
1814
|
+
|
|
1815
|
+
Reads stored GA4 sessions only, with no Google call. `--window 7d|30d|90d|all`
|
|
1816
|
+
defaults to all history. Use either a rolling window or explicit `--start` and `--end`; combining them is rejected. `--source` and `--medium` match exactly; `--channel` and `--host`
|
|
1817
|
+
match exactly without case sensitivity. `--landing-page` normalizes the supplied path like stored pages, removing trailing slashes and tracking parameters while retaining meaningful query parameters. `--path-prefix` matches a path or its
|
|
1818
|
+
descendants, treating percent and underscore literally. Group keys are date,
|
|
1819
|
+
channel, source, medium, host, landing-page (API names are also accepted). Default
|
|
1820
|
+
grouping uses all six. `totalSessions` covers every filtered row, independent of
|
|
1821
|
+
grouping and pagination; `totalRows` counts groups. Maximum page size is 1000.
|
|
1822
|
+
|
|
1823
|
+
### Stored read windows and calendar dates
|
|
1824
|
+
|
|
1825
|
+
Stored reads accept a positive day count such as `30` or its lowercase token
|
|
1826
|
+
`30d`, restricted to the command's available stored windows:
|
|
1827
|
+
|
|
1828
|
+
| Read | Windows |
|
|
1829
|
+
| --- | --- |
|
|
1830
|
+
| Analytics, sources, competitor landscape, GA acquisition, traffic and referral/session histories, Google performance reads | `7d`, `30d`, `90d`, `all` |
|
|
1831
|
+
| GA measurement analysis | `30d`, `60d`, `90d` |
|
|
1832
|
+
| GA search landing pages | `7d`, `28d`, `90d` |
|
|
1833
|
+
| Google Ads performance | `7d`, `14d`, `30d` |
|
|
1834
|
+
| Traffic analytics | `7d`, `14d`, `30d`, `90d` |
|
|
1835
|
+
| Organic evidence | `60d`, `90d` |
|
|
1836
|
+
|
|
1837
|
+
Defaults remain specific to each command. `--help` prints its full set. Traffic
|
|
1838
|
+
analytics and organic evidence accept `--window`; `--period` remains an alias.
|
|
1839
|
+
Use either `--window` or explicit calendar bounds, never both.
|
|
1840
|
+
For reads with calendar bounds, use inclusive `--start` and `--end` in
|
|
1841
|
+
`YYYY-MM-DD` form. Traffic referral assessment retains `--start-date` and
|
|
1842
|
+
`--end-date`; ads insights retains `--from` and `--to`. Supplying an alias and its
|
|
1843
|
+
canonical flag together is an error. Invalid calendar dates or reversed ranges
|
|
1844
|
+
fail before an API request. Explicit dates retain their existing precedence over
|
|
1845
|
+
rolling windows. Google performance `--days` remains a separate span selector.
|
|
1846
|
+
These changes do not alter sync/backfill depths, run/history ISO timestamps, or
|
|
1847
|
+
calendar-month report selections.
|
|
1848
|
+
|
|
1849
|
+
### Applying project configuration
|
|
1850
|
+
|
|
1851
|
+
```bash
|
|
1852
|
+
canonry apply <file...> --dry-run [--format json]
|
|
1853
|
+
canonry apply <file...> [--format json]
|
|
1854
|
+
```
|
|
1855
|
+
|
|
1856
|
+
`--dry-run` calls the server preview route for every YAML document and reports
|
|
1857
|
+
field, query, competitor, schedule and notification changes. It performs no
|
|
1858
|
+
database writes and prints `No DB writes performed. Re-run without --dry-run to
|
|
1859
|
+
apply.` in text mode. JSON and JSONL emit the same `{ dryRun, files,
|
|
1860
|
+
changedCount, errorCount }` document. A server without the preview route exits
|
|
1861
|
+
with `APPLY_PREVIEW_UNSUPPORTED`; the command never falls back to apply.
|
|
1862
|
+
|
|
1863
|
+
### Preview competitor impact
|
|
1864
|
+
|
|
1865
|
+
Add, remove/delete and curated alias set/add/remove/clear accept `--dry-run`.
|
|
1866
|
+
JSON and JSONL include `dryRun`, the planned changes and the server's `impact`;
|
|
1867
|
+
text prints before/after mention share and counts. No competitors, names or
|
|
1868
|
+
audit rows change. The latest stored landscape is the estimate, while a real
|
|
1869
|
+
change restates every stored period. Advanced scope includes all markets; the
|
|
1870
|
+
response names its exact runs and query class. This is not the overview card.
|
|
1871
|
+
|
|
1872
|
+
`project update <project> --competitor-auto-aliases apply --dry-run` previews
|
|
1873
|
+
the mode and detected-name impact. Other change flags are refused. Detection
|
|
1874
|
+
is a stored read; `competitor aliases detect --dry-run --apply` is invalid.
|
|
1875
|
+
Block/unblock, market pin writes, project identity edits, apply and discovery
|
|
1876
|
+
promotion are not covered. Ordinary writes keep their request count and output.
|
|
1877
|
+
Preview POSTs require a write-capable key; the detection GET accepts read keys.
|
|
1878
|
+
### Batch curated competitors and wait for stored fields
|
|
1879
|
+
|
|
1880
|
+
`canonry competitor import <project> <file> [--wait] [--wait-timeout 120]` sends
|
|
1881
|
+
one append request for the whole file. A JSON file is an array of domains or
|
|
1882
|
+
`{ "domain": "rival.example", "aliases": ["Rival Name"] }` entries. Other files
|
|
1883
|
+
contain one `domain[,alias,alias]` per line; blank lines and `#` comments are
|
|
1884
|
+
ignored. Import only a curated set, not every cited domain. Repeating an import
|
|
1885
|
+
is idempotent and reports `addedCount: 0` when nothing is newly tracked.
|
|
1886
|
+
|
|
1887
|
+
`competitor add` and `competitor aliases` also accept `--wait` with a positive
|
|
1888
|
+
integer timeout in seconds (default 120). This waits for stored answer fields
|
|
1889
|
+
to be recomputed after name changes, such as `competitor add demo rival.example
|
|
1890
|
+
--alias "Rival Name" --wait`. A bare-domain add sets no recomputation mark.
|
|
1891
|
+
Answer-derived alias detection has a separate lifecycle and is not covered by
|
|
1892
|
+
this wait. Cloud already completes the backfill within the write request.
|
|
1893
|
+
|
|
1894
|
+
Timeout exits 2 with `OPERATION_IN_PROGRESS` and `writeCommitted: true`; read
|
|
1895
|
+
`project show` to inspect `answerFieldsRecompute` rather than treating the write
|
|
1896
|
+
as failed. Older servers that omit the field return `WAIT_STATUS_UNAVAILABLE`
|
|
1897
|
+
after the committed write. Without `--wait`, add timing and output are unchanged.
|
|
1898
|
+
The existing `canonry_project_get` MCP tool returns this additive field.
|
|
@@ -730,7 +730,7 @@ transactional receipt protects each event ID for ten minutes. Receipt claims,
|
|
|
730
730
|
hourly rollups, samples, and source progress commit in one transaction.
|
|
731
731
|
|
|
732
732
|
Direct push sends one selected event per batch. It does not batch or sample at
|
|
733
|
-
the edge. Canonry
|
|
733
|
+
the edge. Canonry retains at most 100 direct-push samples per source per UTC hour, while
|
|
734
734
|
it continues to add all classified events to hourly rollups.
|
|
735
735
|
|
|
736
736
|
The Worker transports the raw client IP, full query string, and full referer
|
|
@@ -1052,7 +1052,7 @@ The doctor checks are adapter-agnostic. When they fail or warn:
|
|
|
1052
1052
|
| `traffic.source.queue-backlog` | `traffic.queue-backlog.within-drain-budget` | 1–1,000 Queue messages remain. If no new messages arrive, the next scheduled sync can drain them. Run a manual sync to accelerate. |
|
|
1053
1053
|
| `traffic.source.queue-backlog` | `traffic.queue-backlog.remaining` | More than 1,000 Queue messages remain. Run a manual sync. If the backlog persists, shorten the traffic-sync schedule. |
|
|
1054
1054
|
| `traffic.source.credentials` | `traffic.credentials.resolve-failed` | Reconnect from the host that owns the source credentials. Queue pull requires a non-empty Account Queues Edit token paired by source ID. |
|
|
1055
|
-
| `traffic.source.cache-blindspot` | `traffic.cache-blindspot.wordpress-plugin` | Cache-served requests never execute PHP and produce no event, so a source can look active while real page views go uncounted. Exclude selected AI user agents from every page-cache and CDN layer, or switch to a log/edge source. Warns only, not a failure.
|
|
1055
|
+
| `traffic.source.cache-blindspot` | `traffic.cache-blindspot.wordpress-plugin` | Cache-served requests never execute PHP and produce no event, so a source can look active while real page views go uncounted. Exclude selected AI user agents from every page-cache and CDN layer, or switch to a log/edge source. Warns only, not a failure. |
|
|
1056
1056
|
| `traffic.source.worker-version` | `traffic.worker-version.waiting-for-first-event` | Send a smoke-test request through the Worker. For Queue delivery, run a sync to ingest it. Then run the doctor again. |
|
|
1057
1057
|
| `traffic.source.worker-version` | `traffic.worker-version.stale` | Regenerate and redeploy the Worker from the credential-owning host with the source's existing delivery mode, then verify the route or Queue binding. |
|
|
1058
1058
|
|
|
@@ -1169,3 +1169,15 @@ Use GA4 for landed browser visits or edge/access logs for cache-independent
|
|
|
1169
1169
|
request coverage. The `traffic.source.cache-blindspot` doctor check is a static
|
|
1170
1170
|
warning; it does not request or impersonate traffic against the public origin,
|
|
1171
1171
|
and it never sends a `health.degraded` alert.
|
|
1172
|
+
|
|
1173
|
+
### Inspect stored samples
|
|
1174
|
+
|
|
1175
|
+
`cnry traffic samples <project> --kind ai-referral --format json` exposes exact
|
|
1176
|
+
stored timestamps, normalized paths, status, referer host, user agent and
|
|
1177
|
+
classifier evidence. It never exposes IP hashes. These are a sampled tail with
|
|
1178
|
+
30-day retention: pull syncs keep up to 100 per sync, backfills 500 per run, and
|
|
1179
|
+
direct push up to 100 per source per UTC hour. There is no universal hourly cap.
|
|
1180
|
+
`totalRows` counts matching samples, not visits. Use `traffic events` for rollup
|
|
1181
|
+
totals; `traffic status` already reports last-24h sample counts per source across
|
|
1182
|
+
all kinds. The new read excludes unknown requests by default and requires them
|
|
1183
|
+
explicitly with `--kind unknown`. Viewers and embeds cannot read sample rows.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as a,u as h,j as e}from"./vendor-tanstack-ZXXkndVj.js";import{
|
|
1
|
+
import{r as a,u as h,j as e}from"./vendor-tanstack-ZXXkndVj.js";import{dq as L,dr as P,cm as E,y as u,B as x,w as v,T as M}from"./index-BDY3-V1S.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|refused/.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({...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{H as A};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as n,j as e}from"./vendor-tanstack-ZXXkndVj.js";import{dC as _,aG as O,dD as U,dE as J,dF as K,w as i,T as u,z as d,B as k,E as F,dG as V,dH as Y,aJ as X,dI as Z}from"./index-ClA5Nf2V.js";import{H as p}from"./Hint-DICI5Jq9.js";import{I as ee,T as se,E as te,j as ae,N as ne}from"./vendor-lucide-Y6jANGfG.js";import"./vendor-radix-twS1M3FF.js";import"./vendor-recharts-D-cUuLZo.js";import"./vendor-markdown-B4giJcws.js";import"./vendor-yaml-GWJz5H2G.js";const re="https://commoncrawl.org/web-graphs";function w(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 j(t){if(!t)return"—";const f=Date.now()-new Date(t).getTime(),a=Math.floor(f/6e4);if(a<1)return"just now";if(a<60)return`${a}m ago`;const o=Math.floor(a/60);return o<24?`${o}h ago`:`${Math.floor(o/24)}d ago`}function S(t){switch(t){case"ready":return"positive";case"failed":return"negative";case"downloading":case"querying":case"queued":return"caution"}}function ue(){const[t,f]=n.useState(null),[a,o]=n.useState(null),[y,P]=n.useState([]),[g,L]=n.useState([]),[x,T]=n.useState(null),[C,B]=n.useState(!0),[D,R]=n.useState(!1),[h,E]=n.useState(!1),[N,b]=n.useState(""),[$,v]=n.useState(!1),[I,l]=n.useState(null),[A,c]=n.useState(null),m=n.useCallback(async()=>{B(!0),l(null);try{const[s,r,z,Q,W]=await Promise.all([_(),O().catch(()=>null),U().catch(()=>[]),J().catch(()=>[]),K().catch(()=>null)]);f(s),o(r),P(z),L(Q),T(W)}catch(s){l(s instanceof Error?s.message:"Failed to load backlinks status")}finally{B(!1)}},[]);n.useEffect(()=>{m()},[m]);async function q(){R(!0),l(null),c(null);try{const s=await V();c(s.alreadyPresent?`DuckDB already installed (${s.version}).`:`Installed DuckDB ${s.version}.`),await m()}catch(s){l(s instanceof Error?s.message:"Failed to install DuckDB")}finally{R(!1)}}async function M(){const s=N.trim()||void 0;E(!0),l(null),c(null);try{const r=await Y(s);c(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.`),b(""),v(!1),await m()}catch(r){r instanceof X&&r.code==="MISSING_DEPENDENCY"?l("DuckDB is not installed. Install it first."):l(r instanceof Error?r.message:"Failed to trigger sync")}finally{E(!1)}}async function G(s){l(null),c(null);try{await Z(s),c(`Pruned cached release ${s}.`),await m()}catch(r){l(r instanceof Error?r.message:"Failed to prune release")}}const H=a?.status==="ready"&&g.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."})]})}),I&&e.jsx(i,{className:"surface-card p-4 mb-4 border-negative-800/60",children:e.jsx("p",{className:"text-sm text-negative",children:I})}),A&&e.jsx(i,{className:"surface-card p-4 mb-4 border-positive-800/60",children:e.jsx("p",{className:"text-sm text-positive",children:A})}),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(u,{tone:"positive",children:"Installed"}):e.jsx(u,{tone:"caution",children:"Not installed"})]}),e.jsx(i,{className:"surface-card p-4",children:C?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."}),!d()&&e.jsxs(k,{type:"button",size:"sm",disabled:D,onClick:F(q),children:[e.jsx(ee,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),D?"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(p,{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(u,{tone:S(a.status),children:a.status})]}),e.jsxs(i,{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(p,{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:j(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:j(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."}),H&&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"}),x?e.jsxs("p",{className:"text-sm text-strong mt-0.5",children:[e.jsx("code",{className:"text-heading",children:x.release}),e.jsxs("span",{className:"ml-2 text-xs text-muted",children:["Vertex ",w(x.vertexBytes),", edges ",w(x.edgesBytes)]})]}):e.jsx("p",{className:"text-sm text-muted mt-0.5",children:C?"Checking Common Crawl…":"Could not find a release. Choose one below."}),e.jsxs("a",{href:re,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})]})]}),!d()&&e.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[e.jsxs(k,{type:"button",size:"sm",disabled:h||!t?.duckdbInstalled||!x&&!N.trim(),onClick:F(M),children:[e.jsx(ae,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),h?"Queuing…":"Run sync"]}),e.jsxs(p,{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)."]})]})]})]}),d()?null:$?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=>b(s.target.value),disabled:h,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:()=>{b(""),v(!1)},disabled:h,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:()=>v(!0),disabled:h,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(i,{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"}),!d()&&e.jsx("th",{className:"px-4 py-2 font-medium sr-only",children:"Actions"})]})}),e.jsxs("tbody",{children:[g.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(u,{tone:S(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:w(s.bytes)}),e.jsx("td",{className:"px-4 py-2 text-secondary",children:j(s.lastUsedAt)}),!d()&&e.jsx("td",{className:"px-4 py-2 text-right",children:e.jsxs("div",{className:"inline-flex items-center gap-1",children:[e.jsxs(k,{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(p,{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)),g.length===0&&e.jsx("tr",{children:e.jsx("td",{className:"px-4 py-4 text-sm text-muted",colSpan:d()?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."})]})]}),y.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(i,{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:y.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(u,{tone:S(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:j(s.queryFinishedAt??s.updatedAt)})]},s.id))})]})})]})]})}export{ue as BacklinksPage};
|
|
1
|
+
import{r as n,j as e}from"./vendor-tanstack-ZXXkndVj.js";import{dF as _,aJ as O,dG as U,dH as J,dI as K,w as i,T as u,z as d,B as k,E as F,dJ as V,dK as Y,aM as X,dL as Z}from"./index-BDY3-V1S.js";import{H as p}from"./Hint-DICI5Jq9.js";import{I as ee,T as se,E as te,j as ae,N as ne}from"./vendor-lucide-Y6jANGfG.js";import"./vendor-radix-twS1M3FF.js";import"./vendor-recharts-D-cUuLZo.js";import"./vendor-markdown-B4giJcws.js";import"./vendor-yaml-GWJz5H2G.js";const re="https://commoncrawl.org/web-graphs";function w(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 j(t){if(!t)return"—";const f=Date.now()-new Date(t).getTime(),a=Math.floor(f/6e4);if(a<1)return"just now";if(a<60)return`${a}m ago`;const o=Math.floor(a/60);return o<24?`${o}h ago`:`${Math.floor(o/24)}d ago`}function S(t){switch(t){case"ready":return"positive";case"failed":return"negative";case"downloading":case"querying":case"queued":return"caution"}}function ue(){const[t,f]=n.useState(null),[a,o]=n.useState(null),[y,P]=n.useState([]),[g,L]=n.useState([]),[x,T]=n.useState(null),[C,B]=n.useState(!0),[D,R]=n.useState(!1),[h,I]=n.useState(!1),[N,b]=n.useState(""),[$,v]=n.useState(!1),[E,l]=n.useState(null),[A,c]=n.useState(null),m=n.useCallback(async()=>{B(!0),l(null);try{const[s,r,z,Q,W]=await Promise.all([_(),O().catch(()=>null),U().catch(()=>[]),J().catch(()=>[]),K().catch(()=>null)]);f(s),o(r),P(z),L(Q),T(W)}catch(s){l(s instanceof Error?s.message:"Failed to load backlinks status")}finally{B(!1)}},[]);n.useEffect(()=>{m()},[m]);async function q(){R(!0),l(null),c(null);try{const s=await V();c(s.alreadyPresent?`DuckDB already installed (${s.version}).`:`Installed DuckDB ${s.version}.`),await m()}catch(s){l(s instanceof Error?s.message:"Failed to install DuckDB")}finally{R(!1)}}async function M(){const s=N.trim()||void 0;I(!0),l(null),c(null);try{const r=await Y(s);c(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.`),b(""),v(!1),await m()}catch(r){r instanceof X&&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),c(null);try{await Z(s),c(`Pruned cached release ${s}.`),await m()}catch(r){l(r instanceof Error?r.message:"Failed to prune release")}}const H=a?.status==="ready"&&g.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."})]})}),E&&e.jsx(i,{className:"surface-card p-4 mb-4 border-negative-800/60",children:e.jsx("p",{className:"text-sm text-negative",children:E})}),A&&e.jsx(i,{className:"surface-card p-4 mb-4 border-positive-800/60",children:e.jsx("p",{className:"text-sm text-positive",children:A})}),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(u,{tone:"positive",children:"Installed"}):e.jsx(u,{tone:"caution",children:"Not installed"})]}),e.jsx(i,{className:"surface-card p-4",children:C?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."}),!d()&&e.jsxs(k,{type:"button",size:"sm",disabled:D,onClick:F(q),children:[e.jsx(ee,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),D?"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(p,{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(u,{tone:S(a.status),children:a.status})]}),e.jsxs(i,{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(p,{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:j(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:j(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."}),H&&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"}),x?e.jsxs("p",{className:"text-sm text-strong mt-0.5",children:[e.jsx("code",{className:"text-heading",children:x.release}),e.jsxs("span",{className:"ml-2 text-xs text-muted",children:["Vertex ",w(x.vertexBytes),", edges ",w(x.edgesBytes)]})]}):e.jsx("p",{className:"text-sm text-muted mt-0.5",children:C?"Checking Common Crawl…":"Could not find a release. Choose one below."}),e.jsxs("a",{href:re,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})]})]}),!d()&&e.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[e.jsxs(k,{type:"button",size:"sm",disabled:h||!t?.duckdbInstalled||!x&&!N.trim(),onClick:F(M),children:[e.jsx(ae,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),h?"Queuing…":"Run sync"]}),e.jsxs(p,{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)."]})]})]})]}),d()?null:$?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=>b(s.target.value),disabled:h,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:()=>{b(""),v(!1)},disabled:h,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:()=>v(!0),disabled:h,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(i,{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"}),!d()&&e.jsx("th",{className:"px-4 py-2 font-medium sr-only",children:"Actions"})]})}),e.jsxs("tbody",{children:[g.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(u,{tone:S(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:w(s.bytes)}),e.jsx("td",{className:"px-4 py-2 text-secondary",children:j(s.lastUsedAt)}),!d()&&e.jsx("td",{className:"px-4 py-2 text-right",children:e.jsxs("div",{className:"inline-flex items-center gap-1",children:[e.jsxs(k,{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(p,{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)),g.length===0&&e.jsx("tr",{children:e.jsx("td",{className:"px-4 py-4 text-sm text-muted",colSpan:d()?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."})]})]}),y.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(i,{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:y.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(u,{tone:S(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:j(s.queryFinishedAt??s.updatedAt)})]},s.id))})]})})]})]})}export{ue as BacklinksPage};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{j as e}from"./vendor-tanstack-ZXXkndVj.js";import{A as s}from"./AuditHistoryPanel-
|
|
1
|
+
import{j as e}from"./vendor-tanstack-ZXXkndVj.js";import{A as s}from"./AuditHistoryPanel-C5oZsvsR.js";import"./index-BDY3-V1S.js";import"./vendor-radix-twS1M3FF.js";import"./vendor-recharts-D-cUuLZo.js";import"./vendor-lucide-Y6jANGfG.js";import"./vendor-markdown-B4giJcws.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};
|