chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
import { Database, eq } from "../src/drizzle/index.js"
import { BillingTable } from "../src/schema/billing.sql.js"
const workspaceID = process.argv[2]
if (!workspaceID) {
console.error("Usage: bun script/foo.ts <workspaceID>")
process.exit(1)
}
console.log(`Removing from Black waitlist`)
const billing = await Database.use((tx) =>
tx
.select({
subscriptionPlan: BillingTable.subscriptionPlan,
timeSubscriptionBooked: BillingTable.timeSubscriptionBooked,
})
.from(BillingTable)
.where(eq(BillingTable.workspaceID, workspaceID))
.then((rows) => rows[0]),
)
if (!billing?.timeSubscriptionBooked) {
console.error(`Error: Workspace is not on the waitlist`)
process.exit(1)
}
await Database.use((tx) =>
tx
.update(BillingTable)
.set({
subscriptionPlan: null,
timeSubscriptionBooked: null,
})
.where(eq(BillingTable.workspaceID, workspaceID)),
)
console.log(`Done`)

View File

@@ -0,0 +1,115 @@
import { Billing } from "../src/billing.js"
import { and, Database, eq, isNull } from "../src/drizzle/index.js"
import { UserTable } from "../src/schema/user.sql.js"
import { BillingTable, SubscriptionTable } from "../src/schema/billing.sql.js"
import { Identifier } from "../src/identifier.js"
import { AuthTable } from "../src/schema/auth.sql.js"
import { BlackData } from "../src/black.js"
const plan = "200"
const couponID = "JAIr0Pe1"
const workspaceID = process.argv[2]
const seats = parseInt(process.argv[3])
console.log(`Gifting ${seats} seats of Black to workspace ${workspaceID}`)
if (!workspaceID || !seats) throw new Error("Usage: bun foo.ts <workspaceID> <seats>")
// Get workspace user
const users = await Database.use((tx) =>
tx
.select({
id: UserTable.id,
role: UserTable.role,
email: AuthTable.subject,
})
.from(UserTable)
.leftJoin(AuthTable, and(eq(AuthTable.accountID, UserTable.accountID), eq(AuthTable.provider, "email")))
.where(and(eq(UserTable.workspaceID, workspaceID), isNull(UserTable.timeDeleted))),
)
if (users.length === 0) throw new Error(`Error: No users found in workspace ${workspaceID}`)
if (users.length !== seats)
throw new Error(`Error: Workspace ${workspaceID} has ${users.length} users, expected ${seats}`)
const adminUser = users.find((user) => user.role === "admin")
if (!adminUser) throw new Error(`Error: No admin user found in workspace ${workspaceID}`)
if (!adminUser.email) throw new Error(`Error: Admin user ${adminUser.id} has no email`)
// Get Billing
const billing = await Database.use((tx) =>
tx
.select({
customerID: BillingTable.customerID,
subscriptionID: BillingTable.subscriptionID,
})
.from(BillingTable)
.where(eq(BillingTable.workspaceID, workspaceID))
.then((rows) => rows[0]),
)
if (!billing) throw new Error(`Error: Workspace ${workspaceID} has no billing record`)
if (billing.subscriptionID) throw new Error(`Error: Workspace ${workspaceID} already has a subscription`)
// Look up the Stripe customer by email
const customerID =
billing.customerID ??
(await (() =>
Billing.stripe()
.customers.create({
email: adminUser.email,
metadata: {
workspaceID,
},
})
.then((customer) => customer.id))())
console.log(`Customer ID: ${customerID}`)
const subscription = await Billing.stripe().subscriptions.create({
customer: customerID!,
items: [
{
price: BlackData.planToPriceID({ plan }),
discounts: [{ coupon: couponID }],
quantity: seats,
},
],
metadata: {
workspaceID,
},
})
console.log(`Subscription ID: ${subscription.id}`)
await Database.transaction(async (tx) => {
// Set customer id, subscription id, and payment method on workspace billing
await tx
.update(BillingTable)
.set({
customerID,
subscriptionID: subscription.id,
subscription: { status: "subscribed", coupon: couponID, seats, plan },
})
.where(eq(BillingTable.workspaceID, workspaceID))
// Create a row in subscription table
for (const user of users) {
await tx.insert(SubscriptionTable).values({
workspaceID,
id: Identifier.create("subscription"),
userID: user.id,
})
}
//
// // Create a row in payments table
// await tx.insert(PaymentTable).values({
// workspaceID,
// id: Identifier.create("payment"),
// amount: centsToMicroCents(amountInCents),
// customerID,
// invoiceID,
// paymentID,
// enrichment: {
// type: "subscription",
// couponID,
// },
// })
})
console.log(`done`)

View File

@@ -0,0 +1,38 @@
import { Database, eq } from "../src/drizzle/index.js"
import { BillingTable } from "../src/schema/billing.sql.js"
const workspaceID = process.argv[2]
if (!workspaceID) {
console.error("Usage: bun script/foo.ts <workspaceID>")
process.exit(1)
}
console.log(`Onboarding to Black waitlist`)
const billing = await Database.use((tx) =>
tx
.select({
subscriptionPlan: BillingTable.subscriptionPlan,
timeSubscriptionBooked: BillingTable.timeSubscriptionBooked,
})
.from(BillingTable)
.where(eq(BillingTable.workspaceID, workspaceID))
.then((rows) => rows[0]),
)
if (!billing?.timeSubscriptionBooked) {
console.error(`Error: Workspace is not on the waitlist`)
process.exit(1)
}
await Database.use((tx) =>
tx
.update(BillingTable)
.set({
timeSubscriptionSelected: new Date(),
})
.where(eq(BillingTable.workspaceID, workspaceID)),
)
console.log(`Done`)

View File

