@adkit/cli 1.13.20 → 1.13.21

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 (2) hide show
  1. package/dist/cli.js +328 -116
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -88,6 +88,108 @@ var CliError = class extends Error {
88
88
  }
89
89
  };
90
90
 
91
+ // src/cli-utils.ts
92
+ function parseArgs(argv, options) {
93
+ const args = [];
94
+ const flags = {};
95
+ const multiSet = new Set(options?.multi);
96
+ for (let i = 0; i < argv.length; i++) {
97
+ const arg = argv[i];
98
+ if (!arg.startsWith("--")) {
99
+ args.push(arg);
100
+ continue;
101
+ }
102
+ const eqIdx = arg.indexOf("=");
103
+ let key;
104
+ let value;
105
+ if (eqIdx !== -1) {
106
+ key = arg.slice(2, eqIdx);
107
+ value = arg.slice(eqIdx + 1);
108
+ } else {
109
+ key = arg.slice(2);
110
+ const next = argv[i + 1];
111
+ if (next && !next.startsWith("--")) {
112
+ value = next;
113
+ i++;
114
+ } else value = true;
115
+ }
116
+ if (multiSet.has(key) && typeof value === "string") {
117
+ const existing = flags[key];
118
+ if (Array.isArray(existing)) existing.push(value);
119
+ else flags[key] = [value];
120
+ } else flags[key] = value;
121
+ }
122
+ return { args, flags };
123
+ }
124
+ var GLOBAL_FLAGS = ["account", "json", "fields", "publish", "data", "platform-overrides", "force", "project"];
125
+ function validateFlags(flags, allowed, command) {
126
+ const allAllowed = /* @__PURE__ */ new Set([...GLOBAL_FLAGS, ...allowed]);
127
+ const unknown = Object.keys(flags).filter((k) => !allAllowed.has(k));
128
+ if (unknown.length) throw new CliError("UNKNOWN_FLAG", `Unknown flag${unknown.length > 1 ? "s" : ""}: ${unknown.map((f) => `--${f}`).join(", ")}`, `Run: adkit ${command} --help`);
129
+ }
130
+ function requireArg(args, index, label, hint) {
131
+ const val = args[index];
132
+ if (!val) throw new CliError("MISSING_ARGUMENT", `Missing required argument: \`<${label}>\``, hint);
133
+ return val;
134
+ }
135
+ function requireFlag(flags, key, hint) {
136
+ const val = flags[key];
137
+ if (typeof val !== "string") throw new CliError("MISSING_FLAG", `Missing required flag: \`--${key}\``, hint);
138
+ return val;
139
+ }
140
+ function parseDataFlag(flags, hint) {
141
+ const raw = requireFlag(flags, "data", hint);
142
+ try {
143
+ return JSON.parse(raw);
144
+ } catch {
145
+ const truncated = raw.length > 80 ? raw.slice(0, 80) + "..." : raw;
146
+ throw new CliError("INVALID_VALUE", `Invalid JSON in \`--data\` flag: ${truncated}`, "Check JSON syntax in --data");
147
+ }
148
+ }
149
+ function mergeAccountId(body, flags) {
150
+ if (typeof body !== "object" || body === null || Array.isArray(body)) return body;
151
+ const obj = body;
152
+ if (!obj.accountId && typeof flags.account === "string") obj.accountId = flags.account;
153
+ return obj;
154
+ }
155
+ function queryString(params) {
156
+ const entries = Object.entries(params).filter((entry) => entry[1] !== void 0);
157
+ if (entries.length === 0) return "";
158
+ return "?" + entries.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("&");
159
+ }
160
+ function isDisplayRow(value) {
161
+ return typeof value === "object" && value !== null;
162
+ }
163
+ function isDisplayRowEntry(value) {
164
+ return isDisplayRow(value);
165
+ }
166
+ function isDisplayRowArray(value) {
167
+ if (!Array.isArray(value)) return false;
168
+ const everyItemIsDisplayRow = value.every(isDisplayRowEntry);
169
+ return everyItemIsDisplayRow;
170
+ }
171
+ function isWrappedRowArray(value) {
172
+ return isDisplayRowArray(value);
173
+ }
174
+ function getDateStamp() {
175
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
176
+ }
177
+ function collectFlagValues(flags, key) {
178
+ const value = flags[key];
179
+ if (Array.isArray(value)) return value;
180
+ if (typeof value === "string") return [value];
181
+ return [];
182
+ }
183
+ function unwrapList(data) {
184
+ if (isDisplayRowArray(data)) return data;
185
+ if (data && typeof data === "object") {
186
+ const values = Object.values(data);
187
+ const rowArray = values.find(isWrappedRowArray);
188
+ if (rowArray) return rowArray;
189
+ }
190
+ return [];
191
+ }
192
+
91
193
  // src/client.ts
92
194
  var noticePrinted = false;
