flagon-io/g1t

public

Git for AI scale: a forge for thousands of agents working on the same code at once.

g1t/apps/web/app/routes/pricing.tsx

356 lines16,503 bytesCodeBlame

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 pays1import { ArrowUpRight } from "lucide-react";
2
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put3import { DEPLOYMENTS_ALLOWANCE, type FeaturePlan, type FreeTier, MICROS_PER_DOLLAR, type Price } from "@g1t/contracts";
Prices keep themselves current with what g1t pays4
5import type { Route } from "./+types/pricing";
Social cards for every page: og.g1t.sh6import { page } from "../lib/meta";
Prices keep themselves current with what g1t pays7import { TimeAgo } from "../components/ui";
8import { billing } from "../lib/services.server";
9
Social cards for every page: og.g1t.sh10export 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 put13 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.sh15 });
Prices keep themselves current with what g1t pays16}
17
18export async function loader() {
Billing accounts, terms and enterprises; g1t is no longer free19 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 pays21}
22
23/** A price in dollars, with as many digits as it needs to say anything. */
24function 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. */
32function 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 put36/** Whole dollars when they are whole. */
37function dollars(micros: number): string {
38 const d = micros / MICROS_PER_DOLLAR;
39 return Number.isInteger(d) ? `$${d}` : `$${d.toFixed(2)}`;
40}
41
42function 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. */
47const 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
57const 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. */
86const ENTERPRISE_MAIL = "mailto:billing@g1t.sh?subject=Enterprise%20billing%20for%20g1t";
87
Prices keep themselves current with what g1t pays88const HOW = [
89 {
90 title: "Our cost, passed through",
Prices are what g1t pays plus 20%, from the first second91 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 pays92 },
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 second98 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 pays102 title: "No seats, ever",
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put103 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 pays108 },
109 {
110 title: "Limits that protect both of us",
Billing accounts, terms and enterprises; g1t is no longer free111 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 pays113];
114
115export default function Pricing({ loaderData }: Route.ComponentProps) {
Billing accounts, terms and enterprises; g1t is no longer free116 const { book, free } = loaderData;
Prices keep themselves current with what g1t pays117 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 put118 const tier = book?.free ?? DEFAULT_FREE;
119 const plans = book?.plans?.length ? book.plans : DEFAULT_PLANS;
Prices keep themselves current with what g1t pays120 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 second123 <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 pays124 <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 free128 {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 pays136
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 put193 <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 pays284 <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 put285 <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 pays312 </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 second319 // 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 put323 const fresh = after === before;
Prices keep themselves current with what g1t pays324 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 put328 {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 pays335 </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 second344 <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 pays345 )}
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}