g1t/apps/web/app/routes/pricing.tsx
Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Prices keep themselves current with what g1t pays | 1 | import { ArrowUpRight } from "lucide-react"; |
| 2 | ||
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 3 | import { DEPLOYMENTS_ALLOWANCE, type FeaturePlan, type FreeTier, MICROS_PER_DOLLAR, type Price } from "@g1t/contracts"; |
| Prices keep themselves current with what g1t pays | 4 | |
| 5 | import type { Route } from "./+types/pricing"; | |
| Social cards for every page: og.g1t.sh | 6 | import { page } from "../lib/meta"; |
| Prices keep themselves current with what g1t pays | 7 | import { TimeAgo } from "../components/ui"; |
| 8 | import { billing } from "../lib/services.server"; | |
| 9 | ||
| Social cards for every page: og.g1t.sh | 10 | export function meta(args: Route.MetaArgs) { |
| 11 | return page(args, { | |
| 12 | title: "Pricing · g1t", | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 13 | description: |
| 14 | "g1t passes its costs through: what Cloudflare and model providers charge g1t, plus 20%. Public repositories are free. No seats, ever.", | |
| Social cards for every page: og.g1t.sh | 15 | }); |
| Prices keep themselves current with what g1t pays | 16 | } |
| 17 | ||
| 18 | export async function loader() { | |
| Billing accounts, terms and enterprises; g1t is no longer free | 19 | const [book, status] = await Promise.all([billing.prices().catch(() => null), billing.status().catch(() => null)]); |
| 20 | return { book, free: status?.free ?? false }; | |
| Prices keep themselves current with what g1t pays | 21 | } |
| 22 | ||
| 23 | /** A price in dollars, with as many digits as it needs to say anything. */ | |
| 24 | function money(micros: number): string { | |
| 25 | const dollars = micros / MICROS_PER_DOLLAR; | |
| 26 | if (dollars >= 1) return `$${dollars.toFixed(2)}`; | |
| 27 | if (dollars >= 0.01) return `$${dollars.toFixed(3)}`; | |
| 28 | return `$${dollars.toPrecision(2)}`; | |
| 29 | } | |
| 30 | ||
| 31 | /** Per second is easier to read per minute. */ | |
| 32 | function perUnit(price: Price, micros: number): string { | |
| 33 | return price.unit === "second" ? `${money(micros * 60)} a minute` : `${money(micros)} per ${price.unit}`; | |
| 34 | } | |
| 35 | ||
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 36 | /** Whole dollars when they are whole. */ |
| 37 | function dollars(micros: number): string { | |
| 38 | const d = micros / MICROS_PER_DOLLAR; | |
| 39 | return Number.isInteger(d) ? `$${d}` : `$${d.toFixed(2)}`; | |
| 40 | } | |
| 41 | ||
| 42 | function gigabytes(bytes: number): string { | |
| 43 | return `${Math.round((bytes / 1e9) * 10) / 10} GB`; | |
| 44 | } | |
| 45 | ||
| 46 | /** What the page says when billing cannot be reached: the published defaults. */ | |
| 47 | const DEFAULT_FREE: FreeTier = { | |
| 48 | trialWorkspaceMicros: 1_000_000, | |
| 49 | trialMonthlyPoolMicros: 40_000_000, | |
| 50 | ossPoolMicros: 10_000_000, | |
| 51 | ossRepoMicros: 1_000_000, | |
| 52 | freePrivateStorageBytes: 1_000_000_000, | |
| 53 | auditRetentionDays: 30, | |
| 54 | minChargeMicros: 5_000_000, | |
| 55 | }; | |
| 56 | ||
| 57 | const DEFAULT_PLANS: FeaturePlan[] = [ | |
| 58 | { | |
| 59 | feature: "team", | |
| 60 | title: "Team", | |
| 61 | monthlyCents: 2000, | |
| 62 | includes: [ | |
| 63 | "$5 of usage credit each month, drawn first by the month's usage at cost plus 20%. Unused credit does not roll over.", | |
| 64 | "50 GB of private repository storage, rather than 1 GB", | |
| 65 | "The audit log kept for 1 year, rather than 30 days", | |
| 66 | "Everyone in the workspace, at one price: never per person", | |
| 67 | ], | |
| 68 | overage: "Usage past the credit is charged as it is without the plan: at cost plus 20%.", | |
| 69 | }, | |
| 70 | { | |
| 71 | feature: "deployments", | |
| 72 | title: "Deployments", | |
| 73 | monthlyCents: 500, | |
| 74 | includes: [ | |
| 75 | `${DEPLOYMENTS_ALLOWANCE.apps} apps deployed at once, production and previews together`, | |
| 76 | `${DEPLOYMENTS_ALLOWANCE.buildSeconds / 60} build minutes`, | |
| 77 | `${DEPLOYMENTS_ALLOWANCE.requests / 1e6} million requests`, | |
| 78 | `${DEPLOYMENTS_ALLOWANCE.cpuMs / 1e6} million CPU milliseconds`, | |
| 79 | `${DEPLOYMENTS_ALLOWANCE.customDomains} custom domains, with certificates`, | |
| 80 | ], | |
| 81 | overage: "Usage past that is charged at Cloudflare's price plus 20%.", | |
| 82 | }, | |
| 83 | ]; | |
| 84 | ||
| 85 | /** The ways to reach g1t about an enterprise account. */ | |
| 86 | const ENTERPRISE_MAIL = "mailto:billing@g1t.sh?subject=Enterprise%20billing%20for%20g1t"; | |
| 87 | ||
| Prices keep themselves current with what g1t pays | 88 | const HOW = [ |
| 89 | { | |
| 90 | title: "Our cost, passed through", | |
| Prices are what g1t pays plus 20%, from the first second | 91 | body: "Every sandbox second, build, app request and model token costs g1t money at Cloudflare or a model provider. Each is metered and charged at that cost plus 20%, from the first second and the first request. Use a little, pay a little.", |
| Prices keep themselves current with what g1t pays | 92 | }, |
| 93 | { | |
| 94 | title: "Prices follow costs, by themselves", | |
| 95 | body: "Model runs are charged at what Cloudflare's AI Gateway priced each request at, so a provider's price change reaches you the same day. Every day, each Cloudflare cost is checked against what Cloudflare billed g1t; when one moves, its price moves with it, and the change is listed below.", | |
| 96 | }, | |
| 97 | { | |
| Prices are what g1t pays plus 20%, from the first second | 98 | title: "The 20% is the overhead", |
| 99 | body: "It pays for running g1t and for building and keeping up the features you use. The same 20% on everything, and nothing bundled in.", | |
| 100 | }, | |
| 101 | { | |
| Prices keep themselves current with what g1t pays | 102 | title: "No seats, ever", |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 103 | body: "Add as many people and agents as you like. A workspace pays for what it uses, and for the plans it turns on, each a flat price for the whole workspace.", |
| 104 | }, | |
| 105 | { | |
| 106 | title: "Free where something pays for it", | |
| 107 | body: "Public repositories are free, and their agents' compute comes from a capped open-source pool g1t pays for. New workspaces get a small trial from a monthly budget. Nothing free is an open-ended allowance.", | |
| Prices keep themselves current with what g1t pays | 108 | }, |
| 109 | { | |
| 110 | title: "Limits that protect both of us", | |
| Billing accounts, terms and enterprises; g1t is no longer free | 111 | body: "Usage not yet paid for can only go so far: $3 for a new workspace, growing with what it pays. With a card on file, g1t charges it as you near the limit, so work that is paid for never stops. Owners can set a lower limit of their own.", |
| 112 | }, | |
| Prices keep themselves current with what g1t pays | 113 | ]; |
| 114 | ||
| 115 | export default function Pricing({ loaderData }: Route.ComponentProps) { | |
| Billing accounts, terms and enterprises; g1t is no longer free | 116 | const { book, free } = loaderData; |
| Prices keep themselves current with what g1t pays | 117 | const checked = book?.prices.map((p) => p.checkedAt).filter((at): at is string => !!at).sort().at(-1); |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 118 | const tier = book?.free ?? DEFAULT_FREE; |
| 119 | const plans = book?.plans?.length ? book.plans : DEFAULT_PLANS; | |
| Prices keep themselves current with what g1t pays | 120 | return ( |
| 121 | <main className="mx-auto max-w-4xl px-4 py-12"> | |
| 122 | <p className="text-sm font-medium text-accent">Pricing</p> | |
| Prices are what g1t pays plus 20%, from the first second | 123 | <h1 className="mt-2 text-3xl font-semibold tracking-tight sm:text-4xl">What it costs us, plus 20%</h1> |
| Prices keep themselves current with what g1t pays | 124 | <p className="mt-3 max-w-2xl text-muted"> |
| 125 | g1t runs on Cloudflare and model providers, and passes those costs through. The numbers on this page are the | |
| 126 | live price book g1t charges from. | |
| 127 | </p> | |
| Billing accounts, terms and enterprises; g1t is no longer free | 128 | {free && ( |
| 129 | <div className="mt-5 rounded-xl border border-accent/30 bg-accent/5 px-4 py-3 text-sm"> | |
| 130 | <span className="font-medium">Free while g1t is being built out.</span>{" "} | |
| 131 | <span className="text-muted"> | |
| 132 | Usage is recorded at these prices but not charged for now. Paid features, such as Deployments, are charged. | |
| 133 | </span> | |
| 134 | </div> | |
| 135 | )} | |
| Prices keep themselves current with what g1t pays | 136 | |
| 137 | <div className="mt-10 grid gap-4 sm:grid-cols-2"> | |
| 138 | {HOW.map((item) => ( | |
| 139 | <section key={item.title} className="rounded-xl border border-line bg-surface p-5"> | |
| 140 | <h2 className="font-medium">{item.title}</h2> | |
| 141 | <p className="mt-1.5 text-sm text-muted">{item.body}</p> | |
| 142 | </section> | |
| 143 | ))} | |
| 144 | </div> | |
| 145 | ||
| 146 | <h2 className="mt-14 text-xl font-semibold tracking-tight">Usage</h2> | |
| 147 | <p className="mt-1 text-sm text-muted"> | |
| 148 | {checked ? ( | |
| 149 | <> | |
| 150 | Last checked against Cloudflare's bill <TimeAgo at={checked} />. | |
| 151 | </> | |
| 152 | ) : ( | |
| 153 | "Starting from Cloudflare's published prices; checked against its bill daily." | |
| 154 | )} | |
| 155 | </p> | |
| 156 | <div className="mt-4 overflow-x-auto rounded-xl border border-line"> | |
| 157 | <table className="w-full min-w-[36rem] text-left text-sm"> | |
| 158 | <thead className="border-b border-line text-xs text-muted"> | |
| 159 | <tr> | |
| 160 | <th className="px-4 py-2.5 font-medium">What</th> | |
| 161 | <th className="px-4 py-2.5 font-medium">Costs g1t</th> | |
| 162 | <th className="px-4 py-2.5 font-medium">Markup</th> | |
| 163 | <th className="px-4 py-2.5 font-medium">You pay</th> | |
| 164 | </tr> | |
| 165 | </thead> | |
| 166 | <tbody className="divide-y divide-line"> | |
| 167 | <tr> | |
| 168 | <td className="px-4 py-3"> | |
| 169 | <p className="font-medium">Models</p> | |
| 170 | <p className="text-xs text-faint">g1t's hosted models, through AI Gateway</p> | |
| 171 | </td> | |
| 172 | <td className="px-4 py-3 text-muted">What the provider charges, per request</td> | |
| 173 | <td className="px-4 py-3 tabular-nums">{book?.modelMarginPercent ?? 20}%</td> | |
| 174 | <td className="px-4 py-3 text-muted">Cost + {book?.modelMarginPercent ?? 20}%</td> | |
| 175 | </tr> | |
| 176 | {(book?.prices ?? []).map((price) => ( | |
| 177 | <tr key={price.meter}> | |
| 178 | <td className="px-4 py-3"> | |
| 179 | <p className="font-medium">{price.title}</p> | |
| 180 | <p className="text-xs text-faint"> | |
| 181 | {price.source === "cloudflare" ? "Measured from Cloudflare's bill" : "Cloudflare's published price"} | |
| 182 | </p> | |
| 183 | </td> | |
| 184 | <td className="px-4 py-3 font-mono text-xs tabular-nums text-muted">{perUnit(price, price.costMicros)}</td> | |
| 185 | <td className="px-4 py-3 tabular-nums">{price.markupPercent}%</td> | |
| 186 | <td className="px-4 py-3 font-mono text-xs tabular-nums">{perUnit(price, price.priceMicros)}</td> | |
| 187 | </tr> | |
| 188 | ))} | |
| 189 | </tbody> | |
| 190 | </table> | |
| 191 | </div> | |
| 192 | ||
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 193 | <h2 className="mt-14 text-xl font-semibold tracking-tight">Free, and what pays for it</h2> |
| 194 | <p className="mt-1 text-sm text-muted"> | |
| 195 | Hosting, git, issues, pull requests, review, search, the API and MCP cost nothing. What runs for you is metered, | |
| 196 | and these pay for some of it first, each from a fixed budget. | |
| 197 | </p> | |
| 198 | <div className="mt-4 overflow-x-auto rounded-xl border border-line"> | |
| 199 | <table className="w-full min-w-[36rem] text-left text-sm"> | |
| 200 | <thead className="border-b border-line text-xs text-muted"> | |
| 201 | <tr> | |
| 202 | <th className="px-4 py-2.5 font-medium">What is free</th> | |
| 203 | <th className="px-4 py-2.5 font-medium">Paid for by</th> | |
| 204 | </tr> | |
| 205 | </thead> | |
| 206 | <tbody className="divide-y divide-line"> | |
| 207 | <tr> | |
| 208 | <td className="px-4 py-3"> | |
| 209 | <p className="font-medium">Public repositories and open source</p> | |
| 210 | <p className="text-xs text-faint"> | |
| 211 | Hosting, issues, pull requests and search are never charged. Agents' sandbox time and model cost on a | |
| 212 | public repository come from the pool, up to {dollars(tier.ossRepoMicros)} a month per repository. | |
| 213 | </p> | |
| 214 | </td> | |
| 215 | <td className="px-4 py-3 text-muted"> | |
| 216 | g1t's open-source pool, {dollars(tier.ossPoolMicros)} a month in all. When it or a repository's share is | |
| 217 | spent, the workspace pays as usual. | |
| 218 | </td> | |
| 219 | </tr> | |
| 220 | {tier.trialWorkspaceMicros > 0 && ( | |
| 221 | <tr> | |
| 222 | <td className="px-4 py-3"> | |
| 223 | <p className="font-medium">A trial for each new workspace</p> | |
| 224 | <p className="text-xs text-faint"> | |
| 225 | {dollars(tier.trialWorkspaceMicros)} of usage credit, once, given the first time it uses something. | |
| 226 | </p> | |
| 227 | </td> | |
| 228 | <td className="px-4 py-3 text-muted"> | |
| 229 | A trial budget of {dollars(tier.trialMonthlyPoolMicros)} a month. When a month's is given out, new | |
| 230 | trials start again on the 1st. | |
| 231 | </td> | |
| 232 | </tr> | |
| 233 | )} | |
| 234 | <tr> | |
| 235 | <td className="px-4 py-3"> | |
| 236 | <p className="font-medium">Private storage</p> | |
| 237 | <p className="text-xs text-faint"> | |
| 238 | {gigabytes(tier.freePrivateStorageBytes)} per workspace; past it, at the storage price above. | |
| 239 | </p> | |
| 240 | </td> | |
| 241 | <td className="px-4 py-3 text-muted">g1t, as part of the free core: at most {gigabytes(tier.freePrivateStorageBytes)} of storage a workspace, about $0.50 a month at Cloudflare's price.</td> | |
| 242 | </tr> | |
| 243 | </tbody> | |
| 244 | </table> | |
| 245 | </div> | |
| 246 | <p className="mt-3 text-sm text-muted"> | |
| 247 | No card is charged less than {dollars(tier.minChargeMicros)}, so a payment's fee is never most of it. Smaller | |
| 248 | amounts carry over to the next invoice. | |
| 249 | </p> | |
| 250 | ||
| 251 | <h2 className="mt-14 text-xl font-semibold tracking-tight">Plans</h2> | |
| 252 | <p className="mt-1 text-sm text-muted"> | |
| 253 | Turned on by an owner, per workspace, at one flat price a month. Never per person. | |
| 254 | </p> | |
| 255 | <div className="mt-4 grid gap-4 sm:grid-cols-2"> | |
| 256 | {plans.map((plan) => ( | |
| 257 | <section key={plan.feature} className="flex flex-col rounded-xl border border-line bg-surface p-5"> | |
| 258 | <div className="flex flex-wrap items-baseline justify-between gap-2"> | |
| 259 | <h3 className="font-medium">{plan.title}</h3> | |
| 260 | <p> | |
| 261 | <span className="text-2xl font-semibold">${plan.monthlyCents / 100}</span>{" "} | |
| 262 | <span className="text-sm text-muted">/ month per workspace</span> | |
| 263 | </p> | |
| 264 | </div> | |
| 265 | <ul className="mt-3 space-y-1.5 text-sm text-muted"> | |
| 266 | {plan.includes.map((line) => ( | |
| 267 | <li key={line} className="flex gap-2"> | |
| 268 | <span aria-hidden className="text-accent"> | |
| 269 | · | |
| 270 | </span> | |
| 271 | {line} | |
| 272 | </li> | |
| 273 | ))} | |
| 274 | </ul> | |
| 275 | <p className="mt-3 text-xs text-faint">{plan.overage}</p> | |
| 276 | </section> | |
| 277 | ))} | |
| 278 | </div> | |
| 279 | ||
| 280 | <h2 className="mt-14 text-xl font-semibold tracking-tight">Enterprise</h2> | |
| 281 | <p className="mt-1 text-sm text-muted"> | |
| 282 | For organizations that would rather we run it for several teams, on terms set with them. | |
| 283 | </p> | |
| Prices keep themselves current with what g1t pays | 284 | <section className="mt-4 rounded-xl border border-line bg-surface p-5"> |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 285 | <ul className="grid gap-3 text-sm sm:grid-cols-2"> |
| 286 | <li> | |
| 287 | <p className="font-medium">Consolidated invoicing</p> | |
| 288 | <p className="text-muted">One monthly invoice for every workspace, one limit, paid by card or bank transfer.</p> | |
| 289 | </li> | |
| 290 | <li> | |
| 291 | <p className="font-medium">Custom terms</p> | |
| 292 | <p className="text-muted">A limit of your own, discounts on usage, and terms until a date, set with you.</p> | |
| 293 | </li> | |
| 294 | <li> | |
| 295 | <p className="font-medium">Audit log export</p> | |
| 296 | <p className="text-muted">Every action by people and agents, as CSV or JSON, kept a year on Team.</p> | |
| 297 | </li> | |
| 298 | <li> | |
| 299 | <p className="font-medium"> | |
| 300 | Single sign-on <span className="ml-1 rounded bg-raised px-1.5 py-0.5 text-xs text-muted">Coming</span> | |
| 301 | </p> | |
| 302 | <p className="text-muted">Sign in through your identity provider. Not available yet.</p> | |
| 303 | </li> | |
| 304 | </ul> | |
| 305 | <a | |
| 306 | href={ENTERPRISE_MAIL} | |
| 307 | className="mt-5 inline-flex items-center gap-1.5 rounded-md border border-line px-3 py-1.5 text-sm hover:border-line-strong" | |
| 308 | > | |
| 309 | Write to us about enterprise | |
| 310 | <ArrowUpRight size={14} /> | |
| 311 | </a> | |
| Prices keep themselves current with what g1t pays | 312 | </section> |
| 313 | ||
| 314 | <h2 className="mt-14 text-xl font-semibold tracking-tight">Price changes</h2> | |
| 315 | {book && book.changes.length > 0 ? ( | |
| 316 | <ul className="mt-4 divide-y divide-line rounded-xl border border-line"> | |
| 317 | {book.changes.map((change) => { | |
| 318 | const title = book.prices.find((p) => p.meter === change.meter)?.title ?? change.meter; | |
| Prices are what g1t pays plus 20%, from the first second | 319 | // What you pay moved: the cost, the markup, or both. |
| 320 | const before = change.oldCostMicros * (100 + (change.oldMarkupPercent ?? change.markupPercent)); | |
| 321 | const after = change.newCostMicros * (100 + change.markupPercent); | |
| 322 | const up = after > before; | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 323 | const fresh = after === before; |
| Prices keep themselves current with what g1t pays | 324 | return ( |
| 325 | <li key={`${change.meter}-${change.createdAt}`} className="px-4 py-3 text-sm"> | |
| 326 | <p> | |
| 327 | <span className="font-medium">{title}</span>{" "} | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 328 | {fresh ? ( |
| 329 | <span className="text-muted">new</span> | |
| 330 | ) : ( | |
| 331 | <span className={up ? "text-warn" : "text-accent"}> | |
| 332 | {up ? "up" : "down"} {Math.abs((after / before - 1) * 100).toFixed(1)}% | |
| 333 | </span> | |
| 334 | )} | |
| Prices keep themselves current with what g1t pays | 335 | </p> |
| 336 | <p className="mt-0.5 text-xs text-faint"> | |
| 337 | {change.reason} · <TimeAgo at={change.createdAt} /> | |
| 338 | </p> | |
| 339 | </li> | |
| 340 | ); | |
| 341 | })} | |
| 342 | </ul> | |
| 343 | ) : ( | |
| Prices are what g1t pays plus 20%, from the first second | 344 | <p className="mt-2 text-sm text-muted">None yet. When a price moves, it is listed here with why.</p> |
| Prices keep themselves current with what g1t pays | 345 | )} |
| 346 | ||
| 347 | <a | |
| 348 | href="https://docs.g1t.sh/guides/usage-and-billing/" | |
| 349 | className="mt-12 inline-flex items-center gap-1.5 text-sm text-accent hover:underline" | |
| 350 | > | |
| 351 | How usage and billing work | |
| 352 | <ArrowUpRight size={14} /> | |
| 353 | </a> | |
| 354 | </main> | |
| 355 | ); | |
| 356 | } |