@canonry/canonry 4.118.0 → 4.124.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 (34) hide show
  1. package/README.md +9 -0
  2. package/assets/agent-workspace/skills/canonry/SKILL.md +3 -0
  3. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +135 -28
  4. package/assets/assets/{AuditHistoryPanel-Yh7VEe4P.js → AuditHistoryPanel-fPBADGsr.js} +1 -1
  5. package/assets/assets/{BacklinksPage-BmO77kMs.js → BacklinksPage-DKer7s8M.js} +1 -1
  6. package/assets/assets/ChartPrimitives-0DFDFhCZ.js +1 -0
  7. package/assets/assets/{HistoryPage-DkLV_iRc.js → HistoryPage-BTkTlXg0.js} +1 -1
  8. package/assets/assets/ProjectPage-BzqDCkp2.js +7 -0
  9. package/assets/assets/{RunRow-BDH9agxQ.js → RunRow-DyIeH4_d.js} +1 -1
  10. package/assets/assets/{RunsPage-D9YsEeT3.js → RunsPage-KVu_37ds.js} +1 -1
  11. package/assets/assets/{SettingsPage-o5as3Ixw.js → SettingsPage-CCLXSBpq.js} +1 -1
  12. package/assets/assets/{TrafficPage-BCK203aQ.js → TrafficPage-BH7a0sS2.js} +1 -1
  13. package/assets/assets/{TrafficSourceDetailPage-DsVMAZvS.js → TrafficSourceDetailPage-kIlf0kCe.js} +1 -1
  14. package/assets/assets/{arrow-left-B7ZR6VC9.js → arrow-left-jhivZs4g.js} +1 -1
  15. package/assets/assets/{extract-error-message-DeYDUpew.js → extract-error-message-XU_cbcLD.js} +1 -1
  16. package/assets/assets/index-CqFSTLK5.js +210 -0
  17. package/assets/assets/index-jymsMBpZ.css +1 -0
  18. package/assets/assets/{trash-2-xxHTqWbF.js → trash-2-CcmHLt5u.js} +1 -1
  19. package/assets/assets/vendor-recharts-DSxTZhaH.js +36 -0
  20. package/assets/index.html +3 -3
  21. package/dist/{chunk-HGNY5GL4.js → chunk-4D2XUMMV.js} +273 -37
  22. package/dist/{chunk-CMZIPFNQ.js → chunk-BGLBWFMV.js} +5034 -736
  23. package/dist/{chunk-KEZWLP4O.js → chunk-SLCGTOKT.js} +3293 -2337
  24. package/dist/{chunk-75UBTCKS.js → chunk-WGGB7QBD.js} +321 -6
  25. package/dist/cli.js +561 -36
  26. package/dist/index.js +4 -4
  27. package/dist/{intelligence-service-FWPXMAVK.js → intelligence-service-SACHNNCT.js} +2 -2
  28. package/dist/mcp.js +4 -4
  29. package/package.json +8 -8
  30. package/assets/assets/ChartPrimitives-H0ICVpw_.js +0 -1
  31. package/assets/assets/ProjectPage-R3odr_eA.js +0 -7
  32. package/assets/assets/index-Bot6YiD6.css +0 -1
  33. package/assets/assets/index-QCFQv0xh.js +0 -210
  34. package/assets/assets/vendor-recharts-C9EZkgbP.js +0 -36
package/dist/cli.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  setTelemetrySource,
30
30
  showFirstRunNotice,
31
31
  trackEvent
32
- } from "./chunk-HGNY5GL4.js";
32
+ } from "./chunk-4D2XUMMV.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-75UBTCKS.js";
49
+ } from "./chunk-WGGB7QBD.js";
50
50
  import {
51
51
  apiKeys,
52
52
  createClient,
@@ -54,8 +54,11 @@ import {
54
54
  projects,
55
55
  queries,
56
56
  renderReportHtml
57
- } from "./chunk-CMZIPFNQ.js";
57
+ } from "./chunk-BGLBWFMV.js";
58
58
  import {
59
+ AdsOperationKinds,
60
+ AdsOperationStates,
61
+ AdsOperationStepStates,
59
62
  BacklinkSources,
60
63
  CcReleaseSyncStatuses,
61
64
  CheckScopes,
@@ -65,15 +68,19 @@ import {
65
68
  REPORT_PERIOD_OPTIONS,
66
69
  RunStatuses,
67
70
  TrafficEventKinds,
71
+ adsActivateTreeRequestSchema,
72
+ adsActivationGrantCreateRequestSchema,
68
73
  adsAdCreateRequestSchema,
69
74
  adsAdGroupCreateRequestSchema,
70
75
  adsAdGroupUpdateRequestSchema,
71
76
  adsAdUpdateRequestSchema,
72
77
  adsCampaignCreateRequestSchema,
73
78
  adsCampaignUpdateRequestSchema,
79
+ adsGeoSearchQuerySchema,
74
80
  adsImageUploadRequestSchema,
75
81
  adsPauseRequestSchema,
76
82
  backlinkSourceSchema,
83
+ buildModelChangeNotice,
77
84
  discoveryBucketSchema,
78
85
  discoveryCompetitorTypeSchema,
79
86
  effectiveDomains,
@@ -88,7 +95,7 @@ import {
88
95
  providerQuotaPolicySchema,
89
96
  resolveProviderInput,
90
97
  winnabilityClassSchema
91
- } from "./chunk-KEZWLP4O.js";
98
+ } from "./chunk-SLCGTOKT.js";
92
99
 
93
100
  // src/cli.ts
94
101
  import { pathToFileURL } from "url";
@@ -2746,20 +2753,54 @@ function readRequest(inputPath, schema) {
2746
2753
  });
2747
2754
  }
2748
2755
  }
