adapty 0.8.1 → 0.8.3-beta.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 (34) hide show
  1. package/README.md +4 -0
  2. package/dist/commands/asa/ad-groups/create.d.ts +1 -0
  3. package/dist/commands/asa/ad-groups/create.js +20 -5
  4. package/dist/commands/asa/ad-groups/update.js +1 -1
  5. package/dist/commands/asa/ads/create.js +1 -1
  6. package/dist/commands/asa/ads/update.js +1 -1
  7. package/dist/commands/asa/automations/create.js +1 -1
  8. package/dist/commands/asa/automations/run.js +1 -1
  9. package/dist/commands/asa/automations/update.js +1 -1
  10. package/dist/commands/asa/campaigns/bulk-create.js +1 -1
  11. package/dist/commands/asa/campaigns/create.d.ts +5 -0
  12. package/dist/commands/asa/campaigns/create.js +16 -4
  13. package/dist/commands/asa/campaigns/update.d.ts +5 -0
  14. package/dist/commands/asa/campaigns/update.js +15 -4
  15. package/dist/commands/asa/keywords/add.js +1 -1
  16. package/dist/commands/asa/keywords/update.js +1 -1
  17. package/dist/commands/asa/negative-keywords/add.js +1 -1
  18. package/dist/commands/asa/product-pages/sync.js +1 -1
  19. package/dist/commands/flows/publish.d.ts +15 -0
  20. package/dist/commands/flows/publish.js +34 -0
  21. package/dist/commands/flows/update.d.ts +15 -0
  22. package/dist/commands/flows/update.js +28 -0
  23. package/dist/commands/placements/create.js +18 -3
  24. package/dist/commands/placements/update.js +18 -3
  25. package/dist/lib/api-schemas.d.ts +10 -1
  26. package/dist/lib/asa-flags.d.ts +22 -1
  27. package/dist/lib/asa-flags.js +47 -0
  28. package/dist/lib/asa-schemas.d.ts +19 -0
  29. package/dist/lib/placement-audiences.d.ts +6 -0
  30. package/dist/lib/placement-audiences.js +25 -0
  31. package/oclif.manifest.json +1746 -1555
  32. package/package.json +3 -2
  33. /package/dist/lib/{asa-confirm.d.ts → confirm.d.ts} +0 -0
  34. /package/dist/lib/{asa-confirm.js → confirm.js} +0 -0
