@canonry/canonry 4.118.0 → 4.123.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.
- package/README.md +9 -0
- package/assets/agent-workspace/skills/canonry/SKILL.md +3 -0
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +135 -28
- package/assets/assets/{AuditHistoryPanel-Yh7VEe4P.js → AuditHistoryPanel-BVQMDIhQ.js} +1 -1
- package/assets/assets/{BacklinksPage-BmO77kMs.js → BacklinksPage-nrHT9VMG.js} +1 -1
- package/assets/assets/ChartPrimitives-_8qLsQzI.js +1 -0
- package/assets/assets/{HistoryPage-DkLV_iRc.js → HistoryPage-DqsKKdyy.js} +1 -1
- package/assets/assets/ProjectPage-GyKoQUv_.js +7 -0
- package/assets/assets/{RunRow-BDH9agxQ.js → RunRow-BRZZcSjQ.js} +1 -1
- package/assets/assets/{RunsPage-D9YsEeT3.js → RunsPage-24aI8DKF.js} +1 -1
- package/assets/assets/{SettingsPage-o5as3Ixw.js → SettingsPage-BaNeTG1i.js} +1 -1
- package/assets/assets/{TrafficPage-BCK203aQ.js → TrafficPage-ByvcLSjv.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-DsVMAZvS.js → TrafficSourceDetailPage-B-siTXVg.js} +1 -1
- package/assets/assets/{arrow-left-B7ZR6VC9.js → arrow-left-CBh1cZNH.js} +1 -1
- package/assets/assets/{extract-error-message-DeYDUpew.js → extract-error-message-DJZCPudR.js} +1 -1
- package/assets/assets/index-jymsMBpZ.css +1 -0
- package/assets/assets/index-mzLganDP.js +210 -0
- package/assets/assets/{trash-2-xxHTqWbF.js → trash-2-Bh-Yvkgc.js} +1 -1
- package/assets/assets/vendor-recharts-DSxTZhaH.js +36 -0
- package/assets/index.html +3 -3
- package/dist/{chunk-CMZIPFNQ.js → chunk-4AZ7VES6.js} +4915 -719
- package/dist/{chunk-75UBTCKS.js → chunk-HJIIJI7K.js} +321 -6
- package/dist/{chunk-HGNY5GL4.js → chunk-JYBNBK25.js} +273 -37
- package/dist/{chunk-KEZWLP4O.js → chunk-MMFSI3OG.js} +3281 -2341
- package/dist/cli.js +554 -35
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-FWPXMAVK.js → intelligence-service-K35EN43I.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +7 -7
- package/assets/assets/ChartPrimitives-H0ICVpw_.js +0 -1
- package/assets/assets/ProjectPage-R3odr_eA.js +0 -7
- package/assets/assets/index-Bot6YiD6.css +0 -1
- package/assets/assets/index-QCFQv0xh.js +0 -210
- package/assets/assets/vendor-recharts-C9EZkgbP.js +0 -36
package/README.md
CHANGED
|
@@ -45,6 +45,15 @@ cnry evidence my-site
|
|
|
45
45
|
cnry insights my-site
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
To pin a project to a provider model for future sweeps, use an explicit
|
|
49
|
+
override. Omitting a provider keeps the project on all configured engines;
|
|
50
|
+
omitting its model keeps that engine on the instance setting.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
cnry project update my-site --provider gemini --provider-model gemini=gemini-2.5-pro
|
|
54
|
+
cnry project update my-site --clear-provider-model gemini
|
|
55
|
+
```
|
|
56
|
+
|
|
48
57
|
## Or set it up with your AI coding agent
|
|
49
58
|
|
|
50
59
|
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:
|
|
@@ -49,12 +49,15 @@ Agent-first open-source AEO (Answer Engine Optimization) operating platform. Tra
|
|
|
49
49
|
- Optimizing `llms.txt`, sitemaps, robots.txt for AI crawlers
|
|
50
50
|
- Submitting URLs to Google Indexing API and Bing IndexNow
|
|
51
51
|
- Analyzing competitor citation patterns
|
|
52
|
+
- Operating guarded ChatGPT ads lifecycle changes and reconciling unresolved mutation receipts
|
|
52
53
|
|
|
53
54
|
## Core Philosophy
|
|
54
55
|
|
|
55
56
|
- **Measure outcomes** — AI models are black boxes; track mentions + citations, don't assume causality
|
|
56
57
|
- **Signal over noise** — Focus on high‑intent queries; avoid granular targeting until base visibility exists
|
|
57
58
|
- **CLI‑native** — API‑driven changes over manual CMS clicks; faster, repeatable, auditable
|
|
59
|
+
- **Recover before retrying ads writes** — Page through `pending`, `unknown`, and `reconciling` receipts. Resume `campaign_tree_activate` through its bodyless exact-executor recovery operation; reconcile other supported receipts by verifying the checkpointed provider ID on the receipt-bound account. Never resend a mutation under a different key; an uncheckpointed create remains unresolved. A fresh pending generic receipt waits for its minimum-idle window, inconclusive reads back off, and a quarantined receipt requires human remediation.
|
|
60
|
+
- **Separate approval from ads execution** — A human with `ads.approve` creates a short-lived grant for the exact paused campaign tree and a different executor key. The operator may activate only that tree with `ads.activate`; it cannot mint or widen its own approval.
|
|
58
61
|
|
|
59
62
|
## What Canonry Measures (Vocabulary)
|
|
60
63
|
|
|
@@ -566,6 +566,10 @@ Paid-surface data for the project's connected OpenAI ad account. Ads render only
|
|
|
566
566
|
```
|
|
567
567
|
cnry ads connect <project> --api-key <sdk-key> # mint the key in OpenAI Ads Manager; validated upstream, stored in ~/.canonry/config.yaml
|
|
568
568
|
cnry ads status <project>
|
|
569
|
+
cnry ads account <project> # live account identity, currency/timezone, status, and integrity review
|
|
570
|
+
cnry ads geo search <project> --query "New York" --limit 20 --format jsonl
|
|
571
|
+
cnry ads conversions pixels <project> --format jsonl
|
|
572
|
+
cnry ads conversions event-settings <project> --format jsonl
|
|
569
573
|
cnry ads image upload <project> --input image.json
|
|
570
574
|
cnry ads campaign create <project> --input campaign.json
|
|
571
575
|
cnry ads campaign update <project> <campaign-id> --input update.json
|
|
@@ -576,7 +580,21 @@ cnry ads ad-group pause <project> <ad-group-id> --input pause.json
|
|
|
576
580
|
cnry ads ad create <project> --input ad.json
|
|
577
581
|
cnry ads ad update <project> <ad-id> --input update.json
|
|
578
582
|
cnry ads ad pause <project> <ad-id> --input pause.json
|
|
579
|
-
cnry ads operation <project> <operation-key> # inspect
|
|
583
|
+
cnry ads operation <project> <operation-key> # inspect one durable mutation receipt
|
|
584
|
+
cnry ads operations unresolved <project> --limit 100 --format json
|
|
585
|
+
# list pending/unknown/reconciling receipts before new writes
|
|
586
|
+
cnry ads operations unresolved <project> --cursor <nextCursor> --limit 100 --format json
|
|
587
|
+
# advance past permanent rows with the opaque keyset cursor
|
|
588
|
+
cnry ads operation reconcile <project> --operation-key <key>
|
|
589
|
+
# verify nonactivation provider state; never retries the mutation
|
|
590
|
+
cnry ads operation resume-activation <project> --operation-key <key>
|
|
591
|
+
# exact-executor recovery for an existing activation receipt; no body
|
|
592
|
+
cnry ads activation-grant create <project> --input grant.json
|
|
593
|
+
# human approval for one exact tree + executor key + expiry
|
|
594
|
+
cnry ads activation-grant revoke <project> <grant-id>
|
|
595
|
+
# revoke only while the grant is unused
|
|
596
|
+
cnry ads campaign activate-tree <project> <campaign-id> --input activate.json
|
|
597
|
+
# executor consumes the exact approved grant
|
|
580
598
|
cnry ads sync <project> # ads-sync run: entity snapshots + daily rollups
|
|
581
599
|
cnry ads campaigns <project> --format jsonl # lifecycle timestamps, location IDs, context hints, creative file IDs
|
|
582
600
|
cnry ads insights <project> --level campaign --from 2026-06-01 --format jsonl
|
|
@@ -587,14 +605,52 @@ cnry schedule set <project> --kind ads-sync --preset daily
|
|
|
587
605
|
|
|
588
606
|
`ads sync` runs report `completed` / `partial` (some campaigns failed; per-campaign errors on the run) / `failed`. Doctor checks: `ads.auth.connection`, `ads.data.recent-sync` (both skipped when not connected).
|
|
589
607
|
|
|
608
|
+
`ads account`, `ads geo search`, and both `ads conversions` commands read the
|
|
609
|
+
live OpenAI Advertiser API rather than the local synced snapshot. Use `account`
|
|
610
|
+
to verify the connected advertiser and review state, `geo search` to resolve
|
|
611
|
+
campaign `locationIds` from provider-issued IDs, and the conversion reads to
|
|
612
|
+
verify the available pixel/CAPI sources, event goal, and attribution window
|
|
613
|
+
before launch. Geo search defaults to 20 results and accepts 1-100. Its JSONL
|
|
614
|
+
rows carry `{ project, query, ...location }`; conversion rows carry
|
|
615
|
+
`{ project, ...pixel }` or `{ project, ...eventSetting }`.
|
|
616
|
+
|
|
590
617
|
Lifecycle inputs are JSON files, or `--input -` for stdin. Every request carries
|
|
591
618
|
a unique `operationKey`. Identical replays return the stored receipt without a
|
|
592
|
-
second upstream request
|
|
593
|
-
|
|
619
|
+
second upstream request. Before issuing new lifecycle writes, run `ads
|
|
620
|
+
operations unresolved`; if a receipt is `pending`, `unknown`, or `reconciling`,
|
|
621
|
+
do not retry with a different key. Branch on `kind`: use `ads operation
|
|
622
|
+
resume-activation` for `campaign_tree_activate`, and generic `ads operation
|
|
623
|
+
reconcile` for other supported receipts. Generic reconciliation only reads and
|
|
624
|
+
verifies provider state. It never re-sends the mutation or accepts a
|
|
625
|
+
caller-selected provider entity. Canonry resolves a generic receipt only when
|
|
626
|
+
the provider ID was durably checkpointed and its live state matches the stored
|
|
627
|
+
safe fields on the receipt-bound account. An uncheckpointed create remains
|
|
628
|
+
unresolved because mutable-field equality cannot prove which request created an
|
|
629
|
+
entity. A pending generic receipt must be idle for five minutes before either a
|
|
630
|
+
human or the sweeper may claim it, so recovery cannot race a request that is
|
|
631
|
+
still returning from the provider. Automatic inconclusive inspections back off
|
|
632
|
+
from a five-minute base; explicit operator requests may inspect sooner, but
|
|
633
|
+
every generic path stops after five attempts. The receipt then remains visible
|
|
634
|
+
as `unknown` with `ADS_RECONCILIATION_QUARANTINED` and requires manual provider
|
|
635
|
+
remediation. JSON list responses return `nextCursor`; pass it back unchanged
|
|
636
|
+
with the same project and state filter to continue.
|
|
637
|
+
Creates are always paused. Updates require the entity to
|
|
594
638
|
already be paused and `expectedUpdatedAt` to equal the latest
|
|
595
639
|
`upstreamUpdatedAt` from `ads campaigns` after a sync. Canonry exposes pause as
|
|
596
|
-
the kill switch
|
|
597
|
-
|
|
640
|
+
the kill switch and deliberately omits archive and direct entity activation.
|
|
641
|
+
A human can instead approve one exact campaign tree for a different executor
|
|
642
|
+
key. The short-lived grant pins every campaign/ad-group/ad ID and
|
|
643
|
+
`expectedUpdatedAt`; execution rechecks the account and ad review gates, then
|
|
644
|
+
activates ads first, ad groups second, and the campaign last. Each step is
|
|
645
|
+
durably checkpointed. A failure rolls back the campaign before its children,
|
|
646
|
+
and an ambiguous outcome fails closed for manual remediation.
|
|
647
|
+
|
|
648
|
+
For a conversion-optimized campaign, set `biddingType` to `clicks` and pass at
|
|
649
|
+
least one exact `conversionEventSettingIds` value returned by `ads conversions
|
|
650
|
+
event-settings`. Each child ad group must set `billingEventType` to `click`.
|
|
651
|
+
Canonry rejects missing or duplicate conversion IDs and rejects any ad-group
|
|
652
|
+
billing mode that does not match its live parent campaign before writing to the
|
|
653
|
+
provider. Omit these fields to preserve the legacy impressions/impression mode.
|
|
598
654
|
|
|
599
655
|
Campaign updates may omit `locationIds` to preserve current geo targeting or
|
|
600
656
|
pass a non-empty list to replace it. The guarded operator cannot pass `null` or
|
|
@@ -606,28 +662,76 @@ the [campaign update contract](https://developers.openai.com/ads/api-reference/c
|
|
|
606
662
|
|
|
607
663
|
### Guarded operator release gates
|
|
608
664
|
|
|
609
|
-
Default external automation to a project-scoped API key with exactly `read
|
|
610
|
-
and `ads.
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
665
|
+
Default external automation to a project-scoped API key with exactly `read`,
|
|
666
|
+
`ads.write`, and `ads.activate`; never hand an external operator an unscoped
|
|
667
|
+
key:
|
|
668
|
+
|
|
669
|
+
```bash
|
|
670
|
+
canonry key create --name ads-operator --project <project> \
|
|
671
|
+
--scope read --scope ads.write --scope ads.activate
|
|
672
|
+
canonry key create --name ads-approver --project <project> \
|
|
673
|
+
--scope read --scope ads.approve
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
Keep the plaintext keys separate. The human approval request must authenticate
|
|
677
|
+
with the `ads-approver` key and name the operator key's ID as
|
|
678
|
+
`executorApiKeyId`; Canonry refuses a grant whose approver and executor IDs are
|
|
679
|
+
the same. Approval create/revoke exist in REST and CLI only. MCP and Aero expose
|
|
680
|
+
`activate-tree` plus bodyless recovery of its existing receipt, so the ads
|
|
681
|
+
operator cannot mint, replace, or widen its own grant.
|
|
682
|
+
|
|
683
|
+
The grant request is strict JSON. Its entity arrays must be uniquely sorted by
|
|
684
|
+
provider ID so the manifest has one canonical hash:
|
|
685
|
+
|
|
686
|
+
```json
|
|
687
|
+
{
|
|
688
|
+
"manifest": {
|
|
689
|
+
"campaign": {
|
|
690
|
+
"id": "cmpn_...",
|
|
691
|
+
"expectedUpdatedAt": 1780868842,
|
|
692
|
+
"adGroups": [{
|
|
693
|
+
"id": "adgrp_...",
|
|
694
|
+
"expectedUpdatedAt": 1780864410,
|
|
695
|
+
"ads": [{ "id": "ad_...", "expectedUpdatedAt": 1781139491 }]
|
|
696
|
+
}]
|
|
697
|
+
}
|
|
698
|
+
},
|
|
699
|
+
"executorApiKeyId": "key_...",
|
|
700
|
+
"expiresAt": "2026-07-18T18:00:00.000Z"
|
|
701
|
+
}
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
The approval response returns `grant.id` and `grant.manifestHash`. Pass those
|
|
705
|
+
unchanged to the operator:
|
|
706
|
+
|
|
707
|
+
```json
|
|
708
|
+
{
|
|
709
|
+
"operationKey": "launch:campaign:2026-07-18:1",
|
|
710
|
+
"grantId": "grant_...",
|
|
711
|
+
"manifestHash": "64-lowercase-hex-characters"
|
|
712
|
+
}
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
Before enabling spend on an advertiser account, run a paused, disposable
|
|
716
|
+
live-provider smoke test and capture sanitized raw responses for campaign get,
|
|
717
|
+
create, and pause. For every response, verify and record the exact case and type
|
|
718
|
+
of `status`, plus the type and exact returned value of `updated_at`. The
|
|
719
|
+
captured responses must agree with the typed client and fixtures without
|
|
720
|
+
coercion. After explicit budget approval, repeat the check through one minimal
|
|
721
|
+
grant-bound activate-tree execution and immediate campaign pause; verify every
|
|
722
|
+
activation response reports exact `active` plus an integer `updated_at`.
|
|
723
|
+
|
|
724
|
+
The receipt lifecycle is safe across concurrent writers: an atomic claim picks
|
|
725
|
+
one upstream sender and losers replay the canonical receipt. A leased generic
|
|
726
|
+
reconciler settles supported stale `pending` / `unknown` rows by verifying
|
|
727
|
+
provider state. Exact credential/account verification is cached for five
|
|
728
|
+
minutes, keyed by a one-way credential fingerprint plus the project and stored
|
|
729
|
+
account identity; credential rotation or account rebinding misses the cache
|
|
730
|
+
immediately. Activation receipts keep their grant and ordered step ledger
|
|
731
|
+
authoritative: only the exact bound executor may call the bodyless resume route,
|
|
732
|
+
which cannot substitute a manifest, grant, account, or campaign. When another
|
|
733
|
+
worker owns reconciliation or activation recovery, callers wait and read the
|
|
734
|
+
canonical receipt instead of starting a second pass.
|
|
631
735
|
|
|
632
736
|
## Backlinks (source-aware: Common Crawl + Bing Webmaster)
|
|
633
737
|
|
|
@@ -765,7 +869,7 @@ Every command takes `--format`:
|
|
|
765
869
|
- **`json`** — one pretty-printed JSON document (the full envelope). Stable contract.
|
|
766
870
|
- **`jsonl`** — newline-delimited JSON: the command's **primary collection**, one self-contained record per line. The agent-friendly machine format — no envelope key to guess (`.checks` vs `.results` vs `.rows`), no `jq` flattening, greppable line by line.
|
|
767
871
|
|
|
768
|
-
`jsonl` is supported by every **collection** command — one whose primary output is a list: `insights`, `runs`, `evidence`, `history`, `query/keyword/competitor list`, `notify list/events`, `google` reads (`performance`, `performance-daily`, `inspections`, `coverage-history`, `deindexed`, `status`, `properties`, `list-sitemaps`), `bing` reads (`coverage-history`, `inspections`, `performance`, `sites`), `ga` reads (`ai-referral-history`, `social-referral-history`, `session-history`, `coverage`), `traffic events/sources/status`, `discover list/show`, `content targets/sources/gaps/map`, `backlinks list/releases`, `project list/locations`, `key list`, `agent memory list`, `agent providers`, `sources` (streams the ranked cited-domain list), and `doctor`. (`content brief` is an object command — `jsonl` degrades to its JSON document.)
|
|
872
|
+
`jsonl` is supported by every **collection** command — one whose primary output is a list: `insights`, `runs`, `evidence`, `history`, `query/keyword/competitor list`, `notify list/events`, `google` reads (`performance`, `performance-daily`, `inspections`, `coverage-history`, `deindexed`, `status`, `properties`, `list-sitemaps`), `bing` reads (`coverage-history`, `inspections`, `performance`, `sites`), `ga` reads (`ai-referral-history`, `social-referral-history`, `session-history`, `coverage`), `ads geo search` and `ads conversions` reads, `traffic events/sources/status`, `discover list/show`, `content targets/sources/gaps/map`, `backlinks list/releases`, `project list/locations`, `key list`, `agent memory list`, `agent providers`, `sources` (streams the ranked cited-domain list), and `doctor`. (`content brief` is an object command — `jsonl` degrades to its JSON document.)
|
|
769
873
|
|
|
770
874
|
Each `jsonl` line re-injects the envelope context it would otherwise lose, so a line lifted out still self-describes:
|
|
771
875
|
|
|
@@ -792,3 +896,6 @@ Compact reference for the composite / keyed commands agents read most (shapes ca
|
|
|
792
896
|
| `cnry ga attribution <p> [--trend]` | Object — a **renamed projection** of `GaTrafficResponse` (⚠️ field names differ from the DTO): `aiSessions`(←`aiSessionsDeduped`), `organicSessions`(←`totalOrganicSessions`), `directSessions`(←`totalDirectSessions`), plus `totalSessions, totalUsers, aiUsers, aiSessionsBySession, aiUsersBySession, socialSessions, socialUsers, {ai,social,organic,direct}SharePct (+ `*Display`), otherSessions, otherSharePct, channelBreakdown, aiReferrals[], aiReferralLandingPages[], socialReferrals[], periodStart, periodEnd`. With `--trend`: drops `periodStart/End`, adds `trend` (`GaAttributionTrendResponse`). Assembled inline in `commands/ga.ts`. | → degrades to the `json` document |
|
|
793
897
|
| `cnry key list` / `key create` / `key revoke <id>` | `list`: `{ keys[] }` — each `ApiKeyDto{ id, name, keyPrefix, scopes[], createdAt, lastUsedAt, revokedAt }` (SAFE metadata, never the hash or plaintext). `create`: `CreatedApiKeyDto` = `ApiKeyDto` **plus a one-time `key`** (the plaintext `cnry_…` token, shown once). `revoke`: the `ApiKeyDto` with `revokedAt` set. @ `contracts/api-keys.ts` | `key list` streams one key / line; `create` / `revoke` degrade to the `json` document |
|
|
794
898
|
| `cnry gbp summary <p> [--location …]` | `{ scope{locationName,locationCount}, performance{totals,recent7d,prior7d,deltaPct} (metric-keyed maps; keys are raw `BUSINESS_*` / `WEBSITE_CLICKS` tokens — label via `formatGbpMetricLabel`), freshness{dataThroughDate,latestStoredDate,pendingDays}, timeseries[], keywords{total,thresholdedCount,thresholdedPct}, placeActions{total,hasReservationCta,hasBookingCta,hasDirectMerchantCta}, lodging{lodgingLocationCount,populatedLodgingCount,emptyLodgingCount}, profileCompleteness{locationCount,withSecondaryCategories,secondaryCategoryTotal,withDescription,withServiceArea,withHours,withPrimaryPhone,permanentlyClosed,temporarilyClosed} }` — `GbpSummaryDto` @ `contracts/gbp.ts`; `emptyLodgingCount` means 0 readable Lodging API groups, a verify signal rather than proof the Hotel details panel is empty. `timeseries[]`=`{date,pending,metrics}`. | → degrades to the `json` document |
|
|
899
|
+
| `cnry ads account <p>` | `AdsAccountDto{ id, name, status, currencyCode, timezone, url, reviewStatus, integrityReviewStatus, integrityDecision }` @ `contracts/ads.ts`. This is live provider state, not a synced snapshot. | → degrades to the `json` document |
|
|
900
|
+
| `cnry ads geo search <p> --query <text>` | `AdsGeoSearchResponse{ count, query, results[] }` @ `contracts/ads.ts`; each location has `{ id, type, canonicalName, countryCode, name, regionCode }`. | ✅ one result / line as `{project, query, …location}` |
|
|
901
|
+
| `cnry ads conversions pixels <p>` / `event-settings <p>` | `{ pixels[] }` / `{ eventSettings[] }` @ `contracts/ads.ts`. Event settings include the conversion event, attribution window, source IDs/details, archive state, and version. | ✅ one pixel/event setting / line as `{project, …row}` |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as a,u as h,j as e}from"./vendor-tanstack-Be8JdV5M.js";import{
|
|
1
|
+
import{r as a,u as h,j as e}from"./vendor-tanstack-Be8JdV5M.js";import{bQ as L,bR as P,be as E,m as u,B as x,g as v,T as M}from"./index-mzLganDP.js";const o=100;function k(s){if(s==="all")return;const n=Number.parseInt(s,10);return new Date(Date.now()-n*24*60*60*1e3).toISOString()}function O(s){const n=s.userAgent?.toLowerCase()??"";return n.includes("canonry-mcp")?"MCP":n.includes("canonry-cli")?"CLI":n.includes("mozilla/")?"Dashboard":s.actor==="scheduler"?"Scheduler":s.actor==="agent"?"Agent":s.actor==="mcp"?"MCP":s.actor==="cli"?"CLI":s.actor}function B(s){return/delete|remove|revoke|disconnect|failed/.test(s)?"negative":/create|add|connect|completed/.test(s)?"positive":/update|replace|sync|apply/.test(s)?"caution":"neutral"}function D(s){return[s.action,s.actor,s.entityType,s.entityId,s.actorSession,s.userAgent,s.diff==null?"":JSON.stringify(s.diff)].filter(Boolean).join(" ").toLowerCase()}function F({projectName:s}){const[n,l]=a.useState(0),[c,y]=a.useState("90d"),[m,N]=a.useState(""),[p,w]=a.useState(""),S=a.useMemo(()=>k(c),[c]),g={limit:o,offset:n*o,since:S,actor:m||void 0},C=h({...L({client:u,path:{name:s??""},query:g}),enabled:!!s}),A=h({...P({client:u,query:g}),enabled:!s}),f=h({...E({client:u}),enabled:!s}),i=s?C:A,r=i.data??[],I=a.useMemo(()=>new Map((f.data??[]).map(t=>[t.id,t.displayName||t.name])),[f.data]),d=p.trim().toLowerCase(),j=a.useMemo(()=>d?r.filter(t=>D(t).includes(d)):r,[r,d]),b=t=>{l(0),t()};return i.isLoading?e.jsx("p",{className:"supporting-copy mt-6",role:"status",children:"Loading change history…"}):e.jsxs("section",{className:"mt-6","aria-labelledby":"change-history-heading",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Audit trail"}),e.jsx("h2",{id:"change-history-heading",children:"Change history"}),e.jsx("p",{className:"supporting-copy mt-1 max-w-2xl",children:"Configuration and operator actions. Execution outcomes remain in Runs."})]}),e.jsx(x,{type:"button",variant:"outline",size:"sm",onClick:()=>{i.refetch()},disabled:i.isFetching,children:i.isFetching?"Refreshing…":"Refresh"})]}),e.jsxs("div",{className:"mt-4 grid gap-2 sm:grid-cols-3",children:[e.jsx("input",{type:"search","aria-label":"Search change history",value:p,onChange:t=>w(t.target.value),placeholder:"Search actions, entities, or details",className:"min-h-11 rounded-md border border-base bg-bg px-3 text-sm text-strong placeholder-mono-600 focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600"}),e.jsxs("select",{"aria-label":"Filter change history by actor",value:m,onChange:t=>b(()=>N(t.target.value)),className:"min-h-11 rounded-md border border-base bg-bg px-3 text-sm text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600",children:[e.jsx("option",{value:"",children:"All actors"}),e.jsx("option",{value:"api",children:"API and dashboard"}),e.jsx("option",{value:"cli",children:"CLI"}),e.jsx("option",{value:"scheduler",children:"Scheduler"}),e.jsx("option",{value:"agent",children:"Agent"}),e.jsx("option",{value:"mcp",children:"MCP"})]}),e.jsxs("select",{"aria-label":"Filter change history by date range",value:c,onChange:t=>b(()=>y(t.target.value)),className:"min-h-11 rounded-md border border-base bg-bg px-3 text-sm text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600",children:[e.jsx("option",{value:"30d",children:"Last 30 days"}),e.jsx("option",{value:"90d",children:"Last 90 days"}),e.jsx("option",{value:"365d",children:"Last year"}),e.jsx("option",{value:"all",children:"All retained history"})]})]}),i.isError?e.jsxs(v,{className:"surface-card empty-card mt-4",children:[e.jsx("h3",{children:"Change history unavailable"}),e.jsx("p",{children:i.error instanceof Error?i.error.message:"Could not load the audit trail."})]}):j.length===0?e.jsxs(v,{className:"surface-card empty-card mt-4",children:[e.jsx("h3",{children:"No changes match these filters"}),e.jsx("p",{children:"Widen the date range or clear the actor and search filters."})]}):e.jsx("div",{className:"evidence-table-wrap mt-4",children:e.jsxs("table",{className:"evidence-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"When"}),s?null:e.jsx("th",{children:"Scope"}),e.jsx("th",{children:"Action"}),e.jsx("th",{children:"Entity"}),e.jsx("th",{children:"Origin"}),e.jsx("th",{children:"Details"})]})}),e.jsx("tbody",{children:j.map(t=>e.jsxs("tr",{children:[e.jsx("td",{className:"whitespace-nowrap tabular-nums text-muted",children:new Date(t.createdAt).toLocaleString()}),s?null:e.jsx("td",{className:"text-secondary",children:t.projectId?I.get(t.projectId)??`Deleted project · ${t.projectId}`:"Instance settings"}),e.jsx("td",{children:e.jsx(M,{tone:B(t.action),children:t.action})}),e.jsxs("td",{children:[e.jsx("span",{className:"text-strong",children:t.entityType}),t.entityId?e.jsx("span",{className:"mt-0.5 block max-w-48 truncate font-mono text-[11px] text-faint",title:t.entityId,children:t.entityId}):null]}),e.jsxs("td",{children:[e.jsx("span",{className:"text-secondary",children:O(t)}),t.actorSession?e.jsxs("span",{className:"mt-0.5 block max-w-40 truncate font-mono text-[11px] text-faint",title:t.actorSession,children:["Session ",t.actorSession]}):null]}),e.jsx("td",{children:t.diff==null?e.jsx("span",{className:"text-faint",children:"No field diff"}):e.jsxs("details",{children:[e.jsx("summary",{className:"min-h-11 cursor-pointer py-3 text-sm text-secondary hover:text-strong",children:"View diff"}),e.jsx("pre",{className:"max-h-72 max-w-xl overflow-auto whitespace-pre-wrap break-words rounded-md bg-bg-elevated p-3 text-xs text-secondary",children:JSON.stringify(t.diff,null,2)})]})})]},t.id))})]})}),!i.isError&&(n>0||r.length===o)?e.jsxs("div",{className:"mt-4 flex items-center justify-between gap-3",children:[e.jsx(x,{type:"button",variant:"outline",size:"sm",disabled:n===0,onClick:()=>l(t=>Math.max(0,t-1)),children:"Previous"}),e.jsxs("span",{className:"text-xs tabular-nums text-muted",children:["Page ",n+1]}),e.jsx(x,{type:"button",variant:"outline",size:"sm",disabled:r.length<o,onClick:()=>l(t=>t+1),children:"Next"})]}):null]})}export{F as A};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as n,j as e}from"./vendor-tanstack-Be8JdV5M.js";import{c as P,b$ as V,ah as U,c0 as Y,c1 as K,c2 as J,g as i,aL as E,T as y,i as h,B as w,j as M,aq as X,c3 as Z,c4 as ee,ak as se,c5 as te}from"./index-QCFQv0xh.js";import{C as ae,D as ne,T as re,a as le}from"./trash-2-xxHTqWbF.js";import"./vendor-radix-CQ77EO2y.js";import"./vendor-recharts-C9EZkgbP.js";import"./vendor-markdown-6dwjPOKK.js";const ce=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12",key:"1pkeuh"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16",key:"4dfq90"}]],ie=P("circle-alert",ce);const de=[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]],oe=P("external-link",de);function m({children:t,label:u="More info",placement:a="top",className:d}){const o=n.useId(),[g,c]=n.useState(!1);return e.jsxs("span",{className:`relative inline-flex ${d??""}`,children:[e.jsx("button",{type:"button","aria-label":u,"aria-describedby":g?o:void 0,className:"inline-flex h-4 w-4 items-center justify-center rounded-full text-muted hover:text-strong focus:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500",onMouseEnter:()=>c(!0),onMouseLeave:()=>c(!1),onFocus:()=>c(!0),onBlur:()=>c(!1),children:e.jsx(le,{className:"h-3.5 w-3.5","aria-hidden":!0})}),g&&e.jsx("span",{id:o,role:"tooltip",className:`absolute z-50 w-64 rounded border border-strong bg-bg-elevated px-3 py-2 text-xs font-normal leading-relaxed text-strong shadow-lg ${a==="top"?"bottom-full mb-2":"top-full mt-2"} left-1/2 -translate-x-1/2 whitespace-normal`,children:t})]})}const xe="https://commoncrawl.org/web-graphs";function D(t){return t==null?"—":t>=1e12?`${(t/1e12).toFixed(1)} TB`:t>=1e9?`${(t/1e9).toFixed(1)} GB`:t>=1e6?`${(t/1e6).toFixed(1)} MB`:t>=1e3?`${(t/1e3).toFixed(1)} KB`:`${t} B`}function b(t){if(!t)return"—";const u=Date.now()-new Date(t).getTime(),a=Math.floor(u/6e4);if(a<1)return"just now";if(a<60)return`${a}m ago`;const d=Math.floor(a/60);return d<24?`${d}h ago`:`${Math.floor(d/24)}d ago`}function C(t){switch(t){case"ready":return"positive";case"failed":return"negative";case"downloading":case"querying":case"queued":return"caution"}}function ye(){const[t,u]=n.useState(null),[a,d]=n.useState(null),[o,g]=n.useState([]),[c,$]=n.useState([]),[p,F]=n.useState(null),[S,B]=n.useState(!0),[R,q]=n.useState(!1),[j,I]=n.useState(!1),[N,k]=n.useState(""),[T,v]=n.useState(!1),[L,l]=n.useState(null),[A,x]=n.useState(null),f=n.useCallback(async()=>{B(!0),l(null);try{const[s,r,z,H,O]=await Promise.all([V(),U().catch(()=>null),Y().catch(()=>[]),K().catch(()=>[]),J().catch(()=>null)]);u(s),d(r),g(z),$(H),F(O)}catch(s){l(s instanceof Error?s.message:"Failed to load backlinks status")}finally{B(!1)}},[]);n.useEffect(()=>{f()},[f]);async function Q(){q(!0),l(null),x(null);try{const s=await Z();x(s.alreadyPresent?`DuckDB already installed (${s.version}).`:`Installed DuckDB ${s.version}.`),await f()}catch(s){l(s instanceof Error?s.message:"Failed to install DuckDB")}finally{q(!1)}}async function G(){const s=N.trim()||void 0;I(!0),l(null),x(null);try{const r=await ee(s);x(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.`),k(""),v(!1),await f()}catch(r){r instanceof se&&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 _(s){l(null),x(null);try{await te(s),x(`Pruned cached release ${s}.`),await f()}catch(r){l(r instanceof Error?r.message:"Failed to prune release")}}const W=a?.status==="ready"&&c.every(s=>s.release!==a.release);return e.jsxs("div",{className:"page-container",children:[e.jsx("div",{className:"page-header",children:e.jsxs("div",{className:"page-header-left",children:[e.jsx("h1",{className:"page-title",children:"Backlinks"}),e.jsx("p",{className:"page-subtitle",children:"Find domains that link to your projects, computed from the open Common Crawl web graph. Runs entirely on your machine — nothing is sent to third parties."})]})}),e.jsx(i,{className:"surface-card p-4 mb-6 border-caution-800/60",children:e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx(E,{className:"h-5 w-5 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:"Heads up — a release sync is a large download."}),e.jsxs("ul",{className:"mt-1.5 space-y-1 text-secondary",children:[e.jsxs("li",{children:[e.jsx("span",{className:"text-strong",children:"~16 GB"})," of gzipped vertex + edge files per release, stored at"," ",e.jsx("code",{className:"text-neutral",children:"~/.canonry/cache/commoncrawl/"}),"."]}),e.jsxs("li",{children:[e.jsx("span",{className:"text-strong",children:"10–20 min on a fast connection"})," for the download, then ~5 min for the DuckDB query."]}),e.jsx("li",{children:"One sync covers every project in this workspace. Releases are immutable, so the download only happens once per release."})]})]})]})}),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:"About"}),e.jsx("h2",{children:"How it works"})]})}),e.jsxs(i,{className:"surface-card p-5",children:[e.jsxs("p",{className:"text-sm text-secondary leading-relaxed max-w-3xl mb-4",children:["Common Crawl publishes a quarterly snapshot of the public web’s hyperlink graph. Canonry downloads one"," ",e.jsx("span",{className:"text-strong",children:"release"})," at a time and extracts backlinks for every project in this workspace in a single pass."]}),e.jsxs("ol",{className:"space-y-3 text-sm text-secondary max-w-3xl",children:[e.jsxs("li",{className:"flex gap-3",children:[e.jsx("span",{className:"shrink-0 inline-flex h-6 w-6 items-center justify-center rounded-full border border-strong bg-bg-elevated text-xs font-semibold text-neutral tabular-nums",children:"1"}),e.jsxs("span",{children:[e.jsx("span",{className:"text-strong font-medium",children:"Download (one-time, ~16 GB)"})," — vertex + edge files cached to"," ",e.jsx("code",{className:"text-neutral",children:"~/.canonry/cache/commoncrawl/"}),". Runs once per release; subsequent operations reuse the cache."]})]}),e.jsxs("li",{className:"flex gap-3",children:[e.jsx("span",{className:"shrink-0 inline-flex h-6 w-6 items-center justify-center rounded-full border border-strong bg-bg-elevated text-xs font-semibold text-neutral tabular-nums",children:"2"}),e.jsxs("span",{children:[e.jsx("span",{className:"text-strong font-medium",children:"Query (~5 min)"})," — one DuckDB pass scans the cached files and extracts referring domains for every project’s canonical domain. DuckDB is only used to ",e.jsx("span",{className:"text-strong",children:"read"})," these dumps; it doesn’t store any canonry state."]})]}),e.jsxs("li",{className:"flex gap-3",children:[e.jsx("span",{className:"shrink-0 inline-flex h-6 w-6 items-center justify-center rounded-full border border-strong bg-bg-elevated text-xs font-semibold text-neutral tabular-nums",children:"3"}),e.jsxs("span",{children:[e.jsx("span",{className:"text-strong font-medium",children:"Persist"})," — results land in the same SQLite database the rest of canonry uses. After the first sync, per-project reads (and re-run extracts against the cached release) are instant."]})]})]})]})]}),L&&e.jsx(i,{className:"surface-card p-4 mb-4 border-negative-800/60",children:e.jsx("p",{className:"text-sm text-negative",children:L})}),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:"Dependency"}),e.jsxs("h2",{className:"flex items-center gap-2",children:["DuckDB install status",e.jsxs(m,{label:"Why DuckDB?",children:[e.jsx("span",{className:"block",children:"DuckDB is a query engine canonry uses to scan the ~16 GB Common Crawl dumps and pull out your referring domains."}),e.jsxs("span",{className:"mt-2 block text-secondary",children:["It does ",e.jsx("span",{className:"text-strong",children:"not"})," store any canonry data — your backlink results live in SQLite alongside the rest of your projects. DuckDB is purely a tool for processing the raw CSV files."]}),e.jsxs("span",{className:"mt-2 block text-muted",children:["Installed on demand (not bundled) into ",e.jsx("code",{className:"text-neutral",children:"~/.canonry/plugins/"})," so users who never run backlinks don’t pay the ~40 MB install cost."]})]})]})]}),t?.duckdbInstalled?e.jsx(y,{tone:"positive",children:"Installed"}):e.jsx(y,{tone:"caution",children:"Not installed"})]}),e.jsx(i,{className:"surface-card p-5",children:S?e.jsx("p",{className:"text-sm text-muted",children:"Checking…"}):t?.duckdbInstalled?e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx(ae,{className:"h-5 w-5 text-positive-400 shrink-0 mt-0.5","aria-hidden":!0}),e.jsxs("div",{children:[e.jsxs("p",{className:"text-sm text-strong",children:["Version ",t.duckdbVersion??"unknown"," installed at"," ",e.jsx("code",{className:"text-neutral",children:t.pluginDir})]}),e.jsxs("p",{className:"text-xs text-muted mt-1",children:["Required spec: ",t.duckdbSpec]})]})]}):e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx(ie,{className:"h-5 w-5 text-caution-400 shrink-0 mt-0.5","aria-hidden":!0}),e.jsxs("div",{className:"flex-1",children:[e.jsx("p",{className:"text-sm text-strong",children:"DuckDB is not installed. It’s the query engine canonry uses to scan Common Crawl dumps — required before you can run a release sync or per-project extract."}),e.jsx("p",{className:"text-xs text-muted mt-1",children:"Installing doesn’t touch your project data. DuckDB only reads the downloaded CSV files; backlink results are written to the same SQLite database canonry already uses."}),t&&e.jsxs("p",{className:"text-xs text-muted mt-1",children:["Will be installed into ",e.jsx("code",{className:"text-neutral",children:t.pluginDir})," (~40 MB)."]}),!h()&&e.jsx("div",{className:"mt-3",children:e.jsxs(w,{type:"button",size:"sm",disabled:R,onClick:M(Q),children:[e.jsx(ne,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),R?"Installing…":"Install DuckDB"]})})]})]})})]}),e.jsxs("section",{className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Latest sync"}),e.jsxs("h2",{className:"flex items-center gap-2",children:["Release sync",e.jsx(m,{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(y,{tone:C(a.status),children:a.status})]}),e.jsxs(i,{className:"surface-card p-5",children:[e.jsxs("p",{className:"text-xs text-muted max-w-3xl mb-4",children:["A release is one Common Crawl dump (e.g. ",e.jsx("code",{className:"text-secondary",children:"cc-main-2026-jan-feb-mar"}),"). Syncing it downloads the graph and populates backlinks for every project in this workspace."]}),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-muted",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(m,{label:"What are rows?",children:"Total number of (project, referring domain) pairs persisted in SQLite from this sync, across every project in the workspace."})]}),e.jsx("p",{className:"text-neutral mt-0.5",children:a.domainsDiscovered??"—"})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Started"}),e.jsx("p",{className:"text-neutral mt-0.5",children:b(a.downloadStartedAt??a.createdAt)})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Finished"}),e.jsx("p",{className:"text-neutral mt-0.5",children:b(a.queryFinishedAt)})]})]}),a.error&&e.jsx("p",{className:"text-sm text-negative-400 pt-2",children:a.error})]}):e.jsx("p",{className:"text-sm text-muted",children:"No release sync has run in this workspace yet."}),W&&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(E,{className:"h-4 w-4 text-caution-400 shrink-0 mt-0.5","aria-hidden":!0}),e.jsxs("div",{className:"text-xs text-neutral leading-relaxed",children:[e.jsx("p",{className:"font-medium text-caution-200",children:"Cached files for this release are missing."}),e.jsxs("p",{className:"mt-1 text-secondary",children:["The sync record in the database says this release finished successfully, but the ~16 GB dump at"," ",e.jsxs("code",{className:"text-neutral",children:["~/.canonry/cache/commoncrawl/",a?.release,"/"]})," isn’t on disk. Your backlink data is still intact (it lives in SQLite), but per-project re-run extracts will fail until you either re-sync this release or start a new one."]})]})]})}),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"}),p?e.jsxs("p",{className:"text-sm text-strong mt-0.5",children:[e.jsx("code",{className:"text-heading",children:p.release}),e.jsxs("span",{className:"ml-2 text-xs text-muted",children:["— vertex ",D(p.vertexBytes),", edges ",D(p.edgesBytes)]})]}):e.jsx("p",{className:"text-sm text-muted mt-0.5",children:S?"Probing Common Crawl…":"Could not auto-detect — pass an explicit release below."}),e.jsxs("a",{href:xe,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(oe,{className:"h-3 w-3","aria-hidden":!0})]})]}),!h()&&e.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[e.jsxs(w,{type:"button",size:"sm",disabled:j||!t?.duckdbInstalled||!p&&!N.trim(),onClick:M(G),children:[e.jsx(X,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),j?"Queuing…":"Run sync"]}),e.jsxs(m,{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)."]})]})]})]}),h()?null:T?e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("input",{type:"text",className:"flex-1 min-w-[240px] rounded border border-strong bg-transparent px-2.5 py-1.5 text-sm text-strong placeholder-mono-600 focus:border-mono-500 focus:outline-none",placeholder:"cc-main-2026-jan-feb-mar",value:N,onChange:s=>k(s.target.value),disabled:j,autoFocus:!0}),e.jsx("button",{type:"button",className:"text-xs text-muted hover:text-neutral focus:text-neutral focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",onClick:()=>{k(""),v(!1)},disabled:j,children:"Cancel"})]}):e.jsx("button",{type:"button",className:"text-xs text-muted hover:text-neutral focus:text-neutral focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",onClick:()=>v(!0),disabled:j,children:"Use a different release →"})]}),!t?.duckdbInstalled&&e.jsx("p",{className:"text-xs text-faint mt-2",children:"Install DuckDB first to enable sync."})]})]}),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:"Cached releases"}),e.jsxs("h2",{className:"flex items-center gap-2",children:["Local disk cache",e.jsxs(m,{label:"What is this?",children:[e.jsxs("span",{className:"block",children:["Raw Common Crawl dumps stored at"," ",e.jsx("code",{className:"text-neutral",children:"~/.canonry/cache/commoncrawl/<release>/"}),". Each release takes ~16 GB."]}),e.jsxs("span",{className:"mt-2 block text-secondary",children:["These files are needed to re-run per-project extracts against a release without re-downloading. Pruning here ",e.jsx("span",{className:"text-strong",children:"does not delete your backlink data"})," — that lives in SQLite."]})]})]})]})}),e.jsx("p",{className:"text-xs text-muted mb-3 max-w-3xl",children:"Each cached release is a ~16 GB pair of gzipped files. They’re needed to re-query the graph (e.g. for a newly-added project) without re-downloading. Safe to prune — backlink results persist in SQLite."}),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"}),!h()&&e.jsx("th",{className:"px-4 py-2 font-medium sr-only",children:"Actions"})]})}),e.jsxs("tbody",{children:[c.map(s=>e.jsxs("tr",{className:"border-b border-mono-900 last:border-0",children:[e.jsx("td",{className:"px-4 py-2 text-strong",children:e.jsx("code",{children:s.release})}),e.jsx("td",{className:"px-4 py-2",children:s.syncStatus?e.jsx(y,{tone:C(s.syncStatus),children:s.syncStatus}):e.jsx("span",{className:"text-faint",children:"—"})}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:D(s.bytes)}),e.jsx("td",{className:"px-4 py-2 text-secondary",children:b(s.lastUsedAt)}),!h()&&e.jsx("td",{className:"px-4 py-2 text-right",children:e.jsxs("div",{className:"inline-flex items-center gap-1",children:[e.jsxs(w,{type:"button",variant:"outline",size:"sm",onClick:()=>{_(s.release)},children:[e.jsx(re,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),"Prune"]}),e.jsx(m,{label:"What does Prune do?",placement:"top",children:"Deletes the ~16 GB cache for this release from disk. Backlink results already in SQLite remain untouched. To re-run extracts against this release, you’d have to sync it again (another ~16 GB download)."})]})})]},s.release)),c.length===0&&e.jsx("tr",{children:e.jsx("td",{className:"px-4 py-4 text-sm text-muted",colSpan:h()?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."})})]})]})})]}),o.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:o.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(y,{tone:C(s.status),children:s.status})}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:s.projectsProcessed??"—"}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:s.domainsDiscovered??"—"}),e.jsx("td",{className:"px-4 py-2 text-secondary",children:b(s.queryFinishedAt??s.updatedAt)})]},s.id))})]})})]})]})}export{ye as BacklinksPage};
|
|
1
|
+
import{r as n,j as e}from"./vendor-tanstack-Be8JdV5M.js";import{c as P,c1 as V,ah as U,c2 as Y,c3 as K,c4 as J,g as i,aM as E,T as y,i as h,B as w,j as M,aq as X,c5 as Z,c6 as ee,ak as se,c7 as te}from"./index-mzLganDP.js";import{C as ae,D as ne,T as re,a as le}from"./trash-2-Bh-Yvkgc.js";import"./vendor-radix-CQ77EO2y.js";import"./vendor-recharts-DSxTZhaH.js";import"./vendor-markdown-6dwjPOKK.js";const ce=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12",key:"1pkeuh"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16",key:"4dfq90"}]],ie=P("circle-alert",ce);const de=[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]],oe=P("external-link",de);function m({children:t,label:u="More info",placement:a="top",className:d}){const o=n.useId(),[g,c]=n.useState(!1);return e.jsxs("span",{className:`relative inline-flex ${d??""}`,children:[e.jsx("button",{type:"button","aria-label":u,"aria-describedby":g?o:void 0,className:"inline-flex h-4 w-4 items-center justify-center rounded-full text-muted hover:text-strong focus:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500",onMouseEnter:()=>c(!0),onMouseLeave:()=>c(!1),onFocus:()=>c(!0),onBlur:()=>c(!1),children:e.jsx(le,{className:"h-3.5 w-3.5","aria-hidden":!0})}),g&&e.jsx("span",{id:o,role:"tooltip",className:`absolute z-50 w-64 rounded border border-strong bg-bg-elevated px-3 py-2 text-xs font-normal leading-relaxed text-strong shadow-lg ${a==="top"?"bottom-full mb-2":"top-full mt-2"} left-1/2 -translate-x-1/2 whitespace-normal`,children:t})]})}const xe="https://commoncrawl.org/web-graphs";function D(t){return t==null?"—":t>=1e12?`${(t/1e12).toFixed(1)} TB`:t>=1e9?`${(t/1e9).toFixed(1)} GB`:t>=1e6?`${(t/1e6).toFixed(1)} MB`:t>=1e3?`${(t/1e3).toFixed(1)} KB`:`${t} B`}function b(t){if(!t)return"—";const u=Date.now()-new Date(t).getTime(),a=Math.floor(u/6e4);if(a<1)return"just now";if(a<60)return`${a}m ago`;const d=Math.floor(a/60);return d<24?`${d}h ago`:`${Math.floor(d/24)}d ago`}function C(t){switch(t){case"ready":return"positive";case"failed":return"negative";case"downloading":case"querying":case"queued":return"caution"}}function ye(){const[t,u]=n.useState(null),[a,d]=n.useState(null),[o,g]=n.useState([]),[c,F]=n.useState([]),[p,$]=n.useState(null),[S,B]=n.useState(!0),[R,q]=n.useState(!1),[j,I]=n.useState(!1),[N,k]=n.useState(""),[T,v]=n.useState(!1),[L,l]=n.useState(null),[A,x]=n.useState(null),f=n.useCallback(async()=>{B(!0),l(null);try{const[s,r,z,H,O]=await Promise.all([V(),U().catch(()=>null),Y().catch(()=>[]),K().catch(()=>[]),J().catch(()=>null)]);u(s),d(r),g(z),F(H),$(O)}catch(s){l(s instanceof Error?s.message:"Failed to load backlinks status")}finally{B(!1)}},[]);n.useEffect(()=>{f()},[f]);async function Q(){q(!0),l(null),x(null);try{const s=await Z();x(s.alreadyPresent?`DuckDB already installed (${s.version}).`:`Installed DuckDB ${s.version}.`),await f()}catch(s){l(s instanceof Error?s.message:"Failed to install DuckDB")}finally{q(!1)}}async function G(){const s=N.trim()||void 0;I(!0),l(null),x(null);try{const r=await ee(s);x(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.`),k(""),v(!1),await f()}catch(r){r instanceof se&&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 _(s){l(null),x(null);try{await te(s),x(`Pruned cached release ${s}.`),await f()}catch(r){l(r instanceof Error?r.message:"Failed to prune release")}}const W=a?.status==="ready"&&c.every(s=>s.release!==a.release);return e.jsxs("div",{className:"page-container",children:[e.jsx("div",{className:"page-header",children:e.jsxs("div",{className:"page-header-left",children:[e.jsx("h1",{className:"page-title",children:"Backlinks"}),e.jsx("p",{className:"page-subtitle",children:"Find domains that link to your projects, computed from the open Common Crawl web graph. Runs entirely on your machine — nothing is sent to third parties."})]})}),e.jsx(i,{className:"surface-card p-4 mb-6 border-caution-800/60",children:e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx(E,{className:"h-5 w-5 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:"Heads up — a release sync is a large download."}),e.jsxs("ul",{className:"mt-1.5 space-y-1 text-secondary",children:[e.jsxs("li",{children:[e.jsx("span",{className:"text-strong",children:"~16 GB"})," of gzipped vertex + edge files per release, stored at"," ",e.jsx("code",{className:"text-neutral",children:"~/.canonry/cache/commoncrawl/"}),"."]}),e.jsxs("li",{children:[e.jsx("span",{className:"text-strong",children:"10–20 min on a fast connection"})," for the download, then ~5 min for the DuckDB query."]}),e.jsx("li",{children:"One sync covers every project in this workspace. Releases are immutable, so the download only happens once per release."})]})]})]})}),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:"About"}),e.jsx("h2",{children:"How it works"})]})}),e.jsxs(i,{className:"surface-card p-5",children:[e.jsxs("p",{className:"text-sm text-secondary leading-relaxed max-w-3xl mb-4",children:["Common Crawl publishes a quarterly snapshot of the public web’s hyperlink graph. Canonry downloads one"," ",e.jsx("span",{className:"text-strong",children:"release"})," at a time and extracts backlinks for every project in this workspace in a single pass."]}),e.jsxs("ol",{className:"space-y-3 text-sm text-secondary max-w-3xl",children:[e.jsxs("li",{className:"flex gap-3",children:[e.jsx("span",{className:"shrink-0 inline-flex h-6 w-6 items-center justify-center rounded-full border border-strong bg-bg-elevated text-xs font-semibold text-neutral tabular-nums",children:"1"}),e.jsxs("span",{children:[e.jsx("span",{className:"text-strong font-medium",children:"Download (one-time, ~16 GB)"})," — vertex + edge files cached to"," ",e.jsx("code",{className:"text-neutral",children:"~/.canonry/cache/commoncrawl/"}),". Runs once per release; subsequent operations reuse the cache."]})]}),e.jsxs("li",{className:"flex gap-3",children:[e.jsx("span",{className:"shrink-0 inline-flex h-6 w-6 items-center justify-center rounded-full border border-strong bg-bg-elevated text-xs font-semibold text-neutral tabular-nums",children:"2"}),e.jsxs("span",{children:[e.jsx("span",{className:"text-strong font-medium",children:"Query (~5 min)"})," — one DuckDB pass scans the cached files and extracts referring domains for every project’s canonical domain. DuckDB is only used to ",e.jsx("span",{className:"text-strong",children:"read"})," these dumps; it doesn’t store any canonry state."]})]}),e.jsxs("li",{className:"flex gap-3",children:[e.jsx("span",{className:"shrink-0 inline-flex h-6 w-6 items-center justify-center rounded-full border border-strong bg-bg-elevated text-xs font-semibold text-neutral tabular-nums",children:"3"}),e.jsxs("span",{children:[e.jsx("span",{className:"text-strong font-medium",children:"Persist"})," — results land in the same SQLite database the rest of canonry uses. After the first sync, per-project reads (and re-run extracts against the cached release) are instant."]})]})]})]})]}),L&&e.jsx(i,{className:"surface-card p-4 mb-4 border-negative-800/60",children:e.jsx("p",{className:"text-sm text-negative",children:L})}),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:"Dependency"}),e.jsxs("h2",{className:"flex items-center gap-2",children:["DuckDB install status",e.jsxs(m,{label:"Why DuckDB?",children:[e.jsx("span",{className:"block",children:"DuckDB is a query engine canonry uses to scan the ~16 GB Common Crawl dumps and pull out your referring domains."}),e.jsxs("span",{className:"mt-2 block text-secondary",children:["It does ",e.jsx("span",{className:"text-strong",children:"not"})," store any canonry data — your backlink results live in SQLite alongside the rest of your projects. DuckDB is purely a tool for processing the raw CSV files."]}),e.jsxs("span",{className:"mt-2 block text-muted",children:["Installed on demand (not bundled) into ",e.jsx("code",{className:"text-neutral",children:"~/.canonry/plugins/"})," so users who never run backlinks don’t pay the ~40 MB install cost."]})]})]})]}),t?.duckdbInstalled?e.jsx(y,{tone:"positive",children:"Installed"}):e.jsx(y,{tone:"caution",children:"Not installed"})]}),e.jsx(i,{className:"surface-card p-5",children:S?e.jsx("p",{className:"text-sm text-muted",children:"Checking…"}):t?.duckdbInstalled?e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx(ae,{className:"h-5 w-5 text-positive-400 shrink-0 mt-0.5","aria-hidden":!0}),e.jsxs("div",{children:[e.jsxs("p",{className:"text-sm text-strong",children:["Version ",t.duckdbVersion??"unknown"," installed at"," ",e.jsx("code",{className:"text-neutral",children:t.pluginDir})]}),e.jsxs("p",{className:"text-xs text-muted mt-1",children:["Required spec: ",t.duckdbSpec]})]})]}):e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx(ie,{className:"h-5 w-5 text-caution-400 shrink-0 mt-0.5","aria-hidden":!0}),e.jsxs("div",{className:"flex-1",children:[e.jsx("p",{className:"text-sm text-strong",children:"DuckDB is not installed. It’s the query engine canonry uses to scan Common Crawl dumps — required before you can run a release sync or per-project extract."}),e.jsx("p",{className:"text-xs text-muted mt-1",children:"Installing doesn’t touch your project data. DuckDB only reads the downloaded CSV files; backlink results are written to the same SQLite database canonry already uses."}),t&&e.jsxs("p",{className:"text-xs text-muted mt-1",children:["Will be installed into ",e.jsx("code",{className:"text-neutral",children:t.pluginDir})," (~40 MB)."]}),!h()&&e.jsx("div",{className:"mt-3",children:e.jsxs(w,{type:"button",size:"sm",disabled:R,onClick:M(Q),children:[e.jsx(ne,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),R?"Installing…":"Install DuckDB"]})})]})]})})]}),e.jsxs("section",{className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Latest sync"}),e.jsxs("h2",{className:"flex items-center gap-2",children:["Release sync",e.jsx(m,{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(y,{tone:C(a.status),children:a.status})]}),e.jsxs(i,{className:"surface-card p-5",children:[e.jsxs("p",{className:"text-xs text-muted max-w-3xl mb-4",children:["A release is one Common Crawl dump (e.g. ",e.jsx("code",{className:"text-secondary",children:"cc-main-2026-jan-feb-mar"}),"). Syncing it downloads the graph and populates backlinks for every project in this workspace."]}),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-muted",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(m,{label:"What are rows?",children:"Total number of (project, referring domain) pairs persisted in SQLite from this sync, across every project in the workspace."})]}),e.jsx("p",{className:"text-neutral mt-0.5",children:a.domainsDiscovered??"—"})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Started"}),e.jsx("p",{className:"text-neutral mt-0.5",children:b(a.downloadStartedAt??a.createdAt)})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Finished"}),e.jsx("p",{className:"text-neutral mt-0.5",children:b(a.queryFinishedAt)})]})]}),a.error&&e.jsx("p",{className:"text-sm text-negative-400 pt-2",children:a.error})]}):e.jsx("p",{className:"text-sm text-muted",children:"No release sync has run in this workspace yet."}),W&&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(E,{className:"h-4 w-4 text-caution-400 shrink-0 mt-0.5","aria-hidden":!0}),e.jsxs("div",{className:"text-xs text-neutral leading-relaxed",children:[e.jsx("p",{className:"font-medium text-caution-200",children:"Cached files for this release are missing."}),e.jsxs("p",{className:"mt-1 text-secondary",children:["The sync record in the database says this release finished successfully, but the ~16 GB dump at"," ",e.jsxs("code",{className:"text-neutral",children:["~/.canonry/cache/commoncrawl/",a?.release,"/"]})," isn’t on disk. Your backlink data is still intact (it lives in SQLite), but per-project re-run extracts will fail until you either re-sync this release or start a new one."]})]})]})}),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"}),p?e.jsxs("p",{className:"text-sm text-strong mt-0.5",children:[e.jsx("code",{className:"text-heading",children:p.release}),e.jsxs("span",{className:"ml-2 text-xs text-muted",children:["— vertex ",D(p.vertexBytes),", edges ",D(p.edgesBytes)]})]}):e.jsx("p",{className:"text-sm text-muted mt-0.5",children:S?"Probing Common Crawl…":"Could not auto-detect — pass an explicit release below."}),e.jsxs("a",{href:xe,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(oe,{className:"h-3 w-3","aria-hidden":!0})]})]}),!h()&&e.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[e.jsxs(w,{type:"button",size:"sm",disabled:j||!t?.duckdbInstalled||!p&&!N.trim(),onClick:M(G),children:[e.jsx(X,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),j?"Queuing…":"Run sync"]}),e.jsxs(m,{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)."]})]})]})]}),h()?null:T?e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("input",{type:"text",className:"flex-1 min-w-[240px] rounded border border-strong bg-transparent px-2.5 py-1.5 text-sm text-strong placeholder-mono-600 focus:border-mono-500 focus:outline-none",placeholder:"cc-main-2026-jan-feb-mar",value:N,onChange:s=>k(s.target.value),disabled:j,autoFocus:!0}),e.jsx("button",{type:"button",className:"text-xs text-muted hover:text-neutral focus:text-neutral focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",onClick:()=>{k(""),v(!1)},disabled:j,children:"Cancel"})]}):e.jsx("button",{type:"button",className:"text-xs text-muted hover:text-neutral focus:text-neutral focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",onClick:()=>v(!0),disabled:j,children:"Use a different release →"})]}),!t?.duckdbInstalled&&e.jsx("p",{className:"text-xs text-faint mt-2",children:"Install DuckDB first to enable sync."})]})]}),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:"Cached releases"}),e.jsxs("h2",{className:"flex items-center gap-2",children:["Local disk cache",e.jsxs(m,{label:"What is this?",children:[e.jsxs("span",{className:"block",children:["Raw Common Crawl dumps stored at"," ",e.jsx("code",{className:"text-neutral",children:"~/.canonry/cache/commoncrawl/<release>/"}),". Each release takes ~16 GB."]}),e.jsxs("span",{className:"mt-2 block text-secondary",children:["These files are needed to re-run per-project extracts against a release without re-downloading. Pruning here ",e.jsx("span",{className:"text-strong",children:"does not delete your backlink data"})," — that lives in SQLite."]})]})]})]})}),e.jsx("p",{className:"text-xs text-muted mb-3 max-w-3xl",children:"Each cached release is a ~16 GB pair of gzipped files. They’re needed to re-query the graph (e.g. for a newly-added project) without re-downloading. Safe to prune — backlink results persist in SQLite."}),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"}),!h()&&e.jsx("th",{className:"px-4 py-2 font-medium sr-only",children:"Actions"})]})}),e.jsxs("tbody",{children:[c.map(s=>e.jsxs("tr",{className:"border-b border-mono-900 last:border-0",children:[e.jsx("td",{className:"px-4 py-2 text-strong",children:e.jsx("code",{children:s.release})}),e.jsx("td",{className:"px-4 py-2",children:s.syncStatus?e.jsx(y,{tone:C(s.syncStatus),children:s.syncStatus}):e.jsx("span",{className:"text-faint",children:"—"})}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:D(s.bytes)}),e.jsx("td",{className:"px-4 py-2 text-secondary",children:b(s.lastUsedAt)}),!h()&&e.jsx("td",{className:"px-4 py-2 text-right",children:e.jsxs("div",{className:"inline-flex items-center gap-1",children:[e.jsxs(w,{type:"button",variant:"outline",size:"sm",onClick:()=>{_(s.release)},children:[e.jsx(re,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),"Prune"]}),e.jsx(m,{label:"What does Prune do?",placement:"top",children:"Deletes the ~16 GB cache for this release from disk. Backlink results already in SQLite remain untouched. To re-run extracts against this release, you’d have to sync it again (another ~16 GB download)."})]})})]},s.release)),c.length===0&&e.jsx("tr",{children:e.jsx("td",{className:"px-4 py-4 text-sm text-muted",colSpan:h()?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."})})]})]})})]}),o.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:o.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(y,{tone:C(s.status),children:s.status})}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:s.projectsProcessed??"—"}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:s.domainsDiscovered??"—"}),e.jsx("td",{className:"px-4 py-2 text-secondary",children:b(s.queryFinishedAt??s.updatedAt)})]},s.id))})]})})]})]})}export{ye as BacklinksPage};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{c as o}from"./index-mzLganDP.js";const c=[["path",{d:"m15 18-6-6 6-6",key:"1wnfg3"}]],h=o("chevron-left",c),u={contentStyle:{backgroundColor:"var(--chart-tooltip-bg, #18181b)",border:"1px solid var(--chart-tooltip-border, #3f3f46)",borderRadius:8,fontSize:12},labelStyle:{color:"var(--chart-tooltip-label, #e4e4e7)"},itemStyle:{color:"var(--chart-tooltip-item, #a1a1aa)"}},l={fill:"var(--chart-neutral-text-dim, #71717a)",fontSize:11},f="var(--chart-grid, #27272a)",d="var(--chart-axis, #27272a)",e=["var(--chart-series-1, #34d399)","var(--chart-series-2, #60a5fa)","var(--chart-series-3, #f472b6)","var(--chart-series-4, #facc15)","var(--chart-series-5, #a78bfa)","var(--chart-series-6, #fb923c)","var(--chart-series-7, #22d3ee)","var(--chart-series-8, #f87171)"],i={gemini:"#60a5fa",openai:"#34d399",claude:"#fb923c",perplexity:"#22d3ee",local:"#a78bfa"};function v(a,t=0){return i[a]??e[t%e.length]}const b={text:"var(--chart-neutral-text, #a1a1aa)",textDim:"var(--chart-neutral-text-dim, #71717a)",textFaint:"var(--chart-neutral-text-faint, #52525b)",surface:"var(--chart-neutral-surface, #27272a)",trackSubtle:"var(--chart-neutral-track-subtle, rgb(255 255 255 / 0.04))",gridLine:"var(--chart-neutral-grid-line, rgb(255 255 255 / 0.06))"},m={positive:"var(--chart-tone-positive, #34d399)",positiveDeep:"var(--chart-tone-positive-deep, #10b981)",caution:"var(--chart-tone-caution, #fbbf24)",negative:"var(--chart-tone-negative, #fb7185)"};function S(a){return a}function n(a){const t=/^(\d{4})-(\d{2})-(\d{2})/.exec(a);return t?{year:Number(t[1]),month:Number(t[2]),day:Number(t[3])}:null}function p(a){const t=String(a),r=n(t);return r?new Date(Date.UTC(r.year,r.month-1,r.day)).toLocaleDateString(void 0,{timeZone:"UTC",month:"short",day:"numeric",year:"numeric"}):t}function g(a){const t=n(String(a));return t?`${t.month}/${t.day}`:String(a)}function T(a){return new Date(a).toLocaleDateString(void 0,{month:"short",day:"numeric",year:"numeric"})}function C(a){const t=new Date(a);return`${t.getMonth()+1}/${t.getDate()}`}export{d as C,l as a,p as b,u as c,b as d,e,g as f,m as g,h,f as i,C as j,T as k,S as o,v as p};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{j as e}from"./vendor-tanstack-Be8JdV5M.js";import{A as i}from"./AuditHistoryPanel-
|
|
1
|
+
import{j as e}from"./vendor-tanstack-Be8JdV5M.js";import{A as i}from"./AuditHistoryPanel-BVQMDIhQ.js";import"./index-mzLganDP.js";import"./vendor-radix-CQ77EO2y.js";import"./vendor-recharts-DSxTZhaH.js";import"./vendor-markdown-6dwjPOKK.js";function c(){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:"Instance-wide configuration changes, including retained evidence for deleted projects."})]})}),e.jsx(i,{})]})}export{c as HistoryPage};
|