@ainyc/canonry 1.10.1 → 1.12.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-2DC7RBXJ.js";
22
+ } from "./chunk-O4HLQBL7.js";
22
23
 
23
24
  // src/cli.ts
24
25
  import { parseArgs } from "util";
@@ -159,6 +160,14 @@ async function bootstrapCommand(_opts) {
159
160
  if (providers?.openai) mergedProviders.openai = providers.openai;
160
161
  if (providers?.claude) mergedProviders.claude = providers.claude;
161
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;
162
171
  const keyHash = crypto.createHash("sha256").update(rawApiKey).digest("hex");
163
172
  const keyPrefix = rawApiKey.slice(0, 9);
164
173
  const db = createClient(databasePath);
@@ -176,7 +185,8 @@ async function bootstrapCommand(_opts) {
176
185
  apiUrl: env.apiUrl || existingConfig?.apiUrl || `http://localhost:${process.env.CANONRY_PORT || "4100"}`,
177
186
  database: databasePath,
178
187
  apiKey: rawApiKey,
179
- providers: mergedProviders
188
+ providers: mergedProviders,
189
+ google: mergedGoogle
180
190
  });
181
191
  console.log(`Bootstrap complete. Config saved to ${getConfigPath()}`);
182
192
  console.log(`SQLite database path: ${databasePath}`);
