@canonry/canonry 7.19.0 → 7.20.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.
Files changed (32) hide show
  1. package/assets/agent-workspace/skills/aero/references/agent-operations.md +14 -0
  2. package/assets/agent-workspace/skills/canonry/SKILL.md +14 -0
  3. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +33 -1
  4. package/assets/assets/{AuditHistoryPanel-DEEWm-4t.js → AuditHistoryPanel-DVtz1ZuT.js} +1 -1
  5. package/assets/assets/{BacklinksPage-F5QBgXbF.js → BacklinksPage-Tvt0-ppX.js} +1 -1
  6. package/assets/assets/{HistoryPage-CNp42sUO.js → HistoryPage-DqXwPH3k.js} +1 -1
  7. package/assets/assets/{MeasurementPropertyPage-XpI9hVMb.js → MeasurementPropertyPage-CsbfFEjB.js} +1 -1
  8. package/assets/assets/{ProjectPage-B_V4QpyI.js → ProjectPage-Cyvt96AV.js} +2 -2
  9. package/assets/assets/{RunsPage-PTHloZl3.js → RunsPage-Io7ZFw4-.js} +1 -1
  10. package/assets/assets/{SettingsPage-C-aDo2zf.js → SettingsPage-BPYxgchP.js} +1 -1
  11. package/assets/assets/{SiteHealthSection-DvoT8IuX.js → SiteHealthSection-CwffU3tv.js} +3 -3
  12. package/assets/assets/{TrafficPage-DahJoMKF.js → TrafficPage-DIm60m1J.js} +1 -1
  13. package/assets/assets/{TrafficSourceDetailPage-BGlHXOlk.js → TrafficSourceDetailPage-DWT1Ip5P.js} +1 -1
  14. package/assets/assets/{extract-error-message-CP_fyJLM.js → extract-error-message-BVYchXhT.js} +1 -1
  15. package/assets/assets/{index-C5V8xlr0.js → index-DrQLR1jE.js} +20 -20
  16. package/assets/assets/{react-sigma_core.esm.min-DUS8srfg.js → react-sigma_core.esm.min-f31sqfAC.js} +1 -1
  17. package/assets/assets/{v2-overview-adapter-ycJ1guZb.js → v2-overview-adapter-By0930UY.js} +1 -1
  18. package/assets/index.html +1 -1
  19. package/dist/{chunk-A75L7CUV.js → chunk-222NAFTY.js} +2 -2
  20. package/dist/{chunk-UUI4HOEL.js → chunk-46JZR2IV.js} +46 -8
  21. package/dist/{chunk-VSTKTQPS.js → chunk-5IXQAOVK.js} +16 -1
  22. package/dist/{chunk-PBTTPZZE.js → chunk-6H2BC5EY.js} +3 -3
  23. package/dist/{chunk-OMP5OLBK.js → chunk-CIPWGVX6.js} +368 -211
  24. package/dist/{chunk-CYHUZ2JQ.js → chunk-MRKV43TV.js} +19 -0
  25. package/dist/{chunk-35IIFDIT.js → chunk-NDQOCFJQ.js} +61 -43
  26. package/dist/cli.js +213 -39
  27. package/dist/{demo-server-K5G54GOE.js → demo-server-32PU7BM6.js} +4 -4
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.js +6 -6
  30. package/dist/{intelligence-service-LV2TQMMR.js → intelligence-service-256DQBVR.js} +2 -2
  31. package/dist/mcp.js +4 -4
  32. package/package.json +12 -12
package/dist/cli.js CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  listAgentProviders,
22
22
  notePrintedUpdateAvailable,
23
23
  readCachedUpdateAvailable,
24
+ registeredProviderNames,
24
25
  reparseStoredResult,
25
26
  reparseStoredResult2,
26
27
  reparseStoredResult3,
@@ -34,11 +35,11 @@ import {
34
35
  trackCliCommandFinished,
35
36
  trackEvent,
36
37
  waitForServerRuntimeStartup
37
- } from "./chunk-OMP5OLBK.js";
38
+ } from "./chunk-CIPWGVX6.js";
38
39
  import {
39
40
  autoSyncSkills,
40
41
  formatAutoSyncNotice
41
- } from "./chunk-PBTTPZZE.js";
42
+ } from "./chunk-6H2BC5EY.js";
42
43
  import {
43
44
  configExists,
44
45
  createApiClient,
@@ -54,14 +55,15 @@ import {
54
55
  parseSkillsClient,
55
56
  saveConfig,
56
57
  saveConfigPatch,
57
- shouldPrintServeSkillsNudge
58
- } from "./chunk-35IIFDIT.js";
58
+ shouldPrintServeSkillsNudge,
59
+ withBootstrapProviderDefaults
60
+ } from "./chunk-NDQOCFJQ.js";
59
61
  import {
60
62
  DEFAULT_DEMO_TRUSTED_PROXIES,
61
63
  trustedProxiesProblem,
62
64
  trustedProxyProblem
63
65
  } from "./chunk-RB2F4D3Z.js";
