@adkit/cli 1.13.31 → 1.13.32

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 +889 -110
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1110,14 +1110,14 @@ var baseOpen = async (options) => {
1110
1110
  }
1111
1111
  const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
1112
1112
  if (options.wait) {
1113
- return new Promise((resolve4, reject) => {
1113
+ return new Promise((resolve5, reject) => {
1114
1114
  subprocess.once("error", reject);
1115
1115
  subprocess.once("close", (exitCode) => {
1116
1116
  if (!options.allowNonzeroExitCode && exitCode > 0) {
1117
1117
  reject(new Error(`Exited with code ${exitCode}`));
1118
1118
  return;
1119
1119
  }
1120
- resolve4(subprocess);
1120
+ resolve5(subprocess);
1121
1121
  });
1122
1122
  });
1123
1123
  }
@@ -2023,7 +2023,7 @@ var ASSET_GROUP_LIST_FLAGS = ["campaign", "limit", "offset"];
2023
2023
  var ASSET_GROUP_CREATE_FLAGS = ["campaign", "name", "status", "final-url"];
2024
2024
  var ASSET_GROUP_UPDATE_FLAGS = ["name", "status", "final-url"];
2025
2025
  var AD_GROUP_LIST_FLAGS = ["campaign", "status", "limit", "offset"];
2026
- var AD_GROUP_FLAGS = ["campaign", "name", "status", "cpc-bid"];
2026
+ var AD_GROUP_FLAGS = ["campaign", "name", "status", "cpc-bid", "target-cpa", "target-roas"];
2027
2027
  var AD_LIST_FLAGS = ["ad-group", "campaign", "status", "policy-status", "limit", "offset"];
2028
2028
  var AD_FLAGS2 = ["headline", "headline-1", "headline-2", "headline-3", "description", "description-1", "description-2", "callout", "sitelink", "final-url", "path", "ad-group", "status"];
2029
2029
  var KEYWORD_LIST_FLAGS = ["ad-group", "campaign", "status", "limit", "offset"];
@@ -2363,6 +2363,8 @@ function buildAdGroupPayload(flags) {
2363
2363
  if (typeof flags.name === "string") payload.name = flags.name;
2364
2364
  if (typeof flags.status === "string") payload.status = flags.status;
2365
2365
  if (typeof flags["cpc-bid"] === "string") payload.cpcBid = Number.parseFloat(flags["cpc-bid"]);
2366
+ if (typeof flags["target-cpa"] === "string") payload.targetCpa = readNumberFlag(flags, "target-cpa", "Use a positive number for --target-cpa");
2367
+ if (typeof flags["target-roas"] === "string") payload.targetRoas = readNumberFlag(flags, "target-roas", "Use a number from 0.01 to 1000 for --target-roas");
2366
2368
  return payload;
2367
2369
  }
2368
2370
  function buildAdPayload(flags) {
@@ -3587,6 +3589,29 @@ async function listTikTokResults(client, _args, flags) {
3587
3589
  }
3588
3590
 
3589
3591
  // src/commands/reddit.ts
3592
+ import { readFile } from "node:fs/promises";
3593
+ import { basename as basename3, extname as extname3, resolve as resolve3 } from "node:path";
3594
+ var ACCOUNT_RESOURCE_FLAGS = ["limit", "offset"];
3595
+ var CAMPAIGN_LIST_FLAGS3 = ["fields", "limit", "offset"];
3596
+ var CAMPAIGN_CREATE_FLAGS3 = ["name", "objective", "optimization", "status", "budget-lifetime", "bid-strategy", "start-date", "end-date", "funding-instrument", "pixel"];
3597
+ var CAMPAIGN_UPDATE_FLAGS3 = ["name", "status", "budget-lifetime", "bid-strategy", "start-date", "end-date", "funding-instrument", "pixel"];
3598
+ var AD_GROUP_LIST_FLAGS3 = ["campaign-ids", "fields", "limit", "offset"];
3599
+ var AD_GROUP_CREATE_FLAGS2 = ["campaign", "name", "status", "budget-daily", "bid-strategy", "bid-amount", "optimization", "start-date", "end-date", "pixel"];
3600
+ var AD_GROUP_UPDATE_FLAGS2 = ["name", "status", "budget-daily", "bid-strategy", "bid-amount", "start-date", "end-date", "pixel"];
3601
+ var AD_LIST_FLAGS3 = ["campaign-ids", "ad-group-ids", "fields", "limit", "offset"];
3602
+ var AD_CREATE_FLAGS2 = ["ad-group", "name", "status", "ad-type", "existing-post", "profile", "headline", "primary-text", "file", "media-id", "thumbnail-file", "thumbnail-id", "url", "cta"];
3603
+ var AD_UPDATE_FLAGS2 = ["ad-group", "name", "status", "ad-type", "profile", "headline", "primary-text", "file", "media-id", "thumbnail-file", "thumbnail-id", "url", "cta"];
3604
+ var RESULTS_FLAGS2 = ["level", "period", "from", "to", "fields", "breakdowns", "campaign-ids", "ad-group-ids", "ad-ids", "sort", "sort-direction", "limit", "offset", "raw"];
3605
+ var REDDIT_MEDIA_CONTENT_TYPES = {
3606
+ ".gif": "image/gif",
3607
+ ".jpeg": "image/jpeg",
3608
+ ".jpg": "image/jpeg",
3609
+ ".mov": "video/quicktime",
3610
+ ".mp4": "video/mp4",
3611
+ ".png": "image/png",
3612
+ ".webm": "video/webm",
3613
+ ".webp": "image/webp"
3614
+ };
3590
3615
  async function listRedditAccounts(client, _args, flags) {
3591
3616
  validateFlags(flags, [], "manage reddit accounts list");
3592
3617
  return client.get("/manage/reddit/accounts");
@@ -3597,30 +3622,384 @@ async function listRedditAvailableAccounts(client, _args, flags) {
3597
3622
  }
3598
3623
  async function connectRedditAccount(client, args, flags) {
3599
3624
  validateFlags(flags, [], "manage reddit accounts connect");
3600
- const adAccountId = requireArg(args, 0, "ad-account-id", "Run: adkit manage reddit accounts connect <ad-account-id>");
3601
- return client.post("/manage/reddit/accounts/connect", { adAccountId });
3625
+ const accountId = requireArg(args, 0, "account-id", "Run: adkit manage reddit accounts connect <account-id>");
3626
+ return client.post("/manage/reddit/accounts/connect", { adAccountId: accountId });
3602
3627
  }
3603
3628
  async function disconnectRedditAccount(client, args, flags) {
3604
3629
  validateFlags(flags, [], "manage reddit accounts disconnect");
3605
- const adAccountId = requireArg(args, 0, "ad-account-id", "Run: adkit manage reddit accounts disconnect <ad-account-id>");
3606
- return client.delete(`/manage/reddit/accounts/${adAccountId}`);
3630
+ const accountId = requireArg(args, 0, "account-id", "Run: adkit manage reddit accounts disconnect <account-id>");
3631
+ return client.delete(`/manage/reddit/accounts/${accountId}`);
3632
+ }
3633
+ async function listRedditProfiles(client, args, flags) {
3634
+ return listRedditAccountResource(client, args, flags, "profiles");
3635
+ }
3636
+ async function listRedditPixels(client, args, flags) {
3637
+ return listRedditAccountResource(client, args, flags, "pixels");
3638
+ }
3639
+ async function listRedditFundingInstruments(client, args, flags) {
3640
+ return listRedditAccountResource(client, args, flags, "funding-instruments");
3641
+ }
3642
+ async function updateRedditAccount(client, args, flags) {
3643
+ validateFlags(flags, ["default-profile", "default-pixel", "default-funding-instrument"], "manage reddit accounts <account-id> update");
3644
+ const accountId = requireArg(args, 0, "account-id", "Run: adkit manage reddit accounts <account-id> update --default-profile <profile-id>");
3645
+ if (typeof flags.data === "string") {
3646
+ const body = parseDataFlag(flags, 'Use { "defaultProfileId": "...", "defaultPixelId": "...", "defaultFundingInstrumentId": "..." }');
3647
+ return client.patch(`/manage/reddit/accounts/${accountId}`, body);
3648
+ }
3649
+ const defaultProfileId = readStringFlag(flags, "default-profile");
3650
+ const defaultPixelId = readStringFlag(flags, "default-pixel");
3651
+ const defaultFundingInstrumentId = readStringFlag(flags, "default-funding-instrument");
3652
+ if (!defaultProfileId && !defaultPixelId && !defaultFundingInstrumentId) throw new CliError("MISSING_FLAG", "Nothing to update", "Pass --default-profile, --default-pixel, --default-funding-instrument, or --data");
3653
+ return client.patch(`/manage/reddit/accounts/${accountId}`, {
3654
+ ...defaultProfileId ? { defaultProfileId } : {},
3655
+ ...defaultPixelId ? { defaultPixelId } : {},
3656
+ ...defaultFundingInstrumentId ? { defaultFundingInstrumentId } : {}
3657
+ });
3658
+ }
3659
+ async function listRedditCampaigns(client, _args, flags) {
3660
+ validateFlags(flags, CAMPAIGN_LIST_FLAGS3, "manage reddit campaigns list");
3661
+ const query = buildRedditListQuery(flags);
3662
+ return client.get(`/manage/reddit/campaigns${query}`);
3663
+ }
3664
+ async function getRedditCampaign(client, args, flags) {
3665
+ validateFlags(flags, ["fields"], "manage reddit campaigns <campaign-id>");
3666
+ const campaignId = requireArg(args, 0, "campaign-id", "Run: adkit manage reddit campaigns <campaign-id>");
3667
+ const query = buildRedditReadQuery(flags);
3668
+ return client.get(`/manage/reddit/campaigns/${campaignId}${query}`);
3669
+ }
3670
+ async function createRedditCampaign(client, _args, flags) {
3671
+ validateFlags(flags, CAMPAIGN_CREATE_FLAGS3, "manage reddit campaigns create");
3672
+ const query = buildRedditMutationQuery(flags);
3673
+ if (typeof flags.data === "string") {
3674
+ const body2 = parseDataFlag(flags, 'Use { "campaigns": [...] } for Reddit campaign create');
3675
+ return client.post(`/manage/reddit/campaigns${query}`, body2);
3676
+ }
3677
+ const campaign = buildRedditCampaignPayload(flags, "create");
3678
+ const body = { campaigns: [campaign] };
3679
+ return client.post(`/manage/reddit/campaigns${query}`, body);
3680
+ }
3681
+ async function updateRedditCampaign(client, args, flags) {
3682
+ validateFlags(flags, CAMPAIGN_UPDATE_FLAGS3, "manage reddit campaigns update");
3683
+ const campaignId = requireArg(args, 0, "campaign-id", "Run: adkit manage reddit campaigns update <campaign-id> --status paused");
3684
+ const body = typeof flags.data === "string" ? parseDataFlag(flags, "Use a flat Reddit campaign changes object") : buildRedditCampaignPayload(flags, "update");
3685
+ const query = buildRedditMutationQuery(flags);
3686
+ return client.patch(`/manage/reddit/campaigns/${campaignId}${query}`, body);
3687
+ }
3688
+ async function listRedditAdGroups(client, _args, flags) {
3689
+ validateFlags(flags, AD_GROUP_LIST_FLAGS3, "manage reddit ad-groups list");
3690
+ const campaignIds = readStringFlag(flags, "campaign-ids");
3691
+ const query = buildRedditListQuery(flags, { campaignIds });
3692
+ return client.get(`/manage/reddit/ad-groups${query}`);
3693
+ }
3694
+ async function getRedditAdGroup(client, args, flags) {
3695
+ validateFlags(flags, ["fields"], "manage reddit ad-groups <ad-group-id>");
3696
+ const adGroupId = requireArg(args, 0, "ad-group-id", "Run: adkit manage reddit ad-groups <ad-group-id>");
3697
+ const query = buildRedditReadQuery(flags);
3698
+ return client.get(`/manage/reddit/ad-groups/${adGroupId}${query}`);
3699
+ }
3700
+ async function createRedditAdGroup(client, _args, flags) {
3701
+ validateFlags(flags, AD_GROUP_CREATE_FLAGS2, "manage reddit ad-groups create");
3702
+ const query = buildRedditMutationQuery(flags);
3703
+ if (typeof flags.data === "string") {
3704
+ const body2 = parseDataFlag(flags, 'Use { "adGroups": [...] } for Reddit ad-group create');
3705
+ return client.post(`/manage/reddit/ad-groups${query}`, body2);
3706
+ }
3707
+ const adGroup = buildRedditAdGroupPayload(flags, "create");
3708
+ const body = { adGroups: [adGroup] };
3709
+ return client.post(`/manage/reddit/ad-groups${query}`, body);
3710
+ }
3711
+ async function updateRedditAdGroup(client, args, flags) {
3712
+ validateFlags(flags, AD_GROUP_UPDATE_FLAGS2, "manage reddit ad-groups update");
3713
+ const adGroupId = requireArg(args, 0, "ad-group-id", "Run: adkit manage reddit ad-groups update <ad-group-id> --status paused");
3714
+ const body = typeof flags.data === "string" ? parseDataFlag(flags, "Use a flat Reddit ad-group changes object") : buildRedditAdGroupPayload(flags, "update");
3715
+ const query = buildRedditMutationQuery(flags);
3716
+ return client.patch(`/manage/reddit/ad-groups/${adGroupId}${query}`, body);
3717
+ }
3718
+ async function listRedditAds(client, _args, flags) {
3719
+ validateFlags(flags, AD_LIST_FLAGS3, "manage reddit ads list");
3720
+ const campaignIds = readStringFlag(flags, "campaign-ids");
3721
+ const adGroupIds = readStringFlag(flags, "ad-group-ids");
3722
+ const extra = { campaignIds, adGroupIds };
3723
+ const query = buildRedditListQuery(flags, extra);
3724
+ return client.get(`/manage/reddit/ads${query}`);
3725
+ }
3726
+ async function getRedditAd(client, args, flags) {
3727
+ validateFlags(flags, ["fields"], "manage reddit ads <ad-id>");
3728
+ const adId = requireArg(args, 0, "ad-id", "Run: adkit manage reddit ads <ad-id>");
3729
+ const query = buildRedditReadQuery(flags);
3730
+ return client.get(`/manage/reddit/ads/${adId}${query}`);
3731
+ }
3732
+ async function createRedditAd(client, _args, flags) {
3733
+ validateFlags(flags, AD_CREATE_FLAGS2, "manage reddit ads create");
3734
+ const query = buildRedditMutationQuery(flags);
3735
+ if (typeof flags.data === "string") {
3736
+ const body2 = parseDataFlag(flags, 'Use { "ads": [...] } for Reddit ad create');
3737
+ return client.post(`/manage/reddit/ads${query}`, body2);
3738
+ }
3739
+ const resolvedFlags = await resolveRedditAdFiles(client, flags, "create");
3740
+ const ad = buildRedditAdPayload(resolvedFlags, "create");
3741
+ const body = { ads: [ad] };
3742
+ return client.post(`/manage/reddit/ads${query}`, body);
3743
+ }
3744
+ async function updateRedditAd(client, args, flags) {
3745
+ validateFlags(flags, AD_UPDATE_FLAGS2, "manage reddit ads update");
3746
+ const adId = requireArg(args, 0, "ad-id", "Run: adkit manage reddit ads update <ad-id> --status paused");
3747
+ const resolvedFlags = typeof flags.data === "string" ? flags : await resolveRedditAdFiles(client, flags, "update");
3748
+ const body = typeof flags.data === "string" ? parseDataFlag(flags, "Use a flat Reddit ad changes object") : buildRedditAdPayload(resolvedFlags, "update");
3749
+ const query = buildRedditMutationQuery(flags);
3750
+ return client.patch(`/manage/reddit/ads/${adId}${query}`, body);
3751
+ }
3752
+ async function listRedditResults(client, _args, flags) {
3753
+ validateFlags(flags, RESULTS_FLAGS2, "manage reddit results");
3754
+ const accountId = readStringFlag(flags, "account");
3755
+ const level = requireFlag(flags, "level", "Use --level account, campaigns, ad-groups, or ads");
3756
+ const period = readStringFlag(flags, "period");
3757
+ const from = readStringFlag(flags, "from");
3758
+ const to = readStringFlag(flags, "to");
3759
+ const fields = readStringFlag(flags, "fields");
3760
+ const breakdowns = readStringFlag(flags, "breakdowns");
3761
+ const campaignIds = readStringFlag(flags, "campaign-ids");
3762
+ const adGroupIds = readStringFlag(flags, "ad-group-ids");
3763
+ const adIds = readStringFlag(flags, "ad-ids");
3764
+ const sort = readStringFlag(flags, "sort");
3765
+ const sortDirection = readStringFlag(flags, "sort-direction");
3766
+ const limit = readStringFlag(flags, "limit");
3767
+ const offset = readStringFlag(flags, "offset");
3768
+ const raw = flags.raw === true ? "true" : readStringFlag(flags, "raw");
3769
+ const query = queryString({
3770
+ accountId,
3771
+ level,
3772
+ period,
3773
+ from,
3774
+ to,
3775
+ fields,
3776
+ breakdowns,
3777
+ campaignIds,
3778
+ adGroupIds,
3779
+ adIds,
3780
+ sort,
3781
+ sortDirection,
3782
+ limit,
3783
+ offset,
3784
+ raw
3785
+ });
3786
+ return client.get(`/manage/reddit/results${query}`);
3787
+ }
3788
+ async function listRedditAccountResource(client, args, flags, resource) {
3789
+ validateFlags(flags, ACCOUNT_RESOURCE_FLAGS, `manage reddit accounts <account-id> ${resource}`);
3790
+ const accountId = requireArg(args, 0, "account-id", `Run: adkit manage reddit accounts <account-id> ${resource}`);
3791
+ const limit = readStringFlag(flags, "limit");
3792
+ const offset = readStringFlag(flags, "offset");
3793
+ const query = queryString({ limit, offset });
3794
+ return client.get(`/manage/reddit/accounts/${accountId}/${resource}${query}`);
3795
+ }
3796
+ function buildRedditListQuery(flags, extra = {}) {
3797
+ const accountId = readStringFlag(flags, "account");
3798
+ const fields = readStringFlag(flags, "fields");
3799
+ const limit = readStringFlag(flags, "limit");
3800
+ const offset = readStringFlag(flags, "offset");
3801
+ return queryString({ accountId, ...extra, fields, limit, offset });
3802
+ }
3803
+ function buildRedditReadQuery(flags) {
3804
+ const accountId = readStringFlag(flags, "account");
3805
+ const fields = readStringFlag(flags, "fields");
3806
+ return queryString({ accountId, fields });
3807
+ }
3808
+ function buildRedditMutationQuery(flags) {
3809
+ const accountId = readStringFlag(flags, "account");
3810
+ const publish = flags.publish === true ? "true" : void 0;
3811
+ return queryString({ accountId, publish });
3812
+ }
3813
+ function buildRedditCampaignPayload(flags, mode) {
3814
+ const payload = {};
3815
+ if (mode === "create") {
3816
+ payload.name = requireFlag(flags, "name", 'Run: adkit manage reddit campaigns create --name "Traffic" --objective clicks');
3817
+ payload.objective = readRedditCampaignObjective(flags);
3818
+ if (typeof flags.optimization === "string") payload.optimization = validateRedditCampaignOptimization(flags.optimization);
3819
+ } else if (typeof flags.name === "string") payload.name = flags.name;
3820
+ if (typeof flags.status === "string") payload.status = readRedditStatus(flags.status);
3821
+ const lifetime = readNumberFlag3(flags, "budget-lifetime");
3822
+ if (lifetime !== void 0) payload.budget = { lifetime };
3823
+ if (typeof flags["bid-strategy"] === "string") payload.bidStrategy = flags["bid-strategy"];
3824
+ if (typeof flags["start-date"] === "string") payload.startDate = flags["start-date"];
3825
+ if (typeof flags["end-date"] === "string") payload.endDate = flags["end-date"];
3826
+ if (typeof flags["funding-instrument"] === "string") payload.fundingInstrumentId = flags["funding-instrument"];
3827
+ if (typeof flags.pixel === "string") payload.pixelId = flags.pixel;
3828
+ return payload;
3829
+ }
3830
+ function buildRedditAdGroupPayload(flags, mode) {
3831
+ const payload = {};
3832
+ if (mode === "create") {
3833
+ payload.campaignId = requireFlag(flags, "campaign", 'Run: adkit manage reddit ad-groups create --campaign <campaign-id> --name "Poland"');
3834
+ payload.name = requireFlag(flags, "name", 'Run: adkit manage reddit ad-groups create --campaign <campaign-id> --name "Poland"');
3835
+ } else if (typeof flags.name === "string") payload.name = flags.name;
3836
+ if (typeof flags.status === "string") payload.status = readRedditStatus(flags.status);
3837
+ const daily = readNumberFlag3(flags, "budget-daily");
3838
+ if (daily !== void 0) payload.budget = { daily };
3839
+ const bidAmount = readNumberFlag3(flags, "bid-amount");
3840
+ if (bidAmount !== void 0) payload.bidAmount = bidAmount;
3841
+ if (typeof flags["bid-strategy"] === "string") payload.bidStrategy = flags["bid-strategy"];
3842
+ if (mode === "create" && typeof flags.optimization === "string") payload.optimization = flags.optimization;
3843
+ if (typeof flags["start-date"] === "string") payload.startDate = flags["start-date"];
3844
+ if (typeof flags["end-date"] === "string") payload.endDate = flags["end-date"];
3845
+ if (typeof flags.pixel === "string") payload.pixelId = flags.pixel;
3846
+ return payload;
3847
+ }
3848
+ function buildRedditAdPayload(flags, mode) {
3849
+ const payload = {};
3850
+ if (mode === "create") payload.adGroupId = requireFlag(flags, "ad-group", "Run: adkit manage reddit ads create --ad-group <ad-group-id> --ad-type image");
3851
+ else if (typeof flags["ad-group"] === "string") payload.adGroupId = flags["ad-group"];
3852
+ if (typeof flags.name === "string") payload.name = flags.name;
3853
+ if (typeof flags.status === "string") payload.status = readRedditStatus(flags.status);
3854
+ if (typeof flags.profile === "string") payload.profileId = flags.profile;
3855
+ if (mode === "create" && typeof flags["existing-post"] === "string") {
3856
+ const incompatibleFlags = ["ad-type", "primary-text", "file", "media-id", "thumbnail-file", "thumbnail-id"].filter((key) => flags[key] !== void 0);
3857
+ if (incompatibleFlags.length > 0) throw new CliError("INVALID_VALUE", "--existing-post cannot be combined with new creative content", "Keep --headline and optional --url/--cta, and remove --ad-type/--primary-text/--media-id/--thumbnail-id");
3858
+ const headline = requireSingleTextFlag(flags, "headline", "Promoting an existing Reddit post still needs one ad headline");
3859
+ const creative = { headlines: [headline] };
3860
+ if (typeof flags.url === "string") creative.url = flags.url;
3861
+ if (typeof flags.cta === "string") creative.cta = flags.cta;
3862
+ return { ...payload, existingPostId: flags["existing-post"], creative };
3863
+ }
3864
+ const hasCreativeFlags = ["ad-type", "headline", "primary-text", "media-id", "thumbnail-id", "url", "cta"].some((key) => flags[key] !== void 0) || mode === "update" && flags.profile !== void 0;
3865
+ if (!hasCreativeFlags) {
3866
+ if (mode === "create") throw new CliError("MISSING_FLAG", "A Reddit ad needs creative content or an existing post", "Use --ad-type with its creative flags, --existing-post, or --data");
3867
+ return payload;
3868
+ }
3869
+ const adType = readRedditAdType(flags);
3870
+ if (adType === "carousel") throw new CliError("INVALID_VALUE", "Carousel ads need --data", "Pass canonical carouselCards and creative fields through --data");
3871
+ payload.adType = adType;
3872
+ payload.creative = buildRedditCreative(flags, adType);
3873
+ return payload;
3874
+ }
3875
+ async function resolveRedditAdFiles(client, flags, mode) {
3876
+ const file = readOptionalSingleFlag(flags, "file");
3877
+ const thumbnailFile = readOptionalSingleFlag(flags, "thumbnail-file");
3878
+ if (file && flags["media-id"] !== void 0) throw new CliError("INVALID_VALUE", "Use either --file or --media-id", "Pass one source for the Reddit image or video");
3879
+ if (thumbnailFile && flags["thumbnail-id"] !== void 0) throw new CliError("INVALID_VALUE", "Use either --thumbnail-file or --thumbnail-id", "Pass one source for the Reddit video thumbnail");
3880
+ if (!file && !thumbnailFile) return flags;
3881
+ const adType = readStringFlag(flags, "ad-type");
3882
+ if (adType !== "image" && adType !== "video") throw new CliError("INVALID_VALUE", "Local files require --ad-type image or video", "Use --data for carousel ads; freeform and existing-post ads do not accept media");
3883
+ if (adType === "image" && thumbnailFile) throw new CliError("INVALID_VALUE", "--thumbnail-file is only valid for video ads");
3884
+ const validationFlags = {
3885
+ ...flags,
3886
+ ...file ? { "media-id": "pending_upload" } : {},
3887
+ ...thumbnailFile ? { "thumbnail-id": "pending_upload" } : {}
3888
+ };
3889
+ buildRedditAdPayload(validationFlags, mode);
3890
+ const mediaId = file ? await uploadRedditTemporaryFile(client, file) : void 0;
3891
+ const thumbnailId = thumbnailFile ? await uploadRedditTemporaryFile(client, thumbnailFile) : void 0;
3892
+ return {
3893
+ ...flags,
3894
+ ...mediaId ? { "media-id": mediaId } : {},
3895
+ ...thumbnailId ? { "thumbnail-id": thumbnailId } : {}
3896
+ };
3897
+ }
3898
+ async function uploadRedditTemporaryFile(client, filePath) {
3899
+ const resolvedPath = resolve3(filePath);
3900
+ const filename = basename3(resolvedPath);
3901
+ const contentType = REDDIT_MEDIA_CONTENT_TYPES[extname3(filename).toLowerCase()];
3902
+ if (!contentType) throw new CliError("INVALID_VALUE", `Unsupported Reddit media file: ${filename}`, "Use .jpg, .jpeg, .png, .gif, .webp, .mp4, .mov, or .webm");
3903
+ let data;
3904
+ try {
3905
+ data = await readFile(resolvedPath);
3906
+ } catch (error) {
3907
+ const code = error instanceof Error && "code" in error ? error.code : void 0;
3908
+ if (code === "ENOENT") throw new CliError("INVALID_VALUE", `File not found: ${resolvedPath}`);
3909
+ throw error;
3910
+ }
3911
+ const response = await client.post("/manage/media/upload-url", { contentType, filename, sizeBytes: data.byteLength });
3912
+ const target = readTemporaryUploadTarget(response);
3913
+ const signal = AbortSignal.timeout(12e4);
3914
+ const body = new Uint8Array(data);
3915
+ let uploadResponse;
3916
+ try {
3917
+ uploadResponse = await fetch(target.uploadUrl, { method: "PUT", headers: target.headers, body, signal });
3918
+ } catch (error) {
3919
+ if (error instanceof DOMException && (error.name === "AbortError" || error.name === "TimeoutError")) throw new CliError("NETWORK_ERROR", `Upload timed out for ${filename}`, "Retry the command");
3920
+ const detail = error instanceof Error ? error.message : String(error);
3921
+ throw new CliError("NETWORK_ERROR", `Could not upload ${filename} (${detail})`, "Check your connection and retry the command");
3922
+ }
3923
+ await uploadResponse.arrayBuffer().catch(() => new ArrayBuffer(0));
3924
+ if (!uploadResponse.ok) throw new CliError("SERVER_ERROR", `Could not upload ${filename} (HTTP ${String(uploadResponse.status)})`, "Retry the command");
3925
+ return target.uploadId;
3926
+ }
3927
+ function readTemporaryUploadTarget(value) {
3928
+ if (!isObject(value) || !isObject(value.upload)) throw new CliError("SERVER_ERROR", "AdKit did not return a temporary upload target");
3929
+ const { upload } = value;
3930
+ if (typeof upload.uploadId !== "string" || typeof upload.uploadUrl !== "string" || !isStringRecord(upload.headers)) throw new CliError("SERVER_ERROR", "AdKit returned an invalid temporary upload target");
3931
+ return { headers: upload.headers, uploadId: upload.uploadId, uploadUrl: upload.uploadUrl };
3932
+ }
3933
+ function readOptionalSingleFlag(flags, key) {
3934
+ const values = collectFlagValues(flags, key);
3935
+ if (values.length > 1) throw new CliError("INVALID_VALUE", `Use one --${key} value`);
3936
+ return values[0];
3937
+ }
3938
+ function isObject(value) {
3939
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3940
+ }
3941
+ function isStringRecord(value) {
3942
+ return isObject(value) && Object.values(value).every((item) => typeof item === "string");
3943
+ }
3944
+ function buildRedditCreative(flags, adType) {
3945
+ const headline = requireSingleTextFlag(flags, "headline", "Reddit ads need exactly one --headline");
3946
+ const creative = { headlines: [headline] };
3947
+ if (adType === "freeform") creative.primaryTexts = [requireSingleTextFlag(flags, "primary-text", "Freeform Reddit ads need exactly one --primary-text")];
3948
+ if (adType === "image") creative.media = [{ id: requireFlag(flags, "media-id", "Image Reddit ads need --media-id <upload-id>"), role: "image" }];
3949
+ if (adType === "video") creative.media = [{ id: requireFlag(flags, "media-id", "Video Reddit ads need --media-id <upload-id>"), role: "video" }, { id: requireFlag(flags, "thumbnail-id", "Video Reddit ads need --thumbnail-id <upload-id>"), role: "thumbnail" }];
3950
+ if (adType === "image" || adType === "video") creative.url = requireFlag(flags, "url", `${adType === "image" ? "Image" : "Video"} Reddit ads need --url <destination>`);
3951
+ if (typeof flags.cta === "string") creative.cta = flags.cta;
3952
+ return creative;
3953
+ }
3954
+ function requireSingleTextFlag(flags, key, hint) {
3955
+ const values = collectFlagValues(flags, key);
3956
+ if (values.length !== 1) throw new CliError("INVALID_VALUE", `Reddit requires exactly one --${key}`, hint);
3957
+ return values[0];
3958
+ }
3959
+ function readNumberFlag3(flags, key) {
3960
+ const value = readStringFlag(flags, key);
3961
+ if (value === void 0) return void 0;
3962
+ const parsed = Number(value);
3963
+ if (!Number.isFinite(parsed)) throw new CliError("INVALID_VALUE", `Invalid number for --${key}: ${value}`, `Use a numeric value for --${key}`);
3964
+ return parsed;
3965
+ }
3966
+ function readRedditCampaignObjective(flags) {
3967
+ const objective = requireFlag(flags, "objective", "Use brand_awareness, clicks, lead_generation, or sales");
3968
+ if (objective === "brand_awareness" || objective === "clicks" || objective === "lead_generation" || objective === "sales") return objective;
3969
+ throw new CliError("INVALID_VALUE", `Unsupported Reddit objective: ${objective}`, "Use brand_awareness, clicks, lead_generation, or sales");
3970
+ }
3971
+ function validateRedditCampaignOptimization(value) {
3972
+ if (value === "clicks" || value === "video_view_6s") return value;
3973
+ throw new CliError("INVALID_VALUE", `Unsupported Reddit campaign optimization: ${value}`, "Use clicks or video_view_6s");
3974
+ }
3975
+ function readRedditStatus(status2) {
3976
+ if (status2 === "active" || status2 === "paused") return status2;
3977
+ throw new CliError("INVALID_VALUE", `Unsupported Reddit status: ${status2}`, "Use active or paused");
3978
+ }
3979
+ function readRedditAdType(flags) {
3980
+ const adType = requireFlag(flags, "ad-type", "Creative changes need --ad-type freeform, image, video, or carousel");
3981
+ if (adType === "freeform" || adType === "image" || adType === "video" || adType === "carousel") return adType;
3982
+ throw new CliError("INVALID_VALUE", `Unsupported Reddit ad type: ${adType}`, "Use freeform, image, video, or carousel");
3983
+ }
3984
+ function readStringFlag(flags, key) {
3985
+ return typeof flags[key] === "string" ? flags[key] : void 0;
3607
3986
  }
3608
3987
 
3609
3988
  // src/commands/linkedin.ts
3610
3989
  import { readFileSync as readFileSync4 } from "node:fs";
3611
- import { basename as basename3, extname as extname3, resolve as resolve3 } from "node:path";
3990
+ import { basename as basename4, extname as extname4, resolve as resolve4 } from "node:path";
3612
3991
  var MEDIA_UPLOAD_FLAGS3 = ["file", "url", "base64", "adkit-media", "temporary-upload", "filename", "content-type", "name"];
3613
- var CAMPAIGN_LIST_FLAGS3 = ["campaign-ids", "limit", "offset"];
3614
- var AD_GROUP_LIST_FLAGS3 = ["campaign-ids", "ad-group-ids", "limit", "offset"];
3615
- var AD_LIST_FLAGS3 = ["campaign-ids", "limit", "offset"];
3616
- var CAMPAIGN_CREATE_FLAGS3 = ["name", "status", "budget-daily", "budget-lifetime", "end-date"];
3617
- var AD_GROUP_CREATE_FLAGS2 = ["campaign", "name", "objective", "status", "budget-daily", "budget-lifetime", "bid-strategy", "optimization", "bid-amount"];
3618
- var AD_CREATE_FLAGS2 = ["ad-group", "existing-post", "name", "status"];
3619
- var CAMPAIGN_UPDATE_FLAGS3 = ["name", "status", "budget-lifetime", "end-date"];
3620
- var AD_GROUP_UPDATE_FLAGS2 = ["name", "status", "budget-daily", "budget-lifetime", "start-date", "end-date", "bid-strategy", "optimization", "bid-amount"];
3621
- var AD_UPDATE_FLAGS2 = ["name", "status"];
3992
+ var CAMPAIGN_LIST_FLAGS4 = ["campaign-ids", "limit", "offset"];
3993
+ var AD_GROUP_LIST_FLAGS4 = ["campaign-ids", "ad-group-ids", "limit", "offset"];
3994
+ var AD_LIST_FLAGS4 = ["campaign-ids", "limit", "offset"];
3995
+ var CAMPAIGN_CREATE_FLAGS4 = ["name", "status", "budget-daily", "budget-lifetime", "end-date"];
3996
+ var AD_GROUP_CREATE_FLAGS3 = ["campaign", "name", "objective", "status", "budget-daily", "budget-lifetime", "bid-strategy", "optimization", "bid-amount"];
3997
+ var AD_CREATE_FLAGS3 = ["ad-group", "existing-post", "name", "status"];
3998
+ var CAMPAIGN_UPDATE_FLAGS4 = ["name", "status", "budget-lifetime", "end-date"];
3999
+ var AD_GROUP_UPDATE_FLAGS3 = ["name", "status", "budget-daily", "budget-lifetime", "start-date", "end-date", "bid-strategy", "optimization", "bid-amount"];
4000
+ var AD_UPDATE_FLAGS3 = ["name", "status"];
3622
4001
  var TARGETING_SEARCH_FLAGS = ["account", "facet", "query", "limit"];
3623
- var RESULTS_FLAGS2 = ["level", "period", "fields", "breakdowns", "from", "to", "sort", "limit", "offset"];
4002
+ var RESULTS_FLAGS3 = ["level", "period", "fields", "breakdowns", "from", "to", "sort", "limit", "offset"];
3624
4003
  async function listLinkedInAccounts(client, _args, flags) {
3625
4004
  validateFlags(flags, [], "manage linkedin accounts list");
3626
4005
  return client.get("/manage/linkedin/accounts");
@@ -3659,7 +4038,7 @@ async function getLinkedInMedia(client, args, flags) {
3659
4038
  return client.get(`/manage/linkedin/media/${id}${qs}`);
3660
4039
  }
3661
4040
  async function listLinkedInCampaigns(client, _args, flags) {
3662
- validateFlags(flags, CAMPAIGN_LIST_FLAGS3, "manage linkedin campaigns list");
4041
+ validateFlags(flags, CAMPAIGN_LIST_FLAGS4, "manage linkedin campaigns list");
3663
4042
  const qs = buildLinkedInListQuery(flags, ["campaign-ids"]);
3664
4043
  return client.get(`/manage/linkedin/campaigns${qs}`);
3665
4044
  }
@@ -3670,7 +4049,7 @@ async function getLinkedInCampaign(client, args, flags) {
3670
4049
  return client.get(`/manage/linkedin/campaigns/${id}${qs}`);
3671
4050
  }
3672
4051
  async function createLinkedInCampaign(client, _args, flags) {
3673
- validateFlags(flags, CAMPAIGN_CREATE_FLAGS3, "manage linkedin campaigns create");
4052
+ validateFlags(flags, CAMPAIGN_CREATE_FLAGS4, "manage linkedin campaigns create");
3674
4053
  const qs = buildLinkedInMutationQuery(flags);
3675
4054
  if (typeof flags.data === "string") {
3676
4055
  const body2 = parseDataFlag(flags, 'Use { "campaigns": [...] } for LinkedIn campaign create');
@@ -3680,14 +4059,14 @@ async function createLinkedInCampaign(client, _args, flags) {
3680
4059
  return client.post(`/manage/linkedin/campaigns${qs}`, body);
3681
4060
  }
3682
4061
  async function updateLinkedInCampaign(client, args, flags) {
3683
- validateFlags(flags, CAMPAIGN_UPDATE_FLAGS3, "manage linkedin campaigns update");
4062
+ validateFlags(flags, CAMPAIGN_UPDATE_FLAGS4, "manage linkedin campaigns update");
3684
4063
  const id = requireArg(args, 0, "campaign-id", "Run: adkit manage linkedin campaigns update <campaign-id> --status paused");
3685
4064
  const qs = buildLinkedInMutationQuery(flags);
3686
4065
  const body = typeof flags.data === "string" ? parseDataFlag(flags, "Use a flat LinkedIn campaign changes object") : buildLinkedInCampaignUpdatePayload(flags);
3687
4066
  return client.patch(`/manage/linkedin/campaigns/${id}${qs}`, body);
3688
4067
  }
3689
4068
  async function listLinkedInAdGroups(client, _args, flags) {
3690
- validateFlags(flags, AD_GROUP_LIST_FLAGS3, "manage linkedin ad-groups list");
4069
+ validateFlags(flags, AD_GROUP_LIST_FLAGS4, "manage linkedin ad-groups list");
3691
4070
  const qs = buildLinkedInListQuery(flags, ["campaign-ids", "ad-group-ids"]);
3692
4071
  return client.get(`/manage/linkedin/ad-groups${qs}`);
3693
4072
  }
@@ -3698,7 +4077,7 @@ async function getLinkedInAdGroup(client, args, flags) {
3698
4077
  return client.get(`/manage/linkedin/ad-groups/${id}${qs}`);
3699
4078
  }
3700
4079
  async function createLinkedInAdGroup(client, _args, flags) {
3701
- validateFlags(flags, AD_GROUP_CREATE_FLAGS2, "manage linkedin ad-groups create");
4080
+ validateFlags(flags, AD_GROUP_CREATE_FLAGS3, "manage linkedin ad-groups create");
3702
4081
  const qs = buildLinkedInMutationQuery(flags);
3703
4082
  if (typeof flags.data === "string") {
3704
4083
  const body2 = parseDataFlag(flags, 'Use { "adGroups": [...] } for LinkedIn ad group create');
@@ -3708,14 +4087,14 @@ async function createLinkedInAdGroup(client, _args, flags) {
3708
4087
  return client.post(`/manage/linkedin/ad-groups${qs}`, body);
3709
4088
  }
3710
4089
  async function updateLinkedInAdGroup(client, args, flags) {
3711
- validateFlags(flags, AD_GROUP_UPDATE_FLAGS2, "manage linkedin ad-groups update");
4090
+ validateFlags(flags, AD_GROUP_UPDATE_FLAGS3, "manage linkedin ad-groups update");
3712
4091
  const id = requireArg(args, 0, "ad-group-id", "Run: adkit manage linkedin ad-groups update <ad-group-id> --status paused");
3713
4092
  const qs = buildLinkedInMutationQuery(flags);
3714
4093
  const body = typeof flags.data === "string" ? parseDataFlag(flags, "Use a flat LinkedIn ad group changes object") : buildLinkedInAdGroupUpdatePayload(flags);
3715
4094
  return client.patch(`/manage/linkedin/ad-groups/${id}${qs}`, body);
3716
4095
  }
3717
4096
  async function listLinkedInAds(client, _args, flags) {
3718
- validateFlags(flags, AD_LIST_FLAGS3, "manage linkedin ads list");
4097
+ validateFlags(flags, AD_LIST_FLAGS4, "manage linkedin ads list");
3719
4098
  const qs = buildLinkedInListQuery(flags, ["campaign-ids"]);
3720
4099
  return client.get(`/manage/linkedin/ads${qs}`);
3721
4100
  }
@@ -3726,7 +4105,7 @@ async function getLinkedInAd(client, args, flags) {
3726
4105
  return client.get(`/manage/linkedin/ads/${id}${qs}`);
3727
4106
  }
3728
4107
  async function createLinkedInAd(client, _args, flags) {
3729
- validateFlags(flags, AD_CREATE_FLAGS2, "manage linkedin ads create");
4108
+ validateFlags(flags, AD_CREATE_FLAGS3, "manage linkedin ads create");
3730
4109
  const qs = buildLinkedInMutationQuery(flags);
3731
4110
  if (typeof flags.data === "string") {
3732
4111
  const body2 = parseDataFlag(flags, 'Use { "ads": [...] } for LinkedIn ad create');
@@ -3737,7 +4116,7 @@ async function createLinkedInAd(client, _args, flags) {
3737
4116
  return client.post(`/manage/linkedin/ads${qs}`, body);
3738
4117
  }
3739
4118
  async function updateLinkedInAd(client, args, flags) {
3740
- validateFlags(flags, AD_UPDATE_FLAGS2, "manage linkedin ads update");
4119
+ validateFlags(flags, AD_UPDATE_FLAGS3, "manage linkedin ads update");
3741
4120
  const id = requireArg(args, 0, "ad-id", "Run: adkit manage linkedin ads update <ad-id> --status paused");
3742
4121
  const qs = buildLinkedInMutationQuery(flags);
3743
4122
  const body = typeof flags.data === "string" ? parseDataFlag(flags, "Use a flat LinkedIn ad changes object (name/status only)") : buildLinkedInAdUpdatePayload(flags);
@@ -3753,7 +4132,7 @@ async function searchLinkedInTargeting(client, _args, flags) {
3753
4132
  return client.get(`/manage/linkedin/targeting-search${qs}`);
3754
4133
  }
3755
4134
  async function listLinkedInResults(client, _args, flags) {
3756
- validateFlags(flags, RESULTS_FLAGS2, "manage linkedin results");
4135
+ validateFlags(flags, RESULTS_FLAGS3, "manage linkedin results");
3757
4136
  const accountId = typeof flags.account === "string" ? flags.account : void 0;
3758
4137
  const level = requireFlag(flags, "level", "Use --level account, campaigns, ad-groups, or ads");
3759
4138
  const query = queryString({
@@ -3806,7 +4185,7 @@ function buildLinkedInAdGroupPayload(flags) {
3806
4185
  if (typeof flags.objective === "string") payload.objective = flags.objective;
3807
4186
  if (typeof flags["bid-strategy"] === "string") payload.bidStrategy = flags["bid-strategy"];
3808
4187
  if (typeof flags.optimization === "string") payload.optimization = flags.optimization;
3809
- const bidAmount = readNumberFlag3(flags, "bid-amount");
4188
+ const bidAmount = readNumberFlag4(flags, "bid-amount");
3810
4189
  if (bidAmount !== void 0) payload.bidAmount = bidAmount;
3811
4190
  if (typeof flags.status === "string") payload.status = flags.status;
3812
4191
  return payload;
@@ -3839,7 +4218,7 @@ function buildLinkedInAdGroupUpdatePayload(flags) {
3839
4218
  if (budget) payload.budget = budget;
3840
4219
  if (typeof flags["bid-strategy"] === "string") payload.bidStrategy = flags["bid-strategy"];
3841
4220
  if (typeof flags.optimization === "string") payload.optimization = flags.optimization;
3842
- const bidAmount = readNumberFlag3(flags, "bid-amount");
4221
+ const bidAmount = readNumberFlag4(flags, "bid-amount");
3843
4222
  if (bidAmount !== void 0) payload.bidAmount = bidAmount;
3844
4223
  return payload;
3845
4224
  }
@@ -3850,13 +4229,13 @@ function buildLinkedInAdUpdatePayload(flags) {
3850
4229
  return payload;
3851
4230
  }
3852
4231
  function buildLinkedInBudget(flags) {
3853
- const daily = readNumberFlag3(flags, "budget-daily");
3854
- const lifetime = readNumberFlag3(flags, "budget-lifetime");
4232
+ const daily = readNumberFlag4(flags, "budget-daily");
4233
+ const lifetime = readNumberFlag4(flags, "budget-lifetime");
3855
4234
  if (daily === void 0 && lifetime === void 0) return void 0;
3856
4235
  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");
3857
4236
  return daily !== void 0 ? { daily } : { lifetime };
3858
4237
  }
3859
- function readNumberFlag3(flags, key) {
4238
+ function readNumberFlag4(flags, key) {
3860
4239
  const value = flags[key];
3861
4240
  if (value === void 0) return void 0;
3862
4241
  if (typeof value !== "string") throw new CliError("INVALID_VALUE", `Use one --${key} value`, `Run: adkit manage linkedin --help`);
@@ -3873,7 +4252,7 @@ function readSingleFlagValue3(flags, key, duplicateHint) {
3873
4252
  return typeof value === "string" ? value : void 0;
3874
4253
  }
3875
4254
  function inferLinkedInMediaContentType(filename) {
3876
- const extension = extname3(filename).toLowerCase();
4255
+ const extension = extname4(filename).toLowerCase();
3877
4256
  if (extension === ".jpg" || extension === ".jpeg") return "image/jpeg";
3878
4257
  if (extension === ".png") return "image/png";
3879
4258
  if (extension === ".gif") return "image/gif";
@@ -3894,8 +4273,8 @@ function buildLinkedInMediaUploadSource(flags) {
3894
4273
  const contentType = readSingleFlagValue3(flags, "content-type", "Use one --content-type value");
3895
4274
  const name = readSingleFlagValue3(flags, "name", "Use one --name value");
3896
4275
  if (filePath) {
3897
- const resolvedPath = resolve3(filePath);
3898
- const resolvedFilename = filename ?? basename3(resolvedPath);
4276
+ const resolvedPath = resolve4(filePath);
4277
+ const resolvedFilename = filename ?? basename4(resolvedPath);
3899
4278
  const resolvedContentType = contentType ?? inferLinkedInMediaContentType(resolvedFilename);
3900
4279
  const data = readFileSync4(resolvedPath).toString("base64");
3901
4280
  const source2 = { source: "base64", data, filename: resolvedFilename };
@@ -3929,10 +4308,15 @@ function buildLinkedInMediaUploadSource(flags) {
3929
4308
  }
3930
4309
 
3931
4310
  // src/commands/x.ts
3932
- var CAMPAIGN_LIST_FLAGS4 = ["campaign-ids", "status", "limit", "offset"];
3933
- var AD_GROUP_LIST_FLAGS4 = ["campaign-ids", "ad-group-ids", "status", "limit", "offset"];
3934
- var AD_LIST_FLAGS4 = ["campaign-ids", "ad-group-ids", "ad-ids", "status", "limit", "offset"];
3935
- var RESULTS_FLAGS3 = ["level", "from", "to", "fields", "breakdowns", "campaign-ids", "ad-group-ids", "ad-ids", "sort", "sort-direction", "limit", "offset", "raw"];
4311
+ var CAMPAIGN_LIST_FLAGS5 = ["campaign-ids", "status", "limit", "offset"];
4312
+ var CAMPAIGN_CREATE_FLAGS5 = ["name", "status", "budget-daily", "budget-lifetime", "funding-instrument"];
4313
+ var CAMPAIGN_UPDATE_FLAGS5 = ["name", "status", "budget-daily", "budget-lifetime"];
4314
+ var AD_GROUP_LIST_FLAGS5 = ["campaign-ids", "ad-group-ids", "status", "limit", "offset"];
4315
+ var AD_GROUP_CREATE_FLAGS4 = ["campaign", "name", "objective", "status", "budget-daily", "budget-lifetime", "bid-amount", "bid-strategy", "optimization", "start-date", "end-date"];
4316
+ var AD_GROUP_UPDATE_FLAGS4 = ["name", "status", "budget-daily", "budget-lifetime", "bid-amount", "bid-strategy", "optimization", "start-date", "end-date"];
4317
+ var AD_LIST_FLAGS5 = ["campaign-ids", "ad-group-ids", "ad-ids", "status", "limit", "offset"];
4318
+ var AD_CREATE_FLAGS4 = ["ad-group", "existing-post", "status"];
4319
+ var RESULTS_FLAGS4 = ["level", "from", "to", "fields", "breakdowns", "campaign-ids", "ad-group-ids", "ad-ids", "sort", "sort-direction", "limit", "offset", "raw"];
3936
4320
  async function listXAccounts(client, _args, flags) {
3937
4321
  validateFlags(flags, [], "manage x accounts list");
3938
4322
  return client.get("/manage/x/accounts");
@@ -3962,23 +4346,30 @@ async function listXPixels(client, args, flags) {
3962
4346
  const accountId = requireArg(args, 0, "account-id", "Run: adkit manage x accounts <account-id> pixels");
3963
4347
  return client.get(`/manage/x/accounts/${accountId}/pixels`);
3964
4348
  }
4349
+ async function listXFundingInstruments(client, args, flags) {
4350
+ validateFlags(flags, [], "manage x accounts <account-id> funding-instruments");
4351
+ const accountId = requireArg(args, 0, "account-id", "Run: adkit manage x accounts <account-id> funding-instruments");
4352
+ return client.get(`/manage/x/accounts/${accountId}/funding-instruments`);
4353
+ }
3965
4354
  async function updateXAccount(client, args, flags) {
3966
- validateFlags(flags, ["default-profile", "default-pixel"], "manage x accounts <account-id> update");
4355
+ validateFlags(flags, ["default-profile", "default-pixel", "default-funding-instrument"], "manage x accounts <account-id> update");
3967
4356
  const accountId = requireArg(args, 0, "account-id", "Run: adkit manage x accounts <account-id> update --default-profile <profile-id>");
3968
4357
  if (typeof flags.data === "string") {
3969
- const body = parseDataFlag(flags, 'Use { "defaultProfileId": "...", "defaultPixelId": "..." }');
4358
+ const body = parseDataFlag(flags, 'Use { "defaultProfileId": "...", "defaultPixelId": "...", "defaultFundingInstrumentId": "..." }');
3970
4359
  return client.patch(`/manage/x/accounts/${accountId}`, body);
3971
4360
  }
3972
4361
  const defaultProfileId = typeof flags["default-profile"] === "string" ? flags["default-profile"] : void 0;
3973
4362
  const defaultPixelId = typeof flags["default-pixel"] === "string" ? flags["default-pixel"] : void 0;
3974
- if (!defaultProfileId && !defaultPixelId) throw new CliError("MISSING_FLAG", "Nothing to update", "Pass --default-profile, --default-pixel, or --data");
4363
+ const defaultFundingInstrumentId = typeof flags["default-funding-instrument"] === "string" ? flags["default-funding-instrument"] : void 0;
4364
+ if (!defaultProfileId && !defaultPixelId && !defaultFundingInstrumentId) throw new CliError("MISSING_FLAG", "Nothing to update", "Pass --default-profile, --default-pixel, --default-funding-instrument, or --data");
3975
4365
  return client.patch(`/manage/x/accounts/${accountId}`, {
3976
4366
  ...defaultProfileId ? { defaultProfileId } : {},
3977
- ...defaultPixelId ? { defaultPixelId } : {}
4367
+ ...defaultPixelId ? { defaultPixelId } : {},
4368
+ ...defaultFundingInstrumentId ? { defaultFundingInstrumentId } : {}
3978
4369
  });
3979
4370
  }
3980
4371
  async function listXCampaigns(client, _args, flags) {
3981
- validateXReadFlags(flags, CAMPAIGN_LIST_FLAGS4, "manage x campaigns list");
4372
+ validateXReadFlags(flags, CAMPAIGN_LIST_FLAGS5, "manage x campaigns list");
3982
4373
  const query = buildXListQuery(flags, ["campaign-ids"]);
3983
4374
  return client.get(`/manage/x/campaigns${query}`);
3984
4375
  }
@@ -3988,8 +4379,25 @@ async function getXCampaign(client, args, flags) {
3988
4379
  const query = queryString({ accountId: typeof flags.account === "string" ? flags.account : void 0 });
3989
4380
  return client.get(`/manage/x/campaigns/${campaignId}${query}`);
3990
4381
  }
4382
+ async function createXCampaign(client, _args, flags) {
4383
+ validateFlags(flags, CAMPAIGN_CREATE_FLAGS5, "manage x campaigns create");
4384
+ const query = buildXMutationQuery(flags);
4385
+ if (typeof flags.data === "string") {
4386
+ const body = parseDataFlag(flags, 'Use { "campaigns": [...] } for X campaign create');
4387
+ return client.post(`/manage/x/campaigns${query}`, body);
4388
+ }
4389
+ const campaign = buildXCampaignCreatePayload(flags);
4390
+ return client.post(`/manage/x/campaigns${query}`, { campaigns: [campaign] });
4391
+ }
4392
+ async function updateXCampaign(client, args, flags) {
4393
+ validateFlags(flags, CAMPAIGN_UPDATE_FLAGS5, "manage x campaigns update");
4394
+ const campaignId = requireArg(args, 0, "campaign-id", "Run: adkit manage x campaigns update <campaign-id> --status paused");
4395
+ const query = buildXMutationQuery(flags);
4396
+ const body = typeof flags.data === "string" ? parseDataFlag(flags, "Use a flat X campaign changes object") : buildXCampaignUpdatePayload(flags);
4397
+ return client.patch(`/manage/x/campaigns/${campaignId}${query}`, body);
4398
+ }
3991
4399
  async function listXAdGroups(client, _args, flags) {
3992
- validateXReadFlags(flags, AD_GROUP_LIST_FLAGS4, "manage x ad-groups list");
4400
+ validateXReadFlags(flags, AD_GROUP_LIST_FLAGS5, "manage x ad-groups list");
3993
4401
  const query = buildXListQuery(flags, ["campaign-ids", "ad-group-ids"]);
3994
4402
  return client.get(`/manage/x/ad-groups${query}`);
3995
4403
  }
@@ -3999,8 +4407,25 @@ async function getXAdGroup(client, args, flags) {
3999
4407
  const query = queryString({ accountId: typeof flags.account === "string" ? flags.account : void 0 });
4000
4408
  return client.get(`/manage/x/ad-groups/${adGroupId}${query}`);
4001
4409
  }
4410
+ async function createXAdGroup(client, _args, flags) {
4411
+ validateFlags(flags, AD_GROUP_CREATE_FLAGS4, "manage x ad-groups create");
4412
+ const query = buildXMutationQuery(flags);
4413
+ if (typeof flags.data === "string") {
4414
+ const body = parseDataFlag(flags, 'Use { "adGroups": [...] } for X ad-group create');
4415
+ return client.post(`/manage/x/ad-groups${query}`, body);
4416
+ }
4417
+ const adGroup = buildXAdGroupCreatePayload(flags);
4418
+ return client.post(`/manage/x/ad-groups${query}`, { adGroups: [adGroup] });
4419
+ }
4420
+ async function updateXAdGroup(client, args, flags) {
4421
+ validateFlags(flags, AD_GROUP_UPDATE_FLAGS4, "manage x ad-groups update");
4422
+ const adGroupId = requireArg(args, 0, "ad-group-id", "Run: adkit manage x ad-groups update <ad-group-id> --status paused");
4423
+ const query = buildXMutationQuery(flags);
4424
+ const body = typeof flags.data === "string" ? parseDataFlag(flags, "Use a flat X ad-group changes object") : buildXAdGroupUpdatePayload(flags);
4425
+ return client.patch(`/manage/x/ad-groups/${adGroupId}${query}`, body);
4426
+ }
4002
4427
  async function listXAds(client, _args, flags) {
4003
- validateXReadFlags(flags, AD_LIST_FLAGS4, "manage x ads list");
4428
+ validateXReadFlags(flags, AD_LIST_FLAGS5, "manage x ads list");
4004
4429
  const query = buildXListQuery(flags, ["campaign-ids", "ad-group-ids", "ad-ids"]);
4005
4430
  return client.get(`/manage/x/ads${query}`);
4006
4431
  }
@@ -4010,8 +4435,26 @@ async function getXAd(client, args, flags) {
4010
4435
  const query = queryString({ accountId: typeof flags.account === "string" ? flags.account : void 0 });
4011
4436
  return client.get(`/manage/x/ads/${adId}${query}`);
4012
4437
  }
4438
+ async function createXAd(client, _args, flags) {
4439
+ validateFlags(flags, AD_CREATE_FLAGS4, "manage x ads create");
4440
+ const query = buildXMutationQuery(flags);
4441
+ if (typeof flags.data === "string") {
4442
+ const body = parseDataFlag(flags, 'Use { "ads": [...] } for X ad create');
4443
+ return client.post(`/manage/x/ads${query}`, body);
4444
+ }
4445
+ if (typeof flags["existing-post"] !== "string") throw new CliError("MISSING_FLAG", "An X ad needs a creative or an existing Post", "Inline ad: use --data with ads[].creative. Existing Post: pass --existing-post <Post ID>.");
4446
+ const ad = buildXAdCreatePayload(flags);
4447
+ return client.post(`/manage/x/ads${query}`, { ads: [ad] });
4448
+ }
4449
+ async function deleteXAd(client, args, flags) {
4450
+ validateFlags(flags, [], "manage x ads delete");
4451
+ const adId = requireArg(args, 0, "ad-id", "Run: adkit manage x ads delete <promoted-association-id>");
4452
+ const query = buildXMutationQuery(flags);
4453
+ const encodedAdId = encodeURIComponent(adId);
4454
+ return client.delete(`/manage/x/ads/${encodedAdId}${query}`);
4455
+ }
4013
4456
  async function listXResults(client, _args, flags) {
4014
- validateXReadFlags(flags, RESULTS_FLAGS3, "manage x results");
4457
+ validateXReadFlags(flags, RESULTS_FLAGS4, "manage x results");
4015
4458
  const level = requireFlag(flags, "level", "Use --level campaigns, ad-groups, or ads");
4016
4459
  const from = requireFlag(flags, "from", "Use --from YYYY-MM-DD with --to");
4017
4460
  const to = requireFlag(flags, "to", "Use --to YYYY-MM-DD with --from");
@@ -4045,6 +4488,76 @@ function buildXListQuery(flags, filterKeys) {
4045
4488
  offset: typeof flags.offset === "string" ? flags.offset : void 0
4046
4489
  });
4047
4490
  }
4491
+ function buildXMutationQuery(flags) {
4492
+ return queryString({ accountId: typeof flags.account === "string" ? flags.account : void 0, publish: flags.publish === true ? "true" : void 0 });
4493
+ }
4494
+ function buildXCampaignCreatePayload(flags) {
4495
+ const name = requireFlag(flags, "name", 'Run: adkit manage x campaigns create --name "Launch" --budget-daily 25');
4496
+ const budget = buildXBudgetFromFlags(flags, { required: true });
4497
+ const payload = {
4498
+ name,
4499
+ budget
4500
+ };
4501
+ if (typeof flags.status === "string") payload.status = flags.status;
4502
+ if (typeof flags["funding-instrument"] === "string") payload.fundingInstrumentId = flags["funding-instrument"];
4503
+ return payload;
4504
+ }
4505
+ function buildXCampaignUpdatePayload(flags) {
4506
+ const payload = {};
4507
+ if (typeof flags.name === "string") payload.name = flags.name;
4508
+ if (typeof flags.status === "string") payload.status = flags.status;
4509
+ const budget = buildXBudgetFromFlags(flags, { required: false });
4510
+ if (budget) payload.budget = budget;
4511
+ return payload;
4512
+ }
4513
+ function buildXAdGroupCreatePayload(flags) {
4514
+ return {
4515
+ ...buildXAdGroupUpdatePayload(flags),
4516
+ campaignId: requireFlag(flags, "campaign", "Use --campaign <campaign-id>"),
4517
+ name: requireFlag(flags, "name", 'Use --name "Ad group name"'),
4518
+ objective: requireFlag(flags, "objective", "Use --objective engagements, reach, video_views, or website_clicks"),
4519
+ startDate: requireFlag(flags, "start-date", "Use --start-date YYYY-MM-DD")
4520
+ };
4521
+ }
4522
+ function buildXAdGroupUpdatePayload(flags) {
4523
+ const payload = {};
4524
+ if (typeof flags.name === "string") payload.name = flags.name;
4525
+ if (typeof flags.status === "string") payload.status = flags.status;
4526
+ if (typeof flags["bid-strategy"] === "string") payload.bidStrategy = flags["bid-strategy"];
4527
+ if (typeof flags.optimization === "string") payload.optimization = flags.optimization;
4528
+ if (typeof flags["start-date"] === "string") payload.startDate = flags["start-date"];
4529
+ if (typeof flags["end-date"] === "string") payload.endDate = flags["end-date"];
4530
+ const budget = buildXBudgetFromFlags(flags, { required: false });
4531
+ if (budget) payload.budget = budget;
4532
+ const bidAmount = parseXNumberFlag(flags, "bid-amount");
4533
+ if (bidAmount !== void 0) payload.bidAmount = bidAmount;
4534
+ return payload;
4535
+ }
4536
+ function buildXAdCreatePayload(flags) {
4537
+ const ad = {
4538
+ adGroupId: requireFlag(flags, "ad-group", "Run: adkit manage x ads create --ad-group <line-item-id> --existing-post <Post ID>"),
4539
+ existingPostId: requireFlag(flags, "existing-post", "Run: adkit manage x ads create --existing-post <Post ID>")
4540
+ };
4541
+ if (typeof flags.status === "string") ad.status = flags.status;
4542
+ return ad;
4543
+ }
4544
+ function buildXBudgetFromFlags(flags, options) {
4545
+ const daily = parseXNumberFlag(flags, "budget-daily");
4546
+ const lifetime = parseXNumberFlag(flags, "budget-lifetime");
4547
+ if (daily === void 0 && lifetime === void 0) {
4548
+ if (options.required) throw new CliError("MISSING_FLAG", "Missing required campaign budget", "Use --budget-daily <amount>, --budget-lifetime <amount>, or --data");
4549
+ return void 0;
4550
+ }
4551
+ return { ...daily === void 0 ? {} : { daily }, ...lifetime === void 0 ? {} : { lifetime } };
4552
+ }
4553
+ function parseXNumberFlag(flags, key) {
4554
+ const value = flags[key];
4555
+ if (value === void 0) return void 0;
4556
+ if (typeof value !== "string") throw new CliError("INVALID_VALUE", `Use one --${key} value`, "Pass one numeric amount");
4557
+ const parsed = Number(value);
4558
+ if (!Number.isFinite(parsed)) throw new CliError("INVALID_VALUE", `Invalid number for --${key}: ${value}`, `Use a numeric value for --${key}`);
4559
+ return parsed;
4560
+ }
4048
4561
  function validateXReadFlags(flags, allowed, command) {
4049
4562
  const permitted = /* @__PURE__ */ new Set(["account", "json", "project", ...allowed]);
4050
4563
  const unsupported = Object.keys(flags).filter((flag) => !permitted.has(flag));
@@ -4085,6 +4598,7 @@ var STATUS_PLATFORMS = [
4085
4598
  ["microsoft", "Microsoft"]
4086
4599
  ];
4087
4600
  var DEFAULT_LABELS = {
4601
+ fundingInstrumentId: "Funding instrument",
4088
4602
  identityId: "Identity",
4089
4603
  identityType: "Identity type",
4090
4604
  instagramUserId: "Instagram user",
@@ -4209,7 +4723,7 @@ function queryString3(params) {
4209
4723
  if (entries.length === 0) return "";
4210
4724
  return "?" + entries.map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join("&");
4211
4725
  }
4212
- function readStringFlag(flags, key) {
4726
+ function readStringFlag2(flags, key) {
4213
4727
  const value = flags[key];
4214
4728
  return typeof value === "string" ? value : void 0;
4215
4729
  }
@@ -4265,20 +4779,20 @@ function buildAdvertiserQueryString(flags, query) {
4265
4779
  return queryString3({ industry, category, platform: platform2, query, sort, limit, page });
4266
4780
  }
4267
4781
  async function listLibraryAds(client, _args, flags) {
4268
- const platform2 = readStringFlag(flags, "platform");
4269
- const status2 = readStringFlag(flags, "status");
4270
- const format = readStringFlag(flags, "format");
4271
- const minDays = readStringFlag(flags, "min-days");
4272
- const maxDays = readStringFlag(flags, "max-days");
4273
- const language = readStringFlag(flags, "language");
4274
- const minScore = readStringFlag(flags, "min-score");
4275
- const minSpend = readStringFlag(flags, "min-spend");
4276
- const query = readStringFlag(flags, "query") ?? readStringFlag(flags, "search");
4277
- const advertiser = readStringFlag(flags, "advertiser");
4278
- const ids = readStringFlag(flags, "ids");
4279
- const sort = readStringFlag(flags, "sort");
4280
- const limit = readStringFlag(flags, "limit");
4281
- const page = readStringFlag(flags, "page");
4782
+ const platform2 = readStringFlag2(flags, "platform");
4783
+ const status2 = readStringFlag2(flags, "status");
4784
+ const format = readStringFlag2(flags, "format");
4785
+ const minDays = readStringFlag2(flags, "min-days");
4786
+ const maxDays = readStringFlag2(flags, "max-days");
4787
+ const language = readStringFlag2(flags, "language");
4788
+ const minScore = readStringFlag2(flags, "min-score");
4789
+ const minSpend = readStringFlag2(flags, "min-spend");
4790
+ const query = readStringFlag2(flags, "query") ?? readStringFlag2(flags, "search");
4791
+ const advertiser = readStringFlag2(flags, "advertiser");
4792
+ const ids = readStringFlag2(flags, "ids");
4793
+ const sort = readStringFlag2(flags, "sort");
4794
+ const limit = readStringFlag2(flags, "limit");
4795
+ const page = readStringFlag2(flags, "page");
4282
4796
  const qs = queryString3({ platform: platform2, status: status2, format, "min-days": minDays, "max-days": maxDays, language, "min-score": minScore, "min-spend": minSpend, query, advertiser, ids, sort, limit, page });
4283
4797
  return client.get(`/library/ads${qs}`);
4284
4798
  }
@@ -4289,7 +4803,7 @@ async function getLibraryAd(client, args, _flags) {
4289
4803
  }
4290
4804
 
4291
4805
  // src/commands/studio.ts
4292
- import { readFile } from "node:fs/promises";
4806
+ import { readFile as readFile2 } from "node:fs/promises";
4293
4807
  import path2 from "node:path";
4294
4808
  var GENERATE_FLAGS = ["ad", "mode", "aspect", "model", "quality", "quantity", "instructions", "audience", "colors", "ref", "title", "fields", "wait"];
4295
4809
  var ADS_LIST_FLAGS = ["fields", "status", "format", "search", "limit", "offset"];
@@ -4499,7 +5013,7 @@ async function uploadStudioFile(client, adId, filePath) {
4499
5013
  const filename = path2.basename(resolved);
4500
5014
  let data;
4501
5015
  try {
4502
- data = await readFile(resolved);
5016
+ data = await readFile2(resolved);
4503
5017
  } catch (err) {
4504
5018
  const code = err instanceof Error && "code" in err ? err.code : void 0;
4505
5019
  if (code === "ENOENT") throw new CliError("INVALID_VALUE", `File not found: ${resolved}`);
@@ -4568,8 +5082,8 @@ async function pollMedia(client, mediaIds, fields) {
4568
5082
  if (!currentMedia || typeof currentMedia.status !== "string") throw new CliError("SERVER_ERROR", `Invalid media response while polling ${id}`);
4569
5083
  media = currentMedia;
4570
5084
  if (currentMedia.status === "completed" || currentMedia.status === "failed") break;
4571
- await new Promise((resolve4) => {
4572
- setTimeout(resolve4, 2e3);
5085
+ await new Promise((resolve5) => {
5086
+ setTimeout(resolve5, 2e3);
4573
5087
  });
4574
5088
  }
4575
5089
  results.push(media);
@@ -4974,8 +5488,8 @@ async function submitFeedback({ args, client, flags, json }) {
4974
5488
  const payload = readFeedbackPayload(flags);
4975
5489
  const body = {
4976
5490
  context: parseJsonFlag(flags, "context"),
4977
- error: readStringFlag2(flags, "error"),
4978
- intent: readStringFlag2(flags, "intent"),
5491
+ error: readStringFlag3(flags, "error"),
5492
+ intent: readStringFlag3(flags, "intent"),
4979
5493
  message,
4980
5494
  payload,
4981
5495
  severity: readEnumFlag({ allowed: FEEDBACK_SEVERITIES, fallback: "medium", flags, key: "severity" }),
@@ -5029,7 +5543,7 @@ function parseNamedJsonFlag(flags, key) {
5029
5543
  throw new CliError("INVALID_VALUE", `Invalid JSON in \`--${key}\` flag`, `Example: --${key} '{"tool":"adkit_manage"}'`);
5030
5544
  }
5031
5545
  }
5032
- function readStringFlag2(flags, key) {
5546
+ function readStringFlag3(flags, key) {
5033
5547
  const value = flags[key];
5034
5548
  if (typeof value !== "string") return void 0;
5035
5549
  const trimmed = value.trim();
@@ -5121,6 +5635,7 @@ var GOOGLE_NUMERIC_ID_PATTERN2 = /^\d+$/;
5121
5635
  var GOOGLE_CAMPAIGN_CONVERSION_GOAL_CATEGORY_LIST = GOOGLE_CAMPAIGN_CONVERSION_GOAL_CATEGORIES.join(", ");
5122
5636
  var GOOGLE_RESULTS_LEVEL_ACTIONS = /* @__PURE__ */ new Set(["campaigns", "ad-groups", "ads"]);
5123
5637
  var TIKTOK_NUMERIC_ID_PATTERN = /^\d+$/;
5638
+ var REDDIT_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
5124
5639
  var LINKEDIN_ID_PATTERN = /[\d:_]/;
5125
5640
  var USAGE = `
5126
5641
  Usage: adkit <command> [options]
@@ -5193,6 +5708,7 @@ Flags (list/get):
5193
5708
  Note:
5194
5709
  list returns campaign configuration only (name, status, budget).
5195
5710
  AdKit hides deleted and archived campaigns by default. Use --status to include them.
5711
+ App campaigns use promotedApp in --data. Run --help full for the exact shape.
5196
5712
  For spend/clicks/conversions: adkit manage meta results
5197
5713
 
5198
5714
  Examples:
@@ -5236,10 +5752,15 @@ Advanced flags:
5236
5752
  specialAdCategories (string[]) credit, housing, employment, social_issues
5237
5753
  startDate (string) ISO date, e.g. "2026-04-01T00:00:00+0000"
5238
5754
  endDate (string|null) ISO date or null for no end
5755
+ promotedApp.store (string) apple_app_store or google_play
5756
+ promotedApp.storeAppId (string) Apple numeric app ID or Android package name
5757
+ promotedApp.providerAppId (string) Meta App ID (required)
5758
+ promotedApp.storeUrl (string) Optional store URL; AdKit builds the default when omitted
5239
5759
 
5240
5760
  Note:
5241
5761
  list returns campaign configuration only (name, status, budget).
5242
5762
  AdKit hides deleted and archived campaigns by default. Use --status to include them.
5763
+ promotedApp is create-only. To change the app, create a replacement campaign; AdKit never creates one automatically.
5243
5764
  For spend/clicks/conversions: adkit manage meta results
5244
5765
 
5245
5766
  Examples:
@@ -5258,6 +5779,11 @@ Examples:
5258
5779
  "name":"Housing Leads","objective":"leads",
5259
5780
  "budget":{"lifetime":500},"specialAdCategories":["housing"],
5260
5781
  "startDate":"2026-04-01T00:00:00+0000","endDate":"2026-04-30T23:59:59+0000"
5782
+ }' --publish
5783
+ # Create an Apple app campaign. Repeat the same promotedApp on its app ad sets.
5784
+ adkit manage meta campaigns create --data '{
5785
+ "name":"App Installs","objective":"app_promotion",
5786
+ "promotedApp":{"store":"apple_app_store","storeAppId":"123456789","providerAppId":"987654321"}
5261
5787
  }' --publish`;
5262
5788
  var ADSET_HELP = `adkit manage meta adsets \u2014 Meta ad sets
5263
5789
 
@@ -5293,6 +5819,7 @@ Rules:
5293
5819
  Note:
5294
5820
  list returns ad set configuration only (targeting, budget, optimization).
5295
5821
  AdKit hides deleted and archived ad sets by default. Use --status to include them.
5822
+ App ad sets use promotedApp in --data. Run --help full for the exact shape.
5296
5823
  Geo targeting lives in targeting.geoLocations. Use type=country or type=radius with latitude/longitude coordinates.
5297
5824
  Native city/place/zip examples: adkit manage meta adsets --help full
5298
5825
  For spend/clicks/conversions: adkit manage meta results
@@ -5360,6 +5887,10 @@ Advanced flags:
5360
5887
  promotedObject.pixelId (string) Meta Pixel ID
5361
5888
  promotedObject.customEventType (string) Custom conversion event
5362
5889
  promotedObject.pageId (string) Facebook Page ID
5890
+ promotedApp.store (string) apple_app_store or google_play
5891
+ promotedApp.storeAppId (string) Apple numeric app ID or Android package name
5892
+ promotedApp.providerAppId (string) Meta App ID (required)
5893
+ promotedApp.storeUrl (string) Optional store URL; AdKit builds the default when omitted
5363
5894
  targeting.age (object) {"min":25,"max":55}
5364
5895
  targeting.geoLocations (object) {"include":[{"type":"country","country":"US"}]} or {"include":[{"type":"radius","latitude":51.745,"longitude":4.55,"radius":30,"radiusUnit":"km"}]}
5365
5896
  targeting.publisherPlatforms (string[]) facebook, instagram, audience_network, messenger
@@ -5381,6 +5912,7 @@ Note:
5381
5912
  Geo targeting lives in targeting.geoLocations. Use type=country for simple countries or type=radius for coordinate-radius targeting.
5382
5913
  Radius targeting requires latitude/longitude coordinates; AdKit does not geocode addresses yet.
5383
5914
  With platformLocation, pass the exact Meta geo object to send for native city/place/zip IDs and unsupported geo fields.
5915
+ Repeat the campaign promotedApp on app ad sets. It is create-only; changing it requires a replacement.
5384
5916
  For spend/clicks/conversions: adkit manage meta results
5385
5917
 
5386
5918
  Examples:
@@ -5969,6 +6501,7 @@ Examples:
5969
6501
  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
5970
6502
  adkit manage google asset-groups delete 555666777 --account 1234567890 --publish`;
5971
6503
  var GOOGLE_ASSET_GROUP_HELP_FULL = GOOGLE_ASSET_GROUP_HELP;
6504
+ var GOOGLE_AD_GROUP_BIDDING_NOTE = " Set target CPA or ROAS, not both. Google may ignore an incompatible target; get after publish confirms the effective value. Use --data with null to remove an override.";
5972
6505
  var GOOGLE_AD_GROUP_HELP = `adkit manage google ad-groups \u2014 Google Ads ad groups
5973
6506
 
5974
6507
  list List ad groups
@@ -5982,6 +6515,8 @@ Flags (create/update):
5982
6515
  --name <name> Ad group name
5983
6516
  --status <s> enabled, paused, removed
5984
6517
  --cpc-bid <n> CPC bid in account currency
6518
+ --target-cpa <n> Target CPA override in account currency
6519
+ --target-roas <n> Target ROAS override as a ratio (3.5 = 350%)
5985
6520
 
5986
6521
  Flags (list):
5987
6522
  --campaign <id> Filter by Google campaign ID (platformId)
@@ -5994,12 +6529,15 @@ Note:
5994
6529
  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.
5995
6530
  Topic and interest search return IDs used in Display targeting: adkit manage google research topics <query>, adkit manage google research interests <query>.
5996
6531
  WARNING: On update, targeting replaces the full Display targeting set. If you send only one new website, old websites/topics/audiences can be removed.
6532
+ ${GOOGLE_AD_GROUP_BIDDING_NOTE}
5997
6533
  list returns ad group configuration only (name, status, bid).
5998
6534
  For spend/clicks/conversions: adkit manage google results
5999
6535
 
6000
6536
  Examples:
6001
6537
  adkit manage google ad-groups list --account 1234567890 --campaign 987654321
6002
6538
  adkit manage google ad-groups create --campaign 987654321 --name "Brand Terms" --cpc-bid 2 --account 1234567890
6539
+ adkit manage google ad-groups update 555666777 --target-cpa 25 --account 1234567890
6540
+ adkit manage google ad-groups update 555666777 --data '{"targetCpa":null}' --account 1234567890
6003
6541
  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
6004
6542
  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
6005
6543
  adkit manage google ad-groups update 555666777 --data '{"targeting":{"content":{"websites":{"include":["https://example.com","https://nytimes.com"]}}}}' --account 1234567890
@@ -6020,6 +6558,8 @@ Flags (create/update):
6020
6558
  --name <name> Ad group name
6021
6559
  --status <s> enabled, paused, removed
6022
6560
  --cpc-bid <n> CPC bid in account currency
6561
+ --target-cpa <n> Target CPA override in account currency
6562
+ --target-roas <n> Target ROAS override as a ratio (3.5 = 350%)
6023
6563
  ${FLAG.account}
6024
6564
  ${FLAG.publish}
6025
6565
  ${FLAG.data}
@@ -6037,6 +6577,7 @@ Notes:
6037
6577
  Topic and interest search return IDs used in targeting: research topics -> targeting.content.topics; research interests -> targeting.audience.interests.
6038
6578
  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.
6039
6579
  WARNING: On update, targeting replaces the full Display targeting set. If you send only one new website, old websites/topics/audiences can be removed.
6580
+ ${GOOGLE_AD_GROUP_BIDDING_NOTE}
6040
6581
  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.
6041
6582
  list returns ad group configuration only (name, status, bid).
6042
6583
  For spend/clicks/conversions: adkit manage google results
@@ -6044,6 +6585,8 @@ Notes:
6044
6585
  Examples:
6045
6586
  adkit manage google ad-groups list --account 1234567890 --campaign 987654321
6046
6587
  adkit manage google ad-groups create --campaign 987654321 --name "Brand Terms" --cpc-bid 2 --account 1234567890
6588
+ adkit manage google ad-groups update 555666777 --target-roas 3.5 --account 1234567890 --publish
6589
+ adkit manage google ad-groups update 555666777 --data '{"targetRoas":null}' --account 1234567890 --publish
6047
6590
  adkit manage google ad-groups create --data '{"adGroups":[{"campaignId":"987654321","name":"Brand Terms","cpcBid":2}]}' --account 1234567890
6048
6591
  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
6049
6592
  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
@@ -6784,8 +7327,8 @@ ${FLAG.data}
6784
7327
  --campaign <id> Parent campaign ID (create only)
6785
7328
  --name <name> Ad group name
6786
7329
  --status <s> active, paused (create default: paused)
6787
- --budget-daily <n> Daily budget in account currency
6788
- --budget-lifetime <n> Lifetime budget in account currency
7330
+ --budget-daily <n> Daily budget
7331
+ --budget-lifetime <n> Lifetime budget
6789
7332
  --start-date <value> Optional TikTok schedule datetime; omit to start now
6790
7333
  --end-date <value> Optional TikTok schedule datetime; required with lifetime budget
6791
7334
  --billing-event <s> cpc, cpm, ocpm
@@ -7048,12 +7591,123 @@ Example:
7048
7591
  available List accounts available from OAuth
7049
7592
  connect <id> Connect an account to the project
7050
7593
  disconnect <id> Disconnect an account
7594
+ <id> profiles List profiles available to the account
7595
+ <id> pixels List pixels available to the account
7596
+ <id> funding-instruments List funding instruments available to the account
7597
+ <id> update Save default profile, pixel, or funding instrument
7598
+
7599
+ Default flags:
7600
+ --default-profile <id>
7601
+ --default-pixel <id>
7602
+ --default-funding-instrument <id>
7051
7603
 
7052
7604
  Examples:
7053
7605
  adkit manage reddit accounts list
7054
7606
  adkit manage reddit accounts available
7055
7607
  adkit manage reddit accounts connect a2_example
7608
+ adkit manage reddit accounts a2_example profiles
7609
+ adkit manage reddit accounts a2_example update --default-profile profile_1 --default-pixel pixel_1 --default-funding-instrument funding_1
7056
7610
  adkit manage reddit accounts disconnect a2_example`.trim(),
7611
+ "reddit campaigns": `adkit manage reddit campaigns \u2014 Reddit campaigns
7612
+
7613
+ list List campaign configuration
7614
+ <id> Get one campaign by platform ID
7615
+ create Create a draft, or publish with --publish
7616
+ update <id> Create an update draft, or publish with --publish
7617
+
7618
+ Create flags:
7619
+ ${FLAG.account}
7620
+ --name <name> Required
7621
+ --objective <value> brand_awareness, clicks, lead_generation, sales
7622
+ --optimization <v> CBO only: clicks or video_view_6s
7623
+ --status <status> active or paused (default: paused)
7624
+ --budget-lifetime <amount> Optional CBO lifetime budget; requires --end-date
7625
+ --bid-strategy <value> CBO supports bidless
7626
+ --start-date <date> --end-date <date>
7627
+ --funding-instrument <id> Uses the account default when omitted
7628
+ --pixel <id> Uses the account default when required and omitted
7629
+ ${FLAG.publish}
7630
+ ${FLAG.data}
7631
+
7632
+ Examples:
7633
+ adkit manage reddit campaigns create --name "Traffic" --objective clicks
7634
+ adkit manage reddit campaigns update campaign_1 --status paused --publish`.trim(),
7635
+ "reddit ad-groups": `adkit manage reddit ad-groups \u2014 Reddit ad groups
7636
+
7637
+ list List ad-group configuration
7638
+ <id> Get one ad group by platform ID
7639
+ create Create a draft, or publish with --publish
7640
+ update <id> Create an update draft, or publish with --publish
7641
+
7642
+ Create flags:
7643
+ ${FLAG.account}
7644
+ --campaign <id> Required parent campaign ID
7645
+ --name <name> Required
7646
+ --status <status> active or paused (default: active)
7647
+ --budget-daily <amount> ABO daily budget
7648
+ --bid-strategy <value> bidless, manual_bidding, maximize_volume, or target_cpx
7649
+ --bid-amount <amount>
7650
+ --optimization <value>
7651
+ --start-date <date> --end-date <date>
7652
+ --pixel <id> Uses the account default when required and omitted
7653
+ ${FLAG.publish}
7654
+ ${FLAG.data}
7655
+
7656
+ Targeting:
7657
+ Pass the canonical targeting object through --data. Supported branches are geoLocations, demographics, audience (communities/interests/customAudiences), content.keywords, and inventory.
7658
+
7659
+ Example:
7660
+ adkit manage reddit ad-groups create --data '{"adGroups":[{"campaignId":"campaign_1","name":"Poland","budget":{"daily":20},"targeting":{"audience":{"communities":{"include":["advertising"]}}}}]}'`.trim(),
7661
+ "reddit ads": `adkit manage reddit ads \u2014 Reddit ads
7662
+
7663
+ list List ads and visible creative content
7664
+ <id> Get one ad by platform ID
7665
+ create Create a draft, or publish with --publish
7666
+ update <id> Create an update draft, or publish with --publish
7667
+
7668
+ Simple create flags:
7669
+ ${FLAG.account}
7670
+ --ad-group <id> Required parent ad-group ID
7671
+ --ad-type <type> freeform, image, or video; carousel uses --data
7672
+ --headline <text> Exactly one
7673
+ --primary-text <text> Required for freeform
7674
+ --file <path> Local image/video (uploaded internally)
7675
+ --media-id <id> Existing temporary upload ID instead of --file
7676
+ --thumbnail-file <path> Local video thumbnail (uploaded internally)
7677
+ --thumbnail-id <id> Existing thumbnail upload ID instead of --thumbnail-file
7678
+ --url <url> Required for image/video
7679
+ --cta <value>
7680
+ --existing-post <t3_id> Promote an eligible community post instead of creating content
7681
+ --profile <id> Uses the account default when omitted
7682
+ --status <status> active or paused (default: active)
7683
+ ${FLAG.publish}
7684
+ ${FLAG.data}
7685
+
7686
+ Notes:
7687
+ AdKit creates and waits for Reddit's post internally. Agents only create an ad and receive the verified ad result.
7688
+ Local --file/--thumbnail-file uploads are handled internally. Canonical --data bodies use temporary upload IDs in creative.media[].id.
7689
+
7690
+ Examples:
7691
+ adkit manage reddit ads create --ad-group ad_group_1 --ad-type image --headline "Try it" --file ./creative.jpg --url https://example.com
7692
+ adkit manage reddit ads create --ad-group ad_group_1 --existing-post t3_abc123 --headline "Try it"`.trim(),
7693
+ "reddit results": `adkit manage reddit results \u2014 Reddit performance reporting
7694
+
7695
+ list List normalized account, campaign, ad-group, or ad rows
7696
+
7697
+ Flags:
7698
+ ${FLAG.account}
7699
+ --level <level> Required: account, campaigns, ad-groups, ads
7700
+ --period <period> 3d, 7d, 14d, 28d, 30d, or 90d; or use --from/--to
7701
+ --from <date> YYYY-MM-DD
7702
+ --to <date> YYYY-MM-DD
7703
+ --fields <csv> Normalized result fields
7704
+ --breakdowns <csv> Currently day
7705
+ --campaign-ids <csv> --ad-group-ids <csv> --ad-ids <csv>
7706
+ --sort <field> --sort-direction <asc|desc>
7707
+ --limit <n> --offset <n>
7708
+
7709
+ Example:
7710
+ adkit manage reddit results --level campaigns --period 7d --fields spend,impressions,clicks`.trim(),
7057
7711
  "meta media": `adkit manage meta media \u2014 Upload media to Meta
7058
7712
 
7059
7713
  upload Upload an image or video
@@ -7073,6 +7727,7 @@ Examples:
7073
7727
  Must be enabled in Agent Permissions first.
7074
7728
  Use this for unsupported native platform resources/workflows. Use platformOverrides instead when you only need raw fields on a supported normalized resource.
7075
7729
  Use the selected platform's official endpoint paths, field names, enum values, and resource shapes. Do not translate normalized AdKit field names into raw API payloads by guessing.
7730
+ A successful raw request means the platform returned a successful HTTP response. It does not prove an asynchronous platform job or every downstream entity finished creating.
7076
7731
 
7077
7732
  Flags:
7078
7733
  --data <json> Full request object (preferred for agents)
@@ -7092,7 +7747,7 @@ Examples:
7092
7747
  adkit manage platform-api-requests --platform meta --endpoint "act_123/campaigns" --payload '{"name":"Test","objective":"OUTCOME_TRAFFIC","status":"PAUSED","special_ad_categories":[]}' --request-description "Create campaign" --account act_123 --publish
7093
7748
  adkit manage platform-api-requests --platform google --endpoint "customers/123/googleAds:mutate" --payload '{"mutateOperations":[...]}' --request-description "Pause ad" --account 123
7094
7749
  adkit manage platform-api-requests --platform tiktok --endpoint "campaign/get/" --method GET --payload '{"advertiser_id":"123"}' --request-description "List TikTok campaigns" --account 123
7095
- adkit manage platform-api-requests --platform reddit --endpoint "ad_accounts/a2_example/campaigns" --payload '{"name":"Test","objective":"TRAFFIC","configured_status":"PAUSED"}' --request-description "Create paused Reddit campaign" --account a2_example
7750
+ adkit manage platform-api-requests --platform reddit --endpoint "ad_accounts/a2_example/campaigns" --payload '{"name":"Test","objective":"SALES","configured_status":"PAUSED","funding_instrument_id":"fi_example","is_campaign_budget_optimization":false}' --request-description "Create paused Reddit campaign" --account a2_example
7096
7751
  adkit manage platform-api-requests --platform x --endpoint "accounts/18ce54d4x5t/campaigns" --method GET --request-description "List X campaigns" --account 18ce54d4x5t
7097
7752
  adkit manage platform-api-requests --platform linkedin --endpoint "adCampaignGroups/(account:urn:li:sponsoredAccount:512345678)" --method GET --request-description "List LinkedIn campaign groups" --account 512345678
7098
7753
  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`.trim(),
@@ -7103,7 +7758,7 @@ Platforms:
7103
7758
  google Google Ads
7104
7759
  tiktok TikTok Ads
7105
7760
  reddit Reddit Ads
7106
- x X account setup plus read-only campaigns, ad groups, ads, and results
7761
+ x X accounts plus campaign/ad-group reads and writes
7107
7762
  linkedin LinkedIn Ads (campaigns, ad groups, ads, targeting, results)
7108
7763
  microsoft Microsoft Ads raw API access + account setup
7109
7764
  drafts Manage drafts across platforms
@@ -7184,19 +7839,27 @@ Run adkit manage tiktok <group> --help for details.`.trim(),
7184
7839
  reddit: `adkit manage reddit \u2014 Reddit Ads management
7185
7840
 
7186
7841
  Entity groups:
7187
- accounts List and connect Reddit ad accounts
7842
+ accounts Connect accounts and configure profile/pixel/funding defaults
7843
+ campaigns List, get, create, and update campaigns
7844
+ ad-groups List, get, create, and update ad groups with targeting
7845
+ ads List, get, create, and update ads; Reddit post jobs stay internal
7846
+ results Normalized performance reporting
7188
7847
 
7189
7848
  ${rawPlatformCommandLine("reddit")}
7190
7849
 
7191
7850
  General flags:
7192
7851
  ${FLAG.account}
7852
+ ${FLAG.publish}
7853
+ ${FLAG.data}
7193
7854
 
7194
- Run adkit manage reddit accounts --help for account setup.`.trim(),
7855
+ Mutations are draft-first by default. Use --publish only when the user wants immediate platform execution.
7856
+ Recommended order: configure account defaults, create campaign, create ad group, create ad, then read results.
7857
+ Run adkit manage reddit <group> --help for details.`.trim(),
7195
7858
  x: `adkit manage x \u2014 X Ads management
7196
7859
 
7197
7860
  Entity groups:
7198
- accounts Connect accounts and configure profile/pixel defaults
7199
- campaigns List/get campaigns
7861
+ accounts Connect accounts and configure profile/pixel/funding defaults
7862
+ campaigns List/get/create/update campaigns
7200
7863
  ad-groups List/get line items as ad groups
7201
7864
  ads List/get promoted-Post associations
7202
7865
  results Campaign, ad-group, and ad performance
@@ -7215,12 +7878,16 @@ Examples:
7215
7878
  disconnect <id> Disconnect an X ad account
7216
7879
  <id> profiles List X identities available to an account
7217
7880
  <id> pixels List X conversion pixels available to an account
7218
- <id> update Save default identity and pixel IDs
7881
+ <id> funding-instruments
7882
+ List X funding sources available to an account
7883
+ <id> update Save default identity, pixel, and funding IDs
7219
7884
 
7220
7885
  Flags:
7221
7886
  --integration <id> Connect using a specific workspace login returned by available
7222
7887
  --default-profile <id> Default X identity for ad creation
7223
7888
  --default-pixel <id> Default X conversion pixel
7889
+ --default-funding-instrument <id>
7890
+ Default X funding source
7224
7891
  --data <json> Full update body; also supports null to clear a default
7225
7892
 
7226
7893
  Examples:
@@ -7229,15 +7896,23 @@ Examples:
7229
7896
  adkit manage x accounts connect 18ce54d4x5t
7230
7897
  adkit manage x accounts 18ce54d4x5t profiles
7231
7898
  adkit manage x accounts 18ce54d4x5t pixels
7232
- adkit manage x accounts 18ce54d4x5t update --default-profile 12abc --default-pixel p123
7899
+ adkit manage x accounts 18ce54d4x5t funding-instruments
7900
+ adkit manage x accounts 18ce54d4x5t update --default-profile 12abc --default-pixel p123 --default-funding-instrument f123
7233
7901
  adkit manage x accounts disconnect 18ce54d4x5t`.trim(),
7234
- "x campaigns": `adkit manage x campaigns \u2014 X Ads campaigns (read-only)
7902
+ "x campaigns": `adkit manage x campaigns \u2014 X Ads campaigns
7235
7903
 
7236
7904
  list List campaigns
7237
7905
  get <id> Get one campaign
7906
+ create Create a draft; add --publish to send it to X paused by default
7907
+ update <id> Create an update draft; add --publish to apply it
7238
7908
 
7239
7909
  Flags:
7240
7910
  ${FLAG.account}
7911
+ ${FLAG.publish}
7912
+ --name <name> Campaign name
7913
+ --budget-daily <n> Daily budget in account currency
7914
+ --budget-lifetime <n> Lifetime budget in account currency
7915
+ --funding-instrument <id> Override the account funding default (create only)
7241
7916
  --campaign-ids <ids> Comma-separated campaign platform IDs
7242
7917
  --status <s> active, paused, or removed
7243
7918
  --limit <n> Max rows
@@ -7245,26 +7920,46 @@ ${FLAG.account}
7245
7920
 
7246
7921
  Example:
7247
7922
  adkit manage x campaigns list --status active
7248
- adkit manage x campaigns get 8v7jo`.trim(),
7249
- "x ad-groups": `adkit manage x ad-groups \u2014 X line items as AdKit ad groups (read-only)
7923
+ adkit manage x campaigns get 8v7jo
7924
+ adkit manage x campaigns create --name "Launch" --budget-daily 25 --publish
7925
+ adkit manage x campaigns update 8v7jo --status paused --publish`.trim(),
7926
+ "x ad-groups": `adkit manage x ad-groups \u2014 X line items as AdKit ad groups
7250
7927
 
7251
7928
  list List ad groups
7252
7929
  get <id> Get one ad group
7930
+ create Create a draft; add --publish to send it to X paused by default
7931
+ update <id> Create an update draft; add --publish to apply it
7253
7932
 
7254
7933
  Flags:
7255
7934
  ${FLAG.account}
7935
+ ${FLAG.publish}
7936
+ --campaign <id> Parent campaign platform ID (create only)
7937
+ --name <name> Ad-group name
7938
+ --objective <value> engagements, reach, video_views, or website_clicks (create only)
7939
+ --budget-daily <n> Daily budget; parent campaign must use X LINE_ITEM budgeting
7940
+ --budget-lifetime <n> Lifetime budget; parent campaign must use X LINE_ITEM budgeting
7941
+ --bid-amount <n> Bid amount in account currency
7942
+ --bid-strategy <s> auto, max, or target
7943
+ --optimization <s> X optimization goal
7944
+ --start-date <date> ISO 8601 start date (required for create)
7945
+ --end-date <date> ISO 8601 end date
7256
7946
  --campaign-ids <ids> Comma-separated parent campaign IDs
7257
7947
  --ad-group-ids <ids> Comma-separated line-item IDs
7258
7948
  --status <s> active, paused, or removed
7259
7949
  --limit <n> Max rows
7260
7950
  --offset <n> Row offset
7951
+ --data <json> Full request body; use for targeting
7261
7952
 
7262
- Example:
7263
- adkit manage x ad-groups list --campaign-ids 8v7jo`.trim(),
7264
- "x ads": `adkit manage x ads \u2014 X promoted-Post ads (read-only)
7953
+ Examples:
7954
+ adkit manage x ad-groups list --campaign-ids 8v7jo
7955
+ adkit manage x ad-groups create --campaign 8v7jo --name "Poland launch" --objective website_clicks --start-date 2026-09-10 --budget-daily 25
7956
+ adkit manage x ad-groups update b9x4k --status paused --publish`.trim(),
7957
+ "x ads": `adkit manage x ads \u2014 X promoted-Post ads
7265
7958
 
7266
7959
  list List promoted-Post ads
7267
7960
  get <id> Get one ad by promoted association ID
7961
+ create Create an ad draft; add --publish to send it to X
7962
+ delete <id> Remove a promoted association as a draft; add --publish to send
7268
7963
 
7269
7964
  Identity:
7270
7965
  platformId is the promoted association ID used by X Ads and analytics. postId is the underlying Post ID.
@@ -7277,9 +7972,18 @@ ${FLAG.account}
7277
7972
  --status <s> active, paused, or removed
7278
7973
  --limit <n> Max rows
7279
7974
  --offset <n> Row offset
7975
+ --ad-group <id> Parent X line-item ID (create)
7976
+ --existing-post <id> Promote an existing X Post (create)
7977
+ --status active Only active is valid; pause the ad group or delete the ad
7978
+ ${FLAG.publish}
7979
+ --data <json> Full body for inline creative: { "ads": [...] }
7280
7980
 
7281
- Example:
7282
- adkit manage x ads get 1efwlo`.trim(),
7981
+ Create one X ad per request.
7982
+
7983
+ Examples:
7984
+ adkit manage x ads create --ad-group b9x4k --existing-post 880290790664060928 --publish
7985
+ adkit manage x ads create --data '{"ads":[{"adGroupId":"b9x4k","creative":{"primaryTexts":["Hello"]}}]}'
7986
+ adkit manage x ads delete 1efwlo --publish`.trim(),
7283
7987
  "x results": `adkit manage x results \u2014 X Ads performance reporting (read-only)
7284
7988
 
7285
7989
  list List reporting rows (list is optional)
@@ -7724,7 +8428,7 @@ function printList(data, flags, emptyHint = "No results.") {
7724
8428
  if (output) console.log(output);
7725
8429
  else console.log(emptyHint);
7726
8430
  }
7727
- function isObject(value) {
8431
+ function isObject2(value) {
7728
8432
  return value !== null && typeof value === "object" && !Array.isArray(value);
7729
8433
  }
7730
8434
  function fmtNum(n) {
@@ -7770,7 +8474,7 @@ function num(obj, key) {
7770
8474
  }
7771
8475
  function nested(obj, key) {
7772
8476
  const v = obj[key];
7773
- return isObject(v) ? v : void 0;
8477
+ return isObject2(v) ? v : void 0;
7774
8478
  }
7775
8479
  function printClickLine(label, obj, currency) {
7776
8480
  const parts = [];
@@ -7783,7 +8487,7 @@ function printClickLine(label, obj, currency) {
7783
8487
  if (parts.length > 0) console.log(` ${label.padEnd(14)}${parts.join(" \xB7 ")}`);
7784
8488
  }
7785
8489
  function printResultsCard(data) {
7786
- if (!isObject(data)) {
8490
+ if (!isObject2(data)) {
7787
8491
  console.log(JSON.stringify(data, null, 2));
7788
8492
  return;
7789
8493
  }
@@ -7812,7 +8516,7 @@ function printResultsCard(data) {
7812
8516
  return;
7813
8517
  }
7814
8518
  for (const row of results) {
7815
- if (!isObject(row)) continue;
8519
+ if (!isObject2(row)) continue;
7816
8520
  const entity = nested(row, "entity");
7817
8521
  const metrics = nested(row, "metrics");
7818
8522
  const breakdown = nested(row, "breakdown");
@@ -7866,7 +8570,7 @@ ${titleParts.join(" \xB7 ")}`);
7866
8570
  const actions = nested(row, "conversionEvents") ?? nested(row, "actions");
7867
8571
  if (actions) {
7868
8572
  for (const [key, val] of Object.entries(actions)) {
7869
- if (!isObject(val)) continue;
8573
+ if (!isObject2(val)) continue;
7870
8574
  const parts = [];
7871
8575
  const count = num(val, "count");
7872
8576
  const cost = num(val, "costPerResult") ?? num(val, "cost");
@@ -7903,7 +8607,7 @@ function printWarnings(warnings) {
7903
8607
  if (!Array.isArray(warnings) || warnings.length === 0) return;
7904
8608
  console.log("");
7905
8609
  for (const w of warnings) {
7906
- if (!isObject(w)) continue;
8610
+ if (!isObject2(w)) continue;
7907
8611
  const msg = str(w, "message") ?? JSON.stringify(w);
7908
8612
  console.log(`Warning: ${msg}`);
7909
8613
  }
@@ -7922,28 +8626,28 @@ function shortDate(v) {
7922
8626
  return v.slice(0, 10);
7923
8627
  }
7924
8628
  function isCampaignDetail(value) {
7925
- return isObject(value);
8629
+ return isObject2(value);
7926
8630
  }
7927
8631
  function isAdSetDetail(value) {
7928
- return isObject(value);
8632
+ return isObject2(value);
7929
8633
  }
7930
8634
  function isAdDetail(value) {
7931
- return isObject(value);
8635
+ return isObject2(value);
7932
8636
  }
7933
8637
  function isCampaignDetailData(value) {
7934
- if (!isObject(value)) return false;
8638
+ if (!isObject2(value)) return false;
7935
8639
  if ("campaigns" in value) return Array.isArray(value.campaigns) && value.campaigns.every(isCampaignDetail);
7936
8640
  if ("campaign" in value) return isCampaignDetail(value.campaign);
7937
8641
  return true;
7938
8642
  }
7939
8643
  function isAdSetDetailData(value) {
7940
- if (!isObject(value)) return false;
8644
+ if (!isObject2(value)) return false;
7941
8645
  if ("adsets" in value) return Array.isArray(value.adsets) && value.adsets.every(isAdSetDetail);
7942
8646
  if ("adset" in value) return isAdSetDetail(value.adset);
7943
8647
  return true;
7944
8648
  }
7945
8649
  function isAdDetailData(value) {
7946
- if (!isObject(value)) return false;
8650
+ if (!isObject2(value)) return false;
7947
8651
  if ("ads" in value) return Array.isArray(value.ads) && value.ads.every(isAdDetail);
7948
8652
  if ("ad" in value) return isAdDetail(value.ad);
7949
8653
  return true;
@@ -7958,6 +8662,7 @@ function printSingleEntity(e, entity) {
7958
8662
  if (campaign.lifetimeBudget != null) lines.push(` Lifetime: ${formatForTTY(campaign.lifetimeBudget)}`);
7959
8663
  if (campaign.bidStrategy) lines.push(` Bid Strategy: ${formatForTTY(campaign.bidStrategy)}`);
7960
8664
  if (campaign.spendCap != null) lines.push(` Spend Cap: ${formatForTTY(campaign.spendCap)}`);
8665
+ if (campaign.promotedApp) lines.push(` App: ${JSON.stringify(campaign.promotedApp)}`);
7961
8666
  if (campaign.startDate) lines.push(` Start: ${formatForTTY(campaign.startDate)}`);
7962
8667
  if (campaign.endDate) lines.push(` End: ${formatForTTY(campaign.endDate)}`);
7963
8668
  if (campaign.specialAdCategories) lines.push(` Categories: ${JSON.stringify(campaign.specialAdCategories)}`);
@@ -7971,6 +8676,7 @@ function printSingleEntity(e, entity) {
7971
8676
  if (adset.billingEvent) lines.push(` Billing: ${formatForTTY(adset.billingEvent)}`);
7972
8677
  if (adset.targeting) lines.push(` Targeting: ${JSON.stringify(adset.targeting)}`);
7973
8678
  if (adset.promotedObject) lines.push(` Promoted: ${JSON.stringify(adset.promotedObject)}`);
8679
+ if (adset.promotedApp) lines.push(` App: ${JSON.stringify(adset.promotedApp)}`);
7974
8680
  if (adset.startDate) lines.push(` Start: ${formatForTTY(adset.startDate)}`);
7975
8681
  if (adset.endDate) lines.push(` End: ${formatForTTY(adset.endDate)}`);
7976
8682
  if (createdAt) lines.push(` Created: ${createdAt}`);
@@ -8020,14 +8726,14 @@ function printAdDetail(data) {
8020
8726
  printSingleEntity("ad" in data ? data.ad : data, "ad");
8021
8727
  }
8022
8728
  function printGoogleAdDetail(data) {
8023
- if (isObject(data) && "ads" in data && Array.isArray(data.ads)) {
8729
+ if (isObject2(data) && "ads" in data && Array.isArray(data.ads)) {
8024
8730
  for (const item of data.ads) {
8025
8731
  if (!isGoogleCliAdRecord(item)) continue;
8026
8732
  console.log(toGoogleAdDetailLines(item).join("\n"));
8027
8733
  }
8028
8734
  return;
8029
8735
  }
8030
- const ad = isObject(data) && "ad" in data ? data.ad : data;
8736
+ const ad = isObject2(data) && "ad" in data ? data.ad : data;
8031
8737
  if (!isGoogleCliAdRecord(ad)) {
8032
8738
  console.log(JSON.stringify(data, null, 2));
8033
8739
  return;
@@ -8412,11 +9118,11 @@ async function selfUpdate(currentVersion) {
8412
9118
  const installed = execSync("npm list -g @adkit/cli --depth=0 --json", { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
8413
9119
  const parsed = JSON.parse(installed);
8414
9120
  let newVersion = "unknown";
8415
- if (isObject(parsed) && "dependencies" in parsed) {
9121
+ if (isObject2(parsed) && "dependencies" in parsed) {
8416
9122
  const deps = parsed.dependencies;
8417
- if (isObject(deps) && "@adkit/cli" in deps) {
9123
+ if (isObject2(deps) && "@adkit/cli" in deps) {
8418
9124
  const pkg = deps["@adkit/cli"];
8419
- if (isObject(pkg) && "version" in pkg) {
9125
+ if (isObject2(pkg) && "version" in pkg) {
8420
9126
  const v = pkg.version;
8421
9127
  if (typeof v === "string") newVersion = v;
8422
9128
  }
@@ -9211,6 +9917,22 @@ async function main() {
9211
9917
  } else if (platform2 === "reddit") {
9212
9918
  switch (entity) {
9213
9919
  case "accounts": {
9920
+ if (action && action !== "list" && action !== "available" && action !== "connect" && action !== "disconnect") {
9921
+ const accountId = action;
9922
+ const subcommand = args[4];
9923
+ if (subcommand === "profiles") {
9924
+ data = await listRedditProfiles(client, [accountId], flags);
9925
+ emptyHint = "No Reddit profiles found for this account.";
9926
+ } else if (subcommand === "pixels") {
9927
+ data = await listRedditPixels(client, [accountId], flags);
9928
+ emptyHint = "No Reddit pixels found for this account.";
9929
+ } else if (subcommand === "funding-instruments") {
9930
+ data = await listRedditFundingInstruments(client, [accountId], flags);
9931
+ emptyHint = "No Reddit funding instruments found for this account.";
9932
+ } else if (subcommand === "update") data = await updateRedditAccount(client, [accountId], flags);
9933
+ else throw new CliError("UNKNOWN_COMMAND", `Unknown subcommand: reddit accounts ${accountId} ${subcommand ?? ""}`, "Expected: profiles, pixels, funding-instruments, or update");
9934
+ break;
9935
+ }
9214
9936
  switch (action) {
9215
9937
  case "list":
9216
9938
  case void 0:
@@ -9232,8 +9954,56 @@ async function main() {
9232
9954
  }
9233
9955
  break;
9234
9956
  }
9957
+ case "campaigns": {
9958
+ if (!action) {
9959
+ showHelp("reddit campaigns", flags.help === "full");
9960
+ return;
9961
+ }
9962
+ if (action === "list") {
9963
+ data = await listRedditCampaigns(client, restArgs, flags);
9964
+ emptyHint = "No Reddit campaigns found for this account.";
9965
+ } else if (action === "create") data = await createRedditCampaign(client, restArgs, flags);
9966
+ else if (action === "update") data = await updateRedditCampaign(client, restArgs, flags);
9967
+ else if (REDDIT_ID_PATTERN.test(action)) data = await getRedditCampaign(client, [action], flags);
9968
+ else throw new CliError("UNKNOWN_COMMAND", `Unknown action: reddit campaigns ${action}`, "Available: list, create, update, or a campaign ID");
9969
+ break;
9970
+ }
9971
+ case "ad-groups": {
9972
+ if (!action) {
9973
+ showHelp("reddit ad-groups", flags.help === "full");
9974
+ return;
9975
+ }
9976
+ if (action === "list") {
9977
+ data = await listRedditAdGroups(client, restArgs, flags);
9978
+ emptyHint = "No Reddit ad groups found for this account.";
9979
+ } else if (action === "create") data = await createRedditAdGroup(client, restArgs, flags);
9980
+ else if (action === "update") data = await updateRedditAdGroup(client, restArgs, flags);
9981
+ else if (REDDIT_ID_PATTERN.test(action)) data = await getRedditAdGroup(client, [action], flags);
9982
+ else throw new CliError("UNKNOWN_COMMAND", `Unknown action: reddit ad-groups ${action}`, "Available: list, create, update, or an ad-group ID");
9983
+ break;
9984
+ }
9985
+ case "ads": {
9986
+ if (!action) {
9987
+ showHelp("reddit ads", flags.help === "full");
9988
+ return;
9989
+ }
9990
+ if (action === "list") {
9991
+ data = await listRedditAds(client, restArgs, flags);
9992
+ emptyHint = "No Reddit ads found for this account.";
9993
+ } else if (action === "create") data = await createRedditAd(client, restArgs, flags);
9994
+ else if (action === "update") data = await updateRedditAd(client, restArgs, flags);
9995
+ else if (REDDIT_ID_PATTERN.test(action)) data = await getRedditAd(client, [action], flags);
9996
+ else throw new CliError("UNKNOWN_COMMAND", `Unknown action: reddit ads ${action}`, "Available: list, create, update, or an ad ID");
9997
+ break;
9998
+ }
9999
+ case "results": {
10000
+ if (action && action !== "list") throw new CliError("UNKNOWN_COMMAND", `Unknown action: reddit results ${action}`, "Available: list");
10001
+ data = await listRedditResults(client, restArgs, flags);
10002
+ emptyHint = "No Reddit result rows found for the selected date range.";
10003
+ break;
10004
+ }
9235
10005
  default:
9236
- throw new CliError("UNKNOWN_COMMAND", `Unknown entity: reddit ${entity}`, "Available: accounts, platform-api-requests");
10006
+ throw new CliError("UNKNOWN_COMMAND", `Unknown entity: reddit ${entity}`, "Available: accounts, campaigns, ad-groups, ads, results, platform-api-requests");
9237
10007
  }
9238
10008
  } else if (platform2 === "linkedin") {
9239
10009
  switch (entity) {
@@ -9397,8 +10167,11 @@ async function main() {
9397
10167
  } else if (subcommand === "pixels") {
9398
10168
  data = await listXPixels(client, [accountId], flags);
9399
10169
  emptyHint = "No X conversion pixels found for this account.";
10170
+ } else if (subcommand === "funding-instruments") {
10171
+ data = await listXFundingInstruments(client, [accountId], flags);
10172
+ emptyHint = "No X funding sources found for this account.";
9400
10173
  } else if (subcommand === "update") data = await updateXAccount(client, [accountId], flags);
9401
- else throw new CliError("UNKNOWN_COMMAND", `Unknown subcommand: x accounts ${accountId} ${subcommand ?? ""}`, "Expected: profiles, pixels, or update");
10174
+ else throw new CliError("UNKNOWN_COMMAND", `Unknown subcommand: x accounts ${accountId} ${subcommand ?? ""}`, "Expected: profiles, pixels, funding-instruments, or update");
9402
10175
  break;
9403
10176
  }
9404
10177
  switch (action) {
@@ -9427,21 +10200,27 @@ async function main() {
9427
10200
  data = await listXCampaigns(client, restArgs, flags);
9428
10201
  emptyHint = "No X campaigns matched the selected filters.";
9429
10202
  } else if (action === "get") data = await getXCampaign(client, restArgs, flags);
9430
- else throw new CliError("UNKNOWN_COMMAND", `Unknown action: x campaigns ${action}`, "Available: list, get");
10203
+ else if (action === "create") data = await createXCampaign(client, restArgs, flags);
10204
+ else if (action === "update") data = await updateXCampaign(client, restArgs, flags);
10205
+ else throw new CliError("UNKNOWN_COMMAND", `Unknown action: x campaigns ${action}`, "Available: list, get, create, update");
9431
10206
  break;
9432
10207
  case "ad-groups":
9433
10208
  if (action === "list") {
9434
10209
  data = await listXAdGroups(client, restArgs, flags);
9435
10210
  emptyHint = "No X ad groups matched the selected filters.";
9436
10211
  } else if (action === "get") data = await getXAdGroup(client, restArgs, flags);
9437
- else throw new CliError("UNKNOWN_COMMAND", `Unknown action: x ad-groups ${action}`, "Available: list, get");
10212
+ else if (action === "create") data = await createXAdGroup(client, restArgs, flags);
10213
+ else if (action === "update") data = await updateXAdGroup(client, restArgs, flags);
10214
+ else throw new CliError("UNKNOWN_COMMAND", `Unknown action: x ad-groups ${action}`, "Available: list, get, create, update");
9438
10215
  break;
9439
10216
  case "ads":
9440
10217
  if (action === "list") {
9441
10218
  data = await listXAds(client, restArgs, flags);
9442
10219
  emptyHint = "No X promoted-Post ads matched the selected filters.";
9443
10220
  } else if (action === "get") data = await getXAd(client, restArgs, flags);
9444
- else throw new CliError("UNKNOWN_COMMAND", `Unknown action: x ads ${action}`, "Available: list, get");
10221
+ else if (action === "create") data = await createXAd(client, restArgs, flags);
10222
+ else if (action === "delete") data = await deleteXAd(client, restArgs, flags);
10223
+ else throw new CliError("UNKNOWN_COMMAND", `Unknown action: x ads ${action}`, "Available: list, get, create, delete");
9445
10224
  break;
9446
10225
  case "results":
9447
10226
  if (action && action !== "list") throw new CliError("UNKNOWN_COMMAND", `Unknown action: x results ${action}`, "Available: list");