@ainyc/canonry 1.10.0 → 1.10.1

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
@@ -18,7 +18,7 @@ import {
18
18
  saveConfig,
19
19
  showFirstRunNotice,
20
20
  trackEvent
21
- } from "./chunk-P4D4VWJQ.js";
21
+ } from "./chunk-2DC7RBXJ.js";
22
22
 
23
23
  // src/cli.ts
24
24
  import { parseArgs } from "util";
@@ -67,7 +67,9 @@ var bootstrapEnvSchema = z.object({
67
67
  ANTHROPIC_MODEL: z.string().optional(),
68
68
  LOCAL_BASE_URL: z.string().optional(),
69
69
  LOCAL_API_KEY: z.string().optional(),
70
- LOCAL_MODEL: z.string().optional()
70
+ LOCAL_MODEL: z.string().optional(),
71
+ GOOGLE_CLIENT_ID: z.string().optional(),
72
+ GOOGLE_CLIENT_SECRET: z.string().optional()
71
73
  });
72
74
  function getBootstrapEnv(source, overrides) {
73
75
  const filtered = overrides ? Object.fromEntries(Object.entries(overrides).filter(([, v]) => v != null)) : {};
@@ -122,6 +124,8 @@ function getBootstrapEnv(source, overrides) {
122
124
  apiKey: parsed.CANONRY_API_KEY,
123
125
  apiUrl: parsed.CANONRY_API_URL,
124
126
  databasePath: parsed.CANONRY_DATABASE_PATH,
127
+ googleClientId: parsed.GOOGLE_CLIENT_ID,
128
+ googleClientSecret: parsed.GOOGLE_CLIENT_SECRET,
125
129
  providers
126
130
  };
127
131
  }
@@ -556,6 +560,40 @@ var ApiClient = class {
556
560
  { provider, count }
557
561
  );
558
562
  }
