@optima-chat/dev-skills 0.8.0 → 0.8.1
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/bin/helpers/billing-http.ts +20 -1
- package/bin/helpers/confirm-prompt.ts +8 -4
- package/bin/helpers/discount/create.ts +3 -3
- package/bin/helpers/discount/disable.ts +3 -3
- package/bin/helpers/discount/generate.ts +3 -3
- package/bin/helpers/discount/list.ts +3 -3
- package/bin/helpers/entitlement/grant.ts +4 -7
- package/bin/helpers/entitlement/list.ts +4 -7
- package/bin/helpers/entitlement/revoke.ts +4 -7
- package/dist/bin/helpers/billing-http.js +19 -0
- package/dist/bin/helpers/confirm-prompt.js +7 -4
- package/dist/bin/helpers/discount/create.js +2 -2
- package/dist/bin/helpers/discount/disable.js +2 -2
- package/dist/bin/helpers/discount/generate.js +2 -2
- package/dist/bin/helpers/discount/list.js +2 -2
- package/dist/bin/helpers/entitlement/grant.js +3 -6
- package/dist/bin/helpers/entitlement/list.js +3 -6
- package/dist/bin/helpers/entitlement/revoke.js +3 -6
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
2
|
import { fetchInfisicalSecret } from './infisical-secrets';
|
|
3
|
-
import { getInfisicalConfig, getInfisicalToken, getCnInfisicalToken, getCnSecrets } from './db-utils';
|
|
3
|
+
import { getInfisicalConfig, getInfisicalToken, getCnInfisicalToken, getCnSecrets, resolveUserId } from './db-utils';
|
|
4
4
|
|
|
5
5
|
const USER_AUTH_URLS: Record<string, string> = {
|
|
6
6
|
stage: 'https://auth.stage.optima.onl',
|
|
@@ -292,6 +292,25 @@ export async function resolveUserIdByEmail(env: string, email: string): Promise<
|
|
|
292
292
|
return parsed.user_id;
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
+
/**
|
|
296
|
+
* Resolve email → userId across all envs, picking the path that exists for
|
|
297
|
+
* each cloud. AWS (stage/prod) reaches the database over the RDS SSH tunnel
|
|
298
|
+
* (db-utils.resolveUserId). cn-prod / cn-stage have no tunnel into the Aliyun
|
|
299
|
+
* VPC-internal RDS, so they resolve via user-auth's internal HTTP lookup
|
|
300
|
+
* (resolveUserIdByEmail) — the cn dev-skills token carries the
|
|
301
|
+
* internal:users:write scope that endpoint requires. Lets the entitlement
|
|
302
|
+
* subcommands support cn the same way grant-subscription already does, without
|
|
303
|
+
* each call site re-implementing the branch.
|
|
304
|
+
*/
|
|
305
|
+
export async function resolveUserIdByEmailAnyEnv(env: string, email: string): Promise<string> {
|
|
306
|
+
if (env === 'cn-prod' || env === 'cn-stage') {
|
|
307
|
+
return resolveUserIdByEmail(env, email);
|
|
308
|
+
}
|
|
309
|
+
const cfg = getInfisicalConfig();
|
|
310
|
+
const token = getInfisicalToken(cfg);
|
|
311
|
+
return resolveUserId(email, env, cfg, token);
|
|
312
|
+
}
|
|
313
|
+
|
|
295
314
|
/**
|
|
296
315
|
* Resolve a user's id by phone via user-auth's internal lookup endpoint
|
|
297
316
|
* (POST /api/v1/internal/users/lookup with {phone}). Mirrors
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import * as readline from 'readline';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* On
|
|
5
|
-
*
|
|
4
|
+
* On a production env, print the resolved action and require typing "yes" to
|
|
5
|
+
* proceed. Both AWS `prod` and Aliyun `cn-prod` are production — cn-prod must
|
|
6
|
+
* gate identically or the cn rollout silently loses the prod safety prompt.
|
|
7
|
+
* No-op on stage / cn-stage or when --yes was passed. Exits 1 if user declines.
|
|
6
8
|
*/
|
|
9
|
+
const PROD_ENVS = new Set(['prod', 'cn-prod']);
|
|
10
|
+
|
|
7
11
|
export async function confirmIfProd(
|
|
8
12
|
env: string,
|
|
9
13
|
actionDescription: string,
|
|
10
14
|
skipFlag: boolean,
|
|
11
15
|
): Promise<void> {
|
|
12
|
-
if (env
|
|
16
|
+
if (!PROD_ENVS.has(env) || skipFlag) return;
|
|
13
17
|
|
|
14
|
-
console.log(`\n⚠️ About to perform on
|
|
18
|
+
console.log(`\n⚠️ About to perform on ${env.toUpperCase()}:\n${actionDescription}\n`);
|
|
15
19
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
16
20
|
const answer = await new Promise<string>((resolve) => {
|
|
17
21
|
rl.question('Type "yes" to confirm: ', (a) => { rl.close(); resolve(a.trim()); });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { callBilling,
|
|
1
|
+
import { callBilling, validateEnvCnProd } from '../billing-http';
|
|
2
2
|
import { confirmIfProd } from '../confirm-prompt';
|
|
3
3
|
|
|
4
4
|
interface CreateArgs {
|
|
@@ -34,7 +34,7 @@ Optional:
|
|
|
34
34
|
--ends <date> Valid-until
|
|
35
35
|
--max <N> Max total redemptions (default: unlimited; 1 = single-use)
|
|
36
36
|
--campaign <label> Grouping label
|
|
37
|
-
--env stage|prod
|
|
37
|
+
--env stage|prod|cn-prod|cn-stage (default: stage)
|
|
38
38
|
--yes Skip prod confirmation`);
|
|
39
39
|
process.exit(0);
|
|
40
40
|
}
|
|
@@ -62,7 +62,7 @@ Optional:
|
|
|
62
62
|
|
|
63
63
|
export async function runCreate(argv: string[]): Promise<void> {
|
|
64
64
|
const args = parseArgs(argv);
|
|
65
|
-
|
|
65
|
+
validateEnvCnProd(args.env);
|
|
66
66
|
await confirmIfProd(args.env, `Create discount code ${args.code.toUpperCase()} (${args.percentOff}% off)`, args.yes);
|
|
67
67
|
|
|
68
68
|
const body: Record<string, unknown> = { code: args.code, percentOff: args.percentOff };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { callBilling,
|
|
1
|
+
import { callBilling, validateEnvCnProd } from '../billing-http';
|
|
2
2
|
import { confirmIfProd } from '../confirm-prompt';
|
|
3
3
|
|
|
4
4
|
interface DisableArgs {
|
|
@@ -15,7 +15,7 @@ Required:
|
|
|
15
15
|
--code <CODE>
|
|
16
16
|
|
|
17
17
|
Optional:
|
|
18
|
-
--env stage|prod
|
|
18
|
+
--env stage|prod|cn-prod|cn-stage (default: stage)
|
|
19
19
|
--yes Skip prod confirmation`);
|
|
20
20
|
process.exit(0);
|
|
21
21
|
}
|
|
@@ -36,7 +36,7 @@ Optional:
|
|
|
36
36
|
|
|
37
37
|
export async function runDisable(argv: string[]): Promise<void> {
|
|
38
38
|
const args = parseArgs(argv);
|
|
39
|
-
|
|
39
|
+
validateEnvCnProd(args.env);
|
|
40
40
|
await confirmIfProd(args.env, `Disable discount code ${args.code.toUpperCase()}`, args.yes);
|
|
41
41
|
const res = await callBilling(args.env, 'PATCH', `/api/billing/admin/discount-codes/${encodeURIComponent(args.code)}`, { status: 'DISABLED' });
|
|
42
42
|
console.log(`✓ Disabled (HTTP ${res.status}):`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
|
-
import { callBilling,
|
|
2
|
+
import { callBilling, validateEnvCnProd } from '../billing-http';
|
|
3
3
|
import { confirmIfProd } from '../confirm-prompt';
|
|
4
4
|
|
|
5
5
|
interface GenArgs {
|
|
@@ -35,7 +35,7 @@ Optional:
|
|
|
35
35
|
--products <a,b,...> Limit to these productKeys
|
|
36
36
|
--starts <date> Valid-from (YYYY-MM-DD or ISO)
|
|
37
37
|
--ends <date> Valid-until
|
|
38
|
-
--env stage|prod
|
|
38
|
+
--env stage|prod|cn-prod|cn-stage (default: stage)
|
|
39
39
|
--yes Skip prod confirmation`);
|
|
40
40
|
process.exit(0);
|
|
41
41
|
}
|
|
@@ -63,7 +63,7 @@ Optional:
|
|
|
63
63
|
|
|
64
64
|
export async function runGenerate(argv: string[]): Promise<void> {
|
|
65
65
|
const args = parseArgs(argv);
|
|
66
|
-
|
|
66
|
+
validateEnvCnProd(args.env);
|
|
67
67
|
await confirmIfProd(args.env, `Generate ${args.count} unique discount codes (${args.percentOff}% off, campaign ${args.campaign})`, args.yes);
|
|
68
68
|
|
|
69
69
|
const body: Record<string, unknown> = { count: args.count, percentOff: args.percentOff, campaign: args.campaign };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { callBilling,
|
|
1
|
+
import { callBilling, validateEnvCnProd } from '../billing-http';
|
|
2
2
|
|
|
3
3
|
interface ListArgs {
|
|
4
4
|
campaign?: string;
|
|
@@ -15,7 +15,7 @@ Optional:
|
|
|
15
15
|
--campaign <label> Filter by campaign
|
|
16
16
|
--code <CODE> Filter by exact code
|
|
17
17
|
--limit <N> Max rows (default 500, max 1000)
|
|
18
|
-
--env stage|prod
|
|
18
|
+
--env stage|prod|cn-prod|cn-stage (default: stage)`);
|
|
19
19
|
process.exit(0);
|
|
20
20
|
}
|
|
21
21
|
const out: Partial<ListArgs> = { env: 'stage' };
|
|
@@ -35,7 +35,7 @@ Optional:
|
|
|
35
35
|
|
|
36
36
|
export async function runList(argv: string[]): Promise<void> {
|
|
37
37
|
const args = parseArgs(argv);
|
|
38
|
-
|
|
38
|
+
validateEnvCnProd(args.env);
|
|
39
39
|
const qs = new URLSearchParams();
|
|
40
40
|
if (args.campaign) qs.set('campaign', args.campaign);
|
|
41
41
|
if (args.code) qs.set('code', args.code);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { callBilling,
|
|
1
|
+
import { callBilling, validateEnvCnProd, resolveUserIdByEmailAnyEnv } from '../billing-http';
|
|
2
2
|
import { confirmIfProd } from '../confirm-prompt';
|
|
3
|
-
import { getInfisicalConfig, getInfisicalToken, resolveUserId } from '../db-utils';
|
|
4
3
|
|
|
5
4
|
interface GrantArgs {
|
|
6
5
|
email: string;
|
|
@@ -21,7 +20,7 @@ Required:
|
|
|
21
20
|
|
|
22
21
|
Optional:
|
|
23
22
|
--yes Skip prod confirmation prompt (no-op on stage)
|
|
24
|
-
--env stage|prod
|
|
23
|
+
--env stage|prod|cn-prod|cn-stage (default: stage)
|
|
25
24
|
|
|
26
25
|
Hardcoded server-side: source=ADMIN_GRANT, priceCents=0, currency=USD, grantedBy=<clientId>.`);
|
|
27
26
|
process.exit(0);
|
|
@@ -47,11 +46,9 @@ Hardcoded server-side: source=ADMIN_GRANT, priceCents=0, currency=USD, grantedBy
|
|
|
47
46
|
|
|
48
47
|
export async function runGrant(argv: string[]): Promise<void> {
|
|
49
48
|
const args = parseArgs(argv);
|
|
50
|
-
|
|
49
|
+
validateEnvCnProd(args.env);
|
|
51
50
|
|
|
52
|
-
const
|
|
53
|
-
const token = getInfisicalToken(cfg);
|
|
54
|
-
const userId = await resolveUserId(args.email, args.env, cfg, token);
|
|
51
|
+
const userId = await resolveUserIdByEmailAnyEnv(args.env, args.email);
|
|
55
52
|
|
|
56
53
|
await confirmIfProd(
|
|
57
54
|
args.env,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { callBilling,
|
|
2
|
-
import { getInfisicalConfig, getInfisicalToken, resolveUserId } from '../db-utils';
|
|
1
|
+
import { callBilling, validateEnvCnProd, resolveUserIdByEmailAnyEnv } from '../billing-http';
|
|
3
2
|
|
|
4
3
|
interface ListArgs {
|
|
5
4
|
email: string;
|
|
@@ -14,7 +13,7 @@ Required:
|
|
|
14
13
|
--email <user-email> Resolved to userId via user-auth DB
|
|
15
14
|
|
|
16
15
|
Optional:
|
|
17
|
-
--env stage|prod
|
|
16
|
+
--env stage|prod|cn-prod|cn-stage (default: stage)`);
|
|
18
17
|
process.exit(0);
|
|
19
18
|
}
|
|
20
19
|
const out: Partial<ListArgs> = { env: 'stage' };
|
|
@@ -42,11 +41,9 @@ interface EntitlementRow {
|
|
|
42
41
|
|
|
43
42
|
export async function runList(argv: string[]): Promise<void> {
|
|
44
43
|
const args = parseArgs(argv);
|
|
45
|
-
|
|
44
|
+
validateEnvCnProd(args.env);
|
|
46
45
|
|
|
47
|
-
const
|
|
48
|
-
const token = getInfisicalToken(cfg);
|
|
49
|
-
const userId = await resolveUserId(args.email, args.env, cfg, token);
|
|
46
|
+
const userId = await resolveUserIdByEmailAnyEnv(args.env, args.email);
|
|
50
47
|
|
|
51
48
|
const res = await callBilling<{ entitlements: EntitlementRow[] }>(
|
|
52
49
|
args.env,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { callBilling,
|
|
1
|
+
import { callBilling, validateEnvCnProd, resolveUserIdByEmailAnyEnv } from '../billing-http';
|
|
2
2
|
import { confirmIfProd } from '../confirm-prompt';
|
|
3
|
-
import { getInfisicalConfig, getInfisicalToken, resolveUserId } from '../db-utils';
|
|
4
3
|
|
|
5
4
|
interface RevokeArgs {
|
|
6
5
|
email: string;
|
|
@@ -28,7 +27,7 @@ Required:
|
|
|
28
27
|
|
|
29
28
|
Optional:
|
|
30
29
|
--yes Skip prod confirmation prompt (no-op on stage)
|
|
31
|
-
--env stage|prod
|
|
30
|
+
--env stage|prod|cn-prod|cn-stage (default: stage)
|
|
32
31
|
|
|
33
32
|
Refuses non-ADMIN_GRANT sources (PAYMENT, PARTNER) with source-specific
|
|
34
33
|
error pointing to the right reversal flow.`);
|
|
@@ -59,11 +58,9 @@ const PARTNER_REFUSAL = `refusing to revoke a PARTNER-source entitlement via CLI
|
|
|
59
58
|
|
|
60
59
|
export async function runRevoke(argv: string[]): Promise<void> {
|
|
61
60
|
const args = parseArgs(argv);
|
|
62
|
-
|
|
61
|
+
validateEnvCnProd(args.env);
|
|
63
62
|
|
|
64
|
-
const
|
|
65
|
-
const token = getInfisicalToken(cfg);
|
|
66
|
-
const userId = await resolveUserId(args.email, args.env, cfg, token);
|
|
63
|
+
const userId = await resolveUserIdByEmailAnyEnv(args.env, args.email);
|
|
67
64
|
|
|
68
65
|
// Step 1: Fetch user's entitlements
|
|
69
66
|
const listRes = await callBilling<{ entitlements: EntitlementRow[] }>(
|
|
@@ -6,6 +6,7 @@ exports.getServiceToken = getServiceToken;
|
|
|
6
6
|
exports.callBilling = callBilling;
|
|
7
7
|
exports.callSkills = callSkills;
|
|
8
8
|
exports.resolveUserIdByEmail = resolveUserIdByEmail;
|
|
9
|
+
exports.resolveUserIdByEmailAnyEnv = resolveUserIdByEmailAnyEnv;
|
|
9
10
|
exports.resolveUserIdByPhone = resolveUserIdByPhone;
|
|
10
11
|
exports.getUserById = getUserById;
|
|
11
12
|
const child_process_1 = require("child_process");
|
|
@@ -256,6 +257,24 @@ async function resolveUserIdByEmail(env, email) {
|
|
|
256
257
|
console.log(`✓ Found user: ${parsed.user_id}`);
|
|
257
258
|
return parsed.user_id;
|
|
258
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* Resolve email → userId across all envs, picking the path that exists for
|
|
262
|
+
* each cloud. AWS (stage/prod) reaches the database over the RDS SSH tunnel
|
|
263
|
+
* (db-utils.resolveUserId). cn-prod / cn-stage have no tunnel into the Aliyun
|
|
264
|
+
* VPC-internal RDS, so they resolve via user-auth's internal HTTP lookup
|
|
265
|
+
* (resolveUserIdByEmail) — the cn dev-skills token carries the
|
|
266
|
+
* internal:users:write scope that endpoint requires. Lets the entitlement
|
|
267
|
+
* subcommands support cn the same way grant-subscription already does, without
|
|
268
|
+
* each call site re-implementing the branch.
|
|
269
|
+
*/
|
|
270
|
+
async function resolveUserIdByEmailAnyEnv(env, email) {
|
|
271
|
+
if (env === 'cn-prod' || env === 'cn-stage') {
|
|
272
|
+
return resolveUserIdByEmail(env, email);
|
|
273
|
+
}
|
|
274
|
+
const cfg = (0, db_utils_1.getInfisicalConfig)();
|
|
275
|
+
const token = (0, db_utils_1.getInfisicalToken)(cfg);
|
|
276
|
+
return (0, db_utils_1.resolveUserId)(email, env, cfg, token);
|
|
277
|
+
}
|
|
259
278
|
/**
|
|
260
279
|
* Resolve a user's id by phone via user-auth's internal lookup endpoint
|
|
261
280
|
* (POST /api/v1/internal/users/lookup with {phone}). Mirrors
|
|
@@ -36,13 +36,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.confirmIfProd = confirmIfProd;
|
|
37
37
|
const readline = __importStar(require("readline"));
|
|
38
38
|
/**
|
|
39
|
-
* On
|
|
40
|
-
*
|
|
39
|
+
* On a production env, print the resolved action and require typing "yes" to
|
|
40
|
+
* proceed. Both AWS `prod` and Aliyun `cn-prod` are production — cn-prod must
|
|
41
|
+
* gate identically or the cn rollout silently loses the prod safety prompt.
|
|
42
|
+
* No-op on stage / cn-stage or when --yes was passed. Exits 1 if user declines.
|
|
41
43
|
*/
|
|
44
|
+
const PROD_ENVS = new Set(['prod', 'cn-prod']);
|
|
42
45
|
async function confirmIfProd(env, actionDescription, skipFlag) {
|
|
43
|
-
if (env
|
|
46
|
+
if (!PROD_ENVS.has(env) || skipFlag)
|
|
44
47
|
return;
|
|
45
|
-
console.log(`\n⚠️ About to perform on
|
|
48
|
+
console.log(`\n⚠️ About to perform on ${env.toUpperCase()}:\n${actionDescription}\n`);
|
|
46
49
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
47
50
|
const answer = await new Promise((resolve) => {
|
|
48
51
|
rl.question('Type "yes" to confirm: ', (a) => { rl.close(); resolve(a.trim()); });
|
|
@@ -24,7 +24,7 @@ Optional:
|
|
|
24
24
|
--ends <date> Valid-until
|
|
25
25
|
--max <N> Max total redemptions (default: unlimited; 1 = single-use)
|
|
26
26
|
--campaign <label> Grouping label
|
|
27
|
-
--env stage|prod
|
|
27
|
+
--env stage|prod|cn-prod|cn-stage (default: stage)
|
|
28
28
|
--yes Skip prod confirmation`);
|
|
29
29
|
process.exit(0);
|
|
30
30
|
}
|
|
@@ -81,7 +81,7 @@ Optional:
|
|
|
81
81
|
}
|
|
82
82
|
async function runCreate(argv) {
|
|
83
83
|
const args = parseArgs(argv);
|
|
84
|
-
(0, billing_http_1.
|
|
84
|
+
(0, billing_http_1.validateEnvCnProd)(args.env);
|
|
85
85
|
await (0, confirm_prompt_1.confirmIfProd)(args.env, `Create discount code ${args.code.toUpperCase()} (${args.percentOff}% off)`, args.yes);
|
|
86
86
|
const body = { code: args.code, percentOff: args.percentOff };
|
|
87
87
|
if (args.productKeys)
|
|
@@ -11,7 +11,7 @@ Required:
|
|
|
11
11
|
--code <CODE>
|
|
12
12
|
|
|
13
13
|
Optional:
|
|
14
|
-
--env stage|prod
|
|
14
|
+
--env stage|prod|cn-prod|cn-stage (default: stage)
|
|
15
15
|
--yes Skip prod confirmation`);
|
|
16
16
|
process.exit(0);
|
|
17
17
|
}
|
|
@@ -40,7 +40,7 @@ Optional:
|
|
|
40
40
|
}
|
|
41
41
|
async function runDisable(argv) {
|
|
42
42
|
const args = parseArgs(argv);
|
|
43
|
-
(0, billing_http_1.
|
|
43
|
+
(0, billing_http_1.validateEnvCnProd)(args.env);
|
|
44
44
|
await (0, confirm_prompt_1.confirmIfProd)(args.env, `Disable discount code ${args.code.toUpperCase()}`, args.yes);
|
|
45
45
|
const res = await (0, billing_http_1.callBilling)(args.env, 'PATCH', `/api/billing/admin/discount-codes/${encodeURIComponent(args.code)}`, { status: 'DISABLED' });
|
|
46
46
|
console.log(`✓ Disabled (HTTP ${res.status}):`);
|
|
@@ -59,7 +59,7 @@ Optional:
|
|
|
59
59
|
--products <a,b,...> Limit to these productKeys
|
|
60
60
|
--starts <date> Valid-from (YYYY-MM-DD or ISO)
|
|
61
61
|
--ends <date> Valid-until
|
|
62
|
-
--env stage|prod
|
|
62
|
+
--env stage|prod|cn-prod|cn-stage (default: stage)
|
|
63
63
|
--yes Skip prod confirmation`);
|
|
64
64
|
process.exit(0);
|
|
65
65
|
}
|
|
@@ -114,7 +114,7 @@ Optional:
|
|
|
114
114
|
}
|
|
115
115
|
async function runGenerate(argv) {
|
|
116
116
|
const args = parseArgs(argv);
|
|
117
|
-
(0, billing_http_1.
|
|
117
|
+
(0, billing_http_1.validateEnvCnProd)(args.env);
|
|
118
118
|
await (0, confirm_prompt_1.confirmIfProd)(args.env, `Generate ${args.count} unique discount codes (${args.percentOff}% off, campaign ${args.campaign})`, args.yes);
|
|
119
119
|
const body = { count: args.count, percentOff: args.percentOff, campaign: args.campaign };
|
|
120
120
|
if (args.productKeys)
|
|
@@ -10,7 +10,7 @@ Optional:
|
|
|
10
10
|
--campaign <label> Filter by campaign
|
|
11
11
|
--code <CODE> Filter by exact code
|
|
12
12
|
--limit <N> Max rows (default 500, max 1000)
|
|
13
|
-
--env stage|prod
|
|
13
|
+
--env stage|prod|cn-prod|cn-stage (default: stage)`);
|
|
14
14
|
process.exit(0);
|
|
15
15
|
}
|
|
16
16
|
const out = { env: 'stage' };
|
|
@@ -45,7 +45,7 @@ Optional:
|
|
|
45
45
|
}
|
|
46
46
|
async function runList(argv) {
|
|
47
47
|
const args = parseArgs(argv);
|
|
48
|
-
(0, billing_http_1.
|
|
48
|
+
(0, billing_http_1.validateEnvCnProd)(args.env);
|
|
49
49
|
const qs = new URLSearchParams();
|
|
50
50
|
if (args.campaign)
|
|
51
51
|
qs.set('campaign', args.campaign);
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.runGrant = runGrant;
|
|
4
4
|
const billing_http_1 = require("../billing-http");
|
|
5
5
|
const confirm_prompt_1 = require("../confirm-prompt");
|
|
6
|
-
const db_utils_1 = require("../db-utils");
|
|
7
6
|
function parseArgs(argv) {
|
|
8
7
|
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
9
8
|
console.log(`Usage: optima-entitlement grant --email <user> --product-key <slug> --justification "..." [options]
|
|
@@ -15,7 +14,7 @@ Required:
|
|
|
15
14
|
|
|
16
15
|
Optional:
|
|
17
16
|
--yes Skip prod confirmation prompt (no-op on stage)
|
|
18
|
-
--env stage|prod
|
|
17
|
+
--env stage|prod|cn-prod|cn-stage (default: stage)
|
|
19
18
|
|
|
20
19
|
Hardcoded server-side: source=ADMIN_GRANT, priceCents=0, currency=USD, grantedBy=<clientId>.`);
|
|
21
20
|
process.exit(0);
|
|
@@ -57,10 +56,8 @@ Hardcoded server-side: source=ADMIN_GRANT, priceCents=0, currency=USD, grantedBy
|
|
|
57
56
|
}
|
|
58
57
|
async function runGrant(argv) {
|
|
59
58
|
const args = parseArgs(argv);
|
|
60
|
-
(0, billing_http_1.
|
|
61
|
-
const
|
|
62
|
-
const token = (0, db_utils_1.getInfisicalToken)(cfg);
|
|
63
|
-
const userId = await (0, db_utils_1.resolveUserId)(args.email, args.env, cfg, token);
|
|
59
|
+
(0, billing_http_1.validateEnvCnProd)(args.env);
|
|
60
|
+
const userId = await (0, billing_http_1.resolveUserIdByEmailAnyEnv)(args.env, args.email);
|
|
64
61
|
await (0, confirm_prompt_1.confirmIfProd)(args.env, `Action: GRANT product '${args.productKey}' to user ${args.email} (userId=${userId}) on ${args.env.toUpperCase()}\nJustification: ${args.justification}`, args.yes);
|
|
65
62
|
console.log(`\n🎁 Granting ${args.productKey} to ${args.email}...`);
|
|
66
63
|
const res = await (0, billing_http_1.callBilling)(args.env, 'POST', '/api/billing/admin/grant-entitlement', {
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runList = runList;
|
|
4
4
|
const billing_http_1 = require("../billing-http");
|
|
5
|
-
const db_utils_1 = require("../db-utils");
|
|
6
5
|
function parseArgs(argv) {
|
|
7
6
|
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
8
7
|
console.log(`Usage: optima-entitlement list --email <user-email> [options]
|
|
@@ -11,7 +10,7 @@ Required:
|
|
|
11
10
|
--email <user-email> Resolved to userId via user-auth DB
|
|
12
11
|
|
|
13
12
|
Optional:
|
|
14
|
-
--env stage|prod
|
|
13
|
+
--env stage|prod|cn-prod|cn-stage (default: stage)`);
|
|
15
14
|
process.exit(0);
|
|
16
15
|
}
|
|
17
16
|
const out = { env: 'stage' };
|
|
@@ -36,10 +35,8 @@ Optional:
|
|
|
36
35
|
}
|
|
37
36
|
async function runList(argv) {
|
|
38
37
|
const args = parseArgs(argv);
|
|
39
|
-
(0, billing_http_1.
|
|
40
|
-
const
|
|
41
|
-
const token = (0, db_utils_1.getInfisicalToken)(cfg);
|
|
42
|
-
const userId = await (0, db_utils_1.resolveUserId)(args.email, args.env, cfg, token);
|
|
38
|
+
(0, billing_http_1.validateEnvCnProd)(args.env);
|
|
39
|
+
const userId = await (0, billing_http_1.resolveUserIdByEmailAnyEnv)(args.env, args.email);
|
|
43
40
|
const res = await (0, billing_http_1.callBilling)(args.env, 'GET', `/api/billing/admin/entitlements?userId=${encodeURIComponent(userId)}`);
|
|
44
41
|
const rows = res.body.entitlements ?? [];
|
|
45
42
|
if (rows.length === 0) {
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.runRevoke = runRevoke;
|
|
4
4
|
const billing_http_1 = require("../billing-http");
|
|
5
5
|
const confirm_prompt_1 = require("../confirm-prompt");
|
|
6
|
-
const db_utils_1 = require("../db-utils");
|
|
7
6
|
function parseArgs(argv) {
|
|
8
7
|
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
9
8
|
console.log(`Usage: optima-entitlement revoke --email <user> --product-key <slug> --reason "..." [options]
|
|
@@ -15,7 +14,7 @@ Required:
|
|
|
15
14
|
|
|
16
15
|
Optional:
|
|
17
16
|
--yes Skip prod confirmation prompt (no-op on stage)
|
|
18
|
-
--env stage|prod
|
|
17
|
+
--env stage|prod|cn-prod|cn-stage (default: stage)
|
|
19
18
|
|
|
20
19
|
Refuses non-ADMIN_GRANT sources (PAYMENT, PARTNER) with source-specific
|
|
21
20
|
error pointing to the right reversal flow.`);
|
|
@@ -60,10 +59,8 @@ const PAYMENT_REFUSAL = `refusing to revoke a PAYMENT-source entitlement via CLI
|
|
|
60
59
|
const PARTNER_REFUSAL = `refusing to revoke a PARTNER-source entitlement via CLI; PARTNER grants are issued out-of-band and must be reversed via the partner contract / process that issued them. Manual psql is the escape hatch if absolutely necessary.`;
|
|
61
60
|
async function runRevoke(argv) {
|
|
62
61
|
const args = parseArgs(argv);
|
|
63
|
-
(0, billing_http_1.
|
|
64
|
-
const
|
|
65
|
-
const token = (0, db_utils_1.getInfisicalToken)(cfg);
|
|
66
|
-
const userId = await (0, db_utils_1.resolveUserId)(args.email, args.env, cfg, token);
|
|
62
|
+
(0, billing_http_1.validateEnvCnProd)(args.env);
|
|
63
|
+
const userId = await (0, billing_http_1.resolveUserIdByEmailAnyEnv)(args.env, args.email);
|
|
67
64
|
// Step 1: Fetch user's entitlements
|
|
68
65
|
const listRes = await (0, billing_http_1.callBilling)(args.env, 'GET', `/api/billing/admin/entitlements?userId=${encodeURIComponent(userId)}`);
|
|
69
66
|
const all = listRes.body.entitlements ?? [];
|