@canonry/canonry 4.118.0 → 4.124.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-fPBADGsr.js} +1 -1
- package/assets/assets/{BacklinksPage-BmO77kMs.js → BacklinksPage-DKer7s8M.js} +1 -1
- package/assets/assets/ChartPrimitives-0DFDFhCZ.js +1 -0
- package/assets/assets/{HistoryPage-DkLV_iRc.js → HistoryPage-BTkTlXg0.js} +1 -1
- package/assets/assets/ProjectPage-BzqDCkp2.js +7 -0
- package/assets/assets/{RunRow-BDH9agxQ.js → RunRow-DyIeH4_d.js} +1 -1
- package/assets/assets/{RunsPage-D9YsEeT3.js → RunsPage-KVu_37ds.js} +1 -1
- package/assets/assets/{SettingsPage-o5as3Ixw.js → SettingsPage-CCLXSBpq.js} +1 -1
- package/assets/assets/{TrafficPage-BCK203aQ.js → TrafficPage-BH7a0sS2.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-DsVMAZvS.js → TrafficSourceDetailPage-kIlf0kCe.js} +1 -1
- package/assets/assets/{arrow-left-B7ZR6VC9.js → arrow-left-jhivZs4g.js} +1 -1
- package/assets/assets/{extract-error-message-DeYDUpew.js → extract-error-message-XU_cbcLD.js} +1 -1
- package/assets/assets/index-CqFSTLK5.js +210 -0
- package/assets/assets/index-jymsMBpZ.css +1 -0
- package/assets/assets/{trash-2-xxHTqWbF.js → trash-2-CcmHLt5u.js} +1 -1
- package/assets/assets/vendor-recharts-DSxTZhaH.js +36 -0
- package/assets/index.html +3 -3
- package/dist/{chunk-HGNY5GL4.js → chunk-4D2XUMMV.js} +273 -37
- package/dist/{chunk-CMZIPFNQ.js → chunk-BGLBWFMV.js} +5034 -736
- package/dist/{chunk-KEZWLP4O.js → chunk-SLCGTOKT.js} +3293 -2337
- package/dist/{chunk-75UBTCKS.js → chunk-WGGB7QBD.js} +321 -6
- package/dist/cli.js +561 -36
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-FWPXMAVK.js → intelligence-service-SACHNNCT.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +8 -8
- 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-4D2XUMMV.js";
|
|
33
33
|
import {
|
|
34
34
|
CliError,
|
|
35
35
|
EXIT_SYSTEM_ERROR,
|
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
saveConfig,
|
|
47
47
|
saveConfigPatch,
|
|
48
48
|
usageError
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-WGGB7QBD.js";
|
|
50
50
|
import {
|
|
51
51
|
apiKeys,
|
|
52
52
|
createClient,
|
|
@@ -54,8 +54,11 @@ import {
|
|
|
54
54
|
projects,
|
|
55
55
|
queries,
|
|
56
56
|
renderReportHtml
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-BGLBWFMV.js";
|
|
58
58
|
import {
|
|
59
|
+
AdsOperationKinds,
|
|
60
|
+
AdsOperationStates,
|
|
61
|
+
AdsOperationStepStates,
|
|
59
62
|
BacklinkSources,
|
|
60
63
|
CcReleaseSyncStatuses,
|
|
61
64
|
CheckScopes,
|
|
@@ -65,15 +68,19 @@ import {
|
|
|
65
68
|
REPORT_PERIOD_OPTIONS,
|
|
66
69
|
RunStatuses,
|
|
67
70
|
TrafficEventKinds,
|
|
71
|
+
adsActivateTreeRequestSchema,
|
|
72
|
+
adsActivationGrantCreateRequestSchema,
|
|
68
73
|
adsAdCreateRequestSchema,
|
|
69
74
|
adsAdGroupCreateRequestSchema,
|
|
70
75
|
adsAdGroupUpdateRequestSchema,
|
|
71
76
|
adsAdUpdateRequestSchema,
|
|
72
77
|
adsCampaignCreateRequestSchema,
|
|
73
78
|
adsCampaignUpdateRequestSchema,
|
|
79
|
+
adsGeoSearchQuerySchema,
|
|
74
80
|
adsImageUploadRequestSchema,
|
|
75
81
|
adsPauseRequestSchema,
|
|
76
82
|
backlinkSourceSchema,
|
|
83
|
+
buildModelChangeNotice,
|
|
77
84
|
discoveryBucketSchema,
|
|
78
85
|
discoveryCompetitorTypeSchema,
|
|
79
86
|
effectiveDomains,
|
|
@@ -88,7 +95,7 @@ import {
|
|
|
88
95
|
providerQuotaPolicySchema,
|
|
89
96
|
resolveProviderInput,
|
|
90
97
|
winnabilityClassSchema
|
|
91
|
-
} from "./chunk-
|
|
98
|
+
} from "./chunk-SLCGTOKT.js";
|
|
92
99
|
|
|
93
100
|
// src/cli.ts
|
|
94
101
|
import { pathToFileURL } from "url";
|
|
@@ -2746,20 +2753,54 @@ function readRequest(inputPath, schema) {
|
|
|
2746
2753
|
});
|
|
2747
2754
|
}
|
|
2748
2755
|
}
|
|
2756
|
+
function printOperationDetails(operation) {
|
|
2757
|
+
console.log(`Operation: ${operation.operationKey}`);
|
|
2758
|
+
if (operation.entityId) console.log(`Entity: ${operation.entityType ?? "unknown"} ${operation.entityId}`);
|
|
2759
|
+
if (operation.upstreamUpdatedAt != null) console.log(`Updated: ${operation.upstreamUpdatedAt}`);
|
|
2760
|
+
if (operation.errorCode) console.log(`Error: ${operation.errorCode}: ${operation.errorMessage ?? ""}`);
|
|
2761
|
+
if (operation.state === AdsOperationStates.pending || operation.state === AdsOperationStates.unknown || operation.state === AdsOperationStates.reconciling) {
|
|
2762
|
+
if (operation.kind === AdsOperationKinds.campaign_tree_activate) {
|
|
2763
|
+
console.log("Do not retry with a new operation key. Resume activation recovery for the original receipt instead.");
|
|
2764
|
+
} else {
|
|
2765
|
+
console.log("Do not retry with a new operation key. Reconcile the original receipt instead.");
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2749
2769
|
function printOperation(result, format) {
|
|
2750
2770
|
if (isMachineFormat(format)) {
|
|
2751
2771
|
console.log(JSON.stringify(result, null, 2));
|
|
2752
2772
|
return;
|
|
2753
2773
|
}
|
|
2754
|
-
|
|
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]",
|
|
@@ -3409,10 +3763,16 @@ async function gaSessionHistory(project, opts) {
|
|
|
3409
3763
|
console.log(` ${"DATE".padEnd(dateWidth)} ${"SESSIONS".padEnd(10)}${"ORGANIC".padEnd(10)}${"USERS".padEnd(8)}`);
|
|
3410
3764
|
console.log(` ${"\u2500".repeat(dateWidth)} ${"\u2500".repeat(10)}${"\u2500".repeat(10)}${"\u2500".repeat(8)}`);
|
|
3411
3765
|
for (const row of result) {
|
|
3766
|
+
const users = row.usersSource === "deduplicated" ? String(row.users) : `${row.users}*`;
|
|
3412
3767
|
console.log(
|
|
3413
|
-
` ${row.date.padEnd(dateWidth)} ${String(row.sessions).padEnd(10)}${String(row.organicSessions).padEnd(10)}${
|
|
3768
|
+
` ${row.date.padEnd(dateWidth)} ${String(row.sessions).padEnd(10)}${String(row.organicSessions).padEnd(10)}${users.padEnd(8)}`
|
|
3414
3769
|
);
|
|
3415
3770
|
}
|
|
3771
|
+
if (result.some((row) => row.usersSource !== "deduplicated")) {
|
|
3772
|
+
console.log("\n * Visitor count is approximate \u2014 it adds up each page a visitor landed on,");
|
|
3773
|
+
console.log(" so someone who read several pages is counted more than once. Run");
|
|
3774
|
+
console.log(` "canonry ga sync ${project}" to replace these with exact counts.`);
|
|
3775
|
+
}
|
|
3416
3776
|
}
|
|
3417
3777
|
async function gaCoverage(project, format) {
|
|
3418
3778
|
const client = getClient6();
|
|
@@ -7634,11 +7994,29 @@ async function showAnalytics(project, options) {
|
|
|
7634
7994
|
console.log(JSON.stringify(results, null, 2));
|
|
7635
7995
|
}
|
|
7636
7996
|
}
|
|
7997
|
+
var BUCKET_DATE_WIDTH = 35;
|
|
7998
|
+
function bucketDates(bucket) {
|
|
7999
|
+
const legacyCompatible = bucket;
|
|
8000
|
+
const start = legacyCompatible.dataStartDate?.slice(0, 10);
|
|
8001
|
+
const end = legacyCompatible.dataEndDate?.slice(0, 10);
|
|
8002
|
+
if (!start || !end) return "date unavailable";
|
|
8003
|
+
const sweeps = legacyCompatible.sweepCount ?? 1;
|
|
8004
|
+
const pooled = sweeps > 1 ? ` (${sweeps} sweeps)` : "";
|
|
8005
|
+
if (start === end) return `${start}${pooled}`;
|
|
8006
|
+
return `${start} \u2192 ${end}${pooled}`;
|
|
8007
|
+
}
|
|
7637
8008
|
function printMetrics(data) {
|
|
7638
8009
|
console.log(`
|
|
7639
8010
|
Citation Rate Trends (${data.window})`);
|
|
7640
8011
|
console.log("\u2500".repeat(50));
|
|
7641
8012
|
const pct4 = (n) => `${(n * 100).toFixed(1)}%`;
|
|
8013
|
+
const modelChangeNotice = buildModelChangeNotice(readModelPointerChanges(data));
|
|
8014
|
+
if (modelChangeNotice?.kind === "change") {
|
|
8015
|
+
console.log(`
|
|
8016
|
+
Model Updates Behind These Numbers:`);
|
|
8017
|
+
console.log(` ${modelChangeNotice.text}`);
|
|
8018
|
+
console.log("");
|
|
8019
|
+
}
|
|
7642
8020
|
console.log(` Overall: ${pct4(data.overall.citationRate)} (${data.overall.cited}/${data.overall.total})`);
|
|
7643
8021
|
console.log(` Trend: ${data.trend}`);
|
|
7644
8022
|
if (Object.keys(data.byProvider).length > 0) {
|
|
@@ -7650,28 +8028,95 @@ Citation Rate Trends (${data.window})`);
|
|
|
7650
8028
|
}
|
|
7651
8029
|
if (data.buckets.length > 0) {
|
|
7652
8030
|
console.log(`
|
|
7653
|
-
Timeline:`);
|
|
8031
|
+
Timeline (dates in UTC):`);
|
|
7654
8032
|
for (const bucket of data.buckets) {
|
|
7655
|
-
const start = bucket.startDate.slice(0, 10);
|
|
7656
8033
|
const bar = bucket.total > 0 ? "\u2588".repeat(Math.round(bucket.citationRate * 20)) : "";
|
|
7657
|
-
console.log(` ${
|
|
8034
|
+
console.log(` ${bucketDates(bucket).padEnd(BUCKET_DATE_WIDTH)} ${pct4(bucket.citationRate).padStart(6)} ${bar}`);
|
|
7658
8035
|
}
|
|
7659
8036
|
}
|
|
7660
8037
|
const providersInBuckets = [...new Set(data.buckets.flatMap((b) => Object.keys(b.byProvider ?? {})))].sort();
|
|
7661
8038
|
if (data.buckets.length > 0 && providersInBuckets.length > 0) {
|
|
7662
8039
|
console.log(`
|
|
7663
|
-
By Provider Timeline:`);
|
|
8040
|
+
By Provider Timeline (dates in UTC):`);
|
|
7664
8041
|
for (const provider of providersInBuckets) {
|
|
7665
8042
|
console.log(` ${provider}:`);
|
|
7666
8043
|
for (const bucket of data.buckets) {
|
|
7667
8044
|
const metric = bucket.byProvider?.[provider];
|
|
7668
8045
|
if (!metric) continue;
|
|
7669
|
-
const start = bucket.startDate.slice(0, 10);
|
|
7670
8046
|
const bar = metric.total > 0 ? "\u2588".repeat(Math.round(metric.citationRate * 20)) : "";
|
|
7671
|
-
console.log(` ${
|
|
8047
|
+
console.log(` ${bucketDates(bucket).padEnd(BUCKET_DATE_WIDTH)} ${pct4(metric.citationRate).padStart(6)} ${bar}`);
|
|
8048
|
+
}
|
|
8049
|
+
}
|
|
8050
|
+
}
|
|
8051
|
+
if (modelChangeNotice?.kind === "no-known-change") {
|
|
8052
|
+
console.log(`
|
|
8053
|
+
Model Updates Behind These Numbers:`);
|
|
8054
|
+
console.log(` ${modelChangeNotice.text}`);
|
|
8055
|
+
console.log(` ${modelChangeNotice.detail}`);
|
|
8056
|
+
}
|
|
8057
|
+
const attributionEntries = Object.entries(readModelAttribution(data)).sort(([a], [b]) => a.localeCompare(b));
|
|
8058
|
+
if (attributionEntries.length > 0) {
|
|
8059
|
+
console.log(`
|
|
8060
|
+
Model Evidence:`);
|
|
8061
|
+
for (const [provider, attribution] of attributionEntries) {
|
|
8062
|
+
const latest = attribution.latestObservation;
|
|
8063
|
+
console.log(` ${provider}: latest ${formatModelEvidence(latest.state)} at ${latest.observedAt}`);
|
|
8064
|
+
for (const event of attribution.events) {
|
|
8065
|
+
const dating = event.fromPreWindowAnchor ? " (on or before)" : "";
|
|
8066
|
+
const priorSweep = event.fromPreWindowAnchor && event.anchorObservedAt ? ` [last seen ${formatModelEvidence(event.from)} on ${event.anchorObservedAt}]` : "";
|
|
8067
|
+
console.log(` ${event.observedAt}${dating} ${formatModelEvidence(event.from)} \u2192 ${formatModelEvidence(event.to)}${priorSweep}`);
|
|
8068
|
+
}
|
|
8069
|
+
const eventTotal = attribution.eventTotal ?? attribution.events.length;
|
|
8070
|
+
if (eventTotal > attribution.events.length) {
|
|
8071
|
+
console.log(` Showing the latest ${attribution.events.length} of ${eventTotal} model changes.`);
|
|
8072
|
+
}
|
|
8073
|
+
if (attribution.anchorUnavailable) {
|
|
8074
|
+
console.log(` We did not look far enough back to be sure this is every change.`);
|
|
7672
8075
|
}
|
|
7673
8076
|
}
|
|
7674
8077
|
}
|
|
8078
|
+
const servedEntries = Object.entries(readServedModelAttribution(data)).sort(([a], [b]) => a.localeCompare(b));
|
|
8079
|
+
if (servedEntries.length > 0) {
|
|
8080
|
+
const mismatch = readModelServiceMismatch(data);
|
|
8081
|
+
console.log(`
|
|
8082
|
+
What the Engines Answered With:`);
|
|
8083
|
+
for (const [provider, served] of servedEntries) {
|
|
8084
|
+
const rawIds = served.latestServedModelIds.length > 0 ? served.latestServedModelIds.join(", ") : formatModelEvidence(served.latestObservation.state);
|
|
8085
|
+
const substituted = mismatch[provider];
|
|
8086
|
+
const note = substituted ? ` \u2014 not the ${formatModelEvidence(substituted.configured)} you selected` : "";
|
|
8087
|
+
console.log(` ${provider}: ${rawIds} at ${served.latestObservation.observedAt}${note}`);
|
|
8088
|
+
for (const event of served.events) {
|
|
8089
|
+
const dating = event.fromPreWindowAnchor ? " (on or before)" : "";
|
|
8090
|
+
console.log(` ${event.observedAt}${dating} ${formatModelEvidence(event.from)} \u2192 ${formatModelEvidence(event.to)}`);
|
|
8091
|
+
}
|
|
8092
|
+
}
|
|
8093
|
+
}
|
|
8094
|
+
}
|
|
8095
|
+
function readServedModelAttribution(data) {
|
|
8096
|
+
const legacyCompatible = data;
|
|
8097
|
+
return legacyCompatible.servedModelAttribution ?? {};
|
|
8098
|
+
}
|
|
8099
|
+
function readModelServiceMismatch(data) {
|
|
8100
|
+
const legacyCompatible = data;
|
|
8101
|
+
return legacyCompatible.modelServiceMismatch ?? {};
|
|
8102
|
+
}
|
|
8103
|
+
function readModelPointerChanges(data) {
|
|
8104
|
+
const legacyCompatible = data;
|
|
8105
|
+
return legacyCompatible.modelPointerChanges ?? {};
|
|
8106
|
+
}
|
|
8107
|
+
function readModelAttribution(data) {
|
|
8108
|
+
const legacyCompatible = data;
|
|
8109
|
+
return legacyCompatible.modelAttribution ?? {};
|
|
8110
|
+
}
|
|
8111
|
+
function formatModelEvidence(state) {
|
|
8112
|
+
switch (state.status) {
|
|
8113
|
+
case "known":
|
|
8114
|
+
return `known ${state.model}`;
|
|
8115
|
+
case "unknown":
|
|
8116
|
+
return "unknown";
|
|
8117
|
+
case "mixed":
|
|
8118
|
+
return `mixed ${state.models.join(", ")}${state.includesUnknown ? " + unknown" : ""}`;
|
|
8119
|
+
}
|
|
7675
8120
|
}
|
|
7676
8121
|
function printGaps(data) {
|
|
7677
8122
|
console.log(`
|
|
@@ -8128,7 +8573,9 @@ async function createProject(name, opts) {
|
|
|
8128
8573
|
ownedDomains: opts.ownedDomains ?? [],
|
|
8129
8574
|
aliases: normalizeProjectAliases(opts.displayName, opts.aliases ?? []),
|
|
8130
8575
|
country: opts.country,
|
|
8131
|
-
language: opts.language
|
|
8576
|
+
language: opts.language,
|
|
8577
|
+
providers: opts.providers ?? [],
|
|
8578
|
+
providerModels: opts.providerModels ?? {}
|
|
8132
8579
|
});
|
|
8133
8580
|
if (isMachineFormat(opts.format)) {
|
|
8134
8581
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -8191,6 +8638,9 @@ async function showProject(name, format) {
|
|
|
8191
8638
|
console.log(` Language: ${project.language}`);
|
|
8192
8639
|
console.log(` Config source: ${project.configSource}`);
|
|
8193
8640
|
console.log(` Config revision: ${project.configRevision}`);
|
|
8641
|
+
console.log(` Providers: ${(project.providers ?? []).length > 0 ? project.providers.join(", ") : "all configured"}`);
|
|
8642
|
+
const providerModels = Object.entries(project.providerModels ?? {});
|
|
8643
|
+
console.log(` Model overrides: ${providerModels.length > 0 ? providerModels.map(([provider, model]) => `${provider}=${model}`).join(", ") : "(none; instance settings inherited)"}`);
|
|
8194
8644
|
console.log(` Tags: ${project.tags.length > 0 ? project.tags.join(", ") : "(none)"}`);
|
|
8195
8645
|
const labelEntries = Object.entries(project.labels);
|
|
8196
8646
|
console.log(` Labels: ${labelEntries.length > 0 ? labelEntries.map(([k, v]) => `${k}=${v}`).join(", ") : "(none)"}`);
|
|
@@ -8220,19 +8670,45 @@ async function updateProjectSettings(name, opts) {
|
|
|
8220
8670
|
const toRemove = new Set(opts.removeAlias.map((a) => a.toLowerCase()));
|
|
8221
8671
|
aliases = aliases.filter((a) => !toRemove.has(a.toLowerCase()));
|
|
8222
8672
|
}
|
|
8673
|
+
const providerModels = { ...project.providerModels ?? {}, ...opts.providerModels ?? {} };
|
|
8674
|
+
for (const provider of opts.clearProviderModels ?? []) delete providerModels[provider];
|
|
8675
|
+
const nextProviders = opts.providers ?? project.providers ?? [];
|
|
8676
|
+
if (nextProviders.length > 0) {
|
|
8677
|
+
const requested = Object.keys(opts.providerModels ?? {}).filter((p) => !nextProviders.includes(p));
|
|
8678
|
+
if (requested.length > 0) {
|
|
8679
|
+
throw usageError(
|
|
8680
|
+
`Error: --provider-model set for ${requested.join(", ")}, which ${requested.length > 1 ? "are" : "is"} not a selected engine for "${name}"`,
|
|
8681
|
+
{
|
|
8682
|
+
message: "provider-model set for a provider the project does not run",
|
|
8683
|
+
details: { command: "project.update", project: name, providers: nextProviders, unselected: requested }
|
|
8684
|
+
}
|
|
8685
|
+
);
|
|
8686
|
+
}
|
|
8687
|
+
}
|
|
8223
8688
|
const result = await client.putProject(name, {
|
|
8224
8689
|
displayName: nextDisplayName,
|
|
8225
8690
|
canonicalDomain: opts.domain ?? project.canonicalDomain,
|
|
8226
8691
|
ownedDomains,
|
|
8227
8692
|
aliases: normalizeProjectAliases(nextDisplayName, aliases),
|
|
8228
8693
|
country: opts.country ?? project.country,
|
|
8229
|
-
language: opts.language ?? project.language
|
|
8694
|
+
language: opts.language ?? project.language,
|
|
8695
|
+
tags: project.tags,
|
|
8696
|
+
labels: project.labels,
|
|
8697
|
+
providers: opts.providers ?? project.providers,
|
|
8698
|
+
providerModels,
|
|
8699
|
+
locations: project.locations,
|
|
8700
|
+
defaultLocation: project.defaultLocation,
|
|
8701
|
+
autoExtractBacklinks: project.autoExtractBacklinks
|
|
8230
8702
|
});
|
|
8231
8703
|
if (isMachineFormat(opts.format)) {
|
|
8232
8704
|
console.log(JSON.stringify(result, null, 2));
|
|
8233
8705
|
return;
|
|
8234
8706
|
}
|
|
8235
8707
|
console.log(`Project updated: ${result.name}`);
|
|
8708
|
+
const dropped = Object.keys(providerModels).filter((provider) => !(provider in result.providerModels));
|
|
8709
|
+
if (dropped.length > 0) {
|
|
8710
|
+
console.log(` Dropped model override for deselected engine(s): ${dropped.join(", ")}`);
|
|
8711
|
+
}
|
|
8236
8712
|
}
|
|
8237
8713
|
async function deleteProject(name, opts) {
|
|
8238
8714
|
const client = getClient20();
|
|
@@ -8336,20 +8812,22 @@ async function setDefaultLocation(project, label, format) {
|
|
|
8336
8812
|
var PROJECT_CLI_COMMANDS = [
|
|
8337
8813
|
{
|
|
8338
8814
|
path: ["project", "create"],
|
|
8339
|
-
usage: "canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]",
|
|
8815
|
+
usage: "canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--provider <name>...] [--provider-model provider=model...] [--format json]",
|
|
8340
8816
|
options: {
|
|
8341
8817
|
domain: { type: "string", short: "d" },
|
|
8342
8818
|
"owned-domain": multiStringOption(),
|
|
8343
8819
|
alias: multiStringOption(),
|
|
8344
8820
|
country: stringOption(),
|
|
8345
8821
|
language: stringOption(),
|
|
8346
|
-
"display-name": stringOption()
|
|
8822
|
+
"display-name": stringOption(),
|
|
8823
|
+
provider: multiStringOption(),
|
|
8824
|
+
"provider-model": multiStringOption()
|
|
8347
8825
|
},
|
|
8348
8826
|
run: async (input) => {
|
|
8349
8827
|
const name = requireProject(
|
|
8350
8828
|
input,
|
|
8351
8829
|
"project.create",
|
|
8352
|
-
"canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]"
|
|
8830
|
+
"canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--provider <name>...] [--provider-model provider=model...] [--format json]"
|
|
8353
8831
|
);
|
|
8354
8832
|
await createProject(name, {
|
|
8355
8833
|
domain: getString(input.values, "domain") ?? name,
|
|
@@ -8358,13 +8836,15 @@ var PROJECT_CLI_COMMANDS = [
|
|
|
8358
8836
|
country: getString(input.values, "country") ?? "US",
|
|
8359
8837
|
language: getString(input.values, "language") ?? "en",
|
|
8360
8838
|
displayName: getString(input.values, "display-name") ?? name,
|
|
8839
|
+
providers: getStringArray(input.values, "provider") ?? [],
|
|
8840
|
+
providerModels: parseProviderModelAssignments(getStringArray(input.values, "provider-model")),
|
|
8361
8841
|
format: input.format
|
|
8362
8842
|
});
|
|
8363
8843
|
}
|
|
8364
8844
|
},
|
|
8365
8845
|
{
|
|
8366
8846
|
path: ["project", "update"],
|
|
8367
|
-
usage: "canonry project update <name> [--domain <domain>] [--owned-domain <domain>...] [--add-domain <domain>...] [--remove-domain <domain>...] [--alias <name>...] [--add-alias <name>...] [--remove-alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]",
|
|
8847
|
+
usage: "canonry project update <name> [--domain <domain>] [--owned-domain <domain>...] [--add-domain <domain>...] [--remove-domain <domain>...] [--alias <name>...] [--add-alias <name>...] [--remove-alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--provider <name>...] [--all-providers] [--provider-model provider=model...] [--clear-provider-model <provider>...] [--format json]",
|
|
8368
8848
|
options: {
|
|
8369
8849
|
domain: { type: "string", short: "d" },
|
|
8370
8850
|
"owned-domain": multiStringOption(),
|
|
@@ -8375,14 +8855,26 @@ var PROJECT_CLI_COMMANDS = [
|
|
|
8375
8855
|
"remove-alias": multiStringOption(),
|
|
8376
8856
|
country: stringOption(),
|
|
8377
8857
|
language: stringOption(),
|
|
8378
|
-
"display-name": stringOption()
|
|
8858
|
+
"display-name": stringOption(),
|
|
8859
|
+
provider: multiStringOption(),
|
|
8860
|
+
"all-providers": { type: "boolean" },
|
|
8861
|
+
"provider-model": multiStringOption(),
|
|
8862
|
+
"clear-provider-model": multiStringOption()
|
|
8379
8863
|
},
|
|
8380
8864
|
run: async (input) => {
|
|
8381
8865
|
const name = requireProject(
|
|
8382
8866
|
input,
|
|
8383
8867
|
"project.update",
|
|
8384
|
-
"canonry project update <name> [--domain <domain>] [--owned-domain <domain>...] [--add-domain <domain>...] [--remove-domain <domain>...] [--alias <name>...] [--add-alias <name>...] [--remove-alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]"
|
|
8868
|
+
"canonry project update <name> [--domain <domain>] [--owned-domain <domain>...] [--add-domain <domain>...] [--remove-domain <domain>...] [--alias <name>...] [--add-alias <name>...] [--remove-alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--provider <name>...] [--all-providers] [--provider-model provider=model...] [--clear-provider-model <provider>...] [--format json]"
|
|
8385
8869
|
);
|
|
8870
|
+
const providers = getStringArray(input.values, "provider");
|
|
8871
|
+
const allProviders = getBoolean(input.values, "all-providers");
|
|
8872
|
+
if (allProviders && providers?.length) throw usageError("Error: --all-providers conflicts with --provider");
|
|
8873
|
+
const providerModels = parseProviderModelAssignments(getStringArray(input.values, "provider-model"));
|
|
8874
|
+
const clearProviderModels = getStringArray(input.values, "clear-provider-model") ?? [];
|
|
8875
|
+
for (const provider of clearProviderModels) {
|
|
8876
|
+
if (provider in providerModels) throw usageError(`Error: --provider-model and --clear-provider-model conflict for ${provider}`);
|
|
8877
|
+
}
|
|
8386
8878
|
await updateProjectSettings(name, {
|
|
8387
8879
|
displayName: getString(input.values, "display-name"),
|
|
8388
8880
|
domain: getString(input.values, "domain"),
|
|
@@ -8394,6 +8886,9 @@ var PROJECT_CLI_COMMANDS = [
|
|
|
8394
8886
|
removeAlias: getStringArray(input.values, "remove-alias"),
|
|
8395
8887
|
country: getString(input.values, "country"),
|
|
8396
8888
|
language: getString(input.values, "language"),
|
|
8889
|
+
providers: allProviders ? [] : providers,
|
|
8890
|
+
providerModels,
|
|
8891
|
+
clearProviderModels,
|
|
8397
8892
|
format: input.format
|
|
8398
8893
|
});
|
|
8399
8894
|
}
|
|
@@ -8509,6 +9004,18 @@ var PROJECT_CLI_COMMANDS = [
|
|
|
8509
9004
|
}
|
|
8510
9005
|
}
|
|
8511
9006
|
];
|
|
9007
|
+
function parseProviderModelAssignments(assignments) {
|
|
9008
|
+
const result = {};
|
|
9009
|
+
for (const assignment of assignments ?? []) {
|
|
9010
|
+
const separator = assignment.indexOf("=");
|
|
9011
|
+
const provider = separator === -1 ? "" : assignment.slice(0, separator).trim();
|
|
9012
|
+
const model = separator === -1 ? "" : assignment.slice(separator + 1).trim();
|
|
9013
|
+
if (!provider || !model) throw usageError(`Error: --provider-model must use provider=model (received ${assignment})`);
|
|
9014
|
+
if (provider in result) throw usageError(`Error: duplicate --provider-model assignment for ${provider}`);
|
|
9015
|
+
result[provider] = model;
|
|
9016
|
+
}
|
|
9017
|
+
return result;
|
|
9018
|
+
}
|
|
8512
9019
|
|
|
8513
9020
|
// src/commands/report.ts
|
|
8514
9021
|
import fs9 from "fs";
|
|
@@ -10944,18 +11451,36 @@ async function bootstrapCommand(_opts) {
|
|
|
10944
11451
|
} : existingConfig?.google;
|
|
10945
11452
|
const keyHash = crypto.createHash("sha256").update(rawApiKey).digest("hex");
|
|
10946
11453
|
const keyPrefix = rawApiKey.slice(0, 9);
|
|
11454
|
+
const existingConfigKeyHash = existingConfig ? crypto.createHash("sha256").update(existingConfig.apiKey).digest("hex") : void 0;
|
|
10947
11455
|
const db = createClient(databasePath);
|
|
10948
11456
|
migrate(db);
|
|
10949
11457
|
db.transaction((tx) => {
|
|
10950
|
-
|
|
10951
|
-
tx.
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
10955
|
-
|
|
10956
|
-
|
|
10957
|
-
|
|
10958
|
-
|
|
11458
|
+
const rotatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11459
|
+
const existingDefaults = tx.select({ id: apiKeys.id, keyHash: apiKeys.keyHash }).from(apiKeys).where(eq(apiKeys.name, "default")).all();
|
|
11460
|
+
const existingDefault = existingDefaults.find((key) => key.keyHash === existingConfigKeyHash) ?? existingDefaults.at(0);
|
|
11461
|
+
if (existingDefault) {
|
|
11462
|
+
tx.update(apiKeys).set({
|
|
11463
|
+
keyHash,
|
|
11464
|
+
keyPrefix,
|
|
11465
|
+
scopes: ["*"],
|
|
11466
|
+
projectId: null,
|
|
11467
|
+
lastUsedAt: null,
|
|
11468
|
+
revokedAt: null
|
|
11469
|
+
}).where(eq(apiKeys.id, existingDefault.id)).run();
|
|
11470
|
+
for (const duplicate of existingDefaults) {
|
|
11471
|
+
if (duplicate.id === existingDefault.id) continue;
|
|
11472
|
+
tx.update(apiKeys).set({ revokedAt: rotatedAt }).where(eq(apiKeys.id, duplicate.id)).run();
|
|
11473
|
+
}
|
|
11474
|
+
} else {
|
|
11475
|
+
tx.insert(apiKeys).values({
|
|
11476
|
+
id: crypto.randomUUID(),
|
|
11477
|
+
name: "default",
|
|
11478
|
+
keyHash,
|
|
11479
|
+
keyPrefix,
|
|
11480
|
+
scopes: ["*"],
|
|
11481
|
+
createdAt: rotatedAt
|
|
11482
|
+
}).run();
|
|
11483
|
+
}
|
|
10959
11484
|
});
|
|
10960
11485
|
saveConfig({
|
|
10961
11486
|
apiUrl: env.apiUrl || existingConfig?.apiUrl || `http://localhost:${process.env.CANONRY_PORT || "4100"}`,
|