@canonry/canonry 7.18.1 → 7.20.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.
Files changed (36) 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-C0vj3liH.js +1 -0
  5. package/assets/assets/{BacklinksPage-BT3j4tQK.js → BacklinksPage-TnDsI9Wu.js} +1 -1
  6. package/assets/assets/{HistoryPage-BHV_3wbQ.js → HistoryPage-DEEC3g6M.js} +1 -1
  7. package/assets/assets/MeasurementPropertyPage-MT4tFOwx.js +4 -0
  8. package/assets/assets/ProjectPage-NI-bjpfN.js +8 -0
  9. package/assets/assets/{RunsPage-Cx5yMuzN.js → RunsPage-GIKqPu0J.js} +1 -1
  10. package/assets/assets/{SettingsPage-DrZ-eswp.js → SettingsPage-DLFwSj4J.js} +1 -1
  11. package/assets/assets/{SiteHealthSection-XOUI_75e.js → SiteHealthSection-CYG9IMh5.js} +3 -3
  12. package/assets/assets/{TrafficPage-DciX-fl5.js → TrafficPage-oOxDmxQq.js} +3 -3
  13. package/assets/assets/{TrafficSourceDetailPage-D1_lnRoS.js → TrafficSourceDetailPage-BGrwpVvM.js} +1 -1
  14. package/assets/assets/{extract-error-message-Cw5pcZtv.js → extract-error-message-DRDQFsgG.js} +1 -1
  15. package/assets/assets/{index-DIA9jvMi.js → index-B_2gPTCa.js} +36 -36
  16. package/assets/assets/{react-sigma_core.esm.min-Cc8jHlvQ.js → react-sigma_core.esm.min-DzDqz7hq.js} +1 -1
  17. package/assets/assets/v2-overview-adapter-BHzFliuj.js +1 -0
  18. package/assets/index.html +1 -1
  19. package/dist/{chunk-U7D7LNVJ.js → chunk-222NAFTY.js} +2 -2
  20. package/dist/{chunk-COQOQYRZ.js → chunk-46JZR2IV.js} +145 -31
  21. package/dist/{chunk-EAAM47H6.js → chunk-5IXQAOVK.js} +18 -3
  22. package/dist/{chunk-JUOFNJLV.js → chunk-6H2BC5EY.js} +3 -3
  23. package/dist/{chunk-PRL6UQ7L.js → chunk-KF7LRHGJ.js} +368 -211
  24. package/dist/{chunk-X2S7D3MN.js → chunk-MRKV43TV.js} +139 -7
  25. package/dist/{chunk-JMC7CEJB.js → chunk-NDQOCFJQ.js} +61 -43
  26. package/dist/cli.js +229 -45
  27. package/dist/{demo-server-XWRIXME5.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-332S7D7Y.js → intelligence-service-256DQBVR.js} +2 -2
  31. package/dist/mcp.js +4 -4
  32. package/package.json +13 -13
  33. package/assets/assets/AuditHistoryPanel-RAYlf3B4.js +0 -1
  34. package/assets/assets/MeasurementPropertyPage-9UFPKw-i.js +0 -1
  35. package/assets/assets/ProjectPage-C1_-qiHw.js +0 -8
  36. package/assets/assets/v2-overview-adapter-Ca5F20Kr.js +0 -1
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-PRL6UQ7L.js";
38
+ } from "./chunk-KF7LRHGJ.js";
38
39
  import {
39
40
  autoSyncSkills,
40
41
  formatAutoSyncNotice
41
- } from "./chunk-JUOFNJLV.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-JMC7CEJB.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-U7D7LNVJ.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-EAAM47H6.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-COQOQYRZ.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-X2S7D3MN.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-XWRIXME5.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-332S7D7Y.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);
@@ -12326,7 +12325,8 @@ function printMeasurementProperty(response) {
12326
12325
  function shapedPage(response) {
12327
12326
  if (response.answers !== void 0) return { shape: MeasurementEvidenceShapes.answers, ...response.answers };
12328
12327
  if (response.evidence !== void 0) return { shape: MeasurementEvidenceShapes.sources, ...response.evidence };
12329
- throw systemError("The measurement property evidence response carried neither an evidence nor an answers page.");
12328
+ if (response.otherQueries !== void 0) return { shape: MeasurementEvidenceShapes["other-queries"], ...response.otherQueries };
12329
+ throw systemError("The measurement property evidence response carried no evidence, answers or otherQueries page.");
12330
12330
  }
12331
12331
  async function showMeasurementPropertyEvidence(project2, opts) {
12332
12332
  const response = await createApiClient().getMeasurementPropertyEvidence(project2, {
@@ -12373,15 +12373,24 @@ function printMeasurementPropertyEvidence(response) {
12373
12373
  }
12374
12374
  const page = shapedPage(response);
12375
12375
  const answerShape = page.shape === MeasurementEvidenceShapes.answers;
12376
+ const otherQueriesShape = page.shape === MeasurementEvidenceShapes["other-queries"];
12376
12377
  lines.push(`Measurement: ${response.measurement.state}${response.measurement.displayedRunId ? ` \xB7 run ${response.measurement.displayedRunId}` : ""}`);
12377
12378
  if (page.items.length === 0) {
12378
- lines.push(answerShape ? "No answers matched this Property in the displayed run." : "No source evidence matched this Property in the displayed run.");
12379
+ lines.push(answerShape ? "No answers matched this Property in the displayed run." : otherQueriesShape ? "No answer to another query cited this Property's pages in the displayed run." : "No source evidence matched this Property in the displayed run.");
12379
12380
  console.log(lines.join("\n"));
12380
12381
  return;
12381
12382
  }
12382
- lines.push(`${page.items.length} of ${page.totalEstimate ?? page.items.length} ${answerShape ? "answers" : "evidence rows"}`);
12383
+ lines.push(`${page.items.length} of ${page.totalEstimate ?? page.items.length} ${answerShape || otherQueriesShape ? "answers" : "evidence rows"}`);
12384
+ if (otherQueriesShape) lines.push("Cited on queries not assigned to this Property; not counted in its rates.");
12383
12385
  lines.push("");
12384
- if (page.shape === MeasurementEvidenceShapes.answers) {
12386
+ if (page.shape === MeasurementEvidenceShapes["other-queries"]) {
12387
+ lines.push(`${"Engine".padEnd(12)}${"Question".padEnd(40)}${"Class".padEnd(11)}${"Assigned to".padEnd(24)}Pages`);
12388
+ for (const item of page.items) {
12389
+ lines.push(
12390
+ `${item.provider.padEnd(12)}${item.queryText.slice(0, 39).padEnd(40)}${item.queryClass.padEnd(11)}${item.assignedTargetKeys.join(", ").slice(0, 23).padEnd(24)}${item.sources.map((source) => source.sourceUrl).join(" ")}`
12391
+ );
12392
+ }
12393
+ } else if (page.shape === MeasurementEvidenceShapes.answers) {
12385
12394
  lines.push(`${"Engine".padEnd(12)}${"Question".padEnd(40)}${"Mentioned".padEnd(15)}${"Cited".padEnd(8)}Sources`);
12386
12395
  for (const item of page.items) {
12387
12396
  lines.push(
@@ -15607,6 +15616,17 @@ async function showSettings(format) {
15607
15616
  console.log("\nGoogle OAuth:\n");
15608
15617
  console.log(` ${settings.google.configured ? "configured" : "not configured"}`);
15609
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
+ }
15610
15630
  async function setGoogleAuth(opts) {
15611
15631
  if (opts.target === "server") {
15612
15632
  const result = await getClient25().updateGoogleSettings({
@@ -15640,6 +15660,11 @@ async function setGoogleAuth(opts) {
15640
15660
 
15641
15661
  // src/cli-commands/settings.ts
15642
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
+ },
15643
15668
  {
15644
15669
  path: ["settings", "provider"],
15645
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]",
@@ -15767,7 +15792,7 @@ Usage: canonry settings provider ${name} --api-key <key> [--model <model>] [--ma
15767
15792
  },
15768
15793
  {
15769
15794
  path: ["settings"],
15770
- usage: "canonry settings [provider|google] [args]",
15795
+ usage: "canonry settings [provider|reload-providers|google] [args]",
15771
15796
  run: async (input) => {
15772
15797
  const subcommand = input.positionals[0];
15773
15798
  if (!subcommand) {
@@ -15776,8 +15801,8 @@ Usage: canonry settings provider ${name} --api-key <key> [--model <model>] [--ma
15776
15801
  }
15777
15802
  unknownSubcommand(subcommand, {
15778
15803
  command: "settings",
15779
- usage: "canonry settings [provider|google] [args]",
15780
- available: ["provider", "google"]
15804
+ usage: "canonry settings [provider|reload-providers|google] [args]",
15805
+ available: ["provider", "reload-providers", "google"]
15781
15806
  });
15782
15807
  }
15783
15808
  }
@@ -17291,14 +17316,72 @@ function persistedValue(value) {
17291
17316
  }
17292
17317
  return value;
17293
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
+ }
17294
17378
  async function bootstrapCommand(opts) {
17295
17379
  const format = opts?.format ?? "text";
17296
- const env = getBootstrapEnv(process.env);
17297
- const providers = env.providers;
17298
17380
  const configDir = getConfigDir();
17299
17381
  const existing = configExists();
17300
17382
  const existingConfig = existing ? loadConfig() : void 0;
17301
17383
  const existingRaw = existing ? loadConfigRaw() : null;
17384
+ const env = getBootstrapEnv(process.env);
17302
17385
  const databasePath = env.databasePath || existingRaw?.database || path8.join(configDir, "data.db");
17303
17386
  let rawApiKey;
17304
17387
  let generatedApiKey;
@@ -17311,12 +17394,9 @@ async function bootstrapCommand(opts) {
17311
17394
  rawApiKey = generatedApiKey;
17312
17395
  }
17313
17396
  const mergedProviders = { ...existingConfig?.providers };
17314
- if (providers.gemini) mergedProviders.gemini = providers.gemini;
17315
- if (providers.openai) mergedProviders.openai = providers.openai;
17316
- if (providers.claude) mergedProviders.claude = providers.claude;
17317
- if (providers.perplexity) mergedProviders.perplexity = providers.perplexity;
17318
- if (providers.muse) mergedProviders.muse = providers.muse;
17319
- 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
+ }
17320
17400
  if (env.googleClientId && !env.googleClientSecret || !env.googleClientId && env.googleClientSecret) {
17321
17401
  console.warn("Warning: GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET must both be set to configure Google OAuth. Skipping Google auth config.");
17322
17402
  }
@@ -17390,10 +17470,54 @@ async function bootstrapCommand(opts) {
17390
17470
  providers: mergedProviders,
17391
17471
  google: mergedGoogle
17392
17472
  };
17393
- 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));
17394
17475
  if (configChanged) saveConfig(nextConfig);
17395
17476
  const status = !existing ? "created" : configChanged || keyChanged ? "updated" : "unchanged";
17396
- 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)) {
17397
17521
  console.log(JSON.stringify({
17398
17522
  bootstrapped: true,
17399
17523
  status,
@@ -17403,18 +17527,44 @@ async function bootstrapCommand(opts) {
17403
17527
  apiUrl,
17404
17528
  providers: Object.keys(mergedProviders),
17405
17529
  googleConfigured: !!mergedGoogle,
17406
- generatedApiKey
17530
+ generatedApiKey,
17531
+ serverReload,
17532
+ nextSteps
17407
17533
  }, null, 2));
17534
+ if (reloadFailure) throw reloadFailure;
17408
17535
  return;
17409
17536
  }
17410
17537
  console.log(`Bootstrap ${status}. Config: ${getConfigPath()}`);
17411
17538
  console.log(`SQLite database path: ${databasePath}`);
17412
- 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) {
17413
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.");
17414
17563
  }
17415
17564
  if (generatedApiKey) {
17416
17565
  console.log(`API key (for the CLI, MCP, and agents): ${generatedApiKey}`);
17417
17566
  }
17567
+ if (reloadFailure) throw reloadFailure;
17418
17568
  }
17419
17569
 
17420
17570
  // src/commands/daemon.ts
@@ -17746,7 +17896,9 @@ async function initCommand(opts) {
17746
17896
  });
17747
17897
  }
17748
17898
  if (nonInteractive) {
17749
- Object.assign(providers, envProviders);
17899
+ for (const [name, entry] of Object.entries(envProviders)) {
17900
+ if (entry) providers[name] = withBootstrapProviderDefaults(name, entry);
17901
+ }
17750
17902
  if (envGoogleConfigured) {
17751
17903
  google = {
17752
17904
  clientId: bootstrapEnv.googleClientId,
@@ -18374,9 +18526,7 @@ Canonry server running at ${url}`);
18374
18526
  }, null, 2));
18375
18527
  }
18376
18528
  setTelemetrySource("cli-server");
18377
- const providerNames = Object.keys(config.providers ?? {}).filter(
18378
- (k) => config.providers?.[k]?.apiKey || config.providers?.[k]?.baseUrl
18379
- );
18529
+ const providerNames = registeredProviderNames(config);
18380
18530
  trackEvent("serve.started", {
18381
18531
  providerCount: providerNames.length,
18382
18532
  providers: providerNames,
@@ -21747,7 +21897,7 @@ Usage: ${usage}`, {
21747
21897
 
21748
21898
  // src/cli-commands/measurement-plan.ts
21749
21899
  var QUERY_CLASSES = ["all", "branded", "non-brand"];
21750
- var EVIDENCE_SHAPES = ["sources", "answers"];
21900
+ var EVIDENCE_SHAPES = ["sources", "answers", "other-queries"];
21751
21901
  function queryClassOption(input) {
21752
21902
  const value = getString(input.values, "query-class");
21753
21903
  if (value === void 0) return void 0;
@@ -21979,7 +22129,7 @@ Usage: ${usage}`);
21979
22129
  },
21980
22130
  {
21981
22131
  path: ["measurement-plan", "property-evidence"],
21982
- usage: "canonry measurement-plan property-evidence <project> --target-key <key> [--query-class all|branded|non-brand] [--provider <p>] [--location <l>] [--run-id <id>] [--shape sources|answers] [--cursor <c>] [--limit N] [--format json|jsonl]",
22132
+ usage: "canonry measurement-plan property-evidence <project> --target-key <key> [--query-class all|branded|non-brand] [--provider <p>] [--location <l>] [--run-id <id>] [--shape sources|answers|other-queries] [--cursor <c>] [--limit N] [--format json|jsonl]",
21983
22133
  options: { ...PROPERTY_SCOPE_OPTIONS, shape: stringOption(), cursor: stringOption(), limit: stringOption() },
21984
22134
  run: (input) => {
21985
22135
  const usage = "canonry measurement-plan property-evidence <project> --target-key <key>";
@@ -22060,14 +22210,48 @@ var NUDGE_EXEMPT_ROOTS = /* @__PURE__ */ new Set([
22060
22210
  "status",
22061
22211
  "unknown"
22062
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";
22063
22222
  function buildSetupNudgeLine(input) {
22064
- if (input.machineFormat || !input.stderrIsTTY) return null;
22065
- const [root = input.command] = input.command.split(".");
22066
- 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
+ }
22067
22230
  const setupState = input.getSetupState();
22068
22231
  if (!setupState) return null;
22069
22232
  if (setupState.provider_count > 0) return null;
22070
- 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
+ `;
22071
22255
  }
22072
22256
 
22073
22257
  // src/cli.ts
@@ -22247,7 +22431,7 @@ async function runCli(args = process.argv.slice(2)) {
22247
22431
  }
22248
22432
  const nudge = buildSetupNudgeLine({
22249
22433
  command: resolvedCommand,
22250
- machineFormat: isMachineFormat(format),
22434
+ format,
22251
22435
  stderrIsTTY: Boolean(process.stderr.isTTY),
22252
22436
  getSetupState: buildSetupState
22253
22437
  });
@@ -5,11 +5,11 @@ import {
5
5
  computeFactorAverages,
6
6
  layoutSiteCrawlGraphInput,
7
7
  persistSiteCrawlGraphLayout
8
- } from "./chunk-U7D7LNVJ.js";
8
+ } from "./chunk-222NAFTY.js";
9
9
  import {
10
10
  PACKAGE_VERSION,
11
11
  canonryMcpTools
12
- } from "./chunk-EAAM47H6.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-COQOQYRZ.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-X2S7D3MN.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-PRL6UQ7L.js";
6
+ } from "./chunk-KF7LRHGJ.js";
7
7
  import {
8
8
  loadConfig
9
- } from "./chunk-JMC7CEJB.js";
10
- import "./chunk-U7D7LNVJ.js";
11
- import "./chunk-EAAM47H6.js";
12
- import "./chunk-COQOQYRZ.js";
13
- import "./chunk-X2S7D3MN.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-COQOQYRZ.js";
5
- import "./chunk-X2S7D3MN.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-JUOFNJLV.js";
3
+ } from "./chunk-6H2BC5EY.js";
4
4
  import {
5
5
  createApiClient,
6
6
  createCanonryMcpServer
7
- } from "./chunk-JMC7CEJB.js";
8
- import "./chunk-EAAM47H6.js";
7
+ } from "./chunk-NDQOCFJQ.js";
8
+ import "./chunk-5IXQAOVK.js";
9
9
  import {
10
10
  isReadOnlyKey,
11
11
  updateCheckEnvOptOut
12
- } from "./chunk-X2S7D3MN.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.18.1",
3
+ "version": "7.20.0",
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": [
@@ -86,33 +86,33 @@
86
86
  "tsup": "^8.5.1",
87
87
  "tsx": "^4.19.0",
88
88
  "@ainyc/canonry-web": "0.0.0",
89
- "@ainyc/canonry-api-routes": "0.0.0",
90
89
  "@ainyc/canonry-api-client": "0.0.0",
91
- "@ainyc/canonry-config": "0.0.0",
92
- "@ainyc/canonry-contracts": "0.0.0",
93
90
  "@ainyc/canonry-db": "0.0.0",
91
+ "@ainyc/canonry-api-routes": "0.0.0",
92
+ "@ainyc/canonry-contracts": "0.0.0",
93
+ "@ainyc/canonry-config": "0.0.0",
94
94
  "@ainyc/canonry-integration-bing": "0.0.0",
95
- "@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
96
95
  "@ainyc/canonry-integration-cloud-run": "0.0.0",
96
+ "@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
97
97
  "@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
98
+ "@ainyc/canonry-integration-google-ads": "0.0.0",
98
99
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
99
100
  "@ainyc/canonry-integration-google": "0.0.0",
100
- "@ainyc/canonry-integration-google-ads": "0.0.0",
101
101
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
102
- "@ainyc/canonry-integration-openai-ads": "0.0.0",
103
- "@ainyc/canonry-integration-google-places": "0.0.0",
104
102
  "@ainyc/canonry-integration-google-tag-manager": "0.0.0",
103
+ "@ainyc/canonry-integration-google-places": "0.0.0",
104
+ "@ainyc/canonry-integration-openai-ads": "0.0.0",
105
105
  "@ainyc/canonry-integration-traffic": "0.0.0",
106
+ "@ainyc/canonry-intelligence": "0.0.0",
106
107
  "@ainyc/canonry-integration-wordpress": "0.0.0",
107
108
  "@ainyc/canonry-provider-cdp": "0.0.0",
108
109
  "@ainyc/canonry-provider-claude": "0.0.0",
109
- "@ainyc/canonry-intelligence": "0.0.0",
110
110
  "@ainyc/canonry-provider-gemini": "0.0.0",
111
- "@ainyc/canonry-provider-local": "0.0.0",
112
- "@ainyc/canonry-provider-openai": "0.0.0",
113
111
  "@ainyc/canonry-provider-muse": "0.0.0",
114
- "@ainyc/canonry-integration-typesafe": "0.0.0",
115
- "@ainyc/canonry-provider-perplexity": "0.0.0"
112
+ "@ainyc/canonry-provider-openai": "0.0.0",
113
+ "@ainyc/canonry-provider-perplexity": "0.0.0",
114
+ "@ainyc/canonry-provider-local": "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",
@@ -1 +0,0 @@
1
- import{r as a,u as h,j as e}from"./vendor-tanstack-ZXXkndVj.js";import{dJ as L,dK as P,cH as E,x as u,B as x,v,T as M}from"./index-DIA9jvMi.js";const o=100;function k(s){if(s==="all")return;const n=Number.parseInt(s,10);return new Date(Date.now()-n*24*60*60*1e3).toISOString()}function O(s){const n=s.userAgent?.toLowerCase()??"";return n.includes("canonry-mcp")?"MCP":n.includes("canonry-cli")?"CLI":n.includes("mozilla/")?"Dashboard":s.actor==="scheduler"?"Scheduler":s.actor==="agent"?"Agent":s.actor==="mcp"?"MCP":s.actor==="cli"?"CLI":s.actor}function B(s){return/delete|remove|revoke|disconnect|failed|refused/.test(s)?"negative":/create|add|connect|completed/.test(s)?"positive":/update|replace|sync|apply/.test(s)?"caution":"neutral"}function D(s){return[s.action,s.actor,s.entityType,s.entityId,s.actorSession,s.userAgent,s.diff==null?"":JSON.stringify(s.diff)].filter(Boolean).join(" ").toLowerCase()}function F({projectName:s}){const[n,l]=a.useState(0),[c,y]=a.useState("90d"),[m,N]=a.useState(""),[p,w]=a.useState(""),S=a.useMemo(()=>k(c),[c]),g={limit:o,offset:n*o,since:S,actor:m||void 0},C=h({...L({client:u,path:{name:s??""},query:g}),enabled:!!s}),A=h({...P({client:u,query:g}),enabled:!s}),f=h({...E({client:u}),enabled:!s}),i=s?C:A,r=i.data??[],I=a.useMemo(()=>new Map((f.data??[]).map(t=>[t.id,t.displayName||t.name])),[f.data]),d=p.trim().toLowerCase(),j=a.useMemo(()=>d?r.filter(t=>D(t).includes(d)):r,[r,d]),b=t=>{l(0),t()};return i.isLoading?e.jsx("p",{className:"supporting-copy mt-6",role:"status",children:"Loading change history…"}):e.jsxs("section",{className:"mt-6","aria-labelledby":"change-history-heading",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Audit trail"}),e.jsx("h2",{id:"change-history-heading",children:"Change history"}),e.jsx("p",{className:"supporting-copy mt-1 max-w-2xl",children:"Configuration and operator actions. Execution outcomes remain in Runs."})]}),e.jsx(x,{type:"button",variant:"outline",size:"sm",onClick:()=>{i.refetch()},disabled:i.isFetching,children:i.isFetching?"Refreshing…":"Refresh"})]}),e.jsxs("div",{className:"mt-4 grid gap-2 sm:grid-cols-3",children:[e.jsx("input",{type:"search","aria-label":"Search change history",value:p,onChange:t=>w(t.target.value),placeholder:"Search actions, entities, or details",className:"min-h-11 rounded-md border border-base bg-bg px-3 text-sm text-strong placeholder-mono-600 focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600"}),e.jsxs("select",{"aria-label":"Filter change history by actor",value:m,onChange:t=>b(()=>N(t.target.value)),className:"min-h-11 rounded-md border border-base bg-bg px-3 text-sm text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600",children:[e.jsx("option",{value:"",children:"All actors"}),e.jsx("option",{value:"api",children:"API and dashboard"}),e.jsx("option",{value:"cli",children:"CLI"}),e.jsx("option",{value:"scheduler",children:"Scheduler"}),e.jsx("option",{value:"agent",children:"Agent"}),e.jsx("option",{value:"mcp",children:"MCP"})]}),e.jsxs("select",{"aria-label":"Filter change history by date range",value:c,onChange:t=>b(()=>y(t.target.value)),className:"min-h-11 rounded-md border border-base bg-bg px-3 text-sm text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600",children:[e.jsx("option",{value:"30d",children:"Last 30 days"}),e.jsx("option",{value:"90d",children:"Last 90 days"}),e.jsx("option",{value:"365d",children:"Last year"}),e.jsx("option",{value:"all",children:"All retained history"})]})]}),i.isError?e.jsxs(v,{className:"surface-card empty-card mt-4",children:[e.jsx("h3",{children:"Change history unavailable"}),e.jsx("p",{children:i.error instanceof Error?i.error.message:"Could not load the audit trail."})]}):j.length===0?e.jsxs(v,{className:"surface-card empty-card mt-4",children:[e.jsx("h3",{children:"No changes match these filters"}),e.jsx("p",{children:"Widen the date range or clear the actor and search filters."})]}):e.jsx("div",{className:"evidence-table-wrap mt-4",children:e.jsxs("table",{className:"evidence-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"When"}),s?null:e.jsx("th",{children:"Scope"}),e.jsx("th",{children:"Action"}),e.jsx("th",{children:"Entity"}),e.jsx("th",{children:"Origin"}),e.jsx("th",{children:"Details"})]})}),e.jsx("tbody",{children:j.map(t=>e.jsxs("tr",{children:[e.jsx("td",{className:"whitespace-nowrap tabular-nums text-muted",children:new Date(t.createdAt).toLocaleString()}),s?null:e.jsx("td",{className:"text-secondary",children:t.projectId?I.get(t.projectId)??`Deleted project · ${t.projectId}`:"Instance settings"}),e.jsx("td",{children:e.jsx(M,{tone:B(t.action),children:t.action})}),e.jsxs("td",{children:[e.jsx("span",{className:"text-strong",children:t.entityType}),t.entityId?e.jsx("span",{className:"mt-0.5 block max-w-48 truncate font-mono text-[11px] text-faint",title:t.entityId,children:t.entityId}):null]}),e.jsxs("td",{children:[e.jsx("span",{className:"text-secondary",children:O(t)}),t.actorSession?e.jsxs("span",{className:"mt-0.5 block max-w-40 truncate font-mono text-[11px] text-faint",title:t.actorSession,children:["Session ",t.actorSession]}):null]}),e.jsx("td",{children:t.diff==null?e.jsx("span",{className:"text-faint",children:"No field diff"}):e.jsxs("details",{children:[e.jsx("summary",{className:"min-h-11 cursor-pointer py-3 text-sm text-secondary hover:text-strong",children:"View diff"}),e.jsx("pre",{className:"max-h-72 max-w-xl overflow-auto whitespace-pre-wrap break-words rounded-md bg-bg-elevated p-3 text-xs text-secondary",children:JSON.stringify(t.diff,null,2)})]})})]},t.id))})]})}),!i.isError&&(n>0||r.length===o)?e.jsxs("div",{className:"mt-4 flex items-center justify-between gap-3",children:[e.jsx(x,{type:"button",variant:"outline",size:"sm",disabled:n===0,onClick:()=>l(t=>Math.max(0,t-1)),children:"Previous"}),e.jsxs("span",{className:"text-xs tabular-nums text-muted",children:["Page ",n+1]}),e.jsx(x,{type:"button",variant:"outline",size:"sm",disabled:r.length<o,onClick:()=>l(t=>t+1),children:"Next"})]}):null]})}export{F as A};