@ainyc/canonry 1.10.0 → 1.11.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/dist/cli.js CHANGED
@@ -16,9 +16,10 @@ import {
16
16
  notificationEventSchema,
17
17
  providerQuotaPolicySchema,
18
18
  saveConfig,
19
+ setGoogleAuthConfig,
19
20
  showFirstRunNotice,
20
21
  trackEvent
21
- } from "./chunk-P4D4VWJQ.js";
22
+ } from "./chunk-MVIL2UGM.js";
22
23
 
23
24
  // src/cli.ts
24
25
  import { parseArgs } from "util";
@@ -67,7 +68,9 @@ var bootstrapEnvSchema = z.object({
67
68
  ANTHROPIC_MODEL: z.string().optional(),
68
69
  LOCAL_BASE_URL: z.string().optional(),
69
70
  LOCAL_API_KEY: z.string().optional(),
70
- LOCAL_MODEL: z.string().optional()
71
+ LOCAL_MODEL: z.string().optional(),
72
+ GOOGLE_CLIENT_ID: z.string().optional(),
73
+ GOOGLE_CLIENT_SECRET: z.string().optional()
71
74
  });
72
75
  function getBootstrapEnv(source, overrides) {
73
76
  const filtered = overrides ? Object.fromEntries(Object.entries(overrides).filter(([, v]) => v != null)) : {};
@@ -122,6 +125,8 @@ function getBootstrapEnv(source, overrides) {
122
125
  apiKey: parsed.CANONRY_API_KEY,
123
126
  apiUrl: parsed.CANONRY_API_URL,
124
127
  databasePath: parsed.CANONRY_DATABASE_PATH,
128
+ googleClientId: parsed.GOOGLE_CLIENT_ID,
129
+ googleClientSecret: parsed.GOOGLE_CLIENT_SECRET,
125
130
  providers
126
131
  };
127
132
  }
@@ -155,6 +160,14 @@ async function bootstrapCommand(_opts) {
155
160
  if (providers?.openai) mergedProviders.openai = providers.openai;
156
161
  if (providers?.claude) mergedProviders.claude = providers.claude;
157
162
  if (providers?.local) mergedProviders.local = providers.local;
163
+ if (env.googleClientId && !env.googleClientSecret || !env.googleClientId && env.googleClientSecret) {
164
+ console.warn("Warning: GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET must both be set to configure Google OAuth. Skipping Google auth config.");
165
+ }
166
+ const mergedGoogle = env.googleClientId && env.googleClientSecret ? {
167
+ clientId: env.googleClientId,
168
+ clientSecret: env.googleClientSecret,
169
+ connections: existingConfig?.google?.connections ?? []
170
+ } : existingConfig?.google;
158
171
  const keyHash = crypto.createHash("sha256").update(rawApiKey).digest("hex");
159
172
  const keyPrefix = rawApiKey.slice(0, 9);
160
173
  const db = createClient(databasePath);
@@ -172,7 +185,8 @@ async function bootstrapCommand(_opts) {
172
185
  apiUrl: env.apiUrl || existingConfig?.apiUrl || `http://localhost:${process.env.CANONRY_PORT || "4100"}`,
173
186
  database: databasePath,
174
187
  apiKey: rawApiKey,
175
- providers: mergedProviders
188
+ providers: mergedProviders,
189
+ google: mergedGoogle
176
190
  });
177
191
  console.log(`Bootstrap complete. Config saved to ${getConfigPath()}`);
178
192
  console.log(`SQLite database path: ${databasePath}`);
