@zanfia/cli 0.3.47 → 0.3.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2786 -283
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -980,7 +980,7 @@ var require_command = __commonJS({
|
|
|
980
980
|
var { Help: Help2 } = require_help();
|
|
981
981
|
var { Option: Option2, DualOptions } = require_option();
|
|
982
982
|
var { suggestSimilar } = require_suggestSimilar();
|
|
983
|
-
var
|
|
983
|
+
var Command4 = class _Command extends EventEmitter {
|
|
984
984
|
/**
|
|
985
985
|
* Initialize a new `Command`.
|
|
986
986
|
*
|
|
@@ -3006,7 +3006,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3006
3006
|
return arg;
|
|
3007
3007
|
});
|
|
3008
3008
|
}
|
|
3009
|
-
exports.Command =
|
|
3009
|
+
exports.Command = Command4;
|
|
3010
3010
|
}
|
|
3011
3011
|
});
|
|
3012
3012
|
|
|
@@ -3015,15 +3015,15 @@ var require_commander = __commonJS({
|
|
|
3015
3015
|
"node_modules/commander/index.js"(exports) {
|
|
3016
3016
|
"use strict";
|
|
3017
3017
|
var { Argument: Argument2 } = require_argument();
|
|
3018
|
-
var { Command:
|
|
3018
|
+
var { Command: Command4 } = require_command();
|
|
3019
3019
|
var { CommanderError: CommanderError2, InvalidArgumentError: InvalidArgumentError2 } = require_error();
|
|
3020
3020
|
var { Help: Help2 } = require_help();
|
|
3021
3021
|
var { Option: Option2 } = require_option();
|
|
3022
|
-
exports.program = new
|
|
3023
|
-
exports.createCommand = (name) => new
|
|
3022
|
+
exports.program = new Command4();
|
|
3023
|
+
exports.createCommand = (name) => new Command4(name);
|
|
3024
3024
|
exports.createOption = (flags, description) => new Option2(flags, description);
|
|
3025
3025
|
exports.createArgument = (name, description) => new Argument2(name, description);
|
|
3026
|
-
exports.Command =
|
|
3026
|
+
exports.Command = Command4;
|
|
3027
3027
|
exports.Option = Option2;
|
|
3028
3028
|
exports.Argument = Argument2;
|
|
3029
3029
|
exports.Help = Help2;
|
|
@@ -4004,8 +4004,8 @@ var require_lodash3 = __commonJS({
|
|
|
4004
4004
|
}
|
|
4005
4005
|
var stringToPath = memoize(function(string) {
|
|
4006
4006
|
var result = [];
|
|
4007
|
-
toString(string).replace(rePropName, function(match, number,
|
|
4008
|
-
result.push(
|
|
4007
|
+
toString(string).replace(rePropName, function(match, number, quote2, string2) {
|
|
4008
|
+
result.push(quote2 ? string2.replace(reEscapeChar, "$1") : number || match);
|
|
4009
4009
|
});
|
|
4010
4010
|
return result;
|
|
4011
4011
|
});
|
|
@@ -5793,8 +5793,8 @@ var require_graceful_fs = __commonJS({
|
|
|
5793
5793
|
fs3.createReadStream = createReadStream3;
|
|
5794
5794
|
fs3.createWriteStream = createWriteStream;
|
|
5795
5795
|
var fs$readFile = fs3.readFile;
|
|
5796
|
-
fs3.readFile =
|
|
5797
|
-
function
|
|
5796
|
+
fs3.readFile = readFile4;
|
|
5797
|
+
function readFile4(path2, options, cb) {
|
|
5798
5798
|
if (typeof options === "function")
|
|
5799
5799
|
cb = options, options = null;
|
|
5800
5800
|
return go$readFile(path2, options, cb);
|
|
@@ -6901,7 +6901,7 @@ var {
|
|
|
6901
6901
|
} = import_index.default;
|
|
6902
6902
|
|
|
6903
6903
|
// src/program.ts
|
|
6904
|
-
import { readFileSync as
|
|
6904
|
+
import { readFileSync as readFileSync28 } from "fs";
|
|
6905
6905
|
|
|
6906
6906
|
// src/utils/config.ts
|
|
6907
6907
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
@@ -6918,8 +6918,17 @@ var backendDefaults = {
|
|
|
6918
6918
|
memberExchangeUrl: true ? orUndefined("https://europe-central2-mailingr-54736.cloudfunctions.net/users-v1/member/cli/auth/exchange") : void 0,
|
|
6919
6919
|
usersUrl: true ? orUndefined("https://europe-central2-mailingr-54736.cloudfunctions.net/users-v1") : void 0,
|
|
6920
6920
|
/** Firebase web API key — a public client identifier, not a secret. */
|
|
6921
|
-
firebaseApiKey: true ? orUndefined("AIzaSyAcbScoToLCbdhVYMCilqvLn9f7RYsG2XU") : void 0
|
|
6921
|
+
firebaseApiKey: true ? orUndefined("AIzaSyAcbScoToLCbdhVYMCilqvLn9f7RYsG2XU") : void 0,
|
|
6922
|
+
/**
|
|
6923
|
+
* Public platform origin that serves the page widgets (`zanfia-page-forms.js`)
|
|
6924
|
+
* and their `/api/*` endpoints — what an embed snippet points at.
|
|
6925
|
+
*/
|
|
6926
|
+
platformUrl: true ? orUndefined("https://zanfia.co") : void 0
|
|
6922
6927
|
};
|
|
6928
|
+
function resolvePlatformUrl() {
|
|
6929
|
+
const raw = process.env.ZANFIA_PLATFORM_URL || backendDefaults.platformUrl || "https://zanfia.co";
|
|
6930
|
+
return raw.replace(/\/+$/, "");
|
|
6931
|
+
}
|
|
6923
6932
|
function readCredentials() {
|
|
6924
6933
|
try {
|
|
6925
6934
|
const raw = readFileSync(CREDENTIALS_FILE, "utf8");
|
|
@@ -7996,6 +8005,94 @@ function createMockClient() {
|
|
|
7996
8005
|
async getConversionTrackingHealth() {
|
|
7997
8006
|
return { since: (/* @__PURE__ */ new Date()).toISOString(), integrations: [] };
|
|
7998
8007
|
},
|
|
8008
|
+
async getIntegrationsOverview() {
|
|
8009
|
+
return {
|
|
8010
|
+
emailClients: [],
|
|
8011
|
+
metaConversionApi: [],
|
|
8012
|
+
conversionTrackingIntegrationsCount: 0,
|
|
8013
|
+
invoicesClients: [],
|
|
8014
|
+
payments: {
|
|
8015
|
+
stripe: false,
|
|
8016
|
+
payU: false,
|
|
8017
|
+
tpay: false,
|
|
8018
|
+
payNow: false,
|
|
8019
|
+
przelewy24: false,
|
|
8020
|
+
paypal: false,
|
|
8021
|
+
dodo: false,
|
|
8022
|
+
manual: null
|
|
8023
|
+
},
|
|
8024
|
+
sms: { defaultProvider: null, twilio: null, smsapi: null }
|
|
8025
|
+
};
|
|
8026
|
+
},
|
|
8027
|
+
async connectEmailClient() {
|
|
8028
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8029
|
+
},
|
|
8030
|
+
async disconnectEmailClient() {
|
|
8031
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8032
|
+
},
|
|
8033
|
+
async refreshEmailClientLists() {
|
|
8034
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8035
|
+
},
|
|
8036
|
+
async connectMetaConversionApi() {
|
|
8037
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8038
|
+
},
|
|
8039
|
+
async disconnectMetaConversionApi() {
|
|
8040
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8041
|
+
},
|
|
8042
|
+
async configureManualPayment() {
|
|
8043
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8044
|
+
},
|
|
8045
|
+
async disconnectManualPayment() {
|
|
8046
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8047
|
+
},
|
|
8048
|
+
async connectInvoicesClient() {
|
|
8049
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8050
|
+
},
|
|
8051
|
+
async disconnectInvoicesClient() {
|
|
8052
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8053
|
+
},
|
|
8054
|
+
async connectPaymentGateway() {
|
|
8055
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8056
|
+
},
|
|
8057
|
+
async disconnectPaymentGateway() {
|
|
8058
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8059
|
+
},
|
|
8060
|
+
async setTpayRecurringBlik() {
|
|
8061
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8062
|
+
},
|
|
8063
|
+
async connectTwilio() {
|
|
8064
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8065
|
+
},
|
|
8066
|
+
async disconnectTwilio() {
|
|
8067
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8068
|
+
},
|
|
8069
|
+
async connectSmsApi() {
|
|
8070
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8071
|
+
},
|
|
8072
|
+
async disconnectSmsApi() {
|
|
8073
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8074
|
+
},
|
|
8075
|
+
async refreshSmsApiSenders() {
|
|
8076
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8077
|
+
},
|
|
8078
|
+
async setDefaultSmsApiSender() {
|
|
8079
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8080
|
+
},
|
|
8081
|
+
async setDefaultSmsProvider() {
|
|
8082
|
+
throw new ApiError(501, "integrations are not available in mock mode");
|
|
8083
|
+
},
|
|
8084
|
+
async listConversionTrackingIntegrations() {
|
|
8085
|
+
return { defaultIntegrationIds: [], integrations: [] };
|
|
8086
|
+
},
|
|
8087
|
+
async upsertConversionTrackingIntegration() {
|
|
8088
|
+
throw new ApiError(501, "conversion-tracking integrations are not available in mock mode");
|
|
8089
|
+
},
|
|
8090
|
+
async removeConversionTrackingIntegration() {
|
|
8091
|
+
throw new ApiError(501, "conversion-tracking integrations are not available in mock mode");
|
|
8092
|
+
},
|
|
8093
|
+
async setDefaultConversionTracking() {
|
|
8094
|
+
throw new ApiError(501, "conversion-tracking integrations are not available in mock mode");
|
|
8095
|
+
},
|
|
7999
8096
|
async listCommunities() {
|
|
8000
8097
|
return mockBackend.listCommunities();
|
|
8001
8098
|
},
|
|
@@ -8246,6 +8343,9 @@ function createMockClient() {
|
|
|
8246
8343
|
async cancelEmailCampaign() {
|
|
8247
8344
|
throw new ApiError(501, "email campaigns are not available in mock mode");
|
|
8248
8345
|
},
|
|
8346
|
+
async resendEmailCampaign() {
|
|
8347
|
+
throw new ApiError(501, "email campaigns are not available in mock mode");
|
|
8348
|
+
},
|
|
8249
8349
|
async listCampaignRecipients() {
|
|
8250
8350
|
throw new ApiError(501, "email campaigns are not available in mock mode");
|
|
8251
8351
|
},
|
|
@@ -8441,6 +8541,15 @@ function createMockClient() {
|
|
|
8441
8541
|
async copyNewsletterIssues() {
|
|
8442
8542
|
throw new ApiError(501, "newsletter content is not available in mock mode");
|
|
8443
8543
|
},
|
|
8544
|
+
async sendNewsletterIssue() {
|
|
8545
|
+
throw new ApiError(501, "newsletter content is not available in mock mode");
|
|
8546
|
+
},
|
|
8547
|
+
async cancelNewsletterIssueSend() {
|
|
8548
|
+
throw new ApiError(501, "newsletter content is not available in mock mode");
|
|
8549
|
+
},
|
|
8550
|
+
async getNewsletterIssueSendReport() {
|
|
8551
|
+
throw new ApiError(501, "newsletter content is not available in mock mode");
|
|
8552
|
+
},
|
|
8444
8553
|
async getKbContent() {
|
|
8445
8554
|
throw new ApiError(501, "knowledge-base content is not available in mock mode");
|
|
8446
8555
|
},
|
|
@@ -8480,6 +8589,21 @@ function createMockClient() {
|
|
|
8480
8589
|
async getMediaTranscript() {
|
|
8481
8590
|
throw new ApiError(501, "media transcripts are not available in mock mode");
|
|
8482
8591
|
},
|
|
8592
|
+
async listMediaCaptions() {
|
|
8593
|
+
throw new ApiError(501, "video captions are not available in mock mode");
|
|
8594
|
+
},
|
|
8595
|
+
async uploadMediaCaption() {
|
|
8596
|
+
throw new ApiError(501, "video captions are not available in mock mode");
|
|
8597
|
+
},
|
|
8598
|
+
async getMediaCaption() {
|
|
8599
|
+
throw new ApiError(501, "video captions are not available in mock mode");
|
|
8600
|
+
},
|
|
8601
|
+
async deleteMediaCaption() {
|
|
8602
|
+
throw new ApiError(501, "video captions are not available in mock mode");
|
|
8603
|
+
},
|
|
8604
|
+
async regenerateMediaCaption() {
|
|
8605
|
+
throw new ApiError(501, "video captions are not available in mock mode");
|
|
8606
|
+
},
|
|
8483
8607
|
async generateAIImage() {
|
|
8484
8608
|
throw new ApiError(501, "AI image generation is not available in mock mode");
|
|
8485
8609
|
},
|
|
@@ -8609,6 +8733,12 @@ function createMockClient() {
|
|
|
8609
8733
|
async listSignupFormSubmissions() {
|
|
8610
8734
|
return { submissions: [] };
|
|
8611
8735
|
},
|
|
8736
|
+
async listLiveRooms() {
|
|
8737
|
+
return { rooms: [] };
|
|
8738
|
+
},
|
|
8739
|
+
async getLiveRoomAttendance() {
|
|
8740
|
+
throw new ApiError(501, "live rooms are not available in mock mode");
|
|
8741
|
+
},
|
|
8612
8742
|
async call(method, pathTemplate) {
|
|
8613
8743
|
throw new ApiError(501, `${method} ${pathTemplate} is not available in mock mode`);
|
|
8614
8744
|
},
|
|
@@ -8933,7 +9063,10 @@ function createHttpClient(config) {
|
|
|
8933
9063
|
"GET",
|
|
8934
9064
|
productId ? `/checkouts?productId=${encodeURIComponent(productId)}` : "/checkouts"
|
|
8935
9065
|
),
|
|
8936
|
-
getCheckout: (id) => request(
|
|
9066
|
+
getCheckout: (id, options) => request(
|
|
9067
|
+
"GET",
|
|
9068
|
+
options?.full ? `/checkouts/${encodeURIComponent(id)}?include=full` : `/checkouts/${encodeURIComponent(id)}`
|
|
9069
|
+
),
|
|
8937
9070
|
createCheckout: (input) => request("POST", "/checkouts", input),
|
|
8938
9071
|
updateCheckout: (id, input) => request("PATCH", `/checkouts/${encodeURIComponent(id)}`, input),
|
|
8939
9072
|
publishCheckout: (id, active) => request("POST", `/checkouts/${encodeURIComponent(id)}/publish`, { active }),
|
|
@@ -9195,6 +9328,33 @@ function createHttpClient(config) {
|
|
|
9195
9328
|
"GET",
|
|
9196
9329
|
`/conversion-tracking/health${sinceDays !== void 0 ? `?sinceDays=${sinceDays}` : ""}`
|
|
9197
9330
|
),
|
|
9331
|
+
getIntegrationsOverview: () => request("GET", "/integrations"),
|
|
9332
|
+
connectEmailClient: (input) => request("POST", "/integrations/email-clients", input),
|
|
9333
|
+
disconnectEmailClient: (client, url) => request("POST", "/integrations/email-clients/disconnect", { client, url }),
|
|
9334
|
+
refreshEmailClientLists: (input) => request("POST", "/integrations/email-clients/refresh-lists", input),
|
|
9335
|
+
connectMetaConversionApi: (input) => request("PUT", "/integrations/meta-conversion-api", input),
|
|
9336
|
+
disconnectMetaConversionApi: (integrationId) => request("DELETE", `/integrations/meta-conversion-api/${encodeURIComponent(integrationId)}`),
|
|
9337
|
+
configureManualPayment: (input) => request("PUT", "/integrations/payments/manual", input),
|
|
9338
|
+
disconnectManualPayment: () => request("DELETE", "/integrations/payments/manual"),
|
|
9339
|
+
connectInvoicesClient: (input) => request("POST", "/integrations/invoices-clients", input),
|
|
9340
|
+
disconnectInvoicesClient: (client, id) => request("POST", "/integrations/invoices-clients/disconnect", { client, id }),
|
|
9341
|
+
connectPaymentGateway: (input) => request("POST", "/integrations/payments/gateways", input),
|
|
9342
|
+
disconnectPaymentGateway: (gateway) => request("POST", "/integrations/payments/gateways/disconnect", { gateway }),
|
|
9343
|
+
setTpayRecurringBlik: (enabled) => request("PUT", "/integrations/payments/tpay/recurring-blik", { enabled }),
|
|
9344
|
+
connectTwilio: (input) => request("PUT", "/integrations/sms/twilio", input),
|
|
9345
|
+
disconnectTwilio: () => request("DELETE", "/integrations/sms/twilio"),
|
|
9346
|
+
connectSmsApi: (input) => request("PUT", "/integrations/sms/smsapi", input),
|
|
9347
|
+
disconnectSmsApi: () => request("DELETE", "/integrations/sms/smsapi"),
|
|
9348
|
+
refreshSmsApiSenders: () => request("POST", "/integrations/sms/smsapi/refresh-senders"),
|
|
9349
|
+
setDefaultSmsApiSender: (sender) => request("PUT", "/integrations/sms/smsapi/default-sender", { sender }),
|
|
9350
|
+
setDefaultSmsProvider: (provider) => request("PUT", "/integrations/sms/default-provider", { provider }),
|
|
9351
|
+
listConversionTrackingIntegrations: () => request("GET", "/conversion-tracking/integrations"),
|
|
9352
|
+
upsertConversionTrackingIntegration: (input) => request("PUT", "/conversion-tracking/integrations", input),
|
|
9353
|
+
removeConversionTrackingIntegration: (client, integrationId) => request(
|
|
9354
|
+
"DELETE",
|
|
9355
|
+
`/conversion-tracking/integrations/${encodeURIComponent(client)}/${encodeURIComponent(integrationId)}`
|
|
9356
|
+
),
|
|
9357
|
+
setDefaultConversionTracking: (integrationIds) => request("PUT", "/conversion-tracking/defaults", { integrationIds: [...integrationIds] }),
|
|
9198
9358
|
statsProduct: (productId, currency) => request("GET", `/products/${encodeURIComponent(productId)}/stats${statsQuery({ currency })}`),
|
|
9199
9359
|
listCommunities: () => communityRequest("GET", "/v1/communities"),
|
|
9200
9360
|
listCommunityChannels: (communityId) => communityRequest("GET", `/v1/communities/${encodeURIComponent(communityId)}/channels`),
|
|
@@ -9375,6 +9535,7 @@ function createHttpClient(config) {
|
|
|
9375
9535
|
scheduledAt !== void 0 ? { scheduledAt } : {}
|
|
9376
9536
|
),
|
|
9377
9537
|
cancelEmailCampaign: (id) => request("POST", `/campaigns/${encodeURIComponent(id)}/cancel`),
|
|
9538
|
+
resendEmailCampaign: (id, target) => request("POST", `/campaigns/${encodeURIComponent(id)}/resend`, target ? { target } : {}),
|
|
9378
9539
|
listCampaignRecipients: (id, options) => {
|
|
9379
9540
|
const params = new URLSearchParams();
|
|
9380
9541
|
if (options?.status) params.set("status", options.status);
|
|
@@ -9534,6 +9695,19 @@ function createHttpClient(config) {
|
|
|
9534
9695
|
`/products/${encodeURIComponent(productId)}/newsletter/issues/${encodeURIComponent(issueId)}/draft`
|
|
9535
9696
|
),
|
|
9536
9697
|
copyNewsletterIssues: (productId, input) => request("POST", `/products/${encodeURIComponent(productId)}/newsletter/issues/copy`, input),
|
|
9698
|
+
sendNewsletterIssue: (productId, issueId, input) => request(
|
|
9699
|
+
"POST",
|
|
9700
|
+
`/products/${encodeURIComponent(productId)}/newsletter/issues/${encodeURIComponent(issueId)}/send`,
|
|
9701
|
+
input
|
|
9702
|
+
),
|
|
9703
|
+
cancelNewsletterIssueSend: (productId, issueId) => request(
|
|
9704
|
+
"POST",
|
|
9705
|
+
`/products/${encodeURIComponent(productId)}/newsletter/issues/${encodeURIComponent(issueId)}/send/cancel`
|
|
9706
|
+
),
|
|
9707
|
+
getNewsletterIssueSendReport: (productId, issueId) => request(
|
|
9708
|
+
"GET",
|
|
9709
|
+
`/products/${encodeURIComponent(productId)}/newsletter/issues/${encodeURIComponent(issueId)}/send/report`
|
|
9710
|
+
),
|
|
9537
9711
|
// Knowledge-base product content (addressed by product id).
|
|
9538
9712
|
getKbContent: (productId) => request("GET", `/products/${encodeURIComponent(productId)}/kb`),
|
|
9539
9713
|
getKbArticle: (productId, articleId) => request(
|
|
@@ -9574,6 +9748,17 @@ function createHttpClient(config) {
|
|
|
9574
9748
|
"GET",
|
|
9575
9749
|
`/media/${encodeURIComponent(mediaId)}/transcript${includeWords ? "?includeWords=true" : ""}`
|
|
9576
9750
|
),
|
|
9751
|
+
listMediaCaptions: (mediaId) => request("GET", `/media/${encodeURIComponent(mediaId)}/captions`),
|
|
9752
|
+
uploadMediaCaption: (mediaId, input) => request("POST", `/media/${encodeURIComponent(mediaId)}/captions`, input),
|
|
9753
|
+
getMediaCaption: (mediaId, srclang) => request(
|
|
9754
|
+
"GET",
|
|
9755
|
+
`/media/${encodeURIComponent(mediaId)}/captions/${encodeURIComponent(srclang)}`
|
|
9756
|
+
),
|
|
9757
|
+
deleteMediaCaption: (mediaId, srclang) => request(
|
|
9758
|
+
"DELETE",
|
|
9759
|
+
`/media/${encodeURIComponent(mediaId)}/captions/${encodeURIComponent(srclang)}`
|
|
9760
|
+
),
|
|
9761
|
+
regenerateMediaCaption: (mediaId) => request("POST", `/media/${encodeURIComponent(mediaId)}/captions/regenerate`, {}),
|
|
9577
9762
|
// AI image generation + on-demand transcription (both spend credits / minutes).
|
|
9578
9763
|
generateAIImage: (input) => request("POST", "/media/ai-images", input),
|
|
9579
9764
|
transcribeMedia: (mediaId, input) => request("POST", `/media/${encodeURIComponent(mediaId)}/transcribe`, input),
|
|
@@ -9713,6 +9898,12 @@ function createHttpClient(config) {
|
|
|
9713
9898
|
"GET",
|
|
9714
9899
|
`/forms/${encodeURIComponent(id)}/submissions${limit !== void 0 ? `?limit=${limit}` : ""}`
|
|
9715
9900
|
),
|
|
9901
|
+
// Live rooms live on the Workspace API (api-v1), read only.
|
|
9902
|
+
listLiveRooms: () => request("GET", "/live-rooms"),
|
|
9903
|
+
getLiveRoomAttendance: (roomId, session) => request(
|
|
9904
|
+
"GET",
|
|
9905
|
+
`/live-rooms/${encodeURIComponent(roomId)}/attendance${session !== void 0 ? `?session=${encodeURIComponent(session)}` : ""}`
|
|
9906
|
+
),
|
|
9716
9907
|
// Descriptor-generated commands (certificates, …) — Workspace API (api-v1).
|
|
9717
9908
|
call: (method, pathTemplate, params, query, body) => request(method, `${fillPathTemplate(pathTemplate, params)}${toQuery(query)}`, body),
|
|
9718
9909
|
// Referral / affiliate programs live on the Workspace API (api-v1).
|
|
@@ -9819,6 +10010,14 @@ function parseNumberOption(flag, format) {
|
|
|
9819
10010
|
return n;
|
|
9820
10011
|
};
|
|
9821
10012
|
}
|
|
10013
|
+
var WORKFLOW_KINDS = ["transactional", "marketing"];
|
|
10014
|
+
function parseWorkflowKind(value) {
|
|
10015
|
+
const kind = value.trim().toLowerCase();
|
|
10016
|
+
if (!WORKFLOW_KINDS.includes(kind)) {
|
|
10017
|
+
throw new Error(`Invalid --kind: ${value} (expected ${WORKFLOW_KINDS.join(" or ")})`);
|
|
10018
|
+
}
|
|
10019
|
+
return kind;
|
|
10020
|
+
}
|
|
9822
10021
|
function parseCommaList(flag) {
|
|
9823
10022
|
return (value) => {
|
|
9824
10023
|
const items = [
|
|
@@ -10222,9 +10421,133 @@ function registerCertificatesCommands(program2) {
|
|
|
10222
10421
|
});
|
|
10223
10422
|
}
|
|
10224
10423
|
|
|
10424
|
+
// src/commands/_generated/blog.ts
|
|
10425
|
+
var listBlogPostsColumns = ["id", "slug", "title", "status", "publishedAt", "updatedAt"];
|
|
10426
|
+
function registerBlogCommands(program2) {
|
|
10427
|
+
const blog = program2.command("blog");
|
|
10428
|
+
blog.description(
|
|
10429
|
+
"The workspace blog (Your site \u2192 Blog): Markdown posts served at /blog on the store host; drafts are visible to the team only"
|
|
10430
|
+
);
|
|
10431
|
+
blog.command("list").summary("List blog posts").description(
|
|
10432
|
+
"Every post of the workspace blog (drafts and published), newest change first: id, slug (`/blog/{slug}` on the store host), title, excerpt, cover, tags, status and dates. No body - use get-blog-post for the Markdown. The id source for every other blog tool."
|
|
10433
|
+
).action(async (_options, command) => {
|
|
10434
|
+
const flags = getGlobalFlags(command);
|
|
10435
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
10436
|
+
const params = {};
|
|
10437
|
+
const result = await client.call("GET", "/blog-posts", params, void 0, void 0);
|
|
10438
|
+
const rows = tableRowsOf(result, "posts");
|
|
10439
|
+
emit(flags, result ?? {}, () => printTable(rows, listBlogPostsColumns));
|
|
10440
|
+
});
|
|
10441
|
+
blog.command("create").summary("Create a blog post").description(
|
|
10442
|
+
"Creates a blog post as a DRAFT (or published with `publish: true` when it has a body). The body is Markdown. A slug the caller sets must be free (409) and not `feed`; a slug derived from the title takes the next free numeric suffix. Returns the full post. Requires the blog early access on the workspace (403 otherwise)."
|
|
10443
|
+
).requiredOption("--title <value>", "Post title (max 200 characters).").option(
|
|
10444
|
+
"--slug <value>",
|
|
10445
|
+
"Derived from the title when omitted; a taken derived slug gets a numeric suffix."
|
|
10446
|
+
).option("--content <value>", "The body as Markdown (headings, lists, links, images by URL).").option(
|
|
10447
|
+
"--excerpt <value>",
|
|
10448
|
+
"Short plain-text summary (list, RSS, meta description); derived from the body on publish when empty."
|
|
10449
|
+
).option("--cover <value>", "Cover image URL (Media Library).").option("--tags <a,b,...>", "Lowercase labels, at most 10.", parseCommaList("--tags")).option("--seo <json|file>", "seo", parseJsonObjectFileOrInline("--seo")).option("--publish", "Publish right away (needs a non-empty body); default false = draft.").option("--no-publish", "Send `publish: false` explicitly.").action(async (options, command) => {
|
|
10450
|
+
const flags = getGlobalFlags(command);
|
|
10451
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
10452
|
+
const params = {};
|
|
10453
|
+
const payload = {
|
|
10454
|
+
title: options.title,
|
|
10455
|
+
slug: options.slug,
|
|
10456
|
+
content: options.content,
|
|
10457
|
+
excerpt: options.excerpt,
|
|
10458
|
+
coverImageUrl: options.cover,
|
|
10459
|
+
tags: options.tags,
|
|
10460
|
+
seo: options.seo,
|
|
10461
|
+
publish: options.publish
|
|
10462
|
+
};
|
|
10463
|
+
const result = await client.call("POST", "/blog-posts", params, void 0, payload);
|
|
10464
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
10465
|
+
});
|
|
10466
|
+
blog.command("get").summary("Blog post detail").description(
|
|
10467
|
+
"One blog post with its Markdown body and SEO overrides (title, description, noindex). 404 when unknown."
|
|
10468
|
+
).argument("<id>", "Blog post id (`post_\u2026`) from list-blog-posts.").action(async (id, _options, command) => {
|
|
10469
|
+
const flags = getGlobalFlags(command);
|
|
10470
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
10471
|
+
const params = {
|
|
10472
|
+
postId: id
|
|
10473
|
+
};
|
|
10474
|
+
const result = await client.call("GET", "/blog-posts/:postId", params, void 0, void 0);
|
|
10475
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
10476
|
+
});
|
|
10477
|
+
blog.command("update").summary("Update a blog post").description(
|
|
10478
|
+
"Partial update of a blog post: title, slug (must be free, 409), Markdown body, excerpt, cover (null removes it), tags, SEO (whole section replaced). A published post keeps serving the new content at once. Returns the full post."
|
|
10479
|
+
).argument("<id>", "Blog post id (`post_\u2026`) from list-blog-posts.").option("--title <value>", "title").option(
|
|
10480
|
+
"--slug <value>",
|
|
10481
|
+
"Public path segment (`/blog/{slug}`): lowercase letters, digits and hyphens."
|
|
10482
|
+
).option("--content <value>", "The body as Markdown (headings, lists, links, images by URL).").option(
|
|
10483
|
+
"--excerpt <value>",
|
|
10484
|
+
"Short plain-text summary (list, RSS, meta description); derived from the body on publish when empty."
|
|
10485
|
+
).option("--cover <value>", "Cover image URL; null removes the cover.").option("--tags <a,b,...>", "Lowercase labels, at most 10.", parseCommaList("--tags")).option(
|
|
10486
|
+
"--seo <json|file>",
|
|
10487
|
+
"Replaces the whole SEO section.",
|
|
10488
|
+
parseJsonObjectFileOrInline("--seo")
|
|
10489
|
+
).action(async (id, options, command) => {
|
|
10490
|
+
const flags = getGlobalFlags(command);
|
|
10491
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
10492
|
+
const params = {
|
|
10493
|
+
postId: id
|
|
10494
|
+
};
|
|
10495
|
+
const payload = {
|
|
10496
|
+
title: options.title,
|
|
10497
|
+
slug: options.slug,
|
|
10498
|
+
content: options.content,
|
|
10499
|
+
excerpt: options.excerpt,
|
|
10500
|
+
coverImageUrl: options.cover,
|
|
10501
|
+
tags: options.tags,
|
|
10502
|
+
seo: options.seo
|
|
10503
|
+
};
|
|
10504
|
+
const result = await client.call("PATCH", "/blog-posts/:postId", params, void 0, payload);
|
|
10505
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
10506
|
+
});
|
|
10507
|
+
blog.command("publish").summary("Publish or unpublish a blog post").description(
|
|
10508
|
+
"Publishes a post at `/blog/{slug}` (needs a non-empty body, 400 otherwise; the excerpt is derived from the body when empty) or takes it back to draft. The first publication date is the post's date and survives unpublish / republish. Reversible. Returns the full post."
|
|
10509
|
+
).argument("<id>", "Blog post id (`post_\u2026`) from list-blog-posts.").option("--publish", "true = publish, false = back to draft.").action(async (id, options, command) => {
|
|
10510
|
+
const flags = getGlobalFlags(command);
|
|
10511
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
10512
|
+
const params = {
|
|
10513
|
+
postId: id
|
|
10514
|
+
};
|
|
10515
|
+
const payload = {
|
|
10516
|
+
publish: options.publish
|
|
10517
|
+
};
|
|
10518
|
+
const result = await client.call(
|
|
10519
|
+
"POST",
|
|
10520
|
+
"/blog-posts/:postId/publish",
|
|
10521
|
+
params,
|
|
10522
|
+
void 0,
|
|
10523
|
+
payload
|
|
10524
|
+
);
|
|
10525
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
10526
|
+
});
|
|
10527
|
+
blog.command("delete").summary("Delete a blog post").description(
|
|
10528
|
+
"Deletes a blog post for good; a published one disappears from `/blog` at once. No undo - prefer unpublishing."
|
|
10529
|
+
).argument("<id>", "Blog post id (`post_\u2026`) from list-blog-posts.").option("--confirm", "required \u2014 acknowledge that you want to deletes the post for good").action(async (id, options, command) => {
|
|
10530
|
+
const flags = getGlobalFlags(command);
|
|
10531
|
+
requireConfirm(options.confirm, "deletes the post for good");
|
|
10532
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
10533
|
+
const params = {
|
|
10534
|
+
postId: id
|
|
10535
|
+
};
|
|
10536
|
+
const result = await client.call(
|
|
10537
|
+
"DELETE",
|
|
10538
|
+
"/blog-posts/:postId",
|
|
10539
|
+
params,
|
|
10540
|
+
void 0,
|
|
10541
|
+
void 0
|
|
10542
|
+
);
|
|
10543
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
10544
|
+
});
|
|
10545
|
+
}
|
|
10546
|
+
|
|
10225
10547
|
// src/commands/_generated/index.ts
|
|
10226
10548
|
function registerGeneratedCommands(program2) {
|
|
10227
10549
|
registerCertificatesCommands(program2);
|
|
10550
|
+
registerBlogCommands(program2);
|
|
10228
10551
|
}
|
|
10229
10552
|
|
|
10230
10553
|
// src/commands/ads/index.ts
|
|
@@ -11787,10 +12110,10 @@ function registerBrandCommands(program2) {
|
|
|
11787
12110
|
});
|
|
11788
12111
|
}
|
|
11789
12112
|
async function buildUpdatePatch(options) {
|
|
11790
|
-
const { readFile:
|
|
12113
|
+
const { readFile: readFile4 } = await import("fs/promises");
|
|
11791
12114
|
let patch = {};
|
|
11792
12115
|
if (options.jsonFile) {
|
|
11793
|
-
const raw = await
|
|
12116
|
+
const raw = await readFile4(options.jsonFile, "utf8");
|
|
11794
12117
|
const parsed = JSON.parse(raw);
|
|
11795
12118
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
11796
12119
|
throw new Error("--json-file must contain a JSON object with name/visual/editorial keys.");
|
|
@@ -11805,9 +12128,42 @@ async function buildUpdatePatch(options) {
|
|
|
11805
12128
|
}
|
|
11806
12129
|
|
|
11807
12130
|
// src/commands/bumps/index.ts
|
|
12131
|
+
var IMAGE_MEDIA_TYPE = "image";
|
|
12132
|
+
var CLEAR_MEDIA_VALUE = "none";
|
|
12133
|
+
var HTTP_URL_PATTERN = /^https?:\/\//i;
|
|
12134
|
+
var IMAGE_MIME_PREFIX = "image/";
|
|
12135
|
+
var JSON_PATCH_DESCRIPTION = "inline JSON object or .json path merged into the request body \u2014 for the deep `appearance` object (media {type, url, mediaId?}, backgroundColor, borderStyle, borderColor, borderWidth, ctaPosition, ctaSize, ctaBackgroundColor, ctaTextColor, ctaArrowPosition, ctaArrowColor, ctaArrowAnimation, imagePosition, imagePositionMobile, imageWidth). Explicit flags win over --json keys. Validated server-side.";
|
|
12136
|
+
var IMAGE_FLAG_DESCRIPTION = "image for the bump box: an https URL, a media library id (resolved via the media routes), or `none` to clear the media slot";
|
|
11808
12137
|
function formatAmount(amountCents, currency) {
|
|
11809
12138
|
return `${(amountCents / 100).toFixed(2)} ${currency}`;
|
|
11810
12139
|
}
|
|
12140
|
+
function parseBumpJsonPatch(value) {
|
|
12141
|
+
return parseJsonObjectFileOrInline("--json")(value);
|
|
12142
|
+
}
|
|
12143
|
+
async function resolveImageMedia(client, value) {
|
|
12144
|
+
const trimmed = value.trim();
|
|
12145
|
+
if (trimmed.toLowerCase() === CLEAR_MEDIA_VALUE) return null;
|
|
12146
|
+
if (HTTP_URL_PATTERN.test(trimmed)) {
|
|
12147
|
+
return { type: IMAGE_MEDIA_TYPE, url: trimmed, mediaId: null };
|
|
12148
|
+
}
|
|
12149
|
+
const { media } = await client.getMedia(trimmed);
|
|
12150
|
+
if (!media.mimeType.startsWith(IMAGE_MIME_PREFIX)) {
|
|
12151
|
+
throw new Error(
|
|
12152
|
+
`--image: media ${trimmed} is ${media.mimeType}, not an image. Pass an image media id / URL, or set a video slot via --json {"appearance":{"media":{...}}}.`
|
|
12153
|
+
);
|
|
12154
|
+
}
|
|
12155
|
+
return { type: IMAGE_MEDIA_TYPE, url: media.url, mediaId: media.id };
|
|
12156
|
+
}
|
|
12157
|
+
function mergeBumpInput(jsonPatch, flagInput, media) {
|
|
12158
|
+
const merged = { ...jsonPatch, ...flagInput };
|
|
12159
|
+
for (const [key, value] of Object.entries(flagInput)) {
|
|
12160
|
+
if (value === void 0) delete merged[key];
|
|
12161
|
+
}
|
|
12162
|
+
const appearance = media !== void 0 ? { ...jsonPatch.appearance ?? {}, media } : jsonPatch.appearance;
|
|
12163
|
+
if (appearance !== void 0) merged.appearance = appearance;
|
|
12164
|
+
else delete merged.appearance;
|
|
12165
|
+
return merged;
|
|
12166
|
+
}
|
|
11811
12167
|
function parsePriceIds(value) {
|
|
11812
12168
|
const ids = value.split(",").map((id) => id.trim()).filter((id) => id.length > 0);
|
|
11813
12169
|
if (ids.length === 0) {
|
|
@@ -11834,39 +12190,52 @@ function registerBumpsCommands(program2) {
|
|
|
11834
12190
|
);
|
|
11835
12191
|
});
|
|
11836
12192
|
});
|
|
11837
|
-
bumps.command("create").description("Create an order bump from an existing product price (on its default offer)").requiredOption("--product <productId>", "product the bump adds").requiredOption("--price <priceId>", "price id on the product's default offer").requiredOption("--name <name>", "bump headline").option("--description <text>", "bump description").option("--cta <text>", "call-to-action text").action(
|
|
12193
|
+
bumps.command("create").description("Create an order bump from an existing product price (on its default offer)").requiredOption("--product <productId>", "product the bump adds").requiredOption("--price <priceId>", "price id on the product's default offer").requiredOption("--name <name>", "bump headline").option("--description <text>", "bump description").option("--cta <text>", "call-to-action text").option("--image <urlOrMediaId>", IMAGE_FLAG_DESCRIPTION).option("--json <fileOrJson>", JSON_PATCH_DESCRIPTION).action(
|
|
11838
12194
|
async (options, command) => {
|
|
11839
12195
|
const flags = getGlobalFlags(command);
|
|
11840
12196
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11841
|
-
const
|
|
11842
|
-
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
|
|
11847
|
-
|
|
12197
|
+
const jsonPatch = options.json !== void 0 ? parseBumpJsonPatch(options.json) : {};
|
|
12198
|
+
const media = options.image !== void 0 ? await resolveImageMedia(client, options.image) : void 0;
|
|
12199
|
+
const input = mergeBumpInput(
|
|
12200
|
+
jsonPatch,
|
|
12201
|
+
{
|
|
12202
|
+
productId: options.product,
|
|
12203
|
+
priceId: options.price,
|
|
12204
|
+
name: options.name,
|
|
12205
|
+
description: options.description,
|
|
12206
|
+
ctaText: options.cta
|
|
12207
|
+
},
|
|
12208
|
+
media
|
|
12209
|
+
);
|
|
12210
|
+
const result = await client.createOrderBump(input);
|
|
11848
12211
|
emit(flags, result, () => {
|
|
11849
12212
|
process.stdout.write(`Created order bump ${result.orderBumpId} \u2014 ${options.name}
|
|
11850
12213
|
`);
|
|
11851
12214
|
});
|
|
11852
12215
|
}
|
|
11853
12216
|
);
|
|
11854
|
-
bumps.command("update").description("Update an order bump").argument("<bumpId>", "order bump id").option("--name <name>", "new headline").option("--description <text>", "new description").option("--cta <text>", "new call-to-action text").option("--product <productId>", "point the bump at a different product").option("--price <priceId>", "price id on the (new) product's default offer").action(
|
|
12217
|
+
bumps.command("update").description("Update an order bump").argument("<bumpId>", "order bump id").option("--name <name>", "new headline").option("--description <text>", "new description").option("--cta <text>", "new call-to-action text").option("--product <productId>", "point the bump at a different product").option("--price <priceId>", "price id on the (new) product's default offer").option("--image <urlOrMediaId>", IMAGE_FLAG_DESCRIPTION).option("--json <fileOrJson>", JSON_PATCH_DESCRIPTION).action(
|
|
11855
12218
|
async (bumpId, options, command) => {
|
|
11856
12219
|
const flags = getGlobalFlags(command);
|
|
11857
|
-
const
|
|
11858
|
-
|
|
11859
|
-
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
11864
|
-
|
|
12220
|
+
const jsonPatch = options.json !== void 0 ? parseBumpJsonPatch(options.json) : {};
|
|
12221
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12222
|
+
const media = options.image !== void 0 ? await resolveImageMedia(client, options.image) : void 0;
|
|
12223
|
+
const input = mergeBumpInput(
|
|
12224
|
+
jsonPatch,
|
|
12225
|
+
{
|
|
12226
|
+
name: options.name,
|
|
12227
|
+
description: options.description,
|
|
12228
|
+
ctaText: options.cta,
|
|
12229
|
+
productId: options.product,
|
|
12230
|
+
priceId: options.price
|
|
12231
|
+
},
|
|
12232
|
+
media
|
|
12233
|
+
);
|
|
12234
|
+
if (Object.keys(input).length === 0) {
|
|
11865
12235
|
throw new Error(
|
|
11866
|
-
"Nothing to update \u2014 pass at least one of --name, --description, --cta, --product, --price."
|
|
12236
|
+
"Nothing to update \u2014 pass at least one of --name, --description, --cta, --product, --price, --image, or a --json patch."
|
|
11867
12237
|
);
|
|
11868
12238
|
}
|
|
11869
|
-
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11870
12239
|
const result = await client.updateOrderBump(bumpId, input);
|
|
11871
12240
|
emit(flags, result, () => {
|
|
11872
12241
|
process.stdout.write(`Updated order bump ${result.orderBumpId}
|
|
@@ -11967,13 +12336,15 @@ function resolveSender(options) {
|
|
|
11967
12336
|
};
|
|
11968
12337
|
}
|
|
11969
12338
|
function resolveTheme(options) {
|
|
11970
|
-
if (options.backgroundColor === void 0 && options.contentBackgroundColor === void 0 && options.linkColor === void 0) {
|
|
12339
|
+
if (options.backgroundColor === void 0 && options.contentBackgroundColor === void 0 && options.linkColor === void 0 && options.buttonColor === void 0 && options.buttonTextColor === void 0) {
|
|
11971
12340
|
return void 0;
|
|
11972
12341
|
}
|
|
11973
12342
|
return {
|
|
11974
12343
|
...options.backgroundColor !== void 0 ? { backgroundColor: options.backgroundColor } : {},
|
|
11975
12344
|
...options.contentBackgroundColor !== void 0 ? { contentBackgroundColor: options.contentBackgroundColor } : {},
|
|
11976
|
-
...options.linkColor !== void 0 ? { linkColor: options.linkColor } : {}
|
|
12345
|
+
...options.linkColor !== void 0 ? { linkColor: options.linkColor } : {},
|
|
12346
|
+
...options.buttonColor !== void 0 ? { buttonColor: options.buttonColor } : {},
|
|
12347
|
+
...options.buttonTextColor !== void 0 ? { buttonTextColor: options.buttonTextColor } : {}
|
|
11977
12348
|
};
|
|
11978
12349
|
}
|
|
11979
12350
|
function resolveAudience(options) {
|
|
@@ -12086,7 +12457,10 @@ function registerCampaignsCommands(program2) {
|
|
|
12086
12457
|
).option("--template-id <id>", "look template the Markdown body renders into").option("--from-name <name>", "From display name").option("--reply-to <email>", "Reply-To address (requires --from-name)").option(
|
|
12087
12458
|
"--from-email <email>",
|
|
12088
12459
|
"custom From on the workspace's verified sending domain (requires --from-name); omit for the platform sender"
|
|
12089
|
-
).option("--segment <segmentId>", "send to a saved audience segment (re-resolved at send time)").option("--all-subscribed", "send to every subscribed client (the default audience)").option("--background-color <hex>", "theme: page background behind the content column").option("--content-background-color <hex>", "theme: content column (card) background").option("--link-color <hex>", "theme: link color inside the content column").
|
|
12460
|
+
).option("--segment <segmentId>", "send to a saved audience segment (re-resolved at send time)").option("--all-subscribed", "send to every subscribed client (the default audience)").option("--background-color <hex>", "theme: page background behind the content column").option("--content-background-color <hex>", "theme: content column (card) background").option("--link-color <hex>", "theme: link color inside the content column").option(
|
|
12461
|
+
"--button-color <hex>",
|
|
12462
|
+
"theme: background of buttons without their own color (wins over the look default)"
|
|
12463
|
+
).option("--button-text-color <hex>", "theme: text color of buttons without their own color").action(
|
|
12090
12464
|
async (options, command) => {
|
|
12091
12465
|
const flags = getGlobalFlags(command);
|
|
12092
12466
|
const contentMarkdown = resolveContent(options);
|
|
@@ -12114,7 +12488,10 @@ function registerCampaignsCommands(program2) {
|
|
|
12114
12488
|
});
|
|
12115
12489
|
}
|
|
12116
12490
|
);
|
|
12117
|
-
campaigns.command("update").description("Update a DRAFT campaign (campaigns past draft are immutable except cancel)").argument("<id>", "campaign id").option("--name <name>", "new internal label").option("--subject <subject>", "new email subject line").option("--preheader <text>", "new preview text").option("--content <markdown>", "new campaign body as Markdown (replaces the whole body)").option("--content-file <path>", "read the new campaign body (Markdown) from a file").option("--html-file <path>", "read the new HTML document of a designed e-mail from a file").option("--template-id <id>", "look template for the body (`none` = the default shell)").option("--from-name <name>", "new From display name").option("--reply-to <email>", "new Reply-To address (requires --from-name)").option("--from-email <email>", "new custom From address (requires --from-name)").option("--segment <segmentId>", "switch the audience to a saved segment").option("--all-subscribed", "switch the audience to every subscribed client").option("--background-color <hex>", "theme: page background behind the content column").option("--content-background-color <hex>", "theme: content column (card) background").option("--link-color <hex>", "theme: link color inside the content column").
|
|
12491
|
+
campaigns.command("update").description("Update a DRAFT campaign (campaigns past draft are immutable except cancel)").argument("<id>", "campaign id").option("--name <name>", "new internal label").option("--subject <subject>", "new email subject line").option("--preheader <text>", "new preview text").option("--content <markdown>", "new campaign body as Markdown (replaces the whole body)").option("--content-file <path>", "read the new campaign body (Markdown) from a file").option("--html-file <path>", "read the new HTML document of a designed e-mail from a file").option("--template-id <id>", "look template for the body (`none` = the default shell)").option("--from-name <name>", "new From display name").option("--reply-to <email>", "new Reply-To address (requires --from-name)").option("--from-email <email>", "new custom From address (requires --from-name)").option("--segment <segmentId>", "switch the audience to a saved segment").option("--all-subscribed", "switch the audience to every subscribed client").option("--background-color <hex>", "theme: page background behind the content column").option("--content-background-color <hex>", "theme: content column (card) background").option("--link-color <hex>", "theme: link color inside the content column").option(
|
|
12492
|
+
"--button-color <hex>",
|
|
12493
|
+
"theme: background of buttons without their own color (wins over the look default)"
|
|
12494
|
+
).option("--button-text-color <hex>", "theme: text color of buttons without their own color").action(
|
|
12118
12495
|
async (id, options, command) => {
|
|
12119
12496
|
const flags = getGlobalFlags(command);
|
|
12120
12497
|
const input = {};
|
|
@@ -12201,6 +12578,28 @@ function registerCampaignsCommands(program2) {
|
|
|
12201
12578
|
const { stats } = result.campaign;
|
|
12202
12579
|
process.stdout.write(
|
|
12203
12580
|
`Cancelled campaign ${result.campaign.id} \u2014 ${stats.sent} recipient${stats.sent === 1 ? " was" : "s were"} already sent to before the cancel.
|
|
12581
|
+
`
|
|
12582
|
+
);
|
|
12583
|
+
});
|
|
12584
|
+
});
|
|
12585
|
+
campaigns.command("resend").description(
|
|
12586
|
+
"Follow up a SENT campaign: builds a segment over its non-openers (or non-clickers) and creates a DRAFT copy aimed at it - nothing is sent; review and `campaigns send` the draft"
|
|
12587
|
+
).argument("<id>", "campaign id (must be sent)").option("--target <target>", "not-opened (default) | not-clicked").action(async (id, options, command) => {
|
|
12588
|
+
const flags = getGlobalFlags(command);
|
|
12589
|
+
let target;
|
|
12590
|
+
if (options.target !== void 0) {
|
|
12591
|
+
if (options.target !== "not-opened" && options.target !== "not-clicked") {
|
|
12592
|
+
process.stderr.write("--target must be not-opened or not-clicked\n");
|
|
12593
|
+
process.exitCode = 1;
|
|
12594
|
+
return;
|
|
12595
|
+
}
|
|
12596
|
+
target = options.target;
|
|
12597
|
+
}
|
|
12598
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12599
|
+
const result = await client.resendEmailCampaign(id, target);
|
|
12600
|
+
emit(flags, result, () => {
|
|
12601
|
+
process.stdout.write(
|
|
12602
|
+
`Draft ${result.campaign.id} "${result.campaign.name}" created for segment "${result.segment.name}" (${result.segment.id}${result.segmentReused ? ", reused" : ""}). Nothing was sent - review it, then \`campaigns send <id> --confirm\`.
|
|
12204
12603
|
`
|
|
12205
12604
|
);
|
|
12206
12605
|
});
|
|
@@ -12401,7 +12800,19 @@ function registerCheckoutSettingsCommands(program2) {
|
|
|
12401
12800
|
).option("--lang <language>", "language to write, e.g. 'pl' (defaults to 'pl')").option("--terms-url <url|none>", "terms & conditions link (none = clear)").option("--privacy-policy-url <url|none>", "privacy policy link (none = clear)").option(
|
|
12402
12801
|
"--claim <text|none>",
|
|
12403
12802
|
"information-clause / consent-checkbox claim text (none = clear)"
|
|
12404
|
-
).option("--terms-content <text>", "inline terms & conditions text").option("--privacy-policy-content <text>", "inline privacy policy text").option(
|
|
12803
|
+
).option("--terms-content <text>", "inline terms & conditions text").option("--privacy-policy-content <text>", "inline privacy policy text").option(
|
|
12804
|
+
"--google-analytics-id <id|none>",
|
|
12805
|
+
"LEGACY Google Analytics id (none = clear) \u2014 prefer `integrations add ga4`"
|
|
12806
|
+
).option(
|
|
12807
|
+
"--facebook-pixel-id <id|none>",
|
|
12808
|
+
"LEGACY Facebook Pixel id (none = clear) \u2014 Meta pixel is managed in the dashboard"
|
|
12809
|
+
).option(
|
|
12810
|
+
"--gtm-id <id|none>",
|
|
12811
|
+
"LEGACY Google Tag Manager id (none = clear) \u2014 prefer `integrations add gtm`"
|
|
12812
|
+
).option(
|
|
12813
|
+
"--tiktok-pixel-id <id|none>",
|
|
12814
|
+
"LEGACY TikTok Pixel id (none = clear) \u2014 prefer `integrations add tiktok`"
|
|
12815
|
+
).option("--promote-net-prices <on|off>", "display net prices prominently").option(
|
|
12405
12816
|
"--price-variants-placement <belowDescription|aboveDescription|none>",
|
|
12406
12817
|
"where the price list renders relative to the description"
|
|
12407
12818
|
).option(
|
|
@@ -12536,6 +12947,39 @@ var AIImageQuality = /* @__PURE__ */ ((AIImageQuality2) => {
|
|
|
12536
12947
|
AIImageQuality2["Ultra"] = "4K";
|
|
12537
12948
|
return AIImageQuality2;
|
|
12538
12949
|
})(AIImageQuality || {});
|
|
12950
|
+
var MANAGED_CONVERSION_TRACKING_CLIENTS = [
|
|
12951
|
+
"googleAnalytics",
|
|
12952
|
+
"googleTagManager",
|
|
12953
|
+
"tiktok",
|
|
12954
|
+
"x",
|
|
12955
|
+
"linkedin",
|
|
12956
|
+
"microsoftClarity",
|
|
12957
|
+
"hotjar"
|
|
12958
|
+
];
|
|
12959
|
+
var CONNECTABLE_EMAIL_CLIENTS = [
|
|
12960
|
+
"ActiveCampaign",
|
|
12961
|
+
"ConvertKit",
|
|
12962
|
+
"MailerLite",
|
|
12963
|
+
"MailChimp",
|
|
12964
|
+
"GetResponse",
|
|
12965
|
+
"Pipedrive"
|
|
12966
|
+
];
|
|
12967
|
+
var INVOICES_CLIENTS = [
|
|
12968
|
+
"fakturownia",
|
|
12969
|
+
"ifirma",
|
|
12970
|
+
"inFakt",
|
|
12971
|
+
"wFirmaV2",
|
|
12972
|
+
"ingKsiegowosc",
|
|
12973
|
+
"stripe"
|
|
12974
|
+
];
|
|
12975
|
+
var CREDENTIAL_PAYMENT_GATEWAYS = [
|
|
12976
|
+
"payu",
|
|
12977
|
+
"tpay",
|
|
12978
|
+
"przelewy24",
|
|
12979
|
+
"paynow",
|
|
12980
|
+
"paypal",
|
|
12981
|
+
"dodo"
|
|
12982
|
+
];
|
|
12539
12983
|
var COMMUNITY_EVENT_LOCATION_TYPES = [
|
|
12540
12984
|
"online",
|
|
12541
12985
|
"inPerson"
|
|
@@ -13618,6 +14062,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
13618
14062
|
let created = 0;
|
|
13619
14063
|
let existing = 0;
|
|
13620
14064
|
let invalid = 0;
|
|
14065
|
+
let overLimit = 0;
|
|
13621
14066
|
for (let offset = 0; offset < contacts.length; offset += IMPORT_CHUNK_SIZE) {
|
|
13622
14067
|
const chunk2 = contacts.slice(offset, offset + IMPORT_CHUNK_SIZE);
|
|
13623
14068
|
if (!flags.json && contacts.length > IMPORT_CHUNK_SIZE) {
|
|
@@ -13631,13 +14076,18 @@ Next page: --cursor ${page.nextCursor}
|
|
|
13631
14076
|
created += page.created;
|
|
13632
14077
|
existing += page.existing;
|
|
13633
14078
|
invalid += page.invalid;
|
|
14079
|
+
overLimit += page.overLimit ?? 0;
|
|
13634
14080
|
}
|
|
13635
|
-
const summary = { results, created, existing, invalid };
|
|
14081
|
+
const summary = { results, created, existing, invalid, overLimit };
|
|
13636
14082
|
emit(flags, summary, () => {
|
|
13637
14083
|
process.stdout.write(
|
|
13638
|
-
`Imported ${contacts.length} row${contacts.length === 1 ? "" : "s"} from ${csvFile}: ${created} created, ${existing} already existed, ${invalid} invalid
|
|
13639
|
-
`
|
|
14084
|
+
`Imported ${contacts.length} row${contacts.length === 1 ? "" : "s"} from ${csvFile}: ${created} created, ${existing} already existed, ${invalid} invalid` + (overLimit > 0 ? `, ${overLimit} over the plan's contacts limit` : "") + ".\n"
|
|
13640
14085
|
);
|
|
14086
|
+
if (overLimit > 0) {
|
|
14087
|
+
process.stdout.write(
|
|
14088
|
+
"The plan's contacts limit is reached \u2014 upgrade the plan to import the remaining rows.\n"
|
|
14089
|
+
);
|
|
14090
|
+
}
|
|
13641
14091
|
const problems = results.filter((r) => r.status !== "created" && r.status !== "exists");
|
|
13642
14092
|
if (problems.length > 0) {
|
|
13643
14093
|
process.stdout.write("\nRows that were not imported:\n");
|
|
@@ -14727,11 +15177,11 @@ Next page: --cursor ${result.nextCursor}
|
|
|
14727
15177
|
member.command("mute").description("Mute (timeout) a member for N minutes (1..40320 = 28 days)").argument("<communityId>", "community id").argument("<memberId>", "member id (wcid)").requiredOption("--minutes <n>", "mute duration in minutes (1..40320)").action(
|
|
14728
15178
|
async (communityId, memberId, options, command) => {
|
|
14729
15179
|
const flags = getGlobalFlags(command);
|
|
14730
|
-
const
|
|
14731
|
-
if (!Number.isInteger(
|
|
15180
|
+
const minutes2 = Number.parseInt(options.minutes, 10);
|
|
15181
|
+
if (!Number.isInteger(minutes2) || minutes2 < 1 || minutes2 > 40320) {
|
|
14732
15182
|
throw new Error(`Invalid --minutes: ${options.minutes} (expected an integer 1..40320).`);
|
|
14733
15183
|
}
|
|
14734
|
-
const result = await clientFor(flags).muteCommunityMember(communityId, memberId,
|
|
15184
|
+
const result = await clientFor(flags).muteCommunityMember(communityId, memberId, minutes2);
|
|
14735
15185
|
emit(flags, result, () => {
|
|
14736
15186
|
process.stdout.write(`Muted member ${memberId} until ${result.mutedUntil}.
|
|
14737
15187
|
`);
|
|
@@ -17067,6 +17517,10 @@ function printSignupFormDetail(form) {
|
|
|
17067
17517
|
`);
|
|
17068
17518
|
process.stdout.write(` Redirect: ${form.redirectUrl ?? "(success state on the form)"}
|
|
17069
17519
|
`);
|
|
17520
|
+
process.stdout.write(
|
|
17521
|
+
` After confirmation: ${form.confirmationRedirectUrl ?? "(platform confirmation page)"}
|
|
17522
|
+
`
|
|
17523
|
+
);
|
|
17070
17524
|
if (form.confirmationEmail) {
|
|
17071
17525
|
process.stdout.write(
|
|
17072
17526
|
` Confirmation email overrides: subject ${form.confirmationEmail.subject ?? "(default)"}, intro ${form.confirmationEmail.intro ?? "(default)"}
|
|
@@ -17086,6 +17540,36 @@ function printSignupFormDetail(form) {
|
|
|
17086
17540
|
`);
|
|
17087
17541
|
}
|
|
17088
17542
|
}
|
|
17543
|
+
var escapeHtmlAttribute = (value) => value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
17544
|
+
function buildFormSnippet(input) {
|
|
17545
|
+
const copy = input.lang === "pl" ? {
|
|
17546
|
+
email: "Adres e-mail",
|
|
17547
|
+
firstName: "Imi\u0119",
|
|
17548
|
+
submit: "Zapisuj\u0119 si\u0119",
|
|
17549
|
+
success: "Dzi\u0119kujemy! Sprawd\u017A skrzynk\u0119 i potwierd\u017A zapis.",
|
|
17550
|
+
error: "Nie uda\u0142o si\u0119 wys\u0142a\u0107. Spr\xF3buj ponownie."
|
|
17551
|
+
} : {
|
|
17552
|
+
email: "E-mail address",
|
|
17553
|
+
firstName: "First name",
|
|
17554
|
+
submit: "Sign me up",
|
|
17555
|
+
success: "Thank you! Check your inbox to confirm.",
|
|
17556
|
+
error: "Something went wrong. Please try again."
|
|
17557
|
+
};
|
|
17558
|
+
const redirect = input.redirectUrl ? ` data-zanfia-redirect="${escapeHtmlAttribute(input.redirectUrl)}"` : "";
|
|
17559
|
+
const firstName = input.firstName ? `
|
|
17560
|
+
<label>${copy.firstName}<br><input name="firstName" type="text" autocomplete="given-name"></label>` : "";
|
|
17561
|
+
return [
|
|
17562
|
+
`<form data-zanfia-form="${input.workspaceId}/${input.formId}"${redirect}>`,
|
|
17563
|
+
` <label>${copy.email}<br><input name="email" type="email" required autocomplete="email"></label>${firstName}`,
|
|
17564
|
+
` <input name="website" type="text" tabindex="-1" autocomplete="off" aria-hidden="true" style="position:absolute;left:-9999px;opacity:0">`,
|
|
17565
|
+
` <button type="submit">${copy.submit}</button>`,
|
|
17566
|
+
` <div data-zanfia-success hidden>${copy.success}</div>`,
|
|
17567
|
+
` <div data-zanfia-error hidden>${copy.error}</div>`,
|
|
17568
|
+
`</form>`,
|
|
17569
|
+
`<script src="${input.platformUrl}/zanfia-page-forms.js" defer></script>`,
|
|
17570
|
+
""
|
|
17571
|
+
].join("\n");
|
|
17572
|
+
}
|
|
17089
17573
|
async function buildFormUpdateInput(client, formId, options) {
|
|
17090
17574
|
const input = options.json !== void 0 ? parseFormJsonPatch(options.json) : {};
|
|
17091
17575
|
if (options.name !== void 0) input.name = options.name;
|
|
@@ -17094,6 +17578,9 @@ async function buildFormUpdateInput(client, formId, options) {
|
|
|
17094
17578
|
input.optInMode = parseChoice2(options.optIn, OPT_IN_MODES, "--opt-in");
|
|
17095
17579
|
}
|
|
17096
17580
|
if (options.redirect !== void 0) input.redirectUrl = orNull3(options.redirect);
|
|
17581
|
+
if (options.confirmationRedirect !== void 0) {
|
|
17582
|
+
input.confirmationRedirectUrl = orNull3(options.confirmationRedirect);
|
|
17583
|
+
}
|
|
17097
17584
|
let current;
|
|
17098
17585
|
const fetchCurrent = async () => {
|
|
17099
17586
|
current ??= await client.getSignupForm(formId);
|
|
@@ -17156,7 +17643,10 @@ function registerFormsCommands(program2) {
|
|
|
17156
17643
|
});
|
|
17157
17644
|
forms.command("create").description(
|
|
17158
17645
|
"Create a standalone signup form (double opt-in unless --opt-in single). The full confirmation-email copy is set with `forms update --json`"
|
|
17159
|
-
).requiredOption("--name <name>", "internal form name").option("--tags <ids>", "comma-separated client-tag ids applied on (confirmed) submission").option("--opt-in <mode>", `opt-in mode (${OPT_IN_MODES.join(" | ")}; default double)`).option("--redirect <url>", "thank-you redirect after a successful submit").option(
|
|
17646
|
+
).requiredOption("--name <name>", "internal form name").option("--tags <ids>", "comma-separated client-tag ids applied on (confirmed) submission").option("--opt-in <mode>", `opt-in mode (${OPT_IN_MODES.join(" | ")}; default double)`).option("--redirect <url>", "thank-you redirect after a successful submit").option(
|
|
17647
|
+
"--confirmation-redirect <url>",
|
|
17648
|
+
"where the confirmation e-mail click lands (double opt-in): lead magnet, OTO, calendar"
|
|
17649
|
+
).option("--header <text>", "config: header text shown above the form").option("--description <text>", "config: description under the header").option("--button-text <text>", "config: submit button label").option("--success-message <text>", "config: message shown after a successful submit").option("--variant <variant>", `config: layout variant (${FORM_VARIANTS.join(" | ")})`).option("--phone <mode>", `config: phone number field (${PHONE_FIELD_MODES.join(" | ")})`).option(
|
|
17160
17650
|
"--sms-consent <on|off>",
|
|
17161
17651
|
"config: marketing-SMS consent checkbox (needs the phone field)"
|
|
17162
17652
|
).option(
|
|
@@ -17174,7 +17664,8 @@ function registerFormsCommands(program2) {
|
|
|
17174
17664
|
tagIds: options.tags !== void 0 ? parseCsv2(options.tags) : void 0,
|
|
17175
17665
|
config,
|
|
17176
17666
|
...optInMode !== void 0 ? { optInMode } : {},
|
|
17177
|
-
...options.redirect !== void 0 ? { redirectUrl: options.redirect } : {}
|
|
17667
|
+
...options.redirect !== void 0 ? { redirectUrl: options.redirect } : {},
|
|
17668
|
+
...options.confirmationRedirect !== void 0 ? { confirmationRedirectUrl: options.confirmationRedirect } : {}
|
|
17178
17669
|
});
|
|
17179
17670
|
emit(flags, result, () => {
|
|
17180
17671
|
process.stdout.write(
|
|
@@ -17186,7 +17677,10 @@ function registerFormsCommands(program2) {
|
|
|
17186
17677
|
);
|
|
17187
17678
|
forms.command("update").description(
|
|
17188
17679
|
"Update a standalone form. The embed config is REPLACED IN FULL by the API \u2014 flat config flags are merged over the current config for you; a --json `config` object is sent as the full replacement (flat flags still win). `none` clears nullable fields."
|
|
17189
|
-
).argument("<id>", "form id").option("--name <name>", "internal form name").option("--tags <ids>", "comma-separated client-tag ids (full replace; empty string clears)").option("--opt-in <mode>", `opt-in mode (${OPT_IN_MODES.join(" | ")})`).option("--redirect <url|none>", "thank-you redirect after submit; `none` clears it").option(
|
|
17680
|
+
).argument("<id>", "form id").option("--name <name>", "internal form name").option("--tags <ids>", "comma-separated client-tag ids (full replace; empty string clears)").option("--opt-in <mode>", `opt-in mode (${OPT_IN_MODES.join(" | ")})`).option("--redirect <url|none>", "thank-you redirect after submit; `none` clears it").option(
|
|
17681
|
+
"--confirmation-redirect <url|none>",
|
|
17682
|
+
"where the confirmation e-mail click lands (double opt-in); `none` restores the platform page"
|
|
17683
|
+
).option("--confirmation-subject <text>", "double-opt-in confirmation email subject override").option("--confirmation-intro <text>", "double-opt-in confirmation email intro override").option("--clear-confirmation-email", "restore the workspace default confirmation copy").option("--header <text>", "config: header text shown above the form").option("--description <text>", "config: description under the header").option("--button-text <text>", "config: submit button label").option("--success-message <text>", "config: message shown after a successful submit").option("--variant <variant>", `config: layout variant (${FORM_VARIANTS.join(" | ")})`).option("--phone <mode>", `config: phone number field (${PHONE_FIELD_MODES.join(" | ")})`).option(
|
|
17190
17684
|
"--sms-consent <on|off>",
|
|
17191
17685
|
"config: marketing-SMS consent checkbox (needs the phone field)"
|
|
17192
17686
|
).option(
|
|
@@ -17221,6 +17715,45 @@ function registerFormsCommands(program2) {
|
|
|
17221
17715
|
`);
|
|
17222
17716
|
});
|
|
17223
17717
|
});
|
|
17718
|
+
forms.command("snippet").description(
|
|
17719
|
+
"Print the HTML of a native signup form for ANY website (WordPress, Webflow, plain HTML): the form markup + the platform script. Paste it into the page; tags, opt-in mode and the thank-you redirect come from the form settings. Edit the markup freely, keeping the field names and the data-zanfia-* hooks."
|
|
17720
|
+
).argument("<id>", "form id").option("--lang <pl|en>", "language of the placeholder copy (default pl)").option("--first-name", "include a first-name field").option(
|
|
17721
|
+
"--redirect <url>",
|
|
17722
|
+
"thank-you URL for THIS embed (overrides the form setting on the page)"
|
|
17723
|
+
).option(
|
|
17724
|
+
"--platform-url <origin>",
|
|
17725
|
+
"origin that serves zanfia-page-forms.js (default: this build\u2019s platform, or $ZANFIA_PLATFORM_URL)"
|
|
17726
|
+
).action(
|
|
17727
|
+
async (id, options, command) => {
|
|
17728
|
+
const flags = getGlobalFlags(command);
|
|
17729
|
+
const lang = options.lang !== void 0 ? parseChoice2(options.lang, ["pl", "en"], "--lang") : "pl";
|
|
17730
|
+
if (options.redirect !== void 0 && !/^https?:\/\/\S+$/.test(options.redirect)) {
|
|
17731
|
+
throw new Error("--redirect must be an absolute http(s) URL.");
|
|
17732
|
+
}
|
|
17733
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
17734
|
+
const [form, workspace2] = await Promise.all([
|
|
17735
|
+
client.getSignupForm(id),
|
|
17736
|
+
client.getWorkspaceSettings()
|
|
17737
|
+
]);
|
|
17738
|
+
if (form.submitAction === "freeProductSignup") {
|
|
17739
|
+
throw new Error(
|
|
17740
|
+
`Form ${id} is product-bound; the native snippet works for standalone signup forms only.`
|
|
17741
|
+
);
|
|
17742
|
+
}
|
|
17743
|
+
const platformUrl = (options.platformUrl ?? resolvePlatformUrl()).replace(/\/+$/, "");
|
|
17744
|
+
const snippet = buildFormSnippet({
|
|
17745
|
+
workspaceId: workspace2.workspaceId,
|
|
17746
|
+
formId: form.id,
|
|
17747
|
+
platformUrl,
|
|
17748
|
+
lang,
|
|
17749
|
+
redirectUrl: options.redirect ?? form.redirectUrl ?? null,
|
|
17750
|
+
firstName: options.firstName === true
|
|
17751
|
+
});
|
|
17752
|
+
emit(flags, { formId: form.id, workspaceId: workspace2.workspaceId, html: snippet }, () => {
|
|
17753
|
+
process.stdout.write(snippet);
|
|
17754
|
+
});
|
|
17755
|
+
}
|
|
17756
|
+
);
|
|
17224
17757
|
forms.command("submissions").description("List a form's submissions (newest first)").argument("<id>", "form id").option("--limit <n>", "maximum rows to return").action(async (id, options, command) => {
|
|
17225
17758
|
const flags = getGlobalFlags(command);
|
|
17226
17759
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -17258,6 +17791,7 @@ var FUNNEL_TEMPLATE_IDS = [
|
|
|
17258
17791
|
];
|
|
17259
17792
|
var EVENT_MODES = ["live", "on_demand"];
|
|
17260
17793
|
var SCHEDULER_UNAVAILABLE_STATUS2 = 503;
|
|
17794
|
+
var FUNNEL_NOT_READY_STATUS = 409;
|
|
17261
17795
|
function requireConfirm6(confirm, what) {
|
|
17262
17796
|
if (!confirm) {
|
|
17263
17797
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
@@ -17273,6 +17807,16 @@ function withSchedulerHint2(error) {
|
|
|
17273
17807
|
}
|
|
17274
17808
|
throw error;
|
|
17275
17809
|
}
|
|
17810
|
+
function withLiveGateHint(error, funnelId) {
|
|
17811
|
+
if (error instanceof ApiError && error.status === FUNNEL_NOT_READY_STATUS) {
|
|
17812
|
+
throw new ApiError(
|
|
17813
|
+
error.status,
|
|
17814
|
+
error.apiMessage,
|
|
17815
|
+
`The funnel was not changed. Run \`funnels get ${funnelId}\` to see each step; publish the page, activate the checkout, or generate the entry page (\`funnels generate-page\`), then retry.`
|
|
17816
|
+
);
|
|
17817
|
+
}
|
|
17818
|
+
throw error;
|
|
17819
|
+
}
|
|
17276
17820
|
function parsePositiveInt10(value, flag) {
|
|
17277
17821
|
const n = Number.parseInt(value, 10);
|
|
17278
17822
|
if (!Number.isInteger(n) || n <= 0) {
|
|
@@ -17449,19 +17993,19 @@ function registerFunnelsCommands(program2) {
|
|
|
17449
17993
|
const flags = getGlobalFlags(command);
|
|
17450
17994
|
const input = buildFunnelUpdateInput(options);
|
|
17451
17995
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
17452
|
-
const result = await client.updateFunnel(id, input);
|
|
17996
|
+
const result = await client.updateFunnel(id, input).catch((error) => withLiveGateHint(error, id));
|
|
17453
17997
|
emit(flags, result, () => {
|
|
17454
17998
|
process.stdout.write(`Updated funnel ${id}.
|
|
17455
17999
|
`);
|
|
17456
18000
|
});
|
|
17457
18001
|
});
|
|
17458
18002
|
funnels.command("status").description(
|
|
17459
|
-
"Change a funnel's status: draft (building), live (running \u2014 the entry step link is meant to be shared), archived (retired \u2014 the reversible substitute for deletion). All transitions are reversible."
|
|
18003
|
+
"Change a funnel's status: draft (building), live (running \u2014 the entry step link is meant to be shared), archived (retired \u2014 the reversible substitute for deletion). Going live is refused (409 funnel-not-ready) while the entry step is not ready or a referenced page is unpublished / checkout inactive. All transitions are reversible."
|
|
17460
18004
|
).argument("<id>", "funnel id").requiredOption("--set <status>", `new status (${FUNNEL_STATUSES.join(" | ")})`).action(async (id, options, command) => {
|
|
17461
18005
|
const flags = getGlobalFlags(command);
|
|
17462
18006
|
const status = parseChoice3(options.set, FUNNEL_STATUSES, "--set");
|
|
17463
18007
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
17464
|
-
const result = await client.updateFunnel(id, { status });
|
|
18008
|
+
const result = await client.updateFunnel(id, { status }).catch((error) => withLiveGateHint(error, id));
|
|
17465
18009
|
emit(flags, result, () => {
|
|
17466
18010
|
process.stdout.write(`Funnel ${id} status set to ${status}.
|
|
17467
18011
|
`);
|
|
@@ -17530,7 +18074,10 @@ function registerFunnelsCommands(program2) {
|
|
|
17530
18074
|
});
|
|
17531
18075
|
funnels.command("from-template").description(
|
|
17532
18076
|
"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) or --event-mode on_demand. Requires --confirm."
|
|
17533
|
-
).argument("<templateId>", `template id (${FUNNEL_TEMPLATE_IDS.join(" | ")})`).requiredOption("--product <productId>", "the product the funnel sells or delivers").requiredOption("--lang <en|pl>", "language of the generated pages and emails \u2014 the creator's").option("--name <name>", "custom funnel name; omit for the recipe default").option("--event-start <iso>", "webinar: event start as ISO 8601 WITH offset (live)").option("--event-timezone <tz>", "webinar: IANA timezone, e.g. Europe/Warsaw").option("--event-duration <minutes>", "webinar: event length in minutes (default 60)").option("--event-mode <mode>", `webinar: ${EVENT_MODES.join(" | ")} (on_demand = autowebinar)`).option(
|
|
18077
|
+
).argument("<templateId>", `template id (${FUNNEL_TEMPLATE_IDS.join(" | ")})`).requiredOption("--product <productId>", "the product the funnel sells or delivers").requiredOption("--lang <en|pl>", "language of the generated pages and emails \u2014 the creator's").option("--name <name>", "custom funnel name; omit for the recipe default").option("--event-start <iso>", "webinar: event start as ISO 8601 WITH offset (live)").option("--event-timezone <tz>", "webinar: IANA timezone, e.g. Europe/Warsaw").option("--event-duration <minutes>", "webinar: event length in minutes (default 60)").option("--event-mode <mode>", `webinar: ${EVENT_MODES.join(" | ")} (on_demand = autowebinar)`).option(
|
|
18078
|
+
"--page-template <id>",
|
|
18079
|
+
"design template for every generated page (a page-template id, e.g. course-fresh); thank-you steps take the companion"
|
|
18080
|
+
).option("--confirm", "required \u2014 acknowledge that AI credits will be consumed").action(
|
|
17534
18081
|
async (templateId, options, command) => {
|
|
17535
18082
|
const flags = getGlobalFlags(command);
|
|
17536
18083
|
requireConfirm6(options.confirm, "create the funnel and generate its pages (AI credits)");
|
|
@@ -17538,7 +18085,8 @@ function registerFunnelsCommands(program2) {
|
|
|
17538
18085
|
templateId: parseChoice3(templateId, FUNNEL_TEMPLATE_IDS, "<templateId>"),
|
|
17539
18086
|
productId: options.product,
|
|
17540
18087
|
lang: parseChoice3(options.lang, FUNNEL_LANGS, "--lang"),
|
|
17541
|
-
name: options.name
|
|
18088
|
+
name: options.name,
|
|
18089
|
+
...options.pageTemplate ? { pageTemplateId: options.pageTemplate } : {}
|
|
17542
18090
|
};
|
|
17543
18091
|
const webinar = {
|
|
17544
18092
|
eventStartAt: options.eventStart,
|
|
@@ -17651,42 +18199,968 @@ function registerFunnelsCommands(program2) {
|
|
|
17651
18199
|
});
|
|
17652
18200
|
}
|
|
17653
18201
|
|
|
17654
|
-
// src/commands/
|
|
17655
|
-
|
|
17656
|
-
var
|
|
17657
|
-
|
|
17658
|
-
|
|
17659
|
-
|
|
17660
|
-
|
|
17661
|
-
|
|
17662
|
-
|
|
17663
|
-
|
|
17664
|
-
|
|
18202
|
+
// src/commands/integrations/connections.ts
|
|
18203
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
18204
|
+
var EMAIL_CLIENT_ALIASES = {
|
|
18205
|
+
ac: "ActiveCampaign",
|
|
18206
|
+
activecampaign: "ActiveCampaign",
|
|
18207
|
+
kit: "ConvertKit",
|
|
18208
|
+
convertkit: "ConvertKit",
|
|
18209
|
+
mailerlite: "MailerLite",
|
|
18210
|
+
mailchimp: "MailChimp",
|
|
18211
|
+
getresponse: "GetResponse",
|
|
18212
|
+
freshmail: "FreshMail",
|
|
18213
|
+
pipedrive: "Pipedrive"
|
|
18214
|
+
};
|
|
18215
|
+
function parseEmailClient(value) {
|
|
18216
|
+
const match = EMAIL_CLIENT_ALIASES[value.trim().toLowerCase()];
|
|
18217
|
+
if (!match) {
|
|
17665
18218
|
throw new Error(
|
|
17666
|
-
|
|
18219
|
+
`Unknown email client: ${value} (expected ${CONNECTABLE_EMAIL_CLIENTS.join(" | ")}; FreshMail is disconnect-only)`
|
|
17667
18220
|
);
|
|
17668
18221
|
}
|
|
17669
|
-
|
|
17670
|
-
|
|
17671
|
-
|
|
17672
|
-
|
|
17673
|
-
|
|
18222
|
+
return match;
|
|
18223
|
+
}
|
|
18224
|
+
function parseOnOff2(value, flag) {
|
|
18225
|
+
if (value === void 0) return void 0;
|
|
18226
|
+
if (value === "on") return true;
|
|
18227
|
+
if (value === "off") return false;
|
|
18228
|
+
throw new Error(`Invalid ${flag}: ${value} (expected on | off)`);
|
|
18229
|
+
}
|
|
18230
|
+
function requireConfirm7(confirm, what) {
|
|
18231
|
+
if (!confirm) {
|
|
18232
|
+
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
17674
18233
|
}
|
|
17675
|
-
|
|
17676
|
-
|
|
17677
|
-
|
|
17678
|
-
|
|
18234
|
+
}
|
|
18235
|
+
function readSecret(label, value, fromStdin, fromEnv) {
|
|
18236
|
+
const sources = [value !== void 0, fromStdin === true, fromEnv !== void 0].filter(Boolean);
|
|
18237
|
+
if (sources.length > 1) {
|
|
18238
|
+
throw new Error(`Pass ${label} through exactly one of the value, -stdin or -env forms.`);
|
|
17679
18239
|
}
|
|
17680
|
-
|
|
17681
|
-
"
|
|
17682
|
-
|
|
17683
|
-
|
|
17684
|
-
|
|
17685
|
-
)
|
|
17686
|
-
|
|
17687
|
-
|
|
17688
|
-
|
|
17689
|
-
|
|
18240
|
+
if (fromStdin) {
|
|
18241
|
+
const raw = readFileSync15(0, "utf8").trim();
|
|
18242
|
+
if (!raw) throw new Error(`${label}: nothing was read from stdin.`);
|
|
18243
|
+
return raw;
|
|
18244
|
+
}
|
|
18245
|
+
if (fromEnv !== void 0) {
|
|
18246
|
+
const raw = process.env[fromEnv]?.trim();
|
|
18247
|
+
if (!raw) throw new Error(`${label}: environment variable ${fromEnv} is empty or unset.`);
|
|
18248
|
+
return raw;
|
|
18249
|
+
}
|
|
18250
|
+
return value;
|
|
18251
|
+
}
|
|
18252
|
+
function printOverview(overview) {
|
|
18253
|
+
process.stdout.write("Email clients\n");
|
|
18254
|
+
if (overview.emailClients.length === 0) {
|
|
18255
|
+
process.stdout.write(" (none connected)\n");
|
|
18256
|
+
} else {
|
|
18257
|
+
printTable(
|
|
18258
|
+
overview.emailClients.map((account) => ({
|
|
18259
|
+
client: account.client,
|
|
18260
|
+
url: account.url,
|
|
18261
|
+
lists: account.listsCount,
|
|
18262
|
+
tags: account.tagsCount,
|
|
18263
|
+
updatedAt: account.updatedAt ?? account.createdAt
|
|
18264
|
+
})),
|
|
18265
|
+
["client", "url", "lists", "tags", "updatedAt"]
|
|
18266
|
+
);
|
|
18267
|
+
}
|
|
18268
|
+
process.stdout.write("Meta pixel / Conversions API\n");
|
|
18269
|
+
if (overview.metaConversionApi.length === 0) {
|
|
18270
|
+
process.stdout.write(" (none)\n");
|
|
18271
|
+
} else {
|
|
18272
|
+
printMetaEntries(overview.metaConversionApi);
|
|
18273
|
+
}
|
|
18274
|
+
process.stdout.write(
|
|
18275
|
+
`Managed pixel integrations (GA4 / GTM / \u2026): ${overview.conversionTrackingIntegrationsCount} \u2014 see \`integrations list\`
|
|
18276
|
+
`
|
|
18277
|
+
);
|
|
18278
|
+
process.stdout.write("Invoicing\n");
|
|
18279
|
+
if (overview.invoicesClients.length === 0) {
|
|
18280
|
+
process.stdout.write(" (none connected)\n");
|
|
18281
|
+
} else {
|
|
18282
|
+
printTable(
|
|
18283
|
+
overview.invoicesClients.map((account) => ({
|
|
18284
|
+
client: account.client,
|
|
18285
|
+
id: account.id,
|
|
18286
|
+
url: account.url
|
|
18287
|
+
})),
|
|
18288
|
+
["client", "id", "url"]
|
|
18289
|
+
);
|
|
18290
|
+
}
|
|
18291
|
+
const p = overview.payments;
|
|
18292
|
+
const on = (value) => value ? "on" : "off";
|
|
18293
|
+
process.stdout.write(
|
|
18294
|
+
`Payments: stripe=${on(p.stripe)} payU=${on(p.payU)} tpay=${on(p.tpay)} payNow=${on(p.payNow)} przelewy24=${on(p.przelewy24)} paypal=${on(p.paypal)} dodo=${on(p.dodo)} manual=${p.manual ? `on (${p.manual.name})` : "off"}
|
|
18295
|
+
`
|
|
18296
|
+
);
|
|
18297
|
+
}
|
|
18298
|
+
function printMetaEntries(entries) {
|
|
18299
|
+
printTable(
|
|
18300
|
+
entries.map((entry) => ({
|
|
18301
|
+
id: entry.id,
|
|
18302
|
+
name: entry.name,
|
|
18303
|
+
pixelId: entry.pixelId,
|
|
18304
|
+
serverSide: entry.serverSide ? "yes" : "",
|
|
18305
|
+
default: entry.isWorkspaceDefault ? "yes" : "",
|
|
18306
|
+
disabled: entry.disabled ? "yes" : "",
|
|
18307
|
+
events: [
|
|
18308
|
+
entry.trackPurchase ? "purchase" : null,
|
|
18309
|
+
entry.trackInitCheckout ? "initCheckout" : null,
|
|
18310
|
+
entry.trackAddToCart ? "addToCart" : null,
|
|
18311
|
+
entry.trackTrialStarted ? "trialStarted" : null
|
|
18312
|
+
].filter(Boolean).join(",")
|
|
18313
|
+
})),
|
|
18314
|
+
["id", "name", "pixelId", "serverSide", "default", "disabled", "events"]
|
|
18315
|
+
);
|
|
18316
|
+
}
|
|
18317
|
+
function printAccount(account, headline) {
|
|
18318
|
+
process.stdout.write(`${headline} ${account.client} ${account.url}
|
|
18319
|
+
`);
|
|
18320
|
+
process.stdout.write(` Lists: ${account.listsCount} Tags: ${account.tagsCount}
|
|
18321
|
+
`);
|
|
18322
|
+
}
|
|
18323
|
+
function buildEmailConnectInput(client, options) {
|
|
18324
|
+
if (client === "FreshMail") {
|
|
18325
|
+
throw new Error(
|
|
18326
|
+
"FreshMail can no longer be connected (provider retired); existing accounts can only be disconnected."
|
|
18327
|
+
);
|
|
18328
|
+
}
|
|
18329
|
+
const secret = readSecret("--api-key", options.apiKey, options.apiKeyStdin, options.apiKeyEnv);
|
|
18330
|
+
if (!secret) throw new Error("--api-key (or --api-key-stdin / --api-key-env VAR) is required.");
|
|
18331
|
+
const input = { client, url: options.url };
|
|
18332
|
+
switch (client) {
|
|
18333
|
+
case "ConvertKit":
|
|
18334
|
+
input.apiSecret = secret;
|
|
18335
|
+
break;
|
|
18336
|
+
case "Pipedrive":
|
|
18337
|
+
input.apiSecret = secret;
|
|
18338
|
+
if (!options.tagsField || !options.listsField) {
|
|
18339
|
+
throw new Error("Pipedrive needs --tags-field and --lists-field (person field names).");
|
|
18340
|
+
}
|
|
18341
|
+
input.tagsFieldName = options.tagsField;
|
|
18342
|
+
input.listsFieldName = options.listsField;
|
|
18343
|
+
break;
|
|
18344
|
+
case "MailChimp":
|
|
18345
|
+
input.apiKey = secret;
|
|
18346
|
+
if (!options.serverPrefix) throw new Error("MailChimp needs --server-prefix (e.g. us21).");
|
|
18347
|
+
input.serverPrefix = options.serverPrefix;
|
|
18348
|
+
break;
|
|
18349
|
+
default:
|
|
18350
|
+
input.apiKey = secret;
|
|
18351
|
+
}
|
|
18352
|
+
return input;
|
|
18353
|
+
}
|
|
18354
|
+
function buildMetaInput(options, existing) {
|
|
18355
|
+
const name = options.name ?? existing?.name;
|
|
18356
|
+
const pixelId = options.pixelId ?? existing?.pixelId;
|
|
18357
|
+
if (!name) throw new Error("--name is required.");
|
|
18358
|
+
if (!pixelId) throw new Error("--pixel-id is required.");
|
|
18359
|
+
const token = readSecret(
|
|
18360
|
+
"--access-token",
|
|
18361
|
+
options.accessToken,
|
|
18362
|
+
options.accessTokenStdin,
|
|
18363
|
+
options.accessTokenEnv
|
|
18364
|
+
);
|
|
18365
|
+
return {
|
|
18366
|
+
...existing ? { id: existing.id } : {},
|
|
18367
|
+
name,
|
|
18368
|
+
pixelId,
|
|
18369
|
+
...token !== void 0 ? { accessToken: token } : {},
|
|
18370
|
+
trackPurchase: parseOnOff2(options.trackPurchase, "--track-purchase") ?? existing?.trackPurchase ?? true,
|
|
18371
|
+
trackInitCheckout: parseOnOff2(options.trackInitCheckout, "--track-init-checkout") ?? existing?.trackInitCheckout ?? true,
|
|
18372
|
+
trackAddToCart: parseOnOff2(options.trackAddToCart, "--track-add-to-cart") ?? existing?.trackAddToCart ?? true,
|
|
18373
|
+
trackTrialStarted: parseOnOff2(options.trackTrialStarted, "--track-trial-started") ?? existing?.trackTrialStarted ?? false
|
|
18374
|
+
};
|
|
18375
|
+
}
|
|
18376
|
+
function addMetaOptions(command) {
|
|
18377
|
+
return command.option("--name <name>", "creator-facing label").option("--pixel-id <id>", "Meta pixel / dataset id").option(
|
|
18378
|
+
"--access-token <token>",
|
|
18379
|
+
"Conversions API access token (prefer --access-token-stdin / -env)"
|
|
18380
|
+
).option("--access-token-stdin", "read the access token from stdin").option("--access-token-env <VAR>", "read the access token from an environment variable").option("--track-purchase <on|off>", "send Purchase (default on)").option("--track-init-checkout <on|off>", "send InitiateCheckout (default on)").option("--track-add-to-cart <on|off>", "send AddToCart (default on)").option("--track-trial-started <on|off>", "send TrialStarted (default off)");
|
|
18381
|
+
}
|
|
18382
|
+
function registerIntegrationConnectionCommands(integrations) {
|
|
18383
|
+
integrations.command("status").description(
|
|
18384
|
+
"What is connected: email clients, Meta pixel / Conversions API entries, invoicing, payment methods (needs integrations:read)"
|
|
18385
|
+
).action(async (_options, command) => {
|
|
18386
|
+
const flags = getGlobalFlags(command);
|
|
18387
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18388
|
+
const overview = await client.getIntegrationsOverview();
|
|
18389
|
+
emit(flags, overview, () => printOverview(overview));
|
|
18390
|
+
});
|
|
18391
|
+
const emailClients = integrations.command("email-clients").description(
|
|
18392
|
+
"Email-marketing accounts: ActiveCampaign, Kit (ConvertKit), MailerLite, MailChimp, GetResponse, Pipedrive. Keys are verified against the provider before saving and never printed."
|
|
18393
|
+
);
|
|
18394
|
+
emailClients.command("connect <client>").description(
|
|
18395
|
+
"Connect (or re-connect, same url) an account. Client: ac | kit | mailerlite | mailchimp | getresponse | pipedrive. Pass the key via --api-key-stdin or --api-key-env to keep it out of shell history."
|
|
18396
|
+
).requiredOption("--url <url>", "account / API base URL as the provider shows it").option("--api-key <key>", "API key (Kit / Pipedrive: the API secret)").option("--api-key-stdin", "read the key from stdin").option("--api-key-env <VAR>", "read the key from an environment variable").option("--server-prefix <prefix>", "MailChimp data-center prefix, e.g. us21").option("--tags-field <name>", "Pipedrive: person field used as tags").option("--lists-field <name>", "Pipedrive: person field used as lists").action(async (clientArg, options, command) => {
|
|
18397
|
+
const flags = getGlobalFlags(command);
|
|
18398
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18399
|
+
const input = buildEmailConnectInput(parseEmailClient(clientArg), options);
|
|
18400
|
+
const account = await client.connectEmailClient(input);
|
|
18401
|
+
emit(flags, account, () => printAccount(account, "Connected"));
|
|
18402
|
+
});
|
|
18403
|
+
emailClients.command("disconnect <client>").description(
|
|
18404
|
+
"Disconnect an account by url (irreversible, the stored key is deleted). Refused while a live newsletter / automation still uses it. Requires --confirm."
|
|
18405
|
+
).requiredOption("--url <url>", "account url from `integrations status`").option("--confirm", "required \u2014 acknowledges the key is deleted").action(
|
|
18406
|
+
async (clientArg, options, command) => {
|
|
18407
|
+
requireConfirm7(options.confirm, `disconnect ${clientArg} ${options.url}`);
|
|
18408
|
+
const flags = getGlobalFlags(command);
|
|
18409
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18410
|
+
const parsed = parseEmailClient(clientArg);
|
|
18411
|
+
const result = await client.disconnectEmailClient(parsed, options.url);
|
|
18412
|
+
emit(flags, { ...result, client: parsed, url: options.url }, () => {
|
|
18413
|
+
process.stdout.write(`Disconnected ${parsed} ${options.url}
|
|
18414
|
+
`);
|
|
18415
|
+
});
|
|
18416
|
+
}
|
|
18417
|
+
);
|
|
18418
|
+
emailClients.command("refresh <client>").description(
|
|
18419
|
+
"Re-fetch the lists + tags of a connected account with the stored key (not Pipedrive)"
|
|
18420
|
+
).requiredOption("--url <url>", "account url from `integrations status`").option("--server-prefix <prefix>", "MailChimp data-center prefix").action(
|
|
18421
|
+
async (clientArg, options, command) => {
|
|
18422
|
+
const flags = getGlobalFlags(command);
|
|
18423
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18424
|
+
const parsed = parseEmailClient(clientArg);
|
|
18425
|
+
if (parsed === "Pipedrive" || parsed === "FreshMail") {
|
|
18426
|
+
throw new Error(`${parsed} lists cannot be refreshed from the CLI.`);
|
|
18427
|
+
}
|
|
18428
|
+
const account = await client.refreshEmailClientLists({
|
|
18429
|
+
client: parsed,
|
|
18430
|
+
url: options.url,
|
|
18431
|
+
...options.serverPrefix ? { serverPrefix: options.serverPrefix } : {}
|
|
18432
|
+
});
|
|
18433
|
+
emit(flags, account, () => printAccount(account, "Refreshed"));
|
|
18434
|
+
}
|
|
18435
|
+
);
|
|
18436
|
+
const meta = integrations.command("meta-capi").description(
|
|
18437
|
+
"Manual Meta pixel + Conversions API entries (the connect-with-Facebook OAuth path stays in the dashboard)"
|
|
18438
|
+
);
|
|
18439
|
+
meta.command("list").description("Meta pixel / Conversions API entries").action(async (_options, command) => {
|
|
18440
|
+
const flags = getGlobalFlags(command);
|
|
18441
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18442
|
+
const overview = await client.getIntegrationsOverview();
|
|
18443
|
+
emit(flags, overview.metaConversionApi, () => printMetaEntries(overview.metaConversionApi));
|
|
18444
|
+
});
|
|
18445
|
+
addMetaOptions(
|
|
18446
|
+
meta.command("add").description(
|
|
18447
|
+
"Add a Meta pixel entry; with an access token the Conversions API (server-side events) is enabled \u2014 the token is validated against Meta before saving. The first tracking integration a workspace connects becomes the workspace default."
|
|
18448
|
+
)
|
|
18449
|
+
).action(async (options, command) => {
|
|
18450
|
+
const flags = getGlobalFlags(command);
|
|
18451
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18452
|
+
const entry = await client.connectMetaConversionApi(buildMetaInput(options, null));
|
|
18453
|
+
emit(flags, entry, () => printMetaEntries([entry]));
|
|
18454
|
+
});
|
|
18455
|
+
addMetaOptions(
|
|
18456
|
+
meta.command("update <integrationId>").description("Edit an entry \u2014 only the flags you pass change; an omitted token stays stored")
|
|
18457
|
+
).action(async (integrationId, options, command) => {
|
|
18458
|
+
const flags = getGlobalFlags(command);
|
|
18459
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18460
|
+
const overview = await client.getIntegrationsOverview();
|
|
18461
|
+
const existing = overview.metaConversionApi.find((entry2) => entry2.id === integrationId);
|
|
18462
|
+
if (!existing) {
|
|
18463
|
+
throw new Error(
|
|
18464
|
+
`No Meta Conversions API entry with id ${integrationId}. Run \`zanfia integrations meta-capi list\`.`
|
|
18465
|
+
);
|
|
18466
|
+
}
|
|
18467
|
+
const entry = await client.connectMetaConversionApi(buildMetaInput(options, existing));
|
|
18468
|
+
emit(flags, entry, () => printMetaEntries([entry]));
|
|
18469
|
+
});
|
|
18470
|
+
meta.command("remove <integrationId>").description("Remove an entry (irreversible). Requires --confirm.").option("--confirm", "required \u2014 acknowledges the entry is deleted").action(async (integrationId, options, command) => {
|
|
18471
|
+
requireConfirm7(options.confirm, `remove Meta entry ${integrationId}`);
|
|
18472
|
+
const flags = getGlobalFlags(command);
|
|
18473
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18474
|
+
const result = await client.disconnectMetaConversionApi(integrationId);
|
|
18475
|
+
emit(flags, { ...result, integrationId }, () => {
|
|
18476
|
+
process.stdout.write(`Removed Meta entry ${integrationId}
|
|
18477
|
+
`);
|
|
18478
|
+
});
|
|
18479
|
+
});
|
|
18480
|
+
const manual = integrations.command("manual-payment").description(
|
|
18481
|
+
"The manual / wire-transfer payment method (buyers pay by transfer, you approve the order)"
|
|
18482
|
+
);
|
|
18483
|
+
manual.command("set").description("Configure (create or update) the manual payment method").requiredOption("--name <name>", 'method name shown at checkout, e.g. "Przelew tradycyjny"').option("--instructions <text>", "plain-text payment instructions for the buyer").option("--instructions-file <path>", "read the instructions from a file").action(
|
|
18484
|
+
async (options, command) => {
|
|
18485
|
+
const instructions = options.instructions ?? (options.instructionsFile ? readFileSync15(options.instructionsFile, "utf8").trim() : void 0);
|
|
18486
|
+
if (!instructions) throw new Error("--instructions or --instructions-file is required.");
|
|
18487
|
+
const flags = getGlobalFlags(command);
|
|
18488
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18489
|
+
const method = await client.configureManualPayment({ name: options.name, instructions });
|
|
18490
|
+
emit(flags, method, () => {
|
|
18491
|
+
process.stdout.write(`Manual payment: ${method.name}
|
|
18492
|
+
${method.instructions}
|
|
18493
|
+
`);
|
|
18494
|
+
});
|
|
18495
|
+
}
|
|
18496
|
+
);
|
|
18497
|
+
manual.command("remove").description("Remove the manual payment method (idempotent). Requires --confirm.").option("--confirm", "required \u2014 buyers can no longer pick the method").action(async (options, command) => {
|
|
18498
|
+
requireConfirm7(options.confirm, "remove the manual payment method");
|
|
18499
|
+
const flags = getGlobalFlags(command);
|
|
18500
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18501
|
+
const result = await client.disconnectManualPayment();
|
|
18502
|
+
emit(flags, result, () => {
|
|
18503
|
+
process.stdout.write("Manual payment method removed\n");
|
|
18504
|
+
});
|
|
18505
|
+
});
|
|
18506
|
+
}
|
|
18507
|
+
|
|
18508
|
+
// src/commands/integrations/providers.ts
|
|
18509
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
18510
|
+
var INVOICES_ALIASES = {
|
|
18511
|
+
fakturownia: "fakturownia",
|
|
18512
|
+
ifirma: "ifirma",
|
|
18513
|
+
infakt: "inFakt",
|
|
18514
|
+
wfirma: "wFirmaV2",
|
|
18515
|
+
wfirmav2: "wFirmaV2",
|
|
18516
|
+
ing: "ingKsiegowosc",
|
|
18517
|
+
ingksiegowosc: "ingKsiegowosc",
|
|
18518
|
+
stripe: "stripe"
|
|
18519
|
+
};
|
|
18520
|
+
function parseInvoicesClient(value) {
|
|
18521
|
+
const match = INVOICES_ALIASES[value.trim().toLowerCase()];
|
|
18522
|
+
if (!match) {
|
|
18523
|
+
throw new Error(
|
|
18524
|
+
`Unknown invoicing client: ${value} (expected ${INVOICES_CLIENTS.join(" | ")})`
|
|
18525
|
+
);
|
|
18526
|
+
}
|
|
18527
|
+
return match;
|
|
18528
|
+
}
|
|
18529
|
+
function parseGateway(value) {
|
|
18530
|
+
const normalized = value.trim().toLowerCase().replace("p24", "przelewy24");
|
|
18531
|
+
const match = CREDENTIAL_PAYMENT_GATEWAYS.find((gateway) => gateway === normalized);
|
|
18532
|
+
if (!match) {
|
|
18533
|
+
throw new Error(
|
|
18534
|
+
`Unknown gateway: ${value} (expected ${CREDENTIAL_PAYMENT_GATEWAYS.join(" | ")}; Stripe connects in the dashboard, the transfer method is \`integrations manual-payment\`)`
|
|
18535
|
+
);
|
|
18536
|
+
}
|
|
18537
|
+
return match;
|
|
18538
|
+
}
|
|
18539
|
+
function parseEnum(value, allowed, flag) {
|
|
18540
|
+
const match = allowed.find((candidate) => candidate === value);
|
|
18541
|
+
if (!match) throw new Error(`Invalid ${flag}: ${value} (expected ${allowed.join(" | ")})`);
|
|
18542
|
+
return match;
|
|
18543
|
+
}
|
|
18544
|
+
function requireConfirm8(confirm, what) {
|
|
18545
|
+
if (!confirm) {
|
|
18546
|
+
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
18547
|
+
}
|
|
18548
|
+
}
|
|
18549
|
+
function readSecret2(label, value, fromStdin, fromEnv) {
|
|
18550
|
+
const sources = [value !== void 0, fromStdin === true, fromEnv !== void 0].filter(Boolean);
|
|
18551
|
+
if (sources.length > 1) {
|
|
18552
|
+
throw new Error(`Pass ${label} through exactly one of the value, -stdin or -env forms.`);
|
|
18553
|
+
}
|
|
18554
|
+
if (fromStdin) {
|
|
18555
|
+
const raw = readFileSync16(0, "utf8").trim();
|
|
18556
|
+
if (!raw) throw new Error(`${label}: nothing was read from stdin.`);
|
|
18557
|
+
return raw;
|
|
18558
|
+
}
|
|
18559
|
+
if (fromEnv !== void 0) {
|
|
18560
|
+
const raw = process.env[fromEnv]?.trim();
|
|
18561
|
+
if (!raw) throw new Error(`${label}: environment variable ${fromEnv} is empty or unset.`);
|
|
18562
|
+
return raw;
|
|
18563
|
+
}
|
|
18564
|
+
return value;
|
|
18565
|
+
}
|
|
18566
|
+
function printPayments(payments) {
|
|
18567
|
+
const on = (value) => value ? "on" : "off";
|
|
18568
|
+
process.stdout.write(
|
|
18569
|
+
`Payments: stripe=${on(payments.stripe)} payu=${on(payments.payU)} tpay=${on(payments.tpay)} paynow=${on(payments.payNow)} przelewy24=${on(payments.przelewy24)} paypal=${on(payments.paypal)} dodo=${on(payments.dodo)} manual=${payments.manual ? "on" : "off"}
|
|
18570
|
+
`
|
|
18571
|
+
);
|
|
18572
|
+
}
|
|
18573
|
+
function buildInvoicesInput(client, options) {
|
|
18574
|
+
const secret = readSecret2("--api-key", options.apiKey, options.apiKeyStdin, options.apiKeyEnv);
|
|
18575
|
+
const input = { client };
|
|
18576
|
+
if (options.id) input.id = options.id;
|
|
18577
|
+
if (options.name) input.accountName = options.name;
|
|
18578
|
+
if (options.url) input.url = options.url;
|
|
18579
|
+
if (options.departmentId) input.departmentId = options.departmentId;
|
|
18580
|
+
switch (client) {
|
|
18581
|
+
case "wFirmaV2":
|
|
18582
|
+
if (!options.accessKey || !options.secretKey || !options.companyId) {
|
|
18583
|
+
throw new Error("wFirma needs --access-key, --secret-key and --company-id.");
|
|
18584
|
+
}
|
|
18585
|
+
input.accessKey = options.accessKey;
|
|
18586
|
+
input.secretKey = options.secretKey;
|
|
18587
|
+
input.companyId = options.companyId;
|
|
18588
|
+
break;
|
|
18589
|
+
case "stripe":
|
|
18590
|
+
break;
|
|
18591
|
+
case "ifirma":
|
|
18592
|
+
if (secret) input.apiKey = secret;
|
|
18593
|
+
if (options.accountApiKey) input.accountApiKey = options.accountApiKey;
|
|
18594
|
+
break;
|
|
18595
|
+
case "ingKsiegowosc":
|
|
18596
|
+
if (secret) input.apiKey = secret;
|
|
18597
|
+
if (options.bankAccount) input.bankAccountNumber = options.bankAccount;
|
|
18598
|
+
break;
|
|
18599
|
+
default:
|
|
18600
|
+
if (secret) input.apiKey = secret;
|
|
18601
|
+
}
|
|
18602
|
+
if (client !== "stripe" && !input.url) throw new Error("--url is required.");
|
|
18603
|
+
return input;
|
|
18604
|
+
}
|
|
18605
|
+
function resolveSecret(value, fromEnv, flag) {
|
|
18606
|
+
if (value === void 0 || !fromEnv) return value;
|
|
18607
|
+
const raw = process.env[value]?.trim();
|
|
18608
|
+
if (!raw) throw new Error(`${flag}: environment variable ${value} is empty or unset.`);
|
|
18609
|
+
return raw;
|
|
18610
|
+
}
|
|
18611
|
+
function buildGatewayInput(gateway, o) {
|
|
18612
|
+
const need = (value, flag) => {
|
|
18613
|
+
if (!value) throw new Error(`${flag} is required for ${gateway}.`);
|
|
18614
|
+
return value;
|
|
18615
|
+
};
|
|
18616
|
+
const secret = (value, flag) => need(resolveSecret(value, o.secretsEnv, flag), flag);
|
|
18617
|
+
const input = { gateway, name: o.name };
|
|
18618
|
+
switch (gateway) {
|
|
18619
|
+
case "payu":
|
|
18620
|
+
input.posId = need(o.posId, "--pos-id");
|
|
18621
|
+
input.clientId = need(o.clientId, "--client-id");
|
|
18622
|
+
input.clientSecret = secret(o.clientSecret, "--client-secret");
|
|
18623
|
+
input.clientSecondKey = secret(o.secondKey, "--second-key");
|
|
18624
|
+
break;
|
|
18625
|
+
case "tpay":
|
|
18626
|
+
input.posId = need(o.posId, "--pos-id");
|
|
18627
|
+
input.clientId = need(o.clientId, "--client-id");
|
|
18628
|
+
input.clientSecret = secret(o.clientSecret, "--client-secret");
|
|
18629
|
+
input.safetyKey = secret(o.safetyKey, "--safety-key");
|
|
18630
|
+
break;
|
|
18631
|
+
case "przelewy24":
|
|
18632
|
+
input.merchantId = need(o.merchantId, "--merchant-id");
|
|
18633
|
+
input.posId = need(o.posId, "--pos-id");
|
|
18634
|
+
input.apiKey = secret(o.apiKey, "--api-key");
|
|
18635
|
+
input.crcKey = secret(o.crcKey, "--crc-key");
|
|
18636
|
+
break;
|
|
18637
|
+
case "paynow":
|
|
18638
|
+
input.apiKey = secret(o.apiKey, "--api-key");
|
|
18639
|
+
input.signatureKey = secret(o.signatureKey, "--signature-key");
|
|
18640
|
+
break;
|
|
18641
|
+
case "paypal":
|
|
18642
|
+
input.clientId = need(o.clientId, "--client-id");
|
|
18643
|
+
input.clientSecret = secret(o.clientSecret, "--client-secret");
|
|
18644
|
+
break;
|
|
18645
|
+
case "dodo":
|
|
18646
|
+
input.apiKey = secret(o.apiKey, "--api-key");
|
|
18647
|
+
break;
|
|
18648
|
+
}
|
|
18649
|
+
return input;
|
|
18650
|
+
}
|
|
18651
|
+
var TWILIO_REGIONS = ["us1", "ie1", "au1"];
|
|
18652
|
+
var TWILIO_AUTH_KINDS = ["authToken", "apiKey"];
|
|
18653
|
+
var TWILIO_SENDER_KINDS = [
|
|
18654
|
+
"phoneNumber",
|
|
18655
|
+
"messagingService"
|
|
18656
|
+
];
|
|
18657
|
+
var SMSAPI_REGIONS = ["pl", "com"];
|
|
18658
|
+
var SMS_PROVIDERS = ["twilio", "smsapi"];
|
|
18659
|
+
function registerIntegrationProviderCommands(integrations) {
|
|
18660
|
+
const invoices = integrations.command("invoices").description(
|
|
18661
|
+
"Invoicing accounts: fakturownia, ifirma, infakt, wfirma (v2), ing (ING Ksi\u0119gowo\u015B\u0107), stripe. Keys are verified with the provider and never printed."
|
|
18662
|
+
);
|
|
18663
|
+
invoices.command("connect <client>").description("Connect (or edit with --id) an invoicing account").option("--id <id>", "edit this existing account (from `integrations status`)").option("--name <name>", "creator-facing label (accountName)").option("--url <url>", "provider account URL / API base (not for stripe)").option("--api-key <key>", "API key (fakturownia / ifirma invoice key / infakt / ing)").option("--api-key-stdin", "read the API key from stdin").option("--api-key-env <VAR>", "read the API key from an environment variable").option("--account-api-key <key>", "ifirma: the account (user) API key").option("--access-key <key>", "wfirma").option("--secret-key <key>", "wfirma").option("--company-id <id>", "wfirma").option("--department-id <id>", "fakturownia / infakt / ifirma").option("--bank-account <iban>", "ing: bank account number for the invoice").action(async (clientArg, options, command) => {
|
|
18664
|
+
const flags = getGlobalFlags(command);
|
|
18665
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18666
|
+
const account = await client.connectInvoicesClient(
|
|
18667
|
+
buildInvoicesInput(parseInvoicesClient(clientArg), options)
|
|
18668
|
+
);
|
|
18669
|
+
emit(flags, account, () => {
|
|
18670
|
+
process.stdout.write(`Connected ${account.client} ${account.id} (${account.url})
|
|
18671
|
+
`);
|
|
18672
|
+
});
|
|
18673
|
+
});
|
|
18674
|
+
invoices.command("disconnect <client> <accountId>").description("Disconnect an invoicing account (irreversible). Requires --confirm.").option("--confirm", "required \u2014 acknowledges the stored key is deleted").action(
|
|
18675
|
+
async (clientArg, accountId, options, command) => {
|
|
18676
|
+
requireConfirm8(options.confirm, `disconnect ${clientArg} ${accountId}`);
|
|
18677
|
+
const flags = getGlobalFlags(command);
|
|
18678
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18679
|
+
const parsed = parseInvoicesClient(clientArg);
|
|
18680
|
+
const result = await client.disconnectInvoicesClient(parsed, accountId);
|
|
18681
|
+
emit(flags, { ...result, client: parsed, id: accountId }, () => {
|
|
18682
|
+
process.stdout.write(`Disconnected ${parsed} ${accountId}
|
|
18683
|
+
`);
|
|
18684
|
+
});
|
|
18685
|
+
}
|
|
18686
|
+
);
|
|
18687
|
+
const payments = integrations.command("payments").description(
|
|
18688
|
+
"Credential payment gateways: payu, tpay, przelewy24 (p24), paynow, paypal, dodo. Stripe connects in the dashboard (OAuth); the transfer method is `integrations manual-payment`."
|
|
18689
|
+
);
|
|
18690
|
+
payments.command("connect <gateway>").description(
|
|
18691
|
+
"Connect a gateway \u2014 credentials are verified with the gateway first. With --secrets-env every secret flag takes an environment variable NAME instead of the value."
|
|
18692
|
+
).requiredOption("--name <name>", "creator-facing label").option("--pos-id <id>", "payu / tpay / przelewy24").option("--client-id <id>", "payu / tpay / paypal").option("--client-secret <secret>", "payu / tpay / paypal").option("--second-key <key>", "payu: the second (MD5) key").option("--safety-key <key>", "tpay: the security key").option("--api-key <key>", "przelewy24 / paynow / dodo").option("--signature-key <key>", "paynow").option("--merchant-id <id>", "przelewy24").option("--crc-key <key>", "przelewy24").option("--secrets-env", "treat every secret flag value as an environment variable name").action(async (gatewayArg, options, command) => {
|
|
18693
|
+
const flags = getGlobalFlags(command);
|
|
18694
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18695
|
+
const status = await client.connectPaymentGateway(
|
|
18696
|
+
buildGatewayInput(parseGateway(gatewayArg), options)
|
|
18697
|
+
);
|
|
18698
|
+
emit(flags, status, () => printPayments(status));
|
|
18699
|
+
});
|
|
18700
|
+
payments.command("disconnect <gateway>").description("Disconnect a gateway (irreversible, secrets deleted). Requires --confirm.").option("--confirm", "required \u2014 prices that only offered this gateway become unbuyable").action(async (gatewayArg, options, command) => {
|
|
18701
|
+
requireConfirm8(options.confirm, `disconnect ${gatewayArg}`);
|
|
18702
|
+
const flags = getGlobalFlags(command);
|
|
18703
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18704
|
+
const status = await client.disconnectPaymentGateway(parseGateway(gatewayArg));
|
|
18705
|
+
emit(flags, status, () => printPayments(status));
|
|
18706
|
+
});
|
|
18707
|
+
payments.command("tpay-blik <on|off>").description("Turn Tpay recurring BLIK (BLIK subscriptions) on or off").action(async (value, _options, command) => {
|
|
18708
|
+
const enabled = parseEnum(value, ["on", "off"], "tpay-blik") === "on";
|
|
18709
|
+
const flags = getGlobalFlags(command);
|
|
18710
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18711
|
+
const result = await client.setTpayRecurringBlik(enabled);
|
|
18712
|
+
emit(flags, { ...result, enabled }, () => {
|
|
18713
|
+
process.stdout.write(`Tpay recurring BLIK: ${enabled ? "on" : "off"}
|
|
18714
|
+
`);
|
|
18715
|
+
});
|
|
18716
|
+
});
|
|
18717
|
+
const sms = integrations.command("sms").description("SMS providers (Twilio, SMSAPI) and which one campaigns send through");
|
|
18718
|
+
const twilio = sms.command("twilio").description("Twilio account");
|
|
18719
|
+
twilio.command("connect").description("Connect (or replace) the Twilio account \u2014 credentials verified with Twilio first").requiredOption("--account-sid <sid>", "Twilio account SID").option("--auth-token <token>", "auth token, or the API key secret with --auth-kind apiKey").option("--auth-token-stdin", "read the token from stdin").option("--auth-token-env <VAR>", "read the token from an environment variable").option("--auth-kind <authToken|apiKey>", "default authToken").option("--api-key-sid <sid>", "with --auth-kind apiKey: the API key SID (SK\u2026)").option("--region <us1|ie1|au1>", "default us1").requiredOption("--sender-kind <phoneNumber|messagingService>", "what --sender is").requiredOption("--sender <value>", "phone number (E.164) or messaging service SID (MG\u2026)").action(
|
|
18720
|
+
async (options, command) => {
|
|
18721
|
+
const token = readSecret2(
|
|
18722
|
+
"--auth-token",
|
|
18723
|
+
options.authToken,
|
|
18724
|
+
options.authTokenStdin,
|
|
18725
|
+
options.authTokenEnv
|
|
18726
|
+
);
|
|
18727
|
+
if (!token)
|
|
18728
|
+
throw new Error(
|
|
18729
|
+
"--auth-token (or --auth-token-stdin / --auth-token-env VAR) is required."
|
|
18730
|
+
);
|
|
18731
|
+
const input = {
|
|
18732
|
+
accountSid: options.accountSid,
|
|
18733
|
+
authToken: token,
|
|
18734
|
+
senderKind: parseEnum(options.senderKind, TWILIO_SENDER_KINDS, "--sender-kind"),
|
|
18735
|
+
sender: options.sender
|
|
18736
|
+
};
|
|
18737
|
+
if (options.region) input.region = parseEnum(options.region, TWILIO_REGIONS, "--region");
|
|
18738
|
+
if (options.authKind)
|
|
18739
|
+
input.authKind = parseEnum(options.authKind, TWILIO_AUTH_KINDS, "--auth-kind");
|
|
18740
|
+
if (options.apiKeySid) input.apiKeySid = options.apiKeySid;
|
|
18741
|
+
const flags = getGlobalFlags(command);
|
|
18742
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18743
|
+
const result = await client.connectTwilio(input);
|
|
18744
|
+
emit(flags, result, () => {
|
|
18745
|
+
process.stdout.write(
|
|
18746
|
+
`Connected Twilio ${result.accountSid} (${result.region}) sender ${result.sender}
|
|
18747
|
+
`
|
|
18748
|
+
);
|
|
18749
|
+
});
|
|
18750
|
+
}
|
|
18751
|
+
);
|
|
18752
|
+
twilio.command("disconnect").description("Disconnect Twilio (secret deleted). Requires --confirm.").option("--confirm", "required").action(async (options, command) => {
|
|
18753
|
+
requireConfirm8(options.confirm, "disconnect Twilio");
|
|
18754
|
+
const flags = getGlobalFlags(command);
|
|
18755
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18756
|
+
const result = await client.disconnectTwilio();
|
|
18757
|
+
emit(flags, result, () => {
|
|
18758
|
+
process.stdout.write("Twilio disconnected\n");
|
|
18759
|
+
});
|
|
18760
|
+
});
|
|
18761
|
+
const smsapi = sms.command("smsapi").description("SMSAPI account (smsapi.pl / smsapi.com)");
|
|
18762
|
+
smsapi.command("connect").description(
|
|
18763
|
+
"Connect (or replace) the SMSAPI account \u2014 the token is verified with SMSAPI first"
|
|
18764
|
+
).option("--region <pl|com>", "default pl").option("--api-token <token>", "OAuth API token (prefer -stdin / -env)").option("--api-token-stdin", "read the token from stdin").option("--api-token-env <VAR>", "read the token from an environment variable").requiredOption("--sender <name>", "approved sender name (max 11 chars)").action(
|
|
18765
|
+
async (options, command) => {
|
|
18766
|
+
const token = readSecret2(
|
|
18767
|
+
"--api-token",
|
|
18768
|
+
options.apiToken,
|
|
18769
|
+
options.apiTokenStdin,
|
|
18770
|
+
options.apiTokenEnv
|
|
18771
|
+
);
|
|
18772
|
+
if (!token)
|
|
18773
|
+
throw new Error("--api-token (or --api-token-stdin / --api-token-env VAR) is required.");
|
|
18774
|
+
const flags = getGlobalFlags(command);
|
|
18775
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18776
|
+
const result = await client.connectSmsApi({
|
|
18777
|
+
region: options.region ? parseEnum(options.region, SMSAPI_REGIONS, "--region") : "pl",
|
|
18778
|
+
apiToken: token,
|
|
18779
|
+
sender: options.sender
|
|
18780
|
+
});
|
|
18781
|
+
emit(flags, result, () => {
|
|
18782
|
+
process.stdout.write(
|
|
18783
|
+
`Connected SMSAPI (${result.region}) sender ${result.sender}${result.points !== null ? `, ${result.points} points` : ""}
|
|
18784
|
+
`
|
|
18785
|
+
);
|
|
18786
|
+
});
|
|
18787
|
+
}
|
|
18788
|
+
);
|
|
18789
|
+
smsapi.command("disconnect").description("Disconnect SMSAPI (token deleted). Requires --confirm.").option("--confirm", "required").action(async (options, command) => {
|
|
18790
|
+
requireConfirm8(options.confirm, "disconnect SMSAPI");
|
|
18791
|
+
const flags = getGlobalFlags(command);
|
|
18792
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18793
|
+
const result = await client.disconnectSmsApi();
|
|
18794
|
+
emit(flags, result, () => {
|
|
18795
|
+
process.stdout.write("SMSAPI disconnected\n");
|
|
18796
|
+
});
|
|
18797
|
+
});
|
|
18798
|
+
smsapi.command("refresh-senders").description("Re-fetch the approved SMSAPI sender names").action(async (_options, command) => {
|
|
18799
|
+
const flags = getGlobalFlags(command);
|
|
18800
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18801
|
+
const result = await client.refreshSmsApiSenders();
|
|
18802
|
+
emit(flags, result, () => {
|
|
18803
|
+
process.stdout.write(
|
|
18804
|
+
`Senders: ${result.senders.join(", ") || "(none)"}
|
|
18805
|
+
Default: ${result.sender}
|
|
18806
|
+
`
|
|
18807
|
+
);
|
|
18808
|
+
});
|
|
18809
|
+
});
|
|
18810
|
+
smsapi.command("default-sender <sender>").description("Pick which approved SMSAPI sender campaigns send from").action(async (sender, _options, command) => {
|
|
18811
|
+
const flags = getGlobalFlags(command);
|
|
18812
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18813
|
+
const result = await client.setDefaultSmsApiSender(sender);
|
|
18814
|
+
emit(flags, result, () => {
|
|
18815
|
+
process.stdout.write(`Default SMSAPI sender: ${result.sender}
|
|
18816
|
+
`);
|
|
18817
|
+
});
|
|
18818
|
+
});
|
|
18819
|
+
sms.command("default-provider <twilio|smsapi>").description("Choose which connected SMS provider campaigns and automations send through").action(async (value, _options, command) => {
|
|
18820
|
+
const provider = parseEnum(value, SMS_PROVIDERS, "default-provider");
|
|
18821
|
+
const flags = getGlobalFlags(command);
|
|
18822
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18823
|
+
const result = await client.setDefaultSmsProvider(provider);
|
|
18824
|
+
emit(flags, result, () => {
|
|
18825
|
+
process.stdout.write(`Default SMS provider: ${result.defaultProvider}
|
|
18826
|
+
`);
|
|
18827
|
+
});
|
|
18828
|
+
});
|
|
18829
|
+
}
|
|
18830
|
+
|
|
18831
|
+
// src/commands/integrations/index.ts
|
|
18832
|
+
var CLIENT_ALIASES = {
|
|
18833
|
+
gtm: "googleTagManager",
|
|
18834
|
+
ga4: "googleAnalytics",
|
|
18835
|
+
ga: "googleAnalytics",
|
|
18836
|
+
clarity: "microsoftClarity",
|
|
18837
|
+
twitter: "x"
|
|
18838
|
+
};
|
|
18839
|
+
var NONE = "none";
|
|
18840
|
+
function parseClient(value) {
|
|
18841
|
+
const alias = CLIENT_ALIASES[value.trim().toLowerCase()];
|
|
18842
|
+
if (alias) return alias;
|
|
18843
|
+
const match = MANAGED_CONVERSION_TRACKING_CLIENTS.find(
|
|
18844
|
+
(client) => client.toLowerCase() === value.trim().toLowerCase()
|
|
18845
|
+
);
|
|
18846
|
+
if (!match) {
|
|
18847
|
+
throw new Error(
|
|
18848
|
+
`Unknown platform: ${value} (expected ${MANAGED_CONVERSION_TRACKING_CLIENTS.join(" | ")}; aliases: ${Object.keys(CLIENT_ALIASES).join(", ")}). Meta pixel / CAPI is dashboard-only.`
|
|
18849
|
+
);
|
|
18850
|
+
}
|
|
18851
|
+
return match;
|
|
18852
|
+
}
|
|
18853
|
+
function parseOnOff3(value, flag) {
|
|
18854
|
+
if (value === void 0) return void 0;
|
|
18855
|
+
if (value === "on") return true;
|
|
18856
|
+
if (value === "off") return false;
|
|
18857
|
+
throw new Error(`Invalid ${flag}: ${value} (expected on | off)`);
|
|
18858
|
+
}
|
|
18859
|
+
function textOrNull(value) {
|
|
18860
|
+
if (value === void 0) return void 0;
|
|
18861
|
+
return value.trim().toLowerCase() === NONE ? null : value;
|
|
18862
|
+
}
|
|
18863
|
+
function requireConfirm9(confirm, what) {
|
|
18864
|
+
if (!confirm) {
|
|
18865
|
+
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
18866
|
+
}
|
|
18867
|
+
}
|
|
18868
|
+
var TRACK_FLAG_DEFAULTS = {
|
|
18869
|
+
trackPurchase: true,
|
|
18870
|
+
trackInitCheckout: true,
|
|
18871
|
+
trackAddToCart: true,
|
|
18872
|
+
trackTrialStarted: false
|
|
18873
|
+
};
|
|
18874
|
+
var REQUIRED_ID_FLAG = {
|
|
18875
|
+
googleAnalytics: "measurementId",
|
|
18876
|
+
googleTagManager: "containerId",
|
|
18877
|
+
tiktok: "pixelId",
|
|
18878
|
+
x: "pixelId",
|
|
18879
|
+
linkedin: "partnerId",
|
|
18880
|
+
microsoftClarity: "projectId",
|
|
18881
|
+
hotjar: "siteId"
|
|
18882
|
+
};
|
|
18883
|
+
var FLAG_NAME = {
|
|
18884
|
+
name: "--name",
|
|
18885
|
+
containerId: "--container-id",
|
|
18886
|
+
measurementId: "--measurement-id",
|
|
18887
|
+
apiSecret: "--api-secret",
|
|
18888
|
+
pixelId: "--pixel-id",
|
|
18889
|
+
accessToken: "--access-token",
|
|
18890
|
+
partnerId: "--partner-id",
|
|
18891
|
+
projectId: "--project-id",
|
|
18892
|
+
siteId: "--site-id",
|
|
18893
|
+
purchaseTag: "--purchase-tag",
|
|
18894
|
+
initCheckoutTag: "--init-checkout-tag",
|
|
18895
|
+
addToCartTag: "--add-to-cart-tag",
|
|
18896
|
+
trialStartedTag: "--trial-started-tag",
|
|
18897
|
+
trackPurchase: "--track-purchase",
|
|
18898
|
+
trackInitCheckout: "--track-init-checkout",
|
|
18899
|
+
trackAddToCart: "--track-add-to-cart",
|
|
18900
|
+
trackTrialStarted: "--track-trial-started"
|
|
18901
|
+
};
|
|
18902
|
+
var hasEventFlags = (client) => client === "googleAnalytics" || client === "tiktok" || client === "x" || client === "linkedin";
|
|
18903
|
+
function buildUpsertInput(client, options, existing) {
|
|
18904
|
+
const name = options.name ?? existing?.name;
|
|
18905
|
+
if (!name) throw new Error("--name is required when adding an integration.");
|
|
18906
|
+
const input = { client, name };
|
|
18907
|
+
if (existing) input.integrationId = existing.id;
|
|
18908
|
+
const existingFields = existing ? { ...existing } : {};
|
|
18909
|
+
const pick = (key, provided) => {
|
|
18910
|
+
if (provided !== void 0) return provided;
|
|
18911
|
+
const value = existingFields[key];
|
|
18912
|
+
return typeof value === "string" ? value : void 0;
|
|
18913
|
+
};
|
|
18914
|
+
switch (client) {
|
|
18915
|
+
case "googleTagManager":
|
|
18916
|
+
input.containerId = pick("containerId", options.containerId);
|
|
18917
|
+
break;
|
|
18918
|
+
case "googleAnalytics":
|
|
18919
|
+
input.measurementId = pick("measurementId", options.measurementId);
|
|
18920
|
+
if (options.apiSecret !== void 0) input.apiSecret = textOrNull(options.apiSecret);
|
|
18921
|
+
break;
|
|
18922
|
+
case "tiktok":
|
|
18923
|
+
input.pixelId = pick("pixelId", options.pixelId);
|
|
18924
|
+
if (options.accessToken !== void 0) input.accessToken = textOrNull(options.accessToken);
|
|
18925
|
+
break;
|
|
18926
|
+
case "x":
|
|
18927
|
+
input.pixelId = pick("pixelId", options.pixelId);
|
|
18928
|
+
if (options.accessToken !== void 0) input.accessToken = textOrNull(options.accessToken);
|
|
18929
|
+
input.purchaseEventTag = textOrNull(options.purchaseTag) ?? pick("purchaseEventTag", void 0) ?? null;
|
|
18930
|
+
input.initCheckoutEventTag = textOrNull(options.initCheckoutTag) ?? pick("initCheckoutEventTag", void 0) ?? null;
|
|
18931
|
+
input.addToCartEventTag = textOrNull(options.addToCartTag) ?? pick("addToCartEventTag", void 0) ?? null;
|
|
18932
|
+
input.trialStartedEventTag = textOrNull(options.trialStartedTag) ?? pick("trialStartedEventTag", void 0) ?? null;
|
|
18933
|
+
break;
|
|
18934
|
+
case "linkedin":
|
|
18935
|
+
input.partnerId = pick("partnerId", options.partnerId);
|
|
18936
|
+
if (options.accessToken !== void 0) input.accessToken = textOrNull(options.accessToken);
|
|
18937
|
+
input.purchaseConversionUrn = textOrNull(options.purchaseTag) ?? pick("purchaseConversionUrn", void 0) ?? null;
|
|
18938
|
+
input.initCheckoutConversionUrn = textOrNull(options.initCheckoutTag) ?? pick("initCheckoutConversionUrn", void 0) ?? null;
|
|
18939
|
+
input.addToCartConversionUrn = textOrNull(options.addToCartTag) ?? pick("addToCartConversionUrn", void 0) ?? null;
|
|
18940
|
+
input.trialStartedConversionUrn = textOrNull(options.trialStartedTag) ?? pick("trialStartedConversionUrn", void 0) ?? null;
|
|
18941
|
+
break;
|
|
18942
|
+
case "microsoftClarity":
|
|
18943
|
+
input.projectId = pick("projectId", options.projectId);
|
|
18944
|
+
break;
|
|
18945
|
+
case "hotjar":
|
|
18946
|
+
input.siteId = pick("siteId", options.siteId);
|
|
18947
|
+
break;
|
|
18948
|
+
}
|
|
18949
|
+
const requiredKey = REQUIRED_ID_FLAG[client];
|
|
18950
|
+
if (input[requiredKey] === void 0) {
|
|
18951
|
+
throw new Error(`${FLAG_NAME[requiredKey]} is required for ${client}.`);
|
|
18952
|
+
}
|
|
18953
|
+
if (hasEventFlags(client)) {
|
|
18954
|
+
const flags = existing && "trackPurchase" in existing ? existing : TRACK_FLAG_DEFAULTS;
|
|
18955
|
+
input.trackPurchase = parseOnOff3(options.trackPurchase, FLAG_NAME.trackPurchase) ?? flags.trackPurchase;
|
|
18956
|
+
input.trackInitCheckout = parseOnOff3(options.trackInitCheckout, FLAG_NAME.trackInitCheckout) ?? flags.trackInitCheckout;
|
|
18957
|
+
input.trackAddToCart = parseOnOff3(options.trackAddToCart, FLAG_NAME.trackAddToCart) ?? flags.trackAddToCart;
|
|
18958
|
+
input.trackTrialStarted = parseOnOff3(options.trackTrialStarted, FLAG_NAME.trackTrialStarted) ?? flags.trackTrialStarted;
|
|
18959
|
+
}
|
|
18960
|
+
return input;
|
|
18961
|
+
}
|
|
18962
|
+
function platformIdOf(integration) {
|
|
18963
|
+
switch (integration.client) {
|
|
18964
|
+
case "googleAnalytics":
|
|
18965
|
+
return integration.measurementId;
|
|
18966
|
+
case "googleTagManager":
|
|
18967
|
+
return integration.containerId;
|
|
18968
|
+
case "tiktok":
|
|
18969
|
+
case "x":
|
|
18970
|
+
return integration.pixelId;
|
|
18971
|
+
case "linkedin":
|
|
18972
|
+
return integration.partnerId;
|
|
18973
|
+
case "microsoftClarity":
|
|
18974
|
+
return integration.projectId;
|
|
18975
|
+
case "hotjar":
|
|
18976
|
+
return integration.siteId;
|
|
18977
|
+
}
|
|
18978
|
+
}
|
|
18979
|
+
function serverSideOf(integration) {
|
|
18980
|
+
if ("apiSecretPath" in integration) return integration.apiSecretPath ? "yes" : "";
|
|
18981
|
+
return "n/a";
|
|
18982
|
+
}
|
|
18983
|
+
function printIntegrations(integrations) {
|
|
18984
|
+
printTable(
|
|
18985
|
+
integrations.map((integration) => ({
|
|
18986
|
+
id: integration.id,
|
|
18987
|
+
platform: integration.client,
|
|
18988
|
+
name: integration.name,
|
|
18989
|
+
platformId: platformIdOf(integration),
|
|
18990
|
+
serverSide: serverSideOf(integration),
|
|
18991
|
+
default: integration.isWorkspaceDefault ? "yes" : "",
|
|
18992
|
+
updatedAt: integration.updatedAt
|
|
18993
|
+
})),
|
|
18994
|
+
["id", "platform", "name", "platformId", "serverSide", "default", "updatedAt"]
|
|
18995
|
+
);
|
|
18996
|
+
}
|
|
18997
|
+
function printIntegration(integration, headline) {
|
|
18998
|
+
process.stdout.write(`${headline} ${integration.id} (${integration.client})
|
|
18999
|
+
`);
|
|
19000
|
+
process.stdout.write(` Name: ${integration.name}
|
|
19001
|
+
`);
|
|
19002
|
+
process.stdout.write(` Platform id: ${platformIdOf(integration)}
|
|
19003
|
+
`);
|
|
19004
|
+
process.stdout.write(` Workspace default: ${integration.isWorkspaceDefault ? "yes" : "no"}
|
|
19005
|
+
`);
|
|
19006
|
+
if ("trackPurchase" in integration) {
|
|
19007
|
+
process.stdout.write(
|
|
19008
|
+
` Events: purchase=${integration.trackPurchase ? "on" : "off"} initCheckout=${integration.trackInitCheckout ? "on" : "off"} addToCart=${integration.trackAddToCart ? "on" : "off"} trialStarted=${integration.trackTrialStarted ? "on" : "off"}
|
|
19009
|
+
`
|
|
19010
|
+
);
|
|
19011
|
+
}
|
|
19012
|
+
if ("apiSecretPath" in integration) {
|
|
19013
|
+
process.stdout.write(` Server-side events: ${integration.apiSecretPath ? "on" : "off"}
|
|
19014
|
+
`);
|
|
19015
|
+
}
|
|
19016
|
+
}
|
|
19017
|
+
function addPlatformOptions(command) {
|
|
19018
|
+
return command.option("--name <name>", "creator-facing label").option("--container-id <id>", "googleTagManager: GTM container id, e.g. GTM-ABCD123").option("--measurement-id <id>", "googleAnalytics: GA4 measurement id, e.g. G-XXXXXXXXXX").option(
|
|
19019
|
+
"--api-secret <secret|none>",
|
|
19020
|
+
"googleAnalytics: Measurement Protocol API secret for server-side events (validated against GA4); none = client-side only"
|
|
19021
|
+
).option("--pixel-id <id>", "tiktok / x: pixel id").option(
|
|
19022
|
+
"--access-token <token|none>",
|
|
19023
|
+
"tiktok / x / linkedin: events API access token for server-side events (TikTok/LinkedIn tokens are validated); none = client-side only"
|
|
19024
|
+
).option("--partner-id <id>", "linkedin: Insight Tag partner id (6-12 digits)").option("--project-id <id>", "microsoftClarity: project id").option("--site-id <id>", "hotjar: site id").option(
|
|
19025
|
+
"--purchase-tag <tag|none>",
|
|
19026
|
+
"x: purchase event tag / linkedin: purchase conversion URN"
|
|
19027
|
+
).option("--init-checkout-tag <tag|none>", "x / linkedin: initiate-checkout tag / URN").option("--add-to-cart-tag <tag|none>", "x / linkedin: add-to-cart tag / URN").option("--trial-started-tag <tag|none>", "x / linkedin: trial-started tag / URN").option("--track-purchase <on|off>", "GA4 / TikTok / X / LinkedIn: send Purchase (default on)").option(
|
|
19028
|
+
"--track-init-checkout <on|off>",
|
|
19029
|
+
"GA4 / TikTok / X / LinkedIn: send InitiateCheckout (default on)"
|
|
19030
|
+
).option(
|
|
19031
|
+
"--track-add-to-cart <on|off>",
|
|
19032
|
+
"GA4 / TikTok / X / LinkedIn: send AddToCart (default on)"
|
|
19033
|
+
).option(
|
|
19034
|
+
"--track-trial-started <on|off>",
|
|
19035
|
+
"GA4 / TikTok / X / LinkedIn: send TrialStarted (default off)"
|
|
19036
|
+
);
|
|
19037
|
+
}
|
|
19038
|
+
function registerIntegrationsCommands(program2) {
|
|
19039
|
+
const integrations = program2.command("integrations").description(
|
|
19040
|
+
"Conversion-tracking integrations (Settings \u2192 Integrations \u2192 Analytics): Google Tag Manager, Google Analytics 4, TikTok, X, LinkedIn, Microsoft Clarity, Hotjar. Meta pixel / Conversions API, payment gateways and email clients stay dashboard-only for now. Assign per offer with `offers update --tracking-integration-ids`, or workspace-wide with `integrations set-defaults`."
|
|
19041
|
+
);
|
|
19042
|
+
registerIntegrationConnectionCommands(integrations);
|
|
19043
|
+
registerIntegrationProviderCommands(integrations);
|
|
19044
|
+
integrations.command("list").description("List the managed conversion-tracking integrations and the workspace defaults").option(
|
|
19045
|
+
"--client <platform>",
|
|
19046
|
+
"only one platform (e.g. gtm, ga4, tiktok, x, linkedin, clarity, hotjar)"
|
|
19047
|
+
).action(async (options, command) => {
|
|
19048
|
+
const flags = getGlobalFlags(command);
|
|
19049
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
19050
|
+
const result = await client.listConversionTrackingIntegrations();
|
|
19051
|
+
const filter = options.client !== void 0 ? parseClient(options.client) : void 0;
|
|
19052
|
+
const entries = filter ? result.integrations.filter((integration) => integration.client === filter) : result.integrations;
|
|
19053
|
+
emit(flags, { ...result, integrations: entries }, () => {
|
|
19054
|
+
printIntegrations(entries);
|
|
19055
|
+
process.stdout.write(
|
|
19056
|
+
`Workspace defaults: ${result.defaultIntegrationIds.length > 0 ? result.defaultIntegrationIds.join(", ") : "(none)"}
|
|
19057
|
+
`
|
|
19058
|
+
);
|
|
19059
|
+
});
|
|
19060
|
+
});
|
|
19061
|
+
addPlatformOptions(
|
|
19062
|
+
integrations.command("add <platform>").description(
|
|
19063
|
+
"Add a conversion-tracking integration. The first one a workspace connects becomes the workspace default automatically; later ones need `integrations set-defaults` or a per-offer assignment. Platform: gtm | ga4 | tiktok | x | linkedin | clarity | hotjar"
|
|
19064
|
+
)
|
|
19065
|
+
).action(async (platform2, options, command) => {
|
|
19066
|
+
const flags = getGlobalFlags(command);
|
|
19067
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
19068
|
+
const input = buildUpsertInput(parseClient(platform2), options, null);
|
|
19069
|
+
const result = await client.upsertConversionTrackingIntegration(input);
|
|
19070
|
+
emit(flags, result, () => printIntegration(result, "Added"));
|
|
19071
|
+
});
|
|
19072
|
+
addPlatformOptions(
|
|
19073
|
+
integrations.command("update <platform> <integrationId>").description(
|
|
19074
|
+
"Edit an integration \u2014 only the flags you pass change; omitted secrets stay stored. Ids come from `integrations list`."
|
|
19075
|
+
)
|
|
19076
|
+
).action(
|
|
19077
|
+
async (platform2, integrationId, options, command) => {
|
|
19078
|
+
const flags = getGlobalFlags(command);
|
|
19079
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
19080
|
+
const parsedClient = parseClient(platform2);
|
|
19081
|
+
const current = await client.listConversionTrackingIntegrations();
|
|
19082
|
+
const existing = current.integrations.find(
|
|
19083
|
+
(integration) => integration.id === integrationId && integration.client === parsedClient
|
|
19084
|
+
);
|
|
19085
|
+
if (!existing) {
|
|
19086
|
+
throw new Error(
|
|
19087
|
+
`No ${parsedClient} integration with id ${integrationId}. Run \`zanfia integrations list\`.`
|
|
19088
|
+
);
|
|
19089
|
+
}
|
|
19090
|
+
const input = buildUpsertInput(parsedClient, options, existing);
|
|
19091
|
+
const result = await client.upsertConversionTrackingIntegration(input);
|
|
19092
|
+
emit(flags, result, () => printIntegration(result, "Updated"));
|
|
19093
|
+
}
|
|
19094
|
+
);
|
|
19095
|
+
integrations.command("remove <platform> <integrationId>").description(
|
|
19096
|
+
"Remove an integration (irreversible). Offers and the workspace defaults that still list the id simply stop tracking on that platform. Requires --confirm."
|
|
19097
|
+
).option("--confirm", "required \u2014 acknowledges the integration is deleted").action(
|
|
19098
|
+
async (platform2, integrationId, options, command) => {
|
|
19099
|
+
requireConfirm9(options.confirm, `remove integration ${integrationId}`);
|
|
19100
|
+
const flags = getGlobalFlags(command);
|
|
19101
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
19102
|
+
const result = await client.removeConversionTrackingIntegration(
|
|
19103
|
+
parseClient(platform2),
|
|
19104
|
+
integrationId
|
|
19105
|
+
);
|
|
19106
|
+
emit(flags, { ...result, integrationId }, () => {
|
|
19107
|
+
process.stdout.write(`Removed integration ${integrationId}
|
|
19108
|
+
`);
|
|
19109
|
+
});
|
|
19110
|
+
}
|
|
19111
|
+
);
|
|
19112
|
+
integrations.command("set-defaults <ids|none>").description(
|
|
19113
|
+
"Replace the workspace-default integrations (comma-separated ids; none = clear) applied to every offer that inherits defaults. Meta ids from `stats conversion-health` may be included."
|
|
19114
|
+
).action(async (ids, _options, command) => {
|
|
19115
|
+
const flags = getGlobalFlags(command);
|
|
19116
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
19117
|
+
const integrationIds = ids.trim().toLowerCase() === NONE ? [] : ids.split(",").map((id) => id.trim()).filter((id) => id.length > 0);
|
|
19118
|
+
const result = await client.setDefaultConversionTracking(integrationIds);
|
|
19119
|
+
emit(flags, result, () => {
|
|
19120
|
+
process.stdout.write(
|
|
19121
|
+
`Workspace defaults: ${result.integrationIds.length > 0 ? result.integrationIds.join(", ") : "(none)"}
|
|
19122
|
+
`
|
|
19123
|
+
);
|
|
19124
|
+
});
|
|
19125
|
+
});
|
|
19126
|
+
}
|
|
19127
|
+
|
|
19128
|
+
// src/commands/join/index.ts
|
|
19129
|
+
var MAX_CODE_ATTEMPTS2 = 5;
|
|
19130
|
+
var HANDLE_REGEX = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
19131
|
+
var EMAIL_REGEX2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
19132
|
+
var AGENT_DOCS_URL = "https://zanfia.com/help/developer-cli/agents";
|
|
19133
|
+
function joinUrl(base, handle, step) {
|
|
19134
|
+
return `${base.replace(/\/$/, "")}/v1/join/${encodeURIComponent(handle)}/${step}`;
|
|
19135
|
+
}
|
|
19136
|
+
async function runJoin(handleInput, options) {
|
|
19137
|
+
const handle = handleInput.trim().toLowerCase();
|
|
19138
|
+
if (!HANDLE_REGEX.test(handle)) {
|
|
19139
|
+
throw new Error(
|
|
19140
|
+
"Community handle must be lowercase letters, digits and dashes (e.g. crew-of-one)."
|
|
19141
|
+
);
|
|
19142
|
+
}
|
|
19143
|
+
const communityApiUrl = resolveCommunityApiUrl();
|
|
19144
|
+
if (!communityApiUrl) {
|
|
19145
|
+
throw new Error(
|
|
19146
|
+
"No Community API URL configured. Set ZANFIA_COMMUNITY_API_URL (this build has no baked backend)."
|
|
19147
|
+
);
|
|
19148
|
+
}
|
|
19149
|
+
const verbose = options.verbose === true;
|
|
19150
|
+
const email = (options.email ?? await promptLine("Email: ")).trim().toLowerCase();
|
|
19151
|
+
if (!EMAIL_REGEX2.test(email)) {
|
|
19152
|
+
throw new Error("Please enter a valid email address.");
|
|
19153
|
+
}
|
|
19154
|
+
const started = await postJson2(
|
|
19155
|
+
"Join",
|
|
19156
|
+
joinUrl(communityApiUrl, handle, "start"),
|
|
19157
|
+
{ email, lang: "en" },
|
|
19158
|
+
{ verbose }
|
|
19159
|
+
);
|
|
19160
|
+
if (!options.json) {
|
|
19161
|
+
process.stdout.write(
|
|
19162
|
+
`Joining ${started.communityName} (${started.workspaceName}) \u2014 a 6-digit code was sent to ${email}.
|
|
19163
|
+
`
|
|
17690
19164
|
);
|
|
17691
19165
|
}
|
|
17692
19166
|
const firstName = options.name ?? (options.json ? "" : await promptLine("Your name (optional): "));
|
|
@@ -17790,8 +19264,80 @@ join again to refresh it.
|
|
|
17790
19264
|
});
|
|
17791
19265
|
}
|
|
17792
19266
|
|
|
19267
|
+
// src/commands/liveRooms/index.ts
|
|
19268
|
+
var minutes = (seconds) => `${Math.round(seconds / 60)} min`;
|
|
19269
|
+
var percent = (part, whole) => whole > 0 ? `${Math.round(part / whole * 100)}%` : "-";
|
|
19270
|
+
function describeSchedule(room) {
|
|
19271
|
+
if (!room.schedule) return "fixed";
|
|
19272
|
+
const until = room.schedule.until ? ` until ${room.schedule.until}` : "";
|
|
19273
|
+
return `every ${room.schedule.everyMinutes} min${until}`;
|
|
19274
|
+
}
|
|
19275
|
+
function registerLiveRoomsCommands(program2) {
|
|
19276
|
+
const liveRooms = program2.command("live-rooms").description(
|
|
19277
|
+
"Live rooms (webinar rooms, incl. rolling autowebinar sessions) \u2014 list and attendance; rooms are managed in the dashboard"
|
|
19278
|
+
);
|
|
19279
|
+
liveRooms.command("list").description("List live rooms (latest start first)").action(async (_, command) => {
|
|
19280
|
+
const flags = getGlobalFlags(command);
|
|
19281
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
19282
|
+
const result = await client.listLiveRooms();
|
|
19283
|
+
emit(flags, result, () => {
|
|
19284
|
+
printTable(
|
|
19285
|
+
result.rooms.map((room) => ({
|
|
19286
|
+
id: room.id,
|
|
19287
|
+
title: room.title,
|
|
19288
|
+
source: room.source,
|
|
19289
|
+
status: room.endedAt ? "ended" : room.status,
|
|
19290
|
+
startAt: room.startAt,
|
|
19291
|
+
schedule: describeSchedule(room),
|
|
19292
|
+
access: room.access.everyone ? "everyone" : "restricted"
|
|
19293
|
+
})),
|
|
19294
|
+
["id", "title", "source", "status", "startAt", "schedule", "access"]
|
|
19295
|
+
);
|
|
19296
|
+
});
|
|
19297
|
+
});
|
|
19298
|
+
liveRooms.command("attendance").description("A room's attendance per session; --session adds that session's viewers").argument("<roomId>", "live room id (from live-rooms list)").option("--session <iso>", "ISO start of one session to list its attendees").action(async (roomId, options, command) => {
|
|
19299
|
+
const flags = getGlobalFlags(command);
|
|
19300
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
19301
|
+
const result = await client.getLiveRoomAttendance(roomId, options.session);
|
|
19302
|
+
emit(flags, result, () => {
|
|
19303
|
+
printTable(
|
|
19304
|
+
result.sessions.map((session) => ({
|
|
19305
|
+
session: session.sessionStartAt,
|
|
19306
|
+
attendees: session.attendees,
|
|
19307
|
+
identified: session.identified,
|
|
19308
|
+
avgWatched: `${minutes(session.averageWatchedSeconds)} (${percent(
|
|
19309
|
+
session.averageWatchedSeconds,
|
|
19310
|
+
session.durationSeconds
|
|
19311
|
+
)})`,
|
|
19312
|
+
toEnd: `${session.watchedToEnd} (${percent(session.watchedToEnd, session.attendees)})`,
|
|
19313
|
+
sawOffer: session.sawOffer,
|
|
19314
|
+
clickedOffer: session.clickedOffer
|
|
19315
|
+
})),
|
|
19316
|
+
["session", "attendees", "identified", "avgWatched", "toEnd", "sawOffer", "clickedOffer"]
|
|
19317
|
+
);
|
|
19318
|
+
if (options.session !== void 0) {
|
|
19319
|
+
process.stdout.write(`
|
|
19320
|
+
Viewers of ${options.session}:
|
|
19321
|
+
`);
|
|
19322
|
+
printTable(
|
|
19323
|
+
result.attendees.map((attendee) => ({
|
|
19324
|
+
who: attendee.displayName ?? attendee.email ?? attendee.attendee,
|
|
19325
|
+
email: attendee.email ?? "",
|
|
19326
|
+
clientId: attendee.clientId ?? "",
|
|
19327
|
+
watched: minutes(attendee.watchedSeconds),
|
|
19328
|
+
reached: minutes(attendee.maxPositionSeconds),
|
|
19329
|
+
firstSeenAt: attendee.firstSeenAt,
|
|
19330
|
+
offer: attendee.purchasedAt ? "bought" : attendee.offerClickedAt ? "clicked" : attendee.offerSeenAt ? "saw" : ""
|
|
19331
|
+
})),
|
|
19332
|
+
["who", "email", "clientId", "watched", "reached", "firstSeenAt", "offer"]
|
|
19333
|
+
);
|
|
19334
|
+
}
|
|
19335
|
+
});
|
|
19336
|
+
});
|
|
19337
|
+
}
|
|
19338
|
+
|
|
17793
19339
|
// src/commands/media/index.ts
|
|
17794
|
-
import { existsSync, readFileSync as
|
|
19340
|
+
import { existsSync, readFileSync as readFileSync17, writeFileSync as writeFileSync4 } from "fs";
|
|
17795
19341
|
import { readFile, stat as stat3 } from "fs/promises";
|
|
17796
19342
|
import { basename } from "path";
|
|
17797
19343
|
|
|
@@ -20909,16 +22455,61 @@ async function uploadVideoToBunny(params) {
|
|
|
20909
22455
|
});
|
|
20910
22456
|
}
|
|
20911
22457
|
|
|
22458
|
+
// src/utils/mime.ts
|
|
22459
|
+
var OCTET_STREAM_MIME = "application/octet-stream";
|
|
22460
|
+
function mimeFromName(name) {
|
|
22461
|
+
const n = name.toLowerCase();
|
|
22462
|
+
if (n.endsWith(".png")) return "image/png";
|
|
22463
|
+
if (n.endsWith(".jpg") || n.endsWith(".jpeg")) return "image/jpeg";
|
|
22464
|
+
if (n.endsWith(".webp")) return "image/webp";
|
|
22465
|
+
if (n.endsWith(".gif")) return "image/gif";
|
|
22466
|
+
if (n.endsWith(".svg")) return "image/svg+xml";
|
|
22467
|
+
if (n.endsWith(".avif")) return "image/avif";
|
|
22468
|
+
if (n.endsWith(".pdf")) return "application/pdf";
|
|
22469
|
+
if (n.endsWith(".zip")) return "application/zip";
|
|
22470
|
+
if (n.endsWith(".mp3")) return "audio/mpeg";
|
|
22471
|
+
if (n.endsWith(".mp4")) return "video/mp4";
|
|
22472
|
+
if (n.endsWith(".txt") || n.endsWith(".md")) return "text/plain";
|
|
22473
|
+
if (n.endsWith(".csv")) return "text/csv";
|
|
22474
|
+
if (n.endsWith(".json")) return "application/json";
|
|
22475
|
+
if (n.endsWith(".epub")) return "application/epub+zip";
|
|
22476
|
+
if (n.endsWith(".docx")) {
|
|
22477
|
+
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
22478
|
+
}
|
|
22479
|
+
if (n.endsWith(".xlsx")) {
|
|
22480
|
+
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
22481
|
+
}
|
|
22482
|
+
return null;
|
|
22483
|
+
}
|
|
22484
|
+
function mimeFromBytes(bytes) {
|
|
22485
|
+
const ascii = (start, text) => bytes.length >= start + text.length && bytes.toString("latin1", start, start + text.length) === text;
|
|
22486
|
+
if (bytes.length >= 8 && bytes[0] === 137 && ascii(1, "PNG\r\n\n")) return "image/png";
|
|
22487
|
+
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) {
|
|
22488
|
+
return "image/jpeg";
|
|
22489
|
+
}
|
|
22490
|
+
if (ascii(0, "GIF87a") || ascii(0, "GIF89a")) return "image/gif";
|
|
22491
|
+
if (ascii(0, "RIFF") && ascii(8, "WEBP")) return "image/webp";
|
|
22492
|
+
if (ascii(4, "ftypavif")) return "image/avif";
|
|
22493
|
+
if (ascii(0, "%PDF-")) return "application/pdf";
|
|
22494
|
+
if (bytes.toString("utf8", 0, Math.min(bytes.length, 1024)).toLowerCase().includes("<svg")) {
|
|
22495
|
+
return "image/svg+xml";
|
|
22496
|
+
}
|
|
22497
|
+
return null;
|
|
22498
|
+
}
|
|
22499
|
+
function inferMimeType(name, bytes) {
|
|
22500
|
+
return mimeFromName(name) ?? mimeFromBytes(bytes) ?? OCTET_STREAM_MIME;
|
|
22501
|
+
}
|
|
22502
|
+
|
|
20912
22503
|
// src/commands/media/index.ts
|
|
20913
22504
|
var DEFAULT_AI_IMAGE_ASPECT = "1:1" /* Square */;
|
|
20914
22505
|
var DEFAULT_AI_IMAGE_QUALITY = "1K" /* Standard */;
|
|
20915
|
-
function
|
|
22506
|
+
function requireConfirm10(confirm, what) {
|
|
20916
22507
|
if (!confirm) {
|
|
20917
22508
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
20918
22509
|
}
|
|
20919
22510
|
}
|
|
20920
22511
|
function resolveTextOrFile(value) {
|
|
20921
|
-
const text = existsSync(value) ?
|
|
22512
|
+
const text = existsSync(value) ? readFileSync17(value, "utf8") : value;
|
|
20922
22513
|
if (!text.trim()) throw new Error("The text (or the file it points to) is empty.");
|
|
20923
22514
|
return text.trim();
|
|
20924
22515
|
}
|
|
@@ -20974,32 +22565,6 @@ var TRANSCRIBE_STATUS_TEXT = {
|
|
|
20974
22565
|
["already_running" /* AlreadyRunning */]: "Transcription is already running",
|
|
20975
22566
|
["already_transcribed" /* AlreadyTranscribed */]: "Already transcribed \u2014 nothing queued (pass --force to re-transcribe)"
|
|
20976
22567
|
};
|
|
20977
|
-
function mimeFromName(name) {
|
|
20978
|
-
const n = name.toLowerCase();
|
|
20979
|
-
if (n.endsWith(".png")) return "image/png";
|
|
20980
|
-
if (n.endsWith(".jpg") || n.endsWith(".jpeg")) return "image/jpeg";
|
|
20981
|
-
if (n.endsWith(".webp")) return "image/webp";
|
|
20982
|
-
if (n.endsWith(".gif")) return "image/gif";
|
|
20983
|
-
if (n.endsWith(".svg")) return "image/svg+xml";
|
|
20984
|
-
if (n.endsWith(".avif")) return "image/avif";
|
|
20985
|
-
if (n.endsWith(".pdf")) return "application/pdf";
|
|
20986
|
-
return null;
|
|
20987
|
-
}
|
|
20988
|
-
function mimeFromBytes(bytes) {
|
|
20989
|
-
const ascii = (start, text) => bytes.length >= start + text.length && bytes.toString("latin1", start, start + text.length) === text;
|
|
20990
|
-
if (bytes.length >= 8 && bytes[0] === 137 && ascii(1, "PNG\r\n\n")) return "image/png";
|
|
20991
|
-
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) {
|
|
20992
|
-
return "image/jpeg";
|
|
20993
|
-
}
|
|
20994
|
-
if (ascii(0, "GIF87a") || ascii(0, "GIF89a")) return "image/gif";
|
|
20995
|
-
if (ascii(0, "RIFF") && ascii(8, "WEBP")) return "image/webp";
|
|
20996
|
-
if (ascii(4, "ftypavif")) return "image/avif";
|
|
20997
|
-
if (ascii(0, "%PDF-")) return "application/pdf";
|
|
20998
|
-
if (bytes.toString("utf8", 0, Math.min(bytes.length, 1024)).toLowerCase().includes("<svg")) {
|
|
20999
|
-
return "image/svg+xml";
|
|
21000
|
-
}
|
|
21001
|
-
return null;
|
|
21002
|
-
}
|
|
21003
22568
|
function videoMimeFromName(name) {
|
|
21004
22569
|
const n = name.toLowerCase();
|
|
21005
22570
|
if (n.endsWith(".mov")) return "video/quicktime";
|
|
@@ -21022,7 +22587,7 @@ function registerMediaCommands(program2) {
|
|
|
21022
22587
|
const name = options.name ?? basename(options.file);
|
|
21023
22588
|
const input = {
|
|
21024
22589
|
name,
|
|
21025
|
-
mimeType: options.mime ??
|
|
22590
|
+
mimeType: options.mime ?? inferMimeType(name, bytes),
|
|
21026
22591
|
data: bytes.toString("base64"),
|
|
21027
22592
|
folder: options.folder,
|
|
21028
22593
|
description: options.description
|
|
@@ -21172,6 +22737,100 @@ function registerMediaCommands(program2) {
|
|
|
21172
22737
|
});
|
|
21173
22738
|
}
|
|
21174
22739
|
);
|
|
22740
|
+
const captions = media.command("captions").description("Caption (subtitle) tracks of a video - one per language");
|
|
22741
|
+
captions.command("list").description("List a video's caption tracks (language, source, status)").argument("<mediaId>", "video media item id").action(async (mediaId, _options, command) => {
|
|
22742
|
+
const flags = getGlobalFlags(command);
|
|
22743
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
22744
|
+
const result = await client.listMediaCaptions(mediaId);
|
|
22745
|
+
emit(flags, result, () => {
|
|
22746
|
+
if (result.tracks.length === 0) {
|
|
22747
|
+
process.stdout.write(`No caption tracks on ${mediaId}.
|
|
22748
|
+
`);
|
|
22749
|
+
return;
|
|
22750
|
+
}
|
|
22751
|
+
printTable(
|
|
22752
|
+
result.tracks.map((track) => ({
|
|
22753
|
+
lang: track.srclang,
|
|
22754
|
+
label: track.label,
|
|
22755
|
+
source: track.source,
|
|
22756
|
+
status: track.recorded ? track.status : "not recorded",
|
|
22757
|
+
updated: track.updatedAt ?? ""
|
|
22758
|
+
})),
|
|
22759
|
+
["lang", "label", "source", "status", "updated"]
|
|
22760
|
+
);
|
|
22761
|
+
});
|
|
22762
|
+
});
|
|
22763
|
+
captions.command("upload").description(
|
|
22764
|
+
"Add or replace the subtitles in one language from a .vtt or .srt file (UTF-8, up to 2 MB). The transcript's own language is refused - edit the transcript instead."
|
|
22765
|
+
).argument("<mediaId>", "video media item id").requiredOption("--lang <code>", "language code of the track, e.g. en, de, pt-br").requiredOption("--file <path>", ".vtt or .srt file").option("--label <label>", "track name in the player's menu (default: the language name)").action(
|
|
22766
|
+
async (mediaId, options, command) => {
|
|
22767
|
+
const flags = getGlobalFlags(command);
|
|
22768
|
+
if (!existsSync(options.file)) {
|
|
22769
|
+
process.stderr.write(`File not found: ${options.file}
|
|
22770
|
+
`);
|
|
22771
|
+
process.exitCode = 1;
|
|
22772
|
+
return;
|
|
22773
|
+
}
|
|
22774
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
22775
|
+
const result = await client.uploadMediaCaption(mediaId, {
|
|
22776
|
+
srclang: options.lang,
|
|
22777
|
+
content: readFileSync17(options.file, "utf8"),
|
|
22778
|
+
...options.label ? { label: options.label } : {}
|
|
22779
|
+
});
|
|
22780
|
+
emit(flags, result, () => {
|
|
22781
|
+
process.stdout.write(
|
|
22782
|
+
result.track.status === "failed" ? `Stored the ${result.track.srclang} file, but the player refused it - run the same command again in a moment.
|
|
22783
|
+
` : `Captions ${result.track.srclang} (${result.track.label}) are live on ${mediaId}.
|
|
22784
|
+
`
|
|
22785
|
+
);
|
|
22786
|
+
});
|
|
22787
|
+
}
|
|
22788
|
+
);
|
|
22789
|
+
captions.command("download").description("Print (or save) one caption track as WebVTT").argument("<mediaId>", "video media item id").requiredOption("--lang <code>", "language code of the track").option("--out <file>", "write the .vtt to a file instead of stdout").action(async (mediaId, options, command) => {
|
|
22790
|
+
const flags = getGlobalFlags(command);
|
|
22791
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
22792
|
+
const result = await client.getMediaCaption(mediaId, options.lang);
|
|
22793
|
+
if (options.out) {
|
|
22794
|
+
writeFileSync4(options.out, result.content, "utf8");
|
|
22795
|
+
emit(flags, { mediaId, srclang: result.srclang, out: options.out }, () => {
|
|
22796
|
+
process.stdout.write(`Wrote the ${result.srclang} captions to ${options.out}.
|
|
22797
|
+
`);
|
|
22798
|
+
});
|
|
22799
|
+
return;
|
|
22800
|
+
}
|
|
22801
|
+
emit(flags, result, () => {
|
|
22802
|
+
process.stdout.write(result.content);
|
|
22803
|
+
});
|
|
22804
|
+
});
|
|
22805
|
+
captions.command("delete").description("Remove an uploaded caption track (it disappears from the player)").argument("<mediaId>", "video media item id").requiredOption("--lang <code>", "language code of the track").option("--confirm", "required: removes the track from the live player").action(
|
|
22806
|
+
async (mediaId, options, command) => {
|
|
22807
|
+
const flags = getGlobalFlags(command);
|
|
22808
|
+
if (!options.confirm) {
|
|
22809
|
+
process.stderr.write(
|
|
22810
|
+
"Refusing to delete without --confirm (the track disappears from the player).\n"
|
|
22811
|
+
);
|
|
22812
|
+
process.exitCode = 1;
|
|
22813
|
+
return;
|
|
22814
|
+
}
|
|
22815
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
22816
|
+
const result = await client.deleteMediaCaption(mediaId, options.lang);
|
|
22817
|
+
emit(flags, result, () => {
|
|
22818
|
+
process.stdout.write(`Removed the ${result.srclang} captions from ${mediaId}.
|
|
22819
|
+
`);
|
|
22820
|
+
});
|
|
22821
|
+
}
|
|
22822
|
+
);
|
|
22823
|
+
captions.command("regenerate").description("Push the transcript's caption track to the player again").argument("<mediaId>", "video media item id").action(async (mediaId, _options, command) => {
|
|
22824
|
+
const flags = getGlobalFlags(command);
|
|
22825
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
22826
|
+
const result = await client.regenerateMediaCaption(mediaId);
|
|
22827
|
+
emit(flags, result, () => {
|
|
22828
|
+
process.stdout.write(
|
|
22829
|
+
result.track.status === "failed" ? "The player refused the transcript captions - try again in a moment.\n" : `Transcript captions (${result.track.srclang}) pushed to the player.
|
|
22830
|
+
`
|
|
22831
|
+
);
|
|
22832
|
+
});
|
|
22833
|
+
});
|
|
21175
22834
|
media.command("transcript").description(
|
|
21176
22835
|
"Print a video transcript (plain text, or full JSON with --words / --json). Videos without a transcript yet report so; non-video media is a 404."
|
|
21177
22836
|
).argument("<mediaId>", "video media item id").option("--words", "include word-level timings (large payload; implies JSON output)").option("--out <file>", "write the transcript to a file instead of stdout").action(
|
|
@@ -21223,7 +22882,7 @@ function registerMediaCommands(program2) {
|
|
|
21223
22882
|
).option("--folder <name>", "media library folder name to file the image into").option("--text-language <code>", "language of any text rendered inside the image (ISO code)").option("--confirm", "required \u2014 acknowledge spending AI credits").action(
|
|
21224
22883
|
async (options, command) => {
|
|
21225
22884
|
const flags = getGlobalFlags(command);
|
|
21226
|
-
|
|
22885
|
+
requireConfirm10(options.confirm, "generate an AI image (spends AI credits)");
|
|
21227
22886
|
const input = {
|
|
21228
22887
|
prompt: resolveTextOrFile(options.prompt),
|
|
21229
22888
|
aspectRatio: parseEnumOption(
|
|
@@ -21255,7 +22914,7 @@ function registerMediaCommands(program2) {
|
|
|
21255
22914
|
).argument("<mediaId>", "video media item id").option("--language <code>", "spoken language hint (ISO code); omit for auto-detect").option("--force", "re-transcribe even when a transcript already exists").option("--confirm", "required \u2014 acknowledge spending transcription minutes").action(
|
|
21256
22915
|
async (mediaId, options, command) => {
|
|
21257
22916
|
const flags = getGlobalFlags(command);
|
|
21258
|
-
|
|
22917
|
+
requireConfirm10(options.confirm, `transcribe ${mediaId} (spends transcription minutes)`);
|
|
21259
22918
|
const input = {};
|
|
21260
22919
|
if (options.language !== void 0) input.language = options.language;
|
|
21261
22920
|
if (options.force) input.force = true;
|
|
@@ -21329,13 +22988,13 @@ function registerMediaCommands(program2) {
|
|
|
21329
22988
|
}
|
|
21330
22989
|
|
|
21331
22990
|
// src/commands/newsletters/index.ts
|
|
21332
|
-
import { existsSync as existsSync2, readFileSync as
|
|
22991
|
+
import { existsSync as existsSync2, readFileSync as readFileSync18 } from "fs";
|
|
21333
22992
|
var ISSUE_STATUSES = ["public", "hidden"];
|
|
21334
22993
|
var ISSUE_VISIBILITIES = ["public", "hidden", "locked"];
|
|
21335
22994
|
var ISSUE_RENDER_MODES = ["snapshot", "article"];
|
|
21336
22995
|
var THUMBNAIL_NONE = "none";
|
|
21337
22996
|
function resolveMarkdownOrFile(value) {
|
|
21338
|
-
return existsSync2(value) ?
|
|
22997
|
+
return existsSync2(value) ? readFileSync18(value, "utf8") : value;
|
|
21339
22998
|
}
|
|
21340
22999
|
function parseIdList2(value, flag) {
|
|
21341
23000
|
const ids = value.split(",").map((id) => id.trim()).filter(Boolean);
|
|
@@ -21348,6 +23007,7 @@ var ACCESS_STRATEGIES = [
|
|
|
21348
23007
|
"from_subscribe_date"
|
|
21349
23008
|
];
|
|
21350
23009
|
var DOUBLE_OPT_INS = ["none", "external"];
|
|
23010
|
+
var DELIVERIES = ["zanfia", "integration"];
|
|
21351
23011
|
function assertOneOf2(value, allowed, flag) {
|
|
21352
23012
|
if (!allowed.includes(value)) {
|
|
21353
23013
|
throw new Error(`Invalid ${flag}: ${value}. Use one of: ${allowed.join(", ")}`);
|
|
@@ -21376,7 +23036,7 @@ function parseBool2(value, flag) {
|
|
|
21376
23036
|
}
|
|
21377
23037
|
function parseJsonFileOrInline5(value, flag) {
|
|
21378
23038
|
const trimmed = value.trim();
|
|
21379
|
-
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed :
|
|
23039
|
+
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed : readFileSync18(value, "utf8");
|
|
21380
23040
|
try {
|
|
21381
23041
|
return JSON.parse(raw);
|
|
21382
23042
|
} catch (error) {
|
|
@@ -21405,9 +23065,10 @@ function registerNewslettersCommands(program2) {
|
|
|
21405
23065
|
slug: issue.slug,
|
|
21406
23066
|
status: issue.status,
|
|
21407
23067
|
landing: issue.landingPageVisibility ?? "",
|
|
23068
|
+
send: issue.sendStatus ?? "",
|
|
21408
23069
|
createdAt: issue.createdAt
|
|
21409
23070
|
})),
|
|
21410
|
-
["id", "subject", "slug", "status", "landing", "createdAt"]
|
|
23071
|
+
["id", "subject", "slug", "status", "landing", "send", "createdAt"]
|
|
21411
23072
|
);
|
|
21412
23073
|
if (page.nextCursor) {
|
|
21413
23074
|
process.stdout.write(`
|
|
@@ -21591,6 +23252,90 @@ Next: \`newsletters issues draft save ${productId} ${issue.id} --content <markdo
|
|
|
21591
23252
|
});
|
|
21592
23253
|
}
|
|
21593
23254
|
);
|
|
23255
|
+
issues.command("send").description(
|
|
23256
|
+
"\u26A0\uFE0F SENDS the issue from Zanfia to every subscriber of the newsletter product \u2014 REAL bulk e-mail, IRREVERSIBLE once delivered (`issues cancel` only stops the remaining sends). Sending is publishing: a staged draft is promoted first. The newsletter must send from Zanfia (`settings update --delivery zanfia`). Requires --confirm. Omit --at to send NOW; --at schedules (max 30 days out)."
|
|
23257
|
+
).argument("<productId>", "newsletter product id").argument("<issueId>", "issue id").option(
|
|
23258
|
+
"--at <isoTimestamp>",
|
|
23259
|
+
"schedule the send (ISO, max 30 days out) instead of sending now"
|
|
23260
|
+
).option("--confirm", "required \u2014 acknowledge this e-mails the subscribers for real").action(
|
|
23261
|
+
async (productId, issueId, options, command) => {
|
|
23262
|
+
const flags = getGlobalFlags(command);
|
|
23263
|
+
if (!options.confirm) {
|
|
23264
|
+
throw new Error(
|
|
23265
|
+
`Refusing to send without --confirm. \`newsletters issues send\` e-mails EVERY subscriber of the newsletter for real and cannot be undone once mail is delivered. Re-run with --confirm to proceed (check the issue first with \`newsletters issues list --product ${productId}\`).`
|
|
23266
|
+
);
|
|
23267
|
+
}
|
|
23268
|
+
if (options.at !== void 0 && Number.isNaN(Date.parse(options.at))) {
|
|
23269
|
+
throw new Error(
|
|
23270
|
+
`Invalid --at timestamp: ${options.at} (expected ISO, e.g. 2026-07-15T09:00:00Z)`
|
|
23271
|
+
);
|
|
23272
|
+
}
|
|
23273
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23274
|
+
const result = await client.sendNewsletterIssue(productId, issueId, {
|
|
23275
|
+
...options.at !== void 0 ? { scheduledAt: options.at } : {}
|
|
23276
|
+
});
|
|
23277
|
+
emit(flags, result, () => {
|
|
23278
|
+
const { send } = result;
|
|
23279
|
+
if (send.scheduledAt) {
|
|
23280
|
+
process.stdout.write(
|
|
23281
|
+
`Issue ${result.issueId} scheduled for ${send.scheduledAt} \u2014 stop it before then with \`newsletters issues cancel ${productId} ${issueId} --confirm\`.
|
|
23282
|
+
`
|
|
23283
|
+
);
|
|
23284
|
+
} else {
|
|
23285
|
+
process.stdout.write(
|
|
23286
|
+
`Issue ${result.issueId} is sending now (status: ${send.status}). \`newsletters issues cancel\` can only stop the remaining sends.
|
|
23287
|
+
`
|
|
23288
|
+
);
|
|
23289
|
+
}
|
|
23290
|
+
});
|
|
23291
|
+
}
|
|
23292
|
+
);
|
|
23293
|
+
issues.command("cancel").description(
|
|
23294
|
+
"Cancel a scheduled or in-flight issue send \u2014 remaining sends stop; delivered mail is out. Requires --confirm."
|
|
23295
|
+
).argument("<productId>", "newsletter product id").argument("<issueId>", "issue id").option("--confirm", "required \u2014 acknowledge the send is stopped").action(
|
|
23296
|
+
async (productId, issueId, options, command) => {
|
|
23297
|
+
const flags = getGlobalFlags(command);
|
|
23298
|
+
if (!options.confirm) {
|
|
23299
|
+
throw new Error(
|
|
23300
|
+
"Refusing to cancel without --confirm. Re-run with --confirm to un-queue the scheduled send or stop the one in flight."
|
|
23301
|
+
);
|
|
23302
|
+
}
|
|
23303
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23304
|
+
const result = await client.cancelNewsletterIssueSend(productId, issueId);
|
|
23305
|
+
emit(flags, result, () => {
|
|
23306
|
+
process.stdout.write(
|
|
23307
|
+
result.send ? `Stopped the send of issue ${result.issueId} (status: ${result.send.status}).
|
|
23308
|
+
` : `Un-queued the send of issue ${result.issueId}; it is a draft again.
|
|
23309
|
+
`
|
|
23310
|
+
);
|
|
23311
|
+
});
|
|
23312
|
+
}
|
|
23313
|
+
);
|
|
23314
|
+
issues.command("report").description("Progress and delivery / engagement stats of an issue\u2019s native send").argument("<productId>", "newsletter product id").argument("<issueId>", "issue id").action(async (productId, issueId, command) => {
|
|
23315
|
+
const flags = getGlobalFlags(command);
|
|
23316
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23317
|
+
const result = await client.getNewsletterIssueSendReport(productId, issueId);
|
|
23318
|
+
emit(flags, result, () => {
|
|
23319
|
+
if (!result.send) {
|
|
23320
|
+
process.stdout.write(`Issue ${result.issueId} was never sent from Zanfia.
|
|
23321
|
+
`);
|
|
23322
|
+
return;
|
|
23323
|
+
}
|
|
23324
|
+
const { send, report } = result;
|
|
23325
|
+
process.stdout.write(
|
|
23326
|
+
`Issue ${result.issueId}: ${send.status}` + (send.scheduledAt ? ` (scheduled for ${send.scheduledAt})` : "") + (send.recipientCount !== null ? `, ${send.recipientCount} recipients` : "") + "\n"
|
|
23327
|
+
);
|
|
23328
|
+
if (report) {
|
|
23329
|
+
printTable(
|
|
23330
|
+
Object.entries(report.stats).map(([stat4, value]) => ({
|
|
23331
|
+
stat: stat4,
|
|
23332
|
+
value: String(value ?? "")
|
|
23333
|
+
})),
|
|
23334
|
+
["stat", "value"]
|
|
23335
|
+
);
|
|
23336
|
+
}
|
|
23337
|
+
});
|
|
23338
|
+
});
|
|
21594
23339
|
issues.command("delete").description(
|
|
21595
23340
|
"\u26A0\uFE0F PERMANENTLY delete an archived issue \u2014 a HARD delete with no undo. Requires --confirm."
|
|
21596
23341
|
).argument("<issueId>", "archived issue id").requiredOption("--product <id>", "newsletter product id").option("--confirm", "required \u2014 acknowledge the issue is removed permanently").action(
|
|
@@ -21609,9 +23354,11 @@ Next: \`newsletters issues draft save ${productId} ${issue.id} --content <markdo
|
|
|
21609
23354
|
});
|
|
21610
23355
|
}
|
|
21611
23356
|
);
|
|
21612
|
-
const settings = newsletters.command("settings").description(
|
|
23357
|
+
const settings = newsletters.command("settings").description(
|
|
23358
|
+
"Edit newsletter settings (archive, archive inbox, double opt-in, client tags, delivery, sender)"
|
|
23359
|
+
);
|
|
21613
23360
|
settings.command("update").description(
|
|
21614
|
-
"Patch newsletter settings. Archive flags merge per-field; --archive-inbox, --double-opt-in and --client-tag replace their whole block. At least one change is required."
|
|
23361
|
+
"Patch newsletter settings. Archive and sender flags merge per-field; --archive-inbox, --double-opt-in and --client-tag replace their whole block. At least one change is required."
|
|
21615
23362
|
).requiredOption("--product <id>", "newsletter product id").option("--archive-enabled <on|off>", "archive: enable/disable the public archive").option("--archive-access <strategy>", `archive: ${ACCESS_STRATEGIES.join("|")}`).option(
|
|
21616
23363
|
"--publication-delay <days|none>",
|
|
21617
23364
|
'archive: days before a new issue goes public; "none" = immediately'
|
|
@@ -21629,9 +23376,18 @@ Next: \`newsletters issues draft save ${productId} ${issue.id} --content <markdo
|
|
|
21629
23376
|
"tag applied to new subscribers (repeat the flag; replaces the list)",
|
|
21630
23377
|
collectRepeatable4,
|
|
21631
23378
|
[]
|
|
21632
|
-
).
|
|
23379
|
+
).option(
|
|
23380
|
+
"--delivery <mode>",
|
|
23381
|
+
`who sends the issues: ${DELIVERIES.join("|")} (zanfia = native sending from the product)`
|
|
23382
|
+
).option(
|
|
23383
|
+
"--from-name <name>",
|
|
23384
|
+
'sender: From display name of native sends ("" = workspace default)'
|
|
23385
|
+
).option("--reply-to <email>", 'sender: Reply-To of native sends ("" = workspace default)').option("--clear-sender", "sender: drop both defaults (workspace defaults apply)").action(
|
|
21633
23386
|
async (options, command) => {
|
|
21634
23387
|
const flags = getGlobalFlags(command);
|
|
23388
|
+
if (options.clearSender && (options.fromName !== void 0 || options.replyTo !== void 0)) {
|
|
23389
|
+
throw new Error("Use either --clear-sender or --from-name/--reply-to, not both.");
|
|
23390
|
+
}
|
|
21635
23391
|
const archive = {};
|
|
21636
23392
|
if (options.archiveEnabled !== void 0) {
|
|
21637
23393
|
archive.enabled = parseBool2(options.archiveEnabled, "--archive-enabled");
|
|
@@ -21675,6 +23431,17 @@ Next: \`newsletters issues draft save ${productId} ${issue.id} --content <markdo
|
|
|
21675
23431
|
input.doubleOptIn = assertOneOf2(options.doubleOptIn, DOUBLE_OPT_INS, "--double-opt-in");
|
|
21676
23432
|
}
|
|
21677
23433
|
if (options.clientTag.length > 0) input.clientTags = options.clientTag;
|
|
23434
|
+
if (options.delivery !== void 0) {
|
|
23435
|
+
input.delivery = assertOneOf2(options.delivery, DELIVERIES, "--delivery");
|
|
23436
|
+
}
|
|
23437
|
+
if (options.clearSender) {
|
|
23438
|
+
input.sender = null;
|
|
23439
|
+
} else if (options.fromName !== void 0 || options.replyTo !== void 0) {
|
|
23440
|
+
input.sender = {
|
|
23441
|
+
...options.fromName !== void 0 ? { fromName: options.fromName || null } : {},
|
|
23442
|
+
...options.replyTo !== void 0 ? { replyTo: options.replyTo || null } : {}
|
|
23443
|
+
};
|
|
23444
|
+
}
|
|
21678
23445
|
if (Object.keys(input).length === 0) {
|
|
21679
23446
|
throw new Error(
|
|
21680
23447
|
"Nothing to update \u2014 pass at least one settings flag (see `newsletters settings update --help`)."
|
|
@@ -21691,7 +23458,7 @@ Next: \`newsletters issues draft save ${productId} ${issue.id} --content <markdo
|
|
|
21691
23458
|
}
|
|
21692
23459
|
|
|
21693
23460
|
// src/commands/offers/index.ts
|
|
21694
|
-
import { readFileSync as
|
|
23461
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
21695
23462
|
var PRICE_BILLING_TYPES = ["one_time", "recurring", "installments"];
|
|
21696
23463
|
var PRICE_INTERVALS = ["day", "week", "month", "year"];
|
|
21697
23464
|
var SALE_START_DISPLAYS = ["hidden", "countdown"];
|
|
@@ -21701,36 +23468,166 @@ var PRICE_OPERATIONS = [
|
|
|
21701
23468
|
"deactivate",
|
|
21702
23469
|
"hide",
|
|
21703
23470
|
"unhide",
|
|
21704
|
-
"change-amount"
|
|
23471
|
+
"change-amount",
|
|
23472
|
+
"edit-details"
|
|
21705
23473
|
];
|
|
23474
|
+
var EDIT_DETAILS_OPERATION = "edit-details";
|
|
23475
|
+
var CHANGE_AMOUNT_OPERATION = "change-amount";
|
|
21706
23476
|
function parseChoice4(value, allowed, flag) {
|
|
21707
23477
|
const match = allowed.find((candidate) => candidate === value);
|
|
21708
23478
|
if (match === void 0) {
|
|
21709
23479
|
throw new Error(`Invalid ${flag}: ${value} (expected one of: ${allowed.join(", ")})`);
|
|
21710
23480
|
}
|
|
21711
|
-
return match;
|
|
21712
|
-
}
|
|
21713
|
-
function parsePositiveInt12(value, flag) {
|
|
21714
|
-
const n = Number.parseInt(value, 10);
|
|
21715
|
-
if (!Number.isInteger(n) || n <= 0) {
|
|
21716
|
-
throw new Error(`Invalid ${flag}: ${value} (expected a positive integer)`);
|
|
23481
|
+
return match;
|
|
23482
|
+
}
|
|
23483
|
+
function parsePositiveInt12(value, flag) {
|
|
23484
|
+
const n = Number.parseInt(value, 10);
|
|
23485
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
23486
|
+
throw new Error(`Invalid ${flag}: ${value} (expected a positive integer)`);
|
|
23487
|
+
}
|
|
23488
|
+
return n;
|
|
23489
|
+
}
|
|
23490
|
+
function parseBool3(value, flag) {
|
|
23491
|
+
const v = value.trim().toLowerCase();
|
|
23492
|
+
if (["on", "true", "yes", "1"].includes(v)) return true;
|
|
23493
|
+
if (["off", "false", "no", "0"].includes(v)) return false;
|
|
23494
|
+
throw new Error(`Invalid ${flag}: ${value} (expected on/off, true/false, yes/no).`);
|
|
23495
|
+
}
|
|
23496
|
+
function parseJsonFileOrInline6(value, flag) {
|
|
23497
|
+
const trimmed = value.trim();
|
|
23498
|
+
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed : readFileSync19(value, "utf8");
|
|
23499
|
+
try {
|
|
23500
|
+
return JSON.parse(raw);
|
|
23501
|
+
} catch (error) {
|
|
23502
|
+
throw new Error(`Invalid JSON for ${flag}: ${error.message}`);
|
|
23503
|
+
}
|
|
23504
|
+
}
|
|
23505
|
+
function buildPriceUpdateInput(options) {
|
|
23506
|
+
const hasDetails = options.label !== void 0 || options.description !== void 0;
|
|
23507
|
+
const operation = options.operation !== void 0 ? parseChoice4(options.operation, PRICE_OPERATIONS, "--operation") : hasDetails ? EDIT_DETAILS_OPERATION : void 0;
|
|
23508
|
+
if (operation === void 0) {
|
|
23509
|
+
throw new Error(
|
|
23510
|
+
`--operation <${PRICE_OPERATIONS.join("|")}> is required (or pass --label / --description to edit the details).`
|
|
23511
|
+
);
|
|
23512
|
+
}
|
|
23513
|
+
if (hasDetails && operation !== EDIT_DETAILS_OPERATION) {
|
|
23514
|
+
throw new Error(
|
|
23515
|
+
`--label / --description only apply to --operation ${EDIT_DETAILS_OPERATION}, not ${operation}.`
|
|
23516
|
+
);
|
|
23517
|
+
}
|
|
23518
|
+
if (operation === EDIT_DETAILS_OPERATION) {
|
|
23519
|
+
if (!hasDetails) {
|
|
23520
|
+
throw new Error(
|
|
23521
|
+
`--operation ${EDIT_DETAILS_OPERATION} requires --label and/or --description.`
|
|
23522
|
+
);
|
|
23523
|
+
}
|
|
23524
|
+
if (options.label !== void 0 && options.label.trim().length === 0) {
|
|
23525
|
+
throw new Error("--label must not be empty (labels cannot be cleared, only replaced).");
|
|
23526
|
+
}
|
|
23527
|
+
return {
|
|
23528
|
+
operation,
|
|
23529
|
+
newLabel: options.label,
|
|
23530
|
+
// An empty string clears the description (the API accepts '' or null; null is explicit).
|
|
23531
|
+
newDescription: options.description === void 0 ? void 0 : options.description.length === 0 ? null : options.description
|
|
23532
|
+
};
|
|
23533
|
+
}
|
|
23534
|
+
if (operation === CHANGE_AMOUNT_OPERATION && !options.amount) {
|
|
23535
|
+
throw new Error(`--operation ${CHANGE_AMOUNT_OPERATION} requires --amount <minor-units>.`);
|
|
23536
|
+
}
|
|
23537
|
+
return {
|
|
23538
|
+
operation,
|
|
23539
|
+
newAmountCents: operation === CHANGE_AMOUNT_OPERATION && options.amount ? parsePositiveInt12(options.amount, "--amount") : void 0
|
|
23540
|
+
};
|
|
23541
|
+
}
|
|
23542
|
+
var MEDIA_TYPES = ["image", "video"];
|
|
23543
|
+
var IMAGE_MEDIA_TYPE2 = "image";
|
|
23544
|
+
var VIDEO_MEDIA_TYPE = "video";
|
|
23545
|
+
var READY_UPLOAD_STATUS = "ready";
|
|
23546
|
+
var GALLERY_KEYS = /* @__PURE__ */ new Set([
|
|
23547
|
+
"checkoutMedia",
|
|
23548
|
+
"checkoutImages",
|
|
23549
|
+
"checkoutImagesMedia"
|
|
23550
|
+
]);
|
|
23551
|
+
var BASE_DETAIL_KEYS = /* @__PURE__ */ new Set([
|
|
23552
|
+
"id",
|
|
23553
|
+
"productId",
|
|
23554
|
+
"name",
|
|
23555
|
+
"slug",
|
|
23556
|
+
"title",
|
|
23557
|
+
"active",
|
|
23558
|
+
"archived",
|
|
23559
|
+
"isFree",
|
|
23560
|
+
"priceCount",
|
|
23561
|
+
"createdAt",
|
|
23562
|
+
"description",
|
|
23563
|
+
"language",
|
|
23564
|
+
"successUrl",
|
|
23565
|
+
"prices",
|
|
23566
|
+
"orderBumps"
|
|
23567
|
+
]);
|
|
23568
|
+
function galleryOf(checkout) {
|
|
23569
|
+
if (checkout.checkoutMedia) return [...checkout.checkoutMedia];
|
|
23570
|
+
return (checkout.checkoutImages ?? []).map((url, index) => ({
|
|
23571
|
+
type: IMAGE_MEDIA_TYPE2,
|
|
23572
|
+
url,
|
|
23573
|
+
mediaId: checkout.checkoutImagesMedia?.[index]?.mediaId ?? null,
|
|
23574
|
+
thumbnailUrl: null
|
|
23575
|
+
}));
|
|
23576
|
+
}
|
|
23577
|
+
function galleryPatch(media) {
|
|
23578
|
+
const images = media.filter((item) => item.type === IMAGE_MEDIA_TYPE2);
|
|
23579
|
+
return {
|
|
23580
|
+
checkoutMedia: media,
|
|
23581
|
+
checkoutImages: images.map((item) => item.url),
|
|
23582
|
+
checkoutImagesMedia: images.map((item) => ({ url: item.url, mediaId: item.mediaId }))
|
|
23583
|
+
};
|
|
23584
|
+
}
|
|
23585
|
+
function bunnyEmbedUrl(video) {
|
|
23586
|
+
return `https://iframe.mediadelivery.net/embed/${video.bunnyLibraryId}/${video.bunnyVideoId}`;
|
|
23587
|
+
}
|
|
23588
|
+
function galleryEntryFromLibrary(item) {
|
|
23589
|
+
const type = MEDIA_TYPES.find((candidate) => candidate === item.type);
|
|
23590
|
+
if (type === void 0) {
|
|
23591
|
+
throw new Error(
|
|
23592
|
+
`Media ${item.id} is a ${item.type} \u2014 only images and videos can go in the offer gallery.`
|
|
23593
|
+
);
|
|
23594
|
+
}
|
|
23595
|
+
if (type === VIDEO_MEDIA_TYPE) {
|
|
23596
|
+
if (item.uploadStatus !== READY_UPLOAD_STATUS || !item.video) {
|
|
23597
|
+
throw new Error(
|
|
23598
|
+
`Media ${item.id} is a video that is not ready yet (uploadStatus: ${item.uploadStatus}) \u2014 add it once it finishes processing.`
|
|
23599
|
+
);
|
|
23600
|
+
}
|
|
23601
|
+
return {
|
|
23602
|
+
type,
|
|
23603
|
+
url: bunnyEmbedUrl(item.video),
|
|
23604
|
+
mediaId: item.id,
|
|
23605
|
+
thumbnailUrl: item.thumbnailUrl ?? null
|
|
23606
|
+
};
|
|
23607
|
+
}
|
|
23608
|
+
if (!item.url) {
|
|
23609
|
+
throw new Error(`Media ${item.id} has no public URL yet (uploadStatus: ${item.uploadStatus}).`);
|
|
21717
23610
|
}
|
|
21718
|
-
return
|
|
23611
|
+
return { type, url: item.url, mediaId: item.id, thumbnailUrl: item.thumbnailUrl ?? null };
|
|
21719
23612
|
}
|
|
21720
|
-
function
|
|
21721
|
-
|
|
21722
|
-
|
|
21723
|
-
|
|
21724
|
-
|
|
23613
|
+
function printGallery(media) {
|
|
23614
|
+
printTable(
|
|
23615
|
+
media.map((item, index) => ({
|
|
23616
|
+
index,
|
|
23617
|
+
type: item.type,
|
|
23618
|
+
mediaId: item.mediaId ?? "",
|
|
23619
|
+
url: item.url,
|
|
23620
|
+
thumbnailUrl: item.thumbnailUrl ?? ""
|
|
23621
|
+
})),
|
|
23622
|
+
["index", "type", "mediaId", "url", "thumbnailUrl"]
|
|
23623
|
+
);
|
|
21725
23624
|
}
|
|
21726
|
-
function
|
|
21727
|
-
const
|
|
21728
|
-
|
|
21729
|
-
|
|
21730
|
-
|
|
21731
|
-
|
|
21732
|
-
throw new Error(`Invalid JSON for ${flag}: ${error.message}`);
|
|
21733
|
-
}
|
|
23625
|
+
function fullSettingsOf(checkout) {
|
|
23626
|
+
const isRendered = (key) => {
|
|
23627
|
+
const known = key;
|
|
23628
|
+
return BASE_DETAIL_KEYS.has(known) || GALLERY_KEYS.has(known);
|
|
23629
|
+
};
|
|
23630
|
+
return Object.fromEntries(Object.entries(checkout).filter(([key]) => !isRendered(key)));
|
|
21734
23631
|
}
|
|
21735
23632
|
function formatAmount2(amountCents, currency) {
|
|
21736
23633
|
return `${(amountCents / 100).toFixed(2)} ${currency}`;
|
|
@@ -21984,10 +23881,12 @@ function registerOffersCommands(program2) {
|
|
|
21984
23881
|
);
|
|
21985
23882
|
});
|
|
21986
23883
|
});
|
|
21987
|
-
offers.command("get").description(
|
|
23884
|
+
offers.command("get").description(
|
|
23885
|
+
"Get a single offer, including its prices and attached order bumps. --full adds every editable setting (media gallery, theme, deadline, afterPurchase, oto chain, form, legal, invoicing, notifications, tracking) \u2014 the same keys `offers update --json` accepts"
|
|
23886
|
+
).argument("<checkoutId>", "offer (checkout) id").option("--full", "include the whole editable settings surface (?include=full)").action(async (checkoutId, options, command) => {
|
|
21988
23887
|
const flags = getGlobalFlags(command);
|
|
21989
23888
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
21990
|
-
const checkout = await client.getCheckout(checkoutId);
|
|
23889
|
+
const checkout = await client.getCheckout(checkoutId, { full: options.full === true });
|
|
21991
23890
|
emit(flags, checkout, () => {
|
|
21992
23891
|
process.stdout.write(`Offer ${checkout.id} \u2014 ${checkout.name}
|
|
21993
23892
|
`);
|
|
@@ -22047,6 +23946,14 @@ Order bumps (${checkout.orderBumps.length}):
|
|
|
22047
23946
|
process.stdout.write(` ${bump.orderBumpId} (${scope})
|
|
22048
23947
|
`);
|
|
22049
23948
|
}
|
|
23949
|
+
if (!options.full) return;
|
|
23950
|
+
const gallery = galleryOf(checkout);
|
|
23951
|
+
process.stdout.write(`
|
|
23952
|
+
Media gallery (${gallery.length}):
|
|
23953
|
+
`);
|
|
23954
|
+
printGallery(gallery);
|
|
23955
|
+
process.stdout.write("\nSettings (--full):\n");
|
|
23956
|
+
printJson(fullSettingsOf(checkout));
|
|
22050
23957
|
});
|
|
22051
23958
|
});
|
|
22052
23959
|
offers.command("create").description("Create a new offer for a product").requiredOption("--product <productId>", "product the offer sells").option("--free", "create a free offer (cannot carry prices)").option("--name <name>", "offer name").action(
|
|
@@ -22255,21 +24162,23 @@ Order bumps (${checkout.orderBumps.length}):
|
|
|
22255
24162
|
);
|
|
22256
24163
|
});
|
|
22257
24164
|
});
|
|
22258
|
-
price.command("update").description(
|
|
24165
|
+
price.command("update").description(
|
|
24166
|
+
"Mutate a price variant: --operation activate | deactivate | hide | unhide | change-amount (with --amount) | edit-details (with --label / --description). --label / --description alone imply edit-details"
|
|
24167
|
+
).argument("<checkoutId>", "offer (checkout) id").argument("<priceId>", "price id").option(
|
|
24168
|
+
"--operation <op>",
|
|
24169
|
+
"activate | deactivate | hide | unhide | change-amount | edit-details (implied by --label / --description)"
|
|
24170
|
+
).option("--amount <minor-units>", "change-amount: new price in minor units, e.g. 1999 = 19.99").option("--label <text>", "edit-details: new display label").option(
|
|
24171
|
+
"--description <markdown>",
|
|
24172
|
+
"edit-details: new markdown description under the price (empty string clears it)"
|
|
24173
|
+
).action(
|
|
22259
24174
|
async (checkoutId, priceId, options, command) => {
|
|
22260
24175
|
const flags = getGlobalFlags(command);
|
|
22261
|
-
const
|
|
22262
|
-
if (operation === "change-amount" && !options.amount) {
|
|
22263
|
-
throw new Error("--operation change-amount requires --amount <minor-units>.");
|
|
22264
|
-
}
|
|
24176
|
+
const input = buildPriceUpdateInput(options);
|
|
22265
24177
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
22266
|
-
const result = await client.updateCheckoutPrice(checkoutId, priceId,
|
|
22267
|
-
operation,
|
|
22268
|
-
newAmountCents: operation === "change-amount" && options.amount ? parsePositiveInt12(options.amount, "--amount") : void 0
|
|
22269
|
-
});
|
|
24178
|
+
const result = await client.updateCheckoutPrice(checkoutId, priceId, input);
|
|
22270
24179
|
emit(flags, result, () => {
|
|
22271
24180
|
process.stdout.write(
|
|
22272
|
-
`Applied ${operation} to price ${result.priceId} on offer ${result.checkoutId}
|
|
24181
|
+
`Applied ${input.operation} to price ${result.priceId} on offer ${result.checkoutId}
|
|
22273
24182
|
`
|
|
22274
24183
|
);
|
|
22275
24184
|
});
|
|
@@ -22305,6 +24214,87 @@ Order bumps (${checkout.orderBumps.length}):
|
|
|
22305
24214
|
`);
|
|
22306
24215
|
});
|
|
22307
24216
|
});
|
|
24217
|
+
const media = offers.command("media").description("Manage an offer's media gallery (checkout images and videos)");
|
|
24218
|
+
media.command("list").description("List the offer's gallery entries in display order (index 0 is featured)").argument("<checkoutId>", "offer (checkout) id").action(async (checkoutId, _, command) => {
|
|
24219
|
+
const flags = getGlobalFlags(command);
|
|
24220
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
24221
|
+
const checkout = await client.getCheckout(checkoutId, { full: true });
|
|
24222
|
+
const gallery = galleryOf(checkout);
|
|
24223
|
+
emit(flags, { checkoutId: checkout.id, count: gallery.length, media: gallery }, () => {
|
|
24224
|
+
process.stdout.write(`Media gallery of offer ${checkout.id} (${gallery.length}):
|
|
24225
|
+
`);
|
|
24226
|
+
printGallery(gallery);
|
|
24227
|
+
});
|
|
24228
|
+
});
|
|
24229
|
+
media.command("add").description(
|
|
24230
|
+
"Append a Media Library item (--media <mediaId>; videos must be ready) or an external URL (--url with --type) to the gallery"
|
|
24231
|
+
).argument("<checkoutId>", "offer (checkout) id").option("--media <mediaId>", "Media Library item id (see `media list`)").option("--url <url>", "external image URL or video iframe embed URL (needs --type)").option("--type <image|video>", "with --url: what the URL renders as").option("--thumbnail <url>", "with --url: poster/thumbnail URL for the strip").action(
|
|
24232
|
+
async (checkoutId, options, command) => {
|
|
24233
|
+
if (options.media === void 0 === (options.url === void 0)) {
|
|
24234
|
+
throw new Error("Pass exactly one of --media <mediaId> or --url <url>.");
|
|
24235
|
+
}
|
|
24236
|
+
if (options.media !== void 0 && (options.type !== void 0 || options.thumbnail)) {
|
|
24237
|
+
throw new Error(
|
|
24238
|
+
"--type / --thumbnail only apply with --url (a library item carries its own)."
|
|
24239
|
+
);
|
|
24240
|
+
}
|
|
24241
|
+
const flags = getGlobalFlags(command);
|
|
24242
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
24243
|
+
let entry;
|
|
24244
|
+
if (options.media !== void 0) {
|
|
24245
|
+
const { media: item } = await client.getMedia(options.media);
|
|
24246
|
+
entry = galleryEntryFromLibrary(item);
|
|
24247
|
+
} else {
|
|
24248
|
+
if (options.type === void 0) {
|
|
24249
|
+
throw new Error("--url requires --type <image|video>.");
|
|
24250
|
+
}
|
|
24251
|
+
entry = {
|
|
24252
|
+
type: parseChoice4(options.type, MEDIA_TYPES, "--type"),
|
|
24253
|
+
url: options.url ?? "",
|
|
24254
|
+
mediaId: null,
|
|
24255
|
+
thumbnailUrl: options.thumbnail ?? null
|
|
24256
|
+
};
|
|
24257
|
+
}
|
|
24258
|
+
const checkout = await client.getCheckout(checkoutId, { full: true });
|
|
24259
|
+
const gallery = galleryOf(checkout);
|
|
24260
|
+
const duplicate = gallery.find(
|
|
24261
|
+
(item) => entry.mediaId !== null && item.mediaId === entry.mediaId || item.url === entry.url
|
|
24262
|
+
);
|
|
24263
|
+
if (duplicate) {
|
|
24264
|
+
throw new Error(
|
|
24265
|
+
`Already in the gallery: ${duplicate.mediaId ?? duplicate.url} (index ${gallery.indexOf(duplicate)}).`
|
|
24266
|
+
);
|
|
24267
|
+
}
|
|
24268
|
+
const next = [...gallery, entry];
|
|
24269
|
+
const result = await client.updateCheckout(checkoutId, galleryPatch(next));
|
|
24270
|
+
emit(flags, { checkoutId: result.checkoutId, added: entry, media: next }, () => {
|
|
24271
|
+
process.stdout.write(
|
|
24272
|
+
`Added ${entry.type} ${entry.mediaId ?? entry.url} to offer ${result.checkoutId} (gallery: ${next.length})
|
|
24273
|
+
`
|
|
24274
|
+
);
|
|
24275
|
+
});
|
|
24276
|
+
}
|
|
24277
|
+
);
|
|
24278
|
+
media.command("remove").description("Remove a gallery entry by its media id or URL").argument("<checkoutId>", "offer (checkout) id").argument("<mediaIdOrUrl>", "media id or URL of the entry to drop (see `offers media list`)").action(async (checkoutId, ref, _, command) => {
|
|
24279
|
+
const flags = getGlobalFlags(command);
|
|
24280
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
24281
|
+
const checkout = await client.getCheckout(checkoutId, { full: true });
|
|
24282
|
+
const gallery = galleryOf(checkout);
|
|
24283
|
+
const removed = gallery.filter((item) => item.mediaId === ref || item.url === ref);
|
|
24284
|
+
if (removed.length === 0) {
|
|
24285
|
+
throw new Error(
|
|
24286
|
+
`No gallery entry matches ${ref} on offer ${checkoutId} \u2014 run \`offers media list ${checkoutId}\`.`
|
|
24287
|
+
);
|
|
24288
|
+
}
|
|
24289
|
+
const next = gallery.filter((item) => !removed.includes(item));
|
|
24290
|
+
const result = await client.updateCheckout(checkoutId, galleryPatch(next));
|
|
24291
|
+
emit(flags, { checkoutId: result.checkoutId, removed, media: next }, () => {
|
|
24292
|
+
process.stdout.write(
|
|
24293
|
+
`Removed ${removed.length} ${removed.length === 1 ? "entry" : "entries"} (${ref}) from offer ${result.checkoutId} (gallery: ${next.length})
|
|
24294
|
+
`
|
|
24295
|
+
);
|
|
24296
|
+
});
|
|
24297
|
+
});
|
|
22308
24298
|
}
|
|
22309
24299
|
|
|
22310
24300
|
// src/commands/orders/index.ts
|
|
@@ -22326,7 +24316,7 @@ function parsePositiveInt13(value, flag) {
|
|
|
22326
24316
|
function formatAmount3(amountCents, currency) {
|
|
22327
24317
|
return `${(amountCents / 100).toFixed(2)} ${currency}`;
|
|
22328
24318
|
}
|
|
22329
|
-
function
|
|
24319
|
+
function requireConfirm11(confirm, what) {
|
|
22330
24320
|
if (!confirm) {
|
|
22331
24321
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
22332
24322
|
}
|
|
@@ -22425,7 +24415,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
22425
24415
|
).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(
|
|
22426
24416
|
async (orderId, options, command) => {
|
|
22427
24417
|
const flags = getGlobalFlags(command);
|
|
22428
|
-
|
|
24418
|
+
requireConfirm11(options.confirm, `approve order ${orderId} (access + purchase email)`);
|
|
22429
24419
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
22430
24420
|
const result = await client.approveManualOrder(orderId, {
|
|
22431
24421
|
issueInvoice: options.issueInvoice === true ? true : void 0
|
|
@@ -22442,7 +24432,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
22442
24432
|
"Reject a manual (wire-transfer) order \u2014 cancels it and notifies the buyer. 409 = the order was already decided. Requires --confirm"
|
|
22443
24433
|
).argument("<orderId>", "order id (a `pending` order from `orders list --status pending`)").option("--confirm", "required \u2014 acknowledge that the order is cancelled and the buyer emailed").action(async (orderId, options, command) => {
|
|
22444
24434
|
const flags = getGlobalFlags(command);
|
|
22445
|
-
|
|
24435
|
+
requireConfirm11(options.confirm, `reject order ${orderId} (cancel + notify the buyer)`);
|
|
22446
24436
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
22447
24437
|
const result = await client.rejectManualOrder(orderId);
|
|
22448
24438
|
emit(flags, result, () => {
|
|
@@ -22492,12 +24482,12 @@ Next page: --cursor ${page.nextCursor}
|
|
|
22492
24482
|
}
|
|
22493
24483
|
|
|
22494
24484
|
// src/commands/oto/index.ts
|
|
22495
|
-
import { readFileSync as
|
|
24485
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
22496
24486
|
var MIN_TIMER_MINUTES = 1;
|
|
22497
24487
|
var MAX_TIMER_MINUTES = 120;
|
|
22498
24488
|
function parseJsonFileOrInline7(value, flag) {
|
|
22499
24489
|
const trimmed = value.trim();
|
|
22500
|
-
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed :
|
|
24490
|
+
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed : readFileSync20(value, "utf8");
|
|
22501
24491
|
try {
|
|
22502
24492
|
return JSON.parse(raw);
|
|
22503
24493
|
} catch (error) {
|
|
@@ -22685,7 +24675,7 @@ function registerOtoCommands(program2) {
|
|
|
22685
24675
|
}
|
|
22686
24676
|
|
|
22687
24677
|
// src/commands/pages/index.ts
|
|
22688
|
-
import { existsSync as existsSync3, readFileSync as
|
|
24678
|
+
import { existsSync as existsSync3, readFileSync as readFileSync21 } from "fs";
|
|
22689
24679
|
import { readFile as readFile2 } from "fs/promises";
|
|
22690
24680
|
async function resolveHtml2(options) {
|
|
22691
24681
|
if (options.file) {
|
|
@@ -22732,13 +24722,13 @@ function parseCsv4(value) {
|
|
|
22732
24722
|
];
|
|
22733
24723
|
}
|
|
22734
24724
|
function resolveTextOrFile2(value) {
|
|
22735
|
-
const text = existsSync3(value) ?
|
|
24725
|
+
const text = existsSync3(value) ? readFileSync21(value, "utf8") : value;
|
|
22736
24726
|
if (!text.trim()) throw new Error("The text (or the file it points to) is empty.");
|
|
22737
24727
|
return text;
|
|
22738
24728
|
}
|
|
22739
24729
|
function parseJsonObjectFileOrInline3(value, flag) {
|
|
22740
24730
|
const trimmed = value.trim();
|
|
22741
|
-
const raw = trimmed.startsWith("{") ? trimmed :
|
|
24731
|
+
const raw = trimmed.startsWith("{") ? trimmed : readFileSync21(value, "utf8");
|
|
22742
24732
|
let parsed;
|
|
22743
24733
|
try {
|
|
22744
24734
|
parsed = JSON.parse(raw);
|
|
@@ -22750,7 +24740,7 @@ function parseJsonObjectFileOrInline3(value, flag) {
|
|
|
22750
24740
|
}
|
|
22751
24741
|
return parsed;
|
|
22752
24742
|
}
|
|
22753
|
-
function
|
|
24743
|
+
function requireConfirm12(confirm, what) {
|
|
22754
24744
|
if (!confirm) {
|
|
22755
24745
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
22756
24746
|
}
|
|
@@ -22945,7 +24935,7 @@ function registerPagesCommands(program2) {
|
|
|
22945
24935
|
"inline JSON object or .json path merged into the request body (explicit flags win)"
|
|
22946
24936
|
).option("--confirm", "required \u2014 acknowledge that AI credits will be consumed").action(async (options, command) => {
|
|
22947
24937
|
const flags = getGlobalFlags(command);
|
|
22948
|
-
|
|
24938
|
+
requireConfirm12(options.confirm, "generate a page (consumes AI credits)");
|
|
22949
24939
|
const input = buildGenerateInput(options);
|
|
22950
24940
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
22951
24941
|
const result = await client.generatePage(input).catch(withSchedulerHint3);
|
|
@@ -22967,7 +24957,7 @@ function registerPagesCommands(program2) {
|
|
|
22967
24957
|
).action(
|
|
22968
24958
|
async (pageId, options, command) => {
|
|
22969
24959
|
const flags = getGlobalFlags(command);
|
|
22970
|
-
|
|
24960
|
+
requireConfirm12(
|
|
22971
24961
|
options.confirm,
|
|
22972
24962
|
"AI-edit the page (consumes AI credits, replaces the HTML)"
|
|
22973
24963
|
);
|
|
@@ -23026,7 +25016,7 @@ function registerPagesCommands(program2) {
|
|
|
23026
25016
|
).argument("<pageId>", "page id").argument("<revisionId>", "revision id from `pages revisions`").option("--confirm", "required \u2014 acknowledge that the current HTML will be replaced").action(
|
|
23027
25017
|
async (pageId, revisionId, options, command) => {
|
|
23028
25018
|
const flags = getGlobalFlags(command);
|
|
23029
|
-
|
|
25019
|
+
requireConfirm12(options.confirm, "restore the revision (replaces the current HTML)");
|
|
23030
25020
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23031
25021
|
const result = await client.restorePageRevision(pageId, revisionId);
|
|
23032
25022
|
emit(flags, result, () => {
|
|
@@ -23039,7 +25029,7 @@ function registerPagesCommands(program2) {
|
|
|
23039
25029
|
"One-click activation of everything generated around a page: publishes the DRAFT delivery automation and schedules the webinar follow-up broadcasts at their stored send times. Emails will go out for real; items whose copy still carries [FILL IN] placeholders are skipped, never sent. Requires --confirm."
|
|
23040
25030
|
).argument("<pageId>", "page id").option("--confirm", "required \u2014 acknowledge that automations go live and emails will send").action(async (pageId, options, command) => {
|
|
23041
25031
|
const flags = getGlobalFlags(command);
|
|
23042
|
-
|
|
25032
|
+
requireConfirm12(options.confirm, "activate the page automations (emails will send)");
|
|
23043
25033
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23044
25034
|
const result = await client.activatePageAutomations(pageId);
|
|
23045
25035
|
emit(flags, result, () => {
|
|
@@ -23069,7 +25059,9 @@ function registerPagesCommands(program2) {
|
|
|
23069
25059
|
}
|
|
23070
25060
|
|
|
23071
25061
|
// src/commands/products/index.ts
|
|
23072
|
-
import { readFileSync as
|
|
25062
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
25063
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
25064
|
+
import { basename as basename2 } from "path";
|
|
23073
25065
|
var KB_TREE_CHANGED_ERROR = "tree-changed";
|
|
23074
25066
|
var JSON_FLAG_HINT3 = "(write it as --json=<value> \u2014 a bare --json is the global JSON-output flag)";
|
|
23075
25067
|
function parseCoverRef(value) {
|
|
@@ -23175,7 +25167,7 @@ function assertOneOf3(value, allowed, flag) {
|
|
|
23175
25167
|
}
|
|
23176
25168
|
function parseJsonFileOrInline8(value, flag) {
|
|
23177
25169
|
const trimmed = value.trim();
|
|
23178
|
-
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed :
|
|
25170
|
+
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed : readFileSync22(value, "utf8");
|
|
23179
25171
|
try {
|
|
23180
25172
|
return JSON.parse(raw);
|
|
23181
25173
|
} catch (error) {
|
|
@@ -23185,6 +25177,49 @@ function parseJsonFileOrInline8(value, flag) {
|
|
|
23185
25177
|
function collectRepeatable5(value, previous) {
|
|
23186
25178
|
return [...previous, value];
|
|
23187
25179
|
}
|
|
25180
|
+
var HTTP_URL_PATTERN2 = /^https?:\/\//i;
|
|
25181
|
+
async function uploadLocalFile(client, filePath) {
|
|
25182
|
+
const bytes = await readFile3(filePath);
|
|
25183
|
+
const name = basename2(filePath);
|
|
25184
|
+
const result = await client.uploadMedia({
|
|
25185
|
+
name,
|
|
25186
|
+
mimeType: inferMimeType(name, bytes),
|
|
25187
|
+
data: bytes.toString("base64")
|
|
25188
|
+
});
|
|
25189
|
+
return { ...result, name, size: bytes.length };
|
|
25190
|
+
}
|
|
25191
|
+
async function resolveCoverUrl(client, value) {
|
|
25192
|
+
if (HTTP_URL_PATTERN2.test(value)) return value;
|
|
25193
|
+
const uploaded = await uploadLocalFile(client, value);
|
|
25194
|
+
return uploaded.url;
|
|
25195
|
+
}
|
|
25196
|
+
async function setProductCoverImages(client, productId, urls) {
|
|
25197
|
+
for (const url of urls) assertHttpsUrl(url, "--image");
|
|
25198
|
+
return client.setProductImages(productId, urls);
|
|
25199
|
+
}
|
|
25200
|
+
function printFilesTable(rows) {
|
|
25201
|
+
printTable(
|
|
25202
|
+
rows.map((f) => ({
|
|
25203
|
+
uid: f.uid,
|
|
25204
|
+
name: f.name,
|
|
25205
|
+
url: f.url,
|
|
25206
|
+
mediaId: f.mediaId ?? "",
|
|
25207
|
+
size: f.size ?? ""
|
|
25208
|
+
})),
|
|
25209
|
+
["uid", "name", "url", "mediaId", "size"]
|
|
25210
|
+
);
|
|
25211
|
+
}
|
|
25212
|
+
async function addProductFiles(client, productId, inputs) {
|
|
25213
|
+
return client.addDownloadFiles(productId, inputs);
|
|
25214
|
+
}
|
|
25215
|
+
function printAddedFiles(inputCount, result) {
|
|
25216
|
+
const skipped = inputCount - result.added.length;
|
|
25217
|
+
process.stdout.write(
|
|
25218
|
+
`Added ${result.added.length} file(s)${skipped > 0 ? ` (${skipped} skipped \u2014 already attached)` : ""}; ${result.files.length} total.
|
|
25219
|
+
`
|
|
25220
|
+
);
|
|
25221
|
+
printFilesTable(result.files);
|
|
25222
|
+
}
|
|
23188
25223
|
function registerProductsCommands(program2) {
|
|
23189
25224
|
const products2 = program2.command("products").description("Manage products");
|
|
23190
25225
|
products2.command("list").description("List products in the workspace").action(async (_, command) => {
|
|
@@ -23230,18 +25265,54 @@ function registerProductsCommands(program2) {
|
|
|
23230
25265
|
).requiredOption("--name <name>", "product name").requiredOption("--type <type>", "product type (course, newsletter, community, ...)").option("--description <description>", "product description").option("--free", "seed a free default offer").option("--amount <amount>", "seed a paid default offer \u2014 price in MAJOR units (49.99)").option("--currency <currency>", "currency for --amount (pln, usd, eur, gbp)").option(
|
|
23231
25266
|
"--period <period>",
|
|
23232
25267
|
"billing period for --amount: one_time, monthly or yearly (default one_time)"
|
|
25268
|
+
).option("--description-file <path>", "read the product description from a file").option(
|
|
25269
|
+
"--cover <pathOrUrl>",
|
|
25270
|
+
"cover image: a local file (uploaded to the media library first) or an https URL"
|
|
25271
|
+
).option(
|
|
25272
|
+
"--file <path>",
|
|
25273
|
+
"local file to upload and attach to the product (downloads products; repeat for several)",
|
|
25274
|
+
collectRepeatable5,
|
|
25275
|
+
[]
|
|
23233
25276
|
).action(
|
|
23234
25277
|
async (options, command) => {
|
|
23235
25278
|
const flags = getGlobalFlags(command);
|
|
23236
25279
|
const offer = parseCreateOfferFlags(options);
|
|
25280
|
+
if (options.description !== void 0 && options.descriptionFile !== void 0) {
|
|
25281
|
+
throw new Error("Use either --description or --description-file, not both.");
|
|
25282
|
+
}
|
|
25283
|
+
const description = options.descriptionFile !== void 0 ? readFileSync22(options.descriptionFile, "utf8") : options.description;
|
|
23237
25284
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23238
25285
|
const product = await client.createProduct({
|
|
23239
25286
|
name: options.name,
|
|
23240
25287
|
productType: options.type,
|
|
23241
|
-
description
|
|
25288
|
+
description,
|
|
23242
25289
|
offer
|
|
23243
25290
|
});
|
|
23244
|
-
|
|
25291
|
+
const outcome = {};
|
|
25292
|
+
try {
|
|
25293
|
+
if (options.cover !== void 0) {
|
|
25294
|
+
const coverUrl = await resolveCoverUrl(client, options.cover);
|
|
25295
|
+
outcome.cover = await setProductCoverImages(client, product.id, [coverUrl]);
|
|
25296
|
+
}
|
|
25297
|
+
if (options.file.length > 0) {
|
|
25298
|
+
const uploaded = [];
|
|
25299
|
+
for (const filePath of options.file)
|
|
25300
|
+
uploaded.push(await uploadLocalFile(client, filePath));
|
|
25301
|
+
outcome.uploadedFiles = uploaded;
|
|
25302
|
+
outcome.files = await addProductFiles(
|
|
25303
|
+
client,
|
|
25304
|
+
product.id,
|
|
25305
|
+
uploaded.map((u) => ({ url: u.url, name: u.name, mediaId: u.mediaId, size: u.size }))
|
|
25306
|
+
);
|
|
25307
|
+
}
|
|
25308
|
+
} catch (error) {
|
|
25309
|
+
process.stderr.write(
|
|
25310
|
+
`Product ${product.id} was created, but a follow-up step failed \u2014 fix it with \`products images set\` / \`products files add\`.
|
|
25311
|
+
`
|
|
25312
|
+
);
|
|
25313
|
+
throw error;
|
|
25314
|
+
}
|
|
25315
|
+
emit(flags, { ...product, ...outcome }, () => {
|
|
23245
25316
|
process.stdout.write(`Created product ${product.id} \u2014 ${product.name}
|
|
23246
25317
|
`);
|
|
23247
25318
|
if (offer) {
|
|
@@ -23250,6 +25321,17 @@ function registerProductsCommands(program2) {
|
|
|
23250
25321
|
` : " Default offer was requested but not seeded (check the dashboard).\n"
|
|
23251
25322
|
);
|
|
23252
25323
|
}
|
|
25324
|
+
if (outcome.cover) {
|
|
25325
|
+
process.stdout.write(` Cover image: ${outcome.cover.images.join(", ")}
|
|
25326
|
+
`);
|
|
25327
|
+
}
|
|
25328
|
+
if (outcome.uploadedFiles && outcome.files) {
|
|
25329
|
+
process.stdout.write(
|
|
25330
|
+
` Uploaded ${outcome.uploadedFiles.length} file(s): ${outcome.uploadedFiles.map((u) => `${u.name} (${u.mediaId})`).join(", ")}
|
|
25331
|
+
`
|
|
25332
|
+
);
|
|
25333
|
+
printAddedFiles(outcome.uploadedFiles.length, outcome.files);
|
|
25334
|
+
}
|
|
23253
25335
|
});
|
|
23254
25336
|
}
|
|
23255
25337
|
);
|
|
@@ -23727,9 +25809,8 @@ function registerProductsCommands(program2) {
|
|
|
23727
25809
|
if (options.image.length === 0) {
|
|
23728
25810
|
throw new Error("Pass at least one --image <url>.");
|
|
23729
25811
|
}
|
|
23730
|
-
for (const url of options.image) assertHttpsUrl(url, "--image");
|
|
23731
25812
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23732
|
-
const result = await client
|
|
25813
|
+
const result = await setProductCoverImages(client, productId, options.image);
|
|
23733
25814
|
emit(flags, result, () => {
|
|
23734
25815
|
process.stdout.write(
|
|
23735
25816
|
`Set ${result.images.length} cover image(s) on product ${productId}.
|
|
@@ -23738,18 +25819,6 @@ function registerProductsCommands(program2) {
|
|
|
23738
25819
|
});
|
|
23739
25820
|
});
|
|
23740
25821
|
const files = products2.command("files").description("Manage a downloads product content (Markdown description + file list)");
|
|
23741
|
-
const printFilesTable = (rows) => {
|
|
23742
|
-
printTable(
|
|
23743
|
-
rows.map((f) => ({
|
|
23744
|
-
uid: f.uid,
|
|
23745
|
-
name: f.name,
|
|
23746
|
-
url: f.url,
|
|
23747
|
-
mediaId: f.mediaId ?? "",
|
|
23748
|
-
size: f.size ?? ""
|
|
23749
|
-
})),
|
|
23750
|
-
["uid", "name", "url", "mediaId", "size"]
|
|
23751
|
-
);
|
|
23752
|
-
};
|
|
23753
25822
|
files.command("get").description("Show the downloads content: description presence + file table").requiredOption("--product <id>", "downloads product id").action(async (options, command) => {
|
|
23754
25823
|
const flags = getGlobalFlags(command);
|
|
23755
25824
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -23770,7 +25839,7 @@ function registerProductsCommands(program2) {
|
|
|
23770
25839
|
if (options.content !== void 0 && options.contentFile !== void 0) {
|
|
23771
25840
|
throw new Error("Use either --content or --content-file, not both.");
|
|
23772
25841
|
}
|
|
23773
|
-
const editorContent = options.contentFile !== void 0 ?
|
|
25842
|
+
const editorContent = options.contentFile !== void 0 ? readFileSync22(options.contentFile, "utf8") : options.content;
|
|
23774
25843
|
if (editorContent === void 0) {
|
|
23775
25844
|
throw new Error("Pass the description via --content or --content-file.");
|
|
23776
25845
|
}
|
|
@@ -23816,14 +25885,9 @@ function registerProductsCommands(program2) {
|
|
|
23816
25885
|
];
|
|
23817
25886
|
}
|
|
23818
25887
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23819
|
-
const result = await client
|
|
25888
|
+
const result = await addProductFiles(client, options.product, inputs);
|
|
23820
25889
|
emit(flags, result, () => {
|
|
23821
|
-
|
|
23822
|
-
process.stdout.write(
|
|
23823
|
-
`Added ${result.added.length} file(s)${skipped > 0 ? ` (${skipped} skipped \u2014 already attached)` : ""}; ${result.files.length} total.
|
|
23824
|
-
`
|
|
23825
|
-
);
|
|
23826
|
-
printFilesTable(result.files);
|
|
25890
|
+
printAddedFiles(inputs.length, result);
|
|
23827
25891
|
});
|
|
23828
25892
|
}
|
|
23829
25893
|
);
|
|
@@ -23869,7 +25933,7 @@ function registerProductsCommands(program2) {
|
|
|
23869
25933
|
if (options.content !== void 0 && options.contentFile !== void 0) {
|
|
23870
25934
|
throw new Error("Use either --content or --content-file, not both.");
|
|
23871
25935
|
}
|
|
23872
|
-
return options.contentFile !== void 0 ?
|
|
25936
|
+
return options.contentFile !== void 0 ? readFileSync22(options.contentFile, "utf8") : options.content;
|
|
23873
25937
|
};
|
|
23874
25938
|
kb.command("get").description(
|
|
23875
25939
|
"Show the knowledge base: categories (tree keys = the --category ids) + all articles (drafts and unavailable included \u2014 the creator view)"
|
|
@@ -24089,7 +26153,7 @@ function registerProductsCommands(program2) {
|
|
|
24089
26153
|
}
|
|
24090
26154
|
|
|
24091
26155
|
// src/commands/referrals/index.ts
|
|
24092
|
-
import { readFileSync as
|
|
26156
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
24093
26157
|
var PROGRAM_KINDS = ["referral", "affiliate"];
|
|
24094
26158
|
var WRITABLE_STATUSES = ["active", "inactive"];
|
|
24095
26159
|
var ENROLLMENT_MODES = ["open", "inviteOnly", "closed"];
|
|
@@ -24114,7 +26178,7 @@ function parseIdList3(value, flag) {
|
|
|
24114
26178
|
}
|
|
24115
26179
|
function parseJsonObjectFileOrInline4(value, flag) {
|
|
24116
26180
|
const trimmed = value.trim();
|
|
24117
|
-
const raw = trimmed.startsWith("{") ? trimmed :
|
|
26181
|
+
const raw = trimmed.startsWith("{") ? trimmed : readFileSync23(value, "utf8");
|
|
24118
26182
|
let parsed;
|
|
24119
26183
|
try {
|
|
24120
26184
|
parsed = JSON.parse(raw);
|
|
@@ -24152,7 +26216,7 @@ function parseDeepConfig(value) {
|
|
|
24152
26216
|
}
|
|
24153
26217
|
return parsed;
|
|
24154
26218
|
}
|
|
24155
|
-
function
|
|
26219
|
+
function requireConfirm13(confirm, what) {
|
|
24156
26220
|
if (!confirm) {
|
|
24157
26221
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
24158
26222
|
}
|
|
@@ -24387,7 +26451,7 @@ Failed deliveries (${result.failedDeliveries.length})
|
|
|
24387
26451
|
"Archive a program \u2014 new referrals stop counting and it leaves the active list. Requires --confirm."
|
|
24388
26452
|
).argument("<programId>", "program id").option("--confirm", "required \u2014 acknowledge the archive").action(async (programId, options, command) => {
|
|
24389
26453
|
const flags = getGlobalFlags(command);
|
|
24390
|
-
|
|
26454
|
+
requireConfirm13(options.confirm, `archive program ${programId}`);
|
|
24391
26455
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
24392
26456
|
const result = await client.archiveReferralProgram(programId);
|
|
24393
26457
|
emit(flags, result, () => {
|
|
@@ -24419,7 +26483,7 @@ Failed deliveries (${result.failedDeliveries.length})
|
|
|
24419
26483
|
"Enroll EVERY client with access to one of the program products. Requires --confirm. Reports enrolled / alreadyEnrolled / skippedWithoutAccount counts."
|
|
24420
26484
|
).argument("<programId>", "program id").option("--confirm", "required \u2014 acknowledge the bulk enrollment").action(async (programId, options, command) => {
|
|
24421
26485
|
const flags = getGlobalFlags(command);
|
|
24422
|
-
|
|
26486
|
+
requireConfirm13(options.confirm, `enroll all clients in program ${programId}`);
|
|
24423
26487
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
24424
26488
|
const result = await client.enrollAllReferralClients(programId);
|
|
24425
26489
|
emit(flags, result, () => {
|
|
@@ -24435,7 +26499,7 @@ Failed deliveries (${result.failedDeliveries.length})
|
|
|
24435
26499
|
async (programId, entryId, options, command) => {
|
|
24436
26500
|
const flags = getGlobalFlags(command);
|
|
24437
26501
|
const decision = parseChoice6(options.decision, REWARD_DECISIONS, "--decision");
|
|
24438
|
-
|
|
26502
|
+
requireConfirm13(options.confirm, `${decision} reward ${entryId} in program ${programId}`);
|
|
24439
26503
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
24440
26504
|
const result = await client.resolveReferralReward(programId, entryId, { decision });
|
|
24441
26505
|
emit(flags, result, () => {
|
|
@@ -24465,7 +26529,7 @@ Failed deliveries (${result.failedDeliveries.length})
|
|
|
24465
26529
|
}
|
|
24466
26530
|
|
|
24467
26531
|
// src/commands/segments/index.ts
|
|
24468
|
-
import { readFileSync as
|
|
26532
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
24469
26533
|
var SUBSCRIPTION_STATUSES2 = ["pending", "subscribed", "unsubscribed"];
|
|
24470
26534
|
function collectRepeatable6(value, previous) {
|
|
24471
26535
|
return [...previous, value];
|
|
@@ -24477,14 +26541,14 @@ function parseMinorUnits(value, flag) {
|
|
|
24477
26541
|
}
|
|
24478
26542
|
return n;
|
|
24479
26543
|
}
|
|
24480
|
-
var CONDITION_FLAGS_HELP = "--has-tag/--without-tag, --owns/--not-owns, --owns-direct/--not-owns-direct, --spent-gte/--spent-lte, --subscription, or --json-file";
|
|
26544
|
+
var CONDITION_FLAGS_HELP = "--has-tag/--without-tag, --has-any-tag/--has-none-tag, --owns/--not-owns, --owns-direct/--not-owns-direct, --spent-gte/--spent-lte, --subscription, or --json-file";
|
|
24481
26545
|
function resolveConditions(options) {
|
|
24482
|
-
const hasFlagConditions = options.hasTag.length > 0 || options.withoutTag.length > 0 || options.owns.length > 0 || options.notOwns.length > 0 || options.ownsDirect.length > 0 || options.notOwnsDirect.length > 0 || options.spentGte !== void 0 || options.spentLte !== void 0 || options.subscription !== void 0;
|
|
26546
|
+
const hasFlagConditions = options.hasTag.length > 0 || options.withoutTag.length > 0 || options.hasAnyTag.length > 0 || options.hasNoneTag.length > 0 || options.owns.length > 0 || options.notOwns.length > 0 || options.ownsDirect.length > 0 || options.notOwnsDirect.length > 0 || options.spentGte !== void 0 || options.spentLte !== void 0 || options.subscription !== void 0;
|
|
24483
26547
|
if (options.jsonFile !== void 0) {
|
|
24484
26548
|
if (hasFlagConditions) {
|
|
24485
26549
|
throw new Error("Use either condition flags or --json-file, not both.");
|
|
24486
26550
|
}
|
|
24487
|
-
const raw =
|
|
26551
|
+
const raw = readFileSync24(options.jsonFile, "utf8");
|
|
24488
26552
|
let parsed;
|
|
24489
26553
|
try {
|
|
24490
26554
|
parsed = JSON.parse(raw);
|
|
@@ -24506,6 +26570,12 @@ function resolveConditions(options) {
|
|
|
24506
26570
|
for (const tagId of options.withoutTag) {
|
|
24507
26571
|
conditions.push({ field: "tag", operator: "not-has", tagId });
|
|
24508
26572
|
}
|
|
26573
|
+
if (options.hasAnyTag.length > 0) {
|
|
26574
|
+
conditions.push({ field: "tag", operator: "has-any", tagIds: options.hasAnyTag });
|
|
26575
|
+
}
|
|
26576
|
+
if (options.hasNoneTag.length > 0) {
|
|
26577
|
+
conditions.push({ field: "tag", operator: "has-none", tagIds: options.hasNoneTag });
|
|
26578
|
+
}
|
|
24509
26579
|
for (const productId of options.owns) {
|
|
24510
26580
|
conditions.push({ field: "product", operator: "owns", productId });
|
|
24511
26581
|
}
|
|
@@ -24541,6 +26611,12 @@ function resolveConditions(options) {
|
|
|
24541
26611
|
}
|
|
24542
26612
|
conditions.push({ field: "emailSubscription", operator: "is", status });
|
|
24543
26613
|
}
|
|
26614
|
+
if (options.notOpened !== void 0) {
|
|
26615
|
+
conditions.push({ field: "campaign", operator: "not-opened", campaignId: options.notOpened });
|
|
26616
|
+
}
|
|
26617
|
+
if (options.notClicked !== void 0) {
|
|
26618
|
+
conditions.push({ field: "campaign", operator: "not-clicked", campaignId: options.notClicked });
|
|
26619
|
+
}
|
|
24544
26620
|
return conditions;
|
|
24545
26621
|
}
|
|
24546
26622
|
function withConditionOptions(command) {
|
|
@@ -24554,6 +26630,16 @@ function withConditionOptions(command) {
|
|
|
24554
26630
|
"client does NOT carry this tag (repeatable)",
|
|
24555
26631
|
collectRepeatable6,
|
|
24556
26632
|
[]
|
|
26633
|
+
).option(
|
|
26634
|
+
"--has-any-tag <tagId>",
|
|
26635
|
+
"client carries AT LEAST ONE of these tags (repeat the flag; one OR condition, max 10 ids) - one send to several tags without double-mailing",
|
|
26636
|
+
collectRepeatable6,
|
|
26637
|
+
[]
|
|
26638
|
+
).option(
|
|
26639
|
+
"--has-none-tag <tagId>",
|
|
26640
|
+
"client carries NONE of these tags (repeat the flag; one condition, max 10 ids)",
|
|
26641
|
+
collectRepeatable6,
|
|
26642
|
+
[]
|
|
24557
26643
|
).option(
|
|
24558
26644
|
"--owns <productId>",
|
|
24559
26645
|
"client has ACTIVE access to this product - a direct grant OR through a bundle (repeatable)",
|
|
@@ -24580,6 +26666,12 @@ function withConditionOptions(command) {
|
|
|
24580
26666
|
).option("--spent-lte <minorUnits>", "lifetime spend at most this (minor units)").option(
|
|
24581
26667
|
"--subscription <status>",
|
|
24582
26668
|
`marketing-email subscription state is (${SUBSCRIPTION_STATUSES2.join(" | ")})`
|
|
26669
|
+
).option(
|
|
26670
|
+
"--not-opened <campaignId>",
|
|
26671
|
+
"received this SENT campaign and never opened it (the resend-to-non-openers audience)"
|
|
26672
|
+
).option(
|
|
26673
|
+
"--not-clicked <campaignId>",
|
|
26674
|
+
"received this SENT campaign and never clicked a link in it"
|
|
24583
26675
|
).option(
|
|
24584
26676
|
"--json-file <path>",
|
|
24585
26677
|
"read the conditions as a JSON array from a file instead of flags"
|
|
@@ -24588,6 +26680,9 @@ function withConditionOptions(command) {
|
|
|
24588
26680
|
function describeCondition(condition) {
|
|
24589
26681
|
switch (condition.field) {
|
|
24590
26682
|
case "tag":
|
|
26683
|
+
if ("tagIds" in condition) {
|
|
26684
|
+
return `${condition.operator === "has-any" ? "has any of" : "has none of"} tags ${condition.tagIds.join(", ")}`;
|
|
26685
|
+
}
|
|
24591
26686
|
return `${condition.operator === "has" ? "has" : "lacks"} tag ${condition.tagId}`;
|
|
24592
26687
|
case "product":
|
|
24593
26688
|
return `${condition.operator === "owns" ? "owns" : "does not own"} product ${condition.productId}${condition.accessScope === "direct" ? " (direct)" : ""}`;
|
|
@@ -24603,6 +26698,8 @@ function describeCondition(condition) {
|
|
|
24603
26698
|
return `sms consent is ${condition.status}`;
|
|
24604
26699
|
case "phoneNumber":
|
|
24605
26700
|
return condition.operator === "has" ? "has a phone number" : "has no phone number";
|
|
26701
|
+
case "campaign":
|
|
26702
|
+
return `${condition.operator.replace("-", " ")} campaign ${condition.campaignId}`;
|
|
24606
26703
|
}
|
|
24607
26704
|
}
|
|
24608
26705
|
function registerSegmentsCommands(program2) {
|
|
@@ -24700,7 +26797,7 @@ function registerSegmentsCommands(program2) {
|
|
|
24700
26797
|
}
|
|
24701
26798
|
|
|
24702
26799
|
// src/commands/site/index.ts
|
|
24703
|
-
import { existsSync as existsSync4, readFileSync as
|
|
26800
|
+
import { existsSync as existsSync4, readFileSync as readFileSync25 } from "fs";
|
|
24704
26801
|
var MAIN_PAGE_MODES = {
|
|
24705
26802
|
sales: "/",
|
|
24706
26803
|
dashboard: "/dashboard",
|
|
@@ -24724,14 +26821,14 @@ var HTTP_CONFLICT = 409;
|
|
|
24724
26821
|
var HTTP_BAD_REQUEST = 400;
|
|
24725
26822
|
var HTTP_NOT_FOUND = 404;
|
|
24726
26823
|
var HTTP_FORBIDDEN = 403;
|
|
24727
|
-
function
|
|
26824
|
+
function requireConfirm14(confirm, what) {
|
|
24728
26825
|
if (!confirm) {
|
|
24729
26826
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
24730
26827
|
}
|
|
24731
26828
|
}
|
|
24732
26829
|
function parseJsonObjectFileOrInline5(value, flag) {
|
|
24733
26830
|
const trimmed = value.trim();
|
|
24734
|
-
const raw = trimmed.startsWith("{") ? trimmed :
|
|
26831
|
+
const raw = trimmed.startsWith("{") ? trimmed : readFileSync25(value, "utf8");
|
|
24735
26832
|
let parsed;
|
|
24736
26833
|
try {
|
|
24737
26834
|
parsed = JSON.parse(raw);
|
|
@@ -24746,7 +26843,7 @@ function parseJsonObjectFileOrInline5(value, flag) {
|
|
|
24746
26843
|
function baseBody(json) {
|
|
24747
26844
|
return json === void 0 ? {} : parseJsonObjectFileOrInline5(json, "--json");
|
|
24748
26845
|
}
|
|
24749
|
-
function
|
|
26846
|
+
function parseOnOff4(value, flag) {
|
|
24750
26847
|
const v = value.trim().toLowerCase();
|
|
24751
26848
|
if (v === "on" || v === "true") return true;
|
|
24752
26849
|
if (v === "off" || v === "false") return false;
|
|
@@ -24783,7 +26880,7 @@ function parseDomainList(value, what) {
|
|
|
24783
26880
|
return items;
|
|
24784
26881
|
}
|
|
24785
26882
|
function readMarkdownOrFile(value) {
|
|
24786
|
-
return existsSync4(value) ?
|
|
26883
|
+
return existsSync4(value) ? readFileSync25(value, "utf8") : value;
|
|
24787
26884
|
}
|
|
24788
26885
|
function assertNonEmptyBody(body, hint) {
|
|
24789
26886
|
if (Object.keys(body).length === 0) {
|
|
@@ -24990,7 +27087,7 @@ function registerSiteCommands(program2) {
|
|
|
24990
27087
|
body.language = parseChoice7(options.language, PLATFORM_LANGUAGES, "--language");
|
|
24991
27088
|
}
|
|
24992
27089
|
if (options.showPageTitle !== void 0) {
|
|
24993
|
-
body.showPageTitle =
|
|
27090
|
+
body.showPageTitle = parseOnOff4(options.showPageTitle, "--show-page-title");
|
|
24994
27091
|
}
|
|
24995
27092
|
assertNonEmptyBody(body, "pass --title, --description, --language or --json=<patch>.");
|
|
24996
27093
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -25012,7 +27109,7 @@ function registerSiteCommands(program2) {
|
|
|
25012
27109
|
if (options.radius !== void 0) body.radius = options.radius;
|
|
25013
27110
|
if (options.styleVariant !== void 0) body.styleVariant = options.styleVariant;
|
|
25014
27111
|
if (options.fromBrand !== void 0) {
|
|
25015
|
-
body.themeFromBrand =
|
|
27112
|
+
body.themeFromBrand = parseOnOff4(options.fromBrand, "--from-brand");
|
|
25016
27113
|
}
|
|
25017
27114
|
assertNonEmptyBody(body, "pass --theme, --radius, --from-brand \u2026 or --json=<patch>.");
|
|
25018
27115
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -25039,7 +27136,7 @@ function registerSiteCommands(program2) {
|
|
|
25039
27136
|
body.platformFaviconUrl = orNull6(options.faviconUrl);
|
|
25040
27137
|
}
|
|
25041
27138
|
if (options.hideBranding !== void 0) {
|
|
25042
|
-
body.hideZanfiaBranding =
|
|
27139
|
+
body.hideZanfiaBranding = parseOnOff4(options.hideBranding, "--hide-branding");
|
|
25043
27140
|
}
|
|
25044
27141
|
assertNonEmptyBody(
|
|
25045
27142
|
body,
|
|
@@ -25084,7 +27181,7 @@ function registerSiteCommands(program2) {
|
|
|
25084
27181
|
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) => {
|
|
25085
27182
|
const flags = getGlobalFlags(command);
|
|
25086
27183
|
const base = baseBody(options.json);
|
|
25087
|
-
const cookieConsentEnabled = options.cookieConsent !== void 0 ?
|
|
27184
|
+
const cookieConsentEnabled = options.cookieConsent !== void 0 ? parseOnOff4(options.cookieConsent, "--cookie-consent") : base.cookieConsentEnabled;
|
|
25088
27185
|
if (typeof cookieConsentEnabled !== "boolean") {
|
|
25089
27186
|
throw new Error(
|
|
25090
27187
|
'Pass --cookie-consent on|off (or a --json body with "cookieConsentEnabled").'
|
|
@@ -25151,14 +27248,14 @@ function registerSiteCommands(program2) {
|
|
|
25151
27248
|
if (options.libraryOrder === void 0) customization.libraryOrder = "custom";
|
|
25152
27249
|
}
|
|
25153
27250
|
if (options.groupByType !== void 0) {
|
|
25154
|
-
customization.libraryGroupByType =
|
|
27251
|
+
customization.libraryGroupByType = parseOnOff4(options.groupByType, "--group-by-type");
|
|
25155
27252
|
}
|
|
25156
27253
|
if (options.defaultView !== void 0) customization.defaultView = options.defaultView;
|
|
25157
27254
|
if (options.messages !== void 0) {
|
|
25158
|
-
customization.messagesEnabled =
|
|
27255
|
+
customization.messagesEnabled = parseOnOff4(options.messages, "--messages");
|
|
25159
27256
|
}
|
|
25160
27257
|
if (options.clientMessages !== void 0) {
|
|
25161
|
-
customization.clientMessagesEnabled =
|
|
27258
|
+
customization.clientMessagesEnabled = parseOnOff4(
|
|
25162
27259
|
options.clientMessages,
|
|
25163
27260
|
"--client-messages"
|
|
25164
27261
|
);
|
|
@@ -25266,7 +27363,7 @@ function registerSiteCommands(program2) {
|
|
|
25266
27363
|
throw new Error("Pass exactly one of --on or --off.");
|
|
25267
27364
|
}
|
|
25268
27365
|
const active = Boolean(options.on);
|
|
25269
|
-
|
|
27366
|
+
requireConfirm14(options.confirm, active ? "publish the site" : "take the site offline");
|
|
25270
27367
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
25271
27368
|
const result = await client.activateSite({ active });
|
|
25272
27369
|
emit(
|
|
@@ -25281,7 +27378,7 @@ function registerSiteCommands(program2) {
|
|
|
25281
27378
|
"Change the slug \u2014 heavy: re-registers Stripe/Firebase/Vercel hosts and purges caches. 409 = slug taken. Requires --confirm"
|
|
25282
27379
|
).argument("<slug>", 'new slug (lower-case letters, digits, dashes; "www" is refused)').option("--confirm", "required \u2014 acknowledge the URL change for every existing link").action(async (value, options, command) => {
|
|
25283
27380
|
const flags = getGlobalFlags(command);
|
|
25284
|
-
|
|
27381
|
+
requireConfirm14(options.confirm, `change the site slug to "${value}"`);
|
|
25285
27382
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
25286
27383
|
const result = await client.updateSiteSlug({ slug: value.trim() }).catch(withSlugHint);
|
|
25287
27384
|
emit(flags, result, () => process.stdout.write(`Slug changed to "${value.trim()}".
|
|
@@ -25293,7 +27390,7 @@ function registerSiteCommands(program2) {
|
|
|
25293
27390
|
).argument("<domains>", "comma-separated hostnames, e.g. shop.example.com,example.com \u2014 or none").option("--confirm", "required \u2014 acknowledge the domain list replace").action(async (value, options, command) => {
|
|
25294
27391
|
const flags = getGlobalFlags(command);
|
|
25295
27392
|
const list = value.trim().toLowerCase() === "none" ? [] : parseDomainList(value, "<domains>");
|
|
25296
|
-
|
|
27393
|
+
requireConfirm14(
|
|
25297
27394
|
options.confirm,
|
|
25298
27395
|
list.length === 0 ? "remove every custom domain" : `set the custom domains to ${list.join(", ")}`
|
|
25299
27396
|
);
|
|
@@ -25378,7 +27475,7 @@ function registerSiteCommands(program2) {
|
|
|
25378
27475
|
"Nothing to update \u2014 pass --terms-content|--terms-url and/or --privacy-content|--privacy-url."
|
|
25379
27476
|
);
|
|
25380
27477
|
}
|
|
25381
|
-
|
|
27478
|
+
requireConfirm14(options.confirm, "change the legal documents");
|
|
25382
27479
|
const body = {};
|
|
25383
27480
|
if (options.language !== void 0) body.language = options.language;
|
|
25384
27481
|
if (terms) body.terms = terms;
|
|
@@ -25430,8 +27527,8 @@ function printStorefront(config) {
|
|
|
25430
27527
|
}
|
|
25431
27528
|
|
|
25432
27529
|
// src/commands/smsCampaigns/index.ts
|
|
25433
|
-
import { readFileSync as
|
|
25434
|
-
var
|
|
27530
|
+
import { readFileSync as readFileSync26 } from "fs";
|
|
27531
|
+
var SMS_PROVIDERS2 = ["twilio", "smsapi"];
|
|
25435
27532
|
var RECIPIENT_STATUSES2 = [
|
|
25436
27533
|
"pending",
|
|
25437
27534
|
"sent",
|
|
@@ -25446,7 +27543,7 @@ function parsePositiveInt15(value, flag) {
|
|
|
25446
27543
|
}
|
|
25447
27544
|
return n;
|
|
25448
27545
|
}
|
|
25449
|
-
function
|
|
27546
|
+
function requireConfirm15(confirm, refusal) {
|
|
25450
27547
|
if (!confirm) throw new Error(refusal);
|
|
25451
27548
|
}
|
|
25452
27549
|
function withSchedulerHint4(error) {
|
|
@@ -25463,7 +27560,7 @@ function resolveBody(options) {
|
|
|
25463
27560
|
if (options.body !== void 0 && options.bodyFile !== void 0) {
|
|
25464
27561
|
throw new Error("Use either --body or --body-file, not both.");
|
|
25465
27562
|
}
|
|
25466
|
-
return options.bodyFile !== void 0 ?
|
|
27563
|
+
return options.bodyFile !== void 0 ? readFileSync26(options.bodyFile, "utf8") : options.body;
|
|
25467
27564
|
}
|
|
25468
27565
|
function resolveAudience2(options) {
|
|
25469
27566
|
if (options.segment !== void 0 && options.allSubscribed) {
|
|
@@ -25482,10 +27579,10 @@ function resolveProvider(options) {
|
|
|
25482
27579
|
}
|
|
25483
27580
|
if (options.defaultProvider) return null;
|
|
25484
27581
|
if (options.provider === void 0) return void 0;
|
|
25485
|
-
const match =
|
|
27582
|
+
const match = SMS_PROVIDERS2.find((p) => p === options.provider);
|
|
25486
27583
|
if (!match) {
|
|
25487
27584
|
throw new Error(
|
|
25488
|
-
`Invalid --provider: ${options.provider} (expected one of ${
|
|
27585
|
+
`Invalid --provider: ${options.provider} (expected one of ${SMS_PROVIDERS2.join(", ")})`
|
|
25489
27586
|
);
|
|
25490
27587
|
}
|
|
25491
27588
|
return match;
|
|
@@ -25570,7 +27667,7 @@ function registerSmsCampaignsCommands(program2) {
|
|
|
25570
27667
|
});
|
|
25571
27668
|
smsCampaigns.command("create").description(
|
|
25572
27669
|
"Create a DRAFT SMS campaign \u2014 nothing is sent; only --name is required at draft time (the body and a connected provider are enforced by `sms-campaigns send`)"
|
|
25573
|
-
).requiredOption("--name <name>", "internal label shown in the campaigns list").option("--body <text>", "message text; {{firstName}} / {{lastName}} tokens are supported").option("--body-file <path>", "read the message text from a file").option("--opt-out-hint <text>", "opt-out line appended to every message (counts as text)").option("--provider <provider>", `send through this provider (${
|
|
27670
|
+
).requiredOption("--name <name>", "internal label shown in the campaigns list").option("--body <text>", "message text; {{firstName}} / {{lastName}} tokens are supported").option("--body-file <path>", "read the message text from a file").option("--opt-out-hint <text>", "opt-out line appended to every message (counts as text)").option("--provider <provider>", `send through this provider (${SMS_PROVIDERS2.join(" | ")})`).option("--sender <name>", "sender name override (SMSAPI); omit for the connection's default").option("--segment <segmentId>", "send to a saved segment (SMS-subscribed clients in it only)").option("--all-subscribed", "send to every SMS-subscribed client (the default audience)").action(
|
|
25574
27671
|
async (options, command) => {
|
|
25575
27672
|
const flags = getGlobalFlags(command);
|
|
25576
27673
|
const body = resolveBody(options);
|
|
@@ -25593,7 +27690,7 @@ function registerSmsCampaignsCommands(program2) {
|
|
|
25593
27690
|
});
|
|
25594
27691
|
}
|
|
25595
27692
|
);
|
|
25596
|
-
smsCampaigns.command("update").description("Update a DRAFT SMS campaign (campaigns past draft are immutable except cancel)").argument("<id>", "SMS campaign id").option("--name <name>", "new internal label").option("--body <text>", "new message text (replaces the whole body)").option("--body-file <path>", "read the new message text from a file").option("--opt-out-hint <text>", "new opt-out line").option("--clear-opt-out-hint", "remove the opt-out line").option("--provider <provider>", `send through this provider (${
|
|
27693
|
+
smsCampaigns.command("update").description("Update a DRAFT SMS campaign (campaigns past draft are immutable except cancel)").argument("<id>", "SMS campaign id").option("--name <name>", "new internal label").option("--body <text>", "new message text (replaces the whole body)").option("--body-file <path>", "read the new message text from a file").option("--opt-out-hint <text>", "new opt-out line").option("--clear-opt-out-hint", "remove the opt-out line").option("--provider <provider>", `send through this provider (${SMS_PROVIDERS2.join(" | ")})`).option("--default-provider", "go back to the workspace default provider").option("--sender <name>", "sender name override (SMSAPI)").option("--default-sender", "go back to the connection's default sender").option("--segment <segmentId>", "switch the audience to a saved segment").option("--all-subscribed", "switch the audience to every SMS-subscribed client").action(
|
|
25597
27694
|
async (id, options, command) => {
|
|
25598
27695
|
const flags = getGlobalFlags(command);
|
|
25599
27696
|
const input = {};
|
|
@@ -25625,7 +27722,7 @@ function registerSmsCampaignsCommands(program2) {
|
|
|
25625
27722
|
"PERMANENTLY delete an SMS campaign \u2014 cannot be undone; draft/cancelled/failed only (sent campaigns are audit records and stay). Requires --confirm"
|
|
25626
27723
|
).argument("<id>", "SMS campaign id").option("--confirm", "required \u2014 acknowledge the permanent deletion").action(async (id, options, command) => {
|
|
25627
27724
|
const flags = getGlobalFlags(command);
|
|
25628
|
-
|
|
27725
|
+
requireConfirm15(
|
|
25629
27726
|
options.confirm,
|
|
25630
27727
|
`Refusing to delete SMS campaign ${id} without --confirm. Re-run with --confirm to proceed.`
|
|
25631
27728
|
);
|
|
@@ -25643,7 +27740,7 @@ function registerSmsCampaignsCommands(program2) {
|
|
|
25643
27740
|
"schedule the send (ISO, max 30 days out) instead of sending now"
|
|
25644
27741
|
).option("--confirm", "required \u2014 acknowledge this texts the audience for real").action(async (id, options, command) => {
|
|
25645
27742
|
const flags = getGlobalFlags(command);
|
|
25646
|
-
|
|
27743
|
+
requireConfirm15(
|
|
25647
27744
|
options.confirm,
|
|
25648
27745
|
`Refusing to send without --confirm. \`sms-campaigns send\` texts the ENTIRE resolved audience for real through the workspace's SMS provider account, is billed per message part and cannot be undone. Re-run with --confirm to proceed (check the draft first with \`sms-campaigns get ${id}\`).`
|
|
25649
27746
|
);
|
|
@@ -26272,7 +28369,7 @@ function describeAttempts(item) {
|
|
|
26272
28369
|
const next = item.nextPaymentAttempt !== null ? formatEpochSeconds(item.nextPaymentAttempt) : "";
|
|
26273
28370
|
return next ? `${attempts} (next ${next})` : String(attempts);
|
|
26274
28371
|
}
|
|
26275
|
-
function
|
|
28372
|
+
function requireConfirm16(confirm, what) {
|
|
26276
28373
|
if (!confirm) {
|
|
26277
28374
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
26278
28375
|
}
|
|
@@ -26346,7 +28443,7 @@ function registerSubscriptionsCommands(program2) {
|
|
|
26346
28443
|
"Retry a failed subscription charge by its Stripe invoice id (`in_\u2026` from `subscriptions failed`) \u2014 a REAL charge attempt on the customer's saved card. Requires --confirm"
|
|
26347
28444
|
).argument("<invoiceId>", "Stripe invoice id of the failed charge (in_\u2026)").option("--confirm", "required \u2014 acknowledge that the customer is charged for real").action(async (invoiceId, options, command) => {
|
|
26348
28445
|
const flags = getGlobalFlags(command);
|
|
26349
|
-
|
|
28446
|
+
requireConfirm16(options.confirm, `retry the charge for invoice ${invoiceId} (real charge)`);
|
|
26350
28447
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
26351
28448
|
const result = await client.retrySubscriptionPayment(invoiceId);
|
|
26352
28449
|
emit(flags, result, () => {
|
|
@@ -26883,7 +28980,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
26883
28980
|
}
|
|
26884
28981
|
|
|
26885
28982
|
// src/commands/workflows/index.ts
|
|
26886
|
-
import { readFileSync as
|
|
28983
|
+
import { readFileSync as readFileSync27 } from "fs";
|
|
26887
28984
|
|
|
26888
28985
|
// src/commands/workflows/authoring.ts
|
|
26889
28986
|
import { writeFileSync as writeFileSync6 } from "fs";
|
|
@@ -26911,6 +29008,10 @@ function registerWorkflowAuthoringCommands(workflows) {
|
|
|
26911
29008
|
).option(
|
|
26912
29009
|
"--trigger <eventType>",
|
|
26913
29010
|
"business event that starts it, e.g. product.ProductPurchased (see `workflows list` for examples)"
|
|
29011
|
+
).option(
|
|
29012
|
+
"--kind <kind>",
|
|
29013
|
+
"transactional (default for purchase/access triggers: no unsubscribe link, reaches everyone) or marketing (unsubscribe link + headers, e-mail/SMS steps skipped for unsubscribed contacts); omitted = derived from the triggers at publish",
|
|
29014
|
+
parseWorkflowKind
|
|
26914
29015
|
).action(
|
|
26915
29016
|
async (options, command) => {
|
|
26916
29017
|
const flags = getGlobalFlags(command);
|
|
@@ -26918,6 +29019,7 @@ function registerWorkflowAuthoringCommands(workflows) {
|
|
|
26918
29019
|
if (options.description !== void 0) input.description = options.description;
|
|
26919
29020
|
if (options.product !== void 0) input.productIds = options.product;
|
|
26920
29021
|
if (options.trigger !== void 0) input.triggerEventType = options.trigger;
|
|
29022
|
+
if (options.kind !== void 0) input.kind = options.kind;
|
|
26921
29023
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
26922
29024
|
const result = await client.createAutomation(input);
|
|
26923
29025
|
emit(flags, result, () => {
|
|
@@ -26972,6 +29074,10 @@ function registerWorkflowAuthoringCommands(workflows) {
|
|
|
26972
29074
|
"--product <ids>",
|
|
26973
29075
|
"also replace the product scope (comma-separated ids)",
|
|
26974
29076
|
parseCommaList("--product")
|
|
29077
|
+
).option(
|
|
29078
|
+
"--kind <kind>",
|
|
29079
|
+
"also set the kind: transactional or marketing (see `workflows create --help`)",
|
|
29080
|
+
parseWorkflowKind
|
|
26975
29081
|
).action(
|
|
26976
29082
|
async (automationId, options, command) => {
|
|
26977
29083
|
const flags = getGlobalFlags(command);
|
|
@@ -26980,7 +29086,8 @@ function registerWorkflowAuthoringCommands(workflows) {
|
|
|
26980
29086
|
draft: options.json,
|
|
26981
29087
|
...options.name !== void 0 ? { name: options.name } : {},
|
|
26982
29088
|
...options.description !== void 0 ? { description: options.description } : {},
|
|
26983
|
-
...options.product !== void 0 ? { productIds: options.product } : {}
|
|
29089
|
+
...options.product !== void 0 ? { productIds: options.product } : {},
|
|
29090
|
+
...options.kind !== void 0 ? { kind: options.kind } : {}
|
|
26984
29091
|
}).catch(withGraphHint);
|
|
26985
29092
|
emit(flags, result, () => {
|
|
26986
29093
|
if (result.issues.length > 0) {
|
|
@@ -27049,14 +29156,14 @@ function parsePositiveInt18(value, flag) {
|
|
|
27049
29156
|
}
|
|
27050
29157
|
return n;
|
|
27051
29158
|
}
|
|
27052
|
-
function
|
|
29159
|
+
function requireConfirm17(confirm, what) {
|
|
27053
29160
|
if (!confirm) {
|
|
27054
29161
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
27055
29162
|
}
|
|
27056
29163
|
}
|
|
27057
29164
|
function parseTriggerContext(value) {
|
|
27058
29165
|
const trimmed = value.trim();
|
|
27059
|
-
const raw = trimmed.startsWith("{") ? trimmed :
|
|
29166
|
+
const raw = trimmed.startsWith("{") ? trimmed : readFileSync27(value, "utf8");
|
|
27060
29167
|
let parsed;
|
|
27061
29168
|
try {
|
|
27062
29169
|
parsed = JSON.parse(raw);
|
|
@@ -27093,51 +29200,36 @@ function registerWorkflowsCommands(program2) {
|
|
|
27093
29200
|
id: w.id,
|
|
27094
29201
|
name: w.name,
|
|
27095
29202
|
status: w.status,
|
|
29203
|
+
kind: w.kind,
|
|
27096
29204
|
triggers: w.triggers.join(", "),
|
|
27097
29205
|
products: formatProductScope(w),
|
|
27098
29206
|
runsThisMonth: w.runsThisMonth,
|
|
27099
29207
|
lastRun: w.lastRunAt ?? ""
|
|
27100
29208
|
})),
|
|
27101
|
-
["id", "name", "status", "triggers", "products", "runsThisMonth", "lastRun"]
|
|
29209
|
+
["id", "name", "status", "kind", "triggers", "products", "runsThisMonth", "lastRun"]
|
|
27102
29210
|
);
|
|
27103
29211
|
});
|
|
27104
29212
|
});
|
|
27105
|
-
workflows.command("get").description(
|
|
29213
|
+
workflows.command("get").description(
|
|
29214
|
+
"Show a single workflow (triggers, product scope, node counts, last run); --graph walks every step incl. the emails it sends"
|
|
29215
|
+
).argument("<automationId>", "workflow automation id").option(
|
|
29216
|
+
"--graph",
|
|
29217
|
+
"also print the graph step by step in execution order \u2014 email subjects and body excerpts, delays, condition branches. Shows the published graph, plus the draft when it differs. With -o json the raw response includes draftGraph/publishedGraph."
|
|
29218
|
+
).action(async (automationId, options, command) => {
|
|
27106
29219
|
const flags = getGlobalFlags(command);
|
|
27107
29220
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
27108
|
-
|
|
29221
|
+
if (!options.graph) {
|
|
29222
|
+
const workflow2 = await client.getAutomation(automationId);
|
|
29223
|
+
emit(flags, workflow2, () => printWorkflowSummary(workflow2));
|
|
29224
|
+
return;
|
|
29225
|
+
}
|
|
29226
|
+
const workflow = await client.getAutomationGraph(automationId);
|
|
29227
|
+
const published = asAutomationGraph(workflow.publishedGraph);
|
|
29228
|
+
const draft = asAutomationGraph(workflow.draftGraph);
|
|
29229
|
+
const templateNames = await resolveTemplateNames(client, [published, draft]);
|
|
27109
29230
|
emit(flags, workflow, () => {
|
|
27110
|
-
|
|
27111
|
-
|
|
27112
|
-
if (workflow.description) process.stdout.write(` Description: ${workflow.description}
|
|
27113
|
-
`);
|
|
27114
|
-
process.stdout.write(` Status: ${workflow.status}
|
|
27115
|
-
`);
|
|
27116
|
-
process.stdout.write(` Triggers: ${workflow.triggers.join(", ")}
|
|
27117
|
-
`);
|
|
27118
|
-
process.stdout.write(
|
|
27119
|
-
` Products: ${workflow.productIds.length > 0 ? workflow.productIds.join(", ") : "all"}
|
|
27120
|
-
`
|
|
27121
|
-
);
|
|
27122
|
-
process.stdout.write(
|
|
27123
|
-
` Published: ${workflow.published ? `yes${workflow.publishedAt ? ` (${workflow.publishedAt})` : ""}` : "no (draft \u2014 cannot run until published from the dashboard)"}
|
|
27124
|
-
`
|
|
27125
|
-
);
|
|
27126
|
-
const { nodeCounts } = workflow;
|
|
27127
|
-
process.stdout.write(
|
|
27128
|
-
` Nodes: ${nodeCounts.triggers} triggers, ${nodeCounts.actions} actions, ${nodeCounts.conditions} conditions, ${nodeCounts.delays} delays
|
|
27129
|
-
`
|
|
27130
|
-
);
|
|
27131
|
-
process.stdout.write(` Runs this month: ${workflow.runsThisMonth}
|
|
27132
|
-
`);
|
|
27133
|
-
if (workflow.lastRunAt) {
|
|
27134
|
-
process.stdout.write(
|
|
27135
|
-
` Last run: ${workflow.lastRunAt}${workflow.lastRunStatus ? ` (${workflow.lastRunStatus})` : ""}
|
|
27136
|
-
`
|
|
27137
|
-
);
|
|
27138
|
-
}
|
|
27139
|
-
if (workflow.createdAt) process.stdout.write(` Created: ${workflow.createdAt}
|
|
27140
|
-
`);
|
|
29231
|
+
printWorkflowSummary(workflow);
|
|
29232
|
+
printWorkflowGraphs(workflow, published, draft, templateNames);
|
|
27141
29233
|
});
|
|
27142
29234
|
});
|
|
27143
29235
|
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) => {
|
|
@@ -27162,13 +29254,17 @@ function registerWorkflowsCommands(program2) {
|
|
|
27162
29254
|
);
|
|
27163
29255
|
});
|
|
27164
29256
|
});
|
|
27165
|
-
workflows.command("pause").description(
|
|
29257
|
+
workflows.command("pause").description(
|
|
29258
|
+
"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)"
|
|
29259
|
+
).argument("<automationId>", "workflow automation id").action(async (automationId, _, command) => {
|
|
27166
29260
|
const flags = getGlobalFlags(command);
|
|
27167
29261
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
27168
29262
|
const result = await client.setAutomationStatus(automationId, "paused");
|
|
27169
29263
|
emit(flags, result, () => {
|
|
27170
|
-
process.stdout.write(
|
|
27171
|
-
`)
|
|
29264
|
+
process.stdout.write(
|
|
29265
|
+
`Paused workflow ${result.id} (${result.cancelledRuns} in-flight run(s) cancelled).
|
|
29266
|
+
`
|
|
29267
|
+
);
|
|
27172
29268
|
});
|
|
27173
29269
|
});
|
|
27174
29270
|
workflows.command("resume").description(
|
|
@@ -27187,7 +29283,7 @@ function registerWorkflowsCommands(program2) {
|
|
|
27187
29283
|
).argument("<automationId>", "workflow automation id").argument("<runId>", "run id (from `workflows runs`)").option("--confirm", "required \u2014 acknowledge that the workflow actions execute again").action(
|
|
27188
29284
|
async (automationId, runId, options, command) => {
|
|
27189
29285
|
const flags = getGlobalFlags(command);
|
|
27190
|
-
|
|
29286
|
+
requireConfirm17(options.confirm, `retry run ${runId} (its actions execute again)`);
|
|
27191
29287
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
27192
29288
|
const result = await client.retryAutomationRun(automationId, runId).catch(withSchedulerHint5);
|
|
27193
29289
|
emit(flags, result, () => {
|
|
@@ -27211,7 +29307,7 @@ Follow it with: zanfia workflows runs ${automationId}
|
|
|
27211
29307
|
async (automationId, options, command) => {
|
|
27212
29308
|
const flags = getGlobalFlags(command);
|
|
27213
29309
|
const context = parseTriggerContext(options.context);
|
|
27214
|
-
|
|
29310
|
+
requireConfirm17(options.confirm, `test-run workflow ${automationId} (real actions fire)`);
|
|
27215
29311
|
process.stderr.write(
|
|
27216
29312
|
"Warning: a test run executes the published graph for real \u2014 emails are sent, tags applied, webhooks called. Point the context at a test client.\n"
|
|
27217
29313
|
);
|
|
@@ -27240,9 +29336,361 @@ function formatSteps(steps) {
|
|
|
27240
29336
|
if (steps.skipped > 0) parts.push(`${steps.skipped} skipped`);
|
|
27241
29337
|
return parts.join(", ");
|
|
27242
29338
|
}
|
|
29339
|
+
function printWorkflowSummary(workflow) {
|
|
29340
|
+
process.stdout.write(`Workflow ${workflow.name} (${workflow.id})
|
|
29341
|
+
`);
|
|
29342
|
+
if (workflow.description) process.stdout.write(` Description: ${workflow.description}
|
|
29343
|
+
`);
|
|
29344
|
+
process.stdout.write(` Status: ${workflow.status}
|
|
29345
|
+
`);
|
|
29346
|
+
process.stdout.write(` Kind: ${workflow.kind}
|
|
29347
|
+
`);
|
|
29348
|
+
process.stdout.write(` Triggers: ${workflow.triggers.join(", ")}
|
|
29349
|
+
`);
|
|
29350
|
+
process.stdout.write(
|
|
29351
|
+
` Products: ${workflow.productIds.length > 0 ? workflow.productIds.join(", ") : "all"}
|
|
29352
|
+
`
|
|
29353
|
+
);
|
|
29354
|
+
process.stdout.write(
|
|
29355
|
+
` Published: ${workflow.published ? `yes${workflow.publishedAt ? ` (${workflow.publishedAt})` : ""}` : "no (draft \u2014 cannot run until published from the dashboard)"}
|
|
29356
|
+
`
|
|
29357
|
+
);
|
|
29358
|
+
const { nodeCounts } = workflow;
|
|
29359
|
+
process.stdout.write(
|
|
29360
|
+
` Nodes: ${nodeCounts.triggers} triggers, ${nodeCounts.actions} actions, ${nodeCounts.conditions} conditions, ${nodeCounts.delays} delays
|
|
29361
|
+
`
|
|
29362
|
+
);
|
|
29363
|
+
process.stdout.write(` Runs this month: ${workflow.runsThisMonth}
|
|
29364
|
+
`);
|
|
29365
|
+
if (workflow.lastRunAt) {
|
|
29366
|
+
process.stdout.write(
|
|
29367
|
+
` Last run: ${workflow.lastRunAt}${workflow.lastRunStatus ? ` (${workflow.lastRunStatus})` : ""}
|
|
29368
|
+
`
|
|
29369
|
+
);
|
|
29370
|
+
}
|
|
29371
|
+
if (workflow.createdAt) process.stdout.write(` Created: ${workflow.createdAt}
|
|
29372
|
+
`);
|
|
29373
|
+
}
|
|
29374
|
+
var GRAPH_ACTION_LABELS = {
|
|
29375
|
+
sendEmail: "Send email",
|
|
29376
|
+
notifyOwner: "Notify owner (email)",
|
|
29377
|
+
sendSms: "Send SMS",
|
|
29378
|
+
webhook: "Webhook",
|
|
29379
|
+
publishSocialPost: "Publish social post",
|
|
29380
|
+
createCommunityPost: "Create community post",
|
|
29381
|
+
updateClientTags: "Update client tags",
|
|
29382
|
+
setEmailSubscription: "Set email subscription",
|
|
29383
|
+
openClientDeadline: "Open deadline window",
|
|
29384
|
+
requestTestimonial: "Request testimonial"
|
|
29385
|
+
};
|
|
29386
|
+
var GRAPH_NODE_KINDS = /* @__PURE__ */ new Set([
|
|
29387
|
+
"trigger",
|
|
29388
|
+
"action",
|
|
29389
|
+
"condition",
|
|
29390
|
+
"delay"
|
|
29391
|
+
]);
|
|
29392
|
+
var GRAPH_CONDITION_BRANCHES = ["true", "false"];
|
|
29393
|
+
var EXCERPT_LENGTH = 120;
|
|
29394
|
+
function isRecord2(value) {
|
|
29395
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
29396
|
+
}
|
|
29397
|
+
function optionalString(value) {
|
|
29398
|
+
return typeof value === "string" ? value : void 0;
|
|
29399
|
+
}
|
|
29400
|
+
function asAutomationGraph(raw) {
|
|
29401
|
+
if (!isRecord2(raw) || !Array.isArray(raw.nodes) || !Array.isArray(raw.edges)) return null;
|
|
29402
|
+
const nodes = [];
|
|
29403
|
+
for (const candidate of raw.nodes) {
|
|
29404
|
+
if (!isRecord2(candidate)) continue;
|
|
29405
|
+
const { id, kind, config, name, disabled } = candidate;
|
|
29406
|
+
if (typeof id !== "string" || typeof kind !== "string" || !GRAPH_NODE_KINDS.has(kind)) {
|
|
29407
|
+
continue;
|
|
29408
|
+
}
|
|
29409
|
+
nodes.push({
|
|
29410
|
+
id,
|
|
29411
|
+
kind,
|
|
29412
|
+
config: isRecord2(config) ? config : {},
|
|
29413
|
+
...typeof name === "string" && name.length > 0 ? { name } : {},
|
|
29414
|
+
...disabled === true ? { disabled: true } : {}
|
|
29415
|
+
});
|
|
29416
|
+
}
|
|
29417
|
+
const edges = [];
|
|
29418
|
+
for (const candidate of raw.edges) {
|
|
29419
|
+
if (!isRecord2(candidate)) continue;
|
|
29420
|
+
const { source, target, sourceHandle } = candidate;
|
|
29421
|
+
if (typeof source !== "string" || typeof target !== "string") continue;
|
|
29422
|
+
const branch = GRAPH_CONDITION_BRANCHES.find((b) => b === sourceHandle);
|
|
29423
|
+
edges.push({ source, target, ...branch !== void 0 ? { sourceHandle: branch } : {} });
|
|
29424
|
+
}
|
|
29425
|
+
return { nodes, edges };
|
|
29426
|
+
}
|
|
29427
|
+
function emailConfigOf(node) {
|
|
29428
|
+
const raw = node.config.email;
|
|
29429
|
+
if (!isRecord2(raw)) return null;
|
|
29430
|
+
return {
|
|
29431
|
+
templateId: optionalString(raw.templateId) ?? null,
|
|
29432
|
+
templateName: optionalString(raw.templateName) ?? null,
|
|
29433
|
+
subject: optionalString(raw.subject),
|
|
29434
|
+
html: optionalString(raw.html)
|
|
29435
|
+
};
|
|
29436
|
+
}
|
|
29437
|
+
async function resolveTemplateNames(client, graphs) {
|
|
29438
|
+
const ids = /* @__PURE__ */ new Set();
|
|
29439
|
+
for (const graph of graphs) {
|
|
29440
|
+
for (const node of graph?.nodes ?? []) {
|
|
29441
|
+
const templateId = emailConfigOf(node)?.templateId;
|
|
29442
|
+
if (templateId) ids.add(templateId);
|
|
29443
|
+
}
|
|
29444
|
+
}
|
|
29445
|
+
const names = /* @__PURE__ */ new Map();
|
|
29446
|
+
await Promise.all(
|
|
29447
|
+
[...ids].map(async (id) => {
|
|
29448
|
+
try {
|
|
29449
|
+
const { email } = await client.getLibraryEmail(id);
|
|
29450
|
+
names.set(id, email.name);
|
|
29451
|
+
} catch {
|
|
29452
|
+
}
|
|
29453
|
+
})
|
|
29454
|
+
);
|
|
29455
|
+
return names;
|
|
29456
|
+
}
|
|
29457
|
+
function printWorkflowGraphs(workflow, published, draft, templateNames) {
|
|
29458
|
+
if (workflow.draftGraph === void 0 && workflow.publishedGraph === void 0) {
|
|
29459
|
+
process.stdout.write(
|
|
29460
|
+
"\nThis API does not return workflow graphs yet (it predates `?include=graph`) \u2014 update the API to see the steps.\n"
|
|
29461
|
+
);
|
|
29462
|
+
return;
|
|
29463
|
+
}
|
|
29464
|
+
const differs = workflow.hasUnpublishedChanges === true;
|
|
29465
|
+
if (published) {
|
|
29466
|
+
process.stdout.write(
|
|
29467
|
+
`
|
|
29468
|
+
Graph shown: published (what runs now)${differs ? " \u2014 the draft has unpublished changes, listed below" : ""}
|
|
29469
|
+
`
|
|
29470
|
+
);
|
|
29471
|
+
printGraphWalk(published, templateNames);
|
|
29472
|
+
if (differs && draft) {
|
|
29473
|
+
process.stdout.write("\nDraft graph (unpublished changes \u2014 not running):\n");
|
|
29474
|
+
printGraphWalk(draft, templateNames);
|
|
29475
|
+
}
|
|
29476
|
+
return;
|
|
29477
|
+
}
|
|
29478
|
+
if (draft) {
|
|
29479
|
+
process.stdout.write("\nGraph shown: draft (never published \u2014 nothing runs yet)\n");
|
|
29480
|
+
printGraphWalk(draft, templateNames);
|
|
29481
|
+
return;
|
|
29482
|
+
}
|
|
29483
|
+
process.stdout.write("\n(no graph)\n");
|
|
29484
|
+
}
|
|
29485
|
+
function printGraphWalk(graph, templateNames) {
|
|
29486
|
+
const lines = walkGraph(graph, templateNames);
|
|
29487
|
+
if (lines.length === 0) {
|
|
29488
|
+
process.stdout.write(" (empty graph)\n");
|
|
29489
|
+
return;
|
|
29490
|
+
}
|
|
29491
|
+
for (const line of lines) process.stdout.write(line.length > 0 ? ` ${line}
|
|
29492
|
+
` : "\n");
|
|
29493
|
+
}
|
|
29494
|
+
function walkGraph(graph, templateNames) {
|
|
29495
|
+
const nodesById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
29496
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
29497
|
+
for (const edge of graph.edges) {
|
|
29498
|
+
const list = outgoing.get(edge.source) ?? [];
|
|
29499
|
+
list.push(edge);
|
|
29500
|
+
outgoing.set(edge.source, list);
|
|
29501
|
+
}
|
|
29502
|
+
const lines = [];
|
|
29503
|
+
const reached = /* @__PURE__ */ new Set();
|
|
29504
|
+
const visit = (nodeId, depth, path2) => {
|
|
29505
|
+
const node = nodesById.get(nodeId);
|
|
29506
|
+
const indent = " ".repeat(depth);
|
|
29507
|
+
if (!node) {
|
|
29508
|
+
lines.push(`${indent}-> (missing node ${nodeId})`);
|
|
29509
|
+
return;
|
|
29510
|
+
}
|
|
29511
|
+
if (path2.has(nodeId)) {
|
|
29512
|
+
lines.push(`${indent}-> loops back to ${describeNodeRef(node)}`);
|
|
29513
|
+
return;
|
|
29514
|
+
}
|
|
29515
|
+
reached.add(nodeId);
|
|
29516
|
+
lines.push(`${indent}${describeNode(node, templateNames)}`);
|
|
29517
|
+
const nextPath = new Set(path2).add(nodeId);
|
|
29518
|
+
const edges = outgoing.get(nodeId) ?? [];
|
|
29519
|
+
if (node.kind === "condition") {
|
|
29520
|
+
for (const branch of GRAPH_CONDITION_BRANCHES) {
|
|
29521
|
+
const branchEdges = edges.filter((edge) => edge.sourceHandle === branch);
|
|
29522
|
+
lines.push(
|
|
29523
|
+
`${indent} ${branch === "true" ? "yes" : "no"} ->${branchEdges.length === 0 ? " (end)" : ""}`
|
|
29524
|
+
);
|
|
29525
|
+
for (const edge of branchEdges) visit(edge.target, depth + 2, nextPath);
|
|
29526
|
+
}
|
|
29527
|
+
return;
|
|
29528
|
+
}
|
|
29529
|
+
for (const edge of edges) visit(edge.target, depth + (edges.length > 1 ? 1 : 0), nextPath);
|
|
29530
|
+
};
|
|
29531
|
+
const triggers = graph.nodes.filter((node) => node.kind === "trigger");
|
|
29532
|
+
triggers.forEach((trigger, index) => {
|
|
29533
|
+
if (index > 0) lines.push("");
|
|
29534
|
+
visit(trigger.id, 0, /* @__PURE__ */ new Set());
|
|
29535
|
+
});
|
|
29536
|
+
const orphans = graph.nodes.filter((node) => !reached.has(node.id));
|
|
29537
|
+
if (orphans.length > 0) {
|
|
29538
|
+
if (lines.length > 0) lines.push("");
|
|
29539
|
+
lines.push("Not reachable from any trigger (never runs):");
|
|
29540
|
+
for (const node of orphans) lines.push(` ${describeNode(node, templateNames)}`);
|
|
29541
|
+
}
|
|
29542
|
+
return lines;
|
|
29543
|
+
}
|
|
29544
|
+
function describeNodeRef(node) {
|
|
29545
|
+
return node.name ? `"${node.name}" (${node.id})` : node.id;
|
|
29546
|
+
}
|
|
29547
|
+
function describeNode(node, templateNames) {
|
|
29548
|
+
const prefix = `[${node.kind}${node.disabled ? ", disabled" : ""}]`;
|
|
29549
|
+
const label = node.name ? ` "${node.name}"` : "";
|
|
29550
|
+
const suffix = ` (${node.id})`;
|
|
29551
|
+
switch (node.kind) {
|
|
29552
|
+
case "trigger":
|
|
29553
|
+
return `${prefix}${label} ${describeTrigger(node.config)}${suffix}`;
|
|
29554
|
+
case "action":
|
|
29555
|
+
return `${prefix}${label} ${describeAction(node, templateNames)}${suffix}`;
|
|
29556
|
+
case "condition":
|
|
29557
|
+
return `${prefix}${label} if ${describeCondition2(node.config)}${suffix}`;
|
|
29558
|
+
case "delay":
|
|
29559
|
+
return `${prefix}${label} ${describeDelay(node.config)}${suffix}`;
|
|
29560
|
+
}
|
|
29561
|
+
}
|
|
29562
|
+
function describeTrigger(config) {
|
|
29563
|
+
const eventType = optionalString(config.eventType) ?? "(no event type)";
|
|
29564
|
+
const scopes = [];
|
|
29565
|
+
const scopeOf2 = (key, label) => {
|
|
29566
|
+
const ids = config[key];
|
|
29567
|
+
if (Array.isArray(ids) && ids.length > 0) scopes.push(`${label}: ${ids.join(", ")}`);
|
|
29568
|
+
};
|
|
29569
|
+
scopeOf2("productIds", "products");
|
|
29570
|
+
scopeOf2("formIds", "forms");
|
|
29571
|
+
scopeOf2("tagIds", "tags");
|
|
29572
|
+
if (typeof config.inactiveDays === "number") scopes.push(`inactive ${config.inactiveDays} days`);
|
|
29573
|
+
return scopes.length > 0 ? `${eventType} \u2014 ${scopes.join("; ")}` : eventType;
|
|
29574
|
+
}
|
|
29575
|
+
function isKnownActionType(value) {
|
|
29576
|
+
return typeof value === "string" && value in GRAPH_ACTION_LABELS;
|
|
29577
|
+
}
|
|
29578
|
+
function describeAction(node, templateNames) {
|
|
29579
|
+
const { taskType } = node.config;
|
|
29580
|
+
if (!isKnownActionType(taskType)) {
|
|
29581
|
+
const inner = isRecord2(node.config.action) ? optionalString(node.config.action.type) : void 0;
|
|
29582
|
+
return `${typeof taskType === "string" ? taskType : "action"}${inner ? ` ${inner}` : ""}`;
|
|
29583
|
+
}
|
|
29584
|
+
const label = GRAPH_ACTION_LABELS[taskType];
|
|
29585
|
+
switch (taskType) {
|
|
29586
|
+
case "sendEmail":
|
|
29587
|
+
return `${label} \u2014 ${describeEmail(node, templateNames)}`;
|
|
29588
|
+
case "notifyOwner": {
|
|
29589
|
+
const owner = isRecord2(node.config.ownerNotification) ? node.config.ownerNotification : {};
|
|
29590
|
+
return `${label} \u2014 subject: ${quote(optionalString(owner.subject))} \u2014 body: ${quote(excerptHtml(optionalString(owner.html)))}`;
|
|
29591
|
+
}
|
|
29592
|
+
case "sendSms": {
|
|
29593
|
+
const sms = isRecord2(node.config.sms) ? node.config.sms : {};
|
|
29594
|
+
return `${label} \u2014 text: ${quote(excerptText(optionalString(sms.text)))}`;
|
|
29595
|
+
}
|
|
29596
|
+
case "webhook": {
|
|
29597
|
+
const webhook = isRecord2(node.config.webhook) ? node.config.webhook : {};
|
|
29598
|
+
const method = optionalString(webhook.method) ?? optionalString(webhook.type);
|
|
29599
|
+
return `${label} \u2014 ${method ? `${method.toUpperCase()} ` : ""}${optionalString(webhook.url) ?? "(no url)"}`;
|
|
29600
|
+
}
|
|
29601
|
+
case "publishSocialPost": {
|
|
29602
|
+
const social = isRecord2(node.config.social) ? node.config.social : {};
|
|
29603
|
+
const targets = Array.isArray(social.targets) ? social.targets.join(", ") : "";
|
|
29604
|
+
return `${label}${optionalString(social.mode) ? ` (${social.mode})` : ""}${targets ? ` to ${targets}` : ""} \u2014 text: ${quote(excerptText(optionalString(social.text)))}`;
|
|
29605
|
+
}
|
|
29606
|
+
case "createCommunityPost": {
|
|
29607
|
+
const post = isRecord2(node.config.communityPost) ? node.config.communityPost : {};
|
|
29608
|
+
const title = optionalString(post.title);
|
|
29609
|
+
return `${label} in channel ${optionalString(post.channelId) ?? "?"}${title ? ` \u2014 title: ${quote(title)}` : ""} \u2014 text: ${quote(excerptText(optionalString(post.text)))}`;
|
|
29610
|
+
}
|
|
29611
|
+
case "updateClientTags": {
|
|
29612
|
+
const tags = isRecord2(node.config.clientTags) ? node.config.clientTags : {};
|
|
29613
|
+
const ids = Array.isArray(tags.tagIds) ? tags.tagIds.join(", ") : "";
|
|
29614
|
+
return `${label} \u2014 ${optionalString(tags.mode) ?? "update"} ${ids || "(no tags)"}`;
|
|
29615
|
+
}
|
|
29616
|
+
case "setEmailSubscription": {
|
|
29617
|
+
const sub = isRecord2(node.config.emailSubscription) ? node.config.emailSubscription : {};
|
|
29618
|
+
return `${label} \u2014 ${optionalString(sub.status) ?? "?"}`;
|
|
29619
|
+
}
|
|
29620
|
+
case "openClientDeadline": {
|
|
29621
|
+
const deadline = isRecord2(node.config.clientDeadline) ? node.config.clientDeadline : {};
|
|
29622
|
+
const amount = typeof deadline.amount === "number" ? String(deadline.amount) : "?";
|
|
29623
|
+
const endTime = optionalString(deadline.endTime);
|
|
29624
|
+
return `${label} \u2014 ${optionalString(deadline.key) ?? "?"} for ${amount} ${optionalString(deadline.unit) ?? "?"}${endTime ? `, closes at ${endTime}` : ""}`;
|
|
29625
|
+
}
|
|
29626
|
+
case "requestTestimonial":
|
|
29627
|
+
return label;
|
|
29628
|
+
}
|
|
29629
|
+
}
|
|
29630
|
+
function describeEmail(node, templateNames) {
|
|
29631
|
+
const email = emailConfigOf(node);
|
|
29632
|
+
if (!email) return "(no email config)";
|
|
29633
|
+
if (email.templateId) {
|
|
29634
|
+
const name = templateNames.get(email.templateId) ?? email.templateName ?? null;
|
|
29635
|
+
return `library email ${name ? `${quote(name)} ` : ""}(${email.templateId}) \u2014 sent as saved in the library`;
|
|
29636
|
+
}
|
|
29637
|
+
return `subject: ${quote(email.subject)} \u2014 body: ${quote(excerptHtml(email.html))}`;
|
|
29638
|
+
}
|
|
29639
|
+
function describeCondition2(config) {
|
|
29640
|
+
const rules = Array.isArray(config.rules) ? config.rules.filter(isRecord2) : [];
|
|
29641
|
+
if (rules.length === 0) return "(no rules)";
|
|
29642
|
+
return rules.map(describeRule).join(" AND ");
|
|
29643
|
+
}
|
|
29644
|
+
function describeRule(rule) {
|
|
29645
|
+
const field = optionalString(rule.field) ?? "?";
|
|
29646
|
+
const details = [];
|
|
29647
|
+
for (const key of ["productId", "tagId", "consentName", "days"]) {
|
|
29648
|
+
const value = rule[key];
|
|
29649
|
+
if (typeof value === "string" || typeof value === "number") details.push(`${key}=${value}`);
|
|
29650
|
+
}
|
|
29651
|
+
const detail = details.length > 0 ? ` (${details.join(", ")})` : "";
|
|
29652
|
+
const op = optionalString(rule.op);
|
|
29653
|
+
if (op !== void 0) {
|
|
29654
|
+
const value = Array.isArray(rule.values) ? `[${rule.values.join(", ")}]` : typeof rule.value === "string" || typeof rule.value === "number" ? String(rule.value) : "?";
|
|
29655
|
+
return `${field} ${op} ${value}${detail}`;
|
|
29656
|
+
}
|
|
29657
|
+
if (typeof rule.expected === "boolean") return `${field}${detail} is ${rule.expected}`;
|
|
29658
|
+
return `${field}${detail}`;
|
|
29659
|
+
}
|
|
29660
|
+
function describeDelay(config) {
|
|
29661
|
+
const mode = optionalString(config.mode);
|
|
29662
|
+
switch (mode) {
|
|
29663
|
+
case void 0:
|
|
29664
|
+
case "duration":
|
|
29665
|
+
return `wait ${typeof config.amount === "number" ? config.amount : "?"} ${optionalString(config.unit) ?? ""}`.trimEnd();
|
|
29666
|
+
case "untilWeekday":
|
|
29667
|
+
return `wait until weekday ${typeof config.weekday === "number" ? config.weekday : "?"} at ${optionalString(config.time) ?? "?"}${optionalString(config.timezone) ? ` (${config.timezone})` : ""}`;
|
|
29668
|
+
case "untilDate":
|
|
29669
|
+
return `wait until ${optionalString(config.date) ?? "?"}${optionalString(config.timezone) ? ` (${config.timezone})` : ""}`;
|
|
29670
|
+
case "untilContextDate": {
|
|
29671
|
+
const offset = typeof config.offsetMinutes === "number" ? config.offsetMinutes : 0;
|
|
29672
|
+
return `wait until ${optionalString(config.contextKey) ?? "?"}${offset !== 0 ? ` ${offset > 0 ? "+" : ""}${offset} min` : ""}${config.skipWhenPast === true ? " (skip branch when past)" : ""}`;
|
|
29673
|
+
}
|
|
29674
|
+
default:
|
|
29675
|
+
return `wait (${mode})`;
|
|
29676
|
+
}
|
|
29677
|
+
}
|
|
29678
|
+
function quote(value) {
|
|
29679
|
+
return value !== void 0 && value.length > 0 ? `"${value}"` : "(empty)";
|
|
29680
|
+
}
|
|
29681
|
+
function excerptHtml(html) {
|
|
29682
|
+
if (html === void 0) return void 0;
|
|
29683
|
+
const text = html.replace(/<(style|script|head)\b[^>]*>[\s\S]*?<\/\1>/gi, " ").replace(/<br\s*\/?>|<\/(p|div|li|h[1-6]|tr)>/gi, " ").replace(/<[^>]+>/g, "").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'|'/g, "'");
|
|
29684
|
+
return excerptText(text);
|
|
29685
|
+
}
|
|
29686
|
+
function excerptText(text) {
|
|
29687
|
+
if (text === void 0) return void 0;
|
|
29688
|
+
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
29689
|
+
return collapsed.length > EXCERPT_LENGTH ? `${collapsed.slice(0, EXCERPT_LENGTH - 1)}\u2026` : collapsed;
|
|
29690
|
+
}
|
|
27243
29691
|
|
|
27244
29692
|
// src/commands/workspace/index.ts
|
|
27245
|
-
function
|
|
29693
|
+
function parseOnOff5(value, flag) {
|
|
27246
29694
|
const match = ["on", "off"].find((v) => v === value);
|
|
27247
29695
|
if (!match) throw new Error(`Invalid ${flag}: ${value} (expected on | off)`);
|
|
27248
29696
|
return match === "on";
|
|
@@ -27310,7 +29758,7 @@ function registerWorkspaceCommands(program2) {
|
|
|
27310
29758
|
}
|
|
27311
29759
|
if (options.inheritTimezone) input.timezone = null;
|
|
27312
29760
|
if (options.autoTranscribe !== void 0) {
|
|
27313
|
-
input.autoTranscribeUploads =
|
|
29761
|
+
input.autoTranscribeUploads = parseOnOff5(options.autoTranscribe, "--auto-transcribe");
|
|
27314
29762
|
}
|
|
27315
29763
|
if (options.transcriptionLanguage !== void 0) {
|
|
27316
29764
|
input.transcriptionLanguage = parseTranscriptionLanguage(options.transcriptionLanguage);
|
|
@@ -27327,13 +29775,65 @@ function registerWorkspaceCommands(program2) {
|
|
|
27327
29775
|
);
|
|
27328
29776
|
}
|
|
27329
29777
|
|
|
29778
|
+
// src/utils/shortHelp.ts
|
|
29779
|
+
var SHORT_HELP_FLAG = "--short";
|
|
29780
|
+
var SHORT_HELP_DESCRIPTION = "with --help: print only the command names (subcommands indented), no descriptions";
|
|
29781
|
+
var INDENT = " ";
|
|
29782
|
+
function rootOf(command) {
|
|
29783
|
+
let current = command;
|
|
29784
|
+
while (current.parent) current = current.parent;
|
|
29785
|
+
return current;
|
|
29786
|
+
}
|
|
29787
|
+
function rawArgsOf(command) {
|
|
29788
|
+
const root = rootOf(command);
|
|
29789
|
+
if ("rawArgs" in root && Array.isArray(root.rawArgs)) {
|
|
29790
|
+
return root.rawArgs.filter((arg) => typeof arg === "string");
|
|
29791
|
+
}
|
|
29792
|
+
return [];
|
|
29793
|
+
}
|
|
29794
|
+
function isShortHelpRequested(command) {
|
|
29795
|
+
return rawArgsOf(command).includes(SHORT_HELP_FLAG);
|
|
29796
|
+
}
|
|
29797
|
+
function visibleSubcommands(command, helper) {
|
|
29798
|
+
return helper.visibleCommands(command).filter((sub) => command.commands.includes(sub));
|
|
29799
|
+
}
|
|
29800
|
+
function formatShortHelp(command, helper) {
|
|
29801
|
+
const lines = [];
|
|
29802
|
+
const walk = (cmd, depth) => {
|
|
29803
|
+
lines.push(`${INDENT.repeat(depth)}${cmd.name()}`);
|
|
29804
|
+
for (const sub of visibleSubcommands(cmd, helper)) walk(sub, depth + 1);
|
|
29805
|
+
};
|
|
29806
|
+
if (command.parent === null) {
|
|
29807
|
+
for (const sub of visibleSubcommands(command, helper)) walk(sub, 0);
|
|
29808
|
+
} else {
|
|
29809
|
+
walk(command, 0);
|
|
29810
|
+
}
|
|
29811
|
+
return `${lines.join("\n")}
|
|
29812
|
+
`;
|
|
29813
|
+
}
|
|
29814
|
+
function registerOnCommand2(command) {
|
|
29815
|
+
if (!command.options.some((option) => option.long === SHORT_HELP_FLAG)) {
|
|
29816
|
+
command.addOption(new Option(SHORT_HELP_FLAG, SHORT_HELP_DESCRIPTION).hideHelp());
|
|
29817
|
+
}
|
|
29818
|
+
const inherited = command.configureHelp();
|
|
29819
|
+
const previousFormatHelp = inherited.formatHelp;
|
|
29820
|
+
command.configureHelp({
|
|
29821
|
+
...inherited,
|
|
29822
|
+
formatHelp: (cmd, helper) => isShortHelpRequested(cmd) ? formatShortHelp(cmd, helper) : previousFormatHelp ? previousFormatHelp.call(helper, cmd, helper) : Help.prototype.formatHelp.call(helper, cmd, helper)
|
|
29823
|
+
});
|
|
29824
|
+
for (const sub of command.commands) registerOnCommand2(sub);
|
|
29825
|
+
}
|
|
29826
|
+
function applyShortHelp(program2) {
|
|
29827
|
+
registerOnCommand2(program2);
|
|
29828
|
+
}
|
|
29829
|
+
|
|
27330
29830
|
// src/program.ts
|
|
27331
29831
|
function resolveCliVersion() {
|
|
27332
|
-
if ("0.3.
|
|
27333
|
-
return "0.3.
|
|
29832
|
+
if ("0.3.63".length > 0) {
|
|
29833
|
+
return "0.3.63";
|
|
27334
29834
|
}
|
|
27335
29835
|
try {
|
|
27336
|
-
const raw =
|
|
29836
|
+
const raw = readFileSync28(new URL("../package.json", import.meta.url), "utf8");
|
|
27337
29837
|
const parsed = JSON.parse(raw);
|
|
27338
29838
|
if (typeof parsed === "object" && parsed !== null && "version" in parsed) {
|
|
27339
29839
|
const { version: version2 } = parsed;
|
|
@@ -27376,6 +29876,7 @@ Signing in \u2014 two roles, two consent pages:
|
|
|
27376
29876
|
registerProductsCommands(program2);
|
|
27377
29877
|
registerOffersCommands(program2);
|
|
27378
29878
|
registerCheckoutSettingsCommands(program2);
|
|
29879
|
+
registerIntegrationsCommands(program2);
|
|
27379
29880
|
registerSiteCommands(program2);
|
|
27380
29881
|
registerEmailsCommands(program2);
|
|
27381
29882
|
registerOtoCommands(program2);
|
|
@@ -27390,6 +29891,7 @@ Signing in \u2014 two roles, two consent pages:
|
|
|
27390
29891
|
registerSmsCampaignsCommands(program2);
|
|
27391
29892
|
registerSegmentsCommands(program2);
|
|
27392
29893
|
registerFormsCommands(program2);
|
|
29894
|
+
registerLiveRoomsCommands(program2);
|
|
27393
29895
|
registerTestimonialsCommands(program2);
|
|
27394
29896
|
registerBrandCommands(program2);
|
|
27395
29897
|
registerTeamCommands(program2);
|
|
@@ -27406,6 +29908,7 @@ Signing in \u2014 two roles, two consent pages:
|
|
|
27406
29908
|
registerSocialCommands(program2);
|
|
27407
29909
|
registerMediaCommands(program2);
|
|
27408
29910
|
applyGlobalOptions(program2);
|
|
29911
|
+
applyShortHelp(program2);
|
|
27409
29912
|
return program2;
|
|
27410
29913
|
}
|
|
27411
29914
|
|