@canonry/canonry 4.117.2 → 4.123.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 (35) 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 +161 -2
  4. package/assets/assets/AuditHistoryPanel-BVQMDIhQ.js +1 -0
  5. package/assets/assets/{BacklinksPage-CschVQxC.js → BacklinksPage-nrHT9VMG.js} +1 -1
  6. package/assets/assets/ChartPrimitives-_8qLsQzI.js +1 -0
  7. package/assets/assets/HistoryPage-DqsKKdyy.js +1 -0
  8. package/assets/assets/ProjectPage-GyKoQUv_.js +7 -0
  9. package/assets/assets/{RunRow-ggNGlwdN.js → RunRow-BRZZcSjQ.js} +1 -1
  10. package/assets/assets/RunsPage-24aI8DKF.js +1 -0
  11. package/assets/assets/{SettingsPage-Cy9SlBJX.js → SettingsPage-BaNeTG1i.js} +1 -1
  12. package/assets/assets/{TrafficPage-1S2PzSHB.js → TrafficPage-ByvcLSjv.js} +1 -1
  13. package/assets/assets/{TrafficSourceDetailPage-CqXCoaod.js → TrafficSourceDetailPage-B-siTXVg.js} +1 -1
  14. package/assets/assets/{arrow-left-kCK5M8Bp.js → arrow-left-CBh1cZNH.js} +1 -1
  15. package/assets/assets/{extract-error-message-C82LAW6q.js → extract-error-message-DJZCPudR.js} +1 -1
  16. package/assets/assets/index-jymsMBpZ.css +1 -0
  17. package/assets/assets/index-mzLganDP.js +210 -0
  18. package/assets/assets/{trash-2-8Pe4v1T0.js → trash-2-Bh-Yvkgc.js} +1 -1
  19. package/assets/assets/vendor-recharts-DSxTZhaH.js +36 -0
  20. package/assets/index.html +3 -3
  21. package/dist/{chunk-BMPIV35C.js → chunk-4AZ7VES6.js} +5726 -697
  22. package/dist/{chunk-4PMMEFME.js → chunk-HJIIJI7K.js} +816 -18
  23. package/dist/{chunk-F2SRZJMY.js → chunk-JYBNBK25.js} +627 -22
  24. package/dist/{chunk-EZME2J2G.js → chunk-MMFSI3OG.js} +3315 -2191
  25. package/dist/cli.js +861 -109
  26. package/dist/index.js +4 -4
  27. package/dist/{intelligence-service-GNOE4J35.js → intelligence-service-K35EN43I.js} +2 -2
  28. package/dist/mcp.js +4 -4
  29. package/package.json +12 -12
  30. package/assets/assets/ChartPrimitives-D4782Ifx.js +0 -1
  31. package/assets/assets/ProjectPage-BSGTpBUJ.js +0 -7
  32. package/assets/assets/RunsPage-Bfp0K6Ry.js +0 -1
  33. package/assets/assets/index-DrDNyUhv.css +0 -1
  34. package/assets/assets/index-i-t64RXw.js +0 -210
  35. 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-F2SRZJMY.js";
