@adkit/cli 1.13.25 → 1.13.27
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 +1 -1
- package/dist/cli.js +665 -236
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ adkit projects current
|
|
|
44
44
|
Browse competitor ads and advertisers tracked by AdKit.
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
adkit library advertisers
|
|
47
|
+
adkit library advertisers search "notion" --platform meta
|
|
48
48
|
adkit library advertisers <id>
|
|
49
49
|
adkit library advertisers similar --industry saas
|
|
50
50
|
adkit library ads list --advertiser <id>
|
package/dist/cli.js
CHANGED
|
@@ -25,14 +25,6 @@ function readStringProperty(value, key) {
|
|
|
25
25
|
var GOOGLE_LIMITED_AD_APPROVAL_STATUSES = ["APPROVED_LIMITED", "AREA_OF_INTEREST_ONLY"];
|
|
26
26
|
var GOOGLE_PROBLEM_AD_APPROVAL_STATUSES = ["DISAPPROVED", ...GOOGLE_LIMITED_AD_APPROVAL_STATUSES];
|
|
27
27
|
|
|
28
|
-
// ../shared/dist/manage/google/campaign-validation.js
|
|
29
|
-
var VALID_GOOGLE_BID_STRATEGIES = ["manual_cpc", "manual_cpm", "maximize_clicks", "maximize_conversions", "maximize_conversion_value", "target_spend", "target_impression_share"];
|
|
30
|
-
var SUPPORTED_GOOGLE_CAMPAIGN_TYPES = ["search", "display"];
|
|
31
|
-
var validGoogleBidStrategySet = new Set(VALID_GOOGLE_BID_STRATEGIES);
|
|
32
|
-
var validGoogleBidStrategyList = VALID_GOOGLE_BID_STRATEGIES.join(", ");
|
|
33
|
-
var supportedGoogleCampaignTypeSet = new Set(SUPPORTED_GOOGLE_CAMPAIGN_TYPES);
|
|
34
|
-
var supportedGoogleCampaignTypeList = SUPPORTED_GOOGLE_CAMPAIGN_TYPES.join(", ");
|
|
35
|
-
|
|
36
28
|
// ../shared/dist/manage/google/validators/conversion-goals.js
|
|
37
29
|
var GOOGLE_CAMPAIGN_CONVERSION_GOAL_CATEGORIES = [
|
|
38
30
|
"add_to_cart",
|
|
@@ -60,6 +52,188 @@ var GOOGLE_CAMPAIGN_CONVERSION_GOAL_CATEGORIES = [
|
|
|
60
52
|
];
|
|
61
53
|
var googleCampaignConversionGoalCategorySet = new Set(GOOGLE_CAMPAIGN_CONVERSION_GOAL_CATEGORIES);
|
|
62
54
|
|
|
55
|
+
// ../shared/dist/manage/google/validators/campaigns.js
|
|
56
|
+
var VALID_GOOGLE_BID_STRATEGIES = ["manual_cpc", "manual_cpm", "maximize_clicks", "maximize_conversions", "maximize_conversion_value", "target_spend", "target_impression_share"];
|
|
57
|
+
var SUPPORTED_GOOGLE_CAMPAIGN_TYPES = ["search", "display", "demand_gen"];
|
|
58
|
+
var GOOGLE_DEMAND_GEN_BID_STRATEGIES = ["maximize_clicks", "maximize_conversions", "maximize_conversion_value"];
|
|
59
|
+
var GOOGLE_DEMAND_GEN_BID_STRATEGY_LIST = GOOGLE_DEMAND_GEN_BID_STRATEGIES.join(", ");
|
|
60
|
+
var validGoogleBidStrategySet = new Set(VALID_GOOGLE_BID_STRATEGIES);
|
|
61
|
+
var validGoogleBidStrategyList = VALID_GOOGLE_BID_STRATEGIES.join(", ");
|
|
62
|
+
var supportedGoogleCampaignTypeSet = new Set(SUPPORTED_GOOGLE_CAMPAIGN_TYPES);
|
|
63
|
+
var supportedGoogleCampaignTypeList = SUPPORTED_GOOGLE_CAMPAIGN_TYPES.join(", ");
|
|
64
|
+
|
|
65
|
+
// ../shared/dist/manage/google/validators/ad-groups.js
|
|
66
|
+
var GOOGLE_DEMAND_GEN_NETWORKS = ["youtube_in_feed", "youtube_in_stream", "youtube_shorts"];
|
|
67
|
+
var GOOGLE_DEMAND_GEN_NETWORK_LIST = GOOGLE_DEMAND_GEN_NETWORKS.join(", ");
|
|
68
|
+
|
|
69
|
+
// ../shared/dist/manage/google/asset-utils.js
|
|
70
|
+
var SUPPORTED_GOOGLE_STRUCTURED_SNIPPET_HEADERS = ["Amenities", "Brands", "Courses", "Degree programs", "Destinations", "Featured hotels", "Insurance coverage", "Models", "Neighborhoods", "Service catalog", "Shows", "Styles", "Types"];
|
|
71
|
+
var STRUCTURED_SNIPPET_HEADER_LOOKUP = new Map(SUPPORTED_GOOGLE_STRUCTURED_SNIPPET_HEADERS.map((header) => [header.toLowerCase(), header]));
|
|
72
|
+
var DAY_TO_GOOGLE_MAP = {
|
|
73
|
+
monday: "MONDAY",
|
|
74
|
+
tuesday: "TUESDAY",
|
|
75
|
+
wednesday: "WEDNESDAY",
|
|
76
|
+
thursday: "THURSDAY",
|
|
77
|
+
friday: "FRIDAY",
|
|
78
|
+
saturday: "SATURDAY",
|
|
79
|
+
sunday: "SUNDAY"
|
|
80
|
+
};
|
|
81
|
+
var MINUTE_TO_GOOGLE_MAP = {
|
|
82
|
+
0: "ZERO",
|
|
83
|
+
15: "FIFTEEN",
|
|
84
|
+
30: "THIRTY",
|
|
85
|
+
45: "FORTY_FIVE"
|
|
86
|
+
};
|
|
87
|
+
function normalizeWhitespace(value) {
|
|
88
|
+
return value.trim().replace(/\s+/g, " ");
|
|
89
|
+
}
|
|
90
|
+
function normalizeOptionalText(value) {
|
|
91
|
+
if (typeof value !== "string")
|
|
92
|
+
return void 0;
|
|
93
|
+
const normalized = normalizeWhitespace(value);
|
|
94
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
95
|
+
}
|
|
96
|
+
function normalizeDate(value) {
|
|
97
|
+
const normalized = normalizeOptionalText(value);
|
|
98
|
+
if (!normalized)
|
|
99
|
+
return void 0;
|
|
100
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(normalized))
|
|
101
|
+
throw new Error(`Invalid date "${normalized}". Use YYYY-MM-DD.`);
|
|
102
|
+
const [yearRaw, monthRaw, dayRaw] = normalized.split("-");
|
|
103
|
+
const year = Number.parseInt(yearRaw ?? "", 10);
|
|
104
|
+
const month = Number.parseInt(monthRaw ?? "", 10);
|
|
105
|
+
const day = Number.parseInt(dayRaw ?? "", 10);
|
|
106
|
+
const parsed = new Date(Date.UTC(year, month - 1, day));
|
|
107
|
+
if (Number.isNaN(parsed.getTime()) || parsed.getUTCFullYear() !== year || parsed.getUTCMonth() !== month - 1 || parsed.getUTCDate() !== day)
|
|
108
|
+
throw new Error(`Invalid calendar date "${normalized}". Use a real YYYY-MM-DD date.`);
|
|
109
|
+
return normalized;
|
|
110
|
+
}
|
|
111
|
+
function validateDateRange(startDate, endDate) {
|
|
112
|
+
if (!startDate || !endDate)
|
|
113
|
+
return;
|
|
114
|
+
if (Date.parse(`${startDate}T00:00:00Z`) > Date.parse(`${endDate}T00:00:00Z`))
|
|
115
|
+
throw new Error("startDate must be on or before endDate");
|
|
116
|
+
}
|
|
117
|
+
function normalizeUrl(value) {
|
|
118
|
+
const raw = value.trim();
|
|
119
|
+
if (!raw)
|
|
120
|
+
throw new Error("URL cannot be empty");
|
|
121
|
+
try {
|
|
122
|
+
const url = new URL(raw);
|
|
123
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
124
|
+
throw new Error("Only http:// and https:// URLs are allowed");
|
|
125
|
+
return url.toString();
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (error instanceof Error && error.message === "Only http:// and https:// URLs are allowed")
|
|
128
|
+
throw error;
|
|
129
|
+
throw new Error(`Invalid URL "${value}"`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function normalizeScheduleEntry(entry) {
|
|
133
|
+
const { dayOfWeek, startHour, startMinute, endHour, endMinute } = entry;
|
|
134
|
+
if (!(dayOfWeek in DAY_TO_GOOGLE_MAP))
|
|
135
|
+
throw new Error(`Invalid schedule day "${String(dayOfWeek)}"`);
|
|
136
|
+
if (!Number.isInteger(startHour) || startHour < 0 || startHour > 23)
|
|
137
|
+
throw new Error("Schedule startHour must be an integer between 0 and 23");
|
|
138
|
+
if (!Number.isInteger(endHour) || endHour < 0 || endHour > 24)
|
|
139
|
+
throw new Error("Schedule endHour must be an integer between 0 and 24");
|
|
140
|
+
if (!(startMinute in MINUTE_TO_GOOGLE_MAP))
|
|
141
|
+
throw new Error("Schedule startMinute must be one of 0, 15, 30, 45");
|
|
142
|
+
if (!(endMinute in MINUTE_TO_GOOGLE_MAP))
|
|
143
|
+
throw new Error("Schedule endMinute must be one of 0, 15, 30, 45");
|
|
144
|
+
if (endHour === 24 && endMinute !== 0)
|
|
145
|
+
throw new Error("Schedule endMinute must be 0 when endHour is 24");
|
|
146
|
+
const startTotalMinutes = startHour * 60 + startMinute;
|
|
147
|
+
const endTotalMinutes = endHour * 60 + endMinute;
|
|
148
|
+
if (endTotalMinutes <= startTotalMinutes)
|
|
149
|
+
throw new Error("Schedule end must be after start");
|
|
150
|
+
return { dayOfWeek, startHour, startMinute, endHour, endMinute };
|
|
151
|
+
}
|
|
152
|
+
function normalizeSchedule(schedule) {
|
|
153
|
+
if (!schedule)
|
|
154
|
+
return void 0;
|
|
155
|
+
if (!Array.isArray(schedule))
|
|
156
|
+
throw new Error("schedule must be an array");
|
|
157
|
+
if (schedule.length === 0)
|
|
158
|
+
return void 0;
|
|
159
|
+
const normalized = schedule.map(normalizeScheduleEntry).sort((left, right) => {
|
|
160
|
+
const leftKey = `${left.dayOfWeek}:${String(left.startHour).padStart(2, "0")}:${left.startMinute}:${String(left.endHour).padStart(2, "0")}:${left.endMinute}`;
|
|
161
|
+
const rightKey = `${right.dayOfWeek}:${String(right.startHour).padStart(2, "0")}:${right.startMinute}:${String(right.endHour).padStart(2, "0")}:${right.endMinute}`;
|
|
162
|
+
return leftKey.localeCompare(rightKey);
|
|
163
|
+
});
|
|
164
|
+
const perDay = /* @__PURE__ */ new Map();
|
|
165
|
+
for (const entry of normalized) {
|
|
166
|
+
const current = perDay.get(entry.dayOfWeek) ?? 0;
|
|
167
|
+
const next = current + 1;
|
|
168
|
+
if (next > 6)
|
|
169
|
+
throw new Error(`Schedule exceeds Google limit of 6 entries on ${entry.dayOfWeek}`);
|
|
170
|
+
perDay.set(entry.dayOfWeek, next);
|
|
171
|
+
}
|
|
172
|
+
if (normalized.length > 42)
|
|
173
|
+
throw new Error("Schedule exceeds Google limit of 42 total entries");
|
|
174
|
+
return normalized;
|
|
175
|
+
}
|
|
176
|
+
function normalizeSitelinkAsset(asset) {
|
|
177
|
+
const linkText = normalizeOptionalText(asset.linkText);
|
|
178
|
+
if (!linkText)
|
|
179
|
+
throw new Error("Sitelink linkText is required");
|
|
180
|
+
if (linkText.length > 25)
|
|
181
|
+
throw new Error("Sitelink linkText must be between 1 and 25 characters");
|
|
182
|
+
if (!Array.isArray(asset.finalUrls) || asset.finalUrls.length === 0)
|
|
183
|
+
throw new Error("Sitelink finalUrls must contain at least one URL");
|
|
184
|
+
const finalUrls = asset.finalUrls.map(normalizeUrl);
|
|
185
|
+
const description1 = normalizeOptionalText(asset.description1);
|
|
186
|
+
const description2 = normalizeOptionalText(asset.description2);
|
|
187
|
+
if (description1 && !description2 || !description1 && description2)
|
|
188
|
+
throw new Error("Sitelink description1 and description2 must either both be set or both be omitted");
|
|
189
|
+
if (description1 && description1.length > 35)
|
|
190
|
+
throw new Error("Sitelink description1 must be between 1 and 35 characters");
|
|
191
|
+
if (description2 && description2.length > 35)
|
|
192
|
+
throw new Error("Sitelink description2 must be between 1 and 35 characters");
|
|
193
|
+
const startDate = normalizeDate(asset.startDate);
|
|
194
|
+
const endDate = normalizeDate(asset.endDate);
|
|
195
|
+
validateDateRange(startDate, endDate);
|
|
196
|
+
const urlCustomParameters = asset.urlCustomParameters?.map((parameter) => {
|
|
197
|
+
const key = normalizeOptionalText(parameter.key);
|
|
198
|
+
const value = normalizeOptionalText(parameter.value);
|
|
199
|
+
if (!key || !value)
|
|
200
|
+
throw new Error("urlCustomParameters require non-empty key and value");
|
|
201
|
+
return { key, value };
|
|
202
|
+
}) ?? void 0;
|
|
203
|
+
return {
|
|
204
|
+
type: "sitelink",
|
|
205
|
+
linkText,
|
|
206
|
+
finalUrls,
|
|
207
|
+
description1,
|
|
208
|
+
description2,
|
|
209
|
+
startDate,
|
|
210
|
+
endDate,
|
|
211
|
+
schedule: normalizeSchedule(asset.schedule),
|
|
212
|
+
trackingUrlTemplate: normalizeOptionalText(asset.trackingUrlTemplate),
|
|
213
|
+
finalUrlSuffix: normalizeOptionalText(asset.finalUrlSuffix),
|
|
214
|
+
urlCustomParameters: urlCustomParameters && urlCustomParameters.length > 0 ? urlCustomParameters : void 0
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function parseGoogleAssetReference(value) {
|
|
218
|
+
if (!value.startsWith("asset:"))
|
|
219
|
+
return null;
|
|
220
|
+
const assetId = value.slice("asset:".length).trim();
|
|
221
|
+
if (!/^\d+$/.test(assetId))
|
|
222
|
+
throw new Error(`Invalid asset reference "${value}". Expected asset:<numeric-id>.`);
|
|
223
|
+
return assetId;
|
|
224
|
+
}
|
|
225
|
+
function parseGoogleSitelinkShorthand(value) {
|
|
226
|
+
const separatorCount = value.split("|").length - 1;
|
|
227
|
+
if (separatorCount !== 1)
|
|
228
|
+
throw new Error('Invalid sitelink shorthand. Expected exactly one "|" in "text|url".');
|
|
229
|
+
const [rawText, rawUrl] = value.split("|");
|
|
230
|
+
const linkText = normalizeOptionalText(rawText);
|
|
231
|
+
if (!linkText)
|
|
232
|
+
throw new Error("Invalid sitelink shorthand. Text cannot be empty.");
|
|
233
|
+
const finalUrl = normalizeUrl(rawUrl ?? "");
|
|
234
|
+
return normalizeSitelinkAsset({ type: "sitelink", linkText, finalUrls: [finalUrl] });
|
|
235
|
+
}
|
|
236
|
+
|
|
63
237
|
// ../shared/dist/manage/google/types/google-adkit.js
|
|
64
238
|
var GOOGLE_GEO_LOCATION_SEARCH_TYPES = ["country", "region", "city", "postal_code", "metro"];
|
|
65
239
|
|
|
@@ -1820,174 +1994,6 @@ function getAd(client, args, flags) {
|
|
|
1820
1994
|
return getMetaEntity(client, args, flags, { path: "ads", usageHint: "Run: adkit manage meta ads <ad-id>" });
|
|
1821
1995
|
}
|
|
1822
1996
|
|
|
1823
|
-
// ../shared/dist/manage/google/asset-utils.js
|
|
1824
|
-
var SUPPORTED_GOOGLE_STRUCTURED_SNIPPET_HEADERS = ["Amenities", "Brands", "Courses", "Degree programs", "Destinations", "Featured hotels", "Insurance coverage", "Models", "Neighborhoods", "Service catalog", "Shows", "Styles", "Types"];
|
|
1825
|
-
var STRUCTURED_SNIPPET_HEADER_LOOKUP = new Map(SUPPORTED_GOOGLE_STRUCTURED_SNIPPET_HEADERS.map((header) => [header.toLowerCase(), header]));
|
|
1826
|
-
var DAY_TO_GOOGLE_MAP = {
|
|
1827
|
-
monday: "MONDAY",
|
|
1828
|
-
tuesday: "TUESDAY",
|
|
1829
|
-
wednesday: "WEDNESDAY",
|
|
1830
|
-
thursday: "THURSDAY",
|
|
1831
|
-
friday: "FRIDAY",
|
|
1832
|
-
saturday: "SATURDAY",
|
|
1833
|
-
sunday: "SUNDAY"
|
|
1834
|
-
};
|
|
1835
|
-
var MINUTE_TO_GOOGLE_MAP = {
|
|
1836
|
-
0: "ZERO",
|
|
1837
|
-
15: "FIFTEEN",
|
|
1838
|
-
30: "THIRTY",
|
|
1839
|
-
45: "FORTY_FIVE"
|
|
1840
|
-
};
|
|
1841
|
-
function normalizeWhitespace(value) {
|
|
1842
|
-
return value.trim().replace(/\s+/g, " ");
|
|
1843
|
-
}
|
|
1844
|
-
function normalizeOptionalText(value) {
|
|
1845
|
-
if (typeof value !== "string")
|
|
1846
|
-
return void 0;
|
|
1847
|
-
const normalized = normalizeWhitespace(value);
|
|
1848
|
-
return normalized.length > 0 ? normalized : void 0;
|
|
1849
|
-
}
|
|
1850
|
-
function normalizeDate(value) {
|
|
1851
|
-
const normalized = normalizeOptionalText(value);
|
|
1852
|
-
if (!normalized)
|
|
1853
|
-
return void 0;
|
|
1854
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(normalized))
|
|
1855
|
-
throw new Error(`Invalid date "${normalized}". Use YYYY-MM-DD.`);
|
|
1856
|
-
const [yearRaw, monthRaw, dayRaw] = normalized.split("-");
|
|
1857
|
-
const year = Number.parseInt(yearRaw ?? "", 10);
|
|
1858
|
-
const month = Number.parseInt(monthRaw ?? "", 10);
|
|
1859
|
-
const day = Number.parseInt(dayRaw ?? "", 10);
|
|
1860
|
-
const parsed = new Date(Date.UTC(year, month - 1, day));
|
|
1861
|
-
if (Number.isNaN(parsed.getTime()) || parsed.getUTCFullYear() !== year || parsed.getUTCMonth() !== month - 1 || parsed.getUTCDate() !== day)
|
|
1862
|
-
throw new Error(`Invalid calendar date "${normalized}". Use a real YYYY-MM-DD date.`);
|
|
1863
|
-
return normalized;
|
|
1864
|
-
}
|
|
1865
|
-
function validateDateRange(startDate, endDate) {
|
|
1866
|
-
if (!startDate || !endDate)
|
|
1867
|
-
return;
|
|
1868
|
-
if (Date.parse(`${startDate}T00:00:00Z`) > Date.parse(`${endDate}T00:00:00Z`))
|
|
1869
|
-
throw new Error("startDate must be on or before endDate");
|
|
1870
|
-
}
|
|
1871
|
-
function normalizeUrl(value) {
|
|
1872
|
-
const raw = value.trim();
|
|
1873
|
-
if (!raw)
|
|
1874
|
-
throw new Error("URL cannot be empty");
|
|
1875
|
-
try {
|
|
1876
|
-
const url = new URL(raw);
|
|
1877
|
-
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
1878
|
-
throw new Error("Only http:// and https:// URLs are allowed");
|
|
1879
|
-
return url.toString();
|
|
1880
|
-
} catch (error) {
|
|
1881
|
-
if (error instanceof Error && error.message === "Only http:// and https:// URLs are allowed")
|
|
1882
|
-
throw error;
|
|
1883
|
-
throw new Error(`Invalid URL "${value}"`);
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
function normalizeScheduleEntry(entry) {
|
|
1887
|
-
const { dayOfWeek, startHour, startMinute, endHour, endMinute } = entry;
|
|
1888
|
-
if (!(dayOfWeek in DAY_TO_GOOGLE_MAP))
|
|
1889
|
-
throw new Error(`Invalid schedule day "${String(dayOfWeek)}"`);
|
|
1890
|
-
if (!Number.isInteger(startHour) || startHour < 0 || startHour > 23)
|
|
1891
|
-
throw new Error("Schedule startHour must be an integer between 0 and 23");
|
|
1892
|
-
if (!Number.isInteger(endHour) || endHour < 0 || endHour > 24)
|
|
1893
|
-
throw new Error("Schedule endHour must be an integer between 0 and 24");
|
|
1894
|
-
if (!(startMinute in MINUTE_TO_GOOGLE_MAP))
|
|
1895
|
-
throw new Error("Schedule startMinute must be one of 0, 15, 30, 45");
|
|
1896
|
-
if (!(endMinute in MINUTE_TO_GOOGLE_MAP))
|
|
1897
|
-
throw new Error("Schedule endMinute must be one of 0, 15, 30, 45");
|
|
1898
|
-
if (endHour === 24 && endMinute !== 0)
|
|
1899
|
-
throw new Error("Schedule endMinute must be 0 when endHour is 24");
|
|
1900
|
-
const startTotalMinutes = startHour * 60 + startMinute;
|
|
1901
|
-
const endTotalMinutes = endHour * 60 + endMinute;
|
|
1902
|
-
if (endTotalMinutes <= startTotalMinutes)
|
|
1903
|
-
throw new Error("Schedule end must be after start");
|
|
1904
|
-
return { dayOfWeek, startHour, startMinute, endHour, endMinute };
|
|
1905
|
-
}
|
|
1906
|
-
function normalizeSchedule(schedule) {
|
|
1907
|
-
if (!schedule)
|
|
1908
|
-
return void 0;
|
|
1909
|
-
if (!Array.isArray(schedule))
|
|
1910
|
-
throw new Error("schedule must be an array");
|
|
1911
|
-
if (schedule.length === 0)
|
|
1912
|
-
return void 0;
|
|
1913
|
-
const normalized = schedule.map(normalizeScheduleEntry).sort((left, right) => {
|
|
1914
|
-
const leftKey = `${left.dayOfWeek}:${String(left.startHour).padStart(2, "0")}:${left.startMinute}:${String(left.endHour).padStart(2, "0")}:${left.endMinute}`;
|
|
1915
|
-
const rightKey = `${right.dayOfWeek}:${String(right.startHour).padStart(2, "0")}:${right.startMinute}:${String(right.endHour).padStart(2, "0")}:${right.endMinute}`;
|
|
1916
|
-
return leftKey.localeCompare(rightKey);
|
|
1917
|
-
});
|
|
1918
|
-
const perDay = /* @__PURE__ */ new Map();
|
|
1919
|
-
for (const entry of normalized) {
|
|
1920
|
-
const current = perDay.get(entry.dayOfWeek) ?? 0;
|
|
1921
|
-
const next = current + 1;
|
|
1922
|
-
if (next > 6)
|
|
1923
|
-
throw new Error(`Schedule exceeds Google limit of 6 entries on ${entry.dayOfWeek}`);
|
|
1924
|
-
perDay.set(entry.dayOfWeek, next);
|
|
1925
|
-
}
|
|
1926
|
-
if (normalized.length > 42)
|
|
1927
|
-
throw new Error("Schedule exceeds Google limit of 42 total entries");
|
|
1928
|
-
return normalized;
|
|
1929
|
-
}
|
|
1930
|
-
function normalizeSitelinkAsset(asset) {
|
|
1931
|
-
const linkText = normalizeOptionalText(asset.linkText);
|
|
1932
|
-
if (!linkText)
|
|
1933
|
-
throw new Error("Sitelink linkText is required");
|
|
1934
|
-
if (linkText.length > 25)
|
|
1935
|
-
throw new Error("Sitelink linkText must be between 1 and 25 characters");
|
|
1936
|
-
if (!Array.isArray(asset.finalUrls) || asset.finalUrls.length === 0)
|
|
1937
|
-
throw new Error("Sitelink finalUrls must contain at least one URL");
|
|
1938
|
-
const finalUrls = asset.finalUrls.map(normalizeUrl);
|
|
1939
|
-
const description1 = normalizeOptionalText(asset.description1);
|
|
1940
|
-
const description2 = normalizeOptionalText(asset.description2);
|
|
1941
|
-
if (description1 && !description2 || !description1 && description2)
|
|
1942
|
-
throw new Error("Sitelink description1 and description2 must either both be set or both be omitted");
|
|
1943
|
-
if (description1 && description1.length > 35)
|
|
1944
|
-
throw new Error("Sitelink description1 must be between 1 and 35 characters");
|
|
1945
|
-
if (description2 && description2.length > 35)
|
|
1946
|
-
throw new Error("Sitelink description2 must be between 1 and 35 characters");
|
|
1947
|
-
const startDate = normalizeDate(asset.startDate);
|
|
1948
|
-
const endDate = normalizeDate(asset.endDate);
|
|
1949
|
-
validateDateRange(startDate, endDate);
|
|
1950
|
-
const urlCustomParameters = asset.urlCustomParameters?.map((parameter) => {
|
|
1951
|
-
const key = normalizeOptionalText(parameter.key);
|
|
1952
|
-
const value = normalizeOptionalText(parameter.value);
|
|
1953
|
-
if (!key || !value)
|
|
1954
|
-
throw new Error("urlCustomParameters require non-empty key and value");
|
|
1955
|
-
return { key, value };
|
|
1956
|
-
}) ?? void 0;
|
|
1957
|
-
return {
|
|
1958
|
-
type: "sitelink",
|
|
1959
|
-
linkText,
|
|
1960
|
-
finalUrls,
|
|
1961
|
-
description1,
|
|
1962
|
-
description2,
|
|
1963
|
-
startDate,
|
|
1964
|
-
endDate,
|
|
1965
|
-
schedule: normalizeSchedule(asset.schedule),
|
|
1966
|
-
trackingUrlTemplate: normalizeOptionalText(asset.trackingUrlTemplate),
|
|
1967
|
-
finalUrlSuffix: normalizeOptionalText(asset.finalUrlSuffix),
|
|
1968
|
-
urlCustomParameters: urlCustomParameters && urlCustomParameters.length > 0 ? urlCustomParameters : void 0
|
|
1969
|
-
};
|
|
1970
|
-
}
|
|
1971
|
-
function parseGoogleAssetReference(value) {
|
|
1972
|
-
if (!value.startsWith("asset:"))
|
|
1973
|
-
return null;
|
|
1974
|
-
const assetId = value.slice("asset:".length).trim();
|
|
1975
|
-
if (!/^\d+$/.test(assetId))
|
|
1976
|
-
throw new Error(`Invalid asset reference "${value}". Expected asset:<numeric-id>.`);
|
|
1977
|
-
return assetId;
|
|
1978
|
-
}
|
|
1979
|
-
function parseGoogleSitelinkShorthand(value) {
|
|
1980
|
-
const separatorCount = value.split("|").length - 1;
|
|
1981
|
-
if (separatorCount !== 1)
|
|
1982
|
-
throw new Error('Invalid sitelink shorthand. Expected exactly one "|" in "text|url".');
|
|
1983
|
-
const [rawText, rawUrl] = value.split("|");
|
|
1984
|
-
const linkText = normalizeOptionalText(rawText);
|
|
1985
|
-
if (!linkText)
|
|
1986
|
-
throw new Error("Invalid sitelink shorthand. Text cannot be empty.");
|
|
1987
|
-
const finalUrl = normalizeUrl(rawUrl ?? "");
|
|
1988
|
-
return normalizeSitelinkAsset({ type: "sitelink", linkText, finalUrls: [finalUrl] });
|
|
1989
|
-
}
|
|
1990
|
-
|
|
1991
1997
|
// src/commands/google.ts
|
|
1992
1998
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
1993
1999
|
import { basename, extname, resolve } from "node:path";
|
|
@@ -2769,14 +2775,16 @@ async function getAd2(client, args, flags) {
|
|
|
2769
2775
|
async function createAd2(client, _args, flags) {
|
|
2770
2776
|
validateFlags(flags, AD_FLAGS2, "manage google ads create");
|
|
2771
2777
|
if (typeof flags.data === "string") {
|
|
2772
|
-
const
|
|
2773
|
-
const qs2 = buildGoogleMutationQuery(flags,
|
|
2778
|
+
const rawBody2 = parseDataFlag(flags, "Check JSON syntax in --data");
|
|
2779
|
+
const qs2 = buildGoogleMutationQuery(flags, rawBody2);
|
|
2780
|
+
const body2 = removeQueryOnlyBodyKeys(rawBody2, ["accountId"]);
|
|
2774
2781
|
const path4 = `/manage/google/ads${qs2}`;
|
|
2775
2782
|
return client.post(path4, body2);
|
|
2776
2783
|
}
|
|
2777
2784
|
const payload = buildAdPayload(flags);
|
|
2778
|
-
const
|
|
2779
|
-
const qs = buildGoogleMutationQuery(flags,
|
|
2785
|
+
const rawBody = { ads: [payload] };
|
|
2786
|
+
const qs = buildGoogleMutationQuery(flags, rawBody);
|
|
2787
|
+
const body = removeQueryOnlyBodyKeys(rawBody, ["accountId"]);
|
|
2780
2788
|
const path3 = `/manage/google/ads${qs}`;
|
|
2781
2789
|
return client.post(path3, body);
|
|
2782
2790
|
}
|
|
@@ -2784,19 +2792,21 @@ async function updateAd2(client, args, flags) {
|
|
|
2784
2792
|
const id = requireArg(args, 0, "ad-id", "Run: adkit manage google ads update <ad-id> --status paused --ad-group 123456");
|
|
2785
2793
|
validateFlags(flags, AD_FLAGS2, "manage google ads update");
|
|
2786
2794
|
if (typeof flags.data === "string") {
|
|
2787
|
-
const
|
|
2788
|
-
const adGroupId2 = resolveGoogleAdGroupId(
|
|
2795
|
+
const rawBody = parseDataFlag(flags, "Check JSON syntax in --data");
|
|
2796
|
+
const adGroupId2 = resolveGoogleAdGroupId(rawBody, flags);
|
|
2789
2797
|
if (!adGroupId2) throw new CliError("MISSING_FLAG", "Missing required flag: `--ad-group`", "Provide --ad-group <id> or include adGroupId in --data");
|
|
2790
|
-
const qs2 = buildGoogleMutationQuery(flags,
|
|
2798
|
+
const qs2 = buildGoogleMutationQuery(flags, rawBody, { adGroupId: adGroupId2 });
|
|
2799
|
+
const body2 = removeQueryOnlyBodyKeys(rawBody, ["accountId", "adGroupId"]);
|
|
2791
2800
|
const path4 = `/manage/google/ads/${id}${qs2}`;
|
|
2792
|
-
return client.patch(path4,
|
|
2801
|
+
return client.patch(path4, body2);
|
|
2793
2802
|
}
|
|
2794
2803
|
const payload = buildAdPayload(flags);
|
|
2795
2804
|
const adGroupId = resolveGoogleAdGroupId(payload, flags);
|
|
2796
2805
|
if (!adGroupId) throw new CliError("MISSING_FLAG", "Missing required flag: `--ad-group`", "Run: adkit manage google ads update <ad-id> --ad-group <ad-group-id> ...");
|
|
2797
2806
|
const qs = buildGoogleMutationQuery(flags, payload, { adGroupId });
|
|
2807
|
+
const body = removeQueryOnlyBodyKeys(payload, ["accountId", "adGroupId"]);
|
|
2798
2808
|
const path3 = `/manage/google/ads/${id}${qs}`;
|
|
2799
|
-
return client.patch(path3,
|
|
2809
|
+
return client.patch(path3, body);
|
|
2800
2810
|
}
|
|
2801
2811
|
async function deleteAd2(client, args, flags) {
|
|
2802
2812
|
validateFlags(flags, ["ad-group"], "manage google ads delete");
|
|
@@ -3509,10 +3519,25 @@ var AD_GROUP_UPDATE_FLAGS2 = ["name", "status", "budget-daily", "budget-lifetime
|
|
|
3509
3519
|
var AD_UPDATE_FLAGS2 = ["name", "status"];
|
|
3510
3520
|
var TARGETING_SEARCH_FLAGS = ["account", "facet", "query", "limit"];
|
|
3511
3521
|
var RESULTS_FLAGS2 = ["level", "period", "fields", "breakdowns", "from", "to", "sort", "limit", "offset"];
|
|
3522
|
+
async function listLinkedInAccounts(client, _args, flags) {
|
|
3523
|
+
validateFlags(flags, [], "manage linkedin accounts list");
|
|
3524
|
+
return client.get("/manage/linkedin/accounts");
|
|
3525
|
+
}
|
|
3512
3526
|
async function listLinkedInAvailableAccounts(client, _args, flags) {
|
|
3513
3527
|
validateFlags(flags, [], "manage linkedin accounts available");
|
|
3514
3528
|
return client.get("/manage/linkedin/accounts/available");
|
|
3515
3529
|
}
|
|
3530
|
+
async function connectLinkedInAccount(client, args, flags) {
|
|
3531
|
+
validateFlags(flags, ["integration"], "manage linkedin accounts connect");
|
|
3532
|
+
const accountId = requireArg(args, 0, "account-id", "Run: adkit manage linkedin accounts connect <account-id>");
|
|
3533
|
+
const integrationId = typeof flags.integration === "string" ? flags.integration : void 0;
|
|
3534
|
+
return client.post("/manage/linkedin/accounts/connect", { accountId, ...integrationId ? { integrationId } : {} });
|
|
3535
|
+
}
|
|
3536
|
+
async function disconnectLinkedInAccount(client, args, flags) {
|
|
3537
|
+
validateFlags(flags, [], "manage linkedin accounts disconnect");
|
|
3538
|
+
const accountId = requireArg(args, 0, "account-id", "Run: adkit manage linkedin accounts disconnect <account-id>");
|
|
3539
|
+
return client.delete(`/manage/linkedin/accounts/${accountId}`);
|
|
3540
|
+
}
|
|
3516
3541
|
async function uploadLinkedInMedia(client, _args, flags) {
|
|
3517
3542
|
validateFlags(flags, MEDIA_UPLOAD_FLAGS3, "manage linkedin media upload");
|
|
3518
3543
|
if (typeof flags.data === "string") {
|
|
@@ -3801,13 +3826,170 @@ function buildLinkedInMediaUploadSource(flags) {
|
|
|
3801
3826
|
return source;
|
|
3802
3827
|
}
|
|
3803
3828
|
|
|
3829
|
+
// src/commands/x.ts
|
|
3830
|
+
var CAMPAIGN_LIST_FLAGS4 = ["campaign-ids", "status", "limit", "offset"];
|
|
3831
|
+
var AD_GROUP_LIST_FLAGS4 = ["campaign-ids", "ad-group-ids", "status", "limit", "offset"];
|
|
3832
|
+
var AD_LIST_FLAGS4 = ["campaign-ids", "ad-group-ids", "ad-ids", "status", "limit", "offset"];
|
|
3833
|
+
var RESULTS_FLAGS3 = ["level", "from", "to", "fields", "breakdowns", "campaign-ids", "ad-group-ids", "ad-ids", "sort", "sort-direction", "limit", "offset", "raw"];
|
|
3834
|
+
async function listXAccounts(client, _args, flags) {
|
|
3835
|
+
validateFlags(flags, [], "manage x accounts list");
|
|
3836
|
+
return client.get("/manage/x/accounts");
|
|
3837
|
+
}
|
|
3838
|
+
async function listXAvailableAccounts(client, _args, flags) {
|
|
3839
|
+
validateFlags(flags, [], "manage x accounts available");
|
|
3840
|
+
return client.get("/manage/x/accounts/available");
|
|
3841
|
+
}
|
|
3842
|
+
async function connectXAccount(client, args, flags) {
|
|
3843
|
+
validateFlags(flags, ["integration"], "manage x accounts connect");
|
|
3844
|
+
const accountId = requireArg(args, 0, "account-id", "Run: adkit manage x accounts connect <account-id>");
|
|
3845
|
+
const integrationId = typeof flags.integration === "string" ? flags.integration : void 0;
|
|
3846
|
+
return client.post("/manage/x/accounts/connect", { accountId, ...integrationId ? { integrationId } : {} });
|
|
3847
|
+
}
|
|
3848
|
+
async function disconnectXAccount(client, args, flags) {
|
|
3849
|
+
validateFlags(flags, [], "manage x accounts disconnect");
|
|
3850
|
+
const accountId = requireArg(args, 0, "account-id", "Run: adkit manage x accounts disconnect <account-id>");
|
|
3851
|
+
return client.delete(`/manage/x/accounts/${accountId}`);
|
|
3852
|
+
}
|
|
3853
|
+
async function listXProfiles(client, args, flags) {
|
|
3854
|
+
validateFlags(flags, [], "manage x accounts <account-id> profiles");
|
|
3855
|
+
const accountId = requireArg(args, 0, "account-id", "Run: adkit manage x accounts <account-id> profiles");
|
|
3856
|
+
return client.get(`/manage/x/accounts/${accountId}/profiles`);
|
|
3857
|
+
}
|
|
3858
|
+
async function listXPixels(client, args, flags) {
|
|
3859
|
+
validateFlags(flags, [], "manage x accounts <account-id> pixels");
|
|
3860
|
+
const accountId = requireArg(args, 0, "account-id", "Run: adkit manage x accounts <account-id> pixels");
|
|
3861
|
+
return client.get(`/manage/x/accounts/${accountId}/pixels`);
|
|
3862
|
+
}
|
|
3863
|
+
async function updateXAccount(client, args, flags) {
|
|
3864
|
+
validateFlags(flags, ["default-profile", "default-pixel"], "manage x accounts <account-id> update");
|
|
3865
|
+
const accountId = requireArg(args, 0, "account-id", "Run: adkit manage x accounts <account-id> update --default-profile <profile-id>");
|
|
3866
|
+
if (typeof flags.data === "string") {
|
|
3867
|
+
const body = parseDataFlag(flags, 'Use { "defaultProfileId": "...", "defaultPixelId": "..." }');
|
|
3868
|
+
return client.patch(`/manage/x/accounts/${accountId}`, body);
|
|
3869
|
+
}
|
|
3870
|
+
const defaultProfileId = typeof flags["default-profile"] === "string" ? flags["default-profile"] : void 0;
|
|
3871
|
+
const defaultPixelId = typeof flags["default-pixel"] === "string" ? flags["default-pixel"] : void 0;
|
|
3872
|
+
if (!defaultProfileId && !defaultPixelId) throw new CliError("MISSING_FLAG", "Nothing to update", "Pass --default-profile, --default-pixel, or --data");
|
|
3873
|
+
return client.patch(`/manage/x/accounts/${accountId}`, {
|
|
3874
|
+
...defaultProfileId ? { defaultProfileId } : {},
|
|
3875
|
+
...defaultPixelId ? { defaultPixelId } : {}
|
|
3876
|
+
});
|
|
3877
|
+
}
|
|
3878
|
+
async function listXCampaigns(client, _args, flags) {
|
|
3879
|
+
validateXReadFlags(flags, CAMPAIGN_LIST_FLAGS4, "manage x campaigns list");
|
|
3880
|
+
const query = buildXListQuery(flags, ["campaign-ids"]);
|
|
3881
|
+
return client.get(`/manage/x/campaigns${query}`);
|
|
3882
|
+
}
|
|
3883
|
+
async function getXCampaign(client, args, flags) {
|
|
3884
|
+
validateXReadFlags(flags, [], "manage x campaigns get");
|
|
3885
|
+
const campaignId = requireArg(args, 0, "campaign-id", "Run: adkit manage x campaigns get <campaign-id>");
|
|
3886
|
+
const query = queryString({ accountId: typeof flags.account === "string" ? flags.account : void 0 });
|
|
3887
|
+
return client.get(`/manage/x/campaigns/${campaignId}${query}`);
|
|
3888
|
+
}
|
|
3889
|
+
async function listXAdGroups(client, _args, flags) {
|
|
3890
|
+
validateXReadFlags(flags, AD_GROUP_LIST_FLAGS4, "manage x ad-groups list");
|
|
3891
|
+
const query = buildXListQuery(flags, ["campaign-ids", "ad-group-ids"]);
|
|
3892
|
+
return client.get(`/manage/x/ad-groups${query}`);
|
|
3893
|
+
}
|
|
3894
|
+
async function getXAdGroup(client, args, flags) {
|
|
3895
|
+
validateXReadFlags(flags, [], "manage x ad-groups get");
|
|
3896
|
+
const adGroupId = requireArg(args, 0, "ad-group-id", "Run: adkit manage x ad-groups get <ad-group-id>");
|
|
3897
|
+
const query = queryString({ accountId: typeof flags.account === "string" ? flags.account : void 0 });
|
|
3898
|
+
return client.get(`/manage/x/ad-groups/${adGroupId}${query}`);
|
|
3899
|
+
}
|
|
3900
|
+
async function listXAds(client, _args, flags) {
|
|
3901
|
+
validateXReadFlags(flags, AD_LIST_FLAGS4, "manage x ads list");
|
|
3902
|
+
const query = buildXListQuery(flags, ["campaign-ids", "ad-group-ids", "ad-ids"]);
|
|
3903
|
+
return client.get(`/manage/x/ads${query}`);
|
|
3904
|
+
}
|
|
3905
|
+
async function getXAd(client, args, flags) {
|
|
3906
|
+
validateXReadFlags(flags, [], "manage x ads get");
|
|
3907
|
+
const adId = requireArg(args, 0, "ad-id", "Run: adkit manage x ads get <ad-id>");
|
|
3908
|
+
const query = queryString({ accountId: typeof flags.account === "string" ? flags.account : void 0 });
|
|
3909
|
+
return client.get(`/manage/x/ads/${adId}${query}`);
|
|
3910
|
+
}
|
|
3911
|
+
async function listXResults(client, _args, flags) {
|
|
3912
|
+
validateXReadFlags(flags, RESULTS_FLAGS3, "manage x results");
|
|
3913
|
+
const level = requireFlag(flags, "level", "Use --level campaigns, ad-groups, or ads");
|
|
3914
|
+
const from = requireFlag(flags, "from", "Use --from YYYY-MM-DD with --to");
|
|
3915
|
+
const to = requireFlag(flags, "to", "Use --to YYYY-MM-DD with --from");
|
|
3916
|
+
const raw = flags.raw === true ? "true" : typeof flags.raw === "string" ? flags.raw : void 0;
|
|
3917
|
+
const query = queryString({
|
|
3918
|
+
accountId: typeof flags.account === "string" ? flags.account : void 0,
|
|
3919
|
+
level,
|
|
3920
|
+
from,
|
|
3921
|
+
to,
|
|
3922
|
+
fields: typeof flags.fields === "string" ? flags.fields : void 0,
|
|
3923
|
+
breakdowns: typeof flags.breakdowns === "string" ? flags.breakdowns : void 0,
|
|
3924
|
+
campaignIds: typeof flags["campaign-ids"] === "string" ? flags["campaign-ids"] : void 0,
|
|
3925
|
+
adGroupIds: typeof flags["ad-group-ids"] === "string" ? flags["ad-group-ids"] : void 0,
|
|
3926
|
+
adIds: typeof flags["ad-ids"] === "string" ? flags["ad-ids"] : void 0,
|
|
3927
|
+
sort: typeof flags.sort === "string" ? flags.sort : void 0,
|
|
3928
|
+
sortDirection: typeof flags["sort-direction"] === "string" ? flags["sort-direction"] : void 0,
|
|
3929
|
+
limit: typeof flags.limit === "string" ? flags.limit : void 0,
|
|
3930
|
+
offset: typeof flags.offset === "string" ? flags.offset : void 0,
|
|
3931
|
+
raw
|
|
3932
|
+
});
|
|
3933
|
+
return client.get(`/manage/x/results${query}`);
|
|
3934
|
+
}
|
|
3935
|
+
function buildXListQuery(flags, filterKeys) {
|
|
3936
|
+
return queryString({
|
|
3937
|
+
accountId: typeof flags.account === "string" ? flags.account : void 0,
|
|
3938
|
+
campaignIds: filterKeys.includes("campaign-ids") && typeof flags["campaign-ids"] === "string" ? flags["campaign-ids"] : void 0,
|
|
3939
|
+
adGroupIds: filterKeys.includes("ad-group-ids") && typeof flags["ad-group-ids"] === "string" ? flags["ad-group-ids"] : void 0,
|
|
3940
|
+
adIds: filterKeys.includes("ad-ids") && typeof flags["ad-ids"] === "string" ? flags["ad-ids"] : void 0,
|
|
3941
|
+
status: typeof flags.status === "string" ? flags.status : void 0,
|
|
3942
|
+
limit: typeof flags.limit === "string" ? flags.limit : void 0,
|
|
3943
|
+
offset: typeof flags.offset === "string" ? flags.offset : void 0
|
|
3944
|
+
});
|
|
3945
|
+
}
|
|
3946
|
+
function validateXReadFlags(flags, allowed, command) {
|
|
3947
|
+
const permitted = /* @__PURE__ */ new Set(["account", "json", "project", ...allowed]);
|
|
3948
|
+
const unsupported = Object.keys(flags).filter((flag) => !permitted.has(flag));
|
|
3949
|
+
if (!unsupported.length) return;
|
|
3950
|
+
const renderedFlags = unsupported.map((flag) => `--${flag}`).join(", ");
|
|
3951
|
+
throw new CliError("UNKNOWN_FLAG", `Unknown flag${unsupported.length > 1 ? "s" : ""}: ${renderedFlags}`, `Run: adkit ${command} --help`);
|
|
3952
|
+
}
|
|
3953
|
+
|
|
3804
3954
|
// src/commands/microsoft.ts
|
|
3955
|
+
async function listMicrosoftAccounts(client, _args, flags) {
|
|
3956
|
+
validateFlags(flags, [], "manage microsoft accounts list");
|
|
3957
|
+
return client.get("/manage/microsoft/accounts");
|
|
3958
|
+
}
|
|
3805
3959
|
async function listMicrosoftAvailableAccounts(client, _args, flags) {
|
|
3806
3960
|
validateFlags(flags, [], "manage microsoft accounts available");
|
|
3807
3961
|
return client.get("/manage/microsoft/accounts/available");
|
|
3808
3962
|
}
|
|
3963
|
+
async function connectMicrosoftAccount(client, args, flags) {
|
|
3964
|
+
validateFlags(flags, ["integration"], "manage microsoft accounts connect");
|
|
3965
|
+
const accountId = requireArg(args, 0, "account-id", "Run: adkit manage microsoft accounts connect <account-id>");
|
|
3966
|
+
const integrationId = typeof flags.integration === "string" ? flags.integration : void 0;
|
|
3967
|
+
return client.post("/manage/microsoft/accounts/connect", { accountId, ...integrationId ? { integrationId } : {} });
|
|
3968
|
+
}
|
|
3969
|
+
async function disconnectMicrosoftAccount(client, args, flags) {
|
|
3970
|
+
validateFlags(flags, [], "manage microsoft accounts disconnect");
|
|
3971
|
+
const accountId = requireArg(args, 0, "account-id", "Run: adkit manage microsoft accounts disconnect <account-id>");
|
|
3972
|
+
return client.delete(`/manage/microsoft/accounts/${accountId}`);
|
|
3973
|
+
}
|
|
3809
3974
|
|
|
3810
3975
|
// src/commands/status.ts
|
|
3976
|
+
var STATUS_PLATFORMS = [
|
|
3977
|
+
["meta", "Meta"],
|
|
3978
|
+
["google", "Google"],
|
|
3979
|
+
["tiktok", "TikTok"],
|
|
3980
|
+
["reddit", "Reddit"],
|
|
3981
|
+
["x", "X"],
|
|
3982
|
+
["linkedin", "LinkedIn"],
|
|
3983
|
+
["microsoft", "Microsoft"]
|
|
3984
|
+
];
|
|
3985
|
+
var DEFAULT_LABELS = {
|
|
3986
|
+
identityId: "Identity",
|
|
3987
|
+
identityType: "Identity type",
|
|
3988
|
+
instagramUserId: "Instagram user",
|
|
3989
|
+
pageId: "Page",
|
|
3990
|
+
pixelId: "Pixel",
|
|
3991
|
+
profileId: "Profile"
|
|
3992
|
+
};
|
|
3811
3993
|
function isStatusResponse(value) {
|
|
3812
3994
|
if (!value || typeof value !== "object") return false;
|
|
3813
3995
|
if (!("connected" in value) || typeof value.connected !== "boolean") return false;
|
|
@@ -3821,28 +4003,32 @@ async function status(client, json) {
|
|
|
3821
4003
|
if (!isStatusResponse(response)) throw new Error("Unexpected response from AdKit status");
|
|
3822
4004
|
const data = response;
|
|
3823
4005
|
if (json) {
|
|
3824
|
-
|
|
4006
|
+
const serializedStatus = JSON.stringify(data);
|
|
4007
|
+
console.log(serializedStatus);
|
|
3825
4008
|
return;
|
|
3826
4009
|
}
|
|
3827
4010
|
const { project, platforms } = data;
|
|
3828
4011
|
const lines = [];
|
|
3829
4012
|
lines.push(`Project: ${project.name || "unnamed"} (${project.id})`);
|
|
3830
4013
|
lines.push("");
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
lines.push(
|
|
3835
|
-
|
|
3836
|
-
|
|
4014
|
+
for (const [platformKey, label] of STATUS_PLATFORMS) {
|
|
4015
|
+
const platform2 = platforms[platformKey];
|
|
4016
|
+
if (!platform2.connected) {
|
|
4017
|
+
lines.push(`${label}: not connected`);
|
|
4018
|
+
continue;
|
|
4019
|
+
}
|
|
4020
|
+
lines.push(`${label}: connected`);
|
|
4021
|
+
for (const account of platform2.accounts) {
|
|
4022
|
+
const details = [account.currency, account.status].filter(Boolean).join(", ");
|
|
4023
|
+
lines.push(` ${account.id}${account.name ? ` \u2014 ${account.name}` : ""}${details ? ` (${details})` : ""}`);
|
|
4024
|
+
const defaults = Object.entries(account.defaults ?? {});
|
|
4025
|
+
for (const [key, value] of defaults) if (value) lines.push(` ${DEFAULT_LABELS[key] ?? key}: ${value}`);
|
|
3837
4026
|
}
|
|
3838
|
-
}
|
|
3839
|
-
if (platforms.google.connected) {
|
|
3840
|
-
lines.push("Google: connected");
|
|
3841
|
-
for (const a of platforms.google.accounts) lines.push(` ${a.id} \u2014 ${a.name} (${a.currency})`);
|
|
3842
|
-
} else lines.push("Google: not connected");
|
|
4027
|
+
}
|
|
3843
4028
|
lines.push("");
|
|
3844
4029
|
lines.push(data.instructions);
|
|
3845
|
-
|
|
4030
|
+
const output = lines.join("\n");
|
|
4031
|
+
console.log(output);
|
|
3846
4032
|
}
|
|
3847
4033
|
|
|
3848
4034
|
// src/commands/drafts.ts
|
|
@@ -3925,15 +4111,17 @@ function readStringFlag(flags, key) {
|
|
|
3925
4111
|
const value = flags[key];
|
|
3926
4112
|
return typeof value === "string" ? value : void 0;
|
|
3927
4113
|
}
|
|
4114
|
+
var ADVERTISER_FILTER_FLAGS = ["industry", "category", "platform", "sort", "limit", "page"];
|
|
3928
4115
|
async function listAdvertisers(client, _args, flags) {
|
|
3929
|
-
|
|
3930
|
-
const
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
const
|
|
3936
|
-
|
|
4116
|
+
validateFlags(flags, ADVERTISER_FILTER_FLAGS, "library advertisers list");
|
|
4117
|
+
const qs = buildAdvertiserQueryString(flags);
|
|
4118
|
+
return client.get(`/library/advertisers${qs}`);
|
|
4119
|
+
}
|
|
4120
|
+
async function searchAdvertisers(client, args, flags) {
|
|
4121
|
+
validateFlags(flags, ADVERTISER_FILTER_FLAGS, "library advertisers search");
|
|
4122
|
+
const query = args.join(" ").trim();
|
|
4123
|
+
if (!query) throw new CliError("MISSING_ARGUMENT", "Missing advertiser search query", "Run: adkit library advertisers search <query>");
|
|
4124
|
+
const qs = buildAdvertiserQueryString(flags, query);
|
|
3937
4125
|
return client.get(`/library/advertisers${qs}`);
|
|
3938
4126
|
}
|
|
3939
4127
|
async function getAdvertiser(client, args, _flags) {
|
|
@@ -3965,6 +4153,15 @@ async function addAdvertiser(client, _args, flags) {
|
|
|
3965
4153
|
linkedInLibraryUrl: linkedinLibrary
|
|
3966
4154
|
});
|
|
3967
4155
|
}
|
|
4156
|
+
function buildAdvertiserQueryString(flags, query) {
|
|
4157
|
+
const industry = typeof flags.industry === "string" ? flags.industry : void 0;
|
|
4158
|
+
const category = typeof flags.category === "string" ? flags.category : void 0;
|
|
4159
|
+
const platform2 = typeof flags.platform === "string" ? flags.platform : void 0;
|
|
4160
|
+
const sort = typeof flags.sort === "string" ? flags.sort : "name";
|
|
4161
|
+
const limit = typeof flags.limit === "string" ? flags.limit : void 0;
|
|
4162
|
+
const page = typeof flags.page === "string" ? flags.page : void 0;
|
|
4163
|
+
return queryString3({ industry, category, platform: platform2, query, sort, limit, page });
|
|
4164
|
+
}
|
|
3968
4165
|
async function listLibraryAds(client, _args, flags) {
|
|
3969
4166
|
const platform2 = readStringFlag(flags, "platform");
|
|
3970
4167
|
const status2 = readStringFlag(flags, "status");
|
|
@@ -4850,7 +5047,9 @@ Manage platforms:
|
|
|
4850
5047
|
google Google Ads
|
|
4851
5048
|
tiktok TikTok Ads
|
|
4852
5049
|
reddit Reddit Ads
|
|
5050
|
+
x X Ads
|
|
4853
5051
|
linkedin LinkedIn Ads
|
|
5052
|
+
microsoft Microsoft Ads
|
|
4854
5053
|
drafts Drafts across platforms
|
|
4855
5054
|
|
|
4856
5055
|
Run \`adkit <command> --help\` for details.
|
|
@@ -5499,7 +5698,7 @@ Flags (create):
|
|
|
5499
5698
|
--name <name> Campaign name (required)
|
|
5500
5699
|
--status <s> enabled, paused (default: paused)
|
|
5501
5700
|
--budget-daily <n> Daily budget in account currency
|
|
5502
|
-
--campaign-type <type> search, display, performance_max (performance_max needs --data for asset groups)
|
|
5701
|
+
--campaign-type <type> search, display, demand_gen, performance_max (performance_max needs --data for asset groups)
|
|
5503
5702
|
--bid-strategy <s> manual_cpc, manual_cpm, maximize_clicks, maximize_conversions, maximize_conversion_value, target_spend, target_impression_share
|
|
5504
5703
|
--target-cpa <n> Target CPA in account currency (with --bid-strategy maximize_conversions)
|
|
5505
5704
|
--target-roas <n> Target ROAS ratio, e.g. 3.5 = 350% (with --bid-strategy maximize_conversion_value)
|
|
@@ -5527,6 +5726,7 @@ Note:
|
|
|
5527
5726
|
For cities, regions, postal codes, and metros, run google geo-locations search and use the returned bare code.
|
|
5528
5727
|
Advanced geo examples: adkit manage google campaigns --help full
|
|
5529
5728
|
Display creation starts here: campaigns create --campaign-type display, then create Display ad groups and responsive_display ads.
|
|
5729
|
+
Demand Gen uses two calls: create the campaign, then create its ad group with the returned campaign ID. Geo and YouTube networks belong on the ad group.
|
|
5530
5730
|
Performance Max: campaignType "performance_max" with asset groups \u2014 build it with --data. See --help full.
|
|
5531
5731
|
conversion-goals chooses what to optimize toward; bid-strategy controls bidding. It works on Search, Display, and Performance Max.
|
|
5532
5732
|
On create, omit conversion-goals to inherit account defaults. On update, omit it to leave goals unchanged or pass null to restore defaults.
|
|
@@ -5538,6 +5738,7 @@ Examples:
|
|
|
5538
5738
|
adkit manage google campaigns list --account 1234567890
|
|
5539
5739
|
adkit manage google campaigns create --name "Display Prospecting" --campaign-type display --budget-daily 50 --account 1234567890
|
|
5540
5740
|
adkit manage google campaigns create --name "Purchase Search" --campaign-type search --budget-daily 40 --bid-strategy maximize_conversion_value --conversion-goals purchase --account 1234567890
|
|
5741
|
+
adkit manage google campaigns create --name "YouTube Launch" --campaign-type demand_gen --budget-daily 40 --bid-strategy maximize_conversions --account 1234567890
|
|
5541
5742
|
adkit manage google campaigns create --data '{"campaigns":[{"name":"Display US","campaignType":"display","budget":{"daily":50},"targeting":{"geoLocations":{"include":[{"type":"country","country":"US"}]}}}]}' --account 1234567890
|
|
5542
5743
|
adkit manage google campaigns update 987654321 --data '{"targeting":{"geoLocations":{"match":"presence_or_interest"}}}' --account 1234567890
|
|
5543
5744
|
adkit manage google campaigns update 987654321 --status paused --account 1234567890
|
|
@@ -5557,7 +5758,7 @@ Flags (create):
|
|
|
5557
5758
|
--name <name> Campaign name (required)
|
|
5558
5759
|
--status <s> enabled, paused (default: paused)
|
|
5559
5760
|
--budget-daily <n> Daily budget in account currency
|
|
5560
|
-
--campaign-type <type> search, display, performance_max (performance_max needs --data for asset groups)
|
|
5761
|
+
--campaign-type <type> search, display, demand_gen, performance_max (performance_max needs --data for asset groups)
|
|
5561
5762
|
--bid-strategy <s> manual_cpc, manual_cpm, maximize_clicks, maximize_conversions, maximize_conversion_value, target_spend, target_impression_share
|
|
5562
5763
|
--conversion-goals <list> Comma-separated goal categories or conversion-action IDs; never mix them
|
|
5563
5764
|
${FLAG.account}
|
|
@@ -5588,6 +5789,7 @@ Notes:
|
|
|
5588
5789
|
platformLocation accepts exact Google geo objects for advanced criteria and existing raw values.
|
|
5589
5790
|
Google exclusions support named locations. Proximity circles belong in include.
|
|
5590
5791
|
Display campaign creation uses campaignType:"display" or --campaign-type display. Videos are not supported for Display media in v1.
|
|
5792
|
+
Demand Gen: create campaignType:"demand_gen" first. Then use the returned campaign ID in a separate ad-groups create call with targeting.geoLocations and targeting.inventory.networks.
|
|
5591
5793
|
Performance Max: campaignType "performance_max" requires at least one assetGroups[] entry and supports multiple groups. Text assets are inline ({role,text}); upload images with google media first, then reference their id/resourceName. Needs an enabled conversion (publish is blocked without one). Non-retail only. Build it with --data.
|
|
5592
5794
|
conversion-goals works on Search, Display, and Performance Max. It chooses what to optimize toward; bid-strategy controls bidding.
|
|
5593
5795
|
Valid goal categories: ${GOOGLE_CAMPAIGN_CONVERSION_GOAL_CATEGORY_LIST}
|
|
@@ -5600,6 +5802,7 @@ Examples:
|
|
|
5600
5802
|
adkit manage google campaigns list --account 1234567890
|
|
5601
5803
|
adkit manage google campaigns create --name "Display Prospecting" --campaign-type display --budget-daily 50 --account 1234567890
|
|
5602
5804
|
adkit manage google campaigns create --name "Purchase Search" --campaign-type search --budget-daily 40 --bid-strategy maximize_conversion_value --conversion-goals purchase --account 1234567890
|
|
5805
|
+
adkit manage google campaigns create --name "YouTube Launch" --campaign-type demand_gen --budget-daily 40 --bid-strategy maximize_conversions --account 1234567890
|
|
5603
5806
|
adkit manage google campaigns create --data '{"campaigns":[{"name":"Display US","campaignType":"display","budget":{"daily":50},"targeting":{"geoLocations":{"include":[{"type":"country","country":"US"}]}}}]}' --account 1234567890
|
|
5604
5807
|
adkit manage google campaigns create --data '{"campaigns":[{"name":"Austin Search","campaignType":"search","budget":{"daily":5},"targeting":{"geoLocations":{"include":[{"type":"city","code":"1026201"}]}}}]}' --account 1234567890
|
|
5605
5808
|
adkit manage google campaigns create --data '{"campaigns":[{"name":"SF Radius","campaignType":"search","budget":{"daily":5},"targeting":{"geoLocations":{"include":[{"type":"platformLocation","platform":"google","value":{"proximity":{"geo_point":{"latitude_in_micro_degrees":37774900,"longitude_in_micro_degrees":-122419400},"radius":5,"radius_units":"MILES"}}}]}}}]}' --account 1234567890
|
|
@@ -5683,6 +5886,7 @@ Flags (list):
|
|
|
5683
5886
|
|
|
5684
5887
|
Note:
|
|
5685
5888
|
Display targeting is created or updated with --data using targeting.audience.interests, targeting.audience.customAudiences, targeting.content.topics, and targeting.content.websites.
|
|
5889
|
+
Demand Gen creation uses --data with targeting.geoLocations and targeting.inventory.networks (youtube_in_feed, youtube_in_stream, youtube_shorts). These fields are create-only.
|
|
5686
5890
|
Topic and interest search return IDs used in Display targeting: adkit manage google research topics <query>, adkit manage google research interests <query>.
|
|
5687
5891
|
WARNING: On update, targeting replaces the full Display targeting set. If you send only one new website, old websites/topics/audiences can be removed.
|
|
5688
5892
|
list returns ad group configuration only (name, status, bid).
|
|
@@ -5692,6 +5896,7 @@ Examples:
|
|
|
5692
5896
|
adkit manage google ad-groups list --account 1234567890 --campaign 987654321
|
|
5693
5897
|
adkit manage google ad-groups create --campaign 987654321 --name "Brand Terms" --cpc-bid 2 --account 1234567890
|
|
5694
5898
|
adkit manage google ad-groups create --data '{"adGroups":[{"campaignId":"987654321","name":"Display SaaS","targeting":{"audience":{"interests":{"include":["804"]},"customAudiences":{"include":["123456789"]}},"content":{"topics":{"include":["3"]},"websites":{"include":["https://example.com"],"exclude":["https://bad.example.com"]}}}}]}' --account 1234567890
|
|
5899
|
+
adkit manage google ad-groups create --data '{"adGroups":[{"campaignId":"987654321","name":"YouTube US","targeting":{"geoLocations":{"include":[{"type":"country","country":"US"}]},"inventory":{"networks":["youtube_in_feed","youtube_in_stream","youtube_shorts"]}}}]}' --account 1234567890
|
|
5695
5900
|
adkit manage google ad-groups update 555666777 --data '{"targeting":{"content":{"websites":{"include":["https://example.com","https://nytimes.com"]}}}}' --account 1234567890
|
|
5696
5901
|
adkit manage google ad-groups update 555666777 --status paused --account 1234567890
|
|
5697
5902
|
adkit manage google ad-groups delete 555666777 --account 1234567890
|
|
@@ -5727,6 +5932,7 @@ Notes:
|
|
|
5727
5932
|
Topic and interest search return IDs used in targeting: research topics -> targeting.content.topics; research interests -> targeting.audience.interests.
|
|
5728
5933
|
Websites use direct URLs in targeting.content.websites.include/exclude. Bare numeric customAudiences IDs are treated as Google user lists (remarketing/Customer Match); for custom segments, pass a customAudiences/{id} or customers/{accountId}/customAudiences/{id} resource path.
|
|
5729
5934
|
WARNING: On update, targeting replaces the full Display targeting set. If you send only one new website, old websites/topics/audiences can be removed.
|
|
5935
|
+
Demand Gen creation uses targeting.geoLocations plus targeting.inventory.networks (youtube_in_feed, youtube_in_stream, youtube_shorts). Omit targeting.inventory for Google's default channels. Demand Gen geo/inventory updates are not supported yet.
|
|
5730
5936
|
list returns ad group configuration only (name, status, bid).
|
|
5731
5937
|
For spend/clicks/conversions: adkit manage google results
|
|
5732
5938
|
|
|
@@ -5735,6 +5941,7 @@ Examples:
|
|
|
5735
5941
|
adkit manage google ad-groups create --campaign 987654321 --name "Brand Terms" --cpc-bid 2 --account 1234567890
|
|
5736
5942
|
adkit manage google ad-groups create --data '{"adGroups":[{"campaignId":"987654321","name":"Brand Terms","cpcBid":2}]}' --account 1234567890
|
|
5737
5943
|
adkit manage google ad-groups create --data '{"adGroups":[{"campaignId":"987654321","name":"Display SaaS","targeting":{"audience":{"interests":{"include":["804"]},"customAudiences":{"include":["123456789"]}},"content":{"topics":{"include":["3"]},"websites":{"include":["https://example.com"],"exclude":["https://bad.example.com"]}}}}]}' --account 1234567890
|
|
5944
|
+
adkit manage google ad-groups create --data '{"adGroups":[{"campaignId":"987654321","name":"YouTube US","targeting":{"geoLocations":{"include":[{"type":"country","country":"US"}]},"inventory":{"networks":["youtube_in_feed","youtube_in_stream","youtube_shorts"]}}}]}' --account 1234567890
|
|
5738
5945
|
adkit manage google ad-groups update 555666777 --data '{"targeting":{"content":{"websites":{"include":["https://example.com","https://nytimes.com"]}}}}' --account 1234567890 --publish
|
|
5739
5946
|
adkit manage google ad-groups update 555666777 --status paused --account 1234567890
|
|
5740
5947
|
adkit manage google ad-groups delete 555666777 --account 1234567890 --publish`;
|
|
@@ -5754,7 +5961,7 @@ Flags (create/update):
|
|
|
5754
5961
|
--sitelink <value> Repeatable. Use asset:<id> or "text|url"
|
|
5755
5962
|
--final-url <url> Landing page URL
|
|
5756
5963
|
--path <text> Optional display URL path as segment-one[/segment-two]
|
|
5757
|
-
--status <s> enabled, paused, removed
|
|
5964
|
+
--status <s> Legacy Search/Display: enabled, paused, removed. Demand Gen JSON: active, paused
|
|
5758
5965
|
|
|
5759
5966
|
Flags (list):
|
|
5760
5967
|
--campaign <ids> Filter by Google campaign IDs (platformId, comma-separated)
|
|
@@ -5765,8 +5972,10 @@ Flags (list):
|
|
|
5765
5972
|
--offset <n> Pagination offset
|
|
5766
5973
|
|
|
5767
5974
|
Notes:
|
|
5768
|
-
Google enforces strict character limits:
|
|
5975
|
+
Google enforces strict character limits: Search/Display headlines max 30 chars, Demand Gen video headlines max 40 chars, descriptions and long headlines max 90 chars, business name max 25 chars, and each path segment max 15 chars.
|
|
5769
5976
|
update is a partial object update. If --data sends an array field, it replaces that whole array; omitted arrays stay unchanged.
|
|
5977
|
+
Demand Gen video ads use --data with adType:"demand_gen_video_responsive" and creative:{ headlines, longHeadlines, descriptions, businessName, finalUrls, media }.
|
|
5978
|
+
First run google media upload with the videoId from the YouTube URL. Then run google ads create with the returned video asset ID and an existing Google logo asset ID.
|
|
5770
5979
|
Display ads use --data with type:"responsive_display", headlines, longHeadline, descriptions, businessName, finalUrls, and media.
|
|
5771
5980
|
Display media roles: marketing_image, square_marketing_image, logo, square_logo.
|
|
5772
5981
|
Upload images first with google media upload, then use returned Google asset id/resourceName in media[].id.
|
|
@@ -5779,6 +5988,7 @@ Notes:
|
|
|
5779
5988
|
Examples:
|
|
5780
5989
|
adkit manage google ads list --account 1234567890 --campaign 987654321,987654322
|
|
5781
5990
|
adkit manage google ads list --account 1234567890 --ad-group 555666777
|
|
5991
|
+
adkit manage google ads create --data '{"ads":[{"adType":"demand_gen_video_responsive","adGroupId":"555666777","creative":{"headlines":[{"text":"Make every launch count"}],"longHeadlines":[{"text":"Turn your next product launch into lasting demand"}],"descriptions":[{"text":"Reach the right buyers with a focused product story."}],"businessName":"AdKit","finalUrls":["https://example.com/demand-gen"],"media":[{"role":"video","id":"customers/1234567890/assets/501"},{"role":"logo","id":"customers/1234567890/assets/502"}]},"status":"paused"}]}' --account 1234567890
|
|
5782
5992
|
adkit manage google ads create --data '{"ads":[{"type":"responsive_display","adGroupId":"555666777","headlines":[{"text":"Display headline"}],"longHeadline":{"text":"A longer Display headline"},"descriptions":[{"text":"Display description"}],"businessName":"AdKit","finalUrls":["https://example.com"],"media":[{"role":"marketing_image","id":"customers/123/assets/111"},{"role":"square_marketing_image","id":"customers/123/assets/222"},{"role":"logo","id":"customers/123/assets/333"},{"role":"square_logo","id":"customers/123/assets/444"}]}]}' --account 1234567890
|
|
5783
5993
|
adkit manage google ads create --ad-group 555666777 \\
|
|
5784
5994
|
--headline-1 "CRM for B2B SaaS" --headline-2 "Close More Deals" --headline "Book a Demo" \\
|
|
@@ -5807,7 +6017,7 @@ Flags (create/update):
|
|
|
5807
6017
|
--sitelink <value> Repeatable. Use asset:<id> or "text|url"
|
|
5808
6018
|
--final-url <url> Landing page URL
|
|
5809
6019
|
--path <text> Optional display URL path as segment-one[/segment-two]
|
|
5810
|
-
--status <s> enabled, paused, removed
|
|
6020
|
+
--status <s> Legacy Search/Display: enabled, paused, removed. Demand Gen JSON: active, paused
|
|
5811
6021
|
${FLAG.account}
|
|
5812
6022
|
${FLAG.publish}
|
|
5813
6023
|
${FLAG.data}
|
|
@@ -5821,8 +6031,10 @@ Flags (list):
|
|
|
5821
6031
|
--offset <n> Pagination offset
|
|
5822
6032
|
|
|
5823
6033
|
Notes:
|
|
5824
|
-
Google enforces strict character limits:
|
|
6034
|
+
Google enforces strict character limits: Search/Display headlines max 30 chars, Demand Gen video headlines max 40 chars, descriptions and long headlines max 90 chars, business name max 25 chars, and each path segment max 15 chars.
|
|
5825
6035
|
update is a partial object update. If --data sends an array field, it replaces that whole array; omitted arrays stay unchanged.
|
|
6036
|
+
Demand Gen video ads use --data with adType:"demand_gen_video_responsive" and creative:{ headlines, longHeadlines, descriptions, businessName, finalUrls, media }.
|
|
6037
|
+
First run google media upload with the videoId from the YouTube URL. Then run google ads create with the returned video asset ID and an existing Google logo asset ID.
|
|
5826
6038
|
Display ads use type:"responsive_display", headlines, longHeadline, descriptions, businessName, finalUrls, and media.
|
|
5827
6039
|
Display media roles: marketing_image, square_marketing_image, logo, square_logo.
|
|
5828
6040
|
For Display updates, omitted media is preserved. If --data includes media, that media array replaces all Display media.
|
|
@@ -5837,6 +6049,7 @@ Notes:
|
|
|
5837
6049
|
Examples:
|
|
5838
6050
|
adkit manage google ads list --account 1234567890 --campaign 987654321,987654322
|
|
5839
6051
|
adkit manage google ads list --account 1234567890 --ad-group 555666777
|
|
6052
|
+
adkit manage google ads create --data '{"ads":[{"adType":"demand_gen_video_responsive","adGroupId":"555666777","creative":{"headlines":[{"text":"Make every launch count"}],"longHeadlines":[{"text":"Turn your next product launch into lasting demand"}],"descriptions":[{"text":"Reach the right buyers with a focused product story."}],"businessName":"AdKit","finalUrls":["https://example.com/demand-gen"],"media":[{"role":"video","id":"customers/1234567890/assets/501"},{"role":"logo","id":"customers/1234567890/assets/502"}]},"status":"paused"}]}' --account 1234567890
|
|
5840
6053
|
adkit manage google ads create --data '{"ads":[{"type":"responsive_display","adGroupId":"555666777","headlines":[{"text":"Display headline"}],"longHeadline":{"text":"A longer Display headline"},"descriptions":[{"text":"Display description"}],"businessName":"AdKit","finalUrls":["https://example.com"],"media":[{"role":"marketing_image","id":"customers/123/assets/111"},{"role":"square_marketing_image","id":"customers/123/assets/222"},{"role":"logo","id":"customers/123/assets/333"},{"role":"square_logo","id":"customers/123/assets/444"}]}]}' --account 1234567890
|
|
5841
6054
|
adkit manage google ads create --ad-group 555666777 \\
|
|
5842
6055
|
--headline-1 "CRM for B2B SaaS" --headline-2 "Close More Deals" --headline "Book a Demo" \\
|
|
@@ -6724,9 +6937,9 @@ Platforms:
|
|
|
6724
6937
|
google Google Ads
|
|
6725
6938
|
tiktok TikTok Ads
|
|
6726
6939
|
reddit Reddit Ads
|
|
6727
|
-
x X
|
|
6940
|
+
x X account setup plus read-only campaigns, ad groups, ads, and results
|
|
6728
6941
|
linkedin LinkedIn Ads (campaigns, ad groups, ads, targeting, results)
|
|
6729
|
-
microsoft Microsoft Ads raw API access + account
|
|
6942
|
+
microsoft Microsoft Ads raw API access + account setup
|
|
6730
6943
|
drafts Manage drafts across platforms
|
|
6731
6944
|
platform-api-requests Send raw API requests (escape hatch)
|
|
6732
6945
|
|
|
@@ -6812,18 +7025,122 @@ General flags:
|
|
|
6812
7025
|
${FLAG.account}
|
|
6813
7026
|
|
|
6814
7027
|
Run adkit manage reddit accounts --help for account setup.`.trim(),
|
|
6815
|
-
x: `adkit manage x \u2014 X Ads
|
|
7028
|
+
x: `adkit manage x \u2014 X Ads management
|
|
6816
7029
|
|
|
6817
|
-
|
|
6818
|
-
|
|
7030
|
+
Entity groups:
|
|
7031
|
+
accounts Connect accounts and configure profile/pixel defaults
|
|
7032
|
+
campaigns List/get campaigns
|
|
7033
|
+
ad-groups List/get line items as ad groups
|
|
7034
|
+
ads List/get promoted-Post associations
|
|
7035
|
+
results Campaign, ad-group, and ad performance
|
|
6819
7036
|
|
|
6820
7037
|
${rawPlatformCommandLine("x")}
|
|
6821
7038
|
|
|
6822
7039
|
Examples:
|
|
6823
|
-
adkit manage
|
|
6824
|
-
|
|
7040
|
+
adkit manage x accounts available
|
|
7041
|
+
adkit manage x campaigns list --account 18ce54d4x5t
|
|
7042
|
+
adkit manage x results --level ads --from 2026-08-01 --to 2026-08-07 --account 18ce54d4x5t`.trim(),
|
|
7043
|
+
"x accounts": `adkit manage x accounts \u2014 X Ads accounts
|
|
7044
|
+
|
|
7045
|
+
list List connected X ad accounts
|
|
7046
|
+
available List discoverable X ad accounts
|
|
7047
|
+
connect <id> Connect an X ad account
|
|
7048
|
+
disconnect <id> Disconnect an X ad account
|
|
7049
|
+
<id> profiles List X identities available to an account
|
|
7050
|
+
<id> pixels List X conversion pixels available to an account
|
|
7051
|
+
<id> update Save default identity and pixel IDs
|
|
7052
|
+
|
|
7053
|
+
Flags:
|
|
7054
|
+
--integration <id> Connect using a specific workspace login returned by available
|
|
7055
|
+
--default-profile <id> Default X identity for ad creation
|
|
7056
|
+
--default-pixel <id> Default X conversion pixel
|
|
7057
|
+
--data <json> Full update body; also supports null to clear a default
|
|
7058
|
+
|
|
7059
|
+
Examples:
|
|
7060
|
+
adkit manage x accounts list
|
|
7061
|
+
adkit manage x accounts available
|
|
7062
|
+
adkit manage x accounts connect 18ce54d4x5t
|
|
7063
|
+
adkit manage x accounts 18ce54d4x5t profiles
|
|
7064
|
+
adkit manage x accounts 18ce54d4x5t pixels
|
|
7065
|
+
adkit manage x accounts 18ce54d4x5t update --default-profile 12abc --default-pixel p123
|
|
7066
|
+
adkit manage x accounts disconnect 18ce54d4x5t`.trim(),
|
|
7067
|
+
"x campaigns": `adkit manage x campaigns \u2014 X Ads campaigns (read-only)
|
|
7068
|
+
|
|
7069
|
+
list List campaigns
|
|
7070
|
+
get <id> Get one campaign
|
|
7071
|
+
|
|
7072
|
+
Flags:
|
|
7073
|
+
${FLAG.account}
|
|
7074
|
+
--campaign-ids <ids> Comma-separated campaign platform IDs
|
|
7075
|
+
--status <s> active, paused, or removed
|
|
7076
|
+
--limit <n> Max rows
|
|
7077
|
+
--offset <n> Row offset
|
|
7078
|
+
|
|
7079
|
+
Example:
|
|
7080
|
+
adkit manage x campaigns list --status active
|
|
7081
|
+
adkit manage x campaigns get 8v7jo`.trim(),
|
|
7082
|
+
"x ad-groups": `adkit manage x ad-groups \u2014 X line items as AdKit ad groups (read-only)
|
|
7083
|
+
|
|
7084
|
+
list List ad groups
|
|
7085
|
+
get <id> Get one ad group
|
|
7086
|
+
|
|
7087
|
+
Flags:
|
|
7088
|
+
${FLAG.account}
|
|
7089
|
+
--campaign-ids <ids> Comma-separated parent campaign IDs
|
|
7090
|
+
--ad-group-ids <ids> Comma-separated line-item IDs
|
|
7091
|
+
--status <s> active, paused, or removed
|
|
7092
|
+
--limit <n> Max rows
|
|
7093
|
+
--offset <n> Row offset
|
|
7094
|
+
|
|
7095
|
+
Example:
|
|
7096
|
+
adkit manage x ad-groups list --campaign-ids 8v7jo`.trim(),
|
|
7097
|
+
"x ads": `adkit manage x ads \u2014 X promoted-Post ads (read-only)
|
|
7098
|
+
|
|
7099
|
+
list List promoted-Post ads
|
|
7100
|
+
get <id> Get one ad by promoted association ID
|
|
7101
|
+
|
|
7102
|
+
Identity:
|
|
7103
|
+
platformId is the promoted association ID used by X Ads and analytics. postId is the underlying Post ID.
|
|
7104
|
+
|
|
7105
|
+
Flags:
|
|
7106
|
+
${FLAG.account}
|
|
7107
|
+
--campaign-ids <ids> Comma-separated parent campaign IDs
|
|
7108
|
+
--ad-group-ids <ids> Comma-separated parent line-item IDs
|
|
7109
|
+
--ad-ids <ids> Comma-separated promoted association IDs
|
|
7110
|
+
--status <s> active, paused, or removed
|
|
7111
|
+
--limit <n> Max rows
|
|
7112
|
+
--offset <n> Row offset
|
|
7113
|
+
|
|
7114
|
+
Example:
|
|
7115
|
+
adkit manage x ads get 1efwlo`.trim(),
|
|
7116
|
+
"x results": `adkit manage x results \u2014 X Ads performance reporting (read-only)
|
|
6825
7117
|
|
|
6826
|
-
|
|
7118
|
+
list List reporting rows (list is optional)
|
|
7119
|
+
|
|
7120
|
+
Flags:
|
|
7121
|
+
${FLAG.account}
|
|
7122
|
+
--level <level> Required: campaigns, ad-groups, or ads
|
|
7123
|
+
--from <date> Required YYYY-MM-DD start
|
|
7124
|
+
--to <date> Required YYYY-MM-DD end
|
|
7125
|
+
--fields <csv> spend, impressions, clicks, ctr, cpc, cpm, outboundClicks, outboundCtr, costPerOutboundClick, videoViews
|
|
7126
|
+
--breakdowns <csv> day only
|
|
7127
|
+
--campaign-ids <ids> Campaign filters
|
|
7128
|
+
--ad-group-ids <ids> Ad-group filters
|
|
7129
|
+
--ad-ids <ids> Promoted association filters for level ads
|
|
7130
|
+
--sort <field> Metric to sort by (default: spend)
|
|
7131
|
+
--sort-direction <d> asc or desc
|
|
7132
|
+
--limit <n> Max rows
|
|
7133
|
+
--offset <n> Row offset
|
|
7134
|
+
--raw Include queried placements in platformDetails
|
|
7135
|
+
|
|
7136
|
+
Note:
|
|
7137
|
+
X does not name one primary Results metric, so rows omit metrics.results and return conversionEvents: {}.
|
|
7138
|
+
|
|
7139
|
+
Example:
|
|
7140
|
+
adkit manage x results --level ads --from 2026-08-01 --to 2026-08-07 --fields spend,impressions,clicks`.trim(),
|
|
7141
|
+
microsoft: `adkit manage microsoft \u2014 Microsoft Ads raw API access + account setup
|
|
7142
|
+
|
|
7143
|
+
Microsoft (Bing) is currently exposed through Advanced Platform Access, plus account assignment.
|
|
6827
7144
|
Endpoints are FULL https URLs to Bing Ads v13 hosts (campaign/reporting/bulk .api.bingads.microsoft.com) \u2014 not relative paths.
|
|
6828
7145
|
Bing REST puts the operation in the HTTP verb; query reads are POST to .../Query* paths.
|
|
6829
7146
|
Because query reads are POST, add --publish on read-only Query* calls \u2014 otherwise you get a draft instead of data.
|
|
@@ -6831,15 +7148,30 @@ Because query reads are POST, add --publish on read-only Query* calls \u2014 oth
|
|
|
6831
7148
|
${rawPlatformCommandLine("microsoft")}
|
|
6832
7149
|
|
|
6833
7150
|
Commands:
|
|
6834
|
-
accounts
|
|
7151
|
+
accounts List, discover, connect, and disconnect Microsoft ad accounts
|
|
6835
7152
|
|
|
6836
7153
|
Examples:
|
|
6837
7154
|
adkit manage microsoft accounts available
|
|
6838
7155
|
adkit manage platform-api-requests --platform microsoft --endpoint "https://campaign.api.bingads.microsoft.com/CampaignManagement/v13/Campaigns/QueryByAccountId" --payload '{"AccountId":187276743,"CampaignType":"Search"}' --request-description "List Microsoft campaigns" --account 187276743 --publish`.trim(),
|
|
7156
|
+
"microsoft accounts": `adkit manage microsoft accounts \u2014 Microsoft Ads accounts
|
|
7157
|
+
|
|
7158
|
+
list List connected Microsoft ad accounts
|
|
7159
|
+
available List discoverable Microsoft ad accounts
|
|
7160
|
+
connect <id> Connect a Microsoft ad account
|
|
7161
|
+
disconnect <id> Disconnect a Microsoft ad account
|
|
7162
|
+
|
|
7163
|
+
Flags:
|
|
7164
|
+
--integration <id> Connect using a specific workspace login returned by available
|
|
7165
|
+
|
|
7166
|
+
Examples:
|
|
7167
|
+
adkit manage microsoft accounts list
|
|
7168
|
+
adkit manage microsoft accounts available
|
|
7169
|
+
adkit manage microsoft accounts connect 187276743
|
|
7170
|
+
adkit manage microsoft accounts disconnect 187276743`.trim(),
|
|
6839
7171
|
linkedin: `adkit manage linkedin \u2014 LinkedIn Ads management
|
|
6840
7172
|
|
|
6841
7173
|
Entity groups:
|
|
6842
|
-
accounts
|
|
7174
|
+
accounts List, discover, connect, and disconnect LinkedIn ad accounts
|
|
6843
7175
|
media Upload creative and fetch media by URN
|
|
6844
7176
|
campaigns Manage campaigns (LinkedIn "Campaign Groups")
|
|
6845
7177
|
ad-groups Manage ad groups (LinkedIn "Campaigns"/"Ad sets" \u2014 objective, budget, targeting)
|
|
@@ -6859,6 +7191,21 @@ ${FLAG.data}
|
|
|
6859
7191
|
|
|
6860
7192
|
${rawPlatformCommandLine("linkedin")}
|
|
6861
7193
|
Run adkit manage linkedin <group> --help for details.`.trim(),
|
|
7194
|
+
"linkedin accounts": `adkit manage linkedin accounts \u2014 LinkedIn Ads accounts
|
|
7195
|
+
|
|
7196
|
+
list List connected LinkedIn ad accounts
|
|
7197
|
+
available List discoverable LinkedIn ad accounts
|
|
7198
|
+
connect <id> Connect a LinkedIn ad account
|
|
7199
|
+
disconnect <id> Disconnect a LinkedIn ad account
|
|
7200
|
+
|
|
7201
|
+
Flags:
|
|
7202
|
+
--integration <id> Connect using a specific workspace login returned by available
|
|
7203
|
+
|
|
7204
|
+
Examples:
|
|
7205
|
+
adkit manage linkedin accounts list
|
|
7206
|
+
adkit manage linkedin accounts available
|
|
7207
|
+
adkit manage linkedin accounts connect 512345678
|
|
7208
|
+
adkit manage linkedin accounts disconnect 512345678`.trim(),
|
|
6862
7209
|
drafts: `adkit manage drafts \u2014 Manage drafts
|
|
6863
7210
|
|
|
6864
7211
|
list List all drafts
|
|
@@ -8697,16 +9044,28 @@ async function main() {
|
|
|
8697
9044
|
}
|
|
8698
9045
|
} else if (platform2 === "linkedin") {
|
|
8699
9046
|
switch (entity) {
|
|
8700
|
-
case "accounts":
|
|
9047
|
+
case "accounts": {
|
|
8701
9048
|
switch (action) {
|
|
9049
|
+
case "list":
|
|
9050
|
+
case void 0:
|
|
9051
|
+
data = await listLinkedInAccounts(client, restArgs, flags);
|
|
9052
|
+
emptyHint = "No LinkedIn ad accounts connected. Connect a workspace login first if no accounts are available.";
|
|
9053
|
+
break;
|
|
8702
9054
|
case "available":
|
|
8703
9055
|
data = await listLinkedInAvailableAccounts(client, restArgs, flags);
|
|
8704
9056
|
emptyHint = "No LinkedIn ad accounts available from the connected workspace login.";
|
|
8705
9057
|
break;
|
|
9058
|
+
case "connect":
|
|
9059
|
+
data = await connectLinkedInAccount(client, restArgs, flags);
|
|
9060
|
+
break;
|
|
9061
|
+
case "disconnect":
|
|
9062
|
+
data = await disconnectLinkedInAccount(client, restArgs, flags);
|
|
9063
|
+
break;
|
|
8706
9064
|
default:
|
|
8707
|
-
throw new CliError("UNKNOWN_COMMAND", `Unknown action: linkedin accounts ${action}`, "
|
|
9065
|
+
throw new CliError("UNKNOWN_COMMAND", `Unknown action: linkedin accounts ${action}`, "Run: adkit manage linkedin accounts --help");
|
|
8708
9066
|
}
|
|
8709
9067
|
break;
|
|
9068
|
+
}
|
|
8710
9069
|
case "media": {
|
|
8711
9070
|
if (!action) {
|
|
8712
9071
|
showHelp("linkedin media", flags.help === "full");
|
|
@@ -8808,21 +9167,98 @@ async function main() {
|
|
|
8808
9167
|
}
|
|
8809
9168
|
} else if (platform2 === "microsoft") {
|
|
8810
9169
|
switch (entity) {
|
|
8811
|
-
case "accounts":
|
|
9170
|
+
case "accounts": {
|
|
8812
9171
|
switch (action) {
|
|
9172
|
+
case "list":
|
|
9173
|
+
case void 0:
|
|
9174
|
+
data = await listMicrosoftAccounts(client, restArgs, flags);
|
|
9175
|
+
emptyHint = "No Microsoft ad accounts connected. Connect a workspace login first if no accounts are available.";
|
|
9176
|
+
break;
|
|
8813
9177
|
case "available":
|
|
8814
9178
|
data = await listMicrosoftAvailableAccounts(client, restArgs, flags);
|
|
8815
9179
|
emptyHint = "No Microsoft ad accounts available from the connected workspace login.";
|
|
8816
9180
|
break;
|
|
9181
|
+
case "connect":
|
|
9182
|
+
data = await connectMicrosoftAccount(client, restArgs, flags);
|
|
9183
|
+
break;
|
|
9184
|
+
case "disconnect":
|
|
9185
|
+
data = await disconnectMicrosoftAccount(client, restArgs, flags);
|
|
9186
|
+
break;
|
|
8817
9187
|
default:
|
|
8818
|
-
throw new CliError("UNKNOWN_COMMAND", `Unknown action: microsoft accounts ${action}`, "
|
|
9188
|
+
throw new CliError("UNKNOWN_COMMAND", `Unknown action: microsoft accounts ${action}`, "Run: adkit manage microsoft accounts --help");
|
|
8819
9189
|
}
|
|
8820
9190
|
break;
|
|
9191
|
+
}
|
|
8821
9192
|
default:
|
|
8822
9193
|
throw new CliError("UNKNOWN_COMMAND", `Unknown entity: microsoft ${entity}`, "Available: accounts, platform-api-requests");
|
|
8823
9194
|
}
|
|
8824
|
-
} else if (platform2 === "x")
|
|
8825
|
-
|
|
9195
|
+
} else if (platform2 === "x") {
|
|
9196
|
+
switch (entity) {
|
|
9197
|
+
case "accounts": {
|
|
9198
|
+
if (action && action !== "list" && action !== "available" && action !== "connect" && action !== "disconnect") {
|
|
9199
|
+
const accountId = action;
|
|
9200
|
+
const subcommand = args[4];
|
|
9201
|
+
if (subcommand === "profiles") {
|
|
9202
|
+
data = await listXProfiles(client, [accountId], flags);
|
|
9203
|
+
emptyHint = "No X identities found for this account.";
|
|
9204
|
+
} else if (subcommand === "pixels") {
|
|
9205
|
+
data = await listXPixels(client, [accountId], flags);
|
|
9206
|
+
emptyHint = "No X conversion pixels found for this account.";
|
|
9207
|
+
} else if (subcommand === "update") data = await updateXAccount(client, [accountId], flags);
|
|
9208
|
+
else throw new CliError("UNKNOWN_COMMAND", `Unknown subcommand: x accounts ${accountId} ${subcommand ?? ""}`, "Expected: profiles, pixels, or update");
|
|
9209
|
+
break;
|
|
9210
|
+
}
|
|
9211
|
+
switch (action) {
|
|
9212
|
+
case "list":
|
|
9213
|
+
case void 0:
|
|
9214
|
+
data = await listXAccounts(client, restArgs, flags);
|
|
9215
|
+
emptyHint = "No X ad accounts connected. Connect a workspace login first if no accounts are available.";
|
|
9216
|
+
break;
|
|
9217
|
+
case "available":
|
|
9218
|
+
data = await listXAvailableAccounts(client, restArgs, flags);
|
|
9219
|
+
emptyHint = "No X ad accounts available from the connected workspace login.";
|
|
9220
|
+
break;
|
|
9221
|
+
case "connect":
|
|
9222
|
+
data = await connectXAccount(client, restArgs, flags);
|
|
9223
|
+
break;
|
|
9224
|
+
case "disconnect":
|
|
9225
|
+
data = await disconnectXAccount(client, restArgs, flags);
|
|
9226
|
+
break;
|
|
9227
|
+
default:
|
|
9228
|
+
throw new CliError("UNKNOWN_COMMAND", `Unknown action: x accounts ${action}`, "Run: adkit manage x accounts --help");
|
|
9229
|
+
}
|
|
9230
|
+
break;
|
|
9231
|
+
}
|
|
9232
|
+
case "campaigns":
|
|
9233
|
+
if (action === "list") {
|
|
9234
|
+
data = await listXCampaigns(client, restArgs, flags);
|
|
9235
|
+
emptyHint = "No X campaigns matched the selected filters.";
|
|
9236
|
+
} else if (action === "get") data = await getXCampaign(client, restArgs, flags);
|
|
9237
|
+
else throw new CliError("UNKNOWN_COMMAND", `Unknown action: x campaigns ${action}`, "Available: list, get");
|
|
9238
|
+
break;
|
|
9239
|
+
case "ad-groups":
|
|
9240
|
+
if (action === "list") {
|
|
9241
|
+
data = await listXAdGroups(client, restArgs, flags);
|
|
9242
|
+
emptyHint = "No X ad groups matched the selected filters.";
|
|
9243
|
+
} else if (action === "get") data = await getXAdGroup(client, restArgs, flags);
|
|
9244
|
+
else throw new CliError("UNKNOWN_COMMAND", `Unknown action: x ad-groups ${action}`, "Available: list, get");
|
|
9245
|
+
break;
|
|
9246
|
+
case "ads":
|
|
9247
|
+
if (action === "list") {
|
|
9248
|
+
data = await listXAds(client, restArgs, flags);
|
|
9249
|
+
emptyHint = "No X promoted-Post ads matched the selected filters.";
|
|
9250
|
+
} else if (action === "get") data = await getXAd(client, restArgs, flags);
|
|
9251
|
+
else throw new CliError("UNKNOWN_COMMAND", `Unknown action: x ads ${action}`, "Available: list, get");
|
|
9252
|
+
break;
|
|
9253
|
+
case "results":
|
|
9254
|
+
if (action && action !== "list") throw new CliError("UNKNOWN_COMMAND", `Unknown action: x results ${action}`, "Available: list");
|
|
9255
|
+
data = await listXResults(client, restArgs, flags);
|
|
9256
|
+
emptyHint = "No X result rows matched the selected range and filters.";
|
|
9257
|
+
break;
|
|
9258
|
+
default:
|
|
9259
|
+
throw new CliError("UNKNOWN_COMMAND", `Unknown entity: x ${entity}`, "Available: accounts, campaigns, ad-groups, ads, results, platform-api-requests");
|
|
9260
|
+
}
|
|
9261
|
+
} else throw new CliError("UNKNOWN_COMMAND", "Unknown platform", "Available: meta, google, tiktok, reddit, x, linkedin, microsoft, drafts, platform-api-requests");
|
|
8826
9262
|
printResult(data, flags, emptyHint, detailEntity);
|
|
8827
9263
|
return;
|
|
8828
9264
|
}
|
|
@@ -8858,14 +9294,7 @@ async function main() {
|
|
|
8858
9294
|
console.log(output || "No similar advertisers found. Try adjusting --industry, --category, or --tags filters.");
|
|
8859
9295
|
}
|
|
8860
9296
|
} else if (action === "search") {
|
|
8861
|
-
const
|
|
8862
|
-
if (!query) {
|
|
8863
|
-
console.error("Usage: adkit library advertisers search <query>");
|
|
8864
|
-
process.exitCode = 1;
|
|
8865
|
-
return;
|
|
8866
|
-
}
|
|
8867
|
-
flags.search = query;
|
|
8868
|
-
const data = await listAdvertisers(client, [], flags);
|
|
9297
|
+
const data = await searchAdvertisers(client, args.slice(3), flags);
|
|
8869
9298
|
if (wantsJson(flags)) printResult(data, flags, "No advertisers found matching your search.");
|
|
8870
9299
|
else {
|
|
8871
9300
|
const rows = unwrapList(data).map(formatLibraryAdvertiserRow);
|
package/package.json
CHANGED