@ainyc/canonry 4.193.2 → 4.195.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 (29) hide show
  1. package/README.md +1 -1
  2. package/assets/agent-workspace/skills/canonry/SKILL.md +81 -1
  3. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +41 -0
  4. package/assets/assets/{AuditHistoryPanel-BBXiKuD1.js → AuditHistoryPanel-vWUtVXOI.js} +1 -1
  5. package/assets/assets/{BacklinksPage-CE_-DKWO.js → BacklinksPage-CSzuMEuv.js} +1 -1
  6. package/assets/assets/{HistoryPage-D-l4jMt5.js → HistoryPage-DtCTQ7CQ.js} +1 -1
  7. package/assets/assets/{MeasurementPropertyPage-fCJm3sW6.js → MeasurementPropertyPage-B7gTMWNs.js} +1 -1
  8. package/assets/assets/{ProjectPage-Cxrd8lZM.js → ProjectPage-Bsm0_jUY.js} +1 -1
  9. package/assets/assets/{RunRow-C2kmB04V.js → RunRow-D5k4Rdo4.js} +1 -1
  10. package/assets/assets/{RunsPage-B73hjh_l.js → RunsPage-DIXBB2Om.js} +1 -1
  11. package/assets/assets/{SettingsPage-Ba30Jfnn.js → SettingsPage-wYhBm6Az.js} +1 -1
  12. package/assets/assets/{SiteHealthSection-mjpxy6N9.js → SiteHealthSection-YebzsgDh.js} +3 -3
  13. package/assets/assets/{TrafficPage-BZ8H_c8u.js → TrafficPage-DtnSRFO8.js} +1 -1
  14. package/assets/assets/{TrafficSourceDetailPage-sCUyzPpH.js → TrafficSourceDetailPage-DYQ6mpYq.js} +1 -1
  15. package/assets/assets/{extract-error-message-G-MvSIBA.js → extract-error-message-BdChrkmo.js} +1 -1
  16. package/assets/assets/{index-CSktlkUv.js → index-C_E2NRYY.js} +26 -26
  17. package/assets/assets/{react-sigma_core.esm.min-CgN2Dnvk.js → react-sigma_core.esm.min-sKWUk_tG.js} +1 -1
  18. package/assets/assets/{v2-overview-adapter-OmeP2l8O.js → v2-overview-adapter-CkPt-sRK.js} +1 -1
  19. package/assets/index.html +1 -1
  20. package/dist/{chunk-5CXUV3ZX.js → chunk-3WODA4JF.js} +288 -7
  21. package/dist/{chunk-24JHDZ7H.js → chunk-B2XM6AQ6.js} +318 -33
  22. package/dist/{chunk-KJJMIWVD.js → chunk-H3RVNMOO.js} +91 -38
  23. package/dist/{chunk-LIMWYFHS.js → chunk-LJKAB4CX.js} +2 -2
  24. package/dist/{chunk-Q5KGSOT7.js → chunk-UMU3IBXM.js} +1231 -484
  25. package/dist/cli.js +252 -156
  26. package/dist/index.js +4 -4
  27. package/dist/{intelligence-service-6ORSU2M6.js → intelligence-service-MGQRJW4V.js} +2 -2
  28. package/dist/mcp.js +4 -4
  29. package/package.json +10 -10
package/dist/cli.js CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  formatAuditFactorScore,
18
18
  getCloudflareTrafficConnectionBySourceId,
19
19
  getOrCreateAnonymousId,
20
+ getTelemetryStatus,
20
21
  isAeroToolProfile,
21
22
  isFirstRun,
22
23
  isLoopbackBindHost,
