@zanfia/cli 0.23.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +68 -68
  2. package/dist/index.js +553 -62
  3. package/package.json +1 -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 readFileSync29 } from "fs";
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
- 'When the post goes live: Polish time without an offset ("2026-10-26T08:00", read in Europe/Warsaw with the offset of that date, also across a clock change) or an ISO 8601 date-time with an offset / Z (2026-10-26T08:00:00+01:00). At least a minute and at most a year ahead. null = unschedule (back to draft).'
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 a loop (`chain`), `/*` in `to` without a folder `from` (`rest-without-folder`), over 500 lines (`too-many-entries`). Returns the line as stored."
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 replace their whole sub-object; the --gamification / --auto-hide-after-reports flags merge into the existing block instead."
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 ?? current.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}. The body copy and the crossed-out price are template samples (the button and price spans show the checkout's real price): edit the copy, then publish it.
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 readFileSync17, writeFileSync as writeFileSync4 } from "fs";
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) ? readFileSync17(value, "utf8") : 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: readFileSync17(options.file, "utf8"),
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 readFileSync18 } from "fs";
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) ? readFileSync18(value, "utf8") : 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 : readFileSync18(value, "utf8");
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 readFileSync19 } from "fs";
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 : readFileSync19(value, "utf8");
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 of this product").option(
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.productId,
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
- if (order.additionalProducts.length > 0) {
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 of this product").option("--status <status>", "filter by status (paid, pending, refunded, failed)").option("--search <query>", "match client email / name").option("--include-test", "include simulated test purchases (excluded by default)").option("--max-rows <n>", "row cap (server default 10000, max 20000)").option("--out <file>", "output file path", "zanfia-orders.csv").action(
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 readFileSync20 } from "fs";
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 : readFileSync20(value, "utf8");
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 readFileSync21 } from "fs";
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) ? readFileSync21(value, "utf8") : 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 : readFileSync21(value, "utf8");
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
- "Roll a page's HTML back to a revision. The current HTML is snapshotted first (source `restore`), so a restore is itself undoable. On a published page a revision with a buy button that leads nowhere is refused (409, nothing restored). Requires --confirm."
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 readFileSync22 } from "fs";
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 : readFileSync22(value, "utf8");
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 ? readFileSync22(options.descriptionFile, "utf8") : options.description;
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 ? readFileSync22(options.contentFile, "utf8") : options.content;
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 ? readFileSync22(options.contentFile, "utf8") : options.content;
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 readFileSync23 } from "fs";
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 : readFileSync23(value, "utf8");
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 readFileSync24 } from "fs";
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 = readFileSync24(options.jsonFile, "utf8");
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 readFileSync25 } from "fs";
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 : readFileSync25(value, "utf8");
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) ? readFileSync25(value, "utf8") : 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 readFileSync26 } from "fs";
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 ? readFileSync26(options.bodyFile, "utf8") : options.body;
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 registerSocialCommands(program2) {
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("List connected social accounts (the values to pass as --targets)").action(async (_, command) => {
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 readFileSync27 } from "fs";
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 ? readFileSync27(options.textFile, "utf8") : options.text;
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 readFileSync28 } from "fs";
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 : readFileSync28(value, "utf8");
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.23.0".length > 0) {
34703
- return "0.23.0";
35193
+ if ("0.25.0".length > 0) {
35194
+ return "0.25.0";
34704
35195
  }
34705
35196
  try {
34706
- const raw = readFileSync29(new URL("../package.json", import.meta.url), "utf8");
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
- registerSocialCommands(program2);
35270
+ registerSocialCommands2(program2);
34780
35271
  registerMediaCommands(program2);
34781
35272
  registerFilesCommands(program2);
34782
35273
  registerStudioDraftsCommands(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zanfia/cli",
3
- "version": "0.23.0",
3
+ "version": "0.25.0",
4
4
  "private": false,
5
5
  "description": "Zanfia CLI — thin HTTP client over the Zanfia Public API",
6
6
  "type": "module",