adapty 0.3.0 → 0.5.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 (88) hide show
  1. package/README.md +156 -5
  2. package/dist/commands/asa/ad-groups/create.d.ts +22 -0
  3. package/dist/commands/asa/ad-groups/create.js +54 -0
  4. package/dist/commands/asa/ad-groups/get.d.ts +11 -0
  5. package/dist/commands/asa/ad-groups/get.js +21 -0
  6. package/dist/commands/asa/ad-groups/list.d.ts +18 -0
  7. package/dist/commands/asa/ad-groups/list.js +24 -0
  8. package/dist/commands/asa/ad-groups/update.d.ts +23 -0
  9. package/dist/commands/asa/ad-groups/update.js +62 -0
  10. package/dist/commands/asa/ads/create.d.ts +16 -0
  11. package/dist/commands/asa/ads/create.js +41 -0
  12. package/dist/commands/asa/ads/get.d.ts +11 -0
  13. package/dist/commands/asa/ads/get.js +21 -0
  14. package/dist/commands/asa/ads/list.d.ts +18 -0
  15. package/dist/commands/asa/ads/list.js +24 -0
  16. package/dist/commands/asa/ads/update.d.ts +17 -0
  17. package/dist/commands/asa/ads/update.js +44 -0
  18. package/dist/commands/asa/apps/list.d.ts +13 -0
  19. package/dist/commands/asa/apps/list.js +18 -0
  20. package/dist/commands/asa/automations/create.d.ts +16 -0
  21. package/dist/commands/asa/automations/create.js +60 -0
  22. package/dist/commands/asa/automations/get.d.ts +11 -0
  23. package/dist/commands/asa/automations/get.js +21 -0
  24. package/dist/commands/asa/automations/list.d.ts +13 -0
  25. package/dist/commands/asa/automations/list.js +18 -0
  26. package/dist/commands/asa/automations/run.d.ts +16 -0
  27. package/dist/commands/asa/automations/run.js +41 -0
  28. package/dist/commands/asa/automations/runs.d.ts +16 -0
  29. package/dist/commands/asa/automations/runs.js +24 -0
  30. package/dist/commands/asa/automations/update.d.ts +21 -0
  31. package/dist/commands/asa/automations/update.js +73 -0
  32. package/dist/commands/asa/campaigns/bulk-create.d.ts +25 -0
  33. package/dist/commands/asa/campaigns/bulk-create.js +177 -0
  34. package/dist/commands/asa/campaigns/bulk-status.d.ts +15 -0
  35. package/dist/commands/asa/campaigns/bulk-status.js +26 -0
  36. package/dist/commands/asa/campaigns/create.d.ts +25 -0
  37. package/dist/commands/asa/campaigns/create.js +67 -0
  38. package/dist/commands/asa/campaigns/get.d.ts +11 -0
  39. package/dist/commands/asa/campaigns/get.js +21 -0
  40. package/dist/commands/asa/campaigns/list.d.ts +17 -0
  41. package/dist/commands/asa/campaigns/list.js +24 -0
  42. package/dist/commands/asa/campaigns/update.d.ts +23 -0
  43. package/dist/commands/asa/campaigns/update.js +66 -0
  44. package/dist/commands/asa/competitors/summary.d.ts +11 -0
  45. package/dist/commands/asa/competitors/summary.js +56 -0
  46. package/dist/commands/asa/connect.d.ts +12 -0
  47. package/dist/commands/asa/connect.js +39 -0
  48. package/dist/commands/asa/creatives/list.d.ts +15 -0
  49. package/dist/commands/asa/creatives/list.js +24 -0
  50. package/dist/commands/asa/keywords/add.d.ts +20 -0
  51. package/dist/commands/asa/keywords/add.js +73 -0
  52. package/dist/commands/asa/keywords/list.d.ts +19 -0
  53. package/dist/commands/asa/keywords/list.js +24 -0
  54. package/dist/commands/asa/keywords/update.d.ts +21 -0
  55. package/dist/commands/asa/keywords/update.js +64 -0
  56. package/dist/commands/asa/metrics/index.d.ts +21 -0
  57. package/dist/commands/asa/metrics/index.js +68 -0
  58. package/dist/commands/asa/metrics/overview.d.ts +15 -0
  59. package/dist/commands/asa/metrics/overview.js +51 -0
  60. package/dist/commands/asa/negative-keywords/add.d.ts +18 -0
  61. package/dist/commands/asa/negative-keywords/add.js +70 -0
  62. package/dist/commands/asa/negative-keywords/list.d.ts +19 -0
  63. package/dist/commands/asa/negative-keywords/list.js +29 -0
  64. package/dist/commands/asa/orgs/list.d.ts +13 -0
  65. package/dist/commands/asa/orgs/list.js +18 -0
  66. package/dist/commands/asa/product-pages/list.d.ts +15 -0
  67. package/dist/commands/asa/product-pages/list.js +24 -0
  68. package/dist/commands/asa/product-pages/sync.d.ts +13 -0
  69. package/dist/commands/asa/product-pages/sync.js +28 -0
  70. package/dist/commands/asa/search-terms/list.d.ts +20 -0
  71. package/dist/commands/asa/search-terms/list.js +25 -0
  72. package/dist/commands/asa/whoami.d.ts +8 -0
  73. package/dist/commands/asa/whoami.js +22 -0
  74. package/dist/lib/api-client.d.ts +16 -3
  75. package/dist/lib/api-client.js +53 -18
  76. package/dist/lib/asa-client.d.ts +16 -0
  77. package/dist/lib/asa-client.js +46 -0
  78. package/dist/lib/asa-confirm.d.ts +18 -0
  79. package/dist/lib/asa-confirm.js +41 -0
  80. package/dist/lib/asa-flags.d.ts +88 -0
  81. package/dist/lib/asa-flags.js +137 -0
  82. package/dist/lib/asa-schemas.d.ts +327 -0
  83. package/dist/lib/asa-schemas.js +1 -0
  84. package/dist/lib/errors.d.ts +25 -2
  85. package/dist/lib/errors.js +48 -7
  86. package/dist/lib/output.js +45 -15
  87. package/oclif.manifest.json +2956 -157
  88. package/package.json +41 -1