93
195
  function printServerNotice(response) {
@@ -166,22 +268,53 @@ function buildAuthSuggestion(code) {
166
268
  if (!AUTH_CODES.has(normalizedCode)) return void 0;
167
269
  return "Run: adkit setup manage";
168
270
  }
271
+ function appendReadQueryFallback(path3, rawData) {
272
+ const parsedData = parseDataFlag({ data: rawData }, "Use a flat JSON object for read query parameters");
273
+ if (typeof parsedData !== "object" || parsedData === null || Array.isArray(parsedData)) throw new CliError("INVALID_VALUE", "Read --data must be a flat JSON object", 'Use an object such as {"adGroupIds":["123"]}');
274
+ const queryStart = path3.indexOf("?");
275
+ const existingQuery = queryStart === -1 ? "" : path3.slice(queryStart + 1);
276
+ const existingParams = new URLSearchParams(existingQuery);
277
+ const extraParams = [];
278
+ for (const [key, value] of Object.entries(parsedData)) {
279
+ const values = Array.isArray(value) ? value : [value];
280
+ if (values.length === 0) throw new CliError("INVALID_VALUE", `Cannot use --data field \`${key}\` as a read query parameter`, "Use a string, finite number, boolean, or non-empty array of those values");
281
+ const queryItems = [];
282
+ for (const item of values) {
283
+ const isScalar = typeof item === "string" || typeof item === "boolean" || typeof item === "number" && Number.isFinite(item);
284
+ if (!isScalar) throw new CliError("INVALID_VALUE", `Cannot use --data field \`${key}\` as a read query parameter`, "Use a string, finite number, boolean, or non-empty array of those values");
285
+ const queryItem = String(item);
286
+ queryItems.push(queryItem);
287
+ }
288
+ const queryValue = queryItems.join(",");
289
+ if (existingParams.has(key)) continue;
290
+ const encodedKey = encodeURIComponent(key);
291
+ const encodedValue = encodeURIComponent(queryValue);
292
+ extraParams.push(`${encodedKey}=${encodedValue}`);
293
+ }
294
+ if (extraParams.length === 0) return path3;
295
+ const separator = queryStart === -1 ? "?" : "&";
296
+ return `${path3}${separator}${extraParams.join("&")}`;
297
+ }
169
298
  var AdkitClient = class {
170
299
  apiKey;
171
300
  baseUrl;
172
301
  clientVersion;
173
302
  projectId;
174
- constructor({ apiKey, baseUrl, clientVersion, projectId }) {
303
+ readQueryFallback;
304
+ constructor({ apiKey, baseUrl, clientVersion, projectId, readQueryFallback }) {
175
305
  const isLocalhost = baseUrl.startsWith("http://localhost") || baseUrl.startsWith("http://127.0.0.1");
176
306
  if (baseUrl.startsWith("http://") && !isLocalhost) throw new Error("HTTPS is required \u2014 insecure HTTP base URLs are not allowed");
177
307
  this.apiKey = apiKey;
178
308
  this.baseUrl = baseUrl;
179
309
  this.clientVersion = clientVersion;
180
310
  this.projectId = projectId;
311
+ this.readQueryFallback = readQueryFallback;
181
312
  }
182
313
  // eslint-disable-next-line max-lines-per-function -- shared client error mapping is intentionally centralized here.
183
314
  async request(method, path3, body) {
184
- const url = `${this.baseUrl}${path3}`;
315
+ if (method !== "GET") this.readQueryFallback = void 0;
316
+ const requestPath = method === "GET" && this.readQueryFallback !== void 0 ? appendReadQueryFallback(path3, this.readQueryFallback) : path3;
317
+ const url = `${this.baseUrl}${requestPath}`;
185
318
  const headers = {
186
319
  Authorization: `Bearer ${this.apiKey}`,
187
320
  "Content-Type": "application/json",
@@ -249,7 +382,7 @@ var AdkitClient = class {
249
382
  throw error2;
250
383
  }
251
384
  if (response.status === 404) {
252
- const fallback = `Not found: ${method} ${path3} \u2014 the resource may have been deleted or the ID is wrong`;
385
+ const fallback = `Not found: ${method} ${requestPath} \u2014 the resource may have been deleted or the ID is wrong`;
253
386
  const error2 = new CliError("NOT_FOUND", appendErrorDetail(serverMessage || fallback, batchSummary), "Check the resource ID");
254
387
  error2.notice = serverNotice;
255
388
  throw error2;
@@ -1022,108 +1155,6 @@ function logout() {
1022
1155
  console.log("Logged out \u2014 API key removed.");
1023
1156
  }
1024
1157
 
1025
- // src/cli-utils.ts
1026
- function parseArgs(argv, options) {
1027
- const args = [];
1028
- const flags = {};
1029
- const multiSet = new Set(options?.multi);
1030
- for (let i = 0; i < argv.length; i++) {
1031
- const arg = argv[i];
1032
- if (!arg.startsWith("--")) {
1033
- args.push(arg);
1034
- continue;
1035
- }
1036
- const eqIdx = arg.indexOf("=");
1037
- let key;
1038
- let value;
1039
- if (eqIdx !== -1) {
1040
- key = arg.slice(2, eqIdx);
1041
- value = arg.slice(eqIdx + 1);
1042
- } else {
1043
- key = arg.slice(2);
1044
- const next = argv[i + 1];
1045
- if (next && !next.startsWith("--")) {
1046
- value = next;
1047
- i++;
1048
- } else value = true;
1049
- }
1050
- if (multiSet.has(key) && typeof value === "string") {
1051
- const existing = flags[key];
1052
- if (Array.isArray(existing)) existing.push(value);
1053
- else flags[key] = [value];
1054
- } else flags[key] = value;
1055
- }
1056
- return { args, flags };
1057
- }
1058
- var GLOBAL_FLAGS = ["account", "json", "fields", "publish", "data", "platform-overrides", "force", "project"];
1059
- function validateFlags(flags, allowed, command) {
1060
- const allAllowed = /* @__PURE__ */ new Set([...GLOBAL_FLAGS, ...allowed]);
1061
- const unknown = Object.keys(flags).filter((k) => !allAllowed.has(k));
1062
- if (unknown.length) throw new CliError("UNKNOWN_FLAG", `Unknown flag${unknown.length > 1 ? "s" : ""}: ${unknown.map((f) => `--${f}`).join(", ")}`, `Run: adkit ${command} --help`);
1063
- }
1064
- function requireArg(args, index, label, hint) {
1065
- const val = args[index];
1066
- if (!val) throw new CliError("MISSING_ARGUMENT", `Missing required argument: \`<${label}>\``, hint);
1067
- return val;
1068
- }
1069
- function requireFlag(flags, key, hint) {
1070
- const val = flags[key];
1071
- if (typeof val !== "string") throw new CliError("MISSING_FLAG", `Missing required flag: \`--${key}\``, hint);
1072
- return val;
1073
- }
1074
- function parseDataFlag(flags, hint) {
1075
- const raw = requireFlag(flags, "data", hint);
1076
- try {
1077
- return JSON.parse(raw);
1078
- } catch {
1079
- const truncated = raw.length > 80 ? raw.slice(0, 80) + "..." : raw;
1080
- throw new CliError("INVALID_VALUE", `Invalid JSON in \`--data\` flag: ${truncated}`, "Check JSON syntax in --data");
1081
- }
1082
- }
1083
- function mergeAccountId(body, flags) {
1084
- if (typeof body !== "object" || body === null || Array.isArray(body)) return body;
1085
- const obj = body;
1086
- if (!obj.accountId && typeof flags.account === "string") obj.accountId = flags.account;
1087
- return obj;
1088
- }
1089
- function queryString(params) {
1090
- const entries = Object.entries(params).filter((entry) => entry[1] !== void 0);
1091
- if (entries.length === 0) return "";
1092
- return "?" + entries.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("&");
1093
- }
1094
- function isDisplayRow(value) {
1095
- return typeof value === "object" && value !== null;
1096
- }
1097
- function isDisplayRowEntry(value) {
1098
- return isDisplayRow(value);
1099
- }
1100
- function isDisplayRowArray(value) {
1101
- if (!Array.isArray(value)) return false;
1102
- const everyItemIsDisplayRow = value.every(isDisplayRowEntry);
1103
- return everyItemIsDisplayRow;
1104
- }
1105
- function isWrappedRowArray(value) {
1106
- return isDisplayRowArray(value);
1107
- }
1108
- function getDateStamp() {
1109
- return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1110
- }
1111
- function collectFlagValues(flags, key) {
1112
- const value = flags[key];
1113
- if (Array.isArray(value)) return value;
1114
- if (typeof value === "string") return [value];
1115
- return [];
1116
- }
1117
- function unwrapList(data) {
1118
- if (isDisplayRowArray(data)) return data;
1119
- if (data && typeof data === "object") {
1120
- const values = Object.values(data);
1121
- const rowArray = values.find(isWrappedRowArray);
1122
- if (rowArray) return rowArray;
1123
- }
1124
- return [];
1125
- }
1126
-
1127
1158
  // src/commands/projects.ts
1128
1159
  async function createProject(client, options) {
1129
1160
  const response = await client.post("/manage/projects", options);
@@ -1888,6 +1919,18 @@ function parseGoogleSitelinkShorthand(value) {
1888
1919
  return normalizeSitelinkAsset({ type: "sitelink", linkText, finalUrls: [finalUrl] });
1889
1920
  }
1890
1921
 
1922
+ // ../shared/dist/utils/type-guards.js
1923
+ function isPlainObject(value) {
1924
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1925
+ }
1926
+ function readProperty(value, key) {
1927
+ return value[key];
1928
+ }
1929
+ function readStringProperty(value, key) {
1930
+ const raw = value[key];
1931
+ return typeof raw === "string" ? raw : void 0;
1932
+ }
1933
+
1891
1934
  // src/commands/google.ts
1892
1935
  import { readFileSync as readFileSync2 } from "node:fs";
1893
1936
  import { basename, extname, resolve } from "node:path";
@@ -1899,6 +1942,9 @@ var MEDIA_UPLOAD_FLAGS = ["file", "url", "base64", "adkit-media", "video-id", "f
1899
1942
  var CAMPAIGN_LIST_FLAGS = ["status", "limit", "offset"];
1900
1943
  var CAMPAIGN_CREATE_FLAGS = ["name", "status", "budget-daily", "campaign-type", "bid-strategy", "target-cpa", "target-roas", "search-partners", "display-network", "tracking-url-suffix", "countries"];
1901
1944
  var CAMPAIGN_UPDATE_FLAGS = ["name", "status", "budget-daily", "bid-strategy", "target-cpa", "target-roas", "search-partners", "display-network", "tracking-url-suffix", "countries"];
1945
+ var ASSET_GROUP_LIST_FLAGS = ["campaign", "limit", "offset"];
1946
+ var ASSET_GROUP_CREATE_FLAGS = ["campaign", "name", "status", "final-url"];
1947
+ var ASSET_GROUP_UPDATE_FLAGS = ["name", "status", "final-url"];
1902
1948
  var AD_GROUP_LIST_FLAGS = ["campaign", "status", "limit", "offset"];
1903
1949
  var AD_GROUP_FLAGS = ["campaign", "name", "status", "cpc-bid"];
1904
1950
  var AD_LIST_FLAGS = ["ad-group", "campaign", "status", "policy-status", "limit", "offset"];
@@ -1921,6 +1967,7 @@ var KEYWORD_RESEARCH_FLAGS = ["url", "location", "language", "limit", "min-volum
1921
1967
  var TARGETING_RESEARCH_FLAGS = ["limit"];
1922
1968
  var GEO_LOCATION_SEARCH_FLAGS = ["country-code", "locale", "type", "limit"];
1923
1969
  var NUMBER_FLAG_PATTERN = /^(?:\d+|\d+\.\d+|\.\d+)(?:e[+-]?\d+)?$/iu;
1970
+ var GOOGLE_NUMERIC_ID_PATTERN = /^\d+$/u;
1924
1971
  function generateCampaignName2() {
1925
1972
  return `campaign ${getDateStamp()}`;
1926
1973
  }
@@ -1995,6 +2042,52 @@ function requireAssetReference(args, hint) {
1995
2042
  throw new CliError("INVALID_VALUE", `Invalid asset reference: ${value}`, "Use asset:<numeric-id>, e.g. asset:123456789");
1996
2043
  }
1997
2044
  }
2045
+ function requireGoogleNumericId(args, label, hint) {
2046
+ const value = requireArg(args, 0, label, hint);
2047
+ if (!GOOGLE_NUMERIC_ID_PATTERN.test(value)) throw new CliError("INVALID_VALUE", `Invalid ${label}: ${value}`, "Use the numeric Google Ads platformId returned by list/get");
2048
+ return value;
2049
+ }
2050
+ function mergeGoogleAssetGroupAccountId(body, flags) {
2051
+ const bodyAccountId = isPlainObject(body) ? readStringProperty(body, "accountId") : void 0;
2052
+ const flagAccountId = typeof flags.account === "string" ? flags.account : void 0;
2053
+ if (bodyAccountId && flagAccountId && bodyAccountId !== flagAccountId) throw new CliError("INVALID_VALUE", `Conflicting accountId: --account ${flagAccountId} vs data.accountId ${bodyAccountId}`, "Pass the same account once, either with --account or inside --data");
2054
+ return mergeAccountId(body, flags);
2055
+ }
2056
+ function buildGoogleAssetGroupCreateBody(flags) {
2057
+ if (typeof flags.data !== "string") throw new CliError("MISSING_FLAG", "Asset-group create requires `--data` for its assets", `Pass the full {"assetGroups":[...]} body, or combine --campaign, --name, and --final-url with --data '{"assets":[...]}'`);
2058
+ const parsedData = parseDataFlag(flags, "Check JSON syntax in --data");
2059
+ if (!isPlainObject(parsedData)) throw new CliError("INVALID_VALUE", "`--data` must be a JSON object", 'Use {"assetGroups":[...]} or a single asset-group object');
2060
+ const explicitAssetGroups = readProperty(parsedData, "assetGroups");
2061
+ if (Array.isArray(explicitAssetGroups)) return mergeGoogleAssetGroupAccountId(parsedData, flags);
2062
+ const dataCampaignId = readStringProperty(parsedData, "campaignId");
2063
+ const dataName = readStringProperty(parsedData, "name");
2064
+ const dataFinalUrls = readProperty(parsedData, "finalUrls");
2065
+ const campaignId = dataCampaignId ?? requireFlag(flags, "campaign", "Pass --campaign <id> or include campaignId in --data");
2066
+ const name = dataName ?? requireFlag(flags, "name", "Pass --name or include name in --data");
2067
+ const finalUrls = Array.isArray(dataFinalUrls) ? dataFinalUrls : [requireFlag(flags, "final-url", "Pass --final-url or include finalUrls in --data")];
2068
+ const dataStatus = readProperty(parsedData, "status");
2069
+ const status2 = dataStatus ?? (typeof flags.status === "string" ? flags.status : void 0);
2070
+ const fragment = removeQueryOnlyBodyKeys(parsedData, ["accountId", "assetGroups"]);
2071
+ const assetGroup = {
2072
+ ...isPlainObject(fragment) ? fragment : {},
2073
+ campaignId,
2074
+ name,
2075
+ finalUrls,
2076
+ ...status2 === void 0 ? {} : { status: status2 }
2077
+ };
2078
+ const dataAccountId = readStringProperty(parsedData, "accountId");
2079
+ const body = { ...dataAccountId ? { accountId: dataAccountId } : {}, assetGroups: [assetGroup] };
2080
+ return mergeGoogleAssetGroupAccountId(body, flags);
2081
+ }
2082
+ function buildGoogleAssetGroupUpdateBody(flags) {
2083
+ const body = {};
2084
+ if (typeof flags.name === "string") body.name = flags.name;
2085
+ if (flags.status === "enabled" || flags.status === "paused") body.status = flags.status;
2086
+ else if (typeof flags.status === "string") throw new CliError("INVALID_VALUE", `Invalid asset group status: ${flags.status}`, "Use enabled or paused; use delete to remove the group");
2087
+ if (typeof flags["final-url"] === "string") body.finalUrls = [flags["final-url"]];
2088
+ if (Object.keys(body).length === 0) throw new CliError("MISSING_FLAG", "Asset-group update requires a change", "Pass --name, --status, --final-url, or --data with assets.add/assets.remove");
2089
+ return body;
2090
+ }
1998
2091
  function inferGoogleMediaContentType(filename) {
1999
2092
  const extension = extname(filename).toLowerCase();
2000
2093
  if (extension === ".jpg" || extension === ".jpeg") return "image/jpeg";
@@ -2503,6 +2596,45 @@ async function deleteCampaign2(client, args, flags) {
2503
2596
  const path3 = `/manage/google/campaigns/${id}${qs}`;
2504
2597
  return client.delete(path3);
2505
2598
  }
2599
+ async function listGoogleAssetGroups(client, _args, flags) {
2600
+ validateFlags(flags, ASSET_GROUP_LIST_FLAGS, "manage google asset-groups list");
2601
+ const campaignId = requireFlag(flags, "campaign", "Run: adkit manage google asset-groups list --campaign <campaign-id>");
2602
+ const qs = buildGoogleListQuery(flags, { campaignId });
2603
+ return client.get(`/manage/google/asset-groups${qs}`);
2604
+ }
2605
+ async function getGoogleAssetGroup(client, args, flags) {
2606
+ validateFlags(flags, ["raw"], "manage google asset-groups <id>");
2607
+ const id = requireGoogleNumericId(args, "asset-group-id", "Run: adkit manage google asset-groups <asset-group-id>");
2608
+ const qs = buildGoogleListQuery(flags);
2609
+ return client.get(`/manage/google/asset-groups/${id}${qs}`);
2610
+ }
2611
+ async function createGoogleAssetGroup(client, _args, flags) {
2612
+ validateFlags(flags, ASSET_GROUP_CREATE_FLAGS, "manage google asset-groups create");
2613
+ const body = buildGoogleAssetGroupCreateBody(flags);
2614
+ const qs = buildGoogleMutationQuery(flags, body);
2615
+ return client.post(`/manage/google/asset-groups${qs}`, body);
2616
+ }
2617
+ async function updateGoogleAssetGroup(client, args, flags) {
2618
+ validateFlags(flags, ASSET_GROUP_UPDATE_FLAGS, "manage google asset-groups update");
2619
+ const id = requireGoogleNumericId(args, "asset-group-id", "Run: adkit manage google asset-groups update <asset-group-id> --data <json>");
2620
+ if (typeof flags.data === "string") {
2621
+ const parsedBody = parseDataFlag(flags, "Use assets.add and assets.remove for asset mutations");
2622
+ if (!isPlainObject(parsedBody)) throw new CliError("INVALID_VALUE", "`--data` must be a JSON object", "Use an asset-group update object");
2623
+ const scopedBody = mergeGoogleAssetGroupAccountId(parsedBody, flags);
2624
+ const qs2 = buildGoogleMutationQuery(flags, scopedBody);
2625
+ const body2 = removeQueryOnlyBodyKeys(scopedBody, ["accountId"]);
2626
+ return client.patch(`/manage/google/asset-groups/${id}${qs2}`, body2);
2627
+ }
2628
+ const body = buildGoogleAssetGroupUpdateBody(flags);
2629
+ const qs = buildGoogleMutationQuery(flags, body);
2630
+ return client.patch(`/manage/google/asset-groups/${id}${qs}`, body);
2631
+ }
2632
+ async function deleteGoogleAssetGroup(client, args, flags) {
2633
+ validateFlags(flags, [], "manage google asset-groups delete");
2634
+ const id = requireGoogleNumericId(args, "asset-group-id", "Run: adkit manage google asset-groups delete <asset-group-id>");
2635
+ const qs = buildGoogleMutationQuery(flags, {});
2636
+ return client.delete(`/manage/google/asset-groups/${id}${qs}`);
2637
+ }
2506
2638
  async function listGoogleAdGroups(client, _args, flags) {
2507
2639
  validateFlags(flags, AD_GROUP_LIST_FLAGS, "manage google ad-groups list");
2508
2640
  const campaignId = typeof flags.campaign === "string" ? flags.campaign : void 0;
@@ -4622,7 +4754,7 @@ function isHelpSearchResponse(value) {
4622
4754
  var require2 = createRequire(import.meta.url);
4623
4755
  var CLI_VERSION = require2("../package.json").version;
4624
4756
  var DEFAULT_BASE_URL = "https://app.adkit.so/api/v1";
4625
- var GOOGLE_NUMERIC_ID_PATTERN = /^\d+$/;
4757
+ var GOOGLE_NUMERIC_ID_PATTERN2 = /^\d+$/;
4626
4758
  var GOOGLE_RESULTS_LEVEL_ACTIONS = /* @__PURE__ */ new Set(["campaigns", "ad-groups", "ads"]);
4627
4759
  var TIKTOK_NUMERIC_ID_PATTERN = /^\d+$/;
4628
4760
  var LINKEDIN_ID_PATTERN = /[\d:_]/;
@@ -5326,7 +5458,7 @@ Note:
5326
5458
  Advanced geo examples: adkit manage google campaigns --help full
5327
5459
  Display creation starts here: campaigns create --campaign-type display, then create Display ad groups and responsive_display ads.
5328
5460
  Performance Max: campaignType "performance_max" with asset groups \u2014 build it with --data. See --help full.
5329
- View a Performance Max campaign by ID to read back its assetGroups from Google.
5461
+ View a Performance Max campaign by ID to read back its assetGroups; manage existing groups with google asset-groups.
5330
5462
  For spend/clicks/conversions: adkit manage google results
5331
5463
 
5332
5464
  Examples:
@@ -5379,8 +5511,8 @@ Notes:
5379
5511
  platformLocation accepts exact Google geo objects for advanced criteria and existing raw values.
5380
5512
  Google exclusions support named locations. Proximity circles belong in include.
5381
5513
  Display campaign creation uses campaignType:"display" or --campaign-type display. Videos are not supported for Display media in v1.
5382
- Performance Max: campaignType "performance_max" with exactly one assetGroups[] entry. Text assets inline ({role,text}); image assets are uploaded via google media first, then referenced by id/resourceName. Needs an enabled conversion (publish is blocked without one). Non-retail only. Build it with --data.
5383
- View a Performance Max campaign by ID to read back assetGroups from Google. There is no separate google asset-groups command.
5514
+ 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.
5515
+ View a Performance Max campaign by ID to read back its assetGroups. After creation, manage groups with google asset-groups.
5384
5516
  For spend/clicks/conversions: adkit manage google results
5385
5517
 
5386
5518
  Examples:
@@ -5393,6 +5525,58 @@ Examples:
5393
5525
  adkit manage google campaigns update 987654321 --status paused --account 1234567890
5394
5526
  adkit manage google campaigns update 987654321 --data '{"targeting":{"geoLocations":{"match":"presence_or_interest"}}}' --account 1234567890
5395
5527
  adkit manage google campaigns delete 987654321 --account 1234567890 --publish`;
5528
+ var GOOGLE_ASSET_GROUP_HELP = `adkit manage google asset-groups \u2014 Performance Max asset groups
5529
+
5530
+ list List groups in one campaign (requires --campaign)
5531
+ create Create one group or a batch
5532
+ update <id> Update by numeric Google asset group ID (platformId)
5533
+ delete <id> Delete by numeric Google asset group ID (platformId)
5534
+ <id> View one group by numeric Google asset group ID (platformId)
5535
+
5536
+ Flags (list):
5537
+ --campaign <id> Parent Performance Max campaign ID (required)
5538
+ --limit <n> Max results
5539
+ --offset <n> Pagination offset
5540
+ ${FLAG.account}
5541
+
5542
+ Flags (create):
5543
+ --campaign <id> Parent campaign ID for a single-group JSON fragment
5544
+ --name <name> Group name for a single-group JSON fragment
5545
+ --status <s> enabled or paused
5546
+ --final-url <url> Landing page for a single-group JSON fragment
5547
+ --data <json> Required assets. Pass a full {"assetGroups":[...]} body for batches, or one group fragment with assets when using the named flags
5548
+ ${FLAG.account}
5549
+ ${FLAG.publish}
5550
+
5551
+ Flags (update):
5552
+ --name <name> New group name
5553
+ --status <s> enabled or paused; use delete to remove the group
5554
+ --final-url <url> Replace landing pages with one URL
5555
+ --data <json> Full update object. Use assets.add and assets.remove for asset changes
5556
+ ${FLAG.account}
5557
+ ${FLAG.publish}
5558
+
5559
+ Asset JSON:
5560
+ Text asset {"role":"headline|long_headline|description|business_name","text":"..."}
5561
+ Image asset {"role":"marketing_image|square_marketing_image|logo|landscape_logo","id":"<uploaded-google-asset-id>"}
5562
+ Remove asset {"linkResourceName":"customers/.../assetGroupAssets/..."} \u2014 copy the exact value returned by list/get
5563
+ Search theme {"type":"search_theme","text":"..."} \u2014 create/read only; signals cannot be edited
5564
+
5565
+ Notes:
5566
+ Mutations create drafts by default. Add --publish to apply immediately.
5567
+ Minimum per group: 3 headlines, 1 long_headline, 2 descriptions (at least one 60 characters or fewer), 1 business_name, 1 logo, 1 marketing_image, and 1 square_marketing_image.
5568
+ list/get return assets grouped by role; every linked asset includes linkResourceName for later removal.
5569
+ Upload images with google media before referencing their id or resourceName.
5570
+ Creating groups for campaigns with Google brand guidelines enabled is not supported.
5571
+
5572
+ Examples:
5573
+ adkit manage google asset-groups list --campaign 987654321 --account 1234567890
5574
+ adkit manage google asset-groups 555666777 --account 1234567890
5575
+ adkit manage google asset-groups create --campaign 987654321 --name "US Prospects" --final-url https://example.com --data '{"assets":[{"role":"headline","text":"Ship faster"},{"role":"headline","text":"Save hours every week"},{"role":"headline","text":"Start free today"},{"role":"long_headline","text":"Automate your ad workflow"},{"role":"description","text":"Launch campaigns in minutes"},{"role":"description","text":"No credit card required"},{"role":"business_name","text":"Acme"},{"role":"logo","id":"111"},{"role":"marketing_image","id":"222"},{"role":"square_marketing_image","id":"333"}]}' --account 1234567890
5576
+ adkit manage google asset-groups update 555666777 --name "US \u2014 refreshed" --account 1234567890
5577
+ adkit manage google asset-groups update 555666777 --data '{"assets":{"add":[{"role":"headline","text":"New headline"}],"remove":[{"linkResourceName":"customers/1234567890/assetGroupAssets/555666777~444~HEADLINE"}]}}' --account 1234567890
5578
+ adkit manage google asset-groups delete 555666777 --account 1234567890 --publish`;
5579
+ var GOOGLE_ASSET_GROUP_HELP_FULL = GOOGLE_ASSET_GROUP_HELP;
5396
5580
  var GOOGLE_AD_GROUP_HELP = `adkit manage google ad-groups \u2014 Google Ads ad groups
5397
5581
 
5398
5582
  list List ad groups
@@ -6038,6 +6222,7 @@ Examples:
6038
6222
  adkit manage google accounts disconnect 1234567890`.trim(),
6039
6223
  "google assets": GOOGLE_ASSET_HELP,
6040
6224
  "google campaigns": GOOGLE_CAMPAIGN_HELP,
6225
+ "google asset-groups": GOOGLE_ASSET_GROUP_HELP,
6041
6226
  "google ad-groups": GOOGLE_AD_GROUP_HELP,
6042
6227
  "google ads": GOOGLE_AD_HELP,
6043
6228
  "google keywords": GOOGLE_KEYWORD_HELP,
@@ -6487,6 +6672,7 @@ Run adkit manage meta <group> --help for details.`.trim(),
6487
6672
  Entity groups:
6488
6673
  accounts List and connect Google Ads accounts
6489
6674
  campaigns Manage Search, Display, and Performance Max campaigns
6675
+ asset-groups Manage Performance Max asset groups
6490
6676
  ad-groups Manage ad groups and Display targeting
6491
6677
  ads Manage responsive search and Display ads
6492
6678
  keywords Manage keywords and negatives \u2014 config only, not metrics
@@ -6839,6 +7025,7 @@ var HELP_FULL = {
6839
7025
  "meta lead-forms": LEAD_FORM_HELP,
6840
7026
  "google assets": GOOGLE_ASSET_HELP_FULL,
6841
7027
  "google campaigns": GOOGLE_CAMPAIGN_HELP_FULL,
7028
+ "google asset-groups": GOOGLE_ASSET_GROUP_HELP_FULL,
6842
7029
  "google ad-groups": GOOGLE_AD_GROUP_HELP_FULL,
6843
7030
  "google ads": GOOGLE_AD_HELP_FULL,
6844
7031
  "google keywords": GOOGLE_KEYWORD_HELP_FULL,
@@ -6909,7 +7096,7 @@ function requireClient(flags, options = {}) {
6909
7096
  if (!apiKey) throw new CliError("NOT_AUTHENTICATED", "No API key found", "Run: adkit setup");
6910
7097
  const baseUrl = getBaseUrl();
6911
7098
  const projectId = options.projectOptional ? void 0 : selectedProject;
6912
- return new AdkitClient({ apiKey, baseUrl, clientVersion: CLI_VERSION, projectId });
7099
+ return new AdkitClient({ apiKey, baseUrl, clientVersion: CLI_VERSION, projectId, readQueryFallback: flags.data });
6913
7100
  }
6914
7101
  function toPrintListDisplayRows(rows, isJson) {
6915
7102
  if (isJson) return rows;
@@ -8002,7 +8189,7 @@ async function main() {
8002
8189
  data = await detachAsset(client, restArgs, flags);
8003
8190
  break;
8004
8191
  default:
8005
- if (GOOGLE_NUMERIC_ID_PATTERN.test(action)) data = await getAsset(client, [action], flags);
8192
+ if (GOOGLE_NUMERIC_ID_PATTERN2.test(action)) data = await getAsset(client, [action], flags);
8006
8193
  else throw new CliError("UNKNOWN_COMMAND", `Unknown action: google assets ${action}`, "Run: adkit manage google assets --help");
8007
8194
  }
8008
8195
  break;
@@ -8041,11 +8228,36 @@ async function main() {
8041
8228
  data = await deleteCampaign2(client, restArgs, flags);
8042
8229
  break;
8043
8230
  default:
8044
- if (GOOGLE_NUMERIC_ID_PATTERN.test(action)) data = await getCampaign2(client, [action], flags);
8231
+ if (GOOGLE_NUMERIC_ID_PATTERN2.test(action)) data = await getCampaign2(client, [action], flags);
8045
8232
  else throw new CliError("UNKNOWN_COMMAND", `Unknown action: google campaigns ${action}`, "Run: adkit manage google campaigns --help");
8046
8233
  }
8047
8234
  break;
8048
8235
  }
8236
+ case "asset-groups": {
8237
+ if (!action) {
8238
+ showHelp("google asset-groups", flags.help === "full");
8239
+ return;
8240
+ }
8241
+ switch (action) {
8242
+ case "list":
8243
+ data = await listGoogleAssetGroups(client, restArgs, flags);
8244
+ emptyHint = "No asset groups found in this campaign. Create one with `adkit manage google asset-groups create --help`.";
8245
+ break;
8246
+ case "create":
8247
+ data = await createGoogleAssetGroup(client, restArgs, flags);
8248
+ break;
8249
+ case "update":
8250
+ data = await updateGoogleAssetGroup(client, restArgs, flags);
8251
+ break;
8252
+ case "delete":
8253
+ data = await deleteGoogleAssetGroup(client, restArgs, flags);
8254
+ break;
8255
+ default:
8256
+ if (GOOGLE_NUMERIC_ID_PATTERN2.test(action)) data = await getGoogleAssetGroup(client, [action], flags);
8257
+ else throw new CliError("UNKNOWN_COMMAND", `Unknown action: google asset-groups ${action}`, "Run: adkit manage google asset-groups --help");
8258
+ }
8259
+ break;
8260
+ }
8049
8261
  case "ad-groups": {
8050
8262
  if (!action) {
8051
8263
  showHelp("google ad-groups", flags.help === "full");
@@ -8066,7 +8278,7 @@ async function main() {
8066
8278
  data = await deleteAdGroup(client, restArgs, flags);
8067
8279
  break;
8068
8280
  default:
8069
- if (GOOGLE_NUMERIC_ID_PATTERN.test(action)) data = await getAdGroup(client, [action], flags);
8281
+ if (GOOGLE_NUMERIC_ID_PATTERN2.test(action)) data = await getAdGroup(client, [action], flags);
8070
8282
  else throw new CliError("UNKNOWN_COMMAND", `Unknown action: google ad-groups ${action}`, "Run: adkit manage google ad-groups --help");
8071
8283
  }
8072
8284
  break;
@@ -8091,7 +8303,7 @@ async function main() {
8091
8303
  data = await deleteAd2(client, restArgs, flags);
8092
8304
  break;
8093
8305
  default:
8094
- if (GOOGLE_NUMERIC_ID_PATTERN.test(action)) {
8306
+ if (GOOGLE_NUMERIC_ID_PATTERN2.test(action)) {
8095
8307
  data = await getAd2(client, [action], flags);
8096
8308
  detailEntity = "google-ad";
8097
8309
  } else throw new CliError("UNKNOWN_COMMAND", `Unknown action: google ads ${action}`, "Run: adkit manage google ads --help");
@@ -8172,7 +8384,7 @@ async function main() {
8172
8384
  break;
8173
8385
  }
8174
8386
  default:
8175
- if (GOOGLE_NUMERIC_ID_PATTERN.test(action)) data = await getKeyword(client, [action], flags);
8387
+ if (GOOGLE_NUMERIC_ID_PATTERN2.test(action)) data = await getKeyword(client, [action], flags);
8176
8388
  else throw new CliError("UNKNOWN_COMMAND", `Unknown action: google keywords ${action}`, "Run: adkit manage google keywords --help (includes negatives)");
8177
8389
  }
8178
8390
  break;
@@ -8244,7 +8456,7 @@ async function main() {
8244
8456
  break;
8245
8457
  }
8246
8458
  default:
8247
- throw new CliError("UNKNOWN_COMMAND", `Unknown entity: google ${entity}`, "Available: accounts, assets, media, campaigns, ad-groups, ads, keywords, conversions, results, change-history, research, geo-locations");
8459
+ throw new CliError("UNKNOWN_COMMAND", `Unknown entity: google ${entity}`, "Available: accounts, assets, media, campaigns, asset-groups, ad-groups, ads, keywords, conversions, results, change-history, research, geo-locations");
8248
8460
  }
8249
8461
  } else if (platform2 === "tiktok") {
8250
8462
  switch (entity) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adkit/cli",
3
- "version": "1.13.20",
3
+ "version": "1.13.21",
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",