64
- import "./chunk-A75L7CUV.js";
66
+ import "./chunk-222NAFTY.js";
65
67
  import {
66
68
  CliError,
67
69
  EXIT_SYSTEM_ERROR,
@@ -73,7 +75,7 @@ import {
73
75
  runMeasurementDraftAction,
74
76
  systemError,
75
77
  usageError
76
- } from "./chunk-VSTKTQPS.js";
78
+ } from "./chunk-5IXQAOVK.js";
77
79
  import {
78
80
  CLOUDFLARE_WORKER_BINDINGS,
79
81
  CLOUDFLARE_WORKER_GENERATED_MARKER,
@@ -105,7 +107,7 @@ import {
105
107
  rawEventSamples,
106
108
  readMarketCompetitorNames,
107
109
  runs
108
- } from "./chunk-UUI4HOEL.js";
110
+ } from "./chunk-46JZR2IV.js";
109
111
  import {
110
112
  AdsDeliverySnapshotStatuses,
111
113
  AdsHistoricalCampaignRollupStatuses,
@@ -126,6 +128,7 @@ import {
126
128
  OUTSTANDING_PROVIDER_BATCH_STATUSES,
127
129
  ProviderBatchStatuses,
128
130
  ProviderNames,
131
+ ProviderReloadErrorReasons,
129
132
  READ_ONLY_SCOPE,
130
133
  RatioUnits,
131
134
  ResearchRunStatuses,
@@ -238,7 +241,7 @@ import {
238
241
  visibilityCompareSelectionSchema,
239
242
  visibilityReportRequestSchema,
240
243
  winnabilityClassSchema
241
- } from "./chunk-CYHUZ2JQ.js";
244
+ } from "./chunk-MRKV43TV.js";
242
245
 
243
246
  // src/cli.ts
244
247
  import { pathToFileURL } from "url";
@@ -256,11 +259,7 @@ function buildSetupState() {
256
259
  const raw = loadConfigRaw();
257
260
  if (raw) {
258
261
  is_first_run = !raw.anonymousId;
259
- if (raw.providers) {
260
- provider_count = Object.values(raw.providers).filter(
261
- (p) => Boolean(p?.apiKey) || Boolean(p?.baseUrl)
262
- ).length;
263
- }
262
+ provider_count = registeredProviderNames(raw).length;
264
263
  if (typeof raw.database === "string" && raw.database.length > 0) {
265
264
  dbPath = raw.database;
266
265
  }
@@ -788,7 +787,7 @@ function parseDemoTrustedProxies(values) {
788
787
  async function demoCommand(options) {
789
788
  const { host, port } = parseDemoListenOptions(options);
790
789
  const trustProxy = parseDemoTrustedProxies(options.trustProxy);
791
- const { createDemoServer } = await import("./demo-server-K5G54GOE.js");
790
+ const { createDemoServer } = await import("./demo-server-32PU7BM6.js");
792
791
  const app = await createDemoServer({ trustProxy });
793
792
  let stopping = false;
794
793
  const stop = () => {
@@ -1202,7 +1201,7 @@ No DB writes performed. Re-run without --dry-run to apply.`);
1202
1201
  }
1203
1202
  }
1204
1203
  async function backfillInsightsCommand(project2, opts) {
1205
- const { IntelligenceService } = await import("./intelligence-service-LV2TQMMR.js");
1204
+ const { IntelligenceService } = await import("./intelligence-service-256DQBVR.js");
1206
1205
  const config = loadConfig();
1207
1206
  const db = createClient(config.database);
1208
1207
  migrate(db);
@@ -15617,6 +15616,17 @@ async function showSettings(format) {
15617
15616
  console.log("\nGoogle OAuth:\n");
15618
15617
  console.log(` ${settings.google.configured ? "configured" : "not configured"}`);
15619
15618
  }
15619
+ async function reloadProviders(format) {
15620
+ const result = await getClient25().reloadProviders();
15621
+ if (isMachineFormat(format)) {
15622
+ console.log(JSON.stringify(result, null, 2));
15623
+ return;
15624
+ }
15625
+ console.log("Saved provider configuration is now active on the server.");
15626
+ for (const provider of result.providers) {
15627
+ console.log(` ${provider.name}: ${provider.configured ? "configured" : "not configured"}`);
15628
+ }
15629
+ }
15620
15630
  async function setGoogleAuth(opts) {
15621
15631
  if (opts.target === "server") {
15622
15632
  const result = await getClient25().updateGoogleSettings({
@@ -15650,6 +15660,11 @@ async function setGoogleAuth(opts) {
15650
15660
 
15651
15661
  // src/cli-commands/settings.ts
15652
15662
  var SETTINGS_CLI_COMMANDS = [
15663
+ {
15664
+ path: ["settings", "reload-providers"],
15665
+ usage: "canonry settings reload-providers [--format json]",
15666
+ run: async (input) => reloadProviders(input.format)
15667
+ },
15653
15668
  {
15654
15669
  path: ["settings", "provider"],
15655
15670
  usage: "canonry settings provider <name> [--api-key <key>] [--base-url <url>] [--model <model>] [--max-concurrent <n>] [--max-per-minute <n>] [--max-per-day <n>] [--format json]",
@@ -15777,7 +15792,7 @@ Usage: canonry settings provider ${name} --api-key <key> [--model <model>] [--ma
15777
15792
  },
15778
15793
  {
15779
15794
  path: ["settings"],
15780
- usage: "canonry settings [provider|google] [args]",
15795
+ usage: "canonry settings [provider|reload-providers|google] [args]",
15781
15796
  run: async (input) => {
15782
15797
  const subcommand = input.positionals[0];
15783
15798
  if (!subcommand) {
@@ -15786,8 +15801,8 @@ Usage: canonry settings provider ${name} --api-key <key> [--model <model>] [--ma
15786
15801
  }
15787
15802
  unknownSubcommand(subcommand, {
15788
15803
  command: "settings",
15789
- usage: "canonry settings [provider|google] [args]",
15790
- available: ["provider", "google"]
15804
+ usage: "canonry settings [provider|reload-providers|google] [args]",
15805
+ available: ["provider", "reload-providers", "google"]
15791
15806
  });
15792
15807
  }
15793
15808
  }
@@ -17301,14 +17316,72 @@ function persistedValue(value) {
17301
17316
  }
17302
17317
  return value;
17303
17318
  }
17319
+ var PROVIDER_FREE_NEXT_STEPS = [
17320
+ "AI Visibility needs an answer-engine provider. Page Health does not.",
17321
+ "With the server running, the operator runs `canonry settings provider gemini --api-key <key>` (free key at https://aistudio.google.com/apikey). It takes effect immediately.",
17322
+ "Or the operator sets GEMINI_API_KEY and reruns `canonry bootstrap`. It reloads the matching running local server and confirms active providers; an offline server uses the saved settings at startup.",
17323
+ "A provider key is the operator's credential: never ask for it in chat."
17324
+ ];
17325
+ var PROVIDER_CREDENTIAL_FIELDS = {
17326
+ [ProviderNames.gemini]: ["apiKey", "vertexProject", "vertexRegion", "vertexCredentials"],
17327
+ // A local key belongs to the server at its base URL.
17328
+ [ProviderNames.local]: ["baseUrl", "apiKey"]
17329
+ };
17330
+ var API_KEY_CREDENTIAL_FIELDS = ["apiKey"];
17331
+ function mergeEnvProvider(name, saved, fromEnv) {
17332
+ const credentialFields = PROVIDER_CREDENTIAL_FIELDS[name] ?? API_KEY_CREDENTIAL_FIELDS;
17333
+ const envFields = Object.entries(fromEnv);
17334
+ const envValues = new Map(envFields);
17335
+ const merged = {};
17336
+ for (const [field, value] of Object.entries(saved ?? {})) {
17337
+ if (envValues.get(field) !== void 0) merged[field] = envValues.get(field);
17338
+ else if (!credentialFields.includes(field)) merged[field] = value;
17339
+ }
17340
+ for (const [field, value] of envFields) {
17341
+ if (!(field in merged)) merged[field] = value;
17342
+ }
17343
+ return withBootstrapProviderDefaults(name, merged);
17344
+ }
17345
+ var ServerReloadStatuses = {
17346
+ reloaded: "reloaded",
17347
+ unchanged: "unchanged",
17348
+ unavailable: "unavailable",
17349
+ "not-local": "not-local",
17350
+ "not-matching": "not-matching",
17351
+ failed: "failed"
17352
+ };
17353
+ var NotMatchingReasons = {
17354
+ "other-install": "other-install",
17355
+ "reload-unsupported": "reload-unsupported"
17356
+ };
17357
+ function notMatchingReason(err) {
17358
+ const status = err.details?.httpStatus;
17359
+ if (status === 401 || status === 403) return NotMatchingReasons["other-install"];
17360
+ if (err.details?.reason === ProviderReloadErrorReasons["install-identity-mismatch"]) return NotMatchingReasons["other-install"];
17361
+ if (isEndpointMissing(err) || status === 501) return NotMatchingReasons["reload-unsupported"];
17362
+ return void 0;
17363
+ }
17364
+ function notMatchingLines(reason, serverUrl) {
17365
+ switch (reason) {
17366
+ case NotMatchingReasons["other-install"]:
17367
+ return [
17368
+ `The server at ${serverUrl} is not this install's server, so it was not reloaded.`,
17369
+ "To apply the saved providers, run `canonry settings reload-providers` against this install's server, or restart that server."
17370
+ ];
17371
+ case NotMatchingReasons["reload-unsupported"]:
17372
+ return [
17373
+ `The server at ${serverUrl} cannot reload providers: it is too old or does not support reload.`,
17374
+ "Restart this install's server to apply the saved providers."
17375
+ ];
17376
+ }
17377
+ }
17304
17378
  async function bootstrapCommand(opts) {
17305
17379
  const format = opts?.format ?? "text";
17306
- const env = getBootstrapEnv(process.env);
17307
- const providers = env.providers;
17308
17380
  const configDir = getConfigDir();
17309
17381
  const existing = configExists();
17310
17382
  const existingConfig = existing ? loadConfig() : void 0;
17311
17383
  const existingRaw = existing ? loadConfigRaw() : null;
17384
+ const env = getBootstrapEnv(process.env);
17312
17385
  const databasePath = env.databasePath || existingRaw?.database || path8.join(configDir, "data.db");
17313
17386
  let rawApiKey;
17314
17387
  let generatedApiKey;
@@ -17321,12 +17394,9 @@ async function bootstrapCommand(opts) {
17321
17394
  rawApiKey = generatedApiKey;
17322
17395
  }
17323
17396
  const mergedProviders = { ...existingConfig?.providers };
17324
- if (providers.gemini) mergedProviders.gemini = providers.gemini;
17325
- if (providers.openai) mergedProviders.openai = providers.openai;
17326
- if (providers.claude) mergedProviders.claude = providers.claude;
17327
- if (providers.perplexity) mergedProviders.perplexity = providers.perplexity;
17328
- if (providers.muse) mergedProviders.muse = providers.muse;
17329
- if (providers.local) mergedProviders.local = providers.local;
17397
+ for (const [name, fromEnv] of Object.entries(env.providers)) {
17398
+ if (fromEnv) mergedProviders[name] = mergeEnvProvider(name, mergedProviders[name], fromEnv);
17399
+ }
17330
17400
  if (env.googleClientId && !env.googleClientSecret || !env.googleClientId && env.googleClientSecret) {
17331
17401
  console.warn("Warning: GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET must both be set to configure Google OAuth. Skipping Google auth config.");
17332
17402
  }
@@ -17400,10 +17470,54 @@ async function bootstrapCommand(opts) {
17400
17470
  providers: mergedProviders,
17401
17471
  google: mergedGoogle
17402
17472
  };
17403
- const configChanged = !existingRaw || existingRaw.apiUrl !== apiUrl || existingRaw.database !== databasePath || existingRaw.apiKey !== rawApiKey || !isDeepStrictEqual(persistedValue(existingRaw.providers ?? {}), persistedValue(mergedProviders)) || !isDeepStrictEqual(persistedValue(existingRaw.google), persistedValue(mergedGoogle));
17473
+ const providersChanged = !isDeepStrictEqual(persistedValue(existingRaw?.providers ?? {}), persistedValue(mergedProviders));
17474
+ const configChanged = !existingRaw || existingRaw.apiUrl !== apiUrl || existingRaw.database !== databasePath || existingRaw.apiKey !== rawApiKey || providersChanged || !isDeepStrictEqual(persistedValue(existingRaw.google), persistedValue(mergedGoogle));
17404
17475
  if (configChanged) saveConfig(nextConfig);
17405
17476
  const status = !existing ? "created" : configChanged || keyChanged ? "updated" : "unchanged";
17406
- if (format === "json") {
17477
+ const providerFree = registeredProviderNames(nextConfig).length === 0;
17478
+ const serverUrl = loadConfig().apiUrl;
17479
+ let serverReload;
17480
+ let reloadFailure;
17481
+ if (!isLoopbackBindHost(new URL(serverUrl).hostname)) {
17482
+ serverReload = { status: ServerReloadStatuses["not-local"] };
17483
+ } else if (!providersChanged) {
17484
+ serverReload = { status: ServerReloadStatuses.unchanged };
17485
+ } else {
17486
+ const signal = AbortSignal.timeout(4e3);
17487
+ try {
17488
+ const receipt = await createApiClient().reloadProviders({ configPath: getConfigPath(), databasePath }, signal);
17489
+ serverReload = {
17490
+ status: ServerReloadStatuses.reloaded,
17491
+ providers: receipt.providers.filter((provider) => provider.configured).map((provider) => provider.name)
17492
+ };
17493
+ } catch (err) {
17494
+ const reason = err instanceof CliError ? notMatchingReason(err) : void 0;
17495
+ if (err instanceof CliError && err.code === "CONNECTION_ERROR" && err.details?.connectionUnavailable === true && !signal.aborted) {
17496
+ serverReload = { status: ServerReloadStatuses.unavailable };
17497
+ } else if (err instanceof CliError && reason) {
17498
+ serverReload = { status: ServerReloadStatuses["not-matching"], reason, code: err.code };
17499
+ } else {
17500
+ reloadFailure = err instanceof CliError ? err : systemError("Provider configuration was saved, but server reload failed.");
17501
+ serverReload = { status: ServerReloadStatuses.failed, code: reloadFailure.code };
17502
+ }
17503
+ }
17504
+ }
17505
+ const reloadSteps = (() => {
17506
+ switch (serverReload.status) {
17507
+ case ServerReloadStatuses.unavailable:
17508
+ return ["Start `canonry serve` to use the saved configuration."];
17509
+ case ServerReloadStatuses.failed:
17510
+ return ["Resolve the server reload error, then run `canonry settings reload-providers`."];
17511
+ case ServerReloadStatuses["not-matching"]:
17512
+ return [notMatchingLines(serverReload.reason, serverUrl).join(" ")];
17513
+ case ServerReloadStatuses.reloaded:
17514
+ case ServerReloadStatuses.unchanged:
17515
+ case ServerReloadStatuses["not-local"]:
17516
+ return [];
17517
+ }
17518
+ })();
17519
+ const nextSteps = [...providerFree ? PROVIDER_FREE_NEXT_STEPS : [], ...reloadSteps];
17520
+ if (isMachineFormat(format)) {
17407
17521
  console.log(JSON.stringify({
17408
17522
  bootstrapped: true,
17409
17523
  status,
@@ -17413,18 +17527,44 @@ async function bootstrapCommand(opts) {
17413
17527
  apiUrl,
17414
17528
  providers: Object.keys(mergedProviders),
17415
17529
  googleConfigured: !!mergedGoogle,
17416
- generatedApiKey
17530
+ generatedApiKey,
17531
+ serverReload,
17532
+ nextSteps
17417
17533
  }, null, 2));
17534
+ if (reloadFailure) throw reloadFailure;
17418
17535
  return;
17419
17536
  }
17420
17537
  console.log(`Bootstrap ${status}. Config: ${getConfigPath()}`);
17421
17538
  console.log(`SQLite database path: ${databasePath}`);
17422
- if (Object.keys(mergedProviders).length === 0) {
17539
+ switch (serverReload.status) {
17540
+ case ServerReloadStatuses.reloaded:
17541
+ console.log(`Providers active on the running server: ${serverReload.providers.join(", ") || "none"}.`);
17542
+ break;
17543
+ case ServerReloadStatuses.unavailable:
17544
+ console.log("Start `canonry serve` to use the saved configuration.");
17545
+ break;
17546
+ case ServerReloadStatuses["not-local"]:
17547
+ console.log("Configuration saved locally. The configured remote server was not reloaded.");
17548
+ break;
17549
+ case ServerReloadStatuses["not-matching"]:
17550
+ for (const line of notMatchingLines(serverReload.reason, serverUrl)) console.log(line);
17551
+ break;
17552
+ case ServerReloadStatuses.unchanged:
17553
+ if (existing) console.log("Provider settings unchanged, so the server was not reloaded.");
17554
+ break;
17555
+ case ServerReloadStatuses.failed:
17556
+ break;
17557
+ }
17558
+ if (providerFree) {
17423
17559
  console.log("Providers: none (Page Health works now; add one later to enable AI Visibility).");
17560
+ console.log(" To add one with the server running: canonry settings provider gemini --api-key <key>");
17561
+ console.log(" (free key at aistudio.google.com). Or set GEMINI_API_KEY and rerun `canonry bootstrap`;");
17562
+ console.log(" bootstrap reloads the matching running local server and confirms active providers.");
17424
17563
  }
17425
17564
  if (generatedApiKey) {
17426
17565
  console.log(`API key (for the CLI, MCP, and agents): ${generatedApiKey}`);
17427
17566
  }
17567
+ if (reloadFailure) throw reloadFailure;
17428
17568
  }
17429
17569
 
17430
17570
  // src/commands/daemon.ts
@@ -17756,7 +17896,9 @@ async function initCommand(opts) {
17756
17896
  });
17757
17897
  }
17758
17898
  if (nonInteractive) {
17759
- Object.assign(providers, envProviders);
17899
+ for (const [name, entry] of Object.entries(envProviders)) {
17900
+ if (entry) providers[name] = withBootstrapProviderDefaults(name, entry);
17901
+ }
17760
17902
  if (envGoogleConfigured) {
17761
17903
  google = {
17762
17904
  clientId: bootstrapEnv.googleClientId,
@@ -18384,9 +18526,7 @@ Canonry server running at ${url}`);
18384
18526
  }, null, 2));
18385
18527
  }
18386
18528
  setTelemetrySource("cli-server");
18387
- const providerNames = Object.keys(config.providers ?? {}).filter(
18388
- (k) => config.providers?.[k]?.apiKey || config.providers?.[k]?.baseUrl
18389
- );
18529
+ const providerNames = registeredProviderNames(config);
18390
18530
  trackEvent("serve.started", {
18391
18531
  providerCount: providerNames.length,
18392
18532
  providers: providerNames,
@@ -22070,14 +22210,48 @@ var NUDGE_EXEMPT_ROOTS = /* @__PURE__ */ new Set([
22070
22210
  "status",
22071
22211
  "unknown"
22072
22212
  ]);
22213
+ var PAGE_HEALTH_HANDOFF_COMMANDS = /* @__PURE__ */ new Set([
22214
+ "technical-aeo.run",
22215
+ "technical-aeo.score",
22216
+ "technical-aeo.crawl",
22217
+ "site-health.overview"
22218
+ ]);
22219
+ var NO_PROVIDER_NOTICE_CODE = "NO_PROVIDER";
22220
+ var PROVIDER_SETUP_COMMAND = "canonry settings provider gemini --api-key <key>";
22221
+ var GEMINI_KEY_URL = "https://aistudio.google.com/apikey";
22073
22222
  function buildSetupNudgeLine(input) {
22074
- if (input.machineFormat || !input.stderrIsTTY) return null;
22075
- const [root = input.command] = input.command.split(".");
22076
- if (NUDGE_EXEMPT_ROOTS.has(root)) return null;
22223
+ const machineFormat = isMachineFormat(input.format);
22224
+ const handoff = PAGE_HEALTH_HANDOFF_COMMANDS.has(input.command);
22225
+ if (!handoff) {
22226
+ if (machineFormat || !input.stderrIsTTY) return null;
22227
+ const [root = input.command] = input.command.split(".");
22228
+ if (NUDGE_EXEMPT_ROOTS.has(root)) return null;
22229
+ }
22077
22230
  const setupState = input.getSetupState();
22078
22231
  if (!setupState) return null;
22079
22232
  if (setupState.provider_count > 0) return null;
22080
- return "\n\u2192 AI Visibility needs an answer-engine provider. Page Health does not.\n Add one in the dashboard after `canonry serve`, or:\n canonry settings provider gemini --api-key <key>\n";
22233
+ if (setupState.project_count === 0) return null;
22234
+ if (machineFormat) {
22235
+ return `${JSON.stringify({
22236
+ notice: {
22237
+ code: NO_PROVIDER_NOTICE_CODE,
22238
+ message: "AI Visibility needs an answer-engine provider. Page Health does not.",
22239
+ setupCommand: PROVIDER_SETUP_COMMAND,
22240
+ keyUrl: GEMINI_KEY_URL,
22241
+ note: "A provider key is the operator's credential: they add it in the dashboard, or run the setup command in their own terminal while the server is running. Never ask for it in chat."
22242
+ }
22243
+ })}
22244
+ `;
22245
+ }
22246
+ if (!input.stderrIsTTY) {
22247
+ return `[canonry] ${NO_PROVIDER_NOTICE_CODE}: AI Visibility needs an answer-engine provider. Page Health does not. The operator adds a key in the dashboard, or runs \`${PROVIDER_SETUP_COMMAND}\` in their own terminal while the server is running (free key at ${GEMINI_KEY_URL}). Never ask for the key in chat.
22248
+ `;
22249
+ }
22250
+ return `
22251
+ \u2192 AI Visibility needs an answer-engine provider. Page Health does not.
22252
+ Add one in the dashboard after \`canonry serve\`, or while the server is running:
22253
+ ${PROVIDER_SETUP_COMMAND} (free key at aistudio.google.com)
22254
+ `;
22081
22255
  }
22082
22256
 
22083
22257
  // src/cli.ts
@@ -22257,7 +22431,7 @@ async function runCli(args = process.argv.slice(2)) {
22257
22431
  }
22258
22432
  const nudge = buildSetupNudgeLine({
22259
22433
  command: resolvedCommand,
22260
- machineFormat: isMachineFormat(format),
22434
+ format,
22261
22435
  stderrIsTTY: Boolean(process.stderr.isTTY),
22262
22436
  getSetupState: buildSetupState
22263
22437
  });
@@ -5,11 +5,11 @@ import {
5
5
  computeFactorAverages,
6
6
  layoutSiteCrawlGraphInput,
7
7
  persistSiteCrawlGraphLayout
8
- } from "./chunk-A75L7CUV.js";
8
+ } from "./chunk-222NAFTY.js";
9
9
  import {
10
10
  PACKAGE_VERSION,
11
11
  canonryMcpTools
12
- } from "./chunk-VSTKTQPS.js";
12
+ } from "./chunk-5IXQAOVK.js";
13
13
  import {
14
14
  HISTORY_WINDOW_RUNS,
15
15
  IntelligenceService,
@@ -85,7 +85,7 @@ import {
85
85
  siteCrawlPages,
86
86
  siteCrawlSnapshots,
87
87
  trafficSources
88
- } from "./chunk-UUI4HOEL.js";
88
+ } from "./chunk-46JZR2IV.js";
89
89
  import {
90
90
  RunKinds,
91
91
  SiteCrawlFetchStates,
@@ -115,7 +115,7 @@ import {
115
115
  roundPreservingTotal,
116
116
  siteAuditPageFactorSchema,
117
117
  wilsonInterval
118
- } from "./chunk-CYHUZ2JQ.js";
118
+ } from "./chunk-MRKV43TV.js";
119
119
 
120
120
  // src/demo-server.ts
121
121
  import { dirname, join as join2 } from "path";
package/dist/index.d.ts CHANGED
@@ -453,7 +453,7 @@ interface CanonryConfig {
453
453
  places?: PlacesConfigEntry;
454
454
  embed?: EmbedConfigEntry;
455
455
  }
456
- declare function loadConfig(): CanonryConfig;
456
+ declare function loadConfig(configPath?: string): CanonryConfig;
457
457
 
458
458
  declare function createServer(opts: {
459
459
  config: CanonryConfig;
package/dist/index.js CHANGED
@@ -3,14 +3,14 @@ import {
3
3
  createGoogleMarketingCredentialStore,
4
4
  createGoogleMarketingRuntime,
5
5
  createServer
6
- } from "./chunk-OMP5OLBK.js";
6
+ } from "./chunk-CIPWGVX6.js";
7
7
  import {
8
8
  loadConfig
9
- } from "./chunk-35IIFDIT.js";
10
- import "./chunk-A75L7CUV.js";
11
- import "./chunk-VSTKTQPS.js";
12
- import "./chunk-UUI4HOEL.js";
13
- import "./chunk-CYHUZ2JQ.js";
9
+ } from "./chunk-NDQOCFJQ.js";
10
+ import "./chunk-222NAFTY.js";
11
+ import "./chunk-5IXQAOVK.js";
12
+ import "./chunk-46JZR2IV.js";
13
+ import "./chunk-MRKV43TV.js";
14
14
  export {
15
15
  GoogleMarketingRuntimeError,
16
16
  createGoogleMarketingCredentialStore,
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  HISTORY_WINDOW_RUNS,
3
3
  IntelligenceService
4
- } from "./chunk-UUI4HOEL.js";
5
- import "./chunk-CYHUZ2JQ.js";
4
+ } from "./chunk-46JZR2IV.js";
5
+ import "./chunk-MRKV43TV.js";
6
6
  export {
7
7
  HISTORY_WINDOW_RUNS,
8
8
  IntelligenceService
package/dist/mcp.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  autoSyncSkills
3
- } from "./chunk-PBTTPZZE.js";
3
+ } from "./chunk-6H2BC5EY.js";
4
4
  import {
5
5
  createApiClient,
6
6
  createCanonryMcpServer
7
- } from "./chunk-35IIFDIT.js";
8
- import "./chunk-VSTKTQPS.js";
7
+ } from "./chunk-NDQOCFJQ.js";
8
+ import "./chunk-5IXQAOVK.js";
9
9
  import {
10
10
  isReadOnlyKey,
11
11
  updateCheckEnvOptOut
12
- } from "./chunk-CYHUZ2JQ.js";
12
+ } from "./chunk-MRKV43TV.js";
13
13
 
