@zanfia/cli 0.21.0 → 0.23.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/dist/index.js +2348 -125
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6901,7 +6901,7 @@ var {
|
|
|
6901
6901
|
} = import_index.default;
|
|
6902
6902
|
|
|
6903
6903
|
// src/program.ts
|
|
6904
|
-
import { readFileSync as
|
|
6904
|
+
import { readFileSync as readFileSync29 } from "fs";
|
|
6905
6905
|
|
|
6906
6906
|
// src/utils/config.ts
|
|
6907
6907
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
@@ -8011,6 +8011,15 @@ function createMockClient() {
|
|
|
8011
8011
|
async retryAutomationRun() {
|
|
8012
8012
|
throw new ApiError(501, "workflows are not available in mock mode");
|
|
8013
8013
|
},
|
|
8014
|
+
async stopAutomationRun() {
|
|
8015
|
+
throw new ApiError(501, "workflows are not available in mock mode");
|
|
8016
|
+
},
|
|
8017
|
+
async listClientAutomationRuns() {
|
|
8018
|
+
return { items: [] };
|
|
8019
|
+
},
|
|
8020
|
+
async stopClientAutomationRuns() {
|
|
8021
|
+
throw new ApiError(501, "workflows are not available in mock mode");
|
|
8022
|
+
},
|
|
8014
8023
|
async testRunAutomation() {
|
|
8015
8024
|
throw new ApiError(501, "workflows are not available in mock mode");
|
|
8016
8025
|
},
|
|
@@ -8648,6 +8657,30 @@ function createMockClient() {
|
|
|
8648
8657
|
async rejectManualOrder() {
|
|
8649
8658
|
throw new ApiError(501, "manual order decisions are not available in mock mode");
|
|
8650
8659
|
},
|
|
8660
|
+
async markOrderAsTest() {
|
|
8661
|
+
throw new ApiError(501, "order changes are not available in mock mode");
|
|
8662
|
+
},
|
|
8663
|
+
async markOrderRefunded() {
|
|
8664
|
+
throw new ApiError(501, "order changes are not available in mock mode");
|
|
8665
|
+
},
|
|
8666
|
+
async listConsultationBookings() {
|
|
8667
|
+
return { items: [], nextCursor: null, from: "", to: "" };
|
|
8668
|
+
},
|
|
8669
|
+
async listConsultationClientsWithoutBooking() {
|
|
8670
|
+
return { items: [], nextCursor: null };
|
|
8671
|
+
},
|
|
8672
|
+
async listCourseQuestions() {
|
|
8673
|
+
return { questions: [], nextCursor: null };
|
|
8674
|
+
},
|
|
8675
|
+
async replyToCourseQuestion() {
|
|
8676
|
+
throw new ApiError(501, "lesson Q&A is not available in mock mode");
|
|
8677
|
+
},
|
|
8678
|
+
async setCourseQuestionStatus() {
|
|
8679
|
+
throw new ApiError(501, "lesson Q&A is not available in mock mode");
|
|
8680
|
+
},
|
|
8681
|
+
async acceptCourseAnswer() {
|
|
8682
|
+
throw new ApiError(501, "lesson Q&A is not available in mock mode");
|
|
8683
|
+
},
|
|
8651
8684
|
async listFailedSubscriptionPayments() {
|
|
8652
8685
|
return { items: [], stripeConnected: false };
|
|
8653
8686
|
},
|
|
@@ -8852,6 +8885,21 @@ function createMockClient() {
|
|
|
8852
8885
|
async listCommunityEventAttendees() {
|
|
8853
8886
|
throw new ApiError(501, "community events are not available in mock mode");
|
|
8854
8887
|
},
|
|
8888
|
+
async listCommunityJoinRequests() {
|
|
8889
|
+
return { requests: [], pendingCount: 0 };
|
|
8890
|
+
},
|
|
8891
|
+
async approveCommunityJoinRequests() {
|
|
8892
|
+
throw new ApiError(501, "community join requests are not available in mock mode");
|
|
8893
|
+
},
|
|
8894
|
+
async declineCommunityJoinRequests() {
|
|
8895
|
+
throw new ApiError(501, "community join requests are not available in mock mode");
|
|
8896
|
+
},
|
|
8897
|
+
async listCommunityDigestDrafts() {
|
|
8898
|
+
return { digestEnabled: false, current: null, history: [] };
|
|
8899
|
+
},
|
|
8900
|
+
async decideCommunityDigestDraft() {
|
|
8901
|
+
throw new ApiError(501, "the community digest is not available in mock mode");
|
|
8902
|
+
},
|
|
8855
8903
|
async getCommunityModerationQueue() {
|
|
8856
8904
|
return { pending: [], resolved: [] };
|
|
8857
8905
|
},
|
|
@@ -8924,6 +8972,24 @@ function createMockClient() {
|
|
|
8924
8972
|
async getLiveRoomAttendance() {
|
|
8925
8973
|
throw new ApiError(501, "webinars are not available in mock mode");
|
|
8926
8974
|
},
|
|
8975
|
+
async createExternalWebinar() {
|
|
8976
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
8977
|
+
},
|
|
8978
|
+
async setLiveRoomReplay() {
|
|
8979
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
8980
|
+
},
|
|
8981
|
+
async setLiveRoomStudioScene() {
|
|
8982
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
8983
|
+
},
|
|
8984
|
+
async createLiveRoomStudioGuestInvite() {
|
|
8985
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
8986
|
+
},
|
|
8987
|
+
async listLiveRoomStudioGuests() {
|
|
8988
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
8989
|
+
},
|
|
8990
|
+
async revokeLiveRoomStudioGuestInvite() {
|
|
8991
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
8992
|
+
},
|
|
8927
8993
|
async call(method, pathTemplate) {
|
|
8928
8994
|
throw new ApiError(501, `${method} ${pathTemplate} is not available in mock mode`);
|
|
8929
8995
|
},
|
|
@@ -8984,6 +9050,15 @@ function createMockClient() {
|
|
|
8984
9050
|
async createMetaAd() {
|
|
8985
9051
|
throw new ApiError(501, "Meta Ads are not available in mock mode");
|
|
8986
9052
|
},
|
|
9053
|
+
async createMetaCampaign() {
|
|
9054
|
+
throw new ApiError(501, "Meta Ads are not available in mock mode");
|
|
9055
|
+
},
|
|
9056
|
+
async createMetaAdSet() {
|
|
9057
|
+
throw new ApiError(501, "Meta Ads are not available in mock mode");
|
|
9058
|
+
},
|
|
9059
|
+
async createMetaAdFromScratch() {
|
|
9060
|
+
throw new ApiError(501, "Meta Ads are not available in mock mode");
|
|
9061
|
+
},
|
|
8987
9062
|
async updateMetaAd() {
|
|
8988
9063
|
throw new ApiError(501, "Meta Ads are not available in mock mode");
|
|
8989
9064
|
},
|
|
@@ -9014,6 +9089,9 @@ function createMockClient() {
|
|
|
9014
9089
|
async generateFunnelStepPage() {
|
|
9015
9090
|
throw new ApiError(501, "funnels are not available in mock mode");
|
|
9016
9091
|
},
|
|
9092
|
+
async addFunnelOfferPage() {
|
|
9093
|
+
throw new ApiError(501, "funnels are not available in mock mode");
|
|
9094
|
+
},
|
|
9017
9095
|
async listFunnelTemplates() {
|
|
9018
9096
|
return { templates: [] };
|
|
9019
9097
|
},
|
|
@@ -9092,6 +9170,66 @@ function createMockClient() {
|
|
|
9092
9170
|
async updateSiteLegalDocuments() {
|
|
9093
9171
|
throw new ApiError(501, "legal documents are not available in mock mode");
|
|
9094
9172
|
},
|
|
9173
|
+
async listLibrarySections() {
|
|
9174
|
+
return { sections: [], unassignedProducts: [] };
|
|
9175
|
+
},
|
|
9176
|
+
async replaceLibrarySections() {
|
|
9177
|
+
throw new ApiError(501, "library sections are not available in mock mode");
|
|
9178
|
+
},
|
|
9179
|
+
async createLibrarySection() {
|
|
9180
|
+
throw new ApiError(501, "library sections are not available in mock mode");
|
|
9181
|
+
},
|
|
9182
|
+
async reorderLibrarySections() {
|
|
9183
|
+
throw new ApiError(501, "library sections are not available in mock mode");
|
|
9184
|
+
},
|
|
9185
|
+
async moveLibraryProducts() {
|
|
9186
|
+
throw new ApiError(501, "library sections are not available in mock mode");
|
|
9187
|
+
},
|
|
9188
|
+
async renameLibrarySection() {
|
|
9189
|
+
throw new ApiError(501, "library sections are not available in mock mode");
|
|
9190
|
+
},
|
|
9191
|
+
async deleteLibrarySection() {
|
|
9192
|
+
throw new ApiError(501, "library sections are not available in mock mode");
|
|
9193
|
+
},
|
|
9194
|
+
async listStudioSocialDrafts() {
|
|
9195
|
+
throw new ApiError(501, "Studio drafts are not available in mock mode");
|
|
9196
|
+
},
|
|
9197
|
+
async generateStudioSocialDrafts() {
|
|
9198
|
+
throw new ApiError(501, "Studio drafts are not available in mock mode");
|
|
9199
|
+
},
|
|
9200
|
+
async sendStudioSocialDraftFeedback() {
|
|
9201
|
+
throw new ApiError(501, "Studio drafts are not available in mock mode");
|
|
9202
|
+
},
|
|
9203
|
+
async sendStudioSocialDraftToComposer() {
|
|
9204
|
+
throw new ApiError(501, "Studio drafts are not available in mock mode");
|
|
9205
|
+
},
|
|
9206
|
+
async getSocialWeekReport() {
|
|
9207
|
+
throw new ApiError(501, "social insights are not available in mock mode");
|
|
9208
|
+
},
|
|
9209
|
+
async getSocialPostPerformance() {
|
|
9210
|
+
throw new ApiError(501, "social insights are not available in mock mode");
|
|
9211
|
+
},
|
|
9212
|
+
async setYouTubeVideoVisibility() {
|
|
9213
|
+
throw new ApiError(501, "YouTube visibility is not available in mock mode");
|
|
9214
|
+
},
|
|
9215
|
+
async notifyCheckoutWaitlist() {
|
|
9216
|
+
throw new ApiError(501, "the offer waiting list is not available in mock mode");
|
|
9217
|
+
},
|
|
9218
|
+
async duplicateProduct() {
|
|
9219
|
+
throw new ApiError(501, "product duplicate is not available in mock mode");
|
|
9220
|
+
},
|
|
9221
|
+
async getSenderDomainWarmups() {
|
|
9222
|
+
return { warmups: [] };
|
|
9223
|
+
},
|
|
9224
|
+
async declareSenderDomainWarmed() {
|
|
9225
|
+
throw new ApiError(501, "domain warm-up is not available in mock mode");
|
|
9226
|
+
},
|
|
9227
|
+
async deleteClientEmailDrafts() {
|
|
9228
|
+
throw new ApiError(501, "client e-mails are not available in mock mode");
|
|
9229
|
+
},
|
|
9230
|
+
async resendFormSubmissionRoomLink() {
|
|
9231
|
+
throw new ApiError(501, "form submissions are not available in mock mode");
|
|
9232
|
+
},
|
|
9095
9233
|
async listClientDevices() {
|
|
9096
9234
|
throw new ApiError(501, "client devices are not available in mock mode");
|
|
9097
9235
|
},
|
|
@@ -9387,11 +9525,20 @@ function createHttpClient(config) {
|
|
|
9387
9525
|
"GET",
|
|
9388
9526
|
`/automations/${encodeURIComponent(id)}/runs${limit !== void 0 ? `?limit=${limit}` : ""}`
|
|
9389
9527
|
),
|
|
9390
|
-
setAutomationStatus: (id, status) => request("POST", `/automations/${encodeURIComponent(id)}/status`, {
|
|
9528
|
+
setAutomationStatus: (id, status, heldEvents) => request("POST", `/automations/${encodeURIComponent(id)}/status`, {
|
|
9529
|
+
status,
|
|
9530
|
+
...heldEvents ? { heldEvents } : {}
|
|
9531
|
+
}),
|
|
9391
9532
|
retryAutomationRun: (automationId, runId) => request(
|
|
9392
9533
|
"POST",
|
|
9393
9534
|
`/automations/${encodeURIComponent(automationId)}/runs/${encodeURIComponent(runId)}/retry`
|
|
9394
9535
|
),
|
|
9536
|
+
stopAutomationRun: (automationId, runId) => request(
|
|
9537
|
+
"POST",
|
|
9538
|
+
`/automations/${encodeURIComponent(automationId)}/runs/${encodeURIComponent(runId)}/stop`
|
|
9539
|
+
),
|
|
9540
|
+
listClientAutomationRuns: (clientId) => request("GET", `/clients/${encodeURIComponent(clientId)}/automation-runs`),
|
|
9541
|
+
stopClientAutomationRuns: (clientId) => request("POST", `/clients/${encodeURIComponent(clientId)}/automation-runs/stop`),
|
|
9395
9542
|
testRunAutomation: (automationId, input) => request("POST", `/automations/${encodeURIComponent(automationId)}/test-run`, input),
|
|
9396
9543
|
getCourseContent: (productId) => request("GET", `/products/${encodeURIComponent(productId)}/course`),
|
|
9397
9544
|
getCourseAnalytics: (productId, options) => {
|
|
@@ -9833,7 +9980,11 @@ function createHttpClient(config) {
|
|
|
9833
9980
|
createAudienceSegment: (input) => request("POST", "/segments", input),
|
|
9834
9981
|
updateAudienceSegment: (id, input) => request("PATCH", `/segments/${encodeURIComponent(id)}`, input),
|
|
9835
9982
|
deleteAudienceSegment: (id) => request("DELETE", `/segments/${encodeURIComponent(id)}`),
|
|
9836
|
-
previewAudienceSegment: (conditions) => request("POST", "/segments/preview", {
|
|
9983
|
+
previewAudienceSegment: (conditions, options = {}) => request("POST", "/segments/preview", {
|
|
9984
|
+
conditions,
|
|
9985
|
+
...options.requireSendable ? { requireSendable: true } : {},
|
|
9986
|
+
...options.requireSmsSendable ? { requireSmsSendable: true } : {}
|
|
9987
|
+
}),
|
|
9837
9988
|
importClients: (input) => request("POST", "/clients/import", input),
|
|
9838
9989
|
getClientMergePreview: (survivorClientId, duplicateClientId) => {
|
|
9839
9990
|
const params = new URLSearchParams();
|
|
@@ -9863,6 +10014,29 @@ function createHttpClient(config) {
|
|
|
9863
10014
|
// Manual (wire-transfer) order decisions.
|
|
9864
10015
|
approveManualOrder: (orderId, input) => request("POST", `/orders/${encodeURIComponent(orderId)}/approve-payment`, input),
|
|
9865
10016
|
rejectManualOrder: (orderId) => request("POST", `/orders/${encodeURIComponent(orderId)}/reject-payment`),
|
|
10017
|
+
markOrderAsTest: (orderId, isTest) => request("POST", `/orders/${encodeURIComponent(orderId)}/mark-test`, { isTest }),
|
|
10018
|
+
markOrderRefunded: (orderId, input) => request("POST", `/orders/${encodeURIComponent(orderId)}/mark-refunded`, input),
|
|
10019
|
+
listConsultationBookings: (options) => request("GET", `/consultations/bookings${toQuery(options)}`),
|
|
10020
|
+
listConsultationClientsWithoutBooking: (options) => request("GET", `/consultations/clients-without-booking${toQuery(options)}`),
|
|
10021
|
+
listCourseQuestions: (productId, options) => request(
|
|
10022
|
+
"GET",
|
|
10023
|
+
`/products/${encodeURIComponent(productId)}/course/questions${toQuery(options)}`
|
|
10024
|
+
),
|
|
10025
|
+
replyToCourseQuestion: (productId, questionId, content) => request(
|
|
10026
|
+
"POST",
|
|
10027
|
+
`/products/${encodeURIComponent(productId)}/course/questions/${encodeURIComponent(questionId)}/replies`,
|
|
10028
|
+
{ content }
|
|
10029
|
+
),
|
|
10030
|
+
setCourseQuestionStatus: (productId, entryId, status) => request(
|
|
10031
|
+
"PATCH",
|
|
10032
|
+
`/products/${encodeURIComponent(productId)}/course/questions/${encodeURIComponent(entryId)}`,
|
|
10033
|
+
{ status }
|
|
10034
|
+
),
|
|
10035
|
+
acceptCourseAnswer: (productId, questionId, replyId) => request(
|
|
10036
|
+
"POST",
|
|
10037
|
+
`/products/${encodeURIComponent(productId)}/course/questions/${encodeURIComponent(questionId)}/accept`,
|
|
10038
|
+
{ replyId }
|
|
10039
|
+
),
|
|
9866
10040
|
// Subscriptions: failed payments, manual renewals, ending soon.
|
|
9867
10041
|
listFailedSubscriptionPayments: () => request("GET", "/subscriptions/failed-payments"),
|
|
9868
10042
|
retrySubscriptionPayment: (invoiceId) => request("POST", `/subscriptions/failed-payments/${encodeURIComponent(invoiceId)}/retry`),
|
|
@@ -10089,6 +10263,23 @@ function createHttpClient(config) {
|
|
|
10089
10263
|
"GET",
|
|
10090
10264
|
`/v1/communities/${encodeURIComponent(communityId)}/events/${encodeURIComponent(eventId)}/attendees`
|
|
10091
10265
|
),
|
|
10266
|
+
listCommunityJoinRequests: (communityId) => communityRequest("GET", `/v1/communities/${encodeURIComponent(communityId)}/join-requests`),
|
|
10267
|
+
approveCommunityJoinRequests: (communityId, requestIds) => communityRequest(
|
|
10268
|
+
"POST",
|
|
10269
|
+
`/v1/communities/${encodeURIComponent(communityId)}/join-requests/approve`,
|
|
10270
|
+
{ requestIds }
|
|
10271
|
+
),
|
|
10272
|
+
declineCommunityJoinRequests: (communityId, requestIds) => communityRequest(
|
|
10273
|
+
"POST",
|
|
10274
|
+
`/v1/communities/${encodeURIComponent(communityId)}/join-requests/decline`,
|
|
10275
|
+
{ requestIds }
|
|
10276
|
+
),
|
|
10277
|
+
listCommunityDigestDrafts: (communityId) => communityRequest("GET", `/v1/communities/${encodeURIComponent(communityId)}/digest-drafts`),
|
|
10278
|
+
decideCommunityDigestDraft: (communityId, input) => communityRequest(
|
|
10279
|
+
"POST",
|
|
10280
|
+
`/v1/communities/${encodeURIComponent(communityId)}/digest-drafts/decide`,
|
|
10281
|
+
input
|
|
10282
|
+
),
|
|
10092
10283
|
getCommunityModerationQueue: (communityId) => communityRequest(
|
|
10093
10284
|
"GET",
|
|
10094
10285
|
`/v1/communities/${encodeURIComponent(communityId)}/moderation/reports`
|
|
@@ -10141,8 +10332,17 @@ function createHttpClient(config) {
|
|
|
10141
10332
|
"GET",
|
|
10142
10333
|
`/forms/${encodeURIComponent(id)}/submissions${limit !== void 0 ? `?limit=${limit}` : ""}`
|
|
10143
10334
|
),
|
|
10144
|
-
// Live rooms live on the Workspace API (api-v1),
|
|
10335
|
+
// Live rooms live on the Workspace API (api-v1): reads, the external webinar and its replay.
|
|
10145
10336
|
listLiveRooms: () => request("GET", "/live-rooms"),
|
|
10337
|
+
createExternalWebinar: (input) => request("POST", "/live-rooms", input),
|
|
10338
|
+
setLiveRoomReplay: (roomId, mediaId) => request("PUT", `/live-rooms/${encodeURIComponent(roomId)}/replay`, { mediaId }),
|
|
10339
|
+
setLiveRoomStudioScene: (roomId, input) => request("POST", `/live-rooms/${encodeURIComponent(roomId)}/studio/scene`, input),
|
|
10340
|
+
createLiveRoomStudioGuestInvite: (roomId, input) => request("POST", `/live-rooms/${encodeURIComponent(roomId)}/studio/guests`, input),
|
|
10341
|
+
listLiveRoomStudioGuests: (roomId) => request("GET", `/live-rooms/${encodeURIComponent(roomId)}/studio/guests`),
|
|
10342
|
+
revokeLiveRoomStudioGuestInvite: (roomId, guestId) => request(
|
|
10343
|
+
"DELETE",
|
|
10344
|
+
`/live-rooms/${encodeURIComponent(roomId)}/studio/guests/${encodeURIComponent(guestId)}`
|
|
10345
|
+
),
|
|
10146
10346
|
getLiveRoomAttendance: (roomId, session) => request(
|
|
10147
10347
|
"GET",
|
|
10148
10348
|
`/live-rooms/${encodeURIComponent(roomId)}/attendance${session !== void 0 ? `?session=${encodeURIComponent(session)}` : ""}`
|
|
@@ -10181,6 +10381,9 @@ function createHttpClient(config) {
|
|
|
10181
10381
|
updateMetaAdSet: (scope, adSetId, input) => request("PATCH", `${metaAdAccountPath(scope)}/ad-sets/${encodeURIComponent(adSetId)}`, input),
|
|
10182
10382
|
listMetaAds: (scope, query) => request("GET", `${metaAdAccountPath(scope)}/ads${toQuery(query)}`),
|
|
10183
10383
|
createMetaAd: (scope, input) => request("POST", `${metaAdAccountPath(scope)}/ads`, input),
|
|
10384
|
+
createMetaCampaign: (scope, input) => request("POST", `${metaAdAccountPath(scope)}/campaigns`, input),
|
|
10385
|
+
createMetaAdSet: (scope, input) => request("POST", `${metaAdAccountPath(scope)}/ad-sets`, input),
|
|
10386
|
+
createMetaAdFromScratch: (scope, input) => request("POST", `${metaAdAccountPath(scope)}/ads/from-scratch`, input),
|
|
10184
10387
|
updateMetaAd: (scope, adId, input) => request("PATCH", `${metaAdAccountPath(scope)}/ads/${encodeURIComponent(adId)}`, input),
|
|
10185
10388
|
getMetaAdCreative: (scope, adId) => request("GET", `${metaAdAccountPath(scope)}/ads/${encodeURIComponent(adId)}/creative`),
|
|
10186
10389
|
updateMetaAdCreative: (scope, adId, input) => request(
|
|
@@ -10196,6 +10399,7 @@ function createHttpClient(config) {
|
|
|
10196
10399
|
updateFunnel: (id, input) => request("PATCH", `/funnels/${encodeURIComponent(id)}`, input),
|
|
10197
10400
|
deleteFunnel: (id) => request("DELETE", `/funnels/${encodeURIComponent(id)}`),
|
|
10198
10401
|
generateFunnelStepPage: (id, input) => request("POST", `/funnels/${encodeURIComponent(id)}/generate-step-page`, input),
|
|
10402
|
+
addFunnelOfferPage: (id, input) => request("POST", `/funnels/${encodeURIComponent(id)}/offer-page`, input),
|
|
10199
10403
|
listFunnelTemplates: () => request("GET", "/funnel-templates"),
|
|
10200
10404
|
createFunnelFromTemplate: (input) => request("POST", "/funnels/from-template", input),
|
|
10201
10405
|
createFunnelFromPages: (input) => request("POST", "/funnels/from-pages", input),
|
|
@@ -10227,6 +10431,36 @@ function createHttpClient(config) {
|
|
|
10227
10431
|
language ? `/site/legal-documents?language=${encodeURIComponent(language)}` : "/site/legal-documents"
|
|
10228
10432
|
),
|
|
10229
10433
|
updateSiteLegalDocuments: (input) => request("PATCH", "/site/legal-documents", input),
|
|
10434
|
+
listLibrarySections: () => request("GET", "/site/library-sections"),
|
|
10435
|
+
replaceLibrarySections: (sections) => request("PUT", "/site/library-sections", { sections }),
|
|
10436
|
+
createLibrarySection: (input) => request("POST", "/site/library-sections", input),
|
|
10437
|
+
reorderLibrarySections: (sectionIds) => request("POST", "/site/library-sections/reorder", { sectionIds }),
|
|
10438
|
+
moveLibraryProducts: (productIds, sectionId) => request("POST", "/site/library-sections/move-products", { productIds, sectionId }),
|
|
10439
|
+
renameLibrarySection: (sectionId, name) => request("PATCH", `/site/library-sections/${encodeURIComponent(sectionId)}`, { name }),
|
|
10440
|
+
deleteLibrarySection: (sectionId) => request("DELETE", `/site/library-sections/${encodeURIComponent(sectionId)}`),
|
|
10441
|
+
// Studio weekly drafts + post insights (`social-token` routes on api-v1).
|
|
10442
|
+
listStudioSocialDrafts: () => request("GET", "/studio/social-drafts"),
|
|
10443
|
+
generateStudioSocialDrafts: (input) => request("POST", "/studio/social-drafts/generate", input),
|
|
10444
|
+
sendStudioSocialDraftFeedback: (draftId, reason) => request("POST", `/studio/social-drafts/${encodeURIComponent(draftId)}/feedback`, {
|
|
10445
|
+
reason
|
|
10446
|
+
}),
|
|
10447
|
+
sendStudioSocialDraftToComposer: (draftId, input) => request("POST", `/studio/social-drafts/${encodeURIComponent(draftId)}/to-composer`, input),
|
|
10448
|
+
getSocialWeekReport: () => request("GET", "/social/reports/weekly"),
|
|
10449
|
+
getSocialPostPerformance: (postId) => request("GET", `/social/posts/${encodeURIComponent(postId)}/performance`),
|
|
10450
|
+
setYouTubeVideoVisibility: (postId, input) => request("POST", `/social/posts/${encodeURIComponent(postId)}/youtube-visibility`, input),
|
|
10451
|
+
notifyCheckoutWaitlist: (checkoutId, dryRun) => request("POST", `/checkouts/${encodeURIComponent(checkoutId)}/notify-waitlist`, { dryRun }),
|
|
10452
|
+
duplicateProduct: (productId, name) => request(
|
|
10453
|
+
"POST",
|
|
10454
|
+
`/products/${encodeURIComponent(productId)}/duplicate`,
|
|
10455
|
+
name !== void 0 ? { name } : {}
|
|
10456
|
+
),
|
|
10457
|
+
getSenderDomainWarmups: () => request("GET", "/sender-domain/warmups"),
|
|
10458
|
+
declareSenderDomainWarmed: (domain) => request("POST", `/sender-domain/${encodeURIComponent(domain)}/warmup-declarations`),
|
|
10459
|
+
deleteClientEmailDrafts: (input) => request("POST", "/client-emails/delete-drafts", input),
|
|
10460
|
+
resendFormSubmissionRoomLink: (formId, submissionId) => request(
|
|
10461
|
+
"POST",
|
|
10462
|
+
`/forms/${encodeURIComponent(formId)}/submissions/${encodeURIComponent(submissionId)}/room-link`
|
|
10463
|
+
),
|
|
10230
10464
|
// Customer access devices (anti-sharing device slots) — Workspace API.
|
|
10231
10465
|
listClientDevices: (clientId) => request("GET", `/clients/${encodeURIComponent(clientId)}/devices`),
|
|
10232
10466
|
revokeClientDevice: (clientId, deviceId) => request(
|
|
@@ -10949,7 +11183,7 @@ function registerBlogCommands(program2) {
|
|
|
10949
11183
|
"Schedules a DRAFT (or reschedules a scheduled post) to be published at `scheduledAt`: status `scheduled`, not public anywhere (list, feed, sitemap, its URL) until then, then published exactly like publish-blog-post. Needs a non-empty body (400); a published post answers 409 (unpublish it first). `scheduledAt: null` unschedules (back to draft); publishing it now is publish-blog-post. Returns the full post with `scheduledAt`."
|
|
10950
11184
|
).argument("<id>", "Blog post id (`post_\u2026`) from list-blog-posts.").requiredOption(
|
|
10951
11185
|
"--at <value>",
|
|
10952
|
-
|
|
11186
|
+
'When the post goes live: Polish time without an offset ("2026-10-26T08:00", read in Europe/Warsaw with the offset of that date, also across a clock change) or an ISO 8601 date-time with an offset / Z (2026-10-26T08:00:00+01:00). At least a minute and at most a year ahead. null = unschedule (back to draft).'
|
|
10953
11187
|
).action(async (id, options, command) => {
|
|
10954
11188
|
const flags = getGlobalFlags(command);
|
|
10955
11189
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -11338,6 +11572,289 @@ function registerPopupsCommands(program2) {
|
|
|
11338
11572
|
});
|
|
11339
11573
|
}
|
|
11340
11574
|
|
|
11575
|
+
// src/commands/_generated/products.ts
|
|
11576
|
+
var listProductBuyersNotificationsColumns = [
|
|
11577
|
+
"id",
|
|
11578
|
+
"title",
|
|
11579
|
+
"status",
|
|
11580
|
+
"createdAt",
|
|
11581
|
+
"confirmedRecipientCount"
|
|
11582
|
+
];
|
|
11583
|
+
function registerProductsCommands(program2) {
|
|
11584
|
+
const products2 = program2.commands.find((command) => command.name() === "products") ?? program2.command("products");
|
|
11585
|
+
if (!products2.description()) {
|
|
11586
|
+
products2.description(
|
|
11587
|
+
"A product's buyer notifications (\u201EPowiadom kupuj\u0105cych\u201D): list, preview, send"
|
|
11588
|
+
);
|
|
11589
|
+
}
|
|
11590
|
+
const productsNotifications = products2.command("notifications");
|
|
11591
|
+
productsNotifications.description(
|
|
11592
|
+
"A product's buyer notifications (\u201EPowiadom kupuj\u0105cych\u201D): list, preview, send"
|
|
11593
|
+
);
|
|
11594
|
+
productsNotifications.command("preview").summary("Preview a notification to a product's buyers").description(
|
|
11595
|
+
"Step 1 of \u201EPowiadom kupuj\u0105cych\u201D: how many people a push + portal bell (+ optional e-mail) to everyone with access to ONE product would reach right now, and whether a limit stops it (one send per product per hour, a daily cap per workspace; only a published product). Sends nothing. Show the count to the human and get an explicit yes before notify-product-buyers. Also checks the optional link (own platform only)."
|
|
11596
|
+
).argument("<productId>", "Product id (`prod_\u2026`) from list-products.").option(
|
|
11597
|
+
"--link <value>",
|
|
11598
|
+
"Optional link the notification opens: a page of the creator's OWN platform, as a path (`/app/courses/{slug}?lesson={lessonId}`) or a URL on the platform domain. Any other host is refused (400 invalid_link). Empty = the product page."
|
|
11599
|
+
).option(
|
|
11600
|
+
"--send-email",
|
|
11601
|
+
"The e-mail would be ticked: also check the monthly e-mail allowance (`emailSkipped`)."
|
|
11602
|
+
).option("--no-send-email", "Send `sendEmail: false` explicitly.").action(
|
|
11603
|
+
async (productId, options, command) => {
|
|
11604
|
+
const flags = getGlobalFlags(command);
|
|
11605
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11606
|
+
const params = {
|
|
11607
|
+
productId
|
|
11608
|
+
};
|
|
11609
|
+
const payload = {
|
|
11610
|
+
link: options.link,
|
|
11611
|
+
sendEmail: options.sendEmail
|
|
11612
|
+
};
|
|
11613
|
+
const result = await client.call(
|
|
11614
|
+
"POST",
|
|
11615
|
+
"/products/:productId/buyers-notifications/preview",
|
|
11616
|
+
params,
|
|
11617
|
+
void 0,
|
|
11618
|
+
payload
|
|
11619
|
+
);
|
|
11620
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11621
|
+
}
|
|
11622
|
+
);
|
|
11623
|
+
productsNotifications.command("send").summary("Notify a product's buyers").description(
|
|
11624
|
+
"Step 2 of \u201EPowiadom kupuj\u0105cych\u201D: sends a short message (title + up to 200 characters + an optional link on the own platform) to everyone with access to ONE product right now: a push in the mobile app, a bell item on the portal and, with `sendEmail`, an e-mail. HIGHEST-STAKES ACTION: call preview-product-buyers-notification first, tell the human the recipient count and send only after an explicit yes, passing that count as `confirmedRecipientCount`. Limits: one send per product per hour, a daily cap per workspace (429). The delivery runs in the background: list-product-buyers-notifications shows the counts a little later. It cannot be recalled."
|
|
11625
|
+
).argument("<productId>", "Product id (`prod_\u2026`) from list-products.").requiredOption(
|
|
11626
|
+
"--title <value>",
|
|
11627
|
+
"The notification title (push title, bell line, e-mail subject). One line."
|
|
11628
|
+
).requiredOption("--body <value>", "The message, up to 200 characters.").option(
|
|
11629
|
+
"--link <value>",
|
|
11630
|
+
"Optional link the notification opens: a page of the creator's OWN platform, as a path (`/app/courses/{slug}?lesson={lessonId}`) or a URL on the platform domain. Any other host is refused (400 invalid_link). Empty = the product page."
|
|
11631
|
+
).option(
|
|
11632
|
+
"--send-email",
|
|
11633
|
+
"Also send it as an e-mail (clients who unsubscribed are skipped). Default: no.",
|
|
11634
|
+
false
|
|
11635
|
+
).requiredOption(
|
|
11636
|
+
"--confirmed-recipient-count <n>",
|
|
11637
|
+
"The `recipientCount` from preview-product-buyers-notification that the human confirmed. More people with access now = refused (409 recipient_count_changed): preview again.",
|
|
11638
|
+
parseNumberOption("--confirmed-recipient-count", NumberFormat.Integer)
|
|
11639
|
+
).option(
|
|
11640
|
+
"--send-id <value>",
|
|
11641
|
+
"Idempotency key (8-64 of A-Z a-z 0-9 _ -): a retried call with the same id returns the first send instead of sending again. Generated when omitted."
|
|
11642
|
+
).option(
|
|
11643
|
+
"--confirm",
|
|
11644
|
+
"required \u2014 acknowledge that you want to notify every buyer of this product now (it cannot be recalled)"
|
|
11645
|
+
).action(async (productId, options, command) => {
|
|
11646
|
+
const flags = getGlobalFlags(command);
|
|
11647
|
+
requireConfirm(
|
|
11648
|
+
options.confirm,
|
|
11649
|
+
"notify every buyer of this product now (it cannot be recalled)"
|
|
11650
|
+
);
|
|
11651
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11652
|
+
const params = {
|
|
11653
|
+
productId
|
|
11654
|
+
};
|
|
11655
|
+
const payload = {
|
|
11656
|
+
title: options.title,
|
|
11657
|
+
body: options.body,
|
|
11658
|
+
link: options.link,
|
|
11659
|
+
sendEmail: options.sendEmail,
|
|
11660
|
+
confirmedRecipientCount: options.confirmedRecipientCount,
|
|
11661
|
+
sendId: options.sendId
|
|
11662
|
+
};
|
|
11663
|
+
const result = await client.call(
|
|
11664
|
+
"POST",
|
|
11665
|
+
"/products/:productId/buyers-notifications",
|
|
11666
|
+
params,
|
|
11667
|
+
void 0,
|
|
11668
|
+
payload
|
|
11669
|
+
);
|
|
11670
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11671
|
+
});
|
|
11672
|
+
productsNotifications.command("list").summary("List a product's buyer notifications").description(
|
|
11673
|
+
"The send log of \u201EPowiadom kupuj\u0105cych\u201D for ONE product, newest first: date, text, link, status and what was delivered (recipients, bells, pushes, e-mails, opted out)."
|
|
11674
|
+
).argument("<productId>", "Product id (`prod_\u2026`) from list-products.").option(
|
|
11675
|
+
"--limit <n>",
|
|
11676
|
+
"Newest first; default 20, max 50.",
|
|
11677
|
+
parseNumberOption("--limit", NumberFormat.Integer)
|
|
11678
|
+
).action(
|
|
11679
|
+
async (productId, options, command) => {
|
|
11680
|
+
const flags = getGlobalFlags(command);
|
|
11681
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11682
|
+
const params = {
|
|
11683
|
+
productId
|
|
11684
|
+
};
|
|
11685
|
+
const payload = {
|
|
11686
|
+
limit: options.limit
|
|
11687
|
+
};
|
|
11688
|
+
const result = await client.call(
|
|
11689
|
+
"GET",
|
|
11690
|
+
"/products/:productId/buyers-notifications",
|
|
11691
|
+
params,
|
|
11692
|
+
payload,
|
|
11693
|
+
void 0
|
|
11694
|
+
);
|
|
11695
|
+
const rows = tableRowsOf(result, "items");
|
|
11696
|
+
emit(flags, result ?? {}, () => printTable(rows, listProductBuyersNotificationsColumns));
|
|
11697
|
+
}
|
|
11698
|
+
);
|
|
11699
|
+
const productsStock = products2.command("stock", { hidden: true });
|
|
11700
|
+
productsStock.description("A physical product's stock per variant");
|
|
11701
|
+
productsStock.command("get", { hidden: true }).summary("Read a physical product's stock").description(
|
|
11702
|
+
"\u201EWarianty i stan magazynowy\u201D of ONE physical product: per variant (a price of the product's default offer) its stock (the total that may ever sell; null = no limit), the units sold, the units reserved by unpaid orders, what is still available and whether it is personalised. 400 for a digital product, 409 when the product has no offer yet. Read-only."
|
|
11703
|
+
).argument("<productId>", "The physical product id (`prod_\u2026` from list-products).").action(async (productId, _options, command) => {
|
|
11704
|
+
const flags = getGlobalFlags(command);
|
|
11705
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11706
|
+
const params = {
|
|
11707
|
+
productId
|
|
11708
|
+
};
|
|
11709
|
+
const result = await client.call(
|
|
11710
|
+
"GET",
|
|
11711
|
+
"/products/:productId/stock",
|
|
11712
|
+
params,
|
|
11713
|
+
void 0,
|
|
11714
|
+
void 0
|
|
11715
|
+
);
|
|
11716
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11717
|
+
});
|
|
11718
|
+
productsStock.command("set", { hidden: true }).summary("Set a physical product's stock").description(
|
|
11719
|
+
"Sets the stock of a physical product's variants, like saving \u201EWarianty i stan magazynowy\u201D: each listed price of the product's default offer is saved with only its stock (`quantityLimit`) changed; label, amount and every other setting stay. `stock` is the TOTAL that may ever sell (sold and reserved units count against it); null = no limit; 0 is refused (Zanfia reads 0 as no limit). Returns the stock after the save. Reversible: set it again."
|
|
11720
|
+
).argument("<productId>", "The physical product id (`prod_\u2026` from list-products).").requiredOption(
|
|
11721
|
+
"--variants <json|file>",
|
|
11722
|
+
"Only the variants listed change; the rest keep their stock.",
|
|
11723
|
+
parseJsonArrayFileOrInline("--variants")
|
|
11724
|
+
).option("--confirm", "required \u2014 acknowledge that you want to change this product's stock").action(async (productId, options, command) => {
|
|
11725
|
+
const flags = getGlobalFlags(command);
|
|
11726
|
+
requireConfirm(options.confirm, "change this product's stock");
|
|
11727
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11728
|
+
const params = {
|
|
11729
|
+
productId
|
|
11730
|
+
};
|
|
11731
|
+
const payload = {
|
|
11732
|
+
variants: options.variants
|
|
11733
|
+
};
|
|
11734
|
+
const result = await client.call(
|
|
11735
|
+
"PATCH",
|
|
11736
|
+
"/products/:productId/stock",
|
|
11737
|
+
params,
|
|
11738
|
+
void 0,
|
|
11739
|
+
payload
|
|
11740
|
+
);
|
|
11741
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11742
|
+
});
|
|
11743
|
+
}
|
|
11744
|
+
|
|
11745
|
+
// src/commands/_generated/subscriptions.ts
|
|
11746
|
+
var listInstallmentArrearsColumns = [
|
|
11747
|
+
"productName",
|
|
11748
|
+
"clientEmail",
|
|
11749
|
+
"subscriptionStatus",
|
|
11750
|
+
"installmentsPaid",
|
|
11751
|
+
"installmentsTotal",
|
|
11752
|
+
"overdueAmountMinor",
|
|
11753
|
+
"currency"
|
|
11754
|
+
];
|
|
11755
|
+
function registerSubscriptionsCommands(program2) {
|
|
11756
|
+
const subscriptions = program2.commands.find((command) => command.name() === "subscriptions") ?? program2.command("subscriptions");
|
|
11757
|
+
if (!subscriptions.description()) {
|
|
11758
|
+
subscriptions.description(
|
|
11759
|
+
"Instalment arrears (\u201EZaleg\u0142o\u015Bci w ratach\u201D) and the non-payment setting"
|
|
11760
|
+
);
|
|
11761
|
+
}
|
|
11762
|
+
const subscriptionsArrears = subscriptions.command("arrears");
|
|
11763
|
+
subscriptionsArrears.description(
|
|
11764
|
+
"Instalment arrears (\u201EZaleg\u0142o\u015Bci w ratach\u201D) and the non-payment setting"
|
|
11765
|
+
);
|
|
11766
|
+
subscriptionsArrears.command("setting").summary("What happens when a buyer stops paying instalments").description(
|
|
11767
|
+
"The workspace's choice for an instalment plan Stripe ends before its last instalment (a non-payment): `revoke` ends the access like a plain subscription end, `keep` leaves it and lists the buyer on \u201EZaleg\u0142o\u015Bci\u201D. Default `revoke`. It applies to plans bought since the setting shipped; older plans always keep their access (they show on \u201EZaleg\u0142o\u015Bci\u201D). A plan paid in full is never affected."
|
|
11768
|
+
).action(async (_options, command) => {
|
|
11769
|
+
const flags = getGlobalFlags(command);
|
|
11770
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11771
|
+
const params = {};
|
|
11772
|
+
const result = await client.call(
|
|
11773
|
+
"GET",
|
|
11774
|
+
"/settings/installment-non-payment",
|
|
11775
|
+
params,
|
|
11776
|
+
void 0,
|
|
11777
|
+
void 0
|
|
11778
|
+
);
|
|
11779
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11780
|
+
});
|
|
11781
|
+
subscriptionsArrears.command("set-setting").summary("Change what happens when a buyer stops paying instalments").description(
|
|
11782
|
+
"Saves the workspace's choice: `revoke` (end the access like a plain subscription end) or `keep` (leave it, list the buyer on \u201EZaleg\u0142o\u015Bci\u201D). It applies the next time Stripe ends an unfinished plan bought since the setting shipped; no access changes now, and plans from before it always keep their access. Answers the setting as saved."
|
|
11783
|
+
).addOption(
|
|
11784
|
+
new Option(
|
|
11785
|
+
"--action <revoke|keep>",
|
|
11786
|
+
"`revoke` = end the access like a plain subscription end (same e-mails, community / Discord removal); `keep` = leave the access and list the buyer on \u201EZaleg\u0142o\u015Bci\u201D."
|
|
11787
|
+
).choices(["revoke", "keep"]).makeOptionMandatory()
|
|
11788
|
+
).option(
|
|
11789
|
+
"--confirm",
|
|
11790
|
+
"required \u2014 acknowledge that you want to change what happens to every buyer who stops paying instalments"
|
|
11791
|
+
).action(async (options, command) => {
|
|
11792
|
+
const flags = getGlobalFlags(command);
|
|
11793
|
+
requireConfirm(
|
|
11794
|
+
options.confirm,
|
|
11795
|
+
"change what happens to every buyer who stops paying instalments"
|
|
11796
|
+
);
|
|
11797
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11798
|
+
const params = {};
|
|
11799
|
+
const payload = {
|
|
11800
|
+
action: options.action
|
|
11801
|
+
};
|
|
11802
|
+
const result = await client.call(
|
|
11803
|
+
"PATCH",
|
|
11804
|
+
"/settings/installment-non-payment",
|
|
11805
|
+
params,
|
|
11806
|
+
void 0,
|
|
11807
|
+
payload
|
|
11808
|
+
);
|
|
11809
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11810
|
+
});
|
|
11811
|
+
subscriptionsArrears.command("list").summary("List buyers behind on instalments (\u201EZaleg\u0142o\u015Bci\u201D)").description(
|
|
11812
|
+
"Every buyer whose unfinished instalment plan stopped being paid (Stripe past_due / unpaid / canceled), checked live in Stripe first; a buyer who paid outside Stripe (Stripe \u201Emark as paid\u201D, an approved transfer order, a recorded offline payment) is not listed. Per row: what is overdue now and what was never billed (minor units), since when, the instalments paid, whether the access was kept by the setting or because the plan predates it, and whether to check a Fakturownia proforma. Longest overdue first. Read-only."
|
|
11813
|
+
).option(
|
|
11814
|
+
"--product-id <value>",
|
|
11815
|
+
"One product (`prod_\u2026` from list-products); omitted = every product."
|
|
11816
|
+
).option(
|
|
11817
|
+
"--include-kept",
|
|
11818
|
+
"Also list the buyers the creator already chose \u201EZostaw\u201D for. Default false."
|
|
11819
|
+
).option("--no-include-kept", "Send `includeKept: false` explicitly.").action(async (options, command) => {
|
|
11820
|
+
const flags = getGlobalFlags(command);
|
|
11821
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11822
|
+
const params = {};
|
|
11823
|
+
const payload = {
|
|
11824
|
+
productId: options.productId,
|
|
11825
|
+
includeKept: options.includeKept
|
|
11826
|
+
};
|
|
11827
|
+
const result = await client.call("GET", "/installment-arrears", params, payload, void 0);
|
|
11828
|
+
const rows = tableRowsOf(result, "items");
|
|
11829
|
+
emit(flags, result ?? {}, () => printTable(rows, listInstallmentArrearsColumns));
|
|
11830
|
+
});
|
|
11831
|
+
subscriptionsArrears.command("keep").summary("Keep a behind-on-instalments buyer's access (\u201EZostaw\u201D)").description(
|
|
11832
|
+
"\u201EZostaw\u201D on \u201EZaleg\u0142o\u015Bci\u201D: the creator keeps this buyer's access. Only a mark is saved (who, when): the access, Stripe and the buyer are not touched, and the row leaves the default list (`includeKept` shows it). 404 when the buyer is not on the list."
|
|
11833
|
+
).argument("<productId>", "The row's `productId`.").argument("<clientEmail>", "The row's `clientEmail`.").option(
|
|
11834
|
+
"--confirm",
|
|
11835
|
+
"required \u2014 acknowledge that you want to keep this buyer's access despite the unpaid instalments"
|
|
11836
|
+
).action(
|
|
11837
|
+
async (productId, clientEmail, options, command) => {
|
|
11838
|
+
const flags = getGlobalFlags(command);
|
|
11839
|
+
requireConfirm(options.confirm, "keep this buyer's access despite the unpaid instalments");
|
|
11840
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11841
|
+
const params = {};
|
|
11842
|
+
const payload = {
|
|
11843
|
+
productId,
|
|
11844
|
+
clientEmail
|
|
11845
|
+
};
|
|
11846
|
+
const result = await client.call(
|
|
11847
|
+
"POST",
|
|
11848
|
+
"/installment-arrears/keep",
|
|
11849
|
+
params,
|
|
11850
|
+
void 0,
|
|
11851
|
+
payload
|
|
11852
|
+
);
|
|
11853
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11854
|
+
}
|
|
11855
|
+
);
|
|
11856
|
+
}
|
|
11857
|
+
|
|
11341
11858
|
// src/commands/_generated/studio.ts
|
|
11342
11859
|
var suggestStudioVideoClipsColumns = ["startSeconds", "endSeconds", "hook"];
|
|
11343
11860
|
function registerStudioCommands(program2) {
|
|
@@ -11596,35 +12113,195 @@ function registerSiteCommands(program2) {
|
|
|
11596
12113
|
kind,
|
|
11597
12114
|
pageId: options.page
|
|
11598
12115
|
};
|
|
11599
|
-
const result = await client.call("PUT", "/site/layout/template", params, void 0, payload);
|
|
12116
|
+
const result = await client.call("PUT", "/site/layout/template", params, void 0, payload);
|
|
12117
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12118
|
+
});
|
|
12119
|
+
const siteMenu = site.command("menu");
|
|
12120
|
+
siteMenu.description("The site's one shared menu (Your site \u2192 Look and menu): up to 8 entries");
|
|
12121
|
+
siteMenu.command("get").summary("Get the shared site menu").description(
|
|
12122
|
+
"The site's shared menu (dashboard: Your site \u2192 Look and menu \u2192 \u201EMenu witryny\u201D) in order: `label`, `style` (`link` / `button`), `target` (`page` + `pageId`, `blog`, `store`, `product` + `productId`, `login`, `url` + `url`) and `href`, the address it leads to today (null = nowhere now; pages leave it out). It fills every page that marks its menu with `data-zanfia-nav` and the blog in its page frame. Empty = every page keeps its own links. Edit it with set-site-menu (the whole list)."
|
|
12123
|
+
).action(async (_options, command) => {
|
|
12124
|
+
const flags = getGlobalFlags(command);
|
|
12125
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12126
|
+
const params = {};
|
|
12127
|
+
const result = await client.call("GET", "/site/menu", params, void 0, void 0);
|
|
12128
|
+
const rows = tableRowsOf(result, "menu");
|
|
12129
|
+
emit(flags, result ?? {}, () => printTable(rows, getSiteMenuColumns));
|
|
12130
|
+
});
|
|
12131
|
+
siteMenu.command("set").summary("Set the shared site menu").description(
|
|
12132
|
+
'Replaces the site\'s shared menu with the entries sent, in order (up to 8; an entry left out is removed, an empty list turns the menu off). Live at once on every page that marks its menu with `data-zanfia-nav` and on the blog in its page frame. To add one entry (e.g. the blog: `{ label: "Blog", target: { kind: "blog" }, style: "link" }`) read get-site-menu first and send the whole list. Checked by the dashboard rules: 400 `site-menu-refused` with `issue` (`too-many-entries`, `empty-label`, `label-too-long`, `invalid-url`, `invalid-anchor`, `page-not-found`, `page-not-published`, `product-not-found`) and the entry `index`; nothing saved. Returns the whole layout.'
|
|
12133
|
+
).requiredOption(
|
|
12134
|
+
"--menu <json|file>",
|
|
12135
|
+
"EVERY entry of the new menu, in order (up to 8), as a JSON array; an empty list turns the shared menu off.",
|
|
12136
|
+
parseJsonArrayFileOrInline("--menu")
|
|
12137
|
+
).action(async (options, command) => {
|
|
12138
|
+
const flags = getGlobalFlags(command);
|
|
12139
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12140
|
+
const params = {};
|
|
12141
|
+
const payload = {
|
|
12142
|
+
menu: options.menu
|
|
12143
|
+
};
|
|
12144
|
+
const result = await client.call("PUT", "/site/menu", params, void 0, payload);
|
|
12145
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12146
|
+
});
|
|
12147
|
+
}
|
|
12148
|
+
|
|
12149
|
+
// src/commands/_generated/chats.ts
|
|
12150
|
+
function registerChatsCommands(program2) {
|
|
12151
|
+
const chats = program2.command("chats", { hidden: true });
|
|
12152
|
+
chats.description("Czaty: the assistant and hired agents");
|
|
12153
|
+
chats.command("list", { hidden: true }).summary("List chats with unread counts").description(
|
|
12154
|
+
"The caller's chats (Czaty): the creator assistant's main conversation with its last line and unread count (messages it wrote first, e.g. the morning summary), the older assistant topics, and the hired desk agents (Copywriter, Analyst, Marketer, Finance) with their last line and unread reports. Agents only where the agents early access is on. Behind the early_access_chat_home launch flag (403 feature_not_available otherwise)."
|
|
12155
|
+
).action(async (_options, command) => {
|
|
12156
|
+
const flags = getGlobalFlags(command);
|
|
12157
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12158
|
+
const params = {};
|
|
12159
|
+
const result = await client.call("GET", "/chat-home", params, void 0, void 0);
|
|
12160
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12161
|
+
});
|
|
12162
|
+
}
|
|
12163
|
+
|
|
12164
|
+
// src/commands/_generated/orders.ts
|
|
12165
|
+
function registerOrdersCommands(program2) {
|
|
12166
|
+
const orders2 = program2.commands.find((command) => command.name() === "orders") ?? program2.command("orders", { hidden: true });
|
|
12167
|
+
if (!orders2.description()) {
|
|
12168
|
+
orders2.description("Physical orders to ship (\u201EDo wys\u0142ania\u201D): list, mark shipped / delivered");
|
|
12169
|
+
}
|
|
12170
|
+
const ordersFulfilment = orders2.command("fulfilment", { hidden: true });
|
|
12171
|
+
ordersFulfilment.description(
|
|
12172
|
+
"Physical orders to ship (\u201EDo wys\u0142ania\u201D): list, mark shipped / delivered"
|
|
12173
|
+
);
|
|
12174
|
+
ordersFulfilment.command("list", { hidden: true }).summary("List physical orders to ship (\u201EDo wys\u0142ania\u201D)").description(
|
|
12175
|
+
"The \u201EDo wys\u0142ania\u201D queue of paid physical orders by shipping status, newest first, with what packing needs per row: the variant, quantity, personalisation, order bumps, the buyer, the delivery (InPost locker code or courier address, phone, parcel size), the shipping status with carrier and tracking number, the charged total, and the \u201ESprzedane ponad stan\u201D flag. `unfulfilledCount` is the badge. Test purchases are never listed. Read-only."
|
|
12176
|
+
).addOption(
|
|
12177
|
+
new Option(
|
|
12178
|
+
"--status <unfulfilled|shipped|delivered|returned|cancelled>",
|
|
12179
|
+
"The tab: `unfulfilled` (\u201EDo wys\u0142ania\u201D, the default), `shipped` (\u201EWys\u0142ane\u201D), `delivered` (\u201EDor\u0119czone\u201D); also `returned` and `cancelled`."
|
|
12180
|
+
).choices(["unfulfilled", "shipped", "delivered", "returned", "cancelled"])
|
|
12181
|
+
).option("--cursor <value>", "`nextCursor` of the previous page; omit for the first page.").option(
|
|
12182
|
+
"--limit <n>",
|
|
12183
|
+
"Rows per page, newest first (default 50, max 200).",
|
|
12184
|
+
parseNumberOption("--limit", NumberFormat.Integer)
|
|
12185
|
+
).action(async (options, command) => {
|
|
12186
|
+
const flags = getGlobalFlags(command);
|
|
12187
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12188
|
+
const params = {};
|
|
12189
|
+
const payload = {
|
|
12190
|
+
status: options.status,
|
|
12191
|
+
cursor: options.cursor,
|
|
12192
|
+
limit: options.limit
|
|
12193
|
+
};
|
|
12194
|
+
const result = await client.call("GET", "/orders/to-ship", params, payload, void 0);
|
|
12195
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12196
|
+
});
|
|
12197
|
+
ordersFulfilment.command("ship", { hidden: true }).summary("Mark a physical order as shipped").description(
|
|
12198
|
+
"\u201EOznacz jako wys\u0142ane\u201D for ONE paid physical order: records the carrier, the tracking number and link and the shipping date, and by default e-mails the buyer \u201ETwoja paczka jest w drodze\u201D with the tracking link (once per tracking number; `notifyBuyer: false` records it without the e-mail). The first marking fires the order-shipped automations. Marking a shipped order again corrects the carrier or number and keeps the first shipping date. 409 for an unpaid, delivered, returned or cancelled order, 400 for a digital one. The e-mail cannot be recalled: confirm the order and the number first."
|
|
12199
|
+
).argument("<orderId>", "The order id (`orderId` from list-orders-to-ship or list-orders).").addOption(
|
|
12200
|
+
new Option(
|
|
12201
|
+
"--carrier <inpost|poczta_polska|dpd|dhl|gls|orlen_paczka|other>",
|
|
12202
|
+
"inpost | poczta_polska | dpd | dhl | gls | orlen_paczka | other. InPost gets a tracking link built from the number; other carriers only with `trackingUrl`."
|
|
12203
|
+
).choices(["inpost", "poczta_polska", "dpd", "dhl", "gls", "orlen_paczka", "other"]).makeOptionMandatory()
|
|
12204
|
+
).option(
|
|
12205
|
+
"--tracking-number <value>",
|
|
12206
|
+
"The parcel number. Omit for a shipment without one (e.g. a letter)."
|
|
12207
|
+
).option(
|
|
12208
|
+
"--tracking-url <value>",
|
|
12209
|
+
"A tracking link (http/https). Omit for InPost: it is built from the number. Other carriers without one show the number only."
|
|
12210
|
+
).option(
|
|
12211
|
+
"--notify-buyer",
|
|
12212
|
+
"E-mail the buyer \u201ETwoja paczka jest w drodze\u201D with the tracking link (default true). Sent once per tracking number: re-marking with the same number sends nothing, a corrected number e-mails again."
|
|
12213
|
+
).option("--no-notify-buyer", "Send `notifyBuyer: false` explicitly.").option("--confirm", "required \u2014 acknowledge that you want to mark this order as shipped").action(async (orderId, options, command) => {
|
|
12214
|
+
const flags = getGlobalFlags(command);
|
|
12215
|
+
requireConfirm(options.confirm, "mark this order as shipped");
|
|
12216
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12217
|
+
const params = {
|
|
12218
|
+
orderId
|
|
12219
|
+
};
|
|
12220
|
+
const payload = {
|
|
12221
|
+
carrier: options.carrier,
|
|
12222
|
+
trackingNumber: options.trackingNumber,
|
|
12223
|
+
trackingUrl: options.trackingUrl,
|
|
12224
|
+
notifyBuyer: options.notifyBuyer
|
|
12225
|
+
};
|
|
12226
|
+
const result = await client.call(
|
|
12227
|
+
"POST",
|
|
12228
|
+
"/orders/:orderId/mark-shipped",
|
|
12229
|
+
params,
|
|
12230
|
+
void 0,
|
|
12231
|
+
payload
|
|
12232
|
+
);
|
|
12233
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12234
|
+
});
|
|
12235
|
+
ordersFulfilment.command("deliver", { hidden: true }).summary("Mark a physical order as delivered").description(
|
|
12236
|
+
"\u201EOznacz jako dor\u0119czone\u201D (also \u201Eodebrane\u201D for a personal pickup) for ONE paid physical order, from shipped or straight from unfulfilled (a hand delivery). Records the delivery date: the 14-day withdrawal period runs from it. No e-mail goes out. An already delivered order is returned unchanged. 409 for an unpaid, returned or cancelled order, 400 for a digital one."
|
|
12237
|
+
).argument("<orderId>", "The order id (`orderId` from list-orders-to-ship or list-orders).").option("--confirm", "required \u2014 acknowledge that you want to mark this order as delivered").action(async (orderId, options, command) => {
|
|
12238
|
+
const flags = getGlobalFlags(command);
|
|
12239
|
+
requireConfirm(options.confirm, "mark this order as delivered");
|
|
12240
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12241
|
+
const params = {
|
|
12242
|
+
orderId
|
|
12243
|
+
};
|
|
12244
|
+
const result = await client.call(
|
|
12245
|
+
"POST",
|
|
12246
|
+
"/orders/:orderId/mark-delivered",
|
|
12247
|
+
params,
|
|
12248
|
+
void 0,
|
|
12249
|
+
void 0
|
|
12250
|
+
);
|
|
11600
12251
|
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11601
12252
|
});
|
|
11602
|
-
|
|
11603
|
-
|
|
11604
|
-
|
|
11605
|
-
|
|
12253
|
+
}
|
|
12254
|
+
|
|
12255
|
+
// src/commands/_generated/tax.ts
|
|
12256
|
+
function registerTaxCommands(program2) {
|
|
12257
|
+
const tax = program2.command("tax");
|
|
12258
|
+
tax.description(
|
|
12259
|
+
"Stripe Tax settings (on / off, inclusive / exclusive prices, EU companies pay net)"
|
|
12260
|
+
);
|
|
12261
|
+
tax.command("get").summary("Read the Stripe Tax settings").description(
|
|
12262
|
+
"The workspace's Stripe Tax settings, as on Integracje \u2192 P\u0142atno\u015Bci \u2192 Stripe: whether Zanfia calculates tax with Stripe Tax (`enabled`), the default price mode while on (`behavior`: inclusive / exclusive), \u201EEU companies pay the net price\u201D (`euCompaniesPayNet`), and Stripe's own Tax state on the connected Stripe account (`accountStatus`, read live from Stripe). Read-only."
|
|
11606
12263
|
).action(async (_options, command) => {
|
|
11607
12264
|
const flags = getGlobalFlags(command);
|
|
11608
12265
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11609
12266
|
const params = {};
|
|
11610
|
-
const result = await client.call(
|
|
11611
|
-
|
|
11612
|
-
|
|
12267
|
+
const result = await client.call(
|
|
12268
|
+
"GET",
|
|
12269
|
+
"/integrations/payments/stripe/tax",
|
|
12270
|
+
params,
|
|
12271
|
+
void 0,
|
|
12272
|
+
void 0
|
|
12273
|
+
);
|
|
12274
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11613
12275
|
});
|
|
11614
|
-
|
|
11615
|
-
|
|
11616
|
-
).
|
|
11617
|
-
"--
|
|
11618
|
-
"
|
|
11619
|
-
|
|
11620
|
-
|
|
12276
|
+
tax.command("set").summary("Change the Stripe Tax settings").description(
|
|
12277
|
+
"Changes the workspace's Stripe Tax settings like the switches on Integracje \u2192 P\u0142atno\u015Bci \u2192 Stripe; only the fields sent change. Turning it on needs a connected Stripe account with Stripe Tax active in Stripe itself (409 `stripe_tax_not_active_on_account` otherwise, with the Stripe settings link) and sets the default mode to `inclusive` unless `behavior` is sent; turning it off is always allowed. `behavior` and `euCompaniesPayNet` exist only while it is on (409 `stripe_tax_off`). It applies to the next Stripe checkouts and to the next renewals of running subscriptions (turning it off stops tax on renewals; `euCompaniesPayNet` changes what EU-company subscribers pay at renewal); paid orders don't change. Answers the settings as saved. Reversible: change them again."
|
|
12278
|
+
).option(
|
|
12279
|
+
"--enabled",
|
|
12280
|
+
"true = turn Stripe Tax on (needs Stripe Tax active on the connected Stripe account; the default mode becomes `inclusive` unless `behavior` is sent too); false = turn it off (always allowed; clears the mode)."
|
|
12281
|
+
).option("--no-enabled", "Send `enabled: false` explicitly.").addOption(
|
|
12282
|
+
new Option(
|
|
12283
|
+
"--behavior <inclusive|exclusive>",
|
|
12284
|
+
"The default price mode while on: `inclusive` (gross prices) or `exclusive` (net prices, tax added at checkout). Only while Stripe Tax is on."
|
|
12285
|
+
).choices(["inclusive", "exclusive"])
|
|
12286
|
+
).option(
|
|
12287
|
+
"--eu-companies-pay-net",
|
|
12288
|
+
"\u201EFirmy z UE p\u0142ac\u0105 cen\u0119 netto\u201D (KI#1720): a company from another EU country with a valid VAT number pays an inclusive price without the VAT. Only while Stripe Tax is on."
|
|
12289
|
+
).option("--no-eu-companies-pay-net", "Send `euCompaniesPayNet: false` explicitly.").action(async (options, command) => {
|
|
11621
12290
|
const flags = getGlobalFlags(command);
|
|
11622
12291
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11623
12292
|
const params = {};
|
|
11624
12293
|
const payload = {
|
|
11625
|
-
|
|
12294
|
+
enabled: options.enabled,
|
|
12295
|
+
behavior: options.behavior,
|
|
12296
|
+
euCompaniesPayNet: options.euCompaniesPayNet
|
|
11626
12297
|
};
|
|
11627
|
-
const result = await client.call(
|
|
12298
|
+
const result = await client.call(
|
|
12299
|
+
"PATCH",
|
|
12300
|
+
"/integrations/payments/stripe/tax",
|
|
12301
|
+
params,
|
|
12302
|
+
void 0,
|
|
12303
|
+
payload
|
|
12304
|
+
);
|
|
11628
12305
|
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11629
12306
|
});
|
|
11630
12307
|
}
|
|
@@ -11635,11 +12312,17 @@ function registerGeneratedCommands(program2) {
|
|
|
11635
12312
|
registerBlogCommands(program2);
|
|
11636
12313
|
registerSiteRedirectsCommands(program2);
|
|
11637
12314
|
registerPopupsCommands(program2);
|
|
12315
|
+
registerProductsCommands(program2);
|
|
12316
|
+
registerSubscriptionsCommands(program2);
|
|
11638
12317
|
registerStudioCommands(program2);
|
|
11639
12318
|
registerSiteCommands(program2);
|
|
12319
|
+
registerChatsCommands(program2);
|
|
12320
|
+
registerOrdersCommands(program2);
|
|
12321
|
+
registerTaxCommands(program2);
|
|
11640
12322
|
}
|
|
11641
12323
|
|
|
11642
12324
|
// src/commands/ads/index.ts
|
|
12325
|
+
import { randomUUID } from "crypto";
|
|
11643
12326
|
import { readFileSync as readFileSync3 } from "fs";
|
|
11644
12327
|
var SETTABLE_STATUSES = ["ACTIVE", "PAUSED"];
|
|
11645
12328
|
var ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
@@ -11738,6 +12421,74 @@ function parseUpdateCreativeInput(value) {
|
|
|
11738
12421
|
}
|
|
11739
12422
|
return parsed;
|
|
11740
12423
|
}
|
|
12424
|
+
function parseCreateJson(value, allowed, required) {
|
|
12425
|
+
const parsed = parseJsonObjectFileOrInline2(value, "--json");
|
|
12426
|
+
const unknownKeys = Object.keys(parsed).filter((key) => !allowed.includes(key));
|
|
12427
|
+
if (unknownKeys.length > 0) {
|
|
12428
|
+
throw new Error(`--json accepts only ${allowed.join(", ")} (got: ${unknownKeys.join(", ")}).`);
|
|
12429
|
+
}
|
|
12430
|
+
const missing = required.filter((key) => parsed[key] === void 0);
|
|
12431
|
+
if (missing.length > 0) {
|
|
12432
|
+
throw new Error(`--json needs ${missing.join(", ")}.`);
|
|
12433
|
+
}
|
|
12434
|
+
return parsed;
|
|
12435
|
+
}
|
|
12436
|
+
var CREATE_CAMPAIGN_KEYS = [
|
|
12437
|
+
"name",
|
|
12438
|
+
"objective",
|
|
12439
|
+
"specialAdCategories",
|
|
12440
|
+
"dailyBudgetMinor",
|
|
12441
|
+
"lifetimeBudgetMinor",
|
|
12442
|
+
"endTime"
|
|
12443
|
+
];
|
|
12444
|
+
var CREATE_AD_SET_KEYS = [
|
|
12445
|
+
"campaignId",
|
|
12446
|
+
"name",
|
|
12447
|
+
"dailyBudgetMinor",
|
|
12448
|
+
"lifetimeBudgetMinor",
|
|
12449
|
+
"startTime",
|
|
12450
|
+
"endTime",
|
|
12451
|
+
"targeting",
|
|
12452
|
+
"publisherPlatforms",
|
|
12453
|
+
"datasetId"
|
|
12454
|
+
];
|
|
12455
|
+
var CREATE_AD_FROM_SCRATCH_KEYS = [
|
|
12456
|
+
"adSetId",
|
|
12457
|
+
"name",
|
|
12458
|
+
"pageId",
|
|
12459
|
+
"instagramUserId",
|
|
12460
|
+
"imageUrl",
|
|
12461
|
+
"message",
|
|
12462
|
+
"headline",
|
|
12463
|
+
"description",
|
|
12464
|
+
"callToActionType",
|
|
12465
|
+
"link",
|
|
12466
|
+
"urlTags"
|
|
12467
|
+
];
|
|
12468
|
+
function guardFieldsOf(options, what) {
|
|
12469
|
+
if (!options.dryRun) requireConfirm2(options.confirm, what);
|
|
12470
|
+
const idempotencyKey = options.idempotencyKey ?? randomUUID();
|
|
12471
|
+
if (!options.idempotencyKey && !options.dryRun) {
|
|
12472
|
+
process.stderr.write(
|
|
12473
|
+
`Idempotency key: ${idempotencyKey} (re-run with --idempotency-key ${idempotencyKey} to retry safely)
|
|
12474
|
+
`
|
|
12475
|
+
);
|
|
12476
|
+
}
|
|
12477
|
+
return { idempotencyKey, ...options.dryRun ? { dryRun: true } : {} };
|
|
12478
|
+
}
|
|
12479
|
+
function printCreatePreview(result, nextStep) {
|
|
12480
|
+
const { preview: preview2 } = result;
|
|
12481
|
+
const lead = result.dryRun ? "Dry run, nothing created." : result.replayed ? "Already created with this idempotency key (nothing new on Meta)." : "Created (PAUSED).";
|
|
12482
|
+
process.stdout.write(`${lead}
|
|
12483
|
+
${preview2.summary}
|
|
12484
|
+
`);
|
|
12485
|
+
process.stdout.write(
|
|
12486
|
+
`Daily ceiling: ${formatMinor(preview2.ceiling.dailyCeilingMinor, preview2.budget.currency)}, used in the last 24 h: ${formatMinor(preview2.ceiling.usedLast24hMinor, preview2.budget.currency)}, left after this: ${formatMinor(preview2.ceiling.remainingAfterMinor, preview2.budget.currency)}.
|
|
12487
|
+
`
|
|
12488
|
+
);
|
|
12489
|
+
if (!result.dryRun) process.stdout.write(`${nextStep}
|
|
12490
|
+
`);
|
|
12491
|
+
}
|
|
11741
12492
|
function requireConfirm2(confirm, what) {
|
|
11742
12493
|
if (!confirm) {
|
|
11743
12494
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
@@ -12127,6 +12878,33 @@ Daily (${result.daily.length})
|
|
|
12127
12878
|
});
|
|
12128
12879
|
}
|
|
12129
12880
|
);
|
|
12881
|
+
campaign.command("create").description(
|
|
12882
|
+
"Create a NEW campaign (OUTCOME_TRAFFIC | OUTCOME_SALES | OUTCOME_LEADS), always PAUSED. Optional DAILY campaign budget in minor units (a lifetime campaign budget is not supported yet: put lifetime budgets on the ad sets). Try --dry-run first; a real create requires --confirm."
|
|
12883
|
+
).argument(...CONNECTION_ARG).argument(...AD_ACCOUNT_ARG).requiredOption(
|
|
12884
|
+
"--json <fileOrJson>",
|
|
12885
|
+
`name, objective (required); specialAdCategories, dailyBudgetMinor (optional) ${JSON_FLAG_HINT}`
|
|
12886
|
+
).option("--dry-run", "preview only: what would be created, the budget and the ceiling left").option("--idempotency-key <key>", "repeat a key to retry safely (default: a new UUID)").option("--confirm", "required for a real create").action(
|
|
12887
|
+
async (connectionId, adAccountId, options, command) => {
|
|
12888
|
+
const flags = getGlobalFlags(command);
|
|
12889
|
+
const input = parseCreateJson(options.json, CREATE_CAMPAIGN_KEYS, [
|
|
12890
|
+
"name",
|
|
12891
|
+
"objective"
|
|
12892
|
+
]);
|
|
12893
|
+
const guard = guardFieldsOf(options, `create campaign "${input.name}"`);
|
|
12894
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12895
|
+
const result = await client.createMetaCampaign(scopeOf(connectionId, adAccountId), {
|
|
12896
|
+
...input,
|
|
12897
|
+
...guard
|
|
12898
|
+
});
|
|
12899
|
+
emit(flags, result, () => {
|
|
12900
|
+
printCreatePreview(
|
|
12901
|
+
result,
|
|
12902
|
+
`Next: \`ads ad-set create ${connectionId} ${adAccountId} --json '{"campaignId":"${result.campaign?.id ?? "\u2026"}",\u2026}'\`.`
|
|
12903
|
+
);
|
|
12904
|
+
if (result.campaign) printCampaignTable([result.campaign]);
|
|
12905
|
+
});
|
|
12906
|
+
}
|
|
12907
|
+
);
|
|
12130
12908
|
ads.command("ad-sets").description("List ad sets of an ad account (budgets, schedule, effective status)").argument(...CONNECTION_ARG).argument(...AD_ACCOUNT_ARG).option("--campaign <campaignId>", "only ad sets of this campaign").option("--include-archived", "include ARCHIVED / DELETED ad sets").action(
|
|
12131
12909
|
async (connectionId, adAccountId, options, command) => {
|
|
12132
12910
|
const flags = getGlobalFlags(command);
|
|
@@ -12147,6 +12925,34 @@ Daily (${result.daily.length})
|
|
|
12147
12925
|
emit(flags, result, () => printAdSet(result.adSet));
|
|
12148
12926
|
}
|
|
12149
12927
|
);
|
|
12928
|
+
adSet.command("create").description(
|
|
12929
|
+
"Create a NEW ad set under a campaign, always PAUSED: budget (when the campaign has none), schedule, targeting (countries, age, gender, audience ids), placements, pixel. Try --dry-run first; a real create requires --confirm."
|
|
12930
|
+
).argument(...CONNECTION_ARG).argument(...AD_ACCOUNT_ARG).requiredOption(
|
|
12931
|
+
"--json <fileOrJson>",
|
|
12932
|
+
`campaignId, name, targeting { countries, ageMin, ageMax, gender, customAudienceIds, excludedCustomAudienceIds, advantageAudience } (required: campaignId, name, targeting.countries); dailyBudgetMinor | lifetimeBudgetMinor + endTime, startTime, publisherPlatforms, datasetId ${JSON_FLAG_HINT}`
|
|
12933
|
+
).option("--dry-run", "preview only: what would be created, the budget and the ceiling left").option("--idempotency-key <key>", "repeat a key to retry safely (default: a new UUID)").option("--confirm", "required for a real create").action(
|
|
12934
|
+
async (connectionId, adAccountId, options, command) => {
|
|
12935
|
+
const flags = getGlobalFlags(command);
|
|
12936
|
+
const input = parseCreateJson(options.json, CREATE_AD_SET_KEYS, [
|
|
12937
|
+
"campaignId",
|
|
12938
|
+
"name",
|
|
12939
|
+
"targeting"
|
|
12940
|
+
]);
|
|
12941
|
+
const guard = guardFieldsOf(options, `create ad set "${input.name}"`);
|
|
12942
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12943
|
+
const result = await client.createMetaAdSet(scopeOf(connectionId, adAccountId), {
|
|
12944
|
+
...input,
|
|
12945
|
+
...guard
|
|
12946
|
+
});
|
|
12947
|
+
emit(flags, result, () => {
|
|
12948
|
+
printCreatePreview(
|
|
12949
|
+
result,
|
|
12950
|
+
`Next: \`ads create ${connectionId} ${adAccountId} --from-scratch --json '{"adSetId":"${result.adSet?.id ?? "\u2026"}",\u2026}'\`.`
|
|
12951
|
+
);
|
|
12952
|
+
if (result.adSet) printAdSet(result.adSet);
|
|
12953
|
+
});
|
|
12954
|
+
}
|
|
12955
|
+
);
|
|
12150
12956
|
adSet.command("update").description(
|
|
12151
12957
|
"Change an ad set budget (minor units; daily XOR lifetime), schedule end or status \u2014 applies to every ad in the set. Requires --confirm."
|
|
12152
12958
|
).argument(...CONNECTION_ARG).argument(...AD_ACCOUNT_ARG).argument("<adSetId>", "ad set id (from `ads ad-sets`)").option("--daily-budget <minor>", "daily budget in minor units").option("--lifetime-budget <minor>", "lifetime budget in minor units (needs an end time)").option("--end-time <iso>", "schedule end, ISO 8601").option("--status <status>", `ad set status (${SETTABLE_STATUSES.join(" | ")})`).option("--confirm", "required \u2014 acknowledge the change on the live ad account").action(
|
|
@@ -12186,13 +12992,37 @@ More ads available \u2014 re-run with --after ${result.after}
|
|
|
12186
12992
|
}
|
|
12187
12993
|
);
|
|
12188
12994
|
ads.command("create").description(
|
|
12189
|
-
"Create an ad
|
|
12995
|
+
"Create an ad, always PAUSED. Default: duplicate an existing ad (same ad set and page) with creative overrides from --json. --from-scratch: a new single-image ad in an ad set (image URL, Page, copy, link). Requires --confirm (not for --dry-run)."
|
|
12190
12996
|
).argument(...CONNECTION_ARG).argument(...AD_ACCOUNT_ARG).requiredOption(
|
|
12191
12997
|
"--json <fileOrJson>",
|
|
12192
|
-
`
|
|
12193
|
-
).option("--confirm", "required \u2014 acknowledge creating an ad on the live ad account").action(
|
|
12998
|
+
`duplicate: sourceAdId, name (required); message, headline, description, callToActionType, link, imageUrl. --from-scratch: adSetId, name, pageId, imageUrl, message, link (required); instagramUserId, headline, description, callToActionType, urlTags ${JSON_FLAG_HINT}`
|
|
12999
|
+
).option("--from-scratch", "build a new ad instead of duplicating one (fr-0312)").option("--dry-run", "--from-scratch only: preview, nothing created").option("--idempotency-key <key>", "--from-scratch only: repeat a key to retry safely").option("--confirm", "required \u2014 acknowledge creating an ad on the live ad account").action(
|
|
12194
13000
|
async (connectionId, adAccountId, options, command) => {
|
|
12195
13001
|
const flags = getGlobalFlags(command);
|
|
13002
|
+
if (options.fromScratch) {
|
|
13003
|
+
const scratch = parseCreateJson(
|
|
13004
|
+
options.json,
|
|
13005
|
+
CREATE_AD_FROM_SCRATCH_KEYS,
|
|
13006
|
+
["adSetId", "name", "pageId", "imageUrl", "message", "link"]
|
|
13007
|
+
);
|
|
13008
|
+
const guard = guardFieldsOf(options, `create ad "${scratch.name}"`);
|
|
13009
|
+
const scratchClient = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
13010
|
+
const created = await scratchClient.createMetaAdFromScratch(
|
|
13011
|
+
scopeOf(connectionId, adAccountId),
|
|
13012
|
+
{ ...scratch, ...guard }
|
|
13013
|
+
);
|
|
13014
|
+
emit(flags, created, () => {
|
|
13015
|
+
printCreatePreview(
|
|
13016
|
+
created,
|
|
13017
|
+
`Turn it on with \`ads update ${connectionId} ${adAccountId} ${created.ad?.id ?? "\u2026"} --status ACTIVE --confirm\` (it spends).`
|
|
13018
|
+
);
|
|
13019
|
+
if (created.ad) printAd(created.ad);
|
|
13020
|
+
});
|
|
13021
|
+
return;
|
|
13022
|
+
}
|
|
13023
|
+
if (options.dryRun || options.idempotencyKey) {
|
|
13024
|
+
throw new Error("--dry-run and --idempotency-key apply to --from-scratch only.");
|
|
13025
|
+
}
|
|
12196
13026
|
const input = parseCreateAdInput(options.json);
|
|
12197
13027
|
requireConfirm2(options.confirm, `create ad "${input.name}" from ${input.sourceAdId}`);
|
|
12198
13028
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -13091,6 +13921,8 @@ function promptHidden(question) {
|
|
|
13091
13921
|
|
|
13092
13922
|
// src/commands/auth/register.ts
|
|
13093
13923
|
var BOT_CHECK_FAILED_CODE = "auth/bot-check-failed";
|
|
13924
|
+
var DEVICE_VERIFICATION_REQUIRED_CODE = "auth/device-verification-required";
|
|
13925
|
+
var DEVICE_CONFIRMATION_NOTICE = "\nYour account is ready. Confirm this device in the Zanfia panel first:\nopening the panel in your browser; the CLI logs in once you are done.\n\n";
|
|
13094
13926
|
var MAX_CODE_ATTEMPTS = 5;
|
|
13095
13927
|
var MIN_PASSWORD_LENGTH = 8;
|
|
13096
13928
|
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
@@ -13250,12 +14082,22 @@ We sent a 6-digit code to ${email}. Check your inbox.
|
|
|
13250
14082
|
codeChallengeMethod: "S256",
|
|
13251
14083
|
hostname: safeHostname2()
|
|
13252
14084
|
};
|
|
13253
|
-
|
|
13254
|
-
|
|
13255
|
-
|
|
13256
|
-
|
|
13257
|
-
|
|
13258
|
-
|
|
14085
|
+
let minted;
|
|
14086
|
+
try {
|
|
14087
|
+
minted = await postJson2(
|
|
14088
|
+
"Authorizing CLI",
|
|
14089
|
+
`${urls.usersUrl}/cli/auth/codes`,
|
|
14090
|
+
codeBody,
|
|
14091
|
+
{ bearer: signIn.idToken, verbose }
|
|
14092
|
+
);
|
|
14093
|
+
} catch (error) {
|
|
14094
|
+
if (error instanceof HttpJsonError && error.code === DEVICE_VERIFICATION_REQUIRED_CODE) {
|
|
14095
|
+
process.stdout.write(DEVICE_CONFIRMATION_NOTICE);
|
|
14096
|
+
await runLogin({ apiUrl: options.apiUrl, verbose });
|
|
14097
|
+
return;
|
|
14098
|
+
}
|
|
14099
|
+
throw error;
|
|
14100
|
+
}
|
|
13259
14101
|
const exchangeBody = {
|
|
13260
14102
|
code: minted.code,
|
|
13261
14103
|
codeVerifier
|
|
@@ -13306,6 +14148,9 @@ plus its personal workspace are created.
|
|
|
13306
14148
|
Running it with an email that already has a creator account simply signs
|
|
13307
14149
|
that account in.
|
|
13308
14150
|
|
|
14151
|
+
When Zanfia asks you to confirm this device first, the CLI opens the panel in
|
|
14152
|
+
your browser; confirm the device there and the CLI logs in like \`login\`.
|
|
14153
|
+
|
|
13309
14154
|
Skipping the password is fine \u2014 you can always sign in with an emailed code.
|
|
13310
14155
|
`
|
|
13311
14156
|
).action(async (options) => {
|
|
@@ -14311,6 +15156,7 @@ var GRANT_PAYMENT_METHODS = [
|
|
|
14311
15156
|
"cash",
|
|
14312
15157
|
"other"
|
|
14313
15158
|
];
|
|
15159
|
+
var HELD_EVENTS_DECISIONS = ["send", "skip"];
|
|
14314
15160
|
var AIImageAspectRatio = /* @__PURE__ */ ((AIImageAspectRatio2) => {
|
|
14315
15161
|
AIImageAspectRatio2["Square"] = "1:1";
|
|
14316
15162
|
AIImageAspectRatio2["Portrait"] = "4:5";
|
|
@@ -14410,6 +15256,89 @@ var CLIENT_TAG_COLORS = [
|
|
|
14410
15256
|
"purple"
|
|
14411
15257
|
];
|
|
14412
15258
|
var TRANSCRIPTION_LANGUAGES = ["pl", "en", "default"];
|
|
15259
|
+
var COMMUNITY_JOIN_APPROVE_MAX_IDS = 20;
|
|
15260
|
+
var COMMUNITY_JOIN_DECLINE_MAX_IDS = 100;
|
|
15261
|
+
var LESSON_QA_INBOX_FILTERS = [
|
|
15262
|
+
"unanswered",
|
|
15263
|
+
"all",
|
|
15264
|
+
"public",
|
|
15265
|
+
"private",
|
|
15266
|
+
"hidden"
|
|
15267
|
+
];
|
|
15268
|
+
var SETTABLE_LESSON_QA_ENTRY_STATUSES = [
|
|
15269
|
+
"published",
|
|
15270
|
+
"hidden"
|
|
15271
|
+
];
|
|
15272
|
+
var CONSULTATION_BOOKING_STATUSES = [
|
|
15273
|
+
"booked",
|
|
15274
|
+
"cancelled",
|
|
15275
|
+
"done"
|
|
15276
|
+
];
|
|
15277
|
+
var STUDIO_SOCIAL_DRAFT_WEAK_REASONS = [
|
|
15278
|
+
"too-generic",
|
|
15279
|
+
"not-my-voice",
|
|
15280
|
+
"too-long",
|
|
15281
|
+
"too-salesy",
|
|
15282
|
+
"sounds-like-ai",
|
|
15283
|
+
"wrong-topic"
|
|
15284
|
+
];
|
|
15285
|
+
var YOUTUBE_PRIVACY_STATUSES = [
|
|
15286
|
+
"private",
|
|
15287
|
+
"unlisted",
|
|
15288
|
+
"public"
|
|
15289
|
+
];
|
|
15290
|
+
|
|
15291
|
+
// src/commands/clients/automationRuns.ts
|
|
15292
|
+
function stepLabel(run) {
|
|
15293
|
+
const step = run.currentStep;
|
|
15294
|
+
if (!step) return "";
|
|
15295
|
+
return step.name ?? step.actionType ?? step.kind ?? step.nodeId;
|
|
15296
|
+
}
|
|
15297
|
+
function printStoppedRuns(result) {
|
|
15298
|
+
process.stdout.write(
|
|
15299
|
+
`Stopped ${result.stopped.length} run(s)${result.alreadyFinished > 0 ? `; ${result.alreadyFinished} had already finished` : ""}.
|
|
15300
|
+
`
|
|
15301
|
+
);
|
|
15302
|
+
for (const run of result.stopped) {
|
|
15303
|
+
process.stdout.write(` - run ${run.runId} (workflow ${run.automationId})
|
|
15304
|
+
`);
|
|
15305
|
+
}
|
|
15306
|
+
}
|
|
15307
|
+
function registerClientAutomationRunsCommands(clients2) {
|
|
15308
|
+
const runs = clients2.command("automation-runs").description(
|
|
15309
|
+
"One customer's workflow runs in progress (the customer card \u2192 \u201EAutomatyzacje w toku\u201D): list them or take the customer out"
|
|
15310
|
+
);
|
|
15311
|
+
runs.command("list").description(
|
|
15312
|
+
"Live runs of one customer across every workflow, newest first (test runs excluded)"
|
|
15313
|
+
).argument("<clientId>", "workspace client id (wcid)").action(async (clientId, _, command) => {
|
|
15314
|
+
const flags = getGlobalFlags(command);
|
|
15315
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
15316
|
+
const result = await client.listClientAutomationRuns(clientId);
|
|
15317
|
+
emit(flags, result, () => {
|
|
15318
|
+
printTable(
|
|
15319
|
+
result.items.map((run) => ({
|
|
15320
|
+
runId: run.runId,
|
|
15321
|
+
workflow: run.automationName ?? `${run.automationId} (deleted)`,
|
|
15322
|
+
workflowId: run.automationId,
|
|
15323
|
+
status: run.status,
|
|
15324
|
+
step: stepLabel(run),
|
|
15325
|
+
nextStepAt: run.nextScheduledAt ?? "",
|
|
15326
|
+
startedAt: run.startedAt
|
|
15327
|
+
})),
|
|
15328
|
+
["runId", "workflow", "workflowId", "status", "step", "nextStepAt", "startedAt"]
|
|
15329
|
+
);
|
|
15330
|
+
});
|
|
15331
|
+
});
|
|
15332
|
+
runs.command("stop").description(
|
|
15333
|
+
"Take the customer out of EVERY workflow run in progress \u2014 each run ends cancelled and no further step runs (a step already sending finishes). They can enter again when a trigger fires later. To stop one run: `workflows runs stop`. Requires --confirm"
|
|
15334
|
+
).argument("<clientId>", "workspace client id (wcid)").option("--confirm", "required \u2014 acknowledge that every live run of this customer ends").action(async (clientId, options, command) => {
|
|
15335
|
+
const flags = getGlobalFlags(command);
|
|
15336
|
+
requireConfirm(options.confirm, `stop every workflow run of client ${clientId}`);
|
|
15337
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
15338
|
+
const result = await client.stopClientAutomationRuns(clientId);
|
|
15339
|
+
emit(flags, result, () => printStoppedRuns(result));
|
|
15340
|
+
});
|
|
15341
|
+
}
|
|
14413
15342
|
|
|
14414
15343
|
// src/commands/clients/emails.ts
|
|
14415
15344
|
import { readFileSync as readFileSync6 } from "fs";
|
|
@@ -14591,6 +15520,42 @@ function registerClientEmailsCommands(clients2) {
|
|
|
14591
15520
|
});
|
|
14592
15521
|
}
|
|
14593
15522
|
);
|
|
15523
|
+
emails.command("delete-drafts").description(
|
|
15524
|
+
"Delete DRAFTS (and failed hand-offs) across customers: the listed ids, every draft with one subject, or all. Sent e-mails are never touched. --preview only counts; a delete needs --confirm and never removes more than it counted"
|
|
15525
|
+
).option("--ids <a,b,...>", "draft ids (from `clients emails inbox`)", parseCommaList("--ids")).option("--subject <subject>", "every draft whose subject is exactly this").option("--all", "every draft of the workspace").option("--preview", "count the matching drafts, delete nothing").option("--confirm", "required to delete \u2014 acknowledge the drafts are deleted for good").action(
|
|
15526
|
+
async (options, command) => {
|
|
15527
|
+
const flags = getGlobalFlags(command);
|
|
15528
|
+
const scope = draftsScope(options);
|
|
15529
|
+
if (!options.preview) requireConfirm(options.confirm, "delete client e-mail drafts");
|
|
15530
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
15531
|
+
const counted = await client.deleteClientEmailDrafts({ ...scope, preview: true });
|
|
15532
|
+
if (options.preview || counted.matchedCount === 0) {
|
|
15533
|
+
emit(flags, counted, () => printDraftsCount(counted, "match"));
|
|
15534
|
+
return;
|
|
15535
|
+
}
|
|
15536
|
+
const result = await client.deleteClientEmailDrafts({
|
|
15537
|
+
...scope,
|
|
15538
|
+
maxCount: counted.matchedCount
|
|
15539
|
+
});
|
|
15540
|
+
emit(flags, result, () => printDraftsCount(result, "deleted"));
|
|
15541
|
+
}
|
|
15542
|
+
);
|
|
15543
|
+
}
|
|
15544
|
+
function draftsScope(options) {
|
|
15545
|
+
const chosen = [options.ids !== void 0, options.subject !== void 0, options.all === true];
|
|
15546
|
+
if (chosen.filter(Boolean).length !== 1) {
|
|
15547
|
+
throw new Error("Pass exactly one of --ids, --subject or --all.");
|
|
15548
|
+
}
|
|
15549
|
+
if (options.ids !== void 0) return { scope: "ids", emailIds: options.ids };
|
|
15550
|
+
if (options.subject !== void 0) return { scope: "subject", subject: options.subject };
|
|
15551
|
+
return { scope: "all" };
|
|
15552
|
+
}
|
|
15553
|
+
function printDraftsCount(result, verb) {
|
|
15554
|
+
const count2 = verb === "deleted" ? result.deletedCount : result.matchedCount;
|
|
15555
|
+
process.stdout.write(
|
|
15556
|
+
`${count2} draft(s) ${verb === "deleted" ? "deleted" : "match (nothing deleted)"}${result.sampleSubjects.length > 0 ? `, e.g. "${result.sampleSubjects.join('", "')}"` : ""}.
|
|
15557
|
+
`
|
|
15558
|
+
);
|
|
14594
15559
|
}
|
|
14595
15560
|
|
|
14596
15561
|
// src/commands/clients/subscription.ts
|
|
@@ -15079,59 +16044,70 @@ function readImportCsv(csvFile) {
|
|
|
15079
16044
|
}
|
|
15080
16045
|
return contacts;
|
|
15081
16046
|
}
|
|
15082
|
-
function printMergePreview(
|
|
16047
|
+
function printMergePreview(preview2) {
|
|
15083
16048
|
process.stdout.write("Merge preview (dry run \u2014 nothing was written)\n");
|
|
15084
|
-
process.stdout.write(` Survivor: ${
|
|
15085
|
-
`);
|
|
15086
|
-
process.stdout.write(` Duplicate (tombstoned): ${preview.loserWcid} (${preview.loserEmail})
|
|
16049
|
+
process.stdout.write(` Survivor: ${preview2.survivorWcid} (${preview2.survivorEmail})
|
|
15087
16050
|
`);
|
|
15088
|
-
|
|
15089
|
-
process.stdout.write(` \u26A0\uFE0F BLOCKED \u2014 live subscription(s); the merge will be refused (409):
|
|
16051
|
+
process.stdout.write(` Duplicate (tombstoned): ${preview2.loserWcid} (${preview2.loserEmail})
|
|
15090
16052
|
`);
|
|
15091
|
-
|
|
16053
|
+
if (preview2.blocked !== null) {
|
|
16054
|
+
process.stdout.write(
|
|
16055
|
+
preview2.blocked.reason === "subscription-vs-lifetime" ? ` \u26A0\uFE0F BLOCKED \u2014 one client has a subscription, the other lifetime access to the same product; the merge will be refused (409). End the subscription first, or contact Zanfia:
|
|
16056
|
+
` : ` \u26A0\uFE0F BLOCKED \u2014 live subscription(s); the merge will be refused (409):
|
|
16057
|
+
`
|
|
16058
|
+
);
|
|
16059
|
+
for (const sub of preview2.blocked.subscriptions) {
|
|
15092
16060
|
process.stdout.write(
|
|
15093
16061
|
` - ${sub.side}: subscription ${sub.subscriptionId} on product ${sub.productId}
|
|
15094
16062
|
`
|
|
15095
16063
|
);
|
|
15096
16064
|
}
|
|
16065
|
+
if (preview2.blocked.reason === "subscription-vs-lifetime") {
|
|
16066
|
+
for (const grant of preview2.blocked.lifetimeGrants) {
|
|
16067
|
+
process.stdout.write(
|
|
16068
|
+
` - ${grant.side}: lifetime access to product ${grant.productId}
|
|
16069
|
+
`
|
|
16070
|
+
);
|
|
16071
|
+
}
|
|
16072
|
+
}
|
|
15097
16073
|
}
|
|
15098
16074
|
process.stdout.write(
|
|
15099
|
-
` Money: ${
|
|
16075
|
+
` Money: ${preview2.money.survivorTotalSpentMinor} + ${preview2.money.loserTotalSpentMinor} = ${preview2.money.sumMinor} (minor units); ${preview2.money.ordersMovingCount} order${preview2.money.ordersMovingCount === 1 ? "" : "s"} moving
|
|
15100
16076
|
`
|
|
15101
16077
|
);
|
|
15102
|
-
if (
|
|
15103
|
-
process.stdout.write(` Product access grants (${
|
|
16078
|
+
if (preview2.grants.length > 0) {
|
|
16079
|
+
process.stdout.write(` Product access grants (${preview2.grants.length}):
|
|
15104
16080
|
`);
|
|
15105
|
-
for (const grant of
|
|
16081
|
+
for (const grant of preview2.grants) {
|
|
15106
16082
|
process.stdout.write(
|
|
15107
16083
|
` - ${grant.productName} (${grant.productId}): ${grant.action} \u2014 ${grant.reason}
|
|
15108
16084
|
`
|
|
15109
16085
|
);
|
|
15110
16086
|
}
|
|
15111
16087
|
}
|
|
15112
|
-
if (
|
|
16088
|
+
if (preview2.profileOverwrites.length > 0) {
|
|
15113
16089
|
process.stdout.write(" Profile fields where the duplicate's value is DISCARDED:\n");
|
|
15114
|
-
for (const overwrite of
|
|
16090
|
+
for (const overwrite of preview2.profileOverwrites) {
|
|
15115
16091
|
process.stdout.write(
|
|
15116
16092
|
` - ${overwrite.field}: keeping "${overwrite.survivorValueKept ?? ""}", discarding "${overwrite.loserValueDiscarded ?? ""}"${overwrite.downstreamUnlink ? " (downstream unlink)" : ""}
|
|
15117
16093
|
`
|
|
15118
16094
|
);
|
|
15119
16095
|
}
|
|
15120
16096
|
}
|
|
15121
|
-
if (
|
|
16097
|
+
if (preview2.community.communitiesTouchedCount > 0) {
|
|
15122
16098
|
process.stdout.write(
|
|
15123
|
-
` Communities touched: ${
|
|
16099
|
+
` Communities touched: ${preview2.community.communitiesTouchedCount}; ~${preview2.community.authoredRePointCount} authored posts/comments re-pointed
|
|
15124
16100
|
`
|
|
15125
16101
|
);
|
|
15126
16102
|
}
|
|
15127
16103
|
process.stdout.write(
|
|
15128
|
-
` Resulting previous emails: ${
|
|
16104
|
+
` Resulting previous emails: ${preview2.resultingPreviousEmails.join(", ") || "(none)"}
|
|
15129
16105
|
`
|
|
15130
16106
|
);
|
|
15131
16107
|
process.stdout.write("\n This merge is IRREVERSIBLE once executed.\n");
|
|
15132
|
-
if (
|
|
16108
|
+
if (preview2.blocked === null) {
|
|
15133
16109
|
process.stdout.write(
|
|
15134
|
-
` To execute: clients merge --survivor ${
|
|
16110
|
+
` To execute: clients merge --survivor ${preview2.survivorWcid} --duplicate ${preview2.loserWcid} --preview-hash ${preview2.previewHash}
|
|
15135
16111
|
`
|
|
15136
16112
|
);
|
|
15137
16113
|
}
|
|
@@ -15598,7 +16574,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
15598
16574
|
emit(flags, result, () => printMergePreview(result.preview));
|
|
15599
16575
|
});
|
|
15600
16576
|
clients2.command("merge").description(
|
|
15601
|
-
"\u26A0\uFE0F IRREVERSIBLY merge the duplicate client into the survivor \u2014 grants move/dedupe, community content re-points, and the duplicate is TOMBSTONED (no undo). Run `clients merge-preview` first and pass its previewHash; a stale hash is refused (409).
|
|
16577
|
+
"\u26A0\uFE0F IRREVERSIBLY merge the duplicate client into the survivor \u2014 grants move/dedupe, community content re-points, and the duplicate is TOMBSTONED (no undo). Run `clients merge-preview` first and pass its previewHash; a stale hash is refused (409). A live subscription on both sides of one product blocks the merge, and so does a live subscription on one side against lifetime access on the other (409)"
|
|
15602
16578
|
).requiredOption("--survivor <clientId>", "the client that remains (wcid)").requiredOption("--duplicate <clientId>", "the client merged in and tombstoned (wcid)").requiredOption(
|
|
15603
16579
|
"--preview-hash <hash>",
|
|
15604
16580
|
"previewHash from a fresh `clients merge-preview` for the same pair"
|
|
@@ -15854,6 +16830,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
15854
16830
|
});
|
|
15855
16831
|
registerClientSubscriptionCommands(clients2);
|
|
15856
16832
|
registerClientEmailsCommands(clients2);
|
|
16833
|
+
registerClientAutomationRunsCommands(clients2);
|
|
15857
16834
|
}
|
|
15858
16835
|
|
|
15859
16836
|
// src/commands/communities/index.ts
|
|
@@ -15971,6 +16948,100 @@ var MODERATION_COLUMNS = [
|
|
|
15971
16948
|
"createdAt",
|
|
15972
16949
|
"status"
|
|
15973
16950
|
];
|
|
16951
|
+
function joinRequestRow(request) {
|
|
16952
|
+
return {
|
|
16953
|
+
id: request.id,
|
|
16954
|
+
name: [request.firstName, request.lastName].filter(Boolean).join(" "),
|
|
16955
|
+
email: request.email,
|
|
16956
|
+
status: request.status,
|
|
16957
|
+
requestedAt: request.requestedAt,
|
|
16958
|
+
answers: request.answers.length,
|
|
16959
|
+
aiHint: request.aiHint?.verdict ?? ""
|
|
16960
|
+
};
|
|
16961
|
+
}
|
|
16962
|
+
var JOIN_REQUEST_COLUMNS = [
|
|
16963
|
+
"id",
|
|
16964
|
+
"name",
|
|
16965
|
+
"email",
|
|
16966
|
+
"status",
|
|
16967
|
+
"requestedAt",
|
|
16968
|
+
"answers",
|
|
16969
|
+
"aiHint"
|
|
16970
|
+
];
|
|
16971
|
+
function assertIdCount(ids, max) {
|
|
16972
|
+
if (ids.length > max) {
|
|
16973
|
+
throw new Error(
|
|
16974
|
+
`Pass at most ${max} request ids per call (got ${ids.length}); run it again for the rest.`
|
|
16975
|
+
);
|
|
16976
|
+
}
|
|
16977
|
+
}
|
|
16978
|
+
function printJoinDecisions(result) {
|
|
16979
|
+
printTable(
|
|
16980
|
+
result.results.map((r) => ({
|
|
16981
|
+
requestId: r.requestId,
|
|
16982
|
+
outcome: r.outcome,
|
|
16983
|
+
message: r.message ?? ""
|
|
16984
|
+
})),
|
|
16985
|
+
["requestId", "outcome", "message"]
|
|
16986
|
+
);
|
|
16987
|
+
}
|
|
16988
|
+
var DIGEST_OUTCOME_LABELS = {
|
|
16989
|
+
approved: "Approved \u2014 the digest goes out Thursday 09:00.",
|
|
16990
|
+
approvedSendingNow: "Approved \u2014 the send starts now.",
|
|
16991
|
+
alreadyApproved: "Already approved \u2014 nothing changed.",
|
|
16992
|
+
skipped: "Skipped \u2014 no digest goes out this week.",
|
|
16993
|
+
alreadySkipped: "Already skipped \u2014 nothing changed."
|
|
16994
|
+
};
|
|
16995
|
+
function digestRow(draft) {
|
|
16996
|
+
return {
|
|
16997
|
+
week: draft.weekKey,
|
|
16998
|
+
status: draft.skipReason ? `${draft.status} (${draft.skipReason})` : draft.status,
|
|
16999
|
+
sendAt: draft.sendAt,
|
|
17000
|
+
sentAt: draft.sentAt ?? "",
|
|
17001
|
+
recipients: draft.recipientsCount ?? ""
|
|
17002
|
+
};
|
|
17003
|
+
}
|
|
17004
|
+
var DIGEST_COLUMNS = ["week", "status", "sendAt", "sentAt", "recipients"];
|
|
17005
|
+
function printDigestDraft(draft) {
|
|
17006
|
+
process.stdout.write(
|
|
17007
|
+
`Week ${draft.weekKey}: ${draft.status}${draft.skipReason ? ` (${draft.skipReason})` : ""}
|
|
17008
|
+
`
|
|
17009
|
+
);
|
|
17010
|
+
process.stdout.write(` Send at: ${draft.sendAt} (${draft.timezone})
|
|
17011
|
+
`);
|
|
17012
|
+
process.stdout.write(` Approve by: ${draft.approvalDeadline}
|
|
17013
|
+
`);
|
|
17014
|
+
const actions = [draft.canApprove ? "approve" : null, draft.canSkip ? "skip" : null].filter(
|
|
17015
|
+
Boolean
|
|
17016
|
+
);
|
|
17017
|
+
process.stdout.write(` Possible now: ${actions.length > 0 ? actions.join(", ") : "nothing"}
|
|
17018
|
+
`);
|
|
17019
|
+
if (draft.sentAt) {
|
|
17020
|
+
process.stdout.write(` Sent: ${draft.sentAt} to ${draft.recipientsCount ?? 0} member(s)
|
|
17021
|
+
`);
|
|
17022
|
+
}
|
|
17023
|
+
const { preview: preview2 } = draft;
|
|
17024
|
+
if (!preview2) return;
|
|
17025
|
+
const { stats } = preview2;
|
|
17026
|
+
process.stdout.write(
|
|
17027
|
+
` Preview (${preview2.communityName}): ${stats.postsCount} posts, ${stats.commentsCount} comments, ${stats.newMembersCount} new members
|
|
17028
|
+
`
|
|
17029
|
+
);
|
|
17030
|
+
for (const line of preview2.summaryLinesEn ?? preview2.summaryLinesPl ?? []) {
|
|
17031
|
+
process.stdout.write(` ${line.emoji} ${line.text}
|
|
17032
|
+
`);
|
|
17033
|
+
}
|
|
17034
|
+
for (const post of preview2.posts) {
|
|
17035
|
+
process.stdout.write(
|
|
17036
|
+
` - ${post.title ?? post.excerpt} (${post.channelName}, ${post.authorName})
|
|
17037
|
+
`
|
|
17038
|
+
);
|
|
17039
|
+
}
|
|
17040
|
+
for (const event of preview2.events) {
|
|
17041
|
+
process.stdout.write(` event: ${event.title} \u2014 ${event.dateFormatted}
|
|
17042
|
+
`);
|
|
17043
|
+
}
|
|
17044
|
+
}
|
|
15974
17045
|
function authorLabel(author) {
|
|
15975
17046
|
const name = [author.firstName, author.lastName].filter(Boolean).join(" ");
|
|
15976
17047
|
return author.nickname || name || author.id;
|
|
@@ -17026,6 +18097,98 @@ Not going (${result.notGoingCount}):
|
|
|
17026
18097
|
}
|
|
17027
18098
|
});
|
|
17028
18099
|
});
|
|
18100
|
+
const joinRequests = communities2.command("join-requests").description(
|
|
18101
|
+
"People waiting to join a community that approves members by hand: list, approve, decline (workspace staff with member management; member keys get 403)"
|
|
18102
|
+
);
|
|
18103
|
+
joinRequests.command("list").description("Pending requests first (with their answers and the AI hint), then declined ones").argument("<communityId>", "community id").action(async (communityId, _, command) => {
|
|
18104
|
+
const flags = getGlobalFlags(command);
|
|
18105
|
+
const result = await clientFor(flags).listCommunityJoinRequests(communityId);
|
|
18106
|
+
emit(flags, result, () => {
|
|
18107
|
+
process.stdout.write(`Pending: ${result.pendingCount}
|
|
18108
|
+
`);
|
|
18109
|
+
printTable(result.requests.map(joinRequestRow), JOIN_REQUEST_COLUMNS);
|
|
18110
|
+
for (const request of result.requests) {
|
|
18111
|
+
if (request.status !== "pending" || request.answers.length === 0) {
|
|
18112
|
+
continue;
|
|
18113
|
+
}
|
|
18114
|
+
process.stdout.write(`
|
|
18115
|
+
Answers of ${request.id}:
|
|
18116
|
+
`);
|
|
18117
|
+
for (const answer of request.answers) {
|
|
18118
|
+
process.stdout.write(` ${answer.question}
|
|
18119
|
+
${answer.answer}
|
|
18120
|
+
`);
|
|
18121
|
+
}
|
|
18122
|
+
}
|
|
18123
|
+
});
|
|
18124
|
+
});
|
|
18125
|
+
joinRequests.command("approve").description(
|
|
18126
|
+
`Approve requests \u2014 runs the normal join (welcome e-mail with the login link, welcome DM, notifications). 1-${COMMUNITY_JOIN_APPROVE_MAX_IDS} ids per call; repeats change nothing. Requires --confirm`
|
|
18127
|
+
).argument("<communityId>", "community id").argument("<requestIds...>", "request ids (from `communities join-requests list`)").option("--confirm", "required \u2014 acknowledge that the people join and get the welcome e-mail").action(
|
|
18128
|
+
async (communityId, requestIds, options, command) => {
|
|
18129
|
+
const flags = getGlobalFlags(command);
|
|
18130
|
+
assertIdCount(requestIds, COMMUNITY_JOIN_APPROVE_MAX_IDS);
|
|
18131
|
+
requireConfirm5(
|
|
18132
|
+
options.confirm,
|
|
18133
|
+
`approve ${requestIds.length} join request(s) (they join and get the welcome e-mail)`
|
|
18134
|
+
);
|
|
18135
|
+
const result = await clientFor(flags).approveCommunityJoinRequests(communityId, requestIds);
|
|
18136
|
+
emit(flags, result, () => printJoinDecisions(result));
|
|
18137
|
+
}
|
|
18138
|
+
);
|
|
18139
|
+
joinRequests.command("decline").description(
|
|
18140
|
+
`Decline requests quietly (a neutral e-mail, no reason given). 1-${COMMUNITY_JOIN_DECLINE_MAX_IDS} ids per call; repeats change nothing. Requires --confirm`
|
|
18141
|
+
).argument("<communityId>", "community id").argument("<requestIds...>", "request ids (from `communities join-requests list`)").option("--confirm", "required \u2014 acknowledge that the people are declined and e-mailed").action(
|
|
18142
|
+
async (communityId, requestIds, options, command) => {
|
|
18143
|
+
const flags = getGlobalFlags(command);
|
|
18144
|
+
assertIdCount(requestIds, COMMUNITY_JOIN_DECLINE_MAX_IDS);
|
|
18145
|
+
requireConfirm5(options.confirm, `decline ${requestIds.length} join request(s)`);
|
|
18146
|
+
const result = await clientFor(flags).declineCommunityJoinRequests(communityId, requestIds);
|
|
18147
|
+
emit(flags, result, () => printJoinDecisions(result));
|
|
18148
|
+
}
|
|
18149
|
+
);
|
|
18150
|
+
const digest = communities2.command("digest").description(
|
|
18151
|
+
"The weekly digest e-mail to members: nothing goes out until the owner or an admin approves the week (Thursday 09:00 send, closes at the end of Thursday)"
|
|
18152
|
+
);
|
|
18153
|
+
digest.command("list").description("This week's draft with the preview of what members get, then the earlier weeks").argument("<communityId>", "community id").action(async (communityId, _, command) => {
|
|
18154
|
+
const flags = getGlobalFlags(command);
|
|
18155
|
+
const result = await clientFor(flags).listCommunityDigestDrafts(communityId);
|
|
18156
|
+
emit(flags, result, () => {
|
|
18157
|
+
if (!result.digestEnabled) {
|
|
18158
|
+
process.stdout.write(
|
|
18159
|
+
"The weekly digest is OFF for this community \u2014 no drafts are built. Turn it on in the community settings.\n"
|
|
18160
|
+
);
|
|
18161
|
+
}
|
|
18162
|
+
if (result.current) {
|
|
18163
|
+
printDigestDraft(result.current);
|
|
18164
|
+
} else {
|
|
18165
|
+
process.stdout.write("No draft this week.\n");
|
|
18166
|
+
}
|
|
18167
|
+
if (result.history.length > 0) {
|
|
18168
|
+
process.stdout.write("\nEarlier weeks:\n");
|
|
18169
|
+
printTable(result.history.map(digestRow), DIGEST_COLUMNS);
|
|
18170
|
+
}
|
|
18171
|
+
});
|
|
18172
|
+
});
|
|
18173
|
+
const decideDigest = (decision, what) => async (communityId, options, command) => {
|
|
18174
|
+
const flags = getGlobalFlags(command);
|
|
18175
|
+
requireConfirm5(options.confirm, `${what} of community ${communityId}`);
|
|
18176
|
+
const result = await clientFor(flags).decideCommunityDigestDraft(communityId, {
|
|
18177
|
+
decision,
|
|
18178
|
+
...options.week !== void 0 ? { weekKey: options.week } : {}
|
|
18179
|
+
});
|
|
18180
|
+
emit(flags, result, () => {
|
|
18181
|
+
process.stdout.write(`${DIGEST_OUTCOME_LABELS[result.outcome]}
|
|
18182
|
+
`);
|
|
18183
|
+
printDigestDraft(result.draft);
|
|
18184
|
+
});
|
|
18185
|
+
};
|
|
18186
|
+
digest.command("approve").description(
|
|
18187
|
+
"Approve the week \u2014 the digest goes out Thursday 09:00 (at once when approved later that Thursday) to every member who gets it. Owner / admin only; repeats never send twice. Requires --confirm"
|
|
18188
|
+
).argument("<communityId>", "community id").option("--week <weekKey>", "the week (from `communities digest list`); default this week").option("--confirm", "required \u2014 acknowledge that the digest is e-mailed to the members").action(decideDigest("approve", "approve the weekly digest (members are e-mailed)"));
|
|
18189
|
+
digest.command("skip").description(
|
|
18190
|
+
"Skip the week \u2014 no digest goes out (refused once the send has started). Owner / admin only. Requires --confirm"
|
|
18191
|
+
).argument("<communityId>", "community id").option("--week <weekKey>", "the week (from `communities digest list`); default this week").option("--confirm", "required \u2014 acknowledge that no digest goes out that week").action(decideDigest("skip", "skip the weekly digest"));
|
|
17029
18192
|
const moderation = communities2.command("moderation").description("Reported posts and comments \u2014 review the queue and resolve reports (staff)");
|
|
17030
18193
|
moderation.command("queue").description("Show pending reports plus the most recently resolved ones").argument("<communityId>", "community id").action(async (communityId, _, command) => {
|
|
17031
18194
|
const flags = getGlobalFlags(command);
|
|
@@ -17081,10 +18244,16 @@ var SETTINGS_PATCH_KEYS = [
|
|
|
17081
18244
|
"eventsEnabled",
|
|
17082
18245
|
"eventAnnouncements",
|
|
17083
18246
|
"directMessagesEnabled",
|
|
18247
|
+
"membersCanCreateGroups",
|
|
18248
|
+
"channelTypeIcons",
|
|
18249
|
+
"channelGroupsCollapsed",
|
|
17084
18250
|
"gamification",
|
|
17085
18251
|
"moderation",
|
|
17086
18252
|
"assistant",
|
|
17087
|
-
"digest"
|
|
18253
|
+
"digest",
|
|
18254
|
+
"welcomeMessage",
|
|
18255
|
+
"joinQuestions",
|
|
18256
|
+
"joinApproval"
|
|
17088
18257
|
];
|
|
17089
18258
|
function parseSettingsPatch(value) {
|
|
17090
18259
|
const parsed = parseJsonObject(value, "--patch");
|
|
@@ -17110,7 +18279,100 @@ function parseJsonObject(value, flag) {
|
|
|
17110
18279
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
17111
18280
|
throw new Error(`${flag} must be a JSON object.`);
|
|
17112
18281
|
}
|
|
17113
|
-
return parsed;
|
|
18282
|
+
return parsed;
|
|
18283
|
+
}
|
|
18284
|
+
|
|
18285
|
+
// src/commands/consultations/index.ts
|
|
18286
|
+
function assertBookingStatus(value) {
|
|
18287
|
+
const match = CONSULTATION_BOOKING_STATUSES.find((status) => status === value);
|
|
18288
|
+
if (match === void 0) {
|
|
18289
|
+
throw new Error(
|
|
18290
|
+
`Invalid --status: ${value}. Use one of: ${CONSULTATION_BOOKING_STATUSES.join(", ")}`
|
|
18291
|
+
);
|
|
18292
|
+
}
|
|
18293
|
+
return match;
|
|
18294
|
+
}
|
|
18295
|
+
function assertDate(value, flag) {
|
|
18296
|
+
if (Number.isNaN(Date.parse(value))) {
|
|
18297
|
+
throw new Error(`Invalid ${flag}: ${value} (expected an ISO date, e.g. 2026-11-01)`);
|
|
18298
|
+
}
|
|
18299
|
+
return value;
|
|
18300
|
+
}
|
|
18301
|
+
function printNextPage(nextCursor) {
|
|
18302
|
+
if (nextCursor) process.stdout.write(`
|
|
18303
|
+
Next page: --cursor ${nextCursor}
|
|
18304
|
+
`);
|
|
18305
|
+
}
|
|
18306
|
+
function registerConsultationsCommands(program2) {
|
|
18307
|
+
const consultations = program2.command("consultations").description(
|
|
18308
|
+
"Consultations booked in the Zanfia calendar: booked sessions and buyers who never booked"
|
|
18309
|
+
);
|
|
18310
|
+
consultations.command("bookings").description(
|
|
18311
|
+
"Booked sessions of every consultation (or --product) starting in a date range, earliest first, with the client name and e-mail"
|
|
18312
|
+
).option("--product <productId>", "only this consultation product").option("--from <date>", "range start (ISO or YYYY-MM-DD; default today 00:00 UTC)").option("--to <date>", "range end, exclusive (default from + 30 days; at most 366 days)").option("--status <status>", CONSULTATION_BOOKING_STATUSES.join("|")).option(
|
|
18313
|
+
"--limit <n>",
|
|
18314
|
+
"page size (default 50, max 100)",
|
|
18315
|
+
parseNumberOption("--limit", NumberFormat.Integer)
|
|
18316
|
+
).option("--cursor <cursor>", "cursor from a previous page").action(
|
|
18317
|
+
async (options, command) => {
|
|
18318
|
+
const flags = getGlobalFlags(command);
|
|
18319
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18320
|
+
const page = await client.listConsultationBookings({
|
|
18321
|
+
productId: options.product,
|
|
18322
|
+
from: options.from !== void 0 ? assertDate(options.from, "--from") : void 0,
|
|
18323
|
+
to: options.to !== void 0 ? assertDate(options.to, "--to") : void 0,
|
|
18324
|
+
status: options.status !== void 0 ? assertBookingStatus(options.status) : void 0,
|
|
18325
|
+
limit: options.limit,
|
|
18326
|
+
cursor: options.cursor
|
|
18327
|
+
});
|
|
18328
|
+
emit(flags, page, () => {
|
|
18329
|
+
process.stdout.write(`Sessions starting ${page.from} .. ${page.to}
|
|
18330
|
+
`);
|
|
18331
|
+
printTable(
|
|
18332
|
+
page.items.map((b) => ({
|
|
18333
|
+
bookingId: b.bookingId,
|
|
18334
|
+
product: b.productName ?? b.productId,
|
|
18335
|
+
start: b.startAt,
|
|
18336
|
+
minutes: b.durationMinutes,
|
|
18337
|
+
client: b.clientName ? `${b.clientName} <${b.clientEmail}>` : b.clientEmail,
|
|
18338
|
+
status: b.status,
|
|
18339
|
+
location: b.location
|
|
18340
|
+
})),
|
|
18341
|
+
["bookingId", "product", "start", "minutes", "client", "status", "location"]
|
|
18342
|
+
);
|
|
18343
|
+
printNextPage(page.nextCursor);
|
|
18344
|
+
});
|
|
18345
|
+
}
|
|
18346
|
+
);
|
|
18347
|
+
consultations.command("without-booking").description(
|
|
18348
|
+
"Buyers with active access to a consultation (every one, or --product) who never booked a session (a cancelled booking does not count). External-link consultations are skipped"
|
|
18349
|
+
).option("--product <productId>", "only this consultation product").option(
|
|
18350
|
+
"--limit <n>",
|
|
18351
|
+
"page size (default 50, max 100)",
|
|
18352
|
+
parseNumberOption("--limit", NumberFormat.Integer)
|
|
18353
|
+
).option("--cursor <cursor>", "cursor from a previous page").action(
|
|
18354
|
+
async (options, command) => {
|
|
18355
|
+
const flags = getGlobalFlags(command);
|
|
18356
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18357
|
+
const page = await client.listConsultationClientsWithoutBooking({
|
|
18358
|
+
productId: options.product,
|
|
18359
|
+
limit: options.limit,
|
|
18360
|
+
cursor: options.cursor
|
|
18361
|
+
});
|
|
18362
|
+
emit(flags, page, () => {
|
|
18363
|
+
printTable(
|
|
18364
|
+
page.items.map((c) => ({
|
|
18365
|
+
clientId: c.clientId,
|
|
18366
|
+
client: c.clientName ? `${c.clientName} <${c.clientEmail}>` : c.clientEmail,
|
|
18367
|
+
product: c.productName,
|
|
18368
|
+
accessSince: c.accessGrantedAt ?? ""
|
|
18369
|
+
})),
|
|
18370
|
+
["clientId", "client", "product", "accessSince"]
|
|
18371
|
+
);
|
|
18372
|
+
printNextPage(page.nextCursor);
|
|
18373
|
+
});
|
|
18374
|
+
}
|
|
18375
|
+
);
|
|
17114
18376
|
}
|
|
17115
18377
|
|
|
17116
18378
|
// src/commands/conversations/index.ts
|
|
@@ -17516,6 +18778,18 @@ function assertStatus(status) {
|
|
|
17516
18778
|
throw new Error(`Invalid status "${status}". Use one of: ${LESSON_STATUSES.join(", ")}`);
|
|
17517
18779
|
}
|
|
17518
18780
|
}
|
|
18781
|
+
function assertOneOf2(value, allowed, flag) {
|
|
18782
|
+
const match = allowed.find((candidate) => candidate === value);
|
|
18783
|
+
if (match === void 0) {
|
|
18784
|
+
throw new Error(`Invalid ${flag}: ${value}. Use one of: ${allowed.join(", ")}`);
|
|
18785
|
+
}
|
|
18786
|
+
return match;
|
|
18787
|
+
}
|
|
18788
|
+
var QUESTION_EXCERPT_LENGTH = 60;
|
|
18789
|
+
function excerpt(content) {
|
|
18790
|
+
const line = content.replace(/\s+/g, " ").trim();
|
|
18791
|
+
return line.length > QUESTION_EXCERPT_LENGTH ? `${line.slice(0, QUESTION_EXCERPT_LENGTH - 1)}\u2026` : line;
|
|
18792
|
+
}
|
|
17519
18793
|
function collectRepeatable3(value, previous) {
|
|
17520
18794
|
return [...previous, value];
|
|
17521
18795
|
}
|
|
@@ -18301,6 +19575,110 @@ ${NOT_SAVED_NOTE} --moments=${options.out ?? "<file>"} --confirm\`.
|
|
|
18301
19575
|
});
|
|
18302
19576
|
}
|
|
18303
19577
|
);
|
|
19578
|
+
const questions = courses.command("questions").description("Learners' questions under the lessons: read, reply as the team, hide, accept");
|
|
19579
|
+
questions.command("list").description("Questions of a course, newest first, each with its replies (private ones too)").argument("<productId>", "course product id").option("--filter <filter>", LESSON_QA_INBOX_FILTERS.join("|") + " (default all)").option("--lesson <lessonId>", "only the questions under this lesson").option("--cursor <cursor>", "cursor from a previous page").action(
|
|
19580
|
+
async (productId, options, command) => {
|
|
19581
|
+
const flags = getGlobalFlags(command);
|
|
19582
|
+
const filter = options.filter !== void 0 ? assertOneOf2(options.filter, LESSON_QA_INBOX_FILTERS, "--filter") : void 0;
|
|
19583
|
+
const result = await clientFor2(flags).listCourseQuestions(productId, {
|
|
19584
|
+
filter,
|
|
19585
|
+
lessonId: options.lesson,
|
|
19586
|
+
cursor: options.cursor
|
|
19587
|
+
});
|
|
19588
|
+
emit(flags, result, () => {
|
|
19589
|
+
printTable(
|
|
19590
|
+
result.questions.map((q) => ({
|
|
19591
|
+
id: q.id,
|
|
19592
|
+
lesson: q.lessonId,
|
|
19593
|
+
by: q.author.displayName,
|
|
19594
|
+
visibility: q.visibility,
|
|
19595
|
+
status: q.status,
|
|
19596
|
+
replies: q.repliesCount,
|
|
19597
|
+
answered: q.staffReplied ? "yes" : "no",
|
|
19598
|
+
accepted: q.acceptedReplyId ?? "",
|
|
19599
|
+
question: excerpt(q.content)
|
|
19600
|
+
})),
|
|
19601
|
+
[
|
|
19602
|
+
"id",
|
|
19603
|
+
"lesson",
|
|
19604
|
+
"by",
|
|
19605
|
+
"visibility",
|
|
19606
|
+
"status",
|
|
19607
|
+
"replies",
|
|
19608
|
+
"answered",
|
|
19609
|
+
"accepted",
|
|
19610
|
+
"question"
|
|
19611
|
+
]
|
|
19612
|
+
);
|
|
19613
|
+
if (result.nextCursor) {
|
|
19614
|
+
process.stdout.write(`
|
|
19615
|
+
Next page: --cursor ${result.nextCursor}
|
|
19616
|
+
`);
|
|
19617
|
+
}
|
|
19618
|
+
});
|
|
19619
|
+
}
|
|
19620
|
+
);
|
|
19621
|
+
questions.command("reply").description(
|
|
19622
|
+
"Reply to a question as the course team \u2014 the learner sees it under the lesson and is notified. Requires --confirm"
|
|
19623
|
+
).argument("<productId>", "course product id").argument("<questionId>", "question id (from `courses questions list`)").option("--content <text>", "the reply text").option("--content-file <path>", "read the reply text from a file").option("--confirm", "required \u2014 acknowledge that the learner sees the reply").action(
|
|
19624
|
+
async (productId, questionId, options, command) => {
|
|
19625
|
+
const flags = getGlobalFlags(command);
|
|
19626
|
+
if (options.content !== void 0 && options.contentFile !== void 0) {
|
|
19627
|
+
throw new Error("Use either --content or --content-file, not both.");
|
|
19628
|
+
}
|
|
19629
|
+
const content = options.contentFile !== void 0 ? readFileSync10(options.contentFile, "utf8") : options.content;
|
|
19630
|
+
if (content === void 0 || !content.trim()) {
|
|
19631
|
+
throw new Error("Pass the reply with --content or --content-file.");
|
|
19632
|
+
}
|
|
19633
|
+
requireConfirm(options.confirm, `reply to question ${questionId} (the learner sees it)`);
|
|
19634
|
+
const result = await clientFor2(flags).replyToCourseQuestion(productId, questionId, content);
|
|
19635
|
+
emit(flags, result, () => {
|
|
19636
|
+
process.stdout.write(`Replied to question ${questionId} (reply ${result.reply.id}).
|
|
19637
|
+
`);
|
|
19638
|
+
});
|
|
19639
|
+
}
|
|
19640
|
+
);
|
|
19641
|
+
questions.command("status").description(
|
|
19642
|
+
"Hide a question or a reply from learners (the team still sees it), or publish it again. Requires --confirm"
|
|
19643
|
+
).argument("<productId>", "course product id").argument("<entryId>", "question or reply id").requiredOption("--status <status>", SETTABLE_LESSON_QA_ENTRY_STATUSES.join("|")).option("--confirm", "required \u2014 acknowledge the change learners see").action(
|
|
19644
|
+
async (productId, entryId, options, command) => {
|
|
19645
|
+
const flags = getGlobalFlags(command);
|
|
19646
|
+
const status = assertOneOf2(
|
|
19647
|
+
options.status,
|
|
19648
|
+
SETTABLE_LESSON_QA_ENTRY_STATUSES,
|
|
19649
|
+
"--status"
|
|
19650
|
+
);
|
|
19651
|
+
requireConfirm(options.confirm, `set ${entryId} to ${status}`);
|
|
19652
|
+
const result = await clientFor2(flags).setCourseQuestionStatus(productId, entryId, status);
|
|
19653
|
+
emit(flags, result, () => {
|
|
19654
|
+
process.stdout.write(`${result.entry.id} is now ${result.entry.status}.
|
|
19655
|
+
`);
|
|
19656
|
+
});
|
|
19657
|
+
}
|
|
19658
|
+
);
|
|
19659
|
+
questions.command("accept").description(
|
|
19660
|
+
"Mark a reply as the answer to its question (--clear removes the mark). Requires --confirm"
|
|
19661
|
+
).argument("<productId>", "course product id").argument("<questionId>", "question id").option("--reply <replyId>", "the reply to mark as the answer").option("--clear", "remove the answer mark").option("--confirm", "required \u2014 acknowledge the change learners see").action(
|
|
19662
|
+
async (productId, questionId, options, command) => {
|
|
19663
|
+
const flags = getGlobalFlags(command);
|
|
19664
|
+
if (options.reply === void 0 === (options.clear !== true)) {
|
|
19665
|
+
throw new Error("Pass exactly one of --reply <replyId> or --clear.");
|
|
19666
|
+
}
|
|
19667
|
+
const replyId = options.reply ?? null;
|
|
19668
|
+
requireConfirm(
|
|
19669
|
+
options.confirm,
|
|
19670
|
+
replyId ? `mark reply ${replyId} as the answer` : `clear the answer of question ${questionId}`
|
|
19671
|
+
);
|
|
19672
|
+
const result = await clientFor2(flags).acceptCourseAnswer(productId, questionId, replyId);
|
|
19673
|
+
emit(flags, result, () => {
|
|
19674
|
+
process.stdout.write(
|
|
19675
|
+
result.acceptedReplyId ? `Reply ${result.acceptedReplyId} is the answer to question ${result.questionId}.
|
|
19676
|
+
` : `Question ${result.questionId} has no accepted answer now.
|
|
19677
|
+
`
|
|
19678
|
+
);
|
|
19679
|
+
});
|
|
19680
|
+
}
|
|
19681
|
+
);
|
|
18304
19682
|
}
|
|
18305
19683
|
|
|
18306
19684
|
// src/commands/discounts/index.ts
|
|
@@ -18728,6 +20106,40 @@ function registerEmailSenderCommands(emails) {
|
|
|
18728
20106
|
);
|
|
18729
20107
|
});
|
|
18730
20108
|
});
|
|
20109
|
+
sender.command("warmups").description(
|
|
20110
|
+
"Each sending domain's warm-up (KI#1651): new domains send campaigns in hourly batches for a few days; shows the state, why a domain counts as warmed and the cap per hour"
|
|
20111
|
+
).action(async (_, command) => {
|
|
20112
|
+
const flags = getGlobalFlags(command);
|
|
20113
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
20114
|
+
const result = await client.getSenderDomainWarmups();
|
|
20115
|
+
emit(flags, result, () => printWarmups(result.warmups));
|
|
20116
|
+
});
|
|
20117
|
+
sender.command("declare-warmed").description(
|
|
20118
|
+
"\u201EWysy\u0142am z tej domeny w innym narz\u0119dziu\u201D: switch the warm-up of a verified domain off at once, recorded under your name. Requires --confirm and the campaigns.manage permission"
|
|
20119
|
+
).argument("<domain>", "the sending domain (from `emails sender warmups`)").option("--confirm", "required \u2014 acknowledge campaigns go out from it without batches").action(async (domain, options, command) => {
|
|
20120
|
+
const flags = getGlobalFlags(command);
|
|
20121
|
+
requireConfirm(options.confirm, `switch off the warm-up of ${domain}`);
|
|
20122
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
20123
|
+
const result = await client.declareSenderDomainWarmed(domain);
|
|
20124
|
+
emit(flags, result, () => {
|
|
20125
|
+
process.stdout.write(`Warm-up of ${result.warmup.domain} is off.
|
|
20126
|
+
`);
|
|
20127
|
+
printWarmups([result.warmup]);
|
|
20128
|
+
});
|
|
20129
|
+
});
|
|
20130
|
+
}
|
|
20131
|
+
function printWarmups(warmups) {
|
|
20132
|
+
printTable(
|
|
20133
|
+
warmups.map((warmup) => ({
|
|
20134
|
+
domain: warmup.domain,
|
|
20135
|
+
state: warmup.state,
|
|
20136
|
+
reason: warmup.warmedReason ?? "",
|
|
20137
|
+
day: warmup.warmupDay ?? "",
|
|
20138
|
+
capPerHour: warmup.hourlyCap ?? "",
|
|
20139
|
+
canDeclare: warmup.canDeclare ? "yes" : ""
|
|
20140
|
+
})),
|
|
20141
|
+
["domain", "state", "reason", "day", "capPerHour", "canDeclare"]
|
|
20142
|
+
);
|
|
18731
20143
|
}
|
|
18732
20144
|
|
|
18733
20145
|
// src/commands/emails/index.ts
|
|
@@ -19669,16 +21081,33 @@ function registerFormsCommands(program2) {
|
|
|
19669
21081
|
emit(flags, result, () => {
|
|
19670
21082
|
printTable(
|
|
19671
21083
|
result.submissions.map((s) => ({
|
|
21084
|
+
id: s.id,
|
|
19672
21085
|
email: s.email,
|
|
19673
21086
|
firstName: s.firstName ?? "",
|
|
19674
21087
|
clientId: s.clientId ?? "",
|
|
19675
21088
|
language: s.language ?? "",
|
|
19676
21089
|
createdAt: s.createdAt
|
|
19677
21090
|
})),
|
|
19678
|
-
["email", "firstName", "clientId", "language", "createdAt"]
|
|
21091
|
+
["id", "email", "firstName", "clientId", "language", "createdAt"]
|
|
19679
21092
|
);
|
|
19680
21093
|
});
|
|
19681
21094
|
});
|
|
21095
|
+
forms.command("room-link").description(
|
|
21096
|
+
"E-mail a webinar registrant their personal room link again (to THEIR address; one per sign-up per 10 minutes). Requires --confirm"
|
|
21097
|
+
).argument("<formId>", "the webinar sign-up form id").argument("<submissionId>", "the submission id (from `forms submissions`)").option("--confirm", "required \u2014 acknowledge the registrant is e-mailed").action(
|
|
21098
|
+
async (formId, submissionId, options, command) => {
|
|
21099
|
+
const flags = getGlobalFlags(command);
|
|
21100
|
+
requireConfirm(options.confirm, `e-mail the room link of submission ${submissionId}`);
|
|
21101
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
21102
|
+
const result = await client.resendFormSubmissionRoomLink(formId, submissionId);
|
|
21103
|
+
emit(
|
|
21104
|
+
flags,
|
|
21105
|
+
result,
|
|
21106
|
+
() => process.stdout.write(`Room link e-mailed again for submission ${submissionId}.
|
|
21107
|
+
`)
|
|
21108
|
+
);
|
|
21109
|
+
}
|
|
21110
|
+
);
|
|
19682
21111
|
}
|
|
19683
21112
|
|
|
19684
21113
|
// src/commands/funnels/index.ts
|
|
@@ -19701,6 +21130,7 @@ var FUNNEL_TEMPLATE_IDS = [
|
|
|
19701
21130
|
"challenge"
|
|
19702
21131
|
];
|
|
19703
21132
|
var EVENT_MODES = ["live", "on_demand"];
|
|
21133
|
+
var OFFER_PAGE_LAYOUTS = ["digital", "physical"];
|
|
19704
21134
|
var SCHEDULER_UNAVAILABLE_STATUS2 = 503;
|
|
19705
21135
|
var FUNNEL_NOT_READY_STATUS = 409;
|
|
19706
21136
|
function requireConfirm6(confirm, what) {
|
|
@@ -20020,6 +21450,37 @@ function registerFunnelsCommands(program2) {
|
|
|
20020
21450
|
emit(flags, result, () => {
|
|
20021
21451
|
process.stdout.write(
|
|
20022
21452
|
`Generation queued for step ${stepId} (run ${result.runId}). It takes 1-5 minutes; run "funnels get ${id}" to see the page once it lands.
|
|
21453
|
+
`
|
|
21454
|
+
);
|
|
21455
|
+
});
|
|
21456
|
+
}
|
|
21457
|
+
);
|
|
21458
|
+
funnels.command("add-offer-page").description(
|
|
21459
|
+
'Add a designed one-time-offer page right AFTER a signup page step (the dashboard canvas "Add step > Offer"): a template in the signup page colors with a 30-minute timer, "yes" pays for --checkout on the page, "no thanks" goes to the step that followed the signup. The signup redirects to it and the funnel gets the step (in place of the free checkout\'s empty OTO step right after the signup, when there is one). Created as a DRAFT page (edit it, then `pages publish`); template copy, no AI credits. A live funnel is refused: switch it to draft first.'
|
|
21460
|
+
).argument("<id>", "funnel id").requiredOption(
|
|
21461
|
+
"--checkout <checkoutId>",
|
|
21462
|
+
"the PAID checkout the offer sells, with a slug (active or inactive; never a free one)"
|
|
21463
|
+
).requiredOption(
|
|
21464
|
+
"--layout <layout>",
|
|
21465
|
+
`${OFFER_PAGE_LAYOUTS.join(" | ")} (digital = ebook / mini course, physical = free + shipping)`
|
|
21466
|
+
).option(
|
|
21467
|
+
"--after <n|id>",
|
|
21468
|
+
"the signup step to follow: its 1-based position (`funnels get`) or stepId; default the first"
|
|
21469
|
+
).option("--lang <en|pl>", "language of the template copy (default en)").action(
|
|
21470
|
+
async (id, options, command) => {
|
|
21471
|
+
const flags = getGlobalFlags(command);
|
|
21472
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
21473
|
+
const afterStepId = options.after !== void 0 ? await resolveStepId(client, id, options.after) : void 0;
|
|
21474
|
+
const input = {
|
|
21475
|
+
checkoutId: options.checkout,
|
|
21476
|
+
layout: parseChoice3(options.layout, OFFER_PAGE_LAYOUTS, "--layout"),
|
|
21477
|
+
...afterStepId ? { afterStepId } : {},
|
|
21478
|
+
...options.lang !== void 0 ? { lang: parseChoice3(options.lang, FUNNEL_LANGS, "--lang") } : {}
|
|
21479
|
+
};
|
|
21480
|
+
const result = await client.addFunnelOfferPage(id, input);
|
|
21481
|
+
emit(flags, result, () => {
|
|
21482
|
+
process.stdout.write(
|
|
21483
|
+
`Added the DRAFT offer page /${result.slug} (page ${result.pageId}, step ${result.stepId}) after step ${result.afterStepId}. The body copy and the crossed-out price are template samples (the button and price spans show the checkout's real price): edit the copy, then publish it.
|
|
20023
21484
|
`
|
|
20024
21485
|
);
|
|
20025
21486
|
});
|
|
@@ -20046,7 +21507,7 @@ function registerFunnelsCommands(program2) {
|
|
|
20046
21507
|
});
|
|
20047
21508
|
});
|
|
20048
21509
|
funnels.command("from-template").description(
|
|
20049
|
-
"Create a funnel from a catalog template (`funnels templates`) AND AI-generate its page steps in one go \u2014 opt-in / sales / thank-you pages are queued as background generations (202; consumes AI credits, 1-5 minutes each). The template\u2019s product requirement must be met (free or paid checkout on the product), else 409. Webinar template: pass the event fields (live; --join-url for a ClickMeeting / Zoom link) or --event-mode on_demand, or --live-room <roomId> to run it on a webinar that plays a recording (then --product is optional; needs the live_rooms plan feature). Requires --confirm."
|
|
21510
|
+
"Create a funnel from a catalog template (`funnels templates`) AND AI-generate its page steps in one go \u2014 opt-in / sales / thank-you pages are queued as background generations (202; consumes AI credits, 1-5 minutes each). The template\u2019s product requirement must be met (free or paid checkout on the product), else 409. Webinar template: pass the event fields (live; --join-url for a ClickMeeting / Zoom link) or --event-mode on_demand, or --live-room <roomId> to run it on a webinar that plays a recording or an external one (`live-rooms create`; then --product is optional; needs the live_rooms plan feature). Requires --confirm."
|
|
20050
21511
|
).argument("<templateId>", `template id (${FUNNEL_TEMPLATE_IDS.join(" | ")})`).option(
|
|
20051
21512
|
"--product <productId>",
|
|
20052
21513
|
"the product the funnel sells or delivers (required, except with --live-room)"
|
|
@@ -20058,7 +21519,7 @@ function registerFunnelsCommands(program2) {
|
|
|
20058
21519
|
"webinar (live): the meeting link (ClickMeeting, Zoom, Teams, Meet) the pages and reminders carry"
|
|
20059
21520
|
).option(
|
|
20060
21521
|
"--live-room <roomId>",
|
|
20061
|
-
"webinar: a webinar (`live-rooms list`) that plays a recording; its sessions are the event"
|
|
21522
|
+
"webinar: a webinar (`live-rooms list`) that plays a recording, or an external one (`live-rooms create`); its sessions are the event"
|
|
20062
21523
|
).option(
|
|
20063
21524
|
"--page-template <id>",
|
|
20064
21525
|
"design template for every generated page (a page-template id, e.g. course-fresh); thank-you steps take the companion"
|
|
@@ -21294,6 +22755,17 @@ to refresh it.
|
|
|
21294
22755
|
}
|
|
21295
22756
|
|
|
21296
22757
|
// src/commands/liveRooms/index.ts
|
|
22758
|
+
var STUDIO_SCENE_LAYOUTS = ["solo", "presentation", "grid"];
|
|
22759
|
+
function parseSceneLayout(value) {
|
|
22760
|
+
const layout = STUDIO_SCENE_LAYOUTS.find((candidate) => candidate === value);
|
|
22761
|
+
if (!layout) throw new Error(`--layout must be one of ${STUDIO_SCENE_LAYOUTS.join(", ")}`);
|
|
22762
|
+
return layout;
|
|
22763
|
+
}
|
|
22764
|
+
function parseOnOff4(value) {
|
|
22765
|
+
if (value === "on") return true;
|
|
22766
|
+
if (value === "off") return false;
|
|
22767
|
+
throw new Error(`expected on or off, got "${value}"`);
|
|
22768
|
+
}
|
|
21297
22769
|
var minutes = (seconds) => `${Math.round(seconds / 60)} min`;
|
|
21298
22770
|
var percent = (part, whole) => whole > 0 ? `${Math.round(part / whole * 100)}%` : "-";
|
|
21299
22771
|
var countOrDash = (value) => value === null || value === void 0 ? "-" : String(value);
|
|
@@ -21319,9 +22791,33 @@ function describeSchedule(room) {
|
|
|
21319
22791
|
}
|
|
21320
22792
|
return `every ${room.schedule.everyMinutes} min${until}${exclusions}`;
|
|
21321
22793
|
}
|
|
22794
|
+
function isMeetingLink(value) {
|
|
22795
|
+
try {
|
|
22796
|
+
const url = new URL(value);
|
|
22797
|
+
return url.protocol === "https:" && url.hostname !== "";
|
|
22798
|
+
} catch {
|
|
22799
|
+
return false;
|
|
22800
|
+
}
|
|
22801
|
+
}
|
|
22802
|
+
function printRoom(result) {
|
|
22803
|
+
const { room } = result;
|
|
22804
|
+
printTable(
|
|
22805
|
+
[
|
|
22806
|
+
{
|
|
22807
|
+
id: room.id,
|
|
22808
|
+
title: room.title,
|
|
22809
|
+
startAt: room.startAt,
|
|
22810
|
+
endAt: room.endAt,
|
|
22811
|
+
link: room.externalUrl ?? "",
|
|
22812
|
+
replay: room.replay ? room.replay.name ?? room.replay.mediaId : ""
|
|
22813
|
+
}
|
|
22814
|
+
],
|
|
22815
|
+
["id", "title", "startAt", "endAt", "link", "replay"]
|
|
22816
|
+
);
|
|
22817
|
+
}
|
|
21322
22818
|
function registerLiveRoomsCommands(program2) {
|
|
21323
22819
|
const liveRooms = program2.command("live-rooms").description(
|
|
21324
|
-
"Webinars (incl. rolling autowebinar sessions) \u2014 list and
|
|
22820
|
+
"Webinars (incl. rolling autowebinar sessions) \u2014 list, attendance, and an external webinar (ClickMeeting, Zoom, Teams, Meet) with its recording; the rest in the dashboard"
|
|
21325
22821
|
);
|
|
21326
22822
|
liveRooms.command("list").description("List webinars (latest start first)").action(async (_, command) => {
|
|
21327
22823
|
const flags = getGlobalFlags(command);
|
|
@@ -21342,6 +22838,131 @@ function registerLiveRoomsCommands(program2) {
|
|
|
21342
22838
|
);
|
|
21343
22839
|
});
|
|
21344
22840
|
});
|
|
22841
|
+
liveRooms.command("create").description(
|
|
22842
|
+
"An external webinar (ClickMeeting, Zoom, Teams, Meet): the webinar page counts down, from 15 min before the start \u201EWejd\u017A na webinar\u201D opens the link and counts the person as present. Then: funnels from-template webinar --live-room <id>"
|
|
22843
|
+
).requiredOption("--title <title>", "webinar title").requiredOption("--start <iso>", "start, ISO 8601 (e.g. 2026-11-27T19:00:00+01:00)").requiredOption("--join-url <url>", "the meeting link (https)").option("--duration <minutes>", "length in minutes (5-720)", "90").option("--timezone <zone>", "IANA zone the webinar is shown in", "Europe/Warsaw").option("--description <text>", "one or two sentences about it").action(
|
|
22844
|
+
async (options, command) => {
|
|
22845
|
+
const flags = getGlobalFlags(command);
|
|
22846
|
+
const durationMinutes = Number(options.duration);
|
|
22847
|
+
if (!Number.isInteger(durationMinutes) || durationMinutes < 5 || durationMinutes > 720) {
|
|
22848
|
+
throw new Error("--duration must be a whole number of minutes from 5 to 720");
|
|
22849
|
+
}
|
|
22850
|
+
if (Number.isNaN(Date.parse(options.start))) {
|
|
22851
|
+
throw new Error("--start must be an ISO 8601 date-time");
|
|
22852
|
+
}
|
|
22853
|
+
if (!isMeetingLink(options.joinUrl)) {
|
|
22854
|
+
throw new Error("--join-url must be an https link");
|
|
22855
|
+
}
|
|
22856
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
22857
|
+
const result = await client.createExternalWebinar({
|
|
22858
|
+
title: options.title,
|
|
22859
|
+
...options.description !== void 0 ? { description: options.description } : {},
|
|
22860
|
+
startAt: options.start,
|
|
22861
|
+
durationMinutes,
|
|
22862
|
+
timezone: options.timezone,
|
|
22863
|
+
url: options.joinUrl
|
|
22864
|
+
});
|
|
22865
|
+
emit(flags, result, () => {
|
|
22866
|
+
printRoom(result);
|
|
22867
|
+
process.stdout.write(
|
|
22868
|
+
`
|
|
22869
|
+
Next: zanfia funnels from-template webinar --live-room ${result.room.id} --lang pl --confirm (the sign-up page, thank-you page and reminders on it).
|
|
22870
|
+
`
|
|
22871
|
+
);
|
|
22872
|
+
});
|
|
22873
|
+
}
|
|
22874
|
+
);
|
|
22875
|
+
liveRooms.command("replay").description(
|
|
22876
|
+
"An external webinar's recording, attached after it ran (a ready Media Library video, e.g. from media upload): the webinar page plays it as the replay"
|
|
22877
|
+
).argument("<roomId>", "webinar id (from live-rooms list)").option("--media <mediaId>", "the recording (Media Library video id)").option("--remove", "take the recording off").action(
|
|
22878
|
+
async (roomId, options, command) => {
|
|
22879
|
+
const flags = getGlobalFlags(command);
|
|
22880
|
+
if (Boolean(options.media) === Boolean(options.remove)) {
|
|
22881
|
+
throw new Error("pass exactly one of --media <mediaId> or --remove");
|
|
22882
|
+
}
|
|
22883
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
22884
|
+
const result = await client.setLiveRoomReplay(roomId, options.media ?? null);
|
|
22885
|
+
emit(flags, result, () => printRoom(result));
|
|
22886
|
+
}
|
|
22887
|
+
);
|
|
22888
|
+
liveRooms.command("scene").description(
|
|
22889
|
+
"A studio webinar's broadcast picture (studio scenes, early access): layout, banner, the name under the camera, the offer and the featured chat question on screen. Only the options you pass change"
|
|
22890
|
+
).argument("<roomId>", "studio webinar id (from live-rooms list)").option("--layout <layout>", "solo | presentation | grid").option("--banner <text>", "the banner along the top").option("--no-banner", "remove the banner").option("--name <name>", "the name under the camera (lower third)").option("--title <title>", "the line under the name").option("--no-lower-third", "remove the name under the camera").option("--offer <on|off>", "the webinar's offer as a card in the picture").option("--comment <on|off>", "the featured chat question in the picture").option("--logo <on|off>", "the logo in the corner").action(
|
|
22891
|
+
async (roomId, options, command) => {
|
|
22892
|
+
const flags = getGlobalFlags(command);
|
|
22893
|
+
const input = {
|
|
22894
|
+
...options.layout !== void 0 ? { layout: parseSceneLayout(options.layout) } : {},
|
|
22895
|
+
...options.banner === false ? { banner: null } : options.banner !== void 0 ? { banner: options.banner } : {},
|
|
22896
|
+
...options.lowerThird === false ? { lowerThird: null } : options.name !== void 0 ? { lowerThird: { name: options.name, title: options.title ?? null } } : {},
|
|
22897
|
+
...options.offer !== void 0 ? { offerOnScreen: parseOnOff4(options.offer) } : {},
|
|
22898
|
+
...options.comment !== void 0 ? { commentOnScreen: parseOnOff4(options.comment) } : {},
|
|
22899
|
+
...options.logo !== void 0 ? { showLogo: parseOnOff4(options.logo) } : {}
|
|
22900
|
+
};
|
|
22901
|
+
if (Object.keys(input).length === 0) {
|
|
22902
|
+
throw new Error("pass at least one option (e.g. --layout presentation)");
|
|
22903
|
+
}
|
|
22904
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
22905
|
+
const result = await client.setLiveRoomStudioScene(roomId, input);
|
|
22906
|
+
emit(flags, result, () => {
|
|
22907
|
+
printTable(
|
|
22908
|
+
[
|
|
22909
|
+
{
|
|
22910
|
+
layout: result.scene.layout,
|
|
22911
|
+
banner: result.scene.banner ?? "",
|
|
22912
|
+
lowerThird: result.scene.lowerThird?.name ?? "",
|
|
22913
|
+
offer: result.scene.offerOnScreen ? "on" : "off",
|
|
22914
|
+
comment: result.scene.commentOnScreen ? "on" : "off",
|
|
22915
|
+
onAir: result.appliedToRoom ? "yes" : "next go-live"
|
|
22916
|
+
}
|
|
22917
|
+
],
|
|
22918
|
+
["layout", "banner", "lowerThird", "offer", "comment", "onAir"]
|
|
22919
|
+
);
|
|
22920
|
+
});
|
|
22921
|
+
}
|
|
22922
|
+
);
|
|
22923
|
+
const guestRow = (guest) => ({
|
|
22924
|
+
id: guest.id,
|
|
22925
|
+
name: guest.name,
|
|
22926
|
+
state: guest.state,
|
|
22927
|
+
inStudio: guest.connected ? "yes" : "no",
|
|
22928
|
+
muted: guest.mutedForStream ? "yes" : "no",
|
|
22929
|
+
expiresAt: guest.expiresAt
|
|
22930
|
+
});
|
|
22931
|
+
const GUEST_COLUMNS = ["id", "name", "state", "inStudio", "muted", "expiresAt"];
|
|
22932
|
+
liveRooms.command("guest-invite").description(
|
|
22933
|
+
"Invite a guest into a studio webinar's studio (early access): prints their personal link ONCE (only its hash is kept; nothing is sent). They join backstage, the host puts them on stage"
|
|
22934
|
+
).argument("<roomId>", "studio webinar id (from live-rooms list)").requiredOption("--name <name>", "the guest's name (shown under their camera)").option("--email <email>", "for your own reference; nothing is sent to it").action(async (roomId, options, command) => {
|
|
22935
|
+
const flags = getGlobalFlags(command);
|
|
22936
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
22937
|
+
const result = await client.createLiveRoomStudioGuestInvite(roomId, {
|
|
22938
|
+
name: options.name,
|
|
22939
|
+
email: options.email ?? null
|
|
22940
|
+
});
|
|
22941
|
+
emit(flags, result, () => {
|
|
22942
|
+
printTable(
|
|
22943
|
+
[{ ...guestRow(result.guest), inviteUrl: result.inviteUrl }],
|
|
22944
|
+
[...GUEST_COLUMNS, "inviteUrl"]
|
|
22945
|
+
);
|
|
22946
|
+
});
|
|
22947
|
+
});
|
|
22948
|
+
liveRooms.command("guests").description("A studio webinar's invited guests: state, in the studio now, link expiry").argument("<roomId>", "studio webinar id (from live-rooms list)").action(async (roomId, _options, command) => {
|
|
22949
|
+
const flags = getGlobalFlags(command);
|
|
22950
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
22951
|
+
const result = await client.listLiveRoomStudioGuests(roomId);
|
|
22952
|
+
emit(flags, result, () => {
|
|
22953
|
+
printTable(result.guests.map(guestRow), GUEST_COLUMNS);
|
|
22954
|
+
});
|
|
22955
|
+
});
|
|
22956
|
+
liveRooms.command("guest-remove").description("Remove a guest from the studio: the link stops working, they are disconnected").argument("<roomId>", "studio webinar id (from live-rooms list)").argument("<guestId>", "guest id (from live-rooms guests)").action(
|
|
22957
|
+
async (roomId, guestId, _options, command) => {
|
|
22958
|
+
const flags = getGlobalFlags(command);
|
|
22959
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
22960
|
+
const result = await client.revokeLiveRoomStudioGuestInvite(roomId, guestId);
|
|
22961
|
+
emit(flags, result, () => {
|
|
22962
|
+
printTable([guestRow(result.guest)], GUEST_COLUMNS);
|
|
22963
|
+
});
|
|
22964
|
+
}
|
|
22965
|
+
);
|
|
21345
22966
|
liveRooms.command("attendance").description("A webinar's attendance per session; --session adds that session's viewers").argument("<roomId>", "webinar id (from live-rooms list)").option("--session <iso>", "ISO start of one session to list its attendees").action(async (roomId, options, command) => {
|
|
21346
22967
|
const flags = getGlobalFlags(command);
|
|
21347
22968
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -25075,7 +26696,7 @@ var ACCESS_STRATEGIES = [
|
|
|
25075
26696
|
];
|
|
25076
26697
|
var DOUBLE_OPT_INS = ["none", "external"];
|
|
25077
26698
|
var DELIVERIES = ["zanfia", "integration"];
|
|
25078
|
-
function
|
|
26699
|
+
function assertOneOf3(value, allowed, flag) {
|
|
25079
26700
|
if (!allowed.includes(value)) {
|
|
25080
26701
|
throw new Error(`Invalid ${flag}: ${value}. Use one of: ${allowed.join(", ")}`);
|
|
25081
26702
|
}
|
|
@@ -25171,17 +26792,17 @@ Next: \`newsletters issues draft save ${productId} ${issue.id} --content <markdo
|
|
|
25171
26792
|
const flags = getGlobalFlags(command);
|
|
25172
26793
|
const input = {};
|
|
25173
26794
|
if (options.status !== void 0) {
|
|
25174
|
-
input.status =
|
|
26795
|
+
input.status = assertOneOf3(options.status, ISSUE_STATUSES, "--status");
|
|
25175
26796
|
}
|
|
25176
26797
|
if (options.landing !== void 0) {
|
|
25177
|
-
input.landingPageVisibility =
|
|
26798
|
+
input.landingPageVisibility = assertOneOf3(
|
|
25178
26799
|
options.landing,
|
|
25179
26800
|
ISSUE_VISIBILITIES,
|
|
25180
26801
|
"--landing"
|
|
25181
26802
|
);
|
|
25182
26803
|
}
|
|
25183
26804
|
if (options.renderMode !== void 0) {
|
|
25184
|
-
input.renderMode =
|
|
26805
|
+
input.renderMode = assertOneOf3(options.renderMode, ISSUE_RENDER_MODES, "--render-mode");
|
|
25185
26806
|
}
|
|
25186
26807
|
if (options.thumbnail !== void 0) {
|
|
25187
26808
|
input.thumbnailUrl = options.thumbnail === THUMBNAIL_NONE ? null : options.thumbnail;
|
|
@@ -25460,7 +27081,7 @@ Next: \`newsletters issues draft save ${productId} ${issue.id} --content <markdo
|
|
|
25460
27081
|
archive.enabled = parseBool2(options.archiveEnabled, "--archive-enabled");
|
|
25461
27082
|
}
|
|
25462
27083
|
if (options.archiveAccess !== void 0) {
|
|
25463
|
-
archive.accessStrategy =
|
|
27084
|
+
archive.accessStrategy = assertOneOf3(
|
|
25464
27085
|
options.archiveAccess,
|
|
25465
27086
|
ACCESS_STRATEGIES,
|
|
25466
27087
|
"--archive-access"
|
|
@@ -25470,14 +27091,14 @@ Next: \`newsletters issues draft save ${productId} ${issue.id} --content <markdo
|
|
|
25470
27091
|
archive.publicationDelay = options.publicationDelay === "none" ? null : parseNonNegativeInt2(options.publicationDelay, "--publication-delay");
|
|
25471
27092
|
}
|
|
25472
27093
|
if (options.defaultStatus !== void 0) {
|
|
25473
|
-
archive.defaultEmailStatus =
|
|
27094
|
+
archive.defaultEmailStatus = assertOneOf3(
|
|
25474
27095
|
options.defaultStatus,
|
|
25475
27096
|
ISSUE_STATUSES,
|
|
25476
27097
|
"--default-status"
|
|
25477
27098
|
);
|
|
25478
27099
|
}
|
|
25479
27100
|
if (options.defaultVisibility !== void 0) {
|
|
25480
|
-
archive.defaultEmailVisibility =
|
|
27101
|
+
archive.defaultEmailVisibility = assertOneOf3(
|
|
25481
27102
|
options.defaultVisibility,
|
|
25482
27103
|
ISSUE_VISIBILITIES,
|
|
25483
27104
|
"--default-visibility"
|
|
@@ -25495,11 +27116,11 @@ Next: \`newsletters issues draft save ${productId} ${issue.id} --content <markdo
|
|
|
25495
27116
|
input.archiveInbox = inbox;
|
|
25496
27117
|
}
|
|
25497
27118
|
if (options.doubleOptIn !== void 0) {
|
|
25498
|
-
input.doubleOptIn =
|
|
27119
|
+
input.doubleOptIn = assertOneOf3(options.doubleOptIn, DOUBLE_OPT_INS, "--double-opt-in");
|
|
25499
27120
|
}
|
|
25500
27121
|
if (options.clientTag.length > 0) input.clientTags = options.clientTag;
|
|
25501
27122
|
if (options.delivery !== void 0) {
|
|
25502
|
-
input.delivery =
|
|
27123
|
+
input.delivery = assertOneOf3(options.delivery, DELIVERIES, "--delivery");
|
|
25503
27124
|
}
|
|
25504
27125
|
if (options.clearSender) {
|
|
25505
27126
|
input.sender = null;
|
|
@@ -26073,7 +27694,7 @@ Media gallery (${gallery.length}):
|
|
|
26073
27694
|
"suppress subscription-cancelled emails"
|
|
26074
27695
|
).option("--disable-end-subscription-emails <on|off>", "suppress subscription-ended emails").option(
|
|
26075
27696
|
"--disable-access-ending-reminders <on|off>",
|
|
26076
|
-
"suppress access-
|
|
27697
|
+
"suppress the client's access-end emails: reminders before the end and the e-mail after it (cancel-at-end offers too)"
|
|
26077
27698
|
).option("--facebook-pixel-id <id|none>", "legacy Facebook Pixel id (none = clear)").option("--google-analytics-id <id|none>", "legacy GA id (none = clear)").option("--gtm-id <id|none>", "legacy Google Tag Manager id (none = clear)").option("--meta-conversion-integration-id <id|none>", "legacy Meta CAPI id (none = clear)").option(
|
|
26078
27699
|
"--tracking-integration-ids <ids|none>",
|
|
26079
27700
|
"comma-separated workspace conversion-tracking integration ids (none = clear)"
|
|
@@ -26132,6 +27753,28 @@ Media gallery (${gallery.length}):
|
|
|
26132
27753
|
});
|
|
26133
27754
|
}
|
|
26134
27755
|
);
|
|
27756
|
+
offers.command("notify-waitlist").description(
|
|
27757
|
+
"\u201EIt's back\u201D: e-mail the offer's waiting list (fr-0272), once per sign-up. Without --send it only counts; --send needs --confirm"
|
|
27758
|
+
).argument("<checkoutId>", "offer (checkout) id").option("--send", "send the e-mails (default: count only)").option("--confirm", "required with --send \u2014 acknowledge the waiting list is e-mailed").action(
|
|
27759
|
+
async (checkoutId, options, command) => {
|
|
27760
|
+
const flags = getGlobalFlags(command);
|
|
27761
|
+
const send = options.send === true;
|
|
27762
|
+
if (send) requireConfirm(options.confirm, "e-mail this offer's waiting list");
|
|
27763
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
27764
|
+
const result = await client.notifyCheckoutWaitlist(checkoutId, !send);
|
|
27765
|
+
emit(flags, result, () => {
|
|
27766
|
+
process.stdout.write(
|
|
27767
|
+
result.dryRun ? `${result.pendingCount} sign-up(s) of "${result.offerName}" would get the e-mail (nothing sent; add --send --confirm).
|
|
27768
|
+
` : `Sent to ${result.sentCount} sign-up(s) of "${result.offerName}".
|
|
27769
|
+
`
|
|
27770
|
+
);
|
|
27771
|
+
process.stdout.write(
|
|
27772
|
+
`Already notified: ${result.alreadyNotifiedCount}; skipped: ${result.skippedCount} (suppressed ${result.skippedByReason.suppressed}, unsubscribed after sign-up ${result.skippedByReason.unsubscribed_after_sign_up}).
|
|
27773
|
+
`
|
|
27774
|
+
);
|
|
27775
|
+
});
|
|
27776
|
+
}
|
|
27777
|
+
);
|
|
26135
27778
|
offers.command("set-default").description("Make an offer its product's default checkout").argument("<checkoutId>", "offer (checkout) id").action(async (checkoutId, _, command) => {
|
|
26136
27779
|
const flags = getGlobalFlags(command);
|
|
26137
27780
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -26368,6 +28011,7 @@ Media gallery (${gallery.length}):
|
|
|
26368
28011
|
// src/commands/orders/index.ts
|
|
26369
28012
|
import { writeFileSync as writeFileSync5 } from "fs";
|
|
26370
28013
|
var ORDER_STATUSES = ["paid", "pending", "refunded", "failed"];
|
|
28014
|
+
var REFUND_NOTE_MAX_LENGTH = 500;
|
|
26371
28015
|
function assertOrderStatus(status) {
|
|
26372
28016
|
if (!ORDER_STATUSES.includes(status)) {
|
|
26373
28017
|
throw new Error(`Invalid --status: ${status}. Use one of: ${ORDER_STATUSES.join(", ")}`);
|
|
@@ -26389,8 +28033,10 @@ function requireConfirm11(confirm, what) {
|
|
|
26389
28033
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
26390
28034
|
}
|
|
26391
28035
|
}
|
|
26392
|
-
function
|
|
26393
|
-
const orders2 = program2.command("orders").description(
|
|
28036
|
+
function registerOrdersCommands2(program2) {
|
|
28037
|
+
const orders2 = program2.command("orders").description(
|
|
28038
|
+
"Browse orders; approve or reject manual (wire-transfer) payments; mark test / refunded orders"
|
|
28039
|
+
);
|
|
26394
28040
|
orders2.command("list").description("List orders (newest first), with optional filters").option("--product <id>", "only orders of this product").option(
|
|
26395
28041
|
"--status <status>",
|
|
26396
28042
|
"filter by status (paid, pending, refunded, failed) \u2014 manual wire-transfer orders awaiting approval are `pending`"
|
|
@@ -26479,14 +28125,18 @@ Next page: --cursor ${page.nextCursor}
|
|
|
26479
28125
|
});
|
|
26480
28126
|
});
|
|
26481
28127
|
orders2.command("approve").description(
|
|
26482
|
-
"Approve a manual (wire-transfer) order \u2014 grants product access and sends the regular purchase email; --issue-invoice also issues an invoice via the connected invoicing account. 409 = the order was already decided. Requires --confirm"
|
|
26483
|
-
).argument("<orderId>", "order id (a `pending` order from `orders list --status pending`)").option("--issue-invoice", "also issue an invoice for the order (never issued without this)").option(
|
|
28128
|
+
"Approve a manual (wire-transfer) order \u2014 grants product access and sends the regular purchase email; --issue-invoice also issues an invoice via the connected invoicing account. 409 = the order was already decided, or (stock-exhausted) an expired physical order whose stock ran out: re-run with --allow-oversell or reject it. Requires --confirm"
|
|
28129
|
+
).argument("<orderId>", "order id (a `pending` order from `orders list --status pending`)").option("--issue-invoice", "also issue an invoice for the order (never issued without this)").option(
|
|
28130
|
+
"--allow-oversell",
|
|
28131
|
+
"approve an expired physical order (late transfer) even though its stock ran out"
|
|
28132
|
+
).option("--confirm", "required \u2014 acknowledge that access is granted and the buyer is emailed").action(
|
|
26484
28133
|
async (orderId, options, command) => {
|
|
26485
28134
|
const flags = getGlobalFlags(command);
|
|
26486
28135
|
requireConfirm11(options.confirm, `approve order ${orderId} (access + purchase email)`);
|
|
26487
28136
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
26488
28137
|
const result = await client.approveManualOrder(orderId, {
|
|
26489
|
-
issueInvoice: options.issueInvoice === true ? true : void 0
|
|
28138
|
+
issueInvoice: options.issueInvoice === true ? true : void 0,
|
|
28139
|
+
allowOversell: options.allowOversell === true ? true : void 0
|
|
26490
28140
|
});
|
|
26491
28141
|
emit(flags, result, () => {
|
|
26492
28142
|
process.stdout.write(
|
|
@@ -26506,8 +28156,68 @@ Next page: --cursor ${page.nextCursor}
|
|
|
26506
28156
|
emit(flags, result, () => {
|
|
26507
28157
|
process.stdout.write(`Rejected order ${orderId} \u2014 cancelled and the buyer notified.
|
|
26508
28158
|
`);
|
|
26509
|
-
});
|
|
26510
|
-
});
|
|
28159
|
+
});
|
|
28160
|
+
});
|
|
28161
|
+
orders2.command("mark-test").description(
|
|
28162
|
+
"Mark a real order as a test \u2014 it leaves revenue, charts and reports; the buyer keeps access. --undo puts it back. 409 = already in that state. Requires --confirm"
|
|
28163
|
+
).argument("<orderId>", "order id").option("--undo", "undo a mark made this way (the order counts again)").option("--confirm", "required \u2014 acknowledge the change to revenue and reports").action(
|
|
28164
|
+
async (orderId, options, command) => {
|
|
28165
|
+
const flags = getGlobalFlags(command);
|
|
28166
|
+
const isTest = options.undo !== true;
|
|
28167
|
+
requireConfirm11(
|
|
28168
|
+
options.confirm,
|
|
28169
|
+
isTest ? `mark order ${orderId} as a test` : `undo the test mark of order ${orderId}`
|
|
28170
|
+
);
|
|
28171
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
28172
|
+
const result = await client.markOrderAsTest(orderId, isTest);
|
|
28173
|
+
emit(flags, result, () => {
|
|
28174
|
+
process.stdout.write(
|
|
28175
|
+
result.isTest ? `Order ${result.orderId} is marked as a test \u2014 it no longer counts in revenue.
|
|
28176
|
+
` : `Order ${result.orderId} counts again in revenue and reports.
|
|
28177
|
+
`
|
|
28178
|
+
);
|
|
28179
|
+
});
|
|
28180
|
+
}
|
|
28181
|
+
);
|
|
28182
|
+
orders2.command("mark-refunded").description(
|
|
28183
|
+
"Record that you returned the money OUTSIDE Zanfia (bank transfer, cash\u2026). Nothing is charged or paid out and no correcting invoice is issued (do that in your invoicing tool); the order counts as refunded. --revoke-access also ends the buyer\u2019s access (refused while a live subscription would keep billing). Requires --confirm"
|
|
28184
|
+
).argument("<orderId>", "order id").option("--revoke-access", "also end the buyer\u2019s access to the products of this order").option(
|
|
28185
|
+
"--note <text>",
|
|
28186
|
+
`note on the order's audit trail (max ${REFUND_NOTE_MAX_LENGTH} chars)`
|
|
28187
|
+
).option("--confirm", "required \u2014 acknowledge that the order counts as refunded").action(
|
|
28188
|
+
async (orderId, options, command) => {
|
|
28189
|
+
const flags = getGlobalFlags(command);
|
|
28190
|
+
if (options.note !== void 0 && options.note.length > REFUND_NOTE_MAX_LENGTH) {
|
|
28191
|
+
throw new Error(`--note is too long (max ${REFUND_NOTE_MAX_LENGTH} characters).`);
|
|
28192
|
+
}
|
|
28193
|
+
const revokeAccess = options.revokeAccess === true;
|
|
28194
|
+
requireConfirm11(
|
|
28195
|
+
options.confirm,
|
|
28196
|
+
`mark order ${orderId} as refunded outside Zanfia${revokeAccess ? " and end the buyer\u2019s access" : ""}`
|
|
28197
|
+
);
|
|
28198
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
28199
|
+
const result = await client.markOrderRefunded(orderId, {
|
|
28200
|
+
revokeAccess,
|
|
28201
|
+
...options.note !== void 0 ? { note: options.note } : {}
|
|
28202
|
+
});
|
|
28203
|
+
emit(flags, result, () => {
|
|
28204
|
+
process.stdout.write(
|
|
28205
|
+
`Order ${result.orderId} recorded as refunded outside Zanfia (${formatAmount3(result.amountMinor, result.currency)}).
|
|
28206
|
+
`
|
|
28207
|
+
);
|
|
28208
|
+
if (result.revokedProductIds.length > 0) {
|
|
28209
|
+
process.stdout.write(` Access ended: ${result.revokedProductIds.join(", ")}
|
|
28210
|
+
`);
|
|
28211
|
+
}
|
|
28212
|
+
if (result.accessRevokeFailedProductIds.length > 0) {
|
|
28213
|
+
process.stdout.write(
|
|
28214
|
+
` \u26A0\uFE0F Access NOT ended for: ${result.accessRevokeFailedProductIds.join(", ")} \u2014 the refund stands; end it on the client card.
|
|
28215
|
+
`
|
|
28216
|
+
);
|
|
28217
|
+
}
|
|
28218
|
+
});
|
|
28219
|
+
}
|
|
28220
|
+
);
|
|
26511
28221
|
orders2.command("export").description(
|
|
26512
28222
|
"Export orders as CSV to a file. The --from/--to window is required; same filters as `orders list`."
|
|
26513
28223
|
).requiredOption("--from <date>", "inclusive ISO date lower bound on createdAt (required)").requiredOption("--to <date>", "inclusive ISO date upper bound on createdAt (required)").option("--product <id>", "only orders of this product").option("--status <status>", "filter by status (paid, pending, refunded, failed)").option("--search <query>", "match client email / name").option("--include-test", "include simulated test purchases (excluded by default)").option("--max-rows <n>", "row cap (server default 10000, max 20000)").option("--out <file>", "output file path", "zanfia-orders.csv").action(
|
|
@@ -26768,6 +28478,14 @@ function buildEventInput(options) {
|
|
|
26768
28478
|
};
|
|
26769
28479
|
return Object.keys(event).length > 0 ? event : void 0;
|
|
26770
28480
|
}
|
|
28481
|
+
function pointPlaceholderRefusalAtFlag(hint) {
|
|
28482
|
+
return (error) => {
|
|
28483
|
+
if (error instanceof ApiError && error.status === 409 && error.apiMessage.includes("allowPlaceholders")) {
|
|
28484
|
+
throw new ApiError(error.status, error.apiMessage, hint);
|
|
28485
|
+
}
|
|
28486
|
+
throw error;
|
|
28487
|
+
};
|
|
28488
|
+
}
|
|
26771
28489
|
async function resolveHtml2(options) {
|
|
26772
28490
|
if (options.file) {
|
|
26773
28491
|
return readFile4(options.file, "utf8");
|
|
@@ -26934,7 +28652,10 @@ function registerPagesCommands(program2) {
|
|
|
26934
28652
|
);
|
|
26935
28653
|
});
|
|
26936
28654
|
});
|
|
26937
|
-
pages.command("create").description("Create a hosted page from an HTML file or inline HTML").requiredOption("--slug <slug>", "public URL slug (served at /page/<slug>)").requiredOption("--title <title>", "page title").option("--file <path>", "path to an .html file to host").option("--html <html>", "inline HTML body (alternative to --file)").option("--publish", "publish immediately instead of saving as draft").
|
|
28655
|
+
pages.command("create").description("Create a hosted page from an HTML file or inline HTML").requiredOption("--slug <slug>", "public URL slug (served at /page/<slug>)").requiredOption("--title <title>", "page title").option("--file <path>", "path to an .html file to host").option("--html <html>", "inline HTML body (alternative to --file)").option("--publish", "publish immediately instead of saving as draft").option(
|
|
28656
|
+
"--allow-placeholders",
|
|
28657
|
+
"with --publish: publish even though the page still shows [UZUPE\u0141NIJ: \u2026] placeholders"
|
|
28658
|
+
).action(async (options, command) => {
|
|
26938
28659
|
const flags = getGlobalFlags(command);
|
|
26939
28660
|
const html = await resolveHtml2(options);
|
|
26940
28661
|
if (!html) {
|
|
@@ -26944,10 +28665,15 @@ function registerPagesCommands(program2) {
|
|
|
26944
28665
|
slug: options.slug,
|
|
26945
28666
|
title: options.title,
|
|
26946
28667
|
html,
|
|
26947
|
-
publish: options.publish ?? false
|
|
28668
|
+
publish: options.publish ?? false,
|
|
28669
|
+
...options.allowPlaceholders ? { allowPlaceholders: true } : {}
|
|
26948
28670
|
};
|
|
26949
28671
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
26950
|
-
const result = await client.createPage(input)
|
|
28672
|
+
const result = await client.createPage(input).catch(
|
|
28673
|
+
pointPlaceholderRefusalAtFlag(
|
|
28674
|
+
"Fill them in, create it as a draft (without --publish), or re-run with --allow-placeholders to publish anyway."
|
|
28675
|
+
)
|
|
28676
|
+
);
|
|
26951
28677
|
emit(flags, result, () => {
|
|
26952
28678
|
process.stdout.write(`Created page ${result.pageId} \u2014 /${result.slug}
|
|
26953
28679
|
`);
|
|
@@ -26997,14 +28723,26 @@ function registerPagesCommands(program2) {
|
|
|
26997
28723
|
});
|
|
26998
28724
|
});
|
|
26999
28725
|
const publishCommand = (verb, publish, description) => {
|
|
27000
|
-
pages.command(verb).description(description).argument("<id>", "page id").option("--only-this-page", "change this page only, not its companions or site pages").option("--dry-run", "list the pages that would change; change nothing")
|
|
28726
|
+
const verbCommand = pages.command(verb).description(description).argument("<id>", "page id").option("--only-this-page", "change this page only, not its companions or site pages").option("--dry-run", "list the pages that would change; change nothing");
|
|
28727
|
+
if (publish) {
|
|
28728
|
+
verbCommand.option(
|
|
28729
|
+
"--allow-placeholders",
|
|
28730
|
+
"publish even though pages still show [UZUPE\u0141NIJ: \u2026] placeholders"
|
|
28731
|
+
);
|
|
28732
|
+
}
|
|
28733
|
+
verbCommand.action(
|
|
27001
28734
|
async (id, options, command) => {
|
|
27002
28735
|
const flags = getGlobalFlags(command);
|
|
27003
28736
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
27004
28737
|
const result = await client.publishPage(id, publish, {
|
|
27005
28738
|
...options.onlyThisPage ? { scope: "page" } : {},
|
|
27006
|
-
...options.dryRun ? { dryRun: true } : {}
|
|
27007
|
-
|
|
28739
|
+
...options.dryRun ? { dryRun: true } : {},
|
|
28740
|
+
...options.allowPlaceholders ? { allowPlaceholders: true } : {}
|
|
28741
|
+
}).catch(
|
|
28742
|
+
pointPlaceholderRefusalAtFlag(
|
|
28743
|
+
"Fill them in (pages update), or re-run with --allow-placeholders to publish anyway."
|
|
28744
|
+
)
|
|
28745
|
+
);
|
|
27008
28746
|
emit(flags, result, () => {
|
|
27009
28747
|
if (!result.dryRun) {
|
|
27010
28748
|
process.stdout.write(`Page ${result.pageId} is now ${result.status}
|
|
@@ -27165,7 +28903,7 @@ function registerPagesCommands(program2) {
|
|
|
27165
28903
|
});
|
|
27166
28904
|
});
|
|
27167
28905
|
pages.command("restore").description(
|
|
27168
|
-
"Roll a page's HTML back to a revision. The current HTML is snapshotted first (source `restore`), so a restore is itself undoable. Requires --confirm."
|
|
28906
|
+
"Roll a page's HTML back to a revision. The current HTML is snapshotted first (source `restore`), so a restore is itself undoable. On a published page a revision with a buy button that leads nowhere is refused (409, nothing restored). Requires --confirm."
|
|
27169
28907
|
).argument("<pageId>", "page id").argument("<revisionId>", "revision id from `pages revisions`").option("--confirm", "required \u2014 acknowledge that the current HTML will be replaced").action(
|
|
27170
28908
|
async (pageId, revisionId, options, command) => {
|
|
27171
28909
|
const flags = getGlobalFlags(command);
|
|
@@ -27229,6 +28967,11 @@ function registerPagesCommands(program2) {
|
|
|
27229
28967
|
import { readFileSync as readFileSync22 } from "fs";
|
|
27230
28968
|
import { readFile as readFile5 } from "fs/promises";
|
|
27231
28969
|
import { basename as basename2 } from "path";
|
|
28970
|
+
function parseBuyerFiles(value) {
|
|
28971
|
+
if (value === "on") return true;
|
|
28972
|
+
if (value === "off") return false;
|
|
28973
|
+
throw new Error(`Invalid --buyer-files: ${value} (expected on | off)`);
|
|
28974
|
+
}
|
|
27232
28975
|
var KB_TREE_CHANGED_ERROR = "tree-changed";
|
|
27233
28976
|
var JSON_FLAG_HINT3 = "(write it as --json=<value> \u2014 a bare --json is the global JSON-output flag)";
|
|
27234
28977
|
var PRODUCT_TYPE_INPUTS = [
|
|
@@ -27342,7 +29085,7 @@ function assertIsoDate2(value, flag) {
|
|
|
27342
29085
|
}
|
|
27343
29086
|
return value;
|
|
27344
29087
|
}
|
|
27345
|
-
function
|
|
29088
|
+
function assertOneOf4(value, allowed, flag) {
|
|
27346
29089
|
if (!allowed.includes(value)) {
|
|
27347
29090
|
throw new Error(`Invalid ${flag}: ${value}. Use one of: ${allowed.join(", ")}`);
|
|
27348
29091
|
}
|
|
@@ -27403,7 +29146,7 @@ function printAddedFiles(inputCount, result) {
|
|
|
27403
29146
|
);
|
|
27404
29147
|
printFilesTable(result.files);
|
|
27405
29148
|
}
|
|
27406
|
-
function
|
|
29149
|
+
function registerProductsCommands2(program2) {
|
|
27407
29150
|
const products2 = program2.command("products").description("Manage products");
|
|
27408
29151
|
products2.command("list").description("List products in the workspace").action(async (_, command) => {
|
|
27409
29152
|
const flags = getGlobalFlags(command);
|
|
@@ -27441,6 +29184,10 @@ function registerProductsCommands(program2) {
|
|
|
27441
29184
|
}
|
|
27442
29185
|
if (product.language !== void 0) {
|
|
27443
29186
|
process.stdout.write(` Language: ${product.language ?? "none (platform language)"}
|
|
29187
|
+
`);
|
|
29188
|
+
}
|
|
29189
|
+
if (product.fileSetEnabled !== void 0) {
|
|
29190
|
+
process.stdout.write(` Files for buyers: ${product.fileSetEnabled ? "on" : "off"}
|
|
27444
29191
|
`);
|
|
27445
29192
|
}
|
|
27446
29193
|
process.stdout.write(` Clients: ${product.clientCount}
|
|
@@ -27534,7 +29281,9 @@ function registerProductsCommands(program2) {
|
|
|
27534
29281
|
});
|
|
27535
29282
|
}
|
|
27536
29283
|
);
|
|
27537
|
-
products2.command("update").description(
|
|
29284
|
+
products2.command("update").description(
|
|
29285
|
+
"Update a product's name, description, storefront visibility, language and/or files for buyers"
|
|
29286
|
+
).argument("<id>", "product id").option("--name <name>", "new product name").option("--description <description>", "new product description").option(
|
|
27538
29287
|
"--visibility <public|unlisted|private>",
|
|
27539
29288
|
"where buyers find it once published: public (store + product page), unlisted (product page by link only), private (checkout links only)",
|
|
27540
29289
|
parseStorefrontVisibility
|
|
@@ -27542,11 +29291,15 @@ function registerProductsCommands(program2) {
|
|
|
27542
29291
|
"--language <pl|en|none>",
|
|
27543
29292
|
"the product's language (buyer-facing pages and e-mails); none clears it (the platform's language applies)",
|
|
27544
29293
|
parseProductLanguageOrNone
|
|
29294
|
+
).option(
|
|
29295
|
+
"--buyer-files <on|off>",
|
|
29296
|
+
"\u201EPliki dla kupuj\u0105cych\u201D: on opens the product's file set (\u201EPliki\u201D, `files push`) to its buyers, off hides it from them (the files stay saved)",
|
|
29297
|
+
parseBuyerFiles
|
|
27545
29298
|
).action(
|
|
27546
29299
|
async (id, options, command) => {
|
|
27547
|
-
if (options.name === void 0 && options.description === void 0 && options.visibility === void 0 && options.language === void 0) {
|
|
29300
|
+
if (options.name === void 0 && options.description === void 0 && options.visibility === void 0 && options.language === void 0 && options.buyerFiles === void 0) {
|
|
27548
29301
|
throw new Error(
|
|
27549
|
-
"Nothing to update: pass --name, --description, --visibility and/or --
|
|
29302
|
+
"Nothing to update: pass --name, --description, --visibility, --language and/or --buyer-files."
|
|
27550
29303
|
);
|
|
27551
29304
|
}
|
|
27552
29305
|
const flags = getGlobalFlags(command);
|
|
@@ -27555,7 +29308,8 @@ function registerProductsCommands(program2) {
|
|
|
27555
29308
|
name: options.name,
|
|
27556
29309
|
description: options.description,
|
|
27557
29310
|
storefrontVisibility: options.visibility,
|
|
27558
|
-
language: options.language === PRODUCT_LANGUAGE_NONE ? null : options.language
|
|
29311
|
+
language: options.language === PRODUCT_LANGUAGE_NONE ? null : options.language,
|
|
29312
|
+
fileSetEnabled: options.buyerFiles
|
|
27559
29313
|
});
|
|
27560
29314
|
emit(flags, result, () => {
|
|
27561
29315
|
process.stdout.write(`Updated product ${result.productId}
|
|
@@ -27592,6 +29346,23 @@ function registerProductsCommands(program2) {
|
|
|
27592
29346
|
}
|
|
27593
29347
|
});
|
|
27594
29348
|
});
|
|
29349
|
+
products2.command("duplicate").description(
|
|
29350
|
+
"Copy a product as a DRAFT (settings, offers with new prices, content) in a background run; community / knowledge base / newsletter are refused (422). Requires --confirm"
|
|
29351
|
+
).argument("<id>", "product id").option("--name <name>", 'name of the copy (default: "<name> (kopia)" / "(copy)")').option("--confirm", "required \u2014 acknowledge a new draft product is created").action(async (id, options, command) => {
|
|
29352
|
+
requireConfirm(options.confirm, `duplicate product ${id}`);
|
|
29353
|
+
const flags = getGlobalFlags(command);
|
|
29354
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
29355
|
+
const result = await client.duplicateProduct(id, options.name);
|
|
29356
|
+
emit(
|
|
29357
|
+
flags,
|
|
29358
|
+
result,
|
|
29359
|
+
() => process.stdout.write(
|
|
29360
|
+
result.status === "already-running" ? `Product ${id} is already being copied (run ${result.runId}); no second copy.
|
|
29361
|
+
` : `Copying product ${id}${result.productId ? ` into ${result.productId}` : ""} (run ${result.runId}); the draft appears in \`products list\` when the run completes.
|
|
29362
|
+
`
|
|
29363
|
+
)
|
|
29364
|
+
);
|
|
29365
|
+
});
|
|
27595
29366
|
products2.command("publish").description("Publish a product (status \u2192 published; live for customers)").argument("<id>", "product id").action(async (id, _, command) => {
|
|
27596
29367
|
const flags = getGlobalFlags(command);
|
|
27597
29368
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -27849,10 +29620,10 @@ function registerProductsCommands(program2) {
|
|
|
27849
29620
|
).option("--delivery-time <n>", "service: delivery time value").option("--delivery-unit <unit>", `service: ${DELIVERY_UNITS.join("|")} (with --delivery-time)`).option("--revision-rounds <n|unlimited>", 'service: revision rounds count or "unlimited"').option("--brief-method <method>", `service: ${BRIEF_METHODS.join("|")}`).option("--brief-url <url>", "service: external brief/intake form URL").option("--event-mode <mode>", `event: ${EVENT_MODES3.join("|")}`).option("--starts <iso>", "event: start (ISO 8601)").option("--ends <iso>", "event: end (ISO 8601)").option("--capacity <n>", "event: attendee capacity").option("--show-on-success <on|off>", "show the post-purchase CTA on the success page").action(
|
|
27850
29621
|
async (productId, options, command) => {
|
|
27851
29622
|
const flags = getGlobalFlags(command);
|
|
27852
|
-
const bookingMethod = options.bookingMethod !== void 0 ?
|
|
27853
|
-
const briefMethod = options.briefMethod !== void 0 ?
|
|
27854
|
-
const deliveryUnit = options.deliveryUnit !== void 0 ?
|
|
27855
|
-
const eventMode = options.eventMode !== void 0 ?
|
|
29623
|
+
const bookingMethod = options.bookingMethod !== void 0 ? assertOneOf4(options.bookingMethod, BOOKING_METHODS, "--booking-method") : void 0;
|
|
29624
|
+
const briefMethod = options.briefMethod !== void 0 ? assertOneOf4(options.briefMethod, BRIEF_METHODS, "--brief-method") : void 0;
|
|
29625
|
+
const deliveryUnit = options.deliveryUnit !== void 0 ? assertOneOf4(options.deliveryUnit, DELIVERY_UNITS, "--delivery-unit") : void 0;
|
|
29626
|
+
const eventMode = options.eventMode !== void 0 ? assertOneOf4(options.eventMode, EVENT_MODES3, "--event-mode") : void 0;
|
|
27856
29627
|
if (options.deliveryTime === void 0 !== (options.deliveryUnit === void 0)) {
|
|
27857
29628
|
throw new Error("Pass --delivery-time and --delivery-unit together.");
|
|
27858
29629
|
}
|
|
@@ -27984,7 +29755,7 @@ function registerProductsCommands(program2) {
|
|
|
27984
29755
|
).option("--thank-you-type <type>", `${THANK_YOU_TYPES.join("|")}`).option("--redirect-url <url>", "redirect destination (implies --thank-you-type redirect)").option("--email-buyer-purchase <on|off>", "send the buyer a purchase confirmation email").option("--notify-owner-purchase <on|off>", "notify the owner of a purchase").option("--email-buyer-failed <on|off>", "email the buyer on a failed payment").option("--notify-owner-failed <on|off>", "notify the owner of a failed payment").action(
|
|
27985
29756
|
async (productId, options, command) => {
|
|
27986
29757
|
const flags = getGlobalFlags(command);
|
|
27987
|
-
const thankYouType = options.thankYouType !== void 0 ?
|
|
29758
|
+
const thankYouType = options.thankYouType !== void 0 ? assertOneOf4(options.thankYouType, THANK_YOU_TYPES, "--thank-you-type") : void 0;
|
|
27988
29759
|
if (options.redirectUrl !== void 0)
|
|
27989
29760
|
assertHttpsUrl(options.redirectUrl, "--redirect-url");
|
|
27990
29761
|
const emailBuyerPurchase = options.emailBuyerPurchase !== void 0 ? parseBool4(options.emailBuyerPurchase, "--email-buyer-purchase") : void 0;
|
|
@@ -28282,7 +30053,7 @@ function registerProductsCommands(program2) {
|
|
|
28282
30053
|
title: options.title,
|
|
28283
30054
|
parentId: options.category,
|
|
28284
30055
|
editorContent: resolveKbContent(options),
|
|
28285
|
-
status: options.status !== void 0 ?
|
|
30056
|
+
status: options.status !== void 0 ? assertOneOf4(options.status, KB_ARTICLE_STATUSES, "--status") : void 0,
|
|
28286
30057
|
tags: options.tags !== void 0 ? parseKbTags(options.tags) : void 0
|
|
28287
30058
|
});
|
|
28288
30059
|
emit(flags, result, () => {
|
|
@@ -28320,7 +30091,7 @@ function registerProductsCommands(program2) {
|
|
|
28320
30091
|
const result = await client.updateKbArticle(productId, articleId, {
|
|
28321
30092
|
title: options.title,
|
|
28322
30093
|
editorContent,
|
|
28323
|
-
status: options.status !== void 0 ?
|
|
30094
|
+
status: options.status !== void 0 ? assertOneOf4(options.status, KB_ARTICLE_STATUSES, "--status") : void 0,
|
|
28324
30095
|
parentId: options.category,
|
|
28325
30096
|
coverImage,
|
|
28326
30097
|
tags
|
|
@@ -29084,6 +30855,12 @@ function registerSegmentsCommands(program2) {
|
|
|
29084
30855
|
withConditionOptions(
|
|
29085
30856
|
segments.command("preview").description(
|
|
29086
30857
|
"Count the clients matching a condition set WITHOUT saving anything (stateless dry run)"
|
|
30858
|
+
).option(
|
|
30859
|
+
"--email-sendable",
|
|
30860
|
+
"count only clients an e-mail campaign may mail (subscribed): the reach of a send"
|
|
30861
|
+
).option(
|
|
30862
|
+
"--sms-sendable",
|
|
30863
|
+
"count only clients an SMS campaign may text (SMS consent AND a phone): size an SMS send"
|
|
29087
30864
|
)
|
|
29088
30865
|
).action(async (options, command) => {
|
|
29089
30866
|
const flags = getGlobalFlags(command);
|
|
@@ -29091,12 +30868,20 @@ function registerSegmentsCommands(program2) {
|
|
|
29091
30868
|
if (conditions === void 0) {
|
|
29092
30869
|
throw new Error(`Pass at least one condition: ${CONDITION_FLAGS_HELP}.`);
|
|
29093
30870
|
}
|
|
30871
|
+
if (options.emailSendable && options.smsSendable) {
|
|
30872
|
+
throw new Error("Pass either --email-sendable or --sms-sendable, not both.");
|
|
30873
|
+
}
|
|
29094
30874
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
29095
|
-
const result = await client.previewAudienceSegment(conditions
|
|
30875
|
+
const result = await client.previewAudienceSegment(conditions, {
|
|
30876
|
+
requireSendable: options.emailSendable === true,
|
|
30877
|
+
requireSmsSendable: options.smsSendable === true
|
|
30878
|
+
});
|
|
29096
30879
|
emit(flags, result, () => {
|
|
30880
|
+
const count2 = result.partial ? `\u2265 ${result.count}` : String(result.count);
|
|
30881
|
+
const reach = options.smsSendable ? " (SMS reach)" : options.emailSendable ? " (e-mail reach)" : "";
|
|
29097
30882
|
process.stdout.write(
|
|
29098
|
-
`${
|
|
29099
|
-
`
|
|
30883
|
+
`${count2} client${result.count === 1 && !result.partial ? " matches" : "s match"} ${conditions.map(describeCondition).join(" AND ")}${reach}.
|
|
30884
|
+
` + (result.partial ? "The client base was not counted to the end in time: the real number may be higher.\n" : "")
|
|
29100
30885
|
);
|
|
29101
30886
|
});
|
|
29102
30887
|
});
|
|
@@ -29104,6 +30889,157 @@ function registerSegmentsCommands(program2) {
|
|
|
29104
30889
|
|
|
29105
30890
|
// src/commands/site/index.ts
|
|
29106
30891
|
import { existsSync as existsSync4, readFileSync as readFileSync25 } from "fs";
|
|
30892
|
+
|
|
30893
|
+
// src/commands/site/librarySections.ts
|
|
30894
|
+
var NO_SECTION = "none";
|
|
30895
|
+
function isStringArray(value) {
|
|
30896
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
30897
|
+
}
|
|
30898
|
+
function toSectionDrafts(rows) {
|
|
30899
|
+
return rows.map((row, index) => {
|
|
30900
|
+
if (typeof row !== "object" || row === null || Array.isArray(row)) {
|
|
30901
|
+
throw new Error(`--sections[${index}] must be an object {id?, name, productIds}.`);
|
|
30902
|
+
}
|
|
30903
|
+
const name = "name" in row ? row.name : void 0;
|
|
30904
|
+
const productIds = "productIds" in row ? row.productIds : [];
|
|
30905
|
+
const id = "id" in row ? row.id : void 0;
|
|
30906
|
+
if (typeof name !== "string") throw new Error(`--sections[${index}].name must be a string.`);
|
|
30907
|
+
if (!isStringArray(productIds)) {
|
|
30908
|
+
throw new Error(`--sections[${index}].productIds must be an array of product ids.`);
|
|
30909
|
+
}
|
|
30910
|
+
if (id !== void 0 && id !== null && typeof id !== "string") {
|
|
30911
|
+
throw new Error(`--sections[${index}].id must be a section id or null.`);
|
|
30912
|
+
}
|
|
30913
|
+
return { ...typeof id === "string" ? { id } : {}, name, productIds };
|
|
30914
|
+
});
|
|
30915
|
+
}
|
|
30916
|
+
function printSectionsView(result) {
|
|
30917
|
+
printTable(
|
|
30918
|
+
result.sections.map((section) => ({
|
|
30919
|
+
id: section.id,
|
|
30920
|
+
name: section.name,
|
|
30921
|
+
products: section.products.map((product) => product.name).join(", ")
|
|
30922
|
+
})),
|
|
30923
|
+
["id", "name", "products"]
|
|
30924
|
+
);
|
|
30925
|
+
if (result.unassignedProducts.length > 0) {
|
|
30926
|
+
process.stdout.write(
|
|
30927
|
+
`
|
|
30928
|
+
Pozosta\u0142e (no section): ${result.unassignedProducts.map((product) => product.name).join(", ")}
|
|
30929
|
+
`
|
|
30930
|
+
);
|
|
30931
|
+
}
|
|
30932
|
+
}
|
|
30933
|
+
function printStoredSections(result, done) {
|
|
30934
|
+
process.stdout.write(`${done}
|
|
30935
|
+
`);
|
|
30936
|
+
printTable(
|
|
30937
|
+
result.sections.map((section) => ({
|
|
30938
|
+
id: section.id,
|
|
30939
|
+
name: section.name,
|
|
30940
|
+
products: section.productIds.length
|
|
30941
|
+
})),
|
|
30942
|
+
["id", "name", "products"]
|
|
30943
|
+
);
|
|
30944
|
+
if (result.droppedProductIds.length > 0) {
|
|
30945
|
+
process.stdout.write(
|
|
30946
|
+
`\u26A0\uFE0F Not stored (unknown, deleted or a bundle): ${result.droppedProductIds.join(", ")}
|
|
30947
|
+
`
|
|
30948
|
+
);
|
|
30949
|
+
}
|
|
30950
|
+
}
|
|
30951
|
+
function registerSiteLibrarySectionsCommands(site) {
|
|
30952
|
+
const sections = site.command("library-sections").description(
|
|
30953
|
+
"Sections of the member library (\u201EKrok 1\u201D, \u201EBonusy\u201D): a heading + a filter pill each; products in no section show under \u201EPozosta\u0142e\u201D. Products only, never bundles"
|
|
30954
|
+
);
|
|
30955
|
+
sections.command("list").description("The sections in display order with their products, and the unassigned products").action(async (_options, command) => {
|
|
30956
|
+
const flags = getGlobalFlags(command);
|
|
30957
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
30958
|
+
const result = await client.listLibrarySections();
|
|
30959
|
+
emit(flags, result, () => printSectionsView(result));
|
|
30960
|
+
});
|
|
30961
|
+
sections.command("create").description("Add a section (last by default); listed products move into it from any other").requiredOption("--name <name>", "heading + pill label, unique in the workspace").option("--products <a,b,...>", "product ids to put in it", parseCommaList("--products")).option(
|
|
30962
|
+
"--position <n>",
|
|
30963
|
+
"0-based place among the sections (default: last)",
|
|
30964
|
+
parseNumberOption("--position", NumberFormat.Integer)
|
|
30965
|
+
).option("--confirm", "required \u2014 acknowledge the change of the member library").action(
|
|
30966
|
+
async (options, command) => {
|
|
30967
|
+
const flags = getGlobalFlags(command);
|
|
30968
|
+
requireConfirm(options.confirm, "add a section to the member library");
|
|
30969
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
30970
|
+
const result = await client.createLibrarySection({
|
|
30971
|
+
name: options.name,
|
|
30972
|
+
...options.products !== void 0 ? { productIds: options.products } : {},
|
|
30973
|
+
...options.position !== void 0 ? { position: options.position } : {}
|
|
30974
|
+
});
|
|
30975
|
+
emit(
|
|
30976
|
+
flags,
|
|
30977
|
+
result,
|
|
30978
|
+
() => printStoredSections(result, `Created section ${result.sectionId ?? ""}.`)
|
|
30979
|
+
);
|
|
30980
|
+
}
|
|
30981
|
+
);
|
|
30982
|
+
sections.command("rename").description("Rename a section (its id and products stay)").argument("<sectionId>", "section id (`libsec_\u2026` from `site library-sections list`)").requiredOption("--name <name>", "the new heading").option("--confirm", "required \u2014 acknowledge the change of the member library").action(
|
|
30983
|
+
async (sectionId, options, command) => {
|
|
30984
|
+
const flags = getGlobalFlags(command);
|
|
30985
|
+
requireConfirm(options.confirm, `rename section ${sectionId}`);
|
|
30986
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
30987
|
+
const result = await client.renameLibrarySection(sectionId, options.name);
|
|
30988
|
+
emit(flags, result, () => printStoredSections(result, `Renamed section ${sectionId}.`));
|
|
30989
|
+
}
|
|
30990
|
+
);
|
|
30991
|
+
sections.command("reorder").description("Set the section order \u2014 pass EVERY section id once, top first").argument("<sectionIds...>", "section ids in the new order").option("--confirm", "required \u2014 acknowledge the change of the member library").action(async (sectionIds, options, command) => {
|
|
30992
|
+
const flags = getGlobalFlags(command);
|
|
30993
|
+
requireConfirm(options.confirm, "reorder the library sections");
|
|
30994
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
30995
|
+
const result = await client.reorderLibrarySections(sectionIds);
|
|
30996
|
+
emit(flags, result, () => printStoredSections(result, "Reordered the sections."));
|
|
30997
|
+
});
|
|
30998
|
+
sections.command("move").description("Move products into a section (they leave any other), or to none (\u201EPozosta\u0142e\u201D)").argument("<productIds...>", "product ids").requiredOption("--to <sectionId|none>", `target section id, or "${NO_SECTION}"`).option("--confirm", "required \u2014 acknowledge the change of the member library").action(
|
|
30999
|
+
async (productIds, options, command) => {
|
|
31000
|
+
const flags = getGlobalFlags(command);
|
|
31001
|
+
const sectionId = options.to.trim().toLowerCase() === NO_SECTION ? null : options.to;
|
|
31002
|
+
requireConfirm(options.confirm, "move products between library sections");
|
|
31003
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
31004
|
+
const result = await client.moveLibraryProducts(productIds, sectionId);
|
|
31005
|
+
emit(
|
|
31006
|
+
flags,
|
|
31007
|
+
result,
|
|
31008
|
+
() => printStoredSections(
|
|
31009
|
+
result,
|
|
31010
|
+
`Moved ${productIds.length} product(s) to ${sectionId ?? "no section"}.`
|
|
31011
|
+
)
|
|
31012
|
+
);
|
|
31013
|
+
}
|
|
31014
|
+
);
|
|
31015
|
+
sections.command("replace").description(
|
|
31016
|
+
"Full replace of the ordered list \u2014 sections left out are DELETED (their products go to \u201EPozosta\u0142e\u201D); keep a section by sending its id"
|
|
31017
|
+
).requiredOption(
|
|
31018
|
+
"--sections <json|file>",
|
|
31019
|
+
"JSON array of {id?, name, productIds} (read the current list with `--json site library-sections list`)",
|
|
31020
|
+
parseJsonArrayFileOrInline("--sections")
|
|
31021
|
+
).option("--confirm", "required \u2014 acknowledge the full replace").action(async (options, command) => {
|
|
31022
|
+
const flags = getGlobalFlags(command);
|
|
31023
|
+
const drafts = toSectionDrafts(options.sections);
|
|
31024
|
+
requireConfirm(options.confirm, "replace every library section");
|
|
31025
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
31026
|
+
const result = await client.replaceLibrarySections(drafts);
|
|
31027
|
+
emit(
|
|
31028
|
+
flags,
|
|
31029
|
+
result,
|
|
31030
|
+
() => printStoredSections(result, `Stored ${result.sections.length} section(s).`)
|
|
31031
|
+
);
|
|
31032
|
+
});
|
|
31033
|
+
sections.command("delete").description("Delete a section; its products go back to \u201EPozosta\u0142e\u201D (no product is deleted)").argument("<sectionId>", "section id").option("--confirm", "required \u2014 acknowledge the deletion of the section").action(async (sectionId, options, command) => {
|
|
31034
|
+
const flags = getGlobalFlags(command);
|
|
31035
|
+
requireConfirm(options.confirm, `delete library section ${sectionId}`);
|
|
31036
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
31037
|
+
const result = await client.deleteLibrarySection(sectionId);
|
|
31038
|
+
emit(flags, result, () => printStoredSections(result, `Deleted section ${sectionId}.`));
|
|
31039
|
+
});
|
|
31040
|
+
}
|
|
31041
|
+
|
|
31042
|
+
// src/commands/site/index.ts
|
|
29107
31043
|
var MAIN_PAGE_MODES = {
|
|
29108
31044
|
sales: "/",
|
|
29109
31045
|
dashboard: "/dashboard",
|
|
@@ -29149,7 +31085,7 @@ function parseJsonObjectFileOrInline5(value, flag) {
|
|
|
29149
31085
|
function baseBody(json) {
|
|
29150
31086
|
return json === void 0 ? {} : parseJsonObjectFileOrInline5(json, "--json");
|
|
29151
31087
|
}
|
|
29152
|
-
function
|
|
31088
|
+
function parseOnOff5(value, flag) {
|
|
29153
31089
|
const v = value.trim().toLowerCase();
|
|
29154
31090
|
if (v === "on" || v === "true") return true;
|
|
29155
31091
|
if (v === "off" || v === "false") return false;
|
|
@@ -29276,16 +31212,16 @@ function printDomainResults(results) {
|
|
|
29276
31212
|
}
|
|
29277
31213
|
}
|
|
29278
31214
|
function printLegalDocuments(docs) {
|
|
29279
|
-
const
|
|
31215
|
+
const preview2 = (content) => content === null || content === "" ? "" : `${content.length} chars${content.length > 60 ? ` \u2014 "${content.slice(0, 60).replace(/\s+/g, " ")}\u2026"` : ` \u2014 "${content}"`}`;
|
|
29280
31216
|
printTable(
|
|
29281
31217
|
[
|
|
29282
31218
|
{ field: "language", value: docs.language },
|
|
29283
31219
|
{ field: "termsUrl", value: docs.termsUrl },
|
|
29284
31220
|
{ field: "termsSlug", value: docs.termsSlug ?? "" },
|
|
29285
|
-
{ field: "termsContent", value:
|
|
31221
|
+
{ field: "termsContent", value: preview2(docs.termsContent) },
|
|
29286
31222
|
{ field: "privacyPolicyUrl", value: docs.privacyPolicyUrl },
|
|
29287
31223
|
{ field: "privacyPolicySlug", value: docs.privacyPolicySlug ?? "" },
|
|
29288
|
-
{ field: "privacyPolicyContent", value:
|
|
31224
|
+
{ field: "privacyPolicyContent", value: preview2(docs.privacyPolicyContent) }
|
|
29289
31225
|
],
|
|
29290
31226
|
["field", "value"]
|
|
29291
31227
|
);
|
|
@@ -29386,9 +31322,12 @@ function registerSiteCommands2(program2) {
|
|
|
29386
31322
|
).option(
|
|
29387
31323
|
"--robots-rules <text>",
|
|
29388
31324
|
String.raw`extra robots.txt lines (User-agent / Allow / Disallow / Crawl-delay); separate lines with \n, or use --json`
|
|
31325
|
+
).option(
|
|
31326
|
+
"--llms-txt <text|file|none>",
|
|
31327
|
+
"the site's own /llms.txt (Markdown, inline or a file path); none = back to the generated file"
|
|
29389
31328
|
).option("--language <pl|en|default>", "UI language of the public site").option("--show-page-title <on|off>", "render the page title in the header").option(
|
|
29390
31329
|
"--json <fileOrJson>",
|
|
29391
|
-
"inline JSON object or .json path \u2014 pageTitle, pageDescription, seoDescription, robotsTxtRules, showPageTitle, language, mainPage, mainPageId, mainPageCommunityId"
|
|
31330
|
+
"inline JSON object or .json path \u2014 pageTitle, pageDescription, seoDescription, robotsTxtRules, llmsTxt, showPageTitle, language, mainPage, mainPageId, mainPageCommunityId"
|
|
29392
31331
|
).action(
|
|
29393
31332
|
async (options, command) => {
|
|
29394
31333
|
const flags = getGlobalFlags(command);
|
|
@@ -29399,13 +31338,19 @@ function registerSiteCommands2(program2) {
|
|
|
29399
31338
|
if (options.robotsRules !== void 0) {
|
|
29400
31339
|
body.robotsTxtRules = options.robotsRules.split(String.raw`\n`).join("\n");
|
|
29401
31340
|
}
|
|
31341
|
+
if (options.llmsTxt !== void 0) {
|
|
31342
|
+
body.llmsTxt = options.llmsTxt.trim().toLowerCase() === "none" ? null : readMarkdownOrFile(options.llmsTxt);
|
|
31343
|
+
}
|
|
29402
31344
|
if (options.language !== void 0) {
|
|
29403
31345
|
body.language = parseChoice7(options.language, PLATFORM_LANGUAGES, "--language");
|
|
29404
31346
|
}
|
|
29405
31347
|
if (options.showPageTitle !== void 0) {
|
|
29406
|
-
body.showPageTitle =
|
|
31348
|
+
body.showPageTitle = parseOnOff5(options.showPageTitle, "--show-page-title");
|
|
29407
31349
|
}
|
|
29408
|
-
assertNonEmptyBody(
|
|
31350
|
+
assertNonEmptyBody(
|
|
31351
|
+
body,
|
|
31352
|
+
"pass --title, --description, --llms-txt, --language or --json=<patch>."
|
|
31353
|
+
);
|
|
29409
31354
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
29410
31355
|
const result = await client.updateSiteGeneral(body).catch(withMainPageHint);
|
|
29411
31356
|
emit(flags, result, () => process.stdout.write(SECTION_UPDATED("general")));
|
|
@@ -29425,7 +31370,7 @@ function registerSiteCommands2(program2) {
|
|
|
29425
31370
|
if (options.radius !== void 0) body.radius = options.radius;
|
|
29426
31371
|
if (options.styleVariant !== void 0) body.styleVariant = options.styleVariant;
|
|
29427
31372
|
if (options.fromBrand !== void 0) {
|
|
29428
|
-
body.themeFromBrand =
|
|
31373
|
+
body.themeFromBrand = parseOnOff5(options.fromBrand, "--from-brand");
|
|
29429
31374
|
}
|
|
29430
31375
|
assertNonEmptyBody(body, "pass --theme, --radius, --from-brand \u2026 or --json=<patch>.");
|
|
29431
31376
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -29452,7 +31397,7 @@ function registerSiteCommands2(program2) {
|
|
|
29452
31397
|
body.platformFaviconUrl = orNull6(options.faviconUrl);
|
|
29453
31398
|
}
|
|
29454
31399
|
if (options.hideBranding !== void 0) {
|
|
29455
|
-
body.hideZanfiaBranding =
|
|
31400
|
+
body.hideZanfiaBranding = parseOnOff5(options.hideBranding, "--hide-branding");
|
|
29456
31401
|
}
|
|
29457
31402
|
assertNonEmptyBody(
|
|
29458
31403
|
body,
|
|
@@ -29497,7 +31442,7 @@ function registerSiteCommands2(program2) {
|
|
|
29497
31442
|
update.command("privacy").description("Cookie-consent banner on the public site").option("--cookie-consent <on|off>", "show the cookie consent banner").option("--json <fileOrJson>", "inline JSON object or .json path \u2014 cookieConsentEnabled").action(async (options, command) => {
|
|
29498
31443
|
const flags = getGlobalFlags(command);
|
|
29499
31444
|
const base = baseBody(options.json);
|
|
29500
|
-
const cookieConsentEnabled = options.cookieConsent !== void 0 ?
|
|
31445
|
+
const cookieConsentEnabled = options.cookieConsent !== void 0 ? parseOnOff5(options.cookieConsent, "--cookie-consent") : base.cookieConsentEnabled;
|
|
29501
31446
|
if (typeof cookieConsentEnabled !== "boolean") {
|
|
29502
31447
|
throw new Error(
|
|
29503
31448
|
'Pass --cookie-consent on|off (or a --json body with "cookieConsentEnabled").'
|
|
@@ -29564,14 +31509,14 @@ function registerSiteCommands2(program2) {
|
|
|
29564
31509
|
if (options.libraryOrder === void 0) customization.libraryOrder = "custom";
|
|
29565
31510
|
}
|
|
29566
31511
|
if (options.groupByType !== void 0) {
|
|
29567
|
-
customization.libraryGroupByType =
|
|
31512
|
+
customization.libraryGroupByType = parseOnOff5(options.groupByType, "--group-by-type");
|
|
29568
31513
|
}
|
|
29569
31514
|
if (options.defaultView !== void 0) customization.defaultView = options.defaultView;
|
|
29570
31515
|
if (options.messages !== void 0) {
|
|
29571
|
-
customization.messagesEnabled =
|
|
31516
|
+
customization.messagesEnabled = parseOnOff5(options.messages, "--messages");
|
|
29572
31517
|
}
|
|
29573
31518
|
if (options.clientMessages !== void 0) {
|
|
29574
|
-
customization.clientMessagesEnabled =
|
|
31519
|
+
customization.clientMessagesEnabled = parseOnOff5(
|
|
29575
31520
|
options.clientMessages,
|
|
29576
31521
|
"--client-messages"
|
|
29577
31522
|
);
|
|
@@ -29810,6 +31755,7 @@ function registerSiteCommands2(program2) {
|
|
|
29810
31755
|
});
|
|
29811
31756
|
}
|
|
29812
31757
|
);
|
|
31758
|
+
registerSiteLibrarySectionsCommands(site);
|
|
29813
31759
|
}
|
|
29814
31760
|
function printStorefront(config) {
|
|
29815
31761
|
printTable(
|
|
@@ -30321,6 +32267,96 @@ Next page: --after ${response.nextCursor}`);
|
|
|
30321
32267
|
});
|
|
30322
32268
|
}
|
|
30323
32269
|
|
|
32270
|
+
// src/commands/social/insights.ts
|
|
32271
|
+
var HIDDEN = { hidden: true };
|
|
32272
|
+
function parsePrivacy(value) {
|
|
32273
|
+
const privacy = YOUTUBE_PRIVACY_STATUSES.find(
|
|
32274
|
+
(candidate) => candidate === value.trim().toLowerCase()
|
|
32275
|
+
);
|
|
32276
|
+
if (privacy === void 0) {
|
|
32277
|
+
throw new Error(
|
|
32278
|
+
`Invalid --privacy: ${value} (expected one of: ${YOUTUBE_PRIVACY_STATUSES.join(", ")})`
|
|
32279
|
+
);
|
|
32280
|
+
}
|
|
32281
|
+
return privacy;
|
|
32282
|
+
}
|
|
32283
|
+
var formatNumber = (value) => value ?? "";
|
|
32284
|
+
function registerSocialInsightsCommands(social) {
|
|
32285
|
+
social.command("weekly", HIDDEN).description(
|
|
32286
|
+
"Studio \u201ETw\xF3j tydzie\u0144 w social media\u201D: last week's posts per account against your own median (top / bottom posts and signals in `--json`)"
|
|
32287
|
+
).action(async (_options, command) => {
|
|
32288
|
+
const flags = getGlobalFlags(command);
|
|
32289
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
32290
|
+
const report = await client.getSocialWeekReport();
|
|
32291
|
+
emit(flags, report, () => {
|
|
32292
|
+
process.stdout.write(`Week ${report.weekStart} \u2192 ${report.weekEnd}
|
|
32293
|
+
`);
|
|
32294
|
+
printTable(
|
|
32295
|
+
report.networks.map((network) => ({
|
|
32296
|
+
network: network.network,
|
|
32297
|
+
account: network.label,
|
|
32298
|
+
state: network.state,
|
|
32299
|
+
metric: network.metric ?? "",
|
|
32300
|
+
median: formatNumber(network.baselineMedian),
|
|
32301
|
+
postsThisWeek: network.postsThisWeek
|
|
32302
|
+
})),
|
|
32303
|
+
["network", "account", "state", "metric", "median", "postsThisWeek"]
|
|
32304
|
+
);
|
|
32305
|
+
});
|
|
32306
|
+
});
|
|
32307
|
+
social.command("performance", HIDDEN).description(
|
|
32308
|
+
"One Zanfia post's numbers per target against your median (\u201Eten post jeszcze \u017Cyje\u201D); the curve in `--json`"
|
|
32309
|
+
).argument("<postId>", "social post id (from `social posts`)").action(async (postId, _options, command) => {
|
|
32310
|
+
const flags = getGlobalFlags(command);
|
|
32311
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
32312
|
+
const performance = await client.getSocialPostPerformance(postId);
|
|
32313
|
+
emit(
|
|
32314
|
+
flags,
|
|
32315
|
+
performance,
|
|
32316
|
+
() => printTable(
|
|
32317
|
+
performance.targets.map((target) => ({
|
|
32318
|
+
target: target.target,
|
|
32319
|
+
state: target.state,
|
|
32320
|
+
metric: target.metric ?? "",
|
|
32321
|
+
score: formatNumber(target.score),
|
|
32322
|
+
median: formatNumber(target.baselineMedian),
|
|
32323
|
+
publishedAt: target.publishedAt ?? ""
|
|
32324
|
+
})),
|
|
32325
|
+
["target", "state", "metric", "score", "median", "publishedAt"]
|
|
32326
|
+
)
|
|
32327
|
+
);
|
|
32328
|
+
});
|
|
32329
|
+
const youtube = social.commands.find((command) => command.name() === "youtube") ?? social.command("youtube");
|
|
32330
|
+
youtube.command("visibility", HIDDEN).description(
|
|
32331
|
+
"Change the visibility of a post's published YouTube video (private / unlisted / public), or schedule its public release. Requires --confirm"
|
|
32332
|
+
).argument("<postId>", "the social post that published the video").requiredOption("--privacy <private|unlisted|public>", "the new visibility", parsePrivacy).option(
|
|
32333
|
+
"--publish-at <iso>",
|
|
32334
|
+
"public release time, ISO 8601 with an offset (only with --privacy private)"
|
|
32335
|
+
).option("--confirm", "required \u2014 acknowledge the change on YouTube").action(
|
|
32336
|
+
async (postId, options, command) => {
|
|
32337
|
+
const flags = getGlobalFlags(command);
|
|
32338
|
+
if (options.publishAt !== void 0 && options.privacy !== "private") {
|
|
32339
|
+
throw new Error("--publish-at needs --privacy private (YouTube makes it public then).");
|
|
32340
|
+
}
|
|
32341
|
+
requireConfirm(options.confirm, `change the YouTube visibility of post ${postId}`);
|
|
32342
|
+
const input = {
|
|
32343
|
+
privacyStatus: options.privacy,
|
|
32344
|
+
...options.publishAt !== void 0 ? { publishAt: options.publishAt } : {}
|
|
32345
|
+
};
|
|
32346
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
32347
|
+
const result = await client.setYouTubeVideoVisibility(postId, input);
|
|
32348
|
+
emit(
|
|
32349
|
+
flags,
|
|
32350
|
+
result,
|
|
32351
|
+
() => process.stdout.write(
|
|
32352
|
+
`Video ${result.videoId} is ${result.privacyStatus}${result.publishAt ? `, public from ${result.publishAt}` : ""}${result.demo ? " (demo: nothing was sent to YouTube)" : ""}.
|
|
32353
|
+
`
|
|
32354
|
+
)
|
|
32355
|
+
);
|
|
32356
|
+
}
|
|
32357
|
+
);
|
|
32358
|
+
}
|
|
32359
|
+
|
|
30324
32360
|
// src/commands/social/index.ts
|
|
30325
32361
|
function updateInput(options) {
|
|
30326
32362
|
const targets = splitList(options.targets);
|
|
@@ -30516,6 +32552,7 @@ function registerSocialCommands(program2) {
|
|
|
30516
32552
|
});
|
|
30517
32553
|
});
|
|
30518
32554
|
registerSocialChannelCommands(social);
|
|
32555
|
+
registerSocialInsightsCommands(social);
|
|
30519
32556
|
}
|
|
30520
32557
|
|
|
30521
32558
|
// src/commands/stats/index.ts
|
|
@@ -30928,6 +32965,148 @@ ${integration.name}: ${integration.healthExplanation}
|
|
|
30928
32965
|
});
|
|
30929
32966
|
}
|
|
30930
32967
|
|
|
32968
|
+
// src/commands/studio/drafts.ts
|
|
32969
|
+
import { readFileSync as readFileSync27 } from "fs";
|
|
32970
|
+
var TEXT_PREVIEW_LENGTH = 60;
|
|
32971
|
+
function parseWeakReason(value) {
|
|
32972
|
+
const reason = STUDIO_SOCIAL_DRAFT_WEAK_REASONS.find(
|
|
32973
|
+
(candidate) => candidate === value.trim().toLowerCase()
|
|
32974
|
+
);
|
|
32975
|
+
if (reason === void 0) {
|
|
32976
|
+
throw new Error(
|
|
32977
|
+
`Invalid --reason: ${value} (expected one of: ${STUDIO_SOCIAL_DRAFT_WEAK_REASONS.join(", ")})`
|
|
32978
|
+
);
|
|
32979
|
+
}
|
|
32980
|
+
return reason;
|
|
32981
|
+
}
|
|
32982
|
+
var SINGLE_ACCOUNT_TARGETS = [
|
|
32983
|
+
"instagram",
|
|
32984
|
+
"linkedin",
|
|
32985
|
+
"threads",
|
|
32986
|
+
"youtube",
|
|
32987
|
+
"tiktok",
|
|
32988
|
+
"telegram"
|
|
32989
|
+
];
|
|
32990
|
+
function parseTarget(value) {
|
|
32991
|
+
const target = value.trim();
|
|
32992
|
+
const single = SINGLE_ACCOUNT_TARGETS.find((candidate) => candidate === target);
|
|
32993
|
+
if (single !== void 0) return single;
|
|
32994
|
+
const separator = target.indexOf(":");
|
|
32995
|
+
const network = target.slice(0, separator);
|
|
32996
|
+
const accountId = target.slice(separator + 1);
|
|
32997
|
+
if (separator > 0 && accountId !== "") {
|
|
32998
|
+
if (network === "facebook") return `facebook:${accountId}`;
|
|
32999
|
+
if (network === "instagram") return `instagram:${accountId}`;
|
|
33000
|
+
}
|
|
33001
|
+
throw new Error(
|
|
33002
|
+
`Invalid target: ${value} (expected ${SINGLE_ACCOUNT_TARGETS.join(", ")}, facebook:<pageId> or instagram:<accountId>; see \`social accounts\`)`
|
|
33003
|
+
);
|
|
33004
|
+
}
|
|
33005
|
+
function preview(text) {
|
|
33006
|
+
const oneLine2 = text.replace(/\s+/g, " ").trim();
|
|
33007
|
+
return oneLine2.length > TEXT_PREVIEW_LENGTH ? `${oneLine2.slice(0, TEXT_PREVIEW_LENGTH)}\u2026` : oneLine2;
|
|
33008
|
+
}
|
|
33009
|
+
function printDrafts(accounts) {
|
|
33010
|
+
const rows = accounts.flatMap(
|
|
33011
|
+
(account) => account.drafts.map((draft) => ({
|
|
33012
|
+
id: draft.id,
|
|
33013
|
+
account: account.label,
|
|
33014
|
+
slot: draft.slot,
|
|
33015
|
+
status: draft.status,
|
|
33016
|
+
text: preview(draft.text)
|
|
33017
|
+
}))
|
|
33018
|
+
);
|
|
33019
|
+
printTable(rows, ["id", "account", "slot", "status", "text"]);
|
|
33020
|
+
for (const account of accounts) {
|
|
33021
|
+
process.stdout.write(`${account.label} (${account.target}): ${account.remaining} left
|
|
33022
|
+
`);
|
|
33023
|
+
}
|
|
33024
|
+
}
|
|
33025
|
+
function registerStudioDraftsCommands(program2) {
|
|
33026
|
+
const studio = program2.commands.find((command) => command.name() === "studio") ?? program2.command("studio");
|
|
33027
|
+
const drafts = studio.command("drafts", { hidden: true }).description("This week's social post drafts in your voice (Studio \u201E5 szkic\xF3w\u201D)");
|
|
33028
|
+
drafts.command("list").description("This week's drafts per connected account and how many each may still get").action(async (_options, command) => {
|
|
33029
|
+
const flags = getGlobalFlags(command);
|
|
33030
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
33031
|
+
const result = await client.listStudioSocialDrafts();
|
|
33032
|
+
emit(flags, result, () => {
|
|
33033
|
+
process.stdout.write(`Week of ${result.week}
|
|
33034
|
+
`);
|
|
33035
|
+
printDrafts(result.accounts);
|
|
33036
|
+
});
|
|
33037
|
+
});
|
|
33038
|
+
drafts.command("generate").description(
|
|
33039
|
+
"Write this week's drafts for one account; spends AI credits (402 when they are out)"
|
|
33040
|
+
).requiredOption("--target <target>", "the account target (from `social accounts`)", parseTarget).option("--topic <text>", "what the posts should be about").option("--language <pl|en>", "default: the workspace language").option("--confirm", "required \u2014 acknowledge the AI credits spent").action(
|
|
33041
|
+
async (options, command) => {
|
|
33042
|
+
const flags = getGlobalFlags(command);
|
|
33043
|
+
requireConfirm(options.confirm, "spend AI credits on new drafts");
|
|
33044
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
33045
|
+
const result = await client.generateStudioSocialDrafts({
|
|
33046
|
+
target: options.target,
|
|
33047
|
+
...options.topic !== void 0 ? { topic: options.topic } : {},
|
|
33048
|
+
...options.language !== void 0 ? { language: options.language } : {}
|
|
33049
|
+
});
|
|
33050
|
+
emit(flags, result, () => {
|
|
33051
|
+
process.stdout.write(
|
|
33052
|
+
`Wrote ${result.created} draft(s) for ${result.account.label}${result.rejectedByAiTells > 0 ? ` (${result.rejectedByAiTells} held back)` : ""}.
|
|
33053
|
+
`
|
|
33054
|
+
);
|
|
33055
|
+
printDrafts([result.account]);
|
|
33056
|
+
});
|
|
33057
|
+
}
|
|
33058
|
+
);
|
|
33059
|
+
drafts.command("feedback").description(
|
|
33060
|
+
"Mark a draft \u201ES\u0142abe\u201D with a reason: it is hidden and the next drafts learn from it"
|
|
33061
|
+
).argument("<draftId>", "draft id (from `studio drafts list`)").requiredOption(
|
|
33062
|
+
`--reason <${STUDIO_SOCIAL_DRAFT_WEAK_REASONS.join("|")}>`,
|
|
33063
|
+
"why it is weak",
|
|
33064
|
+
parseWeakReason
|
|
33065
|
+
).option("--confirm", "required \u2014 acknowledge the draft is hidden").action(
|
|
33066
|
+
async (draftId, options, command) => {
|
|
33067
|
+
const flags = getGlobalFlags(command);
|
|
33068
|
+
requireConfirm(options.confirm, `mark draft ${draftId} as weak`);
|
|
33069
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
33070
|
+
const result = await client.sendStudioSocialDraftFeedback(draftId, options.reason);
|
|
33071
|
+
emit(
|
|
33072
|
+
flags,
|
|
33073
|
+
result,
|
|
33074
|
+
() => process.stdout.write(
|
|
33075
|
+
`Draft ${draftId} is marked weak.${result.learnedRule ? ` Learned: ${result.learnedRule}` : ""}
|
|
33076
|
+
`
|
|
33077
|
+
)
|
|
33078
|
+
);
|
|
33079
|
+
}
|
|
33080
|
+
);
|
|
33081
|
+
drafts.command("to-composer").description("Turn a draft into a DRAFT social post (nothing is published or scheduled)").argument("<draftId>", "draft id").option("--text <text>", "the edited text (default: the draft as written)").option("--text-file <path>", "read the edited text from a file").option(
|
|
33082
|
+
"--targets <a,b,...>",
|
|
33083
|
+
"accounts to post to (default: the draft's own account)",
|
|
33084
|
+
parseCommaList("--targets")
|
|
33085
|
+
).option("--confirm", "required \u2014 acknowledge a draft post is created").action(
|
|
33086
|
+
async (draftId, options, command) => {
|
|
33087
|
+
const flags = getGlobalFlags(command);
|
|
33088
|
+
if (options.text !== void 0 && options.textFile !== void 0) {
|
|
33089
|
+
throw new Error("Use either --text or --text-file, not both.");
|
|
33090
|
+
}
|
|
33091
|
+
const text = options.textFile !== void 0 ? readFileSync27(options.textFile, "utf8") : options.text;
|
|
33092
|
+
requireConfirm(options.confirm, `create a draft social post from ${draftId}`);
|
|
33093
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
33094
|
+
const result = await client.sendStudioSocialDraftToComposer(draftId, {
|
|
33095
|
+
...text !== void 0 ? { text } : {},
|
|
33096
|
+
...options.targets !== void 0 ? { targets: options.targets.map((target) => parseTarget(target)) } : {}
|
|
33097
|
+
});
|
|
33098
|
+
emit(
|
|
33099
|
+
flags,
|
|
33100
|
+
result,
|
|
33101
|
+
() => process.stdout.write(
|
|
33102
|
+
`Draft post ${result.socialPostId} is in Social \u203A Posty (nothing is published).
|
|
33103
|
+
`
|
|
33104
|
+
)
|
|
33105
|
+
);
|
|
33106
|
+
}
|
|
33107
|
+
);
|
|
33108
|
+
}
|
|
33109
|
+
|
|
30931
33110
|
// src/commands/subscriptions/index.ts
|
|
30932
33111
|
function formatAmount4(amountMinor, currency) {
|
|
30933
33112
|
return `${(amountMinor / 100).toFixed(2)} ${currency.toUpperCase()}`;
|
|
@@ -30965,7 +33144,7 @@ function describeRetryStatus(status) {
|
|
|
30965
33144
|
return "Failed \u2014 the retry did not go through.";
|
|
30966
33145
|
}
|
|
30967
33146
|
}
|
|
30968
|
-
function
|
|
33147
|
+
function registerSubscriptionsCommands2(program2) {
|
|
30969
33148
|
const subscriptions = program2.command("subscriptions").description("Recurring billing: failed payments, manual renewals, subscriptions ending soon");
|
|
30970
33149
|
subscriptions.command("failed").description(
|
|
30971
33150
|
"Failed subscription / installment charges with live Stripe dunning state (attempts, next automatic retry, hosted invoice URL). Retry one with `subscriptions retry`"
|
|
@@ -31557,7 +33736,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
31557
33736
|
}
|
|
31558
33737
|
|
|
31559
33738
|
// src/commands/workflows/index.ts
|
|
31560
|
-
import { readFileSync as
|
|
33739
|
+
import { readFileSync as readFileSync28 } from "fs";
|
|
31561
33740
|
|
|
31562
33741
|
// src/commands/workflows/authoring.ts
|
|
31563
33742
|
import { writeFileSync as writeFileSync6 } from "fs";
|
|
@@ -31740,7 +33919,7 @@ function requireConfirm17(confirm, what) {
|
|
|
31740
33919
|
}
|
|
31741
33920
|
function parseTriggerContext(value) {
|
|
31742
33921
|
const trimmed = value.trim();
|
|
31743
|
-
const raw = trimmed.startsWith("{") ? trimmed :
|
|
33922
|
+
const raw = trimmed.startsWith("{") ? trimmed : readFileSync28(value, "utf8");
|
|
31744
33923
|
let parsed;
|
|
31745
33924
|
try {
|
|
31746
33925
|
parsed = JSON.parse(raw);
|
|
@@ -31809,7 +33988,9 @@ function registerWorkflowsCommands(program2) {
|
|
|
31809
33988
|
printWorkflowGraphs(workflow, published, draft, templateNames);
|
|
31810
33989
|
});
|
|
31811
33990
|
});
|
|
31812
|
-
|
|
33991
|
+
const runs = workflows.command("runs").description(
|
|
33992
|
+
"List recent runs of a workflow (newest first); `workflows runs stop` takes one person out of a run"
|
|
33993
|
+
).argument("<automationId>", "workflow automation id").option("--limit <n>", "number of runs to return (default 20, max 100)").action(async (automationId, options, command) => {
|
|
31813
33994
|
const flags = getGlobalFlags(command);
|
|
31814
33995
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
31815
33996
|
const page = await client.listAutomationRuns(
|
|
@@ -31831,6 +34012,23 @@ function registerWorkflowsCommands(program2) {
|
|
|
31831
34012
|
);
|
|
31832
34013
|
});
|
|
31833
34014
|
});
|
|
34015
|
+
runs.command("stop").description(
|
|
34016
|
+
"Take the person out of this run \u2014 it ends cancelled and nothing after the current step runs (a step already sending finishes). A finished run is left as it is. They can enter again when the trigger fires later. Requires --confirm"
|
|
34017
|
+
).argument("<automationId>", "workflow automation id").argument("<runId>", "run id (from `workflows runs` or `clients automation-runs list`)").option("--confirm", "required \u2014 acknowledge that the run ends for this person").action(
|
|
34018
|
+
async (automationId, runId, options, command) => {
|
|
34019
|
+
const flags = getGlobalFlags(command);
|
|
34020
|
+
requireConfirm17(options.confirm, `stop run ${runId} (no further steps for this person)`);
|
|
34021
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
34022
|
+
const result = await client.stopAutomationRun(automationId, runId);
|
|
34023
|
+
emit(flags, result, () => {
|
|
34024
|
+
process.stdout.write(
|
|
34025
|
+
result.stopped.length > 0 ? `Stopped run ${runId} of workflow ${automationId}.
|
|
34026
|
+
` : `Run ${runId} had already finished \u2014 nothing to stop.
|
|
34027
|
+
`
|
|
34028
|
+
);
|
|
34029
|
+
});
|
|
34030
|
+
}
|
|
34031
|
+
);
|
|
31834
34032
|
workflows.command("pause").description(
|
|
31835
34033
|
"Pause a workflow \u2014 it stops reacting to its trigger events AND cancels runs that are in progress or waiting in a delay (those contacts get no further steps)"
|
|
31836
34034
|
).argument("<automationId>", "workflow automation id").action(async (automationId, _, command) => {
|
|
@@ -31846,13 +34044,36 @@ function registerWorkflowsCommands(program2) {
|
|
|
31846
34044
|
});
|
|
31847
34045
|
workflows.command("resume").description(
|
|
31848
34046
|
"Resume a paused workflow (requires a published version \u2014 a never-published draft cannot run)"
|
|
31849
|
-
).argument("<automationId>", "workflow automation id").
|
|
34047
|
+
).argument("<automationId>", "workflow automation id").option(
|
|
34048
|
+
"--held-events <decision>",
|
|
34049
|
+
"a webhook paused after failed deliveries holds the events that reached it (14 days each): `send` delivers them (30 a minute; a failure pauses it again and the rest wait), `skip` drops them. Without it such a resume is refused with the count."
|
|
34050
|
+
).action(async (automationId, options, command) => {
|
|
31850
34051
|
const flags = getGlobalFlags(command);
|
|
34052
|
+
const heldEvents = HELD_EVENTS_DECISIONS.find((value) => value === options.heldEvents);
|
|
34053
|
+
if (options.heldEvents !== void 0 && !heldEvents) {
|
|
34054
|
+
throw new ApiError(0, "--held-events must be `send` or `skip`");
|
|
34055
|
+
}
|
|
31851
34056
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
31852
|
-
const result = await client.setAutomationStatus(automationId, "active")
|
|
34057
|
+
const result = await client.setAutomationStatus(automationId, "active", heldEvents).catch((error) => {
|
|
34058
|
+
if (error instanceof ApiError && error.status === 409 && !heldEvents) {
|
|
34059
|
+
throw new ApiError(
|
|
34060
|
+
409,
|
|
34061
|
+
error.apiMessage,
|
|
34062
|
+
"Run again with --held-events send (deliver them) or --held-events skip (drop them)."
|
|
34063
|
+
);
|
|
34064
|
+
}
|
|
34065
|
+
throw error;
|
|
34066
|
+
});
|
|
31853
34067
|
emit(flags, result, () => {
|
|
31854
34068
|
process.stdout.write(`Resumed workflow ${result.id}.
|
|
31855
34069
|
`);
|
|
34070
|
+
if (result.heldEvents) {
|
|
34071
|
+
process.stdout.write(
|
|
34072
|
+
result.heldEvents.decision === "send" ? `${result.heldEvents.count} held event(s) are being sent, 30 a minute.
|
|
34073
|
+
` : `${result.heldEvents.count} held event(s) were dropped.
|
|
34074
|
+
`
|
|
34075
|
+
);
|
|
34076
|
+
}
|
|
31856
34077
|
});
|
|
31857
34078
|
});
|
|
31858
34079
|
workflows.command("retry").description(
|
|
@@ -32269,7 +34490,7 @@ function describeDelay(config) {
|
|
|
32269
34490
|
return `wait until ${optionalString(config.date) ?? "?"}${optionalString(config.timezone) ? ` (${config.timezone})` : ""}`;
|
|
32270
34491
|
case "untilContextDate": {
|
|
32271
34492
|
const offset = typeof config.offsetMinutes === "number" ? config.offsetMinutes : 0;
|
|
32272
|
-
return `wait until ${optionalString(config.contextKey) ?? "?"}${offset !== 0 ? ` ${offset > 0 ? "+" : ""}${offset} min` : ""}${config.skipWhenPast === true ? " (skip branch when past)" : ""}`;
|
|
34493
|
+
return `wait until ${optionalString(config.contextKey) ?? "?"}${offset !== 0 ? ` ${offset > 0 ? "+" : ""}${offset} min` : ""}${config.skipWhenPast === true ? config.skipToNextWait === true ? " (skip to the next wait when past)" : " (skip branch when past)" : ""}`;
|
|
32273
34494
|
}
|
|
32274
34495
|
default:
|
|
32275
34496
|
return `wait (${mode})`;
|
|
@@ -32324,7 +34545,7 @@ function printLogins(profiles) {
|
|
|
32324
34545
|
"\n* = active. Switch: `zanfia workspace use <id|slug>`; one command: `--workspace <id|slug>`.\n"
|
|
32325
34546
|
);
|
|
32326
34547
|
}
|
|
32327
|
-
function
|
|
34548
|
+
function parseOnOff6(value, flag) {
|
|
32328
34549
|
const match = ["on", "off"].find((v) => v === value);
|
|
32329
34550
|
if (!match) throw new Error(`Invalid ${flag}: ${value} (expected on | off)`);
|
|
32330
34551
|
return match === "on";
|
|
@@ -32407,7 +34628,7 @@ function registerWorkspaceCommands(program2) {
|
|
|
32407
34628
|
}
|
|
32408
34629
|
if (options.inheritTimezone) input.timezone = null;
|
|
32409
34630
|
if (options.autoTranscribe !== void 0) {
|
|
32410
|
-
input.autoTranscribeUploads =
|
|
34631
|
+
input.autoTranscribeUploads = parseOnOff6(options.autoTranscribe, "--auto-transcribe");
|
|
32411
34632
|
}
|
|
32412
34633
|
if (options.transcriptionLanguage !== void 0) {
|
|
32413
34634
|
input.transcriptionLanguage = parseTranscriptionLanguage(options.transcriptionLanguage);
|
|
@@ -32478,11 +34699,11 @@ function applyShortHelp(program2) {
|
|
|
32478
34699
|
|
|
32479
34700
|
// src/program.ts
|
|
32480
34701
|
function resolveCliVersion() {
|
|
32481
|
-
if ("0.
|
|
32482
|
-
return "0.
|
|
34702
|
+
if ("0.23.0".length > 0) {
|
|
34703
|
+
return "0.23.0";
|
|
32483
34704
|
}
|
|
32484
34705
|
try {
|
|
32485
|
-
const raw =
|
|
34706
|
+
const raw = readFileSync29(new URL("../package.json", import.meta.url), "utf8");
|
|
32486
34707
|
const parsed = JSON.parse(raw);
|
|
32487
34708
|
if (typeof parsed === "object" && parsed !== null && "version" in parsed) {
|
|
32488
34709
|
const { version: version2 } = parsed;
|
|
@@ -32523,7 +34744,7 @@ Signing in \u2014 two roles, two consent pages:
|
|
|
32523
34744
|
registerJoinCommand(program2);
|
|
32524
34745
|
registerLogoutCommand(program2);
|
|
32525
34746
|
registerAuthCommands(program2);
|
|
32526
|
-
|
|
34747
|
+
registerProductsCommands2(program2);
|
|
32527
34748
|
registerOffersCommands(program2);
|
|
32528
34749
|
registerCheckoutSettingsCommands(program2);
|
|
32529
34750
|
registerIntegrationsCommands(program2);
|
|
@@ -32532,7 +34753,7 @@ Signing in \u2014 two roles, two consent pages:
|
|
|
32532
34753
|
registerOtoCommands(program2);
|
|
32533
34754
|
registerBumpsCommands(program2);
|
|
32534
34755
|
registerCoursesCommands(program2);
|
|
32535
|
-
|
|
34756
|
+
registerConsultationsCommands(program2);
|
|
32536
34757
|
registerNewslettersCommands(program2);
|
|
32537
34758
|
registerCommunitiesCommands(program2);
|
|
32538
34759
|
registerConversationsCommands(program2);
|
|
@@ -32545,8 +34766,8 @@ Signing in \u2014 two roles, two consent pages:
|
|
|
32545
34766
|
registerTestimonialsCommands(program2);
|
|
32546
34767
|
registerBrandCommands(program2);
|
|
32547
34768
|
registerTeamCommands(program2);
|
|
32548
|
-
|
|
32549
|
-
|
|
34769
|
+
registerOrdersCommands2(program2);
|
|
34770
|
+
registerSubscriptionsCommands2(program2);
|
|
32550
34771
|
registerStatsCommands(program2);
|
|
32551
34772
|
registerDiscountsCommands(program2);
|
|
32552
34773
|
registerReferralsCommands(program2);
|
|
@@ -32558,6 +34779,8 @@ Signing in \u2014 two roles, two consent pages:
|
|
|
32558
34779
|
registerSocialCommands(program2);
|
|
32559
34780
|
registerMediaCommands(program2);
|
|
32560
34781
|
registerFilesCommands(program2);
|
|
34782
|
+
registerStudioDraftsCommands(program2);
|
|
34783
|
+
registerGeneratedCommands(program2);
|
|
32561
34784
|
applyGlobalOptions(program2);
|
|
32562
34785
|
applyShortHelp(program2);
|
|
32563
34786
|
return program2;
|