@optima-chat/dev-skills 0.7.35 → 0.7.37
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/.claude/skills/discount-codes/SKILL.md +61 -0
- package/AGENTS.md +1 -0
- package/README.md +2 -0
- package/bin/helpers/billing-http.ts +41 -12
- package/bin/helpers/discount/create.ts +79 -0
- package/bin/helpers/discount/disable.ts +44 -0
- package/bin/helpers/discount/generate.ts +84 -0
- package/bin/helpers/discount/list.ts +46 -0
- package/bin/helpers/discount.ts +41 -0
- package/bin/helpers/plugin/set-default.ts +65 -0
- package/bin/helpers/plugin/set-paid.ts +69 -0
- package/bin/helpers/plugin/show.ts +42 -0
- package/bin/helpers/plugin.ts +32 -0
- package/dist/bin/helpers/billing-http.js +23 -9
- package/dist/bin/helpers/discount/create.js +101 -0
- package/dist/bin/helpers/discount/disable.js +48 -0
- package/dist/bin/helpers/discount/generate.js +135 -0
- package/dist/bin/helpers/discount/list.js +59 -0
- package/dist/bin/helpers/discount.js +47 -0
- package/dist/bin/helpers/entitlement.js +0 -0
- package/dist/bin/helpers/generate-test-token.js +0 -0
- package/dist/bin/helpers/grant-balance.js +0 -0
- package/dist/bin/helpers/grant-subscription.js +0 -0
- package/dist/bin/helpers/plugin/set-default.js +61 -0
- package/dist/bin/helpers/plugin/set-paid.js +65 -0
- package/dist/bin/helpers/plugin/show.js +45 -0
- package/dist/bin/helpers/plugin.js +39 -0
- package/dist/bin/helpers/product.js +0 -0
- package/dist/bin/helpers/query-db.js +0 -0
- package/dist/bin/helpers/show-env.js +0 -0
- package/docs/2026-05-27-discount-codes-plan-B-dev-skills-cli.md +517 -0
- package/docs/superpowers/plans/2026-05-25-optima-plugin-cli-impl.md +700 -0
- package/docs/superpowers/specs/2026-05-25-optima-plugin-cli-design.md +156 -0
- package/package.json +3 -1
- package/dist/bin/helpers/grant-credits.js +0 -71
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { runShow } from './plugin/show';
|
|
4
|
+
import { runSetPaid } from './plugin/set-paid';
|
|
5
|
+
import { runSetDefault } from './plugin/set-default';
|
|
6
|
+
|
|
7
|
+
function printHelp() {
|
|
8
|
+
console.log(`Usage: optima-plugin <subcommand> [options]
|
|
9
|
+
|
|
10
|
+
Subcommands:
|
|
11
|
+
show Show a plugin's marketplace state (isPaid, salesUrl, ... ACTIVE plugins only)
|
|
12
|
+
set-paid Flip a plugin's isPaid flag (the user-facing paid/free gate)
|
|
13
|
+
set-default Flip a plugin's defaultForUser flag
|
|
14
|
+
|
|
15
|
+
Run 'optima-plugin <subcommand> --help' for subcommand-specific options.`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function main() {
|
|
19
|
+
const [, , subcommand, ...rest] = process.argv;
|
|
20
|
+
if (!subcommand || subcommand === '-h' || subcommand === '--help') { printHelp(); process.exit(0); }
|
|
21
|
+
switch (subcommand) {
|
|
22
|
+
case 'show': await runShow(rest); break;
|
|
23
|
+
case 'set-paid': await runSetPaid(rest); break;
|
|
24
|
+
case 'set-default': await runSetDefault(rest); break;
|
|
25
|
+
default:
|
|
26
|
+
console.error(`Unknown subcommand: ${subcommand}`);
|
|
27
|
+
printHelp();
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
main().catch((err) => { console.error(err.message); process.exit(1); });
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.validateEnv = validateEnv;
|
|
4
4
|
exports.getServiceToken = getServiceToken;
|
|
5
5
|
exports.callBilling = callBilling;
|
|
6
|
+
exports.callSkills = callSkills;
|
|
6
7
|
const child_process_1 = require("child_process");
|
|
7
8
|
const infisical_secrets_1 = require("./infisical-secrets");
|
|
8
9
|
const db_utils_1 = require("./db-utils");
|
|
@@ -44,6 +45,7 @@ const DEV_SKILLS_CLIENT_SECRET_KEY = 'DEV_SKILLS_OAUTH_CLIENT_SECRET';
|
|
|
44
45
|
// retry; any transient failure surfaces immediately. Re-run the CLI.
|
|
45
46
|
const tokenCache = {};
|
|
46
47
|
const billingUrlCache = {};
|
|
48
|
+
const skillsUrlCache = {};
|
|
47
49
|
function getBillingUrl(env) {
|
|
48
50
|
if (billingUrlCache[env])
|
|
49
51
|
return billingUrlCache[env];
|
|
@@ -51,6 +53,13 @@ function getBillingUrl(env) {
|
|
|
51
53
|
billingUrlCache[env] = url;
|
|
52
54
|
return url;
|
|
53
55
|
}
|
|
56
|
+
function getSkillsUrl(env) {
|
|
57
|
+
if (skillsUrlCache[env])
|
|
58
|
+
return skillsUrlCache[env];
|
|
59
|
+
const url = (0, infisical_secrets_1.fetchInfisicalSecret)(env, '/shared-secrets/domain-urls', 'SKILLS_REGISTRY_URL');
|
|
60
|
+
skillsUrlCache[env] = url;
|
|
61
|
+
return url;
|
|
62
|
+
}
|
|
54
63
|
function getServiceToken(env) {
|
|
55
64
|
if (tokenCache[env])
|
|
56
65
|
return tokenCache[env];
|
|
@@ -77,7 +86,7 @@ function getServiceToken(env) {
|
|
|
77
86
|
tokenCache[env] = parsed.access_token;
|
|
78
87
|
return parsed.access_token;
|
|
79
88
|
}
|
|
80
|
-
function
|
|
89
|
+
function formatServiceError(status, statusText, body) {
|
|
81
90
|
let parsed = null;
|
|
82
91
|
try {
|
|
83
92
|
parsed = JSON.parse(body);
|
|
@@ -104,12 +113,12 @@ function formatBillingError(status, statusText, body) {
|
|
|
104
113
|
return `❌ Error [${status}] ${statusText}\n Response body (first 500 bytes): ${body.slice(0, 500)}`;
|
|
105
114
|
}
|
|
106
115
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
116
|
+
* Authenticated call to an Optima service (billing or skills — same dev-skills
|
|
117
|
+
* M2M token works for both). Returns `{status, body}` on 2xx; throws Error with
|
|
118
|
+
* formatted message on non-2xx. Single retry on 5xx (no backoff — admin CLI).
|
|
110
119
|
*/
|
|
111
|
-
async function
|
|
112
|
-
const url = `${
|
|
120
|
+
async function callService(baseUrl, env, method, path, body) {
|
|
121
|
+
const url = `${baseUrl}${path}`;
|
|
113
122
|
const token = getServiceToken(env);
|
|
114
123
|
const doFetch = async () => fetch(url, {
|
|
115
124
|
method,
|
|
@@ -121,19 +130,24 @@ async function callBilling(env, method, path, body) {
|
|
|
121
130
|
});
|
|
122
131
|
let res = await doFetch();
|
|
123
132
|
if (res.status >= 500) {
|
|
124
|
-
// One retry on 5xx
|
|
125
133
|
res = await doFetch();
|
|
126
134
|
}
|
|
127
135
|
const text = await res.text();
|
|
128
136
|
if (!res.ok) {
|
|
129
|
-
throw new Error(
|
|
137
|
+
throw new Error(formatServiceError(res.status, res.statusText, text));
|
|
130
138
|
}
|
|
131
139
|
let parsed;
|
|
132
140
|
try {
|
|
133
141
|
parsed = text ? JSON.parse(text) : undefined;
|
|
134
142
|
}
|
|
135
143
|
catch {
|
|
136
|
-
throw new Error(`
|
|
144
|
+
throw new Error(`Service returned non-JSON 2xx body: ${text.slice(0, 200)}`);
|
|
137
145
|
}
|
|
138
146
|
return { status: res.status, body: parsed };
|
|
139
147
|
}
|
|
148
|
+
async function callBilling(env, method, path, body) {
|
|
149
|
+
return callService(getBillingUrl(env), env, method, path, body);
|
|
150
|
+
}
|
|
151
|
+
async function callSkills(env, method, path, body) {
|
|
152
|
+
return callService(getSkillsUrl(env), env, method, path, body);
|
|
153
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runCreate = runCreate;
|
|
4
|
+
const billing_http_1 = require("../billing-http");
|
|
5
|
+
const confirm_prompt_1 = require("../confirm-prompt");
|
|
6
|
+
/** Normalize a date/datetime arg to a full ISO datetime string (billing requires z.iso.datetime). */
|
|
7
|
+
function toIso(v) {
|
|
8
|
+
const d = new Date(v);
|
|
9
|
+
if (isNaN(d.getTime()))
|
|
10
|
+
throw new Error(`Invalid date: ${v} (use YYYY-MM-DD or ISO datetime)`);
|
|
11
|
+
return d.toISOString();
|
|
12
|
+
}
|
|
13
|
+
function parseArgs(argv) {
|
|
14
|
+
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
15
|
+
console.log(`Usage: optima-discount create --code <CODE> --percent <1-100> [options]
|
|
16
|
+
|
|
17
|
+
Required:
|
|
18
|
+
--code <CODE> Promo code (stored uppercased)
|
|
19
|
+
--percent <1-100> Percentage off
|
|
20
|
+
|
|
21
|
+
Optional:
|
|
22
|
+
--products <a,b,...> Limit to these productKeys (default: all)
|
|
23
|
+
--starts <date> Valid-from (YYYY-MM-DD or ISO; default: immediately)
|
|
24
|
+
--ends <date> Valid-until
|
|
25
|
+
--max <N> Max total redemptions (default: unlimited; 1 = single-use)
|
|
26
|
+
--campaign <label> Grouping label
|
|
27
|
+
--env stage|prod (default: stage)
|
|
28
|
+
--yes Skip prod confirmation`);
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
const out = { env: 'stage', yes: false };
|
|
32
|
+
for (let i = 0; i < argv.length; i++) {
|
|
33
|
+
const a = argv[i];
|
|
34
|
+
const next = argv[i + 1];
|
|
35
|
+
switch (a) {
|
|
36
|
+
case '--code':
|
|
37
|
+
out.code = next;
|
|
38
|
+
i++;
|
|
39
|
+
break;
|
|
40
|
+
case '--percent':
|
|
41
|
+
out.percentOff = parseInt(next, 10);
|
|
42
|
+
i++;
|
|
43
|
+
break;
|
|
44
|
+
case '--products':
|
|
45
|
+
if (!next)
|
|
46
|
+
throw new Error('--products requires a value');
|
|
47
|
+
out.productKeys = next.split(',').map((s) => s.trim()).filter(Boolean);
|
|
48
|
+
i++;
|
|
49
|
+
break;
|
|
50
|
+
case '--starts':
|
|
51
|
+
out.startsAt = toIso(next);
|
|
52
|
+
i++;
|
|
53
|
+
break;
|
|
54
|
+
case '--ends':
|
|
55
|
+
out.endsAt = toIso(next);
|
|
56
|
+
i++;
|
|
57
|
+
break;
|
|
58
|
+
case '--max':
|
|
59
|
+
out.maxRedemptions = parseInt(next, 10);
|
|
60
|
+
i++;
|
|
61
|
+
break;
|
|
62
|
+
case '--campaign':
|
|
63
|
+
out.campaign = next;
|
|
64
|
+
i++;
|
|
65
|
+
break;
|
|
66
|
+
case '--env':
|
|
67
|
+
out.env = next;
|
|
68
|
+
i++;
|
|
69
|
+
break;
|
|
70
|
+
case '--yes':
|
|
71
|
+
out.yes = true;
|
|
72
|
+
break;
|
|
73
|
+
default: throw new Error(`Unknown arg: ${a}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!out.code)
|
|
77
|
+
throw new Error('--code required');
|
|
78
|
+
if (out.percentOff === undefined || isNaN(out.percentOff))
|
|
79
|
+
throw new Error('--percent required (1-100)');
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
async function runCreate(argv) {
|
|
83
|
+
const args = parseArgs(argv);
|
|
84
|
+
(0, billing_http_1.validateEnv)(args.env);
|
|
85
|
+
await (0, confirm_prompt_1.confirmIfProd)(args.env, `Create discount code ${args.code.toUpperCase()} (${args.percentOff}% off)`, args.yes);
|
|
86
|
+
const body = { code: args.code, percentOff: args.percentOff };
|
|
87
|
+
if (args.productKeys)
|
|
88
|
+
body.productKeys = args.productKeys;
|
|
89
|
+
if (args.startsAt)
|
|
90
|
+
body.startsAt = args.startsAt;
|
|
91
|
+
if (args.endsAt)
|
|
92
|
+
body.endsAt = args.endsAt;
|
|
93
|
+
if (args.maxRedemptions !== undefined)
|
|
94
|
+
body.maxRedemptions = args.maxRedemptions;
|
|
95
|
+
if (args.campaign)
|
|
96
|
+
body.campaign = args.campaign;
|
|
97
|
+
console.log(`\n🎟️ Creating discount code on ${args.env.toUpperCase()}...`);
|
|
98
|
+
const res = await (0, billing_http_1.callBilling)(args.env, 'POST', '/api/billing/admin/discount-codes', body);
|
|
99
|
+
console.log(`✓ Created (HTTP ${res.status}):`);
|
|
100
|
+
console.log(JSON.stringify(res.body, null, 2));
|
|
101
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runDisable = runDisable;
|
|
4
|
+
const billing_http_1 = require("../billing-http");
|
|
5
|
+
const confirm_prompt_1 = require("../confirm-prompt");
|
|
6
|
+
function parseArgs(argv) {
|
|
7
|
+
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
8
|
+
console.log(`Usage: optima-discount disable --code <CODE> [options]
|
|
9
|
+
|
|
10
|
+
Required:
|
|
11
|
+
--code <CODE>
|
|
12
|
+
|
|
13
|
+
Optional:
|
|
14
|
+
--env stage|prod (default: stage)
|
|
15
|
+
--yes Skip prod confirmation`);
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
const out = { env: 'stage', yes: false };
|
|
19
|
+
for (let i = 0; i < argv.length; i++) {
|
|
20
|
+
const a = argv[i];
|
|
21
|
+
const next = argv[i + 1];
|
|
22
|
+
switch (a) {
|
|
23
|
+
case '--code':
|
|
24
|
+
out.code = next;
|
|
25
|
+
i++;
|
|
26
|
+
break;
|
|
27
|
+
case '--env':
|
|
28
|
+
out.env = next;
|
|
29
|
+
i++;
|
|
30
|
+
break;
|
|
31
|
+
case '--yes':
|
|
32
|
+
out.yes = true;
|
|
33
|
+
break;
|
|
34
|
+
default: throw new Error(`Unknown arg: ${a}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!out.code)
|
|
38
|
+
throw new Error('--code required');
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
async function runDisable(argv) {
|
|
42
|
+
const args = parseArgs(argv);
|
|
43
|
+
(0, billing_http_1.validateEnv)(args.env);
|
|
44
|
+
await (0, confirm_prompt_1.confirmIfProd)(args.env, `Disable discount code ${args.code.toUpperCase()}`, args.yes);
|
|
45
|
+
const res = await (0, billing_http_1.callBilling)(args.env, 'PATCH', `/api/billing/admin/discount-codes/${encodeURIComponent(args.code)}`, { status: 'DISABLED' });
|
|
46
|
+
console.log(`✓ Disabled (HTTP ${res.status}):`);
|
|
47
|
+
console.log(JSON.stringify(res.body, null, 2));
|
|
48
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runGenerate = runGenerate;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const billing_http_1 = require("../billing-http");
|
|
39
|
+
const confirm_prompt_1 = require("../confirm-prompt");
|
|
40
|
+
function toIso(v) {
|
|
41
|
+
const d = new Date(v);
|
|
42
|
+
if (isNaN(d.getTime()))
|
|
43
|
+
throw new Error(`Invalid date: ${v} (use YYYY-MM-DD or ISO datetime)`);
|
|
44
|
+
return d.toISOString();
|
|
45
|
+
}
|
|
46
|
+
function parseArgs(argv) {
|
|
47
|
+
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
48
|
+
console.log(`Usage: optima-discount generate --count <N> --percent <1-100> --campaign <label> [options]
|
|
49
|
+
|
|
50
|
+
Generates N unique single-use codes (maxRedemptions=1). Codes are written to a
|
|
51
|
+
local file (NOT printed) to keep them copy-paste clean.
|
|
52
|
+
|
|
53
|
+
Required:
|
|
54
|
+
--count <N> How many codes (1-1000)
|
|
55
|
+
--percent <1-100> Percentage off
|
|
56
|
+
--campaign <label> Grouping label (also the code prefix)
|
|
57
|
+
|
|
58
|
+
Optional:
|
|
59
|
+
--products <a,b,...> Limit to these productKeys
|
|
60
|
+
--starts <date> Valid-from (YYYY-MM-DD or ISO)
|
|
61
|
+
--ends <date> Valid-until
|
|
62
|
+
--env stage|prod (default: stage)
|
|
63
|
+
--yes Skip prod confirmation`);
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
const out = { env: 'stage', yes: false };
|
|
67
|
+
for (let i = 0; i < argv.length; i++) {
|
|
68
|
+
const a = argv[i];
|
|
69
|
+
const next = argv[i + 1];
|
|
70
|
+
switch (a) {
|
|
71
|
+
case '--count':
|
|
72
|
+
out.count = parseInt(next, 10);
|
|
73
|
+
i++;
|
|
74
|
+
break;
|
|
75
|
+
case '--percent':
|
|
76
|
+
out.percentOff = parseInt(next, 10);
|
|
77
|
+
i++;
|
|
78
|
+
break;
|
|
79
|
+
case '--campaign':
|
|
80
|
+
out.campaign = next;
|
|
81
|
+
i++;
|
|
82
|
+
break;
|
|
83
|
+
case '--products':
|
|
84
|
+
if (!next)
|
|
85
|
+
throw new Error('--products requires a value');
|
|
86
|
+
out.productKeys = next.split(',').map((s) => s.trim()).filter(Boolean);
|
|
87
|
+
i++;
|
|
88
|
+
break;
|
|
89
|
+
case '--starts':
|
|
90
|
+
out.startsAt = toIso(next);
|
|
91
|
+
i++;
|
|
92
|
+
break;
|
|
93
|
+
case '--ends':
|
|
94
|
+
out.endsAt = toIso(next);
|
|
95
|
+
i++;
|
|
96
|
+
break;
|
|
97
|
+
case '--env':
|
|
98
|
+
out.env = next;
|
|
99
|
+
i++;
|
|
100
|
+
break;
|
|
101
|
+
case '--yes':
|
|
102
|
+
out.yes = true;
|
|
103
|
+
break;
|
|
104
|
+
default: throw new Error(`Unknown arg: ${a}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (out.count === undefined || isNaN(out.count))
|
|
108
|
+
throw new Error('--count required (1-1000)');
|
|
109
|
+
if (out.percentOff === undefined || isNaN(out.percentOff))
|
|
110
|
+
throw new Error('--percent required (1-100)');
|
|
111
|
+
if (!out.campaign)
|
|
112
|
+
throw new Error('--campaign required');
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
async function runGenerate(argv) {
|
|
116
|
+
const args = parseArgs(argv);
|
|
117
|
+
(0, billing_http_1.validateEnv)(args.env);
|
|
118
|
+
await (0, confirm_prompt_1.confirmIfProd)(args.env, `Generate ${args.count} unique discount codes (${args.percentOff}% off, campaign ${args.campaign})`, args.yes);
|
|
119
|
+
const body = { count: args.count, percentOff: args.percentOff, campaign: args.campaign };
|
|
120
|
+
if (args.productKeys)
|
|
121
|
+
body.productKeys = args.productKeys;
|
|
122
|
+
if (args.startsAt)
|
|
123
|
+
body.startsAt = args.startsAt;
|
|
124
|
+
if (args.endsAt)
|
|
125
|
+
body.endsAt = args.endsAt;
|
|
126
|
+
console.log(`\n🎟️ Generating ${args.count} codes on ${args.env.toUpperCase()}...`);
|
|
127
|
+
const res = await (0, billing_http_1.callBilling)(args.env, 'POST', '/api/billing/admin/discount-codes/batch', body);
|
|
128
|
+
const codes = res.body.codes ?? [];
|
|
129
|
+
const safeCampaign = args.campaign.replace(/[^A-Za-z0-9_-]/g, '');
|
|
130
|
+
const file = `./discount-codes-${safeCampaign}-${Date.now()}.txt`;
|
|
131
|
+
// mode 0o600: single-use codes are sensitive-ish; written to the operator's CWD.
|
|
132
|
+
fs.writeFileSync(file, codes.join('\n') + '\n', { encoding: 'utf-8', mode: 0o600 });
|
|
133
|
+
console.log(`✓ Generated ${codes.length} codes (HTTP ${res.status}). Written to: ${file}`);
|
|
134
|
+
console.log(` (codes are in the file, not printed, to keep them copy-paste clean)`);
|
|
135
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runList = runList;
|
|
4
|
+
const billing_http_1 = require("../billing-http");
|
|
5
|
+
function parseArgs(argv) {
|
|
6
|
+
if (argv[0] === '-h' || argv[0] === '--help') {
|
|
7
|
+
console.log(`Usage: optima-discount list [options]
|
|
8
|
+
|
|
9
|
+
Optional:
|
|
10
|
+
--campaign <label> Filter by campaign
|
|
11
|
+
--code <CODE> Filter by exact code
|
|
12
|
+
--limit <N> Max rows (default 500, max 1000)
|
|
13
|
+
--env stage|prod (default: stage)`);
|
|
14
|
+
process.exit(0);
|
|
15
|
+
}
|
|
16
|
+
const out = { env: 'stage' };
|
|
17
|
+
for (let i = 0; i < argv.length; i++) {
|
|
18
|
+
const a = argv[i];
|
|
19
|
+
const next = argv[i + 1];
|
|
20
|
+
switch (a) {
|
|
21
|
+
case '--campaign':
|
|
22
|
+
out.campaign = next;
|
|
23
|
+
i++;
|
|
24
|
+
break;
|
|
25
|
+
case '--code':
|
|
26
|
+
out.code = next;
|
|
27
|
+
i++;
|
|
28
|
+
break;
|
|
29
|
+
case '--limit': {
|
|
30
|
+
const n = parseInt(next, 10);
|
|
31
|
+
if (isNaN(n))
|
|
32
|
+
throw new Error('--limit requires a number');
|
|
33
|
+
out.limit = n;
|
|
34
|
+
i++;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
case '--env':
|
|
38
|
+
out.env = next;
|
|
39
|
+
i++;
|
|
40
|
+
break;
|
|
41
|
+
default: throw new Error(`Unknown arg: ${a}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
async function runList(argv) {
|
|
47
|
+
const args = parseArgs(argv);
|
|
48
|
+
(0, billing_http_1.validateEnv)(args.env);
|
|
49
|
+
const qs = new URLSearchParams();
|
|
50
|
+
if (args.campaign)
|
|
51
|
+
qs.set('campaign', args.campaign);
|
|
52
|
+
if (args.code)
|
|
53
|
+
qs.set('code', args.code);
|
|
54
|
+
if (args.limit !== undefined)
|
|
55
|
+
qs.set('limit', String(args.limit));
|
|
56
|
+
const suffix = qs.toString() ? `?${qs.toString()}` : '';
|
|
57
|
+
const res = await (0, billing_http_1.callBilling)(args.env, 'GET', `/api/billing/admin/discount-codes${suffix}`);
|
|
58
|
+
console.log(JSON.stringify(res.body, null, 2));
|
|
59
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const create_1 = require("./discount/create");
|
|
5
|
+
const generate_1 = require("./discount/generate");
|
|
6
|
+
const list_1 = require("./discount/list");
|
|
7
|
+
const disable_1 = require("./discount/disable");
|
|
8
|
+
function printHelp() {
|
|
9
|
+
console.log(`Usage: optima-discount <subcommand> [options]
|
|
10
|
+
|
|
11
|
+
Subcommands:
|
|
12
|
+
create Create one discount code (shared or single-use)
|
|
13
|
+
generate Generate N unique single-use codes (written to a file)
|
|
14
|
+
list List discount codes (filter by campaign/code)
|
|
15
|
+
disable Disable a discount code
|
|
16
|
+
|
|
17
|
+
Run 'optima-discount <subcommand> --help' for subcommand-specific options.`);
|
|
18
|
+
}
|
|
19
|
+
async function main() {
|
|
20
|
+
const [, , subcommand, ...rest] = process.argv;
|
|
21
|
+
if (!subcommand || subcommand === '-h' || subcommand === '--help') {
|
|
22
|
+
printHelp();
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
switch (subcommand) {
|
|
26
|
+
case 'create':
|
|
27
|
+
await (0, create_1.runCreate)(rest);
|
|
28
|
+
break;
|
|
29
|
+
case 'generate':
|
|
30
|
+
await (0, generate_1.runGenerate)(rest);
|
|
31
|
+
break;
|
|
32
|
+
case 'list':
|
|
33
|
+
await (0, list_1.runList)(rest);
|
|
34
|
+
break;
|
|
35
|
+
case 'disable':
|
|
36
|
+
await (0, disable_1.runDisable)(rest);
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
console.error(`Unknown subcommand: ${subcommand}`);
|
|
40
|
+
printHelp();
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
main().catch((err) => {
|
|
45
|
+
console.error(err.message);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runSetDefault = runSetDefault;
|
|
4
|
+
const billing_http_1 = require("../billing-http");
|
|
5
|
+
const confirm_prompt_1 = require("../confirm-prompt");
|
|
6
|
+
function parseArgs(argv) {
|
|
7
|
+
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
8
|
+
console.log(`Usage: optima-plugin set-default --slug <slug> --default true|false [options]
|
|
9
|
+
|
|
10
|
+
Required:
|
|
11
|
+
--slug <slug>
|
|
12
|
+
--default true|false Sets Plugin.defaultForUser
|
|
13
|
+
|
|
14
|
+
Optional:
|
|
15
|
+
--yes Skip prod confirmation prompt (no-op on stage)
|
|
16
|
+
--env stage|prod (default: stage)
|
|
17
|
+
|
|
18
|
+
Note: no skill-sync broadcast — changes what NEW user syncs receive; does not
|
|
19
|
+
retroactively add/remove the plugin for existing users until their next sync.`);
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
const out = { env: 'stage', yes: false };
|
|
23
|
+
for (let i = 0; i < argv.length; i++) {
|
|
24
|
+
const a = argv[i];
|
|
25
|
+
const next = argv[i + 1];
|
|
26
|
+
switch (a) {
|
|
27
|
+
case '--slug':
|
|
28
|
+
out.slug = next;
|
|
29
|
+
i++;
|
|
30
|
+
break;
|
|
31
|
+
case '--default':
|
|
32
|
+
if (next !== 'true' && next !== 'false')
|
|
33
|
+
throw new Error('--default must be true or false');
|
|
34
|
+
out.default = next === 'true';
|
|
35
|
+
i++;
|
|
36
|
+
break;
|
|
37
|
+
case '--yes':
|
|
38
|
+
out.yes = true;
|
|
39
|
+
break;
|
|
40
|
+
case '--env':
|
|
41
|
+
out.env = next;
|
|
42
|
+
i++;
|
|
43
|
+
break;
|
|
44
|
+
default: throw new Error(`Unknown arg: ${a}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!out.slug)
|
|
48
|
+
throw new Error('--slug required');
|
|
49
|
+
if (out.default === undefined)
|
|
50
|
+
throw new Error('--default required (true|false)');
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
async function runSetDefault(argv) {
|
|
54
|
+
const args = parseArgs(argv);
|
|
55
|
+
(0, billing_http_1.validateEnv)(args.env);
|
|
56
|
+
await (0, confirm_prompt_1.confirmIfProd)(args.env, `Action: set defaultForUser=${args.default} on plugin '${args.slug}' (${args.env.toUpperCase()})`, args.yes);
|
|
57
|
+
console.log(`\n🔧 Setting defaultForUser=${args.default} on ${args.slug} (${args.env.toUpperCase()})...`);
|
|
58
|
+
const res = await (0, billing_http_1.callSkills)(args.env, 'PATCH', `/api/admin/plugins/${encodeURIComponent(args.slug)}`, { defaultForUser: args.default });
|
|
59
|
+
console.log(`✓ Updated plugin (HTTP ${res.status}):`);
|
|
60
|
+
console.log(JSON.stringify(res.body, null, 2));
|
|
61
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runSetPaid = runSetPaid;
|
|
4
|
+
const billing_http_1 = require("../billing-http");
|
|
5
|
+
const confirm_prompt_1 = require("../confirm-prompt");
|
|
6
|
+
function parseArgs(argv) {
|
|
7
|
+
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
8
|
+
console.log(`Usage: optima-plugin set-paid --slug <slug> --paid true|false [options]
|
|
9
|
+
|
|
10
|
+
Required:
|
|
11
|
+
--slug <slug>
|
|
12
|
+
--paid true|false Sets Plugin.isPaid (the user-facing paid/free gate)
|
|
13
|
+
|
|
14
|
+
Optional:
|
|
15
|
+
--yes Skip prod confirmation prompt (no-op on stage)
|
|
16
|
+
--env stage|prod (default: stage)
|
|
17
|
+
|
|
18
|
+
Note: salesUrl is NOT settable here (skills PATCH is strict; salesUrl is
|
|
19
|
+
publish-time-only via plugin.json metadata). When isPaid=true and salesUrl is
|
|
20
|
+
null, the 402 falls back to sales.optima.onl.`);
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
const out = { env: 'stage', yes: false };
|
|
24
|
+
for (let i = 0; i < argv.length; i++) {
|
|
25
|
+
const a = argv[i];
|
|
26
|
+
const next = argv[i + 1];
|
|
27
|
+
switch (a) {
|
|
28
|
+
case '--slug':
|
|
29
|
+
out.slug = next;
|
|
30
|
+
i++;
|
|
31
|
+
break;
|
|
32
|
+
case '--paid':
|
|
33
|
+
if (next !== 'true' && next !== 'false')
|
|
34
|
+
throw new Error('--paid must be true or false');
|
|
35
|
+
out.paid = next === 'true';
|
|
36
|
+
i++;
|
|
37
|
+
break;
|
|
38
|
+
case '--yes':
|
|
39
|
+
out.yes = true;
|
|
40
|
+
break;
|
|
41
|
+
case '--env':
|
|
42
|
+
out.env = next;
|
|
43
|
+
i++;
|
|
44
|
+
break;
|
|
45
|
+
default: throw new Error(`Unknown arg: ${a}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (!out.slug)
|
|
49
|
+
throw new Error('--slug required');
|
|
50
|
+
if (out.paid === undefined)
|
|
51
|
+
throw new Error('--paid required (true|false)');
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
async function runSetPaid(argv) {
|
|
55
|
+
const args = parseArgs(argv);
|
|
56
|
+
(0, billing_http_1.validateEnv)(args.env);
|
|
57
|
+
await (0, confirm_prompt_1.confirmIfProd)(args.env, `Action: set isPaid=${args.paid} on plugin '${args.slug}' (${args.env.toUpperCase()})`, args.yes);
|
|
58
|
+
console.log(`\n💰 Setting isPaid=${args.paid} on ${args.slug} (${args.env.toUpperCase()})...`);
|
|
59
|
+
const res = await (0, billing_http_1.callSkills)(args.env, 'PATCH', `/api/admin/plugins/${encodeURIComponent(args.slug)}`, { isPaid: args.paid });
|
|
60
|
+
console.log(`✓ Updated plugin (HTTP ${res.status}):`);
|
|
61
|
+
console.log(JSON.stringify(res.body, null, 2));
|
|
62
|
+
if (args.paid) {
|
|
63
|
+
console.log(`\nℹ️ Reminder: ensure a billing Product + channel exists for '${args.slug}' (optima-product) or users will 402 with no purchase path. salesUrl is publish-time-only (currently shown above).`);
|
|
64
|
+
}
|
|
65
|
+
}
|