feat: 增加账号退出利润情景核算

This commit is contained in:
Codex
2026-07-15 04:21:19 +02:00
parent c17bdf424e
commit d191852a2f
7 changed files with 254 additions and 8 deletions
+2 -1
View File
@@ -3,7 +3,8 @@ name: unidesk-sub2api
description: >-
UniDesk Sub2API 平台运维技能。用户提到 Sub2API、platform-infra sub2api、Codex pool、统一 API key、
runtime CRUD、精准批量账号操作、临时不可调度、上游错误率、客户可见错误、换号/failover、
模型映射、可用模型探测、FRP 暴露、管理 UI、配置 master ~/.codex、上游账号增删或校验 /v1/models 时使用。
模型映射、可用模型探测、利润核算、售价与账号退役情景、FRP 暴露、管理 UI、配置 master ~/.codex、
上游账号增删或校验 /v1/models 时使用。
---
# UniDesk Sub2API
@@ -109,6 +109,7 @@ bun scripts/cli.ts platform-infra sub2api codex-pool profit --target PK01 --sinc
bun scripts/cli.ts platform-infra sub2api codex-pool profit --target PK01 --since 24h --missing-costs
bun scripts/cli.ts platform-infra sub2api codex-pool profit --target PK01 --month 2026-07
bun scripts/cli.ts platform-infra sub2api codex-pool profit --target PK01 --month 2026-07 --forecast
bun scripts/cli.ts platform-infra sub2api codex-pool profit --target PK01 --month <YYYY-MM> --forecast --sale-rate <CNY/API_USD> --scenario-without-account '<exact-account-name>'
bun scripts/cli.ts platform-infra sub2api codex-pool credits grant --target PK01 --active-since 24h --amount-usd 20 --reason <text>
bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id <requestId>
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status --target D601
@@ -132,6 +133,12 @@ bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes --target D60
- `periodCosts` 声明按 `YYYY-MM` 入账的一次性人民币固定成本;`--month YYYY-MM` 使用原生 `/api/v1/admin/usage/stats` 按分组和账号聚合该月流水并一次扣除匹配期间成本,任意 `--since` 窗口继续读取逐条 usage 以保持滚动窗口精度且不分摊固定成本;
- 账号调度经济性使用边际采购倍率,不把已经发生的一次性期间成本摊回请求成本或用于压低账号优先级;月度净利润必须单列期间成本后再计算;
- `--month YYYY-MM --forecast` 以月初至当前的实际秒数计算线性 run-rate:收入、盈利池流量成本和自用流量成本按完整月份秒数外推,一次性 `periodCosts` 只扣一次;同时保留 MTD 实际汇总和两种整月预计利润,不能把预测值表述为已实现收益;
- 候选售价或账号退役评估必须在同一次利润查询中使用情景参数:
- `--sale-rate <CNY/API_USD>` 只覆盖本次只读核算售价,不修改 YAML;
- `--scenario-without-account <id-or-exact-name>` 可重复指定多个精确账号;CLI 自动移除绑定这些账号的期间成本,并把其盈利池和自用池流量分别按各自剩余账号的实际加权采购倍率重分配;
- 情景汇总直接披露理论利润上限、重分配后的两种利润、日均营业额与成本、账号流量占比、替代成本和剩余账号负载增幅;不要再使用 `--accounts` 分页后手工拼算;
- 账号 selector 不存在时必须失败;剩余账号存在未知成本、没有可用计费基数或仍有未分类流量时必须标记 `partial`,禁止输出伪精确利润;
- 负载增幅只表示容量需求,不等价于剩余账号能够稳定承接;退役建议还需结合账号质量、并发和客户可见错误评估;
- 默认 Kubernetes 风格输出优先给出总收入、已确认成本、扣除已知成本后的待定利润基数、未知成本对应的 API USD 基数、完整性和参数化利润公式;
- 显式 `--json` 返回同一份稳定紧凑汇总,不携带完整账号数组,避免大窗口触发全局输出保护;
- `--accounts` 固定返回一页完整账号名称、账号 ID、请求数、Token、标准 API 美元计费基数、人民币采购单价、人民币收入、人民币上游成本和人民币利润贡献;存在后续页时返回由分类、分组、账号 ID 和名称生成的 opaque `--page-token` 命令,不提供手工 `limit`
@@ -0,0 +1,69 @@
type JsonRecord = Record<string, unknown>;
export function renderProfitScenarioLines(scenario: JsonRecord): string[] {
if (Object.keys(scenario).length === 0) return [];
const theoretical = record(scenario.theoretical);
const redistributed = record(scenario.redistributed);
const costs = record(redistributed.costsCny);
const profit = record(redistributed.profitCny);
const daily = record(scenario.dailyAverage);
const dailyCosts = record(daily.costsCny);
const dailyProfit = record(daily.profitCny);
const lines = [
"",
`SCENARIO status=${text(scenario.status)} method=${text(scenario.method)} sale-rate=${text(scenario.saleRateCnyPerApiUsd)}CNY/API_USD scale=${text(scenario.scale)} removed-period-cost=${cny(scenario.removedPeriodCostCny)}`,
table(
["SCOPE", "REVENUE_CNY", "TRAFFIC_COST_CNY", "SELF_USE_COST_CNY", "PERIOD_COST_CNY", "THEORETICAL_CEILING_CNY", "REDISTRIBUTED_PROFIT_CNY", "DAILY_PROFIT_CNY"],
[
["不扣自用", cny(redistributed.revenueCny), cny(costs.profitPool), "-", cny(costs.period), cny(theoretical.withoutSelfUseProfitCny), cny(profit.withoutSelfUse), cny(dailyProfit.withoutSelfUse)],
["扣除自用", cny(redistributed.revenueCny), cny(costs.profitPool), cny(costs.selfUse), cny(costs.period), cny(theoretical.withSelfUseProfitCny), cny(profit.withSelfUse), cny(dailyProfit.withSelfUse)],
],
),
`DAILY days=${text(daily.days)} revenue=${cny(daily.revenueCny)} traffic-cost=${cny(dailyCosts.profitPool)} self-use-cost=${cny(dailyCosts.selfUse)} period-cost=${cny(dailyCosts.period)}`,
"SCENARIO ACCOUNTS",
];
const selected = records(scenario.selectedAccounts);
if (selected.length === 0) lines.push("- none; candidate sale-rate only");
for (const row of selected) {
lines.push(`- account=${text(row.accountName)} id=${text(row.accountId)} classes=${arrayText(row.classes)}`);
}
lines.push("REDISTRIBUTION");
for (const row of records(scenario.redistribution)) {
lines.push(`- class=${text(row.class)} selected-share=${percent(row.selectedSharePct)} remaining-rate=${text(row.remainingWeightedCostRateCnyPerApiUsd)} replacement-cost=${cny(row.replacementCostCny)} remaining-load-increase=${percent(row.remainingLoadIncreasePct)}`);
}
const warnings = Array.isArray(scenario.warnings) ? scenario.warnings : [];
for (const warning of warnings) lines.push(`WARNING ${text(warning)}`);
return lines;
}
function record(value: unknown): JsonRecord {
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as JsonRecord : {};
}
function records(value: unknown): JsonRecord[] {
return Array.isArray(value) ? value.filter((item): item is JsonRecord => typeof item === "object" && item !== null && !Array.isArray(item)) : [];
}
function text(value: unknown): string {
if (value === null || value === undefined || value === "") return "-";
return String(value).replace(/[\r\n\t]+/gu, " ");
}
function cny(value: unknown): string {
const numeric = Number(value);
return Number.isFinite(numeric) ? `¥${numeric.toFixed(6)}` : text(value);
}
function percent(value: unknown): string {
const numeric = Number(value);
return Number.isFinite(numeric) ? `${numeric.toFixed(2)}%` : text(value);
}
function arrayText(value: unknown): string {
return Array.isArray(value) ? value.map(text).join(",") : text(value);
}
function table(headers: string[], rows: string[][]): string {
const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length)));
return [headers, ...rows].map((row) => row.map((cell, index) => cell.padEnd(widths[index]!)).join(" ").trimEnd()).join("\n");
}
@@ -2,6 +2,7 @@ import type { UniDeskConfig } from "../config";
import type { RenderedCliResult } from "../output";
import { readCodexPoolConfig } from "./config";
import { renderProfitScenarioLines } from "./profit-scenario-render";
import { boolField, compactCapture, parseJsonOutput, runRemoteCodexPoolScript } from "./remote";
import { profitScript } from "./remote-scripts";
import { codexPoolRuntimeTarget, defaultCodexPoolRuntimeTargetId } from "./runtime-target";
@@ -15,6 +16,8 @@ interface ProfitOptions {
accounts: boolean;
missingCosts: boolean;
pageToken: string | null;
saleRateCnyPerApiUsd: string | null;
scenarioWithoutAccounts: string[];
}
const PROFIT_ACCOUNT_PAGE_SIZE = 8;
@@ -24,13 +27,23 @@ export async function codexPoolProfit(config: UniDeskConfig, args: string[]): Pr
if (args.includes("--help")) return renderProfitHelp(pool.profit.defaultWindow);
const options = parseProfitOptions(args, pool.profit.defaultWindow);
const target = codexPoolRuntimeTarget(options.targetId);
const payload = { since: options.since, month: options.month, forecast: options.forecast, config: pool.profit };
const scenario = options.saleRateCnyPerApiUsd !== null || options.scenarioWithoutAccounts.length > 0
? {
saleRateCnyPerApiUsd: options.saleRateCnyPerApiUsd ?? String(pool.profit.saleRateCnyPerApiUsd),
withoutAccounts: options.scenarioWithoutAccounts,
}
: null;
const payload = { since: options.since, month: options.month, forecast: options.forecast, config: pool.profit, scenario };
const remote = await runRemoteCodexPoolScript(config, "profit", profitScript(payload, pool, target), target);
const report = parseJsonOutput(remote.stdout);
const ok = remote.exitCode === 0 && boolField(report, "ok", false);
const projectedReport = projectProfitReport(report, options);
const windowOption = options.month === null ? `--since ${options.since}` : `--month ${options.month}${options.forecast ? " --forecast" : ""}`;
const baseCommand = `bun scripts/cli.ts platform-infra sub2api codex-pool profit --target ${target.id} ${windowOption}`;
const scenarioOption = [
options.saleRateCnyPerApiUsd === null ? null : `--sale-rate ${options.saleRateCnyPerApiUsd}`,
...options.scenarioWithoutAccounts.map((selector) => `--scenario-without-account ${shellArg(selector)}`),
].filter((item): item is string => item !== null).join(" ");
const baseCommand = `bun scripts/cli.ts platform-infra sub2api codex-pool profit --target ${target.id} ${windowOption}${scenarioOption ? ` ${scenarioOption}` : ""}`;
const accountPage = record(record(projectedReport).accountPage);
const response = {
ok,
@@ -66,6 +79,8 @@ function parseProfitOptions(args: string[], defaultWindow: string): ProfitOption
let accounts = false;
let missingCosts = false;
let pageToken: string | null = null;
let saleRateCnyPerApiUsd: string | null = null;
const scenarioWithoutAccounts: string[] = [];
const readValue = (index: number, option: string): [string, number] => {
const value = args[index + 1];
if (value === undefined || value.startsWith("--")) throw new Error(`${option} requires a value`);
@@ -77,6 +92,14 @@ function parseProfitOptions(args: string[], defaultWindow: string): ProfitOption
else if (arg === "--forecast") forecast = true;
else if (arg === "--accounts") accounts = true;
else if (arg === "--missing-costs") missingCosts = true;
else if (arg === "--sale-rate") {
[saleRateCnyPerApiUsd, index] = readValue(index, "--sale-rate");
} else if (arg.startsWith("--sale-rate=")) saleRateCnyPerApiUsd = arg.slice("--sale-rate=".length).trim();
else if (arg === "--scenario-without-account") {
let selector: string;
[selector, index] = readValue(index, "--scenario-without-account");
scenarioWithoutAccounts.push(selector);
} else if (arg.startsWith("--scenario-without-account=")) scenarioWithoutAccounts.push(arg.slice("--scenario-without-account=".length).trim());
else if (arg === "--month") {
[month, index] = readValue(index, "--month");
} else if (arg.startsWith("--month=")) month = arg.slice("--month=".length).trim();
@@ -99,7 +122,14 @@ function parseProfitOptions(args: string[], defaultWindow: string): ProfitOption
if (accounts && missingCosts) throw new Error("--accounts and --missing-costs are separate semantic disclosures");
if (pageToken !== null && !accounts) throw new Error("--page-token requires --accounts");
if (pageToken !== null && (!pageToken || /[\r\n\0\s]/u.test(pageToken))) throw new Error("--page-token has an unsupported format");
return { since, month, forecast, targetId, json, accounts, missingCosts, pageToken };
if (saleRateCnyPerApiUsd !== null && !/^(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/u.test(saleRateCnyPerApiUsd)) {
throw new Error("--sale-rate must be a non-negative decimal CNY per API USD value");
}
if (scenarioWithoutAccounts.some((selector) => !selector || /[\r\n\0]/u.test(selector))) {
throw new Error("--scenario-without-account requires an exact account ID or name");
}
const uniqueScenarioAccounts = [...new Set(scenarioWithoutAccounts)];
return { since, month, forecast, targetId, json, accounts, missingCosts, pageToken, saleRateCnyPerApiUsd, scenarioWithoutAccounts: uniqueScenarioAccounts };
}
function renderProfitHelp(defaultWindow: string): RenderedCliResult {
@@ -109,18 +139,21 @@ function renderProfitHelp(defaultWindow: string): RenderedCliResult {
renderedText: [
"SUB2API CODEX-POOL PROFIT",
"Usage:",
" bun scripts/cli.ts platform-infra sub2api codex-pool profit [--since 24h|--month YYYY-MM [--forecast]] [--target PK01] [--accounts [--page-token <token>]|--missing-costs] [--json]",
" bun scripts/cli.ts platform-infra sub2api codex-pool profit [--since 24h|--month YYYY-MM [--forecast]] [--target PK01] [--sale-rate <CNY/API_USD>] [--scenario-without-account <id-or-exact-name> ...] [--accounts [--page-token <token>]|--missing-costs] [--json]",
"Options:",
` --since <duration> Native usage window; YAML default is ${defaultWindow}.`,
" --month <YYYY-MM> Calendar-month usage plus YAML-declared one-time period costs.",
" --forecast Extrapolate month-to-date variable revenue and costs to month end; period costs remain one-time.",
" --target <id>",
" --sale-rate <value> Candidate sale price in CNY per API USD; does not change YAML.",
" --scenario-without-account <selector> Read-only retirement scenario; repeat for multiple exact account IDs or names.",
" --accounts Include one fixed-size account page with complete names and IDs.",
" --page-token <token> Continue from the opaque token returned by the previous account page.",
" --missing-costs Include the missing-cost account list and API USD bases.",
" --json Emit exact structured values.",
"Notes:",
" Default output is a compact summary; use --accounts or --missing-costs for semantic disclosure.",
" Retirement scenarios redistribute selected traffic at each class's remaining-account weighted cost rate and report load growth.",
" This command is read-only and does not change Sub2API runtime state.",
].join("\n"),
contentType: "text/plain",
@@ -165,6 +198,7 @@ function renderProfit(response: Record<string, unknown>): RenderedCliResult {
],
));
}
lines.push(...renderProfitScenarioLines(record(report.scenario)));
const periodCosts = arrayOfRecords(report.periodCosts);
if (periodCosts.length > 0) {
lines.push("", "PERIOD COSTS");
@@ -292,6 +326,10 @@ function text(value: unknown): string {
return String(value).replace(/[\r\n\t]+/gu, " ");
}
function shellArg(value: string): string {
return `'${value.replace(/'/gu, `'"'"'`)}'`;
}
function compactTokens(value: unknown): string {
const numeric = Number(value);
if (!Number.isFinite(numeric)) return text(value);
@@ -140,6 +140,135 @@ def profit_empty_account(group, group_class, account_id, account_name):
"apiAmount": profit_decimal(0),
}
def profit_build_scenario(spec, account_rows, period_costs, start_at, end_at, period_end_at, forecast, unclassified):
if not isinstance(spec, dict):
return None
selectors = [str(item).strip() for item in (spec.get("withoutAccounts") or []) if str(item).strip()]
selected_rows = [row for row in account_rows if str(row.get("accountId")) in selectors or str(row.get("accountName")) in selectors]
matched_selectors = {
selector for selector in selectors
if any(str(row.get("accountId")) == selector or str(row.get("accountName")) == selector for row in selected_rows)
}
unmatched = [selector for selector in selectors if selector not in matched_selectors]
if unmatched:
raise RuntimeError("profit scenario account selector not found: " + ", ".join(unmatched))
sale_rate = profit_decimal(spec.get("saleRateCnyPerApiUsd"))
if sale_rate < 0:
raise RuntimeError("profit scenario sale rate must be non-negative")
scale = profit_decimal(forecast.get("scale")) if isinstance(forecast, dict) else profit_decimal(1)
period_seconds = profit_decimal(forecast.get("fullPeriodSeconds")) if isinstance(forecast, dict) else profit_decimal((end_at - start_at).total_seconds())
period_days = period_seconds / profit_decimal(86400)
if period_days <= 0:
raise RuntimeError("profit scenario has no observed period")
selected_names = {str(row.get("accountName")) for row in selected_rows}
retained_period_costs = [item for item in period_costs if str(item.get("accountName")) not in selected_names]
retained_period_cost = sum((profit_decimal(item.get("amountCny")) for item in retained_period_costs), profit_decimal(0))
removed_period_cost = sum((profit_decimal(item.get("amountCny")) for item in period_costs), profit_decimal(0)) - retained_period_cost
warnings = []
complete = not unclassified
if unclassified:
warnings.append("unclassified traffic remains outside the scenario")
class_results = {}
for group_class in ("profit", "self-use"):
class_rows = [row for row in account_rows if row.get("class") == group_class]
chosen = [row for row in class_rows if row in selected_rows]
remaining = [row for row in class_rows if row not in selected_rows]
missing_remaining = [row for row in remaining if row.get("completeness") != "complete"]
total_basis = sum((profit_decimal(row.get("apiAmountUsd")) for row in class_rows), profit_decimal(0))
selected_basis = sum((profit_decimal(row.get("apiAmountUsd")) for row in chosen), profit_decimal(0))
remaining_basis = sum((profit_decimal(row.get("apiAmountUsd")) for row in remaining), profit_decimal(0))
remaining_cost = sum((profit_decimal(row.get("upstreamCostCny")) for row in remaining if row.get("upstreamCostCny") is not None), profit_decimal(0))
weighted_rate = remaining_cost / remaining_basis if remaining_basis > 0 else None
replacement_cost = selected_basis * weighted_rate if weighted_rate is not None else (profit_decimal(0) if selected_basis == 0 else None)
if missing_remaining:
complete = False
warnings.append(f"{group_class} remaining accounts have missing cost rates")
if selected_basis > 0 and remaining_basis <= 0:
complete = False
warnings.append(f"{group_class} has no remaining traffic basis for redistribution")
class_results[group_class] = {
"class": group_class,
"totalBasis": total_basis,
"selectedBasis": selected_basis,
"remainingBasis": remaining_basis,
"remainingCost": remaining_cost,
"weightedRate": weighted_rate,
"replacementCost": replacement_cost,
"selectedSharePct": selected_basis * profit_decimal(100) / total_basis if total_basis > 0 else profit_decimal(0),
"remainingLoadIncreasePct": selected_basis * profit_decimal(100) / remaining_basis if remaining_basis > 0 else None,
"missingRemaining": missing_remaining,
}
profit_class = class_results["profit"]
self_class = class_results["self-use"]
revenue = profit_class["totalBasis"] * sale_rate * scale
theoretical_profit_cost = profit_class["remainingCost"] * scale
theoretical_self_cost = self_class["remainingCost"] * scale
theoretical_without_self = revenue - theoretical_profit_cost - retained_period_cost
theoretical_with_self = theoretical_without_self - theoretical_self_cost
redistributed_profit_cost = theoretical_profit_cost + (profit_class["replacementCost"] * scale if profit_class["replacementCost"] is not None else profit_decimal(0))
redistributed_self_cost = theoretical_self_cost + (self_class["replacementCost"] * scale if self_class["replacementCost"] is not None else profit_decimal(0))
redistributed_without_self = revenue - redistributed_profit_cost - retained_period_cost
redistributed_with_self = redistributed_without_self - redistributed_self_cost
selected_accounts = {}
for row in selected_rows:
key = (str(row.get("accountId")), str(row.get("accountName")))
if key not in selected_accounts:
selected_accounts[key] = {"accountId": row.get("accountId"), "accountName": row.get("accountName"), "classes": []}
if row.get("class") not in selected_accounts[key]["classes"]:
selected_accounts[key]["classes"].append(row.get("class"))
redistribution = []
for item in (profit_class, self_class):
redistribution.append({
"class": item["class"],
"selectedApiUsd": profit_decimal_text(item["selectedBasis"] * scale),
"selectedSharePct": profit_decimal_text(item["selectedSharePct"]),
"remainingWeightedCostRateCnyPerApiUsd": profit_decimal_text(item["weightedRate"]) if item["weightedRate"] is not None else None,
"replacementCostCny": profit_decimal_text(item["replacementCost"] * scale) if item["replacementCost"] is not None else None,
"remainingLoadIncreasePct": profit_decimal_text(item["remainingLoadIncreasePct"]) if item["remainingLoadIncreasePct"] is not None else None,
"missingRemainingCostAccounts": [{"accountId": row.get("accountId"), "accountName": row.get("accountName")} for row in item["missingRemaining"]],
})
status = "complete" if complete else "partial"
return {
"status": status,
"method": "remaining-account-weighted-cost-redistribution",
"scale": profit_decimal_text(scale),
"saleRateCnyPerApiUsd": profit_decimal_text(sale_rate),
"selectedAccounts": list(selected_accounts.values()),
"removedPeriodCostCny": profit_decimal_text(removed_period_cost),
"retainedPeriodCostCny": profit_decimal_text(retained_period_cost),
"theoretical": {
"withoutSelfUseProfitCny": profit_decimal_text(theoretical_without_self) if complete else None,
"withSelfUseProfitCny": profit_decimal_text(theoretical_with_self) if complete else None,
},
"redistributed": {
"revenueCny": profit_decimal_text(revenue),
"costsCny": {
"profitPool": profit_decimal_text(redistributed_profit_cost) if complete else None,
"selfUse": profit_decimal_text(redistributed_self_cost) if complete else None,
"period": profit_decimal_text(retained_period_cost),
},
"profitCny": {
"withoutSelfUse": profit_decimal_text(redistributed_without_self) if complete else None,
"withSelfUse": profit_decimal_text(redistributed_with_self) if complete else None,
},
},
"dailyAverage": {
"days": profit_decimal_text(period_days),
"revenueCny": profit_decimal_text(revenue / period_days),
"costsCny": {
"profitPool": profit_decimal_text(redistributed_profit_cost / period_days) if complete else None,
"selfUse": profit_decimal_text(redistributed_self_cost / period_days) if complete else None,
"period": profit_decimal_text(retained_period_cost / period_days),
},
"profitCny": {
"withoutSelfUse": profit_decimal_text(redistributed_without_self / period_days) if complete else None,
"withSelfUse": profit_decimal_text(redistributed_with_self / period_days) if complete else None,
},
},
"redistribution": redistribution,
"warnings": warnings,
}
def run_profit_report():
payload = json.loads(base64.b64decode(PAYLOAD_B64).decode("utf-8")) if PAYLOAD_B64 else {}
config = payload.get("config") if isinstance(payload.get("config"), dict) else {}
@@ -268,6 +397,7 @@ def run_profit_report():
"withSelfUse": {"status": "complete" if with_complete else "partial"},
}
account_rows.sort(key=lambda row: (row["class"], row["groupName"], str(row["accountId"]), str(row["accountName"])))
scenario = profit_build_scenario(payload.get("scenario"), account_rows, period_costs, start_at, end_at, period_end_at, forecast, unclassified)
return {
"ok": True,
"mode": "profit",
@@ -278,6 +408,7 @@ def run_profit_report():
"groups": group_rows,
"accounts": account_rows,
"periodCosts": [{"id": item.get("id"), "period": item.get("period"), "accountName": item.get("accountName"), "amountCny": profit_decimal_text(profit_decimal(item.get("amountCny"))), "description": item.get("description")} for item in period_costs],
"scenario": scenario,
"summary": {
"revenueCny": profit_decimal_text(revenue),
"confirmedCostsCny": {
@@ -400,7 +400,7 @@ export function codexPoolHelp(): unknown {
"bun scripts/cli.ts platform-infra sub2api codex-pool runtime get --account <name-or-id> [--target PK01] [--json|--full|--raw]",
"bun scripts/cli.ts platform-infra sub2api codex-pool runtime errors [--group <name-or-id>|--all-groups] [--platform <name>] [--page-token <group-id>] [--account <name-or-id>] [--since 24h] [--tail 50000] [--target PK01] [--json|--full|--raw]",
"bun scripts/cli.ts platform-infra sub2api codex-pool faults [--level P0|P1|P2] [--group <name-or-id>] [--account <name-or-id>] [--model <model>] [--stream sync|stream] [--endpoint <path>] [--request-id <id>] [--page-token <token>] [--target PK01] [--json]",
"bun scripts/cli.ts platform-infra sub2api codex-pool profit [--since 24h|--month YYYY-MM [--forecast]] [--target PK01] [--accounts [--page-token <token>]|--missing-costs] [--json]",
"bun scripts/cli.ts platform-infra sub2api codex-pool profit [--since 24h|--month YYYY-MM [--forecast]] [--target PK01] [--sale-rate <CNY/API_USD>] [--scenario-without-account <id-or-exact-name> ...] [--accounts [--page-token <token>]|--missing-costs] [--json]",
"bun scripts/cli.ts platform-infra sub2api codex-pool credits grant (--active-since 24h|--users <id-or-email,...>) --amount-usd <amount> --reason <text> [--target PK01] [--confirm] [--json]",
"bun scripts/cli.ts platform-infra sub2api codex-pool error-passthrough list|apply|delete [--template <id>] [--target PK01] [--confirm] [--json]",
"bun scripts/cli.ts platform-infra sub2api codex-pool runtime apply --account <name-or-id> --template <id> [--target PK01] [--confirm]",
+2 -2
View File
@@ -424,7 +424,7 @@ export function platformInfraHelp(): unknown {
"bun scripts/cli.ts platform-infra sub2api codex-pool validate",
"bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id <requestId>",
"bun scripts/cli.ts platform-infra sub2api codex-pool feedback --user <email-or-id> [--window 30m] [--page-token <token>|--request-id <client-or-internal-id>] [--json]",
"bun scripts/cli.ts platform-infra sub2api codex-pool profit [--since 24h] [--target PK01] [--accounts [--page-token <token>]|--missing-costs] [--json]",
"bun scripts/cli.ts platform-infra sub2api codex-pool profit [--since 24h|--month YYYY-MM [--forecast]] [--target PK01] [--sale-rate <CNY/API_USD>] [--scenario-without-account <id-or-exact-name> ...] [--accounts [--page-token <token>]|--missing-costs] [--json]",
"bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status",
"bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-probe --account unidesk-codex-hy --confirm",
"bun scripts/cli.ts platform-infra sub2rank plan [--target NC01]",
@@ -508,7 +508,7 @@ export function platformInfraHelp(): unknown {
"bun scripts/cli.ts platform-infra sub2api codex-pool validate",
"bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id <requestId>",
"bun scripts/cli.ts platform-infra sub2api codex-pool feedback --user <email-or-id> [--window 30m] [--page-token <token>|--request-id <client-or-internal-id>] [--json]",
"bun scripts/cli.ts platform-infra sub2api codex-pool profit [--since 24h] [--target PK01] [--accounts [--page-token <token>]|--missing-costs] [--json]",
"bun scripts/cli.ts platform-infra sub2api codex-pool profit [--since 24h|--month YYYY-MM [--forecast]] [--target PK01] [--sale-rate <CNY/API_USD>] [--scenario-without-account <id-or-exact-name> ...] [--accounts [--page-token <token>]|--missing-costs] [--json]",
"bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status",
],
module: "scripts/src/platform-infra-sub2api-codex.ts",