package/README.md CHANGED
@@ -137,11 +137,15 @@ Campaign structure. These lists return metadata only — numbers come from `asa
137
137
  adapty asa campaigns list
138
138
  adapty asa campaigns get CAMPAIGN_ID
139
139
  adapty asa campaigns create --org UUID --name "Winter push" --adam-id 123456 --country US --daily-budget 50
140
+ adapty asa campaigns create --org UUID --name "LOC push" --adam-id 123456 --country US --daily-budget 50 \
141
+ --invoice-advertiser "Acme Inc" --invoice-order-number PO-42 --invoice-contact-name "Jane Doe" \
142
+ --invoice-contact-email jane@acme.com --invoice-billing-email billing@acme.com # payment_model LOC
140
143
  adapty asa campaigns update CAMPAIGN_ID [--status PAUSED] [--daily-budget 80] [--country US]
141
144
 
142
145
  adapty asa ad-groups list
143
146
  adapty asa ad-groups get AD_GROUP_ID
144
147
  adapty asa ad-groups create --campaign UUID --name "Brand terms" --default-bid 1.20
148
+ adapty asa ad-groups create --campaign UUID --name "Automated Max Conv" --automated # Max Conversions campaigns
145
149
  adapty asa ad-groups update AD_GROUP_ID [--default-bid 1.50] [--status PAUSED]
146
150
 
147
151
  adapty asa ads list
@@ -5,6 +5,7 @@ export default class AsaAdGroupsCreate extends Command {
5
5
  static enableJsonFlag: boolean;
6
6
  static examples: string[];
7
7
  static flags: {
8
+ automated: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
9
  'automated-keywords': import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
10
  campaign: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
11
  'cpa-goal': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -1,15 +1,16 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
2
  import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
3
- import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
4
3
  import { currencyFlag, idempotencyFlags, money, moneyFlag, pricingModelFlag, scheduleFlags, startOfDayUtc, todayUtc, } from '../../../lib/asa-flags.js';
4
+ import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
5
5
  import { isValidUuid } from '../../../lib/flags.js';
6
6
  import { printResponse } from '../../../lib/output.js';
7
7
  export default class AsaAdGroupsCreate extends Command {
8
- static description = 'Create an ad group inside a campaign';
8
+ static description = 'Create an ad group inside a campaign; --automated creates the automated ad group a Max Conversions campaign needs to run';
9
9
  static enableJsonFlag = true;
10
10
  static examples = [
11
11
  '<%= config.bin %> asa ad-groups create --campaign UUID --name "Brand terms" --default-bid 1.20',
12
12
  '<%= config.bin %> asa ad-groups create --campaign UUID --name "Brand terms" --default-bid 1.20 --start-time 2026-09-01 --pricing-model CPM',
13
+ '<%= config.bin %> asa ad-groups create --campaign UUID --name "Automated Max Conv" --automated',
13
14
  ];
14
15
  static flags = {
15
16
  ...currencyFlag,
@@ -17,10 +18,14 @@ export default class AsaAdGroupsCreate extends Command {
17
18
  ...pricingModelFlag,
18
19
  ...confirmFlags,
19
20
  ...idempotencyFlags,
21
+ automated: Flags.boolean({
22
+ description: 'Create the automated ad group a Max Conversions campaign needs (no bid, no schedule, always ENABLED)',
23
+ exclusive: ['automated-keywords'],
24
+ }),
20
25
  'automated-keywords': Flags.boolean({ allowNo: true, description: 'Let Apple add keywords automatically' }),
21
26
  campaign: Flags.string({ description: 'Campaign ID (UUID)', required: true }),
22
27
  'cpa-goal': moneyFlag('CPA goal'),
23
- 'default-bid': moneyFlag('Default bid', { required: true }),
28
+ 'default-bid': moneyFlag('Default bid; required unless --automated is set'),
24
29
  name: Flags.string({ description: 'Ad group name', required: true }),
25
30
  status: Flags.string({ description: 'Initial status', options: ['ENABLED', 'PAUSED'] }),
26
31
  };
@@ -28,15 +33,25 @@ export default class AsaAdGroupsCreate extends Command {
28
33
  const { flags } = await this.parse(AsaAdGroupsCreate);
29
34
  if (!isValidUuid(flags.campaign))
30
35
  this.error('Invalid campaign ID format.', { exit: 2 });
36
+ if (!flags.automated && flags['default-bid'] === undefined) {
37
+ this.error('--default-bid is required unless --automated is set.', { exit: 2 });
38
+ }
39
+ if (flags.automated && flags['start-time'] !== undefined) {
40
+ this.error('--start-time is not allowed with --automated: Apple schedules the automated ad group itself.', { exit: 2 });
41
+ }
42
+ if (flags.automated && flags.status === 'PAUSED') {
43
+ this.error('An automated ad group must be ENABLED; pause the campaign instead.', { exit: 2 });
44
+ }
31
45
  const body = {
32
- automated_keywords_opt_in: flags['automated-keywords'],
46
+ automated_keywords_opt_in: flags.automated ? true : flags['automated-keywords'],
47
+ automated_keywords_required: flags.automated ? true : undefined,
33
48
  campaign_id: flags.campaign,
34
49
  cpa_goal: money(flags['cpa-goal'], flags.currency),
35
50
  default_bid_amount: money(flags['default-bid'], flags.currency),
36
51
  end_time: startOfDayUtc(flags['end-time']),
37
52
  name: flags.name,
38
53
  pricing_model: flags['pricing-model'],
39
- start_time: startOfDayUtc(flags['start-time'] ?? todayUtc()),
54
+ start_time: flags.automated ? undefined : startOfDayUtc(flags['start-time'] ?? todayUtc()),
40
55
  status: flags.status,
41
56
  };
42
57
  await confirmMutation(this, { body, method: 'POST', path: '/ad-groups/', summary: `Create ad group ${flags.name}` }, flags.yes);
@@ -1,7 +1,7 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
2
  import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
3
- import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
4
3
  import { currencyFlag, idempotencyFlags, money, moneyFlag, scheduleFlags, startOfDayUtc } from '../../../lib/asa-flags.js';
4
+ import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
5
5
  import { isValidUuid } from '../../../lib/flags.js';
6
6
  import { printResponse } from '../../../lib/output.js';
7
7
  export default class AsaAdGroupsUpdate extends Command {
@@ -1,7 +1,7 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
2
  import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
3
- import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
4
3
  import { idempotencyFlags } from '../../../lib/asa-flags.js';
4
+ import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
5
5
  import { isValidUuid } from '../../../lib/flags.js';
6
6
  import { printResponse } from '../../../lib/output.js';
7
7
  export default class AsaAdsCreate extends Command {
@@ -1,7 +1,7 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
2
  import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
3
- import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
4
3
  import { idempotencyFlags } from '../../../lib/asa-flags.js';
4
+ import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
5
5
  import { isValidUuid } from '../../../lib/flags.js';
6
6
  import { printResponse } from '../../../lib/output.js';
7
7
  export default class AsaAdsUpdate extends Command {
@@ -1,8 +1,8 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
4
- import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
5
4
  import { idempotencyFlags } from '../../../lib/asa-flags.js';
5
+ import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
6
6
  import { printResponse } from '../../../lib/output.js';
7
7
  export default class AsaAutomationsCreate extends Command {
8
8
  static description = 'Create an automation rule from a JSON rule file';
@@ -1,7 +1,7 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
2
  import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
3
- import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
4
3
  import { idempotencyFlags } from '../../../lib/asa-flags.js';
4
+ import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
5
5
  import { isValidUuid } from '../../../lib/flags.js';
6
6
  export default class AsaAutomationsRun extends Command {
7
7
  static args = {
@@ -1,8 +1,8 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
4
- import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
5
4
  import { idempotencyFlags } from '../../../lib/asa-flags.js';
5
+ import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
6
6
  import { isValidUuid } from '../../../lib/flags.js';
7
7
  import { printResponse } from '../../../lib/output.js';
8
8
  export default class AsaAutomationsUpdate extends Command {
@@ -2,8 +2,8 @@ import { Command, Flags } from '@oclif/core';
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { basename } from 'node:path';
4
4
  import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
5
- import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
6
5
  import { idempotencyFlags } from '../../../lib/asa-flags.js';
6
+ import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
7
7
  import { printResponse } from '../../../lib/output.js';
8
8
  const TERMINAL_STATUSES = new Set(['failed', 'partial', 'success']);
9
9
  export default class AsaCampaignsBulkCreate extends Command {
@@ -17,6 +17,11 @@ export default class AsaCampaignsCreate extends Command {
17
17
  status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
18
  'supply-source': import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
19
19
  'target-cpa': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
+ 'invoice-advertiser': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
+ 'invoice-billing-email': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
22
+ 'invoice-contact-email': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
23
+ 'invoice-contact-name': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
24
+ 'invoice-order-number': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
25
  'idempotency-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
26
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
22
27
  currency: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
@@ -1,19 +1,22 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
2
  import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
3
- import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
4
- import { currencyFlag, idempotencyFlags, money, moneyFlag } from '../../../lib/asa-flags.js';
3
+ import { currencyFlag, idempotencyFlags, invoiceFlags, locInvoiceDetails, money, moneyFlag, reportServingState, } from '../../../lib/asa-flags.js';
4
+ import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
5
5
  import { isValidUuid } from '../../../lib/flags.js';
6
6
  import { printResponse } from '../../../lib/output.js';
7
7
  export default class AsaCampaignsCreate extends Command {
8
- static description = 'Create a campaign in Apple Search Ads';
8
+ static description = 'Create a campaign in Apple Search Ads. A Max Conversions campaign also needs an automated ad group (`asa ad-groups create --automated`); line-of-credit organizations must pass all five --invoice-* flags';
9
9
  static enableJsonFlag = true;
10
10
  static examples = [
11
11
  '<%= config.bin %> asa campaigns create --org UUID --name "Winter push" --adam-id 123456 --country US --daily-budget 50',
12
+ '<%= config.bin %> asa campaigns create --org UUID --name "Max Conv" --adam-id 123456 --country US --daily-budget 50 --bidding-strategy MAX_CONVERSIONS',
13
+ '<%= config.bin %> asa campaigns create --org UUID --name "LOC push" --adam-id 123456 --country US --daily-budget 50 --invoice-advertiser "Acme Inc" --invoice-order-number PO-42 --invoice-contact-name "Jane Doe" --invoice-contact-email jane@acme.com --invoice-billing-email billing@acme.com',
12
14
  ];
13
15
  static flags = {
14
16
  ...currencyFlag,
15
17
  ...confirmFlags,
16
18
  ...idempotencyFlags,
19
+ ...invoiceFlags,
17
20
  'ad-channel-type': Flags.string({ default: 'SEARCH', description: 'Ad channel type', options: ['DISPLAY', 'SEARCH'] }),
18
21
  'adam-id': Flags.integer({ description: 'App Store app ID (adam_id)', required: true }),
19
22
  'bidding-strategy': Flags.string({
@@ -32,12 +35,19 @@ export default class AsaCampaignsCreate extends Command {
32
35
  description: 'Supply source, repeatable',
33
36
  multiple: true,
34
37
  }),
35
- 'target-cpa': moneyFlag('Target CPA'),
38
+ 'target-cpa': moneyFlag('Target CPA; must be lower than --daily-budget'),
36
39
  };
37
40
  async run() {
38
41
  const { flags } = await this.parse(AsaCampaignsCreate);
39
42
  if (!isValidUuid(flags.org))
40
43
  this.error('Invalid org ID format.', { exit: 2 });
44
+ let invoice;
45
+ try {
46
+ invoice = locInvoiceDetails(flags);
47
+ }
48
+ catch (error) {
49
+ this.error(error.message, { exit: 2 });
50
+ }
41
51
  const body = {
42
52
  ad_channel_type: flags['ad-channel-type'],
43
53
  adam_id: flags['adam-id'],
@@ -47,6 +57,7 @@ export default class AsaCampaignsCreate extends Command {
47
57
  campaign_group_id: flags.org,
48
58
  countries_or_regions: flags.country,
49
59
  daily_budget_amount: money(flags['daily-budget'], flags.currency),
60
+ loc_invoice_details: invoice,
50
61
  name: flags.name,
51
62
  status: flags.status,
52
63
  supply_sources: flags['supply-source'],
@@ -62,6 +73,7 @@ export default class AsaCampaignsCreate extends Command {
62
73
  if (result.campaign && !replayed)
63
74
  this.log('Campaign created!');
64
75
  printResponse(result, this.log.bind(this));
76
+ reportServingState(result.campaign, this.warn.bind(this));
65
77
  return result;
66
78
  }
67
79
  }
@@ -15,6 +15,11 @@ export default class AsaCampaignsUpdate extends Command {
15
15
  name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
16
  status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
17
  'target-cpa': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
+ 'invoice-advertiser': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ 'invoice-billing-email': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
+ 'invoice-contact-email': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
+ 'invoice-contact-name': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
22
+ 'invoice-order-number': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
23
  'idempotency-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
24
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
20
25
  currency: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
@@ -1,23 +1,25 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
2
  import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
3
- import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
4
- import { currencyFlag, idempotencyFlags, money, moneyFlag } from '../../../lib/asa-flags.js';
3
+ import { currencyFlag, idempotencyFlags, invoiceFlags, locInvoiceDetails, money, moneyFlag, reportServingState, } from '../../../lib/asa-flags.js';
4
+ import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
5
5
  import { isValidUuid } from '../../../lib/flags.js';
6
6
  import { printResponse } from '../../../lib/output.js';
7
7
  export default class AsaCampaignsUpdate extends Command {
8
8
  static args = {
9
9
  campaign_id: Args.string({ description: 'Campaign ID (UUID)', required: true }),
10
10
  };
11
- static description = 'Change a campaign: budgets, countries, status or schedule';
11
+ static description = 'Change a campaign: budgets, countries, status, schedule or Invoicing Options (all five --invoice-* flags together; they replace the stored set)';
12
12
  static enableJsonFlag = true;
13
13
  static examples = [
14
14
  '<%= config.bin %> asa campaigns update UUID --status PAUSED',
15
15
  '<%= config.bin %> asa campaigns update UUID --daily-budget 80',
16
+ '<%= config.bin %> asa campaigns update UUID --invoice-advertiser "Acme Inc" --invoice-order-number PO-42 --invoice-contact-name "Jane Doe" --invoice-contact-email jane@acme.com --invoice-billing-email billing@acme.com',
16
17
  ];
17
18
  static flags = {
18
19
  ...currencyFlag,
19
20
  ...confirmFlags,
20
21
  ...idempotencyFlags,
22
+ ...invoiceFlags,
21
23
  'bidding-strategy': Flags.string({
22
24
  description: 'Bidding strategy',
23
25
  options: ['MANUAL_CPT', 'MAX_CONVERSIONS'],
@@ -48,8 +50,16 @@ export default class AsaCampaignsUpdate extends Command {
48
50
  body.target_cpa = money(flags['target-cpa'], flags.currency);
49
51
  if (flags['bidding-strategy'] !== undefined)
50
52
  body.bidding_strategy = flags['bidding-strategy'];
53
+ try {
54
+ const details = locInvoiceDetails(flags);
55
+ if (details)
56
+ body.loc_invoice_details = details;
57
+ }
58
+ catch (error) {
59
+ this.error(error.message, { exit: 2 });
60
+ }
51
61
  if (Object.keys(body).length === 0) {
52
- this.error('Nothing to change. Pass at least one field, e.g. --status PAUSED.', { exit: 2 });
62
+ this.error('Nothing to change. Pass at least one field, e.g. --status PAUSED, or the five --invoice-* flags for a line-of-credit organization.', { exit: 2 });
53
63
  }
54
64
  await confirmMutation(this, { body, method: 'PUT', path: `/campaigns/${args.campaign_id}/`, summary: 'Update campaign' }, flags.yes);
55
65
  const client = await createAsaClient(this.config);
@@ -61,6 +71,7 @@ export default class AsaCampaignsUpdate extends Command {
61
71
  if (result.campaign && !replayed)
62
72
  this.log('Campaign updated!');
63
73
  printResponse(result, this.log.bind(this));
74
+ reportServingState(result.campaign, this.warn.bind(this));
64
75
  return result;
65
76
  }
66
77
  }
@@ -1,8 +1,8 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
4
- import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
5
4
  import { currencyFlag, idempotencyFlags, MAX_BULK_ITEMS, money, moneyFlag, reportBulkOutcome } from '../../../lib/asa-flags.js';
5
+ import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
6
6
  import { isValidUuid } from '../../../lib/flags.js';
7
7
  export default class AsaKeywordsAdd extends Command {
8
8
  static description = 'Add targeting keywords to an ad group';
@@ -1,7 +1,7 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
2
  import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
3
- import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
4
3
  import { currencyFlag, idempotencyFlags, money, moneyFlag, reportBulkOutcome } from '../../../lib/asa-flags.js';
4
+ import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
5
5
  import { isValidUuid } from '../../../lib/flags.js';
6
6
  export default class AsaKeywordsUpdate extends Command {
7
7
  static args = {
@@ -1,7 +1,7 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
2
  import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
3
- import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
4
3
  import { idempotencyFlags, MAX_BULK_ITEMS, reportBulkOutcome } from '../../../lib/asa-flags.js';
4
+ import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
5
5
  import { isValidUuid } from '../../../lib/flags.js';
6
6
  export default class AsaNegativeKeywordsAdd extends Command {
7
7
  static description = 'Add negative keywords to an ad group or across a campaign';
@@ -1,7 +1,7 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
2
  import { asaWrite, createAsaClient } from '../../../lib/asa-client.js';
3
- import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
4
3
  import { idempotencyFlags } from '../../../lib/asa-flags.js';
4
+ import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
5
5
  import { printResponse } from '../../../lib/output.js';
6
6
  export default class AsaProductPagesSync extends Command {
7
7
  static description = 'Refresh custom product pages from Apple';
@@ -0,0 +1,15 @@
1
+ import { Command } from '@oclif/core';
2
+ import type { FlowDTO } from '../../lib/api-schemas.js';
3
+ export default class FlowsPublish extends Command {
4
+ static args: {
5
+ flow_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
+ };
7
+ static description: string;
8
+ static enableJsonFlag: boolean;
9
+ static examples: string[];
10
+ static flags: {
11
+ yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ };
14
+ run(): Promise<FlowDTO>;
15
+ }
@@ -0,0 +1,34 @@
1
+ import { Args, Command } from '@oclif/core';
2
+ import { createAuthenticatedClient } from '../../lib/client-from-config.js';
3
+ import { confirmFlags, confirmMutation } from '../../lib/confirm.js';
4
+ import { appFlag, isValidUuid } from '../../lib/flags.js';
5
+ import { printResponse } from '../../lib/output.js';
6
+ export default class FlowsPublish extends Command {
7
+ static args = {
8
+ flow_id: Args.string({ description: 'Flow ID (UUID)', required: true }),
9
+ };
10
+ static description = 'Publish a flow (async — publication completes in the background)';
11
+ static enableJsonFlag = true;
12
+ static examples = ['<%= config.bin %> flows publish --app UUID 550e8400-e29b-41d4-a716-446655440000'];
13
+ static flags = {
14
+ ...appFlag,
15
+ ...confirmFlags,
16
+ };
17
+ async run() {
18
+ const { args, flags } = await this.parse(FlowsPublish);
19
+ if (!isValidUuid(args.flow_id)) {
20
+ this.error('Invalid flow ID format.', { exit: 2 });
21
+ }
22
+ const client = await createAuthenticatedClient(this.config);
23
+ const flow = await client.get(`/apps/${flags.app}/flows/${args.flow_id}/`);
24
+ await confirmMutation(this, {
25
+ method: 'POST',
26
+ path: `/apps/${flags.app}/flows/${args.flow_id}/publish/`,
27
+ summary: `Publish flow "${flow.name}" live to end users`,
28
+ }, flags.yes);
29
+ const result = await client.post(`/apps/${flags.app}/flows/${args.flow_id}/publish/`);
30
+ this.log('Publishing started — status: publishing. Publication completes asynchronously.');
31
+ printResponse(result, this.log.bind(this));
32
+ return result;
33
+ }
34
+ }
@@ -0,0 +1,15 @@
1
+ import { Command } from '@oclif/core';
2
+ import type { FlowDTO } from '../../lib/api-schemas.js';
3
+ export default class FlowsUpdate extends Command {
4
+ static args: {
5
+ flow_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
+ };
7
+ static description: string;
8
+ static enableJsonFlag: boolean;
9
+ static examples: string[];
10
+ static flags: {
11
+ name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ };
14
+ run(): Promise<FlowDTO>;
15
+ }
@@ -0,0 +1,28 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import { createAuthenticatedClient } from '../../lib/client-from-config.js';
3
+ import { appFlag, isValidUuid } from '../../lib/flags.js';
4
+ import { printResponse } from '../../lib/output.js';
5
+ export default class FlowsUpdate extends Command {
6
+ static args = {
7
+ flow_id: Args.string({ description: 'Flow ID (UUID)', required: true }),
8
+ };
9
+ static description = 'Rename a flow';
10
+ static enableJsonFlag = true;
11
+ static examples = ['<%= config.bin %> flows update --app UUID 550e8400-e29b-41d4-a716-446655440000 --name "Onboarding v2"'];
12
+ static flags = {
13
+ ...appFlag,
14
+ name: Flags.string({ description: 'New flow name', required: true }),
15
+ };
16
+ async run() {
17
+ const { args, flags } = await this.parse(FlowsUpdate);
18
+ if (!isValidUuid(args.flow_id)) {
19
+ this.error('Invalid flow ID format.', { exit: 2 });
20
+ }
21
+ const body = { name: flags.name };
22
+ const client = await createAuthenticatedClient(this.config);
23
+ const result = await client.put(`/apps/${flags.app}/flows/${args.flow_id}/`, body);
24
+ this.log('Flow updated!');
25
+ printResponse(result, this.log.bind(this));
26
+ return result;
27
+ }
28
+ }
@@ -2,17 +2,22 @@ import { Command, Flags } from '@oclif/core';
2
2
  import { createAuthenticatedClient } from '../../lib/client-from-config.js';
3
3
  import { appFlag } from '../../lib/flags.js';
4
4
  import { printResponse } from '../../lib/output.js';
5
+ import { audienceEntryProblem } from '../../lib/placement-audiences.js';
5
6
  export default class PlacementsCreate extends Command {
6
7
  static description = 'Create a placement with a paywall';
7
8
  static enableJsonFlag = true;
8
9
  static examples = [
9
- '<%= config.bin %> placements create --app UUID --title "Default" --developer-id default --audiences \'[{"segment_ids":[],"paywall_id":"PAYWALL_UUID","priority":0}]\'',
10
+ '<%= config.bin %> placements create --app UUID --title "Default" --developer-id default --audiences \'[{"content_type":"paywall","segment_ids":[],"paywall_id":"PAYWALL_UUID","priority":0}]\'',
11
+ '<%= config.bin %> placements create --app UUID --title "Default" --developer-id default --audiences \'[{"content_type":"flow","segment_ids":[],"flow_id":"FLOW_UUID","priority":0}]\'',
10
12
  '<%= config.bin %> placements create --app UUID --title "Default" --developer-id default --paywall-id PAYWALL_UUID',
11
13
  ];
12
14
  static flags = {
13
15
  ...appFlag,
14
16
  audiences: Flags.string({
15
- description: 'JSON array of audience entries: [{segment_ids, paywall_id, priority}]',
17
+ description: 'JSON array of audience entries. Every entry needs an explicit content_type. ' +
18
+ 'Paywall: {content_type:"paywall", segment_ids, paywall_id, priority}. ' +
19
+ 'Flow: {content_type:"flow", segment_ids, flow_id, priority}. ' +
20
+ 'A flow must be published first (flows publish) — attaching a draft flow returns 400.',
16
21
  exactlyOne: ['paywall-id', 'audiences'],
17
22
  }),
18
23
  'developer-id': Flags.string({ description: 'Developer ID for the placement', required: true }),
@@ -36,12 +41,22 @@ export default class PlacementsCreate extends Command {
36
41
  body.paywall_id = flags['paywall-id'];
37
42
  }
38
43
  else {
44
+ let parsed;
39
45
  try {
40
- body.audiences = JSON.parse(flags.audiences);
46
+ parsed = JSON.parse(flags.audiences);
41
47
  }
42
48
  catch (error) {
43
49
  this.error(`Invalid --audiences JSON: ${error instanceof Error ? error.message : String(error)}`, { exit: 2 });
44
50
  }
51
+ if (!Array.isArray(parsed)) {
52
+ this.error('--audiences must be a JSON array of audience entries.', { exit: 2 });
53
+ }
54
+ for (const [index, entry] of parsed.entries()) {
55
+ const problem = audienceEntryProblem(entry);
56
+ if (problem)
57
+ this.error(`--audiences[${index}]: ${problem}`, { exit: 2 });
58
+ }
59
+ body.audiences = parsed;
45
60
  }
46
61
  const client = await createAuthenticatedClient(this.config);
47
62
  const result = await client.post(`/apps/${flags.app}/placements`, body);
@@ -2,6 +2,7 @@ import { Args, Command, Flags } from '@oclif/core';
2
2
  import { createAuthenticatedClient } from '../../lib/client-from-config.js';
3
3
  import { appFlag, isValidUuid } from '../../lib/flags.js';
4
4
  import { printResponse } from '../../lib/output.js';
5
+ import { audienceEntryProblem } from '../../lib/placement-audiences.js';
5
6
  export default class PlacementsUpdate extends Command {
6
7
  static args = {
7
8
  placement_id: Args.string({ description: 'Placement ID (UUID)', required: true }),
@@ -9,13 +10,17 @@ export default class PlacementsUpdate extends Command {
9
10
  static description = 'Update a placement';
10
11
  static enableJsonFlag = true;
11
12
  static examples = [
12
- '<%= config.bin %> placements update --app UUID 550e8400-... --title "Default" --developer-id default --audiences \'[{"segment_ids":[],"paywall_id":"PAYWALL_UUID","priority":0}]\'',
13
+ '<%= config.bin %> placements update --app UUID 550e8400-... --title "Default" --developer-id default --audiences \'[{"content_type":"paywall","segment_ids":[],"paywall_id":"PAYWALL_UUID","priority":0}]\'',
14
+ '<%= config.bin %> placements update --app UUID 550e8400-... --title "Default" --developer-id default --audiences \'[{"content_type":"flow","segment_ids":[],"flow_id":"FLOW_UUID","priority":0}]\'',
13
15
  '<%= config.bin %> placements update --app UUID 550e8400-... --title "Default" --developer-id default --paywall-id PAYWALL_UUID',
14
16
  ];
15
17
  static flags = {
16
18
  ...appFlag,
17
19
  audiences: Flags.string({
18
- description: 'JSON array of audience entries: [{segment_ids, paywall_id, priority}]',
20
+ description: 'JSON array of audience entries. Every entry needs an explicit content_type. ' +
21
+ 'Paywall: {content_type:"paywall", segment_ids, paywall_id, priority}. ' +
22
+ 'Flow: {content_type:"flow", segment_ids, flow_id, priority}. ' +
23
+ 'A flow must be published first (flows publish) — attaching a draft flow returns 400.',
19
24
  exactlyOne: ['paywall-id', 'audiences'],
20
25
  }),
21
26
  'developer-id': Flags.string({ description: 'Developer ID for the placement', required: true }),
@@ -45,12 +50,22 @@ export default class PlacementsUpdate extends Command {
45
50
  body.paywall_id = flags['paywall-id'];
46
51
  }
47
52
  else {
53
+ let parsed;
48
54
  try {
49
- body.audiences = JSON.parse(flags.audiences);
55
+ parsed = JSON.parse(flags.audiences);
50
56
  }
51
57
  catch (error) {
52
58
  this.error(`Invalid --audiences JSON: ${error instanceof Error ? error.message : String(error)}`, { exit: 2 });
53
59
  }
60
+ if (!Array.isArray(parsed)) {
61
+ this.error('--audiences must be a JSON array of audience entries.', { exit: 2 });
62
+ }
63
+ for (const [index, entry] of parsed.entries()) {
64
+ const problem = audienceEntryProblem(entry);
65
+ if (problem)
66
+ this.error(`--audiences[${index}]: ${problem}`, { exit: 2 });
67
+ }
68
+ body.audiences = parsed;
54
69
  }
55
70
  const client = await createAuthenticatedClient(this.config);
56
71
  const result = await client.put(`/apps/${flags.app}/placements/${args.placement_id}`, body);
@@ -129,11 +129,20 @@ export interface SegmentDTO {
129
129
  id: string;
130
130
  title: string;
131
131
  }
132
- export interface PlacementAudienceEntryDTO {
132
+ export interface PlacementPaywallAudienceEntryDTO {
133
+ content_type: 'paywall';
133
134
  paywall_id: string;
134
135
  priority: number;
135
136
  segment_ids?: string[];
136
137
  }
138
+ export interface PlacementFlowAudienceEntryDTO {
139
+ content_type: 'flow';
140
+ /** The flow must be `published` (not draft) — the backend returns 400 otherwise. */
141
+ flow_id: string;
142
+ priority: number;
143
+ segment_ids?: string[];
144
+ }
145
+ export type PlacementAudienceEntryDTO = PlacementFlowAudienceEntryDTO | PlacementPaywallAudienceEntryDTO;
137
146
  export interface PlacementSummaryDTO {
138
147
  developer_id: string;
139
148
  id: string;
@@ -1,5 +1,5 @@
1
1
  import type { QueryParams } from './api-client.js';
2
- import type { AsaMoney, AsaMutationError } from './asa-schemas.js';
2
+ import type { AsaLocInvoiceDetails, AsaMoney, AsaMutationError } from './asa-schemas.js';
3
3
  export declare const MAX_BULK_ITEMS = 100;
4
4
  export declare const MAX_BY_DAYS = 16;
5
5
  export declare const ASA_METRIC_ENTITIES: string[];
@@ -79,6 +79,27 @@ export declare const idempotencyFlags: {
79
79
  'idempotency-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
80
80
  };
81
81
  export declare function money(amount: string | undefined, currency: string): AsaMoney | undefined;
82
+ declare const INVOICE_FIELDS: {
83
+ readonly 'invoice-advertiser': "client_name";
84
+ readonly 'invoice-billing-email': "billing_contact_email";
85
+ readonly 'invoice-contact-email': "buyer_email";
86
+ readonly 'invoice-contact-name': "buyer_name";
87
+ readonly 'invoice-order-number': "order_number";
88
+ };
89
+ type InvoiceFlagName = keyof typeof INVOICE_FIELDS;
90
+ export declare const invoiceFlags: {
91
+ 'invoice-advertiser': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
92
+ 'invoice-billing-email': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
93
+ 'invoice-contact-email': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
94
+ 'invoice-contact-name': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
95
+ 'invoice-order-number': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
96
+ };
97
+ export declare function locInvoiceDetails(flags: Partial<Record<InvoiceFlagName, string>>): Record<keyof AsaLocInvoiceDetails, string> | undefined;
98
+ export declare function reportServingState(campaign: null | {
99
+ internal_id: string;
100
+ serving_state_reasons?: null | string[];
101
+ serving_status?: null | string;
102
+ }, warn: (msg: string) => void): void;
82
103
  interface BulkOutcome {
83
104
  applied: unknown[];
84
105
  errors: AsaMutationError[];