adapty 0.4.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.
- package/README.md +22 -0
- package/dist/commands/asa/campaigns/bulk-create.d.ts +25 -0
- package/dist/commands/asa/campaigns/bulk-create.js +177 -0
- package/dist/commands/asa/campaigns/bulk-status.d.ts +15 -0
- package/dist/commands/asa/campaigns/bulk-status.js +26 -0
- package/dist/lib/api-client.d.ts +1 -0
- package/dist/lib/api-client.js +4 -1
- package/dist/lib/asa-schemas.d.ts +40 -0
- package/oclif.manifest.json +243 -89
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -136,6 +136,28 @@ adapty asa ads create --ad-group UUID --creative-id 4321 --name "Summer ad"
|
|
|
136
136
|
adapty asa ads update AD_ID [--name "..."] [--status PAUSED]
|
|
137
137
|
```
|
|
138
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
|
+
|
|
139
161
|
Keywords are always applied as a batch, at most 100 per call, and a partial rejection is reported per item:
|
|
140
162
|
|
|
141
163
|
```sh
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import type { AsaBulkOperationStateDTO } from '../../../lib/asa-schemas.js';
|
|
3
|
+
export default class AsaCampaignsBulkCreate extends Command {
|
|
4
|
+
static description: string;
|
|
5
|
+
static enableJsonFlag: boolean;
|
|
6
|
+
static examples: string[];
|
|
7
|
+
static flags: {
|
|
8
|
+
file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
'from-file': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'org-id': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'poll-interval': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
preview: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
timeout: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
wait: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
'idempotency-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
};
|
|
18
|
+
run(): Promise<AsaBulkOperationStateDTO | Record<string, unknown>>;
|
|
19
|
+
private convertTemplate;
|
|
20
|
+
private countStructure;
|
|
21
|
+
private formatIssue;
|
|
22
|
+
private readStructure;
|
|
23
|
+
private report;
|
|
24
|
+
private waitForCompletion;
|
|
25
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { basename } from 'node:path';
|
|
4
|
+
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
5
|
+
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
6
|
+
import { idempotencyFlags } from '../../../lib/asa-flags.js';
|
|
7
|
+
import { printResponse } from '../../../lib/output.js';
|
|
8
|
+
const TERMINAL_STATUSES = new Set(['failed', 'partial', 'success']);
|
|
9
|
+
export default class AsaCampaignsBulkCreate extends Command {
|
|
10
|
+
static description = 'Create a whole campaign structure (campaigns → ad groups → keywords/negative keywords/ads) in one bulk operation';
|
|
11
|
+
static enableJsonFlag = true;
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> asa campaigns bulk-create --file structure.json',
|
|
14
|
+
'cat structure.json | <%= config.bin %> asa campaigns bulk-create --file -',
|
|
15
|
+
'<%= config.bin %> asa campaigns bulk-create --from-file Campaign_And_Adgroup_Template.xlsx --org-id 1234567',
|
|
16
|
+
'<%= config.bin %> asa campaigns bulk-create --from-file keywords_template.csv --org-id 1234567 --preview',
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
...confirmFlags,
|
|
20
|
+
...idempotencyFlags,
|
|
21
|
+
file: Flags.string({
|
|
22
|
+
description: 'JSON file with the campaign structure (the POST /bulk-operations/ body); use "-" for stdin',
|
|
23
|
+
exactlyOne: ['file', 'from-file'],
|
|
24
|
+
}),
|
|
25
|
+
'from-file': Flags.string({
|
|
26
|
+
description: 'Apple Ads bulk template (Campaign_And_Adgroup_Template.xlsx or keywords .csv) to convert and submit',
|
|
27
|
+
}),
|
|
28
|
+
'org-id': Flags.integer({
|
|
29
|
+
description: 'Apple org id (campaign_group_id) the converted template will target; required with --from-file',
|
|
30
|
+
}),
|
|
31
|
+
'poll-interval': Flags.integer({ default: 5, description: 'Seconds between progress polls' }),
|
|
32
|
+
preview: Flags.boolean({
|
|
33
|
+
description: 'With --from-file: print the converted request and stop, nothing is created',
|
|
34
|
+
}),
|
|
35
|
+
timeout: Flags.integer({ default: 900, description: 'Max seconds to wait for the operation to finish' }),
|
|
36
|
+
wait: Flags.boolean({
|
|
37
|
+
allowNo: true,
|
|
38
|
+
default: true,
|
|
39
|
+
description: 'Poll the operation until it finishes; --no-wait returns the operation id immediately',
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
async run() {
|
|
43
|
+
const { flags } = await this.parse(AsaCampaignsBulkCreate);
|
|
44
|
+
const client = await createAsaClient(this.config);
|
|
45
|
+
const body = flags['from-file'] ? await this.convertTemplate(client, flags) : await this.readStructure(flags.file);
|
|
46
|
+
if (flags.preview) {
|
|
47
|
+
this.log(JSON.stringify(body, null, 2));
|
|
48
|
+
return body;
|
|
49
|
+
}
|
|
50
|
+
const counts = this.countStructure(body);
|
|
51
|
+
const summary = `Bulk-create: ${counts.campaigns} campaign node(s), ${counts.adGroups} ad group(s), ` +
|
|
52
|
+
`${counts.keywords} keyword(s), ${counts.negativeKeywords} negative keyword(s), ${counts.ads} ad(s)`;
|
|
53
|
+
await confirmMutation(this, { body, method: 'POST', path: '/bulk-operations/', summary }, flags.yes);
|
|
54
|
+
const { replayed, result } = await asaWrite(client, 'post', '/bulk-operations', {
|
|
55
|
+
body,
|
|
56
|
+
idempotencyKey: flags['idempotency-key'],
|
|
57
|
+
});
|
|
58
|
+
noteReplay(replayed, this.log.bind(this));
|
|
59
|
+
this.log(`Operation accepted: ${result.operation_id}`);
|
|
60
|
+
if (!flags.wait) {
|
|
61
|
+
this.log(`Check progress with: adapty asa campaigns bulk-status ${result.operation_id}`);
|
|
62
|
+
return { operation_id: result.operation_id };
|
|
63
|
+
}
|
|
64
|
+
const state = await this.waitForCompletion(client, result.operation_id, flags['poll-interval'], flags.timeout);
|
|
65
|
+
this.report(state);
|
|
66
|
+
return state;
|
|
67
|
+
}
|
|
68
|
+
async convertTemplate(client, flags) {
|
|
69
|
+
if (flags['org-id'] === undefined)
|
|
70
|
+
this.error('--org-id is required with --from-file.', { exit: 2 });
|
|
71
|
+
const path = flags['from-file'];
|
|
72
|
+
let content;
|
|
73
|
+
try {
|
|
74
|
+
content = await readFile(path);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
this.error(`Cannot read file: ${path}`, { exit: 2 });
|
|
78
|
+
}
|
|
79
|
+
const form = new FormData();
|
|
80
|
+
form.append('file', new Blob([new Uint8Array(content)]), basename(path));
|
|
81
|
+
form.append('campaign_group_id', String(flags['org-id']));
|
|
82
|
+
const result = await client.postForm('/bulk-operations/convert', form);
|
|
83
|
+
for (const warning of result.warnings)
|
|
84
|
+
this.warn(this.formatIssue(warning));
|
|
85
|
+
if (result.errors.length > 0 || result.request === null) {
|
|
86
|
+
for (const issue of result.errors)
|
|
87
|
+
this.log(this.formatIssue(issue));
|
|
88
|
+
this.error(`Template conversion failed with ${result.errors.length} error(s); nothing was created.`, { exit: 2 });
|
|
89
|
+
}
|
|
90
|
+
return result.request;
|
|
91
|
+
}
|
|
92
|
+
countStructure(body) {
|
|
93
|
+
const counts = { adGroups: 0, ads: 0, campaigns: 0, keywords: 0, negativeKeywords: 0 };
|
|
94
|
+
const campaigns = Array.isArray(body.campaigns) ? body.campaigns : [];
|
|
95
|
+
counts.campaigns = campaigns.length;
|
|
96
|
+
for (const campaign of campaigns) {
|
|
97
|
+
if (typeof campaign !== 'object' || campaign === null)
|
|
98
|
+
continue;
|
|
99
|
+
const campaignNode = campaign;
|
|
100
|
+
counts.negativeKeywords += Array.isArray(campaignNode.negative_keywords) ? campaignNode.negative_keywords.length : 0;
|
|
101
|
+
const adGroups = Array.isArray(campaignNode.ad_groups) ? campaignNode.ad_groups : [];
|
|
102
|
+
counts.adGroups += adGroups.length;
|
|
103
|
+
for (const adGroup of adGroups) {
|
|
104
|
+
if (typeof adGroup !== 'object' || adGroup === null)
|
|
105
|
+
continue;
|
|
106
|
+
const adGroupNode = adGroup;
|
|
107
|
+
counts.keywords += Array.isArray(adGroupNode.keywords) ? adGroupNode.keywords.length : 0;
|
|
108
|
+
counts.negativeKeywords += Array.isArray(adGroupNode.negative_keywords) ? adGroupNode.negative_keywords.length : 0;
|
|
109
|
+
counts.ads += Array.isArray(adGroupNode.ads) ? adGroupNode.ads.length : 0;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return counts;
|
|
113
|
+
}
|
|
114
|
+
formatIssue(issue) {
|
|
115
|
+
const place = [issue.sheet, issue.row === null ? undefined : `row ${issue.row}`, issue.column ?? undefined]
|
|
116
|
+
.filter(Boolean)
|
|
117
|
+
.join(', ');
|
|
118
|
+
return place ? `${place}: ${issue.message}` : issue.message;
|
|
119
|
+
}
|
|
120
|
+
async readStructure(source) {
|
|
121
|
+
let raw;
|
|
122
|
+
if (source === '-') {
|
|
123
|
+
const chunks = [];
|
|
124
|
+
for await (const chunk of process.stdin)
|
|
125
|
+
chunks.push(chunk);
|
|
126
|
+
raw = Buffer.concat(chunks).toString('utf8');
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
try {
|
|
130
|
+
raw = await readFile(source, 'utf8');
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
this.error(`Cannot read file: ${source}`, { exit: 2 });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
return JSON.parse(raw);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
this.error('The structure is not valid JSON.', { exit: 2 });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
report(state) {
|
|
144
|
+
const { counts } = state;
|
|
145
|
+
this.log(`Finished: ${state.status} — ${counts.applied}/${counts.total} applied, ${counts.failed} failed`);
|
|
146
|
+
const failedObjects = state.objects.filter((object) => object.status === 'failed');
|
|
147
|
+
for (const object of failedObjects) {
|
|
148
|
+
printResponse(object, this.log.bind(this));
|
|
149
|
+
this.log('---');
|
|
150
|
+
}
|
|
151
|
+
if (state.status === 'failed')
|
|
152
|
+
this.error('Bulk operation failed.', { exit: 1 });
|
|
153
|
+
if (state.status === 'partial')
|
|
154
|
+
this.warn('Some objects were not created — see the failures above.');
|
|
155
|
+
}
|
|
156
|
+
async waitForCompletion(client, operationId, pollIntervalSeconds, timeoutSeconds) {
|
|
157
|
+
const startedAt = Date.now();
|
|
158
|
+
let lastProgress = '';
|
|
159
|
+
for (;;) {
|
|
160
|
+
const state = await client.get(`/bulk-operations/${operationId}`);
|
|
161
|
+
const progress = `${state.status}: ${state.counts.applied}/${state.counts.total} applied, ${state.counts.failed} failed`;
|
|
162
|
+
if (progress !== lastProgress) {
|
|
163
|
+
this.log(progress);
|
|
164
|
+
lastProgress = progress;
|
|
165
|
+
}
|
|
166
|
+
if (TERMINAL_STATUSES.has(state.status))
|
|
167
|
+
return state;
|
|
168
|
+
if ((Date.now() - startedAt) / 1000 >= timeoutSeconds) {
|
|
169
|
+
this.warn(`Still ${state.status} after ${timeoutSeconds}s — check later with: adapty asa campaigns bulk-status ${operationId}`);
|
|
170
|
+
return state;
|
|
171
|
+
}
|
|
172
|
+
await new Promise((resolve) => {
|
|
173
|
+
setTimeout(resolve, pollIntervalSeconds * 1000);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import type { AsaBulkOperationStateDTO } from '../../../lib/asa-schemas.js';
|
|
3
|
+
export default class AsaCampaignsBulkStatus extends Command {
|
|
4
|
+
static args: {
|
|
5
|
+
'operation-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
|
+
page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'page-size': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<AsaBulkOperationStateDTO>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Args, Command } from '@oclif/core';
|
|
2
|
+
import { createAsaClient } from '../../../lib/asa-client.js';
|
|
3
|
+
import { asaPaginationFlags } from '../../../lib/asa-flags.js';
|
|
4
|
+
import { isValidUuid, paginationParams } from '../../../lib/flags.js';
|
|
5
|
+
import { printResponse } from '../../../lib/output.js';
|
|
6
|
+
export default class AsaCampaignsBulkStatus extends Command {
|
|
7
|
+
static args = {
|
|
8
|
+
'operation-id': Args.string({ description: 'Bulk operation ID returned by bulk-create', required: true }),
|
|
9
|
+
};
|
|
10
|
+
static description = 'Progress board of one bulk operation: counts, pipelines and the per-object log';
|
|
11
|
+
static enableJsonFlag = true;
|
|
12
|
+
static examples = ['<%= config.bin %> asa campaigns bulk-status 660e8400-e29b-41d4-a716-446655440001'];
|
|
13
|
+
static flags = {
|
|
14
|
+
...asaPaginationFlags,
|
|
15
|
+
};
|
|
16
|
+
async run() {
|
|
17
|
+
const { args, flags } = await this.parse(AsaCampaignsBulkStatus);
|
|
18
|
+
const operationId = args['operation-id'];
|
|
19
|
+
if (!isValidUuid(operationId))
|
|
20
|
+
this.error('Invalid operation ID format.', { exit: 2 });
|
|
21
|
+
const client = await createAsaClient(this.config);
|
|
22
|
+
const state = await client.get(`/bulk-operations/${operationId}`, paginationParams(flags));
|
|
23
|
+
printResponse(state, this.log.bind(this));
|
|
24
|
+
return state;
|
|
25
|
+
}
|
|
26
|
+
}
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export declare class ApiClient {
|
|
|
20
20
|
constructor(opts?: ApiClientOptions);
|
|
21
21
|
get<T = unknown>(path: string, params?: QueryParams): Promise<T>;
|
|
22
22
|
post<T = unknown>(path: string, body?: unknown, params?: QueryParams, opts?: RequestOptions): Promise<T>;
|
|
23
|
+
postForm<T = unknown>(path: string, form: FormData, params?: QueryParams, opts?: RequestOptions): Promise<T>;
|
|
23
24
|
put<T = unknown>(path: string, body?: unknown, params?: QueryParams, opts?: RequestOptions): Promise<T>;
|
|
24
25
|
private buildUrl;
|
|
25
26
|
private isRetryableRateLimit;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -29,6 +29,9 @@ export class ApiClient {
|
|
|
29
29
|
method: 'POST',
|
|
30
30
|
}, opts);
|
|
31
31
|
}
|
|
32
|
+
async postForm(path, form, params, opts) {
|
|
33
|
+
return this.request(this.buildUrl(path, params), { body: form, method: 'POST' }, opts);
|
|
34
|
+
}
|
|
32
35
|
async put(path, body, params, opts) {
|
|
33
36
|
return this.request(this.buildUrl(path, params), {
|
|
34
37
|
body: body ? JSON.stringify(body) : undefined,
|
|
@@ -60,7 +63,7 @@ export class ApiClient {
|
|
|
60
63
|
const headers = {
|
|
61
64
|
'User-Agent': this.userAgent,
|
|
62
65
|
};
|
|
63
|
-
if (init.body) {
|
|
66
|
+
if (init.body && !(init.body instanceof FormData)) {
|
|
64
67
|
headers['Content-Type'] = 'application/json';
|
|
65
68
|
}
|
|
66
69
|
if (this.token) {
|
|
@@ -285,3 +285,43 @@ export interface AsaNegativeKeywordMutationDTO {
|
|
|
285
285
|
export interface AsaAutomationMutationDTO {
|
|
286
286
|
automation: AsaAutomationDTO | null;
|
|
287
287
|
}
|
|
288
|
+
export type AsaBulkOperationStatus = 'failed' | 'partial' | 'pending' | 'running' | 'success';
|
|
289
|
+
export interface AsaBulkOperationAcceptedDTO {
|
|
290
|
+
operation_id: string;
|
|
291
|
+
}
|
|
292
|
+
export interface AsaBulkOperationCountsDTO {
|
|
293
|
+
applied: number;
|
|
294
|
+
failed: number;
|
|
295
|
+
pending: number;
|
|
296
|
+
total: number;
|
|
297
|
+
}
|
|
298
|
+
export interface AsaBulkOperationObjectDTO {
|
|
299
|
+
apple_error_code: null | string;
|
|
300
|
+
apple_error_message: null | string;
|
|
301
|
+
entity_kind: string;
|
|
302
|
+
external_id: null | number;
|
|
303
|
+
item_ref: string;
|
|
304
|
+
status: string;
|
|
305
|
+
step_type: string;
|
|
306
|
+
}
|
|
307
|
+
export interface AsaBulkOperationStateDTO {
|
|
308
|
+
counts: AsaBulkOperationCountsDTO;
|
|
309
|
+
created_at: string;
|
|
310
|
+
finished_at: null | string;
|
|
311
|
+
objects: AsaBulkOperationObjectDTO[];
|
|
312
|
+
operation_id: string;
|
|
313
|
+
pipelines: Record<string, unknown>[];
|
|
314
|
+
started_at: null | string;
|
|
315
|
+
status: AsaBulkOperationStatus;
|
|
316
|
+
}
|
|
317
|
+
export interface AsaTemplateIssueDTO {
|
|
318
|
+
column: null | string;
|
|
319
|
+
message: string;
|
|
320
|
+
row: null | number;
|
|
321
|
+
sheet: null | string;
|
|
322
|
+
}
|
|
323
|
+
export interface AsaBulkConvertResultDTO {
|
|
324
|
+
errors: AsaTemplateIssueDTO[];
|
|
325
|
+
request: null | Record<string, unknown>;
|
|
326
|
+
warnings: AsaTemplateIssueDTO[];
|
|
327
|
+
}
|
package/oclif.manifest.json
CHANGED
|
@@ -2612,6 +2612,160 @@
|
|
|
2612
2612
|
"update.js"
|
|
2613
2613
|
]
|
|
2614
2614
|
},
|
|
2615
|
+
"asa:campaigns:bulk-create": {
|
|
2616
|
+
"aliases": [],
|
|
2617
|
+
"args": {},
|
|
2618
|
+
"description": "Create a whole campaign structure (campaigns → ad groups → keywords/negative keywords/ads) in one bulk operation",
|
|
2619
|
+
"examples": [
|
|
2620
|
+
"<%= config.bin %> asa campaigns bulk-create --file structure.json",
|
|
2621
|
+
"cat structure.json | <%= config.bin %> asa campaigns bulk-create --file -",
|
|
2622
|
+
"<%= config.bin %> asa campaigns bulk-create --from-file Campaign_And_Adgroup_Template.xlsx --org-id 1234567",
|
|
2623
|
+
"<%= config.bin %> asa campaigns bulk-create --from-file keywords_template.csv --org-id 1234567 --preview"
|
|
2624
|
+
],
|
|
2625
|
+
"flags": {
|
|
2626
|
+
"json": {
|
|
2627
|
+
"description": "Format output as json.",
|
|
2628
|
+
"helpGroup": "GLOBAL",
|
|
2629
|
+
"name": "json",
|
|
2630
|
+
"allowNo": false,
|
|
2631
|
+
"type": "boolean"
|
|
2632
|
+
},
|
|
2633
|
+
"yes": {
|
|
2634
|
+
"char": "y",
|
|
2635
|
+
"description": "Apply without asking; required when the output is piped or --json is used",
|
|
2636
|
+
"name": "yes",
|
|
2637
|
+
"allowNo": false,
|
|
2638
|
+
"type": "boolean"
|
|
2639
|
+
},
|
|
2640
|
+
"idempotency-key": {
|
|
2641
|
+
"description": "Idempotency key for this write; re-running with the same key replays the stored result instead of applying twice",
|
|
2642
|
+
"name": "idempotency-key",
|
|
2643
|
+
"hasDynamicHelp": false,
|
|
2644
|
+
"multiple": false,
|
|
2645
|
+
"type": "option"
|
|
2646
|
+
},
|
|
2647
|
+
"file": {
|
|
2648
|
+
"description": "JSON file with the campaign structure (the POST /bulk-operations/ body); use \"-\" for stdin",
|
|
2649
|
+
"name": "file",
|
|
2650
|
+
"hasDynamicHelp": false,
|
|
2651
|
+
"multiple": false,
|
|
2652
|
+
"type": "option"
|
|
2653
|
+
},
|
|
2654
|
+
"from-file": {
|
|
2655
|
+
"description": "Apple Ads bulk template (Campaign_And_Adgroup_Template.xlsx or keywords .csv) to convert and submit",
|
|
2656
|
+
"name": "from-file",
|
|
2657
|
+
"hasDynamicHelp": false,
|
|
2658
|
+
"multiple": false,
|
|
2659
|
+
"type": "option"
|
|
2660
|
+
},
|
|
2661
|
+
"org-id": {
|
|
2662
|
+
"description": "Apple org id (campaign_group_id) the converted template will target; required with --from-file",
|
|
2663
|
+
"name": "org-id",
|
|
2664
|
+
"hasDynamicHelp": false,
|
|
2665
|
+
"multiple": false,
|
|
2666
|
+
"type": "option"
|
|
2667
|
+
},
|
|
2668
|
+
"poll-interval": {
|
|
2669
|
+
"description": "Seconds between progress polls",
|
|
2670
|
+
"name": "poll-interval",
|
|
2671
|
+
"default": 5,
|
|
2672
|
+
"hasDynamicHelp": false,
|
|
2673
|
+
"multiple": false,
|
|
2674
|
+
"type": "option"
|
|
2675
|
+
},
|
|
2676
|
+
"preview": {
|
|
2677
|
+
"description": "With --from-file: print the converted request and stop, nothing is created",
|
|
2678
|
+
"name": "preview",
|
|
2679
|
+
"allowNo": false,
|
|
2680
|
+
"type": "boolean"
|
|
2681
|
+
},
|
|
2682
|
+
"timeout": {
|
|
2683
|
+
"description": "Max seconds to wait for the operation to finish",
|
|
2684
|
+
"name": "timeout",
|
|
2685
|
+
"default": 900,
|
|
2686
|
+
"hasDynamicHelp": false,
|
|
2687
|
+
"multiple": false,
|
|
2688
|
+
"type": "option"
|
|
2689
|
+
},
|
|
2690
|
+
"wait": {
|
|
2691
|
+
"description": "Poll the operation until it finishes; --no-wait returns the operation id immediately",
|
|
2692
|
+
"name": "wait",
|
|
2693
|
+
"allowNo": true,
|
|
2694
|
+
"type": "boolean"
|
|
2695
|
+
}
|
|
2696
|
+
},
|
|
2697
|
+
"hasDynamicHelp": false,
|
|
2698
|
+
"hiddenAliases": [],
|
|
2699
|
+
"id": "asa:campaigns:bulk-create",
|
|
2700
|
+
"pluginAlias": "adapty",
|
|
2701
|
+
"pluginName": "adapty",
|
|
2702
|
+
"pluginType": "core",
|
|
2703
|
+
"strict": true,
|
|
2704
|
+
"enableJsonFlag": true,
|
|
2705
|
+
"isESM": true,
|
|
2706
|
+
"relativePath": [
|
|
2707
|
+
"dist",
|
|
2708
|
+
"commands",
|
|
2709
|
+
"asa",
|
|
2710
|
+
"campaigns",
|
|
2711
|
+
"bulk-create.js"
|
|
2712
|
+
]
|
|
2713
|
+
},
|
|
2714
|
+
"asa:campaigns:bulk-status": {
|
|
2715
|
+
"aliases": [],
|
|
2716
|
+
"args": {
|
|
2717
|
+
"operation-id": {
|
|
2718
|
+
"description": "Bulk operation ID returned by bulk-create",
|
|
2719
|
+
"name": "operation-id",
|
|
2720
|
+
"required": true
|
|
2721
|
+
}
|
|
2722
|
+
},
|
|
2723
|
+
"description": "Progress board of one bulk operation: counts, pipelines and the per-object log",
|
|
2724
|
+
"examples": [
|
|
2725
|
+
"<%= config.bin %> asa campaigns bulk-status 660e8400-e29b-41d4-a716-446655440001"
|
|
2726
|
+
],
|
|
2727
|
+
"flags": {
|
|
2728
|
+
"json": {
|
|
2729
|
+
"description": "Format output as json.",
|
|
2730
|
+
"helpGroup": "GLOBAL",
|
|
2731
|
+
"name": "json",
|
|
2732
|
+
"allowNo": false,
|
|
2733
|
+
"type": "boolean"
|
|
2734
|
+
},
|
|
2735
|
+
"page": {
|
|
2736
|
+
"description": "Page number",
|
|
2737
|
+
"name": "page",
|
|
2738
|
+
"default": 1,
|
|
2739
|
+
"hasDynamicHelp": false,
|
|
2740
|
+
"multiple": false,
|
|
2741
|
+
"type": "option"
|
|
2742
|
+
},
|
|
2743
|
+
"page-size": {
|
|
2744
|
+
"description": "Items per page (max 1000); prefer one big page over a pagination loop",
|
|
2745
|
+
"name": "page-size",
|
|
2746
|
+
"default": 100,
|
|
2747
|
+
"hasDynamicHelp": false,
|
|
2748
|
+
"multiple": false,
|
|
2749
|
+
"type": "option"
|
|
2750
|
+
}
|
|
2751
|
+
},
|
|
2752
|
+
"hasDynamicHelp": false,
|
|
2753
|
+
"hiddenAliases": [],
|
|
2754
|
+
"id": "asa:campaigns:bulk-status",
|
|
2755
|
+
"pluginAlias": "adapty",
|
|
2756
|
+
"pluginName": "adapty",
|
|
2757
|
+
"pluginType": "core",
|
|
2758
|
+
"strict": true,
|
|
2759
|
+
"enableJsonFlag": true,
|
|
2760
|
+
"isESM": true,
|
|
2761
|
+
"relativePath": [
|
|
2762
|
+
"dist",
|
|
2763
|
+
"commands",
|
|
2764
|
+
"asa",
|
|
2765
|
+
"campaigns",
|
|
2766
|
+
"bulk-status.js"
|
|
2767
|
+
]
|
|
2768
|
+
},
|
|
2615
2769
|
"asa:campaigns:create": {
|
|
2616
2770
|
"aliases": [],
|
|
2617
2771
|
"args": {},
|
|
@@ -3906,13 +4060,13 @@
|
|
|
3906
4060
|
"list.js"
|
|
3907
4061
|
]
|
|
3908
4062
|
},
|
|
3909
|
-
"asa:
|
|
4063
|
+
"asa:product-pages:list": {
|
|
3910
4064
|
"aliases": [],
|
|
3911
4065
|
"args": {},
|
|
3912
|
-
"description": "List
|
|
4066
|
+
"description": "List custom product pages (read-only; authoring stays in App Store Connect)",
|
|
3913
4067
|
"examples": [
|
|
3914
|
-
"<%= config.bin %> asa
|
|
3915
|
-
"<%= config.bin %> asa
|
|
4068
|
+
"<%= config.bin %> asa product-pages list",
|
|
4069
|
+
"<%= config.bin %> asa product-pages list --app APP_UUID"
|
|
3916
4070
|
],
|
|
3917
4071
|
"flags": {
|
|
3918
4072
|
"json": {
|
|
@@ -3938,20 +4092,6 @@
|
|
|
3938
4092
|
"multiple": false,
|
|
3939
4093
|
"type": "option"
|
|
3940
4094
|
},
|
|
3941
|
-
"date-from": {
|
|
3942
|
-
"description": "Start of the reporting period (YYYY-MM-DD), defaults to today",
|
|
3943
|
-
"name": "date-from",
|
|
3944
|
-
"hasDynamicHelp": false,
|
|
3945
|
-
"multiple": false,
|
|
3946
|
-
"type": "option"
|
|
3947
|
-
},
|
|
3948
|
-
"date-to": {
|
|
3949
|
-
"description": "End of the reporting period (YYYY-MM-DD), defaults to today",
|
|
3950
|
-
"name": "date-to",
|
|
3951
|
-
"hasDynamicHelp": false,
|
|
3952
|
-
"multiple": false,
|
|
3953
|
-
"type": "option"
|
|
3954
|
-
},
|
|
3955
4095
|
"app": {
|
|
3956
4096
|
"description": "Keep only rows in this app; repeatable",
|
|
3957
4097
|
"name": "app",
|
|
@@ -3965,32 +4105,11 @@
|
|
|
3965
4105
|
"hasDynamicHelp": false,
|
|
3966
4106
|
"multiple": true,
|
|
3967
4107
|
"type": "option"
|
|
3968
|
-
},
|
|
3969
|
-
"search": {
|
|
3970
|
-
"description": "Case-insensitive substring match on the name",
|
|
3971
|
-
"name": "search",
|
|
3972
|
-
"hasDynamicHelp": false,
|
|
3973
|
-
"multiple": false,
|
|
3974
|
-
"type": "option"
|
|
3975
|
-
},
|
|
3976
|
-
"campaign": {
|
|
3977
|
-
"description": "Keep only rows in this campaign; repeatable",
|
|
3978
|
-
"name": "campaign",
|
|
3979
|
-
"hasDynamicHelp": false,
|
|
3980
|
-
"multiple": true,
|
|
3981
|
-
"type": "option"
|
|
3982
|
-
},
|
|
3983
|
-
"ad-group": {
|
|
3984
|
-
"description": "Keep only rows in this ad group; repeatable",
|
|
3985
|
-
"name": "ad-group",
|
|
3986
|
-
"hasDynamicHelp": false,
|
|
3987
|
-
"multiple": true,
|
|
3988
|
-
"type": "option"
|
|
3989
4108
|
}
|
|
3990
4109
|
},
|
|
3991
4110
|
"hasDynamicHelp": false,
|
|
3992
4111
|
"hiddenAliases": [],
|
|
3993
|
-
"id": "asa:
|
|
4112
|
+
"id": "asa:product-pages:list",
|
|
3994
4113
|
"pluginAlias": "adapty",
|
|
3995
4114
|
"pluginName": "adapty",
|
|
3996
4115
|
"pluginType": "core",
|
|
@@ -4001,17 +4120,17 @@
|
|
|
4001
4120
|
"dist",
|
|
4002
4121
|
"commands",
|
|
4003
4122
|
"asa",
|
|
4004
|
-
"
|
|
4123
|
+
"product-pages",
|
|
4005
4124
|
"list.js"
|
|
4006
4125
|
]
|
|
4007
4126
|
},
|
|
4008
|
-
"asa:product-pages:
|
|
4127
|
+
"asa:product-pages:sync": {
|
|
4009
4128
|
"aliases": [],
|
|
4010
4129
|
"args": {},
|
|
4011
|
-
"description": "
|
|
4130
|
+
"description": "Refresh custom product pages from Apple",
|
|
4012
4131
|
"examples": [
|
|
4013
|
-
"<%= config.bin %> asa product-pages
|
|
4014
|
-
"<%= config.bin %> asa product-pages
|
|
4132
|
+
"<%= config.bin %> asa product-pages sync",
|
|
4133
|
+
"<%= config.bin %> asa product-pages sync --adam-id 123456"
|
|
4015
4134
|
],
|
|
4016
4135
|
"flags": {
|
|
4017
4136
|
"json": {
|
|
@@ -4021,40 +4140,31 @@
|
|
|
4021
4140
|
"allowNo": false,
|
|
4022
4141
|
"type": "boolean"
|
|
4023
4142
|
},
|
|
4024
|
-
"
|
|
4025
|
-
"
|
|
4026
|
-
"
|
|
4027
|
-
"
|
|
4028
|
-
"
|
|
4029
|
-
"
|
|
4030
|
-
"type": "option"
|
|
4143
|
+
"yes": {
|
|
4144
|
+
"char": "y",
|
|
4145
|
+
"description": "Apply without asking; required when the output is piped or --json is used",
|
|
4146
|
+
"name": "yes",
|
|
4147
|
+
"allowNo": false,
|
|
4148
|
+
"type": "boolean"
|
|
4031
4149
|
},
|
|
4032
|
-
"
|
|
4033
|
-
"description": "
|
|
4034
|
-
"name": "
|
|
4035
|
-
"default": 100,
|
|
4150
|
+
"idempotency-key": {
|
|
4151
|
+
"description": "Idempotency key for this write; re-running with the same key replays the stored result instead of applying twice",
|
|
4152
|
+
"name": "idempotency-key",
|
|
4036
4153
|
"hasDynamicHelp": false,
|
|
4037
4154
|
"multiple": false,
|
|
4038
4155
|
"type": "option"
|
|
4039
4156
|
},
|
|
4040
|
-
"
|
|
4041
|
-
"description": "
|
|
4042
|
-
"name": "
|
|
4043
|
-
"hasDynamicHelp": false,
|
|
4044
|
-
"multiple": true,
|
|
4045
|
-
"type": "option"
|
|
4046
|
-
},
|
|
4047
|
-
"campaign-group": {
|
|
4048
|
-
"description": "Keep only rows in this campaign group; repeatable",
|
|
4049
|
-
"name": "campaign-group",
|
|
4157
|
+
"adam-id": {
|
|
4158
|
+
"description": "Limit the refresh to one app; omit to cover every app",
|
|
4159
|
+
"name": "adam-id",
|
|
4050
4160
|
"hasDynamicHelp": false,
|
|
4051
|
-
"multiple":
|
|
4161
|
+
"multiple": false,
|
|
4052
4162
|
"type": "option"
|
|
4053
4163
|
}
|
|
4054
4164
|
},
|
|
4055
4165
|
"hasDynamicHelp": false,
|
|
4056
4166
|
"hiddenAliases": [],
|
|
4057
|
-
"id": "asa:product-pages:
|
|
4167
|
+
"id": "asa:product-pages:sync",
|
|
4058
4168
|
"pluginAlias": "adapty",
|
|
4059
4169
|
"pluginName": "adapty",
|
|
4060
4170
|
"pluginType": "core",
|
|
@@ -4066,16 +4176,16 @@
|
|
|
4066
4176
|
"commands",
|
|
4067
4177
|
"asa",
|
|
4068
4178
|
"product-pages",
|
|
4069
|
-
"
|
|
4179
|
+
"sync.js"
|
|
4070
4180
|
]
|
|
4071
4181
|
},
|
|
4072
|
-
"asa:
|
|
4182
|
+
"asa:search-terms:list": {
|
|
4073
4183
|
"aliases": [],
|
|
4074
4184
|
"args": {},
|
|
4075
|
-
"description": "
|
|
4185
|
+
"description": "List the search terms your ads matched, with their metrics",
|
|
4076
4186
|
"examples": [
|
|
4077
|
-
"<%= config.bin %> asa
|
|
4078
|
-
"<%= config.bin %> asa
|
|
4187
|
+
"<%= config.bin %> asa search-terms list --date-from 2026-07-01 --date-to 2026-07-31",
|
|
4188
|
+
"<%= config.bin %> asa search-terms list --ad-group AD_GROUP_UUID"
|
|
4079
4189
|
],
|
|
4080
4190
|
"flags": {
|
|
4081
4191
|
"json": {
|
|
@@ -4085,31 +4195,75 @@
|
|
|
4085
4195
|
"allowNo": false,
|
|
4086
4196
|
"type": "boolean"
|
|
4087
4197
|
},
|
|
4088
|
-
"
|
|
4089
|
-
"
|
|
4090
|
-
"
|
|
4091
|
-
"
|
|
4092
|
-
"
|
|
4093
|
-
"
|
|
4198
|
+
"page": {
|
|
4199
|
+
"description": "Page number",
|
|
4200
|
+
"name": "page",
|
|
4201
|
+
"default": 1,
|
|
4202
|
+
"hasDynamicHelp": false,
|
|
4203
|
+
"multiple": false,
|
|
4204
|
+
"type": "option"
|
|
4094
4205
|
},
|
|
4095
|
-
"
|
|
4096
|
-
"description": "
|
|
4097
|
-
"name": "
|
|
4206
|
+
"page-size": {
|
|
4207
|
+
"description": "Items per page (max 1000); prefer one big page over a pagination loop",
|
|
4208
|
+
"name": "page-size",
|
|
4209
|
+
"default": 100,
|
|
4098
4210
|
"hasDynamicHelp": false,
|
|
4099
4211
|
"multiple": false,
|
|
4100
4212
|
"type": "option"
|
|
4101
4213
|
},
|
|
4102
|
-
"
|
|
4103
|
-
"description": "
|
|
4104
|
-
"name": "
|
|
4214
|
+
"date-from": {
|
|
4215
|
+
"description": "Start of the reporting period (YYYY-MM-DD), defaults to today",
|
|
4216
|
+
"name": "date-from",
|
|
4217
|
+
"hasDynamicHelp": false,
|
|
4218
|
+
"multiple": false,
|
|
4219
|
+
"type": "option"
|
|
4220
|
+
},
|
|
4221
|
+
"date-to": {
|
|
4222
|
+
"description": "End of the reporting period (YYYY-MM-DD), defaults to today",
|
|
4223
|
+
"name": "date-to",
|
|
4224
|
+
"hasDynamicHelp": false,
|
|
4225
|
+
"multiple": false,
|
|
4226
|
+
"type": "option"
|
|
4227
|
+
},
|
|
4228
|
+
"app": {
|
|
4229
|
+
"description": "Keep only rows in this app; repeatable",
|
|
4230
|
+
"name": "app",
|
|
4231
|
+
"hasDynamicHelp": false,
|
|
4232
|
+
"multiple": true,
|
|
4233
|
+
"type": "option"
|
|
4234
|
+
},
|
|
4235
|
+
"campaign-group": {
|
|
4236
|
+
"description": "Keep only rows in this campaign group; repeatable",
|
|
4237
|
+
"name": "campaign-group",
|
|
4238
|
+
"hasDynamicHelp": false,
|
|
4239
|
+
"multiple": true,
|
|
4240
|
+
"type": "option"
|
|
4241
|
+
},
|
|
4242
|
+
"search": {
|
|
4243
|
+
"description": "Case-insensitive substring match on the name",
|
|
4244
|
+
"name": "search",
|
|
4105
4245
|
"hasDynamicHelp": false,
|
|
4106
4246
|
"multiple": false,
|
|
4107
4247
|
"type": "option"
|
|
4248
|
+
},
|
|
4249
|
+
"campaign": {
|
|
4250
|
+
"description": "Keep only rows in this campaign; repeatable",
|
|
4251
|
+
"name": "campaign",
|
|
4252
|
+
"hasDynamicHelp": false,
|
|
4253
|
+
"multiple": true,
|
|
4254
|
+
"type": "option"
|
|
4255
|
+
},
|
|
4256
|
+
"ad-group": {
|
|
4257
|
+
"description": "Keep only rows in this ad group; repeatable",
|
|
4258
|
+
"name": "ad-group",
|
|
4259
|
+
"hasDynamicHelp": false,
|
|
4260
|
+
"multiple": true,
|
|
4261
|
+
"type": "option"
|
|
4108
4262
|
}
|
|
4109
4263
|
},
|
|
4110
4264
|
"hasDynamicHelp": false,
|
|
4111
4265
|
"hiddenAliases": [],
|
|
4112
|
-
"id": "asa:
|
|
4266
|
+
"id": "asa:search-terms:list",
|
|
4113
4267
|
"pluginAlias": "adapty",
|
|
4114
4268
|
"pluginName": "adapty",
|
|
4115
4269
|
"pluginType": "core",
|
|
@@ -4120,10 +4274,10 @@
|
|
|
4120
4274
|
"dist",
|
|
4121
4275
|
"commands",
|
|
4122
4276
|
"asa",
|
|
4123
|
-
"
|
|
4124
|
-
"
|
|
4277
|
+
"search-terms",
|
|
4278
|
+
"list.js"
|
|
4125
4279
|
]
|
|
4126
4280
|
}
|
|
4127
4281
|
},
|
|
4128
|
-
"version": "0.
|
|
4282
|
+
"version": "0.5.0"
|
|
4129
4283
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adapty",
|
|
3
3
|
"description": "Adapty command line interface",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"author": "Adapty team <support@adapty.io>",
|
|
6
6
|
"bin": {
|
|
7
7
|
"adapty": "./bin/run.js"
|
|
@@ -130,6 +130,7 @@
|
|
|
130
130
|
},
|
|
131
131
|
"scripts": {
|
|
132
132
|
"build": "shx rm -rf dist && tsc -b",
|
|
133
|
+
"check:agent-docs": "oclif manifest && node scripts/check-agent-docs.mjs",
|
|
133
134
|
"lint": "eslint",
|
|
134
135
|
"postpack": "shx rm -f oclif.manifest.json",
|
|
135
136
|
"posttest": "pnpm run lint",
|