32
+ } from "./chunk-JYBNBK25.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-HJIIJI7K.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-BMPIV35C.js";
57
+ } from "./chunk-4AZ7VES6.js";
58
58
  import {
59
+ AdsOperationKinds,
60
+ AdsOperationStates,
61
+ AdsOperationStepStates,
59
62
  BacklinkSources,
60
63
  CcReleaseSyncStatuses,
61
64
  CheckScopes,
@@ -65,7 +68,19 @@ import {
65
68
  REPORT_PERIOD_OPTIONS,
66
69
  RunStatuses,
67
70
  TrafficEventKinds,
71
+ adsActivateTreeRequestSchema,
72
+ adsActivationGrantCreateRequestSchema,
73
+ adsAdCreateRequestSchema,
74
+ adsAdGroupCreateRequestSchema,
75
+ adsAdGroupUpdateRequestSchema,
76
+ adsAdUpdateRequestSchema,
77
+ adsCampaignCreateRequestSchema,
78
+ adsCampaignUpdateRequestSchema,
79
+ adsGeoSearchQuerySchema,
80
+ adsImageUploadRequestSchema,
81
+ adsPauseRequestSchema,
68
82
  backlinkSourceSchema,
83
+ buildModelChangeNotice,
69
84
  discoveryBucketSchema,
70
85
  discoveryCompetitorTypeSchema,
71
86
  effectiveDomains,
@@ -80,7 +95,7 @@ import {
80
95
  providerQuotaPolicySchema,
81
96
  resolveProviderInput,
82
97
  winnabilityClassSchema
83
- } from "./chunk-EZME2J2G.js";
98
+ } from "./chunk-MMFSI3OG.js";
84
99
 
85
100
  // src/cli.ts
86
101
  import { pathToFileURL } from "url";
@@ -2713,9 +2728,80 @@ var DOCTOR_CLI_COMMANDS = [
2713
2728
  ];
2714
2729
 
2715
2730
  // src/commands/ads.ts
2731
+ import fs3 from "fs";
2716
2732
  function getClient5() {
2717
2733
  return createApiClient();
2718
2734
  }
2735
+ function readRequest(inputPath, schema) {
2736
+ if (!inputPath) {
2737
+ throw new CliError({
2738
+ code: "ADS_INPUT_REQUIRED",
2739
+ message: "A JSON input file is required",
2740
+ displayMessage: "Error: --input <json-file> is required (use --input - for stdin)"
2741
+ });
2742
+ }
2743
+ try {
2744
+ const raw = fs3.readFileSync(inputPath === "-" ? 0 : inputPath, "utf8");
2745
+ return schema.parse(JSON.parse(raw));
2746
+ } catch (err) {
2747
+ if (err instanceof CliError) throw err;
2748
+ throw new CliError({
2749
+ code: "ADS_INPUT_INVALID",
2750
+ message: err instanceof Error ? err.message : String(err),
2751
+ displayMessage: `Error: invalid ads JSON input (${err instanceof Error ? err.message : String(err)})`,
2752
+ details: { inputPath }
2753
+ });
2754
+ }
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
+ }
2769
+ function printOperation(result, format) {
2770
+ if (isMachineFormat(format)) {
2771
+ console.log(JSON.stringify(result, null, 2));
2772
+ return;
2773
+ }
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;
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`);
2804
+ }
2719
2805
  function describeConnection(status) {
2720
2806
  const lines = [];
2721
2807
  lines.push(`Connected: ${status.connected ? "yes" : "no"}`);
@@ -2768,6 +2854,199 @@ async function adsStatus(project, opts) {
2768
2854
  console.log("Connect with: canonry ads connect " + project + " --api-key <sdk-key>");
2769
2855
  }
2770
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
+ }
2951
+ async function adsOperationGet(project, opts) {
2952
+ printOperation(await getClient5().getAdsOperation(project, opts.operationKey), opts.format);
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
+ }
3006
+ async function adsImageUpload(project, opts) {
3007
+ const request = readRequest(opts.input, adsImageUploadRequestSchema);
3008
+ printOperation(await getClient5().uploadAdsImage(project, request), opts.format);
3009
+ }
3010
+ async function adsCampaignCreate(project, opts) {
3011
+ const request = readRequest(opts.input, adsCampaignCreateRequestSchema);
3012
+ printOperation(await getClient5().createAdsCampaign(project, request), opts.format);
3013
+ }
3014
+ async function adsCampaignUpdate(project, campaignId, opts) {
3015
+ const request = readRequest(opts.input, adsCampaignUpdateRequestSchema);
3016
+ printOperation(await getClient5().updateAdsCampaign(project, campaignId, request), opts.format);
3017
+ }
3018
+ async function adsCampaignPause(project, campaignId, opts) {
3019
+ const request = readRequest(opts.input, adsPauseRequestSchema);
3020
+ printOperation(await getClient5().pauseAdsCampaign(project, campaignId, request), opts.format);
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
+ }
3026
+ async function adsAdGroupCreate(project, opts) {
3027
+ const request = readRequest(opts.input, adsAdGroupCreateRequestSchema);
3028
+ printOperation(await getClient5().createAdsAdGroup(project, request), opts.format);
3029
+ }
3030
+ async function adsAdGroupUpdate(project, adGroupId, opts) {
3031
+ const request = readRequest(opts.input, adsAdGroupUpdateRequestSchema);
3032
+ printOperation(await getClient5().updateAdsAdGroup(project, adGroupId, request), opts.format);
3033
+ }
3034
+ async function adsAdGroupPause(project, adGroupId, opts) {
3035
+ const request = readRequest(opts.input, adsPauseRequestSchema);
3036
+ printOperation(await getClient5().pauseAdsAdGroup(project, adGroupId, request), opts.format);
3037
+ }
3038
+ async function adsAdCreate(project, opts) {
3039
+ const request = readRequest(opts.input, adsAdCreateRequestSchema);
3040
+ printOperation(await getClient5().createAdsAd(project, request), opts.format);
3041
+ }
3042
+ async function adsAdUpdate(project, adId, opts) {
3043
+ const request = readRequest(opts.input, adsAdUpdateRequestSchema);
3044
+ printOperation(await getClient5().updateAdsAd(project, adId, request), opts.format);
3045
+ }
3046
+ async function adsAdPause(project, adId, opts) {
3047
+ const request = readRequest(opts.input, adsPauseRequestSchema);
3048
+ printOperation(await getClient5().pauseAdsAd(project, adId, request), opts.format);
3049
+ }
2771
3050
  async function adsSync(project, opts) {
2772
3051
  const client = getClient5();
2773
3052
  const result = await client.triggerAdsSync(project);
@@ -2885,6 +3164,285 @@ var ADS_CLI_COMMANDS = [
2885
3164
  await adsStatus(project, { format: input.format });
2886
3165
  }
2887
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
+ },
3248
+ {
3249
+ path: ["ads", "operation"],
3250
+ usage: "canonry ads operation <project> <operation-key> [--format json]",
3251
+ run: async (input) => {
3252
+ const usage = "canonry ads operation <project> <operation-key> [--format json]";
3253
+ const project = requireProject(input, "ads.operation", usage);
3254
+ const operationKey = requirePositional(input, 1, {
3255
+ command: "ads.operation",
3256
+ usage,
3257
+ message: "operation key is required"
3258
+ });
3259
+ await adsOperationGet(project, { operationKey, format: input.format });
3260
+ }
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
+ },
3326
+ {
3327
+ path: ["ads", "image", "upload"],
3328
+ usage: "canonry ads image upload <project> --input <json-file|-> [--format json]",
3329
+ options: { input: stringOption() },
3330
+ run: async (input) => {
3331
+ const project = requireProject(input, "ads.image.upload", "canonry ads image upload <project> --input <json-file|-> [--format json]");
3332
+ await adsImageUpload(project, { input: getString(input.values, "input"), format: input.format });
3333
+ }
3334
+ },
3335
+ {
3336
+ path: ["ads", "campaign", "create"],
3337
+ usage: "canonry ads campaign create <project> --input <json-file|-> [--format json]",
3338
+ options: { input: stringOption() },
3339
+ run: async (input) => {
3340
+ const project = requireProject(input, "ads.campaign.create", "canonry ads campaign create <project> --input <json-file|-> [--format json]");
3341
+ await adsCampaignCreate(project, { input: getString(input.values, "input"), format: input.format });
3342
+ }
3343
+ },
3344
+ {
3345
+ path: ["ads", "campaign", "update"],
3346
+ usage: "canonry ads campaign update <project> <campaign-id> --input <json-file|-> [--format json]",
3347
+ options: { input: stringOption() },
3348
+ run: async (input) => {
3349
+ const usage = "canonry ads campaign update <project> <campaign-id> --input <json-file|-> [--format json]";
3350
+ const project = requireProject(input, "ads.campaign.update", usage);
3351
+ const id = requirePositional(input, 1, { command: "ads.campaign.update", usage, message: "campaign id is required" });
3352
+ await adsCampaignUpdate(project, id, { input: getString(input.values, "input"), format: input.format });
3353
+ }
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
+ },
3373
+ {
3374
+ path: ["ads", "campaign", "pause"],
3375
+ usage: "canonry ads campaign pause <project> <campaign-id> --input <json-file|-> [--format json]",
3376
+ options: { input: stringOption() },
3377
+ run: async (input) => {
3378
+ const usage = "canonry ads campaign pause <project> <campaign-id> --input <json-file|-> [--format json]";
3379
+ const project = requireProject(input, "ads.campaign.pause", usage);
3380
+ const id = requirePositional(input, 1, { command: "ads.campaign.pause", usage, message: "campaign id is required" });
3381
+ await adsCampaignPause(project, id, { input: getString(input.values, "input"), format: input.format });
3382
+ }
3383
+ },
3384
+ {
3385
+ path: ["ads", "ad-group", "create"],
3386
+ usage: "canonry ads ad-group create <project> --input <json-file|-> [--format json]",
3387
+ options: { input: stringOption() },
3388
+ run: async (input) => {
3389
+ const project = requireProject(input, "ads.ad-group.create", "canonry ads ad-group create <project> --input <json-file|-> [--format json]");
3390
+ await adsAdGroupCreate(project, { input: getString(input.values, "input"), format: input.format });
3391
+ }
3392
+ },
3393
+ {
3394
+ path: ["ads", "ad-group", "update"],
3395
+ usage: "canonry ads ad-group update <project> <ad-group-id> --input <json-file|-> [--format json]",
3396
+ options: { input: stringOption() },
3397
+ run: async (input) => {
3398
+ const usage = "canonry ads ad-group update <project> <ad-group-id> --input <json-file|-> [--format json]";
3399
+ const project = requireProject(input, "ads.ad-group.update", usage);
3400
+ const id = requirePositional(input, 1, { command: "ads.ad-group.update", usage, message: "ad group id is required" });
3401
+ await adsAdGroupUpdate(project, id, { input: getString(input.values, "input"), format: input.format });
3402
+ }
3403
+ },
3404
+ {
3405
+ path: ["ads", "ad-group", "pause"],
3406
+ usage: "canonry ads ad-group pause <project> <ad-group-id> --input <json-file|-> [--format json]",
3407
+ options: { input: stringOption() },
3408
+ run: async (input) => {
3409
+ const usage = "canonry ads ad-group pause <project> <ad-group-id> --input <json-file|-> [--format json]";
3410
+ const project = requireProject(input, "ads.ad-group.pause", usage);
3411
+ const id = requirePositional(input, 1, { command: "ads.ad-group.pause", usage, message: "ad group id is required" });
3412
+ await adsAdGroupPause(project, id, { input: getString(input.values, "input"), format: input.format });
3413
+ }
3414
+ },
3415
+ {
3416
+ path: ["ads", "ad", "create"],
3417
+ usage: "canonry ads ad create <project> --input <json-file|-> [--format json]",
3418
+ options: { input: stringOption() },
3419
+ run: async (input) => {
3420
+ const project = requireProject(input, "ads.ad.create", "canonry ads ad create <project> --input <json-file|-> [--format json]");
3421
+ await adsAdCreate(project, { input: getString(input.values, "input"), format: input.format });
3422
+ }
3423
+ },
3424
+ {
3425
+ path: ["ads", "ad", "update"],
3426
+ usage: "canonry ads ad update <project> <ad-id> --input <json-file|-> [--format json]",
3427
+ options: { input: stringOption() },
3428
+ run: async (input) => {
3429
+ const usage = "canonry ads ad update <project> <ad-id> --input <json-file|-> [--format json]";
3430
+ const project = requireProject(input, "ads.ad.update", usage);
3431
+ const id = requirePositional(input, 1, { command: "ads.ad.update", usage, message: "ad id is required" });
3432
+ await adsAdUpdate(project, id, { input: getString(input.values, "input"), format: input.format });
3433
+ }
3434
+ },
3435
+ {
3436
+ path: ["ads", "ad", "pause"],
3437
+ usage: "canonry ads ad pause <project> <ad-id> --input <json-file|-> [--format json]",
3438
+ options: { input: stringOption() },
3439
+ run: async (input) => {
3440
+ const usage = "canonry ads ad pause <project> <ad-id> --input <json-file|-> [--format json]";
3441
+ const project = requireProject(input, "ads.ad.pause", usage);
3442
+ const id = requirePositional(input, 1, { command: "ads.ad.pause", usage, message: "ad id is required" });
3443
+ await adsAdPause(project, id, { input: getString(input.values, "input"), format: input.format });
3444
+ }
3445
+ },
2888
3446
  {
2889
3447
  path: ["ads", "sync"],
2890
3448
  usage: "canonry ads sync <project> [--format json]",
@@ -2948,9 +3506,9 @@ async function gaConnect(project, opts) {
2948
3506
  propertyId: opts.propertyId
2949
3507
  };
2950
3508
  if (opts.keyFile) {
2951
- const fs14 = await import("fs");
3509
+ const fs15 = await import("fs");
2952
3510
  try {
2953
- const content = fs14.readFileSync(opts.keyFile, "utf-8");
3511
+ const content = fs15.readFileSync(opts.keyFile, "utf-8");
2954
3512
  JSON.parse(content);
2955
3513
  body.keyJson = content;
2956
3514
  } catch (e) {
@@ -4297,9 +4855,9 @@ async function trafficConnectWordpress(project, opts) {
4297
4855
  }
4298
4856
  let applicationPassword = opts.appPassword?.trim() ?? "";
4299
4857
  if (!applicationPassword && opts.appPasswordFile) {
4300
- const fs14 = await import("fs");
4858
+ const fs15 = await import("fs");
4301
4859
  try {
4302
- applicationPassword = fs14.readFileSync(opts.appPasswordFile, "utf-8").trim();
4860
+ applicationPassword = fs15.readFileSync(opts.appPasswordFile, "utf-8").trim();
4303
4861
  } catch (e) {
4304
4862
  const msg = e instanceof Error ? e.message : String(e);
4305
4863
  throw new CliError({
@@ -4355,10 +4913,10 @@ async function trafficConnectCloudRun(project, opts) {
4355
4913
  details: { project }
4356
4914
  });
4357
4915
  }
4358
- const fs14 = await import("fs");
4916
+ const fs15 = await import("fs");
4359
4917
  let keyJson;
4360
4918
  try {
4361
- keyJson = fs14.readFileSync(opts.serviceAccountKey, "utf-8");
4919
+ keyJson = fs15.readFileSync(opts.serviceAccountKey, "utf-8");
4362
4920
  JSON.parse(keyJson);
4363
4921
  } catch (e) {
4364
4922
  const msg = e instanceof Error ? e.message : String(e);
@@ -4418,9 +4976,9 @@ async function trafficConnectVercel(project, opts) {
4418
4976
  }
4419
4977
  let token = opts.token?.trim() ?? "";
4420
4978
  if (!token && opts.tokenFile) {
4421
- const fs14 = await import("fs");
4979
+ const fs15 = await import("fs");
4422
4980
  try {
4423
- token = fs14.readFileSync(opts.tokenFile, "utf-8").trim();
4981
+ token = fs15.readFileSync(opts.tokenFile, "utf-8").trim();
4424
4982
  } catch (e) {
4425
4983
  const msg = e instanceof Error ? e.message : String(e);
4426
4984
  throw new CliError({
@@ -6238,7 +6796,7 @@ var KEYS_CLI_COMMANDS = [
6238
6796
  ];
6239
6797
 
6240
6798
  // src/commands/keyword.ts
6241
- import fs3 from "fs";
6799
+ import fs4 from "fs";
6242
6800
  function getClient12() {
6243
6801
  return createApiClient();
6244
6802
  }
@@ -6306,7 +6864,7 @@ async function listKeywords(project, format) {
6306
6864
  }
6307
6865
  }
6308
6866
  async function importKeywords(project, filePath, format) {
6309
- if (!fs3.existsSync(filePath)) {
6867
+ if (!fs4.existsSync(filePath)) {
6310
6868
  throw new CliError({
6311
6869
  code: "KEYWORD_IMPORT_FILE_NOT_FOUND",
6312
6870
  message: `File not found: ${filePath}`,
@@ -6317,7 +6875,7 @@ async function importKeywords(project, filePath, format) {
6317
6875
  }
6318
6876
  });
6319
6877
  }
6320
- const content = fs3.readFileSync(filePath, "utf-8");
6878
+ const content = fs4.readFileSync(filePath, "utf-8");
6321
6879
  const keywords = content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
6322
6880
  if (keywords.length === 0) {
6323
6881
  if (isMachineFormat(format)) {
@@ -6518,7 +7076,7 @@ var KEYWORD_CLI_COMMANDS = [
6518
7076
  ];
6519
7077
 
6520
7078
  // src/commands/query.ts
6521
- import fs4 from "fs";
7079
+ import fs5 from "fs";
6522
7080
  function getClient13() {
6523
7081
  return createApiClient();
6524
7082
  }
@@ -6609,7 +7167,7 @@ async function listQueries(project, format) {
6609
7167
  }
6610
7168
  }
6611
7169
  async function importQueries(project, filePath, format) {
6612
- if (!fs4.existsSync(filePath)) {
7170
+ if (!fs5.existsSync(filePath)) {
6613
7171
  throw new CliError({
6614
7172
  code: "QUERY_IMPORT_FILE_NOT_FOUND",
6615
7173
  message: `File not found: ${filePath}`,
@@ -6620,7 +7178,7 @@ async function importQueries(project, filePath, format) {
6620
7178
  }
6621
7179
  });
6622
7180
  }
6623
- const content = fs4.readFileSync(filePath, "utf-8");
7181
+ const content = fs5.readFileSync(filePath, "utf-8");
6624
7182
  const queries2 = content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
6625
7183
  if (queries2.length === 0) {
6626
7184
  if (isMachineFormat(format)) {
@@ -6822,7 +7380,7 @@ var QUERY_CLI_COMMANDS = [
6822
7380
  ];
6823
7381
 
6824
7382
  // src/commands/mcp.ts
6825
- import fs5 from "fs";
7383
+ import fs6 from "fs";
6826
7384
  import path3 from "path";
6827
7385
  import { createRequire } from "module";
6828
7386
 
@@ -6938,8 +7496,8 @@ function renderClientSnippet(client, serverName, entry) {
6938
7496
  return renderJsonSnippet(serverName, entry, client.format);
6939
7497
  }
6940
7498
  function readJsonConfig(configPath) {
6941
- if (!fs5.existsSync(configPath)) return {};
6942
- const raw = fs5.readFileSync(configPath, "utf-8").trim();
7499
+ if (!fs6.existsSync(configPath)) return {};
7500
+ const raw = fs6.readFileSync(configPath, "utf-8").trim();
6943
7501
  if (!raw) return {};
6944
7502
  try {
6945
7503
  const parsed = JSON.parse(raw);
@@ -6957,14 +7515,14 @@ function readJsonConfig(configPath) {
6957
7515
  }
6958
7516
  }
6959
7517
  function writeJsonConfig(configPath, value) {
6960
- fs5.mkdirSync(path3.dirname(configPath), { recursive: true });
6961
- fs5.writeFileSync(configPath, `${JSON.stringify(value, null, 2)}
7518
+ fs6.mkdirSync(path3.dirname(configPath), { recursive: true });
7519
+ fs6.writeFileSync(configPath, `${JSON.stringify(value, null, 2)}
6962
7520
  `, "utf-8");
6963
7521
  }
6964
7522
  function backupConfigIfPresent(configPath) {
6965
- if (!fs5.existsSync(configPath)) return void 0;
7523
+ if (!fs6.existsSync(configPath)) return void 0;
6966
7524
  const backupPath = `${configPath}.canonry.bak`;
6967
- fs5.copyFileSync(configPath, backupPath);
7525
+ fs6.copyFileSync(configPath, backupPath);
6968
7526
  return backupPath;
6969
7527
  }
6970
7528
  function findClientOrThrow(id) {
@@ -7318,13 +7876,13 @@ var NOTIFY_CLI_COMMANDS = [
7318
7876
  ];
7319
7877
 
7320
7878
  // src/commands/apply.ts
7321
- import fs6 from "fs";
7879
+ import fs7 from "fs";
7322
7880
  import { parseAllDocuments } from "yaml";
7323
7881
  async function applyConfigFile(filePath) {
7324
- if (!fs6.existsSync(filePath)) {
7882
+ if (!fs7.existsSync(filePath)) {
7325
7883
  throw new Error(`File not found: ${filePath}`);
7326
7884
  }
7327
- const content = fs6.readFileSync(filePath, "utf-8");
7885
+ const content = fs7.readFileSync(filePath, "utf-8");
7328
7886
  const docs = parseAllDocuments(content);
7329
7887
  const client = createApiClient();
7330
7888
  const errors = [];
@@ -7430,11 +7988,29 @@ async function showAnalytics(project, options) {
7430
7988
  console.log(JSON.stringify(results, null, 2));
7431
7989
  }
7432
7990
  }
7991
+ var BUCKET_DATE_WIDTH = 35;
7992
+ function bucketDates(bucket) {
7993
+ const legacyCompatible = bucket;
7994
+ const start = legacyCompatible.dataStartDate?.slice(0, 10);
7995
+ const end = legacyCompatible.dataEndDate?.slice(0, 10);
7996
+ if (!start || !end) return "date unavailable";
7997
+ const sweeps = legacyCompatible.sweepCount ?? 1;
7998
+ const pooled = sweeps > 1 ? ` (${sweeps} sweeps)` : "";
7999
+ if (start === end) return `${start}${pooled}`;
8000
+ return `${start} \u2192 ${end}${pooled}`;
8001
+ }
7433
8002
  function printMetrics(data) {
7434
8003
  console.log(`
7435
8004
  Citation Rate Trends (${data.window})`);
7436
8005
  console.log("\u2500".repeat(50));
7437
8006
  const pct4 = (n) => `${(n * 100).toFixed(1)}%`;
8007
+ const modelChangeNotice = buildModelChangeNotice(readModelPointerChanges(data));
8008
+ if (modelChangeNotice?.kind === "change") {
8009
+ console.log(`
8010
+ Model Updates Behind These Numbers:`);
8011
+ console.log(` ${modelChangeNotice.text}`);
8012
+ console.log("");
8013
+ }
7438
8014
  console.log(` Overall: ${pct4(data.overall.citationRate)} (${data.overall.cited}/${data.overall.total})`);
7439
8015
  console.log(` Trend: ${data.trend}`);
7440
8016
  if (Object.keys(data.byProvider).length > 0) {
@@ -7446,28 +8022,95 @@ Citation Rate Trends (${data.window})`);
7446
8022
  }
7447
8023
  if (data.buckets.length > 0) {
7448
8024
  console.log(`
7449
- Timeline:`);
8025
+ Timeline (dates in UTC):`);
7450
8026
  for (const bucket of data.buckets) {
7451
- const start = bucket.startDate.slice(0, 10);
7452
8027
  const bar = bucket.total > 0 ? "\u2588".repeat(Math.round(bucket.citationRate * 20)) : "";
7453
- console.log(` ${start} ${pct4(bucket.citationRate).padStart(6)} ${bar}`);
8028
+ console.log(` ${bucketDates(bucket).padEnd(BUCKET_DATE_WIDTH)} ${pct4(bucket.citationRate).padStart(6)} ${bar}`);
7454
8029
  }
7455
8030
  }
7456
8031
  const providersInBuckets = [...new Set(data.buckets.flatMap((b) => Object.keys(b.byProvider ?? {})))].sort();
7457
8032
  if (data.buckets.length > 0 && providersInBuckets.length > 0) {
7458
8033
  console.log(`
7459
- By Provider Timeline:`);
8034
+ By Provider Timeline (dates in UTC):`);
7460
8035
  for (const provider of providersInBuckets) {
7461
8036
  console.log(` ${provider}:`);
7462
8037
  for (const bucket of data.buckets) {
7463
8038
  const metric = bucket.byProvider?.[provider];
7464
8039
  if (!metric) continue;
7465
- const start = bucket.startDate.slice(0, 10);
7466
8040
  const bar = metric.total > 0 ? "\u2588".repeat(Math.round(metric.citationRate * 20)) : "";
7467
- console.log(` ${start} ${pct4(metric.citationRate).padStart(6)} ${bar}`);
8041
+ console.log(` ${bucketDates(bucket).padEnd(BUCKET_DATE_WIDTH)} ${pct4(metric.citationRate).padStart(6)} ${bar}`);
8042
+ }
8043
+ }
8044
+ }
8045
+ if (modelChangeNotice?.kind === "no-known-change") {
8046
+ console.log(`
8047
+ Model Updates Behind These Numbers:`);
8048
+ console.log(` ${modelChangeNotice.text}`);
8049
+ console.log(` ${modelChangeNotice.detail}`);
8050
+ }
8051
+ const attributionEntries = Object.entries(readModelAttribution(data)).sort(([a], [b]) => a.localeCompare(b));
8052
+ if (attributionEntries.length > 0) {
8053
+ console.log(`
8054
+ Model Evidence:`);
8055
+ for (const [provider, attribution] of attributionEntries) {
8056
+ const latest = attribution.latestObservation;
8057
+ console.log(` ${provider}: latest ${formatModelEvidence(latest.state)} at ${latest.observedAt}`);
8058
+ for (const event of attribution.events) {
8059
+ const dating = event.fromPreWindowAnchor ? " (on or before)" : "";
8060
+ const priorSweep = event.fromPreWindowAnchor && event.anchorObservedAt ? ` [last seen ${formatModelEvidence(event.from)} on ${event.anchorObservedAt}]` : "";
8061
+ console.log(` ${event.observedAt}${dating} ${formatModelEvidence(event.from)} \u2192 ${formatModelEvidence(event.to)}${priorSweep}`);
8062
+ }
8063
+ const eventTotal = attribution.eventTotal ?? attribution.events.length;
8064
+ if (eventTotal > attribution.events.length) {
8065
+ console.log(` Showing the latest ${attribution.events.length} of ${eventTotal} model changes.`);
8066
+ }
8067
+ if (attribution.anchorUnavailable) {
8068
+ console.log(` We did not look far enough back to be sure this is every change.`);
7468
8069
  }
7469
8070
  }
7470
8071
  }
8072
+ const servedEntries = Object.entries(readServedModelAttribution(data)).sort(([a], [b]) => a.localeCompare(b));
8073
+ if (servedEntries.length > 0) {
8074
+ const mismatch = readModelServiceMismatch(data);
8075
+ console.log(`
8076
+ What the Engines Answered With:`);
8077
+ for (const [provider, served] of servedEntries) {
8078
+ const rawIds = served.latestServedModelIds.length > 0 ? served.latestServedModelIds.join(", ") : formatModelEvidence(served.latestObservation.state);
8079
+ const substituted = mismatch[provider];
8080
+ const note = substituted ? ` \u2014 not the ${formatModelEvidence(substituted.configured)} you selected` : "";
8081
+ console.log(` ${provider}: ${rawIds} at ${served.latestObservation.observedAt}${note}`);
8082
+ for (const event of served.events) {
8083
+ const dating = event.fromPreWindowAnchor ? " (on or before)" : "";
8084
+ console.log(` ${event.observedAt}${dating} ${formatModelEvidence(event.from)} \u2192 ${formatModelEvidence(event.to)}`);
8085
+ }
8086
+ }
8087
+ }
8088
+ }
8089
+ function readServedModelAttribution(data) {
8090
+ const legacyCompatible = data;
8091
+ return legacyCompatible.servedModelAttribution ?? {};
8092
+ }
8093
+ function readModelServiceMismatch(data) {
8094
+ const legacyCompatible = data;
8095
+ return legacyCompatible.modelServiceMismatch ?? {};
8096
+ }
8097
+ function readModelPointerChanges(data) {
8098
+ const legacyCompatible = data;
8099
+ return legacyCompatible.modelPointerChanges ?? {};
8100
+ }
8101
+ function readModelAttribution(data) {
8102
+ const legacyCompatible = data;
8103
+ return legacyCompatible.modelAttribution ?? {};
8104
+ }
8105
+ function formatModelEvidence(state) {
8106
+ switch (state.status) {
8107
+ case "known":
8108
+ return `known ${state.model}`;
8109
+ case "unknown":
8110
+ return "unknown";
8111
+ case "mixed":
8112
+ return `mixed ${state.models.join(", ")}${state.includesUnknown ? " + unknown" : ""}`;
8113
+ }
7471
8114
  }
7472
8115
  function printGaps(data) {
7473
8116
  console.log(`
@@ -7642,7 +8285,7 @@ async function loadLatestRunForExport(client, project) {
7642
8285
  }
7643
8286
 
7644
8287
  // src/commands/results-export.ts
7645
- import fs7 from "fs";
8288
+ import fs8 from "fs";
7646
8289
  import path4 from "path";
7647
8290
  async function exportResults(project, opts) {
7648
8291
  const { output, ...request } = opts;
@@ -7652,8 +8295,8 @@ async function exportResults(project, opts) {
7652
8295
  return;
7653
8296
  }
7654
8297
  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");
8298
+ fs8.mkdirSync(path4.dirname(target), { recursive: true });
8299
+ fs8.writeFileSync(target, artifact.content, "utf8");
7657
8300
  console.log(`Results export written to ${target}`);
7658
8301
  }
7659
8302
 
@@ -7661,39 +8304,48 @@ async function exportResults(project, opts) {
7661
8304
  function getClient18() {
7662
8305
  return createApiClient();
7663
8306
  }
7664
- async function showHistory(project, format) {
8307
+ function originLabel(entry) {
8308
+ const userAgent = entry.userAgent?.toLowerCase() ?? "";
8309
+ if (userAgent.includes("canonry-mcp")) return "mcp";
8310
+ if (userAgent.includes("canonry-cli")) return "cli";
8311
+ if (userAgent.includes("mozilla/")) return "dashboard";
8312
+ return entry.actor;
8313
+ }
8314
+ async function showHistory(project, format, opts = {}) {
7665
8315
  const client = getClient18();
7666
8316
  try {
7667
- const entries = await client.getHistory(project);
8317
+ const entries = project ? await client.getHistory(project, opts) : await client.getGlobalHistory(opts);
7668
8318
  if (format === "json") {
7669
8319
  console.log(JSON.stringify(entries, null, 2));
7670
8320
  return;
7671
8321
  }
7672
8322
  if (format === "jsonl") {
7673
- emitJsonl(entries.map((entry) => ({ project, ...entry })));
8323
+ emitJsonl(entries.map((entry) => ({ project: project ?? null, ...entry })));
7674
8324
  return;
7675
8325
  }
7676
8326
  if (entries.length === 0) {
7677
- console.log(`No audit history for "${project}".`);
8327
+ console.log(project ? `No audit history for "${project}".` : "No instance audit history.");
7678
8328
  return;
7679
8329
  }
7680
- console.log(`Audit history for "${project}" (${entries.length}):
8330
+ console.log(`${project ? `Audit history for "${project}"` : "Instance audit history"} (${entries.length}):
7681
8331
  `);
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");
8332
+ console.log(" TIMESTAMP ACTION ENTITY TYPE ORIGIN");
8333
+ 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
8334
  for (const entry of entries) {
7685
8335
  console.log(
7686
- ` ${entry.createdAt.padEnd(23)} ${entry.action.padEnd(18)} ${entry.entityType.padEnd(11)} ${entry.actor}`
8336
+ ` ${entry.createdAt.padEnd(23)} ${entry.action.padEnd(18)} ${entry.entityType.padEnd(11)} ${originLabel(entry)}`
7687
8337
  );
8338
+ if (entry.actorSession) console.log(` session: ${entry.actorSession}`);
8339
+ if (entry.diff != null) console.log(` diff: ${JSON.stringify(entry.diff)}`);
7688
8340
  }
7689
8341
  } catch (err) {
7690
8342
  const message = err instanceof Error ? err.message : String(err);
7691
8343
  throw new CliError({
7692
8344
  code: "HISTORY_FETCH_FAILED",
7693
- message: `Failed to fetch history for project "${project}"`,
8345
+ message: project ? `Failed to fetch history for project "${project}"` : "Failed to fetch instance history",
7694
8346
  displayMessage: `Failed to fetch history: ${message}`,
7695
8347
  details: {
7696
- project,
8348
+ project: project ?? null,
7697
8349
  cause: message
7698
8350
  }
7699
8351
  });
@@ -7809,10 +8461,25 @@ var OPERATOR_CLI_COMMANDS = [
7809
8461
  },
7810
8462
  {
7811
8463
  path: ["history"],
7812
- usage: "canonry history <project> [--format json]",
8464
+ usage: "canonry history <project> [--limit <n>] [--since <ISO>] [--action <action>] [--actor <actor>] [--entity-type <type>] [--format json|jsonl]\n canonry history --all [same filters]",
8465
+ options: {
8466
+ all: { type: "boolean", default: false },
8467
+ limit: stringOption(),
8468
+ since: stringOption(),
8469
+ action: stringOption(),
8470
+ actor: stringOption(),
8471
+ "entity-type": stringOption()
8472
+ },
7813
8473
  run: async (input) => {
7814
- const project = requireProject(input, "history", "canonry history <project> [--format json]");
7815
- await showHistory(project, input.format);
8474
+ 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]";
8475
+ const project = getBoolean(input.values, "all") ? void 0 : requireProject(input, "history", usage);
8476
+ await showHistory(project, input.format, {
8477
+ limit: parseIntegerOption(input, "limit", { command: "history", usage, message: "--limit must be an integer" }),
8478
+ since: getString(input.values, "since"),
8479
+ action: getString(input.values, "action"),
8480
+ actor: getString(input.values, "actor"),
8481
+ entityType: getString(input.values, "entity-type")
8482
+ });
7816
8483
  }
7817
8484
  },
7818
8485
  {
@@ -7900,7 +8567,9 @@ async function createProject(name, opts) {
7900
8567
  ownedDomains: opts.ownedDomains ?? [],
7901
8568
  aliases: normalizeProjectAliases(opts.displayName, opts.aliases ?? []),
7902
8569
  country: opts.country,
7903
- language: opts.language
8570
+ language: opts.language,
8571
+ providers: opts.providers ?? [],
8572
+ providerModels: opts.providerModels ?? {}
7904
8573
  });
7905
8574
  if (isMachineFormat(opts.format)) {
7906
8575
  console.log(JSON.stringify(result, null, 2));
@@ -7963,6 +8632,9 @@ async function showProject(name, format) {
7963
8632
  console.log(` Language: ${project.language}`);
7964
8633
  console.log(` Config source: ${project.configSource}`);
7965
8634
  console.log(` Config revision: ${project.configRevision}`);
8635
+ console.log(` Providers: ${(project.providers ?? []).length > 0 ? project.providers.join(", ") : "all configured"}`);
8636
+ const providerModels = Object.entries(project.providerModels ?? {});
8637
+ console.log(` Model overrides: ${providerModels.length > 0 ? providerModels.map(([provider, model]) => `${provider}=${model}`).join(", ") : "(none; instance settings inherited)"}`);
7966
8638
  console.log(` Tags: ${project.tags.length > 0 ? project.tags.join(", ") : "(none)"}`);
7967
8639
  const labelEntries = Object.entries(project.labels);
7968
8640
  console.log(` Labels: ${labelEntries.length > 0 ? labelEntries.map(([k, v]) => `${k}=${v}`).join(", ") : "(none)"}`);
@@ -7992,19 +8664,45 @@ async function updateProjectSettings(name, opts) {
7992
8664
  const toRemove = new Set(opts.removeAlias.map((a) => a.toLowerCase()));
7993
8665
  aliases = aliases.filter((a) => !toRemove.has(a.toLowerCase()));
7994
8666
  }
8667
+ const providerModels = { ...project.providerModels ?? {}, ...opts.providerModels ?? {} };
8668
+ for (const provider of opts.clearProviderModels ?? []) delete providerModels[provider];
8669
+ const nextProviders = opts.providers ?? project.providers ?? [];
8670
+ if (nextProviders.length > 0) {
8671
+ const requested = Object.keys(opts.providerModels ?? {}).filter((p) => !nextProviders.includes(p));
8672
+ if (requested.length > 0) {
8673
+ throw usageError(
8674
+ `Error: --provider-model set for ${requested.join(", ")}, which ${requested.length > 1 ? "are" : "is"} not a selected engine for "${name}"`,
8675
+ {
8676
+ message: "provider-model set for a provider the project does not run",
8677
+ details: { command: "project.update", project: name, providers: nextProviders, unselected: requested }
8678
+ }
8679
+ );
8680
+ }
8681
+ }
7995
8682
  const result = await client.putProject(name, {
7996
8683
  displayName: nextDisplayName,
7997
8684
  canonicalDomain: opts.domain ?? project.canonicalDomain,
7998
8685
  ownedDomains,
7999
8686
  aliases: normalizeProjectAliases(nextDisplayName, aliases),
8000
8687
  country: opts.country ?? project.country,
8001
- language: opts.language ?? project.language
8688
+ language: opts.language ?? project.language,
8689
+ tags: project.tags,
8690
+ labels: project.labels,
8691
+ providers: opts.providers ?? project.providers,
8692
+ providerModels,
8693
+ locations: project.locations,
8694
+ defaultLocation: project.defaultLocation,
8695
+ autoExtractBacklinks: project.autoExtractBacklinks
8002
8696
  });
8003
8697
  if (isMachineFormat(opts.format)) {
8004
8698
  console.log(JSON.stringify(result, null, 2));
8005
8699
  return;
8006
8700
  }
8007
8701
  console.log(`Project updated: ${result.name}`);
8702
+ const dropped = Object.keys(providerModels).filter((provider) => !(provider in result.providerModels));
8703
+ if (dropped.length > 0) {
8704
+ console.log(` Dropped model override for deselected engine(s): ${dropped.join(", ")}`);
8705
+ }
8008
8706
  }
8009
8707
  async function deleteProject(name, opts) {
8010
8708
  const client = getClient20();
@@ -8108,20 +8806,22 @@ async function setDefaultLocation(project, label, format) {
8108
8806
  var PROJECT_CLI_COMMANDS = [
8109
8807
  {
8110
8808
  path: ["project", "create"],
8111
- usage: "canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]",
8809
+ 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]",
8112
8810
  options: {
8113
8811
  domain: { type: "string", short: "d" },
8114
8812
  "owned-domain": multiStringOption(),
8115
8813
  alias: multiStringOption(),
8116
8814
  country: stringOption(),
8117
8815
  language: stringOption(),
8118
- "display-name": stringOption()
8816
+ "display-name": stringOption(),
8817
+ provider: multiStringOption(),
8818
+ "provider-model": multiStringOption()
8119
8819
  },
8120
8820
  run: async (input) => {
8121
8821
  const name = requireProject(
8122
8822
  input,
8123
8823
  "project.create",
8124
- "canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]"
8824
+ "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]"
8125
8825
  );
8126
8826
  await createProject(name, {
8127
8827
  domain: getString(input.values, "domain") ?? name,
@@ -8130,13 +8830,15 @@ var PROJECT_CLI_COMMANDS = [
8130
8830
  country: getString(input.values, "country") ?? "US",
8131
8831
  language: getString(input.values, "language") ?? "en",
8132
8832
  displayName: getString(input.values, "display-name") ?? name,
8833
+ providers: getStringArray(input.values, "provider") ?? [],
8834
+ providerModels: parseProviderModelAssignments(getStringArray(input.values, "provider-model")),
8133
8835
  format: input.format
8134
8836
  });
8135
8837
  }
8136
8838
  },
8137
8839
  {
8138
8840
  path: ["project", "update"],
8139
- 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]",
8841
+ 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]",
8140
8842
  options: {
8141
8843
  domain: { type: "string", short: "d" },
8142
8844
  "owned-domain": multiStringOption(),
@@ -8147,14 +8849,26 @@ var PROJECT_CLI_COMMANDS = [
8147
8849
  "remove-alias": multiStringOption(),
8148
8850
  country: stringOption(),
8149
8851
  language: stringOption(),
8150
- "display-name": stringOption()
8852
+ "display-name": stringOption(),
8853
+ provider: multiStringOption(),
8854
+ "all-providers": { type: "boolean" },
8855
+ "provider-model": multiStringOption(),
8856
+ "clear-provider-model": multiStringOption()
8151
8857
  },
8152
8858
  run: async (input) => {
8153
8859
  const name = requireProject(
8154
8860
  input,
8155
8861
  "project.update",
8156
- "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]"
8862
+ "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]"
8157
8863
  );
8864
+ const providers = getStringArray(input.values, "provider");
8865
+ const allProviders = getBoolean(input.values, "all-providers");
8866
+ if (allProviders && providers?.length) throw usageError("Error: --all-providers conflicts with --provider");
8867
+ const providerModels = parseProviderModelAssignments(getStringArray(input.values, "provider-model"));
8868
+ const clearProviderModels = getStringArray(input.values, "clear-provider-model") ?? [];
8869
+ for (const provider of clearProviderModels) {
8870
+ if (provider in providerModels) throw usageError(`Error: --provider-model and --clear-provider-model conflict for ${provider}`);
8871
+ }
8158
8872
  await updateProjectSettings(name, {
8159
8873
  displayName: getString(input.values, "display-name"),
8160
8874
  domain: getString(input.values, "domain"),
@@ -8166,6 +8880,9 @@ var PROJECT_CLI_COMMANDS = [
8166
8880
  removeAlias: getStringArray(input.values, "remove-alias"),
8167
8881
  country: getString(input.values, "country"),
8168
8882
  language: getString(input.values, "language"),
8883
+ providers: allProviders ? [] : providers,
8884
+ providerModels,
8885
+ clearProviderModels,
8169
8886
  format: input.format
8170
8887
  });
8171
8888
  }
@@ -8281,9 +8998,21 @@ var PROJECT_CLI_COMMANDS = [
8281
8998
  }
8282
8999
  }
8283
9000
  ];
9001
+ function parseProviderModelAssignments(assignments) {
9002
+ const result = {};
9003
+ for (const assignment of assignments ?? []) {
9004
+ const separator = assignment.indexOf("=");
9005
+ const provider = separator === -1 ? "" : assignment.slice(0, separator).trim();
9006
+ const model = separator === -1 ? "" : assignment.slice(separator + 1).trim();
9007
+ if (!provider || !model) throw usageError(`Error: --provider-model must use provider=model (received ${assignment})`);
9008
+ if (provider in result) throw usageError(`Error: duplicate --provider-model assignment for ${provider}`);
9009
+ result[provider] = model;
9010
+ }
9011
+ return result;
9012
+ }
8284
9013
 
8285
9014
  // src/commands/report.ts
8286
- import fs8 from "fs";
9015
+ import fs9 from "fs";
8287
9016
  import path5 from "path";
8288
9017
  function defaultOutputPath(project, audience) {
8289
9018
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -8300,10 +9029,10 @@ async function runReportCommand(project, opts = {}) {
8300
9029
  const html = renderReportHtml(report, { audience });
8301
9030
  const targetPath = opts.output ? path5.resolve(opts.output) : defaultOutputPath(project, audience);
8302
9031
  const dir = path5.dirname(targetPath);
8303
- if (!fs8.existsSync(dir)) {
8304
- fs8.mkdirSync(dir, { recursive: true });
9032
+ if (!fs9.existsSync(dir)) {
9033
+ fs9.mkdirSync(dir, { recursive: true });
8305
9034
  }
8306
- fs8.writeFileSync(targetPath, html, "utf-8");
9035
+ fs9.writeFileSync(targetPath, html, "utf-8");
8307
9036
  console.log(`Report written to ${targetPath}`);
8308
9037
  }
8309
9038
 
@@ -9168,11 +9897,11 @@ var SKILLS_CLI_COMMANDS = [
9168
9897
  ];
9169
9898
 
9170
9899
  // src/commands/snapshot.ts
9171
- import fs10 from "fs";
9900
+ import fs11 from "fs";
9172
9901
  import path7 from "path";
9173
9902
 
9174
9903
  // src/snapshot-pdf.ts
9175
- import fs9 from "fs";
9904
+ import fs10 from "fs";
9176
9905
  import path6 from "path";
9177
9906
  import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
9178
9907
  var PAGE_WIDTH = 612;
@@ -9386,8 +10115,8 @@ async function writeSnapshotPdf(report, outputPath) {
9386
10115
  renderQueries(pdf, report);
9387
10116
  const bytes = await doc.save();
9388
10117
  const resolvedPath = path6.resolve(outputPath);
9389
- fs9.mkdirSync(path6.dirname(resolvedPath), { recursive: true });
9390
- fs9.writeFileSync(resolvedPath, bytes);
10118
+ fs10.mkdirSync(path6.dirname(resolvedPath), { recursive: true });
10119
+ fs10.writeFileSync(resolvedPath, bytes);
9391
10120
  return resolvedPath;
9392
10121
  }
9393
10122
  function renderCover(pdf, report) {
@@ -9546,8 +10275,8 @@ PDF saved: ${savedPdfPath}`);
9546
10275
  }
9547
10276
  function writeSnapshotMarkdown(report, outputPath) {
9548
10277
  const resolvedPath = path7.resolve(outputPath);
9549
- fs10.mkdirSync(path7.dirname(resolvedPath), { recursive: true });
9550
- fs10.writeFileSync(resolvedPath, formatSnapshotMarkdown(report), "utf-8");
10278
+ fs11.mkdirSync(path7.dirname(resolvedPath), { recursive: true });
10279
+ fs11.writeFileSync(resolvedPath, formatSnapshotMarkdown(report), "utf-8");
9551
10280
  return resolvedPath;
9552
10281
  }
9553
10282
  function formatSnapshotMarkdown(report) {
@@ -10716,18 +11445,36 @@ async function bootstrapCommand(_opts) {
10716
11445
  } : existingConfig?.google;
10717
11446
  const keyHash = crypto.createHash("sha256").update(rawApiKey).digest("hex");
10718
11447
  const keyPrefix = rawApiKey.slice(0, 9);
11448
+ const existingConfigKeyHash = existingConfig ? crypto.createHash("sha256").update(existingConfig.apiKey).digest("hex") : void 0;
10719
11449
  const db = createClient(databasePath);
10720
11450
  migrate(db);
10721
11451
  db.transaction((tx) => {
10722
- tx.delete(apiKeys).where(eq(apiKeys.name, "default")).run();
10723
- tx.insert(apiKeys).values({
10724
- id: crypto.randomUUID(),
10725
- name: "default",
10726
- keyHash,
10727
- keyPrefix,
10728
- scopes: ["*"],
10729
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
10730
- }).run();
11452
+ const rotatedAt = (/* @__PURE__ */ new Date()).toISOString();
11453
+ const existingDefaults = tx.select({ id: apiKeys.id, keyHash: apiKeys.keyHash }).from(apiKeys).where(eq(apiKeys.name, "default")).all();
11454
+ const existingDefault = existingDefaults.find((key) => key.keyHash === existingConfigKeyHash) ?? existingDefaults.at(0);
11455
+ if (existingDefault) {
11456
+ tx.update(apiKeys).set({
11457
+ keyHash,
11458
+ keyPrefix,
11459
+ scopes: ["*"],
11460
+ projectId: null,
11461
+ lastUsedAt: null,
11462
+ revokedAt: null
11463
+ }).where(eq(apiKeys.id, existingDefault.id)).run();
11464
+ for (const duplicate of existingDefaults) {
11465
+ if (duplicate.id === existingDefault.id) continue;
11466
+ tx.update(apiKeys).set({ revokedAt: rotatedAt }).where(eq(apiKeys.id, duplicate.id)).run();
11467
+ }
11468
+ } else {
11469
+ tx.insert(apiKeys).values({
11470
+ id: crypto.randomUUID(),
11471
+ name: "default",
11472
+ keyHash,
11473
+ keyPrefix,
11474
+ scopes: ["*"],
11475
+ createdAt: rotatedAt
11476
+ }).run();
11477
+ }
10731
11478
  });
10732
11479
  saveConfig({
10733
11480
  apiUrl: env.apiUrl || existingConfig?.apiUrl || `http://localhost:${process.env.CANONRY_PORT || "4100"}`,
@@ -10757,7 +11504,7 @@ async function bootstrapCommand(_opts) {
10757
11504
 
10758
11505
  // src/commands/daemon.ts
10759
11506
  import { spawn } from "child_process";
10760
- import fs11 from "fs";
11507
+ import fs12 from "fs";
10761
11508
  import path9 from "path";
10762
11509
  function getPidPath() {
10763
11510
  return path9.join(getConfigDir(), "canonry.pid");
@@ -10800,8 +11547,8 @@ function buildServeForwardArgs(opts) {
10800
11547
  async function startDaemon(opts) {
10801
11548
  const pidPath = getPidPath();
10802
11549
  const format = opts.format ?? "text";
10803
- if (fs11.existsSync(pidPath)) {
10804
- const existingPid = parseInt(fs11.readFileSync(pidPath, "utf-8").trim(), 10);
11550
+ if (fs12.existsSync(pidPath)) {
11551
+ const existingPid = parseInt(fs12.readFileSync(pidPath, "utf-8").trim(), 10);
10805
11552
  if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
10806
11553
  throw new CliError({
10807
11554
  code: "DAEMON_ALREADY_RUNNING",
@@ -10812,7 +11559,7 @@ async function startDaemon(opts) {
10812
11559
  }
10813
11560
  });
10814
11561
  }
10815
- fs11.unlinkSync(pidPath);
11562
+ fs12.unlinkSync(pidPath);
10816
11563
  }
10817
11564
  const cliPath = path9.resolve(new URL(import.meta.url).pathname);
10818
11565
  const inSourceMode = new URL(import.meta.url).pathname.endsWith(".ts");
@@ -10831,10 +11578,10 @@ async function startDaemon(opts) {
10831
11578
  });
10832
11579
  }
10833
11580
  const configDir = getConfigDir();
10834
- if (!fs11.existsSync(configDir)) {
10835
- fs11.mkdirSync(configDir, { recursive: true });
11581
+ if (!fs12.existsSync(configDir)) {
11582
+ fs12.mkdirSync(configDir, { recursive: true });
10836
11583
  }
10837
- fs11.writeFileSync(pidPath, String(child.pid), "utf-8");
11584
+ fs12.writeFileSync(pidPath, String(child.pid), "utf-8");
10838
11585
  const port = opts.port ?? "4100";
10839
11586
  const host = opts.host ?? "127.0.0.1";
10840
11587
  if (!isMachineFormat(format)) {
@@ -10843,7 +11590,7 @@ async function startDaemon(opts) {
10843
11590
  const ready = await waitForReady(host, port);
10844
11591
  if (!ready) {
10845
11592
  try {
10846
- fs11.unlinkSync(pidPath);
11593
+ fs12.unlinkSync(pidPath);
10847
11594
  } catch {
10848
11595
  }
10849
11596
  throw new CliError({
@@ -10875,7 +11622,7 @@ async function startDaemon(opts) {
10875
11622
  }
10876
11623
  function stopDaemon(format = "text") {
10877
11624
  const pidPath = getPidPath();
10878
- if (!fs11.existsSync(pidPath)) {
11625
+ if (!fs12.existsSync(pidPath)) {
10879
11626
  if (isMachineFormat(format)) {
10880
11627
  console.log(JSON.stringify({
10881
11628
  stopped: false,
@@ -10886,7 +11633,7 @@ function stopDaemon(format = "text") {
10886
11633
  console.log("Canonry is not running (no PID file found)");
10887
11634
  return;
10888
11635
  }
10889
- const pid = parseInt(fs11.readFileSync(pidPath, "utf-8").trim(), 10);
11636
+ const pid = parseInt(fs12.readFileSync(pidPath, "utf-8").trim(), 10);
10890
11637
  if (isNaN(pid)) {
10891
11638
  if (isMachineFormat(format)) {
10892
11639
  console.log(JSON.stringify({
@@ -10897,7 +11644,7 @@ function stopDaemon(format = "text") {
10897
11644
  } else {
10898
11645
  console.error("Invalid PID file. Removing it.");
10899
11646
  }
10900
- fs11.unlinkSync(pidPath);
11647
+ fs12.unlinkSync(pidPath);
10901
11648
  return;
10902
11649
  }
10903
11650
  if (!isProcessAlive(pid)) {
@@ -10911,12 +11658,12 @@ function stopDaemon(format = "text") {
10911
11658
  } else {
10912
11659
  console.log(`Canonry is not running (stale PID: ${pid}). Cleaning up.`);
10913
11660
  }
10914
- fs11.unlinkSync(pidPath);
11661
+ fs12.unlinkSync(pidPath);
10915
11662
  return;
10916
11663
  }
10917
11664
  try {
10918
11665
  process.kill(pid, "SIGTERM");
10919
- fs11.unlinkSync(pidPath);
11666
+ fs12.unlinkSync(pidPath);
10920
11667
  if (isMachineFormat(format)) {
10921
11668
  console.log(JSON.stringify({
10922
11669
  stopped: true,
@@ -10940,7 +11687,7 @@ function stopDaemon(format = "text") {
10940
11687
 
10941
11688
  // src/commands/init.ts
10942
11689
  import crypto2 from "crypto";
10943
- import fs12 from "fs";
11690
+ import fs13 from "fs";
10944
11691
  import readline from "readline";
10945
11692
  import path10 from "path";
10946
11693
  function prompt(question) {
@@ -10964,7 +11711,7 @@ var PROJECT_MARKERS = [".git", "canonry.yaml", "canonry.yml", "package.json"];
10964
11711
  function cwdLooksLikeProject(dir) {
10965
11712
  const home = process.env.HOME ?? "";
10966
11713
  if (home && path10.resolve(dir) === path10.resolve(home)) return false;
10967
- return PROJECT_MARKERS.some((marker) => fs12.existsSync(path10.join(dir, marker)));
11714
+ return PROJECT_MARKERS.some((marker) => fs13.existsSync(path10.join(dir, marker)));
10968
11715
  }
10969
11716
  var DEFAULT_AGENT_MODELS = {
10970
11717
  anthropic: "anthropic/claude-sonnet-4-6",
@@ -10995,8 +11742,8 @@ async function initCommand(opts) {
10995
11742
  return void 0;
10996
11743
  }
10997
11744
  const configDir = getConfigDir();
10998
- if (!fs12.existsSync(configDir)) {
10999
- fs12.mkdirSync(configDir, { recursive: true });
11745
+ if (!fs13.existsSync(configDir)) {
11746
+ fs13.mkdirSync(configDir, { recursive: true });
11000
11747
  }
11001
11748
  const bootstrapEnv = getBootstrapEnv(process.env, {
11002
11749
  GEMINI_API_KEY: opts?.geminiKey,
@@ -11663,7 +12410,7 @@ function getClient25() {
11663
12410
  }
11664
12411
  async function technicalAeoScore(project, opts) {
11665
12412
  const client = getClient25();
11666
- const score = await client.getTechnicalAeoScore(project);
12413
+ const score = await client.getTechnicalAeoScore(project, { runId: opts.runId });
11667
12414
  if (isMachineFormat(opts.format)) {
11668
12415
  console.log(JSON.stringify(score, null, 2));
11669
12416
  return;
@@ -11700,7 +12447,7 @@ async function technicalAeoScore(project, opts) {
11700
12447
  async function technicalAeoPages(project, opts) {
11701
12448
  const client = getClient25();
11702
12449
  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 });
12450
+ const res = await client.getTechnicalAeoPages(project, { runId: opts.runId, status, sort: opts.sort, limit: opts.limit });
11704
12451
  if (opts.format === "jsonl") {
11705
12452
  emitJsonl(res.pages.map((p) => ({ project, runId: res.runId, ...p })));
11706
12453
  return;
@@ -11784,28 +12531,33 @@ async function technicalAeoRun(project, opts) {
11784
12531
  var TECHNICAL_AEO_CLI_COMMANDS = [
11785
12532
  {
11786
12533
  path: ["technical-aeo", "score"],
11787
- usage: "canonry technical-aeo score <project> [--format json]",
12534
+ usage: "canonry technical-aeo score <project> [--run-id <id>] [--format json]",
12535
+ options: {
12536
+ "run-id": stringOption()
12537
+ },
11788
12538
  run: async (input) => {
11789
12539
  const project = requireProject(
11790
12540
  input,
11791
12541
  "technical-aeo.score",
11792
- "canonry technical-aeo score <project> [--format json]"
12542
+ "canonry technical-aeo score <project> [--run-id <id>] [--format json]"
11793
12543
  );
11794
- await technicalAeoScore(project, { format: input.format });
12544
+ await technicalAeoScore(project, { runId: getString(input.values, "run-id"), format: input.format });
11795
12545
  }
11796
12546
  },
11797
12547
  {
11798
12548
  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]",
12549
+ usage: "canonry technical-aeo pages <project> [--run-id <id>] [--status success|error] [--sort score-asc|score-desc|url] [--limit <n>] [--format json|jsonl]",
11800
12550
  options: {
12551
+ "run-id": stringOption(),
11801
12552
  status: stringOption(),
11802
12553
  sort: stringOption(),
11803
12554
  limit: stringOption()
11804
12555
  },
11805
12556
  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]";
12557
+ 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
12558
  const project = requireProject(input, "technical-aeo.pages", usage);
11808
12559
  await technicalAeoPages(project, {
12560
+ runId: getString(input.values, "run-id"),
11809
12561
  status: getString(input.values, "status"),
11810
12562
  sort: getString(input.values, "sort"),
11811
12563
  limit: parseIntegerOption(input, "limit", {
@@ -12097,7 +12849,7 @@ var VISIBILITY_STATS_CLI_COMMANDS = [
12097
12849
  ];
12098
12850
 
12099
12851
  // src/cli-commands/wordpress.ts
12100
- import fs13 from "fs";
12852
+ import fs14 from "fs";
12101
12853
 
12102
12854
  // src/commands/wordpress.ts
12103
12855
  function getClient26() {
@@ -12336,12 +13088,12 @@ async function wordpressSetMeta(project, body) {
12336
13088
  printPageDetail(result);
12337
13089
  }
12338
13090
  async function wordpressBulkSetMeta(project, opts) {
12339
- const fs14 = await import("fs/promises");
13091
+ const fs15 = await import("fs/promises");
12340
13092
  const path11 = await import("path");
12341
13093
  const filePath = path11.resolve(opts.from);
12342
13094
  let raw;
12343
13095
  try {
12344
- raw = await fs14.readFile(filePath, "utf8");
13096
+ raw = await fs15.readFile(filePath, "utf8");
12345
13097
  } catch {
12346
13098
  throw new CliError({
12347
13099
  code: "FILE_READ_ERROR",
@@ -12438,13 +13190,13 @@ async function wordpressSetSchema(project, body) {
12438
13190
  printManualAssist(`Schema update for "${body.slug}"`, result);
12439
13191
  }
12440
13192
  async function wordpressSchemaDeploy(project, opts) {
12441
- const fs14 = await import("fs/promises");
13193
+ const fs15 = await import("fs/promises");
12442
13194
  const path11 = await import("path");
12443
13195
  const yaml = await loadYamlModule();
12444
13196
  const filePath = path11.resolve(opts.profile);
12445
13197
  let raw;
12446
13198
  try {
12447
- raw = await fs14.readFile(filePath, "utf8");
13199
+ raw = await fs15.readFile(filePath, "utf8");
12448
13200
  } catch {
12449
13201
  throw new CliError({
12450
13202
  code: "FILE_READ_ERROR",
@@ -12549,13 +13301,13 @@ async function wordpressOnboard(project, opts) {
12549
13301
  }
12550
13302
  let profileData;
12551
13303
  if (opts.profile) {
12552
- const fs14 = await import("fs/promises");
13304
+ const fs15 = await import("fs/promises");
12553
13305
  const path11 = await import("path");
12554
13306
  const yaml = await loadYamlModule();
12555
13307
  const filePath = path11.resolve(opts.profile);
12556
13308
  let raw;
12557
13309
  try {
12558
- raw = await fs14.readFile(filePath, "utf8");
13310
+ raw = await fs15.readFile(filePath, "utf8");
12559
13311
  } catch {
12560
13312
  throw new CliError({
12561
13313
  code: "FILE_READ_ERROR",
@@ -12704,7 +13456,7 @@ function resolveContent(input, command, usage, options) {
12704
13456
  }
12705
13457
  if (contentFile) {
12706
13458
  try {
12707
- return fs13.readFileSync(contentFile, "utf-8");
13459
+ return fs14.readFileSync(contentFile, "utf-8");
12708
13460
  } catch (error) {
12709
13461
  const message = error instanceof Error ? error.message : String(error);
12710
13462
  throw usageError(`Error: could not read --content-file "${contentFile}": ${message}`, {