@@ -28,11 +29,11 @@ import {
28
29
  trackCliCommandFinished,
29
30
  trackEvent,
30
31
  waitForServerRuntimeStartup
31
- } from "./chunk-KJJMIWVD.js";
32
+ } from "./chunk-H3RVNMOO.js";
32
33
  import {
33
34
  autoSyncSkills,
34
35
  formatAutoSyncNotice
35
- } from "./chunk-LIMWYFHS.js";
36
+ } from "./chunk-LJKAB4CX.js";
36
37
  import {
37
38
  CliError,
38
39
  EXIT_SYSTEM_ERROR,
@@ -58,7 +59,7 @@ import {
58
59
  saveConfigPatch,
59
60
  systemError,
60
61
  usageError
61
- } from "./chunk-24JHDZ7H.js";
62
+ } from "./chunk-B2XM6AQ6.js";
62
63
  import {
63
64
  CLOUDFLARE_WORKER_BINDINGS,
64
65
  CLOUDFLARE_WORKER_GENERATED_MARKER,
@@ -72,7 +73,7 @@ import {
72
73
  projects,
73
74
  queries,
74
75
  renderReportHtml
75
- } from "./chunk-Q5KGSOT7.js";
76
+ } from "./chunk-UMU3IBXM.js";
76
77
  import {
77
78
  AdsDeliverySnapshotStatuses,
78
79
  AdsHistoricalCampaignRollupStatuses,
@@ -122,6 +123,7 @@ import {
122
123
  formatRatio,
123
124
  formatRunErrorOneLine,
124
125
  googleAdsMetricsWindowSchema,
126
+ logQuerySchema,
125
127
  measurementChangesQuerySchema,
126
128
  measurementDataQualityQuerySchema,
127
129
  measurementDiscoveryRequestSchema,
@@ -138,6 +140,7 @@ import {
138
140
  measurementQueryTemplateUpsertRequestSchema,
139
141
  measurementQuestionResultQuerySchema,
140
142
  normalizeProjectAliases,
143
+ normalizeTelemetryStatus,
141
144
  notificationEventSchema,
142
145
  queryTrackingCommitRequestSchema,
143
146
  queryTrackingPreviewRequestSchema,
@@ -146,9 +149,10 @@ import {
146
149
  resultsClearRequestSchema,
147
150
  shareOfVoiceLabel,
148
151
  shareOfVoiceReason,
152
+ snapshotProviderModeSchema,
149
153
  visibilityReportRequestSchema,
150
154
  winnabilityClassSchema
151
- } from "./chunk-5CXUV3ZX.js";
155
+ } from "./chunk-3WODA4JF.js";
152
156
 
153
157
  // src/cli.ts
154
158
  import { pathToFileURL } from "url";
@@ -10931,8 +10935,12 @@ var EVENT_DESCRIPTIONS = {
10931
10935
  "health.degraded": "A scheduled health check found the measurement itself degraded (sent on change, not every pass)",
10932
10936
  "health.recovered": "A previously degraded health check is passing again"
10933
10937
  };
10934
- function listEvents(format) {
10935
- const events = notificationEventSchema.options;
10938
+ function listEvents(format, target = "local") {
10939
+ if (target === "server") return getClient16().listNotificationEvents().then((events) => printEvents(events, format));
10940
+ if (target !== "local") throw usageError("--target must be local or server");
10941
+ printEvents(notificationEventSchema.options, format);
10942
+ }
10943
+ function printEvents(events, format) {
10936
10944
  const catalog = events.map((e) => ({ event: e, description: EVENT_DESCRIPTIONS[e] ?? "" }));
10937
10945
  if (format === "json") {
10938
10946
  console.log(JSON.stringify(catalog, null, 2));
@@ -10958,9 +10966,10 @@ function printNotification(n) {
10958
10966
  var NOTIFY_CLI_COMMANDS = [
10959
10967
  {
10960
10968
  path: ["notify", "events"],
10961
- usage: "canonry notify events [--format json]",
10962
- run: (input) => {
10963
- listEvents(input.format);
10969
+ usage: "canonry notify events [--target local|server] [--format json|jsonl]",
10970
+ options: { target: stringOption() },
10971
+ run: async (input) => {
10972
+ await listEvents(input.format, getString(input.values, "target"));
10964
10973
  }
10965
10974
  },
10966
10975
  {
@@ -11500,6 +11509,7 @@ async function showHistory(project, format, opts = {}) {
11500
11509
  if (entry.diff != null) console.log(` diff: ${JSON.stringify(entry.diff)}`);
11501
11510
  }
11502
11511
  } catch (err) {
11512
+ if (err instanceof CliError) throw err;
11503
11513
  const message = describeError(err);
11504
11514
  throw new CliError({
11505
11515
  code: "HISTORY_FETCH_FAILED",
@@ -11596,6 +11606,21 @@ function parseResultsExportFormat(value) {
11596
11606
  Usage: ${RESULTS_EXPORT_USAGE}`);
11597
11607
  }
11598
11608
  var RESULTS_CLEAR_USAGE = "canonry results clear <project> [--run <id> ...] [--research-run <id> ...] [--confirm] [--format json|jsonl]";
11609
+ function parseHistoryPageOption(input, key, usage) {
11610
+ const value = getString(input.values, key);
11611
+ if (value === void 0) return void 0;
11612
+ if (!/^\d+$/.test(value)) {
11613
+ throw usageError(`Error: --${key} must be a ${key === "limit" ? "whole number from 1 to 500" : "nonnegative integer"}
11614
+ Usage: ${usage}`);
11615
+ }
11616
+ const parsed = Number(value);
11617
+ const valid = Number.isSafeInteger(parsed) && (key === "limit" ? parsed >= 1 && parsed <= 500 : parsed >= 0);
11618
+ if (!valid) {
11619
+ throw usageError(`Error: --${key} must be a ${key === "limit" ? "whole number from 1 to 500" : "nonnegative integer"}
11620
+ Usage: ${usage}`);
11621
+ }
11622
+ return parsed;
11623
+ }
11599
11624
  var OPERATOR_CLI_COMMANDS = [
11600
11625
  {
11601
11626
  path: ["results", "clear"],
@@ -11648,20 +11673,22 @@ var OPERATOR_CLI_COMMANDS = [
11648
11673
  },
11649
11674
  {
11650
11675
  path: ["history"],
11651
- usage: "canonry history <project> [--limit <n>] [--since <ISO>] [--action <action>] [--actor <actor>] [--entity-type <type>] [--format json|jsonl]\n canonry history --all [same filters]",
11676
+ usage: "canonry history <project> [--limit <1..500>] [--offset <n>] [--since <ISO>] [--action <action>] [--actor <actor>] [--entity-type <type>] [--format json|jsonl]\n canonry history --all [same filters]",
11652
11677
  options: {
11653
11678
  all: { type: "boolean", default: false },
11654
11679
  limit: stringOption(),
11680
+ offset: stringOption(),
11655
11681
  since: stringOption(),
11656
11682
  action: stringOption(),
11657
11683
  actor: stringOption(),
11658
11684
  "entity-type": stringOption()
11659
11685
  },
11660
11686
  run: async (input) => {
11661
- const usage = "canonry history <project> [--limit <n>] [--since <ISO>] [--action <action>] [--actor <actor>] [--entity-type <type>] [--format json|jsonl]\n canonry history --all [same filters]";
11687
+ const usage = "canonry history <project> [--limit <1..500>] [--offset <n>] [--since <ISO>] [--action <action>] [--actor <actor>] [--entity-type <type>] [--format json|jsonl]\n canonry history --all [same filters]";
11662
11688
  const project = getBoolean(input.values, "all") ? void 0 : requireProject(input, "history", usage);
11663
11689
  await showHistory(project, input.format, {
11664
- limit: parseIntegerOption(input, "limit", { command: "history", usage, message: "--limit must be an integer" }),
11690
+ limit: parseHistoryPageOption(input, "limit", usage),
11691
+ offset: parseHistoryPageOption(input, "offset", usage),
11665
11692
  since: getString(input.values, "since"),
11666
11693
  action: getString(input.values, "action"),
11667
11694
  actor: getString(input.values, "actor"),
@@ -12799,6 +12826,19 @@ var RUN_CLI_COMMANDS = [
12799
12826
  ];
12800
12827
 
12801
12828
  // src/commands/schedule.ts
12829
+ async function listSchedules(project, format) {
12830
+ const schedules = await getClient24().listSchedules(project);
12831
+ if (format === "jsonl") {
12832
+ emitJsonl(schedules);
12833
+ return;
12834
+ }
12835
+ if (format === "json") {
12836
+ console.log(JSON.stringify(schedules, null, 2));
12837
+ return;
12838
+ }
12839
+ if (!schedules.length) console.log(`No schedules configured for "${project}"`);
12840
+ for (const schedule of schedules) printSchedule(schedule);
12841
+ }
12802
12842
  function getClient24() {
12803
12843
  return createApiClient();
12804
12844
  }
@@ -12903,6 +12943,13 @@ function printSchedule(s) {
12903
12943
 
12904
12944
  // src/cli-commands/schedule.ts
12905
12945
  var SCHEDULE_CLI_COMMANDS = [
12946
+ {
12947
+ path: ["schedule", "list"],
12948
+ usage: "canonry schedule list <project> [--format json|jsonl]",
12949
+ run: async (input) => {
12950
+ await listSchedules(requireProject(input, "schedule.list", "canonry schedule list <project>"), input.format);
12951
+ }
12952
+ },
12906
12953
  {
12907
12954
  path: ["schedule", "set"],
12908
12955
  usage: "canonry schedule set <project> (--preset <preset> | --cron <expr> | --every-days <n> --start-date <YYYY-MM-DD> --at <HH:mm>) [--kind answer-visibility|traffic-sync|gbp-sync|data-refresh|backlinks-sync] [--source <id>] [--timezone <tz>] [--provider <name>...] [--format json]",
@@ -12991,12 +13038,12 @@ var SCHEDULE_CLI_COMMANDS = [
12991
13038
  },
12992
13039
  {
12993
13040
  path: ["schedule"],
12994
- usage: "canonry schedule <set|show|enable|disable|remove> <project>",
13041
+ usage: "canonry schedule <list|set|show|enable|disable|remove> <project>",
12995
13042
  run: async (input) => {
12996
13043
  unknownSubcommand(input.positionals[0], {
12997
13044
  command: "schedule",
12998
- usage: "canonry schedule <set|show|enable|disable|remove> <project>",
12999
- available: ["set", "show", "enable", "disable", "remove"]
13045
+ usage: "canonry schedule <list|set|show|enable|disable|remove> <project>",
13046
+ available: ["list", "set", "show", "enable", "disable", "remove"]
13000
13047
  });
13001
13048
  }
13002
13049
  }
@@ -13024,15 +13071,9 @@ async function setProvider(name, opts) {
13024
13071
  }
13025
13072
  async function showSettings(format) {
13026
13073
  const client = getClient25();
13027
- const config = loadConfig();
13028
13074
  const settings = await client.getSettings();
13029
13075
  if (isMachineFormat(format)) {
13030
- console.log(JSON.stringify({
13031
- ...settings,
13032
- google: {
13033
- configured: Boolean(config.google?.clientId && config.google?.clientSecret)
13034
- }
13035
- }, null, 2));
13076
+ console.log(JSON.stringify(settings, null, 2));
13036
13077
  return;
13037
13078
  }
13038
13079
  console.log("Provider settings:\n");
@@ -13048,9 +13089,21 @@ async function showSettings(format) {
13048
13089
  }
13049
13090
  }
13050
13091
  console.log("\nGoogle OAuth:\n");
13051
- console.log(` ${config.google?.clientId && config.google?.clientSecret ? "configured" : "not configured"}`);
13092
+ console.log(` ${settings.google.configured ? "configured" : "not configured"}`);
13052
13093
  }
13053
- function setGoogleAuth(opts) {
13094
+ async function setGoogleAuth(opts) {
13095
+ if (opts.target === "server") {
13096
+ const result = await getClient25().updateGoogleSettings({
13097
+ clientId: opts.clientId,
13098
+ clientSecret: opts.clientSecret
13099
+ });
13100
+ if (isMachineFormat(opts.format)) {
13101
+ console.log(JSON.stringify(result, null, 2));
13102
+ return;
13103
+ }
13104
+ console.log("Google OAuth credentials updated on the server.");
13105
+ return;
13106
+ }
13054
13107
  const config = loadConfig();
13055
13108
  setGoogleAuthConfig(config, {
13056
13109
  clientId: opts.clientId,
@@ -13090,8 +13143,25 @@ var SETTINGS_CLI_COMMANDS = [
13090
13143
  });
13091
13144
  const apiKey = getString(input.values, "api-key");
13092
13145
  const baseUrl = getString(input.values, "base-url");
13146
+ const model = getString(input.values, "model");
13147
+ const maxConcurrency = parseIntegerOption(input, "max-concurrent", {
13148
+ command: "settings.provider",
13149
+ 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]",
13150
+ message: "--max-concurrent must be an integer"
13151
+ });
13152
+ const maxRequestsPerMinute = parseIntegerOption(input, "max-per-minute", {
13153
+ command: "settings.provider",
13154
+ 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]",
13155
+ message: "--max-per-minute must be an integer"
13156
+ });
13157
+ const maxRequestsPerDay = parseIntegerOption(input, "max-per-day", {
13158
+ command: "settings.provider",
13159
+ 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]",
13160
+ message: "--max-per-day must be an integer"
13161
+ });
13162
+ const hasNonCredentialEdit = Boolean(model) || maxConcurrency != null || maxRequestsPerMinute != null || maxRequestsPerDay != null;
13093
13163
  if (name === "local") {
13094
- if (!baseUrl) {
13164
+ if (!baseUrl && !hasNonCredentialEdit) {
13095
13165
  throw usageError(
13096
13166
  "Error: --base-url is required for the local provider\nUsage: canonry settings provider local --base-url <url> [--api-key <key>] [--model <model>] [--max-concurrent <n>] [--max-per-minute <n>] [--max-per-day <n>] [--format json]",
13097
13167
  {
@@ -13104,7 +13174,7 @@ var SETTINGS_CLI_COMMANDS = [
13104
13174
  }
13105
13175
  );
13106
13176
  }
13107
- } else if (!apiKey) {
13177
+ } else if (!apiKey && !baseUrl && !hasNonCredentialEdit) {
13108
13178
  throw usageError(
13109
13179
  `Error: --api-key is required
13110
13180
  Usage: canonry settings provider ${name} --api-key <key> [--model <model>] [--max-concurrent <n>] [--max-per-minute <n>] [--max-per-day <n>] [--format json]`,
@@ -13118,21 +13188,6 @@ Usage: canonry settings provider ${name} --api-key <key> [--model <model>] [--ma
13118
13188
  }
13119
13189
  );
13120
13190
  }
13121
- const maxConcurrency = parseIntegerOption(input, "max-concurrent", {
13122
- command: "settings.provider",
13123
- 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]",
13124
- message: "--max-concurrent must be an integer"
13125
- });
13126
- const maxRequestsPerMinute = parseIntegerOption(input, "max-per-minute", {
13127
- command: "settings.provider",
13128
- 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]",
13129
- message: "--max-per-minute must be an integer"
13130
- });
13131
- const maxRequestsPerDay = parseIntegerOption(input, "max-per-day", {
13132
- command: "settings.provider",
13133
- 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]",
13134
- message: "--max-per-day must be an integer"
13135
- });
13136
13191
  const quota = maxConcurrency != null || maxRequestsPerMinute != null || maxRequestsPerDay != null ? {
13137
13192
  ...maxConcurrency != null ? { maxConcurrency } : {},
13138
13193
  ...maxRequestsPerMinute != null ? { maxRequestsPerMinute } : {},
@@ -13141,7 +13196,7 @@ Usage: canonry settings provider ${name} --api-key <key> [--model <model>] [--ma
13141
13196
  await setProvider(name, {
13142
13197
  apiKey,
13143
13198
  baseUrl,
13144
- model: getString(input.values, "model"),
13199
+ model,
13145
13200
  quota,
13146
13201
  format: input.format
13147
13202
  });
@@ -13149,10 +13204,11 @@ Usage: canonry settings provider ${name} --api-key <key> [--model <model>] [--ma
13149
13204
  },
13150
13205
  {
13151
13206
  path: ["settings", "google"],
13152
- usage: "canonry settings google --client-id <id> --client-secret <secret> [--format json]",
13207
+ usage: "canonry settings google --client-id <id> --client-secret <secret> [--target local|server] [--format json]",
13153
13208
  options: {
13154
13209
  "client-id": stringOption(),
13155
- "client-secret": stringOption()
13210
+ "client-secret": stringOption(),
13211
+ target: stringOption()
13156
13212
  },
13157
13213
  run: async (input) => {
13158
13214
  const clientId = getString(input.values, "client-id");
@@ -13170,9 +13226,25 @@ Usage: canonry settings provider ${name} --api-key <key> [--model <model>] [--ma
13170
13226
  }
13171
13227
  );
13172
13228
  }
13173
- setGoogleAuth({
13229
+ const target = getString(input.values, "target") ?? "local";
13230
+ if (target !== "local" && target !== "server") {
13231
+ throw usageError(
13232
+ "Error: invalid --target; must be local or server\nUsage: canonry settings google --client-id <id> --client-secret <secret> [--target local|server] [--format json]",
13233
+ {
13234
+ message: "invalid --target; must be local or server",
13235
+ details: {
13236
+ command: "settings.google",
13237
+ usage: "canonry settings google --client-id <id> --client-secret <secret> [--target local|server] [--format json]",
13238
+ option: "target",
13239
+ value: target
13240
+ }
13241
+ }
13242
+ );
13243
+ }
13244
+ await setGoogleAuth({
13174
13245
  clientId,
13175
13246
  clientSecret,
13247
+ target,
13176
13248
  format: input.format
13177
13249
  });
13178
13250
  }
@@ -13588,6 +13660,8 @@ async function createSnapshotReport(companyName, opts) {
13588
13660
  const report = await client.createSnapshot({
13589
13661
  companyName,
13590
13662
  domain: opts.domain,
13663
+ ...opts.providers !== void 0 ? { providers: opts.providers } : {},
13664
+ ...opts.providerMode !== void 0 ? { providerMode: opts.providerMode } : {},
13591
13665
  ...opts.queries && opts.queries.length > 0 ? { queries: opts.queries } : {},
13592
13666
  ...opts.competitors && opts.competitors.length > 0 ? { competitors: opts.competitors } : {}
13593
13667
  });
@@ -13754,6 +13828,7 @@ function formatProviderLine(result) {
13754
13828
  }
13755
13829
 
13756
13830
  // src/cli-commands/snapshot.ts
13831
+ var SNAPSHOT_USAGE = 'canonry snapshot <company-name> --domain <domain> [--provider <name>...] [--provider-mode all|api|browser] [--queries "a,b"] [--phrases "a,b" (legacy alias)] [--competitors "x,y"] [--md] [--output <path>] [--pdf] [--format table|json]';
13757
13832
  function parseCsvOption(value) {
13758
13833
  if (!value) return void 0;
13759
13834
  const parts = value.split(",").map((part) => part.trim()).filter(Boolean);
@@ -13762,9 +13837,11 @@ function parseCsvOption(value) {
13762
13837
  var SNAPSHOT_CLI_COMMANDS = [
13763
13838
  {
13764
13839
  path: ["snapshot"],
13765
- usage: 'canonry snapshot <company-name> --domain <domain> [--queries "a,b"] [--phrases "a,b" (legacy alias)] [--competitors "x,y"] [--md] [--output <path>] [--pdf] [--format table|json]',
13840
+ usage: SNAPSHOT_USAGE,
13766
13841
  options: {
13767
13842
  domain: stringOption(),
13843
+ provider: multiStringOption(),
13844
+ "provider-mode": stringOption(),
13768
13845
  queries: stringOption(),
13769
13846
  phrases: stringOption(),
13770
13847
  competitors: stringOption(),
@@ -13773,7 +13850,7 @@ var SNAPSHOT_CLI_COMMANDS = [
13773
13850
  output: stringOption()
13774
13851
  },
13775
13852
  run: async (input) => {
13776
- const usage = 'canonry snapshot <company-name> --domain <domain> [--queries "a,b"] [--phrases "a,b" (legacy alias)] [--competitors "x,y"] [--md] [--output <path>] [--pdf] [--format table|json]';
13853
+ const usage = SNAPSHOT_USAGE;
13777
13854
  const companyName = requirePositional(input, 0, {
13778
13855
  command: "snapshot",
13779
13856
  usage,
@@ -13784,12 +13861,23 @@ var SNAPSHOT_CLI_COMMANDS = [
13784
13861
  usage,
13785
13862
  message: "--domain is required"
13786
13863
  });
13864
+ const rawMode = getString(input.values, "provider-mode");
13865
+ const mode = snapshotProviderModeSchema.optional().safeParse(rawMode);
13866
+ if (!mode.success) {
13867
+ throw usageError("Error: --provider-mode must be all, api, or browser", {
13868
+ details: { command: "snapshot", usage }
13869
+ });
13870
+ }
13871
+ const providers = getStringArray(input.values, "provider")?.map((name) => name.trim());
13872
+ if (providers?.some((name) => !name)) throw usageError("Error: --provider must not be blank");
13787
13873
  const outputPath = getString(input.values, "output");
13788
13874
  const explicitMd = getBoolean(input.values, "md");
13789
13875
  const wantsPdf = getBoolean(input.values, "pdf");
13790
13876
  const wantsMd = explicitMd || !!outputPath && !wantsPdf;
13791
13877
  await createSnapshotReport(companyName, {
13792
13878
  domain,
13879
+ providers: providers === void 0 ? void 0 : [...new Set(providers)],
13880
+ providerMode: mode.data,
13793
13881
  queries: parseCsvOption(getString(input.values, "queries") ?? getString(input.values, "phrases")),
13794
13882
  competitors: parseCsvOption(getString(input.values, "competitors")),
13795
13883
  md: wantsMd,
@@ -15645,131 +15733,117 @@ Canonry server running at ${url}`);
15645
15733
  }
15646
15734
 
15647
15735
  // src/commands/telemetry.ts
15648
- function telemetryCommand(subcommand, format = "text") {
15736
+ function telemetryTarget(value) {
15737
+ if (value === void 0 || value === "local") return "local";
15738
+ if (value === "server") return "server";
15739
+ throw usageError('Error: --target must be "local" or "server"', {
15740
+ message: '--target must be "local" or "server"',
15741
+ details: { command: "telemetry", target: value, allowed: ["local", "server"] }
15742
+ });
15743
+ }
15744
+ function emitStatus(payload, format, mutation = false) {
15745
+ if (isMachineFormat(format)) {
15746
+ console.log(JSON.stringify(payload, null, 2));
15747
+ return;
15748
+ }
15749
+ const targetSuffix = payload.target === "server" ? " (server)" : "";
15750
+ if (mutation && payload.enabled === payload.configuredEnabled) {
15751
+ console.log(`Telemetry${targetSuffix} ${payload.enabled ? "enabled." : "disabled. No data will be sent."}`);
15752
+ } else if (payload.reason === "CANONRY_TELEMETRY_DISABLED") {
15753
+ console.log(`Telemetry${targetSuffix}: disabled (CANONRY_TELEMETRY_DISABLED=1)`);
15754
+ } else if (payload.reason === "DO_NOT_TRACK") {
15755
+ console.log(`Telemetry${targetSuffix}: disabled (DO_NOT_TRACK=1)`);
15756
+ } else if (payload.reason === "CI") {
15757
+ console.log(`Telemetry${targetSuffix}: disabled (CI environment detected)`);
15758
+ } else if (payload.reason === "NO_CONFIG") {
15759
+ console.log(`Telemetry${targetSuffix}: enabled (no config yet \u2014 run "canonry bootstrap" first)`);
15760
+ } else {
15761
+ console.log(`Telemetry${targetSuffix}: ${payload.enabled ? "enabled" : "disabled"}`);
15762
+ }
15763
+ if (payload.enabled !== payload.configuredEnabled) {
15764
+ console.log(`Configured telemetry: ${payload.configuredEnabled ? "enabled" : "disabled"}; effective state overridden by ${payload.reason}.`);
15765
+ }
15766
+ if (payload.anonymousId) console.log(`Anonymous ID: ${payload.anonymousId}`);
15767
+ }
15768
+ function localStatus() {
15769
+ const status = getTelemetryStatus();
15770
+ return {
15771
+ ...status,
15772
+ ...status.anonymousId ? { anonymousIdMasked: status.anonymousId } : {},
15773
+ ...configExists() ? { configPath: getConfigPath() } : {}
15774
+ };
15775
+ }
15776
+ function requireLocalConfig(command) {
15777
+ if (!configExists()) {
15778
+ throw new CliError({
15779
+ code: "CONFIG_REQUIRED",
15780
+ message: 'No config found. Run "canonry bootstrap" first.',
15781
+ displayMessage: 'No config found. Run "canonry bootstrap" first.',
15782
+ details: { command }
15783
+ });
15784
+ }
15785
+ }
15786
+ async function serverTelemetryCommand(subcommand, format) {
15787
+ const client = createApiClient();
15788
+ const response = subcommand === "status" ? await client.getTelemetry() : await client.updateTelemetry(subcommand === "enable");
15789
+ emitStatus(normalizeTelemetryStatus(response), format, subcommand !== "status");
15790
+ }
15791
+ function telemetryCommand(subcommand, format = "text", targetValue) {
15649
15792
  const available = ["status", "enable", "disable"];
15793
+ const target = telemetryTarget(targetValue);
15794
+ if (target === "server") {
15795
+ if (subcommand === "status" || subcommand === "enable" || subcommand === "disable") {
15796
+ return serverTelemetryCommand(subcommand, format);
15797
+ }
15798
+ }
15650
15799
  switch (subcommand) {
15651
15800
  case "status": {
15652
- const payload = {
15653
- enabled: true
15654
- };
15655
- if (process.env.CANONRY_TELEMETRY_DISABLED === "1") {
15656
- payload.enabled = false;
15657
- payload.reason = "CANONRY_TELEMETRY_DISABLED";
15658
- if (isMachineFormat(format)) {
15659
- console.log(JSON.stringify(payload, null, 2));
15660
- return;
15661
- }
15662
- console.log("Telemetry: disabled (CANONRY_TELEMETRY_DISABLED=1)");
15663
- return;
15664
- }
15665
- if (process.env.DO_NOT_TRACK === "1") {
15666
- payload.enabled = false;
15667
- payload.reason = "DO_NOT_TRACK";
15668
- if (isMachineFormat(format)) {
15669
- console.log(JSON.stringify(payload, null, 2));
15670
- return;
15671
- }
15672
- console.log("Telemetry: disabled (DO_NOT_TRACK=1)");
15673
- return;
15674
- }
15675
- if (process.env.CI) {
15676
- payload.enabled = false;
15677
- payload.reason = "CI";
15678
- if (isMachineFormat(format)) {
15679
- console.log(JSON.stringify(payload, null, 2));
15680
- return;
15681
- }
15682
- console.log("Telemetry: disabled (CI environment detected)");
15683
- return;
15684
- }
15685
- if (!configExists()) {
15686
- payload.reason = "NO_CONFIG";
15687
- if (isMachineFormat(format)) {
15688
- console.log(JSON.stringify(payload, null, 2));
15689
- return;
15690
- }
15691
- console.log('Telemetry: enabled (no config yet \u2014 run "canonry bootstrap" first)');
15692
- return;
15693
- }
15694
- const config = loadConfig();
15695
- payload.enabled = config.telemetry !== false;
15696
- payload.configPath = getConfigPath();
15697
- if (config.anonymousId) {
15698
- payload.anonymousIdMasked = config.anonymousId.slice(0, 8) + "...";
15699
- }
15700
- if (isMachineFormat(format)) {
15701
- console.log(JSON.stringify(payload, null, 2));
15702
- return;
15703
- }
15704
- const enabled = payload.enabled;
15705
- console.log(`Telemetry: ${enabled ? "enabled" : "disabled"}`);
15706
- if (config.anonymousId) {
15707
- const masked = config.anonymousId.slice(0, 8) + "...";
15708
- console.log(`Anonymous ID: ${masked}`);
15709
- }
15801
+ emitStatus(localStatus(), format);
15710
15802
  break;
15711
15803
  }
15712
15804
  case "enable": {
15713
- if (!configExists()) {
15714
- throw new CliError({
15715
- code: "CONFIG_REQUIRED",
15716
- message: 'No config found. Run "canonry bootstrap" first.',
15717
- displayMessage: 'No config found. Run "canonry bootstrap" first.',
15718
- details: {
15719
- command: "telemetry.enable"
15720
- }
15721
- });
15722
- }
15805
+ requireLocalConfig("telemetry.enable");
15723
15806
  const config = loadConfig();
15724
15807
  config.telemetry = true;
15725
15808
  saveConfigPatch(config);
15726
- if (isMachineFormat(format)) {
15727
- console.log(JSON.stringify({
15728
- enabled: true,
15729
- configPath: getConfigPath()
15730
- }, null, 2));
15731
- return;
15732
- }
15733
- console.log("Telemetry enabled.");
15809
+ emitStatus(localStatus(), format, true);
15734
15810
  break;
15735
15811
  }
15736
15812
  case "disable": {
15737
- if (!configExists()) {
15738
- throw new CliError({
15739
- code: "CONFIG_REQUIRED",
15740
- message: 'No config found. Run "canonry bootstrap" first.',
15741
- displayMessage: 'No config found. Run "canonry bootstrap" first.',
15742
- details: {
15743
- command: "telemetry.disable"
15744
- }
15745
- });
15746
- }
15813
+ requireLocalConfig("telemetry.disable");
15747
15814
  const config = loadConfig();
15748
15815
  config.telemetry = false;
15749
15816
  saveConfigPatch(config);
15750
- if (isMachineFormat(format)) {
15751
- console.log(JSON.stringify({
15752
- enabled: false,
15753
- configPath: getConfigPath()
15754
- }, null, 2));
15755
- return;
15756
- }
15757
- console.log("Telemetry disabled. No data will be sent.");
15817
+ emitStatus(localStatus(), format, true);
15758
15818
  break;
15759
15819
  }
15760
15820
  default:
15761
15821
  throw usageError(`Error: unknown telemetry subcommand: ${subcommand ?? "(none)"}
15762
- Usage: canonry telemetry <status|enable|disable> [--format json]`, {
15822
+ Usage: canonry telemetry <status|enable|disable> [--target local|server] [--format json]`, {
15763
15823
  message: `unknown telemetry subcommand: ${subcommand ?? "(none)"}`,
15764
15824
  details: {
15765
15825
  command: "telemetry",
15766
- usage: "canonry telemetry <status|enable|disable> [--format json]",
15826
+ usage: "canonry telemetry <status|enable|disable> [--target local|server] [--format json]",
15767
15827
  available
15768
15828
  }
15769
15829
  });
15770
15830
  }
15771
15831
  }
15772
15832
 
15833
+ // src/commands/logs.ts
15834
+ async function showOperationalLogs(query, format) {
15835
+ const parsed = logQuerySchema.safeParse(query);
15836
+ if (!parsed.success) throw usageError("Invalid logs filters", { details: { issues: parsed.error.issues } });
15837
+ const result = await createApiClient().listOperationalLogs(parsed.data);
15838
+ if (isMachineFormat(format)) {
15839
+ console.log(JSON.stringify(result, null, 2));
15840
+ return;
15841
+ }
15842
+ console.log(`Runtime logs (${result.retention}; ${result.entries.length} entries; ${result.dropped} evicted; ${result.captureErrors ?? 0} capture errors). Separate from audit history.`);
15843
+ for (const entry of result.entries) console.log(`${entry.ts} ${entry.level} [${entry.module}] ${entry.action}${entry.message ? ` ${entry.message}` : ""} ${JSON.stringify(entry.context)}`);
15844
+ if (result.nextCursor) console.log(`Next cursor: ${result.nextCursor}`);
15845
+ }
15846
+
15773
15847
  // src/cli-commands/system.ts
15774
15848
  function applyServerEnv(values) {
15775
15849
  const port = typeof values.port === "string" ? values.port : void 0;
@@ -15788,6 +15862,25 @@ function applyServerEnv(values) {
15788
15862
  process.env.CANONRY_EMBED_PROJECT_TABS = embedProjectTabs.join(",");
15789
15863
  }
15790
15864
  var SYSTEM_CLI_COMMANDS = [
15865
+ {
15866
+ path: ["logs"],
15867
+ usage: "canonry logs [--level trace|debug|info|warn|error|fatal] [--module <name>] [--run-id <id>] [--project-id <id>] [--actor <user:id|api-key:id>] [--request-id <id>] [--since <ISO>] [--until <ISO>] [--limit <1..200>] [--cursor <cursor>] [--format json|jsonl]",
15868
+ help: "Read bounded, redacted runtime logs. Inspect retention and captureErrors for coverage. Requires an instance-wide logs.read grant; project filters do not grant access. Use history for audit events.",
15869
+ options: { level: stringOption(), module: stringOption(), "run-id": stringOption(), "project-id": stringOption(), actor: stringOption(), "request-id": stringOption(), since: stringOption(), until: stringOption(), limit: stringOption(), cursor: stringOption() },
15870
+ allowPositionals: false,
15871
+ run: async (input) => showOperationalLogs({
15872
+ level: getString(input.values, "level"),
15873
+ module: getString(input.values, "module"),
15874
+ runId: getString(input.values, "run-id"),
15875
+ projectId: getString(input.values, "project-id"),
15876
+ actor: getString(input.values, "actor"),
15877
+ requestId: getString(input.values, "request-id"),
15878
+ since: getString(input.values, "since"),
15879
+ until: getString(input.values, "until"),
15880
+ limit: getString(input.values, "limit"),
15881
+ cursor: getString(input.values, "cursor")
15882
+ }, input.format)
15883
+ },
15791
15884
  {
15792
15885
  path: ["init"],
15793
15886
  usage: "canonry init [--force] [--gemini-key <key>] [--openai-key <key>] [--claude-key <key>] [--perplexity-key <key>] [--local-url <url>] [--local-model <name>] [--local-key <key>] [--google-client-id <id>] [--google-client-secret <key>] [--skip-skills] [--skip-mcp] [--skills-dir <path>] [--format json]",
@@ -15896,35 +15989,38 @@ var SYSTEM_CLI_COMMANDS = [
15896
15989
  },
15897
15990
  {
15898
15991
  path: ["telemetry", "status"],
15899
- usage: "canonry telemetry status [--format json]",
15992
+ usage: "canonry telemetry status [--target local|server] [--format json]",
15993
+ options: { target: stringOption() },
15900
15994
  allowPositionals: false,
15901
- run: (input) => {
15902
- telemetryCommand("status", input.format);
15995
+ run: async (input) => {
15996
+ await telemetryCommand("status", input.format, getString(input.values, "target"));
15903
15997
  }
15904
15998
  },
15905
15999
  {
15906
16000
  path: ["telemetry", "enable"],
15907
- usage: "canonry telemetry enable [--format json]",
16001
+ usage: "canonry telemetry enable [--target local|server] [--format json]",
16002
+ options: { target: stringOption() },
15908
16003
  allowPositionals: false,
15909
- run: (input) => {
15910
- telemetryCommand("enable", input.format);
16004
+ run: async (input) => {
16005
+ await telemetryCommand("enable", input.format, getString(input.values, "target"));
15911
16006
  }
15912
16007
  },
15913
16008
  {
15914
16009
  path: ["telemetry", "disable"],
15915
- usage: "canonry telemetry disable [--format json]",
16010
+ usage: "canonry telemetry disable [--target local|server] [--format json]",
16011
+ options: { target: stringOption() },
15916
16012
  allowPositionals: false,
15917
- run: (input) => {
15918
- telemetryCommand("disable", input.format);
16013
+ run: async (input) => {
16014
+ await telemetryCommand("disable", input.format, getString(input.values, "target"));
15919
16015
  }
15920
16016
  },
15921
16017
  {
15922
16018
  path: ["telemetry"],
15923
- usage: "canonry telemetry <status|enable|disable> [--format json]",
16019
+ usage: "canonry telemetry <status|enable|disable> [--target local|server] [--format json]",
15924
16020
  run: (input) => {
15925
16021
  unknownSubcommand(input.positionals[0], {
15926
16022
  command: "telemetry",
15927
- usage: "canonry telemetry <status|enable|disable> [--format json]",
16023
+ usage: "canonry telemetry <status|enable|disable> [--target local|server] [--format json]",
15928
16024
  available: ["status", "enable", "disable"]
15929
16025
  });
15930
16026
  }