@@ -218,23 +228,40 @@ async function initCommand(opts) {
218
228
  if (!fs.existsSync(configDir)) {
219
229
  fs.mkdirSync(configDir, { recursive: true });
220
230
  }
221
- const envProviders = getBootstrapEnv(process.env, {
231
+ const bootstrapEnv = getBootstrapEnv(process.env, {
222
232
  GEMINI_API_KEY: opts?.geminiKey,
223
233
  OPENAI_API_KEY: opts?.openaiKey,
224
234
  ANTHROPIC_API_KEY: opts?.claudeKey,
225
235
  LOCAL_BASE_URL: opts?.localUrl,
226
236
  LOCAL_MODEL: opts?.localModel,
227
- LOCAL_API_KEY: opts?.localKey
228
- }).providers;
229
- 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);
230
248
  const providers = {};
249
+ let google;
231
250
  if (nonInteractive) {
232
251
  Object.assign(providers, envProviders);
252
+ if (envGoogleConfigured) {
253
+ google = {
254
+ clientId: bootstrapEnv.googleClientId,
255
+ clientSecret: bootstrapEnv.googleClientSecret,
256
+ connections: []
257
+ };
258
+ }
233
259
  } else {
234
260
  console.log("Configure AI providers (at least one required):\n");
235
- console.log("Tip: For non-interactive setup, pass --gemini-key, --openai-key,");
236
- console.log("--claude-key flags or set GEMINI_API_KEY, OPENAI_API_KEY,");
237
- 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');
238
265
  const geminiApiKey = await prompt("Gemini API key (press Enter to skip): ");
239
266
  if (geminiApiKey) {
240
267
  const geminiModel = await prompt(" Gemini model [gemini-2.5-flash]: ") || "gemini-2.5-flash";
@@ -257,6 +284,20 @@ async function initCommand(opts) {
257
284
  const localApiKey = await prompt(" API key (press Enter if not needed): ") || void 0;
258
285
  providers.local = { baseUrl: localBaseUrl, apiKey: localApiKey, model: localModel, quota: DEFAULT_QUOTA };
259
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
+ }
260
301
  }
261
302
  const hasProvider = providers.gemini || providers.openai || providers.claude || providers.local;
262
303
  if (!hasProvider) {
@@ -281,7 +322,8 @@ async function initCommand(opts) {
281
322
  apiUrl: "http://localhost:4100",
282
323
  database: databasePath,
283
324
  apiKey: rawApiKey,
284
- providers
325
+ providers,
326
+ google
285
327
  });
286
328
  const providerNames = Object.keys(providers);
287
329
  console.log(`
@@ -1109,9 +1151,15 @@ async function setProvider(name, opts) {
1109
1151
  }
1110
1152
  async function showSettings(format) {
1111
1153
  const client = getClient8();
1154
+ const config = loadConfig();
1112
1155
  const settings = await client.getSettings();
1113
1156
  if (format === "json") {
1114
- 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));
1115
1163
  return;
1116
1164
  }
1117
1165
  console.log("Provider settings:\n");
@@ -1125,6 +1173,18 @@ async function showSettings(format) {
1125
1173
  }
1126
1174
  }
1127
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.");
1128
1188
  }
1129
1189
 
1130
1190
  // src/commands/schedule.ts
@@ -1331,12 +1391,19 @@ function getClient11() {
1331
1391
  }
1332
1392
  async function googleConnect(project, opts) {
1333
1393
  const client = getClient11();
1334
- const { authUrl } = await client.googleConnect(project, { type: opts.type });
1394
+ const { authUrl, redirectUri } = await client.googleConnect(project, {
1395
+ type: opts.type,
1396
+ publicUrl: opts.publicUrl
1397
+ });
1335
1398
  console.log(`
1336
1399
  Open this URL in your browser to authorize Google ${opts.type.toUpperCase()} access:
1337
1400
  `);
1338
1401
  console.log(` ${authUrl}
1339
1402
  `);
1403
+ if (redirectUri) {
1404
+ console.log(`Redirect URI: ${redirectUri}`);
1405
+ console.log("(Ensure this URI is listed in your Google Cloud Console OAuth client's authorized redirect URIs)\n");
1406
+ }
1340
1407
  try {
1341
1408
  const { exec } = await import("child_process");
1342
1409
  const platform = process.platform;
@@ -1578,7 +1645,7 @@ Usage:
1578
1645
  canonry notify remove <project> <id> Remove notification
1579
1646
  canonry notify test <project> <id> Send test webhook
1580
1647
  canonry notify events List available notification event types
1581
- canonry google connect <project> Connect Google Search Console (--type gsc|ga4)
1648
+ canonry google connect <project> Connect Google Search Console (--type gsc|ga4, --public-url <url>)
1582
1649
  canonry google disconnect <project> Disconnect Google integration
1583
1650
  canonry google status <project> Show Google connection status
1584
1651
  canonry google properties <project> List available GSC properties
@@ -1590,6 +1657,7 @@ Usage:
1590
1657
  canonry google deindexed <project> Show pages that lost indexing
1591
1658
  canonry settings Show active provider and quota settings
1592
1659
  canonry settings provider <name> Update a provider config
1660
+ canonry settings google Update Google OAuth credentials
1593
1661
  canonry telemetry status Show telemetry status
1594
1662
  canonry telemetry enable Enable anonymous telemetry
1595
1663
  canonry telemetry disable Disable anonymous telemetry
@@ -1603,6 +1671,8 @@ Options:
1603
1671
  --local-url <url> Local LLM base URL (or LOCAL_BASE_URL env var)
1604
1672
  --local-model <name> Local LLM model name (default: llama3)
1605
1673
  --local-key <key> Local LLM API key (or LOCAL_API_KEY env var)
1674
+ --google-client-id <id> Google OAuth client ID (or GOOGLE_CLIENT_ID env var)
1675
+ --google-client-secret <key> Google OAuth client secret (or GOOGLE_CLIENT_SECRET env var)
1606
1676
  --port <port> Server port (default: 4100)
1607
1677
  --host <host> Server bind address (default: 127.0.0.1)
1608
1678
  --domain <domain> Canonical domain for project create/update
@@ -1625,6 +1695,8 @@ Options:
1625
1695
  --api-key <key> Provider API key (settings provider)
1626
1696
  --base-url <url> Provider base URL (settings provider)
1627
1697
  --model <name> Provider model name (settings provider)
1698
+ --client-id <id> Google OAuth client ID (settings google)
1699
+ --client-secret <key> Google OAuth client secret (settings google)
1628
1700
  --max-concurrent <n> Max concurrent requests per provider
1629
1701
  --max-per-minute <n> Max requests per minute per provider
1630
1702
  --max-per-day <n> Max requests per day per provider
@@ -1669,7 +1741,9 @@ async function main() {
1669
1741
  "claude-key": { type: "string" },
1670
1742
  "local-url": { type: "string" },
1671
1743
  "local-model": { type: "string" },
1672
- "local-key": { type: "string" }
1744
+ "local-key": { type: "string" },
1745
+ "google-client-id": { type: "string" },
1746
+ "google-client-secret": { type: "string" }
1673
1747
  },
1674
1748
  allowPositionals: false
1675
1749
  });
@@ -1680,7 +1754,9 @@ async function main() {
1680
1754
  claudeKey: initValues["claude-key"],
1681
1755
  localUrl: initValues["local-url"],
1682
1756
  localModel: initValues["local-model"],
1683
- localKey: initValues["local-key"]
1757
+ localKey: initValues["local-key"],
1758
+ googleClientId: initValues["google-client-id"],
1759
+ googleClientSecret: initValues["google-client-secret"]
1684
1760
  });
1685
1761
  break;
1686
1762
  }
@@ -2171,6 +2247,23 @@ async function main() {
2171
2247
  model: values.model,
2172
2248
  quota: Object.keys(quota).length > 0 ? quota : void 0
2173
2249
  });
2250
+ } else if (subcommand === "google") {
2251
+ const { values } = parseArgs({
2252
+ args: args.slice(2),
2253
+ options: {
2254
+ "client-id": { type: "string" },
2255
+ "client-secret": { type: "string" }
2256
+ },
2257
+ allowPositionals: false
2258
+ });
2259
+ if (!values["client-id"] || !values["client-secret"]) {
2260
+ console.error("Error: --client-id and --client-secret are both required");
2261
+ process.exit(1);
2262
+ }
2263
+ setGoogleAuth({
2264
+ clientId: values["client-id"],
2265
+ clientSecret: values["client-secret"]
2266
+ });
2174
2267
  } else {
2175
2268
  await showSettings(format);
2176
2269
  }
@@ -2192,11 +2285,15 @@ async function main() {
2192
2285
  const { values: connectValues } = parseArgs({
2193
2286
  args: args.slice(3),
2194
2287
  options: {
2195
- type: { type: "string", default: "gsc" }
2288
+ type: { type: "string", default: "gsc" },
2289
+ "public-url": { type: "string" }
2196
2290
  },
2197
2291
  allowPositionals: false
2198
2292
  });
2199
- await googleConnect(project, { type: connectValues.type ?? "gsc" });
2293
+ await googleConnect(project, {
2294
+ type: connectValues.type ?? "gsc",
2295
+ publicUrl: connectValues["public-url"]
2296
+ });
2200
2297
  break;
2201
2298
  }
2202
2299
  case "disconnect": {
package/dist/index.d.ts CHANGED
@@ -2,14 +2,32 @@ import { FastifyInstance } from 'fastify';
2
2
  import { DatabaseClient } from '@ainyc/canonry-db';
3
3
  import { ProviderQuotaPolicy } from '@ainyc/canonry-contracts';
4
4
 
5
+ type GoogleConnectionType = 'gsc' | 'ga4';
5
6
  interface ProviderConfigEntry {
6
7
  apiKey?: string;
7
8
  baseUrl?: string;
8
9
  model?: string;
9
10
  quota?: ProviderQuotaPolicy;
10
11
  }
12
+ interface GoogleConnectionConfigEntry {
13
+ domain: string;
14
+ connectionType: GoogleConnectionType;
15
+ propertyId?: string | null;
16
+ accessToken?: string;
17
+ refreshToken?: string | null;
18
+ tokenExpiresAt?: string | null;
19
+ scopes?: string[];
20
+ createdAt: string;
21
+ updatedAt: string;
22
+ }
23
+ interface GoogleConfigEntry {
24
+ clientId?: string;
25
+ clientSecret?: string;
26
+ connections?: GoogleConnectionConfigEntry[];
27
+ }
11
28
  interface CanonryConfig {
12
29
  apiUrl: string;
30
+ publicUrl?: string;
13
31
  database: string;
14
32
  apiKey: string;
15
33
  port?: number;
@@ -22,6 +40,7 @@ interface CanonryConfig {
22
40
  claude?: ProviderConfigEntry;
23
41
  local?: ProviderConfigEntry;
24
42
  };
43
+ google?: GoogleConfigEntry;
25
44
  telemetry?: boolean;
26
45
  anonymousId?: string;
27
46
  }
@@ -31,6 +50,7 @@ declare function createServer(opts: {
31
50
  config: CanonryConfig;
32
51
  db: DatabaseClient;
33
52
  open?: boolean;
53
+ logger?: boolean;
34
54
  }): Promise<FastifyInstance>;
35
55
 
36
56
  export { type CanonryConfig, createServer, loadConfig };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createServer,
3
3
  loadConfig
4
- } from "./chunk-2DC7RBXJ.js";
4
+ } from "./chunk-O4HLQBL7.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.1",
3
+ "version": "1.12.0",
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,15 +51,15 @@
51
51
  "@types/node-cron": "^3.0.11",
52
52
  "tsup": "^8.5.1",
53
53
  "tsx": "^4.19.0",
54
- "@ainyc/canonry-config": "0.0.0",
55
54
  "@ainyc/canonry-api-routes": "0.0.0",
55
+ "@ainyc/canonry-config": "0.0.0",
56
56
  "@ainyc/canonry-contracts": "0.0.0",
57
57
  "@ainyc/canonry-db": "0.0.0",
58
- "@ainyc/canonry-provider-local": "0.0.0",
59
- "@ainyc/canonry-provider-claude": "0.0.0",
60
58
  "@ainyc/canonry-provider-gemini": "0.0.0",
61
- "@ainyc/canonry-integration-google": "0.0.0",
62
- "@ainyc/canonry-provider-openai": "0.0.0"
59
+ "@ainyc/canonry-provider-claude": "0.0.0",
60
+ "@ainyc/canonry-provider-local": "0.0.0",
61
+ "@ainyc/canonry-provider-openai": "0.0.0",
62
+ "@ainyc/canonry-integration-google": "0.0.0"
63
63
  },
64
64
  "scripts": {
65
65
  "build": "tsup && tsx build-web.ts",