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

191 lines8,969 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
3import { DEPLOYMENTS_ALLOWANCE, MICROS_PER_DOLLAR, type Price } from "@g1t/contracts";
4
5import type { Route } from "./+types/pricing";
6import { TimeAgo } from "../components/ui";
7import { billing } from "../lib/services.server";
8
9export function meta() {
10 return [
11 { title: "Pricing · g1t" },
12 {
13 name: "description",
14 content: "g1t passes its costs through: what Cloudflare and model providers charge g1t, plus a set markup. No seats.",
15 },
16 ];
17}
18
19export async function loader() {
Billing accounts, terms and enterprises; g1t is no longer free20 const [book, status] = await Promise.all([billing.prices().catch(() => null), billing.status().catch(() => null)]);
21 return { book, free: status?.free ?? false };
Prices keep themselves current with what g1t pays22}
23
24/** A price in dollars, with as many digits as it needs to say anything. */
25function money(micros: number): string {
26 const dollars = micros / MICROS_PER_DOLLAR;
27 if (dollars >= 1) return `$${dollars.toFixed(2)}`;
28 if (dollars >= 0.01) return `$${dollars.toFixed(3)}`;
29 return `$${dollars.toPrecision(2)}`;
30}
31
32/** Per second is easier to read per minute. */
33function perUnit(price: Price, micros: number): string {
34 return price.unit === "second" ? `${money(micros * 60)} a minute` : `${money(micros)} per ${price.unit}`;
35}
36
37const HOW = [
38 {
39 title: "Our cost, passed through",
40 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 a set markup. Use a little, pay a little.",
41 },
42 {
43 title: "Prices follow costs, by themselves",
44 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.",
45 },
46 {
47 title: "No seats, ever",
48 body: "Add as many people and agents as you like. A workspace pays for what it uses, and for the features it turns on.",
49 },
50 {
51 title: "Limits that protect both of us",
Billing accounts, terms and enterprises; g1t is no longer free52 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.",
Prices keep themselves current with what g1t pays53 },
Billing accounts, terms and enterprises; g1t is no longer free54 {
55 title: "Enterprise billing",
56 body: "One bill, one limit and one set of terms for several workspaces, as GitHub Enterprise does. Write to us to set one up.",
57 },
Prices keep themselves current with what g1t pays58];
59
60export default function Pricing({ loaderData }: Route.ComponentProps) {
Billing accounts, terms and enterprises; g1t is no longer free61 const { book, free } = loaderData;
Prices keep themselves current with what g1t pays62 const checked = book?.prices.map((p) => p.checkedAt).filter((at): at is string => !!at).sort().at(-1);
63 return (
64 <main className="mx-auto max-w-4xl px-4 py-12">
65 <p className="text-sm font-medium text-accent">Pricing</p>
66 <h1 className="mt-2 text-3xl font-semibold tracking-tight sm:text-4xl">What it costs us, plus a markup</h1>
67 <p className="mt-3 max-w-2xl text-muted">
68 g1t runs on Cloudflare and model providers, and passes those costs through. The numbers on this page are the
69 live price book g1t charges from.
70 </p>
Billing accounts, terms and enterprises; g1t is no longer free71 {free && (
72 <div className="mt-5 rounded-xl border border-accent/30 bg-accent/5 px-4 py-3 text-sm">
73 <span className="font-medium">Free while g1t is being built out.</span>{" "}
74 <span className="text-muted">
75 Usage is recorded at these prices but not charged for now. Paid features, such as Deployments, are charged.
76 </span>
77 </div>
78 )}
Prices keep themselves current with what g1t pays79
80 <div className="mt-10 grid gap-4 sm:grid-cols-2">
81 {HOW.map((item) => (
82 <section key={item.title} className="rounded-xl border border-line bg-surface p-5">
83 <h2 className="font-medium">{item.title}</h2>
84 <p className="mt-1.5 text-sm text-muted">{item.body}</p>
85 </section>
86 ))}
87 </div>
88
89 <h2 className="mt-14 text-xl font-semibold tracking-tight">Usage</h2>
90 <p className="mt-1 text-sm text-muted">
91 {checked ? (
92 <>
93 Last checked against Cloudflare's bill <TimeAgo at={checked} />.
94 </>
95 ) : (
96 "Starting from Cloudflare's published prices; checked against its bill daily."
97 )}
98 </p>
99 <div className="mt-4 overflow-x-auto rounded-xl border border-line">
100 <table className="w-full min-w-[36rem] text-left text-sm">
101 <thead className="border-b border-line text-xs text-muted">
102 <tr>
103 <th className="px-4 py-2.5 font-medium">What</th>
104 <th className="px-4 py-2.5 font-medium">Costs g1t</th>
105 <th className="px-4 py-2.5 font-medium">Markup</th>
106 <th className="px-4 py-2.5 font-medium">You pay</th>
107 </tr>
108 </thead>
109 <tbody className="divide-y divide-line">
110 <tr>
111 <td className="px-4 py-3">
112 <p className="font-medium">Models</p>
113 <p className="text-xs text-faint">g1t's hosted models, through AI Gateway</p>
114 </td>
115 <td className="px-4 py-3 text-muted">What the provider charges, per request</td>
116 <td className="px-4 py-3 tabular-nums">{book?.modelMarginPercent ?? 20}%</td>
117 <td className="px-4 py-3 text-muted">Cost + {book?.modelMarginPercent ?? 20}%</td>
118 </tr>
119 {(book?.prices ?? []).map((price) => (
120 <tr key={price.meter}>
121 <td className="px-4 py-3">
122 <p className="font-medium">{price.title}</p>
123 <p className="text-xs text-faint">
124 {price.source === "cloudflare" ? "Measured from Cloudflare's bill" : "Cloudflare's published price"}
125 {price.meter === "sandbox_second" && " · 500 minutes free each month"}
126 </p>
127 </td>
128 <td className="px-4 py-3 font-mono text-xs tabular-nums text-muted">{perUnit(price, price.costMicros)}</td>
129 <td className="px-4 py-3 tabular-nums">{price.markupPercent}%</td>
130 <td className="px-4 py-3 font-mono text-xs tabular-nums">{perUnit(price, price.priceMicros)}</td>
131 </tr>
132 ))}
133 </tbody>
134 </table>
135 </div>
136 <p className="mt-3 text-xs text-faint">
137 Sandbox time carries more markup than the rest: it pays for the orchestration around each sandbox, and the
138 free minutes everyone gets.
139 </p>
140
141 <h2 className="mt-14 text-xl font-semibold tracking-tight">Features</h2>
142 <p className="mt-1 text-sm text-muted">Turned on per workspace with a monthly plan. Never free.</p>
143 <section className="mt-4 rounded-xl border border-line bg-surface p-5">
144 <div className="flex flex-wrap items-baseline justify-between gap-2">
145 <h3 className="font-medium">Deployments</h3>
146 <p>
147 <span className="text-2xl font-semibold">$5</span> <span className="text-sm text-muted">/ month</span>
148 </p>
149 </div>
150 <p className="mt-1 text-sm text-muted">
151 Includes {DEPLOYMENTS_ALLOWANCE.apps} apps up at once, {(DEPLOYMENTS_ALLOWANCE.requests / 1e6).toLocaleString()}{" "}
152 million requests and {(DEPLOYMENTS_ALLOWANCE.cpuMs / 1e6).toLocaleString()} million CPU milliseconds a month;
153 builds, and usage past that, at the prices above.
154 </p>
155 </section>
156
157 <h2 className="mt-14 text-xl font-semibold tracking-tight">Price changes</h2>
158 {book && book.changes.length > 0 ? (
159 <ul className="mt-4 divide-y divide-line rounded-xl border border-line">
160 {book.changes.map((change) => {
161 const title = book.prices.find((p) => p.meter === change.meter)?.title ?? change.meter;
162 const up = change.newCostMicros > change.oldCostMicros;
163 return (
164 <li key={`${change.meter}-${change.createdAt}`} className="px-4 py-3 text-sm">
165 <p>
166 <span className="font-medium">{title}</span>{" "}
167 <span className={up ? "text-warn" : "text-accent"}>
168 {up ? "up" : "down"} {Math.abs((change.newCostMicros / change.oldCostMicros - 1) * 100).toFixed(1)}%
169 </span>
170 </p>
171 <p className="mt-0.5 text-xs text-faint">
172 {change.reason} · <TimeAgo at={change.createdAt} />
173 </p>
174 </li>
175 );
176 })}
177 </ul>
178 ) : (
179 <p className="mt-2 text-sm text-muted">None yet. When a cost moves, it is listed here with why.</p>
180 )}
181
182 <a
183 href="https://docs.g1t.sh/guides/usage-and-billing/"
184 className="mt-12 inline-flex items-center gap-1.5 text-sm text-accent hover:underline"
185 >
186 How usage and billing work
187 <ArrowUpRight size={14} />
188 </a>
189 </main>
190 );
191}