@@ -214,23 +228,40 @@ async function initCommand(opts) {
214
228
  if (!fs.existsSync(configDir)) {
215
229
  fs.mkdirSync(configDir, { recursive: true });
216
230
  }
217
- const envProviders = getBootstrapEnv(process.env, {
231
+ const bootstrapEnv = getBootstrapEnv(process.env, {
218
232
  GEMINI_API_KEY: opts?.geminiKey,
219
233
  OPENAI_API_KEY: opts?.openaiKey,
220
234
  ANTHROPIC_API_KEY: opts?.claudeKey,
221
235
  LOCAL_BASE_URL: opts?.localUrl,
222
236
  LOCAL_MODEL: opts?.localModel,
223
- LOCAL_API_KEY: opts?.localKey
224
- }).providers;
225
- const nonInteractive = !!(envProviders.gemini || envProviders.openai || envProviders.claude || envProviders.local);
237
+ LOCAL_API_KEY: opts?.localKey,
238
+ GOOGLE_CLIENT_ID: opts?.googleClientId,
239
+ GOOGLE_CLIENT_SECRET: opts?.googleClientSecret
240
+ });
241
+ if (bootstrapEnv.googleClientId && !bootstrapEnv.googleClientSecret || !bootstrapEnv.googleClientId && bootstrapEnv.googleClientSecret) {
242
+ console.error("Google OAuth requires both a client ID and client secret when configured non-interactively.");
243
+ process.exit(1);
244
+ }
245
+ const envProviders = bootstrapEnv.providers;
246
+ const envGoogleConfigured = !!(bootstrapEnv.googleClientId && bootstrapEnv.googleClientSecret);
247
+ const nonInteractive = !!(envProviders.gemini || envProviders.openai || envProviders.claude || envProviders.local || envGoogleConfigured);
226
248
  const providers = {};
249
+ let google;
227
250
  if (nonInteractive) {
228
251
  Object.assign(providers, envProviders);
252
+ if (envGoogleConfigured) {
253
+ google = {
254
+ clientId: bootstrapEnv.googleClientId,
255
+ clientSecret: bootstrapEnv.googleClientSecret,
256
+ connections: []
257
+ };
258
+ }
229
259
  } else {
230
260
  console.log("Configure AI providers (at least one required):\n");
231
- console.log("Tip: For non-interactive setup, pass --gemini-key, --openai-key,");
232
- console.log("--claude-key flags or set GEMINI_API_KEY, OPENAI_API_KEY,");
233
- console.log('ANTHROPIC_API_KEY env vars. Or use "canonry bootstrap".\n');
261
+ console.log("Tip: For non-interactive setup, pass provider flags or set");
262
+ console.log("GEMINI_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY,");
263
+ console.log("GOOGLE_CLIENT_ID, and GOOGLE_CLIENT_SECRET env vars.");
264
+ console.log('Or use "canonry bootstrap".\n');
234
265
  const geminiApiKey = await prompt("Gemini API key (press Enter to skip): ");
235
266
  if (geminiApiKey) {
236
267
  const geminiModel = await prompt(" Gemini model [gemini-2.5-flash]: ") || "gemini-2.5-flash";
@@ -253,6 +284,20 @@ async function initCommand(opts) {
253
284
  const localApiKey = await prompt(" API key (press Enter if not needed): ") || void 0;
254
285
  providers.local = { baseUrl: localBaseUrl, apiKey: localApiKey, model: localModel, quota: DEFAULT_QUOTA };
255
286
  }
287
+ console.log("\nGoogle Search Console OAuth (optional):");
288
+ const googleClientId = await prompt("Google OAuth client ID (press Enter to skip): ");
289
+ if (googleClientId) {
290
+ const googleClientSecret = await prompt(" Google OAuth client secret: ");
291
+ if (!googleClientSecret) {
292
+ console.error("\nGoogle OAuth client secret is required when a client ID is provided.");
293
+ process.exit(1);
294
+ }
295
+ google = {
296
+ clientId: googleClientId,
297
+ clientSecret: googleClientSecret,
298
+ connections: []
299
+ };
300
+ }
256
301
  }
257
302
  const hasProvider = providers.gemini || providers.openai || providers.claude || providers.local;
258
303
  if (!hasProvider) {
@@ -277,7 +322,8 @@ async function initCommand(opts) {
277
322
  apiUrl: "http://localhost:4100",
278
323
  database: databasePath,
279
324
  apiKey: rawApiKey,
280
- providers
325
+ providers,
326
+ google
281
327
  });
282
328
  const providerNames = Object.keys(providers);
283
329
  console.log(`
@@ -556,6 +602,40 @@ var ApiClient = class {
556
602
  { provider, count }
557
603
  );
558
604
  }
605
+ // Google connection management
606
+ async googleConnect(project, body) {
607
+ return this.request("POST", `/projects/${encodeURIComponent(project)}/google/connect`, body);
608
+ }
609
+ async googleConnections(project) {
610
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/google/connections`);
611
+ }
612
+ async googleDisconnect(project, type) {
613
+ await this.request("DELETE", `/projects/${encodeURIComponent(project)}/google/connections/${encodeURIComponent(type)}`);
614
+ }
615
+ async googleProperties(project) {
616
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/google/properties`);
617
+ }
618
+ async googleSetProperty(project, type, propertyId) {
619
+ return this.request("PUT", `/projects/${encodeURIComponent(project)}/google/connections/${encodeURIComponent(type)}/property`, { propertyId });
620
+ }
621
+ // GSC data
622
+ async gscSync(project, body) {
623
+ return this.request("POST", `/projects/${encodeURIComponent(project)}/google/gsc/sync`, body ?? {});
624
+ }
625
+ async gscPerformance(project, params) {
626
+ const qs = params ? "?" + new URLSearchParams(params).toString() : "";
627
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/google/gsc/performance${qs}`);
628
+ }
629
+ async gscInspect(project, url) {
630
+ return this.request("POST", `/projects/${encodeURIComponent(project)}/google/gsc/inspect`, { url });
631
+ }
632
+ async gscInspections(project, params) {
633
+ const qs = params ? "?" + new URLSearchParams(params).toString() : "";
634
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/google/gsc/inspections${qs}`);
635
+ }
636
+ async gscDeindexed(project) {
637
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/google/gsc/deindexed`);
638
+ }
559
639
  };
