@a8techads/cli 0.3.0 → 0.4.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 +277 -49
- package/package.json +1 -1
package/dist/a8techads.js
CHANGED
|
@@ -3200,6 +3200,52 @@ function createProfileCommand() {
|
|
|
3200
3200
|
return profile;
|
|
3201
3201
|
}
|
|
3202
3202
|
|
|
3203
|
+
// src/utils/http.ts
|
|
3204
|
+
async function apiRequest(opts) {
|
|
3205
|
+
await refreshTokenIfNeeded();
|
|
3206
|
+
const creds = loadCredentials();
|
|
3207
|
+
const profile = getCurrentProfile(creds);
|
|
3208
|
+
if (!profile) {
|
|
3209
|
+
throw new Error('No active profile. Run "a8techads auth login" to authenticate.');
|
|
3210
|
+
}
|
|
3211
|
+
const ctx = loadContext();
|
|
3212
|
+
const context = getCurrentContext(ctx);
|
|
3213
|
+
validateTenantMatch(profile.access_token, context?.tenant_id ?? null);
|
|
3214
|
+
const headers = {
|
|
3215
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
3216
|
+
"Content-Type": "application/json",
|
|
3217
|
+
...opts.headers
|
|
3218
|
+
};
|
|
3219
|
+
if (context?.current_capability) {
|
|
3220
|
+
headers["X-Effective-Capability"] = context.current_capability;
|
|
3221
|
+
}
|
|
3222
|
+
if (context?.impersonation) {
|
|
3223
|
+
headers["X-Impersonate-Tenant"] = context.impersonation.target_tenant_id;
|
|
3224
|
+
if (context.impersonation.effective_capability) {
|
|
3225
|
+
headers["X-Impersonate-Capability"] = context.impersonation.effective_capability;
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
const url = `${profile.api_url}${opts.path}`;
|
|
3229
|
+
return fetch(url, {
|
|
3230
|
+
method: opts.method ?? "GET",
|
|
3231
|
+
headers,
|
|
3232
|
+
body: opts.body ? JSON.stringify(opts.body) : undefined
|
|
3233
|
+
});
|
|
3234
|
+
}
|
|
3235
|
+
function validateTenantMatch(accessToken, contextTenantId) {
|
|
3236
|
+
const claims = decodeJwt(accessToken);
|
|
3237
|
+
if (!claims)
|
|
3238
|
+
return;
|
|
3239
|
+
const authClaims = getAuthClaims(claims);
|
|
3240
|
+
const tokenTenantId = authClaims?.tenant_id ?? null;
|
|
3241
|
+
if (!contextTenantId || !tokenTenantId)
|
|
3242
|
+
return;
|
|
3243
|
+
if (contextTenantId !== tokenTenantId) {
|
|
3244
|
+
throw new Error(`Token tenant mismatch. Context tenant: ${contextTenantId}, token tenant: ${tokenTenantId}.
|
|
3245
|
+
` + `Run 'a8techads context set-tenant ${contextTenantId}' to re-authenticate.`);
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3203
3249
|
// src/commands/context.ts
|
|
3204
3250
|
function createContextCommand() {
|
|
3205
3251
|
const context = new Command("context").description("Workspace context — tenant, capability, and app selection").addHelpText("after", `
|
|
@@ -3242,6 +3288,16 @@ Examples:
|
|
|
3242
3288
|
}
|
|
3243
3289
|
console.log(`App: ${profileCtx?.app ?? "none"}`);
|
|
3244
3290
|
console.log(`Capability: ${profileCtx?.current_capability ?? "null (platform)"}`);
|
|
3291
|
+
if (profileCtx?.impersonation) {
|
|
3292
|
+
const imp = profileCtx.impersonation;
|
|
3293
|
+
console.log(`
|
|
3294
|
+
--- Managed Access Session ---`);
|
|
3295
|
+
console.log(`Target: ${imp.target_tenant_name} (${imp.target_tenant_id})`);
|
|
3296
|
+
console.log(`Capability: ${imp.effective_capability}`);
|
|
3297
|
+
console.log(`Role: ${imp.effective_role}`);
|
|
3298
|
+
console.log(`Grant: ${imp.managed_grant_id ?? "none"}`);
|
|
3299
|
+
console.log(`Started: ${imp.started_at}`);
|
|
3300
|
+
}
|
|
3245
3301
|
});
|
|
3246
3302
|
context.command("set-tenant").description(`Switch to a different tenant. Triggers re-authentication if the
|
|
3247
3303
|
tenant differs from the current token tenant.
|
|
@@ -3376,6 +3432,74 @@ Shortcuts:
|
|
|
3376
3432
|
console.log("Capability: PUBLISHER");
|
|
3377
3433
|
console.log("App: ssp");
|
|
3378
3434
|
});
|
|
3435
|
+
context.command("assume-role").description(`Start a managed access session as a platform operator.
|
|
3436
|
+
|
|
3437
|
+
Requires: platform_owner or platform_admin role.`).option("--tenant <id>", "Target tenant ID (required)").option("--capability <cap>", "Effective capability: ADVERTISER or PUBLISHER").addHelpText("after", `
|
|
3438
|
+
Examples:
|
|
3439
|
+
$ a8techads context assume-role --tenant 00000000-... --capability ADVERTISER
|
|
3440
|
+
$ a8techads context assume-role --tenant <id>`).action(async (opts) => {
|
|
3441
|
+
if (!opts.tenant) {
|
|
3442
|
+
console.error("Error: --tenant is required.");
|
|
3443
|
+
console.error('Run "a8techads context assume-role --help" for usage.');
|
|
3444
|
+
process.exit(1);
|
|
3445
|
+
}
|
|
3446
|
+
try {
|
|
3447
|
+
const body = { target_tenant_id: opts.tenant };
|
|
3448
|
+
if (opts.capability)
|
|
3449
|
+
body.target_capability = opts.capability;
|
|
3450
|
+
const resp = await apiRequest({
|
|
3451
|
+
method: "POST",
|
|
3452
|
+
path: "/api/v1/console/impersonation/start",
|
|
3453
|
+
body
|
|
3454
|
+
});
|
|
3455
|
+
if (!resp.ok) {
|
|
3456
|
+
const err = await resp.json().catch(() => ({ error: resp.statusText }));
|
|
3457
|
+
console.error(`Error: ${err.error || resp.statusText}`);
|
|
3458
|
+
process.exit(1);
|
|
3459
|
+
}
|
|
3460
|
+
const data = await resp.json();
|
|
3461
|
+
const targetTenant = data.target_tenant || {};
|
|
3462
|
+
const ctx = loadContext();
|
|
3463
|
+
const impersonation = {
|
|
3464
|
+
target_tenant_id: opts.tenant,
|
|
3465
|
+
target_tenant_name: targetTenant.company_name || "Unknown",
|
|
3466
|
+
effective_capability: data.effective_capability || opts.capability || "",
|
|
3467
|
+
effective_role: data.effective_role || "",
|
|
3468
|
+
managed_grant_id: data.managed_grant_id || null,
|
|
3469
|
+
started_at: new Date().toISOString()
|
|
3470
|
+
};
|
|
3471
|
+
setCurrentContext(ctx, { impersonation });
|
|
3472
|
+
saveContext(ctx);
|
|
3473
|
+
console.log("Managed access session started.");
|
|
3474
|
+
console.log(`Target: ${impersonation.target_tenant_name} (${opts.tenant})`);
|
|
3475
|
+
console.log(`Capability: ${impersonation.effective_capability}`);
|
|
3476
|
+
console.log(`Grant: ${impersonation.managed_grant_id || "auto-created"}`);
|
|
3477
|
+
} catch (err) {
|
|
3478
|
+
console.error(`Failed: ${err.message}`);
|
|
3479
|
+
process.exit(1);
|
|
3480
|
+
}
|
|
3481
|
+
});
|
|
3482
|
+
context.command("end-session").description(`End the current managed access session.
|
|
3483
|
+
|
|
3484
|
+
Requires: an active managed access session.`).addHelpText("after", `
|
|
3485
|
+
Examples:
|
|
3486
|
+
$ a8techads context end-session`).action(async () => {
|
|
3487
|
+
const ctx = loadContext();
|
|
3488
|
+
const profileCtx = getCurrentContext(ctx);
|
|
3489
|
+
if (!profileCtx?.impersonation) {
|
|
3490
|
+
console.log("No active managed access session.");
|
|
3491
|
+
return;
|
|
3492
|
+
}
|
|
3493
|
+
try {
|
|
3494
|
+
await apiRequest({
|
|
3495
|
+
method: "POST",
|
|
3496
|
+
path: "/api/v1/console/impersonation/end"
|
|
3497
|
+
});
|
|
3498
|
+
} catch {}
|
|
3499
|
+
setCurrentContext(ctx, { impersonation: null });
|
|
3500
|
+
saveContext(ctx);
|
|
3501
|
+
console.log("Managed access session ended.");
|
|
3502
|
+
});
|
|
3379
3503
|
return context;
|
|
3380
3504
|
}
|
|
3381
3505
|
function deriveAppFromTenant(tenant) {
|
|
@@ -3392,46 +3516,6 @@ function deriveAuthUrl(tokenEndpoint) {
|
|
|
3392
3516
|
return url.origin;
|
|
3393
3517
|
}
|
|
3394
3518
|
|
|
3395
|
-
// src/utils/http.ts
|
|
3396
|
-
async function apiRequest(opts) {
|
|
3397
|
-
await refreshTokenIfNeeded();
|
|
3398
|
-
const creds = loadCredentials();
|
|
3399
|
-
const profile = getCurrentProfile(creds);
|
|
3400
|
-
if (!profile) {
|
|
3401
|
-
throw new Error('No active profile. Run "a8techads auth login" to authenticate.');
|
|
3402
|
-
}
|
|
3403
|
-
const ctx = loadContext();
|
|
3404
|
-
const context = getCurrentContext(ctx);
|
|
3405
|
-
validateTenantMatch(profile.access_token, context?.tenant_id ?? null);
|
|
3406
|
-
const headers = {
|
|
3407
|
-
Authorization: `Bearer ${profile.access_token}`,
|
|
3408
|
-
"Content-Type": "application/json",
|
|
3409
|
-
...opts.headers
|
|
3410
|
-
};
|
|
3411
|
-
if (context?.current_capability) {
|
|
3412
|
-
headers["X-Effective-Capability"] = context.current_capability;
|
|
3413
|
-
}
|
|
3414
|
-
const url = `${profile.api_url}${opts.path}`;
|
|
3415
|
-
return fetch(url, {
|
|
3416
|
-
method: opts.method ?? "GET",
|
|
3417
|
-
headers,
|
|
3418
|
-
body: opts.body ? JSON.stringify(opts.body) : undefined
|
|
3419
|
-
});
|
|
3420
|
-
}
|
|
3421
|
-
function validateTenantMatch(accessToken, contextTenantId) {
|
|
3422
|
-
const claims = decodeJwt(accessToken);
|
|
3423
|
-
if (!claims)
|
|
3424
|
-
return;
|
|
3425
|
-
const authClaims = getAuthClaims(claims);
|
|
3426
|
-
const tokenTenantId = authClaims?.tenant_id ?? null;
|
|
3427
|
-
if (!contextTenantId || !tokenTenantId)
|
|
3428
|
-
return;
|
|
3429
|
-
if (contextTenantId !== tokenTenantId) {
|
|
3430
|
-
throw new Error(`Token tenant mismatch. Context tenant: ${contextTenantId}, token tenant: ${tokenTenantId}.
|
|
3431
|
-
` + `Run 'a8techads context set-tenant ${contextTenantId}' to re-authenticate.`);
|
|
3432
|
-
}
|
|
3433
|
-
}
|
|
3434
|
-
|
|
3435
3519
|
// src/utils/api-prefix.ts
|
|
3436
3520
|
function getApiPrefix() {
|
|
3437
3521
|
const ctx = loadContext();
|
|
@@ -3572,7 +3656,7 @@ Examples:
|
|
|
3572
3656
|
});
|
|
3573
3657
|
cmd.command("create").description(`Create a new audience.
|
|
3574
3658
|
|
|
3575
|
-
Phase 1 only supports type UPLOADED_LIST.`).option("--name <name>", "Audience name (required)").option("--type <type>", "Audience type
|
|
3659
|
+
Phase 1 only supports type UPLOADED_LIST.`).option("--name <name>", "Audience name (required)").option("--type <type>", "Audience type: UPLOADED_LIST or RETARGETING", "UPLOADED_LIST").option("--description <desc>", "Description").option("--ttl <days>", "Membership TTL in days (default: 90)", "90").option("--goal-id <id>", "Conversion goal ID (required for RETARGETING type)").option("--from-json <file>", "Create from JSON file").addHelpText("after", `
|
|
3576
3660
|
Examples:
|
|
3577
3661
|
$ a8techads audiences create --name "High-Value Customers"
|
|
3578
3662
|
$ a8techads audiences create --name "Retarget Pool" --ttl 30
|
|
@@ -3586,6 +3670,10 @@ Examples:
|
|
|
3586
3670
|
console.error('Error: --name is required. Run "a8techads audiences create --help".');
|
|
3587
3671
|
process.exit(1);
|
|
3588
3672
|
}
|
|
3673
|
+
if (opts.type === "RETARGETING" && !opts.goalId) {
|
|
3674
|
+
console.error("Error: --goal-id is required for RETARGETING type.");
|
|
3675
|
+
process.exit(1);
|
|
3676
|
+
}
|
|
3589
3677
|
body = {
|
|
3590
3678
|
name: opts.name,
|
|
3591
3679
|
type: opts.type,
|
|
@@ -3593,6 +3681,9 @@ Examples:
|
|
|
3593
3681
|
};
|
|
3594
3682
|
if (opts.description)
|
|
3595
3683
|
body.description = opts.description;
|
|
3684
|
+
if (opts.goalId) {
|
|
3685
|
+
body.rules = { source: "conversion_goal", goal_id: opts.goalId, action: "positive", recency_days: 30 };
|
|
3686
|
+
}
|
|
3596
3687
|
}
|
|
3597
3688
|
const resp = await apiRequest({ method: "POST", path: `${dspPrefix()}/audiences`, body });
|
|
3598
3689
|
const json = await resp.json();
|
|
@@ -3657,28 +3748,53 @@ Examples:
|
|
|
3657
3748
|
}
|
|
3658
3749
|
console.log(`Audience ${id} archived.`);
|
|
3659
3750
|
});
|
|
3660
|
-
cmd.command("upload").description(`Upload
|
|
3751
|
+
cmd.command("upload").description(`Upload user identifiers to populate audience membership.
|
|
3661
3752
|
|
|
3662
|
-
|
|
3753
|
+
Reads a file with one identifier per line (email hash, device ID, etc.)
|
|
3754
|
+
and uploads to the audience membership store.`).argument("<id>", "Audience ID").option("--file <path>", "File path (one identifier per line, or JSON array)").option("--identifier-type <type>", "Identifier type: EMAIL_HASH or DEVICE_ID", "EMAIL_HASH").addHelpText("after", `
|
|
3663
3755
|
Examples:
|
|
3664
|
-
$ a8techads audiences upload <id> --file users.
|
|
3665
|
-
$ a8techads audiences upload <id> --file devices.
|
|
3756
|
+
$ a8techads audiences upload <id> --file users.txt --identifier-type EMAIL_HASH
|
|
3757
|
+
$ a8techads audiences upload <id> --file devices.txt --identifier-type DEVICE_ID
|
|
3758
|
+
|
|
3759
|
+
File format (one per line):
|
|
3760
|
+
a1b2c3d4e5f6...
|
|
3761
|
+
f6e5d4c3b2a1...`).action(async (id, opts) => {
|
|
3666
3762
|
if (!opts.file) {
|
|
3667
3763
|
console.error("Error: --file is required.");
|
|
3668
3764
|
process.exit(1);
|
|
3669
3765
|
}
|
|
3766
|
+
const { readFileSync: readFileSync3 } = await import("fs");
|
|
3767
|
+
let identifiers;
|
|
3768
|
+
try {
|
|
3769
|
+
const content = readFileSync3(opts.file, "utf-8").trim();
|
|
3770
|
+
try {
|
|
3771
|
+
identifiers = JSON.parse(content);
|
|
3772
|
+
if (!Array.isArray(identifiers))
|
|
3773
|
+
throw new Error("not array");
|
|
3774
|
+
} catch {
|
|
3775
|
+
identifiers = content.split(`
|
|
3776
|
+
`).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
3777
|
+
}
|
|
3778
|
+
} catch (err) {
|
|
3779
|
+
console.error(`Error reading file: ${err.message}`);
|
|
3780
|
+
process.exit(1);
|
|
3781
|
+
}
|
|
3782
|
+
if (identifiers.length === 0) {
|
|
3783
|
+
console.error("Error: file contains no identifiers.");
|
|
3784
|
+
process.exit(1);
|
|
3785
|
+
}
|
|
3786
|
+
console.log(`Uploading ${identifiers.length} identifiers...`);
|
|
3670
3787
|
const body = {
|
|
3788
|
+
identifiers,
|
|
3671
3789
|
identifierType: opts.identifierType
|
|
3672
3790
|
};
|
|
3673
|
-
if (opts.estimatedSize)
|
|
3674
|
-
body.estimatedSize = Number(opts.estimatedSize);
|
|
3675
3791
|
const resp = await apiRequest({ method: "POST", path: `${dspPrefix()}/audiences/${id}/upload`, body });
|
|
3676
3792
|
const json = await resp.json();
|
|
3677
3793
|
if (!resp.ok) {
|
|
3678
3794
|
console.error(`Error: ${json.error ?? resp.statusText}`);
|
|
3679
3795
|
process.exit(1);
|
|
3680
3796
|
}
|
|
3681
|
-
console.log(`Upload
|
|
3797
|
+
console.log(`Upload complete. Status: ${json.data?.status ?? json.status}, Members: ${json.data?.uploadedCount ?? json.uploadedCount ?? "?"}`);
|
|
3682
3798
|
});
|
|
3683
3799
|
return cmd;
|
|
3684
3800
|
}
|
|
@@ -4488,6 +4604,117 @@ Examples:
|
|
|
4488
4604
|
return cmd;
|
|
4489
4605
|
}
|
|
4490
4606
|
|
|
4607
|
+
// src/commands/admin.ts
|
|
4608
|
+
function createAdminCommand() {
|
|
4609
|
+
const cmd = new Command("admin").description(`Platform administration (Console)
|
|
4610
|
+
|
|
4611
|
+
Requires: platform_owner or platform_admin role.`).addHelpText("after", `
|
|
4612
|
+
Examples:
|
|
4613
|
+
$ a8techads admin tenants list
|
|
4614
|
+
$ a8techads admin tenants get <id>
|
|
4615
|
+
$ a8techads admin audit-logs
|
|
4616
|
+
$ a8techads admin system health`);
|
|
4617
|
+
const tenants = cmd.command("tenants").description("Tenant management");
|
|
4618
|
+
addFormatOption(tenants.command("list").description("List all tenants.")).action(async (opts) => {
|
|
4619
|
+
const resp = await apiRequest({ path: "/api/v1/console/tenants" });
|
|
4620
|
+
const json = await resp.json();
|
|
4621
|
+
if (!resp.ok) {
|
|
4622
|
+
console.error(`Error: ${json.error ?? resp.statusText}`);
|
|
4623
|
+
process.exit(1);
|
|
4624
|
+
}
|
|
4625
|
+
const rows = (json.data ?? json).map((t) => ({
|
|
4626
|
+
id: t.id,
|
|
4627
|
+
name: t.companyName ?? t.company_name,
|
|
4628
|
+
type: t.tenantType ?? t.tenant_type,
|
|
4629
|
+
status: t.status
|
|
4630
|
+
}));
|
|
4631
|
+
const columns = [
|
|
4632
|
+
{ key: "id", header: "ID", width: 36 },
|
|
4633
|
+
{ key: "name", header: "NAME", width: 25 },
|
|
4634
|
+
{ key: "type", header: "TYPE", width: 12 },
|
|
4635
|
+
{ key: "status", header: "STATUS", width: 10 }
|
|
4636
|
+
];
|
|
4637
|
+
printData(rows, columns, opts.format);
|
|
4638
|
+
});
|
|
4639
|
+
addFormatOption(tenants.command("get").description("Get tenant details.").argument("<id>", "Tenant ID")).action(async (id, opts) => {
|
|
4640
|
+
const resp = await apiRequest({ path: `/api/v1/console/tenants/${id}` });
|
|
4641
|
+
const json = await resp.json();
|
|
4642
|
+
if (!resp.ok) {
|
|
4643
|
+
console.error(`Error: ${json.error ?? resp.statusText}`);
|
|
4644
|
+
process.exit(1);
|
|
4645
|
+
}
|
|
4646
|
+
printDetail(json.data ?? json, opts.format);
|
|
4647
|
+
});
|
|
4648
|
+
const members = tenants.command("members").description("Tenant member management");
|
|
4649
|
+
addFormatOption(members.command("list").description("List tenant members.").option("--tenant <id>", "Tenant ID (required)")).action(async (opts) => {
|
|
4650
|
+
if (!opts.tenant) {
|
|
4651
|
+
console.error("Error: --tenant is required.");
|
|
4652
|
+
process.exit(1);
|
|
4653
|
+
}
|
|
4654
|
+
const resp = await apiRequest({ path: `/api/v1/console/tenants/${opts.tenant}/members` });
|
|
4655
|
+
const json = await resp.json();
|
|
4656
|
+
if (!resp.ok) {
|
|
4657
|
+
console.error(`Error: ${json.error ?? resp.statusText}`);
|
|
4658
|
+
process.exit(1);
|
|
4659
|
+
}
|
|
4660
|
+
const rows = (json.data ?? json).map((m) => ({
|
|
4661
|
+
id: m.userId ?? m.user_id ?? m.id,
|
|
4662
|
+
email: m.email,
|
|
4663
|
+
role: m.role,
|
|
4664
|
+
status: m.status
|
|
4665
|
+
}));
|
|
4666
|
+
printData(rows, [
|
|
4667
|
+
{ key: "id", header: "USER ID", width: 36 },
|
|
4668
|
+
{ key: "email", header: "EMAIL", width: 30 },
|
|
4669
|
+
{ key: "role", header: "ROLE", width: 20 }
|
|
4670
|
+
], opts.format);
|
|
4671
|
+
});
|
|
4672
|
+
addFormatOption(cmd.command("audit-logs").description("View audit logs.").option("--tenant <id>", "Filter by tenant").option("--limit <n>", "Max results", "20")).action(async (opts) => {
|
|
4673
|
+
const params = new URLSearchParams({ limit: opts.limit });
|
|
4674
|
+
if (opts.tenant)
|
|
4675
|
+
params.set("tenant_id", opts.tenant);
|
|
4676
|
+
const resp = await apiRequest({ path: `/api/v1/console/audit-logs?${params}` });
|
|
4677
|
+
const json = await resp.json();
|
|
4678
|
+
if (!resp.ok) {
|
|
4679
|
+
console.error(`Error: ${json.error ?? resp.statusText}`);
|
|
4680
|
+
process.exit(1);
|
|
4681
|
+
}
|
|
4682
|
+
const rows = (json.data ?? json).map((l) => ({
|
|
4683
|
+
action: l.action,
|
|
4684
|
+
target: l.targetType ?? l.target_type,
|
|
4685
|
+
targetId: l.targetId ?? l.target_id,
|
|
4686
|
+
user: l.adminUserId ?? l.admin_user_id,
|
|
4687
|
+
time: l.createdAt ?? l.created_at
|
|
4688
|
+
}));
|
|
4689
|
+
printData(rows, [
|
|
4690
|
+
{ key: "action", header: "ACTION", width: 25 },
|
|
4691
|
+
{ key: "target", header: "TARGET", width: 12 },
|
|
4692
|
+
{ key: "targetId", header: "TARGET ID", width: 36 },
|
|
4693
|
+
{ key: "time", header: "TIME", width: 22 }
|
|
4694
|
+
], opts.format);
|
|
4695
|
+
});
|
|
4696
|
+
const system = cmd.command("system").description("System operations");
|
|
4697
|
+
addFormatOption(system.command("health").description("System health check.")).action(async (opts) => {
|
|
4698
|
+
const resp = await apiRequest({ path: "/api/v1/console/system/health" });
|
|
4699
|
+
const json = await resp.json();
|
|
4700
|
+
if (!resp.ok) {
|
|
4701
|
+
console.error(`Error: ${json.error ?? resp.statusText}`);
|
|
4702
|
+
process.exit(1);
|
|
4703
|
+
}
|
|
4704
|
+
printDetail(json.data ?? json, opts.format);
|
|
4705
|
+
});
|
|
4706
|
+
system.command("cache-clear").description("Clear system cache.").action(async () => {
|
|
4707
|
+
const resp = await apiRequest({ method: "POST", path: "/api/v1/console/system/cache/clear" });
|
|
4708
|
+
if (!resp.ok) {
|
|
4709
|
+
const j = await resp.json();
|
|
4710
|
+
console.error(`Error: ${j.error ?? resp.statusText}`);
|
|
4711
|
+
process.exit(1);
|
|
4712
|
+
}
|
|
4713
|
+
console.log("Cache cleared.");
|
|
4714
|
+
});
|
|
4715
|
+
return cmd;
|
|
4716
|
+
}
|
|
4717
|
+
|
|
4491
4718
|
// src/commands/settings.ts
|
|
4492
4719
|
function settingsPrefix() {
|
|
4493
4720
|
const ctx = loadContext();
|
|
@@ -4619,7 +4846,7 @@ Examples:
|
|
|
4619
4846
|
|
|
4620
4847
|
// src/index.ts
|
|
4621
4848
|
function createProgram() {
|
|
4622
|
-
const program2 = new Command().name("a8techads").description("A8TechAds CLI — programmatic ad platform management").version("0.
|
|
4849
|
+
const program2 = new Command().name("a8techads").description("A8TechAds CLI — programmatic ad platform management").version("0.4.0").addHelpText("after", `
|
|
4623
4850
|
Command Groups:
|
|
4624
4851
|
auth Authentication (login, logout, token, status)
|
|
4625
4852
|
profile Multi-profile management
|
|
@@ -4654,6 +4881,7 @@ Getting Started:
|
|
|
4654
4881
|
program2.addCommand(createUsersCommand());
|
|
4655
4882
|
program2.addCommand(createSettingsCommand());
|
|
4656
4883
|
program2.addCommand(createInvoicesCommand());
|
|
4884
|
+
program2.addCommand(createAdminCommand());
|
|
4657
4885
|
return program2;
|
|
4658
4886
|
}
|
|
4659
4887
|
|