@@ -0,0 +1,41 @@
import { Database, eq, and, sql, inArray, isNull } from "../src/drizzle/index.js"
import { BillingTable, BlackPlans } from "../src/schema/billing.sql.js"
import { UserTable } from "../src/schema/user.sql.js"
import { AuthTable } from "../src/schema/auth.sql.js"
const plan = process.argv[2] as (typeof BlackPlans)[number]
if (!BlackPlans.includes(plan)) {
console.error("Usage: bun foo.ts <count>")
process.exit(1)
}
const workspaces = await Database.use((tx) =>
tx
.select({ workspaceID: BillingTable.workspaceID })
.from(BillingTable)
.where(and(eq(BillingTable.subscriptionPlan, plan), isNull(BillingTable.timeSubscriptionSelected)))
.orderBy(sql`RAND()`)
.limit(100),
)
console.log(`Found ${workspaces.length} workspaces on Black ${plan} waitlist`)
console.log("== Workspace IDs ==")
const ids = workspaces.map((w) => w.workspaceID)
for (const id of ids) {
console.log(id)
}
console.log("\n== User Emails ==")
const emails = await Database.use((tx) =>
tx
.select({ email: AuthTable.subject })
.from(UserTable)
.innerJoin(AuthTable, and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email")))
.where(inArray(UserTable.workspaceID, ids)),
)
const unique = new Set(emails.map((row) => row.email))
for (const email of unique) {
console.log(email)
}

View File

@@ -0,0 +1,312 @@
import { Database, and, eq, inArray, isNotNull, sql } from "../src/drizzle/index.js"
import { BillingTable, BlackPlans, SubscriptionTable, UsageTable } from "../src/schema/billing.sql.js"
if (process.argv.length < 3) {
console.error("Usage: bun black-stats.ts <plan>")
process.exit(1)
}
const plan = process.argv[2] as (typeof BlackPlans)[number]
if (!BlackPlans.includes(plan)) {
console.error("Usage: bun black-stats.ts <plan>")
process.exit(1)
}
const cutoff = new Date(Date.UTC(2026, 1, 0, 23, 59, 59, 999))
// get workspaces
const workspaces = await Database.use((tx) =>
tx
.select({ workspaceID: BillingTable.workspaceID })
.from(BillingTable)
.where(
and(isNotNull(BillingTable.subscriptionID), sql`JSON_UNQUOTE(JSON_EXTRACT(subscription, '$.plan')) = ${plan}`),
),
)
if (workspaces.length === 0) throw new Error(`No active Black ${plan} subscriptions found`)
const week = sql<number>`YEARWEEK(${UsageTable.timeCreated}, 3)`
const workspaceIDs = workspaces.map((row) => row.workspaceID)
// Get subscription spend
const spend = await Database.use((tx) =>
tx
.select({
workspaceID: UsageTable.workspaceID,
week,
amount: sql<number>`COALESCE(SUM(${UsageTable.cost}), 0)`,
})
.from(UsageTable)
.where(
and(inArray(UsageTable.workspaceID, workspaceIDs), sql`JSON_UNQUOTE(JSON_EXTRACT(enrichment, '$.plan')) = 'sub'`),
)
.groupBy(UsageTable.workspaceID, week),
)
// Get pay per use spend
const ppu = await Database.use((tx) =>
tx
.select({
workspaceID: UsageTable.workspaceID,
week,
amount: sql<number>`COALESCE(SUM(${UsageTable.cost}), 0)`,
})
.from(UsageTable)
.where(
and(
inArray(UsageTable.workspaceID, workspaceIDs),
sql`(${UsageTable.enrichment} IS NULL OR JSON_UNQUOTE(JSON_EXTRACT(enrichment, '$.plan')) != 'sub')`,
),
)
.groupBy(UsageTable.workspaceID, week),
)
const models = await Database.use((tx) =>
tx
.select({
workspaceID: UsageTable.workspaceID,
model: UsageTable.model,
amount: sql<number>`COALESCE(SUM(${UsageTable.cost}), 0)`,
})
.from(UsageTable)
.where(
and(inArray(UsageTable.workspaceID, workspaceIDs), sql`JSON_UNQUOTE(JSON_EXTRACT(enrichment, '$.plan')) = 'sub'`),
)
.groupBy(UsageTable.workspaceID, UsageTable.model),
)
const tokens = await Database.use((tx) =>
tx
.select({
workspaceID: UsageTable.workspaceID,
week,
input: sql<number>`COALESCE(SUM(${UsageTable.inputTokens}), 0)`,
cacheRead: sql<number>`COALESCE(SUM(${UsageTable.cacheReadTokens}), 0)`,
output: sql<number>`COALESCE(SUM(${UsageTable.outputTokens}), 0) + COALESCE(SUM(${UsageTable.reasoningTokens}), 0)`,
})
.from(UsageTable)
.where(
and(inArray(UsageTable.workspaceID, workspaceIDs), sql`JSON_UNQUOTE(JSON_EXTRACT(enrichment, '$.plan')) = 'sub'`),
)
.groupBy(UsageTable.workspaceID, week),
)
const allWeeks = [...spend, ...ppu].map((row) => row.week)
const weeks = [...new Set(allWeeks)].sort((a, b) => a - b)
const spendMap = new Map<string, Map<number, number>>()
const totals = new Map<string, number>()
const ppuMap = new Map<string, Map<number, number>>()
const ppuTotals = new Map<string, number>()
const modelMap = new Map<string, { model: string; amount: number }[]>()
const tokenMap = new Map<string, Map<number, { input: number; cacheRead: number; output: number }>>()
for (const row of spend) {
const workspace = spendMap.get(row.workspaceID) ?? new Map<number, number>()
const total = totals.get(row.workspaceID) ?? 0
const amount = toNumber(row.amount)
workspace.set(row.week, amount)
totals.set(row.workspaceID, total + amount)
spendMap.set(row.workspaceID, workspace)
}
for (const row of ppu) {
const workspace = ppuMap.get(row.workspaceID) ?? new Map<number, number>()
const total = ppuTotals.get(row.workspaceID) ?? 0
const amount = toNumber(row.amount)
workspace.set(row.week, amount)
ppuTotals.set(row.workspaceID, total + amount)
ppuMap.set(row.workspaceID, workspace)
}
for (const row of models) {
const current = modelMap.get(row.workspaceID) ?? []
current.push({ model: row.model, amount: toNumber(row.amount) })
modelMap.set(row.workspaceID, current)
}
for (const row of tokens) {
const workspace = tokenMap.get(row.workspaceID) ?? new Map()
workspace.set(row.week, {
input: toNumber(row.input),
cacheRead: toNumber(row.cacheRead),
output: toNumber(row.output),
})
tokenMap.set(row.workspaceID, workspace)
}
const users = await Database.use((tx) =>
tx
.select({
workspaceID: SubscriptionTable.workspaceID,
subscribed: SubscriptionTable.timeCreated,
subscription: BillingTable.subscription,
})
.from(SubscriptionTable)
.innerJoin(BillingTable, eq(SubscriptionTable.workspaceID, BillingTable.workspaceID))
.where(
and(inArray(SubscriptionTable.workspaceID, workspaceIDs), sql`${SubscriptionTable.timeCreated} <= ${cutoff}`),
),
)
const counts = new Map<string, number>()
for (const user of users) {
const current = counts.get(user.workspaceID) ?? 0
counts.set(user.workspaceID, current + 1)
}
const rows = users
.map((user) => {
const workspace = spendMap.get(user.workspaceID) ?? new Map<number, number>()
const ppuWorkspace = ppuMap.get(user.workspaceID) ?? new Map<number, number>()
const count = counts.get(user.workspaceID) ?? 1
const amount = (totals.get(user.workspaceID) ?? 0) / count
const ppuAmount = (ppuTotals.get(user.workspaceID) ?? 0) / count
const monthStart = user.subscribed ? startOfMonth(user.subscribed) : null
const modelRows = (modelMap.get(user.workspaceID) ?? []).sort((a, b) => b.amount - a.amount).slice(0, 3)
const modelTotal = totals.get(user.workspaceID) ?? 0
const modelCells = modelRows.map((row) => ({
model: row.model,
percent: modelTotal > 0 ? `${((row.amount / modelTotal) * 100).toFixed(1)}%` : "0.0%",
}))
const modelData = [0, 1, 2].map((index) => modelCells[index] ?? { model: "-", percent: "-" })
const weekly = Object.fromEntries(
weeks.map((item) => {
const value = (workspace.get(item) ?? 0) / count
const beforeMonth = monthStart ? isoWeekStart(item) < monthStart : false
return [formatWeek(item), beforeMonth ? "-" : formatMicroCents(value)]
}),
)
const ppuWeekly = Object.fromEntries(
weeks.map((item) => {
const value = (ppuWorkspace.get(item) ?? 0) / count
const beforeMonth = monthStart ? isoWeekStart(item) < monthStart : false
return [formatWeek(item), beforeMonth ? "-" : formatMicroCents(value)]
}),
)
const tokenWorkspace = tokenMap.get(user.workspaceID) ?? new Map()
const weeklyTokens = Object.fromEntries(
weeks.map((item) => {
const t = tokenWorkspace.get(item) ?? { input: 0, cacheRead: 0, output: 0 }
const beforeMonth = monthStart ? isoWeekStart(item) < monthStart : false
return [
formatWeek(item),
beforeMonth
? { input: "-", cacheRead: "-", output: "-" }
: {
input: Math.round(t.input / count),
cacheRead: Math.round(t.cacheRead / count),
output: Math.round(t.output / count),
},
]
}),
)
return {
workspaceID: user.workspaceID,
useBalance: user.subscription?.useBalance ?? false,
subscribed: formatDate(user.subscribed),
subscribedAt: user.subscribed?.getTime() ?? 0,
amount,
ppuAmount,
models: modelData,
weekly,
ppuWeekly,
weeklyTokens,
}
})
.sort((a, b) => a.subscribedAt - b.subscribedAt)
console.log(`Black ${plan} subscribers: ${rows.length}`)
const header = [
"workspaceID",
"subscribed",
"useCredit",
"subTotal",
"ppuTotal",
"model1",
"model1%",
"model2",
"model2%",
"model3",
"model3%",
...weeks.flatMap((item) => [
formatWeek(item) + " sub",
formatWeek(item) + " ppu",
formatWeek(item) + " input",
formatWeek(item) + " cache",
formatWeek(item) + " output",
]),
]
const lines = [header.map(csvCell).join(",")]
for (const row of rows) {
const model1 = row.models[0]
const model2 = row.models[1]
const model3 = row.models[2]
const cells = [
row.workspaceID,
row.subscribed ?? "",
row.useBalance ? "yes" : "no",
formatMicroCents(row.amount),
formatMicroCents(row.ppuAmount),
model1.model,
model1.percent,
model2.model,
model2.percent,
model3.model,
model3.percent,
...weeks.flatMap((item) => {
const t = row.weeklyTokens[formatWeek(item)] ?? { input: "-", cacheRead: "-", output: "-" }
return [
row.weekly[formatWeek(item)] ?? "",
row.ppuWeekly[formatWeek(item)] ?? "",
String(t.input),
String(t.cacheRead),
String(t.output),
]
}),
]
lines.push(cells.map(csvCell).join(","))
}
const output = `${lines.join("\n")}\n`
const file = Bun.file(`black-stats-${plan}.csv`)
await file.write(output)
console.log(`Wrote ${lines.length - 1} rows to ${file.name}`)
const total = rows.reduce((sum, row) => sum + row.amount, 0)
const average = rows.length === 0 ? 0 : total / rows.length
console.log(`Average spending per user: ${formatMicroCents(average)}`)
function formatMicroCents(value: number) {
return `$${(value / 100000000).toFixed(2)}`
}
function formatDate(value: Date | null | undefined) {
if (!value) return null
return value.toISOString().split("T")[0]
}
function formatWeek(value: number) {
return formatDate(isoWeekStart(value)) ?? ""
}
function startOfMonth(value: Date) {
return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), 1))
}
function isoWeekStart(value: number) {
const year = Math.floor(value / 100)
const weekNumber = value % 100
const jan4 = new Date(Date.UTC(year, 0, 4))
const day = jan4.getUTCDay() || 7
const weekStart = new Date(Date.UTC(year, 0, 4 - (day - 1)))
weekStart.setUTCDate(weekStart.getUTCDate() + (weekNumber - 1) * 7)
return weekStart
}
function toNumber(value: unknown) {
if (typeof value === "number") return value
if (typeof value === "bigint") return Number(value)
if (typeof value === "string") return Number(value)
return 0
}
function csvCell(value: string | number) {
const text = String(value)
if (!/[",\n]/.test(text)) return text
return `"${text.replace(/"/g, '""')}"`
}

View File

@@ -0,0 +1,163 @@
import { Billing } from "../src/billing.js"
import { and, Database, desc, eq, isNotNull, lt, sql } from "../src/drizzle/index.js"
import { BillingTable, PaymentTable, SubscriptionTable } from "../src/schema/billing.sql.js"
const fromWrkID = process.argv[2]
const toWrkID = process.argv[3]
if (!fromWrkID || !toWrkID) {
console.error("Usage: bun foo.ts <fromWrkID> <toWrkID>")
process.exit(1)
}
console.log(`Transferring subscription from ${fromWrkID} to ${toWrkID}`)
// Look up the FROM workspace billing
const fromBilling = await Database.use((tx) =>
tx
.select({
customerID: BillingTable.customerID,
subscriptionID: BillingTable.subscriptionID,
subscription: BillingTable.subscription,
paymentMethodID: BillingTable.paymentMethodID,
paymentMethodType: BillingTable.paymentMethodType,
paymentMethodLast4: BillingTable.paymentMethodLast4,
})
.from(BillingTable)
.where(eq(BillingTable.workspaceID, fromWrkID))
.then((rows) => rows[0]),
)
if (!fromBilling) throw new Error(`Error: FROM workspace has no billing record`)
if (!fromBilling.customerID) throw new Error(`Error: FROM workspace has no Stripe customer ID`)
if (!fromBilling.subscriptionID) throw new Error(`Error: FROM workspace has no subscription`)
const fromSubscription = await Database.use((tx) =>
tx
.select({ userID: SubscriptionTable.userID })
.from(SubscriptionTable)
.where(eq(SubscriptionTable.workspaceID, fromWrkID))
.then((rows) => rows[0]),
)
if (!fromSubscription) throw new Error(`Error: FROM workspace has no subscription`)
// Look up the previous customer ID in FROM workspace
const subscriptionPayment = await Database.use((tx) =>
tx
.select({
customerID: PaymentTable.customerID,
timeCreated: PaymentTable.timeCreated,
})
.from(PaymentTable)
.where(and(eq(PaymentTable.workspaceID, fromWrkID), sql`JSON_EXTRACT(enrichment, '$.type') = 'subscription'`))
.then((rows) => {
if (rows.length > 1) {
console.error(`Error: Multiple subscription payments found for workspace ${fromWrkID}`)
process.exit(1)
}
return rows[0]
}),
)
const fromPrevPayment = await Database.use((tx) =>
tx
.select({ customerID: PaymentTable.customerID })
.from(PaymentTable)
.where(
and(
eq(PaymentTable.workspaceID, fromWrkID),
isNotNull(PaymentTable.customerID),
lt(PaymentTable.timeCreated, subscriptionPayment.timeCreated),
),
)
.orderBy(desc(PaymentTable.timeCreated))
.limit(1)
.then((rows) => rows[0]),
)
if (!fromPrevPayment?.customerID) throw new Error(`Error: FROM workspace has no previous Stripe customer to revert to`)
if (fromPrevPayment.customerID === fromBilling.customerID)
throw new Error(`Error: FROM workspace has the same Stripe customer ID as the current one`)
const fromPrevPaymentMethods = await Billing.stripe().customers.listPaymentMethods(fromPrevPayment.customerID, {})
if (fromPrevPaymentMethods.data.length === 0)
throw new Error(`Error: FROM workspace has no previous Stripe payment methods`)
// Look up the TO workspace billing
const toBilling = await Database.use((tx) =>
tx
.select({
customerID: BillingTable.customerID,
subscriptionID: BillingTable.subscriptionID,
})
.from(BillingTable)
.where(eq(BillingTable.workspaceID, toWrkID))
.then((rows) => rows[0]),
)
if (!toBilling) throw new Error(`Error: TO workspace has no billing record`)
if (toBilling.subscriptionID) throw new Error(`Error: TO workspace already has a subscription`)
console.log(`FROM:`)
console.log(` Old Customer ID: ${fromBilling.customerID}`)
console.log(` New Customer ID: ${fromPrevPayment.customerID}`)
console.log(`TO:`)
console.log(` Old Customer ID: ${toBilling.customerID}`)
console.log(` New Customer ID: ${fromBilling.customerID}`)
// Clear workspaceID from Stripe customer metadata
await Billing.stripe().customers.update(fromPrevPayment.customerID, {
metadata: {
workspaceID: fromWrkID,
},
})
await Billing.stripe().customers.update(fromBilling.customerID, {
metadata: {
workspaceID: toWrkID,
},
})
await Database.transaction(async (tx) => {
await tx
.update(BillingTable)
.set({
customerID: fromPrevPayment.customerID,
subscriptionID: null,
subscription: null,
paymentMethodID: fromPrevPaymentMethods.data[0].id,
paymentMethodLast4: fromPrevPaymentMethods.data[0].card?.last4 ?? null,
paymentMethodType: fromPrevPaymentMethods.data[0].type,
})
.where(eq(BillingTable.workspaceID, fromWrkID))
await tx
.update(BillingTable)
.set({
customerID: fromBilling.customerID,
subscriptionID: fromBilling.subscriptionID,
subscription: fromBilling.subscription,
paymentMethodID: fromBilling.paymentMethodID,
paymentMethodLast4: fromBilling.paymentMethodLast4,
paymentMethodType: fromBilling.paymentMethodType,
})
.where(eq(BillingTable.workspaceID, toWrkID))
await tx
.update(SubscriptionTable)
.set({
workspaceID: toWrkID,
userID: fromSubscription.userID,
})
.where(eq(SubscriptionTable.workspaceID, fromWrkID))
await tx
.update(PaymentTable)
.set({
workspaceID: toWrkID,
})
.where(
and(
eq(PaymentTable.workspaceID, fromWrkID),
sql`JSON_EXTRACT(enrichment, '$.type') = 'subscription'`,
eq(PaymentTable.amount, 20000000000),
),
)
})
console.log(`done`)

View File

@@ -0,0 +1,24 @@
import { Database } from "../src/drizzle/index.js"
import { CouponTable, CouponType } from "../src/schema/billing.sql.js"
const email = process.argv[2]
const type = process.argv[3] as (typeof CouponType)[number]
if (!email || !type) {
console.error(`Usage: bun create-coupon.ts <email> <${CouponType.join("|")}>`)
process.exit(1)
}
if (!(CouponType as readonly string[]).includes(type)) {
console.error(`Error: type must be one of ${CouponType.join(", ")}`)
process.exit(1)
}
await Database.use((tx) =>
tx.insert(CouponTable).values({
email,
type,
}),
)
console.log(`Created ${type} coupon for ${email}`)

View File

@@ -0,0 +1,35 @@
import { Billing } from "../src/billing.js"
import { Database, eq } from "../src/drizzle/index.js"
import { WorkspaceTable } from "../src/schema/workspace.sql.js"
// get input from command line
const workspaceID = process.argv[2]
const dollarAmount = process.argv[3]
if (!workspaceID || !dollarAmount) {
console.error("Usage: bun credit-workspace.ts <workspaceID> <dollarAmount>")
process.exit(1)
}
// check workspace exists
const workspace = await Database.use((tx) =>
tx
.select()
.from(WorkspaceTable)
.where(eq(WorkspaceTable.id, workspaceID))
.then((rows) => rows[0]),
)
if (!workspace) {
console.error("Error: Workspace not found")
process.exit(1)
}
const amountInDollars = parseFloat(dollarAmount)
if (isNaN(amountInDollars) || amountInDollars <= 0) {
console.error("Error: dollarAmount must be a positive number")
process.exit(1)
}
await Billing.grantCredit(workspaceID, amountInDollars)
console.log(`Added payment of $${amountInDollars.toFixed(2)} to workspace ${workspaceID}`)

View File

@@ -0,0 +1,34 @@
import { Database, eq } from "../src/drizzle/index.js"
import { BillingTable } from "../src/schema/billing.sql.js"
import { WorkspaceTable } from "../src/schema/workspace.sql.js"
const workspaceID = process.argv[2]
if (!workspaceID) {
console.error("Usage: bun disable-reload.ts <workspaceID>")
process.exit(1)
}
const billing = await Database.use((tx) =>
tx
.select({ reload: BillingTable.reload })
.from(BillingTable)
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, BillingTable.workspaceID))
.where(eq(BillingTable.workspaceID, workspaceID))
.then((rows) => rows[0]),
)
if (!billing) {
console.error("Error: Workspace or billing record not found")
process.exit(1)
}
if (!billing.reload) {
console.log(`Reload is already disabled for workspace ${workspaceID}`)
process.exit(0)
}
await Database.use((tx) =>
tx.update(BillingTable).set({ reload: false }).where(eq(BillingTable.workspaceID, workspaceID)),
)
console.log(`Disabled reload for workspace ${workspaceID}`)

