@adkit/cli 1.12.29 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +584 -13
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -744,14 +744,14 @@ var baseOpen = async (options) => {
744
744
  }
745
745
  const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
746
746
  if (options.wait) {
747
- return new Promise((resolve3, reject) => {
747
+ return new Promise((resolve4, reject) => {
748
748
  subprocess.once("error", reject);
749
749
  subprocess.once("close", (exitCode) => {
750
750
  if (!options.allowNonzeroExitCode && exitCode > 0) {
751
751
  reject(new Error(`Exited with code ${exitCode}`));
752
752
  return;
753
753
  }
754
- resolve3(subprocess);
754
+ resolve4(subprocess);
755
755
  });
756
756
  });
757
757
  }
@@ -3195,10 +3195,303 @@ async function disconnectRedditAccount(client, args, flags) {
3195
3195
  }
3196
3196
 
3197
3197
  // src/commands/linkedin.ts
3198
+ import { readFileSync as readFileSync4 } from "node:fs";
3199
+ import { basename as basename3, extname as extname3, resolve as resolve3 } from "node:path";
3200
+ var MEDIA_UPLOAD_FLAGS3 = ["file", "url", "base64", "adkit-media", "temporary-upload", "filename", "content-type", "name"];
3201
+ var CAMPAIGN_LIST_FLAGS3 = ["campaign-ids", "limit", "offset"];
3202
+ var AD_GROUP_LIST_FLAGS3 = ["campaign-ids", "ad-group-ids", "limit", "offset"];
3203
+ var AD_LIST_FLAGS3 = ["campaign-ids", "limit", "offset"];
3204
+ var CAMPAIGN_CREATE_FLAGS3 = ["name", "status", "budget-daily", "budget-lifetime", "end-date"];
3205
+ var AD_GROUP_CREATE_FLAGS2 = ["campaign", "name", "objective", "status", "budget-daily", "budget-lifetime"];
3206
+ var AD_CREATE_FLAGS2 = ["ad-group", "existing-post", "name", "status"];
3207
+ var CAMPAIGN_UPDATE_FLAGS3 = ["name", "status", "budget-lifetime", "end-date"];
3208
+ var AD_GROUP_UPDATE_FLAGS2 = ["name", "status", "budget-daily", "budget-lifetime", "start-date", "end-date"];
3209
+ var AD_UPDATE_FLAGS2 = ["name", "status"];
3210
+ var TARGETING_SEARCH_FLAGS = ["account", "facet", "query", "limit"];
3211
+ var RESULTS_FLAGS2 = ["level", "period", "fields", "breakdowns", "from", "to", "sort", "limit", "offset"];
3198
3212
  async function listLinkedInAvailableAccounts(client, _args, flags) {
3199
3213
  validateFlags(flags, [], "manage linkedin accounts available");
3200
3214
  return client.get("/manage/linkedin/accounts/available");
3201
3215
  }
3216
+ async function uploadLinkedInMedia(client, _args, flags) {
3217
+ validateFlags(flags, MEDIA_UPLOAD_FLAGS3, "manage linkedin media upload");
3218
+ if (typeof flags.data === "string") {
3219
+ const body2 = parseDataFlag(flags, 'Use { "assets": [...] } for LinkedIn media upload');
3220
+ const qs2 = buildLinkedInMutationQuery(flags);
3221
+ return client.post(`/manage/linkedin/media${qs2}`, body2);
3222
+ }
3223
+ const source = buildLinkedInMediaUploadSource(flags);
3224
+ const body = { assets: [source] };
3225
+ const qs = buildLinkedInMutationQuery(flags);
3226
+ return client.post(`/manage/linkedin/media${qs}`, body);
3227
+ }
3228
+ async function getLinkedInMedia(client, args, flags) {
3229
+ validateFlags(flags, [], "manage linkedin media <urn>");
3230
+ const id = requireArg(args, 0, "media-urn", "Run: adkit manage linkedin media <media-urn>");
3231
+ const qs = buildLinkedInReadQuery(flags);
3232
+ return client.get(`/manage/linkedin/media/${id}${qs}`);
3233
+ }
3234
+ async function listLinkedInCampaigns(client, _args, flags) {
3235
+ validateFlags(flags, CAMPAIGN_LIST_FLAGS3, "manage linkedin campaigns list");
3236
+ const qs = buildLinkedInListQuery(flags, ["campaign-ids"]);
3237
+ return client.get(`/manage/linkedin/campaigns${qs}`);
3238
+ }
3239
+ async function getLinkedInCampaign(client, args, flags) {
3240
+ validateFlags(flags, [], "manage linkedin campaigns <id>");
3241
+ const id = requireArg(args, 0, "campaign-id", "Run: adkit manage linkedin campaigns <campaign-id>");
3242
+ const qs = buildLinkedInReadQuery(flags);
3243
+ return client.get(`/manage/linkedin/campaigns/${id}${qs}`);
3244
+ }
3245
+ async function createLinkedInCampaign(client, _args, flags) {
3246
+ validateFlags(flags, CAMPAIGN_CREATE_FLAGS3, "manage linkedin campaigns create");
3247
+ const qs = buildLinkedInMutationQuery(flags);
3248
+ if (typeof flags.data === "string") {
3249
+ const body2 = parseDataFlag(flags, 'Use { "campaigns": [...] } for LinkedIn campaign create');
3250
+ return client.post(`/manage/linkedin/campaigns${qs}`, body2);
3251
+ }
3252
+ const body = { campaigns: [buildLinkedInCampaignPayload(flags)] };
3253
+ return client.post(`/manage/linkedin/campaigns${qs}`, body);
3254
+ }
3255
+ async function updateLinkedInCampaign(client, args, flags) {
3256
+ validateFlags(flags, CAMPAIGN_UPDATE_FLAGS3, "manage linkedin campaigns update");
3257
+ const id = requireArg(args, 0, "campaign-id", "Run: adkit manage linkedin campaigns update <campaign-id> --status paused");
3258
+ const qs = buildLinkedInMutationQuery(flags);
3259
+ const body = typeof flags.data === "string" ? parseDataFlag(flags, "Use a flat LinkedIn campaign changes object") : buildLinkedInCampaignUpdatePayload(flags);
3260
+ return client.patch(`/manage/linkedin/campaigns/${id}${qs}`, body);
3261
+ }
3262
+ async function listLinkedInAdGroups(client, _args, flags) {
3263
+ validateFlags(flags, AD_GROUP_LIST_FLAGS3, "manage linkedin ad-groups list");
3264
+ const qs = buildLinkedInListQuery(flags, ["campaign-ids", "ad-group-ids"]);
3265
+ return client.get(`/manage/linkedin/ad-groups${qs}`);
3266
+ }
3267
+ async function getLinkedInAdGroup(client, args, flags) {
3268
+ validateFlags(flags, [], "manage linkedin ad-groups <id>");
3269
+ const id = requireArg(args, 0, "ad-group-id", "Run: adkit manage linkedin ad-groups <ad-group-id>");
3270
+ const qs = buildLinkedInReadQuery(flags);
3271
+ return client.get(`/manage/linkedin/ad-groups/${id}${qs}`);
3272
+ }
3273
+ async function createLinkedInAdGroup(client, _args, flags) {
3274
+ validateFlags(flags, AD_GROUP_CREATE_FLAGS2, "manage linkedin ad-groups create");
3275
+ const qs = buildLinkedInMutationQuery(flags);
3276
+ if (typeof flags.data === "string") {
3277
+ const body2 = parseDataFlag(flags, 'Use { "adGroups": [...] } for LinkedIn ad group create');
3278
+ return client.post(`/manage/linkedin/ad-groups${qs}`, body2);
3279
+ }
3280
+ const body = { adGroups: [buildLinkedInAdGroupPayload(flags)] };
3281
+ return client.post(`/manage/linkedin/ad-groups${qs}`, body);
3282
+ }
3283
+ async function updateLinkedInAdGroup(client, args, flags) {
3284
+ validateFlags(flags, AD_GROUP_UPDATE_FLAGS2, "manage linkedin ad-groups update");
3285
+ const id = requireArg(args, 0, "ad-group-id", "Run: adkit manage linkedin ad-groups update <ad-group-id> --status paused");
3286
+ const qs = buildLinkedInMutationQuery(flags);
3287
+ const body = typeof flags.data === "string" ? parseDataFlag(flags, "Use a flat LinkedIn ad group changes object") : buildLinkedInAdGroupUpdatePayload(flags);
3288
+ return client.patch(`/manage/linkedin/ad-groups/${id}${qs}`, body);
3289
+ }
3290
+ async function listLinkedInAds(client, _args, flags) {
3291
+ validateFlags(flags, AD_LIST_FLAGS3, "manage linkedin ads list");
3292
+ const qs = buildLinkedInListQuery(flags, ["campaign-ids"]);
3293
+ return client.get(`/manage/linkedin/ads${qs}`);
3294
+ }
3295
+ async function getLinkedInAd(client, args, flags) {
3296
+ validateFlags(flags, [], "manage linkedin ads <id>");
3297
+ const id = requireArg(args, 0, "ad-id", "Run: adkit manage linkedin ads <ad-id>");
3298
+ const qs = buildLinkedInReadQuery(flags);
3299
+ return client.get(`/manage/linkedin/ads/${id}${qs}`);
3300
+ }
3301
+ async function createLinkedInAd(client, _args, flags) {
3302
+ validateFlags(flags, AD_CREATE_FLAGS2, "manage linkedin ads create");
3303
+ const qs = buildLinkedInMutationQuery(flags);
3304
+ if (typeof flags.data === "string") {
3305
+ const body2 = parseDataFlag(flags, 'Use { "ads": [...] } for LinkedIn ad create');
3306
+ return client.post(`/manage/linkedin/ads${qs}`, body2);
3307
+ }
3308
+ if (typeof flags["existing-post"] !== "string") throw new CliError("MISSING_FLAG", "A LinkedIn ad needs a creative or an existing post", `Inline ad: --data '{"ads":[{"adGroupId":"...","creative":{"media":[{"role":"image","id":"urn:li:image:..."}]}}]}'. Reference ad: --existing-post <share/ugcPost URN>`);
3309
+ const body = { ads: [buildLinkedInAdPayload(flags)] };
3310
+ return client.post(`/manage/linkedin/ads${qs}`, body);
3311
+ }
3312
+ async function updateLinkedInAd(client, args, flags) {
3313
+ validateFlags(flags, AD_UPDATE_FLAGS2, "manage linkedin ads update");
3314
+ const id = requireArg(args, 0, "ad-id", "Run: adkit manage linkedin ads update <ad-id> --status paused");
3315
+ const qs = buildLinkedInMutationQuery(flags);
3316
+ const body = typeof flags.data === "string" ? parseDataFlag(flags, "Use a flat LinkedIn ad changes object (name/status only)") : buildLinkedInAdUpdatePayload(flags);
3317
+ return client.patch(`/manage/linkedin/ads/${id}${qs}`, body);
3318
+ }
3319
+ async function searchLinkedInTargeting(client, _args, flags) {
3320
+ validateFlags(flags, TARGETING_SEARCH_FLAGS, "manage linkedin targeting-search");
3321
+ const facet = requireFlag(flags, "facet", "Use jobTitles, seniorities, jobFunctions, industries, companySizes, companies, skills, interests, geoLocations, ageRanges, genders, or customAudiences");
3322
+ const accountId = typeof flags.account === "string" ? flags.account : void 0;
3323
+ const query = typeof flags.query === "string" ? flags.query : void 0;
3324
+ const limit = typeof flags.limit === "string" ? flags.limit : void 0;
3325
+ const qs = queryString({ accountId, facet, query, limit });
3326
+ return client.get(`/manage/linkedin/targeting-search${qs}`);
3327
+ }
3328
+ async function listLinkedInResults(client, _args, flags) {
3329
+ validateFlags(flags, RESULTS_FLAGS2, "manage linkedin results");
3330
+ const accountId = typeof flags.account === "string" ? flags.account : void 0;
3331
+ const level = requireFlag(flags, "level", "Use --level account, campaigns, ad-groups, or ads");
3332
+ const query = queryString({
3333
+ accountId,
3334
+ level,
3335
+ period: typeof flags.period === "string" ? flags.period : void 0,
3336
+ fields: typeof flags.fields === "string" ? flags.fields : void 0,
3337
+ from: typeof flags.from === "string" ? flags.from : void 0,
3338
+ to: typeof flags.to === "string" ? flags.to : void 0,
3339
+ breakdowns: typeof flags.breakdowns === "string" ? flags.breakdowns : void 0,
3340
+ sort: typeof flags.sort === "string" ? flags.sort : void 0,
3341
+ limit: typeof flags.limit === "string" ? flags.limit : void 0,
3342
+ offset: typeof flags.offset === "string" ? flags.offset : void 0
3343
+ });
3344
+ return client.get(`/manage/linkedin/results${query}`);
3345
+ }
3346
+ function buildLinkedInListQuery(flags, filterKeys) {
3347
+ const accountId = typeof flags.account === "string" ? flags.account : void 0;
3348
+ const campaignIds = filterKeys.includes("campaign-ids") && typeof flags["campaign-ids"] === "string" ? flags["campaign-ids"] : void 0;
3349
+ const adGroupIds = filterKeys.includes("ad-group-ids") && typeof flags["ad-group-ids"] === "string" ? flags["ad-group-ids"] : void 0;
3350
+ const limit = typeof flags.limit === "string" ? flags.limit : void 0;
3351
+ const offset = typeof flags.offset === "string" ? flags.offset : void 0;
3352
+ return queryString({ accountId, campaignIds, adGroupIds, limit, offset });
3353
+ }
3354
+ function buildLinkedInReadQuery(flags) {
3355
+ const accountId = typeof flags.account === "string" ? flags.account : void 0;
3356
+ return queryString({ accountId });
3357
+ }
3358
+ function buildLinkedInMutationQuery(flags) {
3359
+ const accountId = typeof flags.account === "string" ? flags.account : void 0;
3360
+ const publish = flags.publish === true ? "true" : void 0;
3361
+ return queryString({ accountId, publish });
3362
+ }
3363
+ function buildLinkedInCampaignPayload(flags) {
3364
+ const payload = {};
3365
+ payload.name = requireFlag(flags, "name", 'Run: adkit manage linkedin campaigns create --name "Spring Sale"');
3366
+ if (typeof flags.status === "string") payload.status = flags.status;
3367
+ if (typeof flags["end-date"] === "string") payload.endDate = flags["end-date"];
3368
+ const budget = buildLinkedInBudget(flags);
3369
+ if (budget) payload.budget = budget;
3370
+ if (budget?.lifetime !== void 0 && payload.endDate === void 0) throw new CliError("MISSING_FLAG", "A lifetime budget needs an end date", "Pass --end-date YYYY-MM-DD alongside --budget-lifetime");
3371
+ return payload;
3372
+ }
3373
+ function buildLinkedInAdGroupPayload(flags) {
3374
+ const payload = {};
3375
+ payload.campaignId = requireFlag(flags, "campaign", 'Run: adkit manage linkedin ad-groups create --campaign <campaign-id> --name "US Broad"');
3376
+ payload.name = requireFlag(flags, "name", 'Run: adkit manage linkedin ad-groups create --name "US Broad"');
3377
+ const budget = buildLinkedInBudget(flags);
3378
+ if (budget) payload.budget = budget;
3379
+ if (typeof flags.objective === "string") payload.objective = flags.objective;
3380
+ if (typeof flags.status === "string") payload.status = flags.status;
3381
+ return payload;
3382
+ }
3383
+ function buildLinkedInAdPayload(flags) {
3384
+ const payload = {};
3385
+ payload.adGroupId = requireFlag(flags, "ad-group", "Run: adkit manage linkedin ads create --ad-group <ad-group-id> --existing-post <share/ugcPost URN>");
3386
+ payload.existingPostId = requireFlag(flags, "existing-post", "Run: adkit manage linkedin ads create --ad-group <ad-group-id> --existing-post <share/ugcPost URN>");
3387
+ if (typeof flags.name === "string") payload.name = flags.name;
3388
+ if (typeof flags.status === "string") payload.status = flags.status;
3389
+ return payload;
3390
+ }
3391
+ function buildLinkedInCampaignUpdatePayload(flags) {
3392
+ const payload = {};
3393
+ if (typeof flags.name === "string") payload.name = flags.name;
3394
+ if (typeof flags.status === "string") payload.status = flags.status;
3395
+ if (typeof flags["end-date"] === "string") payload.endDate = flags["end-date"];
3396
+ const budget = buildLinkedInBudget(flags);
3397
+ if (budget) payload.budget = budget;
3398
+ if (budget?.lifetime !== void 0 && payload.endDate === void 0) throw new CliError("MISSING_FLAG", "A lifetime budget needs an end date", "Pass --end-date YYYY-MM-DD alongside --budget-lifetime");
3399
+ return payload;
3400
+ }
3401
+ function buildLinkedInAdGroupUpdatePayload(flags) {
3402
+ const payload = {};
3403
+ if (typeof flags.name === "string") payload.name = flags.name;
3404
+ if (typeof flags.status === "string") payload.status = flags.status;
3405
+ if (typeof flags["start-date"] === "string") payload.startDate = flags["start-date"];
3406
+ if (typeof flags["end-date"] === "string") payload.endDate = flags["end-date"];
3407
+ const budget = buildLinkedInBudget(flags);
3408
+ if (budget) payload.budget = budget;
3409
+ return payload;
3410
+ }
3411
+ function buildLinkedInAdUpdatePayload(flags) {
3412
+ const payload = {};
3413
+ if (typeof flags.name === "string") payload.name = flags.name;
3414
+ if (typeof flags.status === "string") payload.status = flags.status;
3415
+ return payload;
3416
+ }
3417
+ function buildLinkedInBudget(flags) {
3418
+ const daily = readNumberFlag3(flags, "budget-daily");
3419
+ const lifetime = readNumberFlag3(flags, "budget-lifetime");
3420
+ if (daily === void 0 && lifetime === void 0) return void 0;
3421
+ if (daily !== void 0 && lifetime !== void 0) throw new CliError("INVALID_VALUE", "Use either --budget-daily or --budget-lifetime", "LinkedIn accepts one budget mode at a time");
3422
+ return daily !== void 0 ? { daily } : { lifetime };
3423
+ }
3424
+ function readNumberFlag3(flags, key) {
3425
+ const value = flags[key];
3426
+ if (value === void 0) return void 0;
3427
+ if (typeof value !== "string") throw new CliError("INVALID_VALUE", `Use one --${key} value`, `Run: adkit manage linkedin --help`);
3428
+ const parsed = Number(value);
3429
+ if (!Number.isFinite(parsed)) throw new CliError("INVALID_VALUE", `Invalid number for --${key}: ${value}`, `Use a numeric value for --${key}`);
3430
+ return parsed;
3431
+ }
3432
+ function readSingleFlagValue3(flags, key, duplicateHint) {
3433
+ const value = flags[key];
3434
+ if (Array.isArray(value)) {
3435
+ if (value.length > 1) throw new CliError("INVALID_VALUE", `Use one --${key} value`, duplicateHint);
3436
+ return typeof value[0] === "string" ? value[0] : void 0;
3437
+ }
3438
+ return typeof value === "string" ? value : void 0;
3439
+ }
3440
+ function inferLinkedInMediaContentType(filename) {
3441
+ const extension = extname3(filename).toLowerCase();
3442
+ if (extension === ".jpg" || extension === ".jpeg") return "image/jpeg";
3443
+ if (extension === ".png") return "image/png";
3444
+ if (extension === ".gif") return "image/gif";
3445
+ if (extension === ".mp4") return "video/mp4";
3446
+ if (extension === ".mov") return "video/quicktime";
3447
+ return void 0;
3448
+ }
3449
+ function buildLinkedInMediaUploadSource(flags) {
3450
+ const filePath = readSingleFlagValue3(flags, "file", "Use one media source per command, or switch to --data for batch upload");
3451
+ const url = readSingleFlagValue3(flags, "url", "Use one media source per command, or switch to --data for batch upload");
3452
+ const base64 = readSingleFlagValue3(flags, "base64", "Use one media source per command, or switch to --data for batch upload");
3453
+ const adkitMediaId = readSingleFlagValue3(flags, "adkit-media", "Use one media source per command, or switch to --data for batch upload");
3454
+ const temporaryUploadId = readSingleFlagValue3(flags, "temporary-upload", "Use one media source per command, or switch to --data for batch upload");
3455
+ const sourceCount = [filePath, url, base64, adkitMediaId, temporaryUploadId].filter(Boolean).length;
3456
+ if (sourceCount === 0) throw new CliError("MISSING_FLAG", "Missing required media source flag", "Use --file, --url, --base64, --adkit-media, or --temporary-upload");
3457
+ if (sourceCount > 1) throw new CliError("INVALID_VALUE", "Use exactly one media source flag", "Use one of --file, --url, --base64, --adkit-media, or --temporary-upload");
3458
+ const filename = readSingleFlagValue3(flags, "filename", "Use one --filename value");
3459
+ const contentType = readSingleFlagValue3(flags, "content-type", "Use one --content-type value");
3460
+ const name = readSingleFlagValue3(flags, "name", "Use one --name value");
3461
+ if (filePath) {
3462
+ const resolvedPath = resolve3(filePath);
3463
+ const resolvedFilename = filename ?? basename3(resolvedPath);
3464
+ const resolvedContentType = contentType ?? inferLinkedInMediaContentType(resolvedFilename);
3465
+ const data = readFileSync4(resolvedPath).toString("base64");
3466
+ const source2 = { source: "base64", data, filename: resolvedFilename };
3467
+ if (resolvedContentType) source2.contentType = resolvedContentType;
3468
+ if (name) source2.name = name;
3469
+ return source2;
3470
+ }
3471
+ if (url) {
3472
+ const source2 = { source: "url", url };
3473
+ if (filename) source2.filename = filename;
3474
+ if (contentType) source2.contentType = contentType;
3475
+ if (name) source2.name = name;
3476
+ return source2;
3477
+ }
3478
+ if (base64) {
3479
+ if (!filename) throw new CliError("MISSING_FLAG", "Missing required flag: `--filename`", "Base64 uploads need a filename so LinkedIn can infer media type");
3480
+ const source2 = { source: "base64", data: base64, filename };
3481
+ if (contentType) source2.contentType = contentType;
3482
+ if (name) source2.name = name;
3483
+ return source2;
3484
+ }
3485
+ if (adkitMediaId) {
3486
+ const source2 = { source: "adkit_media", mediaId: adkitMediaId };
3487
+ if (name) source2.name = name;
3488
+ return source2;
3489
+ }
3490
+ if (!temporaryUploadId) throw new CliError("MISSING_FLAG", "Missing required flag: `--temporary-upload`", "Use --temporary-upload <upload-id>");
3491
+ const source = { source: "temporary_upload", uploadId: temporaryUploadId };
3492
+ if (name) source.name = name;
3493
+ return source;
3494
+ }
3202
3495
 
3203
3496
  // src/commands/status.ts
3204
3497
  function isStatusResponse(value) {
@@ -3659,8 +3952,8 @@ async function pollMedia(client, mediaIds, fields) {
3659
3952
  if (!currentMedia || typeof currentMedia.status !== "string") throw new CliError("SERVER_ERROR", `Invalid media response while polling ${id}`);
3660
3953
  media = currentMedia;
3661
3954
  if (currentMedia.status === "completed" || currentMedia.status === "failed") break;
3662
- await new Promise((resolve3) => {
3663
- setTimeout(resolve3, 2e3);
3955
+ await new Promise((resolve4) => {
3956
+ setTimeout(resolve4, 2e3);
3664
3957
  });
3665
3958
  }
3666
3959
  results.push(media);
@@ -3917,14 +4210,14 @@ function toGoogleSearchTermDisplayRows(row) {
3917
4210
  import { createRequire } from "node:module";
3918
4211
 
3919
4212
  // src/update-check.ts
3920
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "node:fs";
4213
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "node:fs";
3921
4214
  import { join as join2 } from "node:path";
3922
4215
  var CACHE_PATH = join2(CONFIG_DIR, "update-check.json");
3923
4216
  var FETCH_TIMEOUT_MS = 3e3;
3924
4217
  var REGISTRY_URL = "https://registry.npmjs.org/@adkit/cli/latest";
3925
4218
  function readCache() {
3926
4219
  try {
3927
- const raw = readFileSync4(CACHE_PATH, "utf-8");
4220
+ const raw = readFileSync5(CACHE_PATH, "utf-8");
3928
4221
  const parsed = JSON.parse(raw);
3929
4222
  if (!parsed || typeof parsed !== "object") return void 0;
3930
4223
  const cache = parsed;
@@ -4123,6 +4416,7 @@ var DEFAULT_BASE_URL = "https://app.adkit.so/api/v1";
4123
4416
  var GOOGLE_NUMERIC_ID_PATTERN = /^\d+$/;
4124
4417
  var GOOGLE_RESULTS_LEVEL_ACTIONS = /* @__PURE__ */ new Set(["campaigns", "ad-groups", "ads"]);
4125
4418
  var TIKTOK_NUMERIC_ID_PATTERN = /^\d+$/;
4419
+ var LINKEDIN_ID_PATTERN = /[\d:_]/;
4126
4420
  var USAGE = `
4127
4421
  Usage: adkit <command> [options]
4128
4422
 
@@ -5651,6 +5945,177 @@ Note:
5651
5945
 
5652
5946
  Example:
5653
5947
  adkit manage tiktok results --level campaigns --fields spend,impressions,clicks --breakdowns day --from 2026-05-01 --to 2026-05-15`.trim(),
5948
+ "linkedin campaigns": `adkit manage linkedin campaigns \u2014 LinkedIn campaigns (LinkedIn "Campaign Groups")
5949
+
5950
+ list List campaigns
5951
+ create Create a campaign draft
5952
+ update <id> Update a campaign (rename, pause/resume, budget)
5953
+ <id> View campaign details
5954
+
5955
+ Naming note:
5956
+ AdKit "campaigns" are LinkedIn "Campaign Groups". Objective, daily budgets, and targeting live on ad-groups; this tier takes lifetime budgets only.
5957
+ IDs copied from Campaign Manager's "Campaigns" tab are AdKit ad-group IDs, not campaign IDs.
5958
+
5959
+ Flags:
5960
+ ${FLAG.account}
5961
+ ${FLAG.publish}
5962
+ ${FLAG.data}
5963
+ --limit <n> Max rows for list
5964
+ --offset <n> Row offset for list
5965
+ --campaign-ids <ids> Comma-separated campaign IDs for filtered reads
5966
+ --name <name> Campaign name
5967
+ --status <s> create: active or draft (defaults to draft). update: active, paused, or archived
5968
+ --budget-lifetime <n> Lifetime budget in account currency
5969
+ --end-date <date> YYYY-MM-DD; required with --budget-lifetime
5970
+
5971
+ Notes:
5972
+ This tier takes lifetime budgets only; a daily budget here returns a 500. Set daily budgets on ad-groups.
5973
+ A live campaign can never go back to draft.
5974
+
5975
+ Examples:
5976
+ adkit manage linkedin campaigns list --account 512345678
5977
+ adkit manage linkedin campaigns create --name "Spring Sale" --budget-lifetime 500 --end-date 2026-08-31
5978
+ adkit manage linkedin campaigns update 628940516 --account 512345678 --status paused
5979
+ adkit manage linkedin campaigns 628940516 --account 512345678`.trim(),
5980
+ "linkedin ad-groups": `adkit manage linkedin ad-groups \u2014 LinkedIn ad groups (LinkedIn "Campaigns"/"Ad sets")
5981
+
5982
+ list List ad groups
5983
+ create Create an ad group draft
5984
+ update <id> Update an ad group (rename, pause/resume, budget, dates, targeting)
5985
+ <id> View ad group details
5986
+
5987
+ Naming note:
5988
+ AdKit "ad-groups" are what Campaign Manager now calls "Campaigns" (formerly "Ad sets"). Objective, budget, and targeting live here.
5989
+ IDs copied from Campaign Manager's "Campaigns" tab are AdKit ad-group IDs.
5990
+
5991
+ Flags:
5992
+ ${FLAG.account}
5993
+ ${FLAG.publish}
5994
+ ${FLAG.data}
5995
+ --limit <n> Max rows for list
5996
+ --offset <n> Row offset for list
5997
+ --campaign-ids <ids> Comma-separated parent campaign IDs for filtered reads
5998
+ --ad-group-ids <ids> Comma-separated ad group IDs for filtered reads
5999
+ --campaign <id> Parent campaign ID (create only)
6000
+ --name <name> Ad group name
6001
+ --objective <type> brand_awareness, engagement, website_visits, video_views, website_conversions
6002
+ --status <s> create: active or draft (defaults to draft). update: active, paused, or archived
6003
+ --budget-daily <n> Daily budget in account currency
6004
+ --budget-lifetime <n> Lifetime budget in account currency
6005
+ --start-date <date> YYYY-MM-DD (update only)
6006
+ --end-date <date> YYYY-MM-DD (update only)
6007
+
6008
+ Targeting (--data only):
6009
+ targeting is nested: { geoLocations: { include: [{ type: "country", code }] }, audience: { <facet>: { include?: [ids], exclude?: [ids] } } }.
6010
+ Values are bare IDs \u2014 use "adkit manage linkedin targeting-search" to find them.
6011
+ Facets: jobTitles, seniorities, jobFunctions, industries, companySizes, companies, skills, interests, customAudiences, ageRanges, genders.
6012
+ ageRanges/genders are include-only (LinkedIn rejects them in exclude): ageRanges take tuples like (25,34), genders take FEMALE/MALE.
6013
+ customAudiences goes inside targeting.audience (never top-level): pass the segment ID/URN. Matched Audiences are created in Campaign Manager, but you list them with "adkit manage linkedin targeting-search --facet customAudiences". Choose a language via platformOverrides.locale.
6014
+
6015
+ Notes:
6016
+ A live ad group can never go back to draft. targeting on update is a full replace (send the whole targeting object via --data).
6017
+
6018
+ Examples:
6019
+ adkit manage linkedin ad-groups create --campaign 628940516 --name "US Broad" --budget-daily 50 --objective website_visits
6020
+ adkit manage linkedin ad-groups update 361387516 --account 512345678 --status paused
6021
+ adkit manage linkedin ad-groups create --data '{"adGroups":[{"campaignId":"628940516","name":"US Senior Eng","budget":{"daily":50},"targeting":{"geoLocations":{"include":[{"type":"country","code":"103644278"}]},"audience":{"seniorities":{"include":["6"]},"customAudiences":{"include":["123456"]}}}}]}'`.trim(),
6022
+ "linkedin ads": `adkit manage linkedin ads \u2014 LinkedIn ads
6023
+
6024
+ list List ads
6025
+ create Create an ad draft
6026
+ update <id> Update an ad (rename, pause/resume) \u2014 name and status only
6027
+ <id> View ad details
6028
+
6029
+ Flags:
6030
+ ${FLAG.account}
6031
+ ${FLAG.publish}
6032
+ ${FLAG.data}
6033
+ --limit <n> Max rows for list
6034
+ --offset <n> Row offset for list
6035
+ --campaign-ids <ids> Comma-separated parent ad-group IDs for filtered reads
6036
+ --ad-group <id> Parent ad group ID (create only)
6037
+ --existing-post <urn> Reference an existing share/ugcPost URN (create only)
6038
+ --name <name> Ad name
6039
+ --status <s> create: active or draft (defaults to draft). update: active, paused, or archived
6040
+
6041
+ Create:
6042
+ Inline ad: pass --data with a creative \u2014 keys are primaryTexts, headlines, media, url, cta. creative.media needs at least one { role: "image"|"video", id: "urn:li:image:..." } entry.
6043
+ Reference ad: pass --existing-post <share/ugcPost URN>; omit creative entirely (its text/url/media are ignored).
6044
+ creative.cta is the button label \u2014 one of: apply, download, view_quote, learn_more, sign_up, subscribe, register, join, attend, request_demo, see_more, buy_now, shop_now. It needs creative.url and cannot be combined with existing-post.
6045
+ Media IDs are the opaque URNs returned by "adkit manage linkedin media upload".
6046
+
6047
+ Update:
6048
+ Rename + pause/resume only \u2014 creative content and the parent ad-group are locked after create.
6049
+ Pausing an ACTIVE ad only works once LinkedIn review has APPROVED the creative; a still-in-review ad rejects the pause. A live ad can never go back to draft.
6050
+
6051
+ Examples:
6052
+ adkit manage linkedin ads list --account 512345678
6053
+ adkit manage linkedin ads create --data '{"ads":[{"adGroupId":"361387516","creative":{"media":[{"role":"image","id":"urn:li:image:C123"}]}}]}'
6054
+ adkit manage linkedin ads create --ad-group 361387516 --existing-post urn:li:share:123456
6055
+ adkit manage linkedin ads update 361387516 --account 512345678 --status paused`.trim(),
6056
+ "linkedin media": `adkit manage linkedin media \u2014 Upload creative media and fetch by URN
6057
+
6058
+ upload Upload an image or video and return a LinkedIn media URN
6059
+ <urn> Fetch a single media asset by URN
6060
+
6061
+ Flags:
6062
+ --file <path> Local image/video file
6063
+ --url <url> Public image/video URL
6064
+ --base64 <data> Base64 image/video data
6065
+ --adkit-media <id> Existing AdKit Studio media ID
6066
+ --temporary-upload <id> Temporary upload ID from the upload URL helper
6067
+ --filename <name> Required with --base64, optional with --url
6068
+ --content-type <t> image/jpeg, image/png, image/gif, video/mp4, or video/quicktime
6069
+ --name <name> Media asset name
6070
+ ${FLAG.account}
6071
+ --data <json> Batch body: {"assets":[...]}
6072
+
6073
+ Note:
6074
+ Media IDs are opaque URNs (e.g. urn:li:image:C123) returned by upload. Pass them into ad creatives via --data.
6075
+
6076
+ Examples:
6077
+ adkit manage linkedin media upload --url https://example.com/hero.png --filename hero.png --account 512345678
6078
+ adkit manage linkedin media upload --adkit-media media_123 --account 512345678
6079
+ adkit manage linkedin media urn:li:image:C123 --account 512345678`.trim(),
6080
+ "linkedin targeting-search": `adkit manage linkedin targeting-search \u2014 Look up bare targeting IDs
6081
+
6082
+ search Search a facet for the bare IDs used in ad group targeting
6083
+
6084
+ Flags:
6085
+ --facet <facet> jobTitles, seniorities, jobFunctions, industries, companySizes, companies, skills, interests, geoLocations, ageRanges, genders, customAudiences
6086
+ --query <text> Search text; optional for the enum facets (seniorities, jobFunctions, companySizes, ageRanges, genders, customAudiences)
6087
+ --limit <n> Max results
6088
+ --account <id> LinkedIn ad account; required when several accounts are connected
6089
+
6090
+ Note:
6091
+ Returned IDs are bare \u2014 feed them into ad-group targeting via --data.
6092
+ Matched Audiences are created in Campaign Manager, but you list them here with --facet customAudiences.
6093
+
6094
+ Examples:
6095
+ adkit manage linkedin targeting-search --facet jobTitles --query engineer
6096
+ adkit manage linkedin targeting-search --facet seniorities
6097
+ adkit manage linkedin targeting-search --facet customAudiences`.trim(),
6098
+ "linkedin results": `adkit manage linkedin results \u2014 LinkedIn performance reporting
6099
+
6100
+ list List reporting rows
6101
+
6102
+ Flags:
6103
+ ${FLAG.account}
6104
+ --level <level> Required: account, campaigns, ad-groups, ads
6105
+ --period <p> Rolling window, e.g. 14d (default: 14d); or use --from/--to
6106
+ --fields <csv> Normalized fields, e.g. cost,impressions,clicks
6107
+ --breakdowns <csv> Row breakdowns, e.g. day
6108
+ --from <date> YYYY-MM-DD
6109
+ --to <date> YYYY-MM-DD
6110
+ --sort <field> Optional normalized sort field, e.g. cost (default: cost). Rows always sort descending.
6111
+ --limit <n> Max rows
6112
+ --offset <n> Row offset
6113
+
6114
+ Note:
6115
+ --level is required. Results come back as a flat { results, currency, timezone, period, ... } envelope. Spend is reported as the canonical "cost" field. Sort is descending-only.
6116
+
6117
+ Example:
6118
+ adkit manage linkedin results --level campaigns --fields cost,impressions,clicks --breakdowns day --from 2026-05-01 --to 2026-05-15`.trim(),
5654
6119
  "reddit accounts": `adkit manage reddit accounts \u2014 Reddit Ads accounts
5655
6120
 
5656
6121
  list List connected Reddit ad accounts
@@ -5712,7 +6177,7 @@ Platforms:
5712
6177
  tiktok TikTok Ads
5713
6178
  reddit Reddit Ads
5714
6179
  x X Ads raw API access
5715
- linkedin LinkedIn Ads (account discovery + raw API access)
6180
+ linkedin LinkedIn Ads (campaigns, ad groups, ads, targeting, results)
5716
6181
  drafts Manage drafts across platforms
5717
6182
  platform-api-requests Send raw API requests (escape hatch)
5718
6183
 
@@ -5808,16 +6273,26 @@ Examples:
5808
6273
  linkedin: `adkit manage linkedin \u2014 LinkedIn Ads management
5809
6274
 
5810
6275
  Entity groups:
5811
- accounts available List LinkedIn ad accounts discoverable from connected workspace logins
6276
+ accounts Discover LinkedIn ad accounts (available)
6277
+ media Upload creative and fetch media by URN
6278
+ campaigns Manage campaigns (LinkedIn "Campaign Groups")
6279
+ ad-groups Manage ad groups (LinkedIn "Campaigns"/"Ad sets" \u2014 objective, budget, targeting)
6280
+ ads Manage ads
6281
+ targeting-search Look up bare targeting IDs by facet
6282
+ results Performance metrics
5812
6283
 
5813
- Account discovery currently supports "available" only \u2014 connect/disconnect/list are not exposed yet.
5814
- First-class LinkedIn campaign/ad reads and writes are not enabled yet.
5815
- ${rawPlatformCommandLine("linkedin")}
6284
+ Naming note (Campaign Manager renamed things in Oct 2025):
6285
+ AdKit "campaigns" = LinkedIn "Campaign Groups"
6286
+ AdKit "ad-groups" = LinkedIn "Campaigns" / "Ad sets" \u2014 this is where objective, budget, and targeting live.
6287
+ IDs copied from Campaign Manager's "Campaigns" tab are AdKit ad-group IDs.
5816
6288
 
5817
6289
  General flags:
5818
6290
  ${FLAG.account}
6291
+ ${FLAG.publish}
6292
+ ${FLAG.data}
5819
6293
 
5820
- Run adkit manage linkedin accounts available to discover ad accounts.`.trim(),
6294
+ ${rawPlatformCommandLine("linkedin")}
6295
+ Run adkit manage linkedin <group> --help for details.`.trim(),
5821
6296
  drafts: `adkit manage drafts \u2014 Manage drafts
5822
6297
 
5823
6298
  list List all drafts
@@ -7590,8 +8065,104 @@ async function main() {
7590
8065
  throw new CliError("UNKNOWN_COMMAND", `Unknown action: linkedin accounts ${action}`, "Available: available.");
7591
8066
  }
7592
8067
  break;
8068
+ case "media": {
8069
+ if (!action) {
8070
+ showHelp("linkedin media", flags.help === "full");
8071
+ return;
8072
+ }
8073
+ switch (action) {
8074
+ case "upload":
8075
+ data = await uploadLinkedInMedia(client, restArgs, flags);
8076
+ break;
8077
+ case "get":
8078
+ data = await getLinkedInMedia(client, restArgs, flags);
8079
+ break;
8080
+ default:
8081
+ if (LINKEDIN_ID_PATTERN.test(action)) data = await getLinkedInMedia(client, [action], flags);
8082
+ else throw new CliError("UNKNOWN_COMMAND", `Unknown action: linkedin media ${action}`, "Available: upload, get");
8083
+ }
8084
+ break;
8085
+ }
8086
+ case "campaigns": {
8087
+ if (!action) {
8088
+ showHelp("linkedin campaigns", flags.help === "full");
8089
+ return;
8090
+ }
8091
+ switch (action) {
8092
+ case "list":
8093
+ data = await listLinkedInCampaigns(client, restArgs, flags);
8094
+ emptyHint = "No LinkedIn campaigns found for this account.";
8095
+ break;
8096
+ case "create":
8097
+ data = await createLinkedInCampaign(client, restArgs, flags);
8098
+ break;
8099
+ case "update":
8100
+ data = await updateLinkedInCampaign(client, restArgs, flags);
8101
+ break;
8102
+ default:
8103
+ if (LINKEDIN_ID_PATTERN.test(action)) data = await getLinkedInCampaign(client, [action], flags);
8104
+ else throw new CliError("UNKNOWN_COMMAND", `Unknown action: linkedin campaigns ${action}`, "Available: list, create, update");
8105
+ }
8106
+ break;
8107
+ }
8108
+ case "ad-groups": {
8109
+ if (!action) {
8110
+ showHelp("linkedin ad-groups", flags.help === "full");
8111
+ return;
8112
+ }
8113
+ switch (action) {
8114
+ case "list":
8115
+ data = await listLinkedInAdGroups(client, restArgs, flags);
8116
+ emptyHint = "No LinkedIn ad groups found for this account.";
8117
+ break;
8118
+ case "create":
8119
+ data = await createLinkedInAdGroup(client, restArgs, flags);
8120
+ break;
8121
+ case "update":
8122
+ data = await updateLinkedInAdGroup(client, restArgs, flags);
8123
+ break;
8124
+ default:
8125
+ if (LINKEDIN_ID_PATTERN.test(action)) data = await getLinkedInAdGroup(client, [action], flags);
8126
+ else throw new CliError("UNKNOWN_COMMAND", `Unknown action: linkedin ad-groups ${action}`, "Available: list, create, update");
8127
+ }
8128
+ break;
8129
+ }
8130
+ case "ads": {
8131
+ if (!action) {
8132
+ showHelp("linkedin ads", flags.help === "full");
8133
+ return;
8134
+ }
8135
+ switch (action) {
8136
+ case "list":
8137
+ data = await listLinkedInAds(client, restArgs, flags);
8138
+ emptyHint = "No LinkedIn ads found for this account.";
8139
+ break;
8140
+ case "create":
8141
+ data = await createLinkedInAd(client, restArgs, flags);
8142
+ break;
8143
+ case "update":
8144
+ data = await updateLinkedInAd(client, restArgs, flags);
8145
+ break;
8146
+ default:
8147
+ if (LINKEDIN_ID_PATTERN.test(action)) data = await getLinkedInAd(client, [action], flags);
8148
+ else throw new CliError("UNKNOWN_COMMAND", `Unknown action: linkedin ads ${action}`, "Available: list, create, update");
8149
+ }
8150
+ break;
8151
+ }
8152
+ case "targeting-search": {
8153
+ if (action && action !== "search") throw new CliError("UNKNOWN_COMMAND", `Unknown action: linkedin targeting-search ${action}`, "Available: search");
8154
+ data = await searchLinkedInTargeting(client, restArgs, flags);
8155
+ emptyHint = "No LinkedIn targeting entities matched this facet/query.";
8156
+ break;
8157
+ }
8158
+ case "results": {
8159
+ if (action && action !== "list") throw new CliError("UNKNOWN_COMMAND", `Unknown action: linkedin results ${action}`, "Available: list");
8160
+ data = await listLinkedInResults(client, restArgs, flags);
8161
+ emptyHint = "No LinkedIn result rows found for the selected date range.";
8162
+ break;
8163
+ }
7593
8164
  default:
7594
- throw new CliError("UNKNOWN_COMMAND", `Unknown entity: linkedin ${entity}`, "Available: accounts, platform-api-requests");
8165
+ throw new CliError("UNKNOWN_COMMAND", `Unknown entity: linkedin ${entity}`, "Available: accounts, media, campaigns, ad-groups, ads, targeting-search, results, platform-api-requests");
7595
8166
  }
7596
8167
  } else if (platform2 === "x") throw new CliError("UNKNOWN_COMMAND", `Unknown entity: x ${entity}`, "Available: platform-api-requests");
7597
8168
  else throw new CliError("UNKNOWN_COMMAND", "Unknown platform", "Available: meta, google, tiktok, reddit, x, linkedin, drafts, platform-api-requests");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adkit/cli",
3
- "version": "1.12.29",
3
+ "version": "1.13.0",
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",