@canonry/canonry 4.117.2 → 4.118.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 (30) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +53 -1
  2. package/assets/assets/AuditHistoryPanel-Yh7VEe4P.js +1 -0
  3. package/assets/assets/{BacklinksPage-CschVQxC.js → BacklinksPage-BmO77kMs.js} +1 -1
  4. package/assets/assets/{ChartPrimitives-D4782Ifx.js → ChartPrimitives-H0ICVpw_.js} +1 -1
  5. package/assets/assets/HistoryPage-DkLV_iRc.js +1 -0
  6. package/assets/assets/ProjectPage-R3odr_eA.js +7 -0
  7. package/assets/assets/{RunRow-ggNGlwdN.js → RunRow-BDH9agxQ.js} +1 -1
  8. package/assets/assets/RunsPage-D9YsEeT3.js +1 -0
  9. package/assets/assets/{SettingsPage-Cy9SlBJX.js → SettingsPage-o5as3Ixw.js} +1 -1
  10. package/assets/assets/{TrafficPage-1S2PzSHB.js → TrafficPage-BCK203aQ.js} +1 -1
  11. package/assets/assets/{TrafficSourceDetailPage-CqXCoaod.js → TrafficSourceDetailPage-DsVMAZvS.js} +1 -1
  12. package/assets/assets/{arrow-left-kCK5M8Bp.js → arrow-left-B7ZR6VC9.js} +1 -1
  13. package/assets/assets/{extract-error-message-C82LAW6q.js → extract-error-message-DeYDUpew.js} +1 -1
  14. package/assets/assets/index-Bot6YiD6.css +1 -0
  15. package/assets/assets/index-QCFQv0xh.js +210 -0
  16. package/assets/assets/{trash-2-8Pe4v1T0.js → trash-2-xxHTqWbF.js} +1 -1
  17. package/assets/index.html +2 -2
  18. package/dist/{chunk-4PMMEFME.js → chunk-75UBTCKS.js} +499 -16
  19. package/dist/{chunk-BMPIV35C.js → chunk-CMZIPFNQ.js} +937 -104
  20. package/dist/{chunk-F2SRZJMY.js → chunk-HGNY5GL4.js} +384 -15
  21. package/dist/{chunk-EZME2J2G.js → chunk-KEZWLP4O.js} +1196 -1012
  22. package/dist/cli.js +319 -86
  23. package/dist/index.js +4 -4
  24. package/dist/{intelligence-service-GNOE4J35.js → intelligence-service-FWPXMAVK.js} +2 -2
  25. package/dist/mcp.js +2 -2
  26. package/package.json +8 -8
  27. package/assets/assets/ProjectPage-BSGTpBUJ.js +0 -7
  28. package/assets/assets/RunsPage-Bfp0K6Ry.js +0 -1
  29. package/assets/assets/index-DrDNyUhv.css +0 -1
  30. package/assets/assets/index-i-t64RXw.js +0 -210
package/dist/cli.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  setTelemetrySource,
30
30
  showFirstRunNotice,
31
31
  trackEvent
