@adkit/cli 1.13.31 → 1.13.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/dist/cli.js +943 -116
- 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((
|
|
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
|
-
|
|
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
|
|
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
|
|
3606
|
-
return client.delete(`/manage/reddit/accounts/${
|
|
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
|
|
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
|
|
3614
|
-
var
|
|
3615
|
-
var
|
|
3616
|
-
var
|
|
3617
|
-
var
|
|
3618
|
-
var
|
|
3619
|
-
var
|
|
3620
|
-
var
|
|
3621
|
-
var
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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 =
|
|
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 =
|
|
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 =
|
|
3854
|
-
const 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
|
|
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 =
|
|
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 =
|
|
3898
|
-
const resolvedFilename = filename ??
|
|
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
|
|
3933
|
-
var
|
|
3934
|
-
var
|
|
3935
|
-
var
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,11 +4723,11 @@ 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
|
|
4726
|
+
function readStringFlag2(flags, key) {
|
|
4213
4727
|
const value = flags[key];
|
|
4214
4728
|
return typeof value === "string" ? value : void 0;
|
|
4215
4729
|
}
|
|
4216
|
-
var ADVERTISER_FILTER_FLAGS = ["industry", "category", "platform", "sort", "limit", "page"];
|
|
4730
|
+
var ADVERTISER_FILTER_FLAGS = ["industry", "category", "platform", "sort", "limit", "page", "scope"];
|
|
4217
4731
|
async function listAdvertisers(client, _args, flags) {
|
|
4218
4732
|
validateFlags(flags, ADVERTISER_FILTER_FLAGS, "library advertisers list");
|
|
4219
4733
|
const qs = buildAdvertiserQueryString(flags);
|
|
@@ -4255,30 +4769,47 @@ async function addAdvertiser(client, _args, flags) {
|
|
|
4255
4769
|
linkedInLibraryUrl: linkedinLibrary
|
|
4256
4770
|
});
|
|
4257
4771
|
}
|
|
4772
|
+
async function trackAdvertiser(client, args, flags) {
|
|
4773
|
+
const path3 = getAdvertiserWatchlistPath(args, flags);
|
|
4774
|
+
return client.post(path3);
|
|
4775
|
+
}
|
|
4776
|
+
async function untrackAdvertiser(client, args, flags) {
|
|
4777
|
+
const path3 = getAdvertiserWatchlistPath(args, flags);
|
|
4778
|
+
return client.delete(path3);
|
|
4779
|
+
}
|
|
4780
|
+
function getAdvertiserWatchlistPath(args, flags) {
|
|
4781
|
+
validateFlags(flags, [], "library advertisers");
|
|
4782
|
+
if (args.length !== 1 || !args[0]) throw new CliError("MISSING_ARGUMENT", "Expected exactly one advertiser ID", "Run: adkit library advertisers track <id> or adkit library advertisers untrack <id>");
|
|
4783
|
+
if (flags.data !== void 0) throw new CliError("INVALID_VALUE", "Watchlist changes do not accept --data", "Pass the advertiser ID as a positional argument and select the project with --project");
|
|
4784
|
+
const id = encodeURIComponent(args[0]);
|
|
4785
|
+
return `/library/advertisers/${id}/watchlist`;
|
|
4786
|
+
}
|
|
4258
4787
|
function buildAdvertiserQueryString(flags, query) {
|
|
4788
|
+
const scope = flags.scope;
|
|
4789
|
+
if (scope !== void 0 && scope !== "library" && scope !== "watchlist") throw new CliError("INVALID_VALUE", "--scope must be library or watchlist", "Use --scope watchlist for the selected project, or omit it to search the whole library");
|
|
4259
4790
|
const industry = typeof flags.industry === "string" ? flags.industry : void 0;
|
|
4260
4791
|
const category = typeof flags.category === "string" ? flags.category : void 0;
|
|
4261
4792
|
const platform2 = typeof flags.platform === "string" ? flags.platform : void 0;
|
|
4262
|
-
const sort = typeof flags.sort === "string" ? flags.sort :
|
|
4793
|
+
const sort = typeof flags.sort === "string" ? flags.sort : void 0;
|
|
4263
4794
|
const limit = typeof flags.limit === "string" ? flags.limit : void 0;
|
|
4264
4795
|
const page = typeof flags.page === "string" ? flags.page : void 0;
|
|
4265
|
-
return queryString3({ industry, category, platform: platform2, query, sort, limit, page });
|
|
4796
|
+
return queryString3({ industry, category, platform: platform2, query, sort, limit, page, scope });
|
|
4266
4797
|
}
|
|
4267
4798
|
async function listLibraryAds(client, _args, flags) {
|
|
4268
|
-
const platform2 =
|
|
4269
|
-
const status2 =
|
|
4270
|
-
const format =
|
|
4271
|
-
const minDays =
|
|
4272
|
-
const maxDays =
|
|
4273
|
-
const language =
|
|
4274
|
-
const minScore =
|
|
4275
|
-
const minSpend =
|
|
4276
|
-
const query =
|
|
4277
|
-
const advertiser =
|
|
4278
|
-
const ids =
|
|
4279
|
-
const sort =
|
|
4280
|
-
const limit =
|
|
4281
|
-
const page =
|
|
4799
|
+
const platform2 = readStringFlag2(flags, "platform");
|
|
4800
|
+
const status2 = readStringFlag2(flags, "status");
|
|
4801
|
+
const format = readStringFlag2(flags, "format");
|
|
4802
|
+
const minDays = readStringFlag2(flags, "min-days");
|
|
4803
|
+
const maxDays = readStringFlag2(flags, "max-days");
|
|
4804
|
+
const language = readStringFlag2(flags, "language");
|
|
4805
|
+
const minScore = readStringFlag2(flags, "min-score");
|
|
4806
|
+
const minSpend = readStringFlag2(flags, "min-spend");
|
|
4807
|
+
const query = readStringFlag2(flags, "query") ?? readStringFlag2(flags, "search");
|
|
4808
|
+
const advertiser = readStringFlag2(flags, "advertiser");
|
|
4809
|
+
const ids = readStringFlag2(flags, "ids");
|
|
4810
|
+
const sort = readStringFlag2(flags, "sort");
|
|
4811
|
+
const limit = readStringFlag2(flags, "limit");
|
|
4812
|
+
const page = readStringFlag2(flags, "page");
|
|
4282
4813
|
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
4814
|
return client.get(`/library/ads${qs}`);
|
|
4284
4815
|
}
|
|
@@ -4289,7 +4820,7 @@ async function getLibraryAd(client, args, _flags) {
|
|
|
4289
4820
|
}
|
|
4290
4821
|
|
|
4291
4822
|
// src/commands/studio.ts
|
|
4292
|
-
import { readFile } from "node:fs/promises";
|
|
4823
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
4293
4824
|
import path2 from "node:path";
|
|
4294
4825
|
var GENERATE_FLAGS = ["ad", "mode", "aspect", "model", "quality", "quantity", "instructions", "audience", "colors", "ref", "title", "fields", "wait"];
|
|
4295
4826
|
var ADS_LIST_FLAGS = ["fields", "status", "format", "search", "limit", "offset"];
|
|
@@ -4499,7 +5030,7 @@ async function uploadStudioFile(client, adId, filePath) {
|
|
|
4499
5030
|
const filename = path2.basename(resolved);
|
|
4500
5031
|
let data;
|
|
4501
5032
|
try {
|
|
4502
|
-
data = await
|
|
5033
|
+
data = await readFile2(resolved);
|
|
4503
5034
|
} catch (err) {
|
|
4504
5035
|
const code = err instanceof Error && "code" in err ? err.code : void 0;
|
|
4505
5036
|
if (code === "ENOENT") throw new CliError("INVALID_VALUE", `File not found: ${resolved}`);
|
|
@@ -4568,8 +5099,8 @@ async function pollMedia(client, mediaIds, fields) {
|
|
|
4568
5099
|
if (!currentMedia || typeof currentMedia.status !== "string") throw new CliError("SERVER_ERROR", `Invalid media response while polling ${id}`);
|
|
4569
5100
|
media = currentMedia;
|
|
4570
5101
|
if (currentMedia.status === "completed" || currentMedia.status === "failed") break;
|
|
4571
|
-
await new Promise((
|
|
4572
|
-
setTimeout(
|
|
5102
|
+
await new Promise((resolve5) => {
|
|
5103
|
+
setTimeout(resolve5, 2e3);
|
|
4573
5104
|
});
|
|
4574
5105
|
}
|
|
4575
5106
|
results.push(media);
|
|
@@ -4974,8 +5505,8 @@ async function submitFeedback({ args, client, flags, json }) {
|
|
|
4974
5505
|
const payload = readFeedbackPayload(flags);
|
|
4975
5506
|
const body = {
|
|
4976
5507
|
context: parseJsonFlag(flags, "context"),
|
|
4977
|
-
error:
|
|
4978
|
-
intent:
|
|
5508
|
+
error: readStringFlag3(flags, "error"),
|
|
5509
|
+
intent: readStringFlag3(flags, "intent"),
|
|
4979
5510
|
message,
|
|
4980
5511
|
payload,
|
|
4981
5512
|
severity: readEnumFlag({ allowed: FEEDBACK_SEVERITIES, fallback: "medium", flags, key: "severity" }),
|
|
@@ -5029,7 +5560,7 @@ function parseNamedJsonFlag(flags, key) {
|
|
|
5029
5560
|
throw new CliError("INVALID_VALUE", `Invalid JSON in \`--${key}\` flag`, `Example: --${key} '{"tool":"adkit_manage"}'`);
|
|
5030
5561
|
}
|
|
5031
5562
|
}
|
|
5032
|
-
function
|
|
5563
|
+
function readStringFlag3(flags, key) {
|
|
5033
5564
|
const value = flags[key];
|
|
5034
5565
|
if (typeof value !== "string") return void 0;
|
|
5035
5566
|
const trimmed = value.trim();
|
|
@@ -5121,6 +5652,7 @@ var GOOGLE_NUMERIC_ID_PATTERN2 = /^\d+$/;
|
|
|
5121
5652
|
var GOOGLE_CAMPAIGN_CONVERSION_GOAL_CATEGORY_LIST = GOOGLE_CAMPAIGN_CONVERSION_GOAL_CATEGORIES.join(", ");
|
|
5122
5653
|
var GOOGLE_RESULTS_LEVEL_ACTIONS = /* @__PURE__ */ new Set(["campaigns", "ad-groups", "ads"]);
|
|
5123
5654
|
var TIKTOK_NUMERIC_ID_PATTERN = /^\d+$/;
|
|
5655
|
+
var REDDIT_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
5124
5656
|
var LINKEDIN_ID_PATTERN = /[\d:_]/;
|
|
5125
5657
|
var USAGE = `
|
|
5126
5658
|
Usage: adkit <command> [options]
|
|
@@ -5193,6 +5725,7 @@ Flags (list/get):
|
|
|
5193
5725
|
Note:
|
|
5194
5726
|
list returns campaign configuration only (name, status, budget).
|
|
5195
5727
|
AdKit hides deleted and archived campaigns by default. Use --status to include them.
|
|
5728
|
+
App campaigns use promotedApp in --data. Run --help full for the exact shape.
|
|
5196
5729
|
For spend/clicks/conversions: adkit manage meta results
|
|
5197
5730
|
|
|
5198
5731
|
Examples:
|
|
@@ -5236,10 +5769,15 @@ Advanced flags:
|
|
|
5236
5769
|
specialAdCategories (string[]) credit, housing, employment, social_issues
|
|
5237
5770
|
startDate (string) ISO date, e.g. "2026-04-01T00:00:00+0000"
|
|
5238
5771
|
endDate (string|null) ISO date or null for no end
|
|
5772
|
+
promotedApp.store (string) apple_app_store or google_play
|
|
5773
|
+
promotedApp.storeAppId (string) Apple numeric app ID or Android package name
|
|
5774
|
+
promotedApp.providerAppId (string) Meta App ID (required)
|
|
5775
|
+
promotedApp.storeUrl (string) Optional store URL; AdKit builds the default when omitted
|
|
5239
5776
|
|
|
5240
5777
|
Note:
|
|
5241
5778
|
list returns campaign configuration only (name, status, budget).
|
|
5242
5779
|
AdKit hides deleted and archived campaigns by default. Use --status to include them.
|
|
5780
|
+
promotedApp is create-only. To change the app, create a replacement campaign; AdKit never creates one automatically.
|
|
5243
5781
|
For spend/clicks/conversions: adkit manage meta results
|
|
5244
5782
|
|
|
5245
5783
|
Examples:
|
|
@@ -5258,6 +5796,11 @@ Examples:
|
|
|
5258
5796
|
"name":"Housing Leads","objective":"leads",
|
|
5259
5797
|
"budget":{"lifetime":500},"specialAdCategories":["housing"],
|
|
5260
5798
|
"startDate":"2026-04-01T00:00:00+0000","endDate":"2026-04-30T23:59:59+0000"
|
|
5799
|
+
}' --publish
|
|
5800
|
+
# Create an Apple app campaign. Repeat the same promotedApp on its app ad sets.
|
|
5801
|
+
adkit manage meta campaigns create --data '{
|
|
5802
|
+
"name":"App Installs","objective":"app_promotion",
|
|
5803
|
+
"promotedApp":{"store":"apple_app_store","storeAppId":"123456789","providerAppId":"987654321"}
|
|
5261
5804
|
}' --publish`;
|
|
5262
5805
|
var ADSET_HELP = `adkit manage meta adsets \u2014 Meta ad sets
|
|
5263
5806
|
|
|
@@ -5293,6 +5836,7 @@ Rules:
|
|
|
5293
5836
|
Note:
|
|
5294
5837
|
list returns ad set configuration only (targeting, budget, optimization).
|
|
5295
5838
|
AdKit hides deleted and archived ad sets by default. Use --status to include them.
|
|
5839
|
+
App ad sets use promotedApp in --data. Run --help full for the exact shape.
|
|
5296
5840
|
Geo targeting lives in targeting.geoLocations. Use type=country or type=radius with latitude/longitude coordinates.
|
|
5297
5841
|
Native city/place/zip examples: adkit manage meta adsets --help full
|
|
5298
5842
|
For spend/clicks/conversions: adkit manage meta results
|
|
@@ -5360,6 +5904,10 @@ Advanced flags:
|
|
|
5360
5904
|
promotedObject.pixelId (string) Meta Pixel ID
|
|
5361
5905
|
promotedObject.customEventType (string) Custom conversion event
|
|
5362
5906
|
promotedObject.pageId (string) Facebook Page ID
|
|
5907
|
+
promotedApp.store (string) apple_app_store or google_play
|
|
5908
|
+
promotedApp.storeAppId (string) Apple numeric app ID or Android package name
|
|
5909
|
+
promotedApp.providerAppId (string) Meta App ID (required)
|
|
5910
|
+
promotedApp.storeUrl (string) Optional store URL; AdKit builds the default when omitted
|
|
5363
5911
|
targeting.age (object) {"min":25,"max":55}
|
|
5364
5912
|
targeting.geoLocations (object) {"include":[{"type":"country","country":"US"}]} or {"include":[{"type":"radius","latitude":51.745,"longitude":4.55,"radius":30,"radiusUnit":"km"}]}
|
|
5365
5913
|
targeting.publisherPlatforms (string[]) facebook, instagram, audience_network, messenger
|
|
@@ -5381,6 +5929,7 @@ Note:
|
|
|
5381
5929
|
Geo targeting lives in targeting.geoLocations. Use type=country for simple countries or type=radius for coordinate-radius targeting.
|
|
5382
5930
|
Radius targeting requires latitude/longitude coordinates; AdKit does not geocode addresses yet.
|
|
5383
5931
|
With platformLocation, pass the exact Meta geo object to send for native city/place/zip IDs and unsupported geo fields.
|
|
5932
|
+
Repeat the campaign promotedApp on app ad sets. It is create-only; changing it requires a replacement.
|
|
5384
5933
|
For spend/clicks/conversions: adkit manage meta results
|
|
5385
5934
|
|
|
5386
5935
|
Examples:
|
|
@@ -5969,6 +6518,7 @@ Examples:
|
|
|
5969
6518
|
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
6519
|
adkit manage google asset-groups delete 555666777 --account 1234567890 --publish`;
|
|
5971
6520
|
var GOOGLE_ASSET_GROUP_HELP_FULL = GOOGLE_ASSET_GROUP_HELP;
|
|
6521
|
+
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
6522
|
var GOOGLE_AD_GROUP_HELP = `adkit manage google ad-groups \u2014 Google Ads ad groups
|
|
5973
6523
|
|
|
5974
6524
|
list List ad groups
|
|
@@ -5982,6 +6532,8 @@ Flags (create/update):
|
|
|
5982
6532
|
--name <name> Ad group name
|
|
5983
6533
|
--status <s> enabled, paused, removed
|
|
5984
6534
|
--cpc-bid <n> CPC bid in account currency
|
|
6535
|
+
--target-cpa <n> Target CPA override in account currency
|
|
6536
|
+
--target-roas <n> Target ROAS override as a ratio (3.5 = 350%)
|
|
5985
6537
|
|
|
5986
6538
|
Flags (list):
|
|
5987
6539
|
--campaign <id> Filter by Google campaign ID (platformId)
|
|
@@ -5994,12 +6546,15 @@ Note:
|
|
|
5994
6546
|
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
6547
|
Topic and interest search return IDs used in Display targeting: adkit manage google research topics <query>, adkit manage google research interests <query>.
|
|
5996
6548
|
WARNING: On update, targeting replaces the full Display targeting set. If you send only one new website, old websites/topics/audiences can be removed.
|
|
6549
|
+
${GOOGLE_AD_GROUP_BIDDING_NOTE}
|
|
5997
6550
|
list returns ad group configuration only (name, status, bid).
|
|
5998
6551
|
For spend/clicks/conversions: adkit manage google results
|
|
5999
6552
|
|
|
6000
6553
|
Examples:
|
|
6001
6554
|
adkit manage google ad-groups list --account 1234567890 --campaign 987654321
|
|
6002
6555
|
adkit manage google ad-groups create --campaign 987654321 --name "Brand Terms" --cpc-bid 2 --account 1234567890
|
|
6556
|
+
adkit manage google ad-groups update 555666777 --target-cpa 25 --account 1234567890
|
|
6557
|
+
adkit manage google ad-groups update 555666777 --data '{"targetCpa":null}' --account 1234567890
|
|
6003
6558
|
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
6559
|
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
6560
|
adkit manage google ad-groups update 555666777 --data '{"targeting":{"content":{"websites":{"include":["https://example.com","https://nytimes.com"]}}}}' --account 1234567890
|
|
@@ -6020,6 +6575,8 @@ Flags (create/update):
|
|
|
6020
6575
|
--name <name> Ad group name
|
|
6021
6576
|
--status <s> enabled, paused, removed
|
|
6022
6577
|
--cpc-bid <n> CPC bid in account currency
|
|
6578
|
+
--target-cpa <n> Target CPA override in account currency
|
|
6579
|
+
--target-roas <n> Target ROAS override as a ratio (3.5 = 350%)
|
|
6023
6580
|
${FLAG.account}
|
|
6024
6581
|
${FLAG.publish}
|
|
6025
6582
|
${FLAG.data}
|
|
@@ -6037,6 +6594,7 @@ Notes:
|
|
|
6037
6594
|
Topic and interest search return IDs used in targeting: research topics -> targeting.content.topics; research interests -> targeting.audience.interests.
|
|
6038
6595
|
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
6596
|
WARNING: On update, targeting replaces the full Display targeting set. If you send only one new website, old websites/topics/audiences can be removed.
|
|
6597
|
+
${GOOGLE_AD_GROUP_BIDDING_NOTE}
|
|
6040
6598
|
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
6599
|
list returns ad group configuration only (name, status, bid).
|
|
6042
6600
|
For spend/clicks/conversions: adkit manage google results
|
|
@@ -6044,6 +6602,8 @@ Notes:
|
|
|
6044
6602
|
Examples:
|
|
6045
6603
|
adkit manage google ad-groups list --account 1234567890 --campaign 987654321
|
|
6046
6604
|
adkit manage google ad-groups create --campaign 987654321 --name "Brand Terms" --cpc-bid 2 --account 1234567890
|
|
6605
|
+
adkit manage google ad-groups update 555666777 --target-roas 3.5 --account 1234567890 --publish
|
|
6606
|
+
adkit manage google ad-groups update 555666777 --data '{"targetRoas":null}' --account 1234567890 --publish
|
|
6047
6607
|
adkit manage google ad-groups create --data '{"adGroups":[{"campaignId":"987654321","name":"Brand Terms","cpcBid":2}]}' --account 1234567890
|
|
6048
6608
|
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
6609
|
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 +7344,8 @@ ${FLAG.data}
|
|
|
6784
7344
|
--campaign <id> Parent campaign ID (create only)
|
|
6785
7345
|
--name <name> Ad group name
|
|
6786
7346
|
--status <s> active, paused (create default: paused)
|
|
6787
|
-
--budget-daily <n> Daily budget
|
|
6788
|
-
--budget-lifetime <n> Lifetime budget
|
|
7347
|
+
--budget-daily <n> Daily budget
|
|
7348
|
+
--budget-lifetime <n> Lifetime budget
|
|
6789
7349
|
--start-date <value> Optional TikTok schedule datetime; omit to start now
|
|
6790
7350
|
--end-date <value> Optional TikTok schedule datetime; required with lifetime budget
|
|
6791
7351
|
--billing-event <s> cpc, cpm, ocpm
|
|
@@ -7048,12 +7608,123 @@ Example:
|
|
|
7048
7608
|
available List accounts available from OAuth
|
|
7049
7609
|
connect <id> Connect an account to the project
|
|
7050
7610
|
disconnect <id> Disconnect an account
|
|
7611
|
+
<id> profiles List profiles available to the account
|
|
7612
|
+
<id> pixels List pixels available to the account
|
|
7613
|
+
<id> funding-instruments List funding instruments available to the account
|
|
7614
|
+
<id> update Save default profile, pixel, or funding instrument
|
|
7615
|
+
|
|
7616
|
+
Default flags:
|
|
7617
|
+
--default-profile <id>
|
|
7618
|
+
--default-pixel <id>
|
|
7619
|
+
--default-funding-instrument <id>
|
|
7051
7620
|
|
|
7052
7621
|
Examples:
|
|
7053
7622
|
adkit manage reddit accounts list
|
|
7054
7623
|
adkit manage reddit accounts available
|
|
7055
7624
|
adkit manage reddit accounts connect a2_example
|
|
7625
|
+
adkit manage reddit accounts a2_example profiles
|
|
7626
|
+
adkit manage reddit accounts a2_example update --default-profile profile_1 --default-pixel pixel_1 --default-funding-instrument funding_1
|
|
7056
7627
|
adkit manage reddit accounts disconnect a2_example`.trim(),
|
|
7628
|
+
"reddit campaigns": `adkit manage reddit campaigns \u2014 Reddit campaigns
|
|
7629
|
+
|
|
7630
|
+
list List campaign configuration
|
|
7631
|
+
<id> Get one campaign by platform ID
|
|
7632
|
+
create Create a draft, or publish with --publish
|
|
7633
|
+
update <id> Create an update draft, or publish with --publish
|
|
7634
|
+
|
|
7635
|
+
Create flags:
|
|
7636
|
+
${FLAG.account}
|
|
7637
|
+
--name <name> Required
|
|
7638
|
+
--objective <value> brand_awareness, clicks, lead_generation, sales
|
|
7639
|
+
--optimization <v> CBO only: clicks or video_view_6s
|
|
7640
|
+
--status <status> active or paused (default: paused)
|
|
7641
|
+
--budget-lifetime <amount> Optional CBO lifetime budget; requires --end-date
|
|
7642
|
+
--bid-strategy <value> CBO supports bidless
|
|
7643
|
+
--start-date <date> --end-date <date>
|
|
7644
|
+
--funding-instrument <id> Uses the account default when omitted
|
|
7645
|
+
--pixel <id> Uses the account default when required and omitted
|
|
7646
|
+
${FLAG.publish}
|
|
7647
|
+
${FLAG.data}
|
|
7648
|
+
|
|
7649
|
+
Examples:
|
|
7650
|
+
adkit manage reddit campaigns create --name "Traffic" --objective clicks
|
|
7651
|
+
adkit manage reddit campaigns update campaign_1 --status paused --publish`.trim(),
|
|
7652
|
+
"reddit ad-groups": `adkit manage reddit ad-groups \u2014 Reddit ad groups
|
|
7653
|
+
|
|
7654
|
+
list List ad-group configuration
|
|
7655
|
+
<id> Get one ad group by platform ID
|
|
7656
|
+
create Create a draft, or publish with --publish
|
|
7657
|
+
update <id> Create an update draft, or publish with --publish
|
|
7658
|
+
|
|
7659
|
+
Create flags:
|
|
7660
|
+
${FLAG.account}
|
|
7661
|
+
--campaign <id> Required parent campaign ID
|
|
7662
|
+
--name <name> Required
|
|
7663
|
+
--status <status> active or paused (default: active)
|
|
7664
|
+
--budget-daily <amount> ABO daily budget
|
|
7665
|
+
--bid-strategy <value> bidless, manual_bidding, maximize_volume, or target_cpx
|
|
7666
|
+
--bid-amount <amount>
|
|
7667
|
+
--optimization <value>
|
|
7668
|
+
--start-date <date> --end-date <date>
|
|
7669
|
+
--pixel <id> Uses the account default when required and omitted
|
|
7670
|
+
${FLAG.publish}
|
|
7671
|
+
${FLAG.data}
|
|
7672
|
+
|
|
7673
|
+
Targeting:
|
|
7674
|
+
Pass the canonical targeting object through --data. Supported branches are geoLocations, demographics, audience (communities/interests/customAudiences), content.keywords, and inventory.
|
|
7675
|
+
|
|
7676
|
+
Example:
|
|
7677
|
+
adkit manage reddit ad-groups create --data '{"adGroups":[{"campaignId":"campaign_1","name":"Poland","budget":{"daily":20},"targeting":{"audience":{"communities":{"include":["advertising"]}}}}]}'`.trim(),
|
|
7678
|
+
"reddit ads": `adkit manage reddit ads \u2014 Reddit ads
|
|
7679
|
+
|
|
7680
|
+
list List ads and visible creative content
|
|
7681
|
+
<id> Get one ad by platform ID
|
|
7682
|
+
create Create a draft, or publish with --publish
|
|
7683
|
+
update <id> Create an update draft, or publish with --publish
|
|
7684
|
+
|
|
7685
|
+
Simple create flags:
|
|
7686
|
+
${FLAG.account}
|
|
7687
|
+
--ad-group <id> Required parent ad-group ID
|
|
7688
|
+
--ad-type <type> freeform, image, or video; carousel uses --data
|
|
7689
|
+
--headline <text> Exactly one
|
|
7690
|
+
--primary-text <text> Required for freeform
|
|
7691
|
+
--file <path> Local image/video (uploaded internally)
|
|
7692
|
+
--media-id <id> Existing temporary upload ID instead of --file
|
|
7693
|
+
--thumbnail-file <path> Local video thumbnail (uploaded internally)
|
|
7694
|
+
--thumbnail-id <id> Existing thumbnail upload ID instead of --thumbnail-file
|
|
7695
|
+
--url <url> Required for image/video
|
|
7696
|
+
--cta <value>
|
|
7697
|
+
--existing-post <t3_id> Promote an eligible community post instead of creating content
|
|
7698
|
+
--profile <id> Uses the account default when omitted
|
|
7699
|
+
--status <status> active or paused (default: active)
|
|
7700
|
+
${FLAG.publish}
|
|
7701
|
+
${FLAG.data}
|
|
7702
|
+
|
|
7703
|
+
Notes:
|
|
7704
|
+
AdKit creates and waits for Reddit's post internally. Agents only create an ad and receive the verified ad result.
|
|
7705
|
+
Local --file/--thumbnail-file uploads are handled internally. Canonical --data bodies use temporary upload IDs in creative.media[].id.
|
|
7706
|
+
|
|
7707
|
+
Examples:
|
|
7708
|
+
adkit manage reddit ads create --ad-group ad_group_1 --ad-type image --headline "Try it" --file ./creative.jpg --url https://example.com
|
|
7709
|
+
adkit manage reddit ads create --ad-group ad_group_1 --existing-post t3_abc123 --headline "Try it"`.trim(),
|
|
7710
|
+
"reddit results": `adkit manage reddit results \u2014 Reddit performance reporting
|
|
7711
|
+
|
|
7712
|
+
list List normalized account, campaign, ad-group, or ad rows
|
|
7713
|
+
|
|
7714
|
+
Flags:
|
|
7715
|
+
${FLAG.account}
|
|
7716
|
+
--level <level> Required: account, campaigns, ad-groups, ads
|
|
7717
|
+
--period <period> 3d, 7d, 14d, 28d, 30d, or 90d; or use --from/--to
|
|
7718
|
+
--from <date> YYYY-MM-DD
|
|
7719
|
+
--to <date> YYYY-MM-DD
|
|
7720
|
+
--fields <csv> Normalized result fields
|
|
7721
|
+
--breakdowns <csv> Currently day
|
|
7722
|
+
--campaign-ids <csv> --ad-group-ids <csv> --ad-ids <csv>
|
|
7723
|
+
--sort <field> --sort-direction <asc|desc>
|
|
7724
|
+
--limit <n> --offset <n>
|
|
7725
|
+
|
|
7726
|
+
Example:
|
|
7727
|
+
adkit manage reddit results --level campaigns --period 7d --fields spend,impressions,clicks`.trim(),
|
|
7057
7728
|
"meta media": `adkit manage meta media \u2014 Upload media to Meta
|
|
7058
7729
|
|
|
7059
7730
|
upload Upload an image or video
|
|
@@ -7073,6 +7744,7 @@ Examples:
|
|
|
7073
7744
|
Must be enabled in Agent Permissions first.
|
|
7074
7745
|
Use this for unsupported native platform resources/workflows. Use platformOverrides instead when you only need raw fields on a supported normalized resource.
|
|
7075
7746
|
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.
|
|
7747
|
+
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
7748
|
|
|
7077
7749
|
Flags:
|
|
7078
7750
|
--data <json> Full request object (preferred for agents)
|
|
@@ -7092,7 +7764,7 @@ Examples:
|
|
|
7092
7764
|
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
7765
|
adkit manage platform-api-requests --platform google --endpoint "customers/123/googleAds:mutate" --payload '{"mutateOperations":[...]}' --request-description "Pause ad" --account 123
|
|
7094
7766
|
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":"
|
|
7767
|
+
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
7768
|
adkit manage platform-api-requests --platform x --endpoint "accounts/18ce54d4x5t/campaigns" --method GET --request-description "List X campaigns" --account 18ce54d4x5t
|
|
7097
7769
|
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
7770
|
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 +7775,7 @@ Platforms:
|
|
|
7103
7775
|
google Google Ads
|
|
7104
7776
|
tiktok TikTok Ads
|
|
7105
7777
|
reddit Reddit Ads
|
|
7106
|
-
x X
|
|
7778
|
+
x X accounts plus campaign/ad-group reads and writes
|
|
7107
7779
|
linkedin LinkedIn Ads (campaigns, ad groups, ads, targeting, results)
|
|
7108
7780
|
microsoft Microsoft Ads raw API access + account setup
|
|
7109
7781
|
drafts Manage drafts across platforms
|
|
@@ -7184,19 +7856,27 @@ Run adkit manage tiktok <group> --help for details.`.trim(),
|
|
|
7184
7856
|
reddit: `adkit manage reddit \u2014 Reddit Ads management
|
|
7185
7857
|
|
|
7186
7858
|
Entity groups:
|
|
7187
|
-
accounts
|
|
7859
|
+
accounts Connect accounts and configure profile/pixel/funding defaults
|
|
7860
|
+
campaigns List, get, create, and update campaigns
|
|
7861
|
+
ad-groups List, get, create, and update ad groups with targeting
|
|
7862
|
+
ads List, get, create, and update ads; Reddit post jobs stay internal
|
|
7863
|
+
results Normalized performance reporting
|
|
7188
7864
|
|
|
7189
7865
|
${rawPlatformCommandLine("reddit")}
|
|
7190
7866
|
|
|
7191
7867
|
General flags:
|
|
7192
7868
|
${FLAG.account}
|
|
7869
|
+
${FLAG.publish}
|
|
7870
|
+
${FLAG.data}
|
|
7193
7871
|
|
|
7194
|
-
|
|
7872
|
+
Mutations are draft-first by default. Use --publish only when the user wants immediate platform execution.
|
|
7873
|
+
Recommended order: configure account defaults, create campaign, create ad group, create ad, then read results.
|
|
7874
|
+
Run adkit manage reddit <group> --help for details.`.trim(),
|
|
7195
7875
|
x: `adkit manage x \u2014 X Ads management
|
|
7196
7876
|
|
|
7197
7877
|
Entity groups:
|
|
7198
|
-
accounts Connect accounts and configure profile/pixel defaults
|
|
7199
|
-
campaigns List/get campaigns
|
|
7878
|
+
accounts Connect accounts and configure profile/pixel/funding defaults
|
|
7879
|
+
campaigns List/get/create/update campaigns
|
|
7200
7880
|
ad-groups List/get line items as ad groups
|
|
7201
7881
|
ads List/get promoted-Post associations
|
|
7202
7882
|
results Campaign, ad-group, and ad performance
|
|
@@ -7215,12 +7895,16 @@ Examples:
|
|
|
7215
7895
|
disconnect <id> Disconnect an X ad account
|
|
7216
7896
|
<id> profiles List X identities available to an account
|
|
7217
7897
|
<id> pixels List X conversion pixels available to an account
|
|
7218
|
-
<id>
|
|
7898
|
+
<id> funding-instruments
|
|
7899
|
+
List X funding sources available to an account
|
|
7900
|
+
<id> update Save default identity, pixel, and funding IDs
|
|
7219
7901
|
|
|
7220
7902
|
Flags:
|
|
7221
7903
|
--integration <id> Connect using a specific workspace login returned by available
|
|
7222
7904
|
--default-profile <id> Default X identity for ad creation
|
|
7223
7905
|
--default-pixel <id> Default X conversion pixel
|
|
7906
|
+
--default-funding-instrument <id>
|
|
7907
|
+
Default X funding source
|
|
7224
7908
|
--data <json> Full update body; also supports null to clear a default
|
|
7225
7909
|
|
|
7226
7910
|
Examples:
|
|
@@ -7229,15 +7913,23 @@ Examples:
|
|
|
7229
7913
|
adkit manage x accounts connect 18ce54d4x5t
|
|
7230
7914
|
adkit manage x accounts 18ce54d4x5t profiles
|
|
7231
7915
|
adkit manage x accounts 18ce54d4x5t pixels
|
|
7232
|
-
adkit manage x accounts 18ce54d4x5t
|
|
7916
|
+
adkit manage x accounts 18ce54d4x5t funding-instruments
|
|
7917
|
+
adkit manage x accounts 18ce54d4x5t update --default-profile 12abc --default-pixel p123 --default-funding-instrument f123
|
|
7233
7918
|
adkit manage x accounts disconnect 18ce54d4x5t`.trim(),
|
|
7234
|
-
"x campaigns": `adkit manage x campaigns \u2014 X Ads campaigns
|
|
7919
|
+
"x campaigns": `adkit manage x campaigns \u2014 X Ads campaigns
|
|
7235
7920
|
|
|
7236
7921
|
list List campaigns
|
|
7237
7922
|
get <id> Get one campaign
|
|
7923
|
+
create Create a draft; add --publish to send it to X paused by default
|
|
7924
|
+
update <id> Create an update draft; add --publish to apply it
|
|
7238
7925
|
|
|
7239
7926
|
Flags:
|
|
7240
7927
|
${FLAG.account}
|
|
7928
|
+
${FLAG.publish}
|
|
7929
|
+
--name <name> Campaign name
|
|
7930
|
+
--budget-daily <n> Daily budget in account currency
|
|
7931
|
+
--budget-lifetime <n> Lifetime budget in account currency
|
|
7932
|
+
--funding-instrument <id> Override the account funding default (create only)
|
|
7241
7933
|
--campaign-ids <ids> Comma-separated campaign platform IDs
|
|
7242
7934
|
--status <s> active, paused, or removed
|
|
7243
7935
|
--limit <n> Max rows
|
|
@@ -7245,26 +7937,46 @@ ${FLAG.account}
|
|
|
7245
7937
|
|
|
7246
7938
|
Example:
|
|
7247
7939
|
adkit manage x campaigns list --status active
|
|
7248
|
-
adkit manage x campaigns get 8v7jo
|
|
7249
|
-
|
|
7940
|
+
adkit manage x campaigns get 8v7jo
|
|
7941
|
+
adkit manage x campaigns create --name "Launch" --budget-daily 25 --publish
|
|
7942
|
+
adkit manage x campaigns update 8v7jo --status paused --publish`.trim(),
|
|
7943
|
+
"x ad-groups": `adkit manage x ad-groups \u2014 X line items as AdKit ad groups
|
|
7250
7944
|
|
|
7251
7945
|
list List ad groups
|
|
7252
7946
|
get <id> Get one ad group
|
|
7947
|
+
create Create a draft; add --publish to send it to X paused by default
|
|
7948
|
+
update <id> Create an update draft; add --publish to apply it
|
|
7253
7949
|
|
|
7254
7950
|
Flags:
|
|
7255
7951
|
${FLAG.account}
|
|
7952
|
+
${FLAG.publish}
|
|
7953
|
+
--campaign <id> Parent campaign platform ID (create only)
|
|
7954
|
+
--name <name> Ad-group name
|
|
7955
|
+
--objective <value> engagements, reach, video_views, or website_clicks (create only)
|
|
7956
|
+
--budget-daily <n> Daily budget; parent campaign must use X LINE_ITEM budgeting
|
|
7957
|
+
--budget-lifetime <n> Lifetime budget; parent campaign must use X LINE_ITEM budgeting
|
|
7958
|
+
--bid-amount <n> Bid amount in account currency
|
|
7959
|
+
--bid-strategy <s> auto, max, or target
|
|
7960
|
+
--optimization <s> X optimization goal
|
|
7961
|
+
--start-date <date> ISO 8601 start date (required for create)
|
|
7962
|
+
--end-date <date> ISO 8601 end date
|
|
7256
7963
|
--campaign-ids <ids> Comma-separated parent campaign IDs
|
|
7257
7964
|
--ad-group-ids <ids> Comma-separated line-item IDs
|
|
7258
7965
|
--status <s> active, paused, or removed
|
|
7259
7966
|
--limit <n> Max rows
|
|
7260
7967
|
--offset <n> Row offset
|
|
7968
|
+
--data <json> Full request body; use for targeting
|
|
7261
7969
|
|
|
7262
|
-
|
|
7263
|
-
adkit manage x ad-groups list --campaign-ids 8v7jo
|
|
7264
|
-
|
|
7970
|
+
Examples:
|
|
7971
|
+
adkit manage x ad-groups list --campaign-ids 8v7jo
|
|
7972
|
+
adkit manage x ad-groups create --campaign 8v7jo --name "Poland launch" --objective website_clicks --start-date 2026-09-10 --budget-daily 25
|
|
7973
|
+
adkit manage x ad-groups update b9x4k --status paused --publish`.trim(),
|
|
7974
|
+
"x ads": `adkit manage x ads \u2014 X promoted-Post ads
|
|
7265
7975
|
|
|
7266
7976
|
list List promoted-Post ads
|
|
7267
7977
|
get <id> Get one ad by promoted association ID
|
|
7978
|
+
create Create an ad draft; add --publish to send it to X
|
|
7979
|
+
delete <id> Remove a promoted association as a draft; add --publish to send
|
|
7268
7980
|
|
|
7269
7981
|
Identity:
|
|
7270
7982
|
platformId is the promoted association ID used by X Ads and analytics. postId is the underlying Post ID.
|
|
@@ -7277,9 +7989,18 @@ ${FLAG.account}
|
|
|
7277
7989
|
--status <s> active, paused, or removed
|
|
7278
7990
|
--limit <n> Max rows
|
|
7279
7991
|
--offset <n> Row offset
|
|
7992
|
+
--ad-group <id> Parent X line-item ID (create)
|
|
7993
|
+
--existing-post <id> Promote an existing X Post (create)
|
|
7994
|
+
--status active Only active is valid; pause the ad group or delete the ad
|
|
7995
|
+
${FLAG.publish}
|
|
7996
|
+
--data <json> Full body for inline creative: { "ads": [...] }
|
|
7280
7997
|
|
|
7281
|
-
|
|
7282
|
-
|
|
7998
|
+
Create one X ad per request.
|
|
7999
|
+
|
|
8000
|
+
Examples:
|
|
8001
|
+
adkit manage x ads create --ad-group b9x4k --existing-post 880290790664060928 --publish
|
|
8002
|
+
adkit manage x ads create --data '{"ads":[{"adGroupId":"b9x4k","creative":{"primaryTexts":["Hello"]}}]}'
|
|
8003
|
+
adkit manage x ads delete 1efwlo --publish`.trim(),
|
|
7283
8004
|
"x results": `adkit manage x results \u2014 X Ads performance reporting (read-only)
|
|
7284
8005
|
|
|
7285
8006
|
list List reporting rows (list is optional)
|
|
@@ -7504,10 +8225,22 @@ Examples:
|
|
|
7504
8225
|
<id> View advertiser details
|
|
7505
8226
|
similar Find similar to your project
|
|
7506
8227
|
similar <id> Find similar to an advertiser
|
|
7507
|
-
add
|
|
8228
|
+
add Find or add an advertiser and save it to My Watchlist
|
|
8229
|
+
track <id> Save an existing advertiser to My Watchlist
|
|
8230
|
+
untrack <id> Remove it from this project only
|
|
8231
|
+
|
|
8232
|
+
Watchlist:
|
|
8233
|
+
--scope <library|watchlist> List/search the whole library (default) or My Watchlist
|
|
8234
|
+
--project <id> Choose the project; otherwise use the active project
|
|
8235
|
+
Track/untrack return advertiserId and tracked. Repeating either is safe.
|
|
8236
|
+
Untrack keeps the advertiser and other projects intact.
|
|
7508
8237
|
|
|
7509
8238
|
Examples:
|
|
7510
8239
|
adkit library advertisers list
|
|
8240
|
+
adkit library advertisers list --scope watchlist --project <project-id>
|
|
8241
|
+
adkit library advertisers search nike --scope watchlist --project <project-id>
|
|
8242
|
+
adkit library advertisers track <advertiser-id> --project <project-id>
|
|
8243
|
+
adkit library advertisers untrack <advertiser-id> --project <project-id>
|
|
7511
8244
|
adkit library advertisers search nike
|
|
7512
8245
|
adkit library advertisers list --platform meta --industry SaaS
|
|
7513
8246
|
adkit library advertisers similar
|
|
@@ -7656,13 +8389,15 @@ var HELP_FULL = {
|
|
|
7656
8389
|
<id> View advertiser details
|
|
7657
8390
|
similar Find similar to your project
|
|
7658
8391
|
similar <id> Find similar to an advertiser
|
|
7659
|
-
add
|
|
8392
|
+
add Find or add an advertiser and save it to My Watchlist
|
|
8393
|
+
track <id> Save an existing advertiser to My Watchlist
|
|
8394
|
+
untrack <id> Remove it from this project only
|
|
7660
8395
|
|
|
7661
8396
|
Filters (list / search):
|
|
7662
8397
|
--industry <string> Filter by industry (e.g. "SaaS", "E-commerce")
|
|
7663
8398
|
--category <string> Filter by category (e.g. "Productivity Tools")
|
|
7664
8399
|
--platform <meta|google|linkedin> Filter by ad platform
|
|
7665
|
-
--sort <name|newest|spend> Sort order (default:
|
|
8400
|
+
--sort <name|newest|spend> Sort order (default: newest)
|
|
7666
8401
|
--limit <n> Results per page (default: 20, max: 100)
|
|
7667
8402
|
--page <n> Page number (default: 1)
|
|
7668
8403
|
|
|
@@ -7680,8 +8415,18 @@ Add flags:
|
|
|
7680
8415
|
--google-library <url> Google Ads Transparency URL
|
|
7681
8416
|
--linkedin-library <url> LinkedIn Ad Library URL
|
|
7682
8417
|
|
|
8418
|
+
Watchlist:
|
|
8419
|
+
--scope <library|watchlist> List/search the whole library (default) or My Watchlist
|
|
8420
|
+
--project <id> Choose the project; otherwise use the active project
|
|
8421
|
+
Track/untrack return advertiserId and tracked. Repeating either is safe.
|
|
8422
|
+
Untrack keeps the advertiser and other projects intact.
|
|
8423
|
+
|
|
7683
8424
|
Examples:
|
|
7684
8425
|
adkit library advertisers list
|
|
8426
|
+
adkit library advertisers list --scope watchlist --project <project-id>
|
|
8427
|
+
adkit library advertisers search nike --scope watchlist --project <project-id>
|
|
8428
|
+
adkit library advertisers track <advertiser-id> --project <project-id>
|
|
8429
|
+
adkit library advertisers untrack <advertiser-id> --project <project-id>
|
|
7685
8430
|
adkit library advertisers search nike
|
|
7686
8431
|
adkit library advertisers search "productivity tools" --platform meta
|
|
7687
8432
|
adkit library advertisers list --industry SaaS --limit 10
|
|
@@ -7724,7 +8469,7 @@ function printList(data, flags, emptyHint = "No results.") {
|
|
|
7724
8469
|
if (output) console.log(output);
|
|
7725
8470
|
else console.log(emptyHint);
|
|
7726
8471
|
}
|
|
7727
|
-
function
|
|
8472
|
+
function isObject2(value) {
|
|
7728
8473
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7729
8474
|
}
|
|
7730
8475
|
function fmtNum(n) {
|
|
@@ -7770,7 +8515,7 @@ function num(obj, key) {
|
|
|
7770
8515
|
}
|
|
7771
8516
|
function nested(obj, key) {
|
|
7772
8517
|
const v = obj[key];
|
|
7773
|
-
return
|
|
8518
|
+
return isObject2(v) ? v : void 0;
|
|
7774
8519
|
}
|
|
7775
8520
|
function printClickLine(label, obj, currency) {
|
|
7776
8521
|
const parts = [];
|
|
@@ -7783,7 +8528,7 @@ function printClickLine(label, obj, currency) {
|
|
|
7783
8528
|
if (parts.length > 0) console.log(` ${label.padEnd(14)}${parts.join(" \xB7 ")}`);
|
|
7784
8529
|
}
|
|
7785
8530
|
function printResultsCard(data) {
|
|
7786
|
-
if (!
|
|
8531
|
+
if (!isObject2(data)) {
|
|
7787
8532
|
console.log(JSON.stringify(data, null, 2));
|
|
7788
8533
|
return;
|
|
7789
8534
|
}
|
|
@@ -7812,7 +8557,7 @@ function printResultsCard(data) {
|
|
|
7812
8557
|
return;
|
|
7813
8558
|
}
|
|
7814
8559
|
for (const row of results) {
|
|
7815
|
-
if (!
|
|
8560
|
+
if (!isObject2(row)) continue;
|
|
7816
8561
|
const entity = nested(row, "entity");
|
|
7817
8562
|
const metrics = nested(row, "metrics");
|
|
7818
8563
|
const breakdown = nested(row, "breakdown");
|
|
@@ -7866,7 +8611,7 @@ ${titleParts.join(" \xB7 ")}`);
|
|
|
7866
8611
|
const actions = nested(row, "conversionEvents") ?? nested(row, "actions");
|
|
7867
8612
|
if (actions) {
|
|
7868
8613
|
for (const [key, val] of Object.entries(actions)) {
|
|
7869
|
-
if (!
|
|
8614
|
+
if (!isObject2(val)) continue;
|
|
7870
8615
|
const parts = [];
|
|
7871
8616
|
const count = num(val, "count");
|
|
7872
8617
|
const cost = num(val, "costPerResult") ?? num(val, "cost");
|
|
@@ -7903,7 +8648,7 @@ function printWarnings(warnings) {
|
|
|
7903
8648
|
if (!Array.isArray(warnings) || warnings.length === 0) return;
|
|
7904
8649
|
console.log("");
|
|
7905
8650
|
for (const w of warnings) {
|
|
7906
|
-
if (!
|
|
8651
|
+
if (!isObject2(w)) continue;
|
|
7907
8652
|
const msg = str(w, "message") ?? JSON.stringify(w);
|
|
7908
8653
|
console.log(`Warning: ${msg}`);
|
|
7909
8654
|
}
|
|
@@ -7922,28 +8667,28 @@ function shortDate(v) {
|
|
|
7922
8667
|
return v.slice(0, 10);
|
|
7923
8668
|
}
|
|
7924
8669
|
function isCampaignDetail(value) {
|
|
7925
|
-
return
|
|
8670
|
+
return isObject2(value);
|
|
7926
8671
|
}
|
|
7927
8672
|
function isAdSetDetail(value) {
|
|
7928
|
-
return
|
|
8673
|
+
return isObject2(value);
|
|
7929
8674
|
}
|
|
7930
8675
|
function isAdDetail(value) {
|
|
7931
|
-
return
|
|
8676
|
+
return isObject2(value);
|
|
7932
8677
|
}
|
|
7933
8678
|
function isCampaignDetailData(value) {
|
|
7934
|
-
if (!
|
|
8679
|
+
if (!isObject2(value)) return false;
|
|
7935
8680
|
if ("campaigns" in value) return Array.isArray(value.campaigns) && value.campaigns.every(isCampaignDetail);
|
|
7936
8681
|
if ("campaign" in value) return isCampaignDetail(value.campaign);
|
|
7937
8682
|
return true;
|
|
7938
8683
|
}
|
|
7939
8684
|
function isAdSetDetailData(value) {
|
|
7940
|
-
if (!
|
|
8685
|
+
if (!isObject2(value)) return false;
|
|
7941
8686
|
if ("adsets" in value) return Array.isArray(value.adsets) && value.adsets.every(isAdSetDetail);
|
|
7942
8687
|
if ("adset" in value) return isAdSetDetail(value.adset);
|
|
7943
8688
|
return true;
|
|
7944
8689
|
}
|
|
7945
8690
|
function isAdDetailData(value) {
|
|
7946
|
-
if (!
|
|
8691
|
+
if (!isObject2(value)) return false;
|
|
7947
8692
|
if ("ads" in value) return Array.isArray(value.ads) && value.ads.every(isAdDetail);
|
|
7948
8693
|
if ("ad" in value) return isAdDetail(value.ad);
|
|
7949
8694
|
return true;
|
|
@@ -7958,6 +8703,7 @@ function printSingleEntity(e, entity) {
|
|
|
7958
8703
|
if (campaign.lifetimeBudget != null) lines.push(` Lifetime: ${formatForTTY(campaign.lifetimeBudget)}`);
|
|
7959
8704
|
if (campaign.bidStrategy) lines.push(` Bid Strategy: ${formatForTTY(campaign.bidStrategy)}`);
|
|
7960
8705
|
if (campaign.spendCap != null) lines.push(` Spend Cap: ${formatForTTY(campaign.spendCap)}`);
|
|
8706
|
+
if (campaign.promotedApp) lines.push(` App: ${JSON.stringify(campaign.promotedApp)}`);
|
|
7961
8707
|
if (campaign.startDate) lines.push(` Start: ${formatForTTY(campaign.startDate)}`);
|
|
7962
8708
|
if (campaign.endDate) lines.push(` End: ${formatForTTY(campaign.endDate)}`);
|
|
7963
8709
|
if (campaign.specialAdCategories) lines.push(` Categories: ${JSON.stringify(campaign.specialAdCategories)}`);
|
|
@@ -7971,6 +8717,7 @@ function printSingleEntity(e, entity) {
|
|
|
7971
8717
|
if (adset.billingEvent) lines.push(` Billing: ${formatForTTY(adset.billingEvent)}`);
|
|
7972
8718
|
if (adset.targeting) lines.push(` Targeting: ${JSON.stringify(adset.targeting)}`);
|
|
7973
8719
|
if (adset.promotedObject) lines.push(` Promoted: ${JSON.stringify(adset.promotedObject)}`);
|
|
8720
|
+
if (adset.promotedApp) lines.push(` App: ${JSON.stringify(adset.promotedApp)}`);
|
|
7974
8721
|
if (adset.startDate) lines.push(` Start: ${formatForTTY(adset.startDate)}`);
|
|
7975
8722
|
if (adset.endDate) lines.push(` End: ${formatForTTY(adset.endDate)}`);
|
|
7976
8723
|
if (createdAt) lines.push(` Created: ${createdAt}`);
|
|
@@ -8020,14 +8767,14 @@ function printAdDetail(data) {
|
|
|
8020
8767
|
printSingleEntity("ad" in data ? data.ad : data, "ad");
|
|
8021
8768
|
}
|
|
8022
8769
|
function printGoogleAdDetail(data) {
|
|
8023
|
-
if (
|
|
8770
|
+
if (isObject2(data) && "ads" in data && Array.isArray(data.ads)) {
|
|
8024
8771
|
for (const item of data.ads) {
|
|
8025
8772
|
if (!isGoogleCliAdRecord(item)) continue;
|
|
8026
8773
|
console.log(toGoogleAdDetailLines(item).join("\n"));
|
|
8027
8774
|
}
|
|
8028
8775
|
return;
|
|
8029
8776
|
}
|
|
8030
|
-
const ad =
|
|
8777
|
+
const ad = isObject2(data) && "ad" in data ? data.ad : data;
|
|
8031
8778
|
if (!isGoogleCliAdRecord(ad)) {
|
|
8032
8779
|
console.log(JSON.stringify(data, null, 2));
|
|
8033
8780
|
return;
|
|
@@ -8224,11 +8971,13 @@ function printAddAdvertiserResult(data) {
|
|
|
8224
8971
|
const adv = dig(data, "advertiser");
|
|
8225
8972
|
const isNew = dig(data, "isNew");
|
|
8226
8973
|
const jobCreated = dig(data, "jobCreated");
|
|
8974
|
+
const tracked = dig(data, "tracked");
|
|
8227
8975
|
const lines = [""];
|
|
8228
8976
|
if (isNew) lines.push(` \u2713 Created advertiser: ${toStr2(dig(adv, "name"))}`);
|
|
8229
8977
|
else lines.push(` Advertiser already exists: ${toStr2(dig(adv, "name"))}`);
|
|
8230
8978
|
if (dig(adv, "website")) lines.push(` Website: ${toStr2(dig(adv, "website"))}`);
|
|
8231
8979
|
if (dig(adv, "_id")) lines.push(` ID: ${toStr2(dig(adv, "_id"))}`);
|
|
8980
|
+
if (tracked === true) lines.push(" Saved to this project\u2019s watchlist.");
|
|
8232
8981
|
if (jobCreated) lines.push("", " Scraping job queued \u2014 advertiser data will be populated shortly.");
|
|
8233
8982
|
else if (!isNew && dig(adv, "hasDescription")) lines.push("", " Advertiser already has complete data.");
|
|
8234
8983
|
const job = dig(data, "job");
|
|
@@ -8412,11 +9161,11 @@ async function selfUpdate(currentVersion) {
|
|
|
8412
9161
|
const installed = execSync("npm list -g @adkit/cli --depth=0 --json", { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
|
|
8413
9162
|
const parsed = JSON.parse(installed);
|
|
8414
9163
|
let newVersion = "unknown";
|
|
8415
|
-
if (
|
|
9164
|
+
if (isObject2(parsed) && "dependencies" in parsed) {
|
|
8416
9165
|
const deps = parsed.dependencies;
|
|
8417
|
-
if (
|
|
9166
|
+
if (isObject2(deps) && "@adkit/cli" in deps) {
|
|
8418
9167
|
const pkg = deps["@adkit/cli"];
|
|
8419
|
-
if (
|
|
9168
|
+
if (isObject2(pkg) && "version" in pkg) {
|
|
8420
9169
|
const v = pkg.version;
|
|
8421
9170
|
if (typeof v === "string") newVersion = v;
|
|
8422
9171
|
}
|
|
@@ -9211,6 +9960,22 @@ async function main() {
|
|
|
9211
9960
|
} else if (platform2 === "reddit") {
|
|
9212
9961
|
switch (entity) {
|
|
9213
9962
|
case "accounts": {
|
|
9963
|
+
if (action && action !== "list" && action !== "available" && action !== "connect" && action !== "disconnect") {
|
|
9964
|
+
const accountId = action;
|
|
9965
|
+
const subcommand = args[4];
|
|
9966
|
+
if (subcommand === "profiles") {
|
|
9967
|
+
data = await listRedditProfiles(client, [accountId], flags);
|
|
9968
|
+
emptyHint = "No Reddit profiles found for this account.";
|
|
9969
|
+
} else if (subcommand === "pixels") {
|
|
9970
|
+
data = await listRedditPixels(client, [accountId], flags);
|
|
9971
|
+
emptyHint = "No Reddit pixels found for this account.";
|
|
9972
|
+
} else if (subcommand === "funding-instruments") {
|
|
9973
|
+
data = await listRedditFundingInstruments(client, [accountId], flags);
|
|
9974
|
+
emptyHint = "No Reddit funding instruments found for this account.";
|
|
9975
|
+
} else if (subcommand === "update") data = await updateRedditAccount(client, [accountId], flags);
|
|
9976
|
+
else throw new CliError("UNKNOWN_COMMAND", `Unknown subcommand: reddit accounts ${accountId} ${subcommand ?? ""}`, "Expected: profiles, pixels, funding-instruments, or update");
|
|
9977
|
+
break;
|
|
9978
|
+
}
|
|
9214
9979
|
switch (action) {
|
|
9215
9980
|
case "list":
|
|
9216
9981
|
case void 0:
|
|
@@ -9232,8 +9997,56 @@ async function main() {
|
|
|
9232
9997
|
}
|
|
9233
9998
|
break;
|
|
9234
9999
|
}
|
|
10000
|
+
case "campaigns": {
|
|
10001
|
+
if (!action) {
|
|
10002
|
+
showHelp("reddit campaigns", flags.help === "full");
|
|
10003
|
+
return;
|
|
10004
|
+
}
|
|
10005
|
+
if (action === "list") {
|
|
10006
|
+
data = await listRedditCampaigns(client, restArgs, flags);
|
|
10007
|
+
emptyHint = "No Reddit campaigns found for this account.";
|
|
10008
|
+
} else if (action === "create") data = await createRedditCampaign(client, restArgs, flags);
|
|
10009
|
+
else if (action === "update") data = await updateRedditCampaign(client, restArgs, flags);
|
|
10010
|
+
else if (REDDIT_ID_PATTERN.test(action)) data = await getRedditCampaign(client, [action], flags);
|
|
10011
|
+
else throw new CliError("UNKNOWN_COMMAND", `Unknown action: reddit campaigns ${action}`, "Available: list, create, update, or a campaign ID");
|
|
10012
|
+
break;
|
|
10013
|
+
}
|
|
10014
|
+
case "ad-groups": {
|
|
10015
|
+
if (!action) {
|
|
10016
|
+
showHelp("reddit ad-groups", flags.help === "full");
|
|
10017
|
+
return;
|
|
10018
|
+
}
|
|
10019
|
+
if (action === "list") {
|
|
10020
|
+
data = await listRedditAdGroups(client, restArgs, flags);
|
|
10021
|
+
emptyHint = "No Reddit ad groups found for this account.";
|
|
10022
|
+
} else if (action === "create") data = await createRedditAdGroup(client, restArgs, flags);
|
|
10023
|
+
else if (action === "update") data = await updateRedditAdGroup(client, restArgs, flags);
|
|
10024
|
+
else if (REDDIT_ID_PATTERN.test(action)) data = await getRedditAdGroup(client, [action], flags);
|
|
10025
|
+
else throw new CliError("UNKNOWN_COMMAND", `Unknown action: reddit ad-groups ${action}`, "Available: list, create, update, or an ad-group ID");
|
|
10026
|
+
break;
|
|
10027
|
+
}
|
|
10028
|
+
case "ads": {
|
|
10029
|
+
if (!action) {
|
|
10030
|
+
showHelp("reddit ads", flags.help === "full");
|
|
10031
|
+
return;
|
|
10032
|
+
}
|
|
10033
|
+
if (action === "list") {
|
|
10034
|
+
data = await listRedditAds(client, restArgs, flags);
|
|
10035
|
+
emptyHint = "No Reddit ads found for this account.";
|
|
10036
|
+
} else if (action === "create") data = await createRedditAd(client, restArgs, flags);
|
|
10037
|
+
else if (action === "update") data = await updateRedditAd(client, restArgs, flags);
|
|
10038
|
+
else if (REDDIT_ID_PATTERN.test(action)) data = await getRedditAd(client, [action], flags);
|
|
10039
|
+
else throw new CliError("UNKNOWN_COMMAND", `Unknown action: reddit ads ${action}`, "Available: list, create, update, or an ad ID");
|
|
10040
|
+
break;
|
|
10041
|
+
}
|
|
10042
|
+
case "results": {
|
|
10043
|
+
if (action && action !== "list") throw new CliError("UNKNOWN_COMMAND", `Unknown action: reddit results ${action}`, "Available: list");
|
|
10044
|
+
data = await listRedditResults(client, restArgs, flags);
|
|
10045
|
+
emptyHint = "No Reddit result rows found for the selected date range.";
|
|
10046
|
+
break;
|
|
10047
|
+
}
|
|
9235
10048
|
default:
|
|
9236
|
-
throw new CliError("UNKNOWN_COMMAND", `Unknown entity: reddit ${entity}`, "Available: accounts, platform-api-requests");
|
|
10049
|
+
throw new CliError("UNKNOWN_COMMAND", `Unknown entity: reddit ${entity}`, "Available: accounts, campaigns, ad-groups, ads, results, platform-api-requests");
|
|
9237
10050
|
}
|
|
9238
10051
|
} else if (platform2 === "linkedin") {
|
|
9239
10052
|
switch (entity) {
|
|
@@ -9397,8 +10210,11 @@ async function main() {
|
|
|
9397
10210
|
} else if (subcommand === "pixels") {
|
|
9398
10211
|
data = await listXPixels(client, [accountId], flags);
|
|
9399
10212
|
emptyHint = "No X conversion pixels found for this account.";
|
|
10213
|
+
} else if (subcommand === "funding-instruments") {
|
|
10214
|
+
data = await listXFundingInstruments(client, [accountId], flags);
|
|
10215
|
+
emptyHint = "No X funding sources found for this account.";
|
|
9400
10216
|
} 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");
|
|
10217
|
+
else throw new CliError("UNKNOWN_COMMAND", `Unknown subcommand: x accounts ${accountId} ${subcommand ?? ""}`, "Expected: profiles, pixels, funding-instruments, or update");
|
|
9402
10218
|
break;
|
|
9403
10219
|
}
|
|
9404
10220
|
switch (action) {
|
|
@@ -9427,21 +10243,27 @@ async function main() {
|
|
|
9427
10243
|
data = await listXCampaigns(client, restArgs, flags);
|
|
9428
10244
|
emptyHint = "No X campaigns matched the selected filters.";
|
|
9429
10245
|
} else if (action === "get") data = await getXCampaign(client, restArgs, flags);
|
|
9430
|
-
else
|
|
10246
|
+
else if (action === "create") data = await createXCampaign(client, restArgs, flags);
|
|
10247
|
+
else if (action === "update") data = await updateXCampaign(client, restArgs, flags);
|
|
10248
|
+
else throw new CliError("UNKNOWN_COMMAND", `Unknown action: x campaigns ${action}`, "Available: list, get, create, update");
|
|
9431
10249
|
break;
|
|
9432
10250
|
case "ad-groups":
|
|
9433
10251
|
if (action === "list") {
|
|
9434
10252
|
data = await listXAdGroups(client, restArgs, flags);
|
|
9435
10253
|
emptyHint = "No X ad groups matched the selected filters.";
|
|
9436
10254
|
} else if (action === "get") data = await getXAdGroup(client, restArgs, flags);
|
|
9437
|
-
else
|
|
10255
|
+
else if (action === "create") data = await createXAdGroup(client, restArgs, flags);
|
|
10256
|
+
else if (action === "update") data = await updateXAdGroup(client, restArgs, flags);
|
|
10257
|
+
else throw new CliError("UNKNOWN_COMMAND", `Unknown action: x ad-groups ${action}`, "Available: list, get, create, update");
|
|
9438
10258
|
break;
|
|
9439
10259
|
case "ads":
|
|
9440
10260
|
if (action === "list") {
|
|
9441
10261
|
data = await listXAds(client, restArgs, flags);
|
|
9442
10262
|
emptyHint = "No X promoted-Post ads matched the selected filters.";
|
|
9443
10263
|
} else if (action === "get") data = await getXAd(client, restArgs, flags);
|
|
9444
|
-
else
|
|
10264
|
+
else if (action === "create") data = await createXAd(client, restArgs, flags);
|
|
10265
|
+
else if (action === "delete") data = await deleteXAd(client, restArgs, flags);
|
|
10266
|
+
else throw new CliError("UNKNOWN_COMMAND", `Unknown action: x ads ${action}`, "Available: list, get, create, delete");
|
|
9445
10267
|
break;
|
|
9446
10268
|
case "results":
|
|
9447
10269
|
if (action && action !== "list") throw new CliError("UNKNOWN_COMMAND", `Unknown action: x results ${action}`, "Available: list");
|
|
@@ -9476,6 +10298,11 @@ async function main() {
|
|
|
9476
10298
|
const data = await addAdvertiser(client, args.slice(3), flags);
|
|
9477
10299
|
if (wantsJson(flags)) printResult(data, flags);
|
|
9478
10300
|
else printAddAdvertiserResult(data);
|
|
10301
|
+
} else if (action === "track" || action === "untrack") {
|
|
10302
|
+
const changeWatchlist = action === "track" ? trackAdvertiser : untrackAdvertiser;
|
|
10303
|
+
const commandArgs = args.slice(3);
|
|
10304
|
+
const data = await changeWatchlist(client, commandArgs, flags);
|
|
10305
|
+
printResult(data, flags);
|
|
9479
10306
|
} else if (action === "similar") {
|
|
9480
10307
|
const advertiserId = args[3];
|
|
9481
10308
|
if (advertiserId) flags.advertiserId = advertiserId;
|