View File

@@ -0,0 +1,39 @@
import { Billing } from "../src/billing.js"
import { Database, eq } from "../src/drizzle/index.js"
import { BillingTable } from "../src/schema/billing.sql.js"
import { WorkspaceTable } from "../src/schema/workspace.sql.js"
import { microCentsToCents } from "../src/util/price.js"
// get input from command line
const workspaceID = process.argv[2]
if (!workspaceID) {
console.error("Usage: bun freeze-workspace.ts <workspaceID>")
process.exit(1)
}
// check workspace exists
const workspace = await Database.use((tx) =>
tx
.select()
.from(WorkspaceTable)
.where(eq(WorkspaceTable.id, workspaceID))
.then((rows) => rows[0]),
)
if (!workspace) {
console.error("Error: Workspace not found")
process.exit(1)
}
const billing = await Database.use((tx) =>
tx
.select()
.from(BillingTable)
.where(eq(BillingTable.workspaceID, workspaceID))
.then((rows) => rows[0]),
)
const amountInDollars = microCentsToCents(billing.balance) / 100
await Billing.grantCredit(workspaceID, 0 - amountInDollars)
console.log(`Removed payment of $${amountInDollars.toFixed(2)} from workspace ${workspaceID}`)

View File

@@ -0,0 +1,386 @@
import { Database, and, eq, sql } from "../src/drizzle/index.js"
import { AuthTable } from "../src/schema/auth.sql.js"
import { UserTable } from "../src/schema/user.sql.js"
import {
BillingTable,
PaymentTable,
SubscriptionTable,
BlackPlans,
UsageTable,
LiteTable,
} from "../src/schema/billing.sql.js"
import { WorkspaceTable } from "../src/schema/workspace.sql.js"
import { KeyTable } from "../src/schema/key.sql.js"
import { BlackData } from "../src/black.js"
import { centsToMicroCents } from "../src/util/price.js"
import { getWeekBounds } from "../src/util/date.js"
import { ModelTable } from "../src/schema/model.sql.js"
// get input from command line
const identifier = process.argv[2]
const verbose = process.argv[process.argv.length - 1] === "-v"
if (!identifier) {
console.error("Usage: bun lookup-user.ts <email|workspaceID|apiKey> [-v]")
process.exit(1)
}
// loop up by workspace ID
if (identifier.startsWith("wrk_")) {
await printWorkspace(identifier)
}
// lookup by API key ID
else if (identifier.startsWith("key_")) {
const key = await Database.use((tx) =>
tx
.select()
.from(KeyTable)
.where(eq(KeyTable.id, identifier))
.then((rows) => rows[0]),
)
if (!key) {
console.error("API key not found")
process.exit(1)
}
await printWorkspace(key.workspaceID)
}
// lookup by API key value
else if (identifier.startsWith("sk-")) {
const key = await Database.use((tx) =>
tx
.select()
.from(KeyTable)
.where(eq(KeyTable.key, identifier))
.then((rows) => rows[0]),
)
if (!key) {
console.error("API key not found")
process.exit(1)
}
await printWorkspace(key.workspaceID)
}
// lookup by email
else {
const authData = await Database.use(async (tx) =>
tx.select().from(AuthTable).where(eq(AuthTable.subject, identifier)),
)
if (authData.length === 0) {
console.error("Email not found")
process.exit(1)
}
if (authData.length > 1) console.warn("Multiple users found for email", identifier)
// Get all auth records for email
const accountID = authData[0].accountID
await printTable("Auth", (tx) => tx.select().from(AuthTable).where(eq(AuthTable.accountID, accountID)))
// Get all workspaces for this account
const users = await printTable("Workspaces", (tx) =>
tx
.select({
userID: UserTable.id,
workspaceID: UserTable.workspaceID,
workspaceName: WorkspaceTable.name,
role: UserTable.role,
black: SubscriptionTable.timeCreated,
lite: LiteTable.timeCreated,
})
.from(UserTable)
.rightJoin(WorkspaceTable, eq(WorkspaceTable.id, UserTable.workspaceID))
.leftJoin(SubscriptionTable, eq(SubscriptionTable.userID, UserTable.id))
.leftJoin(LiteTable, eq(LiteTable.userID, UserTable.id))
.where(eq(UserTable.accountID, accountID))
.then((rows) =>
rows.map((row) => ({
userID: row.userID,
workspaceID: row.workspaceID,
workspaceName: row.workspaceName,
role: row.role,
black: formatDate(row.black),
lite: formatDate(row.lite),
})),
),
)
for (const user of users) {
await printWorkspace(user.workspaceID)
}
}
async function printWorkspace(workspaceID: string) {
const workspace = await Database.use((tx) =>
tx
.select()
.from(WorkspaceTable)
.where(eq(WorkspaceTable.id, workspaceID))
.then((rows) => rows[0]),
)
printHeader(`Workspace "${workspace.name}" (${workspace.id})`)
await printTable("Users", (tx) =>
tx
.select({
authEmail: AuthTable.subject,
inviteEmail: UserTable.email,
role: UserTable.role,
timeSeen: UserTable.timeSeen,
monthlyLimit: UserTable.monthlyLimit,
monthlyUsage: UserTable.monthlyUsage,
timeDeleted: UserTable.timeDeleted,
fixedUsage: SubscriptionTable.fixedUsage,
rollingUsage: SubscriptionTable.rollingUsage,
timeFixedUpdated: SubscriptionTable.timeFixedUpdated,
timeRollingUpdated: SubscriptionTable.timeRollingUpdated,
timeSubscriptionCreated: SubscriptionTable.timeCreated,
subscription: BillingTable.subscription,
})
.from(UserTable)
.innerJoin(BillingTable, eq(BillingTable.workspaceID, workspace.id))
.leftJoin(AuthTable, and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email")))
.leftJoin(SubscriptionTable, eq(SubscriptionTable.userID, UserTable.id))
.where(eq(UserTable.workspaceID, workspace.id))
.then((rows) =>
rows.map((row) => {
const subStatus = getSubscriptionStatus(row)
return {
email: (row.timeDeleted ? "❌ " : "") + (row.authEmail ?? row.inviteEmail),
role: row.role,
timeSeen: formatDate(row.timeSeen),
monthly: formatMonthlyUsage(row.monthlyUsage, row.monthlyLimit),
subscribed: formatDate(row.timeSubscriptionCreated),
subWeekly: subStatus.weekly,
subRolling: subStatus.rolling,
rateLimited: subStatus.rateLimited,
retryIn: subStatus.retryIn,
}
}),
),
)
await printTable("Billing", (tx) =>
tx
.select({
balance: BillingTable.balance,
customerID: BillingTable.customerID,
reload: BillingTable.reload,
blackSubscriptionID: BillingTable.subscriptionID,
blackSubscription: {
plan: BillingTable.subscriptionPlan,
booked: BillingTable.timeSubscriptionBooked,
enrichment: BillingTable.subscription,
},
timeBlackSubscriptionSelected: BillingTable.timeSubscriptionSelected,
liteSubscriptionID: BillingTable.liteSubscriptionID,
})
.from(BillingTable)
.where(eq(BillingTable.workspaceID, workspace.id))
.then(
(rows) =>
rows.map((row) => ({
balance: `$${(row.balance / 100000000).toFixed(2)}`,
reload: row.reload ? "yes" : "no",
customerID: row.customerID,
GO: row.liteSubscriptionID,
Black: row.blackSubscriptionID
? [
`Black ${row.blackSubscription.enrichment!.plan}`,
row.blackSubscription.enrichment!.seats > 1
? `X ${row.blackSubscription.enrichment!.seats} seats`
: "",
row.blackSubscription.enrichment!.coupon
? `(coupon: ${row.blackSubscription.enrichment!.coupon})`
: "",
`(ref: ${row.blackSubscriptionID})`,
].join(" ")
: row.blackSubscription.booked
? `Waitlist ${row.blackSubscription.plan} plan${row.timeBlackSubscriptionSelected ? " (selected)" : ""}`
: undefined,
}))[0],
),
)
await printTable("Payments", (tx) =>
tx
.select({
amount: PaymentTable.amount,
paymentID: PaymentTable.paymentID,
invoiceID: PaymentTable.invoiceID,
customerID: PaymentTable.customerID,
timeCreated: PaymentTable.timeCreated,
timeRefunded: PaymentTable.timeRefunded,
})
.from(PaymentTable)
.where(eq(PaymentTable.workspaceID, workspace.id))
.orderBy(sql`${PaymentTable.timeCreated} DESC`)
.limit(100)
.then((rows) =>
rows.map((row) => ({
...row,
amount: `$${(row.amount / 100000000).toFixed(2)}`,
paymentID: row.paymentID
? `https://dashboard.stripe.com/acct_1RszBH2StuRr0lbX/payments/${row.paymentID}`
: null,
})),
),
)
if (verbose) {
await printTable("28-Day Usage", (tx) =>
tx
.select({
date: sql<string>`DATE(${UsageTable.timeCreated})`.as("date"),
requests: sql<number>`COUNT(*)`.as("requests"),
inputTokens: sql<number>`SUM(${UsageTable.inputTokens})`.as("input_tokens"),
outputTokens: sql<number>`SUM(${UsageTable.outputTokens})`.as("output_tokens"),
reasoningTokens: sql<number>`SUM(${UsageTable.reasoningTokens})`.as("reasoning_tokens"),
cacheReadTokens: sql<number>`SUM(${UsageTable.cacheReadTokens})`.as("cache_read_tokens"),
cacheWrite5mTokens: sql<number>`SUM(${UsageTable.cacheWrite5mTokens})`.as("cache_write_5m_tokens"),
cacheWrite1hTokens: sql<number>`SUM(${UsageTable.cacheWrite1hTokens})`.as("cache_write_1h_tokens"),
cost: sql<number>`SUM(${UsageTable.cost})`.as("cost"),
})
.from(UsageTable)
.where(
and(
eq(UsageTable.workspaceID, workspace.id),
sql`${UsageTable.timeCreated} >= DATE_SUB(NOW(), INTERVAL 28 DAY)`,
),
)
.groupBy(sql`DATE(${UsageTable.timeCreated})`)
.orderBy(sql`DATE(${UsageTable.timeCreated}) DESC`)
.then((rows) => {
const totalCost = rows.reduce((sum, r) => sum + Number(r.cost), 0)
const mapped = rows.map((row) => ({
...row,
cost: `$${(Number(row.cost) / 100000000).toFixed(2)}`,
}))
if (mapped.length > 0) {
mapped.push({
date: "TOTAL",
requests: null as any,
inputTokens: null as any,
outputTokens: null as any,
reasoningTokens: null as any,
cacheReadTokens: null as any,
cacheWrite5mTokens: null as any,
cacheWrite1hTokens: null as any,
cost: `$${(totalCost / 100000000).toFixed(2)}`,
})
}
return mapped
}),
)
await printTable("Disabled Models", (tx) =>
tx
.select({
model: ModelTable.model,
timeCreated: ModelTable.timeCreated,
})
.from(ModelTable)
.where(eq(ModelTable.workspaceID, workspace.id))
.orderBy(sql`${ModelTable.timeCreated} DESC`)
.then((rows) =>
rows.map((row) => ({
model: row.model,
timeCreated: formatDate(row.timeCreated),
})),
),
)
}
}
function formatMicroCents(value: number | null | undefined) {
if (value === null || value === undefined) return null
return `$${(value / 100000000).toFixed(2)}`
}
function formatDate(value: Date | null | undefined) {
if (!value) return null
return value.toISOString().split("T")[0]
}
function formatMonthlyUsage(usage: number | null | undefined, limit: number | null | undefined) {
const usageText = formatMicroCents(usage) ?? "$0.00"
if (limit === null || limit === undefined) return `${usageText} / no limit`
return `${usageText} / $${limit.toFixed(2)}`
}
function formatRetryTime(seconds: number) {
const days = Math.floor(seconds / 86400)
if (days >= 1) return `${days} day${days > 1 ? "s" : ""}`
const hours = Math.floor(seconds / 3600)
const minutes = Math.ceil((seconds % 3600) / 60)
if (hours >= 1) return `${hours}hr ${minutes}min`
return `${minutes}min`
}
function getSubscriptionStatus(row: {
subscription: {
plan: (typeof BlackPlans)[number]
} | null
timeSubscriptionCreated: Date | null
fixedUsage: number | null
rollingUsage: number | null
timeFixedUpdated: Date | null
timeRollingUpdated: Date | null
}) {
if (!row.timeSubscriptionCreated || !row.subscription) {
return { weekly: null, rolling: null, rateLimited: null, retryIn: null }
}
const black = BlackData.getLimits({ plan: row.subscription.plan })
const now = new Date()
const week = getWeekBounds(now)
const fixedLimit = black.fixedLimit ? centsToMicroCents(black.fixedLimit * 100) : null
const rollingLimit = black.rollingLimit ? centsToMicroCents(black.rollingLimit * 100) : null
const rollingWindowMs = (black.rollingWindow ?? 5) * 3600 * 1000
// Calculate current weekly usage (reset if outside current week)
const currentWeekly =
row.fixedUsage && row.timeFixedUpdated && row.timeFixedUpdated >= week.start ? row.fixedUsage : 0
// Calculate current rolling usage
const windowStart = new Date(now.getTime() - rollingWindowMs)
const currentRolling =
row.rollingUsage && row.timeRollingUpdated && row.timeRollingUpdated >= windowStart ? row.rollingUsage : 0
// Check rate limiting
const isWeeklyLimited = fixedLimit !== null && currentWeekly >= fixedLimit
const isRollingLimited = rollingLimit !== null && currentRolling >= rollingLimit
let retryIn: string | null = null
if (isWeeklyLimited) {
const retryAfter = Math.ceil((week.end.getTime() - now.getTime()) / 1000)
retryIn = formatRetryTime(retryAfter)
} else if (isRollingLimited && row.timeRollingUpdated) {
const retryAfter = Math.ceil((row.timeRollingUpdated.getTime() + rollingWindowMs - now.getTime()) / 1000)
retryIn = formatRetryTime(retryAfter)
}
return {
weekly: fixedLimit !== null ? `${formatMicroCents(currentWeekly)} / $${black.fixedLimit}` : null,
rolling: rollingLimit !== null ? `${formatMicroCents(currentRolling)} / $${black.rollingLimit}` : null,
rateLimited: isWeeklyLimited || isRollingLimited ? "yes" : "no",
retryIn,
}
}
function printHeader(title: string) {
console.log()
console.log("─".repeat(title.length))
console.log(`${title}`)
console.log("─".repeat(title.length))
}
function printTable(title: string, callback: (tx: Database.TxOrDb) => Promise<any>): Promise<any> {
return Database.use(async (tx) => {
const data = await callback(tx)
console.log(`\n== ${title} ==`)
if (data.length === 0) {
console.log("(no data)")
} else {
console.table(data)
}
return data
})
}

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bun
import { $ } from "bun"
import path from "path"
import { Subscription } from "../src/subscription"
const stage = process.argv[2]
if (!stage) throw new Error("Stage is required")
const root = path.resolve(process.cwd(), "..", "..", "..")
// read the secret
const ret = await $`bun sst secret list --stage frank`.cwd(root).text()
const lines = ret.split("\n")
const value = lines.find((line) => line.startsWith("ZEN_LIMITS"))?.split("=")[1]
if (!value) throw new Error("ZEN_LIMITS not found")
// validate value
Subscription.validate(JSON.parse(value))
// update the secret
await $`bun sst secret set ZEN_LIMITS ${value} --stage ${stage}`

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bun
import { $ } from "bun"
import path from "path"
import os from "os"
import { ZenData } from "../src/model"
const stage = process.argv[2]
if (!stage) throw new Error("Stage is required")
const root = path.resolve(process.cwd(), "..", "..", "..")
const PARTS = 30
// read the secret
const ret = await $`bun sst secret list --stage frank`.cwd(root).text()
const lines = ret.split("\n")
const values = Array.from({ length: PARTS }, (_, i) => {
const value = lines
.find((line) => line.startsWith(`ZEN_MODELS${i + 1}=`))
?.split("=")
.slice(1)
.join("=")
if (!value) throw new Error(`ZEN_MODELS${i + 1} not found`)
return value
})
// validate value
ZenData.validate(JSON.parse(values.join("")))
// update the secret
const envFile = Bun.file(path.join(os.tmpdir(), `models-${Date.now()}.env`))
await envFile.write(values.map((v, i) => `ZEN_MODELS${i + 1}="${v.replace(/"/g, '\\"')}"`).join("\n"))
await $`bun sst secret load ${envFile.name} --stage ${stage}`.cwd(root)

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bun
import { $ } from "bun"
import path from "path"
import os from "os"
import { ZenData } from "../src/model"
const stage = process.argv[2]
if (!stage) throw new Error("Stage is required")
const root = path.resolve(process.cwd(), "..", "..", "..")
const PARTS = 20
// read the secret
const ret = await $`bun sst secret list --stage ${stage}`.cwd(root).text()
const lines = ret.split("\n")
const values = Array.from({ length: PARTS }, (_, i) => {
const value = lines
.find((line) => line.startsWith(`ZEN_MODELS${i + 1}=`))
?.split("=")
.slice(1)
.join("=")
if (!value) throw new Error(`ZEN_MODELS${i + 1} not found`)
return value
})
// validate value
ZenData.validate(JSON.parse(values.join("")))
// update the secret
const envFile = Bun.file(path.join(os.tmpdir(), `models-${Date.now()}.env`))
await envFile.write(values.map((v, i) => `ZEN_MODELS${i + 1}="${v.replace(/"/g, '\\"')}"`).join("\n"))
await $`bun sst secret load ${envFile.name}`.cwd(root)

View File

@@ -0,0 +1,13 @@
import { Resource } from "@opencode-ai/console-resource"
import { Database } from "../src/drizzle/index.js"
import { UserTable } from "../src/schema/user.sql.js"
import { AccountTable } from "../src/schema/account.sql.js"
import { WorkspaceTable } from "../src/schema/workspace.sql.js"
import { BillingTable, PaymentTable, UsageTable } from "../src/schema/billing.sql.js"
import { KeyTable } from "../src/schema/key.sql.js"
if (Resource.App.stage !== "frank") throw new Error("This script is only for frank")
for (const table of [AccountTable, BillingTable, KeyTable, PaymentTable, UsageTable, UserTable, WorkspaceTable]) {
await Database.use((tx) => tx.delete(table))
}

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bun
import { $ } from "bun"
import path from "path"
import os from "os"
import { Subscription } from "../src/subscription"
const root = path.resolve(process.cwd(), "..", "..", "..")
const secrets = await $`bun sst secret list --stage frank`.cwd(root).text()
// read value
const lines = secrets.split("\n")
const oldValue = lines.find((line) => line.startsWith("ZEN_LIMITS"))?.split("=")[1] ?? "{}"
if (!oldValue) throw new Error("ZEN_LIMITS not found")
// store the prettified json to a temp file
const filename = `limits-${Date.now()}.json`
const tempFile = Bun.file(path.join(os.tmpdir(), filename))
await tempFile.write(JSON.stringify(JSON.parse(oldValue), null, 2))
console.log("tempFile", tempFile.name)
// open temp file in vim and read the file on close
await $`vim ${tempFile.name}`
const newValue = JSON.stringify(JSON.parse(await tempFile.text()))
Subscription.validate(JSON.parse(newValue))
// update the secret
await $`bun sst secret set ZEN_LIMITS ${newValue} --stage frank`.cwd(root)

View File

@@ -0,0 +1,43 @@
#!/usr/bin/env bun
import { $ } from "bun"
import path from "path"
import os from "os"
import { ZenData } from "../src/model"
const root = path.resolve(process.cwd(), "..", "..", "..")
const models = await $`bun sst secret list --stage frank`.cwd(root).text()
const PARTS = 30
// read the line starting with "ZEN_MODELS"
const lines = models.split("\n")
const oldValues = Array.from({ length: PARTS }, (_, i) => {
const value = lines
.find((line) => line.startsWith(`ZEN_MODELS${i + 1}=`))
?.split("=")
.slice(1)
.join("=")
if (!value) throw new Error(`ZEN_MODELS${i + 1} not found`)
return value
})
// store the prettified json to a temp file
const filename = `models-${Date.now()}.json`
const tempFile = Bun.file(path.join(os.tmpdir(), filename))
await tempFile.write(JSON.stringify(JSON.parse(oldValues.join("")), null, 2))
console.log("tempFile", tempFile.name)
// open temp file in vim and read the file on close
await $`vim ${tempFile.name}`
const newValue = JSON.stringify(JSON.parse(await tempFile.text()))
ZenData.validate(JSON.parse(newValue))
// update the secret
const chunk = Math.ceil(newValue.length / PARTS)
const newValues = Array.from({ length: PARTS }, (_, i) =>
newValue.slice(chunk * i, i === PARTS - 1 ? undefined : chunk * (i + 1)),
)
const envFile = Bun.file(path.join(os.tmpdir(), `models-${Date.now()}.env`))
await envFile.write(newValues.map((v, i) => `ZEN_MODELS${i + 1}="${v.replace(/"/g, '\\"')}"`).join("\n"))
await $`bun sst secret load ${envFile.name} --stage frank`.cwd(root)