@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.
Files changed (2) hide show
  1. package/dist/a8techads.js +183 -12
  2. 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/campaigns.ts
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, COLUMNS, opts.format);
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 COLUMNS2 = [
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, COLUMNS2, opts.format);
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 COLUMNS3 = [
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, COLUMNS3, opts.format);
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 COLUMNS4 = [
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, COLUMNS4, opts.format);
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 COLUMNS5 = [
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, COLUMNS5, opts.format);
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());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a8techads/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "A8TechAds CLI — programmatic ad platform management",
5
5
  "type": "module",
6
6
  "bin": {