@zanfia/cli 0.3.47 → 0.3.52
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 +2308 -360
- 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
|
},
|
|
@@ -8933,7 +9030,10 @@ function createHttpClient(config) {
|
|
|
8933
9030
|
"GET",
|
|
8934
9031
|
productId ? `/checkouts?productId=${encodeURIComponent(productId)}` : "/checkouts"
|
|
8935
9032
|
),
|
|
8936
|
-
getCheckout: (id) => request(
|
|
9033
|
+
getCheckout: (id, options) => request(
|
|
9034
|
+
"GET",
|
|
9035
|
+
options?.full ? `/checkouts/${encodeURIComponent(id)}?include=full` : `/checkouts/${encodeURIComponent(id)}`
|
|
9036
|
+
),
|
|
8937
9037
|
createCheckout: (input) => request("POST", "/checkouts", input),
|
|
8938
9038
|
updateCheckout: (id, input) => request("PATCH", `/checkouts/${encodeURIComponent(id)}`, input),
|
|
8939
9039
|
publishCheckout: (id, active) => request("POST", `/checkouts/${encodeURIComponent(id)}/publish`, { active }),
|
|
@@ -9195,6 +9295,33 @@ function createHttpClient(config) {
|
|
|
9195
9295
|
"GET",
|
|
9196
9296
|
`/conversion-tracking/health${sinceDays !== void 0 ? `?sinceDays=${sinceDays}` : ""}`
|
|
9197
9297
|
),
|
|
9298
|
+
getIntegrationsOverview: () => request("GET", "/integrations"),
|
|
9299
|
+
connectEmailClient: (input) => request("POST", "/integrations/email-clients", input),
|
|
9300
|
+
disconnectEmailClient: (client, url) => request("POST", "/integrations/email-clients/disconnect", { client, url }),
|
|
9301
|
+
refreshEmailClientLists: (input) => request("POST", "/integrations/email-clients/refresh-lists", input),
|
|
9302
|
+
connectMetaConversionApi: (input) => request("PUT", "/integrations/meta-conversion-api", input),
|
|
9303
|
+
disconnectMetaConversionApi: (integrationId) => request("DELETE", `/integrations/meta-conversion-api/${encodeURIComponent(integrationId)}`),
|
|
9304
|
+
configureManualPayment: (input) => request("PUT", "/integrations/payments/manual", input),
|
|
9305
|
+
disconnectManualPayment: () => request("DELETE", "/integrations/payments/manual"),
|
|
9306
|
+
connectInvoicesClient: (input) => request("POST", "/integrations/invoices-clients", input),
|
|
9307
|
+
disconnectInvoicesClient: (client, id) => request("POST", "/integrations/invoices-clients/disconnect", { client, id }),
|
|
9308
|
+
connectPaymentGateway: (input) => request("POST", "/integrations/payments/gateways", input),
|
|
9309
|
+
disconnectPaymentGateway: (gateway) => request("POST", "/integrations/payments/gateways/disconnect", { gateway }),
|
|
9310
|
+
setTpayRecurringBlik: (enabled) => request("PUT", "/integrations/payments/tpay/recurring-blik", { enabled }),
|
|
9311
|
+
connectTwilio: (input) => request("PUT", "/integrations/sms/twilio", input),
|
|
9312
|
+
disconnectTwilio: () => request("DELETE", "/integrations/sms/twilio"),
|
|
9313
|
+
connectSmsApi: (input) => request("PUT", "/integrations/sms/smsapi", input),
|
|
9314
|
+
disconnectSmsApi: () => request("DELETE", "/integrations/sms/smsapi"),
|
|
9315
|
+
refreshSmsApiSenders: () => request("POST", "/integrations/sms/smsapi/refresh-senders"),
|
|
9316
|
+
setDefaultSmsApiSender: (sender) => request("PUT", "/integrations/sms/smsapi/default-sender", { sender }),
|
|
9317
|
+
setDefaultSmsProvider: (provider) => request("PUT", "/integrations/sms/default-provider", { provider }),
|
|
9318
|
+
listConversionTrackingIntegrations: () => request("GET", "/conversion-tracking/integrations"),
|
|
9319
|
+
upsertConversionTrackingIntegration: (input) => request("PUT", "/conversion-tracking/integrations", input),
|
|
9320
|
+
removeConversionTrackingIntegration: (client, integrationId) => request(
|
|
9321
|
+
"DELETE",
|
|
9322
|
+
`/conversion-tracking/integrations/${encodeURIComponent(client)}/${encodeURIComponent(integrationId)}`
|
|
9323
|
+
),
|
|
9324
|
+
setDefaultConversionTracking: (integrationIds) => request("PUT", "/conversion-tracking/defaults", { integrationIds: [...integrationIds] }),
|
|
9198
9325
|
statsProduct: (productId, currency) => request("GET", `/products/${encodeURIComponent(productId)}/stats${statsQuery({ currency })}`),
|
|
9199
9326
|
listCommunities: () => communityRequest("GET", "/v1/communities"),
|
|
9200
9327
|
listCommunityChannels: (communityId) => communityRequest("GET", `/v1/communities/${encodeURIComponent(communityId)}/channels`),
|
|
@@ -11787,10 +11914,10 @@ function registerBrandCommands(program2) {
|
|
|
11787
11914
|
});
|
|
11788
11915
|
}
|
|
11789
11916
|
async function buildUpdatePatch(options) {
|
|
11790
|
-
const { readFile:
|
|
11917
|
+
const { readFile: readFile4 } = await import("fs/promises");
|
|
11791
11918
|
let patch = {};
|
|
11792
11919
|
if (options.jsonFile) {
|
|
11793
|
-
const raw = await
|
|
11920
|
+
const raw = await readFile4(options.jsonFile, "utf8");
|
|
11794
11921
|
const parsed = JSON.parse(raw);
|
|
11795
11922
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
11796
11923
|
throw new Error("--json-file must contain a JSON object with name/visual/editorial keys.");
|
|
@@ -11805,9 +11932,42 @@ async function buildUpdatePatch(options) {
|
|
|
11805
11932
|
}
|
|
11806
11933
|
|
|
11807
11934
|
// src/commands/bumps/index.ts
|
|
11935
|
+
var IMAGE_MEDIA_TYPE = "image";
|
|
11936
|
+
var CLEAR_MEDIA_VALUE = "none";
|
|
11937
|
+
var HTTP_URL_PATTERN = /^https?:\/\//i;
|
|
11938
|
+
var IMAGE_MIME_PREFIX = "image/";
|
|
11939
|
+
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.";
|
|
11940
|
+
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
11941
|
function formatAmount(amountCents, currency) {
|
|
11809
11942
|
return `${(amountCents / 100).toFixed(2)} ${currency}`;
|
|
11810
11943
|
}
|
|
11944
|
+
function parseBumpJsonPatch(value) {
|
|
11945
|
+
return parseJsonObjectFileOrInline("--json")(value);
|
|
11946
|
+
}
|
|
11947
|
+
async function resolveImageMedia(client, value) {
|
|
11948
|
+
const trimmed = value.trim();
|
|
11949
|
+
if (trimmed.toLowerCase() === CLEAR_MEDIA_VALUE) return null;
|
|
11950
|
+
if (HTTP_URL_PATTERN.test(trimmed)) {
|
|
11951
|
+
return { type: IMAGE_MEDIA_TYPE, url: trimmed, mediaId: null };
|
|
11952
|
+
}
|
|
11953
|
+
const { media } = await client.getMedia(trimmed);
|
|
11954
|
+
if (!media.mimeType.startsWith(IMAGE_MIME_PREFIX)) {
|
|
11955
|
+
throw new Error(
|
|
11956
|
+
`--image: media ${trimmed} is ${media.mimeType}, not an image. Pass an image media id / URL, or set a video slot via --json {"appearance":{"media":{...}}}.`
|
|
11957
|
+
);
|
|
11958
|
+
}
|
|
11959
|
+
return { type: IMAGE_MEDIA_TYPE, url: media.url, mediaId: media.id };
|
|
11960
|
+
}
|
|
11961
|
+
function mergeBumpInput(jsonPatch, flagInput, media) {
|
|
11962
|
+
const merged = { ...jsonPatch, ...flagInput };
|
|
11963
|
+
for (const [key, value] of Object.entries(flagInput)) {
|
|
11964
|
+
if (value === void 0) delete merged[key];
|
|
11965
|
+
}
|
|
11966
|
+
const appearance = media !== void 0 ? { ...jsonPatch.appearance ?? {}, media } : jsonPatch.appearance;
|
|
11967
|
+
if (appearance !== void 0) merged.appearance = appearance;
|
|
11968
|
+
else delete merged.appearance;
|
|
11969
|
+
return merged;
|
|
11970
|
+
}
|
|
11811
11971
|
function parsePriceIds(value) {
|
|
11812
11972
|
const ids = value.split(",").map((id) => id.trim()).filter((id) => id.length > 0);
|
|
11813
11973
|
if (ids.length === 0) {
|
|
@@ -11834,39 +11994,52 @@ function registerBumpsCommands(program2) {
|
|
|
11834
11994
|
);
|
|
11835
11995
|
});
|
|
11836
11996
|
});
|
|
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(
|
|
11997
|
+
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
11998
|
async (options, command) => {
|
|
11839
11999
|
const flags = getGlobalFlags(command);
|
|
11840
12000
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11841
|
-
const
|
|
11842
|
-
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
|
|
11847
|
-
|
|
12001
|
+
const jsonPatch = options.json !== void 0 ? parseBumpJsonPatch(options.json) : {};
|
|
12002
|
+
const media = options.image !== void 0 ? await resolveImageMedia(client, options.image) : void 0;
|
|
12003
|
+
const input = mergeBumpInput(
|
|
12004
|
+
jsonPatch,
|
|
12005
|
+
{
|
|
12006
|
+
productId: options.product,
|
|
12007
|
+
priceId: options.price,
|
|
12008
|
+
name: options.name,
|
|
12009
|
+
description: options.description,
|
|
12010
|
+
ctaText: options.cta
|
|
12011
|
+
},
|
|
12012
|
+
media
|
|
12013
|
+
);
|
|
12014
|
+
const result = await client.createOrderBump(input);
|
|
11848
12015
|
emit(flags, result, () => {
|
|
11849
12016
|
process.stdout.write(`Created order bump ${result.orderBumpId} \u2014 ${options.name}
|
|
11850
12017
|
`);
|
|
11851
12018
|
});
|
|
11852
12019
|
}
|
|
11853
12020
|
);
|
|
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(
|
|
12021
|
+
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
12022
|
async (bumpId, options, command) => {
|
|
11856
12023
|
const flags = getGlobalFlags(command);
|
|
11857
|
-
const
|
|
11858
|
-
|
|
11859
|
-
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
11864
|
-
|
|
12024
|
+
const jsonPatch = options.json !== void 0 ? parseBumpJsonPatch(options.json) : {};
|
|
12025
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12026
|
+
const media = options.image !== void 0 ? await resolveImageMedia(client, options.image) : void 0;
|
|
12027
|
+
const input = mergeBumpInput(
|
|
12028
|
+
jsonPatch,
|
|
12029
|
+
{
|
|
12030
|
+
name: options.name,
|
|
12031
|
+
description: options.description,
|
|
12032
|
+
ctaText: options.cta,
|
|
12033
|
+
productId: options.product,
|
|
12034
|
+
priceId: options.price
|
|
12035
|
+
},
|
|
12036
|
+
media
|
|
12037
|
+
);
|
|
12038
|
+
if (Object.keys(input).length === 0) {
|
|
11865
12039
|
throw new Error(
|
|
11866
|
-
"Nothing to update \u2014 pass at least one of --name, --description, --cta, --product, --price."
|
|
12040
|
+
"Nothing to update \u2014 pass at least one of --name, --description, --cta, --product, --price, --image, or a --json patch."
|
|
11867
12041
|
);
|
|
11868
12042
|
}
|
|
11869
|
-
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11870
12043
|
const result = await client.updateOrderBump(bumpId, input);
|
|
11871
12044
|
emit(flags, result, () => {
|
|
11872
12045
|
process.stdout.write(`Updated order bump ${result.orderBumpId}
|
|
@@ -12401,7 +12574,19 @@ function registerCheckoutSettingsCommands(program2) {
|
|
|
12401
12574
|
).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
12575
|
"--claim <text|none>",
|
|
12403
12576
|
"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(
|
|
12577
|
+
).option("--terms-content <text>", "inline terms & conditions text").option("--privacy-policy-content <text>", "inline privacy policy text").option(
|
|
12578
|
+
"--google-analytics-id <id|none>",
|
|
12579
|
+
"LEGACY Google Analytics id (none = clear) \u2014 prefer `integrations add ga4`"
|
|
12580
|
+
).option(
|
|
12581
|
+
"--facebook-pixel-id <id|none>",
|
|
12582
|
+
"LEGACY Facebook Pixel id (none = clear) \u2014 Meta pixel is managed in the dashboard"
|
|
12583
|
+
).option(
|
|
12584
|
+
"--gtm-id <id|none>",
|
|
12585
|
+
"LEGACY Google Tag Manager id (none = clear) \u2014 prefer `integrations add gtm`"
|
|
12586
|
+
).option(
|
|
12587
|
+
"--tiktok-pixel-id <id|none>",
|
|
12588
|
+
"LEGACY TikTok Pixel id (none = clear) \u2014 prefer `integrations add tiktok`"
|
|
12589
|
+
).option("--promote-net-prices <on|off>", "display net prices prominently").option(
|
|
12405
12590
|
"--price-variants-placement <belowDescription|aboveDescription|none>",
|
|
12406
12591
|
"where the price list renders relative to the description"
|
|
12407
12592
|
).option(
|
|
@@ -12536,6 +12721,39 @@ var AIImageQuality = /* @__PURE__ */ ((AIImageQuality2) => {
|
|
|
12536
12721
|
AIImageQuality2["Ultra"] = "4K";
|
|
12537
12722
|
return AIImageQuality2;
|
|
12538
12723
|
})(AIImageQuality || {});
|
|
12724
|
+
var MANAGED_CONVERSION_TRACKING_CLIENTS = [
|
|
12725
|
+
"googleAnalytics",
|
|
12726
|
+
"googleTagManager",
|
|
12727
|
+
"tiktok",
|
|
12728
|
+
"x",
|
|
12729
|
+
"linkedin",
|
|
12730
|
+
"microsoftClarity",
|
|
12731
|
+
"hotjar"
|
|
12732
|
+
];
|
|
12733
|
+
var CONNECTABLE_EMAIL_CLIENTS = [
|
|
12734
|
+
"ActiveCampaign",
|
|
12735
|
+
"ConvertKit",
|
|
12736
|
+
"MailerLite",
|
|
12737
|
+
"MailChimp",
|
|
12738
|
+
"GetResponse",
|
|
12739
|
+
"Pipedrive"
|
|
12740
|
+
];
|
|
12741
|
+
var INVOICES_CLIENTS = [
|
|
12742
|
+
"fakturownia",
|
|
12743
|
+
"ifirma",
|
|
12744
|
+
"inFakt",
|
|
12745
|
+
"wFirmaV2",
|
|
12746
|
+
"ingKsiegowosc",
|
|
12747
|
+
"stripe"
|
|
12748
|
+
];
|
|
12749
|
+
var CREDENTIAL_PAYMENT_GATEWAYS = [
|
|
12750
|
+
"payu",
|
|
12751
|
+
"tpay",
|
|
12752
|
+
"przelewy24",
|
|
12753
|
+
"paynow",
|
|
12754
|
+
"paypal",
|
|
12755
|
+
"dodo"
|
|
12756
|
+
];
|
|
12539
12757
|
var COMMUNITY_EVENT_LOCATION_TYPES = [
|
|
12540
12758
|
"online",
|
|
12541
12759
|
"inPerson"
|
|
@@ -17086,6 +17304,36 @@ function printSignupFormDetail(form) {
|
|
|
17086
17304
|
`);
|
|
17087
17305
|
}
|
|
17088
17306
|
}
|
|
17307
|
+
var escapeHtmlAttribute = (value) => value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
17308
|
+
function buildFormSnippet(input) {
|
|
17309
|
+
const copy = input.lang === "pl" ? {
|
|
17310
|
+
email: "Adres e-mail",
|
|
17311
|
+
firstName: "Imi\u0119",
|
|
17312
|
+
submit: "Zapisuj\u0119 si\u0119",
|
|
17313
|
+
success: "Dzi\u0119kujemy! Sprawd\u017A skrzynk\u0119 i potwierd\u017A zapis.",
|
|
17314
|
+
error: "Nie uda\u0142o si\u0119 wys\u0142a\u0107. Spr\xF3buj ponownie."
|
|
17315
|
+
} : {
|
|
17316
|
+
email: "E-mail address",
|
|
17317
|
+
firstName: "First name",
|
|
17318
|
+
submit: "Sign me up",
|
|
17319
|
+
success: "Thank you! Check your inbox to confirm.",
|
|
17320
|
+
error: "Something went wrong. Please try again."
|
|
17321
|
+
};
|
|
17322
|
+
const redirect = input.redirectUrl ? ` data-zanfia-redirect="${escapeHtmlAttribute(input.redirectUrl)}"` : "";
|
|
17323
|
+
const firstName = input.firstName ? `
|
|
17324
|
+
<label>${copy.firstName}<br><input name="firstName" type="text" autocomplete="given-name"></label>` : "";
|
|
17325
|
+
return [
|
|
17326
|
+
`<form data-zanfia-form="${input.workspaceId}/${input.formId}"${redirect}>`,
|
|
17327
|
+
` <label>${copy.email}<br><input name="email" type="email" required autocomplete="email"></label>${firstName}`,
|
|
17328
|
+
` <input name="website" type="text" tabindex="-1" autocomplete="off" aria-hidden="true" style="position:absolute;left:-9999px;opacity:0">`,
|
|
17329
|
+
` <button type="submit">${copy.submit}</button>`,
|
|
17330
|
+
` <div data-zanfia-success hidden>${copy.success}</div>`,
|
|
17331
|
+
` <div data-zanfia-error hidden>${copy.error}</div>`,
|
|
17332
|
+
`</form>`,
|
|
17333
|
+
`<script src="${input.platformUrl}/zanfia-page-forms.js" defer></script>`,
|
|
17334
|
+
""
|
|
17335
|
+
].join("\n");
|
|
17336
|
+
}
|
|
17089
17337
|
async function buildFormUpdateInput(client, formId, options) {
|
|
17090
17338
|
const input = options.json !== void 0 ? parseFormJsonPatch(options.json) : {};
|
|
17091
17339
|
if (options.name !== void 0) input.name = options.name;
|
|
@@ -17221,6 +17469,45 @@ function registerFormsCommands(program2) {
|
|
|
17221
17469
|
`);
|
|
17222
17470
|
});
|
|
17223
17471
|
});
|
|
17472
|
+
forms.command("snippet").description(
|
|
17473
|
+
"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."
|
|
17474
|
+
).argument("<id>", "form id").option("--lang <pl|en>", "language of the placeholder copy (default pl)").option("--first-name", "include a first-name field").option(
|
|
17475
|
+
"--redirect <url>",
|
|
17476
|
+
"thank-you URL for THIS embed (overrides the form setting on the page)"
|
|
17477
|
+
).option(
|
|
17478
|
+
"--platform-url <origin>",
|
|
17479
|
+
"origin that serves zanfia-page-forms.js (default: this build\u2019s platform, or $ZANFIA_PLATFORM_URL)"
|
|
17480
|
+
).action(
|
|
17481
|
+
async (id, options, command) => {
|
|
17482
|
+
const flags = getGlobalFlags(command);
|
|
17483
|
+
const lang = options.lang !== void 0 ? parseChoice2(options.lang, ["pl", "en"], "--lang") : "pl";
|
|
17484
|
+
if (options.redirect !== void 0 && !/^https?:\/\/\S+$/.test(options.redirect)) {
|
|
17485
|
+
throw new Error("--redirect must be an absolute http(s) URL.");
|
|
17486
|
+
}
|
|
17487
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
17488
|
+
const [form, workspace2] = await Promise.all([
|
|
17489
|
+
client.getSignupForm(id),
|
|
17490
|
+
client.getWorkspaceSettings()
|
|
17491
|
+
]);
|
|
17492
|
+
if (form.submitAction === "freeProductSignup") {
|
|
17493
|
+
throw new Error(
|
|
17494
|
+
`Form ${id} is product-bound; the native snippet works for standalone signup forms only.`
|
|
17495
|
+
);
|
|
17496
|
+
}
|
|
17497
|
+
const platformUrl = (options.platformUrl ?? resolvePlatformUrl()).replace(/\/+$/, "");
|
|
17498
|
+
const snippet = buildFormSnippet({
|
|
17499
|
+
workspaceId: workspace2.workspaceId,
|
|
17500
|
+
formId: form.id,
|
|
17501
|
+
platformUrl,
|
|
17502
|
+
lang,
|
|
17503
|
+
redirectUrl: options.redirect ?? form.redirectUrl ?? null,
|
|
17504
|
+
firstName: options.firstName === true
|
|
17505
|
+
});
|
|
17506
|
+
emit(flags, { formId: form.id, workspaceId: workspace2.workspaceId, html: snippet }, () => {
|
|
17507
|
+
process.stdout.write(snippet);
|
|
17508
|
+
});
|
|
17509
|
+
}
|
|
17510
|
+
);
|
|
17224
17511
|
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
17512
|
const flags = getGlobalFlags(command);
|
|
17226
17513
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -17258,6 +17545,7 @@ var FUNNEL_TEMPLATE_IDS = [
|
|
|
17258
17545
|
];
|
|
17259
17546
|
var EVENT_MODES = ["live", "on_demand"];
|
|
17260
17547
|
var SCHEDULER_UNAVAILABLE_STATUS2 = 503;
|
|
17548
|
+
var FUNNEL_NOT_READY_STATUS = 409;
|
|
17261
17549
|
function requireConfirm6(confirm, what) {
|
|
17262
17550
|
if (!confirm) {
|
|
17263
17551
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
@@ -17273,6 +17561,16 @@ function withSchedulerHint2(error) {
|
|
|
17273
17561
|
}
|
|
17274
17562
|
throw error;
|
|
17275
17563
|
}
|
|
17564
|
+
function withLiveGateHint(error, funnelId) {
|
|
17565
|
+
if (error instanceof ApiError && error.status === FUNNEL_NOT_READY_STATUS) {
|
|
17566
|
+
throw new ApiError(
|
|
17567
|
+
error.status,
|
|
17568
|
+
error.apiMessage,
|
|
17569
|
+
`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.`
|
|
17570
|
+
);
|
|
17571
|
+
}
|
|
17572
|
+
throw error;
|
|
17573
|
+
}
|
|
17276
17574
|
function parsePositiveInt10(value, flag) {
|
|
17277
17575
|
const n = Number.parseInt(value, 10);
|
|
17278
17576
|
if (!Number.isInteger(n) || n <= 0) {
|
|
@@ -17449,19 +17747,19 @@ function registerFunnelsCommands(program2) {
|
|
|
17449
17747
|
const flags = getGlobalFlags(command);
|
|
17450
17748
|
const input = buildFunnelUpdateInput(options);
|
|
17451
17749
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
17452
|
-
const result = await client.updateFunnel(id, input);
|
|
17750
|
+
const result = await client.updateFunnel(id, input).catch((error) => withLiveGateHint(error, id));
|
|
17453
17751
|
emit(flags, result, () => {
|
|
17454
17752
|
process.stdout.write(`Updated funnel ${id}.
|
|
17455
17753
|
`);
|
|
17456
17754
|
});
|
|
17457
17755
|
});
|
|
17458
17756
|
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."
|
|
17757
|
+
"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
17758
|
).argument("<id>", "funnel id").requiredOption("--set <status>", `new status (${FUNNEL_STATUSES.join(" | ")})`).action(async (id, options, command) => {
|
|
17461
17759
|
const flags = getGlobalFlags(command);
|
|
17462
17760
|
const status = parseChoice3(options.set, FUNNEL_STATUSES, "--set");
|
|
17463
17761
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
17464
|
-
const result = await client.updateFunnel(id, { status });
|
|
17762
|
+
const result = await client.updateFunnel(id, { status }).catch((error) => withLiveGateHint(error, id));
|
|
17465
17763
|
emit(flags, result, () => {
|
|
17466
17764
|
process.stdout.write(`Funnel ${id} status set to ${status}.
|
|
17467
17765
|
`);
|
|
@@ -17530,7 +17828,10 @@ function registerFunnelsCommands(program2) {
|
|
|
17530
17828
|
});
|
|
17531
17829
|
funnels.command("from-template").description(
|
|
17532
17830
|
"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(
|
|
17831
|
+
).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(
|
|
17832
|
+
"--page-template <id>",
|
|
17833
|
+
"design template for every generated page (a page-template id, e.g. course-fresh); thank-you steps take the companion"
|
|
17834
|
+
).option("--confirm", "required \u2014 acknowledge that AI credits will be consumed").action(
|
|
17534
17835
|
async (templateId, options, command) => {
|
|
17535
17836
|
const flags = getGlobalFlags(command);
|
|
17536
17837
|
requireConfirm6(options.confirm, "create the funnel and generate its pages (AI credits)");
|
|
@@ -17538,7 +17839,8 @@ function registerFunnelsCommands(program2) {
|
|
|
17538
17839
|
templateId: parseChoice3(templateId, FUNNEL_TEMPLATE_IDS, "<templateId>"),
|
|
17539
17840
|
productId: options.product,
|
|
17540
17841
|
lang: parseChoice3(options.lang, FUNNEL_LANGS, "--lang"),
|
|
17541
|
-
name: options.name
|
|
17842
|
+
name: options.name,
|
|
17843
|
+
...options.pageTemplate ? { pageTemplateId: options.pageTemplate } : {}
|
|
17542
17844
|
};
|
|
17543
17845
|
const webinar = {
|
|
17544
17846
|
eventStartAt: options.eventStart,
|
|
@@ -17651,152 +17953,1078 @@ function registerFunnelsCommands(program2) {
|
|
|
17651
17953
|
});
|
|
17652
17954
|
}
|
|
17653
17955
|
|
|
17654
|
-
// src/commands/
|
|
17655
|
-
|
|
17656
|
-
var
|
|
17657
|
-
|
|
17658
|
-
|
|
17659
|
-
|
|
17660
|
-
|
|
17661
|
-
|
|
17662
|
-
|
|
17663
|
-
|
|
17664
|
-
|
|
17956
|
+
// src/commands/integrations/connections.ts
|
|
17957
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
17958
|
+
var EMAIL_CLIENT_ALIASES = {
|
|
17959
|
+
ac: "ActiveCampaign",
|
|
17960
|
+
activecampaign: "ActiveCampaign",
|
|
17961
|
+
kit: "ConvertKit",
|
|
17962
|
+
convertkit: "ConvertKit",
|
|
17963
|
+
mailerlite: "MailerLite",
|
|
17964
|
+
mailchimp: "MailChimp",
|
|
17965
|
+
getresponse: "GetResponse",
|
|
17966
|
+
freshmail: "FreshMail",
|
|
17967
|
+
pipedrive: "Pipedrive"
|
|
17968
|
+
};
|
|
17969
|
+
function parseEmailClient(value) {
|
|
17970
|
+
const match = EMAIL_CLIENT_ALIASES[value.trim().toLowerCase()];
|
|
17971
|
+
if (!match) {
|
|
17665
17972
|
throw new Error(
|
|
17666
|
-
|
|
17973
|
+
`Unknown email client: ${value} (expected ${CONNECTABLE_EMAIL_CLIENTS.join(" | ")}; FreshMail is disconnect-only)`
|
|
17667
17974
|
);
|
|
17668
17975
|
}
|
|
17669
|
-
|
|
17670
|
-
|
|
17671
|
-
|
|
17672
|
-
|
|
17673
|
-
|
|
17976
|
+
return match;
|
|
17977
|
+
}
|
|
17978
|
+
function parseOnOff2(value, flag) {
|
|
17979
|
+
if (value === void 0) return void 0;
|
|
17980
|
+
if (value === "on") return true;
|
|
17981
|
+
if (value === "off") return false;
|
|
17982
|
+
throw new Error(`Invalid ${flag}: ${value} (expected on | off)`);
|
|
17983
|
+
}
|
|
17984
|
+
function requireConfirm7(confirm, what) {
|
|
17985
|
+
if (!confirm) {
|
|
17986
|
+
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
17674
17987
|
}
|
|
17675
|
-
|
|
17676
|
-
|
|
17677
|
-
|
|
17678
|
-
|
|
17988
|
+
}
|
|
17989
|
+
function readSecret(label, value, fromStdin, fromEnv) {
|
|
17990
|
+
const sources = [value !== void 0, fromStdin === true, fromEnv !== void 0].filter(Boolean);
|
|
17991
|
+
if (sources.length > 1) {
|
|
17992
|
+
throw new Error(`Pass ${label} through exactly one of the value, -stdin or -env forms.`);
|
|
17679
17993
|
}
|
|
17680
|
-
|
|
17681
|
-
"
|
|
17682
|
-
|
|
17683
|
-
|
|
17684
|
-
{ verbose }
|
|
17685
|
-
);
|
|
17686
|
-
if (!options.json) {
|
|
17687
|
-
process.stdout.write(
|
|
17688
|
-
`Joining ${started.communityName} (${started.workspaceName}) \u2014 a 6-digit code was sent to ${email}.
|
|
17689
|
-
`
|
|
17690
|
-
);
|
|
17994
|
+
if (fromStdin) {
|
|
17995
|
+
const raw = readFileSync15(0, "utf8").trim();
|
|
17996
|
+
if (!raw) throw new Error(`${label}: nothing was read from stdin.`);
|
|
17997
|
+
return raw;
|
|
17691
17998
|
}
|
|
17692
|
-
|
|
17693
|
-
|
|
17694
|
-
|
|
17695
|
-
|
|
17696
|
-
const code = options.code ?? (await promptLine("Code from the email (or 'r' to resend): ")).trim();
|
|
17697
|
-
if (code.toLowerCase() === "r") {
|
|
17698
|
-
await postJson2(
|
|
17699
|
-
"Resend",
|
|
17700
|
-
joinUrl(communityApiUrl, handle, "start"),
|
|
17701
|
-
{ email, lang: "en" },
|
|
17702
|
-
{ verbose }
|
|
17703
|
-
);
|
|
17704
|
-
process.stdout.write("Code re-sent.\n");
|
|
17705
|
-
attempt -= 1;
|
|
17706
|
-
continue;
|
|
17707
|
-
}
|
|
17708
|
-
try {
|
|
17709
|
-
completed = await postJson2(
|
|
17710
|
-
"Join",
|
|
17711
|
-
joinUrl(communityApiUrl, handle, "complete"),
|
|
17712
|
-
{ email, code, firstName, hostname: hostname3 },
|
|
17713
|
-
{ verbose }
|
|
17714
|
-
);
|
|
17715
|
-
} catch (err) {
|
|
17716
|
-
if (options.code || attempt === MAX_CODE_ATTEMPTS2) throw err;
|
|
17717
|
-
process.stdout.write(`${err instanceof Error ? err.message : String(err)}
|
|
17718
|
-
`);
|
|
17719
|
-
}
|
|
17999
|
+
if (fromEnv !== void 0) {
|
|
18000
|
+
const raw = process.env[fromEnv]?.trim();
|
|
18001
|
+
if (!raw) throw new Error(`${label}: environment variable ${fromEnv} is empty or unset.`);
|
|
18002
|
+
return raw;
|
|
17720
18003
|
}
|
|
17721
|
-
|
|
17722
|
-
|
|
18004
|
+
return value;
|
|
18005
|
+
}
|
|
18006
|
+
function printOverview(overview) {
|
|
18007
|
+
process.stdout.write("Email clients\n");
|
|
18008
|
+
if (overview.emailClients.length === 0) {
|
|
18009
|
+
process.stdout.write(" (none connected)\n");
|
|
18010
|
+
} else {
|
|
18011
|
+
printTable(
|
|
18012
|
+
overview.emailClients.map((account) => ({
|
|
18013
|
+
client: account.client,
|
|
18014
|
+
url: account.url,
|
|
18015
|
+
lists: account.listsCount,
|
|
18016
|
+
tags: account.tagsCount,
|
|
18017
|
+
updatedAt: account.updatedAt ?? account.createdAt
|
|
18018
|
+
})),
|
|
18019
|
+
["client", "url", "lists", "tags", "updatedAt"]
|
|
18020
|
+
);
|
|
17723
18021
|
}
|
|
17724
|
-
|
|
17725
|
-
|
|
17726
|
-
|
|
17727
|
-
|
|
17728
|
-
|
|
17729
|
-
|
|
17730
|
-
|
|
17731
|
-
|
|
17732
|
-
workspaceName: completed.workspaceName,
|
|
17733
|
-
kind: "member"
|
|
17734
|
-
});
|
|
17735
|
-
if (options.json) {
|
|
17736
|
-
process.stdout.write(
|
|
17737
|
-
`${JSON.stringify(
|
|
17738
|
-
{
|
|
17739
|
-
joined: true,
|
|
17740
|
-
alreadyMember: completed.alreadyMember,
|
|
17741
|
-
communityId: completed.communityId,
|
|
17742
|
-
communityName: completed.communityName,
|
|
17743
|
-
workspaceId: completed.workspaceId,
|
|
17744
|
-
workspaceName: completed.workspaceName,
|
|
17745
|
-
introChannelId: completed.introChannelId,
|
|
17746
|
-
userEmail: completed.userEmail,
|
|
17747
|
-
expiresAt: completed.expiresAt,
|
|
17748
|
-
credentialsPath: path2
|
|
17749
|
-
},
|
|
17750
|
-
null,
|
|
17751
|
-
2
|
|
17752
|
-
)}
|
|
18022
|
+
process.stdout.write("Meta pixel / Conversions API\n");
|
|
18023
|
+
if (overview.metaConversionApi.length === 0) {
|
|
18024
|
+
process.stdout.write(" (none)\n");
|
|
18025
|
+
} else {
|
|
18026
|
+
printMetaEntries(overview.metaConversionApi);
|
|
18027
|
+
}
|
|
18028
|
+
process.stdout.write(
|
|
18029
|
+
`Managed pixel integrations (GA4 / GTM / \u2026): ${overview.conversionTrackingIntegrationsCount} \u2014 see \`integrations list\`
|
|
17753
18030
|
`
|
|
18031
|
+
);
|
|
18032
|
+
process.stdout.write("Invoicing\n");
|
|
18033
|
+
if (overview.invoicesClients.length === 0) {
|
|
18034
|
+
process.stdout.write(" (none connected)\n");
|
|
18035
|
+
} else {
|
|
18036
|
+
printTable(
|
|
18037
|
+
overview.invoicesClients.map((account) => ({
|
|
18038
|
+
client: account.client,
|
|
18039
|
+
id: account.id,
|
|
18040
|
+
url: account.url
|
|
18041
|
+
})),
|
|
18042
|
+
["client", "id", "url"]
|
|
17754
18043
|
);
|
|
17755
|
-
return;
|
|
17756
18044
|
}
|
|
17757
|
-
const
|
|
17758
|
-
|
|
18045
|
+
const p = overview.payments;
|
|
18046
|
+
const on = (value) => value ? "on" : "off";
|
|
17759
18047
|
process.stdout.write(
|
|
17760
|
-
`
|
|
17761
|
-
${completed.alreadyMember ? "Welcome back to" : "You are in:"} ${completed.communityName}.
|
|
17762
|
-
Member key saved to ${path2} (expires ${completed.expiresAt.slice(0, 10)}).
|
|
17763
|
-
|
|
17764
|
-
Next:
|
|
17765
|
-
zanfia communities list
|
|
17766
|
-
zanfia communities channels ${completed.communityId}
|
|
17767
|
-
` + (intro ? `
|
|
17768
|
-
Introduce your agent:
|
|
17769
|
-
${intro}` : "") + `
|
|
17770
|
-
Plug in your agent: ${AGENT_DOCS_URL}
|
|
18048
|
+
`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"}
|
|
17771
18049
|
`
|
|
17772
18050
|
);
|
|
17773
18051
|
}
|
|
17774
|
-
function
|
|
17775
|
-
|
|
17776
|
-
|
|
17777
|
-
|
|
17778
|
-
|
|
17779
|
-
|
|
17780
|
-
|
|
17781
|
-
|
|
17782
|
-
|
|
17783
|
-
|
|
17784
|
-
|
|
17785
|
-
|
|
17786
|
-
|
|
17787
|
-
|
|
17788
|
-
|
|
17789
|
-
|
|
17790
|
-
|
|
18052
|
+
function printMetaEntries(entries) {
|
|
18053
|
+
printTable(
|
|
18054
|
+
entries.map((entry) => ({
|
|
18055
|
+
id: entry.id,
|
|
18056
|
+
name: entry.name,
|
|
18057
|
+
pixelId: entry.pixelId,
|
|
18058
|
+
serverSide: entry.serverSide ? "yes" : "",
|
|
18059
|
+
default: entry.isWorkspaceDefault ? "yes" : "",
|
|
18060
|
+
disabled: entry.disabled ? "yes" : "",
|
|
18061
|
+
events: [
|
|
18062
|
+
entry.trackPurchase ? "purchase" : null,
|
|
18063
|
+
entry.trackInitCheckout ? "initCheckout" : null,
|
|
18064
|
+
entry.trackAddToCart ? "addToCart" : null,
|
|
18065
|
+
entry.trackTrialStarted ? "trialStarted" : null
|
|
18066
|
+
].filter(Boolean).join(",")
|
|
18067
|
+
})),
|
|
18068
|
+
["id", "name", "pixelId", "serverSide", "default", "disabled", "events"]
|
|
18069
|
+
);
|
|
17791
18070
|
}
|
|
17792
|
-
|
|
17793
|
-
|
|
17794
|
-
|
|
17795
|
-
|
|
17796
|
-
|
|
17797
|
-
|
|
17798
|
-
|
|
17799
|
-
|
|
18071
|
+
function printAccount(account, headline) {
|
|
18072
|
+
process.stdout.write(`${headline} ${account.client} ${account.url}
|
|
18073
|
+
`);
|
|
18074
|
+
process.stdout.write(` Lists: ${account.listsCount} Tags: ${account.tagsCount}
|
|
18075
|
+
`);
|
|
18076
|
+
}
|
|
18077
|
+
function buildEmailConnectInput(client, options) {
|
|
18078
|
+
if (client === "FreshMail") {
|
|
18079
|
+
throw new Error(
|
|
18080
|
+
"FreshMail can no longer be connected (provider retired); existing accounts can only be disconnected."
|
|
18081
|
+
);
|
|
18082
|
+
}
|
|
18083
|
+
const secret = readSecret("--api-key", options.apiKey, options.apiKeyStdin, options.apiKeyEnv);
|
|
18084
|
+
if (!secret) throw new Error("--api-key (or --api-key-stdin / --api-key-env VAR) is required.");
|
|
18085
|
+
const input = { client, url: options.url };
|
|
18086
|
+
switch (client) {
|
|
18087
|
+
case "ConvertKit":
|
|
18088
|
+
input.apiSecret = secret;
|
|
18089
|
+
break;
|
|
18090
|
+
case "Pipedrive":
|
|
18091
|
+
input.apiSecret = secret;
|
|
18092
|
+
if (!options.tagsField || !options.listsField) {
|
|
18093
|
+
throw new Error("Pipedrive needs --tags-field and --lists-field (person field names).");
|
|
18094
|
+
}
|
|
18095
|
+
input.tagsFieldName = options.tagsField;
|
|
18096
|
+
input.listsFieldName = options.listsField;
|
|
18097
|
+
break;
|
|
18098
|
+
case "MailChimp":
|
|
18099
|
+
input.apiKey = secret;
|
|
18100
|
+
if (!options.serverPrefix) throw new Error("MailChimp needs --server-prefix (e.g. us21).");
|
|
18101
|
+
input.serverPrefix = options.serverPrefix;
|
|
18102
|
+
break;
|
|
18103
|
+
default:
|
|
18104
|
+
input.apiKey = secret;
|
|
18105
|
+
}
|
|
18106
|
+
return input;
|
|
18107
|
+
}
|
|
18108
|
+
function buildMetaInput(options, existing) {
|
|
18109
|
+
const name = options.name ?? existing?.name;
|
|
18110
|
+
const pixelId = options.pixelId ?? existing?.pixelId;
|
|
18111
|
+
if (!name) throw new Error("--name is required.");
|
|
18112
|
+
if (!pixelId) throw new Error("--pixel-id is required.");
|
|
18113
|
+
const token = readSecret(
|
|
18114
|
+
"--access-token",
|
|
18115
|
+
options.accessToken,
|
|
18116
|
+
options.accessTokenStdin,
|
|
18117
|
+
options.accessTokenEnv
|
|
18118
|
+
);
|
|
18119
|
+
return {
|
|
18120
|
+
...existing ? { id: existing.id } : {},
|
|
18121
|
+
name,
|
|
18122
|
+
pixelId,
|
|
18123
|
+
...token !== void 0 ? { accessToken: token } : {},
|
|
18124
|
+
trackPurchase: parseOnOff2(options.trackPurchase, "--track-purchase") ?? existing?.trackPurchase ?? true,
|
|
18125
|
+
trackInitCheckout: parseOnOff2(options.trackInitCheckout, "--track-init-checkout") ?? existing?.trackInitCheckout ?? true,
|
|
18126
|
+
trackAddToCart: parseOnOff2(options.trackAddToCart, "--track-add-to-cart") ?? existing?.trackAddToCart ?? true,
|
|
18127
|
+
trackTrialStarted: parseOnOff2(options.trackTrialStarted, "--track-trial-started") ?? existing?.trackTrialStarted ?? false
|
|
18128
|
+
};
|
|
18129
|
+
}
|
|
18130
|
+
function addMetaOptions(command) {
|
|
18131
|
+
return command.option("--name <name>", "creator-facing label").option("--pixel-id <id>", "Meta pixel / dataset id").option(
|
|
18132
|
+
"--access-token <token>",
|
|
18133
|
+
"Conversions API access token (prefer --access-token-stdin / -env)"
|
|
18134
|
+
).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)");
|
|
18135
|
+
}
|
|
18136
|
+
function registerIntegrationConnectionCommands(integrations) {
|
|
18137
|
+
integrations.command("status").description(
|
|
18138
|
+
"What is connected: email clients, Meta pixel / Conversions API entries, invoicing, payment methods (needs integrations:read)"
|
|
18139
|
+
).action(async (_options, command) => {
|
|
18140
|
+
const flags = getGlobalFlags(command);
|
|
18141
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18142
|
+
const overview = await client.getIntegrationsOverview();
|
|
18143
|
+
emit(flags, overview, () => printOverview(overview));
|
|
18144
|
+
});
|
|
18145
|
+
const emailClients = integrations.command("email-clients").description(
|
|
18146
|
+
"Email-marketing accounts: ActiveCampaign, Kit (ConvertKit), MailerLite, MailChimp, GetResponse, Pipedrive. Keys are verified against the provider before saving and never printed."
|
|
18147
|
+
);
|
|
18148
|
+
emailClients.command("connect <client>").description(
|
|
18149
|
+
"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."
|
|
18150
|
+
).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) => {
|
|
18151
|
+
const flags = getGlobalFlags(command);
|
|
18152
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18153
|
+
const input = buildEmailConnectInput(parseEmailClient(clientArg), options);
|
|
18154
|
+
const account = await client.connectEmailClient(input);
|
|
18155
|
+
emit(flags, account, () => printAccount(account, "Connected"));
|
|
18156
|
+
});
|
|
18157
|
+
emailClients.command("disconnect <client>").description(
|
|
18158
|
+
"Disconnect an account by url (irreversible, the stored key is deleted). Refused while a live newsletter / automation still uses it. Requires --confirm."
|
|
18159
|
+
).requiredOption("--url <url>", "account url from `integrations status`").option("--confirm", "required \u2014 acknowledges the key is deleted").action(
|
|
18160
|
+
async (clientArg, options, command) => {
|
|
18161
|
+
requireConfirm7(options.confirm, `disconnect ${clientArg} ${options.url}`);
|
|
18162
|
+
const flags = getGlobalFlags(command);
|
|
18163
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18164
|
+
const parsed = parseEmailClient(clientArg);
|
|
18165
|
+
const result = await client.disconnectEmailClient(parsed, options.url);
|
|
18166
|
+
emit(flags, { ...result, client: parsed, url: options.url }, () => {
|
|
18167
|
+
process.stdout.write(`Disconnected ${parsed} ${options.url}
|
|
18168
|
+
`);
|
|
18169
|
+
});
|
|
18170
|
+
}
|
|
18171
|
+
);
|
|
18172
|
+
emailClients.command("refresh <client>").description(
|
|
18173
|
+
"Re-fetch the lists + tags of a connected account with the stored key (not Pipedrive)"
|
|
18174
|
+
).requiredOption("--url <url>", "account url from `integrations status`").option("--server-prefix <prefix>", "MailChimp data-center prefix").action(
|
|
18175
|
+
async (clientArg, options, command) => {
|
|
18176
|
+
const flags = getGlobalFlags(command);
|
|
18177
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18178
|
+
const parsed = parseEmailClient(clientArg);
|
|
18179
|
+
if (parsed === "Pipedrive" || parsed === "FreshMail") {
|
|
18180
|
+
throw new Error(`${parsed} lists cannot be refreshed from the CLI.`);
|
|
18181
|
+
}
|
|
18182
|
+
const account = await client.refreshEmailClientLists({
|
|
18183
|
+
client: parsed,
|
|
18184
|
+
url: options.url,
|
|
18185
|
+
...options.serverPrefix ? { serverPrefix: options.serverPrefix } : {}
|
|
18186
|
+
});
|
|
18187
|
+
emit(flags, account, () => printAccount(account, "Refreshed"));
|
|
18188
|
+
}
|
|
18189
|
+
);
|
|
18190
|
+
const meta = integrations.command("meta-capi").description(
|
|
18191
|
+
"Manual Meta pixel + Conversions API entries (the connect-with-Facebook OAuth path stays in the dashboard)"
|
|
18192
|
+
);
|
|
18193
|
+
meta.command("list").description("Meta pixel / Conversions API entries").action(async (_options, command) => {
|
|
18194
|
+
const flags = getGlobalFlags(command);
|
|
18195
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18196
|
+
const overview = await client.getIntegrationsOverview();
|
|
18197
|
+
emit(flags, overview.metaConversionApi, () => printMetaEntries(overview.metaConversionApi));
|
|
18198
|
+
});
|
|
18199
|
+
addMetaOptions(
|
|
18200
|
+
meta.command("add").description(
|
|
18201
|
+
"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."
|
|
18202
|
+
)
|
|
18203
|
+
).action(async (options, command) => {
|
|
18204
|
+
const flags = getGlobalFlags(command);
|
|
18205
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18206
|
+
const entry = await client.connectMetaConversionApi(buildMetaInput(options, null));
|
|
18207
|
+
emit(flags, entry, () => printMetaEntries([entry]));
|
|
18208
|
+
});
|
|
18209
|
+
addMetaOptions(
|
|
18210
|
+
meta.command("update <integrationId>").description("Edit an entry \u2014 only the flags you pass change; an omitted token stays stored")
|
|
18211
|
+
).action(async (integrationId, options, command) => {
|
|
18212
|
+
const flags = getGlobalFlags(command);
|
|
18213
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18214
|
+
const overview = await client.getIntegrationsOverview();
|
|
18215
|
+
const existing = overview.metaConversionApi.find((entry2) => entry2.id === integrationId);
|
|
18216
|
+
if (!existing) {
|
|
18217
|
+
throw new Error(
|
|
18218
|
+
`No Meta Conversions API entry with id ${integrationId}. Run \`zanfia integrations meta-capi list\`.`
|
|
18219
|
+
);
|
|
18220
|
+
}
|
|
18221
|
+
const entry = await client.connectMetaConversionApi(buildMetaInput(options, existing));
|
|
18222
|
+
emit(flags, entry, () => printMetaEntries([entry]));
|
|
18223
|
+
});
|
|
18224
|
+
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) => {
|
|
18225
|
+
requireConfirm7(options.confirm, `remove Meta entry ${integrationId}`);
|
|
18226
|
+
const flags = getGlobalFlags(command);
|
|
18227
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18228
|
+
const result = await client.disconnectMetaConversionApi(integrationId);
|
|
18229
|
+
emit(flags, { ...result, integrationId }, () => {
|
|
18230
|
+
process.stdout.write(`Removed Meta entry ${integrationId}
|
|
18231
|
+
`);
|
|
18232
|
+
});
|
|
18233
|
+
});
|
|
18234
|
+
const manual = integrations.command("manual-payment").description(
|
|
18235
|
+
"The manual / wire-transfer payment method (buyers pay by transfer, you approve the order)"
|
|
18236
|
+
);
|
|
18237
|
+
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(
|
|
18238
|
+
async (options, command) => {
|
|
18239
|
+
const instructions = options.instructions ?? (options.instructionsFile ? readFileSync15(options.instructionsFile, "utf8").trim() : void 0);
|
|
18240
|
+
if (!instructions) throw new Error("--instructions or --instructions-file is required.");
|
|
18241
|
+
const flags = getGlobalFlags(command);
|
|
18242
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18243
|
+
const method = await client.configureManualPayment({ name: options.name, instructions });
|
|
18244
|
+
emit(flags, method, () => {
|
|
18245
|
+
process.stdout.write(`Manual payment: ${method.name}
|
|
18246
|
+
${method.instructions}
|
|
18247
|
+
`);
|
|
18248
|
+
});
|
|
18249
|
+
}
|
|
18250
|
+
);
|
|
18251
|
+
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) => {
|
|
18252
|
+
requireConfirm7(options.confirm, "remove the manual payment method");
|
|
18253
|
+
const flags = getGlobalFlags(command);
|
|
18254
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18255
|
+
const result = await client.disconnectManualPayment();
|
|
18256
|
+
emit(flags, result, () => {
|
|
18257
|
+
process.stdout.write("Manual payment method removed\n");
|
|
18258
|
+
});
|
|
18259
|
+
});
|
|
18260
|
+
}
|
|
18261
|
+
|
|
18262
|
+
// src/commands/integrations/providers.ts
|
|
18263
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
18264
|
+
var INVOICES_ALIASES = {
|
|
18265
|
+
fakturownia: "fakturownia",
|
|
18266
|
+
ifirma: "ifirma",
|
|
18267
|
+
infakt: "inFakt",
|
|
18268
|
+
wfirma: "wFirmaV2",
|
|
18269
|
+
wfirmav2: "wFirmaV2",
|
|
18270
|
+
ing: "ingKsiegowosc",
|
|
18271
|
+
ingksiegowosc: "ingKsiegowosc",
|
|
18272
|
+
stripe: "stripe"
|
|
18273
|
+
};
|
|
18274
|
+
function parseInvoicesClient(value) {
|
|
18275
|
+
const match = INVOICES_ALIASES[value.trim().toLowerCase()];
|
|
18276
|
+
if (!match) {
|
|
18277
|
+
throw new Error(
|
|
18278
|
+
`Unknown invoicing client: ${value} (expected ${INVOICES_CLIENTS.join(" | ")})`
|
|
18279
|
+
);
|
|
18280
|
+
}
|
|
18281
|
+
return match;
|
|
18282
|
+
}
|
|
18283
|
+
function parseGateway(value) {
|
|
18284
|
+
const normalized = value.trim().toLowerCase().replace("p24", "przelewy24");
|
|
18285
|
+
const match = CREDENTIAL_PAYMENT_GATEWAYS.find((gateway) => gateway === normalized);
|
|
18286
|
+
if (!match) {
|
|
18287
|
+
throw new Error(
|
|
18288
|
+
`Unknown gateway: ${value} (expected ${CREDENTIAL_PAYMENT_GATEWAYS.join(" | ")}; Stripe connects in the dashboard, the transfer method is \`integrations manual-payment\`)`
|
|
18289
|
+
);
|
|
18290
|
+
}
|
|
18291
|
+
return match;
|
|
18292
|
+
}
|
|
18293
|
+
function parseEnum(value, allowed, flag) {
|
|
18294
|
+
const match = allowed.find((candidate) => candidate === value);
|
|
18295
|
+
if (!match) throw new Error(`Invalid ${flag}: ${value} (expected ${allowed.join(" | ")})`);
|
|
18296
|
+
return match;
|
|
18297
|
+
}
|
|
18298
|
+
function requireConfirm8(confirm, what) {
|
|
18299
|
+
if (!confirm) {
|
|
18300
|
+
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
18301
|
+
}
|
|
18302
|
+
}
|
|
18303
|
+
function readSecret2(label, value, fromStdin, fromEnv) {
|
|
18304
|
+
const sources = [value !== void 0, fromStdin === true, fromEnv !== void 0].filter(Boolean);
|
|
18305
|
+
if (sources.length > 1) {
|
|
18306
|
+
throw new Error(`Pass ${label} through exactly one of the value, -stdin or -env forms.`);
|
|
18307
|
+
}
|
|
18308
|
+
if (fromStdin) {
|
|
18309
|
+
const raw = readFileSync16(0, "utf8").trim();
|
|
18310
|
+
if (!raw) throw new Error(`${label}: nothing was read from stdin.`);
|
|
18311
|
+
return raw;
|
|
18312
|
+
}
|
|
18313
|
+
if (fromEnv !== void 0) {
|
|
18314
|
+
const raw = process.env[fromEnv]?.trim();
|
|
18315
|
+
if (!raw) throw new Error(`${label}: environment variable ${fromEnv} is empty or unset.`);
|
|
18316
|
+
return raw;
|
|
18317
|
+
}
|
|
18318
|
+
return value;
|
|
18319
|
+
}
|
|
18320
|
+
function printPayments(payments) {
|
|
18321
|
+
const on = (value) => value ? "on" : "off";
|
|
18322
|
+
process.stdout.write(
|
|
18323
|
+
`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"}
|
|
18324
|
+
`
|
|
18325
|
+
);
|
|
18326
|
+
}
|
|
18327
|
+
function buildInvoicesInput(client, options) {
|
|
18328
|
+
const secret = readSecret2("--api-key", options.apiKey, options.apiKeyStdin, options.apiKeyEnv);
|
|
18329
|
+
const input = { client };
|
|
18330
|
+
if (options.id) input.id = options.id;
|
|
18331
|
+
if (options.name) input.accountName = options.name;
|
|
18332
|
+
if (options.url) input.url = options.url;
|
|
18333
|
+
if (options.departmentId) input.departmentId = options.departmentId;
|
|
18334
|
+
switch (client) {
|
|
18335
|
+
case "wFirmaV2":
|
|
18336
|
+
if (!options.accessKey || !options.secretKey || !options.companyId) {
|
|
18337
|
+
throw new Error("wFirma needs --access-key, --secret-key and --company-id.");
|
|
18338
|
+
}
|
|
18339
|
+
input.accessKey = options.accessKey;
|
|
18340
|
+
input.secretKey = options.secretKey;
|
|
18341
|
+
input.companyId = options.companyId;
|
|
18342
|
+
break;
|
|
18343
|
+
case "stripe":
|
|
18344
|
+
break;
|
|
18345
|
+
case "ifirma":
|
|
18346
|
+
if (secret) input.apiKey = secret;
|
|
18347
|
+
if (options.accountApiKey) input.accountApiKey = options.accountApiKey;
|
|
18348
|
+
break;
|
|
18349
|
+
case "ingKsiegowosc":
|
|
18350
|
+
if (secret) input.apiKey = secret;
|
|
18351
|
+
if (options.bankAccount) input.bankAccountNumber = options.bankAccount;
|
|
18352
|
+
break;
|
|
18353
|
+
default:
|
|
18354
|
+
if (secret) input.apiKey = secret;
|
|
18355
|
+
}
|
|
18356
|
+
if (client !== "stripe" && !input.url) throw new Error("--url is required.");
|
|
18357
|
+
return input;
|
|
18358
|
+
}
|
|
18359
|
+
function resolveSecret(value, fromEnv, flag) {
|
|
18360
|
+
if (value === void 0 || !fromEnv) return value;
|
|
18361
|
+
const raw = process.env[value]?.trim();
|
|
18362
|
+
if (!raw) throw new Error(`${flag}: environment variable ${value} is empty or unset.`);
|
|
18363
|
+
return raw;
|
|
18364
|
+
}
|
|
18365
|
+
function buildGatewayInput(gateway, o) {
|
|
18366
|
+
const need = (value, flag) => {
|
|
18367
|
+
if (!value) throw new Error(`${flag} is required for ${gateway}.`);
|
|
18368
|
+
return value;
|
|
18369
|
+
};
|
|
18370
|
+
const secret = (value, flag) => need(resolveSecret(value, o.secretsEnv, flag), flag);
|
|
18371
|
+
const input = { gateway, name: o.name };
|
|
18372
|
+
switch (gateway) {
|
|
18373
|
+
case "payu":
|
|
18374
|
+
input.posId = need(o.posId, "--pos-id");
|
|
18375
|
+
input.clientId = need(o.clientId, "--client-id");
|
|
18376
|
+
input.clientSecret = secret(o.clientSecret, "--client-secret");
|
|
18377
|
+
input.clientSecondKey = secret(o.secondKey, "--second-key");
|
|
18378
|
+
break;
|
|
18379
|
+
case "tpay":
|
|
18380
|
+
input.posId = need(o.posId, "--pos-id");
|
|
18381
|
+
input.clientId = need(o.clientId, "--client-id");
|
|
18382
|
+
input.clientSecret = secret(o.clientSecret, "--client-secret");
|
|
18383
|
+
input.safetyKey = secret(o.safetyKey, "--safety-key");
|
|
18384
|
+
break;
|
|
18385
|
+
case "przelewy24":
|
|
18386
|
+
input.merchantId = need(o.merchantId, "--merchant-id");
|
|
18387
|
+
input.posId = need(o.posId, "--pos-id");
|
|
18388
|
+
input.apiKey = secret(o.apiKey, "--api-key");
|
|
18389
|
+
input.crcKey = secret(o.crcKey, "--crc-key");
|
|
18390
|
+
break;
|
|
18391
|
+
case "paynow":
|
|
18392
|
+
input.apiKey = secret(o.apiKey, "--api-key");
|
|
18393
|
+
input.signatureKey = secret(o.signatureKey, "--signature-key");
|
|
18394
|
+
break;
|
|
18395
|
+
case "paypal":
|
|
18396
|
+
input.clientId = need(o.clientId, "--client-id");
|
|
18397
|
+
input.clientSecret = secret(o.clientSecret, "--client-secret");
|
|
18398
|
+
break;
|
|
18399
|
+
case "dodo":
|
|
18400
|
+
input.apiKey = secret(o.apiKey, "--api-key");
|
|
18401
|
+
break;
|
|
18402
|
+
}
|
|
18403
|
+
return input;
|
|
18404
|
+
}
|
|
18405
|
+
var TWILIO_REGIONS = ["us1", "ie1", "au1"];
|
|
18406
|
+
var TWILIO_AUTH_KINDS = ["authToken", "apiKey"];
|
|
18407
|
+
var TWILIO_SENDER_KINDS = [
|
|
18408
|
+
"phoneNumber",
|
|
18409
|
+
"messagingService"
|
|
18410
|
+
];
|
|
18411
|
+
var SMSAPI_REGIONS = ["pl", "com"];
|
|
18412
|
+
var SMS_PROVIDERS = ["twilio", "smsapi"];
|
|
18413
|
+
function registerIntegrationProviderCommands(integrations) {
|
|
18414
|
+
const invoices = integrations.command("invoices").description(
|
|
18415
|
+
"Invoicing accounts: fakturownia, ifirma, infakt, wfirma (v2), ing (ING Ksi\u0119gowo\u015B\u0107), stripe. Keys are verified with the provider and never printed."
|
|
18416
|
+
);
|
|
18417
|
+
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) => {
|
|
18418
|
+
const flags = getGlobalFlags(command);
|
|
18419
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18420
|
+
const account = await client.connectInvoicesClient(
|
|
18421
|
+
buildInvoicesInput(parseInvoicesClient(clientArg), options)
|
|
18422
|
+
);
|
|
18423
|
+
emit(flags, account, () => {
|
|
18424
|
+
process.stdout.write(`Connected ${account.client} ${account.id} (${account.url})
|
|
18425
|
+
`);
|
|
18426
|
+
});
|
|
18427
|
+
});
|
|
18428
|
+
invoices.command("disconnect <client> <accountId>").description("Disconnect an invoicing account (irreversible). Requires --confirm.").option("--confirm", "required \u2014 acknowledges the stored key is deleted").action(
|
|
18429
|
+
async (clientArg, accountId, options, command) => {
|
|
18430
|
+
requireConfirm8(options.confirm, `disconnect ${clientArg} ${accountId}`);
|
|
18431
|
+
const flags = getGlobalFlags(command);
|
|
18432
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18433
|
+
const parsed = parseInvoicesClient(clientArg);
|
|
18434
|
+
const result = await client.disconnectInvoicesClient(parsed, accountId);
|
|
18435
|
+
emit(flags, { ...result, client: parsed, id: accountId }, () => {
|
|
18436
|
+
process.stdout.write(`Disconnected ${parsed} ${accountId}
|
|
18437
|
+
`);
|
|
18438
|
+
});
|
|
18439
|
+
}
|
|
18440
|
+
);
|
|
18441
|
+
const payments = integrations.command("payments").description(
|
|
18442
|
+
"Credential payment gateways: payu, tpay, przelewy24 (p24), paynow, paypal, dodo. Stripe connects in the dashboard (OAuth); the transfer method is `integrations manual-payment`."
|
|
18443
|
+
);
|
|
18444
|
+
payments.command("connect <gateway>").description(
|
|
18445
|
+
"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."
|
|
18446
|
+
).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) => {
|
|
18447
|
+
const flags = getGlobalFlags(command);
|
|
18448
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18449
|
+
const status = await client.connectPaymentGateway(
|
|
18450
|
+
buildGatewayInput(parseGateway(gatewayArg), options)
|
|
18451
|
+
);
|
|
18452
|
+
emit(flags, status, () => printPayments(status));
|
|
18453
|
+
});
|
|
18454
|
+
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) => {
|
|
18455
|
+
requireConfirm8(options.confirm, `disconnect ${gatewayArg}`);
|
|
18456
|
+
const flags = getGlobalFlags(command);
|
|
18457
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18458
|
+
const status = await client.disconnectPaymentGateway(parseGateway(gatewayArg));
|
|
18459
|
+
emit(flags, status, () => printPayments(status));
|
|
18460
|
+
});
|
|
18461
|
+
payments.command("tpay-blik <on|off>").description("Turn Tpay recurring BLIK (BLIK subscriptions) on or off").action(async (value, _options, command) => {
|
|
18462
|
+
const enabled = parseEnum(value, ["on", "off"], "tpay-blik") === "on";
|
|
18463
|
+
const flags = getGlobalFlags(command);
|
|
18464
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18465
|
+
const result = await client.setTpayRecurringBlik(enabled);
|
|
18466
|
+
emit(flags, { ...result, enabled }, () => {
|
|
18467
|
+
process.stdout.write(`Tpay recurring BLIK: ${enabled ? "on" : "off"}
|
|
18468
|
+
`);
|
|
18469
|
+
});
|
|
18470
|
+
});
|
|
18471
|
+
const sms = integrations.command("sms").description("SMS providers (Twilio, SMSAPI) and which one campaigns send through");
|
|
18472
|
+
const twilio = sms.command("twilio").description("Twilio account");
|
|
18473
|
+
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(
|
|
18474
|
+
async (options, command) => {
|
|
18475
|
+
const token = readSecret2(
|
|
18476
|
+
"--auth-token",
|
|
18477
|
+
options.authToken,
|
|
18478
|
+
options.authTokenStdin,
|
|
18479
|
+
options.authTokenEnv
|
|
18480
|
+
);
|
|
18481
|
+
if (!token)
|
|
18482
|
+
throw new Error(
|
|
18483
|
+
"--auth-token (or --auth-token-stdin / --auth-token-env VAR) is required."
|
|
18484
|
+
);
|
|
18485
|
+
const input = {
|
|
18486
|
+
accountSid: options.accountSid,
|
|
18487
|
+
authToken: token,
|
|
18488
|
+
senderKind: parseEnum(options.senderKind, TWILIO_SENDER_KINDS, "--sender-kind"),
|
|
18489
|
+
sender: options.sender
|
|
18490
|
+
};
|
|
18491
|
+
if (options.region) input.region = parseEnum(options.region, TWILIO_REGIONS, "--region");
|
|
18492
|
+
if (options.authKind)
|
|
18493
|
+
input.authKind = parseEnum(options.authKind, TWILIO_AUTH_KINDS, "--auth-kind");
|
|
18494
|
+
if (options.apiKeySid) input.apiKeySid = options.apiKeySid;
|
|
18495
|
+
const flags = getGlobalFlags(command);
|
|
18496
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18497
|
+
const result = await client.connectTwilio(input);
|
|
18498
|
+
emit(flags, result, () => {
|
|
18499
|
+
process.stdout.write(
|
|
18500
|
+
`Connected Twilio ${result.accountSid} (${result.region}) sender ${result.sender}
|
|
18501
|
+
`
|
|
18502
|
+
);
|
|
18503
|
+
});
|
|
18504
|
+
}
|
|
18505
|
+
);
|
|
18506
|
+
twilio.command("disconnect").description("Disconnect Twilio (secret deleted). Requires --confirm.").option("--confirm", "required").action(async (options, command) => {
|
|
18507
|
+
requireConfirm8(options.confirm, "disconnect Twilio");
|
|
18508
|
+
const flags = getGlobalFlags(command);
|
|
18509
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18510
|
+
const result = await client.disconnectTwilio();
|
|
18511
|
+
emit(flags, result, () => {
|
|
18512
|
+
process.stdout.write("Twilio disconnected\n");
|
|
18513
|
+
});
|
|
18514
|
+
});
|
|
18515
|
+
const smsapi = sms.command("smsapi").description("SMSAPI account (smsapi.pl / smsapi.com)");
|
|
18516
|
+
smsapi.command("connect").description(
|
|
18517
|
+
"Connect (or replace) the SMSAPI account \u2014 the token is verified with SMSAPI first"
|
|
18518
|
+
).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(
|
|
18519
|
+
async (options, command) => {
|
|
18520
|
+
const token = readSecret2(
|
|
18521
|
+
"--api-token",
|
|
18522
|
+
options.apiToken,
|
|
18523
|
+
options.apiTokenStdin,
|
|
18524
|
+
options.apiTokenEnv
|
|
18525
|
+
);
|
|
18526
|
+
if (!token)
|
|
18527
|
+
throw new Error("--api-token (or --api-token-stdin / --api-token-env VAR) is required.");
|
|
18528
|
+
const flags = getGlobalFlags(command);
|
|
18529
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18530
|
+
const result = await client.connectSmsApi({
|
|
18531
|
+
region: options.region ? parseEnum(options.region, SMSAPI_REGIONS, "--region") : "pl",
|
|
18532
|
+
apiToken: token,
|
|
18533
|
+
sender: options.sender
|
|
18534
|
+
});
|
|
18535
|
+
emit(flags, result, () => {
|
|
18536
|
+
process.stdout.write(
|
|
18537
|
+
`Connected SMSAPI (${result.region}) sender ${result.sender}${result.points !== null ? `, ${result.points} points` : ""}
|
|
18538
|
+
`
|
|
18539
|
+
);
|
|
18540
|
+
});
|
|
18541
|
+
}
|
|
18542
|
+
);
|
|
18543
|
+
smsapi.command("disconnect").description("Disconnect SMSAPI (token deleted). Requires --confirm.").option("--confirm", "required").action(async (options, command) => {
|
|
18544
|
+
requireConfirm8(options.confirm, "disconnect SMSAPI");
|
|
18545
|
+
const flags = getGlobalFlags(command);
|
|
18546
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18547
|
+
const result = await client.disconnectSmsApi();
|
|
18548
|
+
emit(flags, result, () => {
|
|
18549
|
+
process.stdout.write("SMSAPI disconnected\n");
|
|
18550
|
+
});
|
|
18551
|
+
});
|
|
18552
|
+
smsapi.command("refresh-senders").description("Re-fetch the approved SMSAPI sender names").action(async (_options, command) => {
|
|
18553
|
+
const flags = getGlobalFlags(command);
|
|
18554
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18555
|
+
const result = await client.refreshSmsApiSenders();
|
|
18556
|
+
emit(flags, result, () => {
|
|
18557
|
+
process.stdout.write(
|
|
18558
|
+
`Senders: ${result.senders.join(", ") || "(none)"}
|
|
18559
|
+
Default: ${result.sender}
|
|
18560
|
+
`
|
|
18561
|
+
);
|
|
18562
|
+
});
|
|
18563
|
+
});
|
|
18564
|
+
smsapi.command("default-sender <sender>").description("Pick which approved SMSAPI sender campaigns send from").action(async (sender, _options, command) => {
|
|
18565
|
+
const flags = getGlobalFlags(command);
|
|
18566
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18567
|
+
const result = await client.setDefaultSmsApiSender(sender);
|
|
18568
|
+
emit(flags, result, () => {
|
|
18569
|
+
process.stdout.write(`Default SMSAPI sender: ${result.sender}
|
|
18570
|
+
`);
|
|
18571
|
+
});
|
|
18572
|
+
});
|
|
18573
|
+
sms.command("default-provider <twilio|smsapi>").description("Choose which connected SMS provider campaigns and automations send through").action(async (value, _options, command) => {
|
|
18574
|
+
const provider = parseEnum(value, SMS_PROVIDERS, "default-provider");
|
|
18575
|
+
const flags = getGlobalFlags(command);
|
|
18576
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18577
|
+
const result = await client.setDefaultSmsProvider(provider);
|
|
18578
|
+
emit(flags, result, () => {
|
|
18579
|
+
process.stdout.write(`Default SMS provider: ${result.defaultProvider}
|
|
18580
|
+
`);
|
|
18581
|
+
});
|
|
18582
|
+
});
|
|
18583
|
+
}
|
|
18584
|
+
|
|
18585
|
+
// src/commands/integrations/index.ts
|
|
18586
|
+
var CLIENT_ALIASES = {
|
|
18587
|
+
gtm: "googleTagManager",
|
|
18588
|
+
ga4: "googleAnalytics",
|
|
18589
|
+
ga: "googleAnalytics",
|
|
18590
|
+
clarity: "microsoftClarity",
|
|
18591
|
+
twitter: "x"
|
|
18592
|
+
};
|
|
18593
|
+
var NONE = "none";
|
|
18594
|
+
function parseClient(value) {
|
|
18595
|
+
const alias = CLIENT_ALIASES[value.trim().toLowerCase()];
|
|
18596
|
+
if (alias) return alias;
|
|
18597
|
+
const match = MANAGED_CONVERSION_TRACKING_CLIENTS.find(
|
|
18598
|
+
(client) => client.toLowerCase() === value.trim().toLowerCase()
|
|
18599
|
+
);
|
|
18600
|
+
if (!match) {
|
|
18601
|
+
throw new Error(
|
|
18602
|
+
`Unknown platform: ${value} (expected ${MANAGED_CONVERSION_TRACKING_CLIENTS.join(" | ")}; aliases: ${Object.keys(CLIENT_ALIASES).join(", ")}). Meta pixel / CAPI is dashboard-only.`
|
|
18603
|
+
);
|
|
18604
|
+
}
|
|
18605
|
+
return match;
|
|
18606
|
+
}
|
|
18607
|
+
function parseOnOff3(value, flag) {
|
|
18608
|
+
if (value === void 0) return void 0;
|
|
18609
|
+
if (value === "on") return true;
|
|
18610
|
+
if (value === "off") return false;
|
|
18611
|
+
throw new Error(`Invalid ${flag}: ${value} (expected on | off)`);
|
|
18612
|
+
}
|
|
18613
|
+
function textOrNull(value) {
|
|
18614
|
+
if (value === void 0) return void 0;
|
|
18615
|
+
return value.trim().toLowerCase() === NONE ? null : value;
|
|
18616
|
+
}
|
|
18617
|
+
function requireConfirm9(confirm, what) {
|
|
18618
|
+
if (!confirm) {
|
|
18619
|
+
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
18620
|
+
}
|
|
18621
|
+
}
|
|
18622
|
+
var TRACK_FLAG_DEFAULTS = {
|
|
18623
|
+
trackPurchase: true,
|
|
18624
|
+
trackInitCheckout: true,
|
|
18625
|
+
trackAddToCart: true,
|
|
18626
|
+
trackTrialStarted: false
|
|
18627
|
+
};
|
|
18628
|
+
var REQUIRED_ID_FLAG = {
|
|
18629
|
+
googleAnalytics: "measurementId",
|
|
18630
|
+
googleTagManager: "containerId",
|
|
18631
|
+
tiktok: "pixelId",
|
|
18632
|
+
x: "pixelId",
|
|
18633
|
+
linkedin: "partnerId",
|
|
18634
|
+
microsoftClarity: "projectId",
|
|
18635
|
+
hotjar: "siteId"
|
|
18636
|
+
};
|
|
18637
|
+
var FLAG_NAME = {
|
|
18638
|
+
name: "--name",
|
|
18639
|
+
containerId: "--container-id",
|
|
18640
|
+
measurementId: "--measurement-id",
|
|
18641
|
+
apiSecret: "--api-secret",
|
|
18642
|
+
pixelId: "--pixel-id",
|
|
18643
|
+
accessToken: "--access-token",
|
|
18644
|
+
partnerId: "--partner-id",
|
|
18645
|
+
projectId: "--project-id",
|
|
18646
|
+
siteId: "--site-id",
|
|
18647
|
+
purchaseTag: "--purchase-tag",
|
|
18648
|
+
initCheckoutTag: "--init-checkout-tag",
|
|
18649
|
+
addToCartTag: "--add-to-cart-tag",
|
|
18650
|
+
trialStartedTag: "--trial-started-tag",
|
|
18651
|
+
trackPurchase: "--track-purchase",
|
|
18652
|
+
trackInitCheckout: "--track-init-checkout",
|
|
18653
|
+
trackAddToCart: "--track-add-to-cart",
|
|
18654
|
+
trackTrialStarted: "--track-trial-started"
|
|
18655
|
+
};
|
|
18656
|
+
var hasEventFlags = (client) => client === "googleAnalytics" || client === "tiktok" || client === "x" || client === "linkedin";
|
|
18657
|
+
function buildUpsertInput(client, options, existing) {
|
|
18658
|
+
const name = options.name ?? existing?.name;
|
|
18659
|
+
if (!name) throw new Error("--name is required when adding an integration.");
|
|
18660
|
+
const input = { client, name };
|
|
18661
|
+
if (existing) input.integrationId = existing.id;
|
|
18662
|
+
const existingFields = existing ? { ...existing } : {};
|
|
18663
|
+
const pick = (key, provided) => {
|
|
18664
|
+
if (provided !== void 0) return provided;
|
|
18665
|
+
const value = existingFields[key];
|
|
18666
|
+
return typeof value === "string" ? value : void 0;
|
|
18667
|
+
};
|
|
18668
|
+
switch (client) {
|
|
18669
|
+
case "googleTagManager":
|
|
18670
|
+
input.containerId = pick("containerId", options.containerId);
|
|
18671
|
+
break;
|
|
18672
|
+
case "googleAnalytics":
|
|
18673
|
+
input.measurementId = pick("measurementId", options.measurementId);
|
|
18674
|
+
if (options.apiSecret !== void 0) input.apiSecret = textOrNull(options.apiSecret);
|
|
18675
|
+
break;
|
|
18676
|
+
case "tiktok":
|
|
18677
|
+
input.pixelId = pick("pixelId", options.pixelId);
|
|
18678
|
+
if (options.accessToken !== void 0) input.accessToken = textOrNull(options.accessToken);
|
|
18679
|
+
break;
|
|
18680
|
+
case "x":
|
|
18681
|
+
input.pixelId = pick("pixelId", options.pixelId);
|
|
18682
|
+
if (options.accessToken !== void 0) input.accessToken = textOrNull(options.accessToken);
|
|
18683
|
+
input.purchaseEventTag = textOrNull(options.purchaseTag) ?? pick("purchaseEventTag", void 0) ?? null;
|
|
18684
|
+
input.initCheckoutEventTag = textOrNull(options.initCheckoutTag) ?? pick("initCheckoutEventTag", void 0) ?? null;
|
|
18685
|
+
input.addToCartEventTag = textOrNull(options.addToCartTag) ?? pick("addToCartEventTag", void 0) ?? null;
|
|
18686
|
+
input.trialStartedEventTag = textOrNull(options.trialStartedTag) ?? pick("trialStartedEventTag", void 0) ?? null;
|
|
18687
|
+
break;
|
|
18688
|
+
case "linkedin":
|
|
18689
|
+
input.partnerId = pick("partnerId", options.partnerId);
|
|
18690
|
+
if (options.accessToken !== void 0) input.accessToken = textOrNull(options.accessToken);
|
|
18691
|
+
input.purchaseConversionUrn = textOrNull(options.purchaseTag) ?? pick("purchaseConversionUrn", void 0) ?? null;
|
|
18692
|
+
input.initCheckoutConversionUrn = textOrNull(options.initCheckoutTag) ?? pick("initCheckoutConversionUrn", void 0) ?? null;
|
|
18693
|
+
input.addToCartConversionUrn = textOrNull(options.addToCartTag) ?? pick("addToCartConversionUrn", void 0) ?? null;
|
|
18694
|
+
input.trialStartedConversionUrn = textOrNull(options.trialStartedTag) ?? pick("trialStartedConversionUrn", void 0) ?? null;
|
|
18695
|
+
break;
|
|
18696
|
+
case "microsoftClarity":
|
|
18697
|
+
input.projectId = pick("projectId", options.projectId);
|
|
18698
|
+
break;
|
|
18699
|
+
case "hotjar":
|
|
18700
|
+
input.siteId = pick("siteId", options.siteId);
|
|
18701
|
+
break;
|
|
18702
|
+
}
|
|
18703
|
+
const requiredKey = REQUIRED_ID_FLAG[client];
|
|
18704
|
+
if (input[requiredKey] === void 0) {
|
|
18705
|
+
throw new Error(`${FLAG_NAME[requiredKey]} is required for ${client}.`);
|
|
18706
|
+
}
|
|
18707
|
+
if (hasEventFlags(client)) {
|
|
18708
|
+
const flags = existing && "trackPurchase" in existing ? existing : TRACK_FLAG_DEFAULTS;
|
|
18709
|
+
input.trackPurchase = parseOnOff3(options.trackPurchase, FLAG_NAME.trackPurchase) ?? flags.trackPurchase;
|
|
18710
|
+
input.trackInitCheckout = parseOnOff3(options.trackInitCheckout, FLAG_NAME.trackInitCheckout) ?? flags.trackInitCheckout;
|
|
18711
|
+
input.trackAddToCart = parseOnOff3(options.trackAddToCart, FLAG_NAME.trackAddToCart) ?? flags.trackAddToCart;
|
|
18712
|
+
input.trackTrialStarted = parseOnOff3(options.trackTrialStarted, FLAG_NAME.trackTrialStarted) ?? flags.trackTrialStarted;
|
|
18713
|
+
}
|
|
18714
|
+
return input;
|
|
18715
|
+
}
|
|
18716
|
+
function platformIdOf(integration) {
|
|
18717
|
+
switch (integration.client) {
|
|
18718
|
+
case "googleAnalytics":
|
|
18719
|
+
return integration.measurementId;
|
|
18720
|
+
case "googleTagManager":
|
|
18721
|
+
return integration.containerId;
|
|
18722
|
+
case "tiktok":
|
|
18723
|
+
case "x":
|
|
18724
|
+
return integration.pixelId;
|
|
18725
|
+
case "linkedin":
|
|
18726
|
+
return integration.partnerId;
|
|
18727
|
+
case "microsoftClarity":
|
|
18728
|
+
return integration.projectId;
|
|
18729
|
+
case "hotjar":
|
|
18730
|
+
return integration.siteId;
|
|
18731
|
+
}
|
|
18732
|
+
}
|
|
18733
|
+
function serverSideOf(integration) {
|
|
18734
|
+
if ("apiSecretPath" in integration) return integration.apiSecretPath ? "yes" : "";
|
|
18735
|
+
return "n/a";
|
|
18736
|
+
}
|
|
18737
|
+
function printIntegrations(integrations) {
|
|
18738
|
+
printTable(
|
|
18739
|
+
integrations.map((integration) => ({
|
|
18740
|
+
id: integration.id,
|
|
18741
|
+
platform: integration.client,
|
|
18742
|
+
name: integration.name,
|
|
18743
|
+
platformId: platformIdOf(integration),
|
|
18744
|
+
serverSide: serverSideOf(integration),
|
|
18745
|
+
default: integration.isWorkspaceDefault ? "yes" : "",
|
|
18746
|
+
updatedAt: integration.updatedAt
|
|
18747
|
+
})),
|
|
18748
|
+
["id", "platform", "name", "platformId", "serverSide", "default", "updatedAt"]
|
|
18749
|
+
);
|
|
18750
|
+
}
|
|
18751
|
+
function printIntegration(integration, headline) {
|
|
18752
|
+
process.stdout.write(`${headline} ${integration.id} (${integration.client})
|
|
18753
|
+
`);
|
|
18754
|
+
process.stdout.write(` Name: ${integration.name}
|
|
18755
|
+
`);
|
|
18756
|
+
process.stdout.write(` Platform id: ${platformIdOf(integration)}
|
|
18757
|
+
`);
|
|
18758
|
+
process.stdout.write(` Workspace default: ${integration.isWorkspaceDefault ? "yes" : "no"}
|
|
18759
|
+
`);
|
|
18760
|
+
if ("trackPurchase" in integration) {
|
|
18761
|
+
process.stdout.write(
|
|
18762
|
+
` Events: purchase=${integration.trackPurchase ? "on" : "off"} initCheckout=${integration.trackInitCheckout ? "on" : "off"} addToCart=${integration.trackAddToCart ? "on" : "off"} trialStarted=${integration.trackTrialStarted ? "on" : "off"}
|
|
18763
|
+
`
|
|
18764
|
+
);
|
|
18765
|
+
}
|
|
18766
|
+
if ("apiSecretPath" in integration) {
|
|
18767
|
+
process.stdout.write(` Server-side events: ${integration.apiSecretPath ? "on" : "off"}
|
|
18768
|
+
`);
|
|
18769
|
+
}
|
|
18770
|
+
}
|
|
18771
|
+
function addPlatformOptions(command) {
|
|
18772
|
+
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(
|
|
18773
|
+
"--api-secret <secret|none>",
|
|
18774
|
+
"googleAnalytics: Measurement Protocol API secret for server-side events (validated against GA4); none = client-side only"
|
|
18775
|
+
).option("--pixel-id <id>", "tiktok / x: pixel id").option(
|
|
18776
|
+
"--access-token <token|none>",
|
|
18777
|
+
"tiktok / x / linkedin: events API access token for server-side events (TikTok/LinkedIn tokens are validated); none = client-side only"
|
|
18778
|
+
).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(
|
|
18779
|
+
"--purchase-tag <tag|none>",
|
|
18780
|
+
"x: purchase event tag / linkedin: purchase conversion URN"
|
|
18781
|
+
).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(
|
|
18782
|
+
"--track-init-checkout <on|off>",
|
|
18783
|
+
"GA4 / TikTok / X / LinkedIn: send InitiateCheckout (default on)"
|
|
18784
|
+
).option(
|
|
18785
|
+
"--track-add-to-cart <on|off>",
|
|
18786
|
+
"GA4 / TikTok / X / LinkedIn: send AddToCart (default on)"
|
|
18787
|
+
).option(
|
|
18788
|
+
"--track-trial-started <on|off>",
|
|
18789
|
+
"GA4 / TikTok / X / LinkedIn: send TrialStarted (default off)"
|
|
18790
|
+
);
|
|
18791
|
+
}
|
|
18792
|
+
function registerIntegrationsCommands(program2) {
|
|
18793
|
+
const integrations = program2.command("integrations").description(
|
|
18794
|
+
"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`."
|
|
18795
|
+
);
|
|
18796
|
+
registerIntegrationConnectionCommands(integrations);
|
|
18797
|
+
registerIntegrationProviderCommands(integrations);
|
|
18798
|
+
integrations.command("list").description("List the managed conversion-tracking integrations and the workspace defaults").option(
|
|
18799
|
+
"--client <platform>",
|
|
18800
|
+
"only one platform (e.g. gtm, ga4, tiktok, x, linkedin, clarity, hotjar)"
|
|
18801
|
+
).action(async (options, command) => {
|
|
18802
|
+
const flags = getGlobalFlags(command);
|
|
18803
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18804
|
+
const result = await client.listConversionTrackingIntegrations();
|
|
18805
|
+
const filter = options.client !== void 0 ? parseClient(options.client) : void 0;
|
|
18806
|
+
const entries = filter ? result.integrations.filter((integration) => integration.client === filter) : result.integrations;
|
|
18807
|
+
emit(flags, { ...result, integrations: entries }, () => {
|
|
18808
|
+
printIntegrations(entries);
|
|
18809
|
+
process.stdout.write(
|
|
18810
|
+
`Workspace defaults: ${result.defaultIntegrationIds.length > 0 ? result.defaultIntegrationIds.join(", ") : "(none)"}
|
|
18811
|
+
`
|
|
18812
|
+
);
|
|
18813
|
+
});
|
|
18814
|
+
});
|
|
18815
|
+
addPlatformOptions(
|
|
18816
|
+
integrations.command("add <platform>").description(
|
|
18817
|
+
"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"
|
|
18818
|
+
)
|
|
18819
|
+
).action(async (platform2, options, command) => {
|
|
18820
|
+
const flags = getGlobalFlags(command);
|
|
18821
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18822
|
+
const input = buildUpsertInput(parseClient(platform2), options, null);
|
|
18823
|
+
const result = await client.upsertConversionTrackingIntegration(input);
|
|
18824
|
+
emit(flags, result, () => printIntegration(result, "Added"));
|
|
18825
|
+
});
|
|
18826
|
+
addPlatformOptions(
|
|
18827
|
+
integrations.command("update <platform> <integrationId>").description(
|
|
18828
|
+
"Edit an integration \u2014 only the flags you pass change; omitted secrets stay stored. Ids come from `integrations list`."
|
|
18829
|
+
)
|
|
18830
|
+
).action(
|
|
18831
|
+
async (platform2, integrationId, options, command) => {
|
|
18832
|
+
const flags = getGlobalFlags(command);
|
|
18833
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18834
|
+
const parsedClient = parseClient(platform2);
|
|
18835
|
+
const current = await client.listConversionTrackingIntegrations();
|
|
18836
|
+
const existing = current.integrations.find(
|
|
18837
|
+
(integration) => integration.id === integrationId && integration.client === parsedClient
|
|
18838
|
+
);
|
|
18839
|
+
if (!existing) {
|
|
18840
|
+
throw new Error(
|
|
18841
|
+
`No ${parsedClient} integration with id ${integrationId}. Run \`zanfia integrations list\`.`
|
|
18842
|
+
);
|
|
18843
|
+
}
|
|
18844
|
+
const input = buildUpsertInput(parsedClient, options, existing);
|
|
18845
|
+
const result = await client.upsertConversionTrackingIntegration(input);
|
|
18846
|
+
emit(flags, result, () => printIntegration(result, "Updated"));
|
|
18847
|
+
}
|
|
18848
|
+
);
|
|
18849
|
+
integrations.command("remove <platform> <integrationId>").description(
|
|
18850
|
+
"Remove an integration (irreversible). Offers and the workspace defaults that still list the id simply stop tracking on that platform. Requires --confirm."
|
|
18851
|
+
).option("--confirm", "required \u2014 acknowledges the integration is deleted").action(
|
|
18852
|
+
async (platform2, integrationId, options, command) => {
|
|
18853
|
+
requireConfirm9(options.confirm, `remove integration ${integrationId}`);
|
|
18854
|
+
const flags = getGlobalFlags(command);
|
|
18855
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18856
|
+
const result = await client.removeConversionTrackingIntegration(
|
|
18857
|
+
parseClient(platform2),
|
|
18858
|
+
integrationId
|
|
18859
|
+
);
|
|
18860
|
+
emit(flags, { ...result, integrationId }, () => {
|
|
18861
|
+
process.stdout.write(`Removed integration ${integrationId}
|
|
18862
|
+
`);
|
|
18863
|
+
});
|
|
18864
|
+
}
|
|
18865
|
+
);
|
|
18866
|
+
integrations.command("set-defaults <ids|none>").description(
|
|
18867
|
+
"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."
|
|
18868
|
+
).action(async (ids, _options, command) => {
|
|
18869
|
+
const flags = getGlobalFlags(command);
|
|
18870
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
18871
|
+
const integrationIds = ids.trim().toLowerCase() === NONE ? [] : ids.split(",").map((id) => id.trim()).filter((id) => id.length > 0);
|
|
18872
|
+
const result = await client.setDefaultConversionTracking(integrationIds);
|
|
18873
|
+
emit(flags, result, () => {
|
|
18874
|
+
process.stdout.write(
|
|
18875
|
+
`Workspace defaults: ${result.integrationIds.length > 0 ? result.integrationIds.join(", ") : "(none)"}
|
|
18876
|
+
`
|
|
18877
|
+
);
|
|
18878
|
+
});
|
|
18879
|
+
});
|
|
18880
|
+
}
|
|
18881
|
+
|
|
18882
|
+
// src/commands/join/index.ts
|
|
18883
|
+
var MAX_CODE_ATTEMPTS2 = 5;
|
|
18884
|
+
var HANDLE_REGEX = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
18885
|
+
var EMAIL_REGEX2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
18886
|
+
var AGENT_DOCS_URL = "https://zanfia.com/help/developer-cli/agents";
|
|
18887
|
+
function joinUrl(base, handle, step) {
|
|
18888
|
+
return `${base.replace(/\/$/, "")}/v1/join/${encodeURIComponent(handle)}/${step}`;
|
|
18889
|
+
}
|
|
18890
|
+
async function runJoin(handleInput, options) {
|
|
18891
|
+
const handle = handleInput.trim().toLowerCase();
|
|
18892
|
+
if (!HANDLE_REGEX.test(handle)) {
|
|
18893
|
+
throw new Error(
|
|
18894
|
+
"Community handle must be lowercase letters, digits and dashes (e.g. crew-of-one)."
|
|
18895
|
+
);
|
|
18896
|
+
}
|
|
18897
|
+
const communityApiUrl = resolveCommunityApiUrl();
|
|
18898
|
+
if (!communityApiUrl) {
|
|
18899
|
+
throw new Error(
|
|
18900
|
+
"No Community API URL configured. Set ZANFIA_COMMUNITY_API_URL (this build has no baked backend)."
|
|
18901
|
+
);
|
|
18902
|
+
}
|
|
18903
|
+
const verbose = options.verbose === true;
|
|
18904
|
+
const email = (options.email ?? await promptLine("Email: ")).trim().toLowerCase();
|
|
18905
|
+
if (!EMAIL_REGEX2.test(email)) {
|
|
18906
|
+
throw new Error("Please enter a valid email address.");
|
|
18907
|
+
}
|
|
18908
|
+
const started = await postJson2(
|
|
18909
|
+
"Join",
|
|
18910
|
+
joinUrl(communityApiUrl, handle, "start"),
|
|
18911
|
+
{ email, lang: "en" },
|
|
18912
|
+
{ verbose }
|
|
18913
|
+
);
|
|
18914
|
+
if (!options.json) {
|
|
18915
|
+
process.stdout.write(
|
|
18916
|
+
`Joining ${started.communityName} (${started.workspaceName}) \u2014 a 6-digit code was sent to ${email}.
|
|
18917
|
+
`
|
|
18918
|
+
);
|
|
18919
|
+
}
|
|
18920
|
+
const firstName = options.name ?? (options.json ? "" : await promptLine("Your name (optional): "));
|
|
18921
|
+
const hostname3 = safeHostname2();
|
|
18922
|
+
let completed = null;
|
|
18923
|
+
for (let attempt = 1; attempt <= MAX_CODE_ATTEMPTS2 && !completed; attempt += 1) {
|
|
18924
|
+
const code = options.code ?? (await promptLine("Code from the email (or 'r' to resend): ")).trim();
|
|
18925
|
+
if (code.toLowerCase() === "r") {
|
|
18926
|
+
await postJson2(
|
|
18927
|
+
"Resend",
|
|
18928
|
+
joinUrl(communityApiUrl, handle, "start"),
|
|
18929
|
+
{ email, lang: "en" },
|
|
18930
|
+
{ verbose }
|
|
18931
|
+
);
|
|
18932
|
+
process.stdout.write("Code re-sent.\n");
|
|
18933
|
+
attempt -= 1;
|
|
18934
|
+
continue;
|
|
18935
|
+
}
|
|
18936
|
+
try {
|
|
18937
|
+
completed = await postJson2(
|
|
18938
|
+
"Join",
|
|
18939
|
+
joinUrl(communityApiUrl, handle, "complete"),
|
|
18940
|
+
{ email, code, firstName, hostname: hostname3 },
|
|
18941
|
+
{ verbose }
|
|
18942
|
+
);
|
|
18943
|
+
} catch (err) {
|
|
18944
|
+
if (options.code || attempt === MAX_CODE_ATTEMPTS2) throw err;
|
|
18945
|
+
process.stdout.write(`${err instanceof Error ? err.message : String(err)}
|
|
18946
|
+
`);
|
|
18947
|
+
}
|
|
18948
|
+
}
|
|
18949
|
+
if (!completed) {
|
|
18950
|
+
throw new Error("Could not verify the code.");
|
|
18951
|
+
}
|
|
18952
|
+
const path2 = writeCredentials({
|
|
18953
|
+
apiKey: completed.token,
|
|
18954
|
+
apiUrl: communityApiUrl,
|
|
18955
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18956
|
+
source: "oauth",
|
|
18957
|
+
expiresAt: completed.expiresAt,
|
|
18958
|
+
userEmail: completed.userEmail,
|
|
18959
|
+
workspaceId: completed.workspaceId,
|
|
18960
|
+
workspaceName: completed.workspaceName,
|
|
18961
|
+
kind: "member"
|
|
18962
|
+
});
|
|
18963
|
+
if (options.json) {
|
|
18964
|
+
process.stdout.write(
|
|
18965
|
+
`${JSON.stringify(
|
|
18966
|
+
{
|
|
18967
|
+
joined: true,
|
|
18968
|
+
alreadyMember: completed.alreadyMember,
|
|
18969
|
+
communityId: completed.communityId,
|
|
18970
|
+
communityName: completed.communityName,
|
|
18971
|
+
workspaceId: completed.workspaceId,
|
|
18972
|
+
workspaceName: completed.workspaceName,
|
|
18973
|
+
introChannelId: completed.introChannelId,
|
|
18974
|
+
userEmail: completed.userEmail,
|
|
18975
|
+
expiresAt: completed.expiresAt,
|
|
18976
|
+
credentialsPath: path2
|
|
18977
|
+
},
|
|
18978
|
+
null,
|
|
18979
|
+
2
|
|
18980
|
+
)}
|
|
18981
|
+
`
|
|
18982
|
+
);
|
|
18983
|
+
return;
|
|
18984
|
+
}
|
|
18985
|
+
const intro = completed.introChannelId ? ` zanfia communities post create ${completed.communityId} ${completed.introChannelId} --content "Hi, I'm <your agent>. I ..."
|
|
18986
|
+
` : "";
|
|
18987
|
+
process.stdout.write(
|
|
18988
|
+
`
|
|
18989
|
+
${completed.alreadyMember ? "Welcome back to" : "You are in:"} ${completed.communityName}.
|
|
18990
|
+
Member key saved to ${path2} (expires ${completed.expiresAt.slice(0, 10)}).
|
|
18991
|
+
|
|
18992
|
+
Next:
|
|
18993
|
+
zanfia communities list
|
|
18994
|
+
zanfia communities channels ${completed.communityId}
|
|
18995
|
+
` + (intro ? `
|
|
18996
|
+
Introduce your agent:
|
|
18997
|
+
${intro}` : "") + `
|
|
18998
|
+
Plug in your agent: ${AGENT_DOCS_URL}
|
|
18999
|
+
`
|
|
19000
|
+
);
|
|
19001
|
+
}
|
|
19002
|
+
function registerJoinCommand(program2) {
|
|
19003
|
+
program2.command("join <handle>").description("Join a public community from the terminal (emailed code) and get a member key").option("--email <email>", "email to join with (prompted when omitted)").option("--name <name>", "your display name (prompted when omitted)").option("--code <code>", "the 6-digit code from the email (prompted when omitted)").addHelpText(
|
|
19004
|
+
"after",
|
|
19005
|
+
`
|
|
19006
|
+
Examples:
|
|
19007
|
+
zanfia join crew-of-one
|
|
19008
|
+
zanfia join crew-of-one --email me@example.com --name "Sam" --code 123456
|
|
19009
|
+
|
|
19010
|
+
The key is a MEMBER key: it reaches the Community API for that creator's
|
|
19011
|
+
communities only (read/post/comment/chat), never your own workspace. It is
|
|
19012
|
+
saved to ~/.config/zanfia/credentials.json and expires in 90 days \u2014 run
|
|
19013
|
+
join again to refresh it.
|
|
19014
|
+
`
|
|
19015
|
+
).action(async (handle, options, command) => {
|
|
19016
|
+
const globals = getGlobalFlags(command);
|
|
19017
|
+
await runJoin(handle, { ...options, json: globals.json, verbose: globals.verbose });
|
|
19018
|
+
});
|
|
19019
|
+
}
|
|
19020
|
+
|
|
19021
|
+
// src/commands/media/index.ts
|
|
19022
|
+
import { existsSync, readFileSync as readFileSync17, writeFileSync as writeFileSync4 } from "fs";
|
|
19023
|
+
import { readFile, stat as stat3 } from "fs/promises";
|
|
19024
|
+
import { basename } from "path";
|
|
19025
|
+
|
|
19026
|
+
// src/client/bunnyUpload.ts
|
|
19027
|
+
import { createReadStream as createReadStream2 } from "fs";
|
|
17800
19028
|
import { stat as stat2 } from "fs/promises";
|
|
17801
19029
|
|
|
17802
19030
|
// ../../node_modules/tus-js-client/lib.esm/error.js
|
|
@@ -20909,16 +22137,61 @@ async function uploadVideoToBunny(params) {
|
|
|
20909
22137
|
});
|
|
20910
22138
|
}
|
|
20911
22139
|
|
|
22140
|
+
// src/utils/mime.ts
|
|
22141
|
+
var OCTET_STREAM_MIME = "application/octet-stream";
|
|
22142
|
+
function mimeFromName(name) {
|
|
22143
|
+
const n = name.toLowerCase();
|
|
22144
|
+
if (n.endsWith(".png")) return "image/png";
|
|
22145
|
+
if (n.endsWith(".jpg") || n.endsWith(".jpeg")) return "image/jpeg";
|
|
22146
|
+
if (n.endsWith(".webp")) return "image/webp";
|
|
22147
|
+
if (n.endsWith(".gif")) return "image/gif";
|
|
22148
|
+
if (n.endsWith(".svg")) return "image/svg+xml";
|
|
22149
|
+
if (n.endsWith(".avif")) return "image/avif";
|
|
22150
|
+
if (n.endsWith(".pdf")) return "application/pdf";
|
|
22151
|
+
if (n.endsWith(".zip")) return "application/zip";
|
|
22152
|
+
if (n.endsWith(".mp3")) return "audio/mpeg";
|
|
22153
|
+
if (n.endsWith(".mp4")) return "video/mp4";
|
|
22154
|
+
if (n.endsWith(".txt") || n.endsWith(".md")) return "text/plain";
|
|
22155
|
+
if (n.endsWith(".csv")) return "text/csv";
|
|
22156
|
+
if (n.endsWith(".json")) return "application/json";
|
|
22157
|
+
if (n.endsWith(".epub")) return "application/epub+zip";
|
|
22158
|
+
if (n.endsWith(".docx")) {
|
|
22159
|
+
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
22160
|
+
}
|
|
22161
|
+
if (n.endsWith(".xlsx")) {
|
|
22162
|
+
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
22163
|
+
}
|
|
22164
|
+
return null;
|
|
22165
|
+
}
|
|
22166
|
+
function mimeFromBytes(bytes) {
|
|
22167
|
+
const ascii = (start, text) => bytes.length >= start + text.length && bytes.toString("latin1", start, start + text.length) === text;
|
|
22168
|
+
if (bytes.length >= 8 && bytes[0] === 137 && ascii(1, "PNG\r\n\n")) return "image/png";
|
|
22169
|
+
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) {
|
|
22170
|
+
return "image/jpeg";
|
|
22171
|
+
}
|
|
22172
|
+
if (ascii(0, "GIF87a") || ascii(0, "GIF89a")) return "image/gif";
|
|
22173
|
+
if (ascii(0, "RIFF") && ascii(8, "WEBP")) return "image/webp";
|
|
22174
|
+
if (ascii(4, "ftypavif")) return "image/avif";
|
|
22175
|
+
if (ascii(0, "%PDF-")) return "application/pdf";
|
|
22176
|
+
if (bytes.toString("utf8", 0, Math.min(bytes.length, 1024)).toLowerCase().includes("<svg")) {
|
|
22177
|
+
return "image/svg+xml";
|
|
22178
|
+
}
|
|
22179
|
+
return null;
|
|
22180
|
+
}
|
|
22181
|
+
function inferMimeType(name, bytes) {
|
|
22182
|
+
return mimeFromName(name) ?? mimeFromBytes(bytes) ?? OCTET_STREAM_MIME;
|
|
22183
|
+
}
|
|
22184
|
+
|
|
20912
22185
|
// src/commands/media/index.ts
|
|
20913
22186
|
var DEFAULT_AI_IMAGE_ASPECT = "1:1" /* Square */;
|
|
20914
22187
|
var DEFAULT_AI_IMAGE_QUALITY = "1K" /* Standard */;
|
|
20915
|
-
function
|
|
22188
|
+
function requireConfirm10(confirm, what) {
|
|
20916
22189
|
if (!confirm) {
|
|
20917
22190
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
20918
22191
|
}
|
|
20919
22192
|
}
|
|
20920
22193
|
function resolveTextOrFile(value) {
|
|
20921
|
-
const text = existsSync(value) ?
|
|
22194
|
+
const text = existsSync(value) ? readFileSync17(value, "utf8") : value;
|
|
20922
22195
|
if (!text.trim()) throw new Error("The text (or the file it points to) is empty.");
|
|
20923
22196
|
return text.trim();
|
|
20924
22197
|
}
|
|
@@ -20974,32 +22247,6 @@ var TRANSCRIBE_STATUS_TEXT = {
|
|
|
20974
22247
|
["already_running" /* AlreadyRunning */]: "Transcription is already running",
|
|
20975
22248
|
["already_transcribed" /* AlreadyTranscribed */]: "Already transcribed \u2014 nothing queued (pass --force to re-transcribe)"
|
|
20976
22249
|
};
|
|
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
22250
|
function videoMimeFromName(name) {
|
|
21004
22251
|
const n = name.toLowerCase();
|
|
21005
22252
|
if (n.endsWith(".mov")) return "video/quicktime";
|
|
@@ -21022,7 +22269,7 @@ function registerMediaCommands(program2) {
|
|
|
21022
22269
|
const name = options.name ?? basename(options.file);
|
|
21023
22270
|
const input = {
|
|
21024
22271
|
name,
|
|
21025
|
-
mimeType: options.mime ??
|
|
22272
|
+
mimeType: options.mime ?? inferMimeType(name, bytes),
|
|
21026
22273
|
data: bytes.toString("base64"),
|
|
21027
22274
|
folder: options.folder,
|
|
21028
22275
|
description: options.description
|
|
@@ -21223,7 +22470,7 @@ function registerMediaCommands(program2) {
|
|
|
21223
22470
|
).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
22471
|
async (options, command) => {
|
|
21225
22472
|
const flags = getGlobalFlags(command);
|
|
21226
|
-
|
|
22473
|
+
requireConfirm10(options.confirm, "generate an AI image (spends AI credits)");
|
|
21227
22474
|
const input = {
|
|
21228
22475
|
prompt: resolveTextOrFile(options.prompt),
|
|
21229
22476
|
aspectRatio: parseEnumOption(
|
|
@@ -21255,7 +22502,7 @@ function registerMediaCommands(program2) {
|
|
|
21255
22502
|
).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
22503
|
async (mediaId, options, command) => {
|
|
21257
22504
|
const flags = getGlobalFlags(command);
|
|
21258
|
-
|
|
22505
|
+
requireConfirm10(options.confirm, `transcribe ${mediaId} (spends transcription minutes)`);
|
|
21259
22506
|
const input = {};
|
|
21260
22507
|
if (options.language !== void 0) input.language = options.language;
|
|
21261
22508
|
if (options.force) input.force = true;
|
|
@@ -21329,13 +22576,13 @@ function registerMediaCommands(program2) {
|
|
|
21329
22576
|
}
|
|
21330
22577
|
|
|
21331
22578
|
// src/commands/newsletters/index.ts
|
|
21332
|
-
import { existsSync as existsSync2, readFileSync as
|
|
22579
|
+
import { existsSync as existsSync2, readFileSync as readFileSync18 } from "fs";
|
|
21333
22580
|
var ISSUE_STATUSES = ["public", "hidden"];
|
|
21334
22581
|
var ISSUE_VISIBILITIES = ["public", "hidden", "locked"];
|
|
21335
22582
|
var ISSUE_RENDER_MODES = ["snapshot", "article"];
|
|
21336
22583
|
var THUMBNAIL_NONE = "none";
|
|
21337
22584
|
function resolveMarkdownOrFile(value) {
|
|
21338
|
-
return existsSync2(value) ?
|
|
22585
|
+
return existsSync2(value) ? readFileSync18(value, "utf8") : value;
|
|
21339
22586
|
}
|
|
21340
22587
|
function parseIdList2(value, flag) {
|
|
21341
22588
|
const ids = value.split(",").map((id) => id.trim()).filter(Boolean);
|
|
@@ -21376,7 +22623,7 @@ function parseBool2(value, flag) {
|
|
|
21376
22623
|
}
|
|
21377
22624
|
function parseJsonFileOrInline5(value, flag) {
|
|
21378
22625
|
const trimmed = value.trim();
|
|
21379
|
-
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed :
|
|
22626
|
+
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed : readFileSync18(value, "utf8");
|
|
21380
22627
|
try {
|
|
21381
22628
|
return JSON.parse(raw);
|
|
21382
22629
|
} catch (error) {
|
|
@@ -21691,7 +22938,7 @@ Next: \`newsletters issues draft save ${productId} ${issue.id} --content <markdo
|
|
|
21691
22938
|
}
|
|
21692
22939
|
|
|
21693
22940
|
// src/commands/offers/index.ts
|
|
21694
|
-
import { readFileSync as
|
|
22941
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
21695
22942
|
var PRICE_BILLING_TYPES = ["one_time", "recurring", "installments"];
|
|
21696
22943
|
var PRICE_INTERVALS = ["day", "week", "month", "year"];
|
|
21697
22944
|
var SALE_START_DISPLAYS = ["hidden", "countdown"];
|
|
@@ -21701,8 +22948,11 @@ var PRICE_OPERATIONS = [
|
|
|
21701
22948
|
"deactivate",
|
|
21702
22949
|
"hide",
|
|
21703
22950
|
"unhide",
|
|
21704
|
-
"change-amount"
|
|
22951
|
+
"change-amount",
|
|
22952
|
+
"edit-details"
|
|
21705
22953
|
];
|
|
22954
|
+
var EDIT_DETAILS_OPERATION = "edit-details";
|
|
22955
|
+
var CHANGE_AMOUNT_OPERATION = "change-amount";
|
|
21706
22956
|
function parseChoice4(value, allowed, flag) {
|
|
21707
22957
|
const match = allowed.find((candidate) => candidate === value);
|
|
21708
22958
|
if (match === void 0) {
|
|
@@ -21710,27 +22960,154 @@ function parseChoice4(value, allowed, flag) {
|
|
|
21710
22960
|
}
|
|
21711
22961
|
return match;
|
|
21712
22962
|
}
|
|
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)`);
|
|
22963
|
+
function parsePositiveInt12(value, flag) {
|
|
22964
|
+
const n = Number.parseInt(value, 10);
|
|
22965
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
22966
|
+
throw new Error(`Invalid ${flag}: ${value} (expected a positive integer)`);
|
|
22967
|
+
}
|
|
22968
|
+
return n;
|
|
22969
|
+
}
|
|
22970
|
+
function parseBool3(value, flag) {
|
|
22971
|
+
const v = value.trim().toLowerCase();
|
|
22972
|
+
if (["on", "true", "yes", "1"].includes(v)) return true;
|
|
22973
|
+
if (["off", "false", "no", "0"].includes(v)) return false;
|
|
22974
|
+
throw new Error(`Invalid ${flag}: ${value} (expected on/off, true/false, yes/no).`);
|
|
22975
|
+
}
|
|
22976
|
+
function parseJsonFileOrInline6(value, flag) {
|
|
22977
|
+
const trimmed = value.trim();
|
|
22978
|
+
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed : readFileSync19(value, "utf8");
|
|
22979
|
+
try {
|
|
22980
|
+
return JSON.parse(raw);
|
|
22981
|
+
} catch (error) {
|
|
22982
|
+
throw new Error(`Invalid JSON for ${flag}: ${error.message}`);
|
|
22983
|
+
}
|
|
22984
|
+
}
|
|
22985
|
+
function buildPriceUpdateInput(options) {
|
|
22986
|
+
const hasDetails = options.label !== void 0 || options.description !== void 0;
|
|
22987
|
+
const operation = options.operation !== void 0 ? parseChoice4(options.operation, PRICE_OPERATIONS, "--operation") : hasDetails ? EDIT_DETAILS_OPERATION : void 0;
|
|
22988
|
+
if (operation === void 0) {
|
|
22989
|
+
throw new Error(
|
|
22990
|
+
`--operation <${PRICE_OPERATIONS.join("|")}> is required (or pass --label / --description to edit the details).`
|
|
22991
|
+
);
|
|
22992
|
+
}
|
|
22993
|
+
if (hasDetails && operation !== EDIT_DETAILS_OPERATION) {
|
|
22994
|
+
throw new Error(
|
|
22995
|
+
`--label / --description only apply to --operation ${EDIT_DETAILS_OPERATION}, not ${operation}.`
|
|
22996
|
+
);
|
|
22997
|
+
}
|
|
22998
|
+
if (operation === EDIT_DETAILS_OPERATION) {
|
|
22999
|
+
if (!hasDetails) {
|
|
23000
|
+
throw new Error(
|
|
23001
|
+
`--operation ${EDIT_DETAILS_OPERATION} requires --label and/or --description.`
|
|
23002
|
+
);
|
|
23003
|
+
}
|
|
23004
|
+
if (options.label !== void 0 && options.label.trim().length === 0) {
|
|
23005
|
+
throw new Error("--label must not be empty (labels cannot be cleared, only replaced).");
|
|
23006
|
+
}
|
|
23007
|
+
return {
|
|
23008
|
+
operation,
|
|
23009
|
+
newLabel: options.label,
|
|
23010
|
+
// An empty string clears the description (the API accepts '' or null; null is explicit).
|
|
23011
|
+
newDescription: options.description === void 0 ? void 0 : options.description.length === 0 ? null : options.description
|
|
23012
|
+
};
|
|
23013
|
+
}
|
|
23014
|
+
if (operation === CHANGE_AMOUNT_OPERATION && !options.amount) {
|
|
23015
|
+
throw new Error(`--operation ${CHANGE_AMOUNT_OPERATION} requires --amount <minor-units>.`);
|
|
23016
|
+
}
|
|
23017
|
+
return {
|
|
23018
|
+
operation,
|
|
23019
|
+
newAmountCents: operation === CHANGE_AMOUNT_OPERATION && options.amount ? parsePositiveInt12(options.amount, "--amount") : void 0
|
|
23020
|
+
};
|
|
23021
|
+
}
|
|
23022
|
+
var MEDIA_TYPES = ["image", "video"];
|
|
23023
|
+
var IMAGE_MEDIA_TYPE2 = "image";
|
|
23024
|
+
var VIDEO_MEDIA_TYPE = "video";
|
|
23025
|
+
var READY_UPLOAD_STATUS = "ready";
|
|
23026
|
+
var GALLERY_KEYS = /* @__PURE__ */ new Set([
|
|
23027
|
+
"checkoutMedia",
|
|
23028
|
+
"checkoutImages",
|
|
23029
|
+
"checkoutImagesMedia"
|
|
23030
|
+
]);
|
|
23031
|
+
var BASE_DETAIL_KEYS = /* @__PURE__ */ new Set([
|
|
23032
|
+
"id",
|
|
23033
|
+
"productId",
|
|
23034
|
+
"name",
|
|
23035
|
+
"slug",
|
|
23036
|
+
"title",
|
|
23037
|
+
"active",
|
|
23038
|
+
"archived",
|
|
23039
|
+
"isFree",
|
|
23040
|
+
"priceCount",
|
|
23041
|
+
"createdAt",
|
|
23042
|
+
"description",
|
|
23043
|
+
"language",
|
|
23044
|
+
"successUrl",
|
|
23045
|
+
"prices",
|
|
23046
|
+
"orderBumps"
|
|
23047
|
+
]);
|
|
23048
|
+
function galleryOf(checkout) {
|
|
23049
|
+
if (checkout.checkoutMedia) return [...checkout.checkoutMedia];
|
|
23050
|
+
return (checkout.checkoutImages ?? []).map((url, index) => ({
|
|
23051
|
+
type: IMAGE_MEDIA_TYPE2,
|
|
23052
|
+
url,
|
|
23053
|
+
mediaId: checkout.checkoutImagesMedia?.[index]?.mediaId ?? null,
|
|
23054
|
+
thumbnailUrl: null
|
|
23055
|
+
}));
|
|
23056
|
+
}
|
|
23057
|
+
function galleryPatch(media) {
|
|
23058
|
+
const images = media.filter((item) => item.type === IMAGE_MEDIA_TYPE2);
|
|
23059
|
+
return {
|
|
23060
|
+
checkoutMedia: media,
|
|
23061
|
+
checkoutImages: images.map((item) => item.url),
|
|
23062
|
+
checkoutImagesMedia: images.map((item) => ({ url: item.url, mediaId: item.mediaId }))
|
|
23063
|
+
};
|
|
23064
|
+
}
|
|
23065
|
+
function bunnyEmbedUrl(video) {
|
|
23066
|
+
return `https://iframe.mediadelivery.net/embed/${video.bunnyLibraryId}/${video.bunnyVideoId}`;
|
|
23067
|
+
}
|
|
23068
|
+
function galleryEntryFromLibrary(item) {
|
|
23069
|
+
const type = MEDIA_TYPES.find((candidate) => candidate === item.type);
|
|
23070
|
+
if (type === void 0) {
|
|
23071
|
+
throw new Error(
|
|
23072
|
+
`Media ${item.id} is a ${item.type} \u2014 only images and videos can go in the offer gallery.`
|
|
23073
|
+
);
|
|
21717
23074
|
}
|
|
21718
|
-
|
|
23075
|
+
if (type === VIDEO_MEDIA_TYPE) {
|
|
23076
|
+
if (item.uploadStatus !== READY_UPLOAD_STATUS || !item.video) {
|
|
23077
|
+
throw new Error(
|
|
23078
|
+
`Media ${item.id} is a video that is not ready yet (uploadStatus: ${item.uploadStatus}) \u2014 add it once it finishes processing.`
|
|
23079
|
+
);
|
|
23080
|
+
}
|
|
23081
|
+
return {
|
|
23082
|
+
type,
|
|
23083
|
+
url: bunnyEmbedUrl(item.video),
|
|
23084
|
+
mediaId: item.id,
|
|
23085
|
+
thumbnailUrl: item.thumbnailUrl ?? null
|
|
23086
|
+
};
|
|
23087
|
+
}
|
|
23088
|
+
if (!item.url) {
|
|
23089
|
+
throw new Error(`Media ${item.id} has no public URL yet (uploadStatus: ${item.uploadStatus}).`);
|
|
23090
|
+
}
|
|
23091
|
+
return { type, url: item.url, mediaId: item.id, thumbnailUrl: item.thumbnailUrl ?? null };
|
|
21719
23092
|
}
|
|
21720
|
-
function
|
|
21721
|
-
|
|
21722
|
-
|
|
21723
|
-
|
|
21724
|
-
|
|
23093
|
+
function printGallery(media) {
|
|
23094
|
+
printTable(
|
|
23095
|
+
media.map((item, index) => ({
|
|
23096
|
+
index,
|
|
23097
|
+
type: item.type,
|
|
23098
|
+
mediaId: item.mediaId ?? "",
|
|
23099
|
+
url: item.url,
|
|
23100
|
+
thumbnailUrl: item.thumbnailUrl ?? ""
|
|
23101
|
+
})),
|
|
23102
|
+
["index", "type", "mediaId", "url", "thumbnailUrl"]
|
|
23103
|
+
);
|
|
21725
23104
|
}
|
|
21726
|
-
function
|
|
21727
|
-
const
|
|
21728
|
-
|
|
21729
|
-
|
|
21730
|
-
|
|
21731
|
-
|
|
21732
|
-
throw new Error(`Invalid JSON for ${flag}: ${error.message}`);
|
|
21733
|
-
}
|
|
23105
|
+
function fullSettingsOf(checkout) {
|
|
23106
|
+
const isRendered = (key) => {
|
|
23107
|
+
const known = key;
|
|
23108
|
+
return BASE_DETAIL_KEYS.has(known) || GALLERY_KEYS.has(known);
|
|
23109
|
+
};
|
|
23110
|
+
return Object.fromEntries(Object.entries(checkout).filter(([key]) => !isRendered(key)));
|
|
21734
23111
|
}
|
|
21735
23112
|
function formatAmount2(amountCents, currency) {
|
|
21736
23113
|
return `${(amountCents / 100).toFixed(2)} ${currency}`;
|
|
@@ -21984,10 +23361,12 @@ function registerOffersCommands(program2) {
|
|
|
21984
23361
|
);
|
|
21985
23362
|
});
|
|
21986
23363
|
});
|
|
21987
|
-
offers.command("get").description(
|
|
23364
|
+
offers.command("get").description(
|
|
23365
|
+
"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"
|
|
23366
|
+
).argument("<checkoutId>", "offer (checkout) id").option("--full", "include the whole editable settings surface (?include=full)").action(async (checkoutId, options, command) => {
|
|
21988
23367
|
const flags = getGlobalFlags(command);
|
|
21989
23368
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
21990
|
-
const checkout = await client.getCheckout(checkoutId);
|
|
23369
|
+
const checkout = await client.getCheckout(checkoutId, { full: options.full === true });
|
|
21991
23370
|
emit(flags, checkout, () => {
|
|
21992
23371
|
process.stdout.write(`Offer ${checkout.id} \u2014 ${checkout.name}
|
|
21993
23372
|
`);
|
|
@@ -22047,6 +23426,14 @@ Order bumps (${checkout.orderBumps.length}):
|
|
|
22047
23426
|
process.stdout.write(` ${bump.orderBumpId} (${scope})
|
|
22048
23427
|
`);
|
|
22049
23428
|
}
|
|
23429
|
+
if (!options.full) return;
|
|
23430
|
+
const gallery = galleryOf(checkout);
|
|
23431
|
+
process.stdout.write(`
|
|
23432
|
+
Media gallery (${gallery.length}):
|
|
23433
|
+
`);
|
|
23434
|
+
printGallery(gallery);
|
|
23435
|
+
process.stdout.write("\nSettings (--full):\n");
|
|
23436
|
+
printJson(fullSettingsOf(checkout));
|
|
22050
23437
|
});
|
|
22051
23438
|
});
|
|
22052
23439
|
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 +23642,23 @@ Order bumps (${checkout.orderBumps.length}):
|
|
|
22255
23642
|
);
|
|
22256
23643
|
});
|
|
22257
23644
|
});
|
|
22258
|
-
price.command("update").description(
|
|
23645
|
+
price.command("update").description(
|
|
23646
|
+
"Mutate a price variant: --operation activate | deactivate | hide | unhide | change-amount (with --amount) | edit-details (with --label / --description). --label / --description alone imply edit-details"
|
|
23647
|
+
).argument("<checkoutId>", "offer (checkout) id").argument("<priceId>", "price id").option(
|
|
23648
|
+
"--operation <op>",
|
|
23649
|
+
"activate | deactivate | hide | unhide | change-amount | edit-details (implied by --label / --description)"
|
|
23650
|
+
).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(
|
|
23651
|
+
"--description <markdown>",
|
|
23652
|
+
"edit-details: new markdown description under the price (empty string clears it)"
|
|
23653
|
+
).action(
|
|
22259
23654
|
async (checkoutId, priceId, options, command) => {
|
|
22260
23655
|
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
|
-
}
|
|
23656
|
+
const input = buildPriceUpdateInput(options);
|
|
22265
23657
|
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
|
-
});
|
|
23658
|
+
const result = await client.updateCheckoutPrice(checkoutId, priceId, input);
|
|
22270
23659
|
emit(flags, result, () => {
|
|
22271
23660
|
process.stdout.write(
|
|
22272
|
-
`Applied ${operation} to price ${result.priceId} on offer ${result.checkoutId}
|
|
23661
|
+
`Applied ${input.operation} to price ${result.priceId} on offer ${result.checkoutId}
|
|
22273
23662
|
`
|
|
22274
23663
|
);
|
|
22275
23664
|
});
|
|
@@ -22305,6 +23694,87 @@ Order bumps (${checkout.orderBumps.length}):
|
|
|
22305
23694
|
`);
|
|
22306
23695
|
});
|
|
22307
23696
|
});
|
|
23697
|
+
const media = offers.command("media").description("Manage an offer's media gallery (checkout images and videos)");
|
|
23698
|
+
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) => {
|
|
23699
|
+
const flags = getGlobalFlags(command);
|
|
23700
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23701
|
+
const checkout = await client.getCheckout(checkoutId, { full: true });
|
|
23702
|
+
const gallery = galleryOf(checkout);
|
|
23703
|
+
emit(flags, { checkoutId: checkout.id, count: gallery.length, media: gallery }, () => {
|
|
23704
|
+
process.stdout.write(`Media gallery of offer ${checkout.id} (${gallery.length}):
|
|
23705
|
+
`);
|
|
23706
|
+
printGallery(gallery);
|
|
23707
|
+
});
|
|
23708
|
+
});
|
|
23709
|
+
media.command("add").description(
|
|
23710
|
+
"Append a Media Library item (--media <mediaId>; videos must be ready) or an external URL (--url with --type) to the gallery"
|
|
23711
|
+
).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(
|
|
23712
|
+
async (checkoutId, options, command) => {
|
|
23713
|
+
if (options.media === void 0 === (options.url === void 0)) {
|
|
23714
|
+
throw new Error("Pass exactly one of --media <mediaId> or --url <url>.");
|
|
23715
|
+
}
|
|
23716
|
+
if (options.media !== void 0 && (options.type !== void 0 || options.thumbnail)) {
|
|
23717
|
+
throw new Error(
|
|
23718
|
+
"--type / --thumbnail only apply with --url (a library item carries its own)."
|
|
23719
|
+
);
|
|
23720
|
+
}
|
|
23721
|
+
const flags = getGlobalFlags(command);
|
|
23722
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23723
|
+
let entry;
|
|
23724
|
+
if (options.media !== void 0) {
|
|
23725
|
+
const { media: item } = await client.getMedia(options.media);
|
|
23726
|
+
entry = galleryEntryFromLibrary(item);
|
|
23727
|
+
} else {
|
|
23728
|
+
if (options.type === void 0) {
|
|
23729
|
+
throw new Error("--url requires --type <image|video>.");
|
|
23730
|
+
}
|
|
23731
|
+
entry = {
|
|
23732
|
+
type: parseChoice4(options.type, MEDIA_TYPES, "--type"),
|
|
23733
|
+
url: options.url ?? "",
|
|
23734
|
+
mediaId: null,
|
|
23735
|
+
thumbnailUrl: options.thumbnail ?? null
|
|
23736
|
+
};
|
|
23737
|
+
}
|
|
23738
|
+
const checkout = await client.getCheckout(checkoutId, { full: true });
|
|
23739
|
+
const gallery = galleryOf(checkout);
|
|
23740
|
+
const duplicate = gallery.find(
|
|
23741
|
+
(item) => entry.mediaId !== null && item.mediaId === entry.mediaId || item.url === entry.url
|
|
23742
|
+
);
|
|
23743
|
+
if (duplicate) {
|
|
23744
|
+
throw new Error(
|
|
23745
|
+
`Already in the gallery: ${duplicate.mediaId ?? duplicate.url} (index ${gallery.indexOf(duplicate)}).`
|
|
23746
|
+
);
|
|
23747
|
+
}
|
|
23748
|
+
const next = [...gallery, entry];
|
|
23749
|
+
const result = await client.updateCheckout(checkoutId, galleryPatch(next));
|
|
23750
|
+
emit(flags, { checkoutId: result.checkoutId, added: entry, media: next }, () => {
|
|
23751
|
+
process.stdout.write(
|
|
23752
|
+
`Added ${entry.type} ${entry.mediaId ?? entry.url} to offer ${result.checkoutId} (gallery: ${next.length})
|
|
23753
|
+
`
|
|
23754
|
+
);
|
|
23755
|
+
});
|
|
23756
|
+
}
|
|
23757
|
+
);
|
|
23758
|
+
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) => {
|
|
23759
|
+
const flags = getGlobalFlags(command);
|
|
23760
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23761
|
+
const checkout = await client.getCheckout(checkoutId, { full: true });
|
|
23762
|
+
const gallery = galleryOf(checkout);
|
|
23763
|
+
const removed = gallery.filter((item) => item.mediaId === ref || item.url === ref);
|
|
23764
|
+
if (removed.length === 0) {
|
|
23765
|
+
throw new Error(
|
|
23766
|
+
`No gallery entry matches ${ref} on offer ${checkoutId} \u2014 run \`offers media list ${checkoutId}\`.`
|
|
23767
|
+
);
|
|
23768
|
+
}
|
|
23769
|
+
const next = gallery.filter((item) => !removed.includes(item));
|
|
23770
|
+
const result = await client.updateCheckout(checkoutId, galleryPatch(next));
|
|
23771
|
+
emit(flags, { checkoutId: result.checkoutId, removed, media: next }, () => {
|
|
23772
|
+
process.stdout.write(
|
|
23773
|
+
`Removed ${removed.length} ${removed.length === 1 ? "entry" : "entries"} (${ref}) from offer ${result.checkoutId} (gallery: ${next.length})
|
|
23774
|
+
`
|
|
23775
|
+
);
|
|
23776
|
+
});
|
|
23777
|
+
});
|
|
22308
23778
|
}
|
|
22309
23779
|
|
|
22310
23780
|
// src/commands/orders/index.ts
|
|
@@ -22326,7 +23796,7 @@ function parsePositiveInt13(value, flag) {
|
|
|
22326
23796
|
function formatAmount3(amountCents, currency) {
|
|
22327
23797
|
return `${(amountCents / 100).toFixed(2)} ${currency}`;
|
|
22328
23798
|
}
|
|
22329
|
-
function
|
|
23799
|
+
function requireConfirm11(confirm, what) {
|
|
22330
23800
|
if (!confirm) {
|
|
22331
23801
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
22332
23802
|
}
|
|
@@ -22425,7 +23895,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
22425
23895
|
).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
23896
|
async (orderId, options, command) => {
|
|
22427
23897
|
const flags = getGlobalFlags(command);
|
|
22428
|
-
|
|
23898
|
+
requireConfirm11(options.confirm, `approve order ${orderId} (access + purchase email)`);
|
|
22429
23899
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
22430
23900
|
const result = await client.approveManualOrder(orderId, {
|
|
22431
23901
|
issueInvoice: options.issueInvoice === true ? true : void 0
|
|
@@ -22442,7 +23912,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
22442
23912
|
"Reject a manual (wire-transfer) order \u2014 cancels it and notifies the buyer. 409 = the order was already decided. Requires --confirm"
|
|
22443
23913
|
).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
23914
|
const flags = getGlobalFlags(command);
|
|
22445
|
-
|
|
23915
|
+
requireConfirm11(options.confirm, `reject order ${orderId} (cancel + notify the buyer)`);
|
|
22446
23916
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
22447
23917
|
const result = await client.rejectManualOrder(orderId);
|
|
22448
23918
|
emit(flags, result, () => {
|
|
@@ -22492,12 +23962,12 @@ Next page: --cursor ${page.nextCursor}
|
|
|
22492
23962
|
}
|
|
22493
23963
|
|
|
22494
23964
|
// src/commands/oto/index.ts
|
|
22495
|
-
import { readFileSync as
|
|
23965
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
22496
23966
|
var MIN_TIMER_MINUTES = 1;
|
|
22497
23967
|
var MAX_TIMER_MINUTES = 120;
|
|
22498
23968
|
function parseJsonFileOrInline7(value, flag) {
|
|
22499
23969
|
const trimmed = value.trim();
|
|
22500
|
-
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed :
|
|
23970
|
+
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed : readFileSync20(value, "utf8");
|
|
22501
23971
|
try {
|
|
22502
23972
|
return JSON.parse(raw);
|
|
22503
23973
|
} catch (error) {
|
|
@@ -22685,7 +24155,7 @@ function registerOtoCommands(program2) {
|
|
|
22685
24155
|
}
|
|
22686
24156
|
|
|
22687
24157
|
// src/commands/pages/index.ts
|
|
22688
|
-
import { existsSync as existsSync3, readFileSync as
|
|
24158
|
+
import { existsSync as existsSync3, readFileSync as readFileSync21 } from "fs";
|
|
22689
24159
|
import { readFile as readFile2 } from "fs/promises";
|
|
22690
24160
|
async function resolveHtml2(options) {
|
|
22691
24161
|
if (options.file) {
|
|
@@ -22732,13 +24202,13 @@ function parseCsv4(value) {
|
|
|
22732
24202
|
];
|
|
22733
24203
|
}
|
|
22734
24204
|
function resolveTextOrFile2(value) {
|
|
22735
|
-
const text = existsSync3(value) ?
|
|
24205
|
+
const text = existsSync3(value) ? readFileSync21(value, "utf8") : value;
|
|
22736
24206
|
if (!text.trim()) throw new Error("The text (or the file it points to) is empty.");
|
|
22737
24207
|
return text;
|
|
22738
24208
|
}
|
|
22739
24209
|
function parseJsonObjectFileOrInline3(value, flag) {
|
|
22740
24210
|
const trimmed = value.trim();
|
|
22741
|
-
const raw = trimmed.startsWith("{") ? trimmed :
|
|
24211
|
+
const raw = trimmed.startsWith("{") ? trimmed : readFileSync21(value, "utf8");
|
|
22742
24212
|
let parsed;
|
|
22743
24213
|
try {
|
|
22744
24214
|
parsed = JSON.parse(raw);
|
|
@@ -22750,7 +24220,7 @@ function parseJsonObjectFileOrInline3(value, flag) {
|
|
|
22750
24220
|
}
|
|
22751
24221
|
return parsed;
|
|
22752
24222
|
}
|
|
22753
|
-
function
|
|
24223
|
+
function requireConfirm12(confirm, what) {
|
|
22754
24224
|
if (!confirm) {
|
|
22755
24225
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
22756
24226
|
}
|
|
@@ -22945,7 +24415,7 @@ function registerPagesCommands(program2) {
|
|
|
22945
24415
|
"inline JSON object or .json path merged into the request body (explicit flags win)"
|
|
22946
24416
|
).option("--confirm", "required \u2014 acknowledge that AI credits will be consumed").action(async (options, command) => {
|
|
22947
24417
|
const flags = getGlobalFlags(command);
|
|
22948
|
-
|
|
24418
|
+
requireConfirm12(options.confirm, "generate a page (consumes AI credits)");
|
|
22949
24419
|
const input = buildGenerateInput(options);
|
|
22950
24420
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
22951
24421
|
const result = await client.generatePage(input).catch(withSchedulerHint3);
|
|
@@ -22967,7 +24437,7 @@ function registerPagesCommands(program2) {
|
|
|
22967
24437
|
).action(
|
|
22968
24438
|
async (pageId, options, command) => {
|
|
22969
24439
|
const flags = getGlobalFlags(command);
|
|
22970
|
-
|
|
24440
|
+
requireConfirm12(
|
|
22971
24441
|
options.confirm,
|
|
22972
24442
|
"AI-edit the page (consumes AI credits, replaces the HTML)"
|
|
22973
24443
|
);
|
|
@@ -23026,7 +24496,7 @@ function registerPagesCommands(program2) {
|
|
|
23026
24496
|
).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
24497
|
async (pageId, revisionId, options, command) => {
|
|
23028
24498
|
const flags = getGlobalFlags(command);
|
|
23029
|
-
|
|
24499
|
+
requireConfirm12(options.confirm, "restore the revision (replaces the current HTML)");
|
|
23030
24500
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23031
24501
|
const result = await client.restorePageRevision(pageId, revisionId);
|
|
23032
24502
|
emit(flags, result, () => {
|
|
@@ -23039,7 +24509,7 @@ function registerPagesCommands(program2) {
|
|
|
23039
24509
|
"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
24510
|
).argument("<pageId>", "page id").option("--confirm", "required \u2014 acknowledge that automations go live and emails will send").action(async (pageId, options, command) => {
|
|
23041
24511
|
const flags = getGlobalFlags(command);
|
|
23042
|
-
|
|
24512
|
+
requireConfirm12(options.confirm, "activate the page automations (emails will send)");
|
|
23043
24513
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23044
24514
|
const result = await client.activatePageAutomations(pageId);
|
|
23045
24515
|
emit(flags, result, () => {
|
|
@@ -23069,7 +24539,9 @@ function registerPagesCommands(program2) {
|
|
|
23069
24539
|
}
|
|
23070
24540
|
|
|
23071
24541
|
// src/commands/products/index.ts
|
|
23072
|
-
import { readFileSync as
|
|
24542
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
24543
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
24544
|
+
import { basename as basename2 } from "path";
|
|
23073
24545
|
var KB_TREE_CHANGED_ERROR = "tree-changed";
|
|
23074
24546
|
var JSON_FLAG_HINT3 = "(write it as --json=<value> \u2014 a bare --json is the global JSON-output flag)";
|
|
23075
24547
|
function parseCoverRef(value) {
|
|
@@ -23175,7 +24647,7 @@ function assertOneOf3(value, allowed, flag) {
|
|
|
23175
24647
|
}
|
|
23176
24648
|
function parseJsonFileOrInline8(value, flag) {
|
|
23177
24649
|
const trimmed = value.trim();
|
|
23178
|
-
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed :
|
|
24650
|
+
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed : readFileSync22(value, "utf8");
|
|
23179
24651
|
try {
|
|
23180
24652
|
return JSON.parse(raw);
|
|
23181
24653
|
} catch (error) {
|
|
@@ -23185,6 +24657,49 @@ function parseJsonFileOrInline8(value, flag) {
|
|
|
23185
24657
|
function collectRepeatable5(value, previous) {
|
|
23186
24658
|
return [...previous, value];
|
|
23187
24659
|
}
|
|
24660
|
+
var HTTP_URL_PATTERN2 = /^https?:\/\//i;
|
|
24661
|
+
async function uploadLocalFile(client, filePath) {
|
|
24662
|
+
const bytes = await readFile3(filePath);
|
|
24663
|
+
const name = basename2(filePath);
|
|
24664
|
+
const result = await client.uploadMedia({
|
|
24665
|
+
name,
|
|
24666
|
+
mimeType: inferMimeType(name, bytes),
|
|
24667
|
+
data: bytes.toString("base64")
|
|
24668
|
+
});
|
|
24669
|
+
return { ...result, name, size: bytes.length };
|
|
24670
|
+
}
|
|
24671
|
+
async function resolveCoverUrl(client, value) {
|
|
24672
|
+
if (HTTP_URL_PATTERN2.test(value)) return value;
|
|
24673
|
+
const uploaded = await uploadLocalFile(client, value);
|
|
24674
|
+
return uploaded.url;
|
|
24675
|
+
}
|
|
24676
|
+
async function setProductCoverImages(client, productId, urls) {
|
|
24677
|
+
for (const url of urls) assertHttpsUrl(url, "--image");
|
|
24678
|
+
return client.setProductImages(productId, urls);
|
|
24679
|
+
}
|
|
24680
|
+
function printFilesTable(rows) {
|
|
24681
|
+
printTable(
|
|
24682
|
+
rows.map((f) => ({
|
|
24683
|
+
uid: f.uid,
|
|
24684
|
+
name: f.name,
|
|
24685
|
+
url: f.url,
|
|
24686
|
+
mediaId: f.mediaId ?? "",
|
|
24687
|
+
size: f.size ?? ""
|
|
24688
|
+
})),
|
|
24689
|
+
["uid", "name", "url", "mediaId", "size"]
|
|
24690
|
+
);
|
|
24691
|
+
}
|
|
24692
|
+
async function addProductFiles(client, productId, inputs) {
|
|
24693
|
+
return client.addDownloadFiles(productId, inputs);
|
|
24694
|
+
}
|
|
24695
|
+
function printAddedFiles(inputCount, result) {
|
|
24696
|
+
const skipped = inputCount - result.added.length;
|
|
24697
|
+
process.stdout.write(
|
|
24698
|
+
`Added ${result.added.length} file(s)${skipped > 0 ? ` (${skipped} skipped \u2014 already attached)` : ""}; ${result.files.length} total.
|
|
24699
|
+
`
|
|
24700
|
+
);
|
|
24701
|
+
printFilesTable(result.files);
|
|
24702
|
+
}
|
|
23188
24703
|
function registerProductsCommands(program2) {
|
|
23189
24704
|
const products2 = program2.command("products").description("Manage products");
|
|
23190
24705
|
products2.command("list").description("List products in the workspace").action(async (_, command) => {
|
|
@@ -23230,18 +24745,54 @@ function registerProductsCommands(program2) {
|
|
|
23230
24745
|
).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
24746
|
"--period <period>",
|
|
23232
24747
|
"billing period for --amount: one_time, monthly or yearly (default one_time)"
|
|
24748
|
+
).option("--description-file <path>", "read the product description from a file").option(
|
|
24749
|
+
"--cover <pathOrUrl>",
|
|
24750
|
+
"cover image: a local file (uploaded to the media library first) or an https URL"
|
|
24751
|
+
).option(
|
|
24752
|
+
"--file <path>",
|
|
24753
|
+
"local file to upload and attach to the product (downloads products; repeat for several)",
|
|
24754
|
+
collectRepeatable5,
|
|
24755
|
+
[]
|
|
23233
24756
|
).action(
|
|
23234
24757
|
async (options, command) => {
|
|
23235
24758
|
const flags = getGlobalFlags(command);
|
|
23236
24759
|
const offer = parseCreateOfferFlags(options);
|
|
24760
|
+
if (options.description !== void 0 && options.descriptionFile !== void 0) {
|
|
24761
|
+
throw new Error("Use either --description or --description-file, not both.");
|
|
24762
|
+
}
|
|
24763
|
+
const description = options.descriptionFile !== void 0 ? readFileSync22(options.descriptionFile, "utf8") : options.description;
|
|
23237
24764
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23238
24765
|
const product = await client.createProduct({
|
|
23239
24766
|
name: options.name,
|
|
23240
24767
|
productType: options.type,
|
|
23241
|
-
description
|
|
24768
|
+
description,
|
|
23242
24769
|
offer
|
|
23243
24770
|
});
|
|
23244
|
-
|
|
24771
|
+
const outcome = {};
|
|
24772
|
+
try {
|
|
24773
|
+
if (options.cover !== void 0) {
|
|
24774
|
+
const coverUrl = await resolveCoverUrl(client, options.cover);
|
|
24775
|
+
outcome.cover = await setProductCoverImages(client, product.id, [coverUrl]);
|
|
24776
|
+
}
|
|
24777
|
+
if (options.file.length > 0) {
|
|
24778
|
+
const uploaded = [];
|
|
24779
|
+
for (const filePath of options.file)
|
|
24780
|
+
uploaded.push(await uploadLocalFile(client, filePath));
|
|
24781
|
+
outcome.uploadedFiles = uploaded;
|
|
24782
|
+
outcome.files = await addProductFiles(
|
|
24783
|
+
client,
|
|
24784
|
+
product.id,
|
|
24785
|
+
uploaded.map((u) => ({ url: u.url, name: u.name, mediaId: u.mediaId, size: u.size }))
|
|
24786
|
+
);
|
|
24787
|
+
}
|
|
24788
|
+
} catch (error) {
|
|
24789
|
+
process.stderr.write(
|
|
24790
|
+
`Product ${product.id} was created, but a follow-up step failed \u2014 fix it with \`products images set\` / \`products files add\`.
|
|
24791
|
+
`
|
|
24792
|
+
);
|
|
24793
|
+
throw error;
|
|
24794
|
+
}
|
|
24795
|
+
emit(flags, { ...product, ...outcome }, () => {
|
|
23245
24796
|
process.stdout.write(`Created product ${product.id} \u2014 ${product.name}
|
|
23246
24797
|
`);
|
|
23247
24798
|
if (offer) {
|
|
@@ -23250,6 +24801,17 @@ function registerProductsCommands(program2) {
|
|
|
23250
24801
|
` : " Default offer was requested but not seeded (check the dashboard).\n"
|
|
23251
24802
|
);
|
|
23252
24803
|
}
|
|
24804
|
+
if (outcome.cover) {
|
|
24805
|
+
process.stdout.write(` Cover image: ${outcome.cover.images.join(", ")}
|
|
24806
|
+
`);
|
|
24807
|
+
}
|
|
24808
|
+
if (outcome.uploadedFiles && outcome.files) {
|
|
24809
|
+
process.stdout.write(
|
|
24810
|
+
` Uploaded ${outcome.uploadedFiles.length} file(s): ${outcome.uploadedFiles.map((u) => `${u.name} (${u.mediaId})`).join(", ")}
|
|
24811
|
+
`
|
|
24812
|
+
);
|
|
24813
|
+
printAddedFiles(outcome.uploadedFiles.length, outcome.files);
|
|
24814
|
+
}
|
|
23253
24815
|
});
|
|
23254
24816
|
}
|
|
23255
24817
|
);
|
|
@@ -23727,9 +25289,8 @@ function registerProductsCommands(program2) {
|
|
|
23727
25289
|
if (options.image.length === 0) {
|
|
23728
25290
|
throw new Error("Pass at least one --image <url>.");
|
|
23729
25291
|
}
|
|
23730
|
-
for (const url of options.image) assertHttpsUrl(url, "--image");
|
|
23731
25292
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23732
|
-
const result = await client
|
|
25293
|
+
const result = await setProductCoverImages(client, productId, options.image);
|
|
23733
25294
|
emit(flags, result, () => {
|
|
23734
25295
|
process.stdout.write(
|
|
23735
25296
|
`Set ${result.images.length} cover image(s) on product ${productId}.
|
|
@@ -23738,18 +25299,6 @@ function registerProductsCommands(program2) {
|
|
|
23738
25299
|
});
|
|
23739
25300
|
});
|
|
23740
25301
|
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
25302
|
files.command("get").description("Show the downloads content: description presence + file table").requiredOption("--product <id>", "downloads product id").action(async (options, command) => {
|
|
23754
25303
|
const flags = getGlobalFlags(command);
|
|
23755
25304
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -23770,7 +25319,7 @@ function registerProductsCommands(program2) {
|
|
|
23770
25319
|
if (options.content !== void 0 && options.contentFile !== void 0) {
|
|
23771
25320
|
throw new Error("Use either --content or --content-file, not both.");
|
|
23772
25321
|
}
|
|
23773
|
-
const editorContent = options.contentFile !== void 0 ?
|
|
25322
|
+
const editorContent = options.contentFile !== void 0 ? readFileSync22(options.contentFile, "utf8") : options.content;
|
|
23774
25323
|
if (editorContent === void 0) {
|
|
23775
25324
|
throw new Error("Pass the description via --content or --content-file.");
|
|
23776
25325
|
}
|
|
@@ -23816,14 +25365,9 @@ function registerProductsCommands(program2) {
|
|
|
23816
25365
|
];
|
|
23817
25366
|
}
|
|
23818
25367
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23819
|
-
const result = await client
|
|
25368
|
+
const result = await addProductFiles(client, options.product, inputs);
|
|
23820
25369
|
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);
|
|
25370
|
+
printAddedFiles(inputs.length, result);
|
|
23827
25371
|
});
|
|
23828
25372
|
}
|
|
23829
25373
|
);
|
|
@@ -23869,7 +25413,7 @@ function registerProductsCommands(program2) {
|
|
|
23869
25413
|
if (options.content !== void 0 && options.contentFile !== void 0) {
|
|
23870
25414
|
throw new Error("Use either --content or --content-file, not both.");
|
|
23871
25415
|
}
|
|
23872
|
-
return options.contentFile !== void 0 ?
|
|
25416
|
+
return options.contentFile !== void 0 ? readFileSync22(options.contentFile, "utf8") : options.content;
|
|
23873
25417
|
};
|
|
23874
25418
|
kb.command("get").description(
|
|
23875
25419
|
"Show the knowledge base: categories (tree keys = the --category ids) + all articles (drafts and unavailable included \u2014 the creator view)"
|
|
@@ -24089,7 +25633,7 @@ function registerProductsCommands(program2) {
|
|
|
24089
25633
|
}
|
|
24090
25634
|
|
|
24091
25635
|
// src/commands/referrals/index.ts
|
|
24092
|
-
import { readFileSync as
|
|
25636
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
24093
25637
|
var PROGRAM_KINDS = ["referral", "affiliate"];
|
|
24094
25638
|
var WRITABLE_STATUSES = ["active", "inactive"];
|
|
24095
25639
|
var ENROLLMENT_MODES = ["open", "inviteOnly", "closed"];
|
|
@@ -24114,7 +25658,7 @@ function parseIdList3(value, flag) {
|
|
|
24114
25658
|
}
|
|
24115
25659
|
function parseJsonObjectFileOrInline4(value, flag) {
|
|
24116
25660
|
const trimmed = value.trim();
|
|
24117
|
-
const raw = trimmed.startsWith("{") ? trimmed :
|
|
25661
|
+
const raw = trimmed.startsWith("{") ? trimmed : readFileSync23(value, "utf8");
|
|
24118
25662
|
let parsed;
|
|
24119
25663
|
try {
|
|
24120
25664
|
parsed = JSON.parse(raw);
|
|
@@ -24152,7 +25696,7 @@ function parseDeepConfig(value) {
|
|
|
24152
25696
|
}
|
|
24153
25697
|
return parsed;
|
|
24154
25698
|
}
|
|
24155
|
-
function
|
|
25699
|
+
function requireConfirm13(confirm, what) {
|
|
24156
25700
|
if (!confirm) {
|
|
24157
25701
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
24158
25702
|
}
|
|
@@ -24387,7 +25931,7 @@ Failed deliveries (${result.failedDeliveries.length})
|
|
|
24387
25931
|
"Archive a program \u2014 new referrals stop counting and it leaves the active list. Requires --confirm."
|
|
24388
25932
|
).argument("<programId>", "program id").option("--confirm", "required \u2014 acknowledge the archive").action(async (programId, options, command) => {
|
|
24389
25933
|
const flags = getGlobalFlags(command);
|
|
24390
|
-
|
|
25934
|
+
requireConfirm13(options.confirm, `archive program ${programId}`);
|
|
24391
25935
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
24392
25936
|
const result = await client.archiveReferralProgram(programId);
|
|
24393
25937
|
emit(flags, result, () => {
|
|
@@ -24419,7 +25963,7 @@ Failed deliveries (${result.failedDeliveries.length})
|
|
|
24419
25963
|
"Enroll EVERY client with access to one of the program products. Requires --confirm. Reports enrolled / alreadyEnrolled / skippedWithoutAccount counts."
|
|
24420
25964
|
).argument("<programId>", "program id").option("--confirm", "required \u2014 acknowledge the bulk enrollment").action(async (programId, options, command) => {
|
|
24421
25965
|
const flags = getGlobalFlags(command);
|
|
24422
|
-
|
|
25966
|
+
requireConfirm13(options.confirm, `enroll all clients in program ${programId}`);
|
|
24423
25967
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
24424
25968
|
const result = await client.enrollAllReferralClients(programId);
|
|
24425
25969
|
emit(flags, result, () => {
|
|
@@ -24435,7 +25979,7 @@ Failed deliveries (${result.failedDeliveries.length})
|
|
|
24435
25979
|
async (programId, entryId, options, command) => {
|
|
24436
25980
|
const flags = getGlobalFlags(command);
|
|
24437
25981
|
const decision = parseChoice6(options.decision, REWARD_DECISIONS, "--decision");
|
|
24438
|
-
|
|
25982
|
+
requireConfirm13(options.confirm, `${decision} reward ${entryId} in program ${programId}`);
|
|
24439
25983
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
24440
25984
|
const result = await client.resolveReferralReward(programId, entryId, { decision });
|
|
24441
25985
|
emit(flags, result, () => {
|
|
@@ -24465,7 +26009,7 @@ Failed deliveries (${result.failedDeliveries.length})
|
|
|
24465
26009
|
}
|
|
24466
26010
|
|
|
24467
26011
|
// src/commands/segments/index.ts
|
|
24468
|
-
import { readFileSync as
|
|
26012
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
24469
26013
|
var SUBSCRIPTION_STATUSES2 = ["pending", "subscribed", "unsubscribed"];
|
|
24470
26014
|
function collectRepeatable6(value, previous) {
|
|
24471
26015
|
return [...previous, value];
|
|
@@ -24477,14 +26021,14 @@ function parseMinorUnits(value, flag) {
|
|
|
24477
26021
|
}
|
|
24478
26022
|
return n;
|
|
24479
26023
|
}
|
|
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";
|
|
26024
|
+
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
26025
|
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;
|
|
26026
|
+
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
26027
|
if (options.jsonFile !== void 0) {
|
|
24484
26028
|
if (hasFlagConditions) {
|
|
24485
26029
|
throw new Error("Use either condition flags or --json-file, not both.");
|
|
24486
26030
|
}
|
|
24487
|
-
const raw =
|
|
26031
|
+
const raw = readFileSync24(options.jsonFile, "utf8");
|
|
24488
26032
|
let parsed;
|
|
24489
26033
|
try {
|
|
24490
26034
|
parsed = JSON.parse(raw);
|
|
@@ -24506,6 +26050,12 @@ function resolveConditions(options) {
|
|
|
24506
26050
|
for (const tagId of options.withoutTag) {
|
|
24507
26051
|
conditions.push({ field: "tag", operator: "not-has", tagId });
|
|
24508
26052
|
}
|
|
26053
|
+
if (options.hasAnyTag.length > 0) {
|
|
26054
|
+
conditions.push({ field: "tag", operator: "has-any", tagIds: options.hasAnyTag });
|
|
26055
|
+
}
|
|
26056
|
+
if (options.hasNoneTag.length > 0) {
|
|
26057
|
+
conditions.push({ field: "tag", operator: "has-none", tagIds: options.hasNoneTag });
|
|
26058
|
+
}
|
|
24509
26059
|
for (const productId of options.owns) {
|
|
24510
26060
|
conditions.push({ field: "product", operator: "owns", productId });
|
|
24511
26061
|
}
|
|
@@ -24554,6 +26104,16 @@ function withConditionOptions(command) {
|
|
|
24554
26104
|
"client does NOT carry this tag (repeatable)",
|
|
24555
26105
|
collectRepeatable6,
|
|
24556
26106
|
[]
|
|
26107
|
+
).option(
|
|
26108
|
+
"--has-any-tag <tagId>",
|
|
26109
|
+
"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",
|
|
26110
|
+
collectRepeatable6,
|
|
26111
|
+
[]
|
|
26112
|
+
).option(
|
|
26113
|
+
"--has-none-tag <tagId>",
|
|
26114
|
+
"client carries NONE of these tags (repeat the flag; one condition, max 10 ids)",
|
|
26115
|
+
collectRepeatable6,
|
|
26116
|
+
[]
|
|
24557
26117
|
).option(
|
|
24558
26118
|
"--owns <productId>",
|
|
24559
26119
|
"client has ACTIVE access to this product - a direct grant OR through a bundle (repeatable)",
|
|
@@ -24588,6 +26148,9 @@ function withConditionOptions(command) {
|
|
|
24588
26148
|
function describeCondition(condition) {
|
|
24589
26149
|
switch (condition.field) {
|
|
24590
26150
|
case "tag":
|
|
26151
|
+
if ("tagIds" in condition) {
|
|
26152
|
+
return `${condition.operator === "has-any" ? "has any of" : "has none of"} tags ${condition.tagIds.join(", ")}`;
|
|
26153
|
+
}
|
|
24591
26154
|
return `${condition.operator === "has" ? "has" : "lacks"} tag ${condition.tagId}`;
|
|
24592
26155
|
case "product":
|
|
24593
26156
|
return `${condition.operator === "owns" ? "owns" : "does not own"} product ${condition.productId}${condition.accessScope === "direct" ? " (direct)" : ""}`;
|
|
@@ -24700,7 +26263,7 @@ function registerSegmentsCommands(program2) {
|
|
|
24700
26263
|
}
|
|
24701
26264
|
|
|
24702
26265
|
// src/commands/site/index.ts
|
|
24703
|
-
import { existsSync as existsSync4, readFileSync as
|
|
26266
|
+
import { existsSync as existsSync4, readFileSync as readFileSync25 } from "fs";
|
|
24704
26267
|
var MAIN_PAGE_MODES = {
|
|
24705
26268
|
sales: "/",
|
|
24706
26269
|
dashboard: "/dashboard",
|
|
@@ -24724,14 +26287,14 @@ var HTTP_CONFLICT = 409;
|
|
|
24724
26287
|
var HTTP_BAD_REQUEST = 400;
|
|
24725
26288
|
var HTTP_NOT_FOUND = 404;
|
|
24726
26289
|
var HTTP_FORBIDDEN = 403;
|
|
24727
|
-
function
|
|
26290
|
+
function requireConfirm14(confirm, what) {
|
|
24728
26291
|
if (!confirm) {
|
|
24729
26292
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
24730
26293
|
}
|
|
24731
26294
|
}
|
|
24732
26295
|
function parseJsonObjectFileOrInline5(value, flag) {
|
|
24733
26296
|
const trimmed = value.trim();
|
|
24734
|
-
const raw = trimmed.startsWith("{") ? trimmed :
|
|
26297
|
+
const raw = trimmed.startsWith("{") ? trimmed : readFileSync25(value, "utf8");
|
|
24735
26298
|
let parsed;
|
|
24736
26299
|
try {
|
|
24737
26300
|
parsed = JSON.parse(raw);
|
|
@@ -24746,7 +26309,7 @@ function parseJsonObjectFileOrInline5(value, flag) {
|
|
|
24746
26309
|
function baseBody(json) {
|
|
24747
26310
|
return json === void 0 ? {} : parseJsonObjectFileOrInline5(json, "--json");
|
|
24748
26311
|
}
|
|
24749
|
-
function
|
|
26312
|
+
function parseOnOff4(value, flag) {
|
|
24750
26313
|
const v = value.trim().toLowerCase();
|
|
24751
26314
|
if (v === "on" || v === "true") return true;
|
|
24752
26315
|
if (v === "off" || v === "false") return false;
|
|
@@ -24783,7 +26346,7 @@ function parseDomainList(value, what) {
|
|
|
24783
26346
|
return items;
|
|
24784
26347
|
}
|
|
24785
26348
|
function readMarkdownOrFile(value) {
|
|
24786
|
-
return existsSync4(value) ?
|
|
26349
|
+
return existsSync4(value) ? readFileSync25(value, "utf8") : value;
|
|
24787
26350
|
}
|
|
24788
26351
|
function assertNonEmptyBody(body, hint) {
|
|
24789
26352
|
if (Object.keys(body).length === 0) {
|
|
@@ -24990,7 +26553,7 @@ function registerSiteCommands(program2) {
|
|
|
24990
26553
|
body.language = parseChoice7(options.language, PLATFORM_LANGUAGES, "--language");
|
|
24991
26554
|
}
|
|
24992
26555
|
if (options.showPageTitle !== void 0) {
|
|
24993
|
-
body.showPageTitle =
|
|
26556
|
+
body.showPageTitle = parseOnOff4(options.showPageTitle, "--show-page-title");
|
|
24994
26557
|
}
|
|
24995
26558
|
assertNonEmptyBody(body, "pass --title, --description, --language or --json=<patch>.");
|
|
24996
26559
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -25012,7 +26575,7 @@ function registerSiteCommands(program2) {
|
|
|
25012
26575
|
if (options.radius !== void 0) body.radius = options.radius;
|
|
25013
26576
|
if (options.styleVariant !== void 0) body.styleVariant = options.styleVariant;
|
|
25014
26577
|
if (options.fromBrand !== void 0) {
|
|
25015
|
-
body.themeFromBrand =
|
|
26578
|
+
body.themeFromBrand = parseOnOff4(options.fromBrand, "--from-brand");
|
|
25016
26579
|
}
|
|
25017
26580
|
assertNonEmptyBody(body, "pass --theme, --radius, --from-brand \u2026 or --json=<patch>.");
|
|
25018
26581
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -25039,7 +26602,7 @@ function registerSiteCommands(program2) {
|
|
|
25039
26602
|
body.platformFaviconUrl = orNull6(options.faviconUrl);
|
|
25040
26603
|
}
|
|
25041
26604
|
if (options.hideBranding !== void 0) {
|
|
25042
|
-
body.hideZanfiaBranding =
|
|
26605
|
+
body.hideZanfiaBranding = parseOnOff4(options.hideBranding, "--hide-branding");
|
|
25043
26606
|
}
|
|
25044
26607
|
assertNonEmptyBody(
|
|
25045
26608
|
body,
|
|
@@ -25084,7 +26647,7 @@ function registerSiteCommands(program2) {
|
|
|
25084
26647
|
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
26648
|
const flags = getGlobalFlags(command);
|
|
25086
26649
|
const base = baseBody(options.json);
|
|
25087
|
-
const cookieConsentEnabled = options.cookieConsent !== void 0 ?
|
|
26650
|
+
const cookieConsentEnabled = options.cookieConsent !== void 0 ? parseOnOff4(options.cookieConsent, "--cookie-consent") : base.cookieConsentEnabled;
|
|
25088
26651
|
if (typeof cookieConsentEnabled !== "boolean") {
|
|
25089
26652
|
throw new Error(
|
|
25090
26653
|
'Pass --cookie-consent on|off (or a --json body with "cookieConsentEnabled").'
|
|
@@ -25151,14 +26714,14 @@ function registerSiteCommands(program2) {
|
|
|
25151
26714
|
if (options.libraryOrder === void 0) customization.libraryOrder = "custom";
|
|
25152
26715
|
}
|
|
25153
26716
|
if (options.groupByType !== void 0) {
|
|
25154
|
-
customization.libraryGroupByType =
|
|
26717
|
+
customization.libraryGroupByType = parseOnOff4(options.groupByType, "--group-by-type");
|
|
25155
26718
|
}
|
|
25156
26719
|
if (options.defaultView !== void 0) customization.defaultView = options.defaultView;
|
|
25157
26720
|
if (options.messages !== void 0) {
|
|
25158
|
-
customization.messagesEnabled =
|
|
26721
|
+
customization.messagesEnabled = parseOnOff4(options.messages, "--messages");
|
|
25159
26722
|
}
|
|
25160
26723
|
if (options.clientMessages !== void 0) {
|
|
25161
|
-
customization.clientMessagesEnabled =
|
|
26724
|
+
customization.clientMessagesEnabled = parseOnOff4(
|
|
25162
26725
|
options.clientMessages,
|
|
25163
26726
|
"--client-messages"
|
|
25164
26727
|
);
|
|
@@ -25266,7 +26829,7 @@ function registerSiteCommands(program2) {
|
|
|
25266
26829
|
throw new Error("Pass exactly one of --on or --off.");
|
|
25267
26830
|
}
|
|
25268
26831
|
const active = Boolean(options.on);
|
|
25269
|
-
|
|
26832
|
+
requireConfirm14(options.confirm, active ? "publish the site" : "take the site offline");
|
|
25270
26833
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
25271
26834
|
const result = await client.activateSite({ active });
|
|
25272
26835
|
emit(
|
|
@@ -25281,7 +26844,7 @@ function registerSiteCommands(program2) {
|
|
|
25281
26844
|
"Change the slug \u2014 heavy: re-registers Stripe/Firebase/Vercel hosts and purges caches. 409 = slug taken. Requires --confirm"
|
|
25282
26845
|
).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
26846
|
const flags = getGlobalFlags(command);
|
|
25284
|
-
|
|
26847
|
+
requireConfirm14(options.confirm, `change the site slug to "${value}"`);
|
|
25285
26848
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
25286
26849
|
const result = await client.updateSiteSlug({ slug: value.trim() }).catch(withSlugHint);
|
|
25287
26850
|
emit(flags, result, () => process.stdout.write(`Slug changed to "${value.trim()}".
|
|
@@ -25293,7 +26856,7 @@ function registerSiteCommands(program2) {
|
|
|
25293
26856
|
).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
26857
|
const flags = getGlobalFlags(command);
|
|
25295
26858
|
const list = value.trim().toLowerCase() === "none" ? [] : parseDomainList(value, "<domains>");
|
|
25296
|
-
|
|
26859
|
+
requireConfirm14(
|
|
25297
26860
|
options.confirm,
|
|
25298
26861
|
list.length === 0 ? "remove every custom domain" : `set the custom domains to ${list.join(", ")}`
|
|
25299
26862
|
);
|
|
@@ -25378,7 +26941,7 @@ function registerSiteCommands(program2) {
|
|
|
25378
26941
|
"Nothing to update \u2014 pass --terms-content|--terms-url and/or --privacy-content|--privacy-url."
|
|
25379
26942
|
);
|
|
25380
26943
|
}
|
|
25381
|
-
|
|
26944
|
+
requireConfirm14(options.confirm, "change the legal documents");
|
|
25382
26945
|
const body = {};
|
|
25383
26946
|
if (options.language !== void 0) body.language = options.language;
|
|
25384
26947
|
if (terms) body.terms = terms;
|
|
@@ -25430,8 +26993,8 @@ function printStorefront(config) {
|
|
|
25430
26993
|
}
|
|
25431
26994
|
|
|
25432
26995
|
// src/commands/smsCampaigns/index.ts
|
|
25433
|
-
import { readFileSync as
|
|
25434
|
-
var
|
|
26996
|
+
import { readFileSync as readFileSync26 } from "fs";
|
|
26997
|
+
var SMS_PROVIDERS2 = ["twilio", "smsapi"];
|
|
25435
26998
|
var RECIPIENT_STATUSES2 = [
|
|
25436
26999
|
"pending",
|
|
25437
27000
|
"sent",
|
|
@@ -25446,7 +27009,7 @@ function parsePositiveInt15(value, flag) {
|
|
|
25446
27009
|
}
|
|
25447
27010
|
return n;
|
|
25448
27011
|
}
|
|
25449
|
-
function
|
|
27012
|
+
function requireConfirm15(confirm, refusal) {
|
|
25450
27013
|
if (!confirm) throw new Error(refusal);
|
|
25451
27014
|
}
|
|
25452
27015
|
function withSchedulerHint4(error) {
|
|
@@ -25463,7 +27026,7 @@ function resolveBody(options) {
|
|
|
25463
27026
|
if (options.body !== void 0 && options.bodyFile !== void 0) {
|
|
25464
27027
|
throw new Error("Use either --body or --body-file, not both.");
|
|
25465
27028
|
}
|
|
25466
|
-
return options.bodyFile !== void 0 ?
|
|
27029
|
+
return options.bodyFile !== void 0 ? readFileSync26(options.bodyFile, "utf8") : options.body;
|
|
25467
27030
|
}
|
|
25468
27031
|
function resolveAudience2(options) {
|
|
25469
27032
|
if (options.segment !== void 0 && options.allSubscribed) {
|
|
@@ -25482,10 +27045,10 @@ function resolveProvider(options) {
|
|
|
25482
27045
|
}
|
|
25483
27046
|
if (options.defaultProvider) return null;
|
|
25484
27047
|
if (options.provider === void 0) return void 0;
|
|
25485
|
-
const match =
|
|
27048
|
+
const match = SMS_PROVIDERS2.find((p) => p === options.provider);
|
|
25486
27049
|
if (!match) {
|
|
25487
27050
|
throw new Error(
|
|
25488
|
-
`Invalid --provider: ${options.provider} (expected one of ${
|
|
27051
|
+
`Invalid --provider: ${options.provider} (expected one of ${SMS_PROVIDERS2.join(", ")})`
|
|
25489
27052
|
);
|
|
25490
27053
|
}
|
|
25491
27054
|
return match;
|
|
@@ -25570,7 +27133,7 @@ function registerSmsCampaignsCommands(program2) {
|
|
|
25570
27133
|
});
|
|
25571
27134
|
smsCampaigns.command("create").description(
|
|
25572
27135
|
"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 (${
|
|
27136
|
+
).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
27137
|
async (options, command) => {
|
|
25575
27138
|
const flags = getGlobalFlags(command);
|
|
25576
27139
|
const body = resolveBody(options);
|
|
@@ -25593,7 +27156,7 @@ function registerSmsCampaignsCommands(program2) {
|
|
|
25593
27156
|
});
|
|
25594
27157
|
}
|
|
25595
27158
|
);
|
|
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 (${
|
|
27159
|
+
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
27160
|
async (id, options, command) => {
|
|
25598
27161
|
const flags = getGlobalFlags(command);
|
|
25599
27162
|
const input = {};
|
|
@@ -25625,7 +27188,7 @@ function registerSmsCampaignsCommands(program2) {
|
|
|
25625
27188
|
"PERMANENTLY delete an SMS campaign \u2014 cannot be undone; draft/cancelled/failed only (sent campaigns are audit records and stay). Requires --confirm"
|
|
25626
27189
|
).argument("<id>", "SMS campaign id").option("--confirm", "required \u2014 acknowledge the permanent deletion").action(async (id, options, command) => {
|
|
25627
27190
|
const flags = getGlobalFlags(command);
|
|
25628
|
-
|
|
27191
|
+
requireConfirm15(
|
|
25629
27192
|
options.confirm,
|
|
25630
27193
|
`Refusing to delete SMS campaign ${id} without --confirm. Re-run with --confirm to proceed.`
|
|
25631
27194
|
);
|
|
@@ -25643,7 +27206,7 @@ function registerSmsCampaignsCommands(program2) {
|
|
|
25643
27206
|
"schedule the send (ISO, max 30 days out) instead of sending now"
|
|
25644
27207
|
).option("--confirm", "required \u2014 acknowledge this texts the audience for real").action(async (id, options, command) => {
|
|
25645
27208
|
const flags = getGlobalFlags(command);
|
|
25646
|
-
|
|
27209
|
+
requireConfirm15(
|
|
25647
27210
|
options.confirm,
|
|
25648
27211
|
`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
27212
|
);
|
|
@@ -26272,7 +27835,7 @@ function describeAttempts(item) {
|
|
|
26272
27835
|
const next = item.nextPaymentAttempt !== null ? formatEpochSeconds(item.nextPaymentAttempt) : "";
|
|
26273
27836
|
return next ? `${attempts} (next ${next})` : String(attempts);
|
|
26274
27837
|
}
|
|
26275
|
-
function
|
|
27838
|
+
function requireConfirm16(confirm, what) {
|
|
26276
27839
|
if (!confirm) {
|
|
26277
27840
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
26278
27841
|
}
|
|
@@ -26346,7 +27909,7 @@ function registerSubscriptionsCommands(program2) {
|
|
|
26346
27909
|
"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
27910
|
).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
27911
|
const flags = getGlobalFlags(command);
|
|
26349
|
-
|
|
27912
|
+
requireConfirm16(options.confirm, `retry the charge for invoice ${invoiceId} (real charge)`);
|
|
26350
27913
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
26351
27914
|
const result = await client.retrySubscriptionPayment(invoiceId);
|
|
26352
27915
|
emit(flags, result, () => {
|
|
@@ -26883,7 +28446,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
26883
28446
|
}
|
|
26884
28447
|
|
|
26885
28448
|
// src/commands/workflows/index.ts
|
|
26886
|
-
import { readFileSync as
|
|
28449
|
+
import { readFileSync as readFileSync27 } from "fs";
|
|
26887
28450
|
|
|
26888
28451
|
// src/commands/workflows/authoring.ts
|
|
26889
28452
|
import { writeFileSync as writeFileSync6 } from "fs";
|
|
@@ -27049,14 +28612,14 @@ function parsePositiveInt18(value, flag) {
|
|
|
27049
28612
|
}
|
|
27050
28613
|
return n;
|
|
27051
28614
|
}
|
|
27052
|
-
function
|
|
28615
|
+
function requireConfirm17(confirm, what) {
|
|
27053
28616
|
if (!confirm) {
|
|
27054
28617
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
27055
28618
|
}
|
|
27056
28619
|
}
|
|
27057
28620
|
function parseTriggerContext(value) {
|
|
27058
28621
|
const trimmed = value.trim();
|
|
27059
|
-
const raw = trimmed.startsWith("{") ? trimmed :
|
|
28622
|
+
const raw = trimmed.startsWith("{") ? trimmed : readFileSync27(value, "utf8");
|
|
27060
28623
|
let parsed;
|
|
27061
28624
|
try {
|
|
27062
28625
|
parsed = JSON.parse(raw);
|
|
@@ -27102,42 +28665,26 @@ function registerWorkflowsCommands(program2) {
|
|
|
27102
28665
|
);
|
|
27103
28666
|
});
|
|
27104
28667
|
});
|
|
27105
|
-
workflows.command("get").description(
|
|
28668
|
+
workflows.command("get").description(
|
|
28669
|
+
"Show a single workflow (triggers, product scope, node counts, last run); --graph walks every step incl. the emails it sends"
|
|
28670
|
+
).argument("<automationId>", "workflow automation id").option(
|
|
28671
|
+
"--graph",
|
|
28672
|
+
"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."
|
|
28673
|
+
).action(async (automationId, options, command) => {
|
|
27106
28674
|
const flags = getGlobalFlags(command);
|
|
27107
28675
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
27108
|
-
|
|
28676
|
+
if (!options.graph) {
|
|
28677
|
+
const workflow2 = await client.getAutomation(automationId);
|
|
28678
|
+
emit(flags, workflow2, () => printWorkflowSummary(workflow2));
|
|
28679
|
+
return;
|
|
28680
|
+
}
|
|
28681
|
+
const workflow = await client.getAutomationGraph(automationId);
|
|
28682
|
+
const published = asAutomationGraph(workflow.publishedGraph);
|
|
28683
|
+
const draft = asAutomationGraph(workflow.draftGraph);
|
|
28684
|
+
const templateNames = await resolveTemplateNames(client, [published, draft]);
|
|
27109
28685
|
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
|
-
`);
|
|
28686
|
+
printWorkflowSummary(workflow);
|
|
28687
|
+
printWorkflowGraphs(workflow, published, draft, templateNames);
|
|
27141
28688
|
});
|
|
27142
28689
|
});
|
|
27143
28690
|
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 +28709,17 @@ function registerWorkflowsCommands(program2) {
|
|
|
27162
28709
|
);
|
|
27163
28710
|
});
|
|
27164
28711
|
});
|
|
27165
|
-
workflows.command("pause").description(
|
|
28712
|
+
workflows.command("pause").description(
|
|
28713
|
+
"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)"
|
|
28714
|
+
).argument("<automationId>", "workflow automation id").action(async (automationId, _, command) => {
|
|
27166
28715
|
const flags = getGlobalFlags(command);
|
|
27167
28716
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
27168
28717
|
const result = await client.setAutomationStatus(automationId, "paused");
|
|
27169
28718
|
emit(flags, result, () => {
|
|
27170
|
-
process.stdout.write(
|
|
27171
|
-
`)
|
|
28719
|
+
process.stdout.write(
|
|
28720
|
+
`Paused workflow ${result.id} (${result.cancelledRuns} in-flight run(s) cancelled).
|
|
28721
|
+
`
|
|
28722
|
+
);
|
|
27172
28723
|
});
|
|
27173
28724
|
});
|
|
27174
28725
|
workflows.command("resume").description(
|
|
@@ -27187,7 +28738,7 @@ function registerWorkflowsCommands(program2) {
|
|
|
27187
28738
|
).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
28739
|
async (automationId, runId, options, command) => {
|
|
27189
28740
|
const flags = getGlobalFlags(command);
|
|
27190
|
-
|
|
28741
|
+
requireConfirm17(options.confirm, `retry run ${runId} (its actions execute again)`);
|
|
27191
28742
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
27192
28743
|
const result = await client.retryAutomationRun(automationId, runId).catch(withSchedulerHint5);
|
|
27193
28744
|
emit(flags, result, () => {
|
|
@@ -27211,7 +28762,7 @@ Follow it with: zanfia workflows runs ${automationId}
|
|
|
27211
28762
|
async (automationId, options, command) => {
|
|
27212
28763
|
const flags = getGlobalFlags(command);
|
|
27213
28764
|
const context = parseTriggerContext(options.context);
|
|
27214
|
-
|
|
28765
|
+
requireConfirm17(options.confirm, `test-run workflow ${automationId} (real actions fire)`);
|
|
27215
28766
|
process.stderr.write(
|
|
27216
28767
|
"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
28768
|
);
|
|
@@ -27240,9 +28791,352 @@ function formatSteps(steps) {
|
|
|
27240
28791
|
if (steps.skipped > 0) parts.push(`${steps.skipped} skipped`);
|
|
27241
28792
|
return parts.join(", ");
|
|
27242
28793
|
}
|
|
28794
|
+
function printWorkflowSummary(workflow) {
|
|
28795
|
+
process.stdout.write(`Workflow ${workflow.name} (${workflow.id})
|
|
28796
|
+
`);
|
|
28797
|
+
if (workflow.description) process.stdout.write(` Description: ${workflow.description}
|
|
28798
|
+
`);
|
|
28799
|
+
process.stdout.write(` Status: ${workflow.status}
|
|
28800
|
+
`);
|
|
28801
|
+
process.stdout.write(` Triggers: ${workflow.triggers.join(", ")}
|
|
28802
|
+
`);
|
|
28803
|
+
process.stdout.write(
|
|
28804
|
+
` Products: ${workflow.productIds.length > 0 ? workflow.productIds.join(", ") : "all"}
|
|
28805
|
+
`
|
|
28806
|
+
);
|
|
28807
|
+
process.stdout.write(
|
|
28808
|
+
` Published: ${workflow.published ? `yes${workflow.publishedAt ? ` (${workflow.publishedAt})` : ""}` : "no (draft \u2014 cannot run until published from the dashboard)"}
|
|
28809
|
+
`
|
|
28810
|
+
);
|
|
28811
|
+
const { nodeCounts } = workflow;
|
|
28812
|
+
process.stdout.write(
|
|
28813
|
+
` Nodes: ${nodeCounts.triggers} triggers, ${nodeCounts.actions} actions, ${nodeCounts.conditions} conditions, ${nodeCounts.delays} delays
|
|
28814
|
+
`
|
|
28815
|
+
);
|
|
28816
|
+
process.stdout.write(` Runs this month: ${workflow.runsThisMonth}
|
|
28817
|
+
`);
|
|
28818
|
+
if (workflow.lastRunAt) {
|
|
28819
|
+
process.stdout.write(
|
|
28820
|
+
` Last run: ${workflow.lastRunAt}${workflow.lastRunStatus ? ` (${workflow.lastRunStatus})` : ""}
|
|
28821
|
+
`
|
|
28822
|
+
);
|
|
28823
|
+
}
|
|
28824
|
+
if (workflow.createdAt) process.stdout.write(` Created: ${workflow.createdAt}
|
|
28825
|
+
`);
|
|
28826
|
+
}
|
|
28827
|
+
var GRAPH_ACTION_LABELS = {
|
|
28828
|
+
sendEmail: "Send email",
|
|
28829
|
+
notifyOwner: "Notify owner (email)",
|
|
28830
|
+
sendSms: "Send SMS",
|
|
28831
|
+
webhook: "Webhook",
|
|
28832
|
+
publishSocialPost: "Publish social post",
|
|
28833
|
+
createCommunityPost: "Create community post",
|
|
28834
|
+
updateClientTags: "Update client tags",
|
|
28835
|
+
setEmailSubscription: "Set email subscription",
|
|
28836
|
+
requestTestimonial: "Request testimonial"
|
|
28837
|
+
};
|
|
28838
|
+
var GRAPH_NODE_KINDS = /* @__PURE__ */ new Set([
|
|
28839
|
+
"trigger",
|
|
28840
|
+
"action",
|
|
28841
|
+
"condition",
|
|
28842
|
+
"delay"
|
|
28843
|
+
]);
|
|
28844
|
+
var GRAPH_CONDITION_BRANCHES = ["true", "false"];
|
|
28845
|
+
var EXCERPT_LENGTH = 120;
|
|
28846
|
+
function isRecord2(value) {
|
|
28847
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
28848
|
+
}
|
|
28849
|
+
function optionalString(value) {
|
|
28850
|
+
return typeof value === "string" ? value : void 0;
|
|
28851
|
+
}
|
|
28852
|
+
function asAutomationGraph(raw) {
|
|
28853
|
+
if (!isRecord2(raw) || !Array.isArray(raw.nodes) || !Array.isArray(raw.edges)) return null;
|
|
28854
|
+
const nodes = [];
|
|
28855
|
+
for (const candidate of raw.nodes) {
|
|
28856
|
+
if (!isRecord2(candidate)) continue;
|
|
28857
|
+
const { id, kind, config, name, disabled } = candidate;
|
|
28858
|
+
if (typeof id !== "string" || typeof kind !== "string" || !GRAPH_NODE_KINDS.has(kind)) {
|
|
28859
|
+
continue;
|
|
28860
|
+
}
|
|
28861
|
+
nodes.push({
|
|
28862
|
+
id,
|
|
28863
|
+
kind,
|
|
28864
|
+
config: isRecord2(config) ? config : {},
|
|
28865
|
+
...typeof name === "string" && name.length > 0 ? { name } : {},
|
|
28866
|
+
...disabled === true ? { disabled: true } : {}
|
|
28867
|
+
});
|
|
28868
|
+
}
|
|
28869
|
+
const edges = [];
|
|
28870
|
+
for (const candidate of raw.edges) {
|
|
28871
|
+
if (!isRecord2(candidate)) continue;
|
|
28872
|
+
const { source, target, sourceHandle } = candidate;
|
|
28873
|
+
if (typeof source !== "string" || typeof target !== "string") continue;
|
|
28874
|
+
const branch = GRAPH_CONDITION_BRANCHES.find((b) => b === sourceHandle);
|
|
28875
|
+
edges.push({ source, target, ...branch !== void 0 ? { sourceHandle: branch } : {} });
|
|
28876
|
+
}
|
|
28877
|
+
return { nodes, edges };
|
|
28878
|
+
}
|
|
28879
|
+
function emailConfigOf(node) {
|
|
28880
|
+
const raw = node.config.email;
|
|
28881
|
+
if (!isRecord2(raw)) return null;
|
|
28882
|
+
return {
|
|
28883
|
+
templateId: optionalString(raw.templateId) ?? null,
|
|
28884
|
+
templateName: optionalString(raw.templateName) ?? null,
|
|
28885
|
+
subject: optionalString(raw.subject),
|
|
28886
|
+
html: optionalString(raw.html)
|
|
28887
|
+
};
|
|
28888
|
+
}
|
|
28889
|
+
async function resolveTemplateNames(client, graphs) {
|
|
28890
|
+
const ids = /* @__PURE__ */ new Set();
|
|
28891
|
+
for (const graph of graphs) {
|
|
28892
|
+
for (const node of graph?.nodes ?? []) {
|
|
28893
|
+
const templateId = emailConfigOf(node)?.templateId;
|
|
28894
|
+
if (templateId) ids.add(templateId);
|
|
28895
|
+
}
|
|
28896
|
+
}
|
|
28897
|
+
const names = /* @__PURE__ */ new Map();
|
|
28898
|
+
await Promise.all(
|
|
28899
|
+
[...ids].map(async (id) => {
|
|
28900
|
+
try {
|
|
28901
|
+
const { email } = await client.getLibraryEmail(id);
|
|
28902
|
+
names.set(id, email.name);
|
|
28903
|
+
} catch {
|
|
28904
|
+
}
|
|
28905
|
+
})
|
|
28906
|
+
);
|
|
28907
|
+
return names;
|
|
28908
|
+
}
|
|
28909
|
+
function printWorkflowGraphs(workflow, published, draft, templateNames) {
|
|
28910
|
+
if (workflow.draftGraph === void 0 && workflow.publishedGraph === void 0) {
|
|
28911
|
+
process.stdout.write(
|
|
28912
|
+
"\nThis API does not return workflow graphs yet (it predates `?include=graph`) \u2014 update the API to see the steps.\n"
|
|
28913
|
+
);
|
|
28914
|
+
return;
|
|
28915
|
+
}
|
|
28916
|
+
const differs = workflow.hasUnpublishedChanges === true;
|
|
28917
|
+
if (published) {
|
|
28918
|
+
process.stdout.write(
|
|
28919
|
+
`
|
|
28920
|
+
Graph shown: published (what runs now)${differs ? " \u2014 the draft has unpublished changes, listed below" : ""}
|
|
28921
|
+
`
|
|
28922
|
+
);
|
|
28923
|
+
printGraphWalk(published, templateNames);
|
|
28924
|
+
if (differs && draft) {
|
|
28925
|
+
process.stdout.write("\nDraft graph (unpublished changes \u2014 not running):\n");
|
|
28926
|
+
printGraphWalk(draft, templateNames);
|
|
28927
|
+
}
|
|
28928
|
+
return;
|
|
28929
|
+
}
|
|
28930
|
+
if (draft) {
|
|
28931
|
+
process.stdout.write("\nGraph shown: draft (never published \u2014 nothing runs yet)\n");
|
|
28932
|
+
printGraphWalk(draft, templateNames);
|
|
28933
|
+
return;
|
|
28934
|
+
}
|
|
28935
|
+
process.stdout.write("\n(no graph)\n");
|
|
28936
|
+
}
|
|
28937
|
+
function printGraphWalk(graph, templateNames) {
|
|
28938
|
+
const lines = walkGraph(graph, templateNames);
|
|
28939
|
+
if (lines.length === 0) {
|
|
28940
|
+
process.stdout.write(" (empty graph)\n");
|
|
28941
|
+
return;
|
|
28942
|
+
}
|
|
28943
|
+
for (const line of lines) process.stdout.write(line.length > 0 ? ` ${line}
|
|
28944
|
+
` : "\n");
|
|
28945
|
+
}
|
|
28946
|
+
function walkGraph(graph, templateNames) {
|
|
28947
|
+
const nodesById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
28948
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
28949
|
+
for (const edge of graph.edges) {
|
|
28950
|
+
const list = outgoing.get(edge.source) ?? [];
|
|
28951
|
+
list.push(edge);
|
|
28952
|
+
outgoing.set(edge.source, list);
|
|
28953
|
+
}
|
|
28954
|
+
const lines = [];
|
|
28955
|
+
const reached = /* @__PURE__ */ new Set();
|
|
28956
|
+
const visit = (nodeId, depth, path2) => {
|
|
28957
|
+
const node = nodesById.get(nodeId);
|
|
28958
|
+
const indent = " ".repeat(depth);
|
|
28959
|
+
if (!node) {
|
|
28960
|
+
lines.push(`${indent}-> (missing node ${nodeId})`);
|
|
28961
|
+
return;
|
|
28962
|
+
}
|
|
28963
|
+
if (path2.has(nodeId)) {
|
|
28964
|
+
lines.push(`${indent}-> loops back to ${describeNodeRef(node)}`);
|
|
28965
|
+
return;
|
|
28966
|
+
}
|
|
28967
|
+
reached.add(nodeId);
|
|
28968
|
+
lines.push(`${indent}${describeNode(node, templateNames)}`);
|
|
28969
|
+
const nextPath = new Set(path2).add(nodeId);
|
|
28970
|
+
const edges = outgoing.get(nodeId) ?? [];
|
|
28971
|
+
if (node.kind === "condition") {
|
|
28972
|
+
for (const branch of GRAPH_CONDITION_BRANCHES) {
|
|
28973
|
+
const branchEdges = edges.filter((edge) => edge.sourceHandle === branch);
|
|
28974
|
+
lines.push(
|
|
28975
|
+
`${indent} ${branch === "true" ? "yes" : "no"} ->${branchEdges.length === 0 ? " (end)" : ""}`
|
|
28976
|
+
);
|
|
28977
|
+
for (const edge of branchEdges) visit(edge.target, depth + 2, nextPath);
|
|
28978
|
+
}
|
|
28979
|
+
return;
|
|
28980
|
+
}
|
|
28981
|
+
for (const edge of edges) visit(edge.target, depth + (edges.length > 1 ? 1 : 0), nextPath);
|
|
28982
|
+
};
|
|
28983
|
+
const triggers = graph.nodes.filter((node) => node.kind === "trigger");
|
|
28984
|
+
triggers.forEach((trigger, index) => {
|
|
28985
|
+
if (index > 0) lines.push("");
|
|
28986
|
+
visit(trigger.id, 0, /* @__PURE__ */ new Set());
|
|
28987
|
+
});
|
|
28988
|
+
const orphans = graph.nodes.filter((node) => !reached.has(node.id));
|
|
28989
|
+
if (orphans.length > 0) {
|
|
28990
|
+
if (lines.length > 0) lines.push("");
|
|
28991
|
+
lines.push("Not reachable from any trigger (never runs):");
|
|
28992
|
+
for (const node of orphans) lines.push(` ${describeNode(node, templateNames)}`);
|
|
28993
|
+
}
|
|
28994
|
+
return lines;
|
|
28995
|
+
}
|
|
28996
|
+
function describeNodeRef(node) {
|
|
28997
|
+
return node.name ? `"${node.name}" (${node.id})` : node.id;
|
|
28998
|
+
}
|
|
28999
|
+
function describeNode(node, templateNames) {
|
|
29000
|
+
const prefix = `[${node.kind}${node.disabled ? ", disabled" : ""}]`;
|
|
29001
|
+
const label = node.name ? ` "${node.name}"` : "";
|
|
29002
|
+
const suffix = ` (${node.id})`;
|
|
29003
|
+
switch (node.kind) {
|
|
29004
|
+
case "trigger":
|
|
29005
|
+
return `${prefix}${label} ${describeTrigger(node.config)}${suffix}`;
|
|
29006
|
+
case "action":
|
|
29007
|
+
return `${prefix}${label} ${describeAction(node, templateNames)}${suffix}`;
|
|
29008
|
+
case "condition":
|
|
29009
|
+
return `${prefix}${label} if ${describeCondition2(node.config)}${suffix}`;
|
|
29010
|
+
case "delay":
|
|
29011
|
+
return `${prefix}${label} ${describeDelay(node.config)}${suffix}`;
|
|
29012
|
+
}
|
|
29013
|
+
}
|
|
29014
|
+
function describeTrigger(config) {
|
|
29015
|
+
const eventType = optionalString(config.eventType) ?? "(no event type)";
|
|
29016
|
+
const scopes = [];
|
|
29017
|
+
const scopeOf2 = (key, label) => {
|
|
29018
|
+
const ids = config[key];
|
|
29019
|
+
if (Array.isArray(ids) && ids.length > 0) scopes.push(`${label}: ${ids.join(", ")}`);
|
|
29020
|
+
};
|
|
29021
|
+
scopeOf2("productIds", "products");
|
|
29022
|
+
scopeOf2("formIds", "forms");
|
|
29023
|
+
scopeOf2("tagIds", "tags");
|
|
29024
|
+
if (typeof config.inactiveDays === "number") scopes.push(`inactive ${config.inactiveDays} days`);
|
|
29025
|
+
return scopes.length > 0 ? `${eventType} \u2014 ${scopes.join("; ")}` : eventType;
|
|
29026
|
+
}
|
|
29027
|
+
function isKnownActionType(value) {
|
|
29028
|
+
return typeof value === "string" && value in GRAPH_ACTION_LABELS;
|
|
29029
|
+
}
|
|
29030
|
+
function describeAction(node, templateNames) {
|
|
29031
|
+
const { taskType } = node.config;
|
|
29032
|
+
if (!isKnownActionType(taskType)) {
|
|
29033
|
+
const inner = isRecord2(node.config.action) ? optionalString(node.config.action.type) : void 0;
|
|
29034
|
+
return `${typeof taskType === "string" ? taskType : "action"}${inner ? ` ${inner}` : ""}`;
|
|
29035
|
+
}
|
|
29036
|
+
const label = GRAPH_ACTION_LABELS[taskType];
|
|
29037
|
+
switch (taskType) {
|
|
29038
|
+
case "sendEmail":
|
|
29039
|
+
return `${label} \u2014 ${describeEmail(node, templateNames)}`;
|
|
29040
|
+
case "notifyOwner": {
|
|
29041
|
+
const owner = isRecord2(node.config.ownerNotification) ? node.config.ownerNotification : {};
|
|
29042
|
+
return `${label} \u2014 subject: ${quote(optionalString(owner.subject))} \u2014 body: ${quote(excerptHtml(optionalString(owner.html)))}`;
|
|
29043
|
+
}
|
|
29044
|
+
case "sendSms": {
|
|
29045
|
+
const sms = isRecord2(node.config.sms) ? node.config.sms : {};
|
|
29046
|
+
return `${label} \u2014 text: ${quote(excerptText(optionalString(sms.text)))}`;
|
|
29047
|
+
}
|
|
29048
|
+
case "webhook": {
|
|
29049
|
+
const webhook = isRecord2(node.config.webhook) ? node.config.webhook : {};
|
|
29050
|
+
const method = optionalString(webhook.method) ?? optionalString(webhook.type);
|
|
29051
|
+
return `${label} \u2014 ${method ? `${method.toUpperCase()} ` : ""}${optionalString(webhook.url) ?? "(no url)"}`;
|
|
29052
|
+
}
|
|
29053
|
+
case "publishSocialPost": {
|
|
29054
|
+
const social = isRecord2(node.config.social) ? node.config.social : {};
|
|
29055
|
+
const targets = Array.isArray(social.targets) ? social.targets.join(", ") : "";
|
|
29056
|
+
return `${label}${optionalString(social.mode) ? ` (${social.mode})` : ""}${targets ? ` to ${targets}` : ""} \u2014 text: ${quote(excerptText(optionalString(social.text)))}`;
|
|
29057
|
+
}
|
|
29058
|
+
case "createCommunityPost": {
|
|
29059
|
+
const post = isRecord2(node.config.communityPost) ? node.config.communityPost : {};
|
|
29060
|
+
const title = optionalString(post.title);
|
|
29061
|
+
return `${label} in channel ${optionalString(post.channelId) ?? "?"}${title ? ` \u2014 title: ${quote(title)}` : ""} \u2014 text: ${quote(excerptText(optionalString(post.text)))}`;
|
|
29062
|
+
}
|
|
29063
|
+
case "updateClientTags": {
|
|
29064
|
+
const tags = isRecord2(node.config.clientTags) ? node.config.clientTags : {};
|
|
29065
|
+
const ids = Array.isArray(tags.tagIds) ? tags.tagIds.join(", ") : "";
|
|
29066
|
+
return `${label} \u2014 ${optionalString(tags.mode) ?? "update"} ${ids || "(no tags)"}`;
|
|
29067
|
+
}
|
|
29068
|
+
case "setEmailSubscription": {
|
|
29069
|
+
const sub = isRecord2(node.config.emailSubscription) ? node.config.emailSubscription : {};
|
|
29070
|
+
return `${label} \u2014 ${optionalString(sub.status) ?? "?"}`;
|
|
29071
|
+
}
|
|
29072
|
+
case "requestTestimonial":
|
|
29073
|
+
return label;
|
|
29074
|
+
}
|
|
29075
|
+
}
|
|
29076
|
+
function describeEmail(node, templateNames) {
|
|
29077
|
+
const email = emailConfigOf(node);
|
|
29078
|
+
if (!email) return "(no email config)";
|
|
29079
|
+
if (email.templateId) {
|
|
29080
|
+
const name = templateNames.get(email.templateId) ?? email.templateName ?? null;
|
|
29081
|
+
return `library email ${name ? `${quote(name)} ` : ""}(${email.templateId}) \u2014 sent as saved in the library`;
|
|
29082
|
+
}
|
|
29083
|
+
return `subject: ${quote(email.subject)} \u2014 body: ${quote(excerptHtml(email.html))}`;
|
|
29084
|
+
}
|
|
29085
|
+
function describeCondition2(config) {
|
|
29086
|
+
const rules = Array.isArray(config.rules) ? config.rules.filter(isRecord2) : [];
|
|
29087
|
+
if (rules.length === 0) return "(no rules)";
|
|
29088
|
+
return rules.map(describeRule).join(" AND ");
|
|
29089
|
+
}
|
|
29090
|
+
function describeRule(rule) {
|
|
29091
|
+
const field = optionalString(rule.field) ?? "?";
|
|
29092
|
+
const details = [];
|
|
29093
|
+
for (const key of ["productId", "tagId", "consentName", "days"]) {
|
|
29094
|
+
const value = rule[key];
|
|
29095
|
+
if (typeof value === "string" || typeof value === "number") details.push(`${key}=${value}`);
|
|
29096
|
+
}
|
|
29097
|
+
const detail = details.length > 0 ? ` (${details.join(", ")})` : "";
|
|
29098
|
+
const op = optionalString(rule.op);
|
|
29099
|
+
if (op !== void 0) {
|
|
29100
|
+
const value = Array.isArray(rule.values) ? `[${rule.values.join(", ")}]` : typeof rule.value === "string" || typeof rule.value === "number" ? String(rule.value) : "?";
|
|
29101
|
+
return `${field} ${op} ${value}${detail}`;
|
|
29102
|
+
}
|
|
29103
|
+
if (typeof rule.expected === "boolean") return `${field}${detail} is ${rule.expected}`;
|
|
29104
|
+
return `${field}${detail}`;
|
|
29105
|
+
}
|
|
29106
|
+
function describeDelay(config) {
|
|
29107
|
+
const mode = optionalString(config.mode);
|
|
29108
|
+
switch (mode) {
|
|
29109
|
+
case void 0:
|
|
29110
|
+
case "duration":
|
|
29111
|
+
return `wait ${typeof config.amount === "number" ? config.amount : "?"} ${optionalString(config.unit) ?? ""}`.trimEnd();
|
|
29112
|
+
case "untilWeekday":
|
|
29113
|
+
return `wait until weekday ${typeof config.weekday === "number" ? config.weekday : "?"} at ${optionalString(config.time) ?? "?"}${optionalString(config.timezone) ? ` (${config.timezone})` : ""}`;
|
|
29114
|
+
case "untilDate":
|
|
29115
|
+
return `wait until ${optionalString(config.date) ?? "?"}${optionalString(config.timezone) ? ` (${config.timezone})` : ""}`;
|
|
29116
|
+
case "untilContextDate": {
|
|
29117
|
+
const offset = typeof config.offsetMinutes === "number" ? config.offsetMinutes : 0;
|
|
29118
|
+
return `wait until ${optionalString(config.contextKey) ?? "?"}${offset !== 0 ? ` ${offset > 0 ? "+" : ""}${offset} min` : ""}${config.skipWhenPast === true ? " (skip branch when past)" : ""}`;
|
|
29119
|
+
}
|
|
29120
|
+
default:
|
|
29121
|
+
return `wait (${mode})`;
|
|
29122
|
+
}
|
|
29123
|
+
}
|
|
29124
|
+
function quote(value) {
|
|
29125
|
+
return value !== void 0 && value.length > 0 ? `"${value}"` : "(empty)";
|
|
29126
|
+
}
|
|
29127
|
+
function excerptHtml(html) {
|
|
29128
|
+
if (html === void 0) return void 0;
|
|
29129
|
+
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, "'");
|
|
29130
|
+
return excerptText(text);
|
|
29131
|
+
}
|
|
29132
|
+
function excerptText(text) {
|
|
29133
|
+
if (text === void 0) return void 0;
|
|
29134
|
+
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
29135
|
+
return collapsed.length > EXCERPT_LENGTH ? `${collapsed.slice(0, EXCERPT_LENGTH - 1)}\u2026` : collapsed;
|
|
29136
|
+
}
|
|
27243
29137
|
|
|
27244
29138
|
// src/commands/workspace/index.ts
|
|
27245
|
-
function
|
|
29139
|
+
function parseOnOff5(value, flag) {
|
|
27246
29140
|
const match = ["on", "off"].find((v) => v === value);
|
|
27247
29141
|
if (!match) throw new Error(`Invalid ${flag}: ${value} (expected on | off)`);
|
|
27248
29142
|
return match === "on";
|
|
@@ -27310,7 +29204,7 @@ function registerWorkspaceCommands(program2) {
|
|
|
27310
29204
|
}
|
|
27311
29205
|
if (options.inheritTimezone) input.timezone = null;
|
|
27312
29206
|
if (options.autoTranscribe !== void 0) {
|
|
27313
|
-
input.autoTranscribeUploads =
|
|
29207
|
+
input.autoTranscribeUploads = parseOnOff5(options.autoTranscribe, "--auto-transcribe");
|
|
27314
29208
|
}
|
|
27315
29209
|
if (options.transcriptionLanguage !== void 0) {
|
|
27316
29210
|
input.transcriptionLanguage = parseTranscriptionLanguage(options.transcriptionLanguage);
|
|
@@ -27327,13 +29221,65 @@ function registerWorkspaceCommands(program2) {
|
|
|
27327
29221
|
);
|
|
27328
29222
|
}
|
|
27329
29223
|
|
|
29224
|
+
// src/utils/shortHelp.ts
|
|
29225
|
+
var SHORT_HELP_FLAG = "--short";
|
|
29226
|
+
var SHORT_HELP_DESCRIPTION = "with --help: print only the command names (subcommands indented), no descriptions";
|
|
29227
|
+
var INDENT = " ";
|
|
29228
|
+
function rootOf(command) {
|
|
29229
|
+
let current = command;
|
|
29230
|
+
while (current.parent) current = current.parent;
|
|
29231
|
+
return current;
|
|
29232
|
+
}
|
|
29233
|
+
function rawArgsOf(command) {
|
|
29234
|
+
const root = rootOf(command);
|
|
29235
|
+
if ("rawArgs" in root && Array.isArray(root.rawArgs)) {
|
|
29236
|
+
return root.rawArgs.filter((arg) => typeof arg === "string");
|
|
29237
|
+
}
|
|
29238
|
+
return [];
|
|
29239
|
+
}
|
|
29240
|
+
function isShortHelpRequested(command) {
|
|
29241
|
+
return rawArgsOf(command).includes(SHORT_HELP_FLAG);
|
|
29242
|
+
}
|
|
29243
|
+
function visibleSubcommands(command, helper) {
|
|
29244
|
+
return helper.visibleCommands(command).filter((sub) => command.commands.includes(sub));
|
|
29245
|
+
}
|
|
29246
|
+
function formatShortHelp(command, helper) {
|
|
29247
|
+
const lines = [];
|
|
29248
|
+
const walk = (cmd, depth) => {
|
|
29249
|
+
lines.push(`${INDENT.repeat(depth)}${cmd.name()}`);
|
|
29250
|
+
for (const sub of visibleSubcommands(cmd, helper)) walk(sub, depth + 1);
|
|
29251
|
+
};
|
|
29252
|
+
if (command.parent === null) {
|
|
29253
|
+
for (const sub of visibleSubcommands(command, helper)) walk(sub, 0);
|
|
29254
|
+
} else {
|
|
29255
|
+
walk(command, 0);
|
|
29256
|
+
}
|
|
29257
|
+
return `${lines.join("\n")}
|
|
29258
|
+
`;
|
|
29259
|
+
}
|
|
29260
|
+
function registerOnCommand2(command) {
|
|
29261
|
+
if (!command.options.some((option) => option.long === SHORT_HELP_FLAG)) {
|
|
29262
|
+
command.addOption(new Option(SHORT_HELP_FLAG, SHORT_HELP_DESCRIPTION).hideHelp());
|
|
29263
|
+
}
|
|
29264
|
+
const inherited = command.configureHelp();
|
|
29265
|
+
const previousFormatHelp = inherited.formatHelp;
|
|
29266
|
+
command.configureHelp({
|
|
29267
|
+
...inherited,
|
|
29268
|
+
formatHelp: (cmd, helper) => isShortHelpRequested(cmd) ? formatShortHelp(cmd, helper) : previousFormatHelp ? previousFormatHelp.call(helper, cmd, helper) : Help.prototype.formatHelp.call(helper, cmd, helper)
|
|
29269
|
+
});
|
|
29270
|
+
for (const sub of command.commands) registerOnCommand2(sub);
|
|
29271
|
+
}
|
|
29272
|
+
function applyShortHelp(program2) {
|
|
29273
|
+
registerOnCommand2(program2);
|
|
29274
|
+
}
|
|
29275
|
+
|
|
27330
29276
|
// src/program.ts
|
|
27331
29277
|
function resolveCliVersion() {
|
|
27332
|
-
if ("0.3.
|
|
27333
|
-
return "0.3.
|
|
29278
|
+
if ("0.3.52".length > 0) {
|
|
29279
|
+
return "0.3.52";
|
|
27334
29280
|
}
|
|
27335
29281
|
try {
|
|
27336
|
-
const raw =
|
|
29282
|
+
const raw = readFileSync28(new URL("../package.json", import.meta.url), "utf8");
|
|
27337
29283
|
const parsed = JSON.parse(raw);
|
|
27338
29284
|
if (typeof parsed === "object" && parsed !== null && "version" in parsed) {
|
|
27339
29285
|
const { version: version2 } = parsed;
|
|
@@ -27376,6 +29322,7 @@ Signing in \u2014 two roles, two consent pages:
|
|
|
27376
29322
|
registerProductsCommands(program2);
|
|
27377
29323
|
registerOffersCommands(program2);
|
|
27378
29324
|
registerCheckoutSettingsCommands(program2);
|
|
29325
|
+
registerIntegrationsCommands(program2);
|
|
27379
29326
|
registerSiteCommands(program2);
|
|
27380
29327
|
registerEmailsCommands(program2);
|
|
27381
29328
|
registerOtoCommands(program2);
|
|
@@ -27406,6 +29353,7 @@ Signing in \u2014 two roles, two consent pages:
|
|
|
27406
29353
|
registerSocialCommands(program2);
|
|
27407
29354
|
registerMediaCommands(program2);
|
|
27408
29355
|
applyGlobalOptions(program2);
|
|
29356
|
+
applyShortHelp(program2);
|
|
27409
29357
|
return program2;
|
|
27410
29358
|
}
|
|
27411
29359
|
|