@zanfia/cli 0.21.0 → 0.23.0

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