@canonry/canonry 4.118.0 → 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.
- package/README.md +9 -0
- package/assets/agent-workspace/skills/canonry/SKILL.md +3 -0
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +135 -28
- package/assets/assets/{AuditHistoryPanel-Yh7VEe4P.js → AuditHistoryPanel-BVQMDIhQ.js} +1 -1
- package/assets/assets/{BacklinksPage-BmO77kMs.js → BacklinksPage-nrHT9VMG.js} +1 -1
- package/assets/assets/ChartPrimitives-_8qLsQzI.js +1 -0
- package/assets/assets/{HistoryPage-DkLV_iRc.js → HistoryPage-DqsKKdyy.js} +1 -1
- package/assets/assets/ProjectPage-GyKoQUv_.js +7 -0
- package/assets/assets/{RunRow-BDH9agxQ.js → RunRow-BRZZcSjQ.js} +1 -1
- package/assets/assets/{RunsPage-D9YsEeT3.js → RunsPage-24aI8DKF.js} +1 -1
- package/assets/assets/{SettingsPage-o5as3Ixw.js → SettingsPage-BaNeTG1i.js} +1 -1
- package/assets/assets/{TrafficPage-BCK203aQ.js → TrafficPage-ByvcLSjv.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-DsVMAZvS.js → TrafficSourceDetailPage-B-siTXVg.js} +1 -1
- package/assets/assets/{arrow-left-B7ZR6VC9.js → arrow-left-CBh1cZNH.js} +1 -1
- package/assets/assets/{extract-error-message-DeYDUpew.js → extract-error-message-DJZCPudR.js} +1 -1
- package/assets/assets/index-jymsMBpZ.css +1 -0
- package/assets/assets/index-mzLganDP.js +210 -0
- package/assets/assets/{trash-2-xxHTqWbF.js → trash-2-Bh-Yvkgc.js} +1 -1
- package/assets/assets/vendor-recharts-DSxTZhaH.js +36 -0
- package/assets/index.html +3 -3
- package/dist/{chunk-CMZIPFNQ.js → chunk-4AZ7VES6.js} +4915 -719
- package/dist/{chunk-75UBTCKS.js → chunk-HJIIJI7K.js} +321 -6
- package/dist/{chunk-HGNY5GL4.js → chunk-JYBNBK25.js} +273 -37
- package/dist/{chunk-KEZWLP4O.js → chunk-MMFSI3OG.js} +3281 -2341
- package/dist/cli.js +554 -35
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-FWPXMAVK.js → intelligence-service-K35EN43I.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +7 -7
- package/assets/assets/ChartPrimitives-H0ICVpw_.js +0 -1
- package/assets/assets/ProjectPage-R3odr_eA.js +0 -7
- package/assets/assets/index-Bot6YiD6.css +0 -1
- package/assets/assets/index-QCFQv0xh.js +0 -210
- 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-
|
|
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-
|
|
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-
|
|
57
|
+
} from "./chunk-4AZ7VES6.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-
|
|
98
|
+
} from "./chunk-MMFSI3OG.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
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
if (
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
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]",
|
|
@@ -7634,11 +7988,29 @@ async function showAnalytics(project, options) {
|
|
|
7634
7988
|
console.log(JSON.stringify(results, null, 2));
|
|
7635
7989
|
}
|
|
7636
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
|
+
}
|
|
7637
8002
|
function printMetrics(data) {
|
|
7638
8003
|
console.log(`
|
|
7639
8004
|
Citation Rate Trends (${data.window})`);
|
|
7640
8005
|
console.log("\u2500".repeat(50));
|
|
7641
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
|
+
}
|
|
7642
8014
|
console.log(` Overall: ${pct4(data.overall.citationRate)} (${data.overall.cited}/${data.overall.total})`);
|
|
7643
8015
|
console.log(` Trend: ${data.trend}`);
|
|
7644
8016
|
if (Object.keys(data.byProvider).length > 0) {
|
|
@@ -7650,28 +8022,95 @@ Citation Rate Trends (${data.window})`);
|
|
|
7650
8022
|
}
|
|
7651
8023
|
if (data.buckets.length > 0) {
|
|
7652
8024
|
console.log(`
|
|
7653
|
-
Timeline:`);
|
|
8025
|
+
Timeline (dates in UTC):`);
|
|
7654
8026
|
for (const bucket of data.buckets) {
|
|
7655
|
-
const start = bucket.startDate.slice(0, 10);
|
|
7656
8027
|
const bar = bucket.total > 0 ? "\u2588".repeat(Math.round(bucket.citationRate * 20)) : "";
|
|
7657
|
-
console.log(` ${
|
|
8028
|
+
console.log(` ${bucketDates(bucket).padEnd(BUCKET_DATE_WIDTH)} ${pct4(bucket.citationRate).padStart(6)} ${bar}`);
|
|
7658
8029
|
}
|
|
7659
8030
|
}
|
|
7660
8031
|
const providersInBuckets = [...new Set(data.buckets.flatMap((b) => Object.keys(b.byProvider ?? {})))].sort();
|
|
7661
8032
|
if (data.buckets.length > 0 && providersInBuckets.length > 0) {
|
|
7662
8033
|
console.log(`
|
|
7663
|
-
By Provider Timeline:`);
|
|
8034
|
+
By Provider Timeline (dates in UTC):`);
|
|
7664
8035
|
for (const provider of providersInBuckets) {
|
|
7665
8036
|
console.log(` ${provider}:`);
|
|
7666
8037
|
for (const bucket of data.buckets) {
|
|
7667
8038
|
const metric = bucket.byProvider?.[provider];
|
|
7668
8039
|
if (!metric) continue;
|
|
7669
|
-
const start = bucket.startDate.slice(0, 10);
|
|
7670
8040
|
const bar = metric.total > 0 ? "\u2588".repeat(Math.round(metric.citationRate * 20)) : "";
|
|
7671
|
-
console.log(` ${
|
|
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.`);
|
|
7672
8069
|
}
|
|
7673
8070
|
}
|
|
7674
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
|
+
}
|
|
7675
8114
|
}
|
|
7676
8115
|
function printGaps(data) {
|
|
7677
8116
|
console.log(`
|
|
@@ -8128,7 +8567,9 @@ async function createProject(name, opts) {
|
|
|
8128
8567
|
ownedDomains: opts.ownedDomains ?? [],
|
|
8129
8568
|
aliases: normalizeProjectAliases(opts.displayName, opts.aliases ?? []),
|
|
8130
8569
|
country: opts.country,
|
|
8131
|
-
language: opts.language
|
|
8570
|
+
language: opts.language,
|
|
8571
|
+
providers: opts.providers ?? [],
|
|
8572
|
+
providerModels: opts.providerModels ?? {}
|
|
8132
8573
|
});
|
|
8133
8574
|
if (isMachineFormat(opts.format)) {
|
|
8134
8575
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -8191,6 +8632,9 @@ async function showProject(name, format) {
|
|
|
8191
8632
|
console.log(` Language: ${project.language}`);
|
|
8192
8633
|
console.log(` Config source: ${project.configSource}`);
|
|
8193
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)"}`);
|
|
8194
8638
|
console.log(` Tags: ${project.tags.length > 0 ? project.tags.join(", ") : "(none)"}`);
|
|
8195
8639
|
const labelEntries = Object.entries(project.labels);
|
|
8196
8640
|
console.log(` Labels: ${labelEntries.length > 0 ? labelEntries.map(([k, v]) => `${k}=${v}`).join(", ") : "(none)"}`);
|
|
@@ -8220,19 +8664,45 @@ async function updateProjectSettings(name, opts) {
|
|
|
8220
8664
|
const toRemove = new Set(opts.removeAlias.map((a) => a.toLowerCase()));
|
|
8221
8665
|
aliases = aliases.filter((a) => !toRemove.has(a.toLowerCase()));
|
|
8222
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
|
+
}
|
|
8223
8682
|
const result = await client.putProject(name, {
|
|
8224
8683
|
displayName: nextDisplayName,
|
|
8225
8684
|
canonicalDomain: opts.domain ?? project.canonicalDomain,
|
|
8226
8685
|
ownedDomains,
|
|
8227
8686
|
aliases: normalizeProjectAliases(nextDisplayName, aliases),
|
|
8228
8687
|
country: opts.country ?? project.country,
|
|
8229
|
-
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
|
|
8230
8696
|
});
|
|
8231
8697
|
if (isMachineFormat(opts.format)) {
|
|
8232
8698
|
console.log(JSON.stringify(result, null, 2));
|
|
8233
8699
|
return;
|
|
8234
8700
|
}
|
|
8235
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
|
+
}
|
|
8236
8706
|
}
|
|
8237
8707
|
async function deleteProject(name, opts) {
|
|
8238
8708
|
const client = getClient20();
|
|
@@ -8336,20 +8806,22 @@ async function setDefaultLocation(project, label, format) {
|
|
|
8336
8806
|
var PROJECT_CLI_COMMANDS = [
|
|
8337
8807
|
{
|
|
8338
8808
|
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]",
|
|
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]",
|
|
8340
8810
|
options: {
|
|
8341
8811
|
domain: { type: "string", short: "d" },
|
|
8342
8812
|
"owned-domain": multiStringOption(),
|
|
8343
8813
|
alias: multiStringOption(),
|
|
8344
8814
|
country: stringOption(),
|
|
8345
8815
|
language: stringOption(),
|
|
8346
|
-
"display-name": stringOption()
|
|
8816
|
+
"display-name": stringOption(),
|
|
8817
|
+
provider: multiStringOption(),
|
|
8818
|
+
"provider-model": multiStringOption()
|
|
8347
8819
|
},
|
|
8348
8820
|
run: async (input) => {
|
|
8349
8821
|
const name = requireProject(
|
|
8350
8822
|
input,
|
|
8351
8823
|
"project.create",
|
|
8352
|
-
"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]"
|
|
8353
8825
|
);
|
|
8354
8826
|
await createProject(name, {
|
|
8355
8827
|
domain: getString(input.values, "domain") ?? name,
|
|
@@ -8358,13 +8830,15 @@ var PROJECT_CLI_COMMANDS = [
|
|
|
8358
8830
|
country: getString(input.values, "country") ?? "US",
|
|
8359
8831
|
language: getString(input.values, "language") ?? "en",
|
|
8360
8832
|
displayName: getString(input.values, "display-name") ?? name,
|
|
8833
|
+
providers: getStringArray(input.values, "provider") ?? [],
|
|
8834
|
+
providerModels: parseProviderModelAssignments(getStringArray(input.values, "provider-model")),
|
|
8361
8835
|
format: input.format
|
|
8362
8836
|
});
|
|
8363
8837
|
}
|
|
8364
8838
|
},
|
|
8365
8839
|
{
|
|
8366
8840
|
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]",
|
|
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]",
|
|
8368
8842
|
options: {
|
|
8369
8843
|
domain: { type: "string", short: "d" },
|
|
8370
8844
|
"owned-domain": multiStringOption(),
|
|
@@ -8375,14 +8849,26 @@ var PROJECT_CLI_COMMANDS = [
|
|
|
8375
8849
|
"remove-alias": multiStringOption(),
|
|
8376
8850
|
country: stringOption(),
|
|
8377
8851
|
language: stringOption(),
|
|
8378
|
-
"display-name": stringOption()
|
|
8852
|
+
"display-name": stringOption(),
|
|
8853
|
+
provider: multiStringOption(),
|
|
8854
|
+
"all-providers": { type: "boolean" },
|
|
8855
|
+
"provider-model": multiStringOption(),
|
|
8856
|
+
"clear-provider-model": multiStringOption()
|
|
8379
8857
|
},
|
|
8380
8858
|
run: async (input) => {
|
|
8381
8859
|
const name = requireProject(
|
|
8382
8860
|
input,
|
|
8383
8861
|
"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]"
|
|
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]"
|
|
8385
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
|
+
}
|
|
8386
8872
|
await updateProjectSettings(name, {
|
|
8387
8873
|
displayName: getString(input.values, "display-name"),
|
|
8388
8874
|
domain: getString(input.values, "domain"),
|
|
@@ -8394,6 +8880,9 @@ var PROJECT_CLI_COMMANDS = [
|
|
|
8394
8880
|
removeAlias: getStringArray(input.values, "remove-alias"),
|
|
8395
8881
|
country: getString(input.values, "country"),
|
|
8396
8882
|
language: getString(input.values, "language"),
|
|
8883
|
+
providers: allProviders ? [] : providers,
|
|
8884
|
+
providerModels,
|
|
8885
|
+
clearProviderModels,
|
|
8397
8886
|
format: input.format
|
|
8398
8887
|
});
|
|
8399
8888
|
}
|
|
@@ -8509,6 +8998,18 @@ var PROJECT_CLI_COMMANDS = [
|
|
|
8509
8998
|
}
|
|
8510
8999
|
}
|
|
8511
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
|
+
}
|
|
8512
9013
|
|
|
8513
9014
|
// src/commands/report.ts
|
|
8514
9015
|
import fs9 from "fs";
|
|
@@ -10944,18 +11445,36 @@ async function bootstrapCommand(_opts) {
|
|
|
10944
11445
|
} : existingConfig?.google;
|
|
10945
11446
|
const keyHash = crypto.createHash("sha256").update(rawApiKey).digest("hex");
|
|
10946
11447
|
const keyPrefix = rawApiKey.slice(0, 9);
|
|
11448
|
+
const existingConfigKeyHash = existingConfig ? crypto.createHash("sha256").update(existingConfig.apiKey).digest("hex") : void 0;
|
|
10947
11449
|
const db = createClient(databasePath);
|
|
10948
11450
|
migrate(db);
|
|
10949
11451
|
db.transaction((tx) => {
|
|
10950
|
-
|
|
10951
|
-
tx.
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
10955
|
-
|
|
10956
|
-
|
|
10957
|
-
|
|
10958
|
-
|
|
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
|
+
}
|
|
10959
11478
|
});
|
|
10960
11479
|
saveConfig({
|
|
10961
11480
|
apiUrl: env.apiUrl || existingConfig?.apiUrl || `http://localhost:${process.env.CANONRY_PORT || "4100"}`,
|