14
14
  // src/mcp/cli.ts
15
15
  import { randomUUID } from "crypto";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonry/canonry",
3
- "version": "7.19.0",
3
+ "version": "7.20.1",
4
4
  "type": "module",
5
5
  "description": "Self-hosted AI visibility (AEO) platform: track how ChatGPT, Claude, Gemini, and Perplexity cite your domain, join it with Search Console, GA4, server-side traffic, and paid media, and fix what you find through agent tools (CLI, REST, MCP). Local SQLite.",
6
6
  "keywords": [
@@ -89,30 +89,30 @@
89
89
  "@ainyc/canonry-api-client": "0.0.0",
90
90
  "@ainyc/canonry-api-routes": "0.0.0",
91
91
  "@ainyc/canonry-contracts": "0.0.0",
92
- "@ainyc/canonry-config": "0.0.0",
93
92
  "@ainyc/canonry-db": "0.0.0",
94
- "@ainyc/canonry-integration-bing": "0.0.0",
93
+ "@ainyc/canonry-config": "0.0.0",
95
94
  "@ainyc/canonry-integration-cloud-run": "0.0.0",
96
- "@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
95
+ "@ainyc/canonry-integration-bing": "0.0.0",
97
96
  "@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
97
+ "@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
98
+ "@ainyc/canonry-integration-commoncrawl": "0.0.0",
98
99
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
99
100
  "@ainyc/canonry-integration-google-ads": "0.0.0",
100
- "@ainyc/canonry-integration-google": "0.0.0",
101
101
  "@ainyc/canonry-integration-google-places": "0.0.0",
102
- "@ainyc/canonry-integration-openai-ads": "0.0.0",
103
102
  "@ainyc/canonry-integration-google-tag-manager": "0.0.0",
104
103
  "@ainyc/canonry-integration-traffic": "0.0.0",
104
+ "@ainyc/canonry-integration-openai-ads": "0.0.0",
105
105
  "@ainyc/canonry-integration-wordpress": "0.0.0",
106
- "@ainyc/canonry-integration-commoncrawl": "0.0.0",
106
+ "@ainyc/canonry-integration-google": "0.0.0",
107
+ "@ainyc/canonry-provider-claude": "0.0.0",
107
108
  "@ainyc/canonry-intelligence": "0.0.0",
108
109
  "@ainyc/canonry-provider-cdp": "0.0.0",
109
- "@ainyc/canonry-provider-claude": "0.0.0",
110
110
  "@ainyc/canonry-provider-gemini": "0.0.0",
111
- "@ainyc/canonry-provider-muse": "0.0.0",
112
- "@ainyc/canonry-provider-perplexity": "0.0.0",
111
+ "@ainyc/canonry-provider-local": "0.0.0",
113
112
  "@ainyc/canonry-provider-openai": "0.0.0",
114
- "@ainyc/canonry-integration-typesafe": "0.0.0",
115
- "@ainyc/canonry-provider-local": "0.0.0"
113
+ "@ainyc/canonry-provider-perplexity": "0.0.0",
114
+ "@ainyc/canonry-provider-muse": "0.0.0",
115
+ "@ainyc/canonry-integration-typesafe": "0.0.0"
116
116
  },
117
117
  "scripts": {
118
118
  "build": "pnpm run build:cli && pnpm run build:web",