2756
+ function printOperationDetails(operation) {
2757
+ console.log(`Operation: ${operation.operationKey}`);
2758
+ if (operation.entityId) console.log(`Entity: ${operation.entityType ?? "unknown"} ${operation.entityId}`);
2759
+ if (operation.upstreamUpdatedAt != null) console.log(`Updated: ${operation.upstreamUpdatedAt}`);
2760
+ if (operation.errorCode) console.log(`Error: ${operation.errorCode}: ${operation.errorMessage ?? ""}`);
2761
+ if (operation.state === AdsOperationStates.pending || operation.state === AdsOperationStates.unknown || operation.state === AdsOperationStates.reconciling) {
2762
+ if (operation.kind === AdsOperationKinds.campaign_tree_activate) {
2763
+ console.log("Do not retry with a new operation key. Resume activation recovery for the original receipt instead.");
2764
+ } else {
2765
+ console.log("Do not retry with a new operation key. Reconcile the original receipt instead.");
2766
+ }
2767
+ }
2768
+ }
2749
2769
  function printOperation(result, format) {
2750
2770
  if (isMachineFormat(format)) {
2751
2771
  console.log(JSON.stringify(result, null, 2));
2752
2772
  return;
2753
2773
  }
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.");
2774
+ console.log(`${result.replayed ? "Replayed" : "Recorded"} ${result.operation.kind}: ${result.operation.state}`);
2775
+ printOperationDetails(result.operation);
2776
+ }
2777
+ function printReconciliation(result, format) {
2778
+ if (isMachineFormat(format)) {
2779
+ console.log(JSON.stringify(result, null, 2));
2780
+ return;
2781
+ }
2782
+ console.log(`${result.resolved ? "Resolved" : "Still unresolved"} ${result.operation.kind}: ${result.operation.state}`);
2783
+ printOperationDetails(result.operation);
2784
+ }
2785
+ function printActivationGrant(result, verb, format) {
2786
+ if (isMachineFormat(format)) {
2787
+ console.log(JSON.stringify(result, null, 2));
2788
+ return;
2762
2789
  }
2790
+ const cancellationRequested = verb === "Revoked" && result.grant.revocationRequestedAt !== null;
2791
+ console.log(cancellationRequested ? `Cancellation requested for activation grant ${result.grant.id}: ${result.grant.state}` : `${verb} activation grant ${result.grant.id}: ${result.grant.state}`);
2792
+ if (cancellationRequested) console.log(`Requested: ${result.grant.revocationRequestedAt}`);
2793
+ console.log(`Manifest: ${result.grant.manifestHash}`);
2794
+ console.log(`Expires: ${result.grant.expiresAt}`);
2795
+ }
2796
+ function printActivation(result, format) {
2797
+ if (isMachineFormat(format)) {
2798
+ console.log(JSON.stringify(result, null, 2));
2799
+ return;
2800
+ }
2801
+ const activeSteps = result.steps.filter((step) => step.state === AdsOperationStepStates.active).length;
2802
+ console.log(`Activation ${result.operation.operationKey}: ${result.operation.state}`);
2803
+ console.log(`Steps: ${activeSteps}/${result.steps.length} active`);
2763
2804
  }
2764
2805
  function describeConnection(status) {
2765
2806
  const lines = [];
@@ -2813,9 +2854,155 @@ async function adsStatus(project, opts) {
2813
2854
  console.log("Connect with: canonry ads connect " + project + " --api-key <sdk-key>");
2814
2855
  }
2815
2856
  }
2857
+ async function adsAccount(project, opts) {
2858
+ const result = await getClient5().getAdsAccount(project);
2859
+ if (isMachineFormat(opts?.format)) {
2860
+ console.log(JSON.stringify(result, null, 2));
2861
+ return;
2862
+ }
2863
+ console.log(`Account: ${result.name}`);
2864
+ console.log(`ID: ${result.id}`);
2865
+ console.log(`Status: ${result.status}`);
2866
+ console.log(`Currency: ${result.currencyCode ?? "unknown"}`);
2867
+ console.log(`Timezone: ${result.timezone ?? "unknown"}`);
2868
+ console.log(`Review: ${result.reviewStatus ?? "unknown"}`);
2869
+ console.log(`Integrity: ${result.integrityReviewStatus ?? "unknown"}`);
2870
+ if (result.integrityDecision) console.log(`Decision: ${result.integrityDecision}`);
2871
+ if (result.url) console.log(`Ads Manager: ${result.url}`);
2872
+ }
2873
+ async function adsGeoSearch(project, opts) {
2874
+ const parsed = adsGeoSearchQuerySchema.safeParse({ q: opts.q, limit: opts.limit });
2875
+ if (!parsed.success) {
2876
+ throw new CliError({
2877
+ code: "ADS_GEO_QUERY_INVALID",
2878
+ message: "A valid geo search query is required",
2879
+ displayMessage: "Error: --query is required and --limit must be an integer from 1 to 100",
2880
+ details: {
2881
+ project,
2882
+ issues: parsed.error.issues.map((issue) => ({ path: issue.path.join("."), message: issue.message }))
2883
+ }
2884
+ });
2885
+ }
2886
+ const query = parsed.data;
2887
+ const result = await getClient5().searchAdsGeo(project, query);
2888
+ if (opts.format === "jsonl") {
2889
+ emitJsonl(result.results.map((location) => ({ project, query: result.query, ...location })));
2890
+ return;
2891
+ }
2892
+ if (isMachineFormat(opts.format)) {
2893
+ console.log(JSON.stringify(result, null, 2));
2894
+ return;
2895
+ }
2896
+ if (result.results.length === 0) {
2897
+ console.log(`No OpenAI Ads locations matched "${result.query}".`);
2898
+ return;
2899
+ }
2900
+ for (const location of result.results) {
2901
+ const region = location.regionCode ? `, ${location.regionCode}` : "";
2902
+ console.log(`${location.canonicalName} (${location.type}, ${location.countryCode}${region}) [${location.id}]`);
2903
+ }
2904
+ }
2905
+ async function adsConversionPixels(project, opts) {
2906
+ const result = await getClient5().getAdsConversionPixels(project);
2907
+ if (opts?.format === "jsonl") {
2908
+ emitJsonl(result.pixels.map((pixel) => ({ project, ...pixel })));
2909
+ return;
2910
+ }
2911
+ if (isMachineFormat(opts?.format)) {
2912
+ console.log(JSON.stringify(result, null, 2));
2913
+ return;
2914
+ }
2915
+ if (result.pixels.length === 0) {
2916
+ console.log("No OpenAI Ads conversion pixels are configured.");
2917
+ return;
2918
+ }
2919
+ for (const pixel of result.pixels) {
2920
+ const name = pixel.name ?? "Unnamed conversion pixel";
2921
+ const clientType = pixel.clientType ?? "unknown client";
2922
+ const pixelId = pixel.pixelId ? ` pixel ${pixel.pixelId}` : "";
2923
+ console.log(`${name} (${clientType}) [${pixel.id}]${pixelId}`);
2924
+ }
2925
+ }
2926
+ async function adsConversionEventSettings(project, opts) {
2927
+ const result = await getClient5().getAdsConversionEventSettings(project);
2928
+ if (opts?.format === "jsonl") {
2929
+ emitJsonl(result.eventSettings.map((eventSetting) => ({ project, ...eventSetting })));
2930
+ return;
2931
+ }
2932
+ if (isMachineFormat(opts?.format)) {
2933
+ console.log(JSON.stringify(result, null, 2));
2934
+ return;
2935
+ }
2936
+ if (result.eventSettings.length === 0) {
2937
+ console.log("No OpenAI Ads conversion event settings are configured.");
2938
+ return;
2939
+ }
2940
+ for (const eventSetting of result.eventSettings) {
2941
+ const name = eventSetting.name ?? "Unnamed conversion event";
2942
+ const eventType = eventSetting.eventType ?? "unknown event";
2943
+ const attribution = eventSetting.attributionWindowDays === void 0 ? "unknown attribution window" : `${eventSetting.attributionWindowDays}d attribution`;
2944
+ const sources = eventSetting.sources?.map((source) => source.name ?? source.id).join(", ") || "no source details";
2945
+ const archived = eventSetting.archived === true ? " [archived]" : "";
2946
+ console.log(
2947
+ `${name}${archived}: ${eventType}, ${attribution}, ${sources} [${eventSetting.id}]`
2948
+ );
2949
+ }
2950
+ }
2816
2951
  async function adsOperationGet(project, opts) {
2817
2952
  printOperation(await getClient5().getAdsOperation(project, opts.operationKey), opts.format);
2818
2953
  }