560
640
 
561
641
  // src/commands/project.ts
@@ -1071,9 +1151,15 @@ async function setProvider(name, opts) {
1071
1151
  }
1072
1152
  async function showSettings(format) {
1073
1153
  const client = getClient8();
1154
+ const config = loadConfig();
1074
1155
  const settings = await client.getSettings();
1075
1156
  if (format === "json") {
1076
- console.log(JSON.stringify(settings, null, 2));
1157
+ console.log(JSON.stringify({
1158
+ ...settings,
1159
+ google: {
1160
+ configured: Boolean(config.google?.clientId && config.google?.clientSecret)
1161
+ }
1162
+ }, null, 2));
1077
1163
  return;
1078
1164
  }
1079
1165
  console.log("Provider settings:\n");
@@ -1087,6 +1173,18 @@ async function showSettings(format) {
1087
1173
  }
1088
1174
  }
1089
1175
  }
1176
+ console.log("\nGoogle OAuth:\n");
1177
+ console.log(` ${config.google?.clientId && config.google?.clientSecret ? "configured" : "not configured"}`);
1178
+ }
1179
+ function setGoogleAuth(opts) {
1180
+ const config = loadConfig();
1181
+ setGoogleAuthConfig(config, {
1182
+ clientId: opts.clientId,
1183
+ clientSecret: opts.clientSecret
1184
+ });
1185
+ saveConfig(config);
1186
+ console.log(`Google OAuth credentials saved to ${getConfigPath()}.`);
1187
+ console.log("Restart the local server if it is already running.");
1090
1188
  }
1091
1189
 
1092
1190
  // src/commands/schedule.ts
@@ -1286,6 +1384,216 @@ function telemetryCommand(subcommand) {
1286
1384
  }
1287
1385
  }
1288
1386
 
