wip: zen black

This commit is contained in:
Frank
2026-01-22 12:40:42 -05:00
parent bb582416f2
commit a890d51bbc
16 changed files with 130 additions and 42 deletions

View File

@@ -101,15 +101,26 @@ export const stripeWebhook = new stripe.WebhookEndpoint("StripeWebhookEndpoint",
const zenProduct = new stripe.Product("ZenBlack", { const zenProduct = new stripe.Product("ZenBlack", {
name: "OpenCode Black", name: "OpenCode Black",
}) })
const zenPrice = new stripe.Price("ZenBlackPrice", { const zenPriceProps = {
product: zenProduct.id, product: zenProduct.id,
unitAmount: 20000,
currency: "usd", currency: "usd",
recurring: { recurring: {
interval: "month", interval: "month",
intervalCount: 1, intervalCount: 1,
}, },
}
const zenPrice200 = new stripe.Price("ZenBlackPrice", { ...zenPriceProps, unitAmount: 20000, })
const zenPrice100 = new stripe.Price("ZenBlack100Price", { ...zenPriceProps, unitAmount: 10000, })
const zenPrice20 = new stripe.Price("ZenBlack20Price", { ...zenPriceProps, unitAmount: 2000, })
const ZEN_BLACK_PRICE = new sst.Linkable("ZEN_BLACK_PRICE", {
properties: {
product: zenProduct.id,
plan200: zenPrice200.id,
plan100: zenPrice100.id,
plan20: zenPrice20.id,
},
}) })
const ZEN_BLACK_LIMITS = new sst.Secret("ZEN_BLACK_LIMITS")
const ZEN_MODELS = [ const ZEN_MODELS = [
new sst.Secret("ZEN_MODELS1"), new sst.Secret("ZEN_MODELS1"),
@@ -121,7 +132,6 @@ const ZEN_MODELS = [
new sst.Secret("ZEN_MODELS7"), new sst.Secret("ZEN_MODELS7"),
new sst.Secret("ZEN_MODELS8"), new sst.Secret("ZEN_MODELS8"),
] ]
const ZEN_BLACK = new sst.Secret("ZEN_BLACK")
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY") const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
const STRIPE_PUBLISHABLE_KEY = new sst.Secret("STRIPE_PUBLISHABLE_KEY") const STRIPE_PUBLISHABLE_KEY = new sst.Secret("STRIPE_PUBLISHABLE_KEY")
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", { const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
@@ -164,7 +174,8 @@ new sst.cloudflare.x.SolidStart("Console", {
EMAILOCTOPUS_API_KEY, EMAILOCTOPUS_API_KEY,
AWS_SES_ACCESS_KEY_ID, AWS_SES_ACCESS_KEY_ID,
AWS_SES_SECRET_ACCESS_KEY, AWS_SES_SECRET_ACCESS_KEY,
ZEN_BLACK, ZEN_BLACK_PRICE,
ZEN_BLACK_LIMITS,
new sst.Secret("ZEN_SESSION_SECRET"), new sst.Secret("ZEN_SESSION_SECRET"),
...ZEN_MODELS, ...ZEN_MODELS,
...($dev ...($dev

View File

@@ -3,7 +3,7 @@ import { createStore } from "solid-js/store"
import { Show } from "solid-js" import { Show } from "solid-js"
import { Billing } from "@opencode-ai/console-core/billing.js" import { Billing } from "@opencode-ai/console-core/billing.js"
import { Database, eq, and, isNull } from "@opencode-ai/console-core/drizzle/index.js" import { Database, eq, and, isNull } from "@opencode-ai/console-core/drizzle/index.js"
import { SubscriptionTable } from "@opencode-ai/console-core/schema/billing.sql.js" import { BillingTable, SubscriptionTable } from "@opencode-ai/console-core/schema/billing.sql.js"
import { Actor } from "@opencode-ai/console-core/actor.js" import { Actor } from "@opencode-ai/console-core/actor.js"
import { Black } from "@opencode-ai/console-core/black.js" import { Black } from "@opencode-ai/console-core/black.js"
import { withActor } from "~/context/auth.withActor" import { withActor } from "~/context/auth.withActor"
@@ -20,19 +20,24 @@ const querySubscription = query(async (workspaceID: string) => {
fixedUsage: SubscriptionTable.fixedUsage, fixedUsage: SubscriptionTable.fixedUsage,
timeRollingUpdated: SubscriptionTable.timeRollingUpdated, timeRollingUpdated: SubscriptionTable.timeRollingUpdated,
timeFixedUpdated: SubscriptionTable.timeFixedUpdated, timeFixedUpdated: SubscriptionTable.timeFixedUpdated,
subscription: BillingTable.subscription,
}) })
.from(SubscriptionTable) .from(BillingTable)
.innerJoin(SubscriptionTable, eq(SubscriptionTable.workspaceID, BillingTable.workspaceID))
.where(and(eq(SubscriptionTable.workspaceID, Actor.workspace()), isNull(SubscriptionTable.timeDeleted))) .where(and(eq(SubscriptionTable.workspaceID, Actor.workspace()), isNull(SubscriptionTable.timeDeleted)))
.then((r) => r[0]), .then((r) => r[0]),
) )
if (!row) return null if (!row.subscription) return null
return { return {
plan: row.subscription.plan,
rollingUsage: Black.analyzeRollingUsage({ rollingUsage: Black.analyzeRollingUsage({
plan: row.subscription.plan,
usage: row.rollingUsage ?? 0, usage: row.rollingUsage ?? 0,
timeUpdated: row.timeRollingUpdated ?? new Date(), timeUpdated: row.timeRollingUpdated ?? new Date(),
}), }),
weeklyUsage: Black.analyzeWeeklyUsage({ weeklyUsage: Black.analyzeWeeklyUsage({
plan: row.subscription.plan,
usage: row.fixedUsage ?? 0, usage: row.fixedUsage ?? 0,
timeUpdated: row.timeFixedUpdated ?? new Date(), timeUpdated: row.timeFixedUpdated ?? new Date(),
}), }),
@@ -89,10 +94,13 @@ export function BlackSection() {
return ( return (
<section class={styles.root}> <section class={styles.root}>
<Show when={subscription()}>
{(sub) => (
<>
<div data-slot="section-title"> <div data-slot="section-title">
<h2>Subscription</h2> <h2>Subscription</h2>
<div data-slot="title-row"> <div data-slot="title-row">
<p>You are subscribed to OpenCode Black for $200 per month.</p> <p>You are subscribed to OpenCode Black for ${sub().plan} per month.</p>
<button <button
data-color="primary" data-color="primary"
disabled={sessionSubmission.pending || store.sessionRedirecting} disabled={sessionSubmission.pending || store.sessionRedirecting}
@@ -102,8 +110,6 @@ export function BlackSection() {
</button> </button>
</div> </div>
</div> </div>
<Show when={subscription()}>
{(sub) => (
<div data-slot="usage"> <div data-slot="usage">
<div data-slot="usage-item"> <div data-slot="usage-item">
<div data-slot="usage-header"> <div data-slot="usage-header">
@@ -126,6 +132,7 @@ export function BlackSection() {
<span data-slot="reset-time">Resets in {formatResetTime(sub().weeklyUsage.resetInSec)}</span> <span data-slot="reset-time">Resets in {formatResetTime(sub().weeklyUsage.resetInSec)}</span>
</div> </div>
</div> </div>
</>
)} )}
</Show> </Show>
</section> </section>

View File

@@ -16,7 +16,7 @@ export default function () {
<div data-page="workspace-[id]"> <div data-page="workspace-[id]">
<div data-slot="sections"> <div data-slot="sections">
<Show when={sessionInfo()?.isAdmin}> <Show when={sessionInfo()?.isAdmin}>
<Show when={billingInfo()?.subscriptionID}> <Show when={billingInfo()?.subscriptionID || billingInfo()?.timeSubscriptionBooked}>
<BlackSection /> <BlackSection />
</Show> </Show>
<BillingSection /> <BillingSection />

View File

@@ -111,6 +111,8 @@ export const queryBillingInfo = query(async (workspaceID: string) => {
reloadError: billing.reloadError, reloadError: billing.reloadError,
timeReloadError: billing.timeReloadError, timeReloadError: billing.timeReloadError,
subscriptionID: billing.subscriptionID, subscriptionID: billing.subscriptionID,
subscriptionPlan: billing.subscriptionPlan,
timeSubscriptionBooked: billing.timeSubscriptionBooked,
} }
}, workspaceID) }, workspaceID)
}, "billing.get") }, "billing.get")

View File

@@ -417,6 +417,7 @@ export async function handler(
timeMonthlyUsageUpdated: BillingTable.timeMonthlyUsageUpdated, timeMonthlyUsageUpdated: BillingTable.timeMonthlyUsageUpdated,
reloadTrigger: BillingTable.reloadTrigger, reloadTrigger: BillingTable.reloadTrigger,
timeReloadLockedTill: BillingTable.timeReloadLockedTill, timeReloadLockedTill: BillingTable.timeReloadLockedTill,
subscription: BillingTable.subscription,
}, },
user: { user: {
id: UserTable.id, id: UserTable.id,
@@ -488,10 +489,9 @@ export async function handler(
if (modelInfo.allowAnonymous) return if (modelInfo.allowAnonymous) return
// Validate subscription billing // Validate subscription billing
if (authInfo.subscription) { if (authInfo.billing.subscription && authInfo.subscription) {
const black = BlackData.get()
const sub = authInfo.subscription const sub = authInfo.subscription
const now = new Date() const plan = authInfo.billing.subscription.plan
const formatRetryTime = (seconds: number) => { const formatRetryTime = (seconds: number) => {
const days = Math.floor(seconds / 86400) const days = Math.floor(seconds / 86400)
@@ -505,6 +505,7 @@ export async function handler(
// Check weekly limit // Check weekly limit
if (sub.fixedUsage && sub.timeFixedUpdated) { if (sub.fixedUsage && sub.timeFixedUpdated) {
const result = Black.analyzeWeeklyUsage({ const result = Black.analyzeWeeklyUsage({
plan,
usage: sub.fixedUsage, usage: sub.fixedUsage,
timeUpdated: sub.timeFixedUpdated, timeUpdated: sub.timeFixedUpdated,
}) })
@@ -518,6 +519,7 @@ export async function handler(
// Check rolling limit // Check rolling limit
if (sub.rollingUsage && sub.timeRollingUpdated) { if (sub.rollingUsage && sub.timeRollingUpdated) {
const result = Black.analyzeRollingUsage({ const result = Black.analyzeRollingUsage({
plan,
usage: sub.rollingUsage, usage: sub.rollingUsage,
timeUpdated: sub.timeRollingUpdated, timeUpdated: sub.timeRollingUpdated,
}) })
@@ -666,7 +668,8 @@ export async function handler(
.where(and(eq(KeyTable.workspaceID, authInfo.workspaceID), eq(KeyTable.id, authInfo.apiKeyId))), .where(and(eq(KeyTable.workspaceID, authInfo.workspaceID), eq(KeyTable.id, authInfo.apiKeyId))),
...(authInfo.subscription ...(authInfo.subscription
? (() => { ? (() => {
const black = BlackData.get() const plan = authInfo.billing.subscription!.plan
const black = BlackData.get({ plan })
const week = getWeekBounds(new Date()) const week = getWeekBounds(new Date())
const rollingWindowSeconds = black.rollingWindow * 3600 const rollingWindowSeconds = black.rollingWindow * 3600
return [ return [

View File

@@ -1,7 +1,7 @@
import { Database, and, eq, sql } from "../src/drizzle/index.js" import { Database, and, eq, sql } from "../src/drizzle/index.js"
import { AuthTable } from "../src/schema/auth.sql.js" import { AuthTable } from "../src/schema/auth.sql.js"
import { UserTable } from "../src/schema/user.sql.js" import { UserTable } from "../src/schema/user.sql.js"
import { BillingTable, PaymentTable, SubscriptionTable, UsageTable } from "../src/schema/billing.sql.js" import { BillingTable, PaymentTable, SubscriptionTable, SubscriptionPlan, UsageTable } from "../src/schema/billing.sql.js"
import { WorkspaceTable } from "../src/schema/workspace.sql.js" import { WorkspaceTable } from "../src/schema/workspace.sql.js"
import { BlackData } from "../src/black.js" import { BlackData } from "../src/black.js"
import { centsToMicroCents } from "../src/util/price.js" import { centsToMicroCents } from "../src/util/price.js"
@@ -86,8 +86,10 @@ async function printWorkspace(workspaceID: string) {
timeFixedUpdated: SubscriptionTable.timeFixedUpdated, timeFixedUpdated: SubscriptionTable.timeFixedUpdated,
timeRollingUpdated: SubscriptionTable.timeRollingUpdated, timeRollingUpdated: SubscriptionTable.timeRollingUpdated,
timeSubscriptionCreated: SubscriptionTable.timeCreated, timeSubscriptionCreated: SubscriptionTable.timeCreated,
subscription: BillingTable.subscription,
}) })
.from(UserTable) .from(UserTable)
.innerJoin(BillingTable, eq(BillingTable.workspaceID, workspace.id))
.leftJoin(AuthTable, and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email"))) .leftJoin(AuthTable, and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email")))
.leftJoin(SubscriptionTable, eq(SubscriptionTable.userID, UserTable.id)) .leftJoin(SubscriptionTable, eq(SubscriptionTable.userID, UserTable.id))
.where(eq(UserTable.workspaceID, workspace.id)) .where(eq(UserTable.workspaceID, workspace.id))
@@ -223,17 +225,20 @@ function formatRetryTime(seconds: number) {
} }
function getSubscriptionStatus(row: { function getSubscriptionStatus(row: {
subscription: {
plan: typeof SubscriptionPlan[number]
} | null
timeSubscriptionCreated: Date | null timeSubscriptionCreated: Date | null
fixedUsage: number | null fixedUsage: number | null
rollingUsage: number | null rollingUsage: number | null
timeFixedUpdated: Date | null timeFixedUpdated: Date | null
timeRollingUpdated: Date | null timeRollingUpdated: Date | null
}) { }) {
if (!row.timeSubscriptionCreated) { if (!row.timeSubscriptionCreated || !row.subscription) {
return { weekly: null, rolling: null, rateLimited: null, retryIn: null } return { weekly: null, rolling: null, rateLimited: null, retryIn: null }
} }
const black = BlackData.get() const black = BlackData.get({ plan: row.subscription.plan })
const now = new Date() const now = new Date()
const week = getWeekBounds(now) const week = getWeekBounds(now)

View File

@@ -12,11 +12,11 @@ const root = path.resolve(process.cwd(), "..", "..", "..")
// read the secret // read the secret
const ret = await $`bun sst secret list`.cwd(root).text() const ret = await $`bun sst secret list`.cwd(root).text()
const lines = ret.split("\n") const lines = ret.split("\n")
const value = lines.find((line) => line.startsWith("ZEN_BLACK"))?.split("=")[1] const value = lines.find((line) => line.startsWith("ZEN_BLACK_LIMITS"))?.split("=")[1]
if (!value) throw new Error("ZEN_BLACK not found") if (!value) throw new Error("ZEN_BLACK_LIMITS not found")
// validate value // validate value
BlackData.validate(JSON.parse(value)) BlackData.validate(JSON.parse(value))
// update the secret // update the secret
await $`bun sst secret set ZEN_BLACK ${value} --stage ${stage}` await $`bun sst secret set ZEN_BLACK_LIMITS ${value} --stage ${stage}`

View File

@@ -8,10 +8,10 @@ import { BlackData } from "../src/black"
const root = path.resolve(process.cwd(), "..", "..", "..") const root = path.resolve(process.cwd(), "..", "..", "..")
const secrets = await $`bun sst secret list`.cwd(root).text() const secrets = await $`bun sst secret list`.cwd(root).text()
// read the line starting with "ZEN_BLACK" // read value
const lines = secrets.split("\n") const lines = secrets.split("\n")
const oldValue = lines.find((line) => line.startsWith("ZEN_BLACK"))?.split("=")[1] const oldValue = lines.find((line) => line.startsWith("ZEN_BLACK_LIMITS"))?.split("=")[1] ?? "{}"
if (!oldValue) throw new Error("ZEN_BLACK not found") if (!oldValue) throw new Error("ZEN_BLACK_LIMITS not found")
// store the prettified json to a temp file // store the prettified json to a temp file
const filename = `black-${Date.now()}.json` const filename = `black-${Date.now()}.json`
@@ -25,4 +25,4 @@ const newValue = JSON.stringify(JSON.parse(await tempFile.text()))
BlackData.validate(JSON.parse(newValue)) BlackData.validate(JSON.parse(newValue))
// update the secret // update the secret
await $`bun sst secret set ZEN_BLACK ${newValue}` await $`bun sst secret set ZEN_BLACK_LIMITS ${newValue}`

View File

@@ -3,33 +3,49 @@ import { fn } from "./util/fn"
import { Resource } from "@opencode-ai/console-resource" import { Resource } from "@opencode-ai/console-resource"
import { centsToMicroCents } from "./util/price" import { centsToMicroCents } from "./util/price"
import { getWeekBounds } from "./util/date" import { getWeekBounds } from "./util/date"
import { SubscriptionPlan } from "./schema/billing.sql"
export namespace BlackData { export namespace BlackData {
const Schema = z.object({ const Schema = z.object({
fixedLimit: z.number().int(), "200": z.object({
rollingLimit: z.number().int(), fixedLimit: z.number().int(),
rollingWindow: z.number().int(), rollingLimit: z.number().int(),
rollingWindow: z.number().int(),
}),
"100": z.object({
fixedLimit: z.number().int(),
rollingLimit: z.number().int(),
rollingWindow: z.number().int(),
}),
"20": z.object({
fixedLimit: z.number().int(),
rollingLimit: z.number().int(),
rollingWindow: z.number().int(),
}),
}) })
export const validate = fn(Schema, (input) => { export const validate = fn(Schema, (input) => {
return input return input
}) })
export const get = fn(z.void(), () => { export const get = fn(z.object({
const json = JSON.parse(Resource.ZEN_BLACK.value) plan: z.enum(SubscriptionPlan),
return Schema.parse(json) }), ({ plan }) => {
const json = JSON.parse(Resource.ZEN_BLACK_LIMITS.value)
return Schema.parse(json)[plan]
}) })
} }
export namespace Black { export namespace Black {
export const analyzeRollingUsage = fn( export const analyzeRollingUsage = fn(
z.object({ z.object({
plan: z.enum(SubscriptionPlan),
usage: z.number().int(), usage: z.number().int(),
timeUpdated: z.date(), timeUpdated: z.date(),
}), }),
({ usage, timeUpdated }) => { ({ plan, usage, timeUpdated }) => {
const now = new Date() const now = new Date()
const black = BlackData.get() const black = BlackData.get({ plan })
const rollingWindowMs = black.rollingWindow * 3600 * 1000 const rollingWindowMs = black.rollingWindow * 3600 * 1000
const rollingLimitInMicroCents = centsToMicroCents(black.rollingLimit * 100) const rollingLimitInMicroCents = centsToMicroCents(black.rollingLimit * 100)
const windowStart = new Date(now.getTime() - rollingWindowMs) const windowStart = new Date(now.getTime() - rollingWindowMs)
@@ -59,11 +75,12 @@ export namespace Black {
export const analyzeWeeklyUsage = fn( export const analyzeWeeklyUsage = fn(
z.object({ z.object({
plan:z.enum(SubscriptionPlan),
usage: z.number().int(), usage: z.number().int(),
timeUpdated: z.date(), timeUpdated: z.date(),
}), }),
({ usage, timeUpdated }) => { ({ plan, usage, timeUpdated }) => {
const black = BlackData.get() const black = BlackData.get({ plan })
const now = new Date() const now = new Date()
const week = getWeekBounds(now) const week = getWeekBounds(now)
const fixedLimitInMicroCents = centsToMicroCents(black.fixedLimit * 100) const fixedLimitInMicroCents = centsToMicroCents(black.fixedLimit * 100)

View File

@@ -2,6 +2,7 @@ import { bigint, boolean, index, int, json, mysqlEnum, mysqlTable, uniqueIndex,
import { timestamps, ulid, utc, workspaceColumns } from "../drizzle/types" import { timestamps, ulid, utc, workspaceColumns } from "../drizzle/types"
import { workspaceIndexes } from "./workspace.sql" import { workspaceIndexes } from "./workspace.sql"
export const SubscriptionPlan = ["20", "100", "200"] as const
export const BillingTable = mysqlTable( export const BillingTable = mysqlTable(
"billing", "billing",
{ {
@@ -28,7 +29,7 @@ export const BillingTable = mysqlTable(
plan: "20" | "100" | "200" plan: "20" | "100" | "200"
}>(), }>(),
subscriptionID: varchar("subscription_id", { length: 28 }), subscriptionID: varchar("subscription_id", { length: 28 }),
subscriptionPlan: mysqlEnum("subscription_plan", ["20", "100", "200"] as const), subscriptionPlan: mysqlEnum("subscription_plan", SubscriptionPlan),
timeSubscriptionBooked: utc("time_subscription_booked"), timeSubscriptionBooked: utc("time_subscription_booked"),
}, },
(table) => [ (table) => [

View File

@@ -118,10 +118,17 @@ declare module "sst" {
"type": "sst.cloudflare.StaticSite" "type": "sst.cloudflare.StaticSite"
"url": string "url": string
} }
"ZEN_BLACK": { "ZEN_BLACK_LIMITS": {
"type": "sst.sst.Secret" "type": "sst.sst.Secret"
"value": string "value": string
} }
"ZEN_BLACK_PRICE": {
"plan100": string
"plan20": string
"plan200": string
"product": string
"type": "sst.sst.Linkable"
}
"ZEN_MODELS1": { "ZEN_MODELS1": {
"type": "sst.sst.Secret" "type": "sst.sst.Secret"
"value": string "value": string

View File

@@ -118,10 +118,17 @@ declare module "sst" {
"type": "sst.cloudflare.StaticSite" "type": "sst.cloudflare.StaticSite"
"url": string "url": string
} }
"ZEN_BLACK": { "ZEN_BLACK_LIMITS": {
"type": "sst.sst.Secret" "type": "sst.sst.Secret"
"value": string "value": string
} }
"ZEN_BLACK_PRICE": {
"plan100": string
"plan20": string
"plan200": string
"product": string
"type": "sst.sst.Linkable"
}
"ZEN_MODELS1": { "ZEN_MODELS1": {
"type": "sst.sst.Secret" "type": "sst.sst.Secret"
"value": string "value": string

View File

@@ -118,10 +118,17 @@ declare module "sst" {
"type": "sst.cloudflare.StaticSite" "type": "sst.cloudflare.StaticSite"
"url": string "url": string
} }
"ZEN_BLACK": { "ZEN_BLACK_LIMITS": {
"type": "sst.sst.Secret" "type": "sst.sst.Secret"
"value": string "value": string
} }
"ZEN_BLACK_PRICE": {
"plan100": string
"plan20": string
"plan200": string
"product": string
"type": "sst.sst.Linkable"
}
"ZEN_MODELS1": { "ZEN_MODELS1": {
"type": "sst.sst.Secret" "type": "sst.sst.Secret"
"value": string "value": string

View File

@@ -118,10 +118,17 @@ declare module "sst" {
"type": "sst.cloudflare.StaticSite" "type": "sst.cloudflare.StaticSite"
"url": string "url": string
} }
"ZEN_BLACK": { "ZEN_BLACK_LIMITS": {
"type": "sst.sst.Secret" "type": "sst.sst.Secret"
"value": string "value": string
} }
"ZEN_BLACK_PRICE": {
"plan100": string
"plan20": string
"plan200": string
"product": string
"type": "sst.sst.Linkable"
}
"ZEN_MODELS1": { "ZEN_MODELS1": {
"type": "sst.sst.Secret" "type": "sst.sst.Secret"
"value": string "value": string

View File

@@ -118,10 +118,17 @@ declare module "sst" {
"type": "sst.cloudflare.StaticSite" "type": "sst.cloudflare.StaticSite"
"url": string "url": string
} }
"ZEN_BLACK": { "ZEN_BLACK_LIMITS": {
"type": "sst.sst.Secret" "type": "sst.sst.Secret"
"value": string "value": string
} }
"ZEN_BLACK_PRICE": {
"plan100": string
"plan20": string
"plan200": string
"product": string
"type": "sst.sst.Linkable"
}
"ZEN_MODELS1": { "ZEN_MODELS1": {
"type": "sst.sst.Secret" "type": "sst.sst.Secret"
"value": string "value": string

9
sst-env.d.ts vendored
View File

@@ -144,10 +144,17 @@ declare module "sst" {
"type": "sst.cloudflare.StaticSite" "type": "sst.cloudflare.StaticSite"
"url": string "url": string
} }
"ZEN_BLACK": { "ZEN_BLACK_LIMITS": {
"type": "sst.sst.Secret" "type": "sst.sst.Secret"
"value": string "value": string
} }
"ZEN_BLACK_PRICE": {
"plan100": string
"plan20": string
"plan200": string
"product": string
"type": "sst.sst.Linkable"
}
"ZEN_MODELS1": { "ZEN_MODELS1": {
"type": "sst.sst.Secret" "type": "sst.sst.Secret"
"value": string "value": string