package/README.md CHANGED
@@ -86,6 +86,153 @@ adapty access-levels create --app UUID [flags]
86
86
  adapty access-levels update --app UUID ACCESS_LEVEL_ID [flags]
87
87
  ```
88
88
 
89
+ ### Apple Search Ads
90
+
91
+ Apple Search Ads commands live under `adapty asa` and talk to the ASA service rather than the Developer
92
+ API. They take **no `--app`**: the scope is the company behind your token. A connected Apple Ads account and
93
+ an active Ads Manager subscription are required — `adapty asa whoami` tells you where you stand.
94
+
95
+ ```sh
96
+ adapty asa whoami # company, how access was granted, Apple connection state
97
+ adapty asa connect [--no-wait] # link an Apple Search Ads account
98
+ adapty asa apps list # apps promoted in Apple Search Ads
99
+ adapty asa orgs list # Apple Search Ads organizations
100
+ ```
101
+
102
+ Every list takes scope filters, and they narrow the query rather than the printed page — `asa keywords list`
103
+ unfiltered pages through the whole account, while one ad group is a handful of rows, so scope the read:
104
+
105
+ ```sh
106
+ adapty asa campaigns list --app APP_UUID --status PAUSED
107
+ adapty asa ad-groups list --campaign CAMPAIGN_UUID
108
+ adapty asa keywords list --ad-group AD_GROUP_UUID --status ACTIVE
109
+ adapty asa keywords list --ad-group AD_GROUP_UUID --ad-group OTHER_UUID # repeatable
110
+ adapty asa creatives list --app APP_UUID
111
+ ```
112
+
113
+ `--campaign-group`, `--app`, `--campaign`, `--ad-group` are repeatable and take the UUIDs printed by the
114
+ matching list command; `--search` matches names case-insensitively. Each list accepts only the filters that
115
+ make sense for it: `--ad-group` starts at keywords, negative keywords, search terms and ads, `--status` is
116
+ `ENABLED`/`PAUSED` everywhere except keywords, which are `ACTIVE`/`PAUSED`, and `asa ads list` has no `--app`
117
+ because ads hang off ad groups. An id belonging to another company simply matches nothing.
118
+
119
+ Campaign structure. These lists return metadata only — numbers come from `asa metrics`, and only
120
+ `asa search-terms list` takes `--date-from` / `--date-to` (default: today):
121
+
122
+ ```sh
123
+ adapty asa campaigns list
124
+ adapty asa campaigns get CAMPAIGN_ID
125
+ adapty asa campaigns create --org UUID --name "Winter push" --adam-id 123456 --country US --daily-budget 50
126
+ adapty asa campaigns update CAMPAIGN_ID [--status PAUSED] [--daily-budget 80] [--country US]
127
+
128
+ adapty asa ad-groups list
129
+ adapty asa ad-groups get AD_GROUP_ID
130
+ adapty asa ad-groups create --campaign UUID --name "Brand terms" --default-bid 1.20
131
+ adapty asa ad-groups update AD_GROUP_ID [--default-bid 1.50] [--status PAUSED]
132
+
133
+ adapty asa ads list
134
+ adapty asa ads get AD_ID
135
+ adapty asa ads create --ad-group UUID --creative-id 4321 --name "Summer ad"
136
+ adapty asa ads update AD_ID [--name "..."] [--status PAUSED]
137
+ ```
138
+
139
+ Bulk structure creation submits a whole tree — campaigns with nested ad groups, keywords, negative keywords
140
+ and ads — in one call. The input is a JSON structure (the natural path for an AI agent: generate it, pipe it
141
+ in), or a native Apple Ads template converted server-side. The whole payload is validated before anything is
142
+ created — a rejection lists every bad node; on acceptance the command polls progress and prints the final
143
+ report (`success` / `partial` / `failed`, with per-object failures):
144
+
145
+ ```sh
146
+ adapty asa campaigns bulk-create --file structure.json # JSON structure, waits and reports
147
+ cat structure.json | adapty asa campaigns bulk-create --file - # same, from stdin
148
+ adapty asa campaigns bulk-create --file structure.json --no-wait
149
+ adapty asa campaigns bulk-create --from-file Campaign_And_Adgroup_Template.xlsx --org-id 1234567
150
+ adapty asa campaigns bulk-create --from-file keywords_template.csv --org-id 1234567 --preview
151
+ adapty asa campaigns bulk-status OPERATION_ID
152
+ ```
153
+
154
+ The structure is `{campaign_group_id | campaign_group_internal_id, campaigns: [...]}` where each campaign
155
+ node either creates (`payload`) or addresses an existing campaign by id (an *anchor*, optionally carrying
156
+ `update_payload`), and nests `ad_groups` with `keywords`, `negative_keywords` and `ads` the same way.
157
+ `--from-file` takes the native Apple Ads templates and needs `--org-id` (the Apple org id from
158
+ `asa orgs list`); `--preview` prints the converted request without creating anything. Conversion issues are
159
+ reported with their sheet, row and column.
160
+
161
+ Keywords are always applied as a batch, at most 100 per call, and a partial rejection is reported per item:
162
+
163
+ ```sh
164
+ adapty asa keywords list
165
+ adapty asa keywords add --ad-group UUID --text "running shoes" --text "trail shoes" [--bid 1.20] [--match-type EXACT]
166
+ adapty asa keywords add --ad-group UUID --from-file keywords.txt
167
+ adapty asa keywords update KEYWORD_ID [KEYWORD_ID...] [--bid 2.00] [--status PAUSED]
168
+
169
+ adapty asa negative-keywords list
170
+ adapty asa negative-keywords add --ad-group UUID --text free
171
+ adapty asa negative-keywords add --campaign UUID [--all-ad-groups] --text free
172
+
173
+ adapty asa search-terms list [--date-from ... --date-to ...]
174
+ ```
175
+
176
+ Product pages and rule-based automations:
177
+
178
+ ```sh
179
+ adapty asa product-pages list
180
+ adapty asa product-pages sync [--adam-id 123456]
181
+
182
+ adapty asa automations list
183
+ adapty asa automations get AUTOMATION_ID
184
+ adapty asa automations create --file rule.json [--run-now]
185
+ adapty asa automations update AUTOMATION_ID [--stop] [--start] [--name "..."] [--file rule.json]
186
+ adapty asa automations run AUTOMATION_ID [--dry-run]
187
+ adapty asa automations runs AUTOMATION_ID
188
+ ```
189
+
190
+ Metrics take an entity level, a period and an optional metric selection. Rows come back one per entity,
191
+ aggregated and sorted server-side, so a top-N or a breakdown is a single call — use `--order-by` with a small
192
+ `--page-size` for rankings, `metrics overview` for account totals and time series, and one big page (up to
193
+ 1000 rows) when you genuinely need every row; never sum pages client-side:
194
+
195
+ ```sh
196
+ adapty asa metrics --entity campaign --date-from 2026-07-01 --date-to 2026-07-31
197
+ adapty asa metrics --entity campaign --date-from 2026-07-01 --date-to 2026-07-31 --order-by spend --page-size 5
198
+ adapty asa metrics --entity campaign --date-from 2026-07-01 --date-to 2026-07-31 --group-by country --page-size 1000
199
+ adapty asa metrics --entity keyword --date-from 2026-07-01 --date-to 2026-07-31 --metric spend --metric roas
200
+ adapty asa metrics --entity campaign --date-from 2026-07-01 --date-to 2026-07-31 --metric roas --by-days 7 --by-days 90
201
+ adapty asa metrics overview --entity campaign --date-from 2026-07-01 --date-to 2026-07-31 [--period-unit week]
202
+ ```
203
+
204
+ There is no `ltv` metric: lifetime value is a cohort metric read at a renewal window, so `--by-days` is how you
205
+ ask for day-7 or day-90 values — on either route, up to 16 windows per call. `--order-by-day` ranks the rows by
206
+ one of those windows, which is how you get the top campaigns by day-90 ROAS in a single call.
207
+
208
+ Competitor summary takes 1–5 Apple App Store IDs and covers the last full month across every country — there
209
+ are no period or country flags on purpose. The first call on a cold cache can take tens of seconds:
210
+
211
+ ```sh
212
+ adapty asa competitors summary --app-ids 111111111,2222222
213
+ ```
214
+
215
+ Writes go straight to Apple and take seconds, so every writing command first prints the exact request body and
216
+ asks for a yes. `--yes` skips the question for scripts; in a pipe or under `--json` the command refuses instead
217
+ of waiting for input that will never come. There is no undo — the CLI has no delete.
218
+
219
+ Every write also carries an idempotency key. The CLI generates one per invocation and retries once on a
220
+ network error, so a request that died on the wire is never applied twice. Pass `--idempotency-key` to pin the
221
+ key yourself: re-running a script with the same key within 24 hours replays the stored result — the CLI prints
222
+ "Already applied earlier — showing the stored result." — instead of creating a second entity. The same key
223
+ with a different body is rejected (`422 cli_idempotency_key_reuse`), and a concurrent duplicate answers
224
+ `409 cli_idempotency_in_progress`.
225
+
226
+ Analytics is rate limited per company: the metrics routes get 5 calls a minute (at most 2 in any 10 seconds)
227
+ and share a pool of two concurrent queries with the search-terms list — a busy pool answers
228
+ `429 cli_analytics_busy`, an exhausted window `429 cli_rate_limit_exceeded`, both with the exact wait in
229
+ `Retry-After`. The CLI absorbs a single 429 on its own — it waits the announced `Retry-After` (up to 60
230
+ seconds; cool-downs are never waited out) and retries once — so a 429 that reaches you means the retry failed
231
+ too. A burst of 429s puts the token into an escalating cool-down (`cli_cooldown_active`, 5 minutes →
232
+ 30 minutes → 3 hours); retries during the pause don't extend it, but the cure is fixing the failing request,
233
+ not waiting out the pause in a loop. An automation run is queued rather than awaited: `run` prints a run ID
234
+ and the outcome shows up in `adapty asa automations runs`.
235
+
89
236
  ### Global Flags
90
237
 
91
238
  | Flag | Description |
@@ -93,14 +240,18 @@ adapty access-levels update --app UUID ACCESS_LEVEL_ID [flags]
93
240
  | `--json` | Output as JSON |
94
241
  | `--help` | Show help |
95
242
  | `--page` | Page number (default: 1) |
96
- | `--page-size` | Items per page (default: 20, max: 100) |
243
+ | `--page-size` | Items per page (default: 20, max: 100; `asa` commands: default 100, max 1000) |
97
244
 
98
245
  ## Environment Variables
99
246
 
100
- | Variable | Description |
101
- | ---------------- | ------------------------------------------------------------------------------- |
102
- | `ADAPTY_TOKEN` | Override stored auth token |
103
- | `ADAPTY_API_URL` | Override API base URL (default: `https://api-admin.adapty.io/api/v1/developer`) |
247
+ | Variable | Description |
248
+ | -------------------- | --------------------------------------------------------------------------------------- |
249
+ | `ADAPTY_TOKEN` | Override stored auth token |
250
+ | `ADAPTY_API_URL` | Override Developer API base URL (default: `https://api-admin.adapty.io/api/v1/developer`) |
251
+ | `ADAPTY_ASA_API_URL` | Override Apple Search Ads base URL (default: `https://api-asa-admin.adapty.io/api/v1/cli`) |
252
+
253
+ The two API URLs are independent: pointing `ADAPTY_API_URL` at a staging host leaves `adapty asa` on the ASA
254
+ default, and the other way round.
104
255
 
