@zanfia/cli 0.21.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -68
- package/dist/index.js +3092 -378
- 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 readFileSync31 } 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
|
},
|
|
@@ -8612,6 +8621,9 @@ function createMockClient() {
|
|
|
8612
8621
|
async previewAudienceSegment() {
|
|
8613
8622
|
throw new ApiError(501, "audience segments are not available in mock mode");
|
|
8614
8623
|
},
|
|
8624
|
+
async checkEmailConditions() {
|
|
8625
|
+
throw new ApiError(501, "e-mail conditions are not available in mock mode");
|
|
8626
|
+
},
|
|
8615
8627
|
async importClients() {
|
|
8616
8628
|
throw new ApiError(501, "client import is not available in mock mode");
|
|
8617
8629
|
},
|
|
@@ -8648,6 +8660,30 @@ function createMockClient() {
|
|
|
8648
8660
|
async rejectManualOrder() {
|
|
8649
8661
|
throw new ApiError(501, "manual order decisions are not available in mock mode");
|
|
8650
8662
|
},
|
|
8663
|
+
async markOrderAsTest() {
|
|
8664
|
+
throw new ApiError(501, "order changes are not available in mock mode");
|
|
8665
|
+
},
|
|
8666
|
+
async markOrderRefunded() {
|
|
8667
|
+
throw new ApiError(501, "order changes are not available in mock mode");
|
|
8668
|
+
},
|
|
8669
|
+
async listConsultationBookings() {
|
|
8670
|
+
return { items: [], nextCursor: null, from: "", to: "" };
|
|
8671
|
+
},
|
|
8672
|
+
async listConsultationClientsWithoutBooking() {
|
|
8673
|
+
return { items: [], nextCursor: null };
|
|
8674
|
+
},
|
|
8675
|
+
async listCourseQuestions() {
|
|
8676
|
+
return { questions: [], nextCursor: null };
|
|
8677
|
+
},
|
|
8678
|
+
async replyToCourseQuestion() {
|
|
8679
|
+
throw new ApiError(501, "lesson Q&A is not available in mock mode");
|
|
8680
|
+
},
|
|
8681
|
+
async setCourseQuestionStatus() {
|
|
8682
|
+
throw new ApiError(501, "lesson Q&A is not available in mock mode");
|
|
8683
|
+
},
|
|
8684
|
+
async acceptCourseAnswer() {
|
|
8685
|
+
throw new ApiError(501, "lesson Q&A is not available in mock mode");
|
|
8686
|
+
},
|
|
8651
8687
|
async listFailedSubscriptionPayments() {
|
|
8652
8688
|
return { items: [], stripeConnected: false };
|
|
8653
8689
|
},
|
|
@@ -8852,6 +8888,21 @@ function createMockClient() {
|
|
|
8852
8888
|
async listCommunityEventAttendees() {
|
|
8853
8889
|
throw new ApiError(501, "community events are not available in mock mode");
|
|
8854
8890
|
},
|
|
8891
|
+
async listCommunityJoinRequests() {
|
|
8892
|
+
return { requests: [], pendingCount: 0 };
|
|
8893
|
+
},
|
|
8894
|
+
async approveCommunityJoinRequests() {
|
|
8895
|
+
throw new ApiError(501, "community join requests are not available in mock mode");
|
|
8896
|
+
},
|
|
8897
|
+
async declineCommunityJoinRequests() {
|
|
8898
|
+
throw new ApiError(501, "community join requests are not available in mock mode");
|
|
8899
|
+
},
|
|
8900
|
+
async listCommunityDigestDrafts() {
|
|
8901
|
+
return { digestEnabled: false, current: null, history: [] };
|
|
8902
|
+
},
|
|
8903
|
+
async decideCommunityDigestDraft() {
|
|
8904
|
+
throw new ApiError(501, "the community digest is not available in mock mode");
|
|
8905
|
+
},
|
|
8855
8906
|
async getCommunityModerationQueue() {
|
|
8856
8907
|
return { pending: [], resolved: [] };
|
|
8857
8908
|
},
|
|
@@ -8924,6 +8975,36 @@ function createMockClient() {
|
|
|
8924
8975
|
async getLiveRoomAttendance() {
|
|
8925
8976
|
throw new ApiError(501, "webinars are not available in mock mode");
|
|
8926
8977
|
},
|
|
8978
|
+
async createExternalWebinar() {
|
|
8979
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
8980
|
+
},
|
|
8981
|
+
async setLiveRoomReplay() {
|
|
8982
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
8983
|
+
},
|
|
8984
|
+
async setLiveRoomStudioScene() {
|
|
8985
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
8986
|
+
},
|
|
8987
|
+
async createLiveRoomStudioGuestInvite() {
|
|
8988
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
8989
|
+
},
|
|
8990
|
+
async listLiveRoomStudioGuests() {
|
|
8991
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
8992
|
+
},
|
|
8993
|
+
async revokeLiveRoomStudioGuestInvite() {
|
|
8994
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
8995
|
+
},
|
|
8996
|
+
async listLiveRoomStudioOutputs() {
|
|
8997
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
8998
|
+
},
|
|
8999
|
+
async createLiveRoomStudioOutput() {
|
|
9000
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
9001
|
+
},
|
|
9002
|
+
async updateLiveRoomStudioOutput() {
|
|
9003
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
9004
|
+
},
|
|
9005
|
+
async deleteLiveRoomStudioOutput() {
|
|
9006
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
9007
|
+
},
|
|
8927
9008
|
async call(method, pathTemplate) {
|
|
8928
9009
|
throw new ApiError(501, `${method} ${pathTemplate} is not available in mock mode`);
|
|
8929
9010
|
},
|
|
@@ -8984,6 +9065,15 @@ function createMockClient() {
|
|
|
8984
9065
|
async createMetaAd() {
|
|
8985
9066
|
throw new ApiError(501, "Meta Ads are not available in mock mode");
|
|
8986
9067
|
},
|
|
9068
|
+
async createMetaCampaign() {
|
|
9069
|
+
throw new ApiError(501, "Meta Ads are not available in mock mode");
|
|
9070
|
+
},
|
|
9071
|
+
async createMetaAdSet() {
|
|
9072
|
+
throw new ApiError(501, "Meta Ads are not available in mock mode");
|
|
9073
|
+
},
|
|
9074
|
+
async createMetaAdFromScratch() {
|
|
9075
|
+
throw new ApiError(501, "Meta Ads are not available in mock mode");
|
|
9076
|
+
},
|
|
8987
9077
|
async updateMetaAd() {
|
|
8988
9078
|
throw new ApiError(501, "Meta Ads are not available in mock mode");
|
|
8989
9079
|
},
|
|
@@ -9014,6 +9104,9 @@ function createMockClient() {
|
|
|
9014
9104
|
async generateFunnelStepPage() {
|
|
9015
9105
|
throw new ApiError(501, "funnels are not available in mock mode");
|
|
9016
9106
|
},
|
|
9107
|
+
async addFunnelOfferPage() {
|
|
9108
|
+
throw new ApiError(501, "funnels are not available in mock mode");
|
|
9109
|
+
},
|
|
9017
9110
|
async listFunnelTemplates() {
|
|
9018
9111
|
return { templates: [] };
|
|
9019
9112
|
},
|
|
@@ -9044,6 +9137,9 @@ function createMockClient() {
|
|
|
9044
9137
|
async getSiteSettings() {
|
|
9045
9138
|
throw new ApiError(501, "site settings are not available in mock mode");
|
|
9046
9139
|
},
|
|
9140
|
+
async getSiteCustomScripts() {
|
|
9141
|
+
throw new ApiError(501, "site settings are not available in mock mode");
|
|
9142
|
+
},
|
|
9047
9143
|
async updateSiteGeneral() {
|
|
9048
9144
|
throw new ApiError(501, "site settings are not available in mock mode");
|
|
9049
9145
|
},
|
|
@@ -9092,6 +9188,66 @@ function createMockClient() {
|
|
|
9092
9188
|
async updateSiteLegalDocuments() {
|
|
9093
9189
|
throw new ApiError(501, "legal documents are not available in mock mode");
|
|
9094
9190
|
},
|
|
9191
|
+
async listLibrarySections() {
|
|
9192
|
+
return { sections: [], unassignedProducts: [] };
|
|
9193
|
+
},
|
|
9194
|
+
async replaceLibrarySections() {
|
|
9195
|
+
throw new ApiError(501, "library sections are not available in mock mode");
|
|
9196
|
+
},
|
|
9197
|
+
async createLibrarySection() {
|
|
9198
|
+
throw new ApiError(501, "library sections are not available in mock mode");
|
|
9199
|
+
},
|
|
9200
|
+
async reorderLibrarySections() {
|
|
9201
|
+
throw new ApiError(501, "library sections are not available in mock mode");
|
|
9202
|
+
},
|
|
9203
|
+
async moveLibraryProducts() {
|
|
9204
|
+
throw new ApiError(501, "library sections are not available in mock mode");
|
|
9205
|
+
},
|
|
9206
|
+
async renameLibrarySection() {
|
|
9207
|
+
throw new ApiError(501, "library sections are not available in mock mode");
|
|
9208
|
+
},
|
|
9209
|
+
async deleteLibrarySection() {
|
|
9210
|
+
throw new ApiError(501, "library sections are not available in mock mode");
|
|
9211
|
+
},
|
|
9212
|
+
async listStudioSocialDrafts() {
|
|
9213
|
+
throw new ApiError(501, "Studio drafts are not available in mock mode");
|
|
9214
|
+
},
|
|
9215
|
+
async generateStudioSocialDrafts() {
|
|
9216
|
+
throw new ApiError(501, "Studio drafts are not available in mock mode");
|
|
9217
|
+
},
|
|
9218
|
+
async sendStudioSocialDraftFeedback() {
|
|
9219
|
+
throw new ApiError(501, "Studio drafts are not available in mock mode");
|
|
9220
|
+
},
|
|
9221
|
+
async sendStudioSocialDraftToComposer() {
|
|
9222
|
+
throw new ApiError(501, "Studio drafts are not available in mock mode");
|
|
9223
|
+
},
|
|
9224
|
+
async getSocialWeekReport() {
|
|
9225
|
+
throw new ApiError(501, "social insights are not available in mock mode");
|
|
9226
|
+
},
|
|
9227
|
+
async getSocialPostPerformance() {
|
|
9228
|
+
throw new ApiError(501, "social insights are not available in mock mode");
|
|
9229
|
+
},
|
|
9230
|
+
async setYouTubeVideoVisibility() {
|
|
9231
|
+
throw new ApiError(501, "YouTube visibility is not available in mock mode");
|
|
9232
|
+
},
|
|
9233
|
+
async notifyCheckoutWaitlist() {
|
|
9234
|
+
throw new ApiError(501, "the offer waiting list is not available in mock mode");
|
|
9235
|
+
},
|
|
9236
|
+
async duplicateProduct() {
|
|
9237
|
+
throw new ApiError(501, "product duplicate is not available in mock mode");
|
|
9238
|
+
},
|
|
9239
|
+
async getSenderDomainWarmups() {
|
|
9240
|
+
return { warmups: [] };
|
|
9241
|
+
},
|
|
9242
|
+
async declareSenderDomainWarmed() {
|
|
9243
|
+
throw new ApiError(501, "domain warm-up is not available in mock mode");
|
|
9244
|
+
},
|
|
9245
|
+
async deleteClientEmailDrafts() {
|
|
9246
|
+
throw new ApiError(501, "client e-mails are not available in mock mode");
|
|
9247
|
+
},
|
|
9248
|
+
async resendFormSubmissionRoomLink() {
|
|
9249
|
+
throw new ApiError(501, "form submissions are not available in mock mode");
|
|
9250
|
+
},
|
|
9095
9251
|
async listClientDevices() {
|
|
9096
9252
|
throw new ApiError(501, "client devices are not available in mock mode");
|
|
9097
9253
|
},
|
|
@@ -9387,11 +9543,20 @@ function createHttpClient(config) {
|
|
|
9387
9543
|
"GET",
|
|
9388
9544
|
`/automations/${encodeURIComponent(id)}/runs${limit !== void 0 ? `?limit=${limit}` : ""}`
|
|
9389
9545
|
),
|
|
9390
|
-
setAutomationStatus: (id, status) => request("POST", `/automations/${encodeURIComponent(id)}/status`, {
|
|
9546
|
+
setAutomationStatus: (id, status, heldEvents) => request("POST", `/automations/${encodeURIComponent(id)}/status`, {
|
|
9547
|
+
status,
|
|
9548
|
+
...heldEvents ? { heldEvents } : {}
|
|
9549
|
+
}),
|
|
9391
9550
|
retryAutomationRun: (automationId, runId) => request(
|
|
9392
9551
|
"POST",
|
|
9393
9552
|
`/automations/${encodeURIComponent(automationId)}/runs/${encodeURIComponent(runId)}/retry`
|
|
9394
9553
|
),
|
|
9554
|
+
stopAutomationRun: (automationId, runId) => request(
|
|
9555
|
+
"POST",
|
|
9556
|
+
`/automations/${encodeURIComponent(automationId)}/runs/${encodeURIComponent(runId)}/stop`
|
|
9557
|
+
),
|
|
9558
|
+
listClientAutomationRuns: (clientId) => request("GET", `/clients/${encodeURIComponent(clientId)}/automation-runs`),
|
|
9559
|
+
stopClientAutomationRuns: (clientId) => request("POST", `/clients/${encodeURIComponent(clientId)}/automation-runs/stop`),
|
|
9395
9560
|
testRunAutomation: (automationId, input) => request("POST", `/automations/${encodeURIComponent(automationId)}/test-run`, input),
|
|
9396
9561
|
getCourseContent: (productId) => request("GET", `/products/${encodeURIComponent(productId)}/course`),
|
|
9397
9562
|
getCourseAnalytics: (productId, options) => {
|
|
@@ -9833,7 +9998,12 @@ function createHttpClient(config) {
|
|
|
9833
9998
|
createAudienceSegment: (input) => request("POST", "/segments", input),
|
|
9834
9999
|
updateAudienceSegment: (id, input) => request("PATCH", `/segments/${encodeURIComponent(id)}`, input),
|
|
9835
10000
|
deleteAudienceSegment: (id) => request("DELETE", `/segments/${encodeURIComponent(id)}`),
|
|
9836
|
-
previewAudienceSegment: (conditions) => request("POST", "/segments/preview", {
|
|
10001
|
+
previewAudienceSegment: (conditions, options = {}) => request("POST", "/segments/preview", {
|
|
10002
|
+
conditions,
|
|
10003
|
+
...options.requireSendable ? { requireSendable: true } : {},
|
|
10004
|
+
...options.requireSmsSendable ? { requireSmsSendable: true } : {}
|
|
10005
|
+
}),
|
|
10006
|
+
checkEmailConditions: (input) => request("POST", "/automations/check-email-conditions", input),
|
|
9837
10007
|
importClients: (input) => request("POST", "/clients/import", input),
|
|
9838
10008
|
getClientMergePreview: (survivorClientId, duplicateClientId) => {
|
|
9839
10009
|
const params = new URLSearchParams();
|
|
@@ -9863,6 +10033,29 @@ function createHttpClient(config) {
|
|
|
9863
10033
|
// Manual (wire-transfer) order decisions.
|
|
9864
10034
|
approveManualOrder: (orderId, input) => request("POST", `/orders/${encodeURIComponent(orderId)}/approve-payment`, input),
|
|
9865
10035
|
rejectManualOrder: (orderId) => request("POST", `/orders/${encodeURIComponent(orderId)}/reject-payment`),
|
|
10036
|
+
markOrderAsTest: (orderId, isTest) => request("POST", `/orders/${encodeURIComponent(orderId)}/mark-test`, { isTest }),
|
|
10037
|
+
markOrderRefunded: (orderId, input) => request("POST", `/orders/${encodeURIComponent(orderId)}/mark-refunded`, input),
|
|
10038
|
+
listConsultationBookings: (options) => request("GET", `/consultations/bookings${toQuery(options)}`),
|
|
10039
|
+
listConsultationClientsWithoutBooking: (options) => request("GET", `/consultations/clients-without-booking${toQuery(options)}`),
|
|
10040
|
+
listCourseQuestions: (productId, options) => request(
|
|
10041
|
+
"GET",
|
|
10042
|
+
`/products/${encodeURIComponent(productId)}/course/questions${toQuery(options)}`
|
|
10043
|
+
),
|
|
10044
|
+
replyToCourseQuestion: (productId, questionId, content) => request(
|
|
10045
|
+
"POST",
|
|
10046
|
+
`/products/${encodeURIComponent(productId)}/course/questions/${encodeURIComponent(questionId)}/replies`,
|
|
10047
|
+
{ content }
|
|
10048
|
+
),
|
|
10049
|
+
setCourseQuestionStatus: (productId, entryId, status) => request(
|
|
10050
|
+
"PATCH",
|
|
10051
|
+
`/products/${encodeURIComponent(productId)}/course/questions/${encodeURIComponent(entryId)}`,
|
|
10052
|
+
{ status }
|
|
10053
|
+
),
|
|
10054
|
+
acceptCourseAnswer: (productId, questionId, replyId) => request(
|
|
10055
|
+
"POST",
|
|
10056
|
+
`/products/${encodeURIComponent(productId)}/course/questions/${encodeURIComponent(questionId)}/accept`,
|
|
10057
|
+
{ replyId }
|
|
10058
|
+
),
|
|
9866
10059
|
// Subscriptions: failed payments, manual renewals, ending soon.
|
|
9867
10060
|
listFailedSubscriptionPayments: () => request("GET", "/subscriptions/failed-payments"),
|
|
9868
10061
|
retrySubscriptionPayment: (invoiceId) => request("POST", `/subscriptions/failed-payments/${encodeURIComponent(invoiceId)}/retry`),
|
|
@@ -10089,6 +10282,23 @@ function createHttpClient(config) {
|
|
|
10089
10282
|
"GET",
|
|
10090
10283
|
`/v1/communities/${encodeURIComponent(communityId)}/events/${encodeURIComponent(eventId)}/attendees`
|
|
10091
10284
|
),
|
|
10285
|
+
listCommunityJoinRequests: (communityId) => communityRequest("GET", `/v1/communities/${encodeURIComponent(communityId)}/join-requests`),
|
|
10286
|
+
approveCommunityJoinRequests: (communityId, requestIds) => communityRequest(
|
|
10287
|
+
"POST",
|
|
10288
|
+
`/v1/communities/${encodeURIComponent(communityId)}/join-requests/approve`,
|
|
10289
|
+
{ requestIds }
|
|
10290
|
+
),
|
|
10291
|
+
declineCommunityJoinRequests: (communityId, requestIds) => communityRequest(
|
|
10292
|
+
"POST",
|
|
10293
|
+
`/v1/communities/${encodeURIComponent(communityId)}/join-requests/decline`,
|
|
10294
|
+
{ requestIds }
|
|
10295
|
+
),
|
|
10296
|
+
listCommunityDigestDrafts: (communityId) => communityRequest("GET", `/v1/communities/${encodeURIComponent(communityId)}/digest-drafts`),
|
|
10297
|
+
decideCommunityDigestDraft: (communityId, input) => communityRequest(
|
|
10298
|
+
"POST",
|
|
10299
|
+
`/v1/communities/${encodeURIComponent(communityId)}/digest-drafts/decide`,
|
|
10300
|
+
input
|
|
10301
|
+
),
|
|
10092
10302
|
getCommunityModerationQueue: (communityId) => communityRequest(
|
|
10093
10303
|
"GET",
|
|
10094
10304
|
`/v1/communities/${encodeURIComponent(communityId)}/moderation/reports`
|
|
@@ -10141,8 +10351,28 @@ function createHttpClient(config) {
|
|
|
10141
10351
|
"GET",
|
|
10142
10352
|
`/forms/${encodeURIComponent(id)}/submissions${limit !== void 0 ? `?limit=${limit}` : ""}`
|
|
10143
10353
|
),
|
|
10144
|
-
// Live rooms live on the Workspace API (api-v1),
|
|
10354
|
+
// Live rooms live on the Workspace API (api-v1): reads, the external webinar and its replay.
|
|
10145
10355
|
listLiveRooms: () => request("GET", "/live-rooms"),
|
|
10356
|
+
createExternalWebinar: (input) => request("POST", "/live-rooms", input),
|
|
10357
|
+
setLiveRoomReplay: (roomId, mediaId) => request("PUT", `/live-rooms/${encodeURIComponent(roomId)}/replay`, { mediaId }),
|
|
10358
|
+
setLiveRoomStudioScene: (roomId, input) => request("POST", `/live-rooms/${encodeURIComponent(roomId)}/studio/scene`, input),
|
|
10359
|
+
createLiveRoomStudioGuestInvite: (roomId, input) => request("POST", `/live-rooms/${encodeURIComponent(roomId)}/studio/guests`, input),
|
|
10360
|
+
listLiveRoomStudioGuests: (roomId) => request("GET", `/live-rooms/${encodeURIComponent(roomId)}/studio/guests`),
|
|
10361
|
+
revokeLiveRoomStudioGuestInvite: (roomId, guestId) => request(
|
|
10362
|
+
"DELETE",
|
|
10363
|
+
`/live-rooms/${encodeURIComponent(roomId)}/studio/guests/${encodeURIComponent(guestId)}`
|
|
10364
|
+
),
|
|
10365
|
+
listLiveRoomStudioOutputs: (roomId) => request("GET", `/live-rooms/${encodeURIComponent(roomId)}/studio/outputs`),
|
|
10366
|
+
createLiveRoomStudioOutput: (roomId, input) => request("POST", `/live-rooms/${encodeURIComponent(roomId)}/studio/outputs`, input),
|
|
10367
|
+
updateLiveRoomStudioOutput: (roomId, outputId, input) => request(
|
|
10368
|
+
"PATCH",
|
|
10369
|
+
`/live-rooms/${encodeURIComponent(roomId)}/studio/outputs/${encodeURIComponent(outputId)}`,
|
|
10370
|
+
input
|
|
10371
|
+
),
|
|
10372
|
+
deleteLiveRoomStudioOutput: (roomId, outputId) => request(
|
|
10373
|
+
"DELETE",
|
|
10374
|
+
`/live-rooms/${encodeURIComponent(roomId)}/studio/outputs/${encodeURIComponent(outputId)}`
|
|
10375
|
+
),
|
|
10146
10376
|
getLiveRoomAttendance: (roomId, session) => request(
|
|
10147
10377
|
"GET",
|
|
10148
10378
|
`/live-rooms/${encodeURIComponent(roomId)}/attendance${session !== void 0 ? `?session=${encodeURIComponent(session)}` : ""}`
|
|
@@ -10181,6 +10411,9 @@ function createHttpClient(config) {
|
|
|
10181
10411
|
updateMetaAdSet: (scope, adSetId, input) => request("PATCH", `${metaAdAccountPath(scope)}/ad-sets/${encodeURIComponent(adSetId)}`, input),
|
|
10182
10412
|
listMetaAds: (scope, query) => request("GET", `${metaAdAccountPath(scope)}/ads${toQuery(query)}`),
|
|
10183
10413
|
createMetaAd: (scope, input) => request("POST", `${metaAdAccountPath(scope)}/ads`, input),
|
|
10414
|
+
createMetaCampaign: (scope, input) => request("POST", `${metaAdAccountPath(scope)}/campaigns`, input),
|
|
10415
|
+
createMetaAdSet: (scope, input) => request("POST", `${metaAdAccountPath(scope)}/ad-sets`, input),
|
|
10416
|
+
createMetaAdFromScratch: (scope, input) => request("POST", `${metaAdAccountPath(scope)}/ads/from-scratch`, input),
|
|
10184
10417
|
updateMetaAd: (scope, adId, input) => request("PATCH", `${metaAdAccountPath(scope)}/ads/${encodeURIComponent(adId)}`, input),
|
|
10185
10418
|
getMetaAdCreative: (scope, adId) => request("GET", `${metaAdAccountPath(scope)}/ads/${encodeURIComponent(adId)}/creative`),
|
|
10186
10419
|
updateMetaAdCreative: (scope, adId, input) => request(
|
|
@@ -10196,6 +10429,7 @@ function createHttpClient(config) {
|
|
|
10196
10429
|
updateFunnel: (id, input) => request("PATCH", `/funnels/${encodeURIComponent(id)}`, input),
|
|
10197
10430
|
deleteFunnel: (id) => request("DELETE", `/funnels/${encodeURIComponent(id)}`),
|
|
10198
10431
|
generateFunnelStepPage: (id, input) => request("POST", `/funnels/${encodeURIComponent(id)}/generate-step-page`, input),
|
|
10432
|
+
addFunnelOfferPage: (id, input) => request("POST", `/funnels/${encodeURIComponent(id)}/offer-page`, input),
|
|
10199
10433
|
listFunnelTemplates: () => request("GET", "/funnel-templates"),
|
|
10200
10434
|
createFunnelFromTemplate: (input) => request("POST", "/funnels/from-template", input),
|
|
10201
10435
|
createFunnelFromPages: (input) => request("POST", "/funnels/from-pages", input),
|
|
@@ -10208,6 +10442,7 @@ function createHttpClient(config) {
|
|
|
10208
10442
|
deleteOtoOffer: (id) => request("DELETE", `/oto-offers/${encodeURIComponent(id)}`),
|
|
10209
10443
|
// Site (platform settings / storefront / legal documents) lives on the Workspace API (api-v1).
|
|
10210
10444
|
getSiteSettings: () => request("GET", "/site/settings"),
|
|
10445
|
+
getSiteCustomScripts: () => request("GET", "/site/custom-scripts"),
|
|
10211
10446
|
updateSiteGeneral: (input) => request("PATCH", "/site/settings/general", input),
|
|
10212
10447
|
updateSiteTheme: (input) => request("PATCH", "/site/settings/theme", input),
|
|
10213
10448
|
updateSiteBranding: (input) => request("PATCH", "/site/settings/branding", input),
|
|
@@ -10227,6 +10462,36 @@ function createHttpClient(config) {
|
|
|
10227
10462
|
language ? `/site/legal-documents?language=${encodeURIComponent(language)}` : "/site/legal-documents"
|
|
10228
10463
|
),
|
|
10229
10464
|
updateSiteLegalDocuments: (input) => request("PATCH", "/site/legal-documents", input),
|
|
10465
|
+
listLibrarySections: () => request("GET", "/site/library-sections"),
|
|
10466
|
+
replaceLibrarySections: (sections) => request("PUT", "/site/library-sections", { sections }),
|
|
10467
|
+
createLibrarySection: (input) => request("POST", "/site/library-sections", input),
|
|
10468
|
+
reorderLibrarySections: (sectionIds) => request("POST", "/site/library-sections/reorder", { sectionIds }),
|
|
10469
|
+
moveLibraryProducts: (productIds, sectionId) => request("POST", "/site/library-sections/move-products", { productIds, sectionId }),
|
|
10470
|
+
renameLibrarySection: (sectionId, name) => request("PATCH", `/site/library-sections/${encodeURIComponent(sectionId)}`, { name }),
|
|
10471
|
+
deleteLibrarySection: (sectionId) => request("DELETE", `/site/library-sections/${encodeURIComponent(sectionId)}`),
|
|
10472
|
+
// Studio weekly drafts + post insights (`social-token` routes on api-v1).
|
|
10473
|
+
listStudioSocialDrafts: () => request("GET", "/studio/social-drafts"),
|
|
10474
|
+
generateStudioSocialDrafts: (input) => request("POST", "/studio/social-drafts/generate", input),
|
|
10475
|
+
sendStudioSocialDraftFeedback: (draftId, reason) => request("POST", `/studio/social-drafts/${encodeURIComponent(draftId)}/feedback`, {
|
|
10476
|
+
reason
|
|
10477
|
+
}),
|
|
10478
|
+
sendStudioSocialDraftToComposer: (draftId, input) => request("POST", `/studio/social-drafts/${encodeURIComponent(draftId)}/to-composer`, input),
|
|
10479
|
+
getSocialWeekReport: () => request("GET", "/social/reports/weekly"),
|
|
10480
|
+
getSocialPostPerformance: (postId) => request("GET", `/social/posts/${encodeURIComponent(postId)}/performance`),
|
|
10481
|
+
setYouTubeVideoVisibility: (postId, input) => request("POST", `/social/posts/${encodeURIComponent(postId)}/youtube-visibility`, input),
|
|
10482
|
+
notifyCheckoutWaitlist: (checkoutId, dryRun) => request("POST", `/checkouts/${encodeURIComponent(checkoutId)}/notify-waitlist`, { dryRun }),
|
|
10483
|
+
duplicateProduct: (productId, name) => request(
|
|
10484
|
+
"POST",
|
|
10485
|
+
`/products/${encodeURIComponent(productId)}/duplicate`,
|
|
10486
|
+
name !== void 0 ? { name } : {}
|
|
10487
|
+
),
|
|
10488
|
+
getSenderDomainWarmups: () => request("GET", "/sender-domain/warmups"),
|
|
10489
|
+
declareSenderDomainWarmed: (domain) => request("POST", `/sender-domain/${encodeURIComponent(domain)}/warmup-declarations`),
|
|
10490
|
+
deleteClientEmailDrafts: (input) => request("POST", "/client-emails/delete-drafts", input),
|
|
10491
|
+
resendFormSubmissionRoomLink: (formId, submissionId) => request(
|
|
10492
|
+
"POST",
|
|
10493
|
+
`/forms/${encodeURIComponent(formId)}/submissions/${encodeURIComponent(submissionId)}/room-link`
|
|
10494
|
+
),
|
|
10230
10495
|
// Customer access devices (anti-sharing device slots) — Workspace API.
|
|
10231
10496
|
listClientDevices: (clientId) => request("GET", `/clients/${encodeURIComponent(clientId)}/devices`),
|
|
10232
10497
|
revokeClientDevice: (clientId, deviceId) => request(
|
|
@@ -10949,7 +11214,7 @@ function registerBlogCommands(program2) {
|
|
|
10949
11214
|
"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
11215
|
).argument("<id>", "Blog post id (`post_\u2026`) from list-blog-posts.").requiredOption(
|
|
10951
11216
|
"--at <value>",
|
|
10952
|
-
`When the post goes live:
|
|
11217
|
+
`When the post goes live: the workspace's local time without an offset ("2026-10-26T08:00", read in the workspace time zone: its own zone from Ustawienia \u2192 Workspace, else Europe/Warsaw for a Polish workspace (PL invoice / pl site), else the owner's account zone, else 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
11218
|
).action(async (id, options, command) => {
|
|
10954
11219
|
const flags = getGlobalFlags(command);
|
|
10955
11220
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -11077,7 +11342,7 @@ function registerSiteRedirectsCommands(program2) {
|
|
|
11077
11342
|
emit(flags, result ?? {}, () => printTable(rows, listSiteRedirectsColumns));
|
|
11078
11343
|
});
|
|
11079
11344
|
siteRedirects.command("create").summary("Add a site redirect").description(
|
|
11080
|
-
"Adds one line at the end of the site's 301 map; live at once. Refused (400 `site-redirects-refused`, `issues[]` = index, from, to, issue) exactly like the dashboard save: a malformed address (`invalid-from` / `invalid-to`), the home page or an address Zanfia owns (`/blog`, `/p/\u2026`, `/app`: `reserved-from`), the member area (`portal-from`), a published page (`page-exists`), a `from` the map already has (`duplicate`), a chain or
|
|
11345
|
+
"Adds one line at the end of the site's 301 map; live at once. Refused (400 `site-redirects-refused`, `issues[]` = index, from, to, issue) exactly like the dashboard save: a malformed address (`invalid-from` / `invalid-to`), the home page or an address Zanfia owns (`/blog`, `/p/\u2026`, `/app`: `reserved-from`), the member area (`portal-from`), a published page (`page-exists`), a `from` the map already has (`duplicate`), a `to` that is another line (`chain`) or the line itself (`loop`), `/*` in `to` without a folder `from` (`rest-without-folder`), over 500 lines (`too-many-entries`). Returns the line as stored."
|
|
11081
11346
|
).argument(
|
|
11082
11347
|
"<from>",
|
|
11083
11348
|
"The old address on the site: a path (`/o-nas`), a full address on the site (`https://example.com/o-nas/`, stored as its path) or a folder ending with `/*` (`/news/*`, every address under it). Query string and `#\u2026` are dropped; case is kept."
|
|
@@ -11338,131 +11603,414 @@ function registerPopupsCommands(program2) {
|
|
|
11338
11603
|
});
|
|
11339
11604
|
}
|
|
11340
11605
|
|
|
11341
|
-
// src/commands/_generated/
|
|
11342
|
-
var
|
|
11343
|
-
|
|
11344
|
-
|
|
11345
|
-
|
|
11346
|
-
|
|
11347
|
-
|
|
11606
|
+
// src/commands/_generated/products.ts
|
|
11607
|
+
var listProductBuyersNotificationsColumns = [
|
|
11608
|
+
"id",
|
|
11609
|
+
"title",
|
|
11610
|
+
"status",
|
|
11611
|
+
"createdAt",
|
|
11612
|
+
"confirmedRecipientCount"
|
|
11613
|
+
];
|
|
11614
|
+
function registerProductsCommands(program2) {
|
|
11615
|
+
const products2 = program2.commands.find((command) => command.name() === "products") ?? program2.command("products");
|
|
11616
|
+
if (!products2.description()) {
|
|
11617
|
+
products2.description(
|
|
11618
|
+
"A product's buyer notifications (\u201EPowiadom kupuj\u0105cych\u201D): list, preview, send"
|
|
11348
11619
|
);
|
|
11349
11620
|
}
|
|
11350
|
-
const
|
|
11351
|
-
|
|
11352
|
-
"
|
|
11621
|
+
const productsNotifications = products2.command("notifications");
|
|
11622
|
+
productsNotifications.description(
|
|
11623
|
+
"A product's buyer notifications (\u201EPowiadom kupuj\u0105cych\u201D): list, preview, send"
|
|
11353
11624
|
);
|
|
11354
|
-
|
|
11355
|
-
"
|
|
11625
|
+
productsNotifications.command("preview").summary("Preview a notification to a product's buyers").description(
|
|
11626
|
+
"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)."
|
|
11627
|
+
).argument("<productId>", "Product id (`prod_\u2026`) from list-products.").option(
|
|
11628
|
+
"--link <value>",
|
|
11629
|
+
"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."
|
|
11356
11630
|
).option(
|
|
11357
|
-
"--
|
|
11358
|
-
"
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
|
|
11362
|
-
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
11367
|
-
|
|
11368
|
-
|
|
11369
|
-
|
|
11370
|
-
|
|
11371
|
-
|
|
11372
|
-
|
|
11373
|
-
|
|
11374
|
-
|
|
11631
|
+
"--send-email",
|
|
11632
|
+
"The e-mail would be ticked: also check the monthly e-mail allowance (`emailSkipped`)."
|
|
11633
|
+
).option("--no-send-email", "Send `sendEmail: false` explicitly.").action(
|
|
11634
|
+
async (productId, options, command) => {
|
|
11635
|
+
const flags = getGlobalFlags(command);
|
|
11636
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11637
|
+
const params = {
|
|
11638
|
+
productId
|
|
11639
|
+
};
|
|
11640
|
+
const payload = {
|
|
11641
|
+
link: options.link,
|
|
11642
|
+
sendEmail: options.sendEmail
|
|
11643
|
+
};
|
|
11644
|
+
const result = await client.call(
|
|
11645
|
+
"POST",
|
|
11646
|
+
"/products/:productId/buyers-notifications/preview",
|
|
11647
|
+
params,
|
|
11648
|
+
void 0,
|
|
11649
|
+
payload
|
|
11650
|
+
);
|
|
11651
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11652
|
+
}
|
|
11653
|
+
);
|
|
11654
|
+
productsNotifications.command("send").summary("Notify a product's buyers").description(
|
|
11655
|
+
"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."
|
|
11656
|
+
).argument("<productId>", "Product id (`prod_\u2026`) from list-products.").requiredOption(
|
|
11657
|
+
"--title <value>",
|
|
11658
|
+
"The notification title (push title, bell line, e-mail subject). One line."
|
|
11659
|
+
).requiredOption("--body <value>", "The message, up to 200 characters.").option(
|
|
11660
|
+
"--link <value>",
|
|
11661
|
+
"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."
|
|
11375
11662
|
).option(
|
|
11376
|
-
"--
|
|
11377
|
-
"
|
|
11378
|
-
|
|
11663
|
+
"--send-email",
|
|
11664
|
+
"Also send it as an e-mail (clients who unsubscribed are skipped). Default: no.",
|
|
11665
|
+
false
|
|
11666
|
+
).requiredOption(
|
|
11667
|
+
"--confirmed-recipient-count <n>",
|
|
11668
|
+
"The `recipientCount` from preview-product-buyers-notification that the human confirmed. More people with access now = refused (409 recipient_count_changed): preview again.",
|
|
11669
|
+
parseNumberOption("--confirmed-recipient-count", NumberFormat.Integer)
|
|
11670
|
+
).option(
|
|
11671
|
+
"--send-id <value>",
|
|
11672
|
+
"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."
|
|
11673
|
+
).option(
|
|
11674
|
+
"--confirm",
|
|
11675
|
+
"required \u2014 acknowledge that you want to notify every buyer of this product now (it cannot be recalled)"
|
|
11676
|
+
).action(async (productId, options, command) => {
|
|
11379
11677
|
const flags = getGlobalFlags(command);
|
|
11678
|
+
requireConfirm(
|
|
11679
|
+
options.confirm,
|
|
11680
|
+
"notify every buyer of this product now (it cannot be recalled)"
|
|
11681
|
+
);
|
|
11380
11682
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11381
|
-
const params = {
|
|
11683
|
+
const params = {
|
|
11684
|
+
productId
|
|
11685
|
+
};
|
|
11382
11686
|
const payload = {
|
|
11383
|
-
|
|
11384
|
-
|
|
11687
|
+
title: options.title,
|
|
11688
|
+
body: options.body,
|
|
11689
|
+
link: options.link,
|
|
11690
|
+
sendEmail: options.sendEmail,
|
|
11691
|
+
confirmedRecipientCount: options.confirmedRecipientCount,
|
|
11692
|
+
sendId: options.sendId
|
|
11385
11693
|
};
|
|
11386
11694
|
const result = await client.call(
|
|
11387
11695
|
"POST",
|
|
11388
|
-
"/
|
|
11696
|
+
"/products/:productId/buyers-notifications",
|
|
11389
11697
|
params,
|
|
11390
11698
|
void 0,
|
|
11391
11699
|
payload
|
|
11392
11700
|
);
|
|
11393
|
-
|
|
11394
|
-
emit(flags, result ?? {}, () => printTable(rows, suggestStudioVideoClipsColumns));
|
|
11701
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11395
11702
|
});
|
|
11396
|
-
|
|
11397
|
-
"
|
|
11398
|
-
).
|
|
11399
|
-
"--
|
|
11400
|
-
"
|
|
11401
|
-
|
|
11402
|
-
|
|
11403
|
-
|
|
11404
|
-
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
|
|
11703
|
+
productsNotifications.command("list").summary("List a product's buyer notifications").description(
|
|
11704
|
+
"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)."
|
|
11705
|
+
).argument("<productId>", "Product id (`prod_\u2026`) from list-products.").option(
|
|
11706
|
+
"--limit <n>",
|
|
11707
|
+
"Newest first; default 20, max 50.",
|
|
11708
|
+
parseNumberOption("--limit", NumberFormat.Integer)
|
|
11709
|
+
).action(
|
|
11710
|
+
async (productId, options, command) => {
|
|
11711
|
+
const flags = getGlobalFlags(command);
|
|
11712
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11713
|
+
const params = {
|
|
11714
|
+
productId
|
|
11715
|
+
};
|
|
11716
|
+
const payload = {
|
|
11717
|
+
limit: options.limit
|
|
11718
|
+
};
|
|
11719
|
+
const result = await client.call(
|
|
11720
|
+
"GET",
|
|
11721
|
+
"/products/:productId/buyers-notifications",
|
|
11722
|
+
params,
|
|
11723
|
+
payload,
|
|
11724
|
+
void 0
|
|
11725
|
+
);
|
|
11726
|
+
const rows = tableRowsOf(result, "items");
|
|
11727
|
+
emit(flags, result ?? {}, () => printTable(rows, listProductBuyersNotificationsColumns));
|
|
11728
|
+
}
|
|
11729
|
+
);
|
|
11730
|
+
const productsStock = products2.command("stock", { hidden: true });
|
|
11731
|
+
productsStock.description("A physical product's stock per variant");
|
|
11732
|
+
productsStock.command("get", { hidden: true }).summary("Read a physical product's stock").description(
|
|
11733
|
+
"\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."
|
|
11734
|
+
).argument("<productId>", "The physical product id (`prod_\u2026` from list-products).").action(async (productId, _options, command) => {
|
|
11409
11735
|
const flags = getGlobalFlags(command);
|
|
11410
11736
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11411
|
-
const params = {
|
|
11412
|
-
|
|
11413
|
-
topic: options.topic,
|
|
11414
|
-
sourceMediaId: options.sourceMediaId,
|
|
11415
|
-
lengthSeconds: options.lengthSeconds,
|
|
11416
|
-
language: options.language
|
|
11737
|
+
const params = {
|
|
11738
|
+
productId
|
|
11417
11739
|
};
|
|
11418
11740
|
const result = await client.call(
|
|
11419
|
-
"
|
|
11420
|
-
"/
|
|
11741
|
+
"GET",
|
|
11742
|
+
"/products/:productId/stock",
|
|
11421
11743
|
params,
|
|
11422
11744
|
void 0,
|
|
11423
|
-
|
|
11745
|
+
void 0
|
|
11424
11746
|
);
|
|
11425
11747
|
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11426
11748
|
});
|
|
11427
|
-
|
|
11428
|
-
"
|
|
11429
|
-
).requiredOption(
|
|
11430
|
-
"--
|
|
11431
|
-
"
|
|
11432
|
-
parseJsonArrayFileOrInline("--
|
|
11433
|
-
).
|
|
11434
|
-
new Option(
|
|
11435
|
-
"--voice-id <asia|iwona|pawel|piotr>",
|
|
11436
|
-
"Voice: asia, iwona (female), pawel, piotr (male). Default asia."
|
|
11437
|
-
).choices(["asia", "iwona", "pawel", "piotr"])
|
|
11438
|
-
).option(
|
|
11439
|
-
"--language <value>",
|
|
11440
|
-
"Language of the text, e.g. `pl` or `en`. Omitted: the transcript language, else `pl`."
|
|
11441
|
-
).option(
|
|
11442
|
-
"--request-id <value>",
|
|
11443
|
-
"Idempotency key (a uuid): a retry with the same one returns the same voice, charged once. Omitted = a new voice every call."
|
|
11444
|
-
).action(async (options, command) => {
|
|
11749
|
+
productsStock.command("set", { hidden: true }).summary("Set a physical product's stock").description(
|
|
11750
|
+
"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."
|
|
11751
|
+
).argument("<productId>", "The physical product id (`prod_\u2026` from list-products).").requiredOption(
|
|
11752
|
+
"--variants <json|file>",
|
|
11753
|
+
"Only the variants listed change; the rest keep their stock.",
|
|
11754
|
+
parseJsonArrayFileOrInline("--variants")
|
|
11755
|
+
).option("--confirm", "required \u2014 acknowledge that you want to change this product's stock").action(async (productId, options, command) => {
|
|
11445
11756
|
const flags = getGlobalFlags(command);
|
|
11757
|
+
requireConfirm(options.confirm, "change this product's stock");
|
|
11446
11758
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11447
|
-
const params = {
|
|
11759
|
+
const params = {
|
|
11760
|
+
productId
|
|
11761
|
+
};
|
|
11448
11762
|
const payload = {
|
|
11449
|
-
|
|
11450
|
-
voiceId: options.voiceId,
|
|
11451
|
-
language: options.language,
|
|
11452
|
-
requestId: options.requestId
|
|
11763
|
+
variants: options.variants
|
|
11453
11764
|
};
|
|
11454
11765
|
const result = await client.call(
|
|
11455
|
-
"
|
|
11456
|
-
"/
|
|
11766
|
+
"PATCH",
|
|
11767
|
+
"/products/:productId/stock",
|
|
11457
11768
|
params,
|
|
11458
11769
|
void 0,
|
|
11459
11770
|
payload
|
|
11460
11771
|
);
|
|
11461
11772
|
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11462
11773
|
});
|
|
11463
|
-
|
|
11464
|
-
|
|
11465
|
-
|
|
11774
|
+
}
|
|
11775
|
+
|
|
11776
|
+
// src/commands/_generated/subscriptions.ts
|
|
11777
|
+
var listInstallmentArrearsColumns = [
|
|
11778
|
+
"productName",
|
|
11779
|
+
"clientEmail",
|
|
11780
|
+
"subscriptionStatus",
|
|
11781
|
+
"installmentsPaid",
|
|
11782
|
+
"installmentsTotal",
|
|
11783
|
+
"overdueAmountMinor",
|
|
11784
|
+
"currency"
|
|
11785
|
+
];
|
|
11786
|
+
function registerSubscriptionsCommands(program2) {
|
|
11787
|
+
const subscriptions = program2.commands.find((command) => command.name() === "subscriptions") ?? program2.command("subscriptions");
|
|
11788
|
+
if (!subscriptions.description()) {
|
|
11789
|
+
subscriptions.description(
|
|
11790
|
+
"Instalment arrears (\u201EZaleg\u0142o\u015Bci w ratach\u201D) and the non-payment setting"
|
|
11791
|
+
);
|
|
11792
|
+
}
|
|
11793
|
+
const subscriptionsArrears = subscriptions.command("arrears");
|
|
11794
|
+
subscriptionsArrears.description(
|
|
11795
|
+
"Instalment arrears (\u201EZaleg\u0142o\u015Bci w ratach\u201D) and the non-payment setting"
|
|
11796
|
+
);
|
|
11797
|
+
subscriptionsArrears.command("setting").summary("What happens when a buyer stops paying instalments").description(
|
|
11798
|
+
"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."
|
|
11799
|
+
).action(async (_options, command) => {
|
|
11800
|
+
const flags = getGlobalFlags(command);
|
|
11801
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11802
|
+
const params = {};
|
|
11803
|
+
const result = await client.call(
|
|
11804
|
+
"GET",
|
|
11805
|
+
"/settings/installment-non-payment",
|
|
11806
|
+
params,
|
|
11807
|
+
void 0,
|
|
11808
|
+
void 0
|
|
11809
|
+
);
|
|
11810
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11811
|
+
});
|
|
11812
|
+
subscriptionsArrears.command("set-setting").summary("Change what happens when a buyer stops paying instalments").description(
|
|
11813
|
+
"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."
|
|
11814
|
+
).addOption(
|
|
11815
|
+
new Option(
|
|
11816
|
+
"--action <revoke|keep>",
|
|
11817
|
+
"`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."
|
|
11818
|
+
).choices(["revoke", "keep"]).makeOptionMandatory()
|
|
11819
|
+
).option(
|
|
11820
|
+
"--confirm",
|
|
11821
|
+
"required \u2014 acknowledge that you want to change what happens to every buyer who stops paying instalments"
|
|
11822
|
+
).action(async (options, command) => {
|
|
11823
|
+
const flags = getGlobalFlags(command);
|
|
11824
|
+
requireConfirm(
|
|
11825
|
+
options.confirm,
|
|
11826
|
+
"change what happens to every buyer who stops paying instalments"
|
|
11827
|
+
);
|
|
11828
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11829
|
+
const params = {};
|
|
11830
|
+
const payload = {
|
|
11831
|
+
action: options.action
|
|
11832
|
+
};
|
|
11833
|
+
const result = await client.call(
|
|
11834
|
+
"PATCH",
|
|
11835
|
+
"/settings/installment-non-payment",
|
|
11836
|
+
params,
|
|
11837
|
+
void 0,
|
|
11838
|
+
payload
|
|
11839
|
+
);
|
|
11840
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11841
|
+
});
|
|
11842
|
+
subscriptionsArrears.command("list").summary("List buyers behind on instalments (\u201EZaleg\u0142o\u015Bci\u201D)").description(
|
|
11843
|
+
"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."
|
|
11844
|
+
).option(
|
|
11845
|
+
"--product-id <value>",
|
|
11846
|
+
"One product (`prod_\u2026` from list-products); omitted = every product."
|
|
11847
|
+
).option(
|
|
11848
|
+
"--include-kept",
|
|
11849
|
+
"Also list the buyers the creator already chose \u201EZostaw\u201D for. Default false."
|
|
11850
|
+
).option("--no-include-kept", "Send `includeKept: false` explicitly.").action(async (options, command) => {
|
|
11851
|
+
const flags = getGlobalFlags(command);
|
|
11852
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11853
|
+
const params = {};
|
|
11854
|
+
const payload = {
|
|
11855
|
+
productId: options.productId,
|
|
11856
|
+
includeKept: options.includeKept
|
|
11857
|
+
};
|
|
11858
|
+
const result = await client.call("GET", "/installment-arrears", params, payload, void 0);
|
|
11859
|
+
const rows = tableRowsOf(result, "items");
|
|
11860
|
+
emit(flags, result ?? {}, () => printTable(rows, listInstallmentArrearsColumns));
|
|
11861
|
+
});
|
|
11862
|
+
subscriptionsArrears.command("keep").summary("Keep a behind-on-instalments buyer's access (\u201EZostaw\u201D)").description(
|
|
11863
|
+
"\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."
|
|
11864
|
+
).argument("<productId>", "The row's `productId`.").argument("<clientEmail>", "The row's `clientEmail`.").option(
|
|
11865
|
+
"--confirm",
|
|
11866
|
+
"required \u2014 acknowledge that you want to keep this buyer's access despite the unpaid instalments"
|
|
11867
|
+
).action(
|
|
11868
|
+
async (productId, clientEmail, options, command) => {
|
|
11869
|
+
const flags = getGlobalFlags(command);
|
|
11870
|
+
requireConfirm(options.confirm, "keep this buyer's access despite the unpaid instalments");
|
|
11871
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11872
|
+
const params = {};
|
|
11873
|
+
const payload = {
|
|
11874
|
+
productId,
|
|
11875
|
+
clientEmail
|
|
11876
|
+
};
|
|
11877
|
+
const result = await client.call(
|
|
11878
|
+
"POST",
|
|
11879
|
+
"/installment-arrears/keep",
|
|
11880
|
+
params,
|
|
11881
|
+
void 0,
|
|
11882
|
+
payload
|
|
11883
|
+
);
|
|
11884
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11885
|
+
}
|
|
11886
|
+
);
|
|
11887
|
+
}
|
|
11888
|
+
|
|
11889
|
+
// src/commands/_generated/studio.ts
|
|
11890
|
+
var suggestStudioVideoClipsColumns = ["startSeconds", "endSeconds", "hook"];
|
|
11891
|
+
function registerStudioCommands(program2) {
|
|
11892
|
+
const studio = program2.commands.find((command) => command.name() === "studio") ?? program2.command("studio");
|
|
11893
|
+
if (!studio.description()) {
|
|
11894
|
+
studio.description(
|
|
11895
|
+
"Studio videos: suggest clips, write a reel storyboard, voice it, export and download"
|
|
11896
|
+
);
|
|
11897
|
+
}
|
|
11898
|
+
const studioVideo = studio.command("video");
|
|
11899
|
+
studioVideo.description(
|
|
11900
|
+
"Studio videos: suggest clips, write a reel storyboard, voice it, export and download"
|
|
11901
|
+
);
|
|
11902
|
+
studioVideo.command("quote").summary("Quote what a Studio video costs").description(
|
|
11903
|
+
"Before spending anything: the AI credits of each Studio video step (clip suggestion, storyboard, voice-over for a reel of `lengthSeconds`), priced as the panel prices them, and the plan's monthly video exports left (the render costs no credits but uses one). Calls no model. Read-only."
|
|
11904
|
+
).option(
|
|
11905
|
+
"--length-seconds <n>",
|
|
11906
|
+
"Reel length in seconds of voice: 15, 30 or 45. Default 30.",
|
|
11907
|
+
parseNumberOption("--length-seconds", NumberFormat.Integer)
|
|
11908
|
+
).action(async (options, command) => {
|
|
11909
|
+
const flags = getGlobalFlags(command);
|
|
11910
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11911
|
+
const params = {};
|
|
11912
|
+
const payload = {
|
|
11913
|
+
lengthSeconds: options.lengthSeconds
|
|
11914
|
+
};
|
|
11915
|
+
const result = await client.call("GET", "/studio/videos/quote", params, payload, void 0);
|
|
11916
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11917
|
+
});
|
|
11918
|
+
studioVideo.command("suggest").summary("Suggest reel clips from a video transcript").description(
|
|
11919
|
+
"Reads the transcript of one library video and proposes short clips (hook, start / end seconds, why it works) for a MomentClip export. Spends AI credits (see get-studio-video-quote `suggestClipsCredits`; 402 ai-credits-exhausted when they are out). Stores nothing. The video must be transcribed first (400 clip-suggestion-transcript-unavailable)."
|
|
11920
|
+
).argument(
|
|
11921
|
+
"<mediaId>",
|
|
11922
|
+
"A transcribed video: a lesson, a LIVE recording or any library video (a media-library id from list-media)."
|
|
11923
|
+
).option(
|
|
11924
|
+
"--language <value>",
|
|
11925
|
+
"Language of the text, e.g. `pl` or `en`. Omitted: the transcript language, else `pl`."
|
|
11926
|
+
).action(async (mediaId, options, command) => {
|
|
11927
|
+
const flags = getGlobalFlags(command);
|
|
11928
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11929
|
+
const params = {};
|
|
11930
|
+
const payload = {
|
|
11931
|
+
mediaId,
|
|
11932
|
+
language: options.language
|
|
11933
|
+
};
|
|
11934
|
+
const result = await client.call(
|
|
11935
|
+
"POST",
|
|
11936
|
+
"/studio/videos/clip-suggestions",
|
|
11937
|
+
params,
|
|
11938
|
+
void 0,
|
|
11939
|
+
payload
|
|
11940
|
+
);
|
|
11941
|
+
const rows = tableRowsOf(result, "clips");
|
|
11942
|
+
emit(flags, result ?? {}, () => printTable(rows, suggestStudioVideoClipsColumns));
|
|
11943
|
+
});
|
|
11944
|
+
studioVideo.command("storyboard").summary("Write a reel storyboard (ScriptReel)").description(
|
|
11945
|
+
"\u201ENapisz scenariusz\u201D of \u201EWygeneruj rolk\u0119\u201D: 3-6 scenes (on-screen text + what the voice says) for a reel of `lengthSeconds`, about `topic` or grounded in a transcribed video. Spends AI credits (get-studio-video-quote `storyboardCredits`; 402 ai-credits-exhausted). Stores nothing: pass the scenes to generate-studio-video-voiceover and export-studio-video. 422 script-reel-storyboard-failed = the model answered unusably (free): try again."
|
|
11946
|
+
).option("--topic <value>", "What the reel is about; required unless `sourceMediaId`.").option(
|
|
11947
|
+
"--source-media-id <value>",
|
|
11948
|
+
"A transcribed library video (lesson, LIVE) whose transcript grounds the script."
|
|
11949
|
+
).option(
|
|
11950
|
+
"--length-seconds <n>",
|
|
11951
|
+
"Reel length in seconds of voice: 15, 30 or 45. Default 30.",
|
|
11952
|
+
parseNumberOption("--length-seconds", NumberFormat.Integer)
|
|
11953
|
+
).option(
|
|
11954
|
+
"--language <value>",
|
|
11955
|
+
"Language of the text, e.g. `pl` or `en`. Omitted: the transcript language, else `pl`."
|
|
11956
|
+
).action(async (options, command) => {
|
|
11957
|
+
const flags = getGlobalFlags(command);
|
|
11958
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11959
|
+
const params = {};
|
|
11960
|
+
const payload = {
|
|
11961
|
+
topic: options.topic,
|
|
11962
|
+
sourceMediaId: options.sourceMediaId,
|
|
11963
|
+
lengthSeconds: options.lengthSeconds,
|
|
11964
|
+
language: options.language
|
|
11965
|
+
};
|
|
11966
|
+
const result = await client.call(
|
|
11967
|
+
"POST",
|
|
11968
|
+
"/studio/videos/storyboards",
|
|
11969
|
+
params,
|
|
11970
|
+
void 0,
|
|
11971
|
+
payload
|
|
11972
|
+
);
|
|
11973
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11974
|
+
});
|
|
11975
|
+
studioVideo.command("voiceover").summary("Generate the voice-over of a reel storyboard").description(
|
|
11976
|
+
"\u201EWygeneruj g\u0142os\u201D for a storyboard: ONE voice reading every scene's voice text, stored as an Audio item in the media library. AI credits per second of the voice, charged only once it is stored (any failure is free); 402 ai-credits-exhausted before anything is made. Pass the answer's `mediaId` as `voiceoverMediaId` to export-studio-video with the SAME scenes (changed scenes = 409 script-reel-voice-mismatch)."
|
|
11977
|
+
).requiredOption(
|
|
11978
|
+
"--scenes <json|file>",
|
|
11979
|
+
"The storyboard scenes from write-studio-video-storyboard (edited or not), in order. The voice and the export must use the SAME scenes.",
|
|
11980
|
+
parseJsonArrayFileOrInline("--scenes")
|
|
11981
|
+
).addOption(
|
|
11982
|
+
new Option(
|
|
11983
|
+
"--voice-id <asia|iwona|pawel|piotr>",
|
|
11984
|
+
"Voice: asia, iwona (female), pawel, piotr (male). Default asia."
|
|
11985
|
+
).choices(["asia", "iwona", "pawel", "piotr"])
|
|
11986
|
+
).option(
|
|
11987
|
+
"--language <value>",
|
|
11988
|
+
"Language of the text, e.g. `pl` or `en`. Omitted: the transcript language, else `pl`."
|
|
11989
|
+
).option(
|
|
11990
|
+
"--request-id <value>",
|
|
11991
|
+
"Idempotency key (a uuid): a retry with the same one returns the same voice, charged once. Omitted = a new voice every call."
|
|
11992
|
+
).action(async (options, command) => {
|
|
11993
|
+
const flags = getGlobalFlags(command);
|
|
11994
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11995
|
+
const params = {};
|
|
11996
|
+
const payload = {
|
|
11997
|
+
scenes: options.scenes,
|
|
11998
|
+
voiceId: options.voiceId,
|
|
11999
|
+
language: options.language,
|
|
12000
|
+
requestId: options.requestId
|
|
12001
|
+
};
|
|
12002
|
+
const result = await client.call(
|
|
12003
|
+
"POST",
|
|
12004
|
+
"/studio/videos/voiceovers",
|
|
12005
|
+
params,
|
|
12006
|
+
void 0,
|
|
12007
|
+
payload
|
|
12008
|
+
);
|
|
12009
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12010
|
+
});
|
|
12011
|
+
studioVideo.command("export").summary("Export (render) a Studio video").description(
|
|
12012
|
+
"Starts the render of a MomentClip, ScriptReel, Montage or CoursePromo video, the same render the panel runs; the MP4 lands in the media library. No AI credits, but it uses one of the plan's monthly video exports (429 promo-video-quota-exceeded when none are left; get-studio-video-quote `exports`). Answers a job id at once (202): poll get-studio-video-status. Publishes nothing."
|
|
12013
|
+
).addOption(
|
|
11466
12014
|
new Option(
|
|
11467
12015
|
"--template-id <MomentClip|ScriptReel|Montage|CoursePromo>",
|
|
11468
12016
|
"MomentClip = a captioned clip of a transcribed video (`moment`); ScriptReel = the storyboard reel (`scenes` + `voiceoverMediaId`); Montage = library pictures / videos (`montageScenes`); CoursePromo = a course promo (`productId`)."
|
|
@@ -11522,109 +12070,449 @@ function registerStudioCommands(program2) {
|
|
|
11522
12070
|
);
|
|
11523
12071
|
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11524
12072
|
});
|
|
11525
|
-
studioVideo.command("status").summary("Read the status of a Studio video export").description(
|
|
11526
|
-
"The render status of an export-studio-video job; once done, the media-library item it landed (its id attaches to a social post draft via `mediaIds`) and, once the video encoded, a signed MP4 download link valid for 24 hours (ask again for a fresh one). Poll every 15-30 s; a render takes a minute or a few. Read-only."
|
|
11527
|
-
).argument("<id>", "The job id export-studio-video answered.").action(async (id, _options, command) => {
|
|
12073
|
+
studioVideo.command("status").summary("Read the status of a Studio video export").description(
|
|
12074
|
+
"The render status of an export-studio-video job; once done, the media-library item it landed (its id attaches to a social post draft via `mediaIds`) and, once the video encoded, a signed MP4 download link valid for 24 hours (ask again for a fresh one). Poll every 15-30 s; a render takes a minute or a few. Read-only."
|
|
12075
|
+
).argument("<id>", "The job id export-studio-video answered.").action(async (id, _options, command) => {
|
|
12076
|
+
const flags = getGlobalFlags(command);
|
|
12077
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12078
|
+
const params = {
|
|
12079
|
+
jobId: id
|
|
12080
|
+
};
|
|
12081
|
+
const result = await client.call(
|
|
12082
|
+
"GET",
|
|
12083
|
+
"/studio/videos/exports/:jobId",
|
|
12084
|
+
params,
|
|
12085
|
+
void 0,
|
|
12086
|
+
void 0
|
|
12087
|
+
);
|
|
12088
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12089
|
+
});
|
|
12090
|
+
}
|
|
12091
|
+
|
|
12092
|
+
// src/commands/_generated/site.ts
|
|
12093
|
+
var listSiteTemplatePagesColumns = ["pageId", "title", "slug", "issue", "nav"];
|
|
12094
|
+
var getSiteMenuColumns = ["label", "style", "href"];
|
|
12095
|
+
function registerSiteCommands(program2) {
|
|
12096
|
+
const site = program2.commands.find((command) => command.name() === "site") ?? program2.command("site");
|
|
12097
|
+
if (!site.description()) {
|
|
12098
|
+
site.description(
|
|
12099
|
+
"What frames the blog (Your site \u2192 Look and menu): the storefront shell or one of your pages; checked by the same rules as the dashboard"
|
|
12100
|
+
);
|
|
12101
|
+
}
|
|
12102
|
+
const siteLayout = site.command("layout");
|
|
12103
|
+
siteLayout.description(
|
|
12104
|
+
"What frames the blog (Your site \u2192 Look and menu): the storefront shell or one of your pages; checked by the same rules as the dashboard"
|
|
12105
|
+
);
|
|
12106
|
+
siteLayout.command("get").summary("Get the site layout").description(
|
|
12107
|
+
'What frames the blog and the shared site menu (dashboard: Your site \u2192 Look and menu). `template` = `{ kind: "storefront" }` (the storefront shell, the default) or `{ kind: "page", pageId }` (the blog wears that page\'s header, menu and footer: \u201EJak moja strona\u201D). `menu` = the shared menu in order, each entry with `href`, the address it leads to today (null = it leads nowhere now, pages leave it out); an empty menu = every page keeps its own links.'
|
|
12108
|
+
).action(async (_options, command) => {
|
|
12109
|
+
const flags = getGlobalFlags(command);
|
|
12110
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12111
|
+
const params = {};
|
|
12112
|
+
const result = await client.call("GET", "/site/layout", params, void 0, void 0);
|
|
12113
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12114
|
+
});
|
|
12115
|
+
siteLayout.command("pages").summary("List pages that can frame the blog").description(
|
|
12116
|
+
"The workspace's published pages as candidates for the blog's frame, eligible ones first. `issue` null = the page can frame the blog (pass its `pageId` to set-site-template); otherwise why not: `not-published`, `gated` (behind a product or a deadline), `custom-scripts` (its own JS), `no-header-or-footer`. `nav` = whether the shared menu shows on the page: `shared`, `not-marked` (no `data-zanfia-nav`, its own links stay) or `unsupported`."
|
|
12117
|
+
).action(async (_options, command) => {
|
|
12118
|
+
const flags = getGlobalFlags(command);
|
|
12119
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12120
|
+
const params = {};
|
|
12121
|
+
const result = await client.call(
|
|
12122
|
+
"GET",
|
|
12123
|
+
"/site/layout/template-pages",
|
|
12124
|
+
params,
|
|
12125
|
+
void 0,
|
|
12126
|
+
void 0
|
|
12127
|
+
);
|
|
12128
|
+
const rows = tableRowsOf(result, "pages");
|
|
12129
|
+
emit(flags, result ?? {}, () => printTable(rows, listSiteTemplatePagesColumns));
|
|
12130
|
+
});
|
|
12131
|
+
siteLayout.command("set").summary("Set what frames the blog").description(
|
|
12132
|
+
"Sets what frames the blog (\u201EWygl\u0105d bloga\u201D): `page` + `pageId` = the blog wears that page's header, menu, footer, fonts and colours (\u201EJak moja strona\u201D); `storefront` = the storefront shell. Live at once; the shared menu stays as it is. The page must be published, open to everyone, without its own scripts and with a <header> and a <footer>: 400 `site-template-page-ineligible` with `issue` otherwise, 404 `site-template-page-not-found`, 400 `site-template-page-required` for `page` without `pageId`; 403 `feature_not_available` while the blog is not on the plan. Returns the whole layout."
|
|
12133
|
+
).argument(
|
|
12134
|
+
"<kind>",
|
|
12135
|
+
"`storefront` = the storefront shell; `page` = one of your pages (`pageId`)."
|
|
12136
|
+
).option(
|
|
12137
|
+
"--page <value>",
|
|
12138
|
+
"With `kind: page`: the page that frames the blog (from list-site-template-pages)."
|
|
12139
|
+
).action(async (kind, options, command) => {
|
|
12140
|
+
const flags = getGlobalFlags(command);
|
|
12141
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12142
|
+
const params = {};
|
|
12143
|
+
const payload = {
|
|
12144
|
+
kind,
|
|
12145
|
+
pageId: options.page
|
|
12146
|
+
};
|
|
12147
|
+
const result = await client.call("PUT", "/site/layout/template", params, void 0, payload);
|
|
12148
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12149
|
+
});
|
|
12150
|
+
const siteMenu = site.command("menu");
|
|
12151
|
+
siteMenu.description("The site's one shared menu (Your site \u2192 Look and menu): up to 8 entries");
|
|
12152
|
+
siteMenu.command("get").summary("Get the shared site menu").description(
|
|
12153
|
+
"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)."
|
|
12154
|
+
).action(async (_options, command) => {
|
|
12155
|
+
const flags = getGlobalFlags(command);
|
|
12156
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12157
|
+
const params = {};
|
|
12158
|
+
const result = await client.call("GET", "/site/menu", params, void 0, void 0);
|
|
12159
|
+
const rows = tableRowsOf(result, "menu");
|
|
12160
|
+
emit(flags, result ?? {}, () => printTable(rows, getSiteMenuColumns));
|
|
12161
|
+
});
|
|
12162
|
+
siteMenu.command("set").summary("Set the shared site menu").description(
|
|
12163
|
+
'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.'
|
|
12164
|
+
).requiredOption(
|
|
12165
|
+
"--menu <json|file>",
|
|
12166
|
+
"EVERY entry of the new menu, in order (up to 8), as a JSON array; an empty list turns the shared menu off.",
|
|
12167
|
+
parseJsonArrayFileOrInline("--menu")
|
|
12168
|
+
).action(async (options, command) => {
|
|
12169
|
+
const flags = getGlobalFlags(command);
|
|
12170
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12171
|
+
const params = {};
|
|
12172
|
+
const payload = {
|
|
12173
|
+
menu: options.menu
|
|
12174
|
+
};
|
|
12175
|
+
const result = await client.call("PUT", "/site/menu", params, void 0, payload);
|
|
12176
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12177
|
+
});
|
|
12178
|
+
}
|
|
12179
|
+
|
|
12180
|
+
// src/commands/_generated/chats.ts
|
|
12181
|
+
function registerChatsCommands(program2) {
|
|
12182
|
+
const chats = program2.command("chats", { hidden: true });
|
|
12183
|
+
chats.description("Czaty: the assistant and hired agents");
|
|
12184
|
+
chats.command("list", { hidden: true }).summary("List chats with unread counts").description(
|
|
12185
|
+
"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)."
|
|
12186
|
+
).action(async (_options, command) => {
|
|
12187
|
+
const flags = getGlobalFlags(command);
|
|
12188
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12189
|
+
const params = {};
|
|
12190
|
+
const result = await client.call("GET", "/chat-home", params, void 0, void 0);
|
|
12191
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12192
|
+
});
|
|
12193
|
+
}
|
|
12194
|
+
|
|
12195
|
+
// src/commands/_generated/orders.ts
|
|
12196
|
+
function registerOrdersCommands(program2) {
|
|
12197
|
+
const orders2 = program2.commands.find((command) => command.name() === "orders") ?? program2.command("orders", { hidden: true });
|
|
12198
|
+
if (!orders2.description()) {
|
|
12199
|
+
orders2.description("Physical orders to ship (\u201EDo wys\u0142ania\u201D): list, mark shipped / delivered");
|
|
12200
|
+
}
|
|
12201
|
+
const ordersFulfilment = orders2.command("fulfilment", { hidden: true });
|
|
12202
|
+
ordersFulfilment.description(
|
|
12203
|
+
"Physical orders to ship (\u201EDo wys\u0142ania\u201D): list, mark shipped / delivered"
|
|
12204
|
+
);
|
|
12205
|
+
ordersFulfilment.command("list", { hidden: true }).summary("List physical orders to ship (\u201EDo wys\u0142ania\u201D)").description(
|
|
12206
|
+
"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."
|
|
12207
|
+
).addOption(
|
|
12208
|
+
new Option(
|
|
12209
|
+
"--status <unfulfilled|shipped|delivered|returned|cancelled>",
|
|
12210
|
+
"The tab: `unfulfilled` (\u201EDo wys\u0142ania\u201D, the default), `shipped` (\u201EWys\u0142ane\u201D), `delivered` (\u201EDor\u0119czone\u201D); also `returned` and `cancelled`."
|
|
12211
|
+
).choices(["unfulfilled", "shipped", "delivered", "returned", "cancelled"])
|
|
12212
|
+
).option("--cursor <value>", "`nextCursor` of the previous page; omit for the first page.").option(
|
|
12213
|
+
"--limit <n>",
|
|
12214
|
+
"Rows per page, newest first (default 50, max 200).",
|
|
12215
|
+
parseNumberOption("--limit", NumberFormat.Integer)
|
|
12216
|
+
).action(async (options, command) => {
|
|
12217
|
+
const flags = getGlobalFlags(command);
|
|
12218
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12219
|
+
const params = {};
|
|
12220
|
+
const payload = {
|
|
12221
|
+
status: options.status,
|
|
12222
|
+
cursor: options.cursor,
|
|
12223
|
+
limit: options.limit
|
|
12224
|
+
};
|
|
12225
|
+
const result = await client.call("GET", "/orders/to-ship", params, payload, void 0);
|
|
12226
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12227
|
+
});
|
|
12228
|
+
ordersFulfilment.command("ship", { hidden: true }).summary("Mark a physical order as shipped").description(
|
|
12229
|
+
"\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."
|
|
12230
|
+
).argument("<orderId>", "The order id (`orderId` from list-orders-to-ship or list-orders).").addOption(
|
|
12231
|
+
new Option(
|
|
12232
|
+
"--carrier <inpost|poczta_polska|dpd|dhl|gls|orlen_paczka|other>",
|
|
12233
|
+
"inpost | poczta_polska | dpd | dhl | gls | orlen_paczka | other. InPost gets a tracking link built from the number; other carriers only with `trackingUrl`."
|
|
12234
|
+
).choices(["inpost", "poczta_polska", "dpd", "dhl", "gls", "orlen_paczka", "other"]).makeOptionMandatory()
|
|
12235
|
+
).option(
|
|
12236
|
+
"--tracking-number <value>",
|
|
12237
|
+
"The parcel number. Omit for a shipment without one (e.g. a letter)."
|
|
12238
|
+
).option(
|
|
12239
|
+
"--tracking-url <value>",
|
|
12240
|
+
"A tracking link (http/https). Omit for InPost: it is built from the number. Other carriers without one show the number only."
|
|
12241
|
+
).option(
|
|
12242
|
+
"--notify-buyer",
|
|
12243
|
+
"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."
|
|
12244
|
+
).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) => {
|
|
12245
|
+
const flags = getGlobalFlags(command);
|
|
12246
|
+
requireConfirm(options.confirm, "mark this order as shipped");
|
|
12247
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12248
|
+
const params = {
|
|
12249
|
+
orderId
|
|
12250
|
+
};
|
|
12251
|
+
const payload = {
|
|
12252
|
+
carrier: options.carrier,
|
|
12253
|
+
trackingNumber: options.trackingNumber,
|
|
12254
|
+
trackingUrl: options.trackingUrl,
|
|
12255
|
+
notifyBuyer: options.notifyBuyer
|
|
12256
|
+
};
|
|
12257
|
+
const result = await client.call(
|
|
12258
|
+
"POST",
|
|
12259
|
+
"/orders/:orderId/mark-shipped",
|
|
12260
|
+
params,
|
|
12261
|
+
void 0,
|
|
12262
|
+
payload
|
|
12263
|
+
);
|
|
12264
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12265
|
+
});
|
|
12266
|
+
ordersFulfilment.command("deliver", { hidden: true }).summary("Mark a physical order as delivered").description(
|
|
12267
|
+
"\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."
|
|
12268
|
+
).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) => {
|
|
12269
|
+
const flags = getGlobalFlags(command);
|
|
12270
|
+
requireConfirm(options.confirm, "mark this order as delivered");
|
|
12271
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12272
|
+
const params = {
|
|
12273
|
+
orderId
|
|
12274
|
+
};
|
|
12275
|
+
const result = await client.call(
|
|
12276
|
+
"POST",
|
|
12277
|
+
"/orders/:orderId/mark-delivered",
|
|
12278
|
+
params,
|
|
12279
|
+
void 0,
|
|
12280
|
+
void 0
|
|
12281
|
+
);
|
|
12282
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12283
|
+
});
|
|
12284
|
+
}
|
|
12285
|
+
|
|
12286
|
+
// src/commands/_generated/tax.ts
|
|
12287
|
+
function registerTaxCommands(program2) {
|
|
12288
|
+
const tax = program2.command("tax");
|
|
12289
|
+
tax.description(
|
|
12290
|
+
"Stripe Tax settings (on / off, inclusive / exclusive prices, EU companies pay net)"
|
|
12291
|
+
);
|
|
12292
|
+
tax.command("get").summary("Read the Stripe Tax settings").description(
|
|
12293
|
+
"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."
|
|
12294
|
+
).action(async (_options, command) => {
|
|
12295
|
+
const flags = getGlobalFlags(command);
|
|
12296
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12297
|
+
const params = {};
|
|
12298
|
+
const result = await client.call(
|
|
12299
|
+
"GET",
|
|
12300
|
+
"/integrations/payments/stripe/tax",
|
|
12301
|
+
params,
|
|
12302
|
+
void 0,
|
|
12303
|
+
void 0
|
|
12304
|
+
);
|
|
12305
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12306
|
+
});
|
|
12307
|
+
tax.command("set").summary("Change the Stripe Tax settings").description(
|
|
12308
|
+
"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."
|
|
12309
|
+
).option(
|
|
12310
|
+
"--enabled",
|
|
12311
|
+
"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)."
|
|
12312
|
+
).option("--no-enabled", "Send `enabled: false` explicitly.").addOption(
|
|
12313
|
+
new Option(
|
|
12314
|
+
"--behavior <inclusive|exclusive>",
|
|
12315
|
+
"The default price mode while on: `inclusive` (gross prices) or `exclusive` (net prices, tax added at checkout). Only while Stripe Tax is on."
|
|
12316
|
+
).choices(["inclusive", "exclusive"])
|
|
12317
|
+
).option(
|
|
12318
|
+
"--eu-companies-pay-net",
|
|
12319
|
+
"\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."
|
|
12320
|
+
).option("--no-eu-companies-pay-net", "Send `euCompaniesPayNet: false` explicitly.").action(async (options, command) => {
|
|
12321
|
+
const flags = getGlobalFlags(command);
|
|
12322
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12323
|
+
const params = {};
|
|
12324
|
+
const payload = {
|
|
12325
|
+
enabled: options.enabled,
|
|
12326
|
+
behavior: options.behavior,
|
|
12327
|
+
euCompaniesPayNet: options.euCompaniesPayNet
|
|
12328
|
+
};
|
|
12329
|
+
const result = await client.call(
|
|
12330
|
+
"PATCH",
|
|
12331
|
+
"/integrations/payments/stripe/tax",
|
|
12332
|
+
params,
|
|
12333
|
+
void 0,
|
|
12334
|
+
payload
|
|
12335
|
+
);
|
|
12336
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12337
|
+
});
|
|
12338
|
+
}
|
|
12339
|
+
|
|
12340
|
+
// src/commands/_generated/social.ts
|
|
12341
|
+
function registerSocialCommands(program2) {
|
|
12342
|
+
const social = program2.commands.find((command) => command.name() === "social") ?? program2.command("social", { hidden: true });
|
|
12343
|
+
if (!social.description()) {
|
|
12344
|
+
social.description("Automatic messages after an Instagram comment (keyword \u2192 private message)");
|
|
12345
|
+
}
|
|
12346
|
+
const socialCommentDm = social.command("comment-dm", { hidden: true });
|
|
12347
|
+
socialCommentDm.description(
|
|
12348
|
+
"Automatic messages after an Instagram comment (keyword \u2192 private message)"
|
|
12349
|
+
);
|
|
12350
|
+
socialCommentDm.command("list", { hidden: true }).summary("List the automatic messages after a comment").description(
|
|
12351
|
+
"The workspace\u2019s \u201EAutomatyczna wiadomo\u015B\u0107 po komentarzu\u201D rules on Instagram, newest first: the account, the post (or every post of the account), the keywords, the message, the link as sent (`taggedLink`, with UTM tags), the public reply, `status` (active / paused), `ended`, and the results so far in `counts` (matched comments, `sent` and `failed` private messages) with the latest failure. Read-only. 403 `feature_not_available` while the workspace has no early access."
|
|
12352
|
+
).option(
|
|
12353
|
+
"--account <value>",
|
|
12354
|
+
"Only this Instagram account's rules: its id or its target from list-social-accounts. Omitted = every account."
|
|
12355
|
+
).action(async (options, command) => {
|
|
12356
|
+
const flags = getGlobalFlags(command);
|
|
12357
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12358
|
+
const params = {};
|
|
12359
|
+
const payload = {
|
|
12360
|
+
accountId: options.account
|
|
12361
|
+
};
|
|
12362
|
+
const result = await client.call(
|
|
12363
|
+
"GET",
|
|
12364
|
+
"/social/instagram/comment-dm-rules",
|
|
12365
|
+
params,
|
|
12366
|
+
payload,
|
|
12367
|
+
void 0
|
|
12368
|
+
);
|
|
12369
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12370
|
+
});
|
|
12371
|
+
socialCommentDm.command("create", { hidden: true }).summary("Create an automatic message after a comment").description(
|
|
12372
|
+
"Creates a \u201EAutomatyczna wiadomo\u015B\u0107 po komentarzu\u201D rule on Instagram: when someone comments one of the keywords under the post (or any post of the account), Instagram sends them ONE private message with the text and the link (UTM-tagged), optionally with a public reply under the comment. Each person gets it once per rule; comments older than 7 days get nothing. Same rules as the panel: 400 `invalid` (with `problem`), 404 `not_connected`, 409 `reconnect_required` (the account lacks the messages / comments permission), 409 `too_many_rules` (50 per workspace), 403 `feature_not_available` without the early access. `commentsSubscribed: false` = saved but Instagram refused the comment notifications (reconnect the account). Pause it with pause-instagram-comment-dm-rule."
|
|
12373
|
+
).option(
|
|
12374
|
+
"--account <value>",
|
|
12375
|
+
"The Instagram account: its id (`accountId` of a rule), or the `target` of an Instagram account from list-social-accounts (`instagram` = the main account, `instagram:{id}`). Omitted = the main connected Instagram account."
|
|
12376
|
+
).addOption(
|
|
12377
|
+
new Option(
|
|
12378
|
+
"--scope <post|account>",
|
|
12379
|
+
"`post` = comments under one post (send `mediaId` of a published post from list-instagram-media, or `socialPostId` of a composer post not published yet); `account` = comments under any post of the account."
|
|
12380
|
+
).choices(["post", "account"]).makeOptionMandatory()
|
|
12381
|
+
).option("--media-id <value>", "`post` scope: the published post\u2019s media id.").option(
|
|
12382
|
+
"--social-post-id <value>",
|
|
12383
|
+
"`post` scope: a composer post not live yet; the rule waits for its publishing."
|
|
12384
|
+
).option("--media-permalink <value>", "The post\u2019s Instagram link, shown on the rule.").option("--media-caption <value>", "The post\u2019s caption, shown on the rule.").requiredOption(
|
|
12385
|
+
"--keywords <a,b,...>",
|
|
12386
|
+
"Words or short phrases that trigger the message (1-10, each up to 40 characters). A comment matches when it contains one as whole words; case and Polish letters do not matter (\u201EPobierz!\u201D and \u201Epobierz\u201D match `pobierz`).",
|
|
12387
|
+
parseCommaList("--keywords")
|
|
12388
|
+
).requiredOption(
|
|
12389
|
+
"--text <value>",
|
|
12390
|
+
"The private message sent once to each person who comments a keyword. With the link it must fit Instagram\u2019s 1000 bytes."
|
|
12391
|
+
).option(
|
|
12392
|
+
"--link <value>",
|
|
12393
|
+
"Optional full http(s) link added to the message; Zanfia adds UTM tags (utm_source=instagram, utm_medium=comment_dm, utm_campaign=<first keyword>). null removes it."
|
|
12394
|
+
).option(
|
|
12395
|
+
"--public-reply <json|file>",
|
|
12396
|
+
"Optional public reply under the matching comment; off when omitted.",
|
|
12397
|
+
parseJsonObjectFileOrInline("--public-reply")
|
|
12398
|
+
).addOption(
|
|
12399
|
+
new Option(
|
|
12400
|
+
"--status <active|paused>",
|
|
12401
|
+
"Omitted = `active` (answers comments at once)."
|
|
12402
|
+
).choices(["active", "paused"])
|
|
12403
|
+
).option(
|
|
12404
|
+
"--ends-at <value>",
|
|
12405
|
+
"Optional ISO 8601 time after which the rule answers nothing; null = no end."
|
|
12406
|
+
).action(async (options, command) => {
|
|
12407
|
+
const flags = getGlobalFlags(command);
|
|
12408
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12409
|
+
const params = {};
|
|
12410
|
+
const payload = {
|
|
12411
|
+
accountId: options.account,
|
|
12412
|
+
scope: options.scope,
|
|
12413
|
+
mediaId: options.mediaId,
|
|
12414
|
+
socialPostId: options.socialPostId,
|
|
12415
|
+
mediaPermalink: options.mediaPermalink,
|
|
12416
|
+
mediaCaption: options.mediaCaption,
|
|
12417
|
+
keywords: options.keywords,
|
|
12418
|
+
dmText: options.text,
|
|
12419
|
+
link: options.link,
|
|
12420
|
+
publicReply: options.publicReply,
|
|
12421
|
+
status: options.status,
|
|
12422
|
+
endsAt: options.endsAt
|
|
12423
|
+
};
|
|
12424
|
+
const result = await client.call(
|
|
12425
|
+
"POST",
|
|
12426
|
+
"/social/instagram/comment-dm-rules",
|
|
12427
|
+
params,
|
|
12428
|
+
void 0,
|
|
12429
|
+
payload
|
|
12430
|
+
);
|
|
12431
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12432
|
+
});
|
|
12433
|
+
socialCommentDm.command("update", { hidden: true }).summary("Change or resume an automatic message after a comment").description(
|
|
12434
|
+
"Changes a \u201EAutomatyczna wiadomo\u015B\u0107 po komentarzu\u201D rule like the panel\u2019s edit: only the fields sent change (keywords, message, link, public reply, end date) and `status` pauses (`paused`) or resumes (`active`) it. The account and the post never change: a new post is a new rule. People who already got the message do not get it again. Same validation as a create (400 `invalid` with `problem`); 404 `rule_not_found`. Reversible: change it again."
|
|
12435
|
+
).argument("<id>", "The rule id (`id` from list-instagram-comment-dm-rules).").option(
|
|
12436
|
+
"--keywords <a,b,...>",
|
|
12437
|
+
"The full new keyword list (replaces the old one).",
|
|
12438
|
+
parseCommaList("--keywords")
|
|
12439
|
+
).option(
|
|
12440
|
+
"--text <value>",
|
|
12441
|
+
"The private message sent once to each person who comments a keyword. With the link it must fit Instagram\u2019s 1000 bytes."
|
|
12442
|
+
).option(
|
|
12443
|
+
"--link <value>",
|
|
12444
|
+
"Optional full http(s) link added to the message; Zanfia adds UTM tags (utm_source=instagram, utm_medium=comment_dm, utm_campaign=<first keyword>). null removes it."
|
|
12445
|
+
).option(
|
|
12446
|
+
"--public-reply <json|file>",
|
|
12447
|
+
"Optional public reply under the matching comment; off when omitted.",
|
|
12448
|
+
parseJsonObjectFileOrInline("--public-reply")
|
|
12449
|
+
).addOption(
|
|
12450
|
+
new Option(
|
|
12451
|
+
"--status <active|paused>",
|
|
12452
|
+
"`active` resumes the rule, `paused` pauses it."
|
|
12453
|
+
).choices(["active", "paused"])
|
|
12454
|
+
).option(
|
|
12455
|
+
"--ends-at <value>",
|
|
12456
|
+
"Optional ISO 8601 time after which the rule answers nothing; null = no end."
|
|
12457
|
+
).action(async (id, options, command) => {
|
|
12458
|
+
const flags = getGlobalFlags(command);
|
|
12459
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12460
|
+
const params = {
|
|
12461
|
+
ruleId: id
|
|
12462
|
+
};
|
|
12463
|
+
const payload = {
|
|
12464
|
+
keywords: options.keywords,
|
|
12465
|
+
dmText: options.text,
|
|
12466
|
+
link: options.link,
|
|
12467
|
+
publicReply: options.publicReply,
|
|
12468
|
+
status: options.status,
|
|
12469
|
+
endsAt: options.endsAt
|
|
12470
|
+
};
|
|
12471
|
+
const result = await client.call(
|
|
12472
|
+
"PATCH",
|
|
12473
|
+
"/social/instagram/comment-dm-rules/:ruleId",
|
|
12474
|
+
params,
|
|
12475
|
+
void 0,
|
|
12476
|
+
payload
|
|
12477
|
+
);
|
|
12478
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12479
|
+
});
|
|
12480
|
+
socialCommentDm.command("pause", { hidden: true }).summary("Pause an automatic message after a comment").description(
|
|
12481
|
+
"Pauses a \u201EAutomatyczna wiadomo\u015B\u0107 po komentarzu\u201D rule (the panel\u2019s \u201EWstrzymaj\u201D): it answers no new comment until resumed (update-instagram-comment-dm-rule with `status: active`). Its settings and results stay. 404 `rule_not_found`."
|
|
12482
|
+
).argument("<id>", "The rule id (`id` from list-instagram-comment-dm-rules).").action(async (id, _options, command) => {
|
|
11528
12483
|
const flags = getGlobalFlags(command);
|
|
11529
12484
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11530
12485
|
const params = {
|
|
11531
|
-
|
|
12486
|
+
ruleId: id
|
|
11532
12487
|
};
|
|
11533
12488
|
const result = await client.call(
|
|
11534
|
-
"
|
|
11535
|
-
"/
|
|
12489
|
+
"POST",
|
|
12490
|
+
"/social/instagram/comment-dm-rules/:ruleId/pause",
|
|
11536
12491
|
params,
|
|
11537
12492
|
void 0,
|
|
11538
12493
|
void 0
|
|
11539
12494
|
);
|
|
11540
12495
|
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11541
12496
|
});
|
|
11542
|
-
}
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
|
|
11548
|
-
const site = program2.commands.find((command) => command.name() === "site") ?? program2.command("site");
|
|
11549
|
-
if (!site.description()) {
|
|
11550
|
-
site.description(
|
|
11551
|
-
"What frames the blog (Your site \u2192 Look and menu): the storefront shell or one of your pages; checked by the same rules as the dashboard"
|
|
11552
|
-
);
|
|
11553
|
-
}
|
|
11554
|
-
const siteLayout = site.command("layout");
|
|
11555
|
-
siteLayout.description(
|
|
11556
|
-
"What frames the blog (Your site \u2192 Look and menu): the storefront shell or one of your pages; checked by the same rules as the dashboard"
|
|
11557
|
-
);
|
|
11558
|
-
siteLayout.command("get").summary("Get the site layout").description(
|
|
11559
|
-
'What frames the blog and the shared site menu (dashboard: Your site \u2192 Look and menu). `template` = `{ kind: "storefront" }` (the storefront shell, the default) or `{ kind: "page", pageId }` (the blog wears that page\'s header, menu and footer: \u201EJak moja strona\u201D). `menu` = the shared menu in order, each entry with `href`, the address it leads to today (null = it leads nowhere now, pages leave it out); an empty menu = every page keeps its own links.'
|
|
11560
|
-
).action(async (_options, command) => {
|
|
11561
|
-
const flags = getGlobalFlags(command);
|
|
11562
|
-
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11563
|
-
const params = {};
|
|
11564
|
-
const result = await client.call("GET", "/site/layout", params, void 0, void 0);
|
|
11565
|
-
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11566
|
-
});
|
|
11567
|
-
siteLayout.command("pages").summary("List pages that can frame the blog").description(
|
|
11568
|
-
"The workspace's published pages as candidates for the blog's frame, eligible ones first. `issue` null = the page can frame the blog (pass its `pageId` to set-site-template); otherwise why not: `not-published`, `gated` (behind a product or a deadline), `custom-scripts` (its own JS), `no-header-or-footer`. `nav` = whether the shared menu shows on the page: `shared`, `not-marked` (no `data-zanfia-nav`, its own links stay) or `unsupported`."
|
|
11569
|
-
).action(async (_options, command) => {
|
|
12497
|
+
socialCommentDm.command("delete", { hidden: true }).summary("Delete an automatic message after a comment").description(
|
|
12498
|
+
"Deletes a \u201EAutomatyczna wiadomo\u015B\u0107 po komentarzu\u201D rule for good, with its results and the list of people who got the message. No undo: prefer pause-instagram-comment-dm-rule. 404 `rule_not_found`."
|
|
12499
|
+
).argument("<id>", "The rule id (`id` from list-instagram-comment-dm-rules).").option(
|
|
12500
|
+
"--confirm",
|
|
12501
|
+
"required \u2014 acknowledge that you want to delete the rule and its results for good"
|
|
12502
|
+
).action(async (id, options, command) => {
|
|
11570
12503
|
const flags = getGlobalFlags(command);
|
|
12504
|
+
requireConfirm(options.confirm, "delete the rule and its results for good");
|
|
11571
12505
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11572
|
-
const params = {
|
|
12506
|
+
const params = {
|
|
12507
|
+
ruleId: id
|
|
12508
|
+
};
|
|
11573
12509
|
const result = await client.call(
|
|
11574
|
-
"
|
|
11575
|
-
"/
|
|
12510
|
+
"DELETE",
|
|
12511
|
+
"/social/instagram/comment-dm-rules/:ruleId",
|
|
11576
12512
|
params,
|
|
11577
12513
|
void 0,
|
|
11578
12514
|
void 0
|
|
11579
12515
|
);
|
|
11580
|
-
const rows = tableRowsOf(result, "pages");
|
|
11581
|
-
emit(flags, result ?? {}, () => printTable(rows, listSiteTemplatePagesColumns));
|
|
11582
|
-
});
|
|
11583
|
-
siteLayout.command("set").summary("Set what frames the blog").description(
|
|
11584
|
-
"Sets what frames the blog (\u201EWygl\u0105d bloga\u201D): `page` + `pageId` = the blog wears that page's header, menu, footer, fonts and colours (\u201EJak moja strona\u201D); `storefront` = the storefront shell. Live at once; the shared menu stays as it is. The page must be published, open to everyone, without its own scripts and with a <header> and a <footer>: 400 `site-template-page-ineligible` with `issue` otherwise, 404 `site-template-page-not-found`, 400 `site-template-page-required` for `page` without `pageId`; 403 `feature_not_available` while the blog is not on the plan. Returns the whole layout."
|
|
11585
|
-
).argument(
|
|
11586
|
-
"<kind>",
|
|
11587
|
-
"`storefront` = the storefront shell; `page` = one of your pages (`pageId`)."
|
|
11588
|
-
).option(
|
|
11589
|
-
"--page <value>",
|
|
11590
|
-
"With `kind: page`: the page that frames the blog (from list-site-template-pages)."
|
|
11591
|
-
).action(async (kind, options, command) => {
|
|
11592
|
-
const flags = getGlobalFlags(command);
|
|
11593
|
-
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11594
|
-
const params = {};
|
|
11595
|
-
const payload = {
|
|
11596
|
-
kind,
|
|
11597
|
-
pageId: options.page
|
|
11598
|
-
};
|
|
11599
|
-
const result = await client.call("PUT", "/site/layout/template", params, void 0, payload);
|
|
11600
|
-
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11601
|
-
});
|
|
11602
|
-
const siteMenu = site.command("menu");
|
|
11603
|
-
siteMenu.description("The site's one shared menu (Your site \u2192 Look and menu): up to 8 entries");
|
|
11604
|
-
siteMenu.command("get").summary("Get the shared site menu").description(
|
|
11605
|
-
"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)."
|
|
11606
|
-
).action(async (_options, command) => {
|
|
11607
|
-
const flags = getGlobalFlags(command);
|
|
11608
|
-
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11609
|
-
const params = {};
|
|
11610
|
-
const result = await client.call("GET", "/site/menu", params, void 0, void 0);
|
|
11611
|
-
const rows = tableRowsOf(result, "menu");
|
|
11612
|
-
emit(flags, result ?? {}, () => printTable(rows, getSiteMenuColumns));
|
|
11613
|
-
});
|
|
11614
|
-
siteMenu.command("set").summary("Set the shared site menu").description(
|
|
11615
|
-
'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.'
|
|
11616
|
-
).requiredOption(
|
|
11617
|
-
"--menu <json|file>",
|
|
11618
|
-
"EVERY entry of the new menu, in order (up to 8), as a JSON array; an empty list turns the shared menu off.",
|
|
11619
|
-
parseJsonArrayFileOrInline("--menu")
|
|
11620
|
-
).action(async (options, command) => {
|
|
11621
|
-
const flags = getGlobalFlags(command);
|
|
11622
|
-
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11623
|
-
const params = {};
|
|
11624
|
-
const payload = {
|
|
11625
|
-
menu: options.menu
|
|
11626
|
-
};
|
|
11627
|
-
const result = await client.call("PUT", "/site/menu", params, void 0, payload);
|
|
11628
12516
|
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
11629
12517
|
});
|
|
11630
12518
|
}
|
|
@@ -11635,11 +12523,18 @@ function registerGeneratedCommands(program2) {
|
|
|
11635
12523
|
registerBlogCommands(program2);
|
|
11636
12524
|
registerSiteRedirectsCommands(program2);
|
|
11637
12525
|
registerPopupsCommands(program2);
|
|
12526
|
+
registerProductsCommands(program2);
|
|
12527
|
+
registerSubscriptionsCommands(program2);
|
|
11638
12528
|
registerStudioCommands(program2);
|
|
11639
12529
|
registerSiteCommands(program2);
|
|
12530
|
+
registerChatsCommands(program2);
|
|
12531
|
+
registerOrdersCommands(program2);
|
|
12532
|
+
registerTaxCommands(program2);
|
|
12533
|
+
registerSocialCommands(program2);
|
|
11640
12534
|
}
|
|
11641
12535
|
|
|
11642
12536
|
// src/commands/ads/index.ts
|
|
12537
|
+
import { randomUUID } from "crypto";
|
|
11643
12538
|
import { readFileSync as readFileSync3 } from "fs";
|
|
11644
12539
|
var SETTABLE_STATUSES = ["ACTIVE", "PAUSED"];
|
|
11645
12540
|
var ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
@@ -11738,6 +12633,74 @@ function parseUpdateCreativeInput(value) {
|
|
|
11738
12633
|
}
|
|
11739
12634
|
return parsed;
|
|
11740
12635
|
}
|
|
12636
|
+
function parseCreateJson(value, allowed, required) {
|
|
12637
|
+
const parsed = parseJsonObjectFileOrInline2(value, "--json");
|
|
12638
|
+
const unknownKeys = Object.keys(parsed).filter((key) => !allowed.includes(key));
|
|
12639
|
+
if (unknownKeys.length > 0) {
|
|
12640
|
+
throw new Error(`--json accepts only ${allowed.join(", ")} (got: ${unknownKeys.join(", ")}).`);
|
|
12641
|
+
}
|
|
12642
|
+
const missing = required.filter((key) => parsed[key] === void 0);
|
|
12643
|
+
if (missing.length > 0) {
|
|
12644
|
+
throw new Error(`--json needs ${missing.join(", ")}.`);
|
|
12645
|
+
}
|
|
12646
|
+
return parsed;
|
|
12647
|
+
}
|
|
12648
|
+
var CREATE_CAMPAIGN_KEYS = [
|
|
12649
|
+
"name",
|
|
12650
|
+
"objective",
|
|
12651
|
+
"specialAdCategories",
|
|
12652
|
+
"dailyBudgetMinor",
|
|
12653
|
+
"lifetimeBudgetMinor",
|
|
12654
|
+
"endTime"
|
|
12655
|
+
];
|
|
12656
|
+
var CREATE_AD_SET_KEYS = [
|
|
12657
|
+
"campaignId",
|
|
12658
|
+
"name",
|
|
12659
|
+
"dailyBudgetMinor",
|
|
12660
|
+
"lifetimeBudgetMinor",
|
|
12661
|
+
"startTime",
|
|
12662
|
+
"endTime",
|
|
12663
|
+
"targeting",
|
|
12664
|
+
"publisherPlatforms",
|
|
12665
|
+
"datasetId"
|
|
12666
|
+
];
|
|
12667
|
+
var CREATE_AD_FROM_SCRATCH_KEYS = [
|
|
12668
|
+
"adSetId",
|
|
12669
|
+
"name",
|
|
12670
|
+
"pageId",
|
|
12671
|
+
"instagramUserId",
|
|
12672
|
+
"imageUrl",
|
|
12673
|
+
"message",
|
|
12674
|
+
"headline",
|
|
12675
|
+
"description",
|
|
12676
|
+
"callToActionType",
|
|
12677
|
+
"link",
|
|
12678
|
+
"urlTags"
|
|
12679
|
+
];
|
|
12680
|
+
function guardFieldsOf(options, what) {
|
|
12681
|
+
if (!options.dryRun) requireConfirm2(options.confirm, what);
|
|
12682
|
+
const idempotencyKey = options.idempotencyKey ?? randomUUID();
|
|
12683
|
+
if (!options.idempotencyKey && !options.dryRun) {
|
|
12684
|
+
process.stderr.write(
|
|
12685
|
+
`Idempotency key: ${idempotencyKey} (re-run with --idempotency-key ${idempotencyKey} to retry safely)
|
|
12686
|
+
`
|
|
12687
|
+
);
|
|
12688
|
+
}
|
|
12689
|
+
return { idempotencyKey, ...options.dryRun ? { dryRun: true } : {} };
|
|
12690
|
+
}
|
|
12691
|
+
function printCreatePreview(result, nextStep) {
|
|
12692
|
+
const { preview: preview2 } = result;
|
|
12693
|
+
const lead = result.dryRun ? "Dry run, nothing created." : result.replayed ? "Already created with this idempotency key (nothing new on Meta)." : "Created (PAUSED).";
|
|
12694
|
+
process.stdout.write(`${lead}
|
|
12695
|
+
${preview2.summary}
|
|
12696
|
+
`);
|
|
12697
|
+
process.stdout.write(
|
|
12698
|
+
`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)}.
|
|
12699
|
+
`
|
|
12700
|
+
);
|
|
12701
|
+
if (!result.dryRun) process.stdout.write(`${nextStep}
|
|
12702
|
+
`);
|
|
12703
|
+
}
|
|
11741
12704
|
function requireConfirm2(confirm, what) {
|
|
11742
12705
|
if (!confirm) {
|
|
11743
12706
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
@@ -12127,6 +13090,33 @@ Daily (${result.daily.length})
|
|
|
12127
13090
|
});
|
|
12128
13091
|
}
|
|
12129
13092
|
);
|
|
13093
|
+
campaign.command("create").description(
|
|
13094
|
+
"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."
|
|
13095
|
+
).argument(...CONNECTION_ARG).argument(...AD_ACCOUNT_ARG).requiredOption(
|
|
13096
|
+
"--json <fileOrJson>",
|
|
13097
|
+
`name, objective (required); specialAdCategories, dailyBudgetMinor (optional) ${JSON_FLAG_HINT}`
|
|
13098
|
+
).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(
|
|
13099
|
+
async (connectionId, adAccountId, options, command) => {
|
|
13100
|
+
const flags = getGlobalFlags(command);
|
|
13101
|
+
const input = parseCreateJson(options.json, CREATE_CAMPAIGN_KEYS, [
|
|
13102
|
+
"name",
|
|
13103
|
+
"objective"
|
|
13104
|
+
]);
|
|
13105
|
+
const guard = guardFieldsOf(options, `create campaign "${input.name}"`);
|
|
13106
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
13107
|
+
const result = await client.createMetaCampaign(scopeOf(connectionId, adAccountId), {
|
|
13108
|
+
...input,
|
|
13109
|
+
...guard
|
|
13110
|
+
});
|
|
13111
|
+
emit(flags, result, () => {
|
|
13112
|
+
printCreatePreview(
|
|
13113
|
+
result,
|
|
13114
|
+
`Next: \`ads ad-set create ${connectionId} ${adAccountId} --json '{"campaignId":"${result.campaign?.id ?? "\u2026"}",\u2026}'\`.`
|
|
13115
|
+
);
|
|
13116
|
+
if (result.campaign) printCampaignTable([result.campaign]);
|
|
13117
|
+
});
|
|
13118
|
+
}
|
|
13119
|
+
);
|
|
12130
13120
|
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
13121
|
async (connectionId, adAccountId, options, command) => {
|
|
12132
13122
|
const flags = getGlobalFlags(command);
|
|
@@ -12147,6 +13137,34 @@ Daily (${result.daily.length})
|
|
|
12147
13137
|
emit(flags, result, () => printAdSet(result.adSet));
|
|
12148
13138
|
}
|
|
12149
13139
|
);
|
|
13140
|
+
adSet.command("create").description(
|
|
13141
|
+
"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."
|
|
13142
|
+
).argument(...CONNECTION_ARG).argument(...AD_ACCOUNT_ARG).requiredOption(
|
|
13143
|
+
"--json <fileOrJson>",
|
|
13144
|
+
`campaignId, name, targeting { countries, ageMin, ageMax, gender, customAudienceIds, excludedCustomAudienceIds, advantageAudience } (required: campaignId, name, targeting.countries); dailyBudgetMinor | lifetimeBudgetMinor + endTime, startTime, publisherPlatforms, datasetId ${JSON_FLAG_HINT}`
|
|
13145
|
+
).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(
|
|
13146
|
+
async (connectionId, adAccountId, options, command) => {
|
|
13147
|
+
const flags = getGlobalFlags(command);
|
|
13148
|
+
const input = parseCreateJson(options.json, CREATE_AD_SET_KEYS, [
|
|
13149
|
+
"campaignId",
|
|
13150
|
+
"name",
|
|
13151
|
+
"targeting"
|
|
13152
|
+
]);
|
|
13153
|
+
const guard = guardFieldsOf(options, `create ad set "${input.name}"`);
|
|
13154
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
13155
|
+
const result = await client.createMetaAdSet(scopeOf(connectionId, adAccountId), {
|
|
13156
|
+
...input,
|
|
13157
|
+
...guard
|
|
13158
|
+
});
|
|
13159
|
+
emit(flags, result, () => {
|
|
13160
|
+
printCreatePreview(
|
|
13161
|
+
result,
|
|
13162
|
+
`Next: \`ads create ${connectionId} ${adAccountId} --from-scratch --json '{"adSetId":"${result.adSet?.id ?? "\u2026"}",\u2026}'\`.`
|
|
13163
|
+
);
|
|
13164
|
+
if (result.adSet) printAdSet(result.adSet);
|
|
13165
|
+
});
|
|
13166
|
+
}
|
|
13167
|
+
);
|
|
12150
13168
|
adSet.command("update").description(
|
|
12151
13169
|
"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
13170
|
).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 +13204,37 @@ More ads available \u2014 re-run with --after ${result.after}
|
|
|
12186
13204
|
}
|
|
12187
13205
|
);
|
|
12188
13206
|
ads.command("create").description(
|
|
12189
|
-
"Create an ad
|
|
13207
|
+
"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
13208
|
).argument(...CONNECTION_ARG).argument(...AD_ACCOUNT_ARG).requiredOption(
|
|
12191
13209
|
"--json <fileOrJson>",
|
|
12192
|
-
`
|
|
12193
|
-
).option("--confirm", "required \u2014 acknowledge creating an ad on the live ad account").action(
|
|
13210
|
+
`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}`
|
|
13211
|
+
).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
13212
|
async (connectionId, adAccountId, options, command) => {
|
|
12195
13213
|
const flags = getGlobalFlags(command);
|
|
13214
|
+
if (options.fromScratch) {
|
|
13215
|
+
const scratch = parseCreateJson(
|
|
13216
|
+
options.json,
|
|
13217
|
+
CREATE_AD_FROM_SCRATCH_KEYS,
|
|
13218
|
+
["adSetId", "name", "pageId", "imageUrl", "message", "link"]
|
|
13219
|
+
);
|
|
13220
|
+
const guard = guardFieldsOf(options, `create ad "${scratch.name}"`);
|
|
13221
|
+
const scratchClient = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
13222
|
+
const created = await scratchClient.createMetaAdFromScratch(
|
|
13223
|
+
scopeOf(connectionId, adAccountId),
|
|
13224
|
+
{ ...scratch, ...guard }
|
|
13225
|
+
);
|
|
13226
|
+
emit(flags, created, () => {
|
|
13227
|
+
printCreatePreview(
|
|
13228
|
+
created,
|
|
13229
|
+
`Turn it on with \`ads update ${connectionId} ${adAccountId} ${created.ad?.id ?? "\u2026"} --status ACTIVE --confirm\` (it spends).`
|
|
13230
|
+
);
|
|
13231
|
+
if (created.ad) printAd(created.ad);
|
|
13232
|
+
});
|
|
13233
|
+
return;
|
|
13234
|
+
}
|
|
13235
|
+
if (options.dryRun || options.idempotencyKey) {
|
|
13236
|
+
throw new Error("--dry-run and --idempotency-key apply to --from-scratch only.");
|
|
13237
|
+
}
|
|
12196
13238
|
const input = parseCreateAdInput(options.json);
|
|
12197
13239
|
requireConfirm2(options.confirm, `create ad "${input.name}" from ${input.sourceAdId}`);
|
|
12198
13240
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -13091,6 +14133,8 @@ function promptHidden(question) {
|
|
|
13091
14133
|
|
|
13092
14134
|
// src/commands/auth/register.ts
|
|
13093
14135
|
var BOT_CHECK_FAILED_CODE = "auth/bot-check-failed";
|
|
14136
|
+
var DEVICE_VERIFICATION_REQUIRED_CODE = "auth/device-verification-required";
|
|
14137
|
+
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
14138
|
var MAX_CODE_ATTEMPTS = 5;
|
|
13095
14139
|
var MIN_PASSWORD_LENGTH = 8;
|
|
13096
14140
|
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
@@ -13250,12 +14294,22 @@ We sent a 6-digit code to ${email}. Check your inbox.
|
|
|
13250
14294
|
codeChallengeMethod: "S256",
|
|
13251
14295
|
hostname: safeHostname2()
|
|
13252
14296
|
};
|
|
13253
|
-
|
|
13254
|
-
|
|
13255
|
-
|
|
13256
|
-
|
|
13257
|
-
|
|
13258
|
-
|
|
14297
|
+
let minted;
|
|
14298
|
+
try {
|
|
14299
|
+
minted = await postJson2(
|
|
14300
|
+
"Authorizing CLI",
|
|
14301
|
+
`${urls.usersUrl}/cli/auth/codes`,
|
|
14302
|
+
codeBody,
|
|
14303
|
+
{ bearer: signIn.idToken, verbose }
|
|
14304
|
+
);
|
|
14305
|
+
} catch (error) {
|
|
14306
|
+
if (error instanceof HttpJsonError && error.code === DEVICE_VERIFICATION_REQUIRED_CODE) {
|
|
14307
|
+
process.stdout.write(DEVICE_CONFIRMATION_NOTICE);
|
|
14308
|
+
await runLogin({ apiUrl: options.apiUrl, verbose });
|
|
14309
|
+
return;
|
|
14310
|
+
}
|
|
14311
|
+
throw error;
|
|
14312
|
+
}
|
|
13259
14313
|
const exchangeBody = {
|
|
13260
14314
|
code: minted.code,
|
|
13261
14315
|
codeVerifier
|
|
@@ -13306,6 +14360,9 @@ plus its personal workspace are created.
|
|
|
13306
14360
|
Running it with an email that already has a creator account simply signs
|
|
13307
14361
|
that account in.
|
|
13308
14362
|
|
|
14363
|
+
When Zanfia asks you to confirm this device first, the CLI opens the panel in
|
|
14364
|
+
your browser; confirm the device there and the CLI logs in like \`login\`.
|
|
14365
|
+
|
|
13309
14366
|
Skipping the password is fine \u2014 you can always sign in with an emailed code.
|
|
13310
14367
|
`
|
|
13311
14368
|
).action(async (options) => {
|
|
@@ -14311,6 +15368,7 @@ var GRANT_PAYMENT_METHODS = [
|
|
|
14311
15368
|
"cash",
|
|
14312
15369
|
"other"
|
|
14313
15370
|
];
|
|
15371
|
+
var HELD_EVENTS_DECISIONS = ["send", "skip"];
|
|
14314
15372
|
var AIImageAspectRatio = /* @__PURE__ */ ((AIImageAspectRatio2) => {
|
|
14315
15373
|
AIImageAspectRatio2["Square"] = "1:1";
|
|
14316
15374
|
AIImageAspectRatio2["Portrait"] = "4:5";
|
|
@@ -14324,6 +15382,13 @@ var AIImageQuality = /* @__PURE__ */ ((AIImageQuality2) => {
|
|
|
14324
15382
|
AIImageQuality2["Ultra"] = "4K";
|
|
14325
15383
|
return AIImageQuality2;
|
|
14326
15384
|
})(AIImageQuality || {});
|
|
15385
|
+
var STUDIO_OUTPUT_PLATFORMS = [
|
|
15386
|
+
"facebook",
|
|
15387
|
+
"linkedin",
|
|
15388
|
+
"twitch",
|
|
15389
|
+
"kick",
|
|
15390
|
+
"other"
|
|
15391
|
+
];
|
|
14327
15392
|
var MANAGED_CONVERSION_TRACKING_CLIENTS = [
|
|
14328
15393
|
"googleAnalytics",
|
|
14329
15394
|
"googleTagManager",
|
|
@@ -14410,6 +15475,89 @@ var CLIENT_TAG_COLORS = [
|
|
|
14410
15475
|
"purple"
|
|
14411
15476
|
];
|
|
14412
15477
|
var TRANSCRIPTION_LANGUAGES = ["pl", "en", "default"];
|
|
15478
|
+
var COMMUNITY_JOIN_APPROVE_MAX_IDS = 20;
|
|
15479
|
+
var COMMUNITY_JOIN_DECLINE_MAX_IDS = 100;
|
|
15480
|
+
var LESSON_QA_INBOX_FILTERS = [
|
|
15481
|
+
"unanswered",
|
|
15482
|
+
"all",
|
|
15483
|
+
"public",
|
|
15484
|
+
"private",
|
|
15485
|
+
"hidden"
|
|
15486
|
+
];
|
|
15487
|
+
var SETTABLE_LESSON_QA_ENTRY_STATUSES = [
|
|
15488
|
+
"published",
|
|
15489
|
+
"hidden"
|
|
15490
|
+
];
|
|
15491
|
+
var CONSULTATION_BOOKING_STATUSES = [
|
|
15492
|
+
"booked",
|
|
15493
|
+
"cancelled",
|
|
15494
|
+
"done"
|
|
15495
|
+
];
|
|
15496
|
+
var STUDIO_SOCIAL_DRAFT_WEAK_REASONS = [
|
|
15497
|
+
"too-generic",
|
|
15498
|
+
"not-my-voice",
|
|
15499
|
+
"too-long",
|
|
15500
|
+
"too-salesy",
|
|
15501
|
+
"sounds-like-ai",
|
|
15502
|
+
"wrong-topic"
|
|
15503
|
+
];
|
|
15504
|
+
var YOUTUBE_PRIVACY_STATUSES = [
|
|
15505
|
+
"private",
|
|
15506
|
+
"unlisted",
|
|
15507
|
+
"public"
|
|
15508
|
+
];
|
|
15509
|
+
|
|
15510
|
+
// src/commands/clients/automationRuns.ts
|
|
15511
|
+
function stepLabel(run) {
|
|
15512
|
+
const step = run.currentStep;
|
|
15513
|
+
if (!step) return "";
|
|
15514
|
+
return step.name ?? step.actionType ?? step.kind ?? step.nodeId;
|
|
15515
|
+
}
|
|
15516
|
+
function printStoppedRuns(result) {
|
|
15517
|
+
process.stdout.write(
|
|
15518
|
+
`Stopped ${result.stopped.length} run(s)${result.alreadyFinished > 0 ? `; ${result.alreadyFinished} had already finished` : ""}.
|
|
15519
|
+
`
|
|
15520
|
+
);
|
|
15521
|
+
for (const run of result.stopped) {
|
|
15522
|
+
process.stdout.write(` - run ${run.runId} (workflow ${run.automationId})
|
|
15523
|
+
`);
|
|
15524
|
+
}
|
|
15525
|
+
}
|
|
15526
|
+
function registerClientAutomationRunsCommands(clients2) {
|
|
15527
|
+
const runs = clients2.command("automation-runs").description(
|
|
15528
|
+
"One customer's workflow runs in progress (the customer card \u2192 \u201EAutomatyzacje w toku\u201D): list them or take the customer out"
|
|
15529
|
+
);
|
|
15530
|
+
runs.command("list").description(
|
|
15531
|
+
"Live runs of one customer across every workflow, newest first (test runs excluded)"
|
|
15532
|
+
).argument("<clientId>", "workspace client id (wcid)").action(async (clientId, _, command) => {
|
|
15533
|
+
const flags = getGlobalFlags(command);
|
|
15534
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
15535
|
+
const result = await client.listClientAutomationRuns(clientId);
|
|
15536
|
+
emit(flags, result, () => {
|
|
15537
|
+
printTable(
|
|
15538
|
+
result.items.map((run) => ({
|
|
15539
|
+
runId: run.runId,
|
|
15540
|
+
workflow: run.automationName ?? `${run.automationId} (deleted)`,
|
|
15541
|
+
workflowId: run.automationId,
|
|
15542
|
+
status: run.status,
|
|
15543
|
+
step: stepLabel(run),
|
|
15544
|
+
nextStepAt: run.nextScheduledAt ?? "",
|
|
15545
|
+
startedAt: run.startedAt
|
|
15546
|
+
})),
|
|
15547
|
+
["runId", "workflow", "workflowId", "status", "step", "nextStepAt", "startedAt"]
|
|
15548
|
+
);
|
|
15549
|
+
});
|
|
15550
|
+
});
|
|
15551
|
+
runs.command("stop").description(
|
|
15552
|
+
"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"
|
|
15553
|
+
).argument("<clientId>", "workspace client id (wcid)").option("--confirm", "required \u2014 acknowledge that every live run of this customer ends").action(async (clientId, options, command) => {
|
|
15554
|
+
const flags = getGlobalFlags(command);
|
|
15555
|
+
requireConfirm(options.confirm, `stop every workflow run of client ${clientId}`);
|
|
15556
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
15557
|
+
const result = await client.stopClientAutomationRuns(clientId);
|
|
15558
|
+
emit(flags, result, () => printStoppedRuns(result));
|
|
15559
|
+
});
|
|
15560
|
+
}
|
|
14413
15561
|
|
|
14414
15562
|
// src/commands/clients/emails.ts
|
|
14415
15563
|
import { readFileSync as readFileSync6 } from "fs";
|
|
@@ -14591,6 +15739,42 @@ function registerClientEmailsCommands(clients2) {
|
|
|
14591
15739
|
});
|
|
14592
15740
|
}
|
|
14593
15741
|
);
|
|
15742
|
+
emails.command("delete-drafts").description(
|
|
15743
|
+
"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"
|
|
15744
|
+
).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(
|
|
15745
|
+
async (options, command) => {
|
|
15746
|
+
const flags = getGlobalFlags(command);
|
|
15747
|
+
const scope = draftsScope(options);
|
|
15748
|
+
if (!options.preview) requireConfirm(options.confirm, "delete client e-mail drafts");
|
|
15749
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
15750
|
+
const counted = await client.deleteClientEmailDrafts({ ...scope, preview: true });
|
|
15751
|
+
if (options.preview || counted.matchedCount === 0) {
|
|
15752
|
+
emit(flags, counted, () => printDraftsCount(counted, "match"));
|
|
15753
|
+
return;
|
|
15754
|
+
}
|
|
15755
|
+
const result = await client.deleteClientEmailDrafts({
|
|
15756
|
+
...scope,
|
|
15757
|
+
maxCount: counted.matchedCount
|
|
15758
|
+
});
|
|
15759
|
+
emit(flags, result, () => printDraftsCount(result, "deleted"));
|
|
15760
|
+
}
|
|
15761
|
+
);
|
|
15762
|
+
}
|
|
15763
|
+
function draftsScope(options) {
|
|
15764
|
+
const chosen = [options.ids !== void 0, options.subject !== void 0, options.all === true];
|
|
15765
|
+
if (chosen.filter(Boolean).length !== 1) {
|
|
15766
|
+
throw new Error("Pass exactly one of --ids, --subject or --all.");
|
|
15767
|
+
}
|
|
15768
|
+
if (options.ids !== void 0) return { scope: "ids", emailIds: options.ids };
|
|
15769
|
+
if (options.subject !== void 0) return { scope: "subject", subject: options.subject };
|
|
15770
|
+
return { scope: "all" };
|
|
15771
|
+
}
|
|
15772
|
+
function printDraftsCount(result, verb) {
|
|
15773
|
+
const count2 = verb === "deleted" ? result.deletedCount : result.matchedCount;
|
|
15774
|
+
process.stdout.write(
|
|
15775
|
+
`${count2} draft(s) ${verb === "deleted" ? "deleted" : "match (nothing deleted)"}${result.sampleSubjects.length > 0 ? `, e.g. "${result.sampleSubjects.join('", "')}"` : ""}.
|
|
15776
|
+
`
|
|
15777
|
+
);
|
|
14594
15778
|
}
|
|
14595
15779
|
|
|
14596
15780
|
// src/commands/clients/subscription.ts
|
|
@@ -15079,59 +16263,70 @@ function readImportCsv(csvFile) {
|
|
|
15079
16263
|
}
|
|
15080
16264
|
return contacts;
|
|
15081
16265
|
}
|
|
15082
|
-
function printMergePreview(
|
|
16266
|
+
function printMergePreview(preview2) {
|
|
15083
16267
|
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})
|
|
16268
|
+
process.stdout.write(` Survivor: ${preview2.survivorWcid} (${preview2.survivorEmail})
|
|
15087
16269
|
`);
|
|
15088
|
-
|
|
15089
|
-
process.stdout.write(` \u26A0\uFE0F BLOCKED \u2014 live subscription(s); the merge will be refused (409):
|
|
16270
|
+
process.stdout.write(` Duplicate (tombstoned): ${preview2.loserWcid} (${preview2.loserEmail})
|
|
15090
16271
|
`);
|
|
15091
|
-
|
|
16272
|
+
if (preview2.blocked !== null) {
|
|
16273
|
+
process.stdout.write(
|
|
16274
|
+
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:
|
|
16275
|
+
` : ` \u26A0\uFE0F BLOCKED \u2014 live subscription(s); the merge will be refused (409):
|
|
16276
|
+
`
|
|
16277
|
+
);
|
|
16278
|
+
for (const sub of preview2.blocked.subscriptions) {
|
|
15092
16279
|
process.stdout.write(
|
|
15093
16280
|
` - ${sub.side}: subscription ${sub.subscriptionId} on product ${sub.productId}
|
|
15094
16281
|
`
|
|
15095
16282
|
);
|
|
15096
16283
|
}
|
|
16284
|
+
if (preview2.blocked.reason === "subscription-vs-lifetime") {
|
|
16285
|
+
for (const grant of preview2.blocked.lifetimeGrants) {
|
|
16286
|
+
process.stdout.write(
|
|
16287
|
+
` - ${grant.side}: lifetime access to product ${grant.productId}
|
|
16288
|
+
`
|
|
16289
|
+
);
|
|
16290
|
+
}
|
|
16291
|
+
}
|
|
15097
16292
|
}
|
|
15098
16293
|
process.stdout.write(
|
|
15099
|
-
` Money: ${
|
|
16294
|
+
` Money: ${preview2.money.survivorTotalSpentMinor} + ${preview2.money.loserTotalSpentMinor} = ${preview2.money.sumMinor} (minor units); ${preview2.money.ordersMovingCount} order${preview2.money.ordersMovingCount === 1 ? "" : "s"} moving
|
|
15100
16295
|
`
|
|
15101
16296
|
);
|
|
15102
|
-
if (
|
|
15103
|
-
process.stdout.write(` Product access grants (${
|
|
16297
|
+
if (preview2.grants.length > 0) {
|
|
16298
|
+
process.stdout.write(` Product access grants (${preview2.grants.length}):
|
|
15104
16299
|
`);
|
|
15105
|
-
for (const grant of
|
|
16300
|
+
for (const grant of preview2.grants) {
|
|
15106
16301
|
process.stdout.write(
|
|
15107
16302
|
` - ${grant.productName} (${grant.productId}): ${grant.action} \u2014 ${grant.reason}
|
|
15108
16303
|
`
|
|
15109
16304
|
);
|
|
15110
16305
|
}
|
|
15111
16306
|
}
|
|
15112
|
-
if (
|
|
16307
|
+
if (preview2.profileOverwrites.length > 0) {
|
|
15113
16308
|
process.stdout.write(" Profile fields where the duplicate's value is DISCARDED:\n");
|
|
15114
|
-
for (const overwrite of
|
|
16309
|
+
for (const overwrite of preview2.profileOverwrites) {
|
|
15115
16310
|
process.stdout.write(
|
|
15116
16311
|
` - ${overwrite.field}: keeping "${overwrite.survivorValueKept ?? ""}", discarding "${overwrite.loserValueDiscarded ?? ""}"${overwrite.downstreamUnlink ? " (downstream unlink)" : ""}
|
|
15117
16312
|
`
|
|
15118
16313
|
);
|
|
15119
16314
|
}
|
|
15120
16315
|
}
|
|
15121
|
-
if (
|
|
16316
|
+
if (preview2.community.communitiesTouchedCount > 0) {
|
|
15122
16317
|
process.stdout.write(
|
|
15123
|
-
` Communities touched: ${
|
|
16318
|
+
` Communities touched: ${preview2.community.communitiesTouchedCount}; ~${preview2.community.authoredRePointCount} authored posts/comments re-pointed
|
|
15124
16319
|
`
|
|
15125
16320
|
);
|
|
15126
16321
|
}
|
|
15127
16322
|
process.stdout.write(
|
|
15128
|
-
` Resulting previous emails: ${
|
|
16323
|
+
` Resulting previous emails: ${preview2.resultingPreviousEmails.join(", ") || "(none)"}
|
|
15129
16324
|
`
|
|
15130
16325
|
);
|
|
15131
16326
|
process.stdout.write("\n This merge is IRREVERSIBLE once executed.\n");
|
|
15132
|
-
if (
|
|
16327
|
+
if (preview2.blocked === null) {
|
|
15133
16328
|
process.stdout.write(
|
|
15134
|
-
` To execute: clients merge --survivor ${
|
|
16329
|
+
` To execute: clients merge --survivor ${preview2.survivorWcid} --duplicate ${preview2.loserWcid} --preview-hash ${preview2.previewHash}
|
|
15135
16330
|
`
|
|
15136
16331
|
);
|
|
15137
16332
|
}
|
|
@@ -15598,7 +16793,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
15598
16793
|
emit(flags, result, () => printMergePreview(result.preview));
|
|
15599
16794
|
});
|
|
15600
16795
|
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).
|
|
16796
|
+
"\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
16797
|
).requiredOption("--survivor <clientId>", "the client that remains (wcid)").requiredOption("--duplicate <clientId>", "the client merged in and tombstoned (wcid)").requiredOption(
|
|
15603
16798
|
"--preview-hash <hash>",
|
|
15604
16799
|
"previewHash from a fresh `clients merge-preview` for the same pair"
|
|
@@ -15854,6 +17049,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
15854
17049
|
});
|
|
15855
17050
|
registerClientSubscriptionCommands(clients2);
|
|
15856
17051
|
registerClientEmailsCommands(clients2);
|
|
17052
|
+
registerClientAutomationRunsCommands(clients2);
|
|
15857
17053
|
}
|
|
15858
17054
|
|
|
15859
17055
|
// src/commands/communities/index.ts
|
|
@@ -15971,6 +17167,100 @@ var MODERATION_COLUMNS = [
|
|
|
15971
17167
|
"createdAt",
|
|
15972
17168
|
"status"
|
|
15973
17169
|
];
|
|
17170
|
+
function joinRequestRow(request) {
|
|
17171
|
+
return {
|
|
17172
|
+
id: request.id,
|
|
17173
|
+
name: [request.firstName, request.lastName].filter(Boolean).join(" "),
|
|
17174
|
+
email: request.email,
|
|
17175
|
+
status: request.status,
|
|
17176
|
+
requestedAt: request.requestedAt,
|
|
17177
|
+
answers: request.answers.length,
|
|
17178
|
+
aiHint: request.aiHint?.verdict ?? ""
|
|
17179
|
+
};
|
|
17180
|
+
}
|
|
17181
|
+
var JOIN_REQUEST_COLUMNS = [
|
|
17182
|
+
"id",
|
|
17183
|
+
"name",
|
|
17184
|
+
"email",
|
|
17185
|
+
"status",
|
|
17186
|
+
"requestedAt",
|
|
17187
|
+
"answers",
|
|
17188
|
+
"aiHint"
|
|
17189
|
+
];
|
|
17190
|
+
function assertIdCount(ids, max) {
|
|
17191
|
+
if (ids.length > max) {
|
|
17192
|
+
throw new Error(
|
|
17193
|
+
`Pass at most ${max} request ids per call (got ${ids.length}); run it again for the rest.`
|
|
17194
|
+
);
|
|
17195
|
+
}
|
|
17196
|
+
}
|
|
17197
|
+
function printJoinDecisions(result) {
|
|
17198
|
+
printTable(
|
|
17199
|
+
result.results.map((r) => ({
|
|
17200
|
+
requestId: r.requestId,
|
|
17201
|
+
outcome: r.outcome,
|
|
17202
|
+
message: r.message ?? ""
|
|
17203
|
+
})),
|
|
17204
|
+
["requestId", "outcome", "message"]
|
|
17205
|
+
);
|
|
17206
|
+
}
|
|
17207
|
+
var DIGEST_OUTCOME_LABELS = {
|
|
17208
|
+
approved: "Approved \u2014 the digest goes out Thursday 09:00.",
|
|
17209
|
+
approvedSendingNow: "Approved \u2014 the send starts now.",
|
|
17210
|
+
alreadyApproved: "Already approved \u2014 nothing changed.",
|
|
17211
|
+
skipped: "Skipped \u2014 no digest goes out this week.",
|
|
17212
|
+
alreadySkipped: "Already skipped \u2014 nothing changed."
|
|
17213
|
+
};
|
|
17214
|
+
function digestRow(draft) {
|
|
17215
|
+
return {
|
|
17216
|
+
week: draft.weekKey,
|
|
17217
|
+
status: draft.skipReason ? `${draft.status} (${draft.skipReason})` : draft.status,
|
|
17218
|
+
sendAt: draft.sendAt,
|
|
17219
|
+
sentAt: draft.sentAt ?? "",
|
|
17220
|
+
recipients: draft.recipientsCount ?? ""
|
|
17221
|
+
};
|
|
17222
|
+
}
|
|
17223
|
+
var DIGEST_COLUMNS = ["week", "status", "sendAt", "sentAt", "recipients"];
|
|
17224
|
+
function printDigestDraft(draft) {
|
|
17225
|
+
process.stdout.write(
|
|
17226
|
+
`Week ${draft.weekKey}: ${draft.status}${draft.skipReason ? ` (${draft.skipReason})` : ""}
|
|
17227
|
+
`
|
|
17228
|
+
);
|
|
17229
|
+
process.stdout.write(` Send at: ${draft.sendAt} (${draft.timezone})
|
|
17230
|
+
`);
|
|
17231
|
+
process.stdout.write(` Approve by: ${draft.approvalDeadline}
|
|
17232
|
+
`);
|
|
17233
|
+
const actions = [draft.canApprove ? "approve" : null, draft.canSkip ? "skip" : null].filter(
|
|
17234
|
+
Boolean
|
|
17235
|
+
);
|
|
17236
|
+
process.stdout.write(` Possible now: ${actions.length > 0 ? actions.join(", ") : "nothing"}
|
|
17237
|
+
`);
|
|
17238
|
+
if (draft.sentAt) {
|
|
17239
|
+
process.stdout.write(` Sent: ${draft.sentAt} to ${draft.recipientsCount ?? 0} member(s)
|
|
17240
|
+
`);
|
|
17241
|
+
}
|
|
17242
|
+
const { preview: preview2 } = draft;
|
|
17243
|
+
if (!preview2) return;
|
|
17244
|
+
const { stats } = preview2;
|
|
17245
|
+
process.stdout.write(
|
|
17246
|
+
` Preview (${preview2.communityName}): ${stats.postsCount} posts, ${stats.commentsCount} comments, ${stats.newMembersCount} new members
|
|
17247
|
+
`
|
|
17248
|
+
);
|
|
17249
|
+
for (const line of preview2.summaryLinesEn ?? preview2.summaryLinesPl ?? []) {
|
|
17250
|
+
process.stdout.write(` ${line.emoji} ${line.text}
|
|
17251
|
+
`);
|
|
17252
|
+
}
|
|
17253
|
+
for (const post of preview2.posts) {
|
|
17254
|
+
process.stdout.write(
|
|
17255
|
+
` - ${post.title ?? post.excerpt} (${post.channelName}, ${post.authorName})
|
|
17256
|
+
`
|
|
17257
|
+
);
|
|
17258
|
+
}
|
|
17259
|
+
for (const event of preview2.events) {
|
|
17260
|
+
process.stdout.write(` event: ${event.title} \u2014 ${event.dateFormatted}
|
|
17261
|
+
`);
|
|
17262
|
+
}
|
|
17263
|
+
}
|
|
15974
17264
|
function authorLabel(author) {
|
|
15975
17265
|
const name = [author.firstName, author.lastName].filter(Boolean).join(" ");
|
|
15976
17266
|
return author.nickname || name || author.id;
|
|
@@ -16716,7 +18006,7 @@ Next page: --cursor ${result.nextCursor}
|
|
|
16716
18006
|
});
|
|
16717
18007
|
});
|
|
16718
18008
|
settings.command("update").description(
|
|
16719
|
-
"Patch community settings \u2014 via toggle flags and/or a JSON patch. Allowed patch keys: uploadVideo, membersDisplay, viewStyle, displayName, icon, hiddenWhenUnavailable, voiceMessages, enableMemberApiAccess, publicAccess, publicAccessTermsUrl, contentVisibleFromJoinDate, eventsEnabled, eventAnnouncements, directMessagesEnabled, gamification, moderation, assistant, digest. Nested blocks in the JSON patch
|
|
18009
|
+
"Patch community settings \u2014 via toggle flags and/or a JSON patch. Allowed patch keys: uploadVideo, membersDisplay, viewStyle, displayName, icon, hiddenWhenUnavailable, voiceMessages, enableMemberApiAccess, publicAccess, publicAccessTermsUrl, contentVisibleFromJoinDate, eventsEnabled, eventAnnouncements, directMessagesEnabled, gamification (incl. xpRules: XP per activity, only the activities listed change, and xpTemplate: a preset for the whole table, with xpRules = the preset with those rows changed; send gamification.enabled with them), moderation, assistant, digest. Nested blocks in the JSON patch merge into the stored block field by field (lists such as joinQuestions are replaced whole); gamification.enabled and a moderation threshold can also be set with the --gamification / --auto-hide-after-reports flags."
|
|
16720
18010
|
).argument("<communityId>", "community id").option("--patch <fileOrJson>", "the settings patch as inline JSON or a path to a .json file").option("--events-enabled <on|off>", "show / hide the Calendar (events) section for members").option(
|
|
16721
18011
|
"--event-announcements <on|off>",
|
|
16722
18012
|
"bell + e-mail to members when a new event is published (default on)"
|
|
@@ -16752,8 +18042,11 @@ Next page: --cursor ${result.nextCursor}
|
|
|
16752
18042
|
const needsCurrent = options.gamification !== void 0 || options.autoHideAfterReports !== void 0;
|
|
16753
18043
|
const current = needsCurrent ? (await client.getCommunitySettings(communityId)).settings ?? {} : {};
|
|
16754
18044
|
if (options.gamification !== void 0) {
|
|
18045
|
+
const { xpRules, xpTemplate, courseXp, ...storedBlock } = current.gamification ?? {
|
|
18046
|
+
enabled: false
|
|
18047
|
+
};
|
|
16755
18048
|
patch.gamification = {
|
|
16756
|
-
...patch.gamification ??
|
|
18049
|
+
...patch.gamification ?? storedBlock,
|
|
16757
18050
|
enabled: parseOnOff(options.gamification, "--gamification")
|
|
16758
18051
|
};
|
|
16759
18052
|
}
|
|
@@ -17026,6 +18319,98 @@ Not going (${result.notGoingCount}):
|
|
|
17026
18319
|
}
|
|
17027
18320
|
});
|
|
17028
18321
|
});
|
|
18322
|
+
const joinRequests = communities2.command("join-requests").description(
|
|
18323
|
+
"People waiting to join a community that approves members by hand: list, approve, decline (workspace staff with member management; member keys get 403)"
|
|
18324
|
+
);
|
|
18325
|
+
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) => {
|
|
18326
|
+
const flags = getGlobalFlags(command);
|
|
18327
|
+
const result = await clientFor(flags).listCommunityJoinRequests(communityId);
|
|
18328
|
+
emit(flags, result, () => {
|
|
18329
|
+
process.stdout.write(`Pending: ${result.pendingCount}
|
|
18330
|
+
`);
|
|
18331
|
+
printTable(result.requests.map(joinRequestRow), JOIN_REQUEST_COLUMNS);
|
|
18332
|
+
for (const request of result.requests) {
|
|
18333
|
+
if (request.status !== "pending" || request.answers.length === 0) {
|
|
18334
|
+
continue;
|
|
18335
|
+
}
|
|
18336
|
+
process.stdout.write(`
|
|
18337
|
+
Answers of ${request.id}:
|
|
18338
|
+
`);
|
|
18339
|
+
for (const answer of request.answers) {
|
|
18340
|
+
process.stdout.write(` ${answer.question}
|
|
18341
|
+
${answer.answer}
|
|
18342
|
+
`);
|
|
18343
|
+
}
|
|
18344
|
+
}
|
|
18345
|
+
});
|
|
18346
|
+
});
|
|
18347
|
+
joinRequests.command("approve").description(
|
|
18348
|
+
`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`
|
|
18349
|
+
).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(
|
|
18350
|
+
async (communityId, requestIds, options, command) => {
|
|
18351
|
+
const flags = getGlobalFlags(command);
|
|
18352
|
+
assertIdCount(requestIds, COMMUNITY_JOIN_APPROVE_MAX_IDS);
|
|
18353
|
+
requireConfirm5(
|
|
18354
|
+
options.confirm,
|
|
18355
|
+
`approve ${requestIds.length} join request(s) (they join and get the welcome e-mail)`
|
|
18356
|
+
);
|
|
18357
|
+
const result = await clientFor(flags).approveCommunityJoinRequests(communityId, requestIds);
|
|
18358
|
+
emit(flags, result, () => printJoinDecisions(result));
|
|
18359
|
+
}
|
|
18360
|
+
);
|
|
18361
|
+
joinRequests.command("decline").description(
|
|
18362
|
+
`Decline requests quietly (a neutral e-mail, no reason given). 1-${COMMUNITY_JOIN_DECLINE_MAX_IDS} ids per call; repeats change nothing. Requires --confirm`
|
|
18363
|
+
).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(
|
|
18364
|
+
async (communityId, requestIds, options, command) => {
|
|
18365
|
+
const flags = getGlobalFlags(command);
|
|
18366
|
+
assertIdCount(requestIds, COMMUNITY_JOIN_DECLINE_MAX_IDS);
|
|
18367
|
+
requireConfirm5(options.confirm, `decline ${requestIds.length} join request(s)`);
|
|
18368
|
+
const result = await clientFor(flags).declineCommunityJoinRequests(communityId, requestIds);
|
|
18369
|
+
emit(flags, result, () => printJoinDecisions(result));
|
|
18370
|
+
}
|
|
18371
|
+
);
|
|
18372
|
+
const digest = communities2.command("digest").description(
|
|
18373
|
+
"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)"
|
|
18374
|
+
);
|
|
18375
|
+
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) => {
|
|
18376
|
+
const flags = getGlobalFlags(command);
|
|
18377
|
+
const result = await clientFor(flags).listCommunityDigestDrafts(communityId);
|
|
18378
|
+
emit(flags, result, () => {
|
|
18379
|
+
if (!result.digestEnabled) {
|
|
18380
|
+
process.stdout.write(
|
|
18381
|
+
"The weekly digest is OFF for this community \u2014 no drafts are built. Turn it on in the community settings.\n"
|
|
18382
|
+
);
|
|
18383
|
+
}
|
|
18384
|
+
if (result.current) {
|
|
18385
|
+
printDigestDraft(result.current);
|
|
18386
|
+
} else {
|
|
18387
|
+
process.stdout.write("No draft this week.\n");
|
|
18388
|
+
}
|
|
18389
|
+
if (result.history.length > 0) {
|
|
18390
|
+
process.stdout.write("\nEarlier weeks:\n");
|
|
18391
|
+
printTable(result.history.map(digestRow), DIGEST_COLUMNS);
|
|
18392
|
+
}
|
|
18393
|
+
});
|
|
18394
|
+
});
|
|
18395
|
+
const decideDigest = (decision, what) => async (communityId, options, command) => {
|
|
18396
|
+
const flags = getGlobalFlags(command);
|
|
18397
|
+
requireConfirm5(options.confirm, `${what} of community ${communityId}`);
|
|
18398
|
+
const result = await clientFor(flags).decideCommunityDigestDraft(communityId, {
|
|
18399
|
+
decision,
|
|
18400
|
+
...options.week !== void 0 ? { weekKey: options.week } : {}
|
|
18401
|
+
});
|
|
18402
|
+
emit(flags, result, () => {
|
|
18403
|
+
process.stdout.write(`${DIGEST_OUTCOME_LABELS[result.outcome]}
|
|
18404
|
+
`);
|
|
18405
|
+
printDigestDraft(result.draft);
|
|
18406
|
+
});
|
|
18407
|
+
};
|
|
18408
|
+
digest.command("approve").description(
|
|
18409
|
+
"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"
|
|
18410
|
+
).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)"));
|
|
18411
|
+
digest.command("skip").description(
|
|
18412
|
+
"Skip the week \u2014 no digest goes out (refused once the send has started). Owner / admin only. Requires --confirm"
|
|
18413
|
+
).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
18414
|
const moderation = communities2.command("moderation").description("Reported posts and comments \u2014 review the queue and resolve reports (staff)");
|
|
17030
18415
|
moderation.command("queue").description("Show pending reports plus the most recently resolved ones").argument("<communityId>", "community id").action(async (communityId, _, command) => {
|
|
17031
18416
|
const flags = getGlobalFlags(command);
|
|
@@ -17081,10 +18466,16 @@ var SETTINGS_PATCH_KEYS = [
|
|
|
17081
18466
|
"eventsEnabled",
|
|
17082
18467
|
"eventAnnouncements",
|
|
17083
18468
|
"directMessagesEnabled",
|
|
18469
|
+
"membersCanCreateGroups",
|
|
18470
|
+
"channelTypeIcons",
|
|
18471
|
+
"channelGroupsCollapsed",
|
|
17084
18472
|
"gamification",
|
|
17085
18473
|
"moderation",
|
|
17086
18474
|
"assistant",
|
|
17087
|
-
"digest"
|
|
18475
|
+
"digest",
|
|
18476
|
+
"welcomeMessage",
|
|
18477
|
+
"joinQuestions",
|
|
18478
|
+
"joinApproval"
|
|
17088
18479
|
];
|
|
17089
18480
|
function parseSettingsPatch(value) {
|
|
17090
18481
|
const parsed = parseJsonObject(value, "--patch");
|
|
@@ -17113,6 +18504,99 @@ function parseJsonObject(value, flag) {
|
|
|
17113
18504
|
return parsed;
|
|
17114
18505
|
}
|
|
17115
18506
|
|
|
18507
|
+
// src/commands/consultations/index.ts
|
|
18508
|
+
function assertBookingStatus(value) {
|
|
18509
|
+
const match = CONSULTATION_BOOKING_STATUSES.find((status) => status === value);
|
|
18510
|
+
if (match === void 0) {
|
|
18511
|
+
throw new Error(
|
|
18512
|
+
`Invalid --status: ${value}. Use one of: ${CONSULTATION_BOOKING_STATUSES.join(", ")}`
|
|
18513
|
+
);
|
|
18514
|
+
}
|
|
18515
|
+
return match;
|
|
18516
|
+
}
|
|
18517
|
+
function assertDate(value, flag) {
|
|
18518
|
+
if (Number.isNaN(Date.parse(value))) {
|
|
18519
|
+
throw new Error(`Invalid ${flag}: ${value} (expected an ISO date, e.g. 2026-11-01)`);
|
|
18520
|
+
}
|
|
18521
|
+
return value;
|
|
18522
|
+
}
|
|
18523
|
+
function printNextPage(nextCursor) {
|
|
18524
|
+
if (nextCursor) process.stdout.write(`
|
|
18525
|
+
Next page: --cursor ${nextCursor}
|
|
18526
|
+
`);
|
|
18527
|
+
}
|
|
18528
|
+
function registerConsultationsCommands(program2) {
|
|
18529
|
+
const consultations = program2.command("consultations").description(
|
|
18530
|
+
"Consultations booked in the Zanfia calendar: booked sessions and buyers who never booked"
|
|
18531
|
+
);
|
|
18532
|
+
consultations.command("bookings").description(
|
|
18533
|
+
"Booked sessions of every consultation (or --product) starting in a date range, earliest first, with the client name and e-mail"
|
|
18534
|
+
).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(
|
|
18535
|
+
"--limit <n>",
|
|
18536
|
+
"page size (default 50, max 100)",
|
|
18537
|
+
parseNumberOption("--limit", NumberFormat.Integer)
|
|
18538
|
+
).option("--cursor <cursor>", "cursor from a previous page").action(
|
|
18539
|
+
async (options, command) => {
|
|
18540
|
+
const flags = getGlobalFlags(command);
|
|
18541
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18542
|
+
const page = await client.listConsultationBookings({
|
|
18543
|
+
productId: options.product,
|
|
18544
|
+
from: options.from !== void 0 ? assertDate(options.from, "--from") : void 0,
|
|
18545
|
+
to: options.to !== void 0 ? assertDate(options.to, "--to") : void 0,
|
|
18546
|
+
status: options.status !== void 0 ? assertBookingStatus(options.status) : void 0,
|
|
18547
|
+
limit: options.limit,
|
|
18548
|
+
cursor: options.cursor
|
|
18549
|
+
});
|
|
18550
|
+
emit(flags, page, () => {
|
|
18551
|
+
process.stdout.write(`Sessions starting ${page.from} .. ${page.to}
|
|
18552
|
+
`);
|
|
18553
|
+
printTable(
|
|
18554
|
+
page.items.map((b) => ({
|
|
18555
|
+
bookingId: b.bookingId,
|
|
18556
|
+
product: b.productName ?? b.productId,
|
|
18557
|
+
start: b.startAt,
|
|
18558
|
+
minutes: b.durationMinutes,
|
|
18559
|
+
client: b.clientName ? `${b.clientName} <${b.clientEmail}>` : b.clientEmail,
|
|
18560
|
+
status: b.status,
|
|
18561
|
+
location: b.location
|
|
18562
|
+
})),
|
|
18563
|
+
["bookingId", "product", "start", "minutes", "client", "status", "location"]
|
|
18564
|
+
);
|
|
18565
|
+
printNextPage(page.nextCursor);
|
|
18566
|
+
});
|
|
18567
|
+
}
|
|
18568
|
+
);
|
|
18569
|
+
consultations.command("without-booking").description(
|
|
18570
|
+
"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"
|
|
18571
|
+
).option("--product <productId>", "only this consultation product").option(
|
|
18572
|
+
"--limit <n>",
|
|
18573
|
+
"page size (default 50, max 100)",
|
|
18574
|
+
parseNumberOption("--limit", NumberFormat.Integer)
|
|
18575
|
+
).option("--cursor <cursor>", "cursor from a previous page").action(
|
|
18576
|
+
async (options, command) => {
|
|
18577
|
+
const flags = getGlobalFlags(command);
|
|
18578
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18579
|
+
const page = await client.listConsultationClientsWithoutBooking({
|
|
18580
|
+
productId: options.product,
|
|
18581
|
+
limit: options.limit,
|
|
18582
|
+
cursor: options.cursor
|
|
18583
|
+
});
|
|
18584
|
+
emit(flags, page, () => {
|
|
18585
|
+
printTable(
|
|
18586
|
+
page.items.map((c) => ({
|
|
18587
|
+
clientId: c.clientId,
|
|
18588
|
+
client: c.clientName ? `${c.clientName} <${c.clientEmail}>` : c.clientEmail,
|
|
18589
|
+
product: c.productName,
|
|
18590
|
+
accessSince: c.accessGrantedAt ?? ""
|
|
18591
|
+
})),
|
|
18592
|
+
["clientId", "client", "product", "accessSince"]
|
|
18593
|
+
);
|
|
18594
|
+
printNextPage(page.nextCursor);
|
|
18595
|
+
});
|
|
18596
|
+
}
|
|
18597
|
+
);
|
|
18598
|
+
}
|
|
18599
|
+
|
|
17116
18600
|
// src/commands/conversations/index.ts
|
|
17117
18601
|
import { readFileSync as readFileSync9 } from "fs";
|
|
17118
18602
|
var MAX_BROADCAST_RECIPIENTS = 5e3;
|
|
@@ -17516,6 +19000,18 @@ function assertStatus(status) {
|
|
|
17516
19000
|
throw new Error(`Invalid status "${status}". Use one of: ${LESSON_STATUSES.join(", ")}`);
|
|
17517
19001
|
}
|
|
17518
19002
|
}
|
|
19003
|
+
function assertOneOf2(value, allowed, flag) {
|
|
19004
|
+
const match = allowed.find((candidate) => candidate === value);
|
|
19005
|
+
if (match === void 0) {
|
|
19006
|
+
throw new Error(`Invalid ${flag}: ${value}. Use one of: ${allowed.join(", ")}`);
|
|
19007
|
+
}
|
|
19008
|
+
return match;
|
|
19009
|
+
}
|
|
19010
|
+
var QUESTION_EXCERPT_LENGTH = 60;
|
|
19011
|
+
function excerpt(content) {
|
|
19012
|
+
const line = content.replace(/\s+/g, " ").trim();
|
|
19013
|
+
return line.length > QUESTION_EXCERPT_LENGTH ? `${line.slice(0, QUESTION_EXCERPT_LENGTH - 1)}\u2026` : line;
|
|
19014
|
+
}
|
|
17519
19015
|
function collectRepeatable3(value, previous) {
|
|
17520
19016
|
return [...previous, value];
|
|
17521
19017
|
}
|
|
@@ -18301,6 +19797,110 @@ ${NOT_SAVED_NOTE} --moments=${options.out ?? "<file>"} --confirm\`.
|
|
|
18301
19797
|
});
|
|
18302
19798
|
}
|
|
18303
19799
|
);
|
|
19800
|
+
const questions = courses.command("questions").description("Learners' questions under the lessons: read, reply as the team, hide, accept");
|
|
19801
|
+
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(
|
|
19802
|
+
async (productId, options, command) => {
|
|
19803
|
+
const flags = getGlobalFlags(command);
|
|
19804
|
+
const filter = options.filter !== void 0 ? assertOneOf2(options.filter, LESSON_QA_INBOX_FILTERS, "--filter") : void 0;
|
|
19805
|
+
const result = await clientFor2(flags).listCourseQuestions(productId, {
|
|
19806
|
+
filter,
|
|
19807
|
+
lessonId: options.lesson,
|
|
19808
|
+
cursor: options.cursor
|
|
19809
|
+
});
|
|
19810
|
+
emit(flags, result, () => {
|
|
19811
|
+
printTable(
|
|
19812
|
+
result.questions.map((q) => ({
|
|
19813
|
+
id: q.id,
|
|
19814
|
+
lesson: q.lessonId,
|
|
19815
|
+
by: q.author.displayName,
|
|
19816
|
+
visibility: q.visibility,
|
|
19817
|
+
status: q.status,
|
|
19818
|
+
replies: q.repliesCount,
|
|
19819
|
+
answered: q.staffReplied ? "yes" : "no",
|
|
19820
|
+
accepted: q.acceptedReplyId ?? "",
|
|
19821
|
+
question: excerpt(q.content)
|
|
19822
|
+
})),
|
|
19823
|
+
[
|
|
19824
|
+
"id",
|
|
19825
|
+
"lesson",
|
|
19826
|
+
"by",
|
|
19827
|
+
"visibility",
|
|
19828
|
+
"status",
|
|
19829
|
+
"replies",
|
|
19830
|
+
"answered",
|
|
19831
|
+
"accepted",
|
|
19832
|
+
"question"
|
|
19833
|
+
]
|
|
19834
|
+
);
|
|
19835
|
+
if (result.nextCursor) {
|
|
19836
|
+
process.stdout.write(`
|
|
19837
|
+
Next page: --cursor ${result.nextCursor}
|
|
19838
|
+
`);
|
|
19839
|
+
}
|
|
19840
|
+
});
|
|
19841
|
+
}
|
|
19842
|
+
);
|
|
19843
|
+
questions.command("reply").description(
|
|
19844
|
+
"Reply to a question as the course team \u2014 the learner sees it under the lesson and is notified. Requires --confirm"
|
|
19845
|
+
).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(
|
|
19846
|
+
async (productId, questionId, options, command) => {
|
|
19847
|
+
const flags = getGlobalFlags(command);
|
|
19848
|
+
if (options.content !== void 0 && options.contentFile !== void 0) {
|
|
19849
|
+
throw new Error("Use either --content or --content-file, not both.");
|
|
19850
|
+
}
|
|
19851
|
+
const content = options.contentFile !== void 0 ? readFileSync10(options.contentFile, "utf8") : options.content;
|
|
19852
|
+
if (content === void 0 || !content.trim()) {
|
|
19853
|
+
throw new Error("Pass the reply with --content or --content-file.");
|
|
19854
|
+
}
|
|
19855
|
+
requireConfirm(options.confirm, `reply to question ${questionId} (the learner sees it)`);
|
|
19856
|
+
const result = await clientFor2(flags).replyToCourseQuestion(productId, questionId, content);
|
|
19857
|
+
emit(flags, result, () => {
|
|
19858
|
+
process.stdout.write(`Replied to question ${questionId} (reply ${result.reply.id}).
|
|
19859
|
+
`);
|
|
19860
|
+
});
|
|
19861
|
+
}
|
|
19862
|
+
);
|
|
19863
|
+
questions.command("status").description(
|
|
19864
|
+
"Hide a question or a reply from learners (the team still sees it), or publish it again. Requires --confirm"
|
|
19865
|
+
).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(
|
|
19866
|
+
async (productId, entryId, options, command) => {
|
|
19867
|
+
const flags = getGlobalFlags(command);
|
|
19868
|
+
const status = assertOneOf2(
|
|
19869
|
+
options.status,
|
|
19870
|
+
SETTABLE_LESSON_QA_ENTRY_STATUSES,
|
|
19871
|
+
"--status"
|
|
19872
|
+
);
|
|
19873
|
+
requireConfirm(options.confirm, `set ${entryId} to ${status}`);
|
|
19874
|
+
const result = await clientFor2(flags).setCourseQuestionStatus(productId, entryId, status);
|
|
19875
|
+
emit(flags, result, () => {
|
|
19876
|
+
process.stdout.write(`${result.entry.id} is now ${result.entry.status}.
|
|
19877
|
+
`);
|
|
19878
|
+
});
|
|
19879
|
+
}
|
|
19880
|
+
);
|
|
19881
|
+
questions.command("accept").description(
|
|
19882
|
+
"Mark a reply as the answer to its question (--clear removes the mark). Requires --confirm"
|
|
19883
|
+
).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(
|
|
19884
|
+
async (productId, questionId, options, command) => {
|
|
19885
|
+
const flags = getGlobalFlags(command);
|
|
19886
|
+
if (options.reply === void 0 === (options.clear !== true)) {
|
|
19887
|
+
throw new Error("Pass exactly one of --reply <replyId> or --clear.");
|
|
19888
|
+
}
|
|
19889
|
+
const replyId = options.reply ?? null;
|
|
19890
|
+
requireConfirm(
|
|
19891
|
+
options.confirm,
|
|
19892
|
+
replyId ? `mark reply ${replyId} as the answer` : `clear the answer of question ${questionId}`
|
|
19893
|
+
);
|
|
19894
|
+
const result = await clientFor2(flags).acceptCourseAnswer(productId, questionId, replyId);
|
|
19895
|
+
emit(flags, result, () => {
|
|
19896
|
+
process.stdout.write(
|
|
19897
|
+
result.acceptedReplyId ? `Reply ${result.acceptedReplyId} is the answer to question ${result.questionId}.
|
|
19898
|
+
` : `Question ${result.questionId} has no accepted answer now.
|
|
19899
|
+
`
|
|
19900
|
+
);
|
|
19901
|
+
});
|
|
19902
|
+
}
|
|
19903
|
+
);
|
|
18304
19904
|
}
|
|
18305
19905
|
|
|
18306
19906
|
// src/commands/discounts/index.ts
|
|
@@ -18728,6 +20328,40 @@ function registerEmailSenderCommands(emails) {
|
|
|
18728
20328
|
);
|
|
18729
20329
|
});
|
|
18730
20330
|
});
|
|
20331
|
+
sender.command("warmups").description(
|
|
20332
|
+
"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"
|
|
20333
|
+
).action(async (_, command) => {
|
|
20334
|
+
const flags = getGlobalFlags(command);
|
|
20335
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
20336
|
+
const result = await client.getSenderDomainWarmups();
|
|
20337
|
+
emit(flags, result, () => printWarmups(result.warmups));
|
|
20338
|
+
});
|
|
20339
|
+
sender.command("declare-warmed").description(
|
|
20340
|
+
"\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"
|
|
20341
|
+
).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) => {
|
|
20342
|
+
const flags = getGlobalFlags(command);
|
|
20343
|
+
requireConfirm(options.confirm, `switch off the warm-up of ${domain}`);
|
|
20344
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
20345
|
+
const result = await client.declareSenderDomainWarmed(domain);
|
|
20346
|
+
emit(flags, result, () => {
|
|
20347
|
+
process.stdout.write(`Warm-up of ${result.warmup.domain} is off.
|
|
20348
|
+
`);
|
|
20349
|
+
printWarmups([result.warmup]);
|
|
20350
|
+
});
|
|
20351
|
+
});
|
|
20352
|
+
}
|
|
20353
|
+
function printWarmups(warmups) {
|
|
20354
|
+
printTable(
|
|
20355
|
+
warmups.map((warmup) => ({
|
|
20356
|
+
domain: warmup.domain,
|
|
20357
|
+
state: warmup.state,
|
|
20358
|
+
reason: warmup.warmedReason ?? "",
|
|
20359
|
+
day: warmup.warmupDay ?? "",
|
|
20360
|
+
capPerHour: warmup.hourlyCap ?? "",
|
|
20361
|
+
canDeclare: warmup.canDeclare ? "yes" : ""
|
|
20362
|
+
})),
|
|
20363
|
+
["domain", "state", "reason", "day", "capPerHour", "canDeclare"]
|
|
20364
|
+
);
|
|
18731
20365
|
}
|
|
18732
20366
|
|
|
18733
20367
|
// src/commands/emails/index.ts
|
|
@@ -19669,16 +21303,33 @@ function registerFormsCommands(program2) {
|
|
|
19669
21303
|
emit(flags, result, () => {
|
|
19670
21304
|
printTable(
|
|
19671
21305
|
result.submissions.map((s) => ({
|
|
21306
|
+
id: s.id,
|
|
19672
21307
|
email: s.email,
|
|
19673
21308
|
firstName: s.firstName ?? "",
|
|
19674
21309
|
clientId: s.clientId ?? "",
|
|
19675
21310
|
language: s.language ?? "",
|
|
19676
21311
|
createdAt: s.createdAt
|
|
19677
21312
|
})),
|
|
19678
|
-
["email", "firstName", "clientId", "language", "createdAt"]
|
|
21313
|
+
["id", "email", "firstName", "clientId", "language", "createdAt"]
|
|
19679
21314
|
);
|
|
19680
21315
|
});
|
|
19681
21316
|
});
|
|
21317
|
+
forms.command("room-link").description(
|
|
21318
|
+
"E-mail a webinar registrant their personal room link again (to THEIR address; one per sign-up per 10 minutes). Requires --confirm"
|
|
21319
|
+
).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(
|
|
21320
|
+
async (formId, submissionId, options, command) => {
|
|
21321
|
+
const flags = getGlobalFlags(command);
|
|
21322
|
+
requireConfirm(options.confirm, `e-mail the room link of submission ${submissionId}`);
|
|
21323
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
21324
|
+
const result = await client.resendFormSubmissionRoomLink(formId, submissionId);
|
|
21325
|
+
emit(
|
|
21326
|
+
flags,
|
|
21327
|
+
result,
|
|
21328
|
+
() => process.stdout.write(`Room link e-mailed again for submission ${submissionId}.
|
|
21329
|
+
`)
|
|
21330
|
+
);
|
|
21331
|
+
}
|
|
21332
|
+
);
|
|
19682
21333
|
}
|
|
19683
21334
|
|
|
19684
21335
|
// src/commands/funnels/index.ts
|
|
@@ -19701,6 +21352,7 @@ var FUNNEL_TEMPLATE_IDS = [
|
|
|
19701
21352
|
"challenge"
|
|
19702
21353
|
];
|
|
19703
21354
|
var EVENT_MODES = ["live", "on_demand"];
|
|
21355
|
+
var OFFER_PAGE_LAYOUTS = ["digital", "physical"];
|
|
19704
21356
|
var SCHEDULER_UNAVAILABLE_STATUS2 = 503;
|
|
19705
21357
|
var FUNNEL_NOT_READY_STATUS = 409;
|
|
19706
21358
|
function requireConfirm6(confirm, what) {
|
|
@@ -20020,6 +21672,37 @@ function registerFunnelsCommands(program2) {
|
|
|
20020
21672
|
emit(flags, result, () => {
|
|
20021
21673
|
process.stdout.write(
|
|
20022
21674
|
`Generation queued for step ${stepId} (run ${result.runId}). It takes 1-5 minutes; run "funnels get ${id}" to see the page once it lands.
|
|
21675
|
+
`
|
|
21676
|
+
);
|
|
21677
|
+
});
|
|
21678
|
+
}
|
|
21679
|
+
);
|
|
21680
|
+
funnels.command("add-offer-page").description(
|
|
21681
|
+
'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.'
|
|
21682
|
+
).argument("<id>", "funnel id").requiredOption(
|
|
21683
|
+
"--checkout <checkoutId>",
|
|
21684
|
+
"the PAID checkout the offer sells, with a slug (active or inactive; never a free one)"
|
|
21685
|
+
).requiredOption(
|
|
21686
|
+
"--layout <layout>",
|
|
21687
|
+
`${OFFER_PAGE_LAYOUTS.join(" | ")} (digital = ebook / mini course, physical = free + shipping)`
|
|
21688
|
+
).option(
|
|
21689
|
+
"--after <n|id>",
|
|
21690
|
+
"the signup step to follow: its 1-based position (`funnels get`) or stepId; default the first"
|
|
21691
|
+
).option("--lang <en|pl>", "language of the template copy (default en)").action(
|
|
21692
|
+
async (id, options, command) => {
|
|
21693
|
+
const flags = getGlobalFlags(command);
|
|
21694
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
21695
|
+
const afterStepId = options.after !== void 0 ? await resolveStepId(client, id, options.after) : void 0;
|
|
21696
|
+
const input = {
|
|
21697
|
+
checkoutId: options.checkout,
|
|
21698
|
+
layout: parseChoice3(options.layout, OFFER_PAGE_LAYOUTS, "--layout"),
|
|
21699
|
+
...afterStepId ? { afterStepId } : {},
|
|
21700
|
+
...options.lang !== void 0 ? { lang: parseChoice3(options.lang, FUNNEL_LANGS, "--lang") } : {}
|
|
21701
|
+
};
|
|
21702
|
+
const result = await client.addFunnelOfferPage(id, input);
|
|
21703
|
+
emit(flags, result, () => {
|
|
21704
|
+
process.stdout.write(
|
|
21705
|
+
`Added the DRAFT offer page /${result.slug} (page ${result.pageId}, step ${result.stepId}) after step ${result.afterStepId}. It names the checkout's product at its price (crossed-out only when the price has a compare-at amount), no sample claims: add what the buyer gets, then publish it.
|
|
20023
21706
|
`
|
|
20024
21707
|
);
|
|
20025
21708
|
});
|
|
@@ -20046,7 +21729,7 @@ function registerFunnelsCommands(program2) {
|
|
|
20046
21729
|
});
|
|
20047
21730
|
});
|
|
20048
21731
|
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."
|
|
21732
|
+
"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
21733
|
).argument("<templateId>", `template id (${FUNNEL_TEMPLATE_IDS.join(" | ")})`).option(
|
|
20051
21734
|
"--product <productId>",
|
|
20052
21735
|
"the product the funnel sells or delivers (required, except with --live-room)"
|
|
@@ -20058,7 +21741,7 @@ function registerFunnelsCommands(program2) {
|
|
|
20058
21741
|
"webinar (live): the meeting link (ClickMeeting, Zoom, Teams, Meet) the pages and reminders carry"
|
|
20059
21742
|
).option(
|
|
20060
21743
|
"--live-room <roomId>",
|
|
20061
|
-
"webinar: a webinar (`live-rooms list`) that plays a recording; its sessions are the event"
|
|
21744
|
+
"webinar: a webinar (`live-rooms list`) that plays a recording, or an external one (`live-rooms create`); its sessions are the event"
|
|
20062
21745
|
).option(
|
|
20063
21746
|
"--page-template <id>",
|
|
20064
21747
|
"design template for every generated page (a page-template id, e.g. course-fresh); thank-you steps take the companion"
|
|
@@ -21293,7 +22976,68 @@ to refresh it.
|
|
|
21293
22976
|
});
|
|
21294
22977
|
}
|
|
21295
22978
|
|
|
22979
|
+
// src/commands/liveRooms/streamKeyInput.ts
|
|
22980
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
22981
|
+
var streamKeyIo = {
|
|
22982
|
+
readStdin: () => readFileSync17(0, "utf8"),
|
|
22983
|
+
stdinIsTty: () => process.stdin.isTTY === true,
|
|
22984
|
+
promptHidden: (question) => promptHidden(question)
|
|
22985
|
+
};
|
|
22986
|
+
async function readStreamKey(options) {
|
|
22987
|
+
const { readStdin, stdinIsTty, promptHidden: ask } = streamKeyIo;
|
|
22988
|
+
let key;
|
|
22989
|
+
if (stdinIsTty()) {
|
|
22990
|
+
key = (await ask("Stream key (hidden): ")).trim();
|
|
22991
|
+
} else if (options.keyStdin) {
|
|
22992
|
+
key = readStdin().trim();
|
|
22993
|
+
} else {
|
|
22994
|
+
throw new Error(
|
|
22995
|
+
"No terminal to ask for the stream key: pipe it on stdin with --key-stdin (the key is never a command-line argument)."
|
|
22996
|
+
);
|
|
22997
|
+
}
|
|
22998
|
+
if (!key) throw new Error("No stream key was given.");
|
|
22999
|
+
return key;
|
|
23000
|
+
}
|
|
23001
|
+
var MASK = "\u2022\u2022\u2022\u2022";
|
|
23002
|
+
function maskedStreamKey(value) {
|
|
23003
|
+
return value.startsWith(MASK) && value.length <= MASK.length + 4 ? value : MASK;
|
|
23004
|
+
}
|
|
23005
|
+
|
|
21296
23006
|
// src/commands/liveRooms/index.ts
|
|
23007
|
+
var STUDIO_SCENE_LAYOUTS = ["solo", "presentation", "grid"];
|
|
23008
|
+
function parseSceneLayout(value) {
|
|
23009
|
+
const layout = STUDIO_SCENE_LAYOUTS.find((candidate) => candidate === value);
|
|
23010
|
+
if (!layout) throw new Error(`--layout must be one of ${STUDIO_SCENE_LAYOUTS.join(", ")}`);
|
|
23011
|
+
return layout;
|
|
23012
|
+
}
|
|
23013
|
+
function parseOutputPlatform(value) {
|
|
23014
|
+
const platform2 = STUDIO_OUTPUT_PLATFORMS.find((candidate) => candidate === value.toLowerCase());
|
|
23015
|
+
if (!platform2) {
|
|
23016
|
+
throw new Error(`--platform must be one of ${STUDIO_OUTPUT_PLATFORMS.join(", ")}`);
|
|
23017
|
+
}
|
|
23018
|
+
return platform2;
|
|
23019
|
+
}
|
|
23020
|
+
var safeOutput = (output) => ({
|
|
23021
|
+
...output,
|
|
23022
|
+
streamKey: maskedStreamKey(output.streamKey)
|
|
23023
|
+
});
|
|
23024
|
+
var outputRow = (output) => ({
|
|
23025
|
+
id: output.id,
|
|
23026
|
+
platform: output.platform,
|
|
23027
|
+
label: output.label ?? "",
|
|
23028
|
+
url: output.url,
|
|
23029
|
+
key: maskedStreamKey(output.streamKey),
|
|
23030
|
+
enabled: output.enabled ? "on" : "off",
|
|
23031
|
+
state: output.state,
|
|
23032
|
+
message: output.message ?? ""
|
|
23033
|
+
});
|
|
23034
|
+
var OUTPUT_COLUMNS = ["id", "platform", "label", "url", "key", "enabled", "state", "message"];
|
|
23035
|
+
var SENT_NOTE = '\n"sent" = the stream leaves for the platform, which does not say whether it took the key: check the live preview there.\n';
|
|
23036
|
+
function parseOnOff4(value) {
|
|
23037
|
+
if (value === "on") return true;
|
|
23038
|
+
if (value === "off") return false;
|
|
23039
|
+
throw new Error(`expected on or off, got "${value}"`);
|
|
23040
|
+
}
|
|
21297
23041
|
var minutes = (seconds) => `${Math.round(seconds / 60)} min`;
|
|
21298
23042
|
var percent = (part, whole) => whole > 0 ? `${Math.round(part / whole * 100)}%` : "-";
|
|
21299
23043
|
var countOrDash = (value) => value === null || value === void 0 ? "-" : String(value);
|
|
@@ -21319,29 +23063,256 @@ function describeSchedule(room) {
|
|
|
21319
23063
|
}
|
|
21320
23064
|
return `every ${room.schedule.everyMinutes} min${until}${exclusions}`;
|
|
21321
23065
|
}
|
|
23066
|
+
function isMeetingLink(value) {
|
|
23067
|
+
try {
|
|
23068
|
+
const url = new URL(value);
|
|
23069
|
+
return url.protocol === "https:" && url.hostname !== "";
|
|
23070
|
+
} catch {
|
|
23071
|
+
return false;
|
|
23072
|
+
}
|
|
23073
|
+
}
|
|
23074
|
+
function printRoom(result) {
|
|
23075
|
+
const { room } = result;
|
|
23076
|
+
printTable(
|
|
23077
|
+
[
|
|
23078
|
+
{
|
|
23079
|
+
id: room.id,
|
|
23080
|
+
title: room.title,
|
|
23081
|
+
startAt: room.startAt,
|
|
23082
|
+
endAt: room.endAt,
|
|
23083
|
+
link: room.externalUrl ?? "",
|
|
23084
|
+
replay: room.replay ? room.replay.name ?? room.replay.mediaId : ""
|
|
23085
|
+
}
|
|
23086
|
+
],
|
|
23087
|
+
["id", "title", "startAt", "endAt", "link", "replay"]
|
|
23088
|
+
);
|
|
23089
|
+
}
|
|
21322
23090
|
function registerLiveRoomsCommands(program2) {
|
|
21323
23091
|
const liveRooms = program2.command("live-rooms").description(
|
|
21324
|
-
"Webinars (incl. rolling autowebinar sessions) \u2014 list and
|
|
23092
|
+
"Webinars (incl. rolling autowebinar sessions) \u2014 list, attendance, and an external webinar (ClickMeeting, Zoom, Teams, Meet) with its recording; the rest in the dashboard"
|
|
23093
|
+
);
|
|
23094
|
+
liveRooms.command("list").description("List webinars (latest start first)").action(async (_, command) => {
|
|
23095
|
+
const flags = getGlobalFlags(command);
|
|
23096
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23097
|
+
const result = await client.listLiveRooms();
|
|
23098
|
+
emit(flags, result, () => {
|
|
23099
|
+
printTable(
|
|
23100
|
+
result.rooms.map((room) => ({
|
|
23101
|
+
id: room.id,
|
|
23102
|
+
title: room.title,
|
|
23103
|
+
source: room.source,
|
|
23104
|
+
status: room.endedAt ? "ended" : room.status,
|
|
23105
|
+
startAt: room.startAt,
|
|
23106
|
+
schedule: describeSchedule(room),
|
|
23107
|
+
access: room.access.everyone ? "everyone" : "restricted"
|
|
23108
|
+
})),
|
|
23109
|
+
["id", "title", "source", "status", "startAt", "schedule", "access"]
|
|
23110
|
+
);
|
|
23111
|
+
});
|
|
23112
|
+
});
|
|
23113
|
+
liveRooms.command("create").description(
|
|
23114
|
+
"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>"
|
|
23115
|
+
).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(
|
|
23116
|
+
async (options, command) => {
|
|
23117
|
+
const flags = getGlobalFlags(command);
|
|
23118
|
+
const durationMinutes = Number(options.duration);
|
|
23119
|
+
if (!Number.isInteger(durationMinutes) || durationMinutes < 5 || durationMinutes > 720) {
|
|
23120
|
+
throw new Error("--duration must be a whole number of minutes from 5 to 720");
|
|
23121
|
+
}
|
|
23122
|
+
if (Number.isNaN(Date.parse(options.start))) {
|
|
23123
|
+
throw new Error("--start must be an ISO 8601 date-time");
|
|
23124
|
+
}
|
|
23125
|
+
if (!isMeetingLink(options.joinUrl)) {
|
|
23126
|
+
throw new Error("--join-url must be an https link");
|
|
23127
|
+
}
|
|
23128
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23129
|
+
const result = await client.createExternalWebinar({
|
|
23130
|
+
title: options.title,
|
|
23131
|
+
...options.description !== void 0 ? { description: options.description } : {},
|
|
23132
|
+
startAt: options.start,
|
|
23133
|
+
durationMinutes,
|
|
23134
|
+
timezone: options.timezone,
|
|
23135
|
+
url: options.joinUrl
|
|
23136
|
+
});
|
|
23137
|
+
emit(flags, result, () => {
|
|
23138
|
+
printRoom(result);
|
|
23139
|
+
process.stdout.write(
|
|
23140
|
+
`
|
|
23141
|
+
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).
|
|
23142
|
+
`
|
|
23143
|
+
);
|
|
23144
|
+
});
|
|
23145
|
+
}
|
|
23146
|
+
);
|
|
23147
|
+
liveRooms.command("replay").description(
|
|
23148
|
+
"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"
|
|
23149
|
+
).argument("<roomId>", "webinar id (from live-rooms list)").option("--media <mediaId>", "the recording (Media Library video id)").option("--remove", "take the recording off").action(
|
|
23150
|
+
async (roomId, options, command) => {
|
|
23151
|
+
const flags = getGlobalFlags(command);
|
|
23152
|
+
if (Boolean(options.media) === Boolean(options.remove)) {
|
|
23153
|
+
throw new Error("pass exactly one of --media <mediaId> or --remove");
|
|
23154
|
+
}
|
|
23155
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23156
|
+
const result = await client.setLiveRoomReplay(roomId, options.media ?? null);
|
|
23157
|
+
emit(flags, result, () => printRoom(result));
|
|
23158
|
+
}
|
|
21325
23159
|
);
|
|
21326
|
-
liveRooms.command("
|
|
23160
|
+
liveRooms.command("scene").description(
|
|
23161
|
+
"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"
|
|
23162
|
+
).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(
|
|
23163
|
+
async (roomId, options, command) => {
|
|
23164
|
+
const flags = getGlobalFlags(command);
|
|
23165
|
+
const input = {
|
|
23166
|
+
...options.layout !== void 0 ? { layout: parseSceneLayout(options.layout) } : {},
|
|
23167
|
+
...options.banner === false ? { banner: null } : options.banner !== void 0 ? { banner: options.banner } : {},
|
|
23168
|
+
...options.lowerThird === false ? { lowerThird: null } : options.name !== void 0 ? { lowerThird: { name: options.name, title: options.title ?? null } } : {},
|
|
23169
|
+
...options.offer !== void 0 ? { offerOnScreen: parseOnOff4(options.offer) } : {},
|
|
23170
|
+
...options.comment !== void 0 ? { commentOnScreen: parseOnOff4(options.comment) } : {},
|
|
23171
|
+
...options.logo !== void 0 ? { showLogo: parseOnOff4(options.logo) } : {}
|
|
23172
|
+
};
|
|
23173
|
+
if (Object.keys(input).length === 0) {
|
|
23174
|
+
throw new Error("pass at least one option (e.g. --layout presentation)");
|
|
23175
|
+
}
|
|
23176
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23177
|
+
const result = await client.setLiveRoomStudioScene(roomId, input);
|
|
23178
|
+
emit(flags, result, () => {
|
|
23179
|
+
printTable(
|
|
23180
|
+
[
|
|
23181
|
+
{
|
|
23182
|
+
layout: result.scene.layout,
|
|
23183
|
+
banner: result.scene.banner ?? "",
|
|
23184
|
+
lowerThird: result.scene.lowerThird?.name ?? "",
|
|
23185
|
+
offer: result.scene.offerOnScreen ? "on" : "off",
|
|
23186
|
+
comment: result.scene.commentOnScreen ? "on" : "off",
|
|
23187
|
+
onAir: result.appliedToRoom ? "yes" : "next go-live"
|
|
23188
|
+
}
|
|
23189
|
+
],
|
|
23190
|
+
["layout", "banner", "lowerThird", "offer", "comment", "onAir"]
|
|
23191
|
+
);
|
|
23192
|
+
});
|
|
23193
|
+
}
|
|
23194
|
+
);
|
|
23195
|
+
const guestRow = (guest) => ({
|
|
23196
|
+
id: guest.id,
|
|
23197
|
+
name: guest.name,
|
|
23198
|
+
state: guest.state,
|
|
23199
|
+
inStudio: guest.connected ? "yes" : "no",
|
|
23200
|
+
muted: guest.mutedForStream ? "yes" : "no",
|
|
23201
|
+
expiresAt: guest.expiresAt
|
|
23202
|
+
});
|
|
23203
|
+
const GUEST_COLUMNS = ["id", "name", "state", "inStudio", "muted", "expiresAt"];
|
|
23204
|
+
liveRooms.command("guest-invite").description(
|
|
23205
|
+
"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"
|
|
23206
|
+
).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) => {
|
|
21327
23207
|
const flags = getGlobalFlags(command);
|
|
21328
23208
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
21329
|
-
const result = await client.
|
|
23209
|
+
const result = await client.createLiveRoomStudioGuestInvite(roomId, {
|
|
23210
|
+
name: options.name,
|
|
23211
|
+
email: options.email ?? null
|
|
23212
|
+
});
|
|
21330
23213
|
emit(flags, result, () => {
|
|
21331
23214
|
printTable(
|
|
21332
|
-
result.
|
|
21333
|
-
|
|
21334
|
-
|
|
21335
|
-
|
|
21336
|
-
|
|
21337
|
-
|
|
21338
|
-
|
|
21339
|
-
|
|
21340
|
-
|
|
21341
|
-
|
|
23215
|
+
[{ ...guestRow(result.guest), inviteUrl: result.inviteUrl }],
|
|
23216
|
+
[...GUEST_COLUMNS, "inviteUrl"]
|
|
23217
|
+
);
|
|
23218
|
+
});
|
|
23219
|
+
});
|
|
23220
|
+
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) => {
|
|
23221
|
+
const flags = getGlobalFlags(command);
|
|
23222
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23223
|
+
const result = await client.listLiveRoomStudioGuests(roomId);
|
|
23224
|
+
emit(flags, result, () => {
|
|
23225
|
+
printTable(result.guests.map(guestRow), GUEST_COLUMNS);
|
|
23226
|
+
});
|
|
23227
|
+
});
|
|
23228
|
+
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(
|
|
23229
|
+
async (roomId, guestId, _options, command) => {
|
|
23230
|
+
const flags = getGlobalFlags(command);
|
|
23231
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23232
|
+
const result = await client.revokeLiveRoomStudioGuestInvite(roomId, guestId);
|
|
23233
|
+
emit(flags, result, () => {
|
|
23234
|
+
printTable([guestRow(result.guest)], GUEST_COLUMNS);
|
|
23235
|
+
});
|
|
23236
|
+
}
|
|
23237
|
+
);
|
|
23238
|
+
liveRooms.command("outputs").description(
|
|
23239
|
+
"A studio webinar's extra streams (Facebook, LinkedIn, Twitch, Kick, other RTMP): state and errors; the stream key shows masked"
|
|
23240
|
+
).argument("<roomId>", "studio webinar id (from live-rooms list)").action(async (roomId, _options, command) => {
|
|
23241
|
+
const flags = getGlobalFlags(command);
|
|
23242
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23243
|
+
const result = await client.listLiveRoomStudioOutputs(roomId);
|
|
23244
|
+
const safe = { ...result, outputs: result.outputs.map(safeOutput) };
|
|
23245
|
+
emit(flags, safe, () => {
|
|
23246
|
+
printTable(safe.outputs.map(outputRow), OUTPUT_COLUMNS);
|
|
23247
|
+
process.stdout.write(
|
|
23248
|
+
`
|
|
23249
|
+
${safe.outputs.length} of ${safe.maxOutputs} outputs.` + (safe.simulcastAvailable ? "" : " This webinar streams to YouTube, which does not run through Cloudflare here: extra streams need the Zanfia player as the destination.") + "\n"
|
|
21342
23250
|
);
|
|
23251
|
+
if (safe.outputs.some((output) => output.state === "sent")) {
|
|
23252
|
+
process.stdout.write(SENT_NOTE);
|
|
23253
|
+
}
|
|
21343
23254
|
});
|
|
21344
23255
|
});
|
|
23256
|
+
liveRooms.command("output-add").description(
|
|
23257
|
+
"Add an extra stream to a studio webinar (early access). The stream key is asked for hidden, or read from stdin with --key-stdin; it is never an argument and never shown again. Refused (409) on a YouTube webinar that cannot run through Cloudflare"
|
|
23258
|
+
).argument("<roomId>", "studio webinar id (from live-rooms list)").requiredOption("--platform <platform>", STUDIO_OUTPUT_PLATFORMS.join(" | ")).option(
|
|
23259
|
+
"--url <url>",
|
|
23260
|
+
"rtmp:// or rtmps:// ingest address (prefilled for facebook, twitch, kick; required otherwise)"
|
|
23261
|
+
).option("--label <label>", 'your own name for it (e.g. "Company page")').option("--off", "add it switched off").option("--key-stdin", "read the stream key from stdin (e.g. from a password manager)").action(
|
|
23262
|
+
async (roomId, options, command) => {
|
|
23263
|
+
const flags = getGlobalFlags(command);
|
|
23264
|
+
const platform2 = parseOutputPlatform(options.platform);
|
|
23265
|
+
const streamKey = await readStreamKey({ keyStdin: options.keyStdin });
|
|
23266
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23267
|
+
const result = await client.createLiveRoomStudioOutput(roomId, {
|
|
23268
|
+
platform: platform2,
|
|
23269
|
+
...options.url !== void 0 ? { url: options.url } : {},
|
|
23270
|
+
...options.label !== void 0 ? { label: options.label } : {},
|
|
23271
|
+
...options.off ? { enabled: false } : {},
|
|
23272
|
+
streamKey
|
|
23273
|
+
});
|
|
23274
|
+
const output = safeOutput(result.output);
|
|
23275
|
+
emit(flags, { output }, () => printTable([outputRow(output)], OUTPUT_COLUMNS));
|
|
23276
|
+
}
|
|
23277
|
+
);
|
|
23278
|
+
liveRooms.command("output-update").description(
|
|
23279
|
+
"Change an extra stream: address, name, on / off, or a NEW stream key (replace only: the stored key is never read back). Only the options you pass change"
|
|
23280
|
+
).argument("<roomId>", "studio webinar id (from live-rooms list)").argument("<outputId>", "output id (from live-rooms outputs)").option("--url <url>", "new rtmp:// or rtmps:// ingest address").option("--label <label>", "your own name for it").option("--no-label", "remove your name (the platform's name shows)").option("--enabled <on|off>", "switch it on or off (works while on air)").option("--new-key", "replace the stream key: asked for hidden").option("--key-stdin", "replace the stream key with the one read from stdin").action(
|
|
23281
|
+
async (roomId, outputId, options, command) => {
|
|
23282
|
+
const flags = getGlobalFlags(command);
|
|
23283
|
+
const input = {
|
|
23284
|
+
...options.url !== void 0 ? { url: options.url } : {},
|
|
23285
|
+
...options.label === false ? { label: null } : options.label !== void 0 ? { label: options.label } : {},
|
|
23286
|
+
...options.enabled !== void 0 ? { enabled: parseOnOff4(options.enabled) } : {}
|
|
23287
|
+
};
|
|
23288
|
+
if (options.newKey || options.keyStdin) {
|
|
23289
|
+
input.streamKey = await readStreamKey({ keyStdin: options.keyStdin });
|
|
23290
|
+
}
|
|
23291
|
+
if (Object.keys(input).length === 0) {
|
|
23292
|
+
throw new Error("pass at least one option (e.g. --enabled off or --new-key)");
|
|
23293
|
+
}
|
|
23294
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23295
|
+
const result = await client.updateLiveRoomStudioOutput(roomId, outputId, input);
|
|
23296
|
+
const output = safeOutput(result.output);
|
|
23297
|
+
emit(flags, { output }, () => printTable([outputRow(output)], OUTPUT_COLUMNS));
|
|
23298
|
+
}
|
|
23299
|
+
);
|
|
23300
|
+
liveRooms.command("output-remove").description(
|
|
23301
|
+
"Remove an extra stream and delete its stored stream key (stops it at once when on air). Requires --confirm"
|
|
23302
|
+
).argument("<roomId>", "studio webinar id (from live-rooms list)").argument("<outputId>", "output id (from live-rooms outputs)").option("--confirm", "confirm the removal").action(
|
|
23303
|
+
async (roomId, outputId, options, command) => {
|
|
23304
|
+
if (!options.confirm) {
|
|
23305
|
+
throw new Error("Refusing to remove the output without --confirm.");
|
|
23306
|
+
}
|
|
23307
|
+
const flags = getGlobalFlags(command);
|
|
23308
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23309
|
+
const result = await client.deleteLiveRoomStudioOutput(roomId, outputId);
|
|
23310
|
+
emit(flags, result, () => {
|
|
23311
|
+
process.stdout.write(`Removed output ${outputId} and its stream key.
|
|
23312
|
+
`);
|
|
23313
|
+
});
|
|
23314
|
+
}
|
|
23315
|
+
);
|
|
21345
23316
|
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
23317
|
const flags = getGlobalFlags(command);
|
|
21347
23318
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -21404,7 +23375,7 @@ Viewers of ${options.session}:
|
|
|
21404
23375
|
}
|
|
21405
23376
|
|
|
21406
23377
|
// src/commands/media/index.ts
|
|
21407
|
-
import { existsSync, readFileSync as
|
|
23378
|
+
import { existsSync, readFileSync as readFileSync18, writeFileSync as writeFileSync4 } from "fs";
|
|
21408
23379
|
import { readFile as readFile3, stat as stat5 } from "fs/promises";
|
|
21409
23380
|
import { basename } from "path";
|
|
21410
23381
|
|
|
@@ -24576,7 +26547,7 @@ function requireConfirm10(confirm, what) {
|
|
|
24576
26547
|
}
|
|
24577
26548
|
}
|
|
24578
26549
|
function resolveTextOrFile(value) {
|
|
24579
|
-
const text = existsSync(value) ?
|
|
26550
|
+
const text = existsSync(value) ? readFileSync18(value, "utf8") : value;
|
|
24580
26551
|
if (!text.trim()) throw new Error("The text (or the file it points to) is empty.");
|
|
24581
26552
|
return text.trim();
|
|
24582
26553
|
}
|
|
@@ -24841,7 +26812,7 @@ function registerMediaCommands(program2) {
|
|
|
24841
26812
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
24842
26813
|
const result = await client.uploadMediaCaption(mediaId, {
|
|
24843
26814
|
srclang: options.lang,
|
|
24844
|
-
content:
|
|
26815
|
+
content: readFileSync18(options.file, "utf8"),
|
|
24845
26816
|
...options.label ? { label: options.label } : {}
|
|
24846
26817
|
});
|
|
24847
26818
|
emit(flags, result, () => {
|
|
@@ -25055,13 +27026,13 @@ function registerMediaCommands(program2) {
|
|
|
25055
27026
|
}
|
|
25056
27027
|
|
|
25057
27028
|
// src/commands/newsletters/index.ts
|
|
25058
|
-
import { existsSync as existsSync2, readFileSync as
|
|
27029
|
+
import { existsSync as existsSync2, readFileSync as readFileSync19 } from "fs";
|
|
25059
27030
|
var ISSUE_STATUSES = ["public", "hidden"];
|
|
25060
27031
|
var ISSUE_VISIBILITIES = ["public", "hidden", "locked"];
|
|
25061
27032
|
var ISSUE_RENDER_MODES = ["snapshot", "article"];
|
|
25062
27033
|
var THUMBNAIL_NONE = "none";
|
|
25063
27034
|
function resolveMarkdownOrFile(value) {
|
|
25064
|
-
return existsSync2(value) ?
|
|
27035
|
+
return existsSync2(value) ? readFileSync19(value, "utf8") : value;
|
|
25065
27036
|
}
|
|
25066
27037
|
function parseIdList2(value, flag) {
|
|
25067
27038
|
const ids = value.split(",").map((id) => id.trim()).filter(Boolean);
|
|
@@ -25075,7 +27046,7 @@ var ACCESS_STRATEGIES = [
|
|
|
25075
27046
|
];
|
|
25076
27047
|
var DOUBLE_OPT_INS = ["none", "external"];
|
|
25077
27048
|
var DELIVERIES = ["zanfia", "integration"];
|
|
25078
|
-
function
|
|
27049
|
+
function assertOneOf3(value, allowed, flag) {
|
|
25079
27050
|
if (!allowed.includes(value)) {
|
|
25080
27051
|
throw new Error(`Invalid ${flag}: ${value}. Use one of: ${allowed.join(", ")}`);
|
|
25081
27052
|
}
|
|
@@ -25103,7 +27074,7 @@ function parseBool2(value, flag) {
|
|
|
25103
27074
|
}
|
|
25104
27075
|
function parseJsonFileOrInline5(value, flag) {
|
|
25105
27076
|
const trimmed = value.trim();
|
|
25106
|
-
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed :
|
|
27077
|
+
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed : readFileSync19(value, "utf8");
|
|
25107
27078
|
try {
|
|
25108
27079
|
return JSON.parse(raw);
|
|
25109
27080
|
} catch (error) {
|
|
@@ -25171,17 +27142,17 @@ Next: \`newsletters issues draft save ${productId} ${issue.id} --content <markdo
|
|
|
25171
27142
|
const flags = getGlobalFlags(command);
|
|
25172
27143
|
const input = {};
|
|
25173
27144
|
if (options.status !== void 0) {
|
|
25174
|
-
input.status =
|
|
27145
|
+
input.status = assertOneOf3(options.status, ISSUE_STATUSES, "--status");
|
|
25175
27146
|
}
|
|
25176
27147
|
if (options.landing !== void 0) {
|
|
25177
|
-
input.landingPageVisibility =
|
|
27148
|
+
input.landingPageVisibility = assertOneOf3(
|
|
25178
27149
|
options.landing,
|
|
25179
27150
|
ISSUE_VISIBILITIES,
|
|
25180
27151
|
"--landing"
|
|
25181
27152
|
);
|
|
25182
27153
|
}
|
|
25183
27154
|
if (options.renderMode !== void 0) {
|
|
25184
|
-
input.renderMode =
|
|
27155
|
+
input.renderMode = assertOneOf3(options.renderMode, ISSUE_RENDER_MODES, "--render-mode");
|
|
25185
27156
|
}
|
|
25186
27157
|
if (options.thumbnail !== void 0) {
|
|
25187
27158
|
input.thumbnailUrl = options.thumbnail === THUMBNAIL_NONE ? null : options.thumbnail;
|
|
@@ -25460,7 +27431,7 @@ Next: \`newsletters issues draft save ${productId} ${issue.id} --content <markdo
|
|
|
25460
27431
|
archive.enabled = parseBool2(options.archiveEnabled, "--archive-enabled");
|
|
25461
27432
|
}
|
|
25462
27433
|
if (options.archiveAccess !== void 0) {
|
|
25463
|
-
archive.accessStrategy =
|
|
27434
|
+
archive.accessStrategy = assertOneOf3(
|
|
25464
27435
|
options.archiveAccess,
|
|
25465
27436
|
ACCESS_STRATEGIES,
|
|
25466
27437
|
"--archive-access"
|
|
@@ -25470,14 +27441,14 @@ Next: \`newsletters issues draft save ${productId} ${issue.id} --content <markdo
|
|
|
25470
27441
|
archive.publicationDelay = options.publicationDelay === "none" ? null : parseNonNegativeInt2(options.publicationDelay, "--publication-delay");
|
|
25471
27442
|
}
|
|
25472
27443
|
if (options.defaultStatus !== void 0) {
|
|
25473
|
-
archive.defaultEmailStatus =
|
|
27444
|
+
archive.defaultEmailStatus = assertOneOf3(
|
|
25474
27445
|
options.defaultStatus,
|
|
25475
27446
|
ISSUE_STATUSES,
|
|
25476
27447
|
"--default-status"
|
|
25477
27448
|
);
|
|
25478
27449
|
}
|
|
25479
27450
|
if (options.defaultVisibility !== void 0) {
|
|
25480
|
-
archive.defaultEmailVisibility =
|
|
27451
|
+
archive.defaultEmailVisibility = assertOneOf3(
|
|
25481
27452
|
options.defaultVisibility,
|
|
25482
27453
|
ISSUE_VISIBILITIES,
|
|
25483
27454
|
"--default-visibility"
|
|
@@ -25495,11 +27466,11 @@ Next: \`newsletters issues draft save ${productId} ${issue.id} --content <markdo
|
|
|
25495
27466
|
input.archiveInbox = inbox;
|
|
25496
27467
|
}
|
|
25497
27468
|
if (options.doubleOptIn !== void 0) {
|
|
25498
|
-
input.doubleOptIn =
|
|
27469
|
+
input.doubleOptIn = assertOneOf3(options.doubleOptIn, DOUBLE_OPT_INS, "--double-opt-in");
|
|
25499
27470
|
}
|
|
25500
27471
|
if (options.clientTag.length > 0) input.clientTags = options.clientTag;
|
|
25501
27472
|
if (options.delivery !== void 0) {
|
|
25502
|
-
input.delivery =
|
|
27473
|
+
input.delivery = assertOneOf3(options.delivery, DELIVERIES, "--delivery");
|
|
25503
27474
|
}
|
|
25504
27475
|
if (options.clearSender) {
|
|
25505
27476
|
input.sender = null;
|
|
@@ -25525,7 +27496,7 @@ Next: \`newsletters issues draft save ${productId} ${issue.id} --content <markdo
|
|
|
25525
27496
|
}
|
|
25526
27497
|
|
|
25527
27498
|
// src/commands/offers/index.ts
|
|
25528
|
-
import { readFileSync as
|
|
27499
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
25529
27500
|
var PRICE_BILLING_TYPES = ["one_time", "recurring", "installments"];
|
|
25530
27501
|
var PRICE_INTERVALS = ["day", "week", "month", "year"];
|
|
25531
27502
|
var SALE_START_DISPLAYS = ["hidden", "countdown"];
|
|
@@ -25562,7 +27533,7 @@ function parseBool3(value, flag) {
|
|
|
25562
27533
|
}
|
|
25563
27534
|
function parseJsonFileOrInline6(value, flag) {
|
|
25564
27535
|
const trimmed = value.trim();
|
|
25565
|
-
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed :
|
|
27536
|
+
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed : readFileSync20(value, "utf8");
|
|
25566
27537
|
try {
|
|
25567
27538
|
return JSON.parse(raw);
|
|
25568
27539
|
} catch (error) {
|
|
@@ -26073,7 +28044,7 @@ Media gallery (${gallery.length}):
|
|
|
26073
28044
|
"suppress subscription-cancelled emails"
|
|
26074
28045
|
).option("--disable-end-subscription-emails <on|off>", "suppress subscription-ended emails").option(
|
|
26075
28046
|
"--disable-access-ending-reminders <on|off>",
|
|
26076
|
-
"suppress access-
|
|
28047
|
+
"suppress the client's access-end emails: reminders before the end and the e-mail after it (cancel-at-end offers too)"
|
|
26077
28048
|
).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
28049
|
"--tracking-integration-ids <ids|none>",
|
|
26079
28050
|
"comma-separated workspace conversion-tracking integration ids (none = clear)"
|
|
@@ -26132,6 +28103,28 @@ Media gallery (${gallery.length}):
|
|
|
26132
28103
|
});
|
|
26133
28104
|
}
|
|
26134
28105
|
);
|
|
28106
|
+
offers.command("notify-waitlist").description(
|
|
28107
|
+
"\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"
|
|
28108
|
+
).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(
|
|
28109
|
+
async (checkoutId, options, command) => {
|
|
28110
|
+
const flags = getGlobalFlags(command);
|
|
28111
|
+
const send = options.send === true;
|
|
28112
|
+
if (send) requireConfirm(options.confirm, "e-mail this offer's waiting list");
|
|
28113
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
28114
|
+
const result = await client.notifyCheckoutWaitlist(checkoutId, !send);
|
|
28115
|
+
emit(flags, result, () => {
|
|
28116
|
+
process.stdout.write(
|
|
28117
|
+
result.dryRun ? `${result.pendingCount} sign-up(s) of "${result.offerName}" would get the e-mail (nothing sent; add --send --confirm).
|
|
28118
|
+
` : `Sent to ${result.sentCount} sign-up(s) of "${result.offerName}".
|
|
28119
|
+
`
|
|
28120
|
+
);
|
|
28121
|
+
process.stdout.write(
|
|
28122
|
+
`Already notified: ${result.alreadyNotifiedCount}; skipped: ${result.skippedCount} (suppressed ${result.skippedByReason.suppressed}, unsubscribed after sign-up ${result.skippedByReason.unsubscribed_after_sign_up}).
|
|
28123
|
+
`
|
|
28124
|
+
);
|
|
28125
|
+
});
|
|
28126
|
+
}
|
|
28127
|
+
);
|
|
26135
28128
|
offers.command("set-default").description("Make an offer its product's default checkout").argument("<checkoutId>", "offer (checkout) id").action(async (checkoutId, _, command) => {
|
|
26136
28129
|
const flags = getGlobalFlags(command);
|
|
26137
28130
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -26368,6 +28361,7 @@ Media gallery (${gallery.length}):
|
|
|
26368
28361
|
// src/commands/orders/index.ts
|
|
26369
28362
|
import { writeFileSync as writeFileSync5 } from "fs";
|
|
26370
28363
|
var ORDER_STATUSES = ["paid", "pending", "refunded", "failed"];
|
|
28364
|
+
var REFUND_NOTE_MAX_LENGTH = 500;
|
|
26371
28365
|
function assertOrderStatus(status) {
|
|
26372
28366
|
if (!ORDER_STATUSES.includes(status)) {
|
|
26373
28367
|
throw new Error(`Invalid --status: ${status}. Use one of: ${ORDER_STATUSES.join(", ")}`);
|
|
@@ -26384,14 +28378,58 @@ function parsePositiveInt13(value, flag) {
|
|
|
26384
28378
|
function formatAmount3(amountCents, currency) {
|
|
26385
28379
|
return `${(amountCents / 100).toFixed(2)} ${currency}`;
|
|
26386
28380
|
}
|
|
28381
|
+
function formatProducts(order) {
|
|
28382
|
+
const others = (order.productIds ?? []).filter((id) => id !== order.productId).length;
|
|
28383
|
+
return others > 0 ? `${order.productId} +${others}` : order.productId;
|
|
28384
|
+
}
|
|
28385
|
+
function formatLine(line) {
|
|
28386
|
+
const variant = line.variantLabel ? ` (${line.variantLabel})` : "";
|
|
28387
|
+
const discount = line.discountCents > 0 ? ` (\u2212${formatAmount3(line.discountCents, line.currency)})` : "";
|
|
28388
|
+
return ` - ${line.productName}${variant} (${line.productId}) \u2014 ${line.quantity} \xD7 ${formatAmount3(line.unitAmountCents, line.currency)} = ${formatAmount3(line.amountCents, line.currency)}${discount}
|
|
28389
|
+
`;
|
|
28390
|
+
}
|
|
28391
|
+
function writeOrderLines(order) {
|
|
28392
|
+
const lines = order.lineItems ?? [];
|
|
28393
|
+
if (lines.length > 1 || order.origin === "shopCart") {
|
|
28394
|
+
process.stdout.write(` Lines:
|
|
28395
|
+
`);
|
|
28396
|
+
for (const line of lines) process.stdout.write(formatLine(line));
|
|
28397
|
+
} else if (order.additionalProducts.length > 0) {
|
|
28398
|
+
process.stdout.write(` Order bumps:
|
|
28399
|
+
`);
|
|
28400
|
+
for (const bump of order.additionalProducts) {
|
|
28401
|
+
const quantity = bump.quantity > 1 ? `${bump.quantity} \xD7 ` : "";
|
|
28402
|
+
process.stdout.write(
|
|
28403
|
+
` - ${bump.productName} (${bump.productId}) \u2014 ${quantity}${formatAmount3(bump.amountCents, bump.currency)}
|
|
28404
|
+
`
|
|
28405
|
+
);
|
|
28406
|
+
}
|
|
28407
|
+
}
|
|
28408
|
+
if (order.shipping) {
|
|
28409
|
+
const { shipping } = order;
|
|
28410
|
+
const destination = shipping.lockerPointId ? `locker ${shipping.lockerPointId}` : shipping.address ? `${shipping.address.street}, ${shipping.address.postalCode} ${shipping.address.city}` : "";
|
|
28411
|
+
process.stdout.write(
|
|
28412
|
+
` Delivery: ${shipping.methodLabel} \u2014 ${formatAmount3(shipping.amountCents, shipping.currency)}${destination ? `, ${destination}` : ""} (${shipping.recipientName})
|
|
28413
|
+
`
|
|
28414
|
+
);
|
|
28415
|
+
}
|
|
28416
|
+
if (order.fulfillment) {
|
|
28417
|
+
const { fulfillment } = order;
|
|
28418
|
+
const tracking = fulfillment.trackingNumber ? `, tracking ${fulfillment.trackingNumber}` : "";
|
|
28419
|
+
process.stdout.write(` Fulfilment: ${fulfillment.status}${tracking}
|
|
28420
|
+
`);
|
|
28421
|
+
}
|
|
28422
|
+
}
|
|
26387
28423
|
function requireConfirm11(confirm, what) {
|
|
26388
28424
|
if (!confirm) {
|
|
26389
28425
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
26390
28426
|
}
|
|
26391
28427
|
}
|
|
26392
|
-
function
|
|
26393
|
-
const orders2 = program2.command("orders").description(
|
|
26394
|
-
|
|
28428
|
+
function registerOrdersCommands2(program2) {
|
|
28429
|
+
const orders2 = program2.command("orders").description(
|
|
28430
|
+
"Browse orders; approve or reject manual (wire-transfer) payments; mark test / refunded orders"
|
|
28431
|
+
);
|
|
28432
|
+
orders2.command("list").description("List orders (newest first), with optional filters").option("--product <id>", "only orders that sold this product (on any line)").option(
|
|
26395
28433
|
"--status <status>",
|
|
26396
28434
|
"filter by status (paid, pending, refunded, failed) \u2014 manual wire-transfer orders awaiting approval are `pending`"
|
|
26397
28435
|
).option("--from <date>", "only orders created on/after this ISO date").option("--to <date>", "only orders created on/before this ISO date").option("--search <query>", "match client email / name").option("--include-test", "include simulated test purchases (excluded by default)").option("--limit <n>", "page size (max 100)").option("--cursor <cursor>", "keyset cursor from a previous page").action(
|
|
@@ -26412,7 +28450,7 @@ function registerOrdersCommands(program2) {
|
|
|
26412
28450
|
printTable(
|
|
26413
28451
|
page.items.map((o) => ({
|
|
26414
28452
|
id: o.id,
|
|
26415
|
-
product: o
|
|
28453
|
+
product: formatProducts(o),
|
|
26416
28454
|
client: o.clientEmail,
|
|
26417
28455
|
amount: formatAmount3(o.amountCents, o.currency),
|
|
26418
28456
|
status: o.status,
|
|
@@ -26428,7 +28466,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
26428
28466
|
});
|
|
26429
28467
|
}
|
|
26430
28468
|
);
|
|
26431
|
-
orders2.command("get").description("Show a single order (payment, refund, order bumps, invoice)").argument("<orderId>", "order id").action(async (orderId, _, command) => {
|
|
28469
|
+
orders2.command("get").description("Show a single order (payment, refund, lines or order bumps, delivery, invoice)").argument("<orderId>", "order id").action(async (orderId, _, command) => {
|
|
26432
28470
|
const flags = getGlobalFlags(command);
|
|
26433
28471
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
26434
28472
|
const order = await client.getOrder(orderId);
|
|
@@ -26463,30 +28501,24 @@ Next page: --cursor ${page.nextCursor}
|
|
|
26463
28501
|
`
|
|
26464
28502
|
);
|
|
26465
28503
|
}
|
|
26466
|
-
|
|
26467
|
-
process.stdout.write(` Order bumps:
|
|
26468
|
-
`);
|
|
26469
|
-
for (const bump of order.additionalProducts) {
|
|
26470
|
-
const quantity = bump.quantity > 1 ? `${bump.quantity} \xD7 ` : "";
|
|
26471
|
-
process.stdout.write(
|
|
26472
|
-
` - ${bump.productName} (${bump.productId}) \u2014 ${quantity}${formatAmount3(bump.amountCents, bump.currency)}
|
|
26473
|
-
`
|
|
26474
|
-
);
|
|
26475
|
-
}
|
|
26476
|
-
}
|
|
28504
|
+
writeOrderLines(order);
|
|
26477
28505
|
if (order.invoiceUrl) process.stdout.write(` Invoice: ${order.invoiceUrl}
|
|
26478
28506
|
`);
|
|
26479
28507
|
});
|
|
26480
28508
|
});
|
|
26481
28509
|
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(
|
|
28510
|
+
"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"
|
|
28511
|
+
).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(
|
|
28512
|
+
"--allow-oversell",
|
|
28513
|
+
"approve an expired physical order (late transfer) even though its stock ran out"
|
|
28514
|
+
).option("--confirm", "required \u2014 acknowledge that access is granted and the buyer is emailed").action(
|
|
26484
28515
|
async (orderId, options, command) => {
|
|
26485
28516
|
const flags = getGlobalFlags(command);
|
|
26486
28517
|
requireConfirm11(options.confirm, `approve order ${orderId} (access + purchase email)`);
|
|
26487
28518
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
26488
28519
|
const result = await client.approveManualOrder(orderId, {
|
|
26489
|
-
issueInvoice: options.issueInvoice === true ? true : void 0
|
|
28520
|
+
issueInvoice: options.issueInvoice === true ? true : void 0,
|
|
28521
|
+
allowOversell: options.allowOversell === true ? true : void 0
|
|
26490
28522
|
});
|
|
26491
28523
|
emit(flags, result, () => {
|
|
26492
28524
|
process.stdout.write(
|
|
@@ -26508,9 +28540,69 @@ Next page: --cursor ${page.nextCursor}
|
|
|
26508
28540
|
`);
|
|
26509
28541
|
});
|
|
26510
28542
|
});
|
|
28543
|
+
orders2.command("mark-test").description(
|
|
28544
|
+
"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"
|
|
28545
|
+
).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(
|
|
28546
|
+
async (orderId, options, command) => {
|
|
28547
|
+
const flags = getGlobalFlags(command);
|
|
28548
|
+
const isTest = options.undo !== true;
|
|
28549
|
+
requireConfirm11(
|
|
28550
|
+
options.confirm,
|
|
28551
|
+
isTest ? `mark order ${orderId} as a test` : `undo the test mark of order ${orderId}`
|
|
28552
|
+
);
|
|
28553
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
28554
|
+
const result = await client.markOrderAsTest(orderId, isTest);
|
|
28555
|
+
emit(flags, result, () => {
|
|
28556
|
+
process.stdout.write(
|
|
28557
|
+
result.isTest ? `Order ${result.orderId} is marked as a test \u2014 it no longer counts in revenue.
|
|
28558
|
+
` : `Order ${result.orderId} counts again in revenue and reports.
|
|
28559
|
+
`
|
|
28560
|
+
);
|
|
28561
|
+
});
|
|
28562
|
+
}
|
|
28563
|
+
);
|
|
28564
|
+
orders2.command("mark-refunded").description(
|
|
28565
|
+
"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"
|
|
28566
|
+
).argument("<orderId>", "order id").option("--revoke-access", "also end the buyer\u2019s access to the products of this order").option(
|
|
28567
|
+
"--note <text>",
|
|
28568
|
+
`note on the order's audit trail (max ${REFUND_NOTE_MAX_LENGTH} chars)`
|
|
28569
|
+
).option("--confirm", "required \u2014 acknowledge that the order counts as refunded").action(
|
|
28570
|
+
async (orderId, options, command) => {
|
|
28571
|
+
const flags = getGlobalFlags(command);
|
|
28572
|
+
if (options.note !== void 0 && options.note.length > REFUND_NOTE_MAX_LENGTH) {
|
|
28573
|
+
throw new Error(`--note is too long (max ${REFUND_NOTE_MAX_LENGTH} characters).`);
|
|
28574
|
+
}
|
|
28575
|
+
const revokeAccess = options.revokeAccess === true;
|
|
28576
|
+
requireConfirm11(
|
|
28577
|
+
options.confirm,
|
|
28578
|
+
`mark order ${orderId} as refunded outside Zanfia${revokeAccess ? " and end the buyer\u2019s access" : ""}`
|
|
28579
|
+
);
|
|
28580
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
28581
|
+
const result = await client.markOrderRefunded(orderId, {
|
|
28582
|
+
revokeAccess,
|
|
28583
|
+
...options.note !== void 0 ? { note: options.note } : {}
|
|
28584
|
+
});
|
|
28585
|
+
emit(flags, result, () => {
|
|
28586
|
+
process.stdout.write(
|
|
28587
|
+
`Order ${result.orderId} recorded as refunded outside Zanfia (${formatAmount3(result.amountMinor, result.currency)}).
|
|
28588
|
+
`
|
|
28589
|
+
);
|
|
28590
|
+
if (result.revokedProductIds.length > 0) {
|
|
28591
|
+
process.stdout.write(` Access ended: ${result.revokedProductIds.join(", ")}
|
|
28592
|
+
`);
|
|
28593
|
+
}
|
|
28594
|
+
if (result.accessRevokeFailedProductIds.length > 0) {
|
|
28595
|
+
process.stdout.write(
|
|
28596
|
+
` \u26A0\uFE0F Access NOT ended for: ${result.accessRevokeFailedProductIds.join(", ")} \u2014 the refund stands; end it on the client card.
|
|
28597
|
+
`
|
|
28598
|
+
);
|
|
28599
|
+
}
|
|
28600
|
+
});
|
|
28601
|
+
}
|
|
28602
|
+
);
|
|
26511
28603
|
orders2.command("export").description(
|
|
26512
28604
|
"Export orders as CSV to a file. The --from/--to window is required; same filters as `orders list`."
|
|
26513
|
-
).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
|
|
28605
|
+
).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 that sold this product (on any line)").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(
|
|
26514
28606
|
async (options, command) => {
|
|
26515
28607
|
const flags = getGlobalFlags(command);
|
|
26516
28608
|
for (const [flag, value] of [
|
|
@@ -26550,12 +28642,12 @@ Next page: --cursor ${page.nextCursor}
|
|
|
26550
28642
|
}
|
|
26551
28643
|
|
|
26552
28644
|
// src/commands/oto/index.ts
|
|
26553
|
-
import { readFileSync as
|
|
28645
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
26554
28646
|
var MIN_TIMER_MINUTES = 1;
|
|
26555
28647
|
var MAX_TIMER_MINUTES = 120;
|
|
26556
28648
|
function parseJsonFileOrInline7(value, flag) {
|
|
26557
28649
|
const trimmed = value.trim();
|
|
26558
|
-
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed :
|
|
28650
|
+
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed : readFileSync21(value, "utf8");
|
|
26559
28651
|
try {
|
|
26560
28652
|
return JSON.parse(raw);
|
|
26561
28653
|
} catch (error) {
|
|
@@ -26743,7 +28835,7 @@ function registerOtoCommands(program2) {
|
|
|
26743
28835
|
}
|
|
26744
28836
|
|
|
26745
28837
|
// src/commands/pages/index.ts
|
|
26746
|
-
import { existsSync as existsSync3, readFileSync as
|
|
28838
|
+
import { existsSync as existsSync3, readFileSync as readFileSync22 } from "fs";
|
|
26747
28839
|
import { readFile as readFile4 } from "fs/promises";
|
|
26748
28840
|
var CLEAR_VALUE = "none";
|
|
26749
28841
|
var FAMILY_STEPS = ["follow-ups", "thank-you-page", "room-page"];
|
|
@@ -26768,6 +28860,14 @@ function buildEventInput(options) {
|
|
|
26768
28860
|
};
|
|
26769
28861
|
return Object.keys(event).length > 0 ? event : void 0;
|
|
26770
28862
|
}
|
|
28863
|
+
function pointPlaceholderRefusalAtFlag(hint) {
|
|
28864
|
+
return (error) => {
|
|
28865
|
+
if (error instanceof ApiError && error.status === 409 && error.apiMessage.includes("allowPlaceholders")) {
|
|
28866
|
+
throw new ApiError(error.status, error.apiMessage, hint);
|
|
28867
|
+
}
|
|
28868
|
+
throw error;
|
|
28869
|
+
};
|
|
28870
|
+
}
|
|
26771
28871
|
async function resolveHtml2(options) {
|
|
26772
28872
|
if (options.file) {
|
|
26773
28873
|
return readFile4(options.file, "utf8");
|
|
@@ -26814,13 +28914,13 @@ function parseCsv4(value) {
|
|
|
26814
28914
|
];
|
|
26815
28915
|
}
|
|
26816
28916
|
function resolveTextOrFile2(value) {
|
|
26817
|
-
const text = existsSync3(value) ?
|
|
28917
|
+
const text = existsSync3(value) ? readFileSync22(value, "utf8") : value;
|
|
26818
28918
|
if (!text.trim()) throw new Error("The text (or the file it points to) is empty.");
|
|
26819
28919
|
return text;
|
|
26820
28920
|
}
|
|
26821
28921
|
function parseJsonObjectFileOrInline3(value, flag) {
|
|
26822
28922
|
const trimmed = value.trim();
|
|
26823
|
-
const raw = trimmed.startsWith("{") ? trimmed :
|
|
28923
|
+
const raw = trimmed.startsWith("{") ? trimmed : readFileSync22(value, "utf8");
|
|
26824
28924
|
let parsed;
|
|
26825
28925
|
try {
|
|
26826
28926
|
parsed = JSON.parse(raw);
|
|
@@ -26934,7 +29034,10 @@ function registerPagesCommands(program2) {
|
|
|
26934
29034
|
);
|
|
26935
29035
|
});
|
|
26936
29036
|
});
|
|
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").
|
|
29037
|
+
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(
|
|
29038
|
+
"--allow-placeholders",
|
|
29039
|
+
"with --publish: publish even though the page still shows [UZUPE\u0141NIJ: \u2026] placeholders"
|
|
29040
|
+
).action(async (options, command) => {
|
|
26938
29041
|
const flags = getGlobalFlags(command);
|
|
26939
29042
|
const html = await resolveHtml2(options);
|
|
26940
29043
|
if (!html) {
|
|
@@ -26944,10 +29047,15 @@ function registerPagesCommands(program2) {
|
|
|
26944
29047
|
slug: options.slug,
|
|
26945
29048
|
title: options.title,
|
|
26946
29049
|
html,
|
|
26947
|
-
publish: options.publish ?? false
|
|
29050
|
+
publish: options.publish ?? false,
|
|
29051
|
+
...options.allowPlaceholders ? { allowPlaceholders: true } : {}
|
|
26948
29052
|
};
|
|
26949
29053
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
26950
|
-
const result = await client.createPage(input)
|
|
29054
|
+
const result = await client.createPage(input).catch(
|
|
29055
|
+
pointPlaceholderRefusalAtFlag(
|
|
29056
|
+
"Fill them in, create it as a draft (without --publish), or re-run with --allow-placeholders to publish anyway."
|
|
29057
|
+
)
|
|
29058
|
+
);
|
|
26951
29059
|
emit(flags, result, () => {
|
|
26952
29060
|
process.stdout.write(`Created page ${result.pageId} \u2014 /${result.slug}
|
|
26953
29061
|
`);
|
|
@@ -26997,14 +29105,26 @@ function registerPagesCommands(program2) {
|
|
|
26997
29105
|
});
|
|
26998
29106
|
});
|
|
26999
29107
|
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")
|
|
29108
|
+
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");
|
|
29109
|
+
if (publish) {
|
|
29110
|
+
verbCommand.option(
|
|
29111
|
+
"--allow-placeholders",
|
|
29112
|
+
"publish even though pages still show [UZUPE\u0141NIJ: \u2026] placeholders"
|
|
29113
|
+
);
|
|
29114
|
+
}
|
|
29115
|
+
verbCommand.action(
|
|
27001
29116
|
async (id, options, command) => {
|
|
27002
29117
|
const flags = getGlobalFlags(command);
|
|
27003
29118
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
27004
29119
|
const result = await client.publishPage(id, publish, {
|
|
27005
29120
|
...options.onlyThisPage ? { scope: "page" } : {},
|
|
27006
|
-
...options.dryRun ? { dryRun: true } : {}
|
|
27007
|
-
|
|
29121
|
+
...options.dryRun ? { dryRun: true } : {},
|
|
29122
|
+
...options.allowPlaceholders ? { allowPlaceholders: true } : {}
|
|
29123
|
+
}).catch(
|
|
29124
|
+
pointPlaceholderRefusalAtFlag(
|
|
29125
|
+
"Fill them in (pages update), or re-run with --allow-placeholders to publish anyway."
|
|
29126
|
+
)
|
|
29127
|
+
);
|
|
27008
29128
|
emit(flags, result, () => {
|
|
27009
29129
|
if (!result.dryRun) {
|
|
27010
29130
|
process.stdout.write(`Page ${result.pageId} is now ${result.status}
|
|
@@ -27165,7 +29285,7 @@ function registerPagesCommands(program2) {
|
|
|
27165
29285
|
});
|
|
27166
29286
|
});
|
|
27167
29287
|
pages.command("restore").description(
|
|
27168
|
-
|
|
29288
|
+
'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, or with placeholder text the live page does not show ([UZUPE\u0141NIJ: \u2026], a <span class="todo"> note, LINK: \u2026), is refused (409, nothing restored). Requires --confirm.'
|
|
27169
29289
|
).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
29290
|
async (pageId, revisionId, options, command) => {
|
|
27171
29291
|
const flags = getGlobalFlags(command);
|
|
@@ -27226,9 +29346,14 @@ function registerPagesCommands(program2) {
|
|
|
27226
29346
|
}
|
|
27227
29347
|
|
|
27228
29348
|
// src/commands/products/index.ts
|
|
27229
|
-
import { readFileSync as
|
|
29349
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
27230
29350
|
import { readFile as readFile5 } from "fs/promises";
|
|
27231
29351
|
import { basename as basename2 } from "path";
|
|
29352
|
+
function parseBuyerFiles(value) {
|
|
29353
|
+
if (value === "on") return true;
|
|
29354
|
+
if (value === "off") return false;
|
|
29355
|
+
throw new Error(`Invalid --buyer-files: ${value} (expected on | off)`);
|
|
29356
|
+
}
|
|
27232
29357
|
var KB_TREE_CHANGED_ERROR = "tree-changed";
|
|
27233
29358
|
var JSON_FLAG_HINT3 = "(write it as --json=<value> \u2014 a bare --json is the global JSON-output flag)";
|
|
27234
29359
|
var PRODUCT_TYPE_INPUTS = [
|
|
@@ -27342,7 +29467,7 @@ function assertIsoDate2(value, flag) {
|
|
|
27342
29467
|
}
|
|
27343
29468
|
return value;
|
|
27344
29469
|
}
|
|
27345
|
-
function
|
|
29470
|
+
function assertOneOf4(value, allowed, flag) {
|
|
27346
29471
|
if (!allowed.includes(value)) {
|
|
27347
29472
|
throw new Error(`Invalid ${flag}: ${value}. Use one of: ${allowed.join(", ")}`);
|
|
27348
29473
|
}
|
|
@@ -27350,7 +29475,7 @@ function assertOneOf3(value, allowed, flag) {
|
|
|
27350
29475
|
}
|
|
27351
29476
|
function parseJsonFileOrInline8(value, flag) {
|
|
27352
29477
|
const trimmed = value.trim();
|
|
27353
|
-
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed :
|
|
29478
|
+
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed : readFileSync23(value, "utf8");
|
|
27354
29479
|
try {
|
|
27355
29480
|
return JSON.parse(raw);
|
|
27356
29481
|
} catch (error) {
|
|
@@ -27403,7 +29528,7 @@ function printAddedFiles(inputCount, result) {
|
|
|
27403
29528
|
);
|
|
27404
29529
|
printFilesTable(result.files);
|
|
27405
29530
|
}
|
|
27406
|
-
function
|
|
29531
|
+
function registerProductsCommands2(program2) {
|
|
27407
29532
|
const products2 = program2.command("products").description("Manage products");
|
|
27408
29533
|
products2.command("list").description("List products in the workspace").action(async (_, command) => {
|
|
27409
29534
|
const flags = getGlobalFlags(command);
|
|
@@ -27441,6 +29566,10 @@ function registerProductsCommands(program2) {
|
|
|
27441
29566
|
}
|
|
27442
29567
|
if (product.language !== void 0) {
|
|
27443
29568
|
process.stdout.write(` Language: ${product.language ?? "none (platform language)"}
|
|
29569
|
+
`);
|
|
29570
|
+
}
|
|
29571
|
+
if (product.fileSetEnabled !== void 0) {
|
|
29572
|
+
process.stdout.write(` Files for buyers: ${product.fileSetEnabled ? "on" : "off"}
|
|
27444
29573
|
`);
|
|
27445
29574
|
}
|
|
27446
29575
|
process.stdout.write(` Clients: ${product.clientCount}
|
|
@@ -27478,7 +29607,7 @@ function registerProductsCommands(program2) {
|
|
|
27478
29607
|
if (options.description !== void 0 && options.descriptionFile !== void 0) {
|
|
27479
29608
|
throw new Error("Use either --description or --description-file, not both.");
|
|
27480
29609
|
}
|
|
27481
|
-
const description = options.descriptionFile !== void 0 ?
|
|
29610
|
+
const description = options.descriptionFile !== void 0 ? readFileSync23(options.descriptionFile, "utf8") : options.description;
|
|
27482
29611
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
27483
29612
|
const product = await client.createProduct({
|
|
27484
29613
|
name: options.name,
|
|
@@ -27534,7 +29663,9 @@ function registerProductsCommands(program2) {
|
|
|
27534
29663
|
});
|
|
27535
29664
|
}
|
|
27536
29665
|
);
|
|
27537
|
-
products2.command("update").description(
|
|
29666
|
+
products2.command("update").description(
|
|
29667
|
+
"Update a product's name, description, storefront visibility, language and/or files for buyers"
|
|
29668
|
+
).argument("<id>", "product id").option("--name <name>", "new product name").option("--description <description>", "new product description").option(
|
|
27538
29669
|
"--visibility <public|unlisted|private>",
|
|
27539
29670
|
"where buyers find it once published: public (store + product page), unlisted (product page by link only), private (checkout links only)",
|
|
27540
29671
|
parseStorefrontVisibility
|
|
@@ -27542,11 +29673,15 @@ function registerProductsCommands(program2) {
|
|
|
27542
29673
|
"--language <pl|en|none>",
|
|
27543
29674
|
"the product's language (buyer-facing pages and e-mails); none clears it (the platform's language applies)",
|
|
27544
29675
|
parseProductLanguageOrNone
|
|
29676
|
+
).option(
|
|
29677
|
+
"--buyer-files <on|off>",
|
|
29678
|
+
"\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)",
|
|
29679
|
+
parseBuyerFiles
|
|
27545
29680
|
).action(
|
|
27546
29681
|
async (id, options, command) => {
|
|
27547
|
-
if (options.name === void 0 && options.description === void 0 && options.visibility === void 0 && options.language === void 0) {
|
|
29682
|
+
if (options.name === void 0 && options.description === void 0 && options.visibility === void 0 && options.language === void 0 && options.buyerFiles === void 0) {
|
|
27548
29683
|
throw new Error(
|
|
27549
|
-
"Nothing to update: pass --name, --description, --visibility and/or --
|
|
29684
|
+
"Nothing to update: pass --name, --description, --visibility, --language and/or --buyer-files."
|
|
27550
29685
|
);
|
|
27551
29686
|
}
|
|
27552
29687
|
const flags = getGlobalFlags(command);
|
|
@@ -27555,7 +29690,8 @@ function registerProductsCommands(program2) {
|
|
|
27555
29690
|
name: options.name,
|
|
27556
29691
|
description: options.description,
|
|
27557
29692
|
storefrontVisibility: options.visibility,
|
|
27558
|
-
language: options.language === PRODUCT_LANGUAGE_NONE ? null : options.language
|
|
29693
|
+
language: options.language === PRODUCT_LANGUAGE_NONE ? null : options.language,
|
|
29694
|
+
fileSetEnabled: options.buyerFiles
|
|
27559
29695
|
});
|
|
27560
29696
|
emit(flags, result, () => {
|
|
27561
29697
|
process.stdout.write(`Updated product ${result.productId}
|
|
@@ -27592,6 +29728,23 @@ function registerProductsCommands(program2) {
|
|
|
27592
29728
|
}
|
|
27593
29729
|
});
|
|
27594
29730
|
});
|
|
29731
|
+
products2.command("duplicate").description(
|
|
29732
|
+
"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"
|
|
29733
|
+
).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) => {
|
|
29734
|
+
requireConfirm(options.confirm, `duplicate product ${id}`);
|
|
29735
|
+
const flags = getGlobalFlags(command);
|
|
29736
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
29737
|
+
const result = await client.duplicateProduct(id, options.name);
|
|
29738
|
+
emit(
|
|
29739
|
+
flags,
|
|
29740
|
+
result,
|
|
29741
|
+
() => process.stdout.write(
|
|
29742
|
+
result.status === "already-running" ? `Product ${id} is already being copied (run ${result.runId}); no second copy.
|
|
29743
|
+
` : `Copying product ${id}${result.productId ? ` into ${result.productId}` : ""} (run ${result.runId}); the draft appears in \`products list\` when the run completes.
|
|
29744
|
+
`
|
|
29745
|
+
)
|
|
29746
|
+
);
|
|
29747
|
+
});
|
|
27595
29748
|
products2.command("publish").description("Publish a product (status \u2192 published; live for customers)").argument("<id>", "product id").action(async (id, _, command) => {
|
|
27596
29749
|
const flags = getGlobalFlags(command);
|
|
27597
29750
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -27849,10 +30002,10 @@ function registerProductsCommands(program2) {
|
|
|
27849
30002
|
).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
30003
|
async (productId, options, command) => {
|
|
27851
30004
|
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 ?
|
|
30005
|
+
const bookingMethod = options.bookingMethod !== void 0 ? assertOneOf4(options.bookingMethod, BOOKING_METHODS, "--booking-method") : void 0;
|
|
30006
|
+
const briefMethod = options.briefMethod !== void 0 ? assertOneOf4(options.briefMethod, BRIEF_METHODS, "--brief-method") : void 0;
|
|
30007
|
+
const deliveryUnit = options.deliveryUnit !== void 0 ? assertOneOf4(options.deliveryUnit, DELIVERY_UNITS, "--delivery-unit") : void 0;
|
|
30008
|
+
const eventMode = options.eventMode !== void 0 ? assertOneOf4(options.eventMode, EVENT_MODES3, "--event-mode") : void 0;
|
|
27856
30009
|
if (options.deliveryTime === void 0 !== (options.deliveryUnit === void 0)) {
|
|
27857
30010
|
throw new Error("Pass --delivery-time and --delivery-unit together.");
|
|
27858
30011
|
}
|
|
@@ -27984,7 +30137,7 @@ function registerProductsCommands(program2) {
|
|
|
27984
30137
|
).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
30138
|
async (productId, options, command) => {
|
|
27986
30139
|
const flags = getGlobalFlags(command);
|
|
27987
|
-
const thankYouType = options.thankYouType !== void 0 ?
|
|
30140
|
+
const thankYouType = options.thankYouType !== void 0 ? assertOneOf4(options.thankYouType, THANK_YOU_TYPES, "--thank-you-type") : void 0;
|
|
27988
30141
|
if (options.redirectUrl !== void 0)
|
|
27989
30142
|
assertHttpsUrl(options.redirectUrl, "--redirect-url");
|
|
27990
30143
|
const emailBuyerPurchase = options.emailBuyerPurchase !== void 0 ? parseBool4(options.emailBuyerPurchase, "--email-buyer-purchase") : void 0;
|
|
@@ -28077,7 +30230,7 @@ function registerProductsCommands(program2) {
|
|
|
28077
30230
|
if (options.content !== void 0 && options.contentFile !== void 0) {
|
|
28078
30231
|
throw new Error("Use either --content or --content-file, not both.");
|
|
28079
30232
|
}
|
|
28080
|
-
const editorContent = options.contentFile !== void 0 ?
|
|
30233
|
+
const editorContent = options.contentFile !== void 0 ? readFileSync23(options.contentFile, "utf8") : options.content;
|
|
28081
30234
|
if (editorContent === void 0) {
|
|
28082
30235
|
throw new Error("Pass the description via --content or --content-file.");
|
|
28083
30236
|
}
|
|
@@ -28183,7 +30336,7 @@ function registerProductsCommands(program2) {
|
|
|
28183
30336
|
if (options.content !== void 0 && options.contentFile !== void 0) {
|
|
28184
30337
|
throw new Error("Use either --content or --content-file, not both.");
|
|
28185
30338
|
}
|
|
28186
|
-
return options.contentFile !== void 0 ?
|
|
30339
|
+
return options.contentFile !== void 0 ? readFileSync23(options.contentFile, "utf8") : options.content;
|
|
28187
30340
|
};
|
|
28188
30341
|
kb.command("get").description(
|
|
28189
30342
|
"Show the knowledge base: categories (tree keys = the --category ids) + all articles (drafts and unavailable included \u2014 the creator view)"
|
|
@@ -28282,7 +30435,7 @@ function registerProductsCommands(program2) {
|
|
|
28282
30435
|
title: options.title,
|
|
28283
30436
|
parentId: options.category,
|
|
28284
30437
|
editorContent: resolveKbContent(options),
|
|
28285
|
-
status: options.status !== void 0 ?
|
|
30438
|
+
status: options.status !== void 0 ? assertOneOf4(options.status, KB_ARTICLE_STATUSES, "--status") : void 0,
|
|
28286
30439
|
tags: options.tags !== void 0 ? parseKbTags(options.tags) : void 0
|
|
28287
30440
|
});
|
|
28288
30441
|
emit(flags, result, () => {
|
|
@@ -28320,7 +30473,7 @@ function registerProductsCommands(program2) {
|
|
|
28320
30473
|
const result = await client.updateKbArticle(productId, articleId, {
|
|
28321
30474
|
title: options.title,
|
|
28322
30475
|
editorContent,
|
|
28323
|
-
status: options.status !== void 0 ?
|
|
30476
|
+
status: options.status !== void 0 ? assertOneOf4(options.status, KB_ARTICLE_STATUSES, "--status") : void 0,
|
|
28324
30477
|
parentId: options.category,
|
|
28325
30478
|
coverImage,
|
|
28326
30479
|
tags
|
|
@@ -28403,7 +30556,7 @@ function registerProductsCommands(program2) {
|
|
|
28403
30556
|
}
|
|
28404
30557
|
|
|
28405
30558
|
// src/commands/referrals/index.ts
|
|
28406
|
-
import { readFileSync as
|
|
30559
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
28407
30560
|
var PROGRAM_KINDS = ["referral", "affiliate"];
|
|
28408
30561
|
var WRITABLE_STATUSES = ["active", "inactive"];
|
|
28409
30562
|
var ENROLLMENT_MODES = ["open", "inviteOnly", "closed"];
|
|
@@ -28432,7 +30585,7 @@ function parseIdList3(value, flag) {
|
|
|
28432
30585
|
}
|
|
28433
30586
|
function parseJsonObjectFileOrInline4(value, flag) {
|
|
28434
30587
|
const trimmed = value.trim();
|
|
28435
|
-
const raw = trimmed.startsWith("{") ? trimmed :
|
|
30588
|
+
const raw = trimmed.startsWith("{") ? trimmed : readFileSync24(value, "utf8");
|
|
28436
30589
|
let parsed;
|
|
28437
30590
|
try {
|
|
28438
30591
|
parsed = JSON.parse(raw);
|
|
@@ -28799,7 +30952,7 @@ Failed deliveries (${result.failedDeliveries.length})
|
|
|
28799
30952
|
}
|
|
28800
30953
|
|
|
28801
30954
|
// src/commands/segments/index.ts
|
|
28802
|
-
import { readFileSync as
|
|
30955
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
28803
30956
|
var SUBSCRIPTION_STATUSES2 = ["pending", "subscribed", "unsubscribed"];
|
|
28804
30957
|
function collectRepeatable7(value, previous) {
|
|
28805
30958
|
return [...previous, value];
|
|
@@ -28818,7 +30971,7 @@ function resolveConditions(options) {
|
|
|
28818
30971
|
if (hasFlagConditions) {
|
|
28819
30972
|
throw new Error("Use either condition flags or --json-file, not both.");
|
|
28820
30973
|
}
|
|
28821
|
-
const raw =
|
|
30974
|
+
const raw = readFileSync25(options.jsonFile, "utf8");
|
|
28822
30975
|
let parsed;
|
|
28823
30976
|
try {
|
|
28824
30977
|
parsed = JSON.parse(raw);
|
|
@@ -29051,59 +31204,224 @@ function registerSegmentsCommands(program2) {
|
|
|
29051
31204
|
).argument("<id>", "segment id").option("--name <name>", "new segment name")
|
|
29052
31205
|
).action(async (id, options, command) => {
|
|
29053
31206
|
const flags = getGlobalFlags(command);
|
|
29054
|
-
const input = {};
|
|
29055
|
-
if (options.name !== void 0) input.name = options.name;
|
|
29056
|
-
const conditions = resolveConditions(options);
|
|
29057
|
-
if (conditions !== void 0) input.conditions = conditions;
|
|
29058
|
-
if (Object.keys(input).length === 0) {
|
|
29059
|
-
throw new Error(
|
|
29060
|
-
`Nothing to update \u2014 pass --name and/or conditions (${CONDITION_FLAGS_HELP}).`
|
|
31207
|
+
const input = {};
|
|
31208
|
+
if (options.name !== void 0) input.name = options.name;
|
|
31209
|
+
const conditions = resolveConditions(options);
|
|
31210
|
+
if (conditions !== void 0) input.conditions = conditions;
|
|
31211
|
+
if (Object.keys(input).length === 0) {
|
|
31212
|
+
throw new Error(
|
|
31213
|
+
`Nothing to update \u2014 pass --name and/or conditions (${CONDITION_FLAGS_HELP}).`
|
|
31214
|
+
);
|
|
31215
|
+
}
|
|
31216
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
31217
|
+
const result = await client.updateAudienceSegment(id, input);
|
|
31218
|
+
emit(flags, result, () => {
|
|
31219
|
+
const { segment } = result;
|
|
31220
|
+
process.stdout.write(
|
|
31221
|
+
`Updated segment ${segment.id} (${segment.name})${segment.lastEvaluatedCount !== null ? ` \u2014 ~${segment.lastEvaluatedCount} clients match` : ""}.
|
|
31222
|
+
`
|
|
31223
|
+
);
|
|
31224
|
+
});
|
|
31225
|
+
});
|
|
31226
|
+
segments.command("delete").description(
|
|
31227
|
+
"Delete a segment \u2014 campaigns already sent to it are unaffected; drafts targeting it will fail to send until re-pointed"
|
|
31228
|
+
).argument("<id>", "segment id").action(async (id, _options, command) => {
|
|
31229
|
+
const flags = getGlobalFlags(command);
|
|
31230
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
31231
|
+
const result = await client.deleteAudienceSegment(id);
|
|
31232
|
+
emit(flags, result, () => {
|
|
31233
|
+
process.stdout.write(`Deleted segment ${result.segmentId}.
|
|
31234
|
+
`);
|
|
31235
|
+
});
|
|
31236
|
+
});
|
|
31237
|
+
withConditionOptions(
|
|
31238
|
+
segments.command("preview").description(
|
|
31239
|
+
"Count the clients matching a condition set WITHOUT saving anything (stateless dry run)"
|
|
31240
|
+
).option(
|
|
31241
|
+
"--email-sendable",
|
|
31242
|
+
"count only clients an e-mail campaign may mail (subscribed): the reach of a send"
|
|
31243
|
+
).option(
|
|
31244
|
+
"--sms-sendable",
|
|
31245
|
+
"count only clients an SMS campaign may text (SMS consent AND a phone): size an SMS send"
|
|
31246
|
+
)
|
|
31247
|
+
).action(async (options, command) => {
|
|
31248
|
+
const flags = getGlobalFlags(command);
|
|
31249
|
+
const conditions = resolveConditions(options);
|
|
31250
|
+
if (conditions === void 0) {
|
|
31251
|
+
throw new Error(`Pass at least one condition: ${CONDITION_FLAGS_HELP}.`);
|
|
31252
|
+
}
|
|
31253
|
+
if (options.emailSendable && options.smsSendable) {
|
|
31254
|
+
throw new Error("Pass either --email-sendable or --sms-sendable, not both.");
|
|
31255
|
+
}
|
|
31256
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
31257
|
+
const result = await client.previewAudienceSegment(conditions, {
|
|
31258
|
+
requireSendable: options.emailSendable === true,
|
|
31259
|
+
requireSmsSendable: options.smsSendable === true
|
|
31260
|
+
});
|
|
31261
|
+
emit(flags, result, () => {
|
|
31262
|
+
const count2 = result.partial ? `\u2265 ${result.count}` : String(result.count);
|
|
31263
|
+
const reach = options.smsSendable ? " (SMS reach)" : options.emailSendable ? " (e-mail reach)" : "";
|
|
31264
|
+
process.stdout.write(
|
|
31265
|
+
`${count2} client${result.count === 1 && !result.partial ? " matches" : "s match"} ${conditions.map(describeCondition).join(" AND ")}${reach}.
|
|
31266
|
+
` + (result.partial ? "The client base was not counted to the end in time: the real number may be higher.\n" : "")
|
|
31267
|
+
);
|
|
31268
|
+
});
|
|
31269
|
+
});
|
|
31270
|
+
}
|
|
31271
|
+
|
|
31272
|
+
// src/commands/site/index.ts
|
|
31273
|
+
import { existsSync as existsSync4, readFileSync as readFileSync26 } from "fs";
|
|
31274
|
+
|
|
31275
|
+
// src/commands/site/librarySections.ts
|
|
31276
|
+
var NO_SECTION = "none";
|
|
31277
|
+
function isStringArray(value) {
|
|
31278
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
31279
|
+
}
|
|
31280
|
+
function toSectionDrafts(rows) {
|
|
31281
|
+
return rows.map((row, index) => {
|
|
31282
|
+
if (typeof row !== "object" || row === null || Array.isArray(row)) {
|
|
31283
|
+
throw new Error(`--sections[${index}] must be an object {id?, name, productIds}.`);
|
|
31284
|
+
}
|
|
31285
|
+
const name = "name" in row ? row.name : void 0;
|
|
31286
|
+
const productIds = "productIds" in row ? row.productIds : [];
|
|
31287
|
+
const id = "id" in row ? row.id : void 0;
|
|
31288
|
+
if (typeof name !== "string") throw new Error(`--sections[${index}].name must be a string.`);
|
|
31289
|
+
if (!isStringArray(productIds)) {
|
|
31290
|
+
throw new Error(`--sections[${index}].productIds must be an array of product ids.`);
|
|
31291
|
+
}
|
|
31292
|
+
if (id !== void 0 && id !== null && typeof id !== "string") {
|
|
31293
|
+
throw new Error(`--sections[${index}].id must be a section id or null.`);
|
|
31294
|
+
}
|
|
31295
|
+
return { ...typeof id === "string" ? { id } : {}, name, productIds };
|
|
31296
|
+
});
|
|
31297
|
+
}
|
|
31298
|
+
function printSectionsView(result) {
|
|
31299
|
+
printTable(
|
|
31300
|
+
result.sections.map((section) => ({
|
|
31301
|
+
id: section.id,
|
|
31302
|
+
name: section.name,
|
|
31303
|
+
products: section.products.map((product) => product.name).join(", ")
|
|
31304
|
+
})),
|
|
31305
|
+
["id", "name", "products"]
|
|
31306
|
+
);
|
|
31307
|
+
if (result.unassignedProducts.length > 0) {
|
|
31308
|
+
process.stdout.write(
|
|
31309
|
+
`
|
|
31310
|
+
Pozosta\u0142e (no section): ${result.unassignedProducts.map((product) => product.name).join(", ")}
|
|
31311
|
+
`
|
|
31312
|
+
);
|
|
31313
|
+
}
|
|
31314
|
+
}
|
|
31315
|
+
function printStoredSections(result, done) {
|
|
31316
|
+
process.stdout.write(`${done}
|
|
31317
|
+
`);
|
|
31318
|
+
printTable(
|
|
31319
|
+
result.sections.map((section) => ({
|
|
31320
|
+
id: section.id,
|
|
31321
|
+
name: section.name,
|
|
31322
|
+
products: section.productIds.length
|
|
31323
|
+
})),
|
|
31324
|
+
["id", "name", "products"]
|
|
31325
|
+
);
|
|
31326
|
+
if (result.droppedProductIds.length > 0) {
|
|
31327
|
+
process.stdout.write(
|
|
31328
|
+
`\u26A0\uFE0F Not stored (unknown, deleted or a bundle): ${result.droppedProductIds.join(", ")}
|
|
31329
|
+
`
|
|
31330
|
+
);
|
|
31331
|
+
}
|
|
31332
|
+
}
|
|
31333
|
+
function registerSiteLibrarySectionsCommands(site) {
|
|
31334
|
+
const sections = site.command("library-sections").description(
|
|
31335
|
+
"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"
|
|
31336
|
+
);
|
|
31337
|
+
sections.command("list").description("The sections in display order with their products, and the unassigned products").action(async (_options, command) => {
|
|
31338
|
+
const flags = getGlobalFlags(command);
|
|
31339
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
31340
|
+
const result = await client.listLibrarySections();
|
|
31341
|
+
emit(flags, result, () => printSectionsView(result));
|
|
31342
|
+
});
|
|
31343
|
+
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(
|
|
31344
|
+
"--position <n>",
|
|
31345
|
+
"0-based place among the sections (default: last)",
|
|
31346
|
+
parseNumberOption("--position", NumberFormat.Integer)
|
|
31347
|
+
).option("--confirm", "required \u2014 acknowledge the change of the member library").action(
|
|
31348
|
+
async (options, command) => {
|
|
31349
|
+
const flags = getGlobalFlags(command);
|
|
31350
|
+
requireConfirm(options.confirm, "add a section to the member library");
|
|
31351
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
31352
|
+
const result = await client.createLibrarySection({
|
|
31353
|
+
name: options.name,
|
|
31354
|
+
...options.products !== void 0 ? { productIds: options.products } : {},
|
|
31355
|
+
...options.position !== void 0 ? { position: options.position } : {}
|
|
31356
|
+
});
|
|
31357
|
+
emit(
|
|
31358
|
+
flags,
|
|
31359
|
+
result,
|
|
31360
|
+
() => printStoredSections(result, `Created section ${result.sectionId ?? ""}.`)
|
|
29061
31361
|
);
|
|
29062
31362
|
}
|
|
31363
|
+
);
|
|
31364
|
+
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(
|
|
31365
|
+
async (sectionId, options, command) => {
|
|
31366
|
+
const flags = getGlobalFlags(command);
|
|
31367
|
+
requireConfirm(options.confirm, `rename section ${sectionId}`);
|
|
31368
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
31369
|
+
const result = await client.renameLibrarySection(sectionId, options.name);
|
|
31370
|
+
emit(flags, result, () => printStoredSections(result, `Renamed section ${sectionId}.`));
|
|
31371
|
+
}
|
|
31372
|
+
);
|
|
31373
|
+
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) => {
|
|
31374
|
+
const flags = getGlobalFlags(command);
|
|
31375
|
+
requireConfirm(options.confirm, "reorder the library sections");
|
|
29063
31376
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
29064
|
-
const result = await client.
|
|
29065
|
-
emit(flags, result, () =>
|
|
29066
|
-
const { segment } = result;
|
|
29067
|
-
process.stdout.write(
|
|
29068
|
-
`Updated segment ${segment.id} (${segment.name})${segment.lastEvaluatedCount !== null ? ` \u2014 ~${segment.lastEvaluatedCount} clients match` : ""}.
|
|
29069
|
-
`
|
|
29070
|
-
);
|
|
29071
|
-
});
|
|
31377
|
+
const result = await client.reorderLibrarySections(sectionIds);
|
|
31378
|
+
emit(flags, result, () => printStoredSections(result, "Reordered the sections."));
|
|
29072
31379
|
});
|
|
29073
|
-
|
|
29074
|
-
|
|
29075
|
-
|
|
31380
|
+
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(
|
|
31381
|
+
async (productIds, options, command) => {
|
|
31382
|
+
const flags = getGlobalFlags(command);
|
|
31383
|
+
const sectionId = options.to.trim().toLowerCase() === NO_SECTION ? null : options.to;
|
|
31384
|
+
requireConfirm(options.confirm, "move products between library sections");
|
|
31385
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
31386
|
+
const result = await client.moveLibraryProducts(productIds, sectionId);
|
|
31387
|
+
emit(
|
|
31388
|
+
flags,
|
|
31389
|
+
result,
|
|
31390
|
+
() => printStoredSections(
|
|
31391
|
+
result,
|
|
31392
|
+
`Moved ${productIds.length} product(s) to ${sectionId ?? "no section"}.`
|
|
31393
|
+
)
|
|
31394
|
+
);
|
|
31395
|
+
}
|
|
31396
|
+
);
|
|
31397
|
+
sections.command("replace").description(
|
|
31398
|
+
"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"
|
|
31399
|
+
).requiredOption(
|
|
31400
|
+
"--sections <json|file>",
|
|
31401
|
+
"JSON array of {id?, name, productIds} (read the current list with `--json site library-sections list`)",
|
|
31402
|
+
parseJsonArrayFileOrInline("--sections")
|
|
31403
|
+
).option("--confirm", "required \u2014 acknowledge the full replace").action(async (options, command) => {
|
|
29076
31404
|
const flags = getGlobalFlags(command);
|
|
31405
|
+
const drafts = toSectionDrafts(options.sections);
|
|
31406
|
+
requireConfirm(options.confirm, "replace every library section");
|
|
29077
31407
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
29078
|
-
const result = await client.
|
|
29079
|
-
emit(
|
|
29080
|
-
|
|
29081
|
-
|
|
29082
|
-
|
|
31408
|
+
const result = await client.replaceLibrarySections(drafts);
|
|
31409
|
+
emit(
|
|
31410
|
+
flags,
|
|
31411
|
+
result,
|
|
31412
|
+
() => printStoredSections(result, `Stored ${result.sections.length} section(s).`)
|
|
31413
|
+
);
|
|
29083
31414
|
});
|
|
29084
|
-
|
|
29085
|
-
segments.command("preview").description(
|
|
29086
|
-
"Count the clients matching a condition set WITHOUT saving anything (stateless dry run)"
|
|
29087
|
-
)
|
|
29088
|
-
).action(async (options, command) => {
|
|
31415
|
+
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) => {
|
|
29089
31416
|
const flags = getGlobalFlags(command);
|
|
29090
|
-
|
|
29091
|
-
if (conditions === void 0) {
|
|
29092
|
-
throw new Error(`Pass at least one condition: ${CONDITION_FLAGS_HELP}.`);
|
|
29093
|
-
}
|
|
31417
|
+
requireConfirm(options.confirm, `delete library section ${sectionId}`);
|
|
29094
31418
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
29095
|
-
const result = await client.
|
|
29096
|
-
emit(flags, result, () => {
|
|
29097
|
-
process.stdout.write(
|
|
29098
|
-
`${result.count} client${result.count === 1 ? " matches" : "s match"} ${conditions.map(describeCondition).join(" AND ")}.
|
|
29099
|
-
`
|
|
29100
|
-
);
|
|
29101
|
-
});
|
|
31419
|
+
const result = await client.deleteLibrarySection(sectionId);
|
|
31420
|
+
emit(flags, result, () => printStoredSections(result, `Deleted section ${sectionId}.`));
|
|
29102
31421
|
});
|
|
29103
31422
|
}
|
|
29104
31423
|
|
|
29105
31424
|
// src/commands/site/index.ts
|
|
29106
|
-
import { existsSync as existsSync4, readFileSync as readFileSync25 } from "fs";
|
|
29107
31425
|
var MAIN_PAGE_MODES = {
|
|
29108
31426
|
sales: "/",
|
|
29109
31427
|
dashboard: "/dashboard",
|
|
@@ -29134,7 +31452,7 @@ function requireConfirm14(confirm, what) {
|
|
|
29134
31452
|
}
|
|
29135
31453
|
function parseJsonObjectFileOrInline5(value, flag) {
|
|
29136
31454
|
const trimmed = value.trim();
|
|
29137
|
-
const raw = trimmed.startsWith("{") ? trimmed :
|
|
31455
|
+
const raw = trimmed.startsWith("{") ? trimmed : readFileSync26(value, "utf8");
|
|
29138
31456
|
let parsed;
|
|
29139
31457
|
try {
|
|
29140
31458
|
parsed = JSON.parse(raw);
|
|
@@ -29149,7 +31467,7 @@ function parseJsonObjectFileOrInline5(value, flag) {
|
|
|
29149
31467
|
function baseBody(json) {
|
|
29150
31468
|
return json === void 0 ? {} : parseJsonObjectFileOrInline5(json, "--json");
|
|
29151
31469
|
}
|
|
29152
|
-
function
|
|
31470
|
+
function parseOnOff5(value, flag) {
|
|
29153
31471
|
const v = value.trim().toLowerCase();
|
|
29154
31472
|
if (v === "on" || v === "true") return true;
|
|
29155
31473
|
if (v === "off" || v === "false") return false;
|
|
@@ -29186,7 +31504,7 @@ function parseDomainList(value, what) {
|
|
|
29186
31504
|
return items;
|
|
29187
31505
|
}
|
|
29188
31506
|
function readMarkdownOrFile(value) {
|
|
29189
|
-
return existsSync4(value) ?
|
|
31507
|
+
return existsSync4(value) ? readFileSync26(value, "utf8") : value;
|
|
29190
31508
|
}
|
|
29191
31509
|
function assertNonEmptyBody(body, hint) {
|
|
29192
31510
|
if (Object.keys(body).length === 0) {
|
|
@@ -29276,16 +31594,16 @@ function printDomainResults(results) {
|
|
|
29276
31594
|
}
|
|
29277
31595
|
}
|
|
29278
31596
|
function printLegalDocuments(docs) {
|
|
29279
|
-
const
|
|
31597
|
+
const preview2 = (content) => content === null || content === "" ? "" : `${content.length} chars${content.length > 60 ? ` \u2014 "${content.slice(0, 60).replace(/\s+/g, " ")}\u2026"` : ` \u2014 "${content}"`}`;
|
|
29280
31598
|
printTable(
|
|
29281
31599
|
[
|
|
29282
31600
|
{ field: "language", value: docs.language },
|
|
29283
31601
|
{ field: "termsUrl", value: docs.termsUrl },
|
|
29284
31602
|
{ field: "termsSlug", value: docs.termsSlug ?? "" },
|
|
29285
|
-
{ field: "termsContent", value:
|
|
31603
|
+
{ field: "termsContent", value: preview2(docs.termsContent) },
|
|
29286
31604
|
{ field: "privacyPolicyUrl", value: docs.privacyPolicyUrl },
|
|
29287
31605
|
{ field: "privacyPolicySlug", value: docs.privacyPolicySlug ?? "" },
|
|
29288
|
-
{ field: "privacyPolicyContent", value:
|
|
31606
|
+
{ field: "privacyPolicyContent", value: preview2(docs.privacyPolicyContent) }
|
|
29289
31607
|
],
|
|
29290
31608
|
["field", "value"]
|
|
29291
31609
|
);
|
|
@@ -29375,6 +31693,26 @@ function registerSiteCommands2(program2) {
|
|
|
29375
31693
|
const result = await client.getSiteSettings();
|
|
29376
31694
|
emit(flags, result, () => printSiteSummary(result.settings, result.communication));
|
|
29377
31695
|
});
|
|
31696
|
+
site.command("scripts").description(
|
|
31697
|
+
"The site's own scripts (head / end of body of every page-editor page), read only: change them in the dashboard (Site \u2192 Settings \u2192 Custom scripts)"
|
|
31698
|
+
).action(async (_options, command) => {
|
|
31699
|
+
const flags = getGlobalFlags(command);
|
|
31700
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
31701
|
+
const result = await client.getSiteCustomScripts();
|
|
31702
|
+
emit(flags, result, () => {
|
|
31703
|
+
if (!result.available) {
|
|
31704
|
+
process.stdout.write("Custom scripts are not available for this workspace.\n");
|
|
31705
|
+
return;
|
|
31706
|
+
}
|
|
31707
|
+
process.stdout.write(`head:
|
|
31708
|
+
${result.head || "(empty)"}
|
|
31709
|
+
|
|
31710
|
+
`);
|
|
31711
|
+
process.stdout.write(`body end:
|
|
31712
|
+
${result.bodyEnd || "(empty)"}
|
|
31713
|
+
`);
|
|
31714
|
+
});
|
|
31715
|
+
});
|
|
29378
31716
|
const update = site.command("update").description(
|
|
29379
31717
|
"Update ONE section of the site settings atomically. Every section accepts --json=<file|inline> (write it with `=` \u2014 a bare --json is the global JSON-output flag); typed flags override keys from --json"
|
|
29380
31718
|
);
|
|
@@ -29386,9 +31724,12 @@ function registerSiteCommands2(program2) {
|
|
|
29386
31724
|
).option(
|
|
29387
31725
|
"--robots-rules <text>",
|
|
29388
31726
|
String.raw`extra robots.txt lines (User-agent / Allow / Disallow / Crawl-delay); separate lines with \n, or use --json`
|
|
31727
|
+
).option(
|
|
31728
|
+
"--llms-txt <text|file|none>",
|
|
31729
|
+
"the site's own /llms.txt (Markdown, inline or a file path); none = back to the generated file"
|
|
29389
31730
|
).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
31731
|
"--json <fileOrJson>",
|
|
29391
|
-
"inline JSON object or .json path \u2014 pageTitle, pageDescription, seoDescription, robotsTxtRules, showPageTitle, language, mainPage, mainPageId, mainPageCommunityId"
|
|
31732
|
+
"inline JSON object or .json path \u2014 pageTitle, pageDescription, seoDescription, robotsTxtRules, llmsTxt, showPageTitle, language, mainPage, mainPageId, mainPageCommunityId"
|
|
29392
31733
|
).action(
|
|
29393
31734
|
async (options, command) => {
|
|
29394
31735
|
const flags = getGlobalFlags(command);
|
|
@@ -29399,13 +31740,19 @@ function registerSiteCommands2(program2) {
|
|
|
29399
31740
|
if (options.robotsRules !== void 0) {
|
|
29400
31741
|
body.robotsTxtRules = options.robotsRules.split(String.raw`\n`).join("\n");
|
|
29401
31742
|
}
|
|
31743
|
+
if (options.llmsTxt !== void 0) {
|
|
31744
|
+
body.llmsTxt = options.llmsTxt.trim().toLowerCase() === "none" ? null : readMarkdownOrFile(options.llmsTxt);
|
|
31745
|
+
}
|
|
29402
31746
|
if (options.language !== void 0) {
|
|
29403
31747
|
body.language = parseChoice7(options.language, PLATFORM_LANGUAGES, "--language");
|
|
29404
31748
|
}
|
|
29405
31749
|
if (options.showPageTitle !== void 0) {
|
|
29406
|
-
body.showPageTitle =
|
|
31750
|
+
body.showPageTitle = parseOnOff5(options.showPageTitle, "--show-page-title");
|
|
29407
31751
|
}
|
|
29408
|
-
assertNonEmptyBody(
|
|
31752
|
+
assertNonEmptyBody(
|
|
31753
|
+
body,
|
|
31754
|
+
"pass --title, --description, --llms-txt, --language or --json=<patch>."
|
|
31755
|
+
);
|
|
29409
31756
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
29410
31757
|
const result = await client.updateSiteGeneral(body).catch(withMainPageHint);
|
|
29411
31758
|
emit(flags, result, () => process.stdout.write(SECTION_UPDATED("general")));
|
|
@@ -29425,7 +31772,7 @@ function registerSiteCommands2(program2) {
|
|
|
29425
31772
|
if (options.radius !== void 0) body.radius = options.radius;
|
|
29426
31773
|
if (options.styleVariant !== void 0) body.styleVariant = options.styleVariant;
|
|
29427
31774
|
if (options.fromBrand !== void 0) {
|
|
29428
|
-
body.themeFromBrand =
|
|
31775
|
+
body.themeFromBrand = parseOnOff5(options.fromBrand, "--from-brand");
|
|
29429
31776
|
}
|
|
29430
31777
|
assertNonEmptyBody(body, "pass --theme, --radius, --from-brand \u2026 or --json=<patch>.");
|
|
29431
31778
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -29452,7 +31799,7 @@ function registerSiteCommands2(program2) {
|
|
|
29452
31799
|
body.platformFaviconUrl = orNull6(options.faviconUrl);
|
|
29453
31800
|
}
|
|
29454
31801
|
if (options.hideBranding !== void 0) {
|
|
29455
|
-
body.hideZanfiaBranding =
|
|
31802
|
+
body.hideZanfiaBranding = parseOnOff5(options.hideBranding, "--hide-branding");
|
|
29456
31803
|
}
|
|
29457
31804
|
assertNonEmptyBody(
|
|
29458
31805
|
body,
|
|
@@ -29497,7 +31844,7 @@ function registerSiteCommands2(program2) {
|
|
|
29497
31844
|
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
31845
|
const flags = getGlobalFlags(command);
|
|
29499
31846
|
const base = baseBody(options.json);
|
|
29500
|
-
const cookieConsentEnabled = options.cookieConsent !== void 0 ?
|
|
31847
|
+
const cookieConsentEnabled = options.cookieConsent !== void 0 ? parseOnOff5(options.cookieConsent, "--cookie-consent") : base.cookieConsentEnabled;
|
|
29501
31848
|
if (typeof cookieConsentEnabled !== "boolean") {
|
|
29502
31849
|
throw new Error(
|
|
29503
31850
|
'Pass --cookie-consent on|off (or a --json body with "cookieConsentEnabled").'
|
|
@@ -29564,14 +31911,14 @@ function registerSiteCommands2(program2) {
|
|
|
29564
31911
|
if (options.libraryOrder === void 0) customization.libraryOrder = "custom";
|
|
29565
31912
|
}
|
|
29566
31913
|
if (options.groupByType !== void 0) {
|
|
29567
|
-
customization.libraryGroupByType =
|
|
31914
|
+
customization.libraryGroupByType = parseOnOff5(options.groupByType, "--group-by-type");
|
|
29568
31915
|
}
|
|
29569
31916
|
if (options.defaultView !== void 0) customization.defaultView = options.defaultView;
|
|
29570
31917
|
if (options.messages !== void 0) {
|
|
29571
|
-
customization.messagesEnabled =
|
|
31918
|
+
customization.messagesEnabled = parseOnOff5(options.messages, "--messages");
|
|
29572
31919
|
}
|
|
29573
31920
|
if (options.clientMessages !== void 0) {
|
|
29574
|
-
customization.clientMessagesEnabled =
|
|
31921
|
+
customization.clientMessagesEnabled = parseOnOff5(
|
|
29575
31922
|
options.clientMessages,
|
|
29576
31923
|
"--client-messages"
|
|
29577
31924
|
);
|
|
@@ -29810,6 +32157,7 @@ function registerSiteCommands2(program2) {
|
|
|
29810
32157
|
});
|
|
29811
32158
|
}
|
|
29812
32159
|
);
|
|
32160
|
+
registerSiteLibrarySectionsCommands(site);
|
|
29813
32161
|
}
|
|
29814
32162
|
function printStorefront(config) {
|
|
29815
32163
|
printTable(
|
|
@@ -29847,13 +32195,15 @@ function printStorefront(config) {
|
|
|
29847
32195
|
}
|
|
29848
32196
|
|
|
29849
32197
|
// src/commands/smsCampaigns/index.ts
|
|
29850
|
-
import { readFileSync as
|
|
32198
|
+
import { readFileSync as readFileSync27 } from "fs";
|
|
29851
32199
|
var SMS_PROVIDERS2 = ["twilio", "smsapi"];
|
|
29852
32200
|
var RECIPIENT_STATUSES2 = [
|
|
29853
32201
|
"pending",
|
|
29854
32202
|
"sent",
|
|
29855
32203
|
"failed",
|
|
29856
|
-
"skipped-invalid-number"
|
|
32204
|
+
"skipped-invalid-number",
|
|
32205
|
+
"skipped-duplicate-number",
|
|
32206
|
+
"skipped-opted-out-number"
|
|
29857
32207
|
];
|
|
29858
32208
|
var SCHEDULER_UNAVAILABLE_STATUS4 = 503;
|
|
29859
32209
|
function parsePositiveInt15(value, flag) {
|
|
@@ -29880,7 +32230,7 @@ function resolveBody(options) {
|
|
|
29880
32230
|
if (options.body !== void 0 && options.bodyFile !== void 0) {
|
|
29881
32231
|
throw new Error("Use either --body or --body-file, not both.");
|
|
29882
32232
|
}
|
|
29883
|
-
return options.bodyFile !== void 0 ?
|
|
32233
|
+
return options.bodyFile !== void 0 ? readFileSync27(options.bodyFile, "utf8") : options.body;
|
|
29884
32234
|
}
|
|
29885
32235
|
function resolveAudience2(options) {
|
|
29886
32236
|
if (options.segment !== void 0 && options.allSubscribed) {
|
|
@@ -29946,7 +32296,7 @@ function printCampaignDetail2(campaign) {
|
|
|
29946
32296
|
`);
|
|
29947
32297
|
const { stats } = campaign;
|
|
29948
32298
|
process.stdout.write(
|
|
29949
|
-
` Stats: sent ${stats.sent}, failed ${stats.failed}, skipped (invalid number) ${stats.skippedInvalidNumber}, billed parts ${stats.segments}
|
|
32299
|
+
` Stats: sent ${stats.sent}, failed ${stats.failed}, skipped (invalid number) ${stats.skippedInvalidNumber}, skipped (duplicate number) ${stats.skippedDuplicateNumber ?? 0}, skipped (opted-out number) ${stats.skippedOptedOutNumber ?? 0}, billed parts ${stats.segments}
|
|
29950
32300
|
`
|
|
29951
32301
|
);
|
|
29952
32302
|
process.stdout.write(` Opt-out line: ${campaign.optOutHint ?? "(none)"}
|
|
@@ -30321,6 +32671,96 @@ Next page: --after ${response.nextCursor}`);
|
|
|
30321
32671
|
});
|
|
30322
32672
|
}
|
|
30323
32673
|
|
|
32674
|
+
// src/commands/social/insights.ts
|
|
32675
|
+
var HIDDEN = { hidden: true };
|
|
32676
|
+
function parsePrivacy(value) {
|
|
32677
|
+
const privacy = YOUTUBE_PRIVACY_STATUSES.find(
|
|
32678
|
+
(candidate) => candidate === value.trim().toLowerCase()
|
|
32679
|
+
);
|
|
32680
|
+
if (privacy === void 0) {
|
|
32681
|
+
throw new Error(
|
|
32682
|
+
`Invalid --privacy: ${value} (expected one of: ${YOUTUBE_PRIVACY_STATUSES.join(", ")})`
|
|
32683
|
+
);
|
|
32684
|
+
}
|
|
32685
|
+
return privacy;
|
|
32686
|
+
}
|
|
32687
|
+
var formatNumber = (value) => value ?? "";
|
|
32688
|
+
function registerSocialInsightsCommands(social) {
|
|
32689
|
+
social.command("weekly", HIDDEN).description(
|
|
32690
|
+
"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`)"
|
|
32691
|
+
).action(async (_options, command) => {
|
|
32692
|
+
const flags = getGlobalFlags(command);
|
|
32693
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
32694
|
+
const report = await client.getSocialWeekReport();
|
|
32695
|
+
emit(flags, report, () => {
|
|
32696
|
+
process.stdout.write(`Week ${report.weekStart} \u2192 ${report.weekEnd}
|
|
32697
|
+
`);
|
|
32698
|
+
printTable(
|
|
32699
|
+
report.networks.map((network) => ({
|
|
32700
|
+
network: network.network,
|
|
32701
|
+
account: network.label,
|
|
32702
|
+
state: network.state,
|
|
32703
|
+
metric: network.metric ?? "",
|
|
32704
|
+
median: formatNumber(network.baselineMedian),
|
|
32705
|
+
postsThisWeek: network.postsThisWeek
|
|
32706
|
+
})),
|
|
32707
|
+
["network", "account", "state", "metric", "median", "postsThisWeek"]
|
|
32708
|
+
);
|
|
32709
|
+
});
|
|
32710
|
+
});
|
|
32711
|
+
social.command("performance", HIDDEN).description(
|
|
32712
|
+
"One Zanfia post's numbers per target against your median (\u201Eten post jeszcze \u017Cyje\u201D); the curve in `--json`"
|
|
32713
|
+
).argument("<postId>", "social post id (from `social posts`)").action(async (postId, _options, command) => {
|
|
32714
|
+
const flags = getGlobalFlags(command);
|
|
32715
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
32716
|
+
const performance = await client.getSocialPostPerformance(postId);
|
|
32717
|
+
emit(
|
|
32718
|
+
flags,
|
|
32719
|
+
performance,
|
|
32720
|
+
() => printTable(
|
|
32721
|
+
performance.targets.map((target) => ({
|
|
32722
|
+
target: target.target,
|
|
32723
|
+
state: target.state,
|
|
32724
|
+
metric: target.metric ?? "",
|
|
32725
|
+
score: formatNumber(target.score),
|
|
32726
|
+
median: formatNumber(target.baselineMedian),
|
|
32727
|
+
publishedAt: target.publishedAt ?? ""
|
|
32728
|
+
})),
|
|
32729
|
+
["target", "state", "metric", "score", "median", "publishedAt"]
|
|
32730
|
+
)
|
|
32731
|
+
);
|
|
32732
|
+
});
|
|
32733
|
+
const youtube = social.commands.find((command) => command.name() === "youtube") ?? social.command("youtube");
|
|
32734
|
+
youtube.command("visibility", HIDDEN).description(
|
|
32735
|
+
"Change the visibility of a post's published YouTube video (private / unlisted / public), or schedule its public release. Requires --confirm"
|
|
32736
|
+
).argument("<postId>", "the social post that published the video").requiredOption("--privacy <private|unlisted|public>", "the new visibility", parsePrivacy).option(
|
|
32737
|
+
"--publish-at <iso>",
|
|
32738
|
+
"public release time, ISO 8601 with an offset (only with --privacy private)"
|
|
32739
|
+
).option("--confirm", "required \u2014 acknowledge the change on YouTube").action(
|
|
32740
|
+
async (postId, options, command) => {
|
|
32741
|
+
const flags = getGlobalFlags(command);
|
|
32742
|
+
if (options.publishAt !== void 0 && options.privacy !== "private") {
|
|
32743
|
+
throw new Error("--publish-at needs --privacy private (YouTube makes it public then).");
|
|
32744
|
+
}
|
|
32745
|
+
requireConfirm(options.confirm, `change the YouTube visibility of post ${postId}`);
|
|
32746
|
+
const input = {
|
|
32747
|
+
privacyStatus: options.privacy,
|
|
32748
|
+
...options.publishAt !== void 0 ? { publishAt: options.publishAt } : {}
|
|
32749
|
+
};
|
|
32750
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
32751
|
+
const result = await client.setYouTubeVideoVisibility(postId, input);
|
|
32752
|
+
emit(
|
|
32753
|
+
flags,
|
|
32754
|
+
result,
|
|
32755
|
+
() => process.stdout.write(
|
|
32756
|
+
`Video ${result.videoId} is ${result.privacyStatus}${result.publishAt ? `, public from ${result.publishAt}` : ""}${result.demo ? " (demo: nothing was sent to YouTube)" : ""}.
|
|
32757
|
+
`
|
|
32758
|
+
)
|
|
32759
|
+
);
|
|
32760
|
+
}
|
|
32761
|
+
);
|
|
32762
|
+
}
|
|
32763
|
+
|
|
30324
32764
|
// src/commands/social/index.ts
|
|
30325
32765
|
function updateInput(options) {
|
|
30326
32766
|
const targets = splitList(options.targets);
|
|
@@ -30365,11 +32805,13 @@ function composeInput(options) {
|
|
|
30365
32805
|
...options.publish ? { publish: true } : {}
|
|
30366
32806
|
};
|
|
30367
32807
|
}
|
|
30368
|
-
function
|
|
32808
|
+
function registerSocialCommands2(program2) {
|
|
30369
32809
|
const social = program2.command("social").description(
|
|
30370
32810
|
"Social media \u2014 connected accounts, posts (drafts, scheduling, publishing) and the stats of the connected accounts (youtube / instagram / threads / snapshot)"
|
|
30371
32811
|
);
|
|
30372
|
-
social.command("accounts").description(
|
|
32812
|
+
social.command("accounts").description(
|
|
32813
|
+
"List connected social accounts (the values to pass as --targets; a bare network name such as `instagram` goes to the account marked default)"
|
|
32814
|
+
).action(async (_, command) => {
|
|
30373
32815
|
const flags = getGlobalFlags(command);
|
|
30374
32816
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
30375
32817
|
const response = await client.listSocialAccounts();
|
|
@@ -30379,11 +32821,12 @@ function registerSocialCommands(program2) {
|
|
|
30379
32821
|
target: a.target,
|
|
30380
32822
|
network: a.network,
|
|
30381
32823
|
label: a.label,
|
|
32824
|
+
default: a.isDefault ? "yes" : "no",
|
|
30382
32825
|
schedule: a.supportsScheduling ? "yes" : "no",
|
|
30383
32826
|
publishNow: a.supportsImmediatePublish ? "yes" : "no",
|
|
30384
32827
|
firstComment: a.firstCommentNeedsReconnect ? "reconnect" : ""
|
|
30385
32828
|
})),
|
|
30386
|
-
["target", "network", "label", "schedule", "publishNow", "firstComment"]
|
|
32829
|
+
["target", "network", "label", "default", "schedule", "publishNow", "firstComment"]
|
|
30387
32830
|
);
|
|
30388
32831
|
});
|
|
30389
32832
|
});
|
|
@@ -30516,6 +32959,7 @@ function registerSocialCommands(program2) {
|
|
|
30516
32959
|
});
|
|
30517
32960
|
});
|
|
30518
32961
|
registerSocialChannelCommands(social);
|
|
32962
|
+
registerSocialInsightsCommands(social);
|
|
30519
32963
|
}
|
|
30520
32964
|
|
|
30521
32965
|
// src/commands/stats/index.ts
|
|
@@ -30928,6 +33372,148 @@ ${integration.name}: ${integration.healthExplanation}
|
|
|
30928
33372
|
});
|
|
30929
33373
|
}
|
|
30930
33374
|
|
|
33375
|
+
// src/commands/studio/drafts.ts
|
|
33376
|
+
import { readFileSync as readFileSync28 } from "fs";
|
|
33377
|
+
var TEXT_PREVIEW_LENGTH = 60;
|
|
33378
|
+
function parseWeakReason(value) {
|
|
33379
|
+
const reason = STUDIO_SOCIAL_DRAFT_WEAK_REASONS.find(
|
|
33380
|
+
(candidate) => candidate === value.trim().toLowerCase()
|
|
33381
|
+
);
|
|
33382
|
+
if (reason === void 0) {
|
|
33383
|
+
throw new Error(
|
|
33384
|
+
`Invalid --reason: ${value} (expected one of: ${STUDIO_SOCIAL_DRAFT_WEAK_REASONS.join(", ")})`
|
|
33385
|
+
);
|
|
33386
|
+
}
|
|
33387
|
+
return reason;
|
|
33388
|
+
}
|
|
33389
|
+
var SINGLE_ACCOUNT_TARGETS = [
|
|
33390
|
+
"instagram",
|
|
33391
|
+
"linkedin",
|
|
33392
|
+
"threads",
|
|
33393
|
+
"youtube",
|
|
33394
|
+
"tiktok",
|
|
33395
|
+
"telegram"
|
|
33396
|
+
];
|
|
33397
|
+
function parseTarget(value) {
|
|
33398
|
+
const target = value.trim();
|
|
33399
|
+
const single = SINGLE_ACCOUNT_TARGETS.find((candidate) => candidate === target);
|
|
33400
|
+
if (single !== void 0) return single;
|
|
33401
|
+
const separator = target.indexOf(":");
|
|
33402
|
+
const network = target.slice(0, separator);
|
|
33403
|
+
const accountId = target.slice(separator + 1);
|
|
33404
|
+
if (separator > 0 && accountId !== "") {
|
|
33405
|
+
if (network === "facebook") return `facebook:${accountId}`;
|
|
33406
|
+
if (network === "instagram") return `instagram:${accountId}`;
|
|
33407
|
+
}
|
|
33408
|
+
throw new Error(
|
|
33409
|
+
`Invalid target: ${value} (expected ${SINGLE_ACCOUNT_TARGETS.join(", ")}, facebook:<pageId> or instagram:<accountId>; see \`social accounts\`)`
|
|
33410
|
+
);
|
|
33411
|
+
}
|
|
33412
|
+
function preview(text) {
|
|
33413
|
+
const oneLine2 = text.replace(/\s+/g, " ").trim();
|
|
33414
|
+
return oneLine2.length > TEXT_PREVIEW_LENGTH ? `${oneLine2.slice(0, TEXT_PREVIEW_LENGTH)}\u2026` : oneLine2;
|
|
33415
|
+
}
|
|
33416
|
+
function printDrafts(accounts) {
|
|
33417
|
+
const rows = accounts.flatMap(
|
|
33418
|
+
(account) => account.drafts.map((draft) => ({
|
|
33419
|
+
id: draft.id,
|
|
33420
|
+
account: account.label,
|
|
33421
|
+
slot: draft.slot,
|
|
33422
|
+
status: draft.status,
|
|
33423
|
+
text: preview(draft.text)
|
|
33424
|
+
}))
|
|
33425
|
+
);
|
|
33426
|
+
printTable(rows, ["id", "account", "slot", "status", "text"]);
|
|
33427
|
+
for (const account of accounts) {
|
|
33428
|
+
process.stdout.write(`${account.label} (${account.target}): ${account.remaining} left
|
|
33429
|
+
`);
|
|
33430
|
+
}
|
|
33431
|
+
}
|
|
33432
|
+
function registerStudioDraftsCommands(program2) {
|
|
33433
|
+
const studio = program2.commands.find((command) => command.name() === "studio") ?? program2.command("studio");
|
|
33434
|
+
const drafts = studio.command("drafts", { hidden: true }).description("This week's social post drafts in your voice (Studio \u201E5 szkic\xF3w\u201D)");
|
|
33435
|
+
drafts.command("list").description("This week's drafts per connected account and how many each may still get").action(async (_options, command) => {
|
|
33436
|
+
const flags = getGlobalFlags(command);
|
|
33437
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
33438
|
+
const result = await client.listStudioSocialDrafts();
|
|
33439
|
+
emit(flags, result, () => {
|
|
33440
|
+
process.stdout.write(`Week of ${result.week}
|
|
33441
|
+
`);
|
|
33442
|
+
printDrafts(result.accounts);
|
|
33443
|
+
});
|
|
33444
|
+
});
|
|
33445
|
+
drafts.command("generate").description(
|
|
33446
|
+
"Write this week's drafts for one account; spends AI credits (402 when they are out)"
|
|
33447
|
+
).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(
|
|
33448
|
+
async (options, command) => {
|
|
33449
|
+
const flags = getGlobalFlags(command);
|
|
33450
|
+
requireConfirm(options.confirm, "spend AI credits on new drafts");
|
|
33451
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
33452
|
+
const result = await client.generateStudioSocialDrafts({
|
|
33453
|
+
target: options.target,
|
|
33454
|
+
...options.topic !== void 0 ? { topic: options.topic } : {},
|
|
33455
|
+
...options.language !== void 0 ? { language: options.language } : {}
|
|
33456
|
+
});
|
|
33457
|
+
emit(flags, result, () => {
|
|
33458
|
+
process.stdout.write(
|
|
33459
|
+
`Wrote ${result.created} draft(s) for ${result.account.label}${result.rejectedByAiTells > 0 ? ` (${result.rejectedByAiTells} held back)` : ""}.
|
|
33460
|
+
`
|
|
33461
|
+
);
|
|
33462
|
+
printDrafts([result.account]);
|
|
33463
|
+
});
|
|
33464
|
+
}
|
|
33465
|
+
);
|
|
33466
|
+
drafts.command("feedback").description(
|
|
33467
|
+
"Mark a draft \u201ES\u0142abe\u201D with a reason: it is hidden and the next drafts learn from it"
|
|
33468
|
+
).argument("<draftId>", "draft id (from `studio drafts list`)").requiredOption(
|
|
33469
|
+
`--reason <${STUDIO_SOCIAL_DRAFT_WEAK_REASONS.join("|")}>`,
|
|
33470
|
+
"why it is weak",
|
|
33471
|
+
parseWeakReason
|
|
33472
|
+
).option("--confirm", "required \u2014 acknowledge the draft is hidden").action(
|
|
33473
|
+
async (draftId, options, command) => {
|
|
33474
|
+
const flags = getGlobalFlags(command);
|
|
33475
|
+
requireConfirm(options.confirm, `mark draft ${draftId} as weak`);
|
|
33476
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
33477
|
+
const result = await client.sendStudioSocialDraftFeedback(draftId, options.reason);
|
|
33478
|
+
emit(
|
|
33479
|
+
flags,
|
|
33480
|
+
result,
|
|
33481
|
+
() => process.stdout.write(
|
|
33482
|
+
`Draft ${draftId} is marked weak.${result.learnedRule ? ` Learned: ${result.learnedRule}` : ""}
|
|
33483
|
+
`
|
|
33484
|
+
)
|
|
33485
|
+
);
|
|
33486
|
+
}
|
|
33487
|
+
);
|
|
33488
|
+
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(
|
|
33489
|
+
"--targets <a,b,...>",
|
|
33490
|
+
"accounts to post to (default: the draft's own account)",
|
|
33491
|
+
parseCommaList("--targets")
|
|
33492
|
+
).option("--confirm", "required \u2014 acknowledge a draft post is created").action(
|
|
33493
|
+
async (draftId, options, command) => {
|
|
33494
|
+
const flags = getGlobalFlags(command);
|
|
33495
|
+
if (options.text !== void 0 && options.textFile !== void 0) {
|
|
33496
|
+
throw new Error("Use either --text or --text-file, not both.");
|
|
33497
|
+
}
|
|
33498
|
+
const text = options.textFile !== void 0 ? readFileSync28(options.textFile, "utf8") : options.text;
|
|
33499
|
+
requireConfirm(options.confirm, `create a draft social post from ${draftId}`);
|
|
33500
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
33501
|
+
const result = await client.sendStudioSocialDraftToComposer(draftId, {
|
|
33502
|
+
...text !== void 0 ? { text } : {},
|
|
33503
|
+
...options.targets !== void 0 ? { targets: options.targets.map((target) => parseTarget(target)) } : {}
|
|
33504
|
+
});
|
|
33505
|
+
emit(
|
|
33506
|
+
flags,
|
|
33507
|
+
result,
|
|
33508
|
+
() => process.stdout.write(
|
|
33509
|
+
`Draft post ${result.socialPostId} is in Social \u203A Posty (nothing is published).
|
|
33510
|
+
`
|
|
33511
|
+
)
|
|
33512
|
+
);
|
|
33513
|
+
}
|
|
33514
|
+
);
|
|
33515
|
+
}
|
|
33516
|
+
|
|
30931
33517
|
// src/commands/subscriptions/index.ts
|
|
30932
33518
|
function formatAmount4(amountMinor, currency) {
|
|
30933
33519
|
return `${(amountMinor / 100).toFixed(2)} ${currency.toUpperCase()}`;
|
|
@@ -30965,7 +33551,7 @@ function describeRetryStatus(status) {
|
|
|
30965
33551
|
return "Failed \u2014 the retry did not go through.";
|
|
30966
33552
|
}
|
|
30967
33553
|
}
|
|
30968
|
-
function
|
|
33554
|
+
function registerSubscriptionsCommands2(program2) {
|
|
30969
33555
|
const subscriptions = program2.command("subscriptions").description("Recurring billing: failed payments, manual renewals, subscriptions ending soon");
|
|
30970
33556
|
subscriptions.command("failed").description(
|
|
30971
33557
|
"Failed subscription / installment charges with live Stripe dunning state (attempts, next automatic retry, hosted invoice URL). Retry one with `subscriptions retry`"
|
|
@@ -31557,7 +34143,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
31557
34143
|
}
|
|
31558
34144
|
|
|
31559
34145
|
// src/commands/workflows/index.ts
|
|
31560
|
-
import { readFileSync as
|
|
34146
|
+
import { readFileSync as readFileSync30 } from "fs";
|
|
31561
34147
|
|
|
31562
34148
|
// src/commands/workflows/authoring.ts
|
|
31563
34149
|
import { writeFileSync as writeFileSync6 } from "fs";
|
|
@@ -31725,6 +34311,89 @@ function registerWorkflowAuthoringCommands(workflows) {
|
|
|
31725
34311
|
});
|
|
31726
34312
|
}
|
|
31727
34313
|
|
|
34314
|
+
// src/commands/workflows/emailConditions.ts
|
|
34315
|
+
import { readFileSync as readFileSync29 } from "fs";
|
|
34316
|
+
function collectRepeatable8(value, previous) {
|
|
34317
|
+
return [...previous, value];
|
|
34318
|
+
}
|
|
34319
|
+
function parseCustomValues(pairs) {
|
|
34320
|
+
const values = {};
|
|
34321
|
+
for (const pair of pairs) {
|
|
34322
|
+
const at = pair.indexOf("=");
|
|
34323
|
+
const key = at > 0 ? pair.slice(0, at).trim().replace(/^custom\./, "") : "";
|
|
34324
|
+
if (!key) throw new Error(`Invalid --custom ${pair} (expected <key>=<value>)`);
|
|
34325
|
+
values[key] = pair.slice(at + 1);
|
|
34326
|
+
}
|
|
34327
|
+
return values;
|
|
34328
|
+
}
|
|
34329
|
+
function buildInput(options) {
|
|
34330
|
+
if (options.body !== void 0 && options.bodyFile !== void 0) {
|
|
34331
|
+
throw new Error("Pass either --body or --body-file, not both.");
|
|
34332
|
+
}
|
|
34333
|
+
const body = options.bodyFile !== void 0 ? readFileSync29(options.bodyFile, "utf8") : options.body;
|
|
34334
|
+
if (options.subject === void 0 && options.preheader === void 0 && body === void 0) {
|
|
34335
|
+
throw new Error("Pass at least one of --subject, --preheader, --body or --body-file.");
|
|
34336
|
+
}
|
|
34337
|
+
const custom = parseCustomValues(options.custom);
|
|
34338
|
+
const withContact = options.firstName !== void 0 || Object.keys(custom).length > 0;
|
|
34339
|
+
return {
|
|
34340
|
+
...options.subject !== void 0 ? { subject: options.subject } : {},
|
|
34341
|
+
...options.preheader !== void 0 ? { preheader: options.preheader } : {},
|
|
34342
|
+
...body !== void 0 ? { body } : {},
|
|
34343
|
+
...withContact ? {
|
|
34344
|
+
exampleContact: {
|
|
34345
|
+
...options.firstName !== void 0 ? { firstName: options.firstName } : {},
|
|
34346
|
+
...Object.keys(custom).length > 0 ? { custom } : {}
|
|
34347
|
+
}
|
|
34348
|
+
} : {}
|
|
34349
|
+
};
|
|
34350
|
+
}
|
|
34351
|
+
function printResult(result) {
|
|
34352
|
+
const lines = [];
|
|
34353
|
+
if (!result.hasConditions) {
|
|
34354
|
+
lines.push("No {% %} conditions in these texts.");
|
|
34355
|
+
} else if (result.valid) {
|
|
34356
|
+
lines.push("Every condition tag parses.");
|
|
34357
|
+
} else {
|
|
34358
|
+
lines.push(`${result.warnings.length} problem(s); the send would use the safe text:`);
|
|
34359
|
+
for (const warning of result.warnings) {
|
|
34360
|
+
lines.push(` [${warning.field}] ${warning.tag} (${warning.code}): ${warning.detail}`);
|
|
34361
|
+
}
|
|
34362
|
+
}
|
|
34363
|
+
if (result.customFieldKeys.length > 0) {
|
|
34364
|
+
lines.push(`Custom fields read: ${result.customFieldKeys.join(", ")}`);
|
|
34365
|
+
}
|
|
34366
|
+
if (result.exampleResult) {
|
|
34367
|
+
lines.push("For the example contact:");
|
|
34368
|
+
for (const field of ["subject", "preheader", "body"]) {
|
|
34369
|
+
const text = result.exampleResult[field];
|
|
34370
|
+
if (text !== void 0) lines.push(` ${field}: ${text}`);
|
|
34371
|
+
}
|
|
34372
|
+
}
|
|
34373
|
+
if (result.evaluatedForWorkspace) {
|
|
34374
|
+
lines.push("Conditions are evaluated in this workspace (workflow and library e-mails).");
|
|
34375
|
+
}
|
|
34376
|
+
if (result.note) lines.push(result.note);
|
|
34377
|
+
process.stdout.write(`${lines.join("\n")}
|
|
34378
|
+
`);
|
|
34379
|
+
}
|
|
34380
|
+
function registerEmailConditionsCommand(workflows) {
|
|
34381
|
+
workflows.command("check-email-conditions").description(
|
|
34382
|
+
"Check {% if %} / {% case %} conditional content in a workflow or library e-mail before saving it (read-only; not for e-mail campaigns, which send the default version)"
|
|
34383
|
+
).option("--subject <text>", "e-mail subject, as it will be saved").option("--preheader <text>", "inbox preview line").option("--body <markdown>", "e-mail body (markdown)").option("--body-file <path>", "read the body (markdown) from a file").option("--first-name <name>", "example contact: first name").option(
|
|
34384
|
+
"--custom <key=value>",
|
|
34385
|
+
"example contact: a custom field value as the e-mail prints it (a select = its option label; repeatable)",
|
|
34386
|
+
collectRepeatable8,
|
|
34387
|
+
[]
|
|
34388
|
+
).action(async (options, command) => {
|
|
34389
|
+
const flags = getGlobalFlags(command);
|
|
34390
|
+
const input = buildInput(options);
|
|
34391
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
34392
|
+
const result = await client.checkEmailConditions(input);
|
|
34393
|
+
emit(flags, result, () => printResult(result));
|
|
34394
|
+
});
|
|
34395
|
+
}
|
|
34396
|
+
|
|
31728
34397
|
// src/commands/workflows/index.ts
|
|
31729
34398
|
function parsePositiveInt18(value, flag) {
|
|
31730
34399
|
const n = Number.parseInt(value, 10);
|
|
@@ -31740,7 +34409,7 @@ function requireConfirm17(confirm, what) {
|
|
|
31740
34409
|
}
|
|
31741
34410
|
function parseTriggerContext(value) {
|
|
31742
34411
|
const trimmed = value.trim();
|
|
31743
|
-
const raw = trimmed.startsWith("{") ? trimmed :
|
|
34412
|
+
const raw = trimmed.startsWith("{") ? trimmed : readFileSync30(value, "utf8");
|
|
31744
34413
|
let parsed;
|
|
31745
34414
|
try {
|
|
31746
34415
|
parsed = JSON.parse(raw);
|
|
@@ -31809,7 +34478,9 @@ function registerWorkflowsCommands(program2) {
|
|
|
31809
34478
|
printWorkflowGraphs(workflow, published, draft, templateNames);
|
|
31810
34479
|
});
|
|
31811
34480
|
});
|
|
31812
|
-
|
|
34481
|
+
const runs = workflows.command("runs").description(
|
|
34482
|
+
"List recent runs of a workflow (newest first); `workflows runs stop` takes one person out of a run"
|
|
34483
|
+
).argument("<automationId>", "workflow automation id").option("--limit <n>", "number of runs to return (default 20, max 100)").action(async (automationId, options, command) => {
|
|
31813
34484
|
const flags = getGlobalFlags(command);
|
|
31814
34485
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
31815
34486
|
const page = await client.listAutomationRuns(
|
|
@@ -31831,6 +34502,23 @@ function registerWorkflowsCommands(program2) {
|
|
|
31831
34502
|
);
|
|
31832
34503
|
});
|
|
31833
34504
|
});
|
|
34505
|
+
runs.command("stop").description(
|
|
34506
|
+
"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"
|
|
34507
|
+
).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(
|
|
34508
|
+
async (automationId, runId, options, command) => {
|
|
34509
|
+
const flags = getGlobalFlags(command);
|
|
34510
|
+
requireConfirm17(options.confirm, `stop run ${runId} (no further steps for this person)`);
|
|
34511
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
34512
|
+
const result = await client.stopAutomationRun(automationId, runId);
|
|
34513
|
+
emit(flags, result, () => {
|
|
34514
|
+
process.stdout.write(
|
|
34515
|
+
result.stopped.length > 0 ? `Stopped run ${runId} of workflow ${automationId}.
|
|
34516
|
+
` : `Run ${runId} had already finished \u2014 nothing to stop.
|
|
34517
|
+
`
|
|
34518
|
+
);
|
|
34519
|
+
});
|
|
34520
|
+
}
|
|
34521
|
+
);
|
|
31834
34522
|
workflows.command("pause").description(
|
|
31835
34523
|
"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
34524
|
).argument("<automationId>", "workflow automation id").action(async (automationId, _, command) => {
|
|
@@ -31846,13 +34534,36 @@ function registerWorkflowsCommands(program2) {
|
|
|
31846
34534
|
});
|
|
31847
34535
|
workflows.command("resume").description(
|
|
31848
34536
|
"Resume a paused workflow (requires a published version \u2014 a never-published draft cannot run)"
|
|
31849
|
-
).argument("<automationId>", "workflow automation id").
|
|
34537
|
+
).argument("<automationId>", "workflow automation id").option(
|
|
34538
|
+
"--held-events <decision>",
|
|
34539
|
+
"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."
|
|
34540
|
+
).action(async (automationId, options, command) => {
|
|
31850
34541
|
const flags = getGlobalFlags(command);
|
|
34542
|
+
const heldEvents = HELD_EVENTS_DECISIONS.find((value) => value === options.heldEvents);
|
|
34543
|
+
if (options.heldEvents !== void 0 && !heldEvents) {
|
|
34544
|
+
throw new ApiError(0, "--held-events must be `send` or `skip`");
|
|
34545
|
+
}
|
|
31851
34546
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
31852
|
-
const result = await client.setAutomationStatus(automationId, "active")
|
|
34547
|
+
const result = await client.setAutomationStatus(automationId, "active", heldEvents).catch((error) => {
|
|
34548
|
+
if (error instanceof ApiError && error.status === 409 && !heldEvents) {
|
|
34549
|
+
throw new ApiError(
|
|
34550
|
+
409,
|
|
34551
|
+
error.apiMessage,
|
|
34552
|
+
"Run again with --held-events send (deliver them) or --held-events skip (drop them)."
|
|
34553
|
+
);
|
|
34554
|
+
}
|
|
34555
|
+
throw error;
|
|
34556
|
+
});
|
|
31853
34557
|
emit(flags, result, () => {
|
|
31854
34558
|
process.stdout.write(`Resumed workflow ${result.id}.
|
|
31855
34559
|
`);
|
|
34560
|
+
if (result.heldEvents) {
|
|
34561
|
+
process.stdout.write(
|
|
34562
|
+
result.heldEvents.decision === "send" ? `${result.heldEvents.count} held event(s) are being sent, 30 a minute.
|
|
34563
|
+
` : `${result.heldEvents.count} held event(s) were dropped.
|
|
34564
|
+
`
|
|
34565
|
+
);
|
|
34566
|
+
}
|
|
31856
34567
|
});
|
|
31857
34568
|
});
|
|
31858
34569
|
workflows.command("retry").description(
|
|
@@ -31903,6 +34614,7 @@ Follow it with: zanfia workflows runs ${automationId}
|
|
|
31903
34614
|
}
|
|
31904
34615
|
);
|
|
31905
34616
|
registerWorkflowAuthoringCommands(workflows);
|
|
34617
|
+
registerEmailConditionsCommand(workflows);
|
|
31906
34618
|
}
|
|
31907
34619
|
function formatProductScope(workflow) {
|
|
31908
34620
|
return workflow.productIds.length > 0 ? String(workflow.productIds.length) : "all";
|
|
@@ -32269,7 +34981,7 @@ function describeDelay(config) {
|
|
|
32269
34981
|
return `wait until ${optionalString(config.date) ?? "?"}${optionalString(config.timezone) ? ` (${config.timezone})` : ""}`;
|
|
32270
34982
|
case "untilContextDate": {
|
|
32271
34983
|
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)" : ""}`;
|
|
34984
|
+
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
34985
|
}
|
|
32274
34986
|
default:
|
|
32275
34987
|
return `wait (${mode})`;
|
|
@@ -32324,7 +35036,7 @@ function printLogins(profiles) {
|
|
|
32324
35036
|
"\n* = active. Switch: `zanfia workspace use <id|slug>`; one command: `--workspace <id|slug>`.\n"
|
|
32325
35037
|
);
|
|
32326
35038
|
}
|
|
32327
|
-
function
|
|
35039
|
+
function parseOnOff6(value, flag) {
|
|
32328
35040
|
const match = ["on", "off"].find((v) => v === value);
|
|
32329
35041
|
if (!match) throw new Error(`Invalid ${flag}: ${value} (expected on | off)`);
|
|
32330
35042
|
return match === "on";
|
|
@@ -32407,7 +35119,7 @@ function registerWorkspaceCommands(program2) {
|
|
|
32407
35119
|
}
|
|
32408
35120
|
if (options.inheritTimezone) input.timezone = null;
|
|
32409
35121
|
if (options.autoTranscribe !== void 0) {
|
|
32410
|
-
input.autoTranscribeUploads =
|
|
35122
|
+
input.autoTranscribeUploads = parseOnOff6(options.autoTranscribe, "--auto-transcribe");
|
|
32411
35123
|
}
|
|
32412
35124
|
if (options.transcriptionLanguage !== void 0) {
|
|
32413
35125
|
input.transcriptionLanguage = parseTranscriptionLanguage(options.transcriptionLanguage);
|
|
@@ -32478,11 +35190,11 @@ function applyShortHelp(program2) {
|
|
|
32478
35190
|
|
|
32479
35191
|
// src/program.ts
|
|
32480
35192
|
function resolveCliVersion() {
|
|
32481
|
-
if ("0.
|
|
32482
|
-
return "0.
|
|
35193
|
+
if ("0.25.0".length > 0) {
|
|
35194
|
+
return "0.25.0";
|
|
32483
35195
|
}
|
|
32484
35196
|
try {
|
|
32485
|
-
const raw =
|
|
35197
|
+
const raw = readFileSync31(new URL("../package.json", import.meta.url), "utf8");
|
|
32486
35198
|
const parsed = JSON.parse(raw);
|
|
32487
35199
|
if (typeof parsed === "object" && parsed !== null && "version" in parsed) {
|
|
32488
35200
|
const { version: version2 } = parsed;
|
|
@@ -32523,7 +35235,7 @@ Signing in \u2014 two roles, two consent pages:
|
|
|
32523
35235
|
registerJoinCommand(program2);
|
|
32524
35236
|
registerLogoutCommand(program2);
|
|
32525
35237
|
registerAuthCommands(program2);
|
|
32526
|
-
|
|
35238
|
+
registerProductsCommands2(program2);
|
|
32527
35239
|
registerOffersCommands(program2);
|
|
32528
35240
|
registerCheckoutSettingsCommands(program2);
|
|
32529
35241
|
registerIntegrationsCommands(program2);
|
|
@@ -32532,7 +35244,7 @@ Signing in \u2014 two roles, two consent pages:
|
|
|
32532
35244
|
registerOtoCommands(program2);
|
|
32533
35245
|
registerBumpsCommands(program2);
|
|
32534
35246
|
registerCoursesCommands(program2);
|
|
32535
|
-
|
|
35247
|
+
registerConsultationsCommands(program2);
|
|
32536
35248
|
registerNewslettersCommands(program2);
|
|
32537
35249
|
registerCommunitiesCommands(program2);
|
|
32538
35250
|
registerConversationsCommands(program2);
|
|
@@ -32545,8 +35257,8 @@ Signing in \u2014 two roles, two consent pages:
|
|
|
32545
35257
|
registerTestimonialsCommands(program2);
|
|
32546
35258
|
registerBrandCommands(program2);
|
|
32547
35259
|
registerTeamCommands(program2);
|
|
32548
|
-
|
|
32549
|
-
|
|
35260
|
+
registerOrdersCommands2(program2);
|
|
35261
|
+
registerSubscriptionsCommands2(program2);
|
|
32550
35262
|
registerStatsCommands(program2);
|
|
32551
35263
|
registerDiscountsCommands(program2);
|
|
32552
35264
|
registerReferralsCommands(program2);
|
|
@@ -32555,9 +35267,11 @@ Signing in \u2014 two roles, two consent pages:
|
|
|
32555
35267
|
registerWorkspaceCommands(program2);
|
|
32556
35268
|
registerFunnelsCommands(program2);
|
|
32557
35269
|
registerPagesCommands(program2);
|
|
32558
|
-
|
|
35270
|
+
registerSocialCommands2(program2);
|
|
32559
35271
|
registerMediaCommands(program2);
|
|
32560
35272
|
registerFilesCommands(program2);
|
|
35273
|
+
registerStudioDraftsCommands(program2);
|
|
35274
|
+
registerGeneratedCommands(program2);
|
|
32561
35275
|
applyGlobalOptions(program2);
|
|
32562
35276
|
applyShortHelp(program2);
|
|
32563
35277
|
return program2;
|