563
+ // Google connection management
564
+ async googleConnect(project, body) {
565
+ return this.request("POST", `/projects/${encodeURIComponent(project)}/google/connect`, body);
566
+ }
567
+ async googleConnections(project) {
568
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/google/connections`);
569
+ }
570
+ async googleDisconnect(project, type) {
571
+ await this.request("DELETE", `/projects/${encodeURIComponent(project)}/google/connections/${encodeURIComponent(type)}`);
572
+ }
573
+ async googleProperties(project) {
574
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/google/properties`);
575
+ }
576
+ async googleSetProperty(project, type, propertyId) {
577
+ return this.request("PUT", `/projects/${encodeURIComponent(project)}/google/connections/${encodeURIComponent(type)}/property`, { propertyId });
578
+ }
579
+ // GSC data
580
+ async gscSync(project, body) {
581
+ return this.request("POST", `/projects/${encodeURIComponent(project)}/google/gsc/sync`, body ?? {});
582
+ }
583
+ async gscPerformance(project, params) {
584
+ const qs = params ? "?" + new URLSearchParams(params).toString() : "";
585
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/google/gsc/performance${qs}`);
586
+ }
587
+ async gscInspect(project, url) {
588
+ return this.request("POST", `/projects/${encodeURIComponent(project)}/google/gsc/inspect`, { url });
589
+ }
590
+ async gscInspections(project, params) {
591
+ const qs = params ? "?" + new URLSearchParams(params).toString() : "";
592
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/google/gsc/inspections${qs}`);
593
+ }
594
+ async gscDeindexed(project) {
595
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/google/gsc/deindexed`);
596
+ }
559
597
  };
560
598
 
561
599
  // src/commands/project.ts
@@ -1286,6 +1324,216 @@ function telemetryCommand(subcommand) {
1286
1324
  }
1287
1325
  }
1288
1326
 
1327
+ // src/commands/google.ts
1328
+ function getClient11() {
1329
+ const config = loadConfig();
1330
+ return new ApiClient(config.apiUrl, config.apiKey);
1331
+ }
1332
+ async function googleConnect(project, opts) {
1333
+ const client = getClient11();
1334
+ const { authUrl } = await client.googleConnect(project, { type: opts.type });
1335
+ console.log(`
1336
+ Open this URL in your browser to authorize Google ${opts.type.toUpperCase()} access:
1337
+ `);
1338
+ console.log(` ${authUrl}
1339
+ `);
1340
+ try {
1341
+ const { exec } = await import("child_process");
1342
+ const platform = process.platform;
1343
+ const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
1344
+ exec(`${cmd} "${authUrl}"`);
1345
+ console.log("(Browser opened automatically)");
1346
+ } catch {
1347
+ console.log("(Could not open browser automatically \u2014 please copy the URL above)");
1348
+ }
1349
+ }
1350
+ async function googleDisconnect(project, opts) {
1351
+ const client = getClient11();
1352
+ await client.googleDisconnect(project, opts.type);
1353
+ console.log(`Disconnected Google ${opts.type.toUpperCase()} from project "${project}".`);
1354
+ }
1355
+ async function googleStatus(project, format) {
1356
+ const client = getClient11();
1357
+ const connections = await client.googleConnections(project);
1358
+ if (format === "json") {
1359
+ console.log(JSON.stringify({ connections }, null, 2));
1360
+ return;
1361
+ }
1362
+ if (connections.length === 0) {
1363
+ console.log(`No Google connections for project "${project}".`);
1364
+ console.log('Run "canonry google connect <project> --type gsc" to connect.');
1365
+ return;
1366
+ }
1367
+ console.log(`Google connections for "${project}":
1368
+ `);
1369
+ for (const conn of connections) {
1370
+ const type = conn.connectionType.toUpperCase();
1371
+ const property = conn.propertyId ?? "(not set)";
1372
+ console.log(` ${type}`);
1373
+ console.log(` Property: ${property}`);
1374
+ console.log(` Connected: ${conn.createdAt}`);
1375
+ console.log(` Updated: ${conn.updatedAt}`);
1376
+ console.log();
1377
+ }
1378
+ }
1379
+ async function googleProperties(project, format) {
1380
+ const client = getClient11();
1381
+ const { sites } = await client.googleProperties(project);
1382
+ if (format === "json") {
1383
+ console.log(JSON.stringify({ sites }, null, 2));
1384
+ return;
1385
+ }
1386
+ if (sites.length === 0) {
1387
+ console.log("No verified sites found for this Google account.");
1388
+ return;
1389
+ }
1390
+ console.log("Available GSC properties:\n");
1391
+ const urlWidth = Math.max(10, ...sites.map((s) => s.siteUrl.length));
1392
+ console.log(` ${"SITE URL".padEnd(urlWidth)} PERMISSION`);
1393
+ console.log(` ${"\u2500".repeat(urlWidth)} ${"\u2500".repeat(12)}`);
1394
+ for (const site of sites) {
1395
+ console.log(` ${site.siteUrl.padEnd(urlWidth)} ${site.permissionLevel}`);
1396
+ }
1397
+ console.log(`
1398
+ Use "canonry google set-property <project> <siteUrl>" to select a property.`);
1399
+ }
1400
+ async function googleSetProperty(project, propertyUrl) {
1401
+ const client = getClient11();
1402
+ await client.googleSetProperty(project, "gsc", propertyUrl);
1403
+ console.log(`GSC property set to "${propertyUrl}" for project "${project}".`);
1404
+ }
1405
+ async function googleSync(project, opts) {
1406
+ const client = getClient11();
1407
+ const run = await client.gscSync(project, { days: opts.days, full: opts.full });
1408
+ if (opts.format === "json") {
1409
+ console.log(JSON.stringify(run, null, 2));
1410
+ return;
1411
+ }
1412
+ console.log(`GSC sync started (run ${run.id})`);
1413
+ if (opts.wait) {
1414
+ const timeout = 10 * 60 * 1e3;
1415
+ const start = Date.now();
1416
+ process.stderr.write("Waiting for sync to complete");
1417
+ while (Date.now() - start < timeout) {
1418
+ await new Promise((r) => setTimeout(r, 2e3));
1419
+ const current = await client.getRun(run.id);
1420
+ process.stderr.write(".");
1421
+ if (current.status === "completed" || current.status === "failed") {
1422
+ process.stderr.write("\n");
1423
+ if (current.status === "completed") {
1424
+ console.log("GSC sync completed successfully.");
1425
+ } else {
1426
+ console.error("GSC sync failed.");
1427
+ }
1428
+ return;
1429
+ }
1430
+ }
1431
+ process.stderr.write("\n");
1432
+ console.error("Timed out waiting for GSC sync to complete.");
1433
+ process.exit(1);
1434
+ }
1435
+ }
1436
+ async function googlePerformance(project, opts) {
1437
+ const client = getClient11();
1438
+ const params = {};
1439
+ if (opts.days) {
1440
+ const end = /* @__PURE__ */ new Date();
1441
+ const start = /* @__PURE__ */ new Date();
1442
+ start.setDate(start.getDate() - opts.days);
1443
+ params.startDate = start.toISOString().split("T")[0];
1444
+ params.endDate = end.toISOString().split("T")[0];
1445
+ }
1446
+ if (opts.keyword) params.query = opts.keyword;
1447
+ if (opts.page) params.page = opts.page;
1448
+ const rows = await client.gscPerformance(project, Object.keys(params).length > 0 ? params : void 0);
1449
+ if (opts.format === "json") {
1450
+ console.log(JSON.stringify(rows, null, 2));
1451
+ return;
1452
+ }
1453
+ if (rows.length === 0) {
1454
+ console.log('No GSC data found. Run "canonry google sync" first.');
1455
+ return;
1456
+ }
1457
+ console.log(`GSC performance data (${rows.length} rows):
1458
+ `);
1459
+ console.log(` ${"DATE".padEnd(12)}${"QUERY".padEnd(30)}${"CLICKS".padEnd(8)}${"IMPR".padEnd(8)}${"CTR".padEnd(8)}${"POS".padEnd(6)}`);
1460
+ console.log(` ${"\u2500".repeat(12)}${"\u2500".repeat(30)}${"\u2500".repeat(8)}${"\u2500".repeat(8)}${"\u2500".repeat(8)}${"\u2500".repeat(6)}`);
1461
+ for (const row of rows.slice(0, 50)) {
1462
+ const query = row.query.length > 28 ? row.query.slice(0, 25) + "..." : row.query;
1463
+ console.log(
1464
+ ` ${row.date.padEnd(12)}${query.padEnd(30)}${String(row.clicks).padEnd(8)}${String(row.impressions).padEnd(8)}${(row.ctr * 100).toFixed(1).padStart(5)}% ${row.position.toFixed(1).padStart(5)}`
1465
+ );
1466
+ }
1467
+ if (rows.length > 50) {
1468
+ console.log(`
1469
+ ... and ${rows.length - 50} more rows (use --format json for full output)`);
1470
+ }
1471
+ }
1472
+ async function googleInspect(project, url, format) {
1473
+ const client = getClient11();
1474
+ const result = await client.gscInspect(project, url);
1475
+ if (format === "json") {
1476
+ console.log(JSON.stringify(result, null, 2));
1477
+ return;
1478
+ }
1479
+ console.log(`
1480
+ URL Inspection: ${result.url}
1481
+ `);
1482
+ console.log(` Indexing State: ${result.indexingState ?? "unknown"}`);
1483
+ console.log(` Verdict: ${result.verdict ?? "unknown"}`);
1484
+ console.log(` Coverage: ${result.coverageState ?? "unknown"}`);
1485
+ console.log(` Page Fetch: ${result.pageFetchState ?? "unknown"}`);
1486
+ console.log(` Robots.txt: ${result.robotsTxtState ?? "unknown"}`);
1487
+ console.log(` Last Crawled: ${result.crawlTime ?? "unknown"}`);
1488
+ console.log(` Mobile Friendly: ${result.isMobileFriendly === true ? "Yes" : result.isMobileFriendly === false ? "No" : "unknown"}`);
1489
+ console.log(` Rich Results: ${result.richResults?.length ? result.richResults.join(", ") : "none"}`);
1490
+ console.log(` Inspected At: ${result.inspectedAt}`);
1491
+ }
1492
+ async function googleInspections(project, opts) {
1493
+ const client = getClient11();
1494
+ const params = {};
1495
+ if (opts.url) params.url = opts.url;
1496
+ const rows = await client.gscInspections(project, Object.keys(params).length > 0 ? params : void 0);
1497
+ if (opts.format === "json") {
1498
+ console.log(JSON.stringify(rows, null, 2));
1499
+ return;
1500
+ }
1501
+ if (rows.length === 0) {
1502
+ console.log("No URL inspections found.");
1503
+ return;
1504
+ }
1505
+ console.log(`URL inspection history (${rows.length} records):
1506
+ `);
1507
+ const urlWidth = Math.min(50, Math.max(10, ...rows.map((r) => r.url.length)));
1508
+ console.log(` ${"URL".padEnd(urlWidth)} ${"INDEXING".padEnd(14)}${"VERDICT".padEnd(10)}${"INSPECTED".padEnd(22)}`);
1509
+ console.log(` ${"\u2500".repeat(urlWidth)} ${"\u2500".repeat(14)}${"\u2500".repeat(10)}${"\u2500".repeat(22)}`);
1510
+ for (const row of rows) {
1511
+ const url = row.url.length > urlWidth ? row.url.slice(0, urlWidth - 3) + "..." : row.url;
1512
+ console.log(
1513
+ ` ${url.padEnd(urlWidth)} ${(row.indexingState ?? "unknown").padEnd(14)}${(row.verdict ?? "-").padEnd(10)}${row.inspectedAt}`
1514
+ );
1515
+ }
1516
+ }
1517
+ async function googleDeindexed(project, format) {
1518
+ const client = getClient11();
1519
+ const rows = await client.gscDeindexed(project);
1520
+ if (format === "json") {
1521
+ console.log(JSON.stringify(rows, null, 2));
1522
+ return;
1523
+ }
1524
+ if (rows.length === 0) {
1525
+ console.log("No deindexed pages detected.");
1526
+ return;
1527
+ }
1528
+ console.log(`Deindexed pages (${rows.length}):
1529
+ `);
1530
+ for (const row of rows) {
1531
+ console.log(` ${row.url}`);
1532
+ console.log(` ${row.previousState} -> ${row.currentState} (detected: ${row.transitionDate})`);
1533
+ console.log();
1534
+ }
1535
+ }
1536
+
1289
1537
  // src/cli.ts
1290
1538
  import { createRequire } from "module";
1291
1539
  var USAGE = `
@@ -1330,6 +1578,16 @@ Usage:
1330
1578
  canonry notify remove <project> <id> Remove notification
1331
1579
  canonry notify test <project> <id> Send test webhook
1332
1580
  canonry notify events List available notification event types
1581
+ canonry google connect <project> Connect Google Search Console (--type gsc|ga4)
1582
+ canonry google disconnect <project> Disconnect Google integration
1583
+ canonry google status <project> Show Google connection status
1584
+ canonry google properties <project> List available GSC properties
1585
+ canonry google set-property <project> <url> Set GSC property URL
1586
+ canonry google sync <project> Sync GSC data (--days 30, --full, --wait)
1587
+ canonry google performance <project> Show GSC search performance data
1588
+ canonry google inspect <project> <url> Inspect a URL via GSC
1589
+ canonry google inspections <project> Show URL inspection history (--url <url>)
1590
+ canonry google deindexed <project> Show pages that lost indexing
1333
1591
  canonry settings Show active provider and quota settings
1334
1592
  canonry settings provider <name> Update a provider config
1335
1593
  canonry telemetry status Show telemetry status
@@ -1394,7 +1652,7 @@ async function main() {
1394
1652
  showFirstRunNotice();
1395
1653
  getOrCreateAnonymousId();
1396
1654
  }
1397
- const SUBCOMMAND_COMMANDS = /* @__PURE__ */ new Set(["project", "keyword", "competitor", "schedule", "notify", "settings", "telemetry"]);
1655
+ const SUBCOMMAND_COMMANDS = /* @__PURE__ */ new Set(["project", "keyword", "competitor", "schedule", "notify", "settings", "telemetry", "google"]);
1398
1656
  const resolvedCommand = SUBCOMMAND_COMMANDS.has(command) && args[1] && !args[1].startsWith("-") ? `${command}.${args[1]}` : command;
1399
1657
  if (command !== "telemetry") {
1400
1658
  trackEvent("cli.command", { command: resolvedCommand });
@@ -1922,6 +2180,165 @@ async function main() {
1922
2180
  telemetryCommand(args[1]);
1923
2181
  break;
1924
2182
  }
2183
+ case "google": {
2184
+ const subcommand = args[1];
2185
+ switch (subcommand) {
2186
+ case "connect": {
2187
+ const project = args[2];
2188
+ if (!project) {
2189
+ console.error("Error: project name is required");
2190
+ process.exit(1);
2191
+ }
2192
+ const { values: connectValues } = parseArgs({
2193
+ args: args.slice(3),
2194
+ options: {
2195
+ type: { type: "string", default: "gsc" }
2196
+ },
2197
+ allowPositionals: false
2198
+ });
2199
+ await googleConnect(project, { type: connectValues.type ?? "gsc" });
2200
+ break;
2201
+ }
2202
+ case "disconnect": {
2203
+ const project = args[2];
2204
+ if (!project) {
2205
+ console.error("Error: project name is required");
2206
+ process.exit(1);
2207
+ }
2208
+ const { values: disconnectValues } = parseArgs({
2209
+ args: args.slice(3),
2210
+ options: {
2211
+ type: { type: "string", default: "gsc" }
2212
+ },
2213
+ allowPositionals: false
2214
+ });
2215
+ await googleDisconnect(project, { type: disconnectValues.type ?? "gsc" });
2216
+ break;
2217
+ }
2218
+ case "status": {
2219
+ const project = args[2];
2220
+ if (!project) {
2221
+ console.error("Error: project name is required");
2222
+ process.exit(1);
2223
+ }
2224
+ await googleStatus(project, format);
2225
+ break;
2226
+ }
2227
+ case "properties": {
2228
+ const project = args[2];
2229
+ if (!project) {
2230
+ console.error("Error: project name is required");
2231
+ process.exit(1);
2232
+ }
2233
+ await googleProperties(project, format);
2234
+ break;
2235
+ }
2236
+ case "set-property": {
2237
+ const project = args[2];
2238
+ const propertyUrl = args[3];
2239
+ if (!project || !propertyUrl) {
2240
+ console.error("Error: project name and property URL are required");
2241
+ process.exit(1);
2242
+ }
2243
+ await googleSetProperty(project, propertyUrl);
2244
+ break;
2245
+ }
2246
+ case "sync": {
2247
+ const project = args[2];
2248
+ if (!project) {
2249
+ console.error("Error: project name is required");
2250
+ process.exit(1);
2251
+ }
2252
+ const { values: syncValues } = parseArgs({
2253
+ args: args.slice(3),
2254
+ options: {
2255
+ type: { type: "string", default: "gsc" },
2256
+ days: { type: "string" },
2257
+ full: { type: "boolean", default: false },
2258
+ wait: { type: "boolean", default: false },
2259
+ format: { type: "string" }
2260
+ },
2261
+ allowPositionals: false
2262
+ });
2263
+ await googleSync(project, {
2264
+ type: syncValues.type,
2265
+ days: syncValues.days ? parseInt(syncValues.days, 10) : void 0,
2266
+ full: syncValues.full,
2267
+ wait: syncValues.wait,
2268
+ format: syncValues.format === "json" ? "json" : format
2269
+ });
2270
+ break;
2271
+ }
2272
+ case "performance": {
2273
+ const project = args[2];
2274
+ if (!project) {
2275
+ console.error("Error: project name is required");
2276
+ process.exit(1);
2277
+ }
2278
+ const { values: perfValues } = parseArgs({
2279
+ args: args.slice(3),
2280
+ options: {
2281
+ days: { type: "string" },
2282
+ keyword: { type: "string" },
2283
+ page: { type: "string" },
2284
+ format: { type: "string" }
2285
+ },
2286
+ allowPositionals: false
2287
+ });
2288
+ await googlePerformance(project, {
2289
+ days: perfValues.days ? parseInt(perfValues.days, 10) : void 0,
2290
+ keyword: perfValues.keyword,
2291
+ page: perfValues.page,
2292
+ format: perfValues.format === "json" ? "json" : format
2293
+ });
2294
+ break;
2295
+ }
2296
+ case "inspect": {
2297
+ const project = args[2];
2298
+ const url = args[3];
2299
+ if (!project || !url) {
2300
+ console.error("Error: project name and URL are required");
2301
+ process.exit(1);
2302
+ }
2303
+ await googleInspect(project, url, format);
2304
+ break;
2305
+ }
2306
+ case "inspections": {
2307
+ const project = args[2];
2308
+ if (!project) {
2309
+ console.error("Error: project name is required");
2310
+ process.exit(1);
2311
+ }
2312
+ const { values: inspValues } = parseArgs({
2313
+ args: args.slice(3),
2314
+ options: {
2315
+ url: { type: "string" },
2316
+ format: { type: "string" }
2317
+ },
2318
+ allowPositionals: false
2319
+ });
2320
+ await googleInspections(project, {
2321
+ url: inspValues.url,
2322
+ format: inspValues.format === "json" ? "json" : format
2323
+ });
2324
+ break;
2325
+ }
2326
+ case "deindexed": {
2327
+ const project = args[2];
2328
+ if (!project) {
2329
+ console.error("Error: project name is required");
2330
+ process.exit(1);
2331
+ }
2332
+ await googleDeindexed(project, format);
2333
+ break;
2334
+ }
2335
+ default:
2336
+ console.error(`Unknown google subcommand: ${subcommand ?? "(none)"}`);
2337
+ console.log("Available: connect, disconnect, status, properties, set-property, sync, performance, inspect, inspections, deindexed");
2338
+ process.exit(1);
2339
+ }
2340
+ break;
2341
+ }
1925
2342
  default:
1926
2343
  console.error(`Unknown command: ${command}`);
1927
2344
  console.log('Run "canonry --help" for usage.');
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createServer,
3
3
  loadConfig
4
- } from "./chunk-P4D4VWJQ.js";
4
+ } from "./chunk-2DC7RBXJ.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.10.0",
3
+ "version": "1.10.1",
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",
@@ -51,13 +51,14 @@
51
51
  "@types/node-cron": "^3.0.11",
52
52
  "tsup": "^8.5.1",
53
53
  "tsx": "^4.19.0",
54
- "@ainyc/canonry-api-routes": "0.0.0",
55
54
  "@ainyc/canonry-config": "0.0.0",
56
- "@ainyc/canonry-db": "0.0.0",
55
+ "@ainyc/canonry-api-routes": "0.0.0",
57
56
  "@ainyc/canonry-contracts": "0.0.0",
58
- "@ainyc/canonry-provider-gemini": "0.0.0",
59
- "@ainyc/canonry-provider-claude": "0.0.0",
57
+ "@ainyc/canonry-db": "0.0.0",
60
58
  "@ainyc/canonry-provider-local": "0.0.0",
59
+ "@ainyc/canonry-provider-claude": "0.0.0",
60
+ "@ainyc/canonry-provider-gemini": "0.0.0",
61
+ "@ainyc/canonry-integration-google": "0.0.0",
61
62
  "@ainyc/canonry-provider-openai": "0.0.0"
62
63
  },
63
64
  "scripts": {