Files
recap-relay/startos/actions/setTierPrices.ts
T

94 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { sdk } from '../sdk'
import { configFile } from '../file-models/config.json'
const { InputSpec, Value } = sdk
// Operator-set per-tier monthly subscription prices, in USD. Used by
// the dashboard to compute revenue and operating margin (Gemini cost
// already comes out of the audit log). Pure accounting — the relay
// itself does no billing.
const inputSpec = InputSpec.of({
core_price: Value.number({
name: 'Core (Free) — Monthly Price',
description:
'Monthly subscription price for the Core tier in USD. Typically $0 since Core is the free entry tier. Used by the dashboard to compute total revenue; leave at 0 unless you actually charge for Core.',
required: true,
default: 0,
min: 0,
max: 10_000,
integer: false,
step: 0.01,
units: 'USD',
placeholder: null,
}),
pro_price: Value.number({
name: 'Pro — Monthly Price',
description:
'Monthly subscription price for the Pro tier in USD. Should match what you actually charge Pro customers on the licensing side.',
required: true,
default: 5,
min: 0,
max: 10_000,
integer: false,
step: 0.01,
units: 'USD',
placeholder: null,
}),
max_price: Value.number({
name: 'Max — Monthly Price',
description:
'Monthly subscription price for the Max tier in USD. Should match what you actually charge Max customers on the licensing side.',
required: true,
default: 15,
min: 0,
max: 10_000,
integer: false,
step: 0.01,
units: 'USD',
placeholder: null,
}),
})
export const setTierPrices = sdk.Action.withInput(
'set-tier-prices',
async ({ effects }) => ({
name: 'Set Tier Prices (USD)',
description:
'Configure the monthly USD price you charge per tier. The dashboard uses these numbers to compute revenue and operating margin against Gemini API cost. Has no effect on actual billing — it is for the operators accounting view only.',
warning: null,
allowedStatuses: 'any',
group: 'Tiers',
visibility: 'enabled',
}),
inputSpec,
async ({ effects }) => {
const config = await configFile.read().once()
let parsed: any = {}
try {
parsed = JSON.parse(config?.relay_tier_prices_usd_json || '{}')
} catch {
parsed = {}
}
return {
core_price: typeof parsed?.core === 'number' ? parsed.core : 0,
pro_price: typeof parsed?.pro === 'number' ? parsed.pro : 5,
max_price: typeof parsed?.max === 'number' ? parsed.max : 15,
}
},
async ({ effects, input }) => {
const prices = {
core: Number(input.core_price ?? 0),
pro: Number(input.pro_price ?? 5),
max: Number(input.max_price ?? 15),
}
await configFile.merge(effects, {
relay_tier_prices_usd_json: JSON.stringify(prices),
})
return null
},
)