1387
+ // src/commands/google.ts
1388
+ function getClient11() {
1389
+ const config = loadConfig();
1390
+ return new ApiClient(config.apiUrl, config.apiKey);
1391
+ }
1392
+ async function googleConnect(project, opts) {
1393
+ const client = getClient11();
1394
+ const { authUrl } = await client.googleConnect(project, { type: opts.type });
1395
+ console.log(`
1396
+ Open this URL in your browser to authorize Google ${opts.type.toUpperCase()} access:
1397
+ `);
1398
+ console.log(` ${authUrl}
1399
+ `);
1400
+ try {
1401
+ const { exec } = await import("child_process");
1402
+ const platform = process.platform;
1403
+ const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
1404
+ exec(`${cmd} "${authUrl}"`);
1405
+ console.log("(Browser opened automatically)");
1406
+ } catch {
1407
+ console.log("(Could not open browser automatically \u2014 please copy the URL above)");
1408
+ }
1409
+ }
1410
+ async function googleDisconnect(project, opts) {
1411
+ const client = getClient11();
1412
+ await client.googleDisconnect(project, opts.type);
1413
+ console.log(`Disconnected Google ${opts.type.toUpperCase()} from project "${project}".`);
1414
+ }
1415
+ async function googleStatus(project, format) {
1416
+ const client = getClient11();
1417
+ const connections = await client.googleConnections(project);
1418
+ if (format === "json") {
1419
+ console.log(JSON.stringify({ connections }, null, 2));
1420
+ return;
1421
+ }
1422
+ if (connections.length === 0) {
1423
+ console.log(`No Google connections for project "${project}".`);
1424
+ console.log('Run "canonry google connect <project> --type gsc" to connect.');
1425
+ return;
1426
+ }
1427
+ console.log(`Google connections for "${project}":
1428
+ `);
1429
+ for (const conn of connections) {
1430
+ const type = conn.connectionType.toUpperCase();
1431
+ const property = conn.propertyId ?? "(not set)";
1432
+ console.log(` ${type}`);
1433
+ console.log(` Property: ${property}`);
1434
+ console.log(` Connected: ${conn.createdAt}`);
1435
+ console.log(` Updated: ${conn.updatedAt}`);
1436
+ console.log();
1437
+ }
1438
+ }
1439
+ async function googleProperties(project, format) {
1440
+ const client = getClient11();
1441
+ const { sites } = await client.googleProperties(project);
1442
+ if (format === "json") {
1443
+ console.log(JSON.stringify({ sites }, null, 2));
1444
+ return;
1445
+ }
1446
+ if (sites.length === 0) {
1447
+ console.log("No verified sites found for this Google account.");
1448
+ return;
1449
+ }
1450
+ console.log("Available GSC properties:\n");
1451
+ const urlWidth = Math.max(10, ...sites.map((s) => s.siteUrl.length));
1452
+ console.log(` ${"SITE URL".padEnd(urlWidth)} PERMISSION`);
1453
+ console.log(` ${"\u2500".repeat(urlWidth)} ${"\u2500".repeat(12)}`);
1454
+ for (const site of sites) {
1455
+ console.log(` ${site.siteUrl.padEnd(urlWidth)} ${site.permissionLevel}`);
1456
+ }
1457
+ console.log(`
1458
+ Use "canonry google set-property <project> <siteUrl>" to select a property.`);
1459
+ }
1460
+ async function googleSetProperty(project, propertyUrl) {
1461
+ const client = getClient11();
1462
+ await client.googleSetProperty(project, "gsc", propertyUrl);
1463
+ console.log(`GSC property set to "${propertyUrl}" for project "${project}".`);
1464
+ }
1465
+ async function googleSync(project, opts) {
1466
+ const client = getClient11();
1467
+ const run = await client.gscSync(project, { days: opts.days, full: opts.full });
1468
+ if (opts.format === "json") {
1469
+ console.log(JSON.stringify(run, null, 2));
1470
+ return;
1471
+ }
1472
+ console.log(`GSC sync started (run ${run.id})`);
1473
+ if (opts.wait) {
1474
+ const timeout = 10 * 60 * 1e3;
1475
+ const start = Date.now();
1476
+ process.stderr.write("Waiting for sync to complete");
1477
+ while (Date.now() - start < timeout) {
1478
+ await new Promise((r) => setTimeout(r, 2e3));
1479
+ const current = await client.getRun(run.id);
1480
+ process.stderr.write(".");
1481
+ if (current.status === "completed" || current.status === "failed") {
1482
+ process.stderr.write("\n");
1483
+ if (current.status === "completed") {
1484
+ console.log("GSC sync completed successfully.");
1485
+ } else {
1486
+ console.error("GSC sync failed.");
1487
+ }
1488
+ return;
1489
+ }
1490
+ }
1491
+ process.stderr.write("\n");
1492
+ console.error("Timed out waiting for GSC sync to complete.");
1493
+ process.exit(1);
1494
+ }
1495
+ }
1496
+ async function googlePerformance(project, opts) {
1497
+ const client = getClient11();
1498
+ const params = {};
1499
+ if (opts.days) {
1500
+ const end = /* @__PURE__ */ new Date();
1501
+ const start = /* @__PURE__ */ new Date();
1502
+ start.setDate(start.getDate() - opts.days);
1503
+ params.startDate = start.toISOString().split("T")[0];
1504
+ params.endDate = end.toISOString().split("T")[0];
1505
+ }
1506
+ if (opts.keyword) params.query = opts.keyword;
1507
+ if (opts.page) params.page = opts.page;
1508
+ const rows = await client.gscPerformance(project, Object.keys(params).length > 0 ? params : void 0);
1509
+ if (opts.format === "json") {
1510
+ console.log(JSON.stringify(rows, null, 2));
1511
+ return;
1512
+ }
1513
+ if (rows.length === 0) {
1514
+ console.log('No GSC data found. Run "canonry google sync" first.');
1515
+ return;
1516
+ }
1517
+ console.log(`GSC performance data (${rows.length} rows):
1518
+ `);
1519
+ console.log(` ${"DATE".padEnd(12)}${"QUERY".padEnd(30)}${"CLICKS".padEnd(8)}${"IMPR".padEnd(8)}${"CTR".padEnd(8)}${"POS".padEnd(6)}`);
1520
+ console.log(` ${"\u2500".repeat(12)}${"\u2500".repeat(30)}${"\u2500".repeat(8)}${"\u2500".repeat(8)}${"\u2500".repeat(8)}${"\u2500".repeat(6)}`);
1521
+ for (const row of rows.slice(0, 50)) {
1522
+ const query = row.query.length > 28 ? row.query.slice(0, 25) + "..." : row.query;
1523
+ console.log(
1524
+ ` ${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)}`
1525
+ );
1526
+ }
1527
+ if (rows.length > 50) {
1528
+ console.log(`
1529
+ ... and ${rows.length - 50} more rows (use --format json for full output)`);
1530
+ }
1531
+ }
1532
+ async function googleInspect(project, url, format) {
1533
+ const client = getClient11();
1534
+ const result = await client.gscInspect(project, url);
1535
+ if (format === "json") {
1536
+ console.log(JSON.stringify(result, null, 2));
1537
+ return;
1538
+ }
1539
+ console.log(`
1540
+ URL Inspection: ${result.url}
1541
+ `);
1542
+ console.log(` Indexing State: ${result.indexingState ?? "unknown"}`);
1543
+ console.log(` Verdict: ${result.verdict ?? "unknown"}`);
1544
+ console.log(` Coverage: ${result.coverageState ?? "unknown"}`);
1545
+ console.log(` Page Fetch: ${result.pageFetchState ?? "unknown"}`);
1546
+ console.log(` Robots.txt: ${result.robotsTxtState ?? "unknown"}`);
1547
+ console.log(` Last Crawled: ${result.crawlTime ?? "unknown"}`);
1548
+ console.log(` Mobile Friendly: ${result.isMobileFriendly === true ? "Yes" : result.isMobileFriendly === false ? "No" : "unknown"}`);
1549
+ console.log(` Rich Results: ${result.richResults?.length ? result.richResults.join(", ") : "none"}`);
1550
+ console.log(` Inspected At: ${result.inspectedAt}`);
1551
+ }
1552
+ async function googleInspections(project, opts) {
1553
+ const client = getClient11();
1554
+ const params = {};
1555
+ if (opts.url) params.url = opts.url;
1556
+ const rows = await client.gscInspections(project, Object.keys(params).length > 0 ? params : void 0);
1557
+ if (opts.format === "json") {
1558
+ console.log(JSON.stringify(rows, null, 2));
1559
+ return;
1560
+ }
1561
+ if (rows.length === 0) {
1562
+ console.log("No URL inspections found.");
1563
+ return;
1564
+ }
1565
+ console.log(`URL inspection history (${rows.length} records):
1566
+ `);
1567
+ const urlWidth = Math.min(50, Math.max(10, ...rows.map((r) => r.url.length)));
1568
+ console.log(` ${"URL".padEnd(urlWidth)} ${"INDEXING".padEnd(14)}${"VERDICT".padEnd(10)}${"INSPECTED".padEnd(22)}`);
1569
+ console.log(` ${"\u2500".repeat(urlWidth)} ${"\u2500".repeat(14)}${"\u2500".repeat(10)}${"\u2500".repeat(22)}`);
1570
+ for (const row of rows) {
1571
+ const url = row.url.length > urlWidth ? row.url.slice(0, urlWidth - 3) + "..." : row.url;
1572
+ console.log(
1573
+ ` ${url.padEnd(urlWidth)} ${(row.indexingState ?? "unknown").padEnd(14)}${(row.verdict ?? "-").padEnd(10)}${row.inspectedAt}`
1574
+ );
1575
+ }
1576
+ }
1577
+ async function googleDeindexed(project, format) {
1578
+ const client = getClient11();
1579
+ const rows = await client.gscDeindexed(project);
1580
+ if (format === "json") {
1581
+ console.log(JSON.stringify(rows, null, 2));
1582
+ return;
1583
+ }
1584
+ if (rows.length === 0) {
1585
+ console.log("No deindexed pages detected.");
1586
+ return;
1587
+ }
1588
+ console.log(`Deindexed pages (${rows.length}):
1589
+ `);
1590
+ for (const row of rows) {
1591
+ console.log(` ${row.url}`);
1592
+ console.log(` ${row.previousState} -> ${row.currentState} (detected: ${row.transitionDate})`);
1593
+ console.log();
1594
+ }
1595
+ }
1596
+
1289
1597
  // src/cli.ts
