@ainyc/canonry 1.16.0 → 1.19.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/dist/cli.js CHANGED
@@ -15,11 +15,12 @@ import {
15
15
  migrate,
16
16
  notificationEventSchema,
17
17
  providerQuotaPolicySchema,
18
+ resolveProviderInput,
18
19
  saveConfig,
19
20
  setGoogleAuthConfig,
20
21
  showFirstRunNotice,
21
22
  trackEvent
22
- } from "./chunk-RU7RHCWK.js";
23
+ } from "./chunk-GX673NKZ.js";
23
24
 
24
25
  // src/cli.ts
25
26
  import { parseArgs } from "util";
@@ -673,10 +674,33 @@ var ApiClient = class {
673
674
  async gscDiscoverSitemaps(project) {
674
675
  return this.request("POST", `/projects/${encodeURIComponent(project)}/google/gsc/discover-sitemaps`, {});
675
676
  }
677
+ // Analytics
678
+ async getAnalyticsMetrics(project, window) {
679
+ const qs = window ? `?window=${encodeURIComponent(window)}` : "";
680
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/analytics/metrics${qs}`);
681
+ }
682
+ async getAnalyticsGaps(project, window) {
683
+ const qs = window ? `?window=${encodeURIComponent(window)}` : "";
684
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/analytics/gaps${qs}`);
685
+ }
686
+ async getAnalyticsSources(project, window) {
687
+ const qs = window ? `?window=${encodeURIComponent(window)}` : "";
688
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/analytics/sources${qs}`);
689
+ }
676
690
  // Google Indexing API
677
691
  async googleRequestIndexing(project, body) {
678
692
  return this.request("POST", `/projects/${encodeURIComponent(project)}/google/indexing/request`, body);
679
693
  }
694
+ // CDP browser provider
695
+ async getCdpStatus() {
696
+ return this.request("GET", "/cdp/status");
697
+ }
698
+ async cdpScreenshot(query, targets) {
699
+ return this.request("POST", "/cdp/screenshot", { query, targets });
700
+ }
701
+ async getBrowserDiff(project, runId) {
702
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/runs/${encodeURIComponent(runId)}/browser-diff`);
703
+ }
680
704
  };
681
705
 
682
706
  // src/commands/project.ts
