@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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +665 -236
  3. 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 list --search "notion" --platform meta
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 body2 = parseDataFlag(flags, "Check JSON syntax in --data");
2773
- const qs2 = buildGoogleMutationQuery(flags, body2);
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 body = { ads: [payload] };
2779
- const qs = buildGoogleMutationQuery(flags, body);
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 body = parseDataFlag(flags, "Check JSON syntax in --data");
2788
- const adGroupId2 = resolveGoogleAdGroupId(body, flags);
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, body, { adGroupId: adGroupId2 });
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, body);
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, payload);
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
- console.log(JSON.stringify(data));
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
- if (platforms.meta.connected) {
3832
- lines.push("Meta: connected");
3833
- for (const a of platforms.meta.accounts) {
3834
- lines.push(` ${a.id} \u2014 ${a.name} (${a.currency}, ${a.status})`);
3835
- if (a.defaults.pageId) lines.push(` Page: ${a.defaults.pageId}`);
3836
- if (a.defaults.pixelId) lines.push(` Pixel: ${a.defaults.pixelId}`);
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
- } else lines.push("Meta: not connected");
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
- console.log(lines.join("\n"));
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
- const industry = typeof flags.industry === "string" ? flags.industry : void 0;
3930
- const category = typeof flags.category === "string" ? flags.category : void 0;
3931
- const platform2 = typeof flags.platform === "string" ? flags.platform : void 0;
3932
- const search = typeof flags.search === "string" ? flags.search : void 0;
3933
- const sort = typeof flags.sort === "string" ? flags.sort : "name";
3934
- const limit = typeof flags.limit === "string" ? flags.limit : void 0;
3935
- const page = typeof flags.page === "string" ? flags.page : void 0;
3936
- const qs = queryString3({ industry, category, platform: platform2, search, sort, limit, page });
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: each headline max 30 chars, each description max 90 chars, long headline max 90 chars, each path segment max 15 chars.
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: each headline max 30 chars, each description max 90 chars, long headline max 90 chars, each path segment max 15 chars.
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 Ads raw API access
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 discovery
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 raw API access
7028
+ x: `adkit manage x \u2014 X Ads management
6816
7029
 
6817
- X is currently exposed through Advanced Platform Access only.
6818
- Use platform-api-requests with official X Ads API endpoint paths.
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 platform-api-requests --platform x --endpoint "accounts/18ce54d4x5t/campaigns" --method GET --request-description "List X campaigns" --account 18ce54d4x5t`.trim(),
6824
- microsoft: `adkit manage microsoft \u2014 Microsoft Ads raw API access + account discovery
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
- Microsoft (Bing) is currently exposed through Advanced Platform Access, plus account discovery.
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 available List Microsoft ad accounts discoverable from the connected workspace login
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 Discover LinkedIn ad accounts (available)
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}`, "Available: available.");
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}`, "Available: available.");
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") throw new CliError("UNKNOWN_COMMAND", `Unknown entity: x ${entity}`, "Available: platform-api-requests");
8825
- else throw new CliError("UNKNOWN_COMMAND", "Unknown platform", "Available: meta, google, tiktok, reddit, x, linkedin, microsoft, drafts, platform-api-requests");
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 query = args.slice(3).join(" ");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adkit/cli",
3
- "version": "1.13.25",
3
+ "version": "1.13.27",
4
4
  "description": "The Ads CLI for AI agents — manage Meta & Google ad campaigns, browse the ad library, and generate creatives from your terminal.",
5
5
  "keywords": [
6
6
  "ads cli",