1290
1598
  import { createRequire } from "module";
1291
1599
  var USAGE = `
@@ -1330,8 +1638,19 @@ Usage:
1330
1638
  canonry notify remove <project> <id> Remove notification
1331
1639
  canonry notify test <project> <id> Send test webhook
1332
1640
  canonry notify events List available notification event types
1641
+ canonry google connect <project> Connect Google Search Console (--type gsc|ga4)
1642
+ canonry google disconnect <project> Disconnect Google integration
1643
+ canonry google status <project> Show Google connection status
1644
+ canonry google properties <project> List available GSC properties
1645
+ canonry google set-property <project> <url> Set GSC property URL
1646
+ canonry google sync <project> Sync GSC data (--days 30, --full, --wait)
1647
+ canonry google performance <project> Show GSC search performance data
1648
+ canonry google inspect <project> <url> Inspect a URL via GSC
1649
+ canonry google inspections <project> Show URL inspection history (--url <url>)
1650
+ canonry google deindexed <project> Show pages that lost indexing
1333
1651
  canonry settings Show active provider and quota settings
1334
1652
  canonry settings provider <name> Update a provider config
1653
+ canonry settings google Update Google OAuth credentials
1335
1654
  canonry telemetry status Show telemetry status
1336
1655
  canonry telemetry enable Enable anonymous telemetry
1337
1656
  canonry telemetry disable Disable anonymous telemetry
@@ -1345,6 +1664,8 @@ Options:
1345
1664
  --local-url <url> Local LLM base URL (or LOCAL_BASE_URL env var)
1346
1665
  --local-model <name> Local LLM model name (default: llama3)
1347
1666
  --local-key <key> Local LLM API key (or LOCAL_API_KEY env var)
1667
+ --google-client-id <id> Google OAuth client ID (or GOOGLE_CLIENT_ID env var)
1668
+ --google-client-secret <key> Google OAuth client secret (or GOOGLE_CLIENT_SECRET env var)
1348
1669
  --port <port> Server port (default: 4100)
1349
1670
  --host <host> Server bind address (default: 127.0.0.1)
1350
1671
  --domain <domain> Canonical domain for project create/update
@@ -1367,6 +1688,8 @@ Options:
1367
1688
  --api-key <key> Provider API key (settings provider)
1368
1689
  --base-url <url> Provider base URL (settings provider)
1369
1690
  --model <name> Provider model name (settings provider)
1691
+ --client-id <id> Google OAuth client ID (settings google)
1692
+ --client-secret <key> Google OAuth client secret (settings google)
1370
1693
  --max-concurrent <n> Max concurrent requests per provider
1371
1694
  --max-per-minute <n> Max requests per minute per provider
1372
1695
  --max-per-day <n> Max requests per day per provider
@@ -1394,7 +1717,7 @@ async function main() {
1394
1717
  showFirstRunNotice();
1395
1718
  getOrCreateAnonymousId();
1396
1719
  }
1397
- const SUBCOMMAND_COMMANDS = /* @__PURE__ */ new Set(["project", "keyword", "competitor", "schedule", "notify", "settings", "telemetry"]);
1720
+ const SUBCOMMAND_COMMANDS = /* @__PURE__ */ new Set(["project", "keyword", "competitor", "schedule", "notify", "settings", "telemetry", "google"]);
1398
1721
  const resolvedCommand = SUBCOMMAND_COMMANDS.has(command) && args[1] && !args[1].startsWith("-") ? `${command}.${args[1]}` : command;
1399
1722
  if (command !== "telemetry") {
1400
1723
  trackEvent("cli.command", { command: resolvedCommand });
@@ -1411,7 +1734,9 @@ async function main() {
1411
1734
  "claude-key": { type: "string" },
1412
1735
  "local-url": { type: "string" },
1413
1736
  "local-model": { type: "string" },
1414
- "local-key": { type: "string" }
1737
+ "local-key": { type: "string" },
1738
+ "google-client-id": { type: "string" },
1739
+ "google-client-secret": { type: "string" }
1415
1740
  },
1416
1741
  allowPositionals: false
1417
1742
  });
@@ -1422,7 +1747,9 @@ async function main() {
1422
1747
  claudeKey: initValues["claude-key"],
1423
1748
  localUrl: initValues["local-url"],
1424
1749
  localModel: initValues["local-model"],
1425
- localKey: initValues["local-key"]
1750
+ localKey: initValues["local-key"],
1751
+ googleClientId: initValues["google-client-id"],
1752
+ googleClientSecret: initValues["google-client-secret"]
1426
1753
  });
1427
1754
  break;
1428
1755
  }
@@ -1913,6 +2240,23 @@ async function main() {
1913
2240
  model: values.model,
1914
2241
  quota: Object.keys(quota).length > 0 ? quota : void 0
1915
2242
  });
2243
+ } else if (subcommand === "google") {
2244
+ const { values } = parseArgs({
2245
+ args: args.slice(2),
2246
+ options: {
2247
+ "client-id": { type: "string" },
2248
+ "client-secret": { type: "string" }
2249
+ },
2250
+ allowPositionals: false
2251
+ });
2252
+ if (!values["client-id"] || !values["client-secret"]) {
2253
+ console.error("Error: --client-id and --client-secret are both required");
2254
+ process.exit(1);
2255
+ }
2256
+ setGoogleAuth({
2257
+ clientId: values["client-id"],
2258
+ clientSecret: values["client-secret"]
2259
+ });
1916
2260
  } else {
1917
2261
  await showSettings(format);
1918
2262
  }
@@ -1922,6 +2266,165 @@ async function main() {
1922
2266
  telemetryCommand(args[1]);
1923
2267
  break;
1924
2268
  }
2269
+ case "google": {
2270
+ const subcommand = args[1];
2271
+ switch (subcommand) {
2272
+ case "connect": {
2273
+ const project = args[2];
2274
+ if (!project) {
2275
+ console.error("Error: project name is required");
2276
+ process.exit(1);
2277
+ }
2278
+ const { values: connectValues } = parseArgs({
2279
+ args: args.slice(3),
2280
+ options: {
2281
+ type: { type: "string", default: "gsc" }
2282
+ },
2283
+ allowPositionals: false
2284
+ });
2285
+ await googleConnect(project, { type: connectValues.type ?? "gsc" });
2286
+ break;
2287
+ }
2288
+ case "disconnect": {
2289
+ const project = args[2];
2290
+ if (!project) {
2291
+ console.error("Error: project name is required");
2292
+ process.exit(1);
2293
+ }
2294
+ const { values: disconnectValues } = parseArgs({
2295
+ args: args.slice(3),
2296
+ options: {
2297
+ type: { type: "string", default: "gsc" }
2298
+ },
2299
+ allowPositionals: false
2300
+ });
2301
+ await googleDisconnect(project, { type: disconnectValues.type ?? "gsc" });
2302
+ break;
2303
+ }
2304
+ case "status": {
2305
+ const project = args[2];
2306
+ if (!project) {
2307
+ console.error("Error: project name is required");
2308
+ process.exit(1);
2309
+ }
2310
+ await googleStatus(project, format);
2311
+ break;
2312
+ }
2313
+ case "properties": {
2314
+ const project = args[2];
2315
+ if (!project) {
2316
+ console.error("Error: project name is required");
2317
+ process.exit(1);
2318
+ }
2319
+ await googleProperties(project, format);
2320
+ break;
2321
+ }
2322
+ case "set-property": {
2323
+ const project = args[2];
2324
+ const propertyUrl = args[3];
2325
+ if (!project || !propertyUrl) {
2326
+ console.error("Error: project name and property URL are required");
2327
+ process.exit(1);
2328
+ }
2329
+ await googleSetProperty(project, propertyUrl);
2330
+ break;
2331
+ }
2332
+ case "sync": {
2333
+ const project = args[2];
2334
+ if (!project) {
2335
+ console.error("Error: project name is required");
2336
+ process.exit(1);
2337
+ }
2338
+ const { values: syncValues } = parseArgs({
2339
+ args: args.slice(3),
2340
+ options: {
2341
+ type: { type: "string", default: "gsc" },
2342
+ days: { type: "string" },
2343
+ full: { type: "boolean", default: false },
2344
+ wait: { type: "boolean", default: false },
2345
+ format: { type: "string" }
2346
+ },
2347
+ allowPositionals: false
2348
+ });
2349
+ await googleSync(project, {
2350
+ type: syncValues.type,
2351
+ days: syncValues.days ? parseInt(syncValues.days, 10) : void 0,
2352
+ full: syncValues.full,
2353
+ wait: syncValues.wait,
2354
+ format: syncValues.format === "json" ? "json" : format
2355
+ });
2356
+ break;
2357
+ }
2358
+ case "performance": {
2359
+ const project = args[2];
2360
+ if (!project) {
2361
+ console.error("Error: project name is required");
2362
+ process.exit(1);
2363
+ }
2364
+ const { values: perfValues } = parseArgs({
2365
+ args: args.slice(3),
2366
+ options: {
2367
+ days: { type: "string" },
2368
+ keyword: { type: "string" },
2369
+ page: { type: "string" },
2370
+ format: { type: "string" }
2371
+ },
2372
+ allowPositionals: false
2373
+ });
2374
+ await googlePerformance(project, {
2375
+ days: perfValues.days ? parseInt(perfValues.days, 10) : void 0,
2376
+ keyword: perfValues.keyword,
2377
+ page: perfValues.page,
2378
+ format: perfValues.format === "json" ? "json" : format
2379
+ });
2380
+ break;
2381
+ }
2382
+ case "inspect": {
2383
+ const project = args[2];
2384
+ const url = args[3];
2385
+ if (!project || !url) {
2386
+ console.error("Error: project name and URL are required");
2387
+ process.exit(1);
2388
+ }
2389
+ await googleInspect(project, url, format);
2390
+ break;
2391
+ }
2392
+ case "inspections": {
2393
+ const project = args[2];
2394
+ if (!project) {
2395
+ console.error("Error: project name is required");
2396
+ process.exit(1);
2397
+ }
2398
+ const { values: inspValues } = parseArgs({
2399
+ args: args.slice(3),
2400
+ options: {
2401
+ url: { type: "string" },
2402
+ format: { type: "string" }
2403
+ },
2404
+ allowPositionals: false
2405
+ });
2406
+ await googleInspections(project, {
2407
+ url: inspValues.url,
2408
+ format: inspValues.format === "json" ? "json" : format
2409
+ });
2410
+ break;
2411
+ }
2412
+ case "deindexed": {
2413
+ const project = args[2];
2414
+ if (!project) {
2415
+ console.error("Error: project name is required");
2416
+ process.exit(1);
2417
+ }
2418
+ await googleDeindexed(project, format);
2419
+ break;
2420
+ }
2421
+ default:
2422
+ console.error(`Unknown google subcommand: ${subcommand ?? "(none)"}`);
2423
+ console.log("Available: connect, disconnect, status, properties, set-property, sync, performance, inspect, inspections, deindexed");
2424
+ process.exit(1);
2425
+ }
2426
+ break;
2427
+ }
1925
2428
  default:
1926
2429
  console.error(`Unknown command: ${command}`);
1927
2430
  console.log('Run "canonry --help" for usage.');