@a8techads/cli 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/a8techads.js +183 -12
- package/package.json +1 -1
package/dist/a8techads.js
CHANGED
|
@@ -2646,6 +2646,9 @@ async function login(opts = {}) {
|
|
|
2646
2646
|
if (opts.tenantId) {
|
|
2647
2647
|
authorizeUrl.searchParams.set("tenant_id", opts.tenantId);
|
|
2648
2648
|
}
|
|
2649
|
+
if (opts.forceLogin) {
|
|
2650
|
+
authorizeUrl.searchParams.set("prompt", "login");
|
|
2651
|
+
}
|
|
2649
2652
|
const code = await waitForAuthorizationCode(authorizeUrl.toString(), state);
|
|
2650
2653
|
console.log("Exchanging authorization code for tokens...");
|
|
2651
2654
|
const tokenResponse = await fetch(tokenEndpoint, {
|
|
@@ -3083,11 +3086,12 @@ Examples:
|
|
|
3083
3086
|
Browser mode (default): Opens browser for OAuth 2.1 Authorization Code + PKCE.
|
|
3084
3087
|
Client credentials mode: Non-interactive auth using --client-id and --client-secret.
|
|
3085
3088
|
|
|
3086
|
-
Requires: network access to auth server.`).option("-p, --profile <name>", "Profile name (browser mode only)", "default").option("--api-url <url>", "API base URL (default: https://api.a8.tech)").option("--auth-url <url>", "Auth server URL (default: https://auth.a8.tech)").option("--client-id <id>", "OAuth client ID (enables client_credentials flow)").option("--client-secret <secret>", "OAuth client secret (requires --client-id)").addHelpText("after", `
|
|
3089
|
+
Requires: network access to auth server.`).option("-p, --profile <name>", "Profile name (browser mode only)", "default").option("--api-url <url>", "API base URL (default: https://api.a8.tech)").option("--auth-url <url>", "Auth server URL (default: https://auth.a8.tech)").option("--client-id <id>", "OAuth client ID (enables client_credentials flow)").option("--client-secret <secret>", "OAuth client secret (requires --client-id)").option("--force-login", "Force login prompt even if browser session exists (use to switch users)").addHelpText("after", `
|
|
3087
3090
|
Examples:
|
|
3088
3091
|
$ a8techads auth login # Interactive browser login
|
|
3089
3092
|
$ a8techads auth login -p staging --api-url https://api.staging.a8.tech
|
|
3090
3093
|
$ a8techads auth login --client-id svc-001 --client-secret s3cret
|
|
3094
|
+
$ a8techads auth login -p owner --force-login # Switch to different user
|
|
3091
3095
|
|
|
3092
3096
|
Note: Client credentials tokens cannot be refreshed. The CLI will prompt
|
|
3093
3097
|
for re-authentication when the token expires.`).action(async (opts) => {
|
|
@@ -3107,7 +3111,8 @@ Note: Client credentials tokens cannot be refreshed. The CLI will prompt
|
|
|
3107
3111
|
await login({
|
|
3108
3112
|
profile: opts.profile,
|
|
3109
3113
|
apiUrl: opts.apiUrl,
|
|
3110
|
-
authUrl: opts.authUrl
|
|
3114
|
+
authUrl: opts.authUrl,
|
|
3115
|
+
forceLogin: opts.forceLogin
|
|
3111
3116
|
});
|
|
3112
3117
|
}
|
|
3113
3118
|
} catch (err) {
|
|
@@ -3514,8 +3519,172 @@ function addFormatOption(cmd) {
|
|
|
3514
3519
|
return cmd.option("-f, --format <format>", "Output format: table, json, csv (default: table)", "table");
|
|
3515
3520
|
}
|
|
3516
3521
|
|
|
3517
|
-
// src/commands/
|
|
3522
|
+
// src/commands/audiences.ts
|
|
3518
3523
|
var COLUMNS = [
|
|
3524
|
+
{ key: "id", header: "ID", width: 36 },
|
|
3525
|
+
{ key: "name", header: "NAME", width: 25 },
|
|
3526
|
+
{ key: "type", header: "TYPE", width: 15 },
|
|
3527
|
+
{ key: "status", header: "STATUS", width: 10 },
|
|
3528
|
+
{ key: "estimatedSize", header: "SIZE", width: 10, format: (v) => v != null ? Number(v).toLocaleString() : "-" }
|
|
3529
|
+
];
|
|
3530
|
+
function createAudiencesCommand() {
|
|
3531
|
+
const cmd = new Command("audiences").description(`Audience management (DSP)
|
|
3532
|
+
|
|
3533
|
+
Phase 1: UPLOADED_LIST only.
|
|
3534
|
+
|
|
3535
|
+
Requires: ADVERTISER capability.`).addHelpText("after", `
|
|
3536
|
+
Examples:
|
|
3537
|
+
$ a8techads audiences list
|
|
3538
|
+
$ a8techads audiences get <id>
|
|
3539
|
+
$ a8techads audiences create --name "High-Value Customers" --type UPLOADED_LIST
|
|
3540
|
+
$ a8techads audiences upload <id> --file users.csv --identifier-type EMAIL_HASH
|
|
3541
|
+
$ a8techads audiences activate <id>
|
|
3542
|
+
$ a8techads audiences pause <id>`);
|
|
3543
|
+
addFormatOption(cmd.command("list").description("List audiences.").option("--type <type>", "Filter by type (UPLOADED_LIST)").option("--status <status>", "Filter by status (DRAFT, READY, ACTIVE, PAUSED, etc.)").option("--limit <n>", "Max results", "20")).action(async (opts) => {
|
|
3544
|
+
const params = new URLSearchParams({ limit: opts.limit });
|
|
3545
|
+
if (opts.type)
|
|
3546
|
+
params.set("type", opts.type);
|
|
3547
|
+
if (opts.status)
|
|
3548
|
+
params.set("status", opts.status);
|
|
3549
|
+
const resp = await apiRequest({ path: `${dspPrefix()}/audiences?${params}` });
|
|
3550
|
+
const json = await resp.json();
|
|
3551
|
+
if (!resp.ok) {
|
|
3552
|
+
console.error(`Error: ${json.error ?? resp.statusText}`);
|
|
3553
|
+
process.exit(1);
|
|
3554
|
+
}
|
|
3555
|
+
const rows = (json.data ?? json).map((a) => ({
|
|
3556
|
+
id: a.id,
|
|
3557
|
+
name: a.name,
|
|
3558
|
+
type: a.type,
|
|
3559
|
+
status: a.status,
|
|
3560
|
+
estimatedSize: a.estimatedSize ?? a.estimated_size
|
|
3561
|
+
}));
|
|
3562
|
+
printData(rows, COLUMNS, opts.format);
|
|
3563
|
+
});
|
|
3564
|
+
addFormatOption(cmd.command("get").description("Get audience details by ID.").argument("<id>", "Audience ID")).action(async (id, opts) => {
|
|
3565
|
+
const resp = await apiRequest({ path: `${dspPrefix()}/audiences/${id}` });
|
|
3566
|
+
const json = await resp.json();
|
|
3567
|
+
if (!resp.ok) {
|
|
3568
|
+
console.error(`Error: ${json.error ?? resp.statusText}`);
|
|
3569
|
+
process.exit(1);
|
|
3570
|
+
}
|
|
3571
|
+
printDetail(json.data ?? json, opts.format);
|
|
3572
|
+
});
|
|
3573
|
+
cmd.command("create").description(`Create a new audience.
|
|
3574
|
+
|
|
3575
|
+
Phase 1 only supports type UPLOADED_LIST.`).option("--name <name>", "Audience name (required)").option("--type <type>", "Audience type (default: UPLOADED_LIST)", "UPLOADED_LIST").option("--description <desc>", "Description").option("--ttl <days>", "Membership TTL in days (default: 90)", "90").option("--from-json <file>", "Create from JSON file").addHelpText("after", `
|
|
3576
|
+
Examples:
|
|
3577
|
+
$ a8techads audiences create --name "High-Value Customers"
|
|
3578
|
+
$ a8techads audiences create --name "Retarget Pool" --ttl 30
|
|
3579
|
+
$ a8techads audiences create --from-json audience.json`).action(async (opts) => {
|
|
3580
|
+
let body;
|
|
3581
|
+
if (opts.fromJson) {
|
|
3582
|
+
const { readFileSync: readFileSync3 } = await import("fs");
|
|
3583
|
+
body = JSON.parse(readFileSync3(opts.fromJson, "utf-8"));
|
|
3584
|
+
} else {
|
|
3585
|
+
if (!opts.name) {
|
|
3586
|
+
console.error('Error: --name is required. Run "a8techads audiences create --help".');
|
|
3587
|
+
process.exit(1);
|
|
3588
|
+
}
|
|
3589
|
+
body = {
|
|
3590
|
+
name: opts.name,
|
|
3591
|
+
type: opts.type,
|
|
3592
|
+
membershipTtlDays: Number(opts.ttl)
|
|
3593
|
+
};
|
|
3594
|
+
if (opts.description)
|
|
3595
|
+
body.description = opts.description;
|
|
3596
|
+
}
|
|
3597
|
+
const resp = await apiRequest({ method: "POST", path: `${dspPrefix()}/audiences`, body });
|
|
3598
|
+
const json = await resp.json();
|
|
3599
|
+
if (!resp.ok) {
|
|
3600
|
+
console.error(`Error: ${json.error ?? json.errors ?? resp.statusText}`);
|
|
3601
|
+
process.exit(1);
|
|
3602
|
+
}
|
|
3603
|
+
console.log(`Audience created: ${json.data?.id ?? json.id}`);
|
|
3604
|
+
});
|
|
3605
|
+
cmd.command("update").description("Update an audience.").argument("<id>", "Audience ID").option("--name <name>", "New name").option("--description <desc>", "New description").option("--ttl <days>", "New membership TTL in days").action(async (id, opts) => {
|
|
3606
|
+
const body = {};
|
|
3607
|
+
if (opts.name)
|
|
3608
|
+
body.name = opts.name;
|
|
3609
|
+
if (opts.description)
|
|
3610
|
+
body.description = opts.description;
|
|
3611
|
+
if (opts.ttl)
|
|
3612
|
+
body.membershipTtlDays = Number(opts.ttl);
|
|
3613
|
+
const resp = await apiRequest({ method: "PATCH", path: `${dspPrefix()}/audiences/${id}`, body });
|
|
3614
|
+
if (!resp.ok) {
|
|
3615
|
+
const j = await resp.json();
|
|
3616
|
+
console.error(`Error: ${j.error ?? resp.statusText}`);
|
|
3617
|
+
process.exit(1);
|
|
3618
|
+
}
|
|
3619
|
+
console.log(`Audience ${id} updated.`);
|
|
3620
|
+
});
|
|
3621
|
+
cmd.command("delete").description("Delete an audience.").argument("<id>", "Audience ID").option("--yes", "Skip confirmation").action(async (id, opts) => {
|
|
3622
|
+
if (!opts.yes) {
|
|
3623
|
+
console.error("Add --yes to confirm deletion.");
|
|
3624
|
+
process.exit(1);
|
|
3625
|
+
}
|
|
3626
|
+
const resp = await apiRequest({ method: "DELETE", path: `${dspPrefix()}/audiences/${id}` });
|
|
3627
|
+
if (!resp.ok && resp.status !== 204) {
|
|
3628
|
+
console.error(`Error: ${resp.statusText}`);
|
|
3629
|
+
process.exit(1);
|
|
3630
|
+
}
|
|
3631
|
+
console.log(`Audience ${id} deleted.`);
|
|
3632
|
+
});
|
|
3633
|
+
cmd.command("activate").description("Activate an audience (must be READY or PAUSED).").argument("<id>", "Audience ID").action(async (id) => {
|
|
3634
|
+
const resp = await apiRequest({ method: "PATCH", path: `${dspPrefix()}/audiences/${id}/activate` });
|
|
3635
|
+
if (!resp.ok) {
|
|
3636
|
+
const j = await resp.json();
|
|
3637
|
+
console.error(`Error: ${j.error ?? resp.statusText}`);
|
|
3638
|
+
process.exit(1);
|
|
3639
|
+
}
|
|
3640
|
+
console.log(`Audience ${id} activated.`);
|
|
3641
|
+
});
|
|
3642
|
+
cmd.command("pause").description("Pause an active audience.").argument("<id>", "Audience ID").action(async (id) => {
|
|
3643
|
+
const resp = await apiRequest({ method: "PATCH", path: `${dspPrefix()}/audiences/${id}/pause` });
|
|
3644
|
+
if (!resp.ok) {
|
|
3645
|
+
const j = await resp.json();
|
|
3646
|
+
console.error(`Error: ${j.error ?? resp.statusText}`);
|
|
3647
|
+
process.exit(1);
|
|
3648
|
+
}
|
|
3649
|
+
console.log(`Audience ${id} paused.`);
|
|
3650
|
+
});
|
|
3651
|
+
cmd.command("archive").description("Archive an audience.").argument("<id>", "Audience ID").action(async (id) => {
|
|
3652
|
+
const resp = await apiRequest({ method: "PATCH", path: `${dspPrefix()}/audiences/${id}/archive` });
|
|
3653
|
+
if (!resp.ok) {
|
|
3654
|
+
const j = await resp.json();
|
|
3655
|
+
console.error(`Error: ${j.error ?? resp.statusText}`);
|
|
3656
|
+
process.exit(1);
|
|
3657
|
+
}
|
|
3658
|
+
console.log(`Audience ${id} archived.`);
|
|
3659
|
+
});
|
|
3660
|
+
cmd.command("upload").description(`Upload a user list to populate audience membership.
|
|
3661
|
+
|
|
3662
|
+
Accepts CSV or JSON files with user identifiers.`).argument("<id>", "Audience ID").option("--file <path>", "File path (CSV or JSON)").option("--identifier-type <type>", "Identifier type: EMAIL_HASH or DEVICE_ID", "EMAIL_HASH").option("--estimated-size <n>", "Estimated number of users in file").addHelpText("after", `
|
|
3663
|
+
Examples:
|
|
3664
|
+
$ a8techads audiences upload <id> --file users.csv --identifier-type EMAIL_HASH
|
|
3665
|
+
$ a8techads audiences upload <id> --file devices.json --identifier-type DEVICE_ID`).action(async (id, opts) => {
|
|
3666
|
+
if (!opts.file) {
|
|
3667
|
+
console.error("Error: --file is required.");
|
|
3668
|
+
process.exit(1);
|
|
3669
|
+
}
|
|
3670
|
+
const body = {
|
|
3671
|
+
identifierType: opts.identifierType
|
|
3672
|
+
};
|
|
3673
|
+
if (opts.estimatedSize)
|
|
3674
|
+
body.estimatedSize = Number(opts.estimatedSize);
|
|
3675
|
+
const resp = await apiRequest({ method: "POST", path: `${dspPrefix()}/audiences/${id}/upload`, body });
|
|
3676
|
+
const json = await resp.json();
|
|
3677
|
+
if (!resp.ok) {
|
|
3678
|
+
console.error(`Error: ${json.error ?? resp.statusText}`);
|
|
3679
|
+
process.exit(1);
|
|
3680
|
+
}
|
|
3681
|
+
console.log(`Upload processed. Audience status: ${json.data?.status ?? json.status}`);
|
|
3682
|
+
});
|
|
3683
|
+
return cmd;
|
|
3684
|
+
}
|
|
3685
|
+
|
|
3686
|
+
// src/commands/campaigns.ts
|
|
3687
|
+
var COLUMNS2 = [
|
|
3519
3688
|
{ key: "id", header: "ID", width: 36 },
|
|
3520
3689
|
{ key: "name", header: "NAME", width: 30 },
|
|
3521
3690
|
{ key: "status", header: "STATUS", width: 12 },
|
|
@@ -3549,7 +3718,7 @@ Examples:
|
|
|
3549
3718
|
budget: c.budget ?? c.dailyBudget,
|
|
3550
3719
|
spent: c.stats?.spend ?? c.spent
|
|
3551
3720
|
}));
|
|
3552
|
-
printData(rows,
|
|
3721
|
+
printData(rows, COLUMNS2, opts.format);
|
|
3553
3722
|
});
|
|
3554
3723
|
addFormatOption(cmd.command("get").description("Get campaign details by ID.").argument("<id>", "Campaign ID")).action(async (id, opts) => {
|
|
3555
3724
|
const resp = await apiRequest({ path: `${dspPrefix()}/campaigns/${id}` });
|
|
@@ -3661,7 +3830,7 @@ Examples:
|
|
|
3661
3830
|
}
|
|
3662
3831
|
|
|
3663
3832
|
// src/commands/variations.ts
|
|
3664
|
-
var
|
|
3833
|
+
var COLUMNS3 = [
|
|
3665
3834
|
{ key: "id", header: "ID", width: 36 },
|
|
3666
3835
|
{ key: "name", header: "NAME", width: 25 },
|
|
3667
3836
|
{ key: "type", header: "TYPE", width: 10 },
|
|
@@ -3692,7 +3861,7 @@ Examples:
|
|
|
3692
3861
|
status: v.status,
|
|
3693
3862
|
campaignId: v.campaignId ?? v.campaign_id
|
|
3694
3863
|
}));
|
|
3695
|
-
printData(rows,
|
|
3864
|
+
printData(rows, COLUMNS3, opts.format);
|
|
3696
3865
|
});
|
|
3697
3866
|
addFormatOption(cmd.command("get").description("Get variation details.").argument("<id>", "Variation ID")).action(async (id, opts) => {
|
|
3698
3867
|
const resp = await apiRequest({ path: `${dspPrefix()}/variations/${id}` });
|
|
@@ -3757,7 +3926,7 @@ Examples:
|
|
|
3757
3926
|
}
|
|
3758
3927
|
|
|
3759
3928
|
// src/commands/sites.ts
|
|
3760
|
-
var
|
|
3929
|
+
var COLUMNS4 = [
|
|
3761
3930
|
{ key: "id", header: "ID", width: 36 },
|
|
3762
3931
|
{ key: "name", header: "NAME", width: 25 },
|
|
3763
3932
|
{ key: "domain", header: "DOMAIN", width: 25 },
|
|
@@ -3790,7 +3959,7 @@ Examples:
|
|
|
3790
3959
|
status: s.status,
|
|
3791
3960
|
zoneCount: s.zoneCount ?? s.zone_count ?? "-"
|
|
3792
3961
|
}));
|
|
3793
|
-
printData(rows,
|
|
3962
|
+
printData(rows, COLUMNS4, opts.format);
|
|
3794
3963
|
});
|
|
3795
3964
|
addFormatOption(cmd.command("get").description("Get site details by ID.").argument("<id>", "Site ID")).action(async (id, opts) => {
|
|
3796
3965
|
const resp = await apiRequest({ path: `${sspPrefix()}/sites/${id}` });
|
|
@@ -3876,7 +4045,7 @@ Examples:
|
|
|
3876
4045
|
}
|
|
3877
4046
|
|
|
3878
4047
|
// src/commands/zones.ts
|
|
3879
|
-
var
|
|
4048
|
+
var COLUMNS5 = [
|
|
3880
4049
|
{ key: "id", header: "ID", width: 36 },
|
|
3881
4050
|
{ key: "name", header: "NAME", width: 25 },
|
|
3882
4051
|
{ key: "format", header: "FORMAT", width: 18 },
|
|
@@ -3908,7 +4077,7 @@ Examples:
|
|
|
3908
4077
|
format: z.adFormat ?? z.ad_format ?? "-",
|
|
3909
4078
|
status: z.status
|
|
3910
4079
|
}));
|
|
3911
|
-
printData(rows,
|
|
4080
|
+
printData(rows, COLUMNS5, opts.format);
|
|
3912
4081
|
});
|
|
3913
4082
|
addFormatOption(cmd.command("get").description("Get zone details by ID.").argument("<id>", "Zone ID")).action(async (id, opts) => {
|
|
3914
4083
|
const resp = await apiRequest({ path: `${sspPrefix()}/zones/${id}` });
|
|
@@ -4209,7 +4378,7 @@ function usersPrefix() {
|
|
|
4209
4378
|
}
|
|
4210
4379
|
return app === "ssp" ? "/api/v1/ssp" : "/api/v1/dsp";
|
|
4211
4380
|
}
|
|
4212
|
-
var
|
|
4381
|
+
var COLUMNS6 = [
|
|
4213
4382
|
{ key: "id", header: "ID", width: 36 },
|
|
4214
4383
|
{ key: "email", header: "EMAIL", width: 30 },
|
|
4215
4384
|
{ key: "name", header: "NAME", width: 20 },
|
|
@@ -4240,7 +4409,7 @@ Examples:
|
|
|
4240
4409
|
role: u.role,
|
|
4241
4410
|
status: u.status
|
|
4242
4411
|
}));
|
|
4243
|
-
printData(rows,
|
|
4412
|
+
printData(rows, COLUMNS6, opts.format);
|
|
4244
4413
|
});
|
|
4245
4414
|
addFormatOption(cmd.command("get").description("Get team member details.").argument("<id>", "User ID")).action(async (id, opts) => {
|
|
4246
4415
|
const resp = await apiRequest({ path: `${usersPrefix()}/users/${id}` });
|
|
@@ -4455,6 +4624,7 @@ Command Groups:
|
|
|
4455
4624
|
auth Authentication (login, logout, token, status)
|
|
4456
4625
|
profile Multi-profile management
|
|
4457
4626
|
context Workspace context (tenant, capability, app)
|
|
4627
|
+
audiences Audience management (DSP)
|
|
4458
4628
|
campaigns Campaign management (DSP)
|
|
4459
4629
|
variations Ad variation management (DSP)
|
|
4460
4630
|
sites Site management (SSP)
|
|
@@ -4474,6 +4644,7 @@ Getting Started:
|
|
|
4474
4644
|
program2.addCommand(createAuthCommand());
|
|
4475
4645
|
program2.addCommand(createProfileCommand());
|
|
4476
4646
|
program2.addCommand(createContextCommand());
|
|
4647
|
+
program2.addCommand(createAudiencesCommand());
|
|
4477
4648
|
program2.addCommand(createCampaignsCommand());
|
|
4478
4649
|
program2.addCommand(createVariationsCommand());
|
|
4479
4650
|
program2.addCommand(createSitesCommand());
|