32
- } from "./chunk-F2SRZJMY.js";
32
+ } from "./chunk-HGNY5GL4.js";
33
33
  import {
34
34
  CliError,
35
35
  EXIT_SYSTEM_ERROR,
@@ -46,7 +46,7 @@ import {
46
46
  saveConfig,
47
47
  saveConfigPatch,
48
48
  usageError
49
- } from "./chunk-4PMMEFME.js";
49
+ } from "./chunk-75UBTCKS.js";
50
50
  import {
51
51
  apiKeys,
52
52
  createClient,
@@ -54,7 +54,7 @@ import {
54
54
  projects,
55
55
  queries,
56
56
  renderReportHtml
57
- } from "./chunk-BMPIV35C.js";
57
+ } from "./chunk-CMZIPFNQ.js";
58
58
  import {
59
59
  BacklinkSources,
60
60
  CcReleaseSyncStatuses,
@@ -65,6 +65,14 @@ import {
65
65
  REPORT_PERIOD_OPTIONS,
66
66
  RunStatuses,
67
67
  TrafficEventKinds,
68
+ adsAdCreateRequestSchema,
69
+ adsAdGroupCreateRequestSchema,
70
+ adsAdGroupUpdateRequestSchema,
71
+ adsAdUpdateRequestSchema,
72
+ adsCampaignCreateRequestSchema,
73
+ adsCampaignUpdateRequestSchema,
74
+ adsImageUploadRequestSchema,
75
+ adsPauseRequestSchema,
68
76
  backlinkSourceSchema,
69
77
  discoveryBucketSchema,
70
78
  discoveryCompetitorTypeSchema,
@@ -80,7 +88,7 @@ import {
80
88
  providerQuotaPolicySchema,
81
89
  resolveProviderInput,
82
90
  winnabilityClassSchema
83
- } from "./chunk-EZME2J2G.js";
91
+ } from "./chunk-KEZWLP4O.js";
84
92
 
85
93
  // src/cli.ts
86
94
  import { pathToFileURL } from "url";
@@ -2713,9 +2721,46 @@ var DOCTOR_CLI_COMMANDS = [
2713
2721
  ];
2714
2722
 
2715
2723
  // src/commands/ads.ts
2724
+ import fs3 from "fs";
2716
2725
  function getClient5() {
2717
2726
  return createApiClient();
2718
2727
  }
2728
+ function readRequest(inputPath, schema) {
2729
+ if (!inputPath) {
2730
+ throw new CliError({
2731
+ code: "ADS_INPUT_REQUIRED",
2732
+ message: "A JSON input file is required",
2733
+ displayMessage: "Error: --input <json-file> is required (use --input - for stdin)"
2734
+ });
2735
+ }
2736
+ try {
2737
+ const raw = fs3.readFileSync(inputPath === "-" ? 0 : inputPath, "utf8");
2738
+ return schema.parse(JSON.parse(raw));
2739
+ } catch (err) {
2740
+ if (err instanceof CliError) throw err;
2741
+ throw new CliError({
2742
+ code: "ADS_INPUT_INVALID",
2743
+ message: err instanceof Error ? err.message : String(err),
2744
+ displayMessage: `Error: invalid ads JSON input (${err instanceof Error ? err.message : String(err)})`,
2745
+ details: { inputPath }
2746
+ });
2747
+ }
2748
+ }
2749
+ function printOperation(result, format) {
2750
+ if (isMachineFormat(format)) {
2751
+ console.log(JSON.stringify(result, null, 2));
2752
+ return;
2753
+ }
2754
+ const operation = result.operation;
2755
+ console.log(`${result.replayed ? "Replayed" : "Recorded"} ${operation.kind}: ${operation.state}`);
2756
+ console.log(`Operation: ${operation.operationKey}`);
2757
+ if (operation.entityId) console.log(`Entity: ${operation.entityType ?? "unknown"} ${operation.entityId}`);
2758
+ if (operation.upstreamUpdatedAt != null) console.log(`Updated: ${operation.upstreamUpdatedAt}`);
2759
+ if (operation.errorCode) console.log(`Error: ${operation.errorCode}: ${operation.errorMessage ?? ""}`);
2760
+ if (operation.state === "unknown") {
2761
+ console.log("Do not retry with a new operation key. Reconcile this outcome with a human first.");
2762
+ }
2763
+ }
2719
2764
  function describeConnection(status) {
2720
2765
  const lines = [];
2721
2766
  lines.push(`Connected: ${status.connected ? "yes" : "no"}`);
@@ -2768,6 +2813,49 @@ async function adsStatus(project, opts) {
2768
2813
  console.log("Connect with: canonry ads connect " + project + " --api-key <sdk-key>");
2769
2814
  }
2770
2815
  }
2816
+ async function adsOperationGet(project, opts) {
2817
+ printOperation(await getClient5().getAdsOperation(project, opts.operationKey), opts.format);
2818
+ }
2819
+ async function adsImageUpload(project, opts) {
2820
+ const request = readRequest(opts.input, adsImageUploadRequestSchema);
2821
+ printOperation(await getClient5().uploadAdsImage(project, request), opts.format);
2822
+ }
2823
+ async function adsCampaignCreate(project, opts) {
2824
+ const request = readRequest(opts.input, adsCampaignCreateRequestSchema);
2825
+ printOperation(await getClient5().createAdsCampaign(project, request), opts.format);
2826
+ }
2827
+ async function adsCampaignUpdate(project, campaignId, opts) {
2828
+ const request = readRequest(opts.input, adsCampaignUpdateRequestSchema);
2829
+ printOperation(await getClient5().updateAdsCampaign(project, campaignId, request), opts.format);
2830
+ }
2831
+ async function adsCampaignPause(project, campaignId, opts) {
2832
+ const request = readRequest(opts.input, adsPauseRequestSchema);
2833
+ printOperation(await getClient5().pauseAdsCampaign(project, campaignId, request), opts.format);
2834
+ }
2835
+ async function adsAdGroupCreate(project, opts) {
2836
+ const request = readRequest(opts.input, adsAdGroupCreateRequestSchema);
2837
+ printOperation(await getClient5().createAdsAdGroup(project, request), opts.format);
2838
+ }
2839
+ async function adsAdGroupUpdate(project, adGroupId, opts) {
2840
+ const request = readRequest(opts.input, adsAdGroupUpdateRequestSchema);
2841
+ printOperation(await getClient5().updateAdsAdGroup(project, adGroupId, request), opts.format);
2842
+ }
2843
+ async function adsAdGroupPause(project, adGroupId, opts) {
2844
+ const request = readRequest(opts.input, adsPauseRequestSchema);
2845
+ printOperation(await getClient5().pauseAdsAdGroup(project, adGroupId, request), opts.format);
2846
+ }
2847
+ async function adsAdCreate(project, opts) {
2848
+ const request = readRequest(opts.input, adsAdCreateRequestSchema);
2849
+ printOperation(await getClient5().createAdsAd(project, request), opts.format);
2850
+ }
2851
+ async function adsAdUpdate(project, adId, opts) {
2852
+ const request = readRequest(opts.input, adsAdUpdateRequestSchema);
2853
+ printOperation(await getClient5().updateAdsAd(project, adId, request), opts.format);
2854
+ }
2855
+ async function adsAdPause(project, adId, opts) {
2856
+ const request = readRequest(opts.input, adsPauseRequestSchema);
2857
+ printOperation(await getClient5().pauseAdsAd(project, adId, request), opts.format);
2858
+ }
2771
2859
  async function adsSync(project, opts) {
2772
2860
  const client = getClient5();
2773
2861
  const result = await client.triggerAdsSync(project);
@@ -2885,6 +2973,122 @@ var ADS_CLI_COMMANDS = [
2885
2973
  await adsStatus(project, { format: input.format });
2886
2974
  }
2887
2975
  },
2976
+ {
2977
+ path: ["ads", "operation"],
2978
+ usage: "canonry ads operation <project> <operation-key> [--format json]",
2979
+ run: async (input) => {
2980
+ const usage = "canonry ads operation <project> <operation-key> [--format json]";
2981
+ const project = requireProject(input, "ads.operation", usage);
2982
+ const operationKey = requirePositional(input, 1, {
2983
+ command: "ads.operation",
2984
+ usage,
2985
+ message: "operation key is required"
2986
+ });
2987
+ await adsOperationGet(project, { operationKey, format: input.format });
2988
+ }
2989
+ },
2990
+ {
2991
+ path: ["ads", "image", "upload"],
2992
+ usage: "canonry ads image upload <project> --input <json-file|-> [--format json]",
2993
+ options: { input: stringOption() },
2994
+ run: async (input) => {
2995
+ const project = requireProject(input, "ads.image.upload", "canonry ads image upload <project> --input <json-file|-> [--format json]");
2996
+ await adsImageUpload(project, { input: getString(input.values, "input"), format: input.format });
2997
+ }
2998
+ },
2999
+ {
3000
+ path: ["ads", "campaign", "create"],
3001
+ usage: "canonry ads campaign create <project> --input <json-file|-> [--format json]",
3002
+ options: { input: stringOption() },
3003
+ run: async (input) => {
3004
+ const project = requireProject(input, "ads.campaign.create", "canonry ads campaign create <project> --input <json-file|-> [--format json]");
3005
+ await adsCampaignCreate(project, { input: getString(input.values, "input"), format: input.format });
3006
+ }
3007
+ },
3008
+ {
3009
+ path: ["ads", "campaign", "update"],
3010
+ usage: "canonry ads campaign update <project> <campaign-id> --input <json-file|-> [--format json]",
3011
+ options: { input: stringOption() },
3012
+ run: async (input) => {
3013
+ const usage = "canonry ads campaign update <project> <campaign-id> --input <json-file|-> [--format json]";
3014
+ const project = requireProject(input, "ads.campaign.update", usage);
3015
+ const id = requirePositional(input, 1, { command: "ads.campaign.update", usage, message: "campaign id is required" });
3016
+ await adsCampaignUpdate(project, id, { input: getString(input.values, "input"), format: input.format });
3017
+ }
3018
+ },
3019
+ {
3020
+ path: ["ads", "campaign", "pause"],
3021
+ usage: "canonry ads campaign pause <project> <campaign-id> --input <json-file|-> [--format json]",
3022
+ options: { input: stringOption() },
3023
+ run: async (input) => {
3024
+ const usage = "canonry ads campaign pause <project> <campaign-id> --input <json-file|-> [--format json]";
3025
+ const project = requireProject(input, "ads.campaign.pause", usage);
3026
+ const id = requirePositional(input, 1, { command: "ads.campaign.pause", usage, message: "campaign id is required" });
3027
+ await adsCampaignPause(project, id, { input: getString(input.values, "input"), format: input.format });
3028
+ }
3029
+ },
3030
+ {
3031
+ path: ["ads", "ad-group", "create"],
3032
+ usage: "canonry ads ad-group create <project> --input <json-file|-> [--format json]",
3033
+ options: { input: stringOption() },
3034
+ run: async (input) => {
3035
+ const project = requireProject(input, "ads.ad-group.create", "canonry ads ad-group create <project> --input <json-file|-> [--format json]");
3036
+ await adsAdGroupCreate(project, { input: getString(input.values, "input"), format: input.format });
3037
+ }
3038
+ },
3039
+ {
3040
+ path: ["ads", "ad-group", "update"],
3041
+ usage: "canonry ads ad-group update <project> <ad-group-id> --input <json-file|-> [--format json]",
3042
+ options: { input: stringOption() },
3043
+ run: async (input) => {
3044
+ const usage = "canonry ads ad-group update <project> <ad-group-id> --input <json-file|-> [--format json]";
3045
+ const project = requireProject(input, "ads.ad-group.update", usage);
3046
+ const id = requirePositional(input, 1, { command: "ads.ad-group.update", usage, message: "ad group id is required" });
3047
+ await adsAdGroupUpdate(project, id, { input: getString(input.values, "input"), format: input.format });
3048
+ }
3049
+ },
3050
+ {
3051
+ path: ["ads", "ad-group", "pause"],
3052
+ usage: "canonry ads ad-group pause <project> <ad-group-id> --input <json-file|-> [--format json]",
3053
+ options: { input: stringOption() },
3054
+ run: async (input) => {
3055
+ const usage = "canonry ads ad-group pause <project> <ad-group-id> --input <json-file|-> [--format json]";
3056
+ const project = requireProject(input, "ads.ad-group.pause", usage);
3057
+ const id = requirePositional(input, 1, { command: "ads.ad-group.pause", usage, message: "ad group id is required" });
3058
+ await adsAdGroupPause(project, id, { input: getString(input.values, "input"), format: input.format });
3059
+ }
3060
+ },
3061
+ {
3062
+ path: ["ads", "ad", "create"],
3063
+ usage: "canonry ads ad create <project> --input <json-file|-> [--format json]",
3064
+ options: { input: stringOption() },
3065
+ run: async (input) => {
3066
+ const project = requireProject(input, "ads.ad.create", "canonry ads ad create <project> --input <json-file|-> [--format json]");
3067
+ await adsAdCreate(project, { input: getString(input.values, "input"), format: input.format });
3068
+ }
3069
+ },
3070
+ {
3071
+ path: ["ads", "ad", "update"],
3072
+ usage: "canonry ads ad update <project> <ad-id> --input <json-file|-> [--format json]",
3073
+ options: { input: stringOption() },
3074
+ run: async (input) => {
3075
+ const usage = "canonry ads ad update <project> <ad-id> --input <json-file|-> [--format json]";
3076
+ const project = requireProject(input, "ads.ad.update", usage);
3077
+ const id = requirePositional(input, 1, { command: "ads.ad.update", usage, message: "ad id is required" });
3078
+ await adsAdUpdate(project, id, { input: getString(input.values, "input"), format: input.format });
3079
+ }
3080
+ },
3081
+ {
3082
+ path: ["ads", "ad", "pause"],
3083
+ usage: "canonry ads ad pause <project> <ad-id> --input <json-file|-> [--format json]",
3084
+ options: { input: stringOption() },
3085
+ run: async (input) => {
3086
+ const usage = "canonry ads ad pause <project> <ad-id> --input <json-file|-> [--format json]";
3087
+ const project = requireProject(input, "ads.ad.pause", usage);
3088
+ const id = requirePositional(input, 1, { command: "ads.ad.pause", usage, message: "ad id is required" });
3089
+ await adsAdPause(project, id, { input: getString(input.values, "input"), format: input.format });
3090
+ }
3091
+ },
2888
3092
  {
2889
3093
  path: ["ads", "sync"],
2890
3094
  usage: "canonry ads sync <project> [--format json]",
@@ -2948,9 +3152,9 @@ async function gaConnect(project, opts) {
2948
3152
  propertyId: opts.propertyId
2949
3153
  };
2950
3154
  if (opts.keyFile) {
2951
- const fs14 = await import("fs");
3155
+ const fs15 = await import("fs");
2952
3156
  try {
2953
- const content = fs14.readFileSync(opts.keyFile, "utf-8");
3157
+ const content = fs15.readFileSync(opts.keyFile, "utf-8");
2954
3158
  JSON.parse(content);
2955
3159
  body.keyJson = content;
2956
3160
  } catch (e) {
@@ -4297,9 +4501,9 @@ async function trafficConnectWordpress(project, opts) {
4297
4501
  }
4298
4502
  let applicationPassword = opts.appPassword?.trim() ?? "";
4299
4503
  if (!applicationPassword && opts.appPasswordFile) {
4300
- const fs14 = await import("fs");
4504
+ const fs15 = await import("fs");
4301
4505
  try {
4302
- applicationPassword = fs14.readFileSync(opts.appPasswordFile, "utf-8").trim();
4506
+ applicationPassword = fs15.readFileSync(opts.appPasswordFile, "utf-8").trim();
4303
4507
  } catch (e) {
4304
4508
  const msg = e instanceof Error ? e.message : String(e);
4305
4509
  throw new CliError({
@@ -4355,10 +4559,10 @@ async function trafficConnectCloudRun(project, opts) {
4355
4559
  details: { project }
4356
4560
  });
4357
4561
  }
4358
- const fs14 = await import("fs");
4562
+ const fs15 = await import("fs");
4359
4563
  let keyJson;
4360
4564
  try {
4361
- keyJson = fs14.readFileSync(opts.serviceAccountKey, "utf-8");
4565
+ keyJson = fs15.readFileSync(opts.serviceAccountKey, "utf-8");
4362
4566
  JSON.parse(keyJson);
4363
4567
  } catch (e) {
4364
4568
  const msg = e instanceof Error ? e.message : String(e);
@@ -4418,9 +4622,9 @@ async function trafficConnectVercel(project, opts) {
4418
4622
  }
4419
4623
  let token = opts.token?.trim() ?? "";
4420
4624
  if (!token && opts.tokenFile) {
4421
- const fs14 = await import("fs");
4625
+ const fs15 = await import("fs");
4422
4626
  try {
4423
- token = fs14.readFileSync(opts.tokenFile, "utf-8").trim();
4627
+ token = fs15.readFileSync(opts.tokenFile, "utf-8").trim();
4424
4628
  } catch (e) {
4425
4629
  const msg = e instanceof Error ? e.message : String(e);
4426
4630
  throw new CliError({
@@ -6238,7 +6442,7 @@ var KEYS_CLI_COMMANDS = [
6238
6442
  ];
6239
6443
 
6240
6444
  // src/commands/keyword.ts
6241
- import fs3 from "fs";
6445
+ import fs4 from "fs";
6242
6446
  function getClient12() {
6243
6447
  return createApiClient();
6244
6448
  }
@@ -6306,7 +6510,7 @@ async function listKeywords(project, format) {
6306
6510
  }
6307
6511
  }
6308
6512
  async function importKeywords(project, filePath, format) {
6309
- if (!fs3.existsSync(filePath)) {
6513
+ if (!fs4.existsSync(filePath)) {
6310
6514
  throw new CliError({
6311
6515
  code: "KEYWORD_IMPORT_FILE_NOT_FOUND",
6312
6516
  message: `File not found: ${filePath}`,
@@ -6317,7 +6521,7 @@ async function importKeywords(project, filePath, format) {
6317
6521
  }
6318
6522
  });
6319
6523
  }
6320
- const content = fs3.readFileSync(filePath, "utf-8");
6524
+ const content = fs4.readFileSync(filePath, "utf-8");
6321
6525
  const keywords = content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
6322
6526
  if (keywords.length === 0) {
6323
6527
  if (isMachineFormat(format)) {
@@ -6518,7 +6722,7 @@ var KEYWORD_CLI_COMMANDS = [
6518
6722
  ];
6519
6723
 
6520
6724
  // src/commands/query.ts
6521
- import fs4 from "fs";
6725
+ import fs5 from "fs";
6522
6726
  function getClient13() {
6523
6727
  return createApiClient();
6524
6728
  }
@@ -6609,7 +6813,7 @@ async function listQueries(project, format) {
6609
6813
  }
6610
6814
  }
6611
6815
  async function importQueries(project, filePath, format) {
6612
- if (!fs4.existsSync(filePath)) {
6816
+ if (!fs5.existsSync(filePath)) {
6613
6817
  throw new CliError({
6614
6818
  code: "QUERY_IMPORT_FILE_NOT_FOUND",
6615
6819
  message: `File not found: ${filePath}`,
@@ -6620,7 +6824,7 @@ async function importQueries(project, filePath, format) {
6620
6824
  }
6621
6825
  });
6622
6826
  }
6623
- const content = fs4.readFileSync(filePath, "utf-8");
6827
+ const content = fs5.readFileSync(filePath, "utf-8");
6624
6828
  const queries2 = content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
6625
6829
  if (queries2.length === 0) {
6626
6830
  if (isMachineFormat(format)) {
@@ -6822,7 +7026,7 @@ var QUERY_CLI_COMMANDS = [
6822
7026
  ];
6823
7027
 
6824
7028
  // src/commands/mcp.ts
6825
- import fs5 from "fs";
7029
+ import fs6 from "fs";
6826
7030
  import path3 from "path";
6827
7031
  import { createRequire } from "module";
6828
7032
 
@@ -6938,8 +7142,8 @@ function renderClientSnippet(client, serverName, entry) {
6938
7142
  return renderJsonSnippet(serverName, entry, client.format);
6939
7143
  }
6940
7144
  function readJsonConfig(configPath) {
6941
- if (!fs5.existsSync(configPath)) return {};
6942
- const raw = fs5.readFileSync(configPath, "utf-8").trim();
7145
+ if (!fs6.existsSync(configPath)) return {};
7146
+ const raw = fs6.readFileSync(configPath, "utf-8").trim();
6943
7147
  if (!raw) return {};
6944
7148
  try {
6945
7149
  const parsed = JSON.parse(raw);
@@ -6957,14 +7161,14 @@ function readJsonConfig(configPath) {
6957
7161
  }
6958
7162
  }
6959
7163
  function writeJsonConfig(configPath, value) {
6960
- fs5.mkdirSync(path3.dirname(configPath), { recursive: true });
6961
- fs5.writeFileSync(configPath, `${JSON.stringify(value, null, 2)}
7164
+ fs6.mkdirSync(path3.dirname(configPath), { recursive: true });
7165
+ fs6.writeFileSync(configPath, `${JSON.stringify(value, null, 2)}
6962
7166
  `, "utf-8");
6963
7167
  }
6964
7168
  function backupConfigIfPresent(configPath) {
6965
- if (!fs5.existsSync(configPath)) return void 0;
7169
+ if (!fs6.existsSync(configPath)) return void 0;
6966
7170
  const backupPath = `${configPath}.canonry.bak`;
6967
- fs5.copyFileSync(configPath, backupPath);
7171
+ fs6.copyFileSync(configPath, backupPath);
6968
7172
  return backupPath;
6969
7173
  }
6970
7174
  function findClientOrThrow(id) {
@@ -7318,13 +7522,13 @@ var NOTIFY_CLI_COMMANDS = [
7318
7522
  ];
7319
7523
 
7320
7524
  // src/commands/apply.ts
7321
- import fs6 from "fs";
7525
+ import fs7 from "fs";
7322
7526
  import { parseAllDocuments } from "yaml";
7323
7527
  async function applyConfigFile(filePath) {
7324
- if (!fs6.existsSync(filePath)) {
7528
+ if (!fs7.existsSync(filePath)) {
7325
7529
  throw new Error(`File not found: ${filePath}`);
7326
7530
  }
7327
- const content = fs6.readFileSync(filePath, "utf-8");
7531
+ const content = fs7.readFileSync(filePath, "utf-8");
7328
7532
  const docs = parseAllDocuments(content);
7329
7533
  const client = createApiClient();
7330
7534
  const errors = [];
@@ -7642,7 +7846,7 @@ async function loadLatestRunForExport(client, project) {
7642
7846
  }
7643
7847
 
7644
7848
  // src/commands/results-export.ts
7645
- import fs7 from "fs";
7849
+ import fs8 from "fs";
7646
7850
  import path4 from "path";
7647
7851
  async function exportResults(project, opts) {
7648
7852
  const { output, ...request } = opts;
@@ -7652,8 +7856,8 @@ async function exportResults(project, opts) {
7652
7856
  return;
7653
7857
  }
7654
7858
  const target = output ? path4.resolve(output) : path4.resolve(process.cwd(), path4.basename(artifact.filename));
7655
- fs7.mkdirSync(path4.dirname(target), { recursive: true });
7656
- fs7.writeFileSync(target, artifact.content, "utf8");
7859
+ fs8.mkdirSync(path4.dirname(target), { recursive: true });
7860
+ fs8.writeFileSync(target, artifact.content, "utf8");
7657
7861
  console.log(`Results export written to ${target}`);
7658
7862
  }
7659
7863
 
@@ -7661,39 +7865,48 @@ async function exportResults(project, opts) {
7661
7865
  function getClient18() {
7662
7866
  return createApiClient();
7663
7867
  }
7664
- async function showHistory(project, format) {
7868
+ function originLabel(entry) {
7869
+ const userAgent = entry.userAgent?.toLowerCase() ?? "";
7870
+ if (userAgent.includes("canonry-mcp")) return "mcp";
7871
+ if (userAgent.includes("canonry-cli")) return "cli";
7872
+ if (userAgent.includes("mozilla/")) return "dashboard";
7873
+ return entry.actor;
7874
+ }
7875
+ async function showHistory(project, format, opts = {}) {
7665
7876
  const client = getClient18();
7666
7877
  try {
7667
- const entries = await client.getHistory(project);
7878
+ const entries = project ? await client.getHistory(project, opts) : await client.getGlobalHistory(opts);
7668
7879
  if (format === "json") {
7669
7880
  console.log(JSON.stringify(entries, null, 2));
7670
7881
  return;
7671
7882
  }
7672
7883
  if (format === "jsonl") {
7673
- emitJsonl(entries.map((entry) => ({ project, ...entry })));
7884
+ emitJsonl(entries.map((entry) => ({ project: project ?? null, ...entry })));
7674
7885
  return;
7675
7886
  }
7676
7887
  if (entries.length === 0) {
7677
- console.log(`No audit history for "${project}".`);
7888
+ console.log(project ? `No audit history for "${project}".` : "No instance audit history.");
7678
7889
  return;
7679
7890
  }
7680
- console.log(`Audit history for "${project}" (${entries.length}):
7891
+ console.log(`${project ? `Audit history for "${project}"` : "Instance audit history"} (${entries.length}):
7681
7892
  `);
7682
- console.log(" TIMESTAMP ACTION ENTITY TYPE ACTOR");
7683
- console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500");
7893
+ console.log(" TIMESTAMP ACTION ENTITY TYPE ORIGIN");
7894
+ console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
7684
7895
  for (const entry of entries) {
7685
7896
  console.log(
7686
- ` ${entry.createdAt.padEnd(23)} ${entry.action.padEnd(18)} ${entry.entityType.padEnd(11)} ${entry.actor}`
7897
+ ` ${entry.createdAt.padEnd(23)} ${entry.action.padEnd(18)} ${entry.entityType.padEnd(11)} ${originLabel(entry)}`
7687
7898
  );
7899
+ if (entry.actorSession) console.log(` session: ${entry.actorSession}`);
7900
+ if (entry.diff != null) console.log(` diff: ${JSON.stringify(entry.diff)}`);
7688
7901
  }
7689
7902
  } catch (err) {
7690
7903
  const message = err instanceof Error ? err.message : String(err);
7691
7904
  throw new CliError({
7692
7905
  code: "HISTORY_FETCH_FAILED",
7693
- message: `Failed to fetch history for project "${project}"`,
7906
+ message: project ? `Failed to fetch history for project "${project}"` : "Failed to fetch instance history",
7694
7907
  displayMessage: `Failed to fetch history: ${message}`,
7695
7908
  details: {
7696
- project,
7909
+ project: project ?? null,
7697
7910
  cause: message
7698
7911
  }
7699
7912
  });
@@ -7809,10 +8022,25 @@ var OPERATOR_CLI_COMMANDS = [
7809
8022
  },
7810
8023
  {
7811
8024
  path: ["history"],
7812
- usage: "canonry history <project> [--format json]",
8025
+ usage: "canonry history <project> [--limit <n>] [--since <ISO>] [--action <action>] [--actor <actor>] [--entity-type <type>] [--format json|jsonl]\n canonry history --all [same filters]",
8026
+ options: {
8027
+ all: { type: "boolean", default: false },
8028
+ limit: stringOption(),
8029
+ since: stringOption(),
8030
+ action: stringOption(),
8031
+ actor: stringOption(),
8032
+ "entity-type": stringOption()
8033
+ },
7813
8034
  run: async (input) => {
7814
- const project = requireProject(input, "history", "canonry history <project> [--format json]");
7815
- await showHistory(project, input.format);
8035
+ 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]";
8036
+ const project = getBoolean(input.values, "all") ? void 0 : requireProject(input, "history", usage);
8037
+ await showHistory(project, input.format, {
8038
+ limit: parseIntegerOption(input, "limit", { command: "history", usage, message: "--limit must be an integer" }),
8039
+ since: getString(input.values, "since"),
8040
+ action: getString(input.values, "action"),
8041
+ actor: getString(input.values, "actor"),
8042
+ entityType: getString(input.values, "entity-type")
8043
+ });
7816
8044
  }
7817
8045
  },
7818
8046
  {
@@ -8283,7 +8511,7 @@ var PROJECT_CLI_COMMANDS = [
8283
8511
  ];
8284
8512
 
8285
8513
  // src/commands/report.ts
8286
- import fs8 from "fs";
8514
+ import fs9 from "fs";
8287
8515
  import path5 from "path";
8288
8516
  function defaultOutputPath(project, audience) {
8289
8517
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -8300,10 +8528,10 @@ async function runReportCommand(project, opts = {}) {
8300
8528
  const html = renderReportHtml(report, { audience });
8301
8529
  const targetPath = opts.output ? path5.resolve(opts.output) : defaultOutputPath(project, audience);
8302
8530
  const dir = path5.dirname(targetPath);
8303
- if (!fs8.existsSync(dir)) {
8304
- fs8.mkdirSync(dir, { recursive: true });
8531
+ if (!fs9.existsSync(dir)) {
8532
+ fs9.mkdirSync(dir, { recursive: true });
8305
8533
  }
8306
- fs8.writeFileSync(targetPath, html, "utf-8");
8534
+ fs9.writeFileSync(targetPath, html, "utf-8");
8307
8535
  console.log(`Report written to ${targetPath}`);
8308
8536
  }
8309
8537
 
@@ -9168,11 +9396,11 @@ var SKILLS_CLI_COMMANDS = [
9168
9396
  ];
9169
9397
 
9170
9398
  // src/commands/snapshot.ts
9171
- import fs10 from "fs";
9399
+ import fs11 from "fs";
9172
9400
  import path7 from "path";
9173
9401
 
9174
9402
  // src/snapshot-pdf.ts
9175
- import fs9 from "fs";
9403
+ import fs10 from "fs";
9176
9404
  import path6 from "path";
9177
9405
  import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
9178
9406
  var PAGE_WIDTH = 612;
@@ -9386,8 +9614,8 @@ async function writeSnapshotPdf(report, outputPath) {
9386
9614
  renderQueries(pdf, report);
9387
9615
  const bytes = await doc.save();
9388
9616
  const resolvedPath = path6.resolve(outputPath);
9389
- fs9.mkdirSync(path6.dirname(resolvedPath), { recursive: true });
9390
- fs9.writeFileSync(resolvedPath, bytes);
9617
+ fs10.mkdirSync(path6.dirname(resolvedPath), { recursive: true });
9618
+ fs10.writeFileSync(resolvedPath, bytes);
9391
9619
  return resolvedPath;
9392
9620
  }
9393
9621
  function renderCover(pdf, report) {
@@ -9546,8 +9774,8 @@ PDF saved: ${savedPdfPath}`);
9546
9774
  }
9547
9775
  function writeSnapshotMarkdown(report, outputPath) {
9548
9776
  const resolvedPath = path7.resolve(outputPath);
9549
- fs10.mkdirSync(path7.dirname(resolvedPath), { recursive: true });
9550
- fs10.writeFileSync(resolvedPath, formatSnapshotMarkdown(report), "utf-8");
9777
+ fs11.mkdirSync(path7.dirname(resolvedPath), { recursive: true });
9778
+ fs11.writeFileSync(resolvedPath, formatSnapshotMarkdown(report), "utf-8");
9551
9779
  return resolvedPath;
9552
9780
  }
9553
9781
  function formatSnapshotMarkdown(report) {
@@ -10757,7 +10985,7 @@ async function bootstrapCommand(_opts) {
10757
10985
 
10758
10986
  // src/commands/daemon.ts
10759
10987
  import { spawn } from "child_process";
10760
- import fs11 from "fs";
10988
+ import fs12 from "fs";
10761
10989
  import path9 from "path";
10762
10990
  function getPidPath() {
10763
10991
  return path9.join(getConfigDir(), "canonry.pid");
@@ -10800,8 +11028,8 @@ function buildServeForwardArgs(opts) {
10800
11028
  async function startDaemon(opts) {
10801
11029
  const pidPath = getPidPath();
10802
11030
  const format = opts.format ?? "text";
10803
- if (fs11.existsSync(pidPath)) {
10804
- const existingPid = parseInt(fs11.readFileSync(pidPath, "utf-8").trim(), 10);
11031
+ if (fs12.existsSync(pidPath)) {
11032
+ const existingPid = parseInt(fs12.readFileSync(pidPath, "utf-8").trim(), 10);
10805
11033
  if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
10806
11034
  throw new CliError({
10807
11035
  code: "DAEMON_ALREADY_RUNNING",
@@ -10812,7 +11040,7 @@ async function startDaemon(opts) {
10812
11040
  }
10813
11041
  });
10814
11042
  }
10815
- fs11.unlinkSync(pidPath);
11043
+ fs12.unlinkSync(pidPath);
10816
11044
  }
10817
11045
  const cliPath = path9.resolve(new URL(import.meta.url).pathname);
10818
11046
  const inSourceMode = new URL(import.meta.url).pathname.endsWith(".ts");
@@ -10831,10 +11059,10 @@ async function startDaemon(opts) {
10831
11059
  });
10832
11060
  }
10833
11061
  const configDir = getConfigDir();
10834
- if (!fs11.existsSync(configDir)) {
10835
- fs11.mkdirSync(configDir, { recursive: true });
11062
+ if (!fs12.existsSync(configDir)) {
11063
+ fs12.mkdirSync(configDir, { recursive: true });
10836
11064
  }
10837
- fs11.writeFileSync(pidPath, String(child.pid), "utf-8");
11065
+ fs12.writeFileSync(pidPath, String(child.pid), "utf-8");
10838
11066
  const port = opts.port ?? "4100";
10839
11067
  const host = opts.host ?? "127.0.0.1";
10840
11068
  if (!isMachineFormat(format)) {
@@ -10843,7 +11071,7 @@ async function startDaemon(opts) {
10843
11071
  const ready = await waitForReady(host, port);
10844
11072
  if (!ready) {
10845
11073
  try {
10846
- fs11.unlinkSync(pidPath);
11074
+ fs12.unlinkSync(pidPath);
10847
11075
  } catch {
10848
11076
  }
10849
11077
  throw new CliError({
@@ -10875,7 +11103,7 @@ async function startDaemon(opts) {
10875
11103
  }
10876
11104
  function stopDaemon(format = "text") {
10877
11105
  const pidPath = getPidPath();
10878
- if (!fs11.existsSync(pidPath)) {
11106
+ if (!fs12.existsSync(pidPath)) {
10879
11107
  if (isMachineFormat(format)) {
10880
11108
  console.log(JSON.stringify({
10881
11109
  stopped: false,
@@ -10886,7 +11114,7 @@ function stopDaemon(format = "text") {
10886
11114
  console.log("Canonry is not running (no PID file found)");
10887
11115
  return;
10888
11116
  }
10889
- const pid = parseInt(fs11.readFileSync(pidPath, "utf-8").trim(), 10);
11117
+ const pid = parseInt(fs12.readFileSync(pidPath, "utf-8").trim(), 10);
10890
11118
  if (isNaN(pid)) {
10891
11119
  if (isMachineFormat(format)) {
10892
11120
  console.log(JSON.stringify({
@@ -10897,7 +11125,7 @@ function stopDaemon(format = "text") {
10897
11125
  } else {
10898
11126
  console.error("Invalid PID file. Removing it.");
10899
11127
  }
10900
- fs11.unlinkSync(pidPath);
11128
+ fs12.unlinkSync(pidPath);
10901
11129
  return;
10902
11130
  }
10903
11131
  if (!isProcessAlive(pid)) {
@@ -10911,12 +11139,12 @@ function stopDaemon(format = "text") {
10911
11139
  } else {
10912
11140
  console.log(`Canonry is not running (stale PID: ${pid}). Cleaning up.`);
10913
11141
  }
10914
- fs11.unlinkSync(pidPath);
11142
+ fs12.unlinkSync(pidPath);
10915
11143
  return;
10916
11144
  }
10917
11145
  try {
10918
11146
  process.kill(pid, "SIGTERM");
10919
- fs11.unlinkSync(pidPath);
11147
+ fs12.unlinkSync(pidPath);
10920
11148
  if (isMachineFormat(format)) {
10921
11149
  console.log(JSON.stringify({
10922
11150
  stopped: true,
@@ -10940,7 +11168,7 @@ function stopDaemon(format = "text") {
10940
11168
 
10941
11169
  // src/commands/init.ts
10942
11170
  import crypto2 from "crypto";
10943
- import fs12 from "fs";
11171
+ import fs13 from "fs";
10944
11172
  import readline from "readline";
10945
11173
  import path10 from "path";
10946
11174
  function prompt(question) {
@@ -10964,7 +11192,7 @@ var PROJECT_MARKERS = [".git", "canonry.yaml", "canonry.yml", "package.json"];
10964
11192
  function cwdLooksLikeProject(dir) {
10965
11193
  const home = process.env.HOME ?? "";
10966
11194
  if (home && path10.resolve(dir) === path10.resolve(home)) return false;
10967
- return PROJECT_MARKERS.some((marker) => fs12.existsSync(path10.join(dir, marker)));
11195
+ return PROJECT_MARKERS.some((marker) => fs13.existsSync(path10.join(dir, marker)));
10968
11196
  }
10969
11197
  var DEFAULT_AGENT_MODELS = {
10970
11198
  anthropic: "anthropic/claude-sonnet-4-6",
@@ -10995,8 +11223,8 @@ async function initCommand(opts) {
10995
11223
  return void 0;
10996
11224
  }
10997
11225
  const configDir = getConfigDir();
10998
- if (!fs12.existsSync(configDir)) {
10999
- fs12.mkdirSync(configDir, { recursive: true });
11226
+ if (!fs13.existsSync(configDir)) {
11227
+ fs13.mkdirSync(configDir, { recursive: true });
11000
11228
  }
11001
11229
  const bootstrapEnv = getBootstrapEnv(process.env, {
11002
11230
  GEMINI_API_KEY: opts?.geminiKey,
@@ -11663,7 +11891,7 @@ function getClient25() {
11663
11891
  }
11664
11892
  async function technicalAeoScore(project, opts) {
11665
11893
  const client = getClient25();
11666
- const score = await client.getTechnicalAeoScore(project);
11894
+ const score = await client.getTechnicalAeoScore(project, { runId: opts.runId });
11667
11895
  if (isMachineFormat(opts.format)) {
11668
11896
  console.log(JSON.stringify(score, null, 2));
11669
11897
  return;
@@ -11700,7 +11928,7 @@ async function technicalAeoScore(project, opts) {
11700
11928
  async function technicalAeoPages(project, opts) {
11701
11929
  const client = getClient25();
11702
11930
  const status = opts.status === "success" || opts.status === "error" ? opts.status : void 0;
11703
- const res = await client.getTechnicalAeoPages(project, { status, sort: opts.sort, limit: opts.limit });
11931
+ const res = await client.getTechnicalAeoPages(project, { runId: opts.runId, status, sort: opts.sort, limit: opts.limit });
11704
11932
  if (opts.format === "jsonl") {
11705
11933
  emitJsonl(res.pages.map((p) => ({ project, runId: res.runId, ...p })));
11706
11934
  return;
@@ -11784,28 +12012,33 @@ async function technicalAeoRun(project, opts) {
11784
12012
  var TECHNICAL_AEO_CLI_COMMANDS = [
11785
12013
  {
11786
12014
  path: ["technical-aeo", "score"],
11787
- usage: "canonry technical-aeo score <project> [--format json]",
12015
+ usage: "canonry technical-aeo score <project> [--run-id <id>] [--format json]",
12016
+ options: {
12017
+ "run-id": stringOption()
12018
+ },
11788
12019
  run: async (input) => {
11789
12020
  const project = requireProject(
11790
12021
  input,
11791
12022
  "technical-aeo.score",
11792
- "canonry technical-aeo score <project> [--format json]"
12023
+ "canonry technical-aeo score <project> [--run-id <id>] [--format json]"
11793
12024
  );
11794
- await technicalAeoScore(project, { format: input.format });
12025
+ await technicalAeoScore(project, { runId: getString(input.values, "run-id"), format: input.format });
11795
12026
  }
11796
12027
  },
11797
12028
  {
11798
12029
  path: ["technical-aeo", "pages"],
11799
- usage: "canonry technical-aeo pages <project> [--status success|error] [--sort score-asc|score-desc|url] [--limit <n>] [--format json|jsonl]",
12030
+ usage: "canonry technical-aeo pages <project> [--run-id <id>] [--status success|error] [--sort score-asc|score-desc|url] [--limit <n>] [--format json|jsonl]",
11800
12031
  options: {
12032
+ "run-id": stringOption(),
11801
12033
  status: stringOption(),
11802
12034
  sort: stringOption(),
11803
12035
  limit: stringOption()
11804
12036
  },
11805
12037
  run: async (input) => {
11806
- const usage = "canonry technical-aeo pages <project> [--status success|error] [--sort score-asc|score-desc|url] [--limit <n>] [--format json|jsonl]";
12038
+ const usage = "canonry technical-aeo pages <project> [--run-id <id>] [--status success|error] [--sort score-asc|score-desc|url] [--limit <n>] [--format json|jsonl]";
11807
12039
  const project = requireProject(input, "technical-aeo.pages", usage);
11808
12040
  await technicalAeoPages(project, {
12041
+ runId: getString(input.values, "run-id"),
11809
12042
  status: getString(input.values, "status"),
11810
12043
  sort: getString(input.values, "sort"),
11811
12044
  limit: parseIntegerOption(input, "limit", {
@@ -12097,7 +12330,7 @@ var VISIBILITY_STATS_CLI_COMMANDS = [
12097
12330
  ];
12098
12331
 
12099
12332
  // src/cli-commands/wordpress.ts
12100
- import fs13 from "fs";
12333
+ import fs14 from "fs";
12101
12334
 
12102
12335
  // src/commands/wordpress.ts
12103
12336
  function getClient26() {
@@ -12336,12 +12569,12 @@ async function wordpressSetMeta(project, body) {
12336
12569
  printPageDetail(result);
12337
12570
  }
12338
12571
  async function wordpressBulkSetMeta(project, opts) {
12339
- const fs14 = await import("fs/promises");
12572
+ const fs15 = await import("fs/promises");
12340
12573
  const path11 = await import("path");
12341
12574
  const filePath = path11.resolve(opts.from);
12342
12575
  let raw;
12343
12576
  try {
12344
- raw = await fs14.readFile(filePath, "utf8");
12577
+ raw = await fs15.readFile(filePath, "utf8");
12345
12578
  } catch {
12346
12579
  throw new CliError({
12347
12580
  code: "FILE_READ_ERROR",
@@ -12438,13 +12671,13 @@ async function wordpressSetSchema(project, body) {
12438
12671
  printManualAssist(`Schema update for "${body.slug}"`, result);
12439
12672
  }
12440
12673
  async function wordpressSchemaDeploy(project, opts) {
12441
- const fs14 = await import("fs/promises");
12674
+ const fs15 = await import("fs/promises");
12442
12675
  const path11 = await import("path");
12443
12676
  const yaml = await loadYamlModule();
12444
12677
  const filePath = path11.resolve(opts.profile);
12445
12678
  let raw;
12446
12679
  try {
12447
- raw = await fs14.readFile(filePath, "utf8");
12680
+ raw = await fs15.readFile(filePath, "utf8");
12448
12681
  } catch {
12449
12682
  throw new CliError({
12450
12683
  code: "FILE_READ_ERROR",
@@ -12549,13 +12782,13 @@ async function wordpressOnboard(project, opts) {
12549
12782
  }
12550
12783
  let profileData;
12551
12784
  if (opts.profile) {
12552
- const fs14 = await import("fs/promises");
12785
+ const fs15 = await import("fs/promises");
12553
12786
  const path11 = await import("path");
12554
12787
  const yaml = await loadYamlModule();
12555
12788
  const filePath = path11.resolve(opts.profile);
12556
12789
  let raw;
12557
12790
  try {
12558
- raw = await fs14.readFile(filePath, "utf8");
12791
+ raw = await fs15.readFile(filePath, "utf8");
12559
12792
  } catch {
12560
12793
  throw new CliError({
12561
12794
  code: "FILE_READ_ERROR",
@@ -12704,7 +12937,7 @@ function resolveContent(input, command, usage, options) {
12704
12937
  }
12705
12938
  if (contentFile) {
12706
12939
  try {
12707
- return fs13.readFileSync(contentFile, "utf-8");
12940
+ return fs14.readFileSync(contentFile, "utf-8");
12708
12941
  } catch (error) {
12709
12942
  const message = error instanceof Error ? error.message : String(error);
12710
12943
  throw usageError(`Error: could not read --content-file "${contentFile}": ${message}`, {