@@ -926,7 +950,9 @@ async function triggerRun(project, opts) {
926
950
  const client = getClient4();
927
951
  const body = {};
928
952
  if (opts?.provider) {
929
- body.providers = [opts.provider];
953
+ const providerInputs = opts.provider.split(",").map((s) => s.trim()).filter(Boolean);
954
+ const resolved = providerInputs.flatMap((p) => resolveProviderInput(p));
955
+ body.providers = resolved.length > 0 ? resolved : providerInputs;
930
956
  }
931
957
  if (opts?.location) {
932
958
  body.location = opts.location;
@@ -1020,7 +1046,9 @@ async function triggerRunAll(opts) {
1020
1046
  }
1021
1047
  const body = {};
1022
1048
  if (opts?.provider) {
1023
- body.providers = [opts.provider];
1049
+ const providerInputs = opts.provider.split(",").map((s) => s.trim()).filter(Boolean);
1050
+ const resolved = providerInputs.flatMap((p) => resolveProviderInput(p));
1051
+ body.providers = resolved.length > 0 ? resolved : providerInputs;
1024
1052
  }
1025
1053
  const results = [];
1026
1054
  for (const p of projects) {
@@ -1226,6 +1254,107 @@ async function showHistory(project, format) {
1226
1254
  }
1227
1255
  }
1228
1256
 
1257
+ // src/commands/analytics.ts
1258
+ function getClient8() {
1259
+ const config = loadConfig();
1260
+ return new ApiClient(config.apiUrl, config.apiKey);
1261
+ }
1262
+ async function showAnalytics(project, options) {
1263
+ const client = getClient8();
1264
+ const features = options.feature ? [options.feature] : ["metrics", "gaps", "sources"];
1265
+ const results = {};
1266
+ for (const feature of features) {
1267
+ switch (feature) {
1268
+ case "metrics": {
1269
+ const data = await client.getAnalyticsMetrics(project, options.window);
1270
+ results.metrics = data;
1271
+ if (options.format !== "json") printMetrics(data);
1272
+ break;
1273
+ }
1274
+ case "gaps": {
1275
+ const data = await client.getAnalyticsGaps(project, options.window);
1276
+ results.gaps = data;
1277
+ if (options.format !== "json") printGaps(data);
1278
+ break;
1279
+ }
1280
+ case "sources": {
1281
+ const data = await client.getAnalyticsSources(project, options.window);
1282
+ results.sources = data;
1283
+ if (options.format !== "json") printSources(data);
1284
+ break;
1285
+ }
1286
+ default:
1287
+ console.error(`Unknown feature: ${feature}. Use: metrics, gaps, sources`);
1288
+ process.exit(1);
1289
+ }
1290
+ }
1291
+ if (options.format === "json") {
1292
+ console.log(JSON.stringify(results, null, 2));
1293
+ }
1294
+ }
1295
+ function printMetrics(data) {
1296
+ console.log(`
1297
+ Citation Rate Trends (${data.window})`);
1298
+ console.log("\u2500".repeat(50));
1299
+ const pct = (n) => `${(n * 100).toFixed(1)}%`;
1300
+ console.log(` Overall: ${pct(data.overall.citationRate)} (${data.overall.cited}/${data.overall.total})`);
1301
+ console.log(` Trend: ${data.trend}`);
1302
+ if (Object.keys(data.byProvider).length > 0) {
1303
+ console.log(`
1304
+ By Provider:`);
1305
+ for (const [provider, metric] of Object.entries(data.byProvider)) {
1306
+ console.log(` ${provider.padEnd(10)} ${pct(metric.citationRate).padStart(6)} (${metric.cited}/${metric.total})`);
1307
+ }
1308
+ }
1309
+ if (data.buckets.length > 0) {
1310
+ console.log(`
1311
+ Timeline:`);
1312
+ for (const bucket of data.buckets) {
1313
+ const start = bucket.startDate.slice(0, 10);
1314
+ const bar = bucket.total > 0 ? "\u2588".repeat(Math.round(bucket.citationRate * 20)) : "";
1315
+ console.log(` ${start} ${pct(bucket.citationRate).padStart(6)} ${bar}`);
1316
+ }
1317
+ }
1318
+ }
1319
+ function printGaps(data) {
1320
+ console.log(`
1321
+ Brand Gap Analysis`);
1322
+ console.log("\u2500".repeat(50));
1323
+ console.log(` Cited: ${data.cited.length} | Gap: ${data.gap.length} | Uncited: ${data.uncited.length}`);
1324
+ if (data.gap.length > 0) {
1325
+ console.log(`
1326
+ Opportunity Gaps (competitors cited, you're not):`);
1327
+ for (const kw of data.gap) {
1328
+ const competitors = kw.competitorsCiting.join(", ");
1329
+ const cons = kw.consistency.totalRuns > 0 ? ` [cited ${kw.consistency.citedRuns}/${kw.consistency.totalRuns} runs]` : "";
1330
+ console.log(` \u2022 ${kw.keyword}${cons}`);
1331
+ console.log(` Competitors: ${competitors}`);
1332
+ }
1333
+ }
1334
+ if (data.cited.length > 0) {
1335
+ console.log(`
1336
+ Cited Keywords:`);
1337
+ for (const kw of data.cited) {
1338
+ const cons = kw.consistency.totalRuns > 0 ? ` [${kw.consistency.citedRuns}/${kw.consistency.totalRuns} runs]` : "";
1339
+ console.log(` \u2713 ${kw.keyword} (${kw.providers.join(", ")})${cons}`);
1340
+ }
1341
+ }
1342
+ }
1343
+ function printSources(data) {
1344
+ console.log(`
1345
+ Source Origin Breakdown`);
1346
+ console.log("\u2500".repeat(50));
1347
+ if (data.overall.length === 0) {
1348
+ console.log(" No source data available");
1349
+ return;
1350
+ }
1351
+ for (const cat of data.overall) {
1352
+ const pct = `${(cat.percentage * 100).toFixed(1)}%`;
1353
+ const domains = cat.topDomains.slice(0, 3).map((d) => d.domain).join(", ");
1354
+ console.log(` ${cat.label.padEnd(20)} ${pct.padStart(6)} (${cat.count}) ${domains}`);
1355
+ }
1356
+ }
1357
+
1229
1358
  // src/commands/apply.ts
1230
1359
  import fs4 from "fs";
1231
1360
  import { parseAllDocuments } from "yaml";
@@ -1279,13 +1408,99 @@ async function exportProject(project, opts) {
1279
1408
  console.log(stringify(data));
1280
1409
  }
1281
1410
 
1411
+ // src/commands/cdp.ts
1412
+ function getClient9() {
1413
+ const config = loadConfig();
1414
+ return new ApiClient(config.apiUrl, config.apiKey);
1415
+ }
1416
+ async function cdpConnect(opts) {
1417
+ const config = loadConfig();
1418
+ const host = opts.host ?? "localhost";
1419
+ const port = parseInt(opts.port ?? "9222", 10);
1420
+ config.cdp = {
1421
+ ...config.cdp,
1422
+ host,
1423
+ port
1424
+ };
1425
+ saveConfig(config);
1426
+ console.log(`CDP endpoint configured: ws://${host}:${port}`);
1427
+ console.log("Restart canonry server for changes to take effect.");
1428
+ }
1429
+ async function cdpStatus() {
1430
+ const client = getClient9();
1431
+ try {
1432
+ const status = await client.getCdpStatus();
1433
+ if (status.connected) {
1434
+ console.log(`CDP connected: ${status.endpoint}`);
1435
+ if (status.browserVersion) console.log(`Browser: ${status.browserVersion}`);
1436
+ if (status.targets?.length) {
1437
+ console.log("\nTargets:");
1438
+ for (const t of status.targets) {
1439
+ const status_label = t.alive ? "\u25CF alive" : "\u25CB idle";
1440
+ const lastUsed = t.lastUsed ? ` (last used: ${t.lastUsed})` : "";
1441
+ console.log(` ${t.name}: ${status_label}${lastUsed}`);
1442
+ }
1443
+ }
1444
+ } else {
1445
+ console.log(`CDP not connected at ${status.endpoint}`);
1446
+ console.log("Launch Chrome with: chrome --remote-debugging-port=9222");
1447
+ }
1448
+ } catch (err) {
1449
+ const msg = err instanceof Error ? err.message : String(err);
1450
+ if (msg.includes("501") || msg.includes("not configured")) {
1451
+ console.log("CDP not configured. Run: canonry cdp connect --host <host> --port <port>");
1452
+ } else {
1453
+ console.error(`Error checking CDP status: ${msg}`);
1454
+ }
1455
+ }
1456
+ }
1457
+ async function cdpTargets() {
1458
+ await cdpStatus();
1459
+ }
1460
+ async function cdpScreenshot(query, opts) {
1461
+ if (!query) {
1462
+ console.error("Error: query is required");
1463
+ console.error('Usage: canonry cdp screenshot "best coffee in NYC"');
1464
+ process.exit(1);
1465
+ }
1466
+ const client = getClient9();
1467
+ const body = { query };
1468
+ if (opts?.targets) {
1469
+ body.targets = opts.targets.split(",").map((s) => s.trim());
1470
+ }
1471
+ try {
1472
+ const response = await client.cdpScreenshot(query, body.targets);
1473
+ for (const r of response.results) {
1474
+ console.log(`
1475
+ --- ${r.target} ---`);
1476
+ console.log(`Screenshot: ${r.screenshotPath}`);
1477
+ if (r.citations.length > 0) {
1478
+ console.log("Citations:");
1479
+ for (const c of r.citations) {
1480
+ console.log(` ${c.title}: ${c.uri}`);
1481
+ }
1482
+ } else {
1483
+ console.log("No citations found.");
1484
+ }
1485
+ if (r.answerText) {
1486
+ const preview = r.answerText.length > 200 ? r.answerText.slice(0, 200) + "..." : r.answerText;
1487
+ console.log(`Answer preview: ${preview}`);
1488
+ }
1489
+ }
1490
+ } catch (err) {
1491
+ const msg = err instanceof Error ? err.message : String(err);
1492
+ console.error(`CDP screenshot failed: ${msg}`);
1493
+ process.exit(1);
1494
+ }
1495
+ }
1496
+
1282
1497
  // src/commands/settings.ts
1283
- function getClient8() {
1498
+ function getClient10() {
1284
1499
  const config = loadConfig();
1285
1500
  return new ApiClient(config.apiUrl, config.apiKey);
1286
1501
  }
1287
1502
  async function setProvider(name, opts) {
1288
- const client = getClient8();
1503
+ const client = getClient10();
1289
1504
  const result = await client.updateProvider(name, opts);
1290
1505
  console.log(`Provider ${result.name} updated successfully.`);
1291
1506
  if (result.model) {
@@ -1296,7 +1511,7 @@ async function setProvider(name, opts) {
1296
1511
  }
1297
1512
  }
1298
1513
  async function showSettings(format) {
1299
- const client = getClient8();
1514
+ const client = getClient10();
1300
1515
  const config = loadConfig();
1301
1516
  const settings = await client.getSettings();
1302
1517
  if (format === "json") {
@@ -1334,12 +1549,12 @@ function setGoogleAuth(opts) {
1334
1549
  }
1335
1550
 
1336
1551
  // src/commands/schedule.ts
1337
- function getClient9() {
1552
+ function getClient11() {
1338
1553
  const config = loadConfig();
1339
1554
  return new ApiClient(config.apiUrl, config.apiKey);
1340
1555
  }
1341
1556
  async function setSchedule(project, opts) {
1342
- const client = getClient9();
1557
+ const client = getClient11();
1343
1558
  const body = {};
1344
1559
  if (opts.preset) body.preset = opts.preset;
1345
1560
  if (opts.cron) body.cron = opts.cron;
@@ -1350,7 +1565,7 @@ async function setSchedule(project, opts) {
1350
1565
  printSchedule(result);
1351
1566
  }
1352
1567
  async function showSchedule(project, format) {
1353
- const client = getClient9();
1568
+ const client = getClient11();
1354
1569
  const result = await client.getSchedule(project);
1355
1570
  if (format === "json") {
1356
1571
  console.log(JSON.stringify(result, null, 2));
@@ -1359,7 +1574,7 @@ async function showSchedule(project, format) {
1359
1574
  printSchedule(result);
1360
1575
  }
1361
1576
  async function enableSchedule(project) {
1362
- const client = getClient9();
1577
+ const client = getClient11();
1363
1578
  const current = await client.getSchedule(project);
1364
1579
  const body = { timezone: current.timezone };
1365
1580
  if (current.preset) body.preset = current.preset;
@@ -1369,7 +1584,7 @@ async function enableSchedule(project) {
1369
1584
  console.log(`Schedule enabled for "${project}"`);
1370
1585
  }
1371
1586
  async function disableSchedule(project) {
1372
- const client = getClient9();
1587
+ const client = getClient11();
1373
1588
  const current = await client.getSchedule(project);
1374
1589
  const body = { timezone: current.timezone, enabled: false };
1375
1590
  if (current.preset) body.preset = current.preset;
@@ -1379,7 +1594,7 @@ async function disableSchedule(project) {
1379
1594
  console.log(`Schedule disabled for "${project}"`);
1380
1595
  }
1381
1596
  async function removeSchedule(project) {
1382
- const client = getClient9();
1597
+ const client = getClient11();
1383
1598
  await client.deleteSchedule(project);
1384
1599
  console.log(`Schedule removed for "${project}"`);
1385
1600
  }
@@ -1401,12 +1616,12 @@ function printSchedule(s) {
1401
1616
  }
1402
1617
 
1403
1618
  // src/commands/notify.ts
1404
- function getClient10() {
1619
+ function getClient12() {
1405
1620
  const config = loadConfig();
1406
1621
  return new ApiClient(config.apiUrl, config.apiKey);
1407
1622
  }
1408
1623
  async function addNotification(project, opts) {
1409
- const client = getClient10();
1624
+ const client = getClient12();
1410
1625
  const result = await client.createNotification(project, {
1411
1626
  channel: "webhook",
1412
1627
  url: opts.webhook,
@@ -1416,7 +1631,7 @@ async function addNotification(project, opts) {
1416
1631
  printNotification(result);
1417
1632
  }
1418
1633
  async function listNotifications(project, format) {
1419
- const client = getClient10();
1634
+ const client = getClient12();
1420
1635
  const results = await client.listNotifications(project);
1421
1636
  if (format === "json") {
1422
1637
  console.log(JSON.stringify(results, null, 2));
@@ -1434,12 +1649,12 @@ async function listNotifications(project, format) {
1434
1649
  }
1435
1650
  }
1436
1651
  async function removeNotification(project, id) {
1437
- const client = getClient10();
1652
+ const client = getClient12();
1438
1653
  await client.deleteNotification(project, id);
1439
1654
  console.log(`Notification ${id} removed from "${project}"`);
1440
1655
  }
1441
1656
  async function testNotification(project, id) {
1442
- const client = getClient10();
1657
+ const client = getClient12();
1443
1658
  const result = await client.testNotification(project, id);
1444
1659
  if (result.ok) {
1445
1660
  console.log(`Test webhook delivered successfully (HTTP ${result.status})`);
@@ -1531,12 +1746,12 @@ function telemetryCommand(subcommand) {
1531
1746
  }
1532
1747
 
1533
1748
  // src/commands/google.ts
1534
- function getClient11() {
1749
+ function getClient13() {
1535
1750
  const config = loadConfig();
1536
1751
  return new ApiClient(config.apiUrl, config.apiKey);
1537
1752
  }
1538
1753
  async function googleConnect(project, opts) {
1539
- const client = getClient11();
1754
+ const client = getClient13();
1540
1755
  const { authUrl, redirectUri } = await client.googleConnect(project, {
1541
1756
  type: opts.type,
1542
1757
  publicUrl: opts.publicUrl
@@ -1561,12 +1776,12 @@ Open this URL in your browser to authorize Google ${opts.type.toUpperCase()} acc
1561
1776
  }
1562
1777
  }
1563
1778
  async function googleDisconnect(project, opts) {
1564
- const client = getClient11();
1779
+ const client = getClient13();
1565
1780
  await client.googleDisconnect(project, opts.type);
1566
1781
  console.log(`Disconnected Google ${opts.type.toUpperCase()} from project "${project}".`);
1567
1782
  }
1568
1783
  async function googleStatus(project, format) {
1569
- const client = getClient11();
1784
+ const client = getClient13();
1570
1785
  const connections = await client.googleConnections(project);
1571
1786
  if (format === "json") {
1572
1787
  console.log(JSON.stringify({ connections }, null, 2));
@@ -1590,7 +1805,7 @@ async function googleStatus(project, format) {
1590
1805
  }
1591
1806
  }
1592
1807
  async function googleProperties(project, format) {
1593
- const client = getClient11();
1808
+ const client = getClient13();
1594
1809
  const { sites } = await client.googleProperties(project);
1595
1810
  if (format === "json") {
1596
1811
  console.log(JSON.stringify({ sites }, null, 2));
@@ -1611,12 +1826,12 @@ async function googleProperties(project, format) {
1611
1826
  Use "canonry google set-property <project> <siteUrl>" to select a property.`);
1612
1827
  }
1613
1828
  async function googleSetProperty(project, propertyUrl) {
1614
- const client = getClient11();
1829
+ const client = getClient13();
1615
1830
  await client.googleSetProperty(project, "gsc", propertyUrl);
1616
1831
  console.log(`GSC property set to "${propertyUrl}" for project "${project}".`);
1617
1832
  }
1618
1833
  async function googleSync(project, opts) {
1619
- const client = getClient11();
1834
+ const client = getClient13();
1620
1835
  const run = await client.gscSync(project, { days: opts.days, full: opts.full });
1621
1836
  if (opts.format === "json") {
1622
1837
  console.log(JSON.stringify(run, null, 2));
@@ -1647,7 +1862,7 @@ async function googleSync(project, opts) {
1647
1862
  }
1648
1863
  }
1649
1864
  async function googlePerformance(project, opts) {
1650
- const client = getClient11();
1865
+ const client = getClient13();
1651
1866
  const params = {};
1652
1867
  if (opts.days) {
1653
1868
  const end = /* @__PURE__ */ new Date();
@@ -1683,7 +1898,7 @@ async function googlePerformance(project, opts) {
1683
1898
  }
1684
1899
  }
1685
1900
  async function googleInspect(project, url, format) {
1686
- const client = getClient11();
1901
+ const client = getClient13();
1687
1902
  const result = await client.gscInspect(project, url);
1688
1903
  if (format === "json") {
1689
1904
  console.log(JSON.stringify(result, null, 2));
@@ -1703,7 +1918,7 @@ URL Inspection: ${result.url}
1703
1918
  console.log(` Inspected At: ${result.inspectedAt}`);
1704
1919
  }
1705
1920
  async function googleInspections(project, opts) {
1706
- const client = getClient11();
1921
+ const client = getClient13();
1707
1922
  const params = {};
1708
1923
  if (opts.url) params.url = opts.url;
1709
1924
  const rows = await client.gscInspections(project, Object.keys(params).length > 0 ? params : void 0);
@@ -1728,7 +1943,7 @@ async function googleInspections(project, opts) {
1728
1943
  }
1729
1944
  }
1730
1945
  async function googleCoverage(project, format) {
1731
- const client = getClient11();
1946
+ const client = getClient13();
1732
1947
  const result = await client.gscCoverage(project);
1733
1948
  if (format === "json") {
1734
1949
  console.log(JSON.stringify(result, null, 2));
@@ -1774,12 +1989,12 @@ Index Coverage for "${project}"
1774
1989
  }
1775
1990
  }
1776
1991
  async function googleSetSitemap(project, sitemapUrl) {
1777
- const client = getClient11();
1992
+ const client = getClient13();
1778
1993
  await client.googleSetSitemap(project, "gsc", sitemapUrl);
1779
1994
  console.log(`GSC sitemap URL set to "${sitemapUrl}" for project "${project}".`);
1780
1995
  }
1781
1996
  async function googleListSitemaps(project, opts) {
1782
- const client = getClient11();
1997
+ const client = getClient13();
1783
1998
  const result = await client.gscSitemaps(project);
1784
1999
  if (opts.format === "json") {
1785
2000
  console.log(JSON.stringify(result, null, 2));
@@ -1801,7 +2016,7 @@ Sitemaps for project "${project}":
1801
2016
  }
1802
2017
  }
1803
2018
  async function googleInspectSitemap(project, opts) {
1804
- const client = getClient11();
2019
+ const client = getClient13();
1805
2020
  const run = await client.gscInspectSitemap(project, {
1806
2021
  sitemapUrl: opts.sitemapUrl
1807
2022
  });
@@ -1836,7 +2051,7 @@ async function googleInspectSitemap(project, opts) {
1836
2051
  }
1837
2052
  }
1838
2053
  async function googleCoverageHistory(project, opts) {
1839
- const client = getClient11();
2054
+ const client = getClient13();
1840
2055
  const rows = await client.gscCoverageHistory(project, { limit: opts.limit });
1841
2056
  if (opts.format === "json") {
1842
2057
  console.log(JSON.stringify(rows, null, 2));
@@ -1858,7 +2073,7 @@ GSC Coverage History for "${project}" (${rows.length} snapshots):
1858
2073
  }
1859
2074
  }
1860
2075
  async function googleDiscoverSitemaps(project, opts) {
1861
- const client = getClient11();
2076
+ const client = getClient13();
1862
2077
  const result = await client.gscDiscoverSitemaps(project);
1863
2078
  if (opts.format === "json") {
1864
2079
  console.log(JSON.stringify(result, null, 2));
@@ -1903,7 +2118,7 @@ Primary sitemap: ${result.primarySitemapUrl}`);
1903
2118
  }
1904
2119
  }
1905
2120
  async function googleRequestIndexing(project, opts) {
1906
- const client = getClient11();
2121
+ const client = getClient13();
1907
2122
  const body = { urls: [] };
1908
2123
  if (opts.allUnindexed) {
1909
2124
  body.allUnindexed = true;
@@ -1965,7 +2180,7 @@ async function googleRequestIndexing(project, opts) {
1965
2180
  }
1966
2181
  }
1967
2182
  async function googleDeindexed(project, format) {
1968
- const client = getClient11();
2183
+ const client = getClient13();
1969
2184
  const rows = await client.gscDeindexed(project);
1970
2185
  if (format === "json") {
1971
2186
  console.log(JSON.stringify(rows, null, 2));
@@ -2023,6 +2238,7 @@ Usage:
2023
2238
  canonry runs <project> List runs for a project
2024
2239
  canonry status <project> Show project summary
2025
2240
  canonry evidence <project> Show per-phrase results
2241
+ canonry analytics <project> Show analytics (--feature metrics|gaps|sources, --window 7d|30d|90d|all)
2026
2242
  canonry history <project> Show audit trail
2027
2243
  canonry export <project> Export project as YAML
2028
2244
  canonry apply <file...> Apply declarative config (multi-doc YAML supported)
@@ -2080,7 +2296,7 @@ Options:
2080
2296
  --display-name <name> Display name for project create/update
2081
2297
  --country <code> Country code (default: US)
2082
2298
  --language <lang> Language code (default: en)
2083
- --provider <name> Provider to use (gemini, openai, claude, local)
2299
+ --provider <name> Provider to use (gemini, openai, claude, local, cdp:chatgpt, or cdp for all CDP targets)
2084
2300
  --format <fmt> Output format: text (default) or json
2085
2301
  --location <label> Run with a specific configured location
2086
2302
  --all-locations Run for every configured location
@@ -2539,6 +2755,19 @@ async function main() {
2539
2755
  await showHistory(project, format);
2540
2756
  break;
2541
2757
  }
2758
+ case "analytics": {
2759
+ const project = args[1];
2760
+ if (!project) {
2761
+ console.error("Error: project name is required");
2762
+ process.exit(1);
2763
+ }
2764
+ const featureIdx = args.indexOf("--feature");
2765
+ const feature = featureIdx !== -1 ? args[featureIdx + 1] : void 0;
2766
+ const windowIdx = args.indexOf("--window");
2767
+ const windowArg = windowIdx !== -1 ? args[windowIdx + 1] : void 0;
2768
+ await showAnalytics(project, { feature, window: windowArg, format });
2769
+ break;
2770
+ }
2542
2771
  case "export": {
2543
2772
  const project = args[1];
2544
2773
  if (!project) {
@@ -3035,6 +3264,51 @@ async function main() {
3035
3264
  }
3036
3265
  break;
3037
3266
  }
3267
+ case "cdp": {
3268
+ const subcommand = args[1];
3269
+ switch (subcommand) {
3270
+ case "connect": {
3271
+ const { values: connectValues } = parseArgs({
3272
+ args: args.slice(2),
3273
+ options: {
3274
+ host: { type: "string", default: "localhost" },
3275
+ port: { type: "string", default: "9222" }
3276
+ },
3277
+ allowPositionals: false
3278
+ });
3279
+ await cdpConnect({ host: connectValues.host, port: connectValues.port });
3280
+ break;
3281
+ }
3282
+ case "status":
3283
+ await cdpStatus();
3284
+ break;
3285
+ case "targets":
3286
+ await cdpTargets();
3287
+ break;
3288
+ case "screenshot": {
3289
+ const query = args[2];
3290
+ if (!query) {
3291
+ console.error("Error: query is required");
3292
+ console.error('Usage: canonry cdp screenshot "best coffee in NYC"');
3293
+ process.exit(1);
3294
+ }
3295
+ const { values: screenshotValues } = parseArgs({
3296
+ args: args.slice(3),
3297
+ options: {
3298
+ targets: { type: "string" }
3299
+ },
3300
+ allowPositionals: false
3301
+ });
3302
+ await cdpScreenshot(query, { targets: screenshotValues.targets });
3303
+ break;
3304
+ }
3305
+ default:
3306
+ console.error(`Unknown cdp subcommand: ${subcommand ?? "(none)"}`);
3307
+ console.log("Available: connect, status, targets, screenshot");
3308
+ process.exit(1);
3309
+ }
3310
+ break;
3311
+ }
3038
3312
  default:
3039
3313
  console.error(`Unknown command: ${command}`);
3040
3314
  console.log('Run "canonry --help" for usage.');
package/dist/index.d.ts CHANGED
@@ -9,6 +9,11 @@ interface ProviderConfigEntry {
9
9
  model?: string;
10
10
  quota?: ProviderQuotaPolicy;
11
11
  }
12
+ interface CdpConfigEntry {
13
+ host?: string;
14
+ port?: number;
15
+ quota?: ProviderQuotaPolicy;
16
+ }
12
17
  interface GoogleConnectionConfigEntry {
13
18
  domain: string;
14
19
  connectionType: GoogleConnectionType;
@@ -43,6 +48,7 @@ interface CanonryConfig {
43
48
  claude?: ProviderConfigEntry;
44
49
  local?: ProviderConfigEntry;
45
50
  };
51
+ cdp?: CdpConfigEntry;
46
52
  google?: GoogleConfigEntry;
47
53
  telemetry?: boolean;
48
54
  anonymousId?: string;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createServer,
3
3
  loadConfig
4
- } from "./chunk-RU7RHCWK.js";
4
+ } from "./chunk-GX673NKZ.js";
5
5
  export {
6
6
  createServer,
7
7
  loadConfig
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "1.16.0",
3
+ "version": "1.19.2",
4
4
  "type": "module",
5
5
  "description": "The ultimate open-source AEO monitoring tool - track how answer engines cite your domain",
6
6
  "license": "FSL-1.1-ALv2",
@@ -44,7 +44,8 @@
44
44
  "openai": "^6.0.0",
45
45
  "pino-pretty": "^13.1.3",
46
46
  "yaml": "^2.7.1",
47
- "zod": "^4.1.12"
47
+ "zod": "^4.1.12",
48
+ "chrome-remote-interface": "^0.33.2"
48
49
  },
49
50
  "devDependencies": {
50
51
  "@types/better-sqlite3": "^7.6.13",
@@ -52,13 +53,14 @@
52
53
  "tsup": "^8.5.1",
53
54
  "tsx": "^4.19.0",
54
55
  "@ainyc/canonry-api-routes": "0.0.0",
55
- "@ainyc/canonry-contracts": "0.0.0",
56
56
  "@ainyc/canonry-db": "0.0.0",
57
57
  "@ainyc/canonry-config": "0.0.0",
58
- "@ainyc/canonry-provider-gemini": "0.0.0",
59
- "@ainyc/canonry-integration-google": "0.0.0",
60
58
  "@ainyc/canonry-provider-claude": "0.0.0",
59
+ "@ainyc/canonry-contracts": "0.0.0",
60
+ "@ainyc/canonry-provider-cdp": "0.0.0",
61
+ "@ainyc/canonry-provider-gemini": "0.0.0",
61
62
  "@ainyc/canonry-provider-openai": "0.0.0",
63
+ "@ainyc/canonry-integration-google": "0.0.0",
62
64
  "@ainyc/canonry-provider-local": "0.0.0"
63
65
  },
64
66
  "scripts": {