Merge pull request #3095 from twodogegg/docs/update-sub2api-admin-skill

docs: update Sub2API admin skill
This commit is contained in:
Wesley Liddick
2026-06-07 22:22:04 +08:00
committed by GitHub
3 changed files with 275 additions and 65 deletions
+14 -11
View File
@@ -1,17 +1,16 @@
---
name: sub2api-admin
description: Manage Sub2API admin APIs for accounts, groups, proxies, error passthrough rules, TLS fingerprint profiles, imports, exports, batch updates, and raw administrator API calls. Use when the user mentions Sub2API, admin API keys, account management, bulk account import/export, keeping or deleting accounts, refreshing accounts, clearing errors, CRS sync, or managing Sub2API backend settings through the admin API.
description: Manage Sub2API admin APIs for accounts, redeem codes, groups, proxies, error passthrough rules, TLS fingerprint profiles, imports, exports, batch updates, and raw administrator API calls. Use when the user mentions Sub2API, admin API keys, account management, redeem code management, recharge codes, invitation codes, bulk account import/export, keeping or deleting accounts, refreshing accounts, clearing errors, CRS sync, or managing Sub2API backend settings through the admin API.
---
# Sub2API Admin
Use the bundled CLI instead of ad hoc `curl`.
Use the bundled CLI instead of ad hoc `curl`. Run examples from this skill directory.
```bash
export SUB2API_BASE_URL='https://your-sub2api-host'
export SUB2API_ADMIN_API_KEY='<admin api key>'
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts list
node scripts/sub2api-admin.js accounts list
```
For all commands and payload examples, read [references/admin-cli.md](references/admin-cli.md).
@@ -27,13 +26,16 @@ For all commands and payload examples, read [references/admin-cli.md](references
## Common Commands
```bash
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts list --page-size 20
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts get 40
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts usage 40
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts set-schedulable 40 true
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts bulk-update --ids 40,39 --json '{"concurrency":10}'
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js error-rules list
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js tls-profiles list
node scripts/sub2api-admin.js accounts list --page-size 20
node scripts/sub2api-admin.js accounts get 40
node scripts/sub2api-admin.js accounts usage 40
node scripts/sub2api-admin.js accounts set-schedulable 40 true
node scripts/sub2api-admin.js accounts bulk-update --ids 40,39 --json '{"concurrency":10}'
node scripts/sub2api-admin.js redeem-codes list --page-size 20
node scripts/sub2api-admin.js redeem-codes generate --json '{"count":1,"type":"balance","value":10}' --idempotency-key redeem-$(date +%s)
node scripts/sub2api-admin.js redeem-codes create-and-redeem --json '{"code":"order_123","type":"balance","value":10,"user_id":123}' --idempotency-key order-123
node scripts/sub2api-admin.js error-rules list
node scripts/sub2api-admin.js tls-profiles list
```
## Safety Notes
@@ -41,4 +43,5 @@ node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js tls-profiles list
- Authentication uses only `x-api-key`.
- If the API returns `INVALID_ADMIN_KEY`, ask the user to regenerate the admin API key.
- `accounts export` includes credentials and tokens. Prefer `--file` and avoid printing exports in chat.
- Redeem code create/redeem commands should use `--idempotency-key` for payment or recharge workflows.
- For uncertain or newly added backend APIs, use `api <METHOD> <admin-path>` after a read-only check.
+116 -53
View File
@@ -11,8 +11,10 @@ export SUB2API_ADMIN_API_KEY='<admin api key>'
## CLI
以下命令都假设当前目录是这个 skill 目录。
```bash
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js <command>
node scripts/sub2api-admin.js <command>
```
## Accounts
@@ -20,41 +22,41 @@ node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js <command>
### 只读
```bash
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts list --page-size 20
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts list --search outlook --platform openai --type oauth --status active
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts get 40
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts usage 40
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts stats 40 --days 30
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts today-stats 40
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts batch-today-stats --ids 40,39
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts models 40
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts temp-unschedulable 40
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts antigravity-default-model-mapping
node scripts/sub2api-admin.js accounts list --page-size 20
node scripts/sub2api-admin.js accounts list --search outlook --platform openai --type oauth --status active
node scripts/sub2api-admin.js accounts get 40
node scripts/sub2api-admin.js accounts usage 40
node scripts/sub2api-admin.js accounts stats 40 --days 30
node scripts/sub2api-admin.js accounts today-stats 40
node scripts/sub2api-admin.js accounts batch-today-stats --ids 40,39
node scripts/sub2api-admin.js accounts models 40
node scripts/sub2api-admin.js accounts temp-unschedulable 40
node scripts/sub2api-admin.js accounts antigravity-default-model-mapping
```
`accounts export` 会包含账号凭据和 token,建议写入文件,不要直接刷屏:
```bash
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts export --ids 40,39 --file accounts-export.json
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts export --platform openai --type oauth --include-proxies false --file accounts-export.json
node scripts/sub2api-admin.js accounts export --ids 40,39 --file accounts-export.json
node scripts/sub2api-admin.js accounts export --platform openai --type oauth --include-proxies false --file accounts-export.json
```
### 单账号写入
```bash
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts create --file account.json
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts update 40 --json '{"concurrency":20}'
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts set-status 40 active
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts set-schedulable 40 true
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts clear-error 40
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts clear-rate-limit 40
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts recover-state 40
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts reset-quota 40
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts refresh 40
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts test 40
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts sync-models 40
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts apply-oauth 40 --file credentials.json
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts reset-temp-unschedulable 40
node scripts/sub2api-admin.js accounts create --file account.json
node scripts/sub2api-admin.js accounts update 40 --json '{"concurrency":20}'
node scripts/sub2api-admin.js accounts set-status 40 active
node scripts/sub2api-admin.js accounts set-schedulable 40 true
node scripts/sub2api-admin.js accounts clear-error 40
node scripts/sub2api-admin.js accounts clear-rate-limit 40
node scripts/sub2api-admin.js accounts recover-state 40
node scripts/sub2api-admin.js accounts reset-quota 40
node scripts/sub2api-admin.js accounts refresh 40
node scripts/sub2api-admin.js accounts test 40
node scripts/sub2api-admin.js accounts sync-models 40
node scripts/sub2api-admin.js accounts apply-oauth 40 --file credentials.json
node scripts/sub2api-admin.js accounts reset-temp-unschedulable 40
```
### 删除与清理
@@ -62,18 +64,18 @@ node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts reset-temp-
删除前先列出目标账号名和 ID。
```bash
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts delete 25
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts keep-only --name 'target@example.com'
node scripts/sub2api-admin.js accounts delete 25
node scripts/sub2api-admin.js accounts keep-only --name 'target@example.com'
```
### 批量写入
```bash
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts batch-create --file accounts.json
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts batch-update-credentials --file payload.json
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts bulk-update --ids 40,39 --json '{"concurrency":10,"priority":2}'
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts batch-refresh --ids 40,39
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts batch-clear-error --ids 40,39
node scripts/sub2api-admin.js accounts batch-create --file accounts.json
node scripts/sub2api-admin.js accounts batch-update-credentials --file payload.json
node scripts/sub2api-admin.js accounts bulk-update --ids 40,39 --json '{"concurrency":10,"priority":2}'
node scripts/sub2api-admin.js accounts batch-refresh --ids 40,39
node scripts/sub2api-admin.js accounts batch-clear-error --ids 40,39
```
`bulk-update` 可覆盖页面“批量更新”的字段,payload 由后台表单字段决定,例如 `base_url``model_mapping``group_ids``proxy_id``concurrency``priority``rate_multiplier``status``compact_mode` 等。更新前先用 `accounts get <id>` 确认字段名。
@@ -83,21 +85,21 @@ node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts batch-clear
通用后台导入:
```bash
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts import-data --file accounts-export.json
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts import-codex-session --file payload.json
node scripts/sub2api-admin.js accounts import-data --file accounts-export.json
node scripts/sub2api-admin.js accounts import-codex-session --file payload.json
```
CRS 同步:
```bash
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts crs-preview --file payload.json
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts crs-sync --file payload.json
node scripts/sub2api-admin.js accounts crs-preview --file payload.json
node scripts/sub2api-admin.js accounts crs-sync --file payload.json
```
旧版 JSON 导入仍可用,会把模板账号的配置复制给导入账号:
```bash
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts import-json \
node scripts/sub2api-admin.js accounts import-json \
--file /path/accounts.json \
--template-name 'template@example.com' \
--dry-run
@@ -113,8 +115,59 @@ node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js accounts import-json
## Groups And Proxies
```bash
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js groups all
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js proxies all
node scripts/sub2api-admin.js groups all
node scripts/sub2api-admin.js proxies all
```
## Redeem Codes
兑换码类型包括 `balance``concurrency``subscription``invitation`。状态常用 `unused``used``expired`
### 只读
```bash
node scripts/sub2api-admin.js redeem-codes list --page-size 20
node scripts/sub2api-admin.js redeem-codes list --type balance --status unused --search user@example.com
node scripts/sub2api-admin.js redeem-codes get 123
node scripts/sub2api-admin.js redeem-codes stats
node scripts/sub2api-admin.js redeem-codes export --file redeem-codes.csv
```
### 生成兑换码
```bash
node scripts/sub2api-admin.js redeem-codes generate \
--json '{"count":1,"type":"balance","value":10}' \
--idempotency-key "redeem-generate-$(date +%s)"
```
订阅兑换码需要 `group_id` 和非零 `validity_days`
```bash
node scripts/sub2api-admin.js redeem-codes generate \
--json '{"count":1,"type":"subscription","value":0,"group_id":2,"validity_days":30}' \
--idempotency-key "redeem-subscription-$(date +%s)"
```
### 创建并兑换
用于支付回调或人工充值,一步完成创建兑换码并兑换到用户。生产流程必须传稳定的 `--idempotency-key`
```bash
node scripts/sub2api-admin.js redeem-codes create-and-redeem \
--json '{"code":"order_123","type":"balance","value":10,"user_id":123,"notes":"manual recharge"}' \
--idempotency-key order-123
```
### 修改与清理
写入前先 `list``get` 核对目标 ID。
```bash
node scripts/sub2api-admin.js redeem-codes batch-update --ids 123,124 --json '{"notes":"campaign A"}'
node scripts/sub2api-admin.js redeem-codes expire 123
node scripts/sub2api-admin.js redeem-codes delete 123
node scripts/sub2api-admin.js redeem-codes batch-delete --ids 123,124
```
## Error Rules And TLS Profiles
@@ -122,18 +175,18 @@ node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js proxies all
对应账号页顶部“错误透传规则”和“TLS 指纹模板”。
```bash
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js error-rules list
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js error-rules get 1
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js error-rules create --file rule.json
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js error-rules update 1 --json '{"enabled":true}'
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js error-rules toggle 1 false
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js error-rules delete 1
node scripts/sub2api-admin.js error-rules list
node scripts/sub2api-admin.js error-rules get 1
node scripts/sub2api-admin.js error-rules create --file rule.json
node scripts/sub2api-admin.js error-rules update 1 --json '{"enabled":true}'
node scripts/sub2api-admin.js error-rules toggle 1 false
node scripts/sub2api-admin.js error-rules delete 1
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js tls-profiles list
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js tls-profiles get 1
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js tls-profiles create --file profile.json
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js tls-profiles update 1 --file profile.json
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js tls-profiles delete 1
node scripts/sub2api-admin.js tls-profiles list
node scripts/sub2api-admin.js tls-profiles get 1
node scripts/sub2api-admin.js tls-profiles create --file profile.json
node scripts/sub2api-admin.js tls-profiles update 1 --file profile.json
node scripts/sub2api-admin.js tls-profiles delete 1
```
## Raw Admin API
@@ -141,8 +194,8 @@ node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js tls-profiles delete
未封装或新版本后台接口可用 `api` 直通。路径可写 `/admin/...``/api/v1/admin/...`
```bash
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js api GET /admin/groups/all
node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js api POST /admin/accounts/bulk-update \
node scripts/sub2api-admin.js api GET /admin/groups/all
node scripts/sub2api-admin.js api POST /admin/accounts/bulk-update \
--json '{"account_ids":[40],"concurrency":10}'
```
@@ -183,6 +236,16 @@ node ~/.codex/skills/sub2api-admin/scripts/sub2api-admin.js api POST /admin/acco
- `GET /api/v1/admin/accounts/antigravity/default-model-mapping`
- `GET /api/v1/admin/groups/all`
- `GET /api/v1/admin/proxies/all`
- `GET /api/v1/admin/redeem-codes`
- `GET /api/v1/admin/redeem-codes/export`
- `GET /api/v1/admin/redeem-codes/stats`
- `GET /api/v1/admin/redeem-codes/:id`
- `POST /api/v1/admin/redeem-codes/generate`
- `POST /api/v1/admin/redeem-codes/create-and-redeem`
- `POST /api/v1/admin/redeem-codes/batch-update`
- `POST /api/v1/admin/redeem-codes/batch-delete`
- `POST /api/v1/admin/redeem-codes/:id/expire`
- `DELETE /api/v1/admin/redeem-codes/:id`
- `GET /api/v1/admin/error-passthrough-rules`
- `GET /api/v1/admin/error-passthrough-rules/:id`
- `POST /api/v1/admin/error-passthrough-rules`
+145 -1
View File
@@ -45,6 +45,16 @@ function usage() {
sub2api-admin.js accounts import-json --file <path> --template-name <name> [--skip-name <name>] [--dry-run]
sub2api-admin.js groups all
sub2api-admin.js proxies all
sub2api-admin.js redeem-codes list [--page-size 200] [--page N] [--type balance] [--status unused] [--search TEXT] [--sort-by id] [--sort-order desc]
sub2api-admin.js redeem-codes export [--file redeem-codes.csv] [list filters...]
sub2api-admin.js redeem-codes get <id>
sub2api-admin.js redeem-codes generate --json '{...}' | --file payload.json [--idempotency-key KEY]
sub2api-admin.js redeem-codes create-and-redeem --json '{...}' | --file payload.json [--idempotency-key KEY]
sub2api-admin.js redeem-codes batch-update --ids 1,2 --json '{...}' | --file fields.json
sub2api-admin.js redeem-codes delete <id>
sub2api-admin.js redeem-codes batch-delete --ids 1,2
sub2api-admin.js redeem-codes expire <id>
sub2api-admin.js redeem-codes stats
sub2api-admin.js error-rules list|get|create|update|delete|toggle ...
sub2api-admin.js tls-profiles list|get|create|update|delete ...
sub2api-admin.js api <GET|POST|PUT|DELETE> <admin-path> [--json '{...}' | --file payload.json]
@@ -84,10 +94,11 @@ function authHeaders() {
throw new Error("Missing SUB2API_ADMIN_API_KEY");
}
async function apiRequest(method, pathname, body) {
async function apiRequest(method, pathname, body, extraHeaders = {}) {
const headers = {
...authHeaders(),
Accept: "application/json",
...extraHeaders,
};
const options = { method, headers };
if (body !== undefined) {
@@ -109,6 +120,32 @@ async function apiRequest(method, pathname, body) {
return data.data;
}
async function apiRawRequest(method, pathname, body, extraHeaders = {}) {
const headers = {
...authHeaders(),
Accept: "*/*",
...extraHeaders,
};
const options = { method, headers };
if (body !== undefined) {
headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const res = await fetch(`${BASE_URL}${pathname}`, options);
const text = await res.text();
if (!res.ok) {
let detail = res.statusText;
try {
const data = JSON.parse(text);
detail = data.message || data.code || detail;
} catch {
if (text) detail = text;
}
throw new Error(`${method} ${pathname} failed: ${detail}`);
}
return text;
}
function encodeQuery(params) {
const pairs = [];
for (const [key, value] of Object.entries(params)) {
@@ -129,6 +166,14 @@ async function adminRequest(method, adminPath, body) {
return apiRequest(method, normalizeAdminPath(adminPath), body);
}
async function adminRequestWithHeaders(method, adminPath, body, headers = {}) {
return apiRequest(method, normalizeAdminPath(adminPath), body, headers);
}
async function adminRawRequest(method, adminPath, body, headers = {}) {
return apiRawRequest(method, normalizeAdminPath(adminPath), body, headers);
}
async function listAccounts(options = {}) {
const params = {
page: options.page || 1,
@@ -201,6 +246,36 @@ function accountListOptions(flags) {
};
}
function redeemCodesListOptions(flags) {
return {
page: Number(flags.page || 1),
pageSize: Number(flags["page-size"] || 200),
type: flags.type,
status: flags.status,
search: flags.search,
sortBy: flags["sort-by"] || "id",
sortOrder: flags["sort-order"] || "desc",
};
}
function redeemCodesQuery(flags) {
const options = redeemCodesListOptions(flags);
return encodeQuery({
page: options.page,
page_size: options.pageSize,
type: options.type,
status: options.status,
search: options.search,
sort_by: options.sortBy,
sort_order: options.sortOrder,
});
}
function idempotencyHeaders(flags) {
if (!flags["idempotency-key"]) return {};
return { "Idempotency-Key": flags["idempotency-key"] };
}
function printJson(data) {
console.log(JSON.stringify(data, null, 2));
}
@@ -530,6 +605,71 @@ async function commandProxies(args) {
throw new Error(`unknown proxies subcommand: ${sub || "(missing)"}`);
}
async function commandRedeemCodes(args) {
const sub = args.positional[1];
if (sub === "list") {
printJson(await adminRequest("GET", `/admin/redeem-codes${redeemCodesQuery(args.flags)}`));
return;
}
if (sub === "export") {
const csv = await adminRawRequest("GET", `/admin/redeem-codes/export${redeemCodesQuery(args.flags)}`);
if (args.flags.file) {
fs.writeFileSync(path.resolve(args.flags.file), csv);
printJson({ file: path.resolve(args.flags.file) });
} else {
process.stdout.write(csv);
}
return;
}
if (sub === "get") {
const id = args.positional[2];
if (!id) throw new Error("redeem-codes get requires <id>");
printJson(await adminRequest("GET", `/admin/redeem-codes/${id}`));
return;
}
if (sub === "generate") {
printJson(await adminRequestWithHeaders("POST", "/admin/redeem-codes/generate", readJsonPayload(args.flags), idempotencyHeaders(args.flags)));
return;
}
if (sub === "create-and-redeem") {
printJson(await adminRequestWithHeaders("POST", "/admin/redeem-codes/create-and-redeem", readJsonPayload(args.flags), idempotencyHeaders(args.flags)));
return;
}
if (sub === "batch-update") {
const fields = readJsonPayload(args.flags, { required: false }) || {};
const payload = args.flags.ids ? { ids: parseIds(args.flags.ids), fields } : fields;
if (!Array.isArray(payload.ids) || payload.ids.length === 0) {
throw new Error("redeem-codes batch-update requires --ids or payload.ids");
}
if (!payload.fields || typeof payload.fields !== "object") {
throw new Error("redeem-codes batch-update requires fields");
}
printJson(await adminRequest("POST", "/admin/redeem-codes/batch-update", payload));
return;
}
if (sub === "delete") {
const id = args.positional[2];
if (!id) throw new Error("redeem-codes delete requires <id>");
printJson(await adminRequest("DELETE", `/admin/redeem-codes/${id}`));
return;
}
if (sub === "batch-delete") {
printJson(await adminRequest("POST", "/admin/redeem-codes/batch-delete", { ids: parseIds(args.flags.ids) }));
return;
}
if (sub === "expire") {
const id = args.positional[2];
if (!id) throw new Error("redeem-codes expire requires <id>");
printJson(await adminRequest("POST", `/admin/redeem-codes/${id}/expire`));
return;
}
if (sub === "stats") {
printJson(await adminRequest("GET", "/admin/redeem-codes/stats"));
return;
}
throw new Error(`unknown redeem-codes subcommand: ${sub || "(missing)"}`);
}
async function commandCrudResource(args, name, basePath, options = {}) {
const sub = args.positional[1];
const id = args.positional[2];
@@ -592,6 +732,10 @@ async function main() {
await commandProxies(args);
return;
}
if (root === "redeem-codes") {
await commandRedeemCodes(args);
return;
}
if (root === "error-rules") {
await commandCrudResource(args, "error-rules", "/admin/error-passthrough-rules", { toggle: true });
return;