2954
+ async function adsOperationsUnresolved(project, opts) {
2955
+ const query = {
2956
+ state: opts?.state,
2957
+ limit: opts?.limit,
2958
+ cursor: opts?.cursor
2959
+ };
2960
+ const client = getClient5();
2961
+ const result = Object.values(query).some((value) => value !== void 0) ? await client.getUnresolvedAdsOperations(project, query) : await client.getUnresolvedAdsOperations(project);
2962
+ if (opts?.format === "jsonl") {
2963
+ emitJsonl(result.operations.map((operation) => ({ project, ...operation })));
2964
+ return;
2965
+ }
2966
+ if (isMachineFormat(opts?.format)) {
2967
+ console.log(JSON.stringify(result, null, 2));
2968
+ return;
2969
+ }
2970
+ if (result.operations.length === 0) {
2971
+ console.log("No unresolved OpenAI Ads mutation receipts.");
2972
+ return;
2973
+ }
2974
+ console.log("STATE KIND OPERATION KEY");
2975
+ for (const operation of result.operations) {
2976
+ console.log(`${operation.state.padEnd(13)} ${operation.kind.padEnd(21)} ${operation.operationKey}`);
2977
+ }
2978
+ if (result.nextCursor) console.log(`Next cursor: ${result.nextCursor}`);
2979
+ }
2980
+ async function adsOperationReconcile(project, opts) {
2981
+ printReconciliation(
2982
+ await getClient5().reconcileAdsOperation(project, opts.operationKey),
2983
+ opts.format
2984
+ );
2985
+ }
2986
+ async function adsOperationResumeActivation(project, opts) {
2987
+ printActivation(
2988
+ await getClient5().resumeAdsActivation(project, opts.operationKey),
2989
+ opts.format
2990
+ );
2991
+ }
2992
+ async function adsActivationGrantCreate(project, opts) {
2993
+ const request = readRequest(
2994
+ opts.input,
2995
+ adsActivationGrantCreateRequestSchema
2996
+ );
2997
+ printActivationGrant(await getClient5().createAdsActivationGrant(project, request), "Approved", opts.format);
2998
+ }
2999
+ async function adsActivationGrantRevoke(project, grantId, opts) {
3000
+ printActivationGrant(
3001
+ await getClient5().revokeAdsActivationGrant(project, grantId),
3002
+ "Revoked",
3003
+ opts?.format
3004
+ );
3005
+ }
2819
3006
  async function adsImageUpload(project, opts) {
2820
3007
  const request = readRequest(opts.input, adsImageUploadRequestSchema);
2821
3008
  printOperation(await getClient5().uploadAdsImage(project, request), opts.format);
@@ -2832,6 +3019,10 @@ async function adsCampaignPause(project, campaignId, opts) {
2832
3019
  const request = readRequest(opts.input, adsPauseRequestSchema);
2833
3020
  printOperation(await getClient5().pauseAdsCampaign(project, campaignId, request), opts.format);
2834
3021
  }
3022
+ async function adsCampaignActivateTree(project, campaignId, opts) {
3023
+ const request = readRequest(opts.input, adsActivateTreeRequestSchema);
3024
+ printActivation(await getClient5().activateAdsCampaignTree(project, campaignId, request), opts.format);
3025
+ }
2835
3026
  async function adsAdGroupCreate(project, opts) {
2836
3027
  const request = readRequest(opts.input, adsAdGroupCreateRequestSchema);
2837
3028
  printOperation(await getClient5().createAdsAdGroup(project, request), opts.format);
@@ -2973,6 +3164,87 @@ var ADS_CLI_COMMANDS = [
2973
3164
  await adsStatus(project, { format: input.format });
2974
3165
  }
2975
3166
  },
3167
+ {
3168
+ path: ["ads", "account"],
3169
+ usage: "canonry ads account <project> [--format json]",
3170
+ run: async (input) => {
3171
+ const project = requireProject(input, "ads.account", "canonry ads account <project> [--format json]");
3172
+ await adsAccount(project, { format: input.format });
3173
+ }
3174
+ },
3175
+ {
3176
+ path: ["ads", "geo", "search"],
3177
+ usage: "canonry ads geo search <project> --query <text> [--limit <n>] [--format json|jsonl]",
3178
+ options: {
3179
+ query: stringOption(),
3180
+ limit: stringOption()
3181
+ },
3182
+ run: async (input) => {
3183
+ const usage = "canonry ads geo search <project> --query <text> [--limit <n>] [--format json|jsonl]";
3184
+ const project = requireProject(input, "ads.geo.search", usage);
3185
+ await adsGeoSearch(project, {
3186
+ q: getString(input.values, "query"),
3187
+ limit: parseIntegerOption(input, "limit", {
3188
+ command: "ads.geo.search",
3189
+ message: "--limit must be an integer from 1 to 100",
3190
+ usage
3191
+ }),
3192
+ format: input.format
3193
+ });
3194
+ }
3195
+ },
3196
+ {
3197
+ path: ["ads", "conversions", "pixels"],
3198
+ usage: "canonry ads conversions pixels <project> [--format json|jsonl]",
3199
+ run: async (input) => {
3200
+ const project = requireProject(input, "ads.conversions.pixels", "canonry ads conversions pixels <project> [--format json|jsonl]");
3201
+ await adsConversionPixels(project, { format: input.format });
3202
+ }
3203
+ },
3204
+ {
3205
+ path: ["ads", "conversions", "event-settings"],
3206
+ usage: "canonry ads conversions event-settings <project> [--format json|jsonl]",
3207
+ run: async (input) => {
3208
+ const project = requireProject(input, "ads.conversions.event-settings", "canonry ads conversions event-settings <project> [--format json|jsonl]");
3209
+ await adsConversionEventSettings(project, { format: input.format });
3210
+ }
3211
+ },
3212
+ {
3213
+ path: ["ads", "operations", "unresolved"],
3214
+ usage: "canonry ads operations unresolved <project> [--state <csv>] [--limit <n>] [--cursor <opaque>] [--format json|jsonl]",
3215
+ options: {
3216
+ state: stringOption(),
3217
+ limit: stringOption(),
3218
+ cursor: stringOption()
3219
+ },
3220
+ run: async (input) => {
3221
+ const usage = "canonry ads operations unresolved <project> [--state <csv>] [--limit <n>] [--cursor <opaque>] [--format json|jsonl]";
3222
+ const project = requireProject(
3223
+ input,
3224
+ "ads.operations.unresolved",
3225
+ usage
3226
+ );
3227
+ const rawState = getString(input.values, "state");
3228
+ const state = rawState === void 0 ? void 0 : rawState.split(",").filter((value) => value === "pending" || value === "unknown" || value === "reconciling");
3229
+ if (rawState !== void 0 && state?.length !== rawState.split(",").length) {
3230
+ throw usageError(`Error: --state must contain only pending, unknown, or reconciling
3231
+ Usage: ${usage}`, {
3232
+ message: "--state must contain only pending, unknown, or reconciling",
3233
+ details: { command: "ads.operations.unresolved", usage }
3234
+ });
3235
+ }
3236
+ await adsOperationsUnresolved(project, {
3237
+ state,
3238
+ limit: parseIntegerOption(input, "limit", {
3239
+ command: "ads.operations.unresolved",
3240
+ message: "--limit must be an integer from 1 to 200",
3241
+ usage
3242
+ }),
3243
+ cursor: getString(input.values, "cursor"),
3244
+ format: input.format
3245
+ });
3246
+ }
3247
+ },
2976
3248
  {
2977
3249
  path: ["ads", "operation"],
2978
3250
  usage: "canonry ads operation <project> <operation-key> [--format json]",
@@ -2987,6 +3259,70 @@ var ADS_CLI_COMMANDS = [
2987
3259
  await adsOperationGet(project, { operationKey, format: input.format });
2988
3260
  }
2989
3261
  },
3262
+ {
3263
+ path: ["ads", "operation", "reconcile"],
3264
+ usage: "canonry ads operation reconcile <project> --operation-key <key> [--format json]",
3265
+ options: {
3266
+ "operation-key": stringOption()
3267
+ },
3268
+ run: async (input) => {
3269
+ const usage = "canonry ads operation reconcile <project> --operation-key <key> [--format json]";
3270
+ const project = requireProject(input, "ads.operation.reconcile", usage);
3271
+ const operationKey = requireStringOption(input, "operation-key", {
3272
+ command: "ads.operation.reconcile",
3273
+ usage,
3274
+ message: "--operation-key is required"
3275
+ });
3276
+ await adsOperationReconcile(project, {
3277
+ operationKey,
3278
+ format: input.format
3279
+ });
3280
+ }
3281
+ },
3282
+ {
3283
+ path: ["ads", "operation", "resume-activation"],
3284
+ usage: "canonry ads operation resume-activation <project> --operation-key <key> [--format json]",
3285
+ options: { "operation-key": stringOption() },
3286
+ run: async (input) => {
3287
+ const usage = "canonry ads operation resume-activation <project> --operation-key <key> [--format json]";
3288
+ const project = requireProject(input, "ads.operation.resume-activation", usage);
3289
+ await adsOperationResumeActivation(project, {
3290
+ operationKey: requireStringOption(input, "operation-key", {
3291
+ command: "ads.operation.resume-activation",
3292
+ message: "--operation-key is required",
3293
+ usage
3294
+ }),
3295
+ format: input.format
3296
+ });
3297
+ }
3298
+ },
3299
+ {
3300
+ path: ["ads", "activation-grant", "create"],
3301
+ usage: "canonry ads activation-grant create <project> --input <json-file|-> [--format json]",
3302
+ options: { input: stringOption() },
3303
+ run: async (input) => {
3304
+ const usage = "canonry ads activation-grant create <project> --input <json-file|-> [--format json]";
3305
+ const project = requireProject(input, "ads.activation-grant.create", usage);
3306
+ await adsActivationGrantCreate(project, {
3307
+ input: getString(input.values, "input"),
3308
+ format: input.format
3309
+ });
3310
+ }
3311
+ },
3312
+ {
3313
+ path: ["ads", "activation-grant", "revoke"],
3314
+ usage: "canonry ads activation-grant revoke <project> <grant-id> [--format json]",
3315
+ run: async (input) => {
3316
+ const usage = "canonry ads activation-grant revoke <project> <grant-id> [--format json]";
3317
+ const project = requireProject(input, "ads.activation-grant.revoke", usage);
3318
+ const grantId = requirePositional(input, 1, {
3319
+ command: "ads.activation-grant.revoke",
3320
+ usage,
3321
+ message: "activation grant id is required"
3322
+ });
3323
+ await adsActivationGrantRevoke(project, grantId, { format: input.format });
3324
+ }
3325
+ },
2990
3326
  {
2991
3327
  path: ["ads", "image", "upload"],
2992
3328
  usage: "canonry ads image upload <project> --input <json-file|-> [--format json]",
@@ -3016,6 +3352,24 @@ var ADS_CLI_COMMANDS = [
3016
3352
  await adsCampaignUpdate(project, id, { input: getString(input.values, "input"), format: input.format });
3017
3353
  }
3018
3354
  },
3355
+ {
3356
+ path: ["ads", "campaign", "activate-tree"],
3357
+ usage: "canonry ads campaign activate-tree <project> <campaign-id> --input <json-file|-> [--format json]",
3358
+ options: { input: stringOption() },
3359
+ run: async (input) => {
3360
+ const usage = "canonry ads campaign activate-tree <project> <campaign-id> --input <json-file|-> [--format json]";
3361
+ const project = requireProject(input, "ads.campaign.activate-tree", usage);
3362
+ const id = requirePositional(input, 1, {
3363
+ command: "ads.campaign.activate-tree",
3364
+ usage,
3365
+ message: "campaign id is required"
3366
+ });
3367
+ await adsCampaignActivateTree(project, id, {
3368
+ input: getString(input.values, "input"),
3369
+ format: input.format
3370
+ });
3371
+ }
3372
+ },
3019
3373
  {
3020
3374
  path: ["ads", "campaign", "pause"],
3021
3375
  usage: "canonry ads campaign pause <project> <campaign-id> --input <json-file|-> [--format json]",
@@ -3409,10 +3763,16 @@ async function gaSessionHistory(project, opts) {
3409
3763
  console.log(` ${"DATE".padEnd(dateWidth)} ${"SESSIONS".padEnd(10)}${"ORGANIC".padEnd(10)}${"USERS".padEnd(8)}`);
3410
3764
  console.log(` ${"\u2500".repeat(dateWidth)} ${"\u2500".repeat(10)}${"\u2500".repeat(10)}${"\u2500".repeat(8)}`);
3411
3765
  for (const row of result) {
3766
+ const users = row.usersSource === "deduplicated" ? String(row.users) : `${row.users}*`;
3412
3767
  console.log(
3413
- ` ${row.date.padEnd(dateWidth)} ${String(row.sessions).padEnd(10)}${String(row.organicSessions).padEnd(10)}${String(row.users).padEnd(8)}`
3768
+ ` ${row.date.padEnd(dateWidth)} ${String(row.sessions).padEnd(10)}${String(row.organicSessions).padEnd(10)}${users.padEnd(8)}`
3414
3769
  );
3415
3770
  }
3771
+ if (result.some((row) => row.usersSource !== "deduplicated")) {
3772
+ console.log("\n * Visitor count is approximate \u2014 it adds up each page a visitor landed on,");
3773
+ console.log(" so someone who read several pages is counted more than once. Run");
3774
+ console.log(` "canonry ga sync ${project}" to replace these with exact counts.`);
3775
+ }
3416
3776
  }
3417
3777
  async function gaCoverage(project, format) {
3418
3778
  const client = getClient6();
@@ -7634,11 +7994,29 @@ async function showAnalytics(project, options) {
7634
7994
  console.log(JSON.stringify(results, null, 2));
7635
7995
  }
7636
7996
  }
7997
+ var BUCKET_DATE_WIDTH = 35;
7998
+ function bucketDates(bucket) {
7999
+ const legacyCompatible = bucket;
8000
+ const start = legacyCompatible.dataStartDate?.slice(0, 10);
8001
+ const end = legacyCompatible.dataEndDate?.slice(0, 10);
8002
+ if (!start || !end) return "date unavailable";
8003
+ const sweeps = legacyCompatible.sweepCount ?? 1;
8004
+ const pooled = sweeps > 1 ? ` (${sweeps} sweeps)` : "";
8005
+ if (start === end) return `${start}${pooled}`;
8006
+ return `${start} \u2192 ${end}${pooled}`;
8007
+ }
7637
8008
  function printMetrics(data) {
7638
8009
  console.log(`
7639
8010
  Citation Rate Trends (${data.window})`);
7640
8011
  console.log("\u2500".repeat(50));
7641
8012
  const pct4 = (n) => `${(n * 100).toFixed(1)}%`;
8013
+ const modelChangeNotice = buildModelChangeNotice(readModelPointerChanges(data));
8014
+ if (modelChangeNotice?.kind === "change") {
8015
+ console.log(`
8016
+ Model Updates Behind These Numbers:`);
8017
+ console.log(` ${modelChangeNotice.text}`);
8018
+ console.log("");
8019
+ }
7642
8020
  console.log(` Overall: ${pct4(data.overall.citationRate)} (${data.overall.cited}/${data.overall.total})`);
7643
8021
  console.log(` Trend: ${data.trend}`);
7644
8022
  if (Object.keys(data.byProvider).length > 0) {
@@ -7650,28 +8028,95 @@ Citation Rate Trends (${data.window})`);
7650
8028
  }
7651
8029
  if (data.buckets.length > 0) {
7652
8030
  console.log(`
7653
- Timeline:`);
8031
+ Timeline (dates in UTC):`);
7654
8032
  for (const bucket of data.buckets) {
7655
- const start = bucket.startDate.slice(0, 10);
7656
8033
  const bar = bucket.total > 0 ? "\u2588".repeat(Math.round(bucket.citationRate * 20)) : "";
7657
- console.log(` ${start} ${pct4(bucket.citationRate).padStart(6)} ${bar}`);
8034
+ console.log(` ${bucketDates(bucket).padEnd(BUCKET_DATE_WIDTH)} ${pct4(bucket.citationRate).padStart(6)} ${bar}`);
7658
8035
  }
7659
8036
  }
7660
8037
  const providersInBuckets = [...new Set(data.buckets.flatMap((b) => Object.keys(b.byProvider ?? {})))].sort();
7661
8038
  if (data.buckets.length > 0 && providersInBuckets.length > 0) {
7662
8039
  console.log(`
7663
- By Provider Timeline:`);
8040
+ By Provider Timeline (dates in UTC):`);
7664
8041
  for (const provider of providersInBuckets) {
7665
8042
  console.log(` ${provider}:`);
7666
8043
  for (const bucket of data.buckets) {
7667
8044
  const metric = bucket.byProvider?.[provider];
7668
8045
  if (!metric) continue;
7669
- const start = bucket.startDate.slice(0, 10);
7670
8046
  const bar = metric.total > 0 ? "\u2588".repeat(Math.round(metric.citationRate * 20)) : "";
7671
- console.log(` ${start} ${pct4(metric.citationRate).padStart(6)} ${bar}`);
8047
+ console.log(` ${bucketDates(bucket).padEnd(BUCKET_DATE_WIDTH)} ${pct4(metric.citationRate).padStart(6)} ${bar}`);
8048
+ }
8049
+ }
8050
+ }
8051
+ if (modelChangeNotice?.kind === "no-known-change") {
8052
+ console.log(`
8053
+ Model Updates Behind These Numbers:`);
8054
+ console.log(` ${modelChangeNotice.text}`);
8055
+ console.log(` ${modelChangeNotice.detail}`);
8056
+ }
8057
+ const attributionEntries = Object.entries(readModelAttribution(data)).sort(([a], [b]) => a.localeCompare(b));
8058
+ if (attributionEntries.length > 0) {
8059
+ console.log(`
8060
+ Model Evidence:`);
8061
+ for (const [provider, attribution] of attributionEntries) {
8062
+ const latest = attribution.latestObservation;
8063
+ console.log(` ${provider}: latest ${formatModelEvidence(latest.state)} at ${latest.observedAt}`);
8064
+ for (const event of attribution.events) {
8065
+ const dating = event.fromPreWindowAnchor ? " (on or before)" : "";
8066
+ const priorSweep = event.fromPreWindowAnchor && event.anchorObservedAt ? ` [last seen ${formatModelEvidence(event.from)} on ${event.anchorObservedAt}]` : "";
8067
+ console.log(` ${event.observedAt}${dating} ${formatModelEvidence(event.from)} \u2192 ${formatModelEvidence(event.to)}${priorSweep}`);
8068
+ }
8069
+ const eventTotal = attribution.eventTotal ?? attribution.events.length;
8070
+ if (eventTotal > attribution.events.length) {
8071
+ console.log(` Showing the latest ${attribution.events.length} of ${eventTotal} model changes.`);
8072
+ }
8073
+ if (attribution.anchorUnavailable) {
8074
+ console.log(` We did not look far enough back to be sure this is every change.`);
7672
8075
  }
7673
8076
  }
7674
8077
  }
8078
+ const servedEntries = Object.entries(readServedModelAttribution(data)).sort(([a], [b]) => a.localeCompare(b));
8079
+ if (servedEntries.length > 0) {
8080
+ const mismatch = readModelServiceMismatch(data);
8081
+ console.log(`
8082
+ What the Engines Answered With:`);
8083
+ for (const [provider, served] of servedEntries) {
8084
+ const rawIds = served.latestServedModelIds.length > 0 ? served.latestServedModelIds.join(", ") : formatModelEvidence(served.latestObservation.state);
8085
+ const substituted = mismatch[provider];
8086
+ const note = substituted ? ` \u2014 not the ${formatModelEvidence(substituted.configured)} you selected` : "";
8087
+ console.log(` ${provider}: ${rawIds} at ${served.latestObservation.observedAt}${note}`);
8088
+ for (const event of served.events) {
8089
+ const dating = event.fromPreWindowAnchor ? " (on or before)" : "";
8090
+ console.log(` ${event.observedAt}${dating} ${formatModelEvidence(event.from)} \u2192 ${formatModelEvidence(event.to)}`);
8091
+ }
8092
+ }
8093
+ }
8094
+ }
8095
+ function readServedModelAttribution(data) {
8096
+ const legacyCompatible = data;
8097
+ return legacyCompatible.servedModelAttribution ?? {};
8098
+ }
8099
+ function readModelServiceMismatch(data) {
8100
+ const legacyCompatible = data;
8101
+ return legacyCompatible.modelServiceMismatch ?? {};
8102
+ }
8103
+ function readModelPointerChanges(data) {
8104
+ const legacyCompatible = data;
8105
+ return legacyCompatible.modelPointerChanges ?? {};
8106
+ }
8107
+ function readModelAttribution(data) {
8108
+ const legacyCompatible = data;
8109
+ return legacyCompatible.modelAttribution ?? {};
8110
+ }
8111
+ function formatModelEvidence(state) {
8112
+ switch (state.status) {
8113
+ case "known":
8114
+ return `known ${state.model}`;
8115
+ case "unknown":
8116
+ return "unknown";
8117
+ case "mixed":
8118
+ return `mixed ${state.models.join(", ")}${state.includesUnknown ? " + unknown" : ""}`;
8119
+ }
7675
8120
  }
7676
8121
  function printGaps(data) {
7677
8122
  console.log(`
@@ -8128,7 +8573,9 @@ async function createProject(name, opts) {
8128
8573
  ownedDomains: opts.ownedDomains ?? [],
8129
8574
  aliases: normalizeProjectAliases(opts.displayName, opts.aliases ?? []),
8130
8575
  country: opts.country,
8131
- language: opts.language
8576
+ language: opts.language,
8577
+ providers: opts.providers ?? [],
8578
+ providerModels: opts.providerModels ?? {}
8132
8579
  });
8133
8580
  if (isMachineFormat(opts.format)) {
8134
8581
  console.log(JSON.stringify(result, null, 2));
@@ -8191,6 +8638,9 @@ async function showProject(name, format) {
8191
8638
  console.log(` Language: ${project.language}`);
8192
8639
  console.log(` Config source: ${project.configSource}`);
8193
8640
  console.log(` Config revision: ${project.configRevision}`);
8641
+ console.log(` Providers: ${(project.providers ?? []).length > 0 ? project.providers.join(", ") : "all configured"}`);
8642
+ const providerModels = Object.entries(project.providerModels ?? {});
8643
+ console.log(` Model overrides: ${providerModels.length > 0 ? providerModels.map(([provider, model]) => `${provider}=${model}`).join(", ") : "(none; instance settings inherited)"}`);
8194
8644
  console.log(` Tags: ${project.tags.length > 0 ? project.tags.join(", ") : "(none)"}`);
8195
8645
  const labelEntries = Object.entries(project.labels);
8196
8646
  console.log(` Labels: ${labelEntries.length > 0 ? labelEntries.map(([k, v]) => `${k}=${v}`).join(", ") : "(none)"}`);
@@ -8220,19 +8670,45 @@ async function updateProjectSettings(name, opts) {
8220
8670
  const toRemove = new Set(opts.removeAlias.map((a) => a.toLowerCase()));
8221
8671
  aliases = aliases.filter((a) => !toRemove.has(a.toLowerCase()));
8222
8672
  }
8673
+ const providerModels = { ...project.providerModels ?? {}, ...opts.providerModels ?? {} };
8674
+ for (const provider of opts.clearProviderModels ?? []) delete providerModels[provider];
8675
+ const nextProviders = opts.providers ?? project.providers ?? [];
8676
+ if (nextProviders.length > 0) {
8677
+ const requested = Object.keys(opts.providerModels ?? {}).filter((p) => !nextProviders.includes(p));
8678
+ if (requested.length > 0) {
8679
+ throw usageError(
8680
+ `Error: --provider-model set for ${requested.join(", ")}, which ${requested.length > 1 ? "are" : "is"} not a selected engine for "${name}"`,
8681
+ {
8682
+ message: "provider-model set for a provider the project does not run",
8683
+ details: { command: "project.update", project: name, providers: nextProviders, unselected: requested }
8684
+ }
8685
+ );
8686
+ }
8687
+ }
8223
8688
  const result = await client.putProject(name, {
8224
8689
  displayName: nextDisplayName,
8225
8690
  canonicalDomain: opts.domain ?? project.canonicalDomain,
8226
8691
  ownedDomains,
8227
8692
  aliases: normalizeProjectAliases(nextDisplayName, aliases),
8228
8693
  country: opts.country ?? project.country,
8229
- language: opts.language ?? project.language
8694
+ language: opts.language ?? project.language,
8695
+ tags: project.tags,
8696
+ labels: project.labels,
8697
+ providers: opts.providers ?? project.providers,
8698
+ providerModels,
8699
+ locations: project.locations,
8700
+ defaultLocation: project.defaultLocation,
8701
+ autoExtractBacklinks: project.autoExtractBacklinks
8230
8702
  });
8231
8703
  if (isMachineFormat(opts.format)) {
8232
8704
  console.log(JSON.stringify(result, null, 2));
8233
8705
  return;
8234
8706
  }
8235
8707
  console.log(`Project updated: ${result.name}`);
8708
+ const dropped = Object.keys(providerModels).filter((provider) => !(provider in result.providerModels));
8709
+ if (dropped.length > 0) {
8710
+ console.log(` Dropped model override for deselected engine(s): ${dropped.join(", ")}`);
8711
+ }
8236
8712
  }
8237
8713
  async function deleteProject(name, opts) {
8238
8714
  const client = getClient20();
@@ -8336,20 +8812,22 @@ async function setDefaultLocation(project, label, format) {
8336
8812
  var PROJECT_CLI_COMMANDS = [
8337
8813
  {
8338
8814
  path: ["project", "create"],
8339
- usage: "canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]",
8815
+ usage: "canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--provider <name>...] [--provider-model provider=model...] [--format json]",
8340
8816
  options: {
8341
8817
  domain: { type: "string", short: "d" },
8342
8818
  "owned-domain": multiStringOption(),
8343
8819
  alias: multiStringOption(),
8344
8820
  country: stringOption(),
8345
8821
  language: stringOption(),
8346
- "display-name": stringOption()
8822
+ "display-name": stringOption(),
8823
+ provider: multiStringOption(),
8824
+ "provider-model": multiStringOption()
8347
8825
  },
8348
8826
  run: async (input) => {
8349
8827
  const name = requireProject(
8350
8828
  input,
8351
8829
  "project.create",
8352
- "canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]"
8830
+ "canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--provider <name>...] [--provider-model provider=model...] [--format json]"
8353
8831
  );
8354
8832
  await createProject(name, {
8355
8833
  domain: getString(input.values, "domain") ?? name,
@@ -8358,13 +8836,15 @@ var PROJECT_CLI_COMMANDS = [
8358
8836
  country: getString(input.values, "country") ?? "US",
8359
8837
  language: getString(input.values, "language") ?? "en",
8360
8838
  displayName: getString(input.values, "display-name") ?? name,
8839
+ providers: getStringArray(input.values, "provider") ?? [],
8840
+ providerModels: parseProviderModelAssignments(getStringArray(input.values, "provider-model")),
8361
8841
  format: input.format
8362
8842
  });
8363
8843
  }
8364
8844
  },
8365
8845
  {
8366
8846
  path: ["project", "update"],
8367
- usage: "canonry project update <name> [--domain <domain>] [--owned-domain <domain>...] [--add-domain <domain>...] [--remove-domain <domain>...] [--alias <name>...] [--add-alias <name>...] [--remove-alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]",
8847
+ usage: "canonry project update <name> [--domain <domain>] [--owned-domain <domain>...] [--add-domain <domain>...] [--remove-domain <domain>...] [--alias <name>...] [--add-alias <name>...] [--remove-alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--provider <name>...] [--all-providers] [--provider-model provider=model...] [--clear-provider-model <provider>...] [--format json]",
8368
8848
  options: {
8369
8849
  domain: { type: "string", short: "d" },
8370
8850
  "owned-domain": multiStringOption(),
@@ -8375,14 +8855,26 @@ var PROJECT_CLI_COMMANDS = [
8375
8855
  "remove-alias": multiStringOption(),
8376
8856
  country: stringOption(),
8377
8857
  language: stringOption(),
8378
- "display-name": stringOption()
8858
+ "display-name": stringOption(),
8859
+ provider: multiStringOption(),
8860
+ "all-providers": { type: "boolean" },
8861
+ "provider-model": multiStringOption(),
8862
+ "clear-provider-model": multiStringOption()
8379
8863
  },
8380
8864
  run: async (input) => {
8381
8865
  const name = requireProject(
8382
8866
  input,
8383
8867
  "project.update",
8384
- "canonry project update <name> [--domain <domain>] [--owned-domain <domain>...] [--add-domain <domain>...] [--remove-domain <domain>...] [--alias <name>...] [--add-alias <name>...] [--remove-alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]"
8868
+ "canonry project update <name> [--domain <domain>] [--owned-domain <domain>...] [--add-domain <domain>...] [--remove-domain <domain>...] [--alias <name>...] [--add-alias <name>...] [--remove-alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--provider <name>...] [--all-providers] [--provider-model provider=model...] [--clear-provider-model <provider>...] [--format json]"
8385
8869
  );
8870
+ const providers = getStringArray(input.values, "provider");
8871
+ const allProviders = getBoolean(input.values, "all-providers");
8872
+ if (allProviders && providers?.length) throw usageError("Error: --all-providers conflicts with --provider");
8873
+ const providerModels = parseProviderModelAssignments(getStringArray(input.values, "provider-model"));
8874
+ const clearProviderModels = getStringArray(input.values, "clear-provider-model") ?? [];
8875
+ for (const provider of clearProviderModels) {
8876
+ if (provider in providerModels) throw usageError(`Error: --provider-model and --clear-provider-model conflict for ${provider}`);
8877
+ }
8386
8878
  await updateProjectSettings(name, {
8387
8879
  displayName: getString(input.values, "display-name"),
8388
8880
  domain: getString(input.values, "domain"),
@@ -8394,6 +8886,9 @@ var PROJECT_CLI_COMMANDS = [
8394
8886
  removeAlias: getStringArray(input.values, "remove-alias"),
8395
8887
  country: getString(input.values, "country"),
8396
8888
  language: getString(input.values, "language"),
8889
+ providers: allProviders ? [] : providers,
8890
+ providerModels,
8891
+ clearProviderModels,
8397
8892
  format: input.format
8398
8893
  });
8399
8894
  }
@@ -8509,6 +9004,18 @@ var PROJECT_CLI_COMMANDS = [
8509
9004
  }
8510
9005
  }
8511
9006
  ];
9007
+ function parseProviderModelAssignments(assignments) {
9008
+ const result = {};
9009
+ for (const assignment of assignments ?? []) {
9010
+ const separator = assignment.indexOf("=");
9011
+ const provider = separator === -1 ? "" : assignment.slice(0, separator).trim();
9012
+ const model = separator === -1 ? "" : assignment.slice(separator + 1).trim();
9013
+ if (!provider || !model) throw usageError(`Error: --provider-model must use provider=model (received ${assignment})`);
9014
+ if (provider in result) throw usageError(`Error: duplicate --provider-model assignment for ${provider}`);
9015
+ result[provider] = model;
9016
+ }
9017
+ return result;
9018
+ }
8512
9019
 
8513
9020
  // src/commands/report.ts
8514
9021
  import fs9 from "fs";
@@ -10944,18 +11451,36 @@ async function bootstrapCommand(_opts) {
10944
11451
  } : existingConfig?.google;
10945
11452
  const keyHash = crypto.createHash("sha256").update(rawApiKey).digest("hex");
10946
11453
  const keyPrefix = rawApiKey.slice(0, 9);
11454
+ const existingConfigKeyHash = existingConfig ? crypto.createHash("sha256").update(existingConfig.apiKey).digest("hex") : void 0;
10947
11455
  const db = createClient(databasePath);
10948
11456
  migrate(db);
10949
11457
  db.transaction((tx) => {
10950
- tx.delete(apiKeys).where(eq(apiKeys.name, "default")).run();
10951
- tx.insert(apiKeys).values({
10952
- id: crypto.randomUUID(),
10953
- name: "default",
10954
- keyHash,
10955
- keyPrefix,
10956
- scopes: ["*"],
10957
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
10958
- }).run();
11458
+ const rotatedAt = (/* @__PURE__ */ new Date()).toISOString();
11459
+ const existingDefaults = tx.select({ id: apiKeys.id, keyHash: apiKeys.keyHash }).from(apiKeys).where(eq(apiKeys.name, "default")).all();
11460
+ const existingDefault = existingDefaults.find((key) => key.keyHash === existingConfigKeyHash) ?? existingDefaults.at(0);
11461
+ if (existingDefault) {
11462
+ tx.update(apiKeys).set({
11463
+ keyHash,
11464
+ keyPrefix,
11465
+ scopes: ["*"],
11466
+ projectId: null,
11467
+ lastUsedAt: null,
11468
+ revokedAt: null
11469
+ }).where(eq(apiKeys.id, existingDefault.id)).run();
11470
+ for (const duplicate of existingDefaults) {
11471
+ if (duplicate.id === existingDefault.id) continue;
11472
+ tx.update(apiKeys).set({ revokedAt: rotatedAt }).where(eq(apiKeys.id, duplicate.id)).run();
11473
+ }
11474
+ } else {
11475
+ tx.insert(apiKeys).values({
11476
+ id: crypto.randomUUID(),
11477
+ name: "default",
11478
+ keyHash,
11479
+ keyPrefix,
11480
+ scopes: ["*"],
11481
+ createdAt: rotatedAt
11482
+ }).run();
11483
+ }
10959
11484
  });
10960
11485
  saveConfig({
10961
11486
  apiUrl: env.apiUrl || existingConfig?.apiUrl || `http://localhost:${process.env.CANONRY_PORT || "4100"}`,