@zanfia/cli 0.23.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -68
- package/dist/index.js +553 -62
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
# @zanfia/cli (`zanfia`)
|
|
2
|
-
|
|
3
|
-
Thin HTTP client over the Zanfia Public API.
|
|
4
|
-
|
|
5
|
-
> **Status:** published to npm as `@zanfia/cli` (bin `zanfia`), talking to the production Workspace API by default (`ZANFIA_CLI_TARGET=prod` bakes the URLs at build time). Set `ZANFIA_API_URL` to target another environment; local `tsx` runs without baked defaults fall back to the in-memory mock.
|
|
6
|
-
|
|
7
|
-
## Local usage
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
# from the monorepo root, after `pnpm install`
|
|
11
|
-
pnpm --filter @zanfia/cli dev products list
|
|
12
|
-
|
|
13
|
-
# or run a one-off
|
|
14
|
-
pnpm --filter @zanfia/cli dev auth whoami
|
|
15
|
-
|
|
16
|
-
# global flags (--json, --verbose, --api-key) must come BEFORE the subcommand:
|
|
17
|
-
pnpm --filter @zanfia/cli dev --json products list
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## System emails
|
|
21
|
-
|
|
22
|
-
`zanfia emails` manages the customer-facing emails Zanfia sends in your name (Site → Emails):
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
zanfia emails list # key, group, status, subject
|
|
26
|
-
zanfia emails get purchase-confirmation # subject/headline EN+PL, layout blocks, variables
|
|
27
|
-
zanfia emails set purchase-confirmation --subject-pl "Dzięki za zakup {{productName}}"
|
|
28
|
-
zanfia emails set access-ending-reminder --disable # optional emails only
|
|
29
|
-
zanfia emails set login-code --layout ./layout.json # full EmailBlock[] (inline JSON or file)
|
|
30
|
-
zanfia emails set login-code --reset # back to the stock email
|
|
31
|
-
zanfia emails branding get
|
|
32
|
-
zanfia emails branding set --accent-color "#2563eb" --reply-to none
|
|
33
|
-
zanfia emails test purchase-confirmation --lang en # queued to YOUR own address
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Raw HTTP: `GET|PATCH /email-templates`, `POST /email-templates/{key}/test-sends`. Full reference for every
|
|
37
|
-
command group: `.claude/skills/zanfia-cli/SKILL.md`.
|
|
38
|
-
|
|
39
|
-
## Community member keys (read-only option)
|
|
40
|
-
|
|
41
|
-
A member key acts as a creator's customer in their communities (Community API + the member MCP
|
|
42
|
-
tools), never in a workspace. Three ways to get one:
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
zanfia login --address https://<creator-platform-url> [--read-only] # browser consent on that platform
|
|
46
|
-
zanfia join <handle> [--read-only] # public community, emailed code
|
|
47
|
-
zanfia auth set-key --api-url <api-community-url> <key> # key from the platform's settings
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
`--read-only` (or „Tylko odczyt” when generating a key under **Settings → AI agents** on the
|
|
51
|
-
platform) grants `communities:read` only: the key reads and summarizes, and
|
|
52
|
-
every write (post, comment, chat, ticket) gets a 403 / a refused MCP tool. `zanfia auth status` and
|
|
53
|
-
`zanfia whoami` show the scope, e.g. `scope : communities:read (read-only)`.
|
|
54
|
-
|
|
55
|
-
## Auth resolution order
|
|
56
|
-
|
|
57
|
-
1. `--api-key` flag
|
|
58
|
-
2. `ZANFIA_API_KEY` environment variable
|
|
59
|
-
3. Stored key in `~/.config/zanfia/credentials.json`
|
|
60
|
-
4. Prompt to run `zanfia auth set-key`
|
|
61
|
-
|
|
62
|
-
## Environment
|
|
63
|
-
|
|
64
|
-
| Variable | Purpose |
|
|
65
|
-
| ----------------- | ------------------------------------------------ |
|
|
66
|
-
| `ZANFIA_API_URL` | Public API base URL. Unset → mock mode. |
|
|
67
|
-
| `ZANFIA_API_KEY` | API key (overrides stored credentials). |
|
|
68
|
-
| `ZANFIA_CLI_MOCK` | Force mock mode even if `ZANFIA_API_URL` is set. |
|
|
1
|
+
# @zanfia/cli (`zanfia`)
|
|
2
|
+
|
|
3
|
+
Thin HTTP client over the Zanfia Public API.
|
|
4
|
+
|
|
5
|
+
> **Status:** published to npm as `@zanfia/cli` (bin `zanfia`), talking to the production Workspace API by default (`ZANFIA_CLI_TARGET=prod` bakes the URLs at build time). Set `ZANFIA_API_URL` to target another environment; local `tsx` runs without baked defaults fall back to the in-memory mock.
|
|
6
|
+
|
|
7
|
+
## Local usage
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# from the monorepo root, after `pnpm install`
|
|
11
|
+
pnpm --filter @zanfia/cli dev products list
|
|
12
|
+
|
|
13
|
+
# or run a one-off
|
|
14
|
+
pnpm --filter @zanfia/cli dev auth whoami
|
|
15
|
+
|
|
16
|
+
# global flags (--json, --verbose, --api-key) must come BEFORE the subcommand:
|
|
17
|
+
pnpm --filter @zanfia/cli dev --json products list
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## System emails
|
|
21
|
+
|
|
22
|
+
`zanfia emails` manages the customer-facing emails Zanfia sends in your name (Site → Emails):
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
zanfia emails list # key, group, status, subject
|
|
26
|
+
zanfia emails get purchase-confirmation # subject/headline EN+PL, layout blocks, variables
|
|
27
|
+
zanfia emails set purchase-confirmation --subject-pl "Dzięki za zakup {{productName}}"
|
|
28
|
+
zanfia emails set access-ending-reminder --disable # optional emails only
|
|
29
|
+
zanfia emails set login-code --layout ./layout.json # full EmailBlock[] (inline JSON or file)
|
|
30
|
+
zanfia emails set login-code --reset # back to the stock email
|
|
31
|
+
zanfia emails branding get
|
|
32
|
+
zanfia emails branding set --accent-color "#2563eb" --reply-to none
|
|
33
|
+
zanfia emails test purchase-confirmation --lang en # queued to YOUR own address
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Raw HTTP: `GET|PATCH /email-templates`, `POST /email-templates/{key}/test-sends`. Full reference for every
|
|
37
|
+
command group: `.claude/skills/zanfia-cli/SKILL.md`.
|
|
38
|
+
|
|
39
|
+
## Community member keys (read-only option)
|
|
40
|
+
|
|
41
|
+
A member key acts as a creator's customer in their communities (Community API + the member MCP
|
|
42
|
+
tools), never in a workspace. Three ways to get one:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
zanfia login --address https://<creator-platform-url> [--read-only] # browser consent on that platform
|
|
46
|
+
zanfia join <handle> [--read-only] # public community, emailed code
|
|
47
|
+
zanfia auth set-key --api-url <api-community-url> <key> # key from the platform's settings
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`--read-only` (or „Tylko odczyt” when generating a key under **Settings → AI agents** on the
|
|
51
|
+
platform) grants `communities:read` only: the key reads and summarizes, and
|
|
52
|
+
every write (post, comment, chat, ticket) gets a 403 / a refused MCP tool. `zanfia auth status` and
|
|
53
|
+
`zanfia whoami` show the scope, e.g. `scope : communities:read (read-only)`.
|
|
54
|
+
|
|
55
|
+
## Auth resolution order
|
|
56
|
+
|
|
57
|
+
1. `--api-key` flag
|
|
58
|
+
2. `ZANFIA_API_KEY` environment variable
|
|
59
|
+
3. Stored key in `~/.config/zanfia/credentials.json`
|
|
60
|
+
4. Prompt to run `zanfia auth set-key`
|
|
61
|
+
|
|
62
|
+
## Environment
|
|
63
|
+
|
|
64
|
+
| Variable | Purpose |
|
|
65
|
+
| ----------------- | ------------------------------------------------ |
|
|
66
|
+
| `ZANFIA_API_URL` | Public API base URL. Unset → mock mode. |
|
|
67
|
+
| `ZANFIA_API_KEY` | API key (overrides stored credentials). |
|
|
68
|
+
| `ZANFIA_CLI_MOCK` | Force mock mode even if `ZANFIA_API_URL` is set. |
|
package/dist/index.js
CHANGED
|
@@ -6901,7 +6901,7 @@ var {
|
|
|
6901
6901
|
} = import_index.default;
|
|
6902
6902
|
|
|
6903
6903
|
// src/program.ts
|
|
6904
|
-
import { readFileSync as
|
|
6904
|
+
import { readFileSync as readFileSync31 } from "fs";
|
|
6905
6905
|
|
|
6906
6906
|
// src/utils/config.ts
|
|
6907
6907
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
@@ -8621,6 +8621,9 @@ function createMockClient() {
|
|
|
8621
8621
|
async previewAudienceSegment() {
|
|
8622
8622
|
throw new ApiError(501, "audience segments are not available in mock mode");
|
|
8623
8623
|
},
|
|
8624
|
+
async checkEmailConditions() {
|
|
8625
|
+
throw new ApiError(501, "e-mail conditions are not available in mock mode");
|
|
8626
|
+
},
|
|
8624
8627
|
async importClients() {
|
|
8625
8628
|
throw new ApiError(501, "client import is not available in mock mode");
|
|
8626
8629
|
},
|
|
@@ -8990,6 +8993,18 @@ function createMockClient() {
|
|
|
8990
8993
|
async revokeLiveRoomStudioGuestInvite() {
|
|
8991
8994
|
throw new ApiError(501, "webinars are not available in mock mode");
|
|
8992
8995
|
},
|
|
8996
|
+
async listLiveRoomStudioOutputs() {
|
|
8997
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
8998
|
+
},
|
|
8999
|
+
async createLiveRoomStudioOutput() {
|
|
9000
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
9001
|
+
},
|
|
9002
|
+
async updateLiveRoomStudioOutput() {
|
|
9003
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
9004
|
+
},
|
|
9005
|
+
async deleteLiveRoomStudioOutput() {
|
|
9006
|
+
throw new ApiError(501, "webinars are not available in mock mode");
|
|
9007
|
+
},
|
|
8993
9008
|
async call(method, pathTemplate) {
|
|
8994
9009
|
throw new ApiError(501, `${method} ${pathTemplate} is not available in mock mode`);
|
|
8995
9010
|
},
|
|
@@ -9122,6 +9137,9 @@ function createMockClient() {
|
|
|
9122
9137
|
async getSiteSettings() {
|
|
9123
9138
|
throw new ApiError(501, "site settings are not available in mock mode");
|
|
9124
9139
|
},
|
|
9140
|
+
async getSiteCustomScripts() {
|
|
9141
|
+
throw new ApiError(501, "site settings are not available in mock mode");
|
|
9142
|
+
},
|
|
9125
9143
|
async updateSiteGeneral() {
|
|
9126
9144
|
throw new ApiError(501, "site settings are not available in mock mode");
|
|
9127
9145
|
},
|
|
@@ -9985,6 +10003,7 @@ function createHttpClient(config) {
|
|
|
9985
10003
|
...options.requireSendable ? { requireSendable: true } : {},
|
|
9986
10004
|
...options.requireSmsSendable ? { requireSmsSendable: true } : {}
|
|
9987
10005
|
}),
|
|
10006
|
+
checkEmailConditions: (input) => request("POST", "/automations/check-email-conditions", input),
|
|
9988
10007
|
importClients: (input) => request("POST", "/clients/import", input),
|
|
9989
10008
|
getClientMergePreview: (survivorClientId, duplicateClientId) => {
|
|
9990
10009
|
const params = new URLSearchParams();
|
|
@@ -10343,6 +10362,17 @@ function createHttpClient(config) {
|
|
|
10343
10362
|
"DELETE",
|
|
10344
10363
|
`/live-rooms/${encodeURIComponent(roomId)}/studio/guests/${encodeURIComponent(guestId)}`
|
|
10345
10364
|
),
|
|
10365
|
+
listLiveRoomStudioOutputs: (roomId) => request("GET", `/live-rooms/${encodeURIComponent(roomId)}/studio/outputs`),
|
|
10366
|
+
createLiveRoomStudioOutput: (roomId, input) => request("POST", `/live-rooms/${encodeURIComponent(roomId)}/studio/outputs`, input),
|
|
10367
|
+
updateLiveRoomStudioOutput: (roomId, outputId, input) => request(
|
|
10368
|
+
"PATCH",
|
|
10369
|
+
`/live-rooms/${encodeURIComponent(roomId)}/studio/outputs/${encodeURIComponent(outputId)}`,
|
|
10370
|
+
input
|
|
10371
|
+
),
|
|
10372
|
+
deleteLiveRoomStudioOutput: (roomId, outputId) => request(
|
|
10373
|
+
"DELETE",
|
|
10374
|
+
`/live-rooms/${encodeURIComponent(roomId)}/studio/outputs/${encodeURIComponent(outputId)}`
|
|
10375
|
+
),
|
|
10346
10376
|
getLiveRoomAttendance: (roomId, session) => request(
|
|
10347
10377
|
"GET",
|
|
10348
10378
|
`/live-rooms/${encodeURIComponent(roomId)}/attendance${session !== void 0 ? `?session=${encodeURIComponent(session)}` : ""}`
|
|
@@ -10412,6 +10442,7 @@ function createHttpClient(config) {
|
|
|
10412
10442
|
deleteOtoOffer: (id) => request("DELETE", `/oto-offers/${encodeURIComponent(id)}`),
|
|
10413
10443
|
// Site (platform settings / storefront / legal documents) lives on the Workspace API (api-v1).
|
|
10414
10444
|
getSiteSettings: () => request("GET", "/site/settings"),
|
|
10445
|
+
getSiteCustomScripts: () => request("GET", "/site/custom-scripts"),
|
|
10415
10446
|
updateSiteGeneral: (input) => request("PATCH", "/site/settings/general", input),
|
|
10416
10447
|
updateSiteTheme: (input) => request("PATCH", "/site/settings/theme", input),
|
|
10417
10448
|
updateSiteBranding: (input) => request("PATCH", "/site/settings/branding", input),
|
|
@@ -11183,7 +11214,7 @@ function registerBlogCommands(program2) {
|
|
|
11183
11214
|
"Schedules a DRAFT (or reschedules a scheduled post) to be published at `scheduledAt`: status `scheduled`, not public anywhere (list, feed, sitemap, its URL) until then, then published exactly like publish-blog-post. Needs a non-empty body (400); a published post answers 409 (unpublish it first). `scheduledAt: null` unschedules (back to draft); publishing it now is publish-blog-post. Returns the full post with `scheduledAt`."
|
|
11184
11215
|
).argument("<id>", "Blog post id (`post_\u2026`) from list-blog-posts.").requiredOption(
|
|
11185
11216
|
"--at <value>",
|
|
11186
|
-
|
|
11217
|
+
`When the post goes live: the workspace's local time without an offset ("2026-10-26T08:00", read in the workspace time zone: its own zone from Ustawienia \u2192 Workspace, else Europe/Warsaw for a Polish workspace (PL invoice / pl site), else the owner's account zone, else Europe/Warsaw; with the offset of that date, also across a clock change) or an ISO 8601 date-time with an offset / Z (2026-10-26T08:00:00+01:00). At least a minute and at most a year ahead. null = unschedule (back to draft).`
|
|
11187
11218
|
).action(async (id, options, command) => {
|
|
11188
11219
|
const flags = getGlobalFlags(command);
|
|
11189
11220
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -11311,7 +11342,7 @@ function registerSiteRedirectsCommands(program2) {
|
|
|
11311
11342
|
emit(flags, result ?? {}, () => printTable(rows, listSiteRedirectsColumns));
|
|
11312
11343
|
});
|
|
11313
11344
|
siteRedirects.command("create").summary("Add a site redirect").description(
|
|
11314
|
-
"Adds one line at the end of the site's 301 map; live at once. Refused (400 `site-redirects-refused`, `issues[]` = index, from, to, issue) exactly like the dashboard save: a malformed address (`invalid-from` / `invalid-to`), the home page or an address Zanfia owns (`/blog`, `/p/\u2026`, `/app`: `reserved-from`), the member area (`portal-from`), a published page (`page-exists`), a `from` the map already has (`duplicate`), a chain or
|
|
11345
|
+
"Adds one line at the end of the site's 301 map; live at once. Refused (400 `site-redirects-refused`, `issues[]` = index, from, to, issue) exactly like the dashboard save: a malformed address (`invalid-from` / `invalid-to`), the home page or an address Zanfia owns (`/blog`, `/p/\u2026`, `/app`: `reserved-from`), the member area (`portal-from`), a published page (`page-exists`), a `from` the map already has (`duplicate`), a `to` that is another line (`chain`) or the line itself (`loop`), `/*` in `to` without a folder `from` (`rest-without-folder`), over 500 lines (`too-many-entries`). Returns the line as stored."
|
|
11315
11346
|
).argument(
|
|
11316
11347
|
"<from>",
|
|
11317
11348
|
"The old address on the site: a path (`/o-nas`), a full address on the site (`https://example.com/o-nas/`, stored as its path) or a folder ending with `/*` (`/news/*`, every address under it). Query string and `#\u2026` are dropped; case is kept."
|
|
@@ -12306,6 +12337,186 @@ function registerTaxCommands(program2) {
|
|
|
12306
12337
|
});
|
|
12307
12338
|
}
|
|
12308
12339
|
|
|
12340
|
+
// src/commands/_generated/social.ts
|
|
12341
|
+
function registerSocialCommands(program2) {
|
|
12342
|
+
const social = program2.commands.find((command) => command.name() === "social") ?? program2.command("social", { hidden: true });
|
|
12343
|
+
if (!social.description()) {
|
|
12344
|
+
social.description("Automatic messages after an Instagram comment (keyword \u2192 private message)");
|
|
12345
|
+
}
|
|
12346
|
+
const socialCommentDm = social.command("comment-dm", { hidden: true });
|
|
12347
|
+
socialCommentDm.description(
|
|
12348
|
+
"Automatic messages after an Instagram comment (keyword \u2192 private message)"
|
|
12349
|
+
);
|
|
12350
|
+
socialCommentDm.command("list", { hidden: true }).summary("List the automatic messages after a comment").description(
|
|
12351
|
+
"The workspace\u2019s \u201EAutomatyczna wiadomo\u015B\u0107 po komentarzu\u201D rules on Instagram, newest first: the account, the post (or every post of the account), the keywords, the message, the link as sent (`taggedLink`, with UTM tags), the public reply, `status` (active / paused), `ended`, and the results so far in `counts` (matched comments, `sent` and `failed` private messages) with the latest failure. Read-only. 403 `feature_not_available` while the workspace has no early access."
|
|
12352
|
+
).option(
|
|
12353
|
+
"--account <value>",
|
|
12354
|
+
"Only this Instagram account's rules: its id or its target from list-social-accounts. Omitted = every account."
|
|
12355
|
+
).action(async (options, command) => {
|
|
12356
|
+
const flags = getGlobalFlags(command);
|
|
12357
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12358
|
+
const params = {};
|
|
12359
|
+
const payload = {
|
|
12360
|
+
accountId: options.account
|
|
12361
|
+
};
|
|
12362
|
+
const result = await client.call(
|
|
12363
|
+
"GET",
|
|
12364
|
+
"/social/instagram/comment-dm-rules",
|
|
12365
|
+
params,
|
|
12366
|
+
payload,
|
|
12367
|
+
void 0
|
|
12368
|
+
);
|
|
12369
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12370
|
+
});
|
|
12371
|
+
socialCommentDm.command("create", { hidden: true }).summary("Create an automatic message after a comment").description(
|
|
12372
|
+
"Creates a \u201EAutomatyczna wiadomo\u015B\u0107 po komentarzu\u201D rule on Instagram: when someone comments one of the keywords under the post (or any post of the account), Instagram sends them ONE private message with the text and the link (UTM-tagged), optionally with a public reply under the comment. Each person gets it once per rule; comments older than 7 days get nothing. Same rules as the panel: 400 `invalid` (with `problem`), 404 `not_connected`, 409 `reconnect_required` (the account lacks the messages / comments permission), 409 `too_many_rules` (50 per workspace), 403 `feature_not_available` without the early access. `commentsSubscribed: false` = saved but Instagram refused the comment notifications (reconnect the account). Pause it with pause-instagram-comment-dm-rule."
|
|
12373
|
+
).option(
|
|
12374
|
+
"--account <value>",
|
|
12375
|
+
"The Instagram account: its id (`accountId` of a rule), or the `target` of an Instagram account from list-social-accounts (`instagram` = the main account, `instagram:{id}`). Omitted = the main connected Instagram account."
|
|
12376
|
+
).addOption(
|
|
12377
|
+
new Option(
|
|
12378
|
+
"--scope <post|account>",
|
|
12379
|
+
"`post` = comments under one post (send `mediaId` of a published post from list-instagram-media, or `socialPostId` of a composer post not published yet); `account` = comments under any post of the account."
|
|
12380
|
+
).choices(["post", "account"]).makeOptionMandatory()
|
|
12381
|
+
).option("--media-id <value>", "`post` scope: the published post\u2019s media id.").option(
|
|
12382
|
+
"--social-post-id <value>",
|
|
12383
|
+
"`post` scope: a composer post not live yet; the rule waits for its publishing."
|
|
12384
|
+
).option("--media-permalink <value>", "The post\u2019s Instagram link, shown on the rule.").option("--media-caption <value>", "The post\u2019s caption, shown on the rule.").requiredOption(
|
|
12385
|
+
"--keywords <a,b,...>",
|
|
12386
|
+
"Words or short phrases that trigger the message (1-10, each up to 40 characters). A comment matches when it contains one as whole words; case and Polish letters do not matter (\u201EPobierz!\u201D and \u201Epobierz\u201D match `pobierz`).",
|
|
12387
|
+
parseCommaList("--keywords")
|
|
12388
|
+
).requiredOption(
|
|
12389
|
+
"--text <value>",
|
|
12390
|
+
"The private message sent once to each person who comments a keyword. With the link it must fit Instagram\u2019s 1000 bytes."
|
|
12391
|
+
).option(
|
|
12392
|
+
"--link <value>",
|
|
12393
|
+
"Optional full http(s) link added to the message; Zanfia adds UTM tags (utm_source=instagram, utm_medium=comment_dm, utm_campaign=<first keyword>). null removes it."
|
|
12394
|
+
).option(
|
|
12395
|
+
"--public-reply <json|file>",
|
|
12396
|
+
"Optional public reply under the matching comment; off when omitted.",
|
|
12397
|
+
parseJsonObjectFileOrInline("--public-reply")
|
|
12398
|
+
).addOption(
|
|
12399
|
+
new Option(
|
|
12400
|
+
"--status <active|paused>",
|
|
12401
|
+
"Omitted = `active` (answers comments at once)."
|
|
12402
|
+
).choices(["active", "paused"])
|
|
12403
|
+
).option(
|
|
12404
|
+
"--ends-at <value>",
|
|
12405
|
+
"Optional ISO 8601 time after which the rule answers nothing; null = no end."
|
|
12406
|
+
).action(async (options, command) => {
|
|
12407
|
+
const flags = getGlobalFlags(command);
|
|
12408
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12409
|
+
const params = {};
|
|
12410
|
+
const payload = {
|
|
12411
|
+
accountId: options.account,
|
|
12412
|
+
scope: options.scope,
|
|
12413
|
+
mediaId: options.mediaId,
|
|
12414
|
+
socialPostId: options.socialPostId,
|
|
12415
|
+
mediaPermalink: options.mediaPermalink,
|
|
12416
|
+
mediaCaption: options.mediaCaption,
|
|
12417
|
+
keywords: options.keywords,
|
|
12418
|
+
dmText: options.text,
|
|
12419
|
+
link: options.link,
|
|
12420
|
+
publicReply: options.publicReply,
|
|
12421
|
+
status: options.status,
|
|
12422
|
+
endsAt: options.endsAt
|
|
12423
|
+
};
|
|
12424
|
+
const result = await client.call(
|
|
12425
|
+
"POST",
|
|
12426
|
+
"/social/instagram/comment-dm-rules",
|
|
12427
|
+
params,
|
|
12428
|
+
void 0,
|
|
12429
|
+
payload
|
|
12430
|
+
);
|
|
12431
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12432
|
+
});
|
|
12433
|
+
socialCommentDm.command("update", { hidden: true }).summary("Change or resume an automatic message after a comment").description(
|
|
12434
|
+
"Changes a \u201EAutomatyczna wiadomo\u015B\u0107 po komentarzu\u201D rule like the panel\u2019s edit: only the fields sent change (keywords, message, link, public reply, end date) and `status` pauses (`paused`) or resumes (`active`) it. The account and the post never change: a new post is a new rule. People who already got the message do not get it again. Same validation as a create (400 `invalid` with `problem`); 404 `rule_not_found`. Reversible: change it again."
|
|
12435
|
+
).argument("<id>", "The rule id (`id` from list-instagram-comment-dm-rules).").option(
|
|
12436
|
+
"--keywords <a,b,...>",
|
|
12437
|
+
"The full new keyword list (replaces the old one).",
|
|
12438
|
+
parseCommaList("--keywords")
|
|
12439
|
+
).option(
|
|
12440
|
+
"--text <value>",
|
|
12441
|
+
"The private message sent once to each person who comments a keyword. With the link it must fit Instagram\u2019s 1000 bytes."
|
|
12442
|
+
).option(
|
|
12443
|
+
"--link <value>",
|
|
12444
|
+
"Optional full http(s) link added to the message; Zanfia adds UTM tags (utm_source=instagram, utm_medium=comment_dm, utm_campaign=<first keyword>). null removes it."
|
|
12445
|
+
).option(
|
|
12446
|
+
"--public-reply <json|file>",
|
|
12447
|
+
"Optional public reply under the matching comment; off when omitted.",
|
|
12448
|
+
parseJsonObjectFileOrInline("--public-reply")
|
|
12449
|
+
).addOption(
|
|
12450
|
+
new Option(
|
|
12451
|
+
"--status <active|paused>",
|
|
12452
|
+
"`active` resumes the rule, `paused` pauses it."
|
|
12453
|
+
).choices(["active", "paused"])
|
|
12454
|
+
).option(
|
|
12455
|
+
"--ends-at <value>",
|
|
12456
|
+
"Optional ISO 8601 time after which the rule answers nothing; null = no end."
|
|
12457
|
+
).action(async (id, options, command) => {
|
|
12458
|
+
const flags = getGlobalFlags(command);
|
|
12459
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12460
|
+
const params = {
|
|
12461
|
+
ruleId: id
|
|
12462
|
+
};
|
|
12463
|
+
const payload = {
|
|
12464
|
+
keywords: options.keywords,
|
|
12465
|
+
dmText: options.text,
|
|
12466
|
+
link: options.link,
|
|
12467
|
+
publicReply: options.publicReply,
|
|
12468
|
+
status: options.status,
|
|
12469
|
+
endsAt: options.endsAt
|
|
12470
|
+
};
|
|
12471
|
+
const result = await client.call(
|
|
12472
|
+
"PATCH",
|
|
12473
|
+
"/social/instagram/comment-dm-rules/:ruleId",
|
|
12474
|
+
params,
|
|
12475
|
+
void 0,
|
|
12476
|
+
payload
|
|
12477
|
+
);
|
|
12478
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12479
|
+
});
|
|
12480
|
+
socialCommentDm.command("pause", { hidden: true }).summary("Pause an automatic message after a comment").description(
|
|
12481
|
+
"Pauses a \u201EAutomatyczna wiadomo\u015B\u0107 po komentarzu\u201D rule (the panel\u2019s \u201EWstrzymaj\u201D): it answers no new comment until resumed (update-instagram-comment-dm-rule with `status: active`). Its settings and results stay. 404 `rule_not_found`."
|
|
12482
|
+
).argument("<id>", "The rule id (`id` from list-instagram-comment-dm-rules).").action(async (id, _options, command) => {
|
|
12483
|
+
const flags = getGlobalFlags(command);
|
|
12484
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12485
|
+
const params = {
|
|
12486
|
+
ruleId: id
|
|
12487
|
+
};
|
|
12488
|
+
const result = await client.call(
|
|
12489
|
+
"POST",
|
|
12490
|
+
"/social/instagram/comment-dm-rules/:ruleId/pause",
|
|
12491
|
+
params,
|
|
12492
|
+
void 0,
|
|
12493
|
+
void 0
|
|
12494
|
+
);
|
|
12495
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12496
|
+
});
|
|
12497
|
+
socialCommentDm.command("delete", { hidden: true }).summary("Delete an automatic message after a comment").description(
|
|
12498
|
+
"Deletes a \u201EAutomatyczna wiadomo\u015B\u0107 po komentarzu\u201D rule for good, with its results and the list of people who got the message. No undo: prefer pause-instagram-comment-dm-rule. 404 `rule_not_found`."
|
|
12499
|
+
).argument("<id>", "The rule id (`id` from list-instagram-comment-dm-rules).").option(
|
|
12500
|
+
"--confirm",
|
|
12501
|
+
"required \u2014 acknowledge that you want to delete the rule and its results for good"
|
|
12502
|
+
).action(async (id, options, command) => {
|
|
12503
|
+
const flags = getGlobalFlags(command);
|
|
12504
|
+
requireConfirm(options.confirm, "delete the rule and its results for good");
|
|
12505
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
12506
|
+
const params = {
|
|
12507
|
+
ruleId: id
|
|
12508
|
+
};
|
|
12509
|
+
const result = await client.call(
|
|
12510
|
+
"DELETE",
|
|
12511
|
+
"/social/instagram/comment-dm-rules/:ruleId",
|
|
12512
|
+
params,
|
|
12513
|
+
void 0,
|
|
12514
|
+
void 0
|
|
12515
|
+
);
|
|
12516
|
+
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
12517
|
+
});
|
|
12518
|
+
}
|
|
12519
|
+
|
|
12309
12520
|
// src/commands/_generated/index.ts
|
|
12310
12521
|
function registerGeneratedCommands(program2) {
|
|
12311
12522
|
registerCertificatesCommands(program2);
|
|
@@ -12319,6 +12530,7 @@ function registerGeneratedCommands(program2) {
|
|
|
12319
12530
|
registerChatsCommands(program2);
|
|
12320
12531
|
registerOrdersCommands(program2);
|
|
12321
12532
|
registerTaxCommands(program2);
|
|
12533
|
+
registerSocialCommands(program2);
|
|
12322
12534
|
}
|
|
12323
12535
|
|
|
12324
12536
|
// src/commands/ads/index.ts
|
|
@@ -15170,6 +15382,13 @@ var AIImageQuality = /* @__PURE__ */ ((AIImageQuality2) => {
|
|
|
15170
15382
|
AIImageQuality2["Ultra"] = "4K";
|
|
15171
15383
|
return AIImageQuality2;
|
|
15172
15384
|
})(AIImageQuality || {});
|
|
15385
|
+
var STUDIO_OUTPUT_PLATFORMS = [
|
|
15386
|
+
"facebook",
|
|
15387
|
+
"linkedin",
|
|
15388
|
+
"twitch",
|
|
15389
|
+
"kick",
|
|
15390
|
+
"other"
|
|
15391
|
+
];
|
|
15173
15392
|
var MANAGED_CONVERSION_TRACKING_CLIENTS = [
|
|
15174
15393
|
"googleAnalytics",
|
|
15175
15394
|
"googleTagManager",
|
|
@@ -17787,7 +18006,7 @@ Next page: --cursor ${result.nextCursor}
|
|
|
17787
18006
|
});
|
|
17788
18007
|
});
|
|
17789
18008
|
settings.command("update").description(
|
|
17790
|
-
"Patch community settings \u2014 via toggle flags and/or a JSON patch. Allowed patch keys: uploadVideo, membersDisplay, viewStyle, displayName, icon, hiddenWhenUnavailable, voiceMessages, enableMemberApiAccess, publicAccess, publicAccessTermsUrl, contentVisibleFromJoinDate, eventsEnabled, eventAnnouncements, directMessagesEnabled, gamification, moderation, assistant, digest. Nested blocks in the JSON patch
|
|
18009
|
+
"Patch community settings \u2014 via toggle flags and/or a JSON patch. Allowed patch keys: uploadVideo, membersDisplay, viewStyle, displayName, icon, hiddenWhenUnavailable, voiceMessages, enableMemberApiAccess, publicAccess, publicAccessTermsUrl, contentVisibleFromJoinDate, eventsEnabled, eventAnnouncements, directMessagesEnabled, gamification (incl. xpRules: XP per activity, only the activities listed change, and xpTemplate: a preset for the whole table, with xpRules = the preset with those rows changed; send gamification.enabled with them), moderation, assistant, digest. Nested blocks in the JSON patch merge into the stored block field by field (lists such as joinQuestions are replaced whole); gamification.enabled and a moderation threshold can also be set with the --gamification / --auto-hide-after-reports flags."
|
|
17791
18010
|
).argument("<communityId>", "community id").option("--patch <fileOrJson>", "the settings patch as inline JSON or a path to a .json file").option("--events-enabled <on|off>", "show / hide the Calendar (events) section for members").option(
|
|
17792
18011
|
"--event-announcements <on|off>",
|
|
17793
18012
|
"bell + e-mail to members when a new event is published (default on)"
|
|
@@ -17823,8 +18042,11 @@ Next page: --cursor ${result.nextCursor}
|
|
|
17823
18042
|
const needsCurrent = options.gamification !== void 0 || options.autoHideAfterReports !== void 0;
|
|
17824
18043
|
const current = needsCurrent ? (await client.getCommunitySettings(communityId)).settings ?? {} : {};
|
|
17825
18044
|
if (options.gamification !== void 0) {
|
|
18045
|
+
const { xpRules, xpTemplate, courseXp, ...storedBlock } = current.gamification ?? {
|
|
18046
|
+
enabled: false
|
|
18047
|
+
};
|
|
17826
18048
|
patch.gamification = {
|
|
17827
|
-
...patch.gamification ??
|
|
18049
|
+
...patch.gamification ?? storedBlock,
|
|
17828
18050
|
enabled: parseOnOff(options.gamification, "--gamification")
|
|
17829
18051
|
};
|
|
17830
18052
|
}
|
|
@@ -21480,7 +21702,7 @@ function registerFunnelsCommands(program2) {
|
|
|
21480
21702
|
const result = await client.addFunnelOfferPage(id, input);
|
|
21481
21703
|
emit(flags, result, () => {
|
|
21482
21704
|
process.stdout.write(
|
|
21483
|
-
`Added the DRAFT offer page /${result.slug} (page ${result.pageId}, step ${result.stepId}) after step ${result.afterStepId}.
|
|
21705
|
+
`Added the DRAFT offer page /${result.slug} (page ${result.pageId}, step ${result.stepId}) after step ${result.afterStepId}. It names the checkout's product at its price (crossed-out only when the price has a compare-at amount), no sample claims: add what the buyer gets, then publish it.
|
|
21484
21706
|
`
|
|
21485
21707
|
);
|
|
21486
21708
|
});
|
|
@@ -22754,6 +22976,33 @@ to refresh it.
|
|
|
22754
22976
|
});
|
|
22755
22977
|
}
|
|
22756
22978
|
|
|
22979
|
+
// src/commands/liveRooms/streamKeyInput.ts
|
|
22980
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
22981
|
+
var streamKeyIo = {
|
|
22982
|
+
readStdin: () => readFileSync17(0, "utf8"),
|
|
22983
|
+
stdinIsTty: () => process.stdin.isTTY === true,
|
|
22984
|
+
promptHidden: (question) => promptHidden(question)
|
|
22985
|
+
};
|
|
22986
|
+
async function readStreamKey(options) {
|
|
22987
|
+
const { readStdin, stdinIsTty, promptHidden: ask } = streamKeyIo;
|
|
22988
|
+
let key;
|
|
22989
|
+
if (stdinIsTty()) {
|
|
22990
|
+
key = (await ask("Stream key (hidden): ")).trim();
|
|
22991
|
+
} else if (options.keyStdin) {
|
|
22992
|
+
key = readStdin().trim();
|
|
22993
|
+
} else {
|
|
22994
|
+
throw new Error(
|
|
22995
|
+
"No terminal to ask for the stream key: pipe it on stdin with --key-stdin (the key is never a command-line argument)."
|
|
22996
|
+
);
|
|
22997
|
+
}
|
|
22998
|
+
if (!key) throw new Error("No stream key was given.");
|
|
22999
|
+
return key;
|
|
23000
|
+
}
|
|
23001
|
+
var MASK = "\u2022\u2022\u2022\u2022";
|
|
23002
|
+
function maskedStreamKey(value) {
|
|
23003
|
+
return value.startsWith(MASK) && value.length <= MASK.length + 4 ? value : MASK;
|
|
23004
|
+
}
|
|
23005
|
+
|
|
22757
23006
|
// src/commands/liveRooms/index.ts
|
|
22758
23007
|
var STUDIO_SCENE_LAYOUTS = ["solo", "presentation", "grid"];
|
|
22759
23008
|
function parseSceneLayout(value) {
|
|
@@ -22761,6 +23010,29 @@ function parseSceneLayout(value) {
|
|
|
22761
23010
|
if (!layout) throw new Error(`--layout must be one of ${STUDIO_SCENE_LAYOUTS.join(", ")}`);
|
|
22762
23011
|
return layout;
|
|
22763
23012
|
}
|
|
23013
|
+
function parseOutputPlatform(value) {
|
|
23014
|
+
const platform2 = STUDIO_OUTPUT_PLATFORMS.find((candidate) => candidate === value.toLowerCase());
|
|
23015
|
+
if (!platform2) {
|
|
23016
|
+
throw new Error(`--platform must be one of ${STUDIO_OUTPUT_PLATFORMS.join(", ")}`);
|
|
23017
|
+
}
|
|
23018
|
+
return platform2;
|
|
23019
|
+
}
|
|
23020
|
+
var safeOutput = (output) => ({
|
|
23021
|
+
...output,
|
|
23022
|
+
streamKey: maskedStreamKey(output.streamKey)
|
|
23023
|
+
});
|
|
23024
|
+
var outputRow = (output) => ({
|
|
23025
|
+
id: output.id,
|
|
23026
|
+
platform: output.platform,
|
|
23027
|
+
label: output.label ?? "",
|
|
23028
|
+
url: output.url,
|
|
23029
|
+
key: maskedStreamKey(output.streamKey),
|
|
23030
|
+
enabled: output.enabled ? "on" : "off",
|
|
23031
|
+
state: output.state,
|
|
23032
|
+
message: output.message ?? ""
|
|
23033
|
+
});
|
|
23034
|
+
var OUTPUT_COLUMNS = ["id", "platform", "label", "url", "key", "enabled", "state", "message"];
|
|
23035
|
+
var SENT_NOTE = '\n"sent" = the stream leaves for the platform, which does not say whether it took the key: check the live preview there.\n';
|
|
22764
23036
|
function parseOnOff4(value) {
|
|
22765
23037
|
if (value === "on") return true;
|
|
22766
23038
|
if (value === "off") return false;
|
|
@@ -22963,6 +23235,84 @@ Next: zanfia funnels from-template webinar --live-room ${result.room.id} --lang
|
|
|
22963
23235
|
});
|
|
22964
23236
|
}
|
|
22965
23237
|
);
|
|
23238
|
+
liveRooms.command("outputs").description(
|
|
23239
|
+
"A studio webinar's extra streams (Facebook, LinkedIn, Twitch, Kick, other RTMP): state and errors; the stream key shows masked"
|
|
23240
|
+
).argument("<roomId>", "studio webinar id (from live-rooms list)").action(async (roomId, _options, command) => {
|
|
23241
|
+
const flags = getGlobalFlags(command);
|
|
23242
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23243
|
+
const result = await client.listLiveRoomStudioOutputs(roomId);
|
|
23244
|
+
const safe = { ...result, outputs: result.outputs.map(safeOutput) };
|
|
23245
|
+
emit(flags, safe, () => {
|
|
23246
|
+
printTable(safe.outputs.map(outputRow), OUTPUT_COLUMNS);
|
|
23247
|
+
process.stdout.write(
|
|
23248
|
+
`
|
|
23249
|
+
${safe.outputs.length} of ${safe.maxOutputs} outputs.` + (safe.simulcastAvailable ? "" : " This webinar streams to YouTube, which does not run through Cloudflare here: extra streams need the Zanfia player as the destination.") + "\n"
|
|
23250
|
+
);
|
|
23251
|
+
if (safe.outputs.some((output) => output.state === "sent")) {
|
|
23252
|
+
process.stdout.write(SENT_NOTE);
|
|
23253
|
+
}
|
|
23254
|
+
});
|
|
23255
|
+
});
|
|
23256
|
+
liveRooms.command("output-add").description(
|
|
23257
|
+
"Add an extra stream to a studio webinar (early access). The stream key is asked for hidden, or read from stdin with --key-stdin; it is never an argument and never shown again. Refused (409) on a YouTube webinar that cannot run through Cloudflare"
|
|
23258
|
+
).argument("<roomId>", "studio webinar id (from live-rooms list)").requiredOption("--platform <platform>", STUDIO_OUTPUT_PLATFORMS.join(" | ")).option(
|
|
23259
|
+
"--url <url>",
|
|
23260
|
+
"rtmp:// or rtmps:// ingest address (prefilled for facebook, twitch, kick; required otherwise)"
|
|
23261
|
+
).option("--label <label>", 'your own name for it (e.g. "Company page")').option("--off", "add it switched off").option("--key-stdin", "read the stream key from stdin (e.g. from a password manager)").action(
|
|
23262
|
+
async (roomId, options, command) => {
|
|
23263
|
+
const flags = getGlobalFlags(command);
|
|
23264
|
+
const platform2 = parseOutputPlatform(options.platform);
|
|
23265
|
+
const streamKey = await readStreamKey({ keyStdin: options.keyStdin });
|
|
23266
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23267
|
+
const result = await client.createLiveRoomStudioOutput(roomId, {
|
|
23268
|
+
platform: platform2,
|
|
23269
|
+
...options.url !== void 0 ? { url: options.url } : {},
|
|
23270
|
+
...options.label !== void 0 ? { label: options.label } : {},
|
|
23271
|
+
...options.off ? { enabled: false } : {},
|
|
23272
|
+
streamKey
|
|
23273
|
+
});
|
|
23274
|
+
const output = safeOutput(result.output);
|
|
23275
|
+
emit(flags, { output }, () => printTable([outputRow(output)], OUTPUT_COLUMNS));
|
|
23276
|
+
}
|
|
23277
|
+
);
|
|
23278
|
+
liveRooms.command("output-update").description(
|
|
23279
|
+
"Change an extra stream: address, name, on / off, or a NEW stream key (replace only: the stored key is never read back). Only the options you pass change"
|
|
23280
|
+
).argument("<roomId>", "studio webinar id (from live-rooms list)").argument("<outputId>", "output id (from live-rooms outputs)").option("--url <url>", "new rtmp:// or rtmps:// ingest address").option("--label <label>", "your own name for it").option("--no-label", "remove your name (the platform's name shows)").option("--enabled <on|off>", "switch it on or off (works while on air)").option("--new-key", "replace the stream key: asked for hidden").option("--key-stdin", "replace the stream key with the one read from stdin").action(
|
|
23281
|
+
async (roomId, outputId, options, command) => {
|
|
23282
|
+
const flags = getGlobalFlags(command);
|
|
23283
|
+
const input = {
|
|
23284
|
+
...options.url !== void 0 ? { url: options.url } : {},
|
|
23285
|
+
...options.label === false ? { label: null } : options.label !== void 0 ? { label: options.label } : {},
|
|
23286
|
+
...options.enabled !== void 0 ? { enabled: parseOnOff4(options.enabled) } : {}
|
|
23287
|
+
};
|
|
23288
|
+
if (options.newKey || options.keyStdin) {
|
|
23289
|
+
input.streamKey = await readStreamKey({ keyStdin: options.keyStdin });
|
|
23290
|
+
}
|
|
23291
|
+
if (Object.keys(input).length === 0) {
|
|
23292
|
+
throw new Error("pass at least one option (e.g. --enabled off or --new-key)");
|
|
23293
|
+
}
|
|
23294
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23295
|
+
const result = await client.updateLiveRoomStudioOutput(roomId, outputId, input);
|
|
23296
|
+
const output = safeOutput(result.output);
|
|
23297
|
+
emit(flags, { output }, () => printTable([outputRow(output)], OUTPUT_COLUMNS));
|
|
23298
|
+
}
|
|
23299
|
+
);
|
|
23300
|
+
liveRooms.command("output-remove").description(
|
|
23301
|
+
"Remove an extra stream and delete its stored stream key (stops it at once when on air). Requires --confirm"
|
|
23302
|
+
).argument("<roomId>", "studio webinar id (from live-rooms list)").argument("<outputId>", "output id (from live-rooms outputs)").option("--confirm", "confirm the removal").action(
|
|
23303
|
+
async (roomId, outputId, options, command) => {
|
|
23304
|
+
if (!options.confirm) {
|
|
23305
|
+
throw new Error("Refusing to remove the output without --confirm.");
|
|
23306
|
+
}
|
|
23307
|
+
const flags = getGlobalFlags(command);
|
|
23308
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
23309
|
+
const result = await client.deleteLiveRoomStudioOutput(roomId, outputId);
|
|
23310
|
+
emit(flags, result, () => {
|
|
23311
|
+
process.stdout.write(`Removed output ${outputId} and its stream key.
|
|
23312
|
+
`);
|
|
23313
|
+
});
|
|
23314
|
+
}
|
|
23315
|
+
);
|
|
22966
23316
|
liveRooms.command("attendance").description("A webinar's attendance per session; --session adds that session's viewers").argument("<roomId>", "webinar id (from live-rooms list)").option("--session <iso>", "ISO start of one session to list its attendees").action(async (roomId, options, command) => {
|
|
22967
23317
|
const flags = getGlobalFlags(command);
|
|
22968
23318
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -23025,7 +23375,7 @@ Viewers of ${options.session}:
|
|
|
23025
23375
|
}
|
|
23026
23376
|
|
|
23027
23377
|
// src/commands/media/index.ts
|
|
23028
|
-
import { existsSync, readFileSync as
|
|
23378
|
+
import { existsSync, readFileSync as readFileSync18, writeFileSync as writeFileSync4 } from "fs";
|
|
23029
23379
|
import { readFile as readFile3, stat as stat5 } from "fs/promises";
|
|
23030
23380
|
import { basename } from "path";
|
|
23031
23381
|
|
|
@@ -26197,7 +26547,7 @@ function requireConfirm10(confirm, what) {
|
|
|
26197
26547
|
}
|
|
26198
26548
|
}
|
|
26199
26549
|
function resolveTextOrFile(value) {
|
|
26200
|
-
const text = existsSync(value) ?
|
|
26550
|
+
const text = existsSync(value) ? readFileSync18(value, "utf8") : value;
|
|
26201
26551
|
if (!text.trim()) throw new Error("The text (or the file it points to) is empty.");
|
|
26202
26552
|
return text.trim();
|
|
26203
26553
|
}
|
|
@@ -26462,7 +26812,7 @@ function registerMediaCommands(program2) {
|
|
|
26462
26812
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
26463
26813
|
const result = await client.uploadMediaCaption(mediaId, {
|
|
26464
26814
|
srclang: options.lang,
|
|
26465
|
-
content:
|
|
26815
|
+
content: readFileSync18(options.file, "utf8"),
|
|
26466
26816
|
...options.label ? { label: options.label } : {}
|
|
26467
26817
|
});
|
|
26468
26818
|
emit(flags, result, () => {
|
|
@@ -26676,13 +27026,13 @@ function registerMediaCommands(program2) {
|
|
|
26676
27026
|
}
|
|
26677
27027
|
|
|
26678
27028
|
// src/commands/newsletters/index.ts
|
|
26679
|
-
import { existsSync as existsSync2, readFileSync as
|
|
27029
|
+
import { existsSync as existsSync2, readFileSync as readFileSync19 } from "fs";
|
|
26680
27030
|
var ISSUE_STATUSES = ["public", "hidden"];
|
|
26681
27031
|
var ISSUE_VISIBILITIES = ["public", "hidden", "locked"];
|
|
26682
27032
|
var ISSUE_RENDER_MODES = ["snapshot", "article"];
|
|
26683
27033
|
var THUMBNAIL_NONE = "none";
|
|
26684
27034
|
function resolveMarkdownOrFile(value) {
|
|
26685
|
-
return existsSync2(value) ?
|
|
27035
|
+
return existsSync2(value) ? readFileSync19(value, "utf8") : value;
|
|
26686
27036
|
}
|
|
26687
27037
|
function parseIdList2(value, flag) {
|
|
26688
27038
|
const ids = value.split(",").map((id) => id.trim()).filter(Boolean);
|
|
@@ -26724,7 +27074,7 @@ function parseBool2(value, flag) {
|
|
|
26724
27074
|
}
|
|
26725
27075
|
function parseJsonFileOrInline5(value, flag) {
|
|
26726
27076
|
const trimmed = value.trim();
|
|
26727
|
-
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed :
|
|
27077
|
+
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed : readFileSync19(value, "utf8");
|
|
26728
27078
|
try {
|
|
26729
27079
|
return JSON.parse(raw);
|
|
26730
27080
|
} catch (error) {
|
|
@@ -27146,7 +27496,7 @@ Next: \`newsletters issues draft save ${productId} ${issue.id} --content <markdo
|
|
|
27146
27496
|
}
|
|
27147
27497
|
|
|
27148
27498
|
// src/commands/offers/index.ts
|
|
27149
|
-
import { readFileSync as
|
|
27499
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
27150
27500
|
var PRICE_BILLING_TYPES = ["one_time", "recurring", "installments"];
|
|
27151
27501
|
var PRICE_INTERVALS = ["day", "week", "month", "year"];
|
|
27152
27502
|
var SALE_START_DISPLAYS = ["hidden", "countdown"];
|
|
@@ -27183,7 +27533,7 @@ function parseBool3(value, flag) {
|
|
|
27183
27533
|
}
|
|
27184
27534
|
function parseJsonFileOrInline6(value, flag) {
|
|
27185
27535
|
const trimmed = value.trim();
|
|
27186
|
-
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed :
|
|
27536
|
+
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed : readFileSync20(value, "utf8");
|
|
27187
27537
|
try {
|
|
27188
27538
|
return JSON.parse(raw);
|
|
27189
27539
|
} catch (error) {
|
|
@@ -28028,6 +28378,48 @@ function parsePositiveInt13(value, flag) {
|
|
|
28028
28378
|
function formatAmount3(amountCents, currency) {
|
|
28029
28379
|
return `${(amountCents / 100).toFixed(2)} ${currency}`;
|
|
28030
28380
|
}
|
|
28381
|
+
function formatProducts(order) {
|
|
28382
|
+
const others = (order.productIds ?? []).filter((id) => id !== order.productId).length;
|
|
28383
|
+
return others > 0 ? `${order.productId} +${others}` : order.productId;
|
|
28384
|
+
}
|
|
28385
|
+
function formatLine(line) {
|
|
28386
|
+
const variant = line.variantLabel ? ` (${line.variantLabel})` : "";
|
|
28387
|
+
const discount = line.discountCents > 0 ? ` (\u2212${formatAmount3(line.discountCents, line.currency)})` : "";
|
|
28388
|
+
return ` - ${line.productName}${variant} (${line.productId}) \u2014 ${line.quantity} \xD7 ${formatAmount3(line.unitAmountCents, line.currency)} = ${formatAmount3(line.amountCents, line.currency)}${discount}
|
|
28389
|
+
`;
|
|
28390
|
+
}
|
|
28391
|
+
function writeOrderLines(order) {
|
|
28392
|
+
const lines = order.lineItems ?? [];
|
|
28393
|
+
if (lines.length > 1 || order.origin === "shopCart") {
|
|
28394
|
+
process.stdout.write(` Lines:
|
|
28395
|
+
`);
|
|
28396
|
+
for (const line of lines) process.stdout.write(formatLine(line));
|
|
28397
|
+
} else if (order.additionalProducts.length > 0) {
|
|
28398
|
+
process.stdout.write(` Order bumps:
|
|
28399
|
+
`);
|
|
28400
|
+
for (const bump of order.additionalProducts) {
|
|
28401
|
+
const quantity = bump.quantity > 1 ? `${bump.quantity} \xD7 ` : "";
|
|
28402
|
+
process.stdout.write(
|
|
28403
|
+
` - ${bump.productName} (${bump.productId}) \u2014 ${quantity}${formatAmount3(bump.amountCents, bump.currency)}
|
|
28404
|
+
`
|
|
28405
|
+
);
|
|
28406
|
+
}
|
|
28407
|
+
}
|
|
28408
|
+
if (order.shipping) {
|
|
28409
|
+
const { shipping } = order;
|
|
28410
|
+
const destination = shipping.lockerPointId ? `locker ${shipping.lockerPointId}` : shipping.address ? `${shipping.address.street}, ${shipping.address.postalCode} ${shipping.address.city}` : "";
|
|
28411
|
+
process.stdout.write(
|
|
28412
|
+
` Delivery: ${shipping.methodLabel} \u2014 ${formatAmount3(shipping.amountCents, shipping.currency)}${destination ? `, ${destination}` : ""} (${shipping.recipientName})
|
|
28413
|
+
`
|
|
28414
|
+
);
|
|
28415
|
+
}
|
|
28416
|
+
if (order.fulfillment) {
|
|
28417
|
+
const { fulfillment } = order;
|
|
28418
|
+
const tracking = fulfillment.trackingNumber ? `, tracking ${fulfillment.trackingNumber}` : "";
|
|
28419
|
+
process.stdout.write(` Fulfilment: ${fulfillment.status}${tracking}
|
|
28420
|
+
`);
|
|
28421
|
+
}
|
|
28422
|
+
}
|
|
28031
28423
|
function requireConfirm11(confirm, what) {
|
|
28032
28424
|
if (!confirm) {
|
|
28033
28425
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
@@ -28037,7 +28429,7 @@ function registerOrdersCommands2(program2) {
|
|
|
28037
28429
|
const orders2 = program2.command("orders").description(
|
|
28038
28430
|
"Browse orders; approve or reject manual (wire-transfer) payments; mark test / refunded orders"
|
|
28039
28431
|
);
|
|
28040
|
-
orders2.command("list").description("List orders (newest first), with optional filters").option("--product <id>", "only orders
|
|
28432
|
+
orders2.command("list").description("List orders (newest first), with optional filters").option("--product <id>", "only orders that sold this product (on any line)").option(
|
|
28041
28433
|
"--status <status>",
|
|
28042
28434
|
"filter by status (paid, pending, refunded, failed) \u2014 manual wire-transfer orders awaiting approval are `pending`"
|
|
28043
28435
|
).option("--from <date>", "only orders created on/after this ISO date").option("--to <date>", "only orders created on/before this ISO date").option("--search <query>", "match client email / name").option("--include-test", "include simulated test purchases (excluded by default)").option("--limit <n>", "page size (max 100)").option("--cursor <cursor>", "keyset cursor from a previous page").action(
|
|
@@ -28058,7 +28450,7 @@ function registerOrdersCommands2(program2) {
|
|
|
28058
28450
|
printTable(
|
|
28059
28451
|
page.items.map((o) => ({
|
|
28060
28452
|
id: o.id,
|
|
28061
|
-
product: o
|
|
28453
|
+
product: formatProducts(o),
|
|
28062
28454
|
client: o.clientEmail,
|
|
28063
28455
|
amount: formatAmount3(o.amountCents, o.currency),
|
|
28064
28456
|
status: o.status,
|
|
@@ -28074,7 +28466,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
28074
28466
|
});
|
|
28075
28467
|
}
|
|
28076
28468
|
);
|
|
28077
|
-
orders2.command("get").description("Show a single order (payment, refund, order bumps, invoice)").argument("<orderId>", "order id").action(async (orderId, _, command) => {
|
|
28469
|
+
orders2.command("get").description("Show a single order (payment, refund, lines or order bumps, delivery, invoice)").argument("<orderId>", "order id").action(async (orderId, _, command) => {
|
|
28078
28470
|
const flags = getGlobalFlags(command);
|
|
28079
28471
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
28080
28472
|
const order = await client.getOrder(orderId);
|
|
@@ -28109,17 +28501,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
28109
28501
|
`
|
|
28110
28502
|
);
|
|
28111
28503
|
}
|
|
28112
|
-
|
|
28113
|
-
process.stdout.write(` Order bumps:
|
|
28114
|
-
`);
|
|
28115
|
-
for (const bump of order.additionalProducts) {
|
|
28116
|
-
const quantity = bump.quantity > 1 ? `${bump.quantity} \xD7 ` : "";
|
|
28117
|
-
process.stdout.write(
|
|
28118
|
-
` - ${bump.productName} (${bump.productId}) \u2014 ${quantity}${formatAmount3(bump.amountCents, bump.currency)}
|
|
28119
|
-
`
|
|
28120
|
-
);
|
|
28121
|
-
}
|
|
28122
|
-
}
|
|
28504
|
+
writeOrderLines(order);
|
|
28123
28505
|
if (order.invoiceUrl) process.stdout.write(` Invoice: ${order.invoiceUrl}
|
|
28124
28506
|
`);
|
|
28125
28507
|
});
|
|
@@ -28220,7 +28602,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
28220
28602
|
);
|
|
28221
28603
|
orders2.command("export").description(
|
|
28222
28604
|
"Export orders as CSV to a file. The --from/--to window is required; same filters as `orders list`."
|
|
28223
|
-
).requiredOption("--from <date>", "inclusive ISO date lower bound on createdAt (required)").requiredOption("--to <date>", "inclusive ISO date upper bound on createdAt (required)").option("--product <id>", "only orders
|
|
28605
|
+
).requiredOption("--from <date>", "inclusive ISO date lower bound on createdAt (required)").requiredOption("--to <date>", "inclusive ISO date upper bound on createdAt (required)").option("--product <id>", "only orders that sold this product (on any line)").option("--status <status>", "filter by status (paid, pending, refunded, failed)").option("--search <query>", "match client email / name").option("--include-test", "include simulated test purchases (excluded by default)").option("--max-rows <n>", "row cap (server default 10000, max 20000)").option("--out <file>", "output file path", "zanfia-orders.csv").action(
|
|
28224
28606
|
async (options, command) => {
|
|
28225
28607
|
const flags = getGlobalFlags(command);
|
|
28226
28608
|
for (const [flag, value] of [
|
|
@@ -28260,12 +28642,12 @@ Next page: --cursor ${page.nextCursor}
|
|
|
28260
28642
|
}
|
|
28261
28643
|
|
|
28262
28644
|
// src/commands/oto/index.ts
|
|
28263
|
-
import { readFileSync as
|
|
28645
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
28264
28646
|
var MIN_TIMER_MINUTES = 1;
|
|
28265
28647
|
var MAX_TIMER_MINUTES = 120;
|
|
28266
28648
|
function parseJsonFileOrInline7(value, flag) {
|
|
28267
28649
|
const trimmed = value.trim();
|
|
28268
|
-
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed :
|
|
28650
|
+
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed : readFileSync21(value, "utf8");
|
|
28269
28651
|
try {
|
|
28270
28652
|
return JSON.parse(raw);
|
|
28271
28653
|
} catch (error) {
|
|
@@ -28453,7 +28835,7 @@ function registerOtoCommands(program2) {
|
|
|
28453
28835
|
}
|
|
28454
28836
|
|
|
28455
28837
|
// src/commands/pages/index.ts
|
|
28456
|
-
import { existsSync as existsSync3, readFileSync as
|
|
28838
|
+
import { existsSync as existsSync3, readFileSync as readFileSync22 } from "fs";
|
|
28457
28839
|
import { readFile as readFile4 } from "fs/promises";
|
|
28458
28840
|
var CLEAR_VALUE = "none";
|
|
28459
28841
|
var FAMILY_STEPS = ["follow-ups", "thank-you-page", "room-page"];
|
|
@@ -28532,13 +28914,13 @@ function parseCsv4(value) {
|
|
|
28532
28914
|
];
|
|
28533
28915
|
}
|
|
28534
28916
|
function resolveTextOrFile2(value) {
|
|
28535
|
-
const text = existsSync3(value) ?
|
|
28917
|
+
const text = existsSync3(value) ? readFileSync22(value, "utf8") : value;
|
|
28536
28918
|
if (!text.trim()) throw new Error("The text (or the file it points to) is empty.");
|
|
28537
28919
|
return text;
|
|
28538
28920
|
}
|
|
28539
28921
|
function parseJsonObjectFileOrInline3(value, flag) {
|
|
28540
28922
|
const trimmed = value.trim();
|
|
28541
|
-
const raw = trimmed.startsWith("{") ? trimmed :
|
|
28923
|
+
const raw = trimmed.startsWith("{") ? trimmed : readFileSync22(value, "utf8");
|
|
28542
28924
|
let parsed;
|
|
28543
28925
|
try {
|
|
28544
28926
|
parsed = JSON.parse(raw);
|
|
@@ -28903,7 +29285,7 @@ function registerPagesCommands(program2) {
|
|
|
28903
29285
|
});
|
|
28904
29286
|
});
|
|
28905
29287
|
pages.command("restore").description(
|
|
28906
|
-
|
|
29288
|
+
'Roll a page\'s HTML back to a revision. The current HTML is snapshotted first (source `restore`), so a restore is itself undoable. On a published page a revision with a buy button that leads nowhere, or with placeholder text the live page does not show ([UZUPE\u0141NIJ: \u2026], a <span class="todo"> note, LINK: \u2026), is refused (409, nothing restored). Requires --confirm.'
|
|
28907
29289
|
).argument("<pageId>", "page id").argument("<revisionId>", "revision id from `pages revisions`").option("--confirm", "required \u2014 acknowledge that the current HTML will be replaced").action(
|
|
28908
29290
|
async (pageId, revisionId, options, command) => {
|
|
28909
29291
|
const flags = getGlobalFlags(command);
|
|
@@ -28964,7 +29346,7 @@ function registerPagesCommands(program2) {
|
|
|
28964
29346
|
}
|
|
28965
29347
|
|
|
28966
29348
|
// src/commands/products/index.ts
|
|
28967
|
-
import { readFileSync as
|
|
29349
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
28968
29350
|
import { readFile as readFile5 } from "fs/promises";
|
|
28969
29351
|
import { basename as basename2 } from "path";
|
|
28970
29352
|
function parseBuyerFiles(value) {
|
|
@@ -29093,7 +29475,7 @@ function assertOneOf4(value, allowed, flag) {
|
|
|
29093
29475
|
}
|
|
29094
29476
|
function parseJsonFileOrInline8(value, flag) {
|
|
29095
29477
|
const trimmed = value.trim();
|
|
29096
|
-
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed :
|
|
29478
|
+
const raw = trimmed.startsWith("{") || trimmed.startsWith("[") ? trimmed : readFileSync23(value, "utf8");
|
|
29097
29479
|
try {
|
|
29098
29480
|
return JSON.parse(raw);
|
|
29099
29481
|
} catch (error) {
|
|
@@ -29225,7 +29607,7 @@ function registerProductsCommands2(program2) {
|
|
|
29225
29607
|
if (options.description !== void 0 && options.descriptionFile !== void 0) {
|
|
29226
29608
|
throw new Error("Use either --description or --description-file, not both.");
|
|
29227
29609
|
}
|
|
29228
|
-
const description = options.descriptionFile !== void 0 ?
|
|
29610
|
+
const description = options.descriptionFile !== void 0 ? readFileSync23(options.descriptionFile, "utf8") : options.description;
|
|
29229
29611
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
29230
29612
|
const product = await client.createProduct({
|
|
29231
29613
|
name: options.name,
|
|
@@ -29848,7 +30230,7 @@ function registerProductsCommands2(program2) {
|
|
|
29848
30230
|
if (options.content !== void 0 && options.contentFile !== void 0) {
|
|
29849
30231
|
throw new Error("Use either --content or --content-file, not both.");
|
|
29850
30232
|
}
|
|
29851
|
-
const editorContent = options.contentFile !== void 0 ?
|
|
30233
|
+
const editorContent = options.contentFile !== void 0 ? readFileSync23(options.contentFile, "utf8") : options.content;
|
|
29852
30234
|
if (editorContent === void 0) {
|
|
29853
30235
|
throw new Error("Pass the description via --content or --content-file.");
|
|
29854
30236
|
}
|
|
@@ -29954,7 +30336,7 @@ function registerProductsCommands2(program2) {
|
|
|
29954
30336
|
if (options.content !== void 0 && options.contentFile !== void 0) {
|
|
29955
30337
|
throw new Error("Use either --content or --content-file, not both.");
|
|
29956
30338
|
}
|
|
29957
|
-
return options.contentFile !== void 0 ?
|
|
30339
|
+
return options.contentFile !== void 0 ? readFileSync23(options.contentFile, "utf8") : options.content;
|
|
29958
30340
|
};
|
|
29959
30341
|
kb.command("get").description(
|
|
29960
30342
|
"Show the knowledge base: categories (tree keys = the --category ids) + all articles (drafts and unavailable included \u2014 the creator view)"
|
|
@@ -30174,7 +30556,7 @@ function registerProductsCommands2(program2) {
|
|
|
30174
30556
|
}
|
|
30175
30557
|
|
|
30176
30558
|
// src/commands/referrals/index.ts
|
|
30177
|
-
import { readFileSync as
|
|
30559
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
30178
30560
|
var PROGRAM_KINDS = ["referral", "affiliate"];
|
|
30179
30561
|
var WRITABLE_STATUSES = ["active", "inactive"];
|
|
30180
30562
|
var ENROLLMENT_MODES = ["open", "inviteOnly", "closed"];
|
|
@@ -30203,7 +30585,7 @@ function parseIdList3(value, flag) {
|
|
|
30203
30585
|
}
|
|
30204
30586
|
function parseJsonObjectFileOrInline4(value, flag) {
|
|
30205
30587
|
const trimmed = value.trim();
|
|
30206
|
-
const raw = trimmed.startsWith("{") ? trimmed :
|
|
30588
|
+
const raw = trimmed.startsWith("{") ? trimmed : readFileSync24(value, "utf8");
|
|
30207
30589
|
let parsed;
|
|
30208
30590
|
try {
|
|
30209
30591
|
parsed = JSON.parse(raw);
|
|
@@ -30570,7 +30952,7 @@ Failed deliveries (${result.failedDeliveries.length})
|
|
|
30570
30952
|
}
|
|
30571
30953
|
|
|
30572
30954
|
// src/commands/segments/index.ts
|
|
30573
|
-
import { readFileSync as
|
|
30955
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
30574
30956
|
var SUBSCRIPTION_STATUSES2 = ["pending", "subscribed", "unsubscribed"];
|
|
30575
30957
|
function collectRepeatable7(value, previous) {
|
|
30576
30958
|
return [...previous, value];
|
|
@@ -30589,7 +30971,7 @@ function resolveConditions(options) {
|
|
|
30589
30971
|
if (hasFlagConditions) {
|
|
30590
30972
|
throw new Error("Use either condition flags or --json-file, not both.");
|
|
30591
30973
|
}
|
|
30592
|
-
const raw =
|
|
30974
|
+
const raw = readFileSync25(options.jsonFile, "utf8");
|
|
30593
30975
|
let parsed;
|
|
30594
30976
|
try {
|
|
30595
30977
|
parsed = JSON.parse(raw);
|
|
@@ -30888,7 +31270,7 @@ function registerSegmentsCommands(program2) {
|
|
|
30888
31270
|
}
|
|
30889
31271
|
|
|
30890
31272
|
// src/commands/site/index.ts
|
|
30891
|
-
import { existsSync as existsSync4, readFileSync as
|
|
31273
|
+
import { existsSync as existsSync4, readFileSync as readFileSync26 } from "fs";
|
|
30892
31274
|
|
|
30893
31275
|
// src/commands/site/librarySections.ts
|
|
30894
31276
|
var NO_SECTION = "none";
|
|
@@ -31070,7 +31452,7 @@ function requireConfirm14(confirm, what) {
|
|
|
31070
31452
|
}
|
|
31071
31453
|
function parseJsonObjectFileOrInline5(value, flag) {
|
|
31072
31454
|
const trimmed = value.trim();
|
|
31073
|
-
const raw = trimmed.startsWith("{") ? trimmed :
|
|
31455
|
+
const raw = trimmed.startsWith("{") ? trimmed : readFileSync26(value, "utf8");
|
|
31074
31456
|
let parsed;
|
|
31075
31457
|
try {
|
|
31076
31458
|
parsed = JSON.parse(raw);
|
|
@@ -31122,7 +31504,7 @@ function parseDomainList(value, what) {
|
|
|
31122
31504
|
return items;
|
|
31123
31505
|
}
|
|
31124
31506
|
function readMarkdownOrFile(value) {
|
|
31125
|
-
return existsSync4(value) ?
|
|
31507
|
+
return existsSync4(value) ? readFileSync26(value, "utf8") : value;
|
|
31126
31508
|
}
|
|
31127
31509
|
function assertNonEmptyBody(body, hint) {
|
|
31128
31510
|
if (Object.keys(body).length === 0) {
|
|
@@ -31311,6 +31693,26 @@ function registerSiteCommands2(program2) {
|
|
|
31311
31693
|
const result = await client.getSiteSettings();
|
|
31312
31694
|
emit(flags, result, () => printSiteSummary(result.settings, result.communication));
|
|
31313
31695
|
});
|
|
31696
|
+
site.command("scripts").description(
|
|
31697
|
+
"The site's own scripts (head / end of body of every page-editor page), read only: change them in the dashboard (Site \u2192 Settings \u2192 Custom scripts)"
|
|
31698
|
+
).action(async (_options, command) => {
|
|
31699
|
+
const flags = getGlobalFlags(command);
|
|
31700
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
31701
|
+
const result = await client.getSiteCustomScripts();
|
|
31702
|
+
emit(flags, result, () => {
|
|
31703
|
+
if (!result.available) {
|
|
31704
|
+
process.stdout.write("Custom scripts are not available for this workspace.\n");
|
|
31705
|
+
return;
|
|
31706
|
+
}
|
|
31707
|
+
process.stdout.write(`head:
|
|
31708
|
+
${result.head || "(empty)"}
|
|
31709
|
+
|
|
31710
|
+
`);
|
|
31711
|
+
process.stdout.write(`body end:
|
|
31712
|
+
${result.bodyEnd || "(empty)"}
|
|
31713
|
+
`);
|
|
31714
|
+
});
|
|
31715
|
+
});
|
|
31314
31716
|
const update = site.command("update").description(
|
|
31315
31717
|
"Update ONE section of the site settings atomically. Every section accepts --json=<file|inline> (write it with `=` \u2014 a bare --json is the global JSON-output flag); typed flags override keys from --json"
|
|
31316
31718
|
);
|
|
@@ -31793,13 +32195,15 @@ function printStorefront(config) {
|
|
|
31793
32195
|
}
|
|
31794
32196
|
|
|
31795
32197
|
// src/commands/smsCampaigns/index.ts
|
|
31796
|
-
import { readFileSync as
|
|
32198
|
+
import { readFileSync as readFileSync27 } from "fs";
|
|
31797
32199
|
var SMS_PROVIDERS2 = ["twilio", "smsapi"];
|
|
31798
32200
|
var RECIPIENT_STATUSES2 = [
|
|
31799
32201
|
"pending",
|
|
31800
32202
|
"sent",
|
|
31801
32203
|
"failed",
|
|
31802
|
-
"skipped-invalid-number"
|
|
32204
|
+
"skipped-invalid-number",
|
|
32205
|
+
"skipped-duplicate-number",
|
|
32206
|
+
"skipped-opted-out-number"
|
|
31803
32207
|
];
|
|
31804
32208
|
var SCHEDULER_UNAVAILABLE_STATUS4 = 503;
|
|
31805
32209
|
function parsePositiveInt15(value, flag) {
|
|
@@ -31826,7 +32230,7 @@ function resolveBody(options) {
|
|
|
31826
32230
|
if (options.body !== void 0 && options.bodyFile !== void 0) {
|
|
31827
32231
|
throw new Error("Use either --body or --body-file, not both.");
|
|
31828
32232
|
}
|
|
31829
|
-
return options.bodyFile !== void 0 ?
|
|
32233
|
+
return options.bodyFile !== void 0 ? readFileSync27(options.bodyFile, "utf8") : options.body;
|
|
31830
32234
|
}
|
|
31831
32235
|
function resolveAudience2(options) {
|
|
31832
32236
|
if (options.segment !== void 0 && options.allSubscribed) {
|
|
@@ -31892,7 +32296,7 @@ function printCampaignDetail2(campaign) {
|
|
|
31892
32296
|
`);
|
|
31893
32297
|
const { stats } = campaign;
|
|
31894
32298
|
process.stdout.write(
|
|
31895
|
-
` Stats: sent ${stats.sent}, failed ${stats.failed}, skipped (invalid number) ${stats.skippedInvalidNumber}, billed parts ${stats.segments}
|
|
32299
|
+
` Stats: sent ${stats.sent}, failed ${stats.failed}, skipped (invalid number) ${stats.skippedInvalidNumber}, skipped (duplicate number) ${stats.skippedDuplicateNumber ?? 0}, skipped (opted-out number) ${stats.skippedOptedOutNumber ?? 0}, billed parts ${stats.segments}
|
|
31896
32300
|
`
|
|
31897
32301
|
);
|
|
31898
32302
|
process.stdout.write(` Opt-out line: ${campaign.optOutHint ?? "(none)"}
|
|
@@ -32401,11 +32805,13 @@ function composeInput(options) {
|
|
|
32401
32805
|
...options.publish ? { publish: true } : {}
|
|
32402
32806
|
};
|
|
32403
32807
|
}
|
|
32404
|
-
function
|
|
32808
|
+
function registerSocialCommands2(program2) {
|
|
32405
32809
|
const social = program2.command("social").description(
|
|
32406
32810
|
"Social media \u2014 connected accounts, posts (drafts, scheduling, publishing) and the stats of the connected accounts (youtube / instagram / threads / snapshot)"
|
|
32407
32811
|
);
|
|
32408
|
-
social.command("accounts").description(
|
|
32812
|
+
social.command("accounts").description(
|
|
32813
|
+
"List connected social accounts (the values to pass as --targets; a bare network name such as `instagram` goes to the account marked default)"
|
|
32814
|
+
).action(async (_, command) => {
|
|
32409
32815
|
const flags = getGlobalFlags(command);
|
|
32410
32816
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
32411
32817
|
const response = await client.listSocialAccounts();
|
|
@@ -32415,11 +32821,12 @@ function registerSocialCommands(program2) {
|
|
|
32415
32821
|
target: a.target,
|
|
32416
32822
|
network: a.network,
|
|
32417
32823
|
label: a.label,
|
|
32824
|
+
default: a.isDefault ? "yes" : "no",
|
|
32418
32825
|
schedule: a.supportsScheduling ? "yes" : "no",
|
|
32419
32826
|
publishNow: a.supportsImmediatePublish ? "yes" : "no",
|
|
32420
32827
|
firstComment: a.firstCommentNeedsReconnect ? "reconnect" : ""
|
|
32421
32828
|
})),
|
|
32422
|
-
["target", "network", "label", "schedule", "publishNow", "firstComment"]
|
|
32829
|
+
["target", "network", "label", "default", "schedule", "publishNow", "firstComment"]
|
|
32423
32830
|
);
|
|
32424
32831
|
});
|
|
32425
32832
|
});
|
|
@@ -32966,7 +33373,7 @@ ${integration.name}: ${integration.healthExplanation}
|
|
|
32966
33373
|
}
|
|
32967
33374
|
|
|
32968
33375
|
// src/commands/studio/drafts.ts
|
|
32969
|
-
import { readFileSync as
|
|
33376
|
+
import { readFileSync as readFileSync28 } from "fs";
|
|
32970
33377
|
var TEXT_PREVIEW_LENGTH = 60;
|
|
32971
33378
|
function parseWeakReason(value) {
|
|
32972
33379
|
const reason = STUDIO_SOCIAL_DRAFT_WEAK_REASONS.find(
|
|
@@ -33088,7 +33495,7 @@ function registerStudioDraftsCommands(program2) {
|
|
|
33088
33495
|
if (options.text !== void 0 && options.textFile !== void 0) {
|
|
33089
33496
|
throw new Error("Use either --text or --text-file, not both.");
|
|
33090
33497
|
}
|
|
33091
|
-
const text = options.textFile !== void 0 ?
|
|
33498
|
+
const text = options.textFile !== void 0 ? readFileSync28(options.textFile, "utf8") : options.text;
|
|
33092
33499
|
requireConfirm(options.confirm, `create a draft social post from ${draftId}`);
|
|
33093
33500
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
33094
33501
|
const result = await client.sendStudioSocialDraftToComposer(draftId, {
|
|
@@ -33736,7 +34143,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
33736
34143
|
}
|
|
33737
34144
|
|
|
33738
34145
|
// src/commands/workflows/index.ts
|
|
33739
|
-
import { readFileSync as
|
|
34146
|
+
import { readFileSync as readFileSync30 } from "fs";
|
|
33740
34147
|
|
|
33741
34148
|
// src/commands/workflows/authoring.ts
|
|
33742
34149
|
import { writeFileSync as writeFileSync6 } from "fs";
|
|
@@ -33904,6 +34311,89 @@ function registerWorkflowAuthoringCommands(workflows) {
|
|
|
33904
34311
|
});
|
|
33905
34312
|
}
|
|
33906
34313
|
|
|
34314
|
+
// src/commands/workflows/emailConditions.ts
|
|
34315
|
+
import { readFileSync as readFileSync29 } from "fs";
|
|
34316
|
+
function collectRepeatable8(value, previous) {
|
|
34317
|
+
return [...previous, value];
|
|
34318
|
+
}
|
|
34319
|
+
function parseCustomValues(pairs) {
|
|
34320
|
+
const values = {};
|
|
34321
|
+
for (const pair of pairs) {
|
|
34322
|
+
const at = pair.indexOf("=");
|
|
34323
|
+
const key = at > 0 ? pair.slice(0, at).trim().replace(/^custom\./, "") : "";
|
|
34324
|
+
if (!key) throw new Error(`Invalid --custom ${pair} (expected <key>=<value>)`);
|
|
34325
|
+
values[key] = pair.slice(at + 1);
|
|
34326
|
+
}
|
|
34327
|
+
return values;
|
|
34328
|
+
}
|
|
34329
|
+
function buildInput(options) {
|
|
34330
|
+
if (options.body !== void 0 && options.bodyFile !== void 0) {
|
|
34331
|
+
throw new Error("Pass either --body or --body-file, not both.");
|
|
34332
|
+
}
|
|
34333
|
+
const body = options.bodyFile !== void 0 ? readFileSync29(options.bodyFile, "utf8") : options.body;
|
|
34334
|
+
if (options.subject === void 0 && options.preheader === void 0 && body === void 0) {
|
|
34335
|
+
throw new Error("Pass at least one of --subject, --preheader, --body or --body-file.");
|
|
34336
|
+
}
|
|
34337
|
+
const custom = parseCustomValues(options.custom);
|
|
34338
|
+
const withContact = options.firstName !== void 0 || Object.keys(custom).length > 0;
|
|
34339
|
+
return {
|
|
34340
|
+
...options.subject !== void 0 ? { subject: options.subject } : {},
|
|
34341
|
+
...options.preheader !== void 0 ? { preheader: options.preheader } : {},
|
|
34342
|
+
...body !== void 0 ? { body } : {},
|
|
34343
|
+
...withContact ? {
|
|
34344
|
+
exampleContact: {
|
|
34345
|
+
...options.firstName !== void 0 ? { firstName: options.firstName } : {},
|
|
34346
|
+
...Object.keys(custom).length > 0 ? { custom } : {}
|
|
34347
|
+
}
|
|
34348
|
+
} : {}
|
|
34349
|
+
};
|
|
34350
|
+
}
|
|
34351
|
+
function printResult(result) {
|
|
34352
|
+
const lines = [];
|
|
34353
|
+
if (!result.hasConditions) {
|
|
34354
|
+
lines.push("No {% %} conditions in these texts.");
|
|
34355
|
+
} else if (result.valid) {
|
|
34356
|
+
lines.push("Every condition tag parses.");
|
|
34357
|
+
} else {
|
|
34358
|
+
lines.push(`${result.warnings.length} problem(s); the send would use the safe text:`);
|
|
34359
|
+
for (const warning of result.warnings) {
|
|
34360
|
+
lines.push(` [${warning.field}] ${warning.tag} (${warning.code}): ${warning.detail}`);
|
|
34361
|
+
}
|
|
34362
|
+
}
|
|
34363
|
+
if (result.customFieldKeys.length > 0) {
|
|
34364
|
+
lines.push(`Custom fields read: ${result.customFieldKeys.join(", ")}`);
|
|
34365
|
+
}
|
|
34366
|
+
if (result.exampleResult) {
|
|
34367
|
+
lines.push("For the example contact:");
|
|
34368
|
+
for (const field of ["subject", "preheader", "body"]) {
|
|
34369
|
+
const text = result.exampleResult[field];
|
|
34370
|
+
if (text !== void 0) lines.push(` ${field}: ${text}`);
|
|
34371
|
+
}
|
|
34372
|
+
}
|
|
34373
|
+
if (result.evaluatedForWorkspace) {
|
|
34374
|
+
lines.push("Conditions are evaluated in this workspace (workflow and library e-mails).");
|
|
34375
|
+
}
|
|
34376
|
+
if (result.note) lines.push(result.note);
|
|
34377
|
+
process.stdout.write(`${lines.join("\n")}
|
|
34378
|
+
`);
|
|
34379
|
+
}
|
|
34380
|
+
function registerEmailConditionsCommand(workflows) {
|
|
34381
|
+
workflows.command("check-email-conditions").description(
|
|
34382
|
+
"Check {% if %} / {% case %} conditional content in a workflow or library e-mail before saving it (read-only; not for e-mail campaigns, which send the default version)"
|
|
34383
|
+
).option("--subject <text>", "e-mail subject, as it will be saved").option("--preheader <text>", "inbox preview line").option("--body <markdown>", "e-mail body (markdown)").option("--body-file <path>", "read the body (markdown) from a file").option("--first-name <name>", "example contact: first name").option(
|
|
34384
|
+
"--custom <key=value>",
|
|
34385
|
+
"example contact: a custom field value as the e-mail prints it (a select = its option label; repeatable)",
|
|
34386
|
+
collectRepeatable8,
|
|
34387
|
+
[]
|
|
34388
|
+
).action(async (options, command) => {
|
|
34389
|
+
const flags = getGlobalFlags(command);
|
|
34390
|
+
const input = buildInput(options);
|
|
34391
|
+
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
34392
|
+
const result = await client.checkEmailConditions(input);
|
|
34393
|
+
emit(flags, result, () => printResult(result));
|
|
34394
|
+
});
|
|
34395
|
+
}
|
|
34396
|
+
|
|
33907
34397
|
// src/commands/workflows/index.ts
|
|
33908
34398
|
function parsePositiveInt18(value, flag) {
|
|
33909
34399
|
const n = Number.parseInt(value, 10);
|
|
@@ -33919,7 +34409,7 @@ function requireConfirm17(confirm, what) {
|
|
|
33919
34409
|
}
|
|
33920
34410
|
function parseTriggerContext(value) {
|
|
33921
34411
|
const trimmed = value.trim();
|
|
33922
|
-
const raw = trimmed.startsWith("{") ? trimmed :
|
|
34412
|
+
const raw = trimmed.startsWith("{") ? trimmed : readFileSync30(value, "utf8");
|
|
33923
34413
|
let parsed;
|
|
33924
34414
|
try {
|
|
33925
34415
|
parsed = JSON.parse(raw);
|
|
@@ -34124,6 +34614,7 @@ Follow it with: zanfia workflows runs ${automationId}
|
|
|
34124
34614
|
}
|
|
34125
34615
|
);
|
|
34126
34616
|
registerWorkflowAuthoringCommands(workflows);
|
|
34617
|
+
registerEmailConditionsCommand(workflows);
|
|
34127
34618
|
}
|
|
34128
34619
|
function formatProductScope(workflow) {
|
|
34129
34620
|
return workflow.productIds.length > 0 ? String(workflow.productIds.length) : "all";
|
|
@@ -34699,11 +35190,11 @@ function applyShortHelp(program2) {
|
|
|
34699
35190
|
|
|
34700
35191
|
// src/program.ts
|
|
34701
35192
|
function resolveCliVersion() {
|
|
34702
|
-
if ("0.
|
|
34703
|
-
return "0.
|
|
35193
|
+
if ("0.25.1".length > 0) {
|
|
35194
|
+
return "0.25.1";
|
|
34704
35195
|
}
|
|
34705
35196
|
try {
|
|
34706
|
-
const raw =
|
|
35197
|
+
const raw = readFileSync31(new URL("../package.json", import.meta.url), "utf8");
|
|
34707
35198
|
const parsed = JSON.parse(raw);
|
|
34708
35199
|
if (typeof parsed === "object" && parsed !== null && "version" in parsed) {
|
|
34709
35200
|
const { version: version2 } = parsed;
|
|
@@ -34776,7 +35267,7 @@ Signing in \u2014 two roles, two consent pages:
|
|
|
34776
35267
|
registerWorkspaceCommands(program2);
|
|
34777
35268
|
registerFunnelsCommands(program2);
|
|
34778
35269
|
registerPagesCommands(program2);
|
|
34779
|
-
|
|
35270
|
+
registerSocialCommands2(program2);
|
|
34780
35271
|
registerMediaCommands(program2);
|
|
34781
35272
|
registerFilesCommands(program2);
|
|
34782
35273
|
registerStudioDraftsCommands(program2);
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zanfia/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Zanfia CLI — thin HTTP client over the Zanfia Public API",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/MailingR/mailingr"
|
|
9
|
+
},
|
|
6
10
|
"type": "module",
|
|
7
11
|
"files": [
|
|
8
12
|
"dist"
|