@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/README.md +26 -2
- package/assets/assets/index-CVK9fWdD.css +1 -0
- package/assets/assets/index-DnEgRRTR.js +243 -0
- package/assets/index.html +2 -2
- package/dist/{chunk-P4D4VWJQ.js → chunk-MVIL2UGM.js} +1104 -122
- package/dist/cli.js +518 -15
- package/dist/index.d.ts +19 -0
- package/dist/index.js +1 -1
- package/package.json +5 -4
- package/assets/assets/index-C710XAPy.js +0 -243
- package/assets/assets/index-Ca3ICz3n.css +0 -1
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-
|
|
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
|
|
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
|
-
|
|
225
|
-
|
|
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
|
|
232
|
-
console.log("
|
|
233
|
-
console.log(
|
|
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(
|
|
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.');
|