@zanfia/cli 0.3.63 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -52
- package/dist/index.js +627 -170
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6907,8 +6907,15 @@ import { readFileSync as readFileSync28 } from "fs";
|
|
|
6907
6907
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
6908
6908
|
import { homedir } from "os";
|
|
6909
6909
|
import { dirname, join } from "path";
|
|
6910
|
-
var
|
|
6911
|
-
var
|
|
6910
|
+
var CREDENTIALS_STORE_VERSION = 2;
|
|
6911
|
+
var MEMBER_PROFILE_PREFIX = "member:";
|
|
6912
|
+
var FALLBACK_PROFILE_KEY = "default";
|
|
6913
|
+
function configDir() {
|
|
6914
|
+
return process.env.ZANFIA_CONFIG_DIR || join(homedir(), ".config", "zanfia");
|
|
6915
|
+
}
|
|
6916
|
+
function credentialsFilePath() {
|
|
6917
|
+
return join(configDir(), "credentials.json");
|
|
6918
|
+
}
|
|
6912
6919
|
var orUndefined = (value) => value.length > 0 ? value : void 0;
|
|
6913
6920
|
var backendDefaults = {
|
|
6914
6921
|
apiUrl: true ? orUndefined("https://api.zanfia.com") : void 0,
|
|
@@ -6929,24 +6936,110 @@ function resolvePlatformUrl() {
|
|
|
6929
6936
|
const raw = process.env.ZANFIA_PLATFORM_URL || backendDefaults.platformUrl || "https://zanfia.co";
|
|
6930
6937
|
return raw.replace(/\/+$/, "");
|
|
6931
6938
|
}
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6939
|
+
var isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6940
|
+
var isStoredCredentials = (value) => isRecord(value) && typeof value.apiKey === "string";
|
|
6941
|
+
var emptyStore = () => ({
|
|
6942
|
+
version: CREDENTIALS_STORE_VERSION,
|
|
6943
|
+
active: null,
|
|
6944
|
+
profiles: {}
|
|
6945
|
+
});
|
|
6946
|
+
function profileKeyFor(credentials) {
|
|
6947
|
+
const claims = decodeTokenClaims(credentials.apiKey);
|
|
6948
|
+
const kind = credentials.kind ?? claims.kind;
|
|
6949
|
+
const base = credentials.workspaceId ?? claims.workspaceId ?? FALLBACK_PROFILE_KEY;
|
|
6950
|
+
return kind === "member" ? `${MEMBER_PROFILE_PREFIX}${base}` : base;
|
|
6951
|
+
}
|
|
6952
|
+
function parseCredentialsStore(raw) {
|
|
6953
|
+
if (isStoredCredentials(raw)) {
|
|
6954
|
+
const key = profileKeyFor(raw);
|
|
6955
|
+
return { version: CREDENTIALS_STORE_VERSION, active: key, profiles: { [key]: raw } };
|
|
6956
|
+
}
|
|
6957
|
+
if (isRecord(raw) && isRecord(raw.profiles)) {
|
|
6958
|
+
const profiles = {};
|
|
6959
|
+
for (const [key, value] of Object.entries(raw.profiles)) {
|
|
6960
|
+
if (isStoredCredentials(value)) profiles[key] = value;
|
|
6938
6961
|
}
|
|
6939
|
-
|
|
6962
|
+
const keys = Object.keys(profiles);
|
|
6963
|
+
const active = typeof raw.active === "string" && raw.active in profiles ? raw.active : keys[0] ?? null;
|
|
6964
|
+
return { version: CREDENTIALS_STORE_VERSION, active, profiles };
|
|
6965
|
+
}
|
|
6966
|
+
return emptyStore();
|
|
6967
|
+
}
|
|
6968
|
+
function readCredentialsStore() {
|
|
6969
|
+
try {
|
|
6970
|
+
return parseCredentialsStore(JSON.parse(readFileSync(credentialsFilePath(), "utf8")));
|
|
6940
6971
|
} catch {
|
|
6941
|
-
return
|
|
6972
|
+
return emptyStore();
|
|
6942
6973
|
}
|
|
6943
6974
|
}
|
|
6975
|
+
function writeCredentialsStore(store) {
|
|
6976
|
+
const path2 = credentialsFilePath();
|
|
6977
|
+
mkdirSync(dirname(path2), { recursive: true, mode: 448 });
|
|
6978
|
+
writeFileSync(path2, JSON.stringify(store, null, 2), { mode: 384 });
|
|
6979
|
+
return path2;
|
|
6980
|
+
}
|
|
6981
|
+
function readCredentials() {
|
|
6982
|
+
const store = readCredentialsStore();
|
|
6983
|
+
return store.active ? store.profiles[store.active] ?? null : null;
|
|
6984
|
+
}
|
|
6944
6985
|
function writeCredentials(credentials) {
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6986
|
+
const store = readCredentialsStore();
|
|
6987
|
+
const key = profileKeyFor(credentials);
|
|
6988
|
+
store.profiles[key] = credentials;
|
|
6989
|
+
store.active = key;
|
|
6990
|
+
return writeCredentialsStore(store);
|
|
6991
|
+
}
|
|
6992
|
+
function listStoredProfiles() {
|
|
6993
|
+
const store = readCredentialsStore();
|
|
6994
|
+
return Object.entries(store.profiles).map(([key, credentials]) => ({ key, credentials, active: key === store.active })).sort((a, b) => {
|
|
6995
|
+
if (a.active !== b.active) return a.active ? -1 : 1;
|
|
6996
|
+
return (a.credentials.workspaceName ?? a.key).localeCompare(
|
|
6997
|
+
b.credentials.workspaceName ?? b.key
|
|
6998
|
+
);
|
|
6948
6999
|
});
|
|
6949
|
-
|
|
7000
|
+
}
|
|
7001
|
+
function findStoredProfile(store, ref) {
|
|
7002
|
+
const wanted = ref.trim().toLowerCase();
|
|
7003
|
+
if (!wanted) return null;
|
|
7004
|
+
const match = Object.entries(store.profiles).find(
|
|
7005
|
+
([key, credentials]) => [key, credentials.workspaceId, credentials.workspaceSlug, credentials.workspaceName].some(
|
|
7006
|
+
(candidate) => typeof candidate === "string" && candidate.toLowerCase() === wanted
|
|
7007
|
+
)
|
|
7008
|
+
);
|
|
7009
|
+
return match ? { key: match[0], credentials: match[1], active: match[0] === store.active } : null;
|
|
7010
|
+
}
|
|
7011
|
+
var StoredWorkspaceNotFoundError = class extends Error {
|
|
7012
|
+
constructor(ref, available) {
|
|
7013
|
+
super(
|
|
7014
|
+
`No stored login for workspace "${ref}".` + (available.length > 0 ? ` Stored: ${available.join(", ")}.` : "") + ` Log in to it with: zanfia login --workspace ${ref}`
|
|
7015
|
+
);
|
|
7016
|
+
this.name = "StoredWorkspaceNotFoundError";
|
|
7017
|
+
}
|
|
7018
|
+
};
|
|
7019
|
+
var profileLabel = (profile) => profile.credentials.workspaceSlug ?? profile.credentials.workspaceId ?? profile.key;
|
|
7020
|
+
function useStoredProfile(ref) {
|
|
7021
|
+
const store = readCredentialsStore();
|
|
7022
|
+
const profile = findStoredProfile(store, ref);
|
|
7023
|
+
if (!profile) {
|
|
7024
|
+
throw new StoredWorkspaceNotFoundError(
|
|
7025
|
+
ref,
|
|
7026
|
+
Object.entries(store.profiles).map(([key, credentials]) => profileLabel({ key, credentials }))
|
|
7027
|
+
);
|
|
7028
|
+
}
|
|
7029
|
+
store.active = profile.key;
|
|
7030
|
+
writeCredentialsStore(store);
|
|
7031
|
+
return { ...profile, active: true };
|
|
7032
|
+
}
|
|
7033
|
+
function apiKeyForStoredWorkspace(ref) {
|
|
7034
|
+
const store = readCredentialsStore();
|
|
7035
|
+
const profile = findStoredProfile(store, ref);
|
|
7036
|
+
if (!profile) {
|
|
7037
|
+
throw new StoredWorkspaceNotFoundError(
|
|
7038
|
+
ref,
|
|
7039
|
+
Object.entries(store.profiles).map(([key, credentials]) => profileLabel({ key, credentials }))
|
|
7040
|
+
);
|
|
7041
|
+
}
|
|
7042
|
+
return profile.credentials.apiKey;
|
|
6950
7043
|
}
|
|
6951
7044
|
function resolveApiKey(explicit) {
|
|
6952
7045
|
if (explicit) return explicit;
|
|
@@ -6995,15 +7088,19 @@ function isMockMode() {
|
|
|
6995
7088
|
}
|
|
6996
7089
|
return resolveApiUrl() === null;
|
|
6997
7090
|
}
|
|
6998
|
-
function
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
return
|
|
7091
|
+
function removeStoredLogin(options = {}) {
|
|
7092
|
+
const store = readCredentialsStore();
|
|
7093
|
+
const removed = store.active ? store.profiles[store.active] ?? null : null;
|
|
7094
|
+
if (options.all || Object.keys(store.profiles).length <= 1) {
|
|
7095
|
+
rmSync(credentialsFilePath(), { force: true });
|
|
7096
|
+
return { removed, nowActive: null };
|
|
7004
7097
|
}
|
|
7098
|
+
if (store.active) delete store.profiles[store.active];
|
|
7099
|
+
const [nextKey] = Object.entries(store.profiles).sort(([, a], [, b]) => b.savedAt.localeCompare(a.savedAt)).map(([key]) => key);
|
|
7100
|
+
store.active = nextKey ?? null;
|
|
7101
|
+
writeCredentialsStore(store);
|
|
7102
|
+
return { removed, nowActive: nextKey ? store.profiles[nextKey] ?? null : null };
|
|
7005
7103
|
}
|
|
7006
|
-
var credentialsPath = CREDENTIALS_FILE;
|
|
7007
7104
|
|
|
7008
7105
|
// src/utils/errors.ts
|
|
7009
7106
|
var ApiError = class extends Error {
|
|
@@ -7671,6 +7768,9 @@ function createMockClient() {
|
|
|
7671
7768
|
if (!result) throw new ApiError(404, `Product ${id} not found`);
|
|
7672
7769
|
return result;
|
|
7673
7770
|
},
|
|
7771
|
+
async deleteProduct() {
|
|
7772
|
+
throw new ApiError(501, "product delete is not available in mock mode");
|
|
7773
|
+
},
|
|
7674
7774
|
async setProductStatus() {
|
|
7675
7775
|
throw new ApiError(501, "product status changes are not available in mock mode");
|
|
7676
7776
|
},
|
|
@@ -8343,6 +8443,12 @@ function createMockClient() {
|
|
|
8343
8443
|
async cancelEmailCampaign() {
|
|
8344
8444
|
throw new ApiError(501, "email campaigns are not available in mock mode");
|
|
8345
8445
|
},
|
|
8446
|
+
async rescheduleEmailCampaign() {
|
|
8447
|
+
throw new ApiError(501, "email campaigns are not available in mock mode");
|
|
8448
|
+
},
|
|
8449
|
+
async sendEmailCampaignTest() {
|
|
8450
|
+
throw new ApiError(501, "email campaigns are not available in mock mode");
|
|
8451
|
+
},
|
|
8346
8452
|
async resendEmailCampaign() {
|
|
8347
8453
|
throw new ApiError(501, "email campaigns are not available in mock mode");
|
|
8348
8454
|
},
|
|
@@ -8511,6 +8617,9 @@ function createMockClient() {
|
|
|
8511
8617
|
async removeDownloadFile() {
|
|
8512
8618
|
throw new ApiError(501, "downloads content is not available in mock mode");
|
|
8513
8619
|
},
|
|
8620
|
+
async renameDownloadFile() {
|
|
8621
|
+
throw new ApiError(501, "downloads content is not available in mock mode");
|
|
8622
|
+
},
|
|
8514
8623
|
async reorderDownloadFiles() {
|
|
8515
8624
|
throw new ApiError(501, "downloads content is not available in mock mode");
|
|
8516
8625
|
},
|
|
@@ -9042,6 +9151,7 @@ function createHttpClient(config) {
|
|
|
9042
9151
|
createProduct: (input) => request("POST", "/products", input),
|
|
9043
9152
|
updateProduct: (id, input) => request("PATCH", `/products/${encodeURIComponent(id)}`, input),
|
|
9044
9153
|
archiveProduct: (id) => request("POST", `/products/${encodeURIComponent(id)}/archive`),
|
|
9154
|
+
deleteProduct: (id) => request("DELETE", `/products/${encodeURIComponent(id)}`),
|
|
9045
9155
|
setProductStatus: (id, status) => request("POST", `/products/${encodeURIComponent(id)}/status`, { status }),
|
|
9046
9156
|
listProductPrices: (productId) => request("GET", `/products/${encodeURIComponent(productId)}/prices`),
|
|
9047
9157
|
createProductPrice: (productId, input) => request("POST", `/products/${encodeURIComponent(productId)}/prices`, input),
|
|
@@ -9120,6 +9230,8 @@ function createHttpClient(config) {
|
|
|
9120
9230
|
if (options?.limit !== void 0) params.set("limit", String(options.limit));
|
|
9121
9231
|
if (options?.cursor) params.set("cursor", options.cursor);
|
|
9122
9232
|
if (options?.products) params.set("products", options.products);
|
|
9233
|
+
if (options?.createdFrom) params.set("createdFrom", options.createdFrom);
|
|
9234
|
+
if (options?.createdTo) params.set("createdTo", options.createdTo);
|
|
9123
9235
|
const qs = params.toString();
|
|
9124
9236
|
return request("GET", `/clients${qs ? `?${qs}` : ""}`);
|
|
9125
9237
|
},
|
|
@@ -9535,6 +9647,12 @@ function createHttpClient(config) {
|
|
|
9535
9647
|
scheduledAt !== void 0 ? { scheduledAt } : {}
|
|
9536
9648
|
),
|
|
9537
9649
|
cancelEmailCampaign: (id) => request("POST", `/campaigns/${encodeURIComponent(id)}/cancel`),
|
|
9650
|
+
rescheduleEmailCampaign: (id, scheduledAt) => request("POST", `/campaigns/${encodeURIComponent(id)}/reschedule`, { scheduledAt }),
|
|
9651
|
+
sendEmailCampaignTest: (id, recipients) => request(
|
|
9652
|
+
"POST",
|
|
9653
|
+
`/campaigns/${encodeURIComponent(id)}/test-sends`,
|
|
9654
|
+
recipients && recipients.length > 0 ? { recipients } : {}
|
|
9655
|
+
),
|
|
9538
9656
|
resendEmailCampaign: (id, target) => request("POST", `/campaigns/${encodeURIComponent(id)}/resend`, target ? { target } : {}),
|
|
9539
9657
|
listCampaignRecipients: (id, options) => {
|
|
9540
9658
|
const params = new URLSearchParams();
|
|
@@ -9661,6 +9779,11 @@ function createHttpClient(config) {
|
|
|
9661
9779
|
"DELETE",
|
|
9662
9780
|
`/products/${encodeURIComponent(productId)}/downloads/files/${encodeURIComponent(uid)}`
|
|
9663
9781
|
),
|
|
9782
|
+
renameDownloadFile: (productId, uid, name) => request(
|
|
9783
|
+
"PATCH",
|
|
9784
|
+
`/products/${encodeURIComponent(productId)}/downloads/files/${encodeURIComponent(uid)}`,
|
|
9785
|
+
{ name }
|
|
9786
|
+
),
|
|
9664
9787
|
reorderDownloadFiles: (productId, uids) => request("POST", `/products/${encodeURIComponent(productId)}/downloads/files/reorder`, {
|
|
9665
9788
|
uids
|
|
9666
9789
|
}),
|
|
@@ -10018,6 +10141,22 @@ function parseWorkflowKind(value) {
|
|
|
10018
10141
|
}
|
|
10019
10142
|
return kind;
|
|
10020
10143
|
}
|
|
10144
|
+
var STOREFRONT_VISIBILITIES = [
|
|
10145
|
+
"public",
|
|
10146
|
+
"unlisted",
|
|
10147
|
+
"private"
|
|
10148
|
+
];
|
|
10149
|
+
function parseStorefrontVisibility(value) {
|
|
10150
|
+
const visibility = STOREFRONT_VISIBILITIES.find(
|
|
10151
|
+
(candidate) => candidate === value.trim().toLowerCase()
|
|
10152
|
+
);
|
|
10153
|
+
if (visibility === void 0) {
|
|
10154
|
+
throw new Error(
|
|
10155
|
+
`Invalid --visibility: ${value} (expected one of: ${STOREFRONT_VISIBILITIES.join(", ")})`
|
|
10156
|
+
);
|
|
10157
|
+
}
|
|
10158
|
+
return visibility;
|
|
10159
|
+
}
|
|
10021
10160
|
function parseCommaList(flag) {
|
|
10022
10161
|
return (value) => {
|
|
10023
10162
|
const items = [
|
|
@@ -10057,6 +10196,8 @@ function requireConfirm(confirm, what) {
|
|
|
10057
10196
|
var OUTPUT_FORMAT_JSON = "json";
|
|
10058
10197
|
var API_KEY_FLAG = "--api-key <key>";
|
|
10059
10198
|
var API_KEY_DESCRIPTION = "override stored API key for this invocation";
|
|
10199
|
+
var WORKSPACE_FLAG = "--workspace <id|slug>";
|
|
10200
|
+
var WORKSPACE_DESCRIPTION = "use the stored login of this workspace for this invocation (see `workspace list`)";
|
|
10060
10201
|
var VERBOSE_FLAG = "--verbose";
|
|
10061
10202
|
var VERBOSE_DESCRIPTION = "log request/response debug info";
|
|
10062
10203
|
var JSON_FLAG = "--json";
|
|
@@ -10076,6 +10217,7 @@ function registerOnCommand(command) {
|
|
|
10076
10217
|
command.enablePositionalOptions();
|
|
10077
10218
|
}
|
|
10078
10219
|
addOptionIfMissing(command, new Option(API_KEY_FLAG, API_KEY_DESCRIPTION).hideHelp());
|
|
10220
|
+
addOptionIfMissing(command, new Option(WORKSPACE_FLAG, WORKSPACE_DESCRIPTION).hideHelp());
|
|
10079
10221
|
addOptionIfMissing(command, new Option(VERBOSE_FLAG, VERBOSE_DESCRIPTION).hideHelp());
|
|
10080
10222
|
addOptionIfMissing(
|
|
10081
10223
|
command,
|
|
@@ -10087,13 +10229,14 @@ function registerOnCommand(command) {
|
|
|
10087
10229
|
function applyGlobalOptions(program2) {
|
|
10088
10230
|
program2.enablePositionalOptions();
|
|
10089
10231
|
program2.option(API_KEY_FLAG, API_KEY_DESCRIPTION);
|
|
10232
|
+
program2.option(WORKSPACE_FLAG, WORKSPACE_DESCRIPTION);
|
|
10090
10233
|
program2.option(JSON_FLAG, JSON_DESCRIPTION);
|
|
10091
10234
|
program2.option(VERBOSE_FLAG, VERBOSE_DESCRIPTION);
|
|
10092
10235
|
for (const sub of program2.commands) registerOnCommand(sub);
|
|
10093
10236
|
}
|
|
10094
10237
|
|
|
10095
10238
|
// src/utils/output.ts
|
|
10096
|
-
function
|
|
10239
|
+
function isRecord2(value) {
|
|
10097
10240
|
return value !== null && typeof value === "object";
|
|
10098
10241
|
}
|
|
10099
10242
|
function getGlobalFlags(command) {
|
|
@@ -10104,11 +10247,15 @@ function getGlobalFlags(command) {
|
|
|
10104
10247
|
const flags = {};
|
|
10105
10248
|
for (const node of chain) {
|
|
10106
10249
|
const raw = node.opts();
|
|
10107
|
-
if (!
|
|
10250
|
+
if (!isRecord2(raw)) continue;
|
|
10108
10251
|
if (typeof raw.apiKey === "string") flags.apiKey = raw.apiKey;
|
|
10252
|
+
if (typeof raw.workspace === "string") flags.workspace = raw.workspace;
|
|
10109
10253
|
if (raw.verbose === true) flags.verbose = true;
|
|
10110
10254
|
if (raw.json === true || raw.output === OUTPUT_FORMAT_JSON) flags.json = true;
|
|
10111
10255
|
}
|
|
10256
|
+
if (flags.workspace && !flags.apiKey && !process.env.ZANFIA_API_KEY) {
|
|
10257
|
+
flags.apiKey = apiKeyForStoredWorkspace(flags.workspace);
|
|
10258
|
+
}
|
|
10112
10259
|
return flags;
|
|
10113
10260
|
}
|
|
10114
10261
|
function emit(flags, data, humanRenderer) {
|
|
@@ -10124,10 +10271,10 @@ function printJson(data) {
|
|
|
10124
10271
|
`);
|
|
10125
10272
|
}
|
|
10126
10273
|
function tableRowsOf(result, key) {
|
|
10127
|
-
if (!
|
|
10274
|
+
if (!isRecord2(result)) return [];
|
|
10128
10275
|
const rows = result[key];
|
|
10129
10276
|
if (!Array.isArray(rows)) return [];
|
|
10130
|
-
return rows.filter(
|
|
10277
|
+
return rows.filter(isRecord2);
|
|
10131
10278
|
}
|
|
10132
10279
|
function printTable(rows, columns) {
|
|
10133
10280
|
if (rows.length === 0) {
|
|
@@ -11150,16 +11297,93 @@ More ads available \u2014 re-run with --after ${result.after}
|
|
|
11150
11297
|
}
|
|
11151
11298
|
|
|
11152
11299
|
// src/commands/auth/index.ts
|
|
11300
|
+
function keySource(flags, rawApiKey) {
|
|
11301
|
+
if (typeof rawApiKey === "string") return "--api-key";
|
|
11302
|
+
if (process.env.ZANFIA_API_KEY) return "$ZANFIA_API_KEY";
|
|
11303
|
+
if (flags.workspace) return `stored login of --workspace ${flags.workspace}`;
|
|
11304
|
+
return "active stored login";
|
|
11305
|
+
}
|
|
11306
|
+
async function runWhoami(command) {
|
|
11307
|
+
const flags = getGlobalFlags(command);
|
|
11308
|
+
const rawApiKey = command.optsWithGlobals().apiKey;
|
|
11309
|
+
const logins = listStoredProfiles().length;
|
|
11310
|
+
const apiKey = resolveApiKey(flags.apiKey);
|
|
11311
|
+
if (decodeTokenKind(apiKey) === "member") {
|
|
11312
|
+
const stored = readCredentials();
|
|
11313
|
+
const claims = decodeTokenClaims(apiKey);
|
|
11314
|
+
const workspaceId = stored?.workspaceId ?? claims.workspaceId ?? "?";
|
|
11315
|
+
const info2 = {
|
|
11316
|
+
role: "member",
|
|
11317
|
+
workspaceId,
|
|
11318
|
+
workspaceName: stored?.workspaceName ?? null,
|
|
11319
|
+
userEmail: stored?.userEmail ?? null,
|
|
11320
|
+
uid: claims.subject ?? null,
|
|
11321
|
+
scopes: ["communities:read", "communities:write"],
|
|
11322
|
+
communityApiUrl: resolveCommunityApiUrl()
|
|
11323
|
+
};
|
|
11324
|
+
emit(flags, info2, () => {
|
|
11325
|
+
process.stdout.write(`Role: community member
|
|
11326
|
+
`);
|
|
11327
|
+
process.stdout.write(
|
|
11328
|
+
`Workspace: ${info2.workspaceName ?? "(unknown)"} (${info2.workspaceId})
|
|
11329
|
+
`
|
|
11330
|
+
);
|
|
11331
|
+
if (info2.userEmail) process.stdout.write(`User: ${info2.userEmail}
|
|
11332
|
+
`);
|
|
11333
|
+
else if (info2.uid) process.stdout.write(`User id: ${info2.uid}
|
|
11334
|
+
`);
|
|
11335
|
+
process.stdout.write(`Scopes: ${info2.scopes.join(", ")}
|
|
11336
|
+
`);
|
|
11337
|
+
process.stdout.write(`Community API: ${info2.communityApiUrl ?? "<unresolved>"}
|
|
11338
|
+
`);
|
|
11339
|
+
});
|
|
11340
|
+
return;
|
|
11341
|
+
}
|
|
11342
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11343
|
+
const info = await client.whoami();
|
|
11344
|
+
const source = keySource(flags, rawApiKey);
|
|
11345
|
+
emit(flags, { ...info, keySource: source, storedLogins: logins }, () => {
|
|
11346
|
+
const modeTag = isMockMode() ? " (mock)" : "";
|
|
11347
|
+
process.stdout.write(`Workspace: ${info.workspaceName}${modeTag}
|
|
11348
|
+
`);
|
|
11349
|
+
process.stdout.write(`Workspace ID: ${info.workspaceId}
|
|
11350
|
+
`);
|
|
11351
|
+
process.stdout.write(`User: ${info.userEmail}
|
|
11352
|
+
`);
|
|
11353
|
+
process.stdout.write(`Scopes: ${info.scopes.join(", ")}
|
|
11354
|
+
`);
|
|
11355
|
+
process.stdout.write(`Key: ${source}
|
|
11356
|
+
`);
|
|
11357
|
+
if (logins > 1) {
|
|
11358
|
+
process.stdout.write(
|
|
11359
|
+
`Logins on this device: ${logins} (\`workspace list\`, switch with \`workspace use <id|slug>\`)
|
|
11360
|
+
`
|
|
11361
|
+
);
|
|
11362
|
+
}
|
|
11363
|
+
});
|
|
11364
|
+
}
|
|
11153
11365
|
function registerAuthCommands(program2) {
|
|
11154
11366
|
const auth = program2.command("auth").description("Manage CLI authentication");
|
|
11367
|
+
program2.command("whoami").description("Show the active workspace and identity (same as `auth whoami`)").action(async (_, command) => runWhoami(command));
|
|
11155
11368
|
auth.command("status").description("Show what the CLI currently has stored (path, source, expiry, api URL).").action(() => {
|
|
11156
11369
|
const stored = readCredentials();
|
|
11157
11370
|
const apiKey = resolveApiKey(void 0);
|
|
11158
11371
|
const apiUrl = resolveApiUrl();
|
|
11159
11372
|
const envKey = process.env.ZANFIA_API_KEY ? "(from $ZANFIA_API_KEY)" : "";
|
|
11160
11373
|
const envUrl = process.env.ZANFIA_API_URL ? "(from $ZANFIA_API_URL)" : "";
|
|
11161
|
-
|
|
11374
|
+
const profiles = listStoredProfiles();
|
|
11375
|
+
process.stdout.write(`Credentials file: ${credentialsFilePath()}
|
|
11376
|
+
`);
|
|
11377
|
+
if (profiles.length > 1) {
|
|
11378
|
+
process.stdout.write(`Stored logins: ${profiles.length} (active one below)
|
|
11162
11379
|
`);
|
|
11380
|
+
for (const profile of profiles) {
|
|
11381
|
+
process.stdout.write(
|
|
11382
|
+
` ${profile.active ? "*" : " "} ${profile.credentials.workspaceName ?? "?"} (${profile.key})
|
|
11383
|
+
`
|
|
11384
|
+
);
|
|
11385
|
+
}
|
|
11386
|
+
}
|
|
11163
11387
|
if (!stored) {
|
|
11164
11388
|
process.stdout.write(` status: no credentials file found
|
|
11165
11389
|
`);
|
|
@@ -11236,54 +11460,7 @@ Resolved by next call:
|
|
|
11236
11460
|
);
|
|
11237
11461
|
}
|
|
11238
11462
|
});
|
|
11239
|
-
auth.command("whoami").description("Show the authenticated identity (creator workspace, or community member)").action(async (_, command) =>
|
|
11240
|
-
const flags = getGlobalFlags(command);
|
|
11241
|
-
const apiKey = resolveApiKey(flags.apiKey);
|
|
11242
|
-
if (decodeTokenKind(apiKey) === "member") {
|
|
11243
|
-
const stored = readCredentials();
|
|
11244
|
-
const claims = decodeTokenClaims(apiKey);
|
|
11245
|
-
const workspaceId = stored?.workspaceId ?? claims.workspaceId ?? "?";
|
|
11246
|
-
const info2 = {
|
|
11247
|
-
role: "member",
|
|
11248
|
-
workspaceId,
|
|
11249
|
-
workspaceName: stored?.workspaceName ?? null,
|
|
11250
|
-
userEmail: stored?.userEmail ?? null,
|
|
11251
|
-
uid: claims.subject ?? null,
|
|
11252
|
-
scopes: ["communities:read", "communities:write"],
|
|
11253
|
-
communityApiUrl: resolveCommunityApiUrl()
|
|
11254
|
-
};
|
|
11255
|
-
emit(flags, info2, () => {
|
|
11256
|
-
process.stdout.write(`Role: community member
|
|
11257
|
-
`);
|
|
11258
|
-
process.stdout.write(
|
|
11259
|
-
`Workspace: ${info2.workspaceName ?? "(unknown)"} (${info2.workspaceId})
|
|
11260
|
-
`
|
|
11261
|
-
);
|
|
11262
|
-
if (info2.userEmail) process.stdout.write(`User: ${info2.userEmail}
|
|
11263
|
-
`);
|
|
11264
|
-
else if (info2.uid) process.stdout.write(`User id: ${info2.uid}
|
|
11265
|
-
`);
|
|
11266
|
-
process.stdout.write(`Scopes: ${info2.scopes.join(", ")}
|
|
11267
|
-
`);
|
|
11268
|
-
process.stdout.write(`Community API: ${info2.communityApiUrl ?? "<unresolved>"}
|
|
11269
|
-
`);
|
|
11270
|
-
});
|
|
11271
|
-
return;
|
|
11272
|
-
}
|
|
11273
|
-
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
11274
|
-
const info = await client.whoami();
|
|
11275
|
-
emit(flags, info, () => {
|
|
11276
|
-
const modeTag = isMockMode() ? " (mock)" : "";
|
|
11277
|
-
process.stdout.write(`Workspace: ${info.workspaceName}${modeTag}
|
|
11278
|
-
`);
|
|
11279
|
-
process.stdout.write(`Workspace ID: ${info.workspaceId}
|
|
11280
|
-
`);
|
|
11281
|
-
process.stdout.write(`User: ${info.userEmail}
|
|
11282
|
-
`);
|
|
11283
|
-
process.stdout.write(`Scopes: ${info.scopes.join(", ")}
|
|
11284
|
-
`);
|
|
11285
|
-
});
|
|
11286
|
-
});
|
|
11463
|
+
auth.command("whoami").description("Show the authenticated identity (creator workspace, or community member)").action(async (_, command) => runWhoami(command));
|
|
11287
11464
|
}
|
|
11288
11465
|
|
|
11289
11466
|
// src/commands/auth/login.ts
|
|
@@ -11482,25 +11659,54 @@ async function postJson(url, body) {
|
|
|
11482
11659
|
}
|
|
11483
11660
|
return JSON.parse(text);
|
|
11484
11661
|
}
|
|
11662
|
+
function buildCreatorConsentParams(input) {
|
|
11663
|
+
return {
|
|
11664
|
+
redirect_uri: input.redirectUri,
|
|
11665
|
+
state: input.state,
|
|
11666
|
+
code_challenge: input.challenge,
|
|
11667
|
+
code_challenge_method: "S256",
|
|
11668
|
+
hostname: input.hostname,
|
|
11669
|
+
client: "zanfia-cli",
|
|
11670
|
+
...input.workspace?.trim() ? { workspace: input.workspace.trim() } : null
|
|
11671
|
+
};
|
|
11672
|
+
}
|
|
11673
|
+
function buildRegisterUrl(consentUrl, email) {
|
|
11674
|
+
const consent = new URL(consentUrl);
|
|
11675
|
+
const continuePath = `${consent.pathname}${consent.search}`;
|
|
11676
|
+
const register = new URL("/auth/register", consent.origin);
|
|
11677
|
+
register.searchParams.set("continue", continuePath);
|
|
11678
|
+
register.searchParams.set("next", continuePath);
|
|
11679
|
+
if (email) register.searchParams.set("email", email);
|
|
11680
|
+
return register.toString();
|
|
11681
|
+
}
|
|
11485
11682
|
async function runLogin(options) {
|
|
11683
|
+
await runBrowserAuth(options, (consentUrl) => consentUrl);
|
|
11684
|
+
}
|
|
11685
|
+
async function runBrowserRegister(options) {
|
|
11686
|
+
await runBrowserAuth(options, (consentUrl) => buildRegisterUrl(consentUrl, options.email));
|
|
11687
|
+
}
|
|
11688
|
+
async function runBrowserAuth(options, entryUrl) {
|
|
11486
11689
|
const urls = resolveUrls(options.apiUrl);
|
|
11487
11690
|
const state = randomBytes(32).toString("base64url");
|
|
11488
11691
|
const { verifier, challenge } = generatePkce();
|
|
11489
11692
|
const host = safeHostname();
|
|
11490
11693
|
const server = await startLoopbackServer();
|
|
11491
11694
|
const redirectUri = `http://127.0.0.1:${server.port}${CALLBACK_PATH}`;
|
|
11492
|
-
const consentUrl = buildConsentUrl(
|
|
11493
|
-
|
|
11494
|
-
|
|
11495
|
-
|
|
11496
|
-
|
|
11497
|
-
|
|
11498
|
-
|
|
11499
|
-
|
|
11500
|
-
|
|
11695
|
+
const consentUrl = buildConsentUrl(
|
|
11696
|
+
urls.loginUrl,
|
|
11697
|
+
buildCreatorConsentParams({
|
|
11698
|
+
redirectUri,
|
|
11699
|
+
state,
|
|
11700
|
+
challenge,
|
|
11701
|
+
hostname: host,
|
|
11702
|
+
workspace: options.workspace
|
|
11703
|
+
})
|
|
11704
|
+
);
|
|
11705
|
+
const openUrl = entryUrl(consentUrl);
|
|
11706
|
+
process.stdout.write(`Opening ${openUrl}
|
|
11501
11707
|
`);
|
|
11502
11708
|
process.stdout.write("Waiting for browser callback...\n\n");
|
|
11503
|
-
const opened = await openBrowser(
|
|
11709
|
+
const opened = await openBrowser(openUrl);
|
|
11504
11710
|
if (!opened) {
|
|
11505
11711
|
process.stdout.write("Could not open browser automatically \u2014 paste the URL above.\n\n");
|
|
11506
11712
|
}
|
|
@@ -11524,14 +11730,24 @@ async function runLogin(options) {
|
|
|
11524
11730
|
expiresAt: exchanged.expiresAt,
|
|
11525
11731
|
userEmail: exchanged.userEmail,
|
|
11526
11732
|
workspaceId: exchanged.workspaceId,
|
|
11527
|
-
workspaceName: exchanged.workspaceName
|
|
11733
|
+
workspaceName: exchanged.workspaceName,
|
|
11734
|
+
workspaceSlug: exchanged.workspaceSlug ?? null
|
|
11528
11735
|
});
|
|
11529
11736
|
process.stdout.write(`Logged in as ${exchanged.userEmail}.
|
|
11530
11737
|
`);
|
|
11531
|
-
process.stdout.write(
|
|
11532
|
-
`)
|
|
11738
|
+
process.stdout.write(
|
|
11739
|
+
`Workspace: ${exchanged.workspaceName} (${exchanged.workspaceId}) - now active
|
|
11740
|
+
`
|
|
11741
|
+
);
|
|
11533
11742
|
process.stdout.write(`Credentials stored at ${stored}
|
|
11534
11743
|
`);
|
|
11744
|
+
const logins = listStoredProfiles().length;
|
|
11745
|
+
if (logins > 1) {
|
|
11746
|
+
process.stdout.write(
|
|
11747
|
+
`Logins on this device: ${logins}. Switch with \`zanfia workspace use <id|slug>\` (see \`zanfia workspace list\`).
|
|
11748
|
+
`
|
|
11749
|
+
);
|
|
11750
|
+
}
|
|
11535
11751
|
if (!urls.apiUrl) {
|
|
11536
11752
|
process.stdout.write(
|
|
11537
11753
|
"\nTip: ZANFIA_API_URL is not set. Pass --api-url <url> or export the env var so commands like `zanfia products list` know where to call.\n"
|
|
@@ -11644,6 +11860,9 @@ function registerLoginCommand(auth) {
|
|
|
11644
11860
|
auth.command("login").description(
|
|
11645
11861
|
"Open a browser to authorize the CLI via OAuth (pass --address to sign in as a community member)"
|
|
11646
11862
|
).option("--api-url <url>", "API URL to associate with the resulting token").option(
|
|
11863
|
+
"--workspace <id|slug>",
|
|
11864
|
+
"preselect this workspace on the consent page (you can still pick another there)"
|
|
11865
|
+
).option(
|
|
11647
11866
|
"--address <url>",
|
|
11648
11867
|
"sign in as a community member (Community API) against this creator platform URL"
|
|
11649
11868
|
).option("-v, --verbose", "show HTTP request details").addHelpText(
|
|
@@ -11652,9 +11871,13 @@ function registerLoginCommand(auth) {
|
|
|
11652
11871
|
The two flows use different consent pages:
|
|
11653
11872
|
|
|
11654
11873
|
Creator (workspace) login \u2014 full Workspace API:
|
|
11655
|
-
login
|
|
11874
|
+
login [--workspace <id|slug>]
|
|
11656
11875
|
Opens the consent page in the Zanfia dashboard. Sign in with your
|
|
11657
|
-
creator account and approve; the CLI
|
|
11876
|
+
creator account, pick the workspace (Owner or Admin) and approve; the CLI
|
|
11877
|
+
gets a token for that workspace. Each workspace keeps its own login on
|
|
11878
|
+
this device: \`workspace list\` shows them, \`workspace use <id|slug>\`
|
|
11879
|
+
switches without logging in again, and \`--workspace <id|slug>\` on any
|
|
11880
|
+
command uses another stored login just for that command.
|
|
11658
11881
|
|
|
11659
11882
|
Community member login \u2014 Community API only:
|
|
11660
11883
|
login --address https://<creator-platform-url>
|
|
@@ -11678,14 +11901,27 @@ The two flows use different consent pages:
|
|
|
11678
11901
|
await runLogin(options);
|
|
11679
11902
|
});
|
|
11680
11903
|
}
|
|
11681
|
-
function registerLogoutCommand(auth
|
|
11682
|
-
auth.command("logout").description(
|
|
11683
|
-
|
|
11684
|
-
|
|
11685
|
-
|
|
11686
|
-
|
|
11687
|
-
} else {
|
|
11904
|
+
function registerLogoutCommand(auth) {
|
|
11905
|
+
auth.command("logout").description(
|
|
11906
|
+
"Forget the active stored login (--all forgets every workspace login on this device)"
|
|
11907
|
+
).option("--all", "remove every stored login").action((options) => {
|
|
11908
|
+
const { removed, nowActive } = removeStoredLogin({ all: options.all });
|
|
11909
|
+
if (!removed && !options.all) {
|
|
11688
11910
|
process.stdout.write("No stored credentials to remove.\n");
|
|
11911
|
+
return;
|
|
11912
|
+
}
|
|
11913
|
+
if (options.all) {
|
|
11914
|
+
process.stdout.write("Removed every stored login.\n");
|
|
11915
|
+
return;
|
|
11916
|
+
}
|
|
11917
|
+
const label = removed?.workspaceName ?? removed?.workspaceId ?? "the stored login";
|
|
11918
|
+
process.stdout.write(`Logged out of ${label}.
|
|
11919
|
+
`);
|
|
11920
|
+
if (nowActive) {
|
|
11921
|
+
process.stdout.write(
|
|
11922
|
+
`Active now: ${nowActive.workspaceName ?? "?"} (${nowActive.workspaceId ?? "?"})
|
|
11923
|
+
`
|
|
11924
|
+
);
|
|
11689
11925
|
}
|
|
11690
11926
|
});
|
|
11691
11927
|
}
|
|
@@ -11695,6 +11931,16 @@ import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
|
|
|
11695
11931
|
|
|
11696
11932
|
// src/utils/httpJson.ts
|
|
11697
11933
|
import { hostname as hostname2 } from "os";
|
|
11934
|
+
var HttpJsonError = class extends Error {
|
|
11935
|
+
status;
|
|
11936
|
+
code;
|
|
11937
|
+
constructor(message, status, code) {
|
|
11938
|
+
super(message);
|
|
11939
|
+
this.name = "HttpJsonError";
|
|
11940
|
+
this.status = status;
|
|
11941
|
+
this.code = code;
|
|
11942
|
+
}
|
|
11943
|
+
};
|
|
11698
11944
|
async function postJson2(label, url, body, options = {}) {
|
|
11699
11945
|
if (options.verbose) process.stderr.write(`\u2192 POST ${url}
|
|
11700
11946
|
`);
|
|
@@ -11710,8 +11956,10 @@ async function postJson2(label, url, body, options = {}) {
|
|
|
11710
11956
|
});
|
|
11711
11957
|
const text = await response.text();
|
|
11712
11958
|
if (!response.ok) {
|
|
11713
|
-
throw new
|
|
11714
|
-
`${label} failed (${response.status}): ${extractErrorMessage(text, response.statusText)}
|
|
11959
|
+
throw new HttpJsonError(
|
|
11960
|
+
`${label} failed (${response.status}): ${extractErrorMessage(text, response.statusText)}`,
|
|
11961
|
+
response.status,
|
|
11962
|
+
extractErrorCode(text)
|
|
11715
11963
|
);
|
|
11716
11964
|
}
|
|
11717
11965
|
return JSON.parse(text);
|
|
@@ -11726,6 +11974,14 @@ function extractErrorMessage(text, fallback) {
|
|
|
11726
11974
|
}
|
|
11727
11975
|
return text || fallback;
|
|
11728
11976
|
}
|
|
11977
|
+
function extractErrorCode(text) {
|
|
11978
|
+
try {
|
|
11979
|
+
const parsed = JSON.parse(text);
|
|
11980
|
+
return typeof parsed.code === "string" ? parsed.code : null;
|
|
11981
|
+
} catch {
|
|
11982
|
+
return null;
|
|
11983
|
+
}
|
|
11984
|
+
}
|
|
11729
11985
|
function safeHostname2() {
|
|
11730
11986
|
try {
|
|
11731
11987
|
const h = hostname2();
|
|
@@ -11782,6 +12038,7 @@ function promptHidden(question) {
|
|
|
11782
12038
|
}
|
|
11783
12039
|
|
|
11784
12040
|
// src/commands/auth/register.ts
|
|
12041
|
+
var BOT_CHECK_FAILED_CODE = "auth/bot-check-failed";
|
|
11785
12042
|
var MAX_CODE_ATTEMPTS = 5;
|
|
11786
12043
|
var MIN_PASSWORD_LENGTH = 8;
|
|
11787
12044
|
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
@@ -11825,6 +12082,32 @@ async function runRegister(options) {
|
|
|
11825
12082
|
if (!EMAIL_REGEX.test(email)) {
|
|
11826
12083
|
throw new Error(`"${email}" does not look like a valid email address.`);
|
|
11827
12084
|
}
|
|
12085
|
+
const initiateBody = {
|
|
12086
|
+
email,
|
|
12087
|
+
lang,
|
|
12088
|
+
accessCode: options.accessCode ?? null
|
|
12089
|
+
};
|
|
12090
|
+
try {
|
|
12091
|
+
await postJson2(
|
|
12092
|
+
"Sending verification code",
|
|
12093
|
+
`${urls.usersUrl}/initiate-registration`,
|
|
12094
|
+
initiateBody,
|
|
12095
|
+
{ verbose }
|
|
12096
|
+
);
|
|
12097
|
+
} catch (error) {
|
|
12098
|
+
if (error instanceof HttpJsonError && error.code === BOT_CHECK_FAILED_CODE) {
|
|
12099
|
+
process.stdout.write(
|
|
12100
|
+
"\nNew Zanfia accounts finish signing up in the browser (a quick security check).\nOpening the signup page; the CLI logs in once you are done.\n\n"
|
|
12101
|
+
);
|
|
12102
|
+
await runBrowserRegister({ email, apiUrl: options.apiUrl, verbose });
|
|
12103
|
+
return;
|
|
12104
|
+
}
|
|
12105
|
+
throw error;
|
|
12106
|
+
}
|
|
12107
|
+
process.stdout.write(`
|
|
12108
|
+
We sent a 6-digit code to ${email}. Check your inbox.
|
|
12109
|
+
|
|
12110
|
+
`);
|
|
11828
12111
|
const fullName = options.name ?? await promptLine("Full name: ");
|
|
11829
12112
|
if (fullName.length < 3 || fullName.length > 100) {
|
|
11830
12113
|
throw new Error("Full name must be 3-100 characters.");
|
|
@@ -11851,21 +12134,6 @@ async function runRegister(options) {
|
|
|
11851
12134
|
} else if (password.length < MIN_PASSWORD_LENGTH) {
|
|
11852
12135
|
throw new Error(`Password must be at least ${MIN_PASSWORD_LENGTH} characters.`);
|
|
11853
12136
|
}
|
|
11854
|
-
const initiateBody = {
|
|
11855
|
-
email,
|
|
11856
|
-
lang,
|
|
11857
|
-
accessCode: options.accessCode ?? null
|
|
11858
|
-
};
|
|
11859
|
-
await postJson2(
|
|
11860
|
-
"Sending verification code",
|
|
11861
|
-
`${urls.usersUrl}/initiate-registration`,
|
|
11862
|
-
initiateBody,
|
|
11863
|
-
{ verbose }
|
|
11864
|
-
);
|
|
11865
|
-
process.stdout.write(`
|
|
11866
|
-
We sent a 6-digit code to ${email}. Check your inbox.
|
|
11867
|
-
|
|
11868
|
-
`);
|
|
11869
12137
|
let verificationToken = null;
|
|
11870
12138
|
for (let attempt = 1; attempt <= MAX_CODE_ATTEMPTS; attempt++) {
|
|
11871
12139
|
const code = options.code ?? await promptLine("Verification code (or 'r' to resend): ");
|
|
@@ -11970,16 +12238,18 @@ Logged in as ${exchanged.userEmail}.
|
|
|
11970
12238
|
}
|
|
11971
12239
|
}
|
|
11972
12240
|
function registerRegisterCommand(program2) {
|
|
11973
|
-
program2.command("register").description("Create a new Zanfia creator account
|
|
12241
|
+
program2.command("register").description("Create a new Zanfia creator account and log the CLI in").option("--email <email>", "account email (prompted when omitted)").option("--name <fullName>", "your full name (prompted when omitted)").option(
|
|
11974
12242
|
"--password <password>",
|
|
11975
12243
|
"optional account password (min 8 chars); prefer the interactive prompt \u2014 flags leak into shell history"
|
|
11976
12244
|
).option("--lang <lang>", "account language (en|pl)", "en").option("--access-code <code>", "signup access code (only when signups are gated)").option("--code <code>", "email verification code (skips the interactive prompt)").option("--api-url <url>", "API URL to associate with the resulting token").option("-v, --verbose", "show HTTP request details").addHelpText(
|
|
11977
12245
|
"after",
|
|
11978
12246
|
`
|
|
11979
|
-
|
|
11980
|
-
|
|
11981
|
-
|
|
11982
|
-
run \`login
|
|
12247
|
+
New accounts finish signing up in the browser: Zanfia runs a quick security
|
|
12248
|
+
check on the signup page, which a terminal cannot do. The CLI opens the
|
|
12249
|
+
signup page with your email filled in, and once the account exists it logs
|
|
12250
|
+
in exactly as if you had run \`login\`. Where the check does not apply, the
|
|
12251
|
+
whole flow stays in the terminal: a 6-digit code by email, then the account
|
|
12252
|
+
plus its personal workspace are created.
|
|
11983
12253
|
|
|
11984
12254
|
Running it with an email that already has a creator account simply signs
|
|
11985
12255
|
that account in.
|
|
@@ -12299,6 +12569,9 @@ var RECIPIENT_STATUSES = [
|
|
|
12299
12569
|
"complained",
|
|
12300
12570
|
"clicked"
|
|
12301
12571
|
];
|
|
12572
|
+
function collectRepeatable(value, previous) {
|
|
12573
|
+
return [...previous, value];
|
|
12574
|
+
}
|
|
12302
12575
|
function parsePositiveInt2(value, flag) {
|
|
12303
12576
|
const n = Number.parseInt(value, 10);
|
|
12304
12577
|
if (!Number.isInteger(n) || n <= 0) {
|
|
@@ -12534,6 +12807,25 @@ function registerCampaignsCommands(program2) {
|
|
|
12534
12807
|
`);
|
|
12535
12808
|
});
|
|
12536
12809
|
});
|
|
12810
|
+
campaigns.command("test").description(
|
|
12811
|
+
"Send a TEST of the saved campaign to up to 3 addresses (default: your own) \u2014 subject prefixed [Test], sample personalization values; never the audience"
|
|
12812
|
+
).argument("<id>", "campaign id").option(
|
|
12813
|
+
"--to <email>",
|
|
12814
|
+
"test recipient \u2014 repeatable or comma-separated, max 3 (default: your own address)",
|
|
12815
|
+
collectRepeatable,
|
|
12816
|
+
[]
|
|
12817
|
+
).action(async (id, options, command) => {
|
|
12818
|
+
const flags = getGlobalFlags(command);
|
|
12819
|
+
const recipients = options.to.flatMap((entry) => entry.split(",")).map((email) => email.trim()).filter(Boolean);
|
|
12820
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12821
|
+
const result = await client.sendEmailCampaignTest(id, recipients);
|
|
12822
|
+
emit(flags, result, () => {
|
|
12823
|
+
process.stdout.write(
|
|
12824
|
+
`Test of campaign ${id} queued \u2192 ${result.recipients.join(", ")} (subject prefixed [Test]). Nothing was sent to the audience.
|
|
12825
|
+
`
|
|
12826
|
+
);
|
|
12827
|
+
});
|
|
12828
|
+
});
|
|
12537
12829
|
campaigns.command("send").description(
|
|
12538
12830
|
"\u26A0\uFE0F SENDS the campaign \u2014 REAL bulk email to the entire resolved audience, IRREVERSIBLY. Delivered mail cannot be recalled; `campaigns cancel` only stops remaining sends. Requires --confirm. Omit --at to send NOW; --at schedules (max 30 days out)."
|
|
12539
12831
|
).argument("<id>", "campaign id").option(
|
|
@@ -12543,7 +12835,7 @@ function registerCampaignsCommands(program2) {
|
|
|
12543
12835
|
const flags = getGlobalFlags(command);
|
|
12544
12836
|
if (!options.confirm) {
|
|
12545
12837
|
throw new Error(
|
|
12546
|
-
`Refusing to send without --confirm. \`campaigns send\` emails the ENTIRE resolved audience for real and cannot be undone once mail is delivered. Re-run with --confirm to proceed (check the draft first with \`campaigns get ${id}\`).`
|
|
12838
|
+
`Refusing to send without --confirm. \`campaigns send\` emails the ENTIRE resolved audience for real and cannot be undone once mail is delivered. Re-run with --confirm to proceed (check the draft first with \`campaigns get ${id}\` and \`campaigns test ${id}\`).`
|
|
12547
12839
|
);
|
|
12548
12840
|
}
|
|
12549
12841
|
if (options.at !== void 0 && Number.isNaN(Date.parse(options.at))) {
|
|
@@ -12578,6 +12870,28 @@ function registerCampaignsCommands(program2) {
|
|
|
12578
12870
|
const { stats } = result.campaign;
|
|
12579
12871
|
process.stdout.write(
|
|
12580
12872
|
`Cancelled campaign ${result.campaign.id} \u2014 ${stats.sent} recipient${stats.sent === 1 ? " was" : "s were"} already sent to before the cancel.
|
|
12873
|
+
`
|
|
12874
|
+
);
|
|
12875
|
+
});
|
|
12876
|
+
});
|
|
12877
|
+
campaigns.command("reschedule").description(
|
|
12878
|
+
"Move a SCHEDULED campaign to a new send time (min 2 minutes, max 30 days out) \u2014 it stays scheduled and goes out only at the new time. Drafts: `campaigns send --at` instead."
|
|
12879
|
+
).argument("<id>", "campaign id (must be scheduled)").requiredOption(
|
|
12880
|
+
"--at <isoTimestamp>",
|
|
12881
|
+
"new send time (ISO with offset, e.g. 2026-10-15T09:00:00+02:00)"
|
|
12882
|
+
).action(async (id, options, command) => {
|
|
12883
|
+
const flags = getGlobalFlags(command);
|
|
12884
|
+
if (Number.isNaN(Date.parse(options.at))) {
|
|
12885
|
+
throw new Error(
|
|
12886
|
+
`Invalid --at timestamp: ${options.at} (expected ISO, e.g. 2026-10-15T09:00:00+02:00)`
|
|
12887
|
+
);
|
|
12888
|
+
}
|
|
12889
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12890
|
+
const result = await client.rescheduleEmailCampaign(id, options.at);
|
|
12891
|
+
emit(flags, result, () => {
|
|
12892
|
+
const { campaign } = result;
|
|
12893
|
+
process.stdout.write(
|
|
12894
|
+
`Campaign ${campaign.id} moved to ${campaign.scheduledAt} \u2014 still scheduled; stop it before then with \`campaigns cancel\`.
|
|
12581
12895
|
`
|
|
12582
12896
|
);
|
|
12583
12897
|
});
|
|
@@ -13567,7 +13881,7 @@ function parsePositiveInt4(value, flag) {
|
|
|
13567
13881
|
}
|
|
13568
13882
|
return n;
|
|
13569
13883
|
}
|
|
13570
|
-
function
|
|
13884
|
+
function collectRepeatable2(value, previous) {
|
|
13571
13885
|
return [...previous, value];
|
|
13572
13886
|
}
|
|
13573
13887
|
function parseCsv(raw) {
|
|
@@ -13726,7 +14040,15 @@ function printMergePreview(preview) {
|
|
|
13726
14040
|
}
|
|
13727
14041
|
function registerClientsCommands(program2) {
|
|
13728
14042
|
const clients2 = program2.command("clients").description("Manage workspace clients (customers)");
|
|
13729
|
-
clients2.command("list").description(
|
|
14043
|
+
clients2.command("list").description(
|
|
14044
|
+
"List clients (newest first), with optional search, product and date-added filters"
|
|
14045
|
+
).option("--search <query>", "match email / first name / last name").option("--limit <n>", "page size (max 100)").option("--cursor <cursor>", "keyset cursor from a previous page").option("--with-products", "only clients with active product access").option("--without-products", "only clients with no product access").option(
|
|
14046
|
+
"--created-from <date>",
|
|
14047
|
+
"only clients added on or after this date (YYYY-MM-DD or ISO 8601 date-time)"
|
|
14048
|
+
).option(
|
|
14049
|
+
"--created-to <date>",
|
|
14050
|
+
"only clients added on or before this date (YYYY-MM-DD keeps the whole day)"
|
|
14051
|
+
).action(
|
|
13730
14052
|
async (options, command) => {
|
|
13731
14053
|
const flags = getGlobalFlags(command);
|
|
13732
14054
|
if (options.withProducts && options.withoutProducts) {
|
|
@@ -13737,7 +14059,9 @@ function registerClientsCommands(program2) {
|
|
|
13737
14059
|
search: options.search,
|
|
13738
14060
|
limit: options.limit !== void 0 ? parsePositiveInt4(options.limit, "--limit") : void 0,
|
|
13739
14061
|
cursor: options.cursor,
|
|
13740
|
-
products: options.withProducts ? "has" : options.withoutProducts ? "no" : void 0
|
|
14062
|
+
products: options.withProducts ? "has" : options.withoutProducts ? "no" : void 0,
|
|
14063
|
+
createdFrom: options.createdFrom,
|
|
14064
|
+
createdTo: options.createdTo
|
|
13741
14065
|
});
|
|
13742
14066
|
emit(flags, page, () => {
|
|
13743
14067
|
printTable(
|
|
@@ -14051,7 +14375,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
14051
14375
|
).argument("<csvFile>", "path to the CSV file").option(
|
|
14052
14376
|
"--tag <tagId>",
|
|
14053
14377
|
"existing tag id applied to every imported row (repeat for several; unknown ids fail)",
|
|
14054
|
-
|
|
14378
|
+
collectRepeatable2,
|
|
14055
14379
|
[]
|
|
14056
14380
|
).action(async (csvFile, options, command) => {
|
|
14057
14381
|
const flags = getGlobalFlags(command);
|
|
@@ -15969,7 +16293,7 @@ function assertStatus(status) {
|
|
|
15969
16293
|
throw new Error(`Invalid status "${status}". Use one of: ${LESSON_STATUSES.join(", ")}`);
|
|
15970
16294
|
}
|
|
15971
16295
|
}
|
|
15972
|
-
function
|
|
16296
|
+
function collectRepeatable3(value, previous) {
|
|
15973
16297
|
return [...previous, value];
|
|
15974
16298
|
}
|
|
15975
16299
|
function parseNonNegativeInt(value, flag) {
|
|
@@ -16577,7 +16901,7 @@ ${NOT_SAVED_NOTE} --moments=${options.out ?? "<file>"} --confirm\`.
|
|
|
16577
16901
|
).argument("<productId>", "course product id").argument("<lessonId>", "quiz lesson id").option("--type <type>", "single | multi | truefalse").option("--question <text>", "the question prompt").option("--explanation <text>", "explanation shown on the results screen").option("--points <n>", "points awarded (default 1)").option(
|
|
16578
16902
|
"--option <text>",
|
|
16579
16903
|
"an answer option (repeat for several; choice types only)",
|
|
16580
|
-
|
|
16904
|
+
collectRepeatable3,
|
|
16581
16905
|
[]
|
|
16582
16906
|
).option("--correct <indices>", "1-based, comma-separated indices of the correct option(s)").option("--correct-answer <bool>", "true/false questions: the correct answer").option("--json <fileOrJson>", "full QuizQuestionInput object as inline JSON or a .json path").action(
|
|
16583
16907
|
async (productId, lessonId, options, command) => {
|
|
@@ -16593,7 +16917,7 @@ ${NOT_SAVED_NOTE} --moments=${options.out ?? "<file>"} --confirm\`.
|
|
|
16593
16917
|
quiz.command("update").description("Replace a question in full (same flags as `quiz add`, or --json)").argument("<productId>", "course product id").argument("<lessonId>", "quiz lesson id").argument("<questionId>", "question id").option("--type <type>", "single | multi | truefalse").option("--question <text>", "the question prompt").option("--explanation <text>", "explanation shown on the results screen").option("--points <n>", "points awarded (default 1)").option(
|
|
16594
16918
|
"--option <text>",
|
|
16595
16919
|
"an answer option (repeat for several; choice types only)",
|
|
16596
|
-
|
|
16920
|
+
collectRepeatable3,
|
|
16597
16921
|
[]
|
|
16598
16922
|
).option("--correct <indices>", "1-based, comma-separated indices of the correct option(s)").option("--correct-answer <bool>", "true/false questions: the correct answer").option("--json <fileOrJson>", "full QuizQuestionInput object as inline JSON or a .json path").action(
|
|
16599
16923
|
async (productId, lessonId, questionId, options, command) => {
|
|
@@ -16777,7 +17101,7 @@ function parseIsoDate(value, flag) {
|
|
|
16777
17101
|
}
|
|
16778
17102
|
return value;
|
|
16779
17103
|
}
|
|
16780
|
-
function
|
|
17104
|
+
function collectRepeatable4(value, previous) {
|
|
16781
17105
|
return [...previous, value];
|
|
16782
17106
|
}
|
|
16783
17107
|
function registerDiscountsCommands(program2) {
|
|
@@ -16802,7 +17126,7 @@ function registerDiscountsCommands(program2) {
|
|
|
16802
17126
|
discounts2.command("create").description("Create a discount code (exactly one of --percent-off / --amount-off)").requiredOption("--code <code>", "discount code (e.g. LAUNCH20)").option("--percent-off <pct>", "percentage discount (> 0, <= 100)").option("--amount-off <minorUnits>", "fixed discount in minor units (e.g. 1000 = 10.00)").option("--currency <currency>", "ISO currency code (required with --amount-off)").option(
|
|
16803
17127
|
"--product <id>",
|
|
16804
17128
|
"limit to a product (repeat the flag for several)",
|
|
16805
|
-
|
|
17129
|
+
collectRepeatable4,
|
|
16806
17130
|
[]
|
|
16807
17131
|
).option("--max-redemptions <n>", "maximum number of redemptions").option("--ends <isoDate>", "date the code stops working (ISO, e.g. 2026-12-31)").option("--starts <isoDate>", "date the code starts working (ISO, e.g. 2026-12-01)").option("--forever", "apply to every renewal of a subscription (default: first payment only)").option("--inactive", "create the code deactivated (default: active)").action(
|
|
16808
17132
|
async (options, command) => {
|
|
@@ -16852,7 +17176,7 @@ function registerDiscountsCommands(program2) {
|
|
|
16852
17176
|
discounts2.command("update").description("Update a discount code (value, products, limits, dates, active state)").argument("<discountId>", "discount id").option("--percent-off <pct>", "switch to a percentage discount (> 0, <= 100)").option("--amount-off <minorUnits>", "switch to a fixed discount in minor units").option("--currency <currency>", "ISO currency code for --amount-off").option(
|
|
16853
17177
|
"--product <id>",
|
|
16854
17178
|
"replace the product restriction (repeat the flag for several)",
|
|
16855
|
-
|
|
17179
|
+
collectRepeatable4,
|
|
16856
17180
|
[]
|
|
16857
17181
|
).option("--max-redemptions <n>", "maximum number of redemptions").option("--clear-max-redemptions", "remove the redemption limit").option("--ends <isoDate>", "date the code stops working (ISO, e.g. 2026-12-31)").option("--clear-ends", "remove the end date").option("--forever", "apply to every renewal of a subscription").option("--active", "activate the code").option("--inactive", "deactivate the code").action(
|
|
16858
17182
|
async (discountId, options, command) => {
|
|
@@ -19267,9 +19591,14 @@ join again to refresh it.
|
|
|
19267
19591
|
// src/commands/liveRooms/index.ts
|
|
19268
19592
|
var minutes = (seconds) => `${Math.round(seconds / 60)} min`;
|
|
19269
19593
|
var percent = (part, whole) => whole > 0 ? `${Math.round(part / whole * 100)}%` : "-";
|
|
19594
|
+
var WEEKDAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
19270
19595
|
function describeSchedule(room) {
|
|
19271
19596
|
if (!room.schedule) return "fixed";
|
|
19272
19597
|
const until = room.schedule.until ? ` until ${room.schedule.until}` : "";
|
|
19598
|
+
if (room.schedule.kind === "daily") {
|
|
19599
|
+
const days = room.schedule.weekdays ? room.schedule.weekdays.map((day) => WEEKDAYS[day - 1] ?? String(day)).join(", ") : "daily";
|
|
19600
|
+
return `${days} at ${room.schedule.times.join(", ")} (${room.timezone})${until}`;
|
|
19601
|
+
}
|
|
19273
19602
|
return `every ${room.schedule.everyMinutes} min${until}`;
|
|
19274
19603
|
}
|
|
19275
19604
|
function registerLiveRoomsCommands(program2) {
|
|
@@ -23043,7 +23372,7 @@ function parseJsonFileOrInline5(value, flag) {
|
|
|
23043
23372
|
throw new Error(`Invalid JSON for ${flag}: ${error.message}`);
|
|
23044
23373
|
}
|
|
23045
23374
|
}
|
|
23046
|
-
function
|
|
23375
|
+
function collectRepeatable5(value, previous) {
|
|
23047
23376
|
return [...previous, value];
|
|
23048
23377
|
}
|
|
23049
23378
|
function registerNewslettersCommands(program2) {
|
|
@@ -23374,7 +23703,7 @@ Next: \`newsletters issues draft save ${productId} ${issue.id} --content <markdo
|
|
|
23374
23703
|
).option("--double-opt-in <mode>", `subscription confirmation (${DOUBLE_OPT_INS.join("|")})`).option(
|
|
23375
23704
|
"--client-tag <tag>",
|
|
23376
23705
|
"tag applied to new subscribers (repeat the flag; replaces the list)",
|
|
23377
|
-
|
|
23706
|
+
collectRepeatable5,
|
|
23378
23707
|
[]
|
|
23379
23708
|
).option(
|
|
23380
23709
|
"--delivery <mode>",
|
|
@@ -25064,6 +25393,22 @@ import { readFile as readFile3 } from "fs/promises";
|
|
|
25064
25393
|
import { basename as basename2 } from "path";
|
|
25065
25394
|
var KB_TREE_CHANGED_ERROR = "tree-changed";
|
|
25066
25395
|
var JSON_FLAG_HINT3 = "(write it as --json=<value> \u2014 a bare --json is the global JSON-output flag)";
|
|
25396
|
+
var PRODUCT_TYPE_INPUTS = [
|
|
25397
|
+
"downloads",
|
|
25398
|
+
"download",
|
|
25399
|
+
"newsletter",
|
|
25400
|
+
"course",
|
|
25401
|
+
"mailing",
|
|
25402
|
+
"community",
|
|
25403
|
+
"membership",
|
|
25404
|
+
"knowledge_base",
|
|
25405
|
+
"one_time",
|
|
25406
|
+
"custom",
|
|
25407
|
+
"event",
|
|
25408
|
+
"consultation",
|
|
25409
|
+
"service",
|
|
25410
|
+
"bundle"
|
|
25411
|
+
];
|
|
25067
25412
|
function parseCoverRef(value) {
|
|
25068
25413
|
const separator = value.indexOf(":");
|
|
25069
25414
|
if (separator < 0) {
|
|
@@ -25174,7 +25519,7 @@ function parseJsonFileOrInline8(value, flag) {
|
|
|
25174
25519
|
throw new Error(`Invalid JSON for ${flag}: ${error.message}`);
|
|
25175
25520
|
}
|
|
25176
25521
|
}
|
|
25177
|
-
function
|
|
25522
|
+
function collectRepeatable6(value, previous) {
|
|
25178
25523
|
return [...previous, value];
|
|
25179
25524
|
}
|
|
25180
25525
|
var HTTP_URL_PATTERN2 = /^https?:\/\//i;
|
|
@@ -25252,6 +25597,10 @@ function registerProductsCommands(program2) {
|
|
|
25252
25597
|
`);
|
|
25253
25598
|
process.stdout.write(` Status: ${product.status}
|
|
25254
25599
|
`);
|
|
25600
|
+
if (product.storefrontVisibility) {
|
|
25601
|
+
process.stdout.write(` Visibility: ${product.storefrontVisibility}
|
|
25602
|
+
`);
|
|
25603
|
+
}
|
|
25255
25604
|
process.stdout.write(` Clients: ${product.clientCount}
|
|
25256
25605
|
`);
|
|
25257
25606
|
if (product.description) {
|
|
@@ -25262,7 +25611,10 @@ function registerProductsCommands(program2) {
|
|
|
25262
25611
|
});
|
|
25263
25612
|
products2.command("create").description(
|
|
25264
25613
|
"Create a new product. Community/newsletter types seed their full content (community: channels doc, built-in roles, owner member). Add --free or --amount to also seed a default offer \u2014 e.g. a paid community: --type community --amount 49.99 --currency usd --period monthly"
|
|
25265
|
-
).requiredOption("--name <name>", "product name").requiredOption(
|
|
25614
|
+
).requiredOption("--name <name>", "product name").requiredOption(
|
|
25615
|
+
"--type <type>",
|
|
25616
|
+
`product type: ${PRODUCT_TYPE_INPUTS.join(", ")} (downloads/download are stored as one_time, newsletter as mailing)`
|
|
25617
|
+
).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(
|
|
25266
25618
|
"--period <period>",
|
|
25267
25619
|
"billing period for --amount: one_time, monthly or yearly (default one_time)"
|
|
25268
25620
|
).option("--description-file <path>", "read the product description from a file").option(
|
|
@@ -25271,7 +25623,7 @@ function registerProductsCommands(program2) {
|
|
|
25271
25623
|
).option(
|
|
25272
25624
|
"--file <path>",
|
|
25273
25625
|
"local file to upload and attach to the product (downloads products; repeat for several)",
|
|
25274
|
-
|
|
25626
|
+
collectRepeatable6,
|
|
25275
25627
|
[]
|
|
25276
25628
|
).action(
|
|
25277
25629
|
async (options, command) => {
|
|
@@ -25335,13 +25687,21 @@ function registerProductsCommands(program2) {
|
|
|
25335
25687
|
});
|
|
25336
25688
|
}
|
|
25337
25689
|
);
|
|
25338
|
-
products2.command("update").description("Update a product's name and/or
|
|
25690
|
+
products2.command("update").description("Update a product's name, description and/or storefront visibility").argument("<id>", "product id").option("--name <name>", "new product name").option("--description <description>", "new product description").option(
|
|
25691
|
+
"--visibility <public|unlisted|private>",
|
|
25692
|
+
"where buyers find it once published: public (store + product page), unlisted (product page by link only), private (checkout links only)",
|
|
25693
|
+
parseStorefrontVisibility
|
|
25694
|
+
).action(
|
|
25339
25695
|
async (id, options, command) => {
|
|
25696
|
+
if (options.name === void 0 && options.description === void 0 && options.visibility === void 0) {
|
|
25697
|
+
throw new Error("Nothing to update: pass --name, --description and/or --visibility.");
|
|
25698
|
+
}
|
|
25340
25699
|
const flags = getGlobalFlags(command);
|
|
25341
25700
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
25342
25701
|
const result = await client.updateProduct(id, {
|
|
25343
25702
|
name: options.name,
|
|
25344
|
-
description: options.description
|
|
25703
|
+
description: options.description,
|
|
25704
|
+
storefrontVisibility: options.visibility
|
|
25345
25705
|
});
|
|
25346
25706
|
emit(flags, result, () => {
|
|
25347
25707
|
process.stdout.write(`Updated product ${result.productId}
|
|
@@ -25358,6 +25718,26 @@ function registerProductsCommands(program2) {
|
|
|
25358
25718
|
`);
|
|
25359
25719
|
});
|
|
25360
25720
|
});
|
|
25721
|
+
products2.command("delete").description(
|
|
25722
|
+
"Delete a product (soft-delete, like the dashboard): offers archived, customers lose access, subscriptions cancelled without refunds, data purged after 30 days. Requires --confirm."
|
|
25723
|
+
).argument("<id>", "product id").option("--confirm", "required \u2014 acknowledge that customers lose access and subscriptions end").action(async (id, options, command) => {
|
|
25724
|
+
requireConfirm(options.confirm, `delete product ${id}`);
|
|
25725
|
+
const flags = getGlobalFlags(command);
|
|
25726
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
25727
|
+
const result = await client.deleteProduct(id);
|
|
25728
|
+
emit(flags, result, () => {
|
|
25729
|
+
process.stdout.write(
|
|
25730
|
+
`Deleted product ${result.productId}; data is purged after ${new Date(result.purgeAfter * 1e3).toISOString().slice(0, 10)}.
|
|
25731
|
+
`
|
|
25732
|
+
);
|
|
25733
|
+
if (result.activeGrants > 0) {
|
|
25734
|
+
process.stdout.write(
|
|
25735
|
+
`${result.activeGrants} customer(s) lost access; ${result.cancellableSubscriptions} subscription(s) are being cancelled (run ${result.runId ?? "n/a"}).
|
|
25736
|
+
`
|
|
25737
|
+
);
|
|
25738
|
+
}
|
|
25739
|
+
});
|
|
25740
|
+
});
|
|
25361
25741
|
products2.command("publish").description("Publish a product (status \u2192 published; live for customers)").argument("<id>", "product id").action(async (id, _, command) => {
|
|
25362
25742
|
const flags = getGlobalFlags(command);
|
|
25363
25743
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -25606,7 +25986,7 @@ function registerProductsCommands(program2) {
|
|
|
25606
25986
|
).argument("<productId>", "appointment product id").option("--config <fileOrJson>", "full config object as inline JSON or a path to a .json file").option("--booking-method <method>", `consultation: ${BOOKING_METHODS.join("|")}`).option("--booking-url <url>", "consultation: external scheduler URL").option("--duration <minutes>", "consultation: session length in minutes").option("--location <text>", "consultation location / event join URL or address").option("--location-details <text>", "consultation: extra location details").option("--confirmation-message <text>", "consultation: post-booking confirmation message").option(
|
|
25607
25987
|
"--deliverable <text>",
|
|
25608
25988
|
"service: a deliverable line (repeat the flag; replaces the list)",
|
|
25609
|
-
|
|
25989
|
+
collectRepeatable6,
|
|
25610
25990
|
[]
|
|
25611
25991
|
).option("--delivery-time <n>", "service: delivery time value").option("--delivery-unit <unit>", `service: ${DELIVERY_UNITS.join("|")} (with --delivery-time)`).option("--revision-rounds <n|unlimited>", 'service: revision rounds count or "unlimited"').option("--brief-method <method>", `service: ${BRIEF_METHODS.join("|")}`).option("--brief-url <url>", "service: external brief/intake form URL").option("--event-mode <mode>", `event: ${EVENT_MODES3.join("|")}`).option("--starts <iso>", "event: start (ISO 8601)").option("--ends <iso>", "event: end (ISO 8601)").option("--capacity <n>", "event: attendee capacity").option("--show-on-success <on|off>", "show the post-purchase CTA on the success page").action(
|
|
25612
25992
|
async (productId, options, command) => {
|
|
@@ -25802,7 +26182,7 @@ function registerProductsCommands(program2) {
|
|
|
25802
26182
|
).argument("<productId>", "product id").requiredOption(
|
|
25803
26183
|
"--image <url>",
|
|
25804
26184
|
"an image URL (repeat the flag for several; the set fully replaces the current images)",
|
|
25805
|
-
|
|
26185
|
+
collectRepeatable6,
|
|
25806
26186
|
[]
|
|
25807
26187
|
).action(async (productId, options, command) => {
|
|
25808
26188
|
const flags = getGlobalFlags(command);
|
|
@@ -25902,6 +26282,18 @@ function registerProductsCommands(program2) {
|
|
|
25902
26282
|
);
|
|
25903
26283
|
});
|
|
25904
26284
|
});
|
|
26285
|
+
files.command("rename").description(
|
|
26286
|
+
"Rename a file in place (its name is the buyer\u2019s download filename; order and uid stay)"
|
|
26287
|
+
).argument("<uid>", "file uid (see `products files get`)").requiredOption("--product <id>", "downloads product id").requiredOption("--name <name>", 'new file name, e.g. "Tom 1.epub" (max 255 characters)').action(async (uid, options, command) => {
|
|
26288
|
+
const flags = getGlobalFlags(command);
|
|
26289
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
26290
|
+
const result = await client.renameDownloadFile(options.product, uid, options.name);
|
|
26291
|
+
emit(flags, result, () => {
|
|
26292
|
+
process.stdout.write(`Renamed ${result.file.uid} to "${result.file.name}".
|
|
26293
|
+
`);
|
|
26294
|
+
printFilesTable(result.files);
|
|
26295
|
+
});
|
|
26296
|
+
});
|
|
25905
26297
|
files.command("reorder").description("Reorder the file list \u2014 pass EVERY current uid in the new order").argument("<uid...>", "the full uid permutation, in the new order").requiredOption("--product <id>", "downloads product id").action(async (uids, options, command) => {
|
|
25906
26298
|
const flags = getGlobalFlags(command);
|
|
25907
26299
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -26531,7 +26923,7 @@ Failed deliveries (${result.failedDeliveries.length})
|
|
|
26531
26923
|
// src/commands/segments/index.ts
|
|
26532
26924
|
import { readFileSync as readFileSync24 } from "fs";
|
|
26533
26925
|
var SUBSCRIPTION_STATUSES2 = ["pending", "subscribed", "unsubscribed"];
|
|
26534
|
-
function
|
|
26926
|
+
function collectRepeatable7(value, previous) {
|
|
26535
26927
|
return [...previous, value];
|
|
26536
26928
|
}
|
|
26537
26929
|
function parseMinorUnits(value, flag) {
|
|
@@ -26623,42 +27015,42 @@ function withConditionOptions(command) {
|
|
|
26623
27015
|
return command.option(
|
|
26624
27016
|
"--has-tag <tagId>",
|
|
26625
27017
|
"client carries this tag (repeat for several \u2014 conditions are ANDed)",
|
|
26626
|
-
|
|
27018
|
+
collectRepeatable7,
|
|
26627
27019
|
[]
|
|
26628
27020
|
).option(
|
|
26629
27021
|
"--without-tag <tagId>",
|
|
26630
27022
|
"client does NOT carry this tag (repeatable)",
|
|
26631
|
-
|
|
27023
|
+
collectRepeatable7,
|
|
26632
27024
|
[]
|
|
26633
27025
|
).option(
|
|
26634
27026
|
"--has-any-tag <tagId>",
|
|
26635
27027
|
"client carries AT LEAST ONE of these tags (repeat the flag; one OR condition, max 10 ids) - one send to several tags without double-mailing",
|
|
26636
|
-
|
|
27028
|
+
collectRepeatable7,
|
|
26637
27029
|
[]
|
|
26638
27030
|
).option(
|
|
26639
27031
|
"--has-none-tag <tagId>",
|
|
26640
27032
|
"client carries NONE of these tags (repeat the flag; one condition, max 10 ids)",
|
|
26641
|
-
|
|
27033
|
+
collectRepeatable7,
|
|
26642
27034
|
[]
|
|
26643
27035
|
).option(
|
|
26644
27036
|
"--owns <productId>",
|
|
26645
27037
|
"client has ACTIVE access to this product - a direct grant OR through a bundle (repeatable)",
|
|
26646
|
-
|
|
27038
|
+
collectRepeatable7,
|
|
26647
27039
|
[]
|
|
26648
27040
|
).option(
|
|
26649
27041
|
"--not-owns <productId>",
|
|
26650
27042
|
"client has NO active access to this product, direct or through a bundle (repeatable)",
|
|
26651
|
-
|
|
27043
|
+
collectRepeatable7,
|
|
26652
27044
|
[]
|
|
26653
27045
|
).option(
|
|
26654
27046
|
"--owns-direct <productId>",
|
|
26655
27047
|
"client has an ACTIVE grant on this product itself (bought / granted by hand) - bundle access does not count; use to mail only the buyers of that one product (repeatable)",
|
|
26656
|
-
|
|
27048
|
+
collectRepeatable7,
|
|
26657
27049
|
[]
|
|
26658
27050
|
).option(
|
|
26659
27051
|
"--not-owns-direct <productId>",
|
|
26660
27052
|
"client has NO active direct grant on this product - bundle holders DO match (repeatable)",
|
|
26661
|
-
|
|
27053
|
+
collectRepeatable7,
|
|
26662
27054
|
[]
|
|
26663
27055
|
).option(
|
|
26664
27056
|
"--spent-gte <minorUnits>",
|
|
@@ -26700,6 +27092,8 @@ function describeCondition(condition) {
|
|
|
26700
27092
|
return condition.operator === "has" ? "has a phone number" : "has no phone number";
|
|
26701
27093
|
case "campaign":
|
|
26702
27094
|
return `${condition.operator.replace("-", " ")} campaign ${condition.campaignId}`;
|
|
27095
|
+
case "email":
|
|
27096
|
+
return `email ${condition.operator === "is-any" ? "is one of" : "is none of"} ${condition.emails.join(", ")} (set in the dashboard)`;
|
|
26703
27097
|
}
|
|
26704
27098
|
}
|
|
26705
27099
|
function registerSegmentsCommands(program2) {
|
|
@@ -27074,15 +27468,25 @@ function registerSiteCommands(program2) {
|
|
|
27074
27468
|
);
|
|
27075
27469
|
update.command("general").description(
|
|
27076
27470
|
"Page title/description, language, showPageTitle, mainPage* (undefined = untouched)"
|
|
27077
|
-
).option("--title <text>", "browser/page title of the site").option("--description <text>", "
|
|
27471
|
+
).option("--title <text>", "browser/page title of the site").option("--description <text>", "short description (storefront header + meta fallback)").option(
|
|
27472
|
+
"--seo-description <text>",
|
|
27473
|
+
"meta description for search engines (empty = short description)"
|
|
27474
|
+
).option(
|
|
27475
|
+
"--robots-rules <text>",
|
|
27476
|
+
String.raw`extra robots.txt lines (User-agent / Allow / Disallow / Crawl-delay); separate lines with \n, or use --json`
|
|
27477
|
+
).option("--language <pl|en|default>", "UI language of the public site").option("--show-page-title <on|off>", "render the page title in the header").option(
|
|
27078
27478
|
"--json <fileOrJson>",
|
|
27079
|
-
"inline JSON object or .json path \u2014 pageTitle, pageDescription, showPageTitle, language, mainPage, mainPageId, mainPageCommunityId"
|
|
27479
|
+
"inline JSON object or .json path \u2014 pageTitle, pageDescription, seoDescription, robotsTxtRules, showPageTitle, language, mainPage, mainPageId, mainPageCommunityId"
|
|
27080
27480
|
).action(
|
|
27081
27481
|
async (options, command) => {
|
|
27082
27482
|
const flags = getGlobalFlags(command);
|
|
27083
27483
|
const body = baseBody(options.json);
|
|
27084
27484
|
if (options.title !== void 0) body.pageTitle = options.title;
|
|
27085
27485
|
if (options.description !== void 0) body.pageDescription = options.description;
|
|
27486
|
+
if (options.seoDescription !== void 0) body.seoDescription = options.seoDescription;
|
|
27487
|
+
if (options.robotsRules !== void 0) {
|
|
27488
|
+
body.robotsTxtRules = options.robotsRules.split(String.raw`\n`).join("\n");
|
|
27489
|
+
}
|
|
27086
27490
|
if (options.language !== void 0) {
|
|
27087
27491
|
body.language = parseChoice7(options.language, PLATFORM_LANGUAGES, "--language");
|
|
27088
27492
|
}
|
|
@@ -29391,17 +29795,17 @@ var GRAPH_NODE_KINDS = /* @__PURE__ */ new Set([
|
|
|
29391
29795
|
]);
|
|
29392
29796
|
var GRAPH_CONDITION_BRANCHES = ["true", "false"];
|
|
29393
29797
|
var EXCERPT_LENGTH = 120;
|
|
29394
|
-
function
|
|
29798
|
+
function isRecord3(value) {
|
|
29395
29799
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
29396
29800
|
}
|
|
29397
29801
|
function optionalString(value) {
|
|
29398
29802
|
return typeof value === "string" ? value : void 0;
|
|
29399
29803
|
}
|
|
29400
29804
|
function asAutomationGraph(raw) {
|
|
29401
|
-
if (!
|
|
29805
|
+
if (!isRecord3(raw) || !Array.isArray(raw.nodes) || !Array.isArray(raw.edges)) return null;
|
|
29402
29806
|
const nodes = [];
|
|
29403
29807
|
for (const candidate of raw.nodes) {
|
|
29404
|
-
if (!
|
|
29808
|
+
if (!isRecord3(candidate)) continue;
|
|
29405
29809
|
const { id, kind, config, name, disabled } = candidate;
|
|
29406
29810
|
if (typeof id !== "string" || typeof kind !== "string" || !GRAPH_NODE_KINDS.has(kind)) {
|
|
29407
29811
|
continue;
|
|
@@ -29409,14 +29813,14 @@ function asAutomationGraph(raw) {
|
|
|
29409
29813
|
nodes.push({
|
|
29410
29814
|
id,
|
|
29411
29815
|
kind,
|
|
29412
|
-
config:
|
|
29816
|
+
config: isRecord3(config) ? config : {},
|
|
29413
29817
|
...typeof name === "string" && name.length > 0 ? { name } : {},
|
|
29414
29818
|
...disabled === true ? { disabled: true } : {}
|
|
29415
29819
|
});
|
|
29416
29820
|
}
|
|
29417
29821
|
const edges = [];
|
|
29418
29822
|
for (const candidate of raw.edges) {
|
|
29419
|
-
if (!
|
|
29823
|
+
if (!isRecord3(candidate)) continue;
|
|
29420
29824
|
const { source, target, sourceHandle } = candidate;
|
|
29421
29825
|
if (typeof source !== "string" || typeof target !== "string") continue;
|
|
29422
29826
|
const branch = GRAPH_CONDITION_BRANCHES.find((b) => b === sourceHandle);
|
|
@@ -29426,7 +29830,7 @@ function asAutomationGraph(raw) {
|
|
|
29426
29830
|
}
|
|
29427
29831
|
function emailConfigOf(node) {
|
|
29428
29832
|
const raw = node.config.email;
|
|
29429
|
-
if (!
|
|
29833
|
+
if (!isRecord3(raw)) return null;
|
|
29430
29834
|
return {
|
|
29431
29835
|
templateId: optionalString(raw.templateId) ?? null,
|
|
29432
29836
|
templateName: optionalString(raw.templateName) ?? null,
|
|
@@ -29578,7 +29982,7 @@ function isKnownActionType(value) {
|
|
|
29578
29982
|
function describeAction(node, templateNames) {
|
|
29579
29983
|
const { taskType } = node.config;
|
|
29580
29984
|
if (!isKnownActionType(taskType)) {
|
|
29581
|
-
const inner =
|
|
29985
|
+
const inner = isRecord3(node.config.action) ? optionalString(node.config.action.type) : void 0;
|
|
29582
29986
|
return `${typeof taskType === "string" ? taskType : "action"}${inner ? ` ${inner}` : ""}`;
|
|
29583
29987
|
}
|
|
29584
29988
|
const label = GRAPH_ACTION_LABELS[taskType];
|
|
@@ -29586,39 +29990,39 @@ function describeAction(node, templateNames) {
|
|
|
29586
29990
|
case "sendEmail":
|
|
29587
29991
|
return `${label} \u2014 ${describeEmail(node, templateNames)}`;
|
|
29588
29992
|
case "notifyOwner": {
|
|
29589
|
-
const owner =
|
|
29993
|
+
const owner = isRecord3(node.config.ownerNotification) ? node.config.ownerNotification : {};
|
|
29590
29994
|
return `${label} \u2014 subject: ${quote(optionalString(owner.subject))} \u2014 body: ${quote(excerptHtml(optionalString(owner.html)))}`;
|
|
29591
29995
|
}
|
|
29592
29996
|
case "sendSms": {
|
|
29593
|
-
const sms =
|
|
29997
|
+
const sms = isRecord3(node.config.sms) ? node.config.sms : {};
|
|
29594
29998
|
return `${label} \u2014 text: ${quote(excerptText(optionalString(sms.text)))}`;
|
|
29595
29999
|
}
|
|
29596
30000
|
case "webhook": {
|
|
29597
|
-
const webhook =
|
|
30001
|
+
const webhook = isRecord3(node.config.webhook) ? node.config.webhook : {};
|
|
29598
30002
|
const method = optionalString(webhook.method) ?? optionalString(webhook.type);
|
|
29599
30003
|
return `${label} \u2014 ${method ? `${method.toUpperCase()} ` : ""}${optionalString(webhook.url) ?? "(no url)"}`;
|
|
29600
30004
|
}
|
|
29601
30005
|
case "publishSocialPost": {
|
|
29602
|
-
const social =
|
|
30006
|
+
const social = isRecord3(node.config.social) ? node.config.social : {};
|
|
29603
30007
|
const targets = Array.isArray(social.targets) ? social.targets.join(", ") : "";
|
|
29604
30008
|
return `${label}${optionalString(social.mode) ? ` (${social.mode})` : ""}${targets ? ` to ${targets}` : ""} \u2014 text: ${quote(excerptText(optionalString(social.text)))}`;
|
|
29605
30009
|
}
|
|
29606
30010
|
case "createCommunityPost": {
|
|
29607
|
-
const post =
|
|
30011
|
+
const post = isRecord3(node.config.communityPost) ? node.config.communityPost : {};
|
|
29608
30012
|
const title = optionalString(post.title);
|
|
29609
30013
|
return `${label} in channel ${optionalString(post.channelId) ?? "?"}${title ? ` \u2014 title: ${quote(title)}` : ""} \u2014 text: ${quote(excerptText(optionalString(post.text)))}`;
|
|
29610
30014
|
}
|
|
29611
30015
|
case "updateClientTags": {
|
|
29612
|
-
const tags =
|
|
30016
|
+
const tags = isRecord3(node.config.clientTags) ? node.config.clientTags : {};
|
|
29613
30017
|
const ids = Array.isArray(tags.tagIds) ? tags.tagIds.join(", ") : "";
|
|
29614
30018
|
return `${label} \u2014 ${optionalString(tags.mode) ?? "update"} ${ids || "(no tags)"}`;
|
|
29615
30019
|
}
|
|
29616
30020
|
case "setEmailSubscription": {
|
|
29617
|
-
const sub =
|
|
30021
|
+
const sub = isRecord3(node.config.emailSubscription) ? node.config.emailSubscription : {};
|
|
29618
30022
|
return `${label} \u2014 ${optionalString(sub.status) ?? "?"}`;
|
|
29619
30023
|
}
|
|
29620
30024
|
case "openClientDeadline": {
|
|
29621
|
-
const deadline =
|
|
30025
|
+
const deadline = isRecord3(node.config.clientDeadline) ? node.config.clientDeadline : {};
|
|
29622
30026
|
const amount = typeof deadline.amount === "number" ? String(deadline.amount) : "?";
|
|
29623
30027
|
const endTime = optionalString(deadline.endTime);
|
|
29624
30028
|
return `${label} \u2014 ${optionalString(deadline.key) ?? "?"} for ${amount} ${optionalString(deadline.unit) ?? "?"}${endTime ? `, closes at ${endTime}` : ""}`;
|
|
@@ -29637,7 +30041,7 @@ function describeEmail(node, templateNames) {
|
|
|
29637
30041
|
return `subject: ${quote(email.subject)} \u2014 body: ${quote(excerptHtml(email.html))}`;
|
|
29638
30042
|
}
|
|
29639
30043
|
function describeCondition2(config) {
|
|
29640
|
-
const rules = Array.isArray(config.rules) ? config.rules.filter(
|
|
30044
|
+
const rules = Array.isArray(config.rules) ? config.rules.filter(isRecord3) : [];
|
|
29641
30045
|
if (rules.length === 0) return "(no rules)";
|
|
29642
30046
|
return rules.map(describeRule).join(" AND ");
|
|
29643
30047
|
}
|
|
@@ -29662,7 +30066,11 @@ function describeDelay(config) {
|
|
|
29662
30066
|
switch (mode) {
|
|
29663
30067
|
case void 0:
|
|
29664
30068
|
case "duration":
|
|
29665
|
-
return `wait ${typeof config.amount === "number" ? config.amount : "?"} ${optionalString(config.unit) ?? ""}`.trimEnd();
|
|
30069
|
+
return `wait ${typeof config.amount === "number" ? config.amount : "?"} ${optionalString(config.unit) ?? ""}${optionalString(config.atTime) ? `, then at ${config.atTime}` : ""}`.trimEnd();
|
|
30070
|
+
case "untilTime": {
|
|
30071
|
+
const weekdays = Array.isArray(config.weekdays) ? config.weekdays.join(",") : "";
|
|
30072
|
+
return `wait until the next ${optionalString(config.time) ?? "?"}${weekdays ? ` on weekdays ${weekdays}` : ""}${optionalString(config.timezone) ? ` (${config.timezone})` : ""}`;
|
|
30073
|
+
}
|
|
29666
30074
|
case "untilWeekday":
|
|
29667
30075
|
return `wait until weekday ${typeof config.weekday === "number" ? config.weekday : "?"} at ${optionalString(config.time) ?? "?"}${optionalString(config.timezone) ? ` (${config.timezone})` : ""}`;
|
|
29668
30076
|
case "untilDate":
|
|
@@ -29690,6 +30098,40 @@ function excerptText(text) {
|
|
|
29690
30098
|
}
|
|
29691
30099
|
|
|
29692
30100
|
// src/commands/workspace/index.ts
|
|
30101
|
+
function toLoginRow(profile) {
|
|
30102
|
+
const { credentials } = profile;
|
|
30103
|
+
return {
|
|
30104
|
+
key: profile.key,
|
|
30105
|
+
active: profile.active,
|
|
30106
|
+
workspaceId: credentials.workspaceId ?? null,
|
|
30107
|
+
workspaceName: credentials.workspaceName ?? null,
|
|
30108
|
+
workspaceSlug: credentials.workspaceSlug ?? null,
|
|
30109
|
+
kind: credentials.kind ?? "workspace",
|
|
30110
|
+
userEmail: credentials.userEmail ?? null,
|
|
30111
|
+
source: credentials.source ?? "manual",
|
|
30112
|
+
expiresAt: credentials.expiresAt ?? null
|
|
30113
|
+
};
|
|
30114
|
+
}
|
|
30115
|
+
function printLogins(profiles) {
|
|
30116
|
+
if (profiles.length === 0) {
|
|
30117
|
+
process.stdout.write("No stored logins. Run `zanfia login` (add --workspace <id|slug>).\n");
|
|
30118
|
+
return;
|
|
30119
|
+
}
|
|
30120
|
+
for (const profile of profiles) {
|
|
30121
|
+
const row = toLoginRow(profile);
|
|
30122
|
+
const name = row.workspaceName ?? "(unnamed)";
|
|
30123
|
+
const slug = row.workspaceSlug ? ` [${row.workspaceSlug}]` : "";
|
|
30124
|
+
const member = row.kind === "member" ? " (community member)" : "";
|
|
30125
|
+
const expires = row.expiresAt ? `, expires ${row.expiresAt.slice(0, 10)}` : "";
|
|
30126
|
+
process.stdout.write(
|
|
30127
|
+
`${row.active ? "*" : " "} ${name}${slug}${member} - ${row.workspaceId ?? row.key}${row.userEmail ? `, ${row.userEmail}` : ""}${expires}
|
|
30128
|
+
`
|
|
30129
|
+
);
|
|
30130
|
+
}
|
|
30131
|
+
process.stdout.write(
|
|
30132
|
+
"\n* = active. Switch: `zanfia workspace use <id|slug>`; one command: `--workspace <id|slug>`.\n"
|
|
30133
|
+
);
|
|
30134
|
+
}
|
|
29693
30135
|
function parseOnOff5(value, flag) {
|
|
29694
30136
|
const match = ["on", "off"].find((v) => v === value);
|
|
29695
30137
|
if (!match) throw new Error(`Invalid ${flag}: ${value} (expected on | off)`);
|
|
@@ -29731,8 +30173,23 @@ function printSettings(settings) {
|
|
|
29731
30173
|
}
|
|
29732
30174
|
function registerWorkspaceCommands(program2) {
|
|
29733
30175
|
const workspace2 = program2.command("workspace").description(
|
|
29734
|
-
|
|
30176
|
+
"Workspace-level settings (the name, the scheduling timezone and the video transcription defaults) and the workspace logins stored on this device (list, use)"
|
|
29735
30177
|
);
|
|
30178
|
+
workspace2.command("list").description("List the workspace logins stored on this device (* = active)").action((_options, command) => {
|
|
30179
|
+
const flags = getGlobalFlags(command);
|
|
30180
|
+
const profiles = listStoredProfiles();
|
|
30181
|
+
emit(flags, profiles.map(toLoginRow), () => printLogins(profiles));
|
|
30182
|
+
});
|
|
30183
|
+
workspace2.command("use").description("Make another stored workspace login the active one (no new login)").argument("<id|slug>", "workspace id, site slug or name of a stored login").action((ref, _options, command) => {
|
|
30184
|
+
const flags = getGlobalFlags(command);
|
|
30185
|
+
const profile = useStoredProfile(ref);
|
|
30186
|
+
emit(flags, toLoginRow(profile), () => {
|
|
30187
|
+
process.stdout.write(
|
|
30188
|
+
`Active workspace: ${profile.credentials.workspaceName ?? "?"} (${profile.credentials.workspaceId ?? profile.key})
|
|
30189
|
+
`
|
|
30190
|
+
);
|
|
30191
|
+
});
|
|
30192
|
+
});
|
|
29736
30193
|
workspace2.command("get", { isDefault: true }).description("Show the workspace settings").action(async (_options, command) => {
|
|
29737
30194
|
const flags = getGlobalFlags(command);
|
|
29738
30195
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -29829,8 +30286,8 @@ function applyShortHelp(program2) {
|
|
|
29829
30286
|
|
|
29830
30287
|
// src/program.ts
|
|
29831
30288
|
function resolveCliVersion() {
|
|
29832
|
-
if ("0.
|
|
29833
|
-
return "0.
|
|
30289
|
+
if ("0.6.4".length > 0) {
|
|
30290
|
+
return "0.6.4";
|
|
29834
30291
|
}
|
|
29835
30292
|
try {
|
|
29836
30293
|
const raw = readFileSync28(new URL("../package.json", import.meta.url), "utf8");
|
|
@@ -29871,7 +30328,7 @@ Signing in \u2014 two roles, two consent pages:
|
|
|
29871
30328
|
registerLoginCommand(program2);
|
|
29872
30329
|
registerRegisterCommand(program2);
|
|
29873
30330
|
registerJoinCommand(program2);
|
|
29874
|
-
registerLogoutCommand(program2
|
|
30331
|
+
registerLogoutCommand(program2);
|
|
29875
30332
|
registerAuthCommands(program2);
|
|
29876
30333
|
registerProductsCommands(program2);
|
|
29877
30334
|
registerOffersCommands(program2);
|