105
256
  ## Claude Code Skill
106
257
 
@@ -0,0 +1,22 @@
1
+ import { Command } from '@oclif/core';
2
+ import type { AsaAdGroupMutationDTO } from '../../../lib/asa-schemas.js';
3
+ export default class AsaAdGroupsCreate extends Command {
4
+ static description: string;
5
+ static enableJsonFlag: boolean;
6
+ static examples: string[];
7
+ static flags: {
8
+ 'automated-keywords': import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ campaign: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'cpa-goal': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ 'default-bid': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ 'idempotency-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ 'pricing-model': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
17
+ 'end-time': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
+ 'start-time': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ currency: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
20
+ };
21
+ run(): Promise<AsaAdGroupMutationDTO>;
22
+ }
@@ -0,0 +1,54 @@
1
+ import { Command, Flags } from '@oclif/core';
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, pricingModelFlag, scheduleFlags, startOfDayUtc, todayUtc, } from '../../../lib/asa-flags.js';
5
+ import { isValidUuid } from '../../../lib/flags.js';
6
+ import { printResponse } from '../../../lib/output.js';
7
+ export default class AsaAdGroupsCreate extends Command {
8
+ static description = 'Create an ad group inside a campaign';
9
+ static enableJsonFlag = true;
10
+ static examples = [
11
+ '<%= config.bin %> asa ad-groups create --campaign UUID --name "Brand terms" --default-bid 1.20',
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
+ ];
14
+ static flags = {
15
+ ...currencyFlag,
16
+ ...scheduleFlags,
17
+ ...pricingModelFlag,
18
+ ...confirmFlags,
19
+ ...idempotencyFlags,
20
+ 'automated-keywords': Flags.boolean({ allowNo: true, description: 'Let Apple add keywords automatically' }),
21
+ campaign: Flags.string({ description: 'Campaign ID (UUID)', required: true }),
22
+ 'cpa-goal': moneyFlag('CPA goal'),
23
+ 'default-bid': moneyFlag('Default bid', { required: true }),
24
+ name: Flags.string({ description: 'Ad group name', required: true }),
25
+ status: Flags.string({ description: 'Initial status', options: ['ENABLED', 'PAUSED'] }),
26
+ };
27
+ async run() {
28
+ const { flags } = await this.parse(AsaAdGroupsCreate);
29
+ if (!isValidUuid(flags.campaign))
30
+ this.error('Invalid campaign ID format.', { exit: 2 });
31
+ const body = {
32
+ automated_keywords_opt_in: flags['automated-keywords'],
33
+ campaign_id: flags.campaign,
34
+ cpa_goal: money(flags['cpa-goal'], flags.currency),
35
+ default_bid_amount: money(flags['default-bid'], flags.currency),
36
+ end_time: startOfDayUtc(flags['end-time']),
37
+ name: flags.name,
38
+ pricing_model: flags['pricing-model'],
39
+ start_time: startOfDayUtc(flags['start-time'] ?? todayUtc()),
40
+ status: flags.status,
41
+ };
42
+ await confirmMutation(this, { body, method: 'POST', path: '/ad-groups/', summary: `Create ad group ${flags.name}` }, flags.yes);
43
+ const client = await createAsaClient(this.config);
44
+ const { replayed, result } = await asaWrite(client, 'post', '/ad-groups', {
45
+ body,
46
+ idempotencyKey: flags['idempotency-key'],
47
+ });
48
+ noteReplay(replayed, this.log.bind(this));
49
+ if (result.ad_group && !replayed)
50
+ this.log('Ad group created!');
51
+ printResponse(result, this.log.bind(this));
52
+ return result;
53
+ }
54
+ }
@@ -0,0 +1,11 @@
1
+ import { Command } from '@oclif/core';
2
+ import type { AsaAdGroupDTO } from '../../../lib/asa-schemas.js';
3
+ export default class AsaAdGroupsGet extends Command {
4
+ static args: {
5
+ ad_group_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
+ };
7
+ static description: string;
8
+ static enableJsonFlag: boolean;
9
+ static examples: string[];
10
+ run(): Promise<AsaAdGroupDTO>;
11
+ }
@@ -0,0 +1,21 @@
1
+ import { Args, Command } from '@oclif/core';
2
+ import { createAsaClient } from '../../../lib/asa-client.js';
3
+ import { isValidUuid } from '../../../lib/flags.js';
4
+ import { printResponse } from '../../../lib/output.js';
5
+ export default class AsaAdGroupsGet extends Command {
6
+ static args = {
7
+ ad_group_id: Args.string({ description: 'Ad group ID (UUID)', required: true }),
8
+ };
9
+ static description = 'Show one ad group; read numbers with asa metrics';
10
+ static enableJsonFlag = true;
11
+ static examples = ['<%= config.bin %> asa ad-groups get 550e8400-e29b-41d4-a716-446655440000'];
12
+ async run() {
13
+ const { args } = await this.parse(AsaAdGroupsGet);
14
+ if (!isValidUuid(args.ad_group_id))
15
+ this.error('Invalid ad group ID format.', { exit: 2 });
16
+ const client = await createAsaClient(this.config);
17
+ const result = await client.get(`/ad-groups/${args.ad_group_id}`);
18
+ printResponse(result, this.log.bind(this));
19
+ return result;
20
+ }
21
+ }
@@ -0,0 +1,18 @@
1
+ import { Command } from '@oclif/core';
2
+ import type { AsaAdGroupDTO } from '../../../lib/asa-schemas.js';
3
+ import { type PaginatedResponse } from '../../../lib/flags.js';
4
+ export default class AsaAdGroupsList extends Command {
5
+ static description: string;
6
+ static enableJsonFlag: boolean;
7
+ static examples: string[];
8
+ static flags: {
9
+ status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ campaign: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ search: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ app: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ 'campaign-group': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
15
+ 'page-size': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
16
+ };
17
+ run(): Promise<PaginatedResponse<AsaAdGroupDTO>>;
18
+ }
@@ -0,0 +1,24 @@
1
+ import { Command } from '@oclif/core';
2
+ import { createAsaClient } from '../../../lib/asa-client.js';
3
+ import { asaPaginationFlags, campaignScopeFlags, scopeParams, statusFilter } from '../../../lib/asa-flags.js';
4
+ import { paginationParams } from '../../../lib/flags.js';
5
+ import { printList } from '../../../lib/output.js';
6
+ export default class AsaAdGroupsList extends Command {
7
+ static description = 'List ad groups; read numbers with asa metrics';
8
+ static enableJsonFlag = true;
9
+ static examples = [
10
+ '<%= config.bin %> asa ad-groups list',
11
+ '<%= config.bin %> asa ad-groups list --campaign CAMPAIGN_UUID',
12
+ ];
13
+ static flags = { ...asaPaginationFlags, ...campaignScopeFlags, ...statusFilter(['ENABLED', 'PAUSED']) };
14
+ async run() {
15
+ const { flags } = await this.parse(AsaAdGroupsList);
16
+ const client = await createAsaClient(this.config);
17
+ const result = await client.get('/ad-groups', {
18
+ ...paginationParams(flags),
19
+ ...scopeParams(flags),
20
+ });
21
+ printList(result.data, this.log.bind(this), result.meta.pagination);
22
+ return result;
23
+ }
24
+ }
@@ -0,0 +1,23 @@
1
+ import { Command } from '@oclif/core';
2
+ import type { AsaAdGroupMutationDTO } from '../../../lib/asa-schemas.js';
3
+ export default class AsaAdGroupsUpdate extends Command {
4
+ static args: {
5
+ ad_group_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
+ 'automated-keywords': import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ 'cpa-goal': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ 'default-bid': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ 'idempotency-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
+ 'end-time': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ 'start-time': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
+ currency: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
21
+ };
22
+ run(): Promise<AsaAdGroupMutationDTO>;
23
+ }
@@ -0,0 +1,62 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
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, scheduleFlags, startOfDayUtc } from '../../../lib/asa-flags.js';
5
+ import { isValidUuid } from '../../../lib/flags.js';
6
+ import { printResponse } from '../../../lib/output.js';
7
+ export default class AsaAdGroupsUpdate extends Command {
8
+ static args = {
9
+ ad_group_id: Args.string({ description: 'Ad group ID (UUID)', required: true }),
10
+ };
11
+ static description = 'Change an ad group: bid, CPA goal, status or schedule';
12
+ static enableJsonFlag = true;
13
+ static examples = [
14
+ '<%= config.bin %> asa ad-groups update UUID --default-bid 1.50',
15
+ '<%= config.bin %> asa ad-groups update UUID --status PAUSED',
16
+ ];
17
+ static flags = {
18
+ ...currencyFlag,
19
+ ...scheduleFlags,
20
+ ...confirmFlags,
21
+ ...idempotencyFlags,
22
+ 'automated-keywords': Flags.boolean({ allowNo: true, description: 'Let Apple add keywords automatically' }),
23
+ 'cpa-goal': moneyFlag('CPA goal'),
24
+ 'default-bid': moneyFlag('Default bid'),
25
+ name: Flags.string({ description: 'Ad group name' }),
26
+ status: Flags.string({ description: 'Ad group status', options: ['ENABLED', 'PAUSED'] }),
27
+ };
28
+ async run() {
29
+ const { args, flags } = await this.parse(AsaAdGroupsUpdate);
30
+ if (!isValidUuid(args.ad_group_id))
31
+ this.error('Invalid ad group ID format.', { exit: 2 });
32
+ const body = {};
33
+ if (flags.name !== undefined)
34
+ body.name = flags.name;
35
+ if (flags.status !== undefined)
36
+ body.status = flags.status;
37
+ if (flags['default-bid'] !== undefined)
38
+ body.default_bid_amount = money(flags['default-bid'], flags.currency);
39
+ if (flags['cpa-goal'] !== undefined)
40
+ body.cpa_goal = money(flags['cpa-goal'], flags.currency);
41
+ if (flags['automated-keywords'] !== undefined)
42
+ body.automated_keywords_opt_in = flags['automated-keywords'];
43
+ if (flags['start-time'] !== undefined)
44
+ body.start_time = startOfDayUtc(flags['start-time']);
45
+ if (flags['end-time'] !== undefined)
46
+ body.end_time = startOfDayUtc(flags['end-time']);
47
+ if (Object.keys(body).length === 0) {
48
+ this.error('Nothing to change. Pass at least one field, e.g. --status PAUSED.', { exit: 2 });
49
+ }
50
+ await confirmMutation(this, { body, method: 'PUT', path: `/ad-groups/${args.ad_group_id}/`, summary: 'Update ad group' }, flags.yes);
51
+ const client = await createAsaClient(this.config);
52
+ const { replayed, result } = await asaWrite(client, 'put', `/ad-groups/${args.ad_group_id}`, {
53
+ body,
54
+ idempotencyKey: flags['idempotency-key'],
55
+ });
56
+ noteReplay(replayed, this.log.bind(this));
57
+ if (result.ad_group && !replayed)
58
+ this.log('Ad group updated!');
59
+ printResponse(result, this.log.bind(this));
60
+ return result;
61
+ }
62
+ }
@@ -0,0 +1,16 @@
1
+ import { Command } from '@oclif/core';
2
+ import type { AsaAdMutationDTO } from '../../../lib/asa-schemas.js';
3
+ export default class AsaAdsCreate extends Command {
4
+ static description: string;
5
+ static enableJsonFlag: boolean;
6
+ static examples: string[];
7
+ static flags: {
8
+ 'ad-group': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ 'creative-id': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
10
+ name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ 'idempotency-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ };
15
+ run(): Promise<AsaAdMutationDTO>;
16
+ }
@@ -0,0 +1,41 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
3
+ import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
4
+ import { idempotencyFlags } from '../../../lib/asa-flags.js';
5
+ import { isValidUuid } from '../../../lib/flags.js';
6
+ import { printResponse } from '../../../lib/output.js';
7
+ export default class AsaAdsCreate extends Command {
8
+ static description = 'Create an ad from a creative in an ad group';
9
+ static enableJsonFlag = true;
10
+ static examples = ['<%= config.bin %> asa ads create --ad-group UUID --creative-id 4321 --name "Summer ad"'];
11
+ static flags = {
12
+ ...confirmFlags,
13
+ ...idempotencyFlags,
14
+ 'ad-group': Flags.string({ description: 'Ad group ID (UUID) — the campaign is resolved from it', required: true }),
15
+ 'creative-id': Flags.integer({ description: 'Apple creative ID from a product page or the default set', required: true }),
16
+ name: Flags.string({ description: 'Ad name', required: true }),
17
+ status: Flags.string({ description: 'Initial status', options: ['ENABLED', 'PAUSED'] }),
18
+ };
19
+ async run() {
20
+ const { flags } = await this.parse(AsaAdsCreate);
21
+ if (!isValidUuid(flags['ad-group']))
22
+ this.error('Invalid ad group ID format.', { exit: 2 });
23
+ const body = {
24
+ ad_group_id: flags['ad-group'],
25
+ creative_id: flags['creative-id'],
26
+ name: flags.name,
27
+ ...(flags.status === undefined ? {} : { status: flags.status }),
28
+ };
29
+ await confirmMutation(this, { body, method: 'POST', path: '/ads/', summary: `Create ad ${flags.name}` }, flags.yes);
30
+ const client = await createAsaClient(this.config);
31
+ const { replayed, result } = await asaWrite(client, 'post', '/ads', {
32
+ body,
33
+ idempotencyKey: flags['idempotency-key'],
34
+ });
35
+ noteReplay(replayed, this.log.bind(this));
36
+ if (result.ad && !replayed)
37
+ this.log('Ad created!');
38
+ printResponse(result, this.log.bind(this));
39
+ return result;
40
+ }
41
+ }
@@ -0,0 +1,11 @@
1
+ import { Command } from '@oclif/core';
2
+ import type { AsaAdDTO } from '../../../lib/asa-schemas.js';
3
+ export default class AsaAdsGet extends Command {
4
+ static args: {
5
+ ad_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
+ };
7
+ static description: string;
8
+ static enableJsonFlag: boolean;
9
+ static examples: string[];
10
+ run(): Promise<AsaAdDTO>;
11
+ }
@@ -0,0 +1,21 @@
1
+ import { Args, Command } from '@oclif/core';
2
+ import { createAsaClient } from '../../../lib/asa-client.js';
3
+ import { isValidUuid } from '../../../lib/flags.js';
4
+ import { printResponse } from '../../../lib/output.js';
5
+ export default class AsaAdsGet extends Command {
6
+ static args = {
7
+ ad_id: Args.string({ description: 'Ad ID (UUID)', required: true }),
8
+ };
9
+ static description = 'Show one ad and its serving state';
10
+ static enableJsonFlag = true;
11
+ static examples = ['<%= config.bin %> asa ads get 550e8400-e29b-41d4-a716-446655440000'];
12
+ async run() {
13
+ const { args } = await this.parse(AsaAdsGet);
14
+ if (!isValidUuid(args.ad_id))
15
+ this.error('Invalid ad ID format.', { exit: 2 });
16
+ const client = await createAsaClient(this.config);
17
+ const result = await client.get(`/ads/${args.ad_id}`);
18
+ printResponse(result, this.log.bind(this));
19
+ return result;
20
+ }
21
+ }
@@ -0,0 +1,18 @@
1
+ import { Command } from '@oclif/core';
2
+ import type { AsaAdDTO } from '../../../lib/asa-schemas.js';
3
+ import { type PaginatedResponse } from '../../../lib/flags.js';
4
+ export default class AsaAdsList extends Command {
5
+ static description: string;
6
+ static enableJsonFlag: boolean;
7
+ static examples: string[];
8
+ static flags: {
9
+ status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ search: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ 'ad-group': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ campaign: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ 'campaign-group': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
15
+ 'page-size': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
16
+ };
17
+ run(): Promise<PaginatedResponse<AsaAdDTO>>;
18
+ }
@@ -0,0 +1,24 @@
1
+ import { Command } from '@oclif/core';
2
+ import { createAsaClient } from '../../../lib/asa-client.js';
3
+ import { adScopeFlags, asaPaginationFlags, scopeParams, statusFilter } from '../../../lib/asa-flags.js';
4
+ import { paginationParams } from '../../../lib/flags.js';
5
+ import { printList } from '../../../lib/output.js';
6
+ export default class AsaAdsList extends Command {
7
+ static description = 'List ads; serving_state_reasons explains why an enabled ad is not running';
8
+ static enableJsonFlag = true;
9
+ static examples = [
10
+ '<%= config.bin %> asa ads list',
11
+ '<%= config.bin %> asa ads list --ad-group AD_GROUP_UUID --status ENABLED',
12
+ ];
13
+ static flags = { ...asaPaginationFlags, ...adScopeFlags, ...statusFilter(['ENABLED', 'PAUSED']) };
14
+ async run() {
15
+ const { flags } = await this.parse(AsaAdsList);
16
+ const client = await createAsaClient(this.config);
17
+ const result = await client.get('/ads', {
18
+ ...paginationParams(flags),
19
+ ...scopeParams(flags),
20
+ });
21
+ printList(result.data, this.log.bind(this), result.meta.pagination);
22
+ return result;
23
+ }
24
+ }
@@ -0,0 +1,17 @@
1
+ import { Command } from '@oclif/core';
2
+ import type { AsaAdMutationDTO } from '../../../lib/asa-schemas.js';
3
+ export default class AsaAdsUpdate extends Command {
4
+ static args: {
5
+ ad_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 | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ 'idempotency-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
+ };
16
+ run(): Promise<AsaAdMutationDTO>;
17
+ }
@@ -0,0 +1,44 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
3
+ import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
4
+ import { idempotencyFlags } from '../../../lib/asa-flags.js';
5
+ import { isValidUuid } from '../../../lib/flags.js';
6
+ import { printResponse } from '../../../lib/output.js';
7
+ export default class AsaAdsUpdate extends Command {
8
+ static args = {
9
+ ad_id: Args.string({ description: 'Ad ID (UUID)', required: true }),
10
+ };
11
+ static description = 'Rename an ad or pause it; the creative and ad group are fixed at creation';
12
+ static enableJsonFlag = true;
13
+ static examples = ['<%= config.bin %> asa ads update UUID --status PAUSED'];
14
+ static flags = {
15
+ ...confirmFlags,
16
+ ...idempotencyFlags,
17
+ name: Flags.string({ description: 'Ad name' }),
18
+ status: Flags.string({ description: 'Ad status', options: ['ENABLED', 'PAUSED'] }),
19
+ };
20
+ async run() {
21
+ const { args, flags } = await this.parse(AsaAdsUpdate);
22
+ if (!isValidUuid(args.ad_id))
23
+ this.error('Invalid ad ID format.', { exit: 2 });
24
+ const body = {};
25
+ if (flags.name !== undefined)
26
+ body.name = flags.name;
27
+ if (flags.status !== undefined)
28
+ body.status = flags.status;
29
+ if (Object.keys(body).length === 0) {
30
+ this.error('Nothing to change. Pass --name or --status.', { exit: 2 });
31
+ }
32
+ await confirmMutation(this, { body, method: 'PUT', path: `/ads/${args.ad_id}/`, summary: 'Update ad' }, flags.yes);
33
+ const client = await createAsaClient(this.config);
34
+ const { replayed, result } = await asaWrite(client, 'put', `/ads/${args.ad_id}`, {
35
+ body,
36
+ idempotencyKey: flags['idempotency-key'],
37
+ });
38
+ noteReplay(replayed, this.log.bind(this));
39
+ if (result.ad && !replayed)
40
+ this.log('Ad updated!');
41
+ printResponse(result, this.log.bind(this));
42